diff --git a/src/main.rs b/src/main.rs index f31f9d5..0b8bb76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,16 @@ mod database; mod sheet; mod sheet_dao; +mod sheet_validation; mod ui; -use std::{ - path::{Path, PathBuf}, - process, -}; +use std::{path::PathBuf, process}; use clap::Parser; use database::Database; use env_logger::Env; -use log::{debug, error}; +use log::error; use relm4::RelmApp; -use sheet::{Pdf, Sheet}; -use walkdir::WalkDir; use crate::ui::app::{AppInitData, AppModel}; @@ -36,96 +32,16 @@ async fn main() { let database = Database::setup(cli.directory.join("database.sqlite")) .await .unwrap(); - let sheets = sheet_dao::fetch_all_sheets(&database).await.unwrap(); - debug!("Validating sheets from database..."); - let mut validation_result = validate_sheet_files(sheets, &cli.directory); - debug!("{}", validation_result.get_stats()); // TODO: handle invalidated files - for updated in validation_result.updated_sheets.iter() { - sheet_dao::update_sheet_path(&database, updated) - .await - .unwrap(); - } - - let mut sheets = validation_result.validated_sheets; - sheets.append(&mut validation_result.updated_sheets); - - debug!("Inserting unassigned files into orphan table..."); - for unassigned in validation_result.unassigned_files { - let orphan = sheet_dao::insert_file_as_orphan(&database, unassigned) - .await - .unwrap(); - sheets.push(orphan); - } - - let app_init_data = AppInitData { sheets, database }; + let sheets = sheet_validation::load_and_validate_sheets(&database, &cli.directory).await; + let app_init_data = AppInitData { + sheets, + database, + directory: cli.directory, + }; let app = RelmApp::new("de.frajul.sheet-organizer"); // Pass empty command line args to allow my own parsing app.with_args(Vec::new()) .run_async::(app_init_data); } - -pub struct FileValidationResult { - validated_sheets: Vec, - invalidated_sheets: Vec, - updated_sheets: Vec, - - unassigned_files: Vec, -} - -impl FileValidationResult { - fn get_stats(&self) -> String { - format!("Validated sheets: {}\nInvalidated sheets: {}\nUpdated sheets: {}\nUnassigned files: {}", - self.validated_sheets.len(), self.invalidated_sheets.len(), self.updated_sheets.len(), - self.unassigned_files.len()) - } -} - -fn validate_sheet_files(sheets: Vec, dir: impl AsRef) -> FileValidationResult { - let (validated_sheets, mut invalidated_sheets): (Vec<_>, Vec<_>) = sheets - .into_iter() - .partition(|sheet| sheet.pdf.validate_own_path().unwrap_or(false)); - - let mut updated_sheets = Vec::new(); - let mut unassigned_files = Vec::new(); - - // TODO: improve performance? - for pdf_file in find_all_pdfs_in_directory_recursive(dir) { - if let Some((i, _)) = invalidated_sheets - .iter() - .enumerate() - .find(|(_, sheet)| sheet.pdf.validate_path(&pdf_file).unwrap_or(false)) - { - let mut sheet = invalidated_sheets.remove(i); - let new_pdf = Pdf::try_from(pdf_file).unwrap(); - sheet.pdf = new_pdf; - updated_sheets.push(sheet); - } else if !validated_sheets - .iter() - .any(|sheet| sheet.pdf.path == pdf_file) - { - unassigned_files.push(pdf_file); - } - } - - FileValidationResult { - validated_sheets, - invalidated_sheets, - updated_sheets, - unassigned_files, - } -} - -fn find_all_pdfs_in_directory_recursive(dir: impl AsRef) -> impl Iterator { - WalkDir::new(dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|file| file.file_type().is_file()) - .map(|file| file.into_path()) - .filter(|path| { - path.extension() - .map(|s| s.to_string_lossy().to_ascii_lowercase() == "pdf") - .unwrap_or(false) - }) -} diff --git a/src/sheet_validation.rs b/src/sheet_validation.rs new file mode 100644 index 0000000..1f19d7e --- /dev/null +++ b/src/sheet_validation.rs @@ -0,0 +1,103 @@ +use std::path::{Path, PathBuf}; + +use log::debug; +use walkdir::WalkDir; + +use crate::{ + database::Database, + sheet::{Pdf, Sheet}, + sheet_dao, +}; + +pub async fn load_and_validate_sheets( + database: &Database, + directory: impl AsRef, +) -> Vec { + let sheets = sheet_dao::fetch_all_sheets(&database).await.unwrap(); + + debug!("Validating sheets from database..."); + let mut validation_result = validate_sheet_files(sheets, directory); + debug!("{}", validation_result.get_stats()); // TODO: handle invalidated files + for updated in validation_result.updated_sheets.iter() { + sheet_dao::update_sheet_path(&database, updated) + .await + .unwrap(); + } + + let mut sheets = validation_result.validated_sheets; + sheets.append(&mut validation_result.updated_sheets); + + debug!("Inserting unassigned files into orphan table..."); + for unassigned in validation_result.unassigned_files { + let orphan = sheet_dao::insert_file_as_orphan(&database, unassigned) + .await + .unwrap(); + sheets.push(orphan); + } + + sheets +} + +pub struct FileValidationResult { + validated_sheets: Vec, + invalidated_sheets: Vec, + updated_sheets: Vec, + + unassigned_files: Vec, +} + +impl FileValidationResult { + fn get_stats(&self) -> String { + format!("Validated sheets: {}\nInvalidated sheets: {}\nUpdated sheets: {}\nUnassigned files: {}", + self.validated_sheets.len(), self.invalidated_sheets.len(), self.updated_sheets.len(), + self.unassigned_files.len()) + } +} + +fn validate_sheet_files(sheets: Vec, dir: impl AsRef) -> FileValidationResult { + let (validated_sheets, mut invalidated_sheets): (Vec<_>, Vec<_>) = sheets + .into_iter() + .partition(|sheet| sheet.pdf.validate_own_path().unwrap_or(false)); + + let mut updated_sheets = Vec::new(); + let mut unassigned_files = Vec::new(); + + // TODO: improve performance? + for pdf_file in find_all_pdfs_in_directory_recursive(dir) { + if let Some((i, _)) = invalidated_sheets + .iter() + .enumerate() + .find(|(_, sheet)| sheet.pdf.validate_path(&pdf_file).unwrap_or(false)) + { + let mut sheet = invalidated_sheets.remove(i); + let new_pdf = Pdf::try_from(pdf_file).unwrap(); + sheet.pdf = new_pdf; + updated_sheets.push(sheet); + } else if !validated_sheets + .iter() + .any(|sheet| sheet.pdf.path == pdf_file) + { + unassigned_files.push(pdf_file); + } + } + + FileValidationResult { + validated_sheets, + invalidated_sheets, + updated_sheets, + unassigned_files, + } +} + +fn find_all_pdfs_in_directory_recursive(dir: impl AsRef) -> impl Iterator { + WalkDir::new(dir) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|file| file.file_type().is_file()) + .map(|file| file.into_path()) + .filter(|path| { + path.extension() + .map(|s| s.to_string_lossy().to_ascii_lowercase() == "pdf") + .unwrap_or(false) + }) +} diff --git a/src/ui/app.rs b/src/ui/app.rs index 78c66bc..b00cb09 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,3 +1,8 @@ +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + use chrono::Utc; use gtk::prelude::*; use log::debug; @@ -8,9 +13,9 @@ use relm4::{ }; use crate::{ - database::Database, + database::{self, Database}, sheet::{I64DateTime, Sheet}, - sheet_dao, + sheet_dao, sheet_validation, ui::mcdu::McduOutput, }; @@ -20,7 +25,8 @@ use super::{ }; pub struct AppModel { - database: Database, + database: Arc, + directory: Arc, mcdu: Controller, sheets_listing: Controller, edit_mode: bool, @@ -39,6 +45,7 @@ pub enum AppInput { pub struct AppInitData { pub sheets: Vec, pub database: Database, + pub directory: PathBuf, } #[relm4::component(pub, async)] @@ -46,7 +53,7 @@ impl AsyncComponent for AppModel { type Input = AppInput; type Output = (); type Init = AppInitData; - type CommandOutput = (); + type CommandOutput = Vec; view! { #[root] @@ -87,9 +94,9 @@ impl AsyncComponent for AppModel { }, }, gtk::ScrolledWindow { - model.sheets_listing.widget(), - set_vexpand: true, - set_hexpand: true, + model.sheets_listing.widget(), + set_vexpand: true, + set_hexpand: true, }, }, model.mcdu.widget() { @@ -122,7 +129,8 @@ impl AsyncComponent for AppModel { }); let model = AppModel { - database: init_data.database, + database: Arc::new(init_data.database), + directory: Arc::new(init_data.directory), mcdu, sheets_listing, edit_mode: false, @@ -136,7 +144,7 @@ impl AsyncComponent for AppModel { async fn update( &mut self, message: Self::Input, - _sender: AsyncComponentSender, + sender: AsyncComponentSender, _root: &Self::Root, ) { match message { @@ -156,10 +164,31 @@ impl AsyncComponent for AppModel { .unwrap(); } } - AppInput::Refresh => todo!(), + AppInput::Refresh => { + let db = Arc::clone(&self.database); + let dir = Arc::clone(&self.directory); + sender.oneshot_command(async move { + let sheets = + sheet_validation::load_and_validate_sheets(&db, dir.as_ref()).await; + return sheets; + }) + } AppInput::Sort => self.sheets_listing.emit(SheetListingInput::Sort), AppInput::Shuffle => self.sheets_listing.emit(SheetListingInput::Shuffle), AppInput::SetEditMode(edit_mode) => self.edit_mode = edit_mode, } } + + async fn update_cmd( + &mut self, + message: Self::CommandOutput, + _sender: AsyncComponentSender, + _: &Self::Root, + ) { + let mut sheets = message; + sheets.sort_by(|a, b| a.cmp(b).reverse()); + + self.sheets_listing + .emit(SheetListingInput::ReloadSheets(sheets)); + } } diff --git a/src/ui/sheet_listing.rs b/src/ui/sheet_listing.rs index 773df85..35beb86 100644 --- a/src/ui/sheet_listing.rs +++ b/src/ui/sheet_listing.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use gtk::prelude::*; use relm4::factory::FactoryVecDeque; @@ -22,6 +20,7 @@ pub enum SheetListingInput { ListBoxRowClicked(i32), Sort, Shuffle, + ReloadSheets(Vec), } #[derive(Debug)] @@ -82,6 +81,12 @@ impl SimpleComponent for SheetListingModel { } SheetListingInput::Sort => sort_sheets(&mut self.sheets), SheetListingInput::Shuffle => shuffle_sheets(&mut self.sheets), + SheetListingInput::ReloadSheets(sheets) => { + self.sheets.guard().clear(); + for sheet_model_type in sheets { + self.sheets.guard().push_back(sheet_model_type); + } + } } } }