From 4f65715967c0007fc36339639c93570a706e45f0 Mon Sep 17 00:00:00 2001 From: Julian Mutter Date: Fri, 2 Feb 2024 17:27:24 +0100 Subject: [PATCH] Implement sheet path validation --- .env | 2 +- db-migrations/0_creation.sql | 4 +- src/database.rs | 19 +++++++--- src/main.rs | 73 ++++++++++++++++++++++++++++++++++-- src/sheet.rs | 32 +++++++--------- 5 files changed, 99 insertions(+), 31 deletions(-) diff --git a/.env b/.env index 0f963af..51d66f0 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -DATABASE_URL=sqlite://testdb.db +DATABASE_URL=sqlite://testdb.sqlite diff --git a/db-migrations/0_creation.sql b/db-migrations/0_creation.sql index 90cc619..818af70 100644 --- a/db-migrations/0_creation.sql +++ b/db-migrations/0_creation.sql @@ -1,2 +1,2 @@ -CREATE TABLE IF NOT EXISTS sheets (name TEXT, path TEXT, file_size INTEGER, file_hash TEXT); -CREATE TABLE IF NOT EXISTS composers (name TEXT, id INTEGER); +CREATE TABLE IF NOT EXISTS sheets (id integer primary key autoincrement, name TEXT, path TEXT, file_size INTEGER, file_hash TEXT); +CREATE TABLE IF NOT EXISTS composers (id integer primary key autoincrement, name TEXT); diff --git a/src/database.rs b/src/database.rs index a046b59..3f6eebc 100644 --- a/src/database.rs +++ b/src/database.rs @@ -34,21 +34,30 @@ impl Database { } pub async fn insert_sheet(&self, sheet: Sheet) -> sqlx::Result<()> { - sqlx::query!( + sqlx::query( " INSERT INTO sheets (name, path, file_size, file_hash) VALUES ($1, $2, $3, $4) ", - sheet.name, - sheet.path, - sheet.file_size, - sheet.file_hash, ) + .bind(sheet.name) + .bind(sheet.path) + .bind(sheet.file_size) + .bind(sheet.file_hash) .execute(&self.connection) .await .map(|_| ()) } + pub async fn update_sheet_path(&self, sheet: Sheet) -> sqlx::Result<()> { + sqlx::query("UPDATE sheets SET path = $1 WHERE id = $2") + .bind(sheet.path) + .bind(sheet.id) + .execute(&self.connection) + .await + .map(|_| ()) + } + pub async fn fetch_all_sheets(&self) -> sqlx::Result> { sqlx::query_as::<_, Sheet>("SELECT * FROM sheets") .fetch_all(&self.connection) diff --git a/src/main.rs b/src/main.rs index 042dc8c..9d8f430 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,16 +3,21 @@ mod mcdu; mod sheet; mod sheet_listing; -use std::{path::PathBuf, process}; +use std::{ + path::{Path, PathBuf}, + process, +}; use clap::Parser; use database::Database; use env_logger::Env; use gtk::prelude::*; -use log::error; +use log::{debug, error}; use mcdu::McduModel; use relm4::prelude::*; +use sheet::Sheet; use sheet_listing::{SheetListingInput, SheetListingModel}; +use walkdir::WalkDir; struct AppModel { mcdu: Controller, @@ -103,7 +108,6 @@ struct Cli { #[tokio::main] async fn main() { - // dotenvy::dotenv().unwrap(); env_logger::Builder::from_env(Env::default().default_filter_or("debug")).init(); let cli = Cli::parse(); if !cli.directory.is_dir() { @@ -111,11 +115,72 @@ async fn main() { process::exit(1); } - let database = Database::setup("./testdb.db").await.unwrap(); + let database = Database::setup("./testdb.sqlite").await.unwrap(); // database.insert_sheet(Sheet::new_debug()).await.unwrap(); let sheets = database.fetch_all_sheets().await.unwrap(); + debug!("Validating sheets from database..."); + let validation_result = validate_sheet_files(sheets, &cli.directory); + debug!("{}", validation_result.get_stats()); + for updated in validation_result.updated_sheets { + database.update_sheet_path(updated).await.unwrap(); + } + let app = RelmApp::new("de.frajul.sheet-organizer"); // Pass empty command line args to allow my own parsing app.with_args(Vec::new()).run::(cli.directory); } + +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.validate_path(&sheet.path).unwrap_or(false)); + + let mut updated_sheets = Vec::new(); + let mut unassigned_files = Vec::new(); + + for pdf_file in 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) + }) + { + if let Some((i, _)) = invalidated_sheets + .iter() + .enumerate() + .find(|(_, sheet)| sheet.validate_path(&pdf_file).unwrap_or(false)) + { + let mut sheet = invalidated_sheets.remove(i); + sheet.path = pdf_file.to_str().unwrap().to_string(); + updated_sheets.push(sheet); + } else { + unassigned_files.push(pdf_file); + } + } + + FileValidationResult { + validated_sheets, + invalidated_sheets, + updated_sheets, + unassigned_files, + } +} diff --git a/src/sheet.rs b/src/sheet.rs index 82fa946..08349d2 100644 --- a/src/sheet.rs +++ b/src/sheet.rs @@ -1,5 +1,8 @@ +use std::{fs, path::Path}; + #[derive(sqlx::FromRow, Debug)] pub struct Sheet { + pub id: i32, pub name: String, // #[sqlx(from = "String")] pub path: String, @@ -10,30 +13,21 @@ pub struct Sheet { #[derive(sqlx::FromRow)] pub struct Composer { + pub id: i32, pub name: String, - pub id: u32, } impl Sheet { - pub fn new_debug() -> Self { - Sheet { - name: "Hello world".to_string(), - path: "This/is/my/path".into(), - file_size: 42, - file_hash: "h4sh".to_string(), - } - } - - pub fn verify_path(&self) -> std::io::Result { + pub fn validate_path(&self, path: impl AsRef) -> std::io::Result { // First compare file size since it is faster than hashing - // let file_size = fs::metadata(&self.path)?.len(); - // if file_size == self.file_size { - // let file_content = fs::read(&self.path)?; - // let file_hash = blake3::hash(&file_content); - // if file_hash.to_string() == self.file_hash { - // return Ok(true); - // } - // } + let file_size = fs::metadata(path.as_ref())?.len(); + if file_size as i32 == self.file_size { + let file_content = fs::read(path.as_ref())?; + let file_hash = blake3::hash(&file_content); + if file_hash.to_string() == self.file_hash { + return Ok(true); + } + } Ok(false) }