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<()> {
|
pub async fn _insert_sheet(&self, sheet: Sheet) -> sqlx::Result<()> {
|
||||||
sqlx::query(
|
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)
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
@ -66,20 +66,20 @@ impl Database {
|
|||||||
.await
|
.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(
|
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)
|
VALUES ($1, $2, $3, $4)
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.bind(file.path.to_str().unwrap().to_string())
|
.bind(file.path.to_str().unwrap().to_string())
|
||||||
.bind(file.file_size as i32)
|
.bind(file.file_size as i32)
|
||||||
.bind(file.file_hash)
|
.bind(file.file_hash.clone())
|
||||||
.bind(file.last_opened.timestamp())
|
.bind(file.last_opened.timestamp())
|
||||||
.execute(&self.connection)
|
.execute(&self.connection)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|result| result.last_insert_rowid())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_orphan_file_path(&self, file: &OrphanFile) -> sqlx::Result<()> {
|
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 env_logger::Env;
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use relm4::RelmApp;
|
use relm4::RelmApp;
|
||||||
use sheet::Sheet;
|
use sheet::{OrphanFile, Sheet};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::ui::app::AppModel;
|
use crate::ui::app::AppModel;
|
||||||
@ -35,17 +35,41 @@ async fn main() {
|
|||||||
let database = Database::setup("./testdb.sqlite").await.unwrap();
|
let database = Database::setup("./testdb.sqlite").await.unwrap();
|
||||||
// database.insert_sheet(Sheet::new_debug()).await.unwrap();
|
// database.insert_sheet(Sheet::new_debug()).await.unwrap();
|
||||||
let sheets = database.fetch_all_sheets().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...");
|
debug!("Validating sheets from database...");
|
||||||
let validation_result = validate_sheet_files(sheets, &cli.directory);
|
let mut validation_result = validate_sheet_files(sheets, orphan_files, &cli.directory);
|
||||||
debug!("{}", validation_result.get_stats());
|
debug!("{}", validation_result.get_stats()); // TODO: handle invalidated files
|
||||||
for updated in validation_result.updated_sheets.iter() {
|
for updated in validation_result.updated_sheets.iter() {
|
||||||
database.update_sheet_path(updated).await.unwrap();
|
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");
|
let app = RelmApp::new("de.frajul.sheet-organizer");
|
||||||
// Pass empty command line args to allow my own parsing
|
// 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 {
|
pub struct FileValidationResult {
|
||||||
@ -53,21 +77,39 @@ pub struct FileValidationResult {
|
|||||||
invalidated_sheets: Vec<Sheet>,
|
invalidated_sheets: Vec<Sheet>,
|
||||||
updated_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>,
|
unassigned_files: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileValidationResult {
|
impl FileValidationResult {
|
||||||
fn get_stats(&self) -> String {
|
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
|
let (validated_sheets, mut invalidated_sheets): (Vec<_>, Vec<_>) = sheets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|sheet| sheet.validate_path(&sheet.path).unwrap_or(false));
|
.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_sheets = Vec::new();
|
||||||
|
let mut updated_orphan_files = Vec::new();
|
||||||
let mut unassigned_files = Vec::new();
|
let mut unassigned_files = Vec::new();
|
||||||
|
|
||||||
for pdf_file in WalkDir::new(dir)
|
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);
|
let mut sheet = invalidated_sheets.remove(i);
|
||||||
sheet.path = pdf_file;
|
sheet.path = pdf_file;
|
||||||
updated_sheets.push(sheet);
|
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);
|
unassigned_files.push(pdf_file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,6 +152,9 @@ fn validate_sheet_files(sheets: Vec<Sheet>, dir: impl AsRef<Path>) -> FileValida
|
|||||||
validated_sheets,
|
validated_sheets,
|
||||||
invalidated_sheets,
|
invalidated_sheets,
|
||||||
updated_sheets,
|
updated_sheets,
|
||||||
|
validated_orphan_files,
|
||||||
|
invalidated_orphan_files,
|
||||||
|
updated_orphan_files,
|
||||||
unassigned_files,
|
unassigned_files,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
src/sheet.rs
47
src/sheet.rs
@ -9,9 +9,9 @@ use chrono::{DateTime, NaiveDateTime, Utc};
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Sheet {
|
pub struct Sheet {
|
||||||
pub id: i32,
|
pub id: i64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub composer_id: i32,
|
pub composer_id: i64,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub file_size: u64,
|
pub file_size: u64,
|
||||||
pub file_hash: String,
|
pub file_hash: String,
|
||||||
@ -25,7 +25,7 @@ impl FromRow<'_, SqliteRow> for Sheet {
|
|||||||
name: row.try_get("name")?,
|
name: row.try_get("name")?,
|
||||||
composer_id: row.try_get("composer_id")?,
|
composer_id: row.try_get("composer_id")?,
|
||||||
path: row.try_get::<&str, _>("path")?.into(),
|
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")?,
|
file_hash: row.try_get("file_hash")?,
|
||||||
last_opened: NaiveDateTime::from_timestamp_opt(
|
last_opened: NaiveDateTime::from_timestamp_opt(
|
||||||
row.try_get::<i64, _>("last_opened")?,
|
row.try_get::<i64, _>("last_opened")?,
|
||||||
@ -39,7 +39,7 @@ impl FromRow<'_, SqliteRow> for Sheet {
|
|||||||
|
|
||||||
#[derive(sqlx::FromRow)]
|
#[derive(sqlx::FromRow)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
pub id: i32,
|
pub id: i64,
|
||||||
pub name: String,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OrphanFile {
|
pub struct OrphanFile {
|
||||||
pub id: i32,
|
pub id: i64,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub file_size: u64,
|
pub file_size: u64,
|
||||||
pub file_hash: String,
|
pub file_hash: String,
|
||||||
@ -73,7 +108,7 @@ impl FromRow<'_, SqliteRow> for OrphanFile {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: row.try_get("id")?,
|
id: row.try_get("id")?,
|
||||||
path: row.try_get::<&str, _>("path")?.into(),
|
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")?,
|
file_hash: row.try_get("file_hash")?,
|
||||||
last_opened: NaiveDateTime::from_timestamp_opt(
|
last_opened: NaiveDateTime::from_timestamp_opt(
|
||||||
row.try_get::<i64, _>("last_opened")?,
|
row.try_get::<i64, _>("last_opened")?,
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use relm4::{prelude::*};
|
use relm4::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ui::{mcdu::McduOutput, sheet_model::SheetModelType},
|
ui::{mcdu::McduOutput, sheet_model::SheetModelType},
|
||||||
FileValidationResult,
|
FileValidationResult, SheetsAndOrphans,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -30,7 +28,7 @@ pub enum AppInput {
|
|||||||
impl SimpleComponent for AppModel {
|
impl SimpleComponent for AppModel {
|
||||||
type Input = AppInput;
|
type Input = AppInput;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
type Init = FileValidationResult;
|
type Init = SheetsAndOrphans;
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
#[root]
|
#[root]
|
||||||
@ -69,7 +67,7 @@ impl SimpleComponent for AppModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn init(
|
fn init(
|
||||||
file_validation_result: Self::Init,
|
sheets_and_orphans: Self::Init,
|
||||||
window: &Self::Root,
|
window: &Self::Root,
|
||||||
sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> relm4::ComponentParts<Self> {
|
) -> relm4::ComponentParts<Self> {
|
||||||
@ -81,22 +79,22 @@ impl SimpleComponent for AppModel {
|
|||||||
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
||||||
});
|
});
|
||||||
|
|
||||||
let new_files: Vec<SheetModelType> = file_validation_result
|
let new_files: Vec<SheetModelType> = sheets_and_orphans
|
||||||
.unassigned_files
|
.orphans
|
||||||
.iter()
|
.iter()
|
||||||
.map(|file| SheetModelType::Pdf {
|
.map(|orphan| SheetModelType::Orphan {
|
||||||
path: file.to_path_buf(),
|
orphan: orphan.clone(),
|
||||||
})
|
})
|
||||||
.collect();
|
.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
|
let new_files_clone_iter = sheets_and_orphans
|
||||||
.validated_sheets
|
.orphans
|
||||||
|
.into_iter()
|
||||||
|
.map(|orphan| SheetModelType::Orphan { orphan });
|
||||||
|
|
||||||
|
let sheets_and_files: Vec<SheetModelType> = sheets_and_orphans
|
||||||
|
.sheets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(file_validation_result.updated_sheets)
|
|
||||||
.map(|sheet| SheetModelType::Sheet { sheet })
|
.map(|sheet| SheetModelType::Sheet { sheet })
|
||||||
.chain(new_files_clone_iter)
|
.chain(new_files_clone_iter)
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -3,21 +3,21 @@ use std::path::{Path, PathBuf};
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use relm4::prelude::*;
|
use relm4::prelude::*;
|
||||||
|
|
||||||
use crate::sheet::Sheet;
|
use crate::sheet::{OrphanFile, Sheet};
|
||||||
|
|
||||||
use super::sheet_listing::SheetListingInput;
|
use super::sheet_listing::SheetListingInput;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SheetModelType {
|
pub enum SheetModelType {
|
||||||
Sheet { sheet: Sheet },
|
Sheet { sheet: Sheet },
|
||||||
Pdf { path: PathBuf },
|
Orphan { orphan: OrphanFile },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SheetModelType {
|
impl SheetModelType {
|
||||||
pub fn get_path(&self) -> &Path {
|
pub fn get_path(&self) -> &Path {
|
||||||
match self {
|
match self {
|
||||||
SheetModelType::Sheet { sheet } => &sheet.path.as_path(),
|
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 {
|
fn init_model(value: Self::Init, _index: &DynamicIndex, _sender: FactorySender<Self>) -> Self {
|
||||||
let label = match &value {
|
let label = match &value {
|
||||||
SheetModelType::Sheet { sheet } => sheet.name.to_string(),
|
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 {
|
SheetModel {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user