Successfully implement orphan database inserting

This commit is contained in:
Julian Mutter 2024-02-07 19:21:53 +01:00
parent b102906f11
commit 4d5e6f19fc
5 changed files with 135 additions and 39 deletions

View File

@ -36,7 +36,7 @@ impl Database {
pub async fn _insert_sheet(&self, sheet: Sheet) -> sqlx::Result<()> {
sqlx::query(
"
INSERT INTO sheets (name, composer_id, path, file_size, file_hash, last_modified)
INSERT INTO sheets (name, composer_id, path, file_size, file_hash, last_opened)
VALUES ($1, $2, $3, $4, $5, $6)
",
)
@ -66,20 +66,20 @@ impl Database {
.await
}
pub async fn insert_orphan_file(&self, file: OrphanFile) -> sqlx::Result<()> {
pub async fn insert_orphan_file(&self, file: &OrphanFile) -> sqlx::Result<i64> {
sqlx::query(
"
INSERT INTO orphan_files (path, file_size, file_hash, last_modified)
INSERT INTO orphan_files (path, file_size, file_hash, last_opened)
VALUES ($1, $2, $3, $4)
",
)
.bind(file.path.to_str().unwrap().to_string())
.bind(file.file_size as i32)
.bind(file.file_hash)
.bind(file.file_hash.clone())
.bind(file.last_opened.timestamp())
.execute(&self.connection)
.await
.map(|_| ())
.map(|result| result.last_insert_rowid())
}
pub async fn update_orphan_file_path(&self, file: &OrphanFile) -> sqlx::Result<()> {

View File

@ -12,7 +12,7 @@ use database::Database;
use env_logger::Env;
use log::{debug, error};
use relm4::RelmApp;
use sheet::Sheet;
use sheet::{OrphanFile, Sheet};
use walkdir::WalkDir;
use crate::ui::app::AppModel;
@ -35,17 +35,41 @@ async fn main() {
let database = Database::setup("./testdb.sqlite").await.unwrap();
// database.insert_sheet(Sheet::new_debug()).await.unwrap();
let sheets = database.fetch_all_sheets().await.unwrap();
let orphan_files = database.fetch_all_orphan_files().await.unwrap();
debug!("Validating sheets from database...");
let validation_result = validate_sheet_files(sheets, &cli.directory);
debug!("{}", validation_result.get_stats());
let mut validation_result = validate_sheet_files(sheets, orphan_files, &cli.directory);
debug!("{}", validation_result.get_stats()); // TODO: handle invalidated files
for updated in validation_result.updated_sheets.iter() {
database.update_sheet_path(updated).await.unwrap();
}
for updated in validation_result.updated_orphan_files.iter() {
database.update_orphan_file_path(updated).await.unwrap();
}
let mut orphans = validation_result.validated_orphan_files;
debug!("Inserting unassigned files into orphan table...");
for unassigned in validation_result.unassigned_files {
let mut orphan = OrphanFile::try_from(unassigned).unwrap();
let id = database.insert_orphan_file(&orphan).await.unwrap();
orphan.id = id;
orphans.push(orphan);
}
let mut sheets = validation_result.validated_sheets;
sheets.append(&mut validation_result.updated_sheets);
let sheets_and_orphans = SheetsAndOrphans { sheets, orphans };
let app = RelmApp::new("de.frajul.sheet-organizer");
// Pass empty command line args to allow my own parsing
app.with_args(Vec::new()).run::<AppModel>(validation_result);
app.with_args(Vec::new())
.run::<AppModel>(sheets_and_orphans);
}
pub struct SheetsAndOrphans {
sheets: Vec<Sheet>,
orphans: Vec<OrphanFile>,
}
pub struct FileValidationResult {
@ -53,21 +77,39 @@ pub struct FileValidationResult {
invalidated_sheets: Vec<Sheet>,
updated_sheets: Vec<Sheet>,
validated_orphan_files: Vec<OrphanFile>,
invalidated_orphan_files: Vec<OrphanFile>,
updated_orphan_files: Vec<OrphanFile>,
unassigned_files: Vec<PathBuf>,
}
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())
format!("Validated sheets: {}\nInvalidated sheets: {}\nUpdated sheets: {}\nValidated orphan_files: {}\nInvalidated orphan_files: {}\nUpdated orphan_files: {}\nUnassigned files: {}",
self.validated_sheets.len(), self.invalidated_sheets.len(), self.updated_sheets.len(),
self.validated_orphan_files.len(), self.invalidated_orphan_files.len(), self.updated_orphan_files.len(), self.unassigned_files.len())
}
}
fn validate_sheet_files(sheets: Vec<Sheet>, dir: impl AsRef<Path>) -> FileValidationResult {
fn validate_sheet_files(
sheets: Vec<Sheet>,
orphan_files: Vec<OrphanFile>,
dir: impl AsRef<Path>,
) -> FileValidationResult {
// TODO: fix duplication
let (validated_sheets, mut invalidated_sheets): (Vec<_>, Vec<_>) = sheets
.into_iter()
.partition(|sheet| sheet.validate_path(&sheet.path).unwrap_or(false));
let (validated_orphan_files, mut invalidated_orphan_files): (Vec<_>, Vec<_>) =
orphan_files.into_iter().partition(|orphan_file| {
orphan_file
.validate_path(&orphan_file.path)
.unwrap_or(false)
});
let mut updated_sheets = Vec::new();
let mut updated_orphan_files = Vec::new();
let mut unassigned_files = Vec::new();
for pdf_file in WalkDir::new(dir)
@ -89,7 +131,19 @@ fn validate_sheet_files(sheets: Vec<Sheet>, dir: impl AsRef<Path>) -> FileValida
let mut sheet = invalidated_sheets.remove(i);
sheet.path = pdf_file;
updated_sheets.push(sheet);
} else {
} else if let Some((i, _)) = invalidated_orphan_files
.iter()
.enumerate()
.find(|(_, orphan_file)| orphan_file.validate_path(&pdf_file).unwrap_or(false))
{
let mut orphan_file = invalidated_orphan_files.remove(i);
orphan_file.path = pdf_file;
updated_orphan_files.push(orphan_file);
} else if !validated_sheets.iter().any(|sheet| sheet.path == pdf_file)
&& !validated_orphan_files
.iter()
.any(|orphan| orphan.path == pdf_file)
{
unassigned_files.push(pdf_file);
}
}
@ -98,6 +152,9 @@ fn validate_sheet_files(sheets: Vec<Sheet>, dir: impl AsRef<Path>) -> FileValida
validated_sheets,
invalidated_sheets,
updated_sheets,
validated_orphan_files,
invalidated_orphan_files,
updated_orphan_files,
unassigned_files,
}
}

View File

@ -9,9 +9,9 @@ use chrono::{DateTime, NaiveDateTime, Utc};
#[derive(Debug, Clone)]
pub struct Sheet {
pub id: i32,
pub id: i64,
pub name: String,
pub composer_id: i32,
pub composer_id: i64,
pub path: PathBuf,
pub file_size: u64,
pub file_hash: String,
@ -25,7 +25,7 @@ impl FromRow<'_, SqliteRow> for Sheet {
name: row.try_get("name")?,
composer_id: row.try_get("composer_id")?,
path: row.try_get::<&str, _>("path")?.into(),
file_size: row.try_get::<i32, _>("file_size")? as u64,
file_size: row.try_get::<i64, _>("file_size")? as u64,
file_hash: row.try_get("file_hash")?,
last_opened: NaiveDateTime::from_timestamp_opt(
row.try_get::<i64, _>("last_opened")?,
@ -39,7 +39,7 @@ impl FromRow<'_, SqliteRow> for Sheet {
#[derive(sqlx::FromRow)]
pub struct Composer {
pub id: i32,
pub id: i64,
pub name: String,
}
@ -59,9 +59,44 @@ impl Sheet {
}
}
impl OrphanFile {
// TODO: fix duplication
pub fn validate_path(&self, path: impl AsRef<Path>) -> std::io::Result<bool> {
// First compare file size since it is faster than hashing
let file_size = fs::metadata(path.as_ref())?.len();
if file_size == 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)
}
}
impl TryFrom<PathBuf> for OrphanFile {
type Error = std::io::Error;
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
let file_size = fs::metadata(path.as_path())?.len();
let file_content = fs::read(path.as_path())?;
let file_hash = blake3::hash(&file_content).to_string();
Ok(OrphanFile {
id: -1,
path,
file_size,
file_hash,
last_opened: DateTime::default(),
})
}
}
#[derive(Debug, Clone)]
pub struct OrphanFile {
pub id: i32,
pub id: i64,
pub path: PathBuf,
pub file_size: u64,
pub file_hash: String,
@ -73,7 +108,7 @@ impl FromRow<'_, SqliteRow> for OrphanFile {
Ok(Self {
id: row.try_get("id")?,
path: row.try_get::<&str, _>("path")?.into(),
file_size: row.try_get::<i32, _>("file_size")? as u64,
file_size: row.try_get::<i64, _>("file_size")? as u64,
file_hash: row.try_get("file_hash")?,
last_opened: NaiveDateTime::from_timestamp_opt(
row.try_get::<i64, _>("last_opened")?,

View File

@ -1,11 +1,9 @@
use gtk::prelude::*;
use relm4::{prelude::*};
use relm4::prelude::*;
use crate::{
ui::{mcdu::McduOutput, sheet_model::SheetModelType},
FileValidationResult,
FileValidationResult, SheetsAndOrphans,
};
use super::{
@ -30,7 +28,7 @@ pub enum AppInput {
impl SimpleComponent for AppModel {
type Input = AppInput;
type Output = ();
type Init = FileValidationResult;
type Init = SheetsAndOrphans;
view! {
#[root]
@ -69,7 +67,7 @@ impl SimpleComponent for AppModel {
}
fn init(
file_validation_result: Self::Init,
sheets_and_orphans: Self::Init,
window: &Self::Root,
sender: ComponentSender<Self>,
) -> relm4::ComponentParts<Self> {
@ -81,22 +79,22 @@ impl SimpleComponent for AppModel {
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
});
let new_files: Vec<SheetModelType> = file_validation_result
.unassigned_files
let new_files: Vec<SheetModelType> = sheets_and_orphans
.orphans
.iter()
.map(|file| SheetModelType::Pdf {
path: file.to_path_buf(),
.map(|orphan| SheetModelType::Orphan {
orphan: orphan.clone(),
})
.collect();
let new_files_clone_iter = file_validation_result
.unassigned_files
.into_iter()
.map(|file| SheetModelType::Pdf { path: file });
let sheets_and_files: Vec<SheetModelType> = file_validation_result
.validated_sheets
let new_files_clone_iter = sheets_and_orphans
.orphans
.into_iter()
.map(|orphan| SheetModelType::Orphan { orphan });
let sheets_and_files: Vec<SheetModelType> = sheets_and_orphans
.sheets
.into_iter()
.chain(file_validation_result.updated_sheets)
.map(|sheet| SheetModelType::Sheet { sheet })
.chain(new_files_clone_iter)
.collect();

View File

@ -3,21 +3,21 @@ use std::path::{Path, PathBuf};
use gtk::prelude::*;
use relm4::prelude::*;
use crate::sheet::Sheet;
use crate::sheet::{OrphanFile, Sheet};
use super::sheet_listing::SheetListingInput;
#[derive(Debug, Clone)]
pub enum SheetModelType {
Sheet { sheet: Sheet },
Pdf { path: PathBuf },
Orphan { orphan: OrphanFile },
}
impl SheetModelType {
pub fn get_path(&self) -> &Path {
match self {
SheetModelType::Sheet { sheet } => &sheet.path.as_path(),
SheetModelType::Pdf { path } => path.as_path(),
SheetModelType::Orphan { orphan } => &orphan.path.as_path(),
}
}
}
@ -64,7 +64,13 @@ impl FactoryComponent for SheetModel {
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
let label = match &value {
SheetModelType::Sheet { sheet } => sheet.name.to_string(),
SheetModelType::Pdf { path } => path.file_name().unwrap().to_str().unwrap().to_string(),
SheetModelType::Orphan { orphan } => orphan
.path
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string(),
};
SheetModel {