Successfully implement orphan database inserting
This commit is contained in:
parent
b102906f11
commit
4d5e6f19fc
@ -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<()> {
|
||||
|
71
src/main.rs
71
src/main.rs
@ -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,
|
||||
}
|
||||
}
|
||||
|
47
src/sheet.rs
47
src/sheet.rs
@ -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")?,
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user