Complete major sheet refactoring
Now using books etc is far easier
This commit is contained in:
parent
4ddfd75b2a
commit
d3f2375995
@ -1,4 +1,11 @@
|
|||||||
CREATE TABLE IF NOT EXISTS sheets (id integer primary key autoincrement, name TEXT, composer_id integer, path TEXT, file_size INTEGER, file_hash TEXT, last_opened INTEGER);
|
CREATE TABLE IF NOT EXISTS sheets (id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
CREATE TABLE IF NOT EXISTS composers (id integer primary key autoincrement, name TEXT);
|
last_opened INTEGER, name TEXT, composer_id INTEGER, path TEXT, file_size INTEGER, file_hash TEXT);
|
||||||
|
CREATE TABLE IF NOT EXISTS orphans (id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
last_opened INTEGER, path TEXT, file_size INTEGER, file_hash TEXT);
|
||||||
|
CREATE TABLE IF NOT EXISTS books (id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
last_opened INTEGER, name TEXT, composer_id INTEGER, sheet_ids TEXT, path TEXT, file_size INTEGER, file_hash TEXT);
|
||||||
|
CREATE TABLE IF NOT EXISTS booksheets (id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
last_opened INTEGER, name TEXT, book_id INTEGER, first_page INTEGER, last_page INTEGER);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS orphan_files (id integer primary key autoincrement, path TEXT, file_size INTEGER, file_hash TEXT, last_opened INTEGER);
|
|
||||||
|
CREATE TABLE IF NOT EXISTS composers (id INTEGER primary key autoincrement, name TEXT);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteRow, Executor, Sqlite, SqlitePool};
|
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool};
|
||||||
|
|
||||||
use crate::sheet::{EnumSheet, Sheet};
|
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
connection: SqlitePool,
|
pub connection: SqlitePool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
@ -35,86 +33,4 @@ impl Database {
|
|||||||
debug!("Connected to database");
|
debug!("Connected to database");
|
||||||
Ok(connection)
|
Ok(connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn insert_sheet(&self, sheet: &EnumSheet) -> sqlx::Result<()> {
|
|
||||||
sheet
|
|
||||||
.insert_to_database_query()
|
|
||||||
.execute(&self.connection)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_sheet_path(&self, sheet: &EnumSheet) -> sqlx::Result<()> {
|
|
||||||
sheet
|
|
||||||
.update_path_in_database_query()
|
|
||||||
.execute(&self.connection)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_sheet_last_opened(&self, sheet: &EnumSheet) -> sqlx::Result<()> {
|
|
||||||
sheet
|
|
||||||
.update_last_opened_in_database_query()
|
|
||||||
.execute(&self.connection)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
// TODO: check for success
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_all_sheets(&self) -> sqlx::Result<Vec<EnumSheet>> {
|
|
||||||
let mut stream = sqlx::query("SELECT * FROM users")
|
|
||||||
.map(|row: SqliteRow| {})
|
|
||||||
.fetch(&mut conn);
|
|
||||||
sqlx::query_as::<_, Sheet>("SELECT * FROM sheets")
|
|
||||||
.fetch_all(&self.connection)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert_orphan_file(&self, file: &OrphanFile) -> sqlx::Result<i64> {
|
|
||||||
sqlx::query(
|
|
||||||
"
|
|
||||||
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.clone())
|
|
||||||
.bind(file.last_opened.timestamp())
|
|
||||||
.execute(&self.connection)
|
|
||||||
.await
|
|
||||||
.map(|result| result.last_insert_rowid())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_orphan_file_path(&self, orphan: &OrphanFile) -> sqlx::Result<()> {
|
|
||||||
sqlx::query("UPDATE orphan_files SET path = $1 WHERE id = $2")
|
|
||||||
.bind(orphan.path.to_str().unwrap().to_string())
|
|
||||||
.bind(orphan.id)
|
|
||||||
.execute(&self.connection)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_orphan_last_opened(&self, orphan: &OrphanFile) -> sqlx::Result<()> {
|
|
||||||
sqlx::query("UPDATE orphan_files SET last_opened = $1 WHERE id = $2")
|
|
||||||
.bind(orphan.last_opened.timestamp())
|
|
||||||
.bind(orphan.id)
|
|
||||||
.execute(&self.connection)
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
// TODO: check for success
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn fetch_all_orphan_files(&self) -> sqlx::Result<Vec<OrphanFile>> {
|
|
||||||
sqlx::query_as::<_, OrphanFile>("SELECT * FROM orphan_files")
|
|
||||||
.fetch_all(&self.connection)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_executor(&self) -> E
|
|
||||||
where
|
|
||||||
E: Executor,
|
|
||||||
{
|
|
||||||
self.connection
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
116
src/main.rs
116
src/main.rs
@ -13,10 +13,10 @@ 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::{Pdf, Sheet};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::ui::app::AppModel;
|
use crate::ui::app::{AppInitData, AppModel};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
@ -36,38 +36,29 @@ async fn main() {
|
|||||||
let database = Database::setup(cli.directory.join("database.sqlite"))
|
let database = Database::setup(cli.directory.join("database.sqlite"))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// database.insert_sheet(Sheet::new_debug()).await.unwrap();
|
let sheets = sheet_dao::fetch_all_sheets(&database).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 mut validation_result = validate_sheet_files(sheets, orphan_files, &cli.directory);
|
let mut validation_result = validate_sheet_files(sheets, &cli.directory);
|
||||||
debug!("{}", validation_result.get_stats()); // TODO: handle invalidated files
|
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();
|
sheet_dao::update_sheet_path(&database, updated)
|
||||||
}
|
.await
|
||||||
for updated in validation_result.updated_orphan_files.iter() {
|
.unwrap();
|
||||||
database.update_orphan_file_path(updated).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut orphans = validation_result.validated_orphan_files;
|
|
||||||
orphans.append(&mut validation_result.updated_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;
|
let mut sheets = validation_result.validated_sheets;
|
||||||
sheets.append(&mut validation_result.updated_sheets);
|
sheets.append(&mut validation_result.updated_sheets);
|
||||||
|
|
||||||
let app_init_data = AppInitData {
|
debug!("Inserting unassigned files into orphan table...");
|
||||||
sheets,
|
for unassigned in validation_result.unassigned_files {
|
||||||
orphans,
|
let orphan = sheet_dao::insert_file_as_orphan(&database, unassigned)
|
||||||
database,
|
.await
|
||||||
};
|
.unwrap();
|
||||||
|
sheets.push(orphan);
|
||||||
|
}
|
||||||
|
|
||||||
|
let app_init_data = AppInitData { sheets, database };
|
||||||
|
|
||||||
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
|
||||||
@ -75,83 +66,44 @@ async fn main() {
|
|||||||
.run_async::<AppModel>(app_init_data);
|
.run_async::<AppModel>(app_init_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppInitData {
|
|
||||||
sheets: Vec<Sheet>,
|
|
||||||
orphans: Vec<OrphanFile>,
|
|
||||||
database: Database,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FileValidationResult {
|
pub struct FileValidationResult {
|
||||||
validated_sheets: Vec<Sheet>,
|
validated_sheets: Vec<Sheet>,
|
||||||
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: {}\nValidated orphan_files: {}\nInvalidated orphan_files: {}\nUpdated orphan_files: {}\nUnassigned files: {}",
|
format!("Validated sheets: {}\nInvalidated sheets: {}\nUpdated sheets: {}\nUnassigned files: {}",
|
||||||
self.validated_sheets.len(), self.invalidated_sheets.len(), self.updated_sheets.len(),
|
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())
|
self.unassigned_files.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_sheet_files(
|
fn validate_sheet_files(sheets: Vec<Sheet>, dir: impl AsRef<Path>) -> FileValidationResult {
|
||||||
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_own_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)
|
// TODO: improve performance?
|
||||||
.into_iter()
|
for pdf_file in find_all_pdfs_in_directory_recursive(dir) {
|
||||||
.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
|
if let Some((i, _)) = invalidated_sheets
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, sheet)| sheet.validate_path(&pdf_file).unwrap_or(false))
|
.find(|(_, sheet)| sheet.validate_path(&pdf_file).unwrap_or(false))
|
||||||
{
|
{
|
||||||
let mut sheet = invalidated_sheets.remove(i);
|
let mut sheet = invalidated_sheets.remove(i);
|
||||||
sheet.path = pdf_file;
|
let new_pdf = Pdf::try_from(pdf_file).unwrap();
|
||||||
|
sheet.update_pdf_file(new_pdf);
|
||||||
updated_sheets.push(sheet);
|
updated_sheets.push(sheet);
|
||||||
} else if let Some((i, _)) = invalidated_orphan_files
|
} else if !validated_sheets
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.any(|sheet| sheet.pdf_path_equal(&pdf_file))
|
||||||
.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);
|
||||||
}
|
}
|
||||||
@ -161,9 +113,19 @@ fn validate_sheet_files(
|
|||||||
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_all_pdfs_in_directory_recursive(dir: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
230
src/sheet.rs
230
src/sheet.rs
@ -1,104 +1,133 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use sqlx::{prelude::FromRow, sqlite::SqliteRow, QueryBuilder};
|
use sqlx::database;
|
||||||
use strum_macros::{EnumDiscriminants, EnumIter, EnumMessage};
|
use strum_macros::{EnumDiscriminants, EnumIter};
|
||||||
|
|
||||||
|
use crate::{database::Database, sheet_dao};
|
||||||
|
|
||||||
pub trait PdfSheet {
|
pub trait PdfSheet {
|
||||||
fn get_pdf(&self) -> &Pdf;
|
fn get_pdf(&self) -> &Pdf;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct EnumSheet {
|
pub struct Sheet {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub last_opened: I64DateTime,
|
pub last_opened: I64DateTime,
|
||||||
pub kind: SheetKind,
|
pub kind: SheetKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, EnumDiscriminants)]
|
#[derive(Debug, Clone, PartialEq, Eq, EnumDiscriminants)]
|
||||||
#[strum_discriminants(derive(EnumIter, EnumMessage))]
|
#[strum_discriminants(derive(EnumIter))]
|
||||||
pub enum SheetKind {
|
pub enum SheetKind {
|
||||||
#[strum_discriminants(strum(message = "sheets"))] // Message is the sqlite table name
|
|
||||||
Sheet {
|
Sheet {
|
||||||
pdf: Pdf,
|
pdf: Pdf,
|
||||||
name: String,
|
name: String,
|
||||||
composer_id: i64,
|
composer_id: i64,
|
||||||
},
|
},
|
||||||
#[strum_discriminants(strum(message = "orphans"))] // Message is the sqlite table name
|
Orphan {
|
||||||
Orphan { pdf: Pdf },
|
pdf: Pdf,
|
||||||
#[strum_discriminants(strum(message = "books"))] // Message is the sqlite table name
|
},
|
||||||
Book {
|
Book {
|
||||||
pdf: Pdf,
|
pdf: Pdf,
|
||||||
name: String,
|
name: String,
|
||||||
composer_id: i64,
|
composer_id: i64,
|
||||||
sheet_ids: Vec<i64>,
|
sheet_ids: Vec<i64>,
|
||||||
},
|
},
|
||||||
#[strum_discriminants(strum(message = "booksheets"))] // Message is the sqlite table name
|
|
||||||
BookSheet {
|
BookSheet {
|
||||||
|
name: String,
|
||||||
book_id: i64,
|
book_id: i64,
|
||||||
first_page: i64,
|
first_page: i64,
|
||||||
last_page: i64,
|
last_page: i64,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SheetKindTable {
|
impl PartialOrd for Sheet {
|
||||||
Sheet,
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AnySheet: Ord {}
|
impl Ord for Sheet {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
#[derive(sqlx::FromRow, PartialEq, Eq)]
|
self.last_opened.cmp(&other.last_opened)
|
||||||
pub struct Sheet {
|
}
|
||||||
id: i64,
|
|
||||||
#[sqlx(flatten)]
|
|
||||||
pdf: Pdf,
|
|
||||||
#[sqlx(try_from = "i64")]
|
|
||||||
last_opened: I64DateTime,
|
|
||||||
name: String,
|
|
||||||
composer_id: i64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, PartialEq, Eq)]
|
impl Sheet {
|
||||||
pub struct Orphan {
|
pub async fn open_file(&self, database: &Database) {
|
||||||
id: i64,
|
let path = match &self.kind {
|
||||||
#[sqlx(flatten)]
|
SheetKind::Sheet { pdf, .. } => pdf.path.clone(),
|
||||||
pdf: Pdf,
|
SheetKind::Orphan { pdf } => pdf.path.clone(),
|
||||||
#[sqlx(try_from = "i64")]
|
SheetKind::Book { pdf, .. } => pdf.path.clone(),
|
||||||
last_opened: I64DateTime,
|
SheetKind::BookSheet { book_id, .. } => sheet_dao::find_path_of_book(database, book_id)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
opener::open(path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_pdf_file(&mut self, new_pdf: Pdf) -> std::io::Result<()> {
|
||||||
|
match &mut self.kind {
|
||||||
|
SheetKind::Sheet { pdf, .. } => *pdf = new_pdf,
|
||||||
|
SheetKind::Orphan { pdf, .. } => *pdf = new_pdf,
|
||||||
|
SheetKind::Book { pdf, .. } => *pdf = new_pdf,
|
||||||
|
SheetKind::BookSheet { .. } => {} // TODO: find better solution!
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdf_path_equal(&self, path: impl AsRef<Path>) -> bool {
|
||||||
|
match &self.kind {
|
||||||
|
SheetKind::Sheet { pdf, .. } => pdf.path == path.as_ref(),
|
||||||
|
SheetKind::Orphan { pdf, .. } => pdf.path == path.as_ref(),
|
||||||
|
SheetKind::Book { pdf, .. } => pdf.path == path.as_ref(),
|
||||||
|
SheetKind::BookSheet { .. } => false, // TODO: find better solution!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_own_path(&self) -> std::io::Result<bool> {
|
||||||
|
Ok(match &self.kind {
|
||||||
|
SheetKind::Sheet { pdf, .. } => pdf.validate_path(&pdf.path)?,
|
||||||
|
SheetKind::Orphan { pdf, .. } => pdf.validate_path(&pdf.path)?,
|
||||||
|
SheetKind::Book { pdf, .. } => pdf.validate_path(&pdf.path)?,
|
||||||
|
SheetKind::BookSheet { book_id, .. } => true, // TODO: better solution?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_path(&self) -> Option<&Path> {
|
||||||
|
match &self.kind {
|
||||||
|
SheetKind::Sheet { pdf, .. } => Some(&pdf.path),
|
||||||
|
SheetKind::Orphan { pdf, .. } => Some(&pdf.path),
|
||||||
|
SheetKind::Book { pdf, .. } => Some(&pdf.path),
|
||||||
|
SheetKind::BookSheet { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_path(&self, path: impl AsRef<Path>) -> std::io::Result<bool> {
|
||||||
|
Ok(match &self.kind {
|
||||||
|
SheetKind::Sheet { pdf, .. } => pdf.validate_path(path)?,
|
||||||
|
SheetKind::Orphan { pdf, .. } => pdf.validate_path(path)?,
|
||||||
|
SheetKind::Book { pdf, .. } => pdf.validate_path(path)?,
|
||||||
|
SheetKind::BookSheet { book_id, .. } => true, // TODO: better solution?
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, PartialEq, Eq)]
|
impl SheetKindDiscriminants {
|
||||||
pub struct Book {
|
pub fn get_database_table_name(&self) -> &str {
|
||||||
id: i64,
|
match self {
|
||||||
#[sqlx(flatten)]
|
SheetKindDiscriminants::Sheet => "sheets",
|
||||||
pdf: Pdf,
|
SheetKindDiscriminants::Orphan => "orphans",
|
||||||
#[sqlx(try_from = "i64")]
|
SheetKindDiscriminants::Book => "books",
|
||||||
last_opened: I64DateTime,
|
SheetKindDiscriminants::BookSheet => "booksheets",
|
||||||
name: String,
|
}
|
||||||
composer_id: i64,
|
}
|
||||||
sheet_ids: Vec<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct BookSheet {
|
|
||||||
id: i64,
|
|
||||||
#[sqlx(try_from = "i64")]
|
|
||||||
last_opened: I64DateTime,
|
|
||||||
book_id: i64,
|
|
||||||
first_page: i64,
|
|
||||||
last_page: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Pdf {
|
|
||||||
#[sqlx(try_from = "String")]
|
|
||||||
path: PathBuf,
|
|
||||||
file_size: u64,
|
|
||||||
file_hash: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow)]
|
#[derive(sqlx::FromRow)]
|
||||||
@ -107,8 +136,8 @@ pub struct Composer {
|
|||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct I64DateTime(DateTime<Utc>);
|
pub struct I64DateTime(pub DateTime<Utc>);
|
||||||
|
|
||||||
impl TryFrom<i64> for I64DateTime {
|
impl TryFrom<i64> for I64DateTime {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
@ -122,13 +151,24 @@ impl TryFrom<i64> for I64DateTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<I64DateTime> for i64 {
|
impl From<&I64DateTime> for i64 {
|
||||||
fn from(value: I64DateTime) -> Self {
|
fn from(value: &I64DateTime) -> Self {
|
||||||
value.0.timestamp()
|
value.0.timestamp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Pdf {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub file_size: u64,
|
||||||
|
pub file_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Pdf {
|
impl Pdf {
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
self.path.file_name().unwrap().to_str().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn validate_path(&self, path: impl AsRef<Path>) -> std::io::Result<bool> {
|
pub fn validate_path(&self, path: impl AsRef<Path>) -> std::io::Result<bool> {
|
||||||
// First compare file size since it is faster than hashing
|
// First compare file size since it is faster than hashing
|
||||||
let file_size = fs::metadata(path.as_ref())?.len();
|
let file_size = fs::metadata(path.as_ref())?.len();
|
||||||
@ -159,67 +199,3 @@ impl TryFrom<PathBuf> for Pdf {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PdfSheet for Sheet {
|
|
||||||
fn get_pdf(&self) -> &Pdf {
|
|
||||||
&self.pdf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PdfSheet for Orphan {
|
|
||||||
fn get_pdf(&self) -> &Pdf {
|
|
||||||
&self.pdf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PdfSheet for Book {
|
|
||||||
fn get_pdf(&self) -> &Pdf {
|
|
||||||
&self.pdf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AnySheet for Sheet {}
|
|
||||||
|
|
||||||
impl Ord for EnumSheet {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
self.last_opened.cmp(other.last_opened)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EnumSheet {
|
|
||||||
pub fn update_path_in_database_query(&self) -> String {
|
|
||||||
todo!()
|
|
||||||
// sqlx::query("UPDATE sheets SET path = $1 WHERE id = $2")
|
|
||||||
// .bind(sheet.path.to_str().unwrap().to_string())
|
|
||||||
// .bind(sheet.id)
|
|
||||||
}
|
|
||||||
pub fn update_last_opened_in_database_query(&self) -> String {
|
|
||||||
// sqlx::query("UPDATE sheets SET last_opened = $1 WHERE id = $2")
|
|
||||||
// .bind(sheet.last_opened.timestamp())
|
|
||||||
// .bind(sheet.id)
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
pub fn insert_to_database_query(&self) -> String {
|
|
||||||
todo!()
|
|
||||||
// sqlx::query(
|
|
||||||
// "
|
|
||||||
// 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.clone())
|
|
||||||
// .bind(file.last_opened.timestamp())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_database_table_name(&self) -> &str {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl PartialOrd for EnumSheet {
|
|
||||||
// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
// Some(self.cmp(other))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
136
src/sheet_dao.rs
136
src/sheet_dao.rs
@ -1,72 +1,95 @@
|
|||||||
use chrono::NaiveDateTime;
|
use std::path::{Path, PathBuf};
|
||||||
use sqlx::{sqlite::SqliteRow, SqlitePool};
|
use strum::{EnumMessage, IntoEnumIterator};
|
||||||
|
use strum_macros::{EnumDiscriminants, EnumIter, EnumString};
|
||||||
|
|
||||||
use crate::sheet::{EnumSheet, Pdf, SheetKind, SheetKindDiscriminants};
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
|
use sqlx::{sqlite::SqliteRow, Row, SqlitePool};
|
||||||
|
|
||||||
pub async fn insert_sheet(connection: SqlitePool, sheet: &EnumSheet) -> sqlx::Result<()> {
|
use crate::{
|
||||||
let table = sheet.kind.into::<SheetKindDiscriminants>().get_message();
|
database::Database,
|
||||||
sqlx::query(&format!(
|
sheet::{I64DateTime, Pdf, Sheet, SheetKind, SheetKindDiscriminants},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn insert_file_as_orphan(
|
||||||
|
database: &Database,
|
||||||
|
file: impl AsRef<Path>,
|
||||||
|
) -> sqlx::Result<Sheet> {
|
||||||
|
let pdf = Pdf::try_from(file.as_ref().to_path_buf()).unwrap();
|
||||||
|
let last_opened = DateTime::<Utc>::default();
|
||||||
|
|
||||||
|
let result = sqlx::query(
|
||||||
"
|
"
|
||||||
INSERT INTO {} (path, file_size, file_hash, last_opened)
|
INSERT INTO orphans (path, file_size, file_hash, last_opened)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
",
|
",
|
||||||
table,
|
)
|
||||||
))
|
.bind(pdf.path.to_str().unwrap().to_string())
|
||||||
.bind(sheet.pdf.path.to_str().unwrap().to_string())
|
.bind(pdf.file_size as i32)
|
||||||
.bind(sheet.pdf.file_size as i32)
|
.bind(pdf.file_hash.clone())
|
||||||
.bind(sheet.pdf.file_hash.clone())
|
.bind(last_opened.timestamp())
|
||||||
.bind(sheet.last_opened.timestamp())
|
.execute(&database.connection)
|
||||||
.execute(&mut connection)
|
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.unwrap();
|
||||||
|
|
||||||
|
let id = result.last_insert_rowid();
|
||||||
|
|
||||||
|
Ok(Sheet {
|
||||||
|
id,
|
||||||
|
last_opened: I64DateTime(last_opened),
|
||||||
|
kind: SheetKind::Orphan { pdf },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_sheet_path(connection: SqlitePool, sheet: &EnumSheet) -> sqlx::Result<()> {
|
pub async fn find_path_of_book(database: &Database, book_id: &i64) -> sqlx::Result<PathBuf> {
|
||||||
let table = sheet.kind.into::<SheetKindDiscriminants>().get_message();
|
sqlx::query("SELECT path FROM books WHERE id = $1")
|
||||||
sqlx::query(&format!("UPDATE {} SET path = $1 WHERE id = $2", table))
|
.bind(book_id)
|
||||||
.bind(sheet.kind.pdf.path.to_str().unwrap().to_string())
|
.map(|row: SqliteRow| PathBuf::try_from(row.try_get::<String, _>("path").unwrap()).unwrap())
|
||||||
.bind(sheet.id)
|
.fetch_one(&database.connection)
|
||||||
.execute(&mut connection)
|
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
|
||||||
// TODO: check for success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_sheet_last_opened(
|
pub async fn update_sheet_path(database: &Database, sheet: &Sheet) -> sqlx::Result<()> {
|
||||||
connection: SqlitePool,
|
if let Some(path) = sheet.try_get_path() {
|
||||||
sheet: &EnumSheet,
|
let sheet_kind = SheetKindDiscriminants::from(&sheet.kind);
|
||||||
) -> sqlx::Result<()> {
|
let table = sheet_kind.get_database_table_name();
|
||||||
let table = sheet.kind.into::<SheetKindDiscriminants>().get_message();
|
return sqlx::query(&format!("UPDATE {} SET path = $1 WHERE id = $2", table))
|
||||||
|
.bind(path.to_str().unwrap().to_string())
|
||||||
|
.bind(sheet.id)
|
||||||
|
.execute(&database.connection)
|
||||||
|
.await
|
||||||
|
.map(|_| ());
|
||||||
|
}
|
||||||
|
Ok(()) // TODO: error on else?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_sheet_last_opened(database: &Database, sheet: &Sheet) -> sqlx::Result<()> {
|
||||||
|
let sheet_kind = SheetKindDiscriminants::from(&sheet.kind);
|
||||||
|
let table = sheet_kind.get_database_table_name();
|
||||||
sqlx::query(&format!(
|
sqlx::query(&format!(
|
||||||
"UPDATE {} SET last_opened = $1 WHERE id = $2",
|
"UPDATE {} SET last_opened = $1 WHERE id = $2",
|
||||||
table
|
table
|
||||||
))
|
))
|
||||||
.bind(sheet.last_opened.timestamp())
|
.bind(i64::from(&sheet.last_opened))
|
||||||
.bind(sheet.id)
|
.bind(sheet.id)
|
||||||
.execute(&mut connection)
|
.execute(&database.connection)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
// TODO: check for success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_all_sheets(&connection: SqlitePool) -> sqlx::Result<Vec<EnumSheet>> {
|
pub async fn fetch_all_sheets(database: &Database) -> sqlx::Result<Vec<Sheet>> {
|
||||||
let mut sheets: Vec<EnumSheet> = Vec::new();
|
let mut sheets: Vec<Sheet> = Vec::new();
|
||||||
|
|
||||||
for kind in SheetKindDiscriminants::iter() {
|
for kind in SheetKindDiscriminants::iter() {
|
||||||
let table = kind.get_message();
|
let table = kind.get_database_table_name();
|
||||||
|
|
||||||
let mut sheets_of_kind = sqlx::query(&format!("SELECT * FROM {}", table))
|
let mut sheets_of_kind = sqlx::query(&format!("SELECT * FROM {}", table))
|
||||||
.map(|row: SqliteRow| EnumSheet {
|
.map(|row: SqliteRow| Sheet {
|
||||||
id: row.try_get("id")?,
|
id: row.try_get("id").unwrap(),
|
||||||
last_opened: NaiveDateTime::from_timestamp_opt(
|
last_opened: I64DateTime::try_from(row.try_get::<i64, _>("last_opened").unwrap())
|
||||||
row.try_get::<i64, _>("last_opened")?,
|
.unwrap(),
|
||||||
0,
|
kind: parse_kind_from_row(kind, row).unwrap(),
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.and_utc(),
|
|
||||||
kind: parse_kind_from_row(kind, row),
|
|
||||||
})
|
})
|
||||||
.fetch_all(&mut connection)
|
.fetch_all(&database.connection)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
sheets.append(&mut sheets_of_kind);
|
sheets.append(&mut sheets_of_kind);
|
||||||
@ -80,21 +103,38 @@ fn parse_kind_from_row(kind: SheetKindDiscriminants, row: SqliteRow) -> sqlx::Re
|
|||||||
SheetKindDiscriminants::Sheet => SheetKind::Sheet {
|
SheetKindDiscriminants::Sheet => SheetKind::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")?,
|
||||||
pdf: Pdf::from_row(row)?,
|
pdf: parse_pdf_from_row(&row)?,
|
||||||
},
|
},
|
||||||
SheetKindDiscriminants::Orphan => SheetKind::Orphan {
|
SheetKindDiscriminants::Orphan => SheetKind::Orphan {
|
||||||
pdf: Pdf::from_row(row)?,
|
pdf: parse_pdf_from_row(&row)?,
|
||||||
},
|
},
|
||||||
SheetKindDiscriminants::Book => SheetKind::Book {
|
SheetKindDiscriminants::Book => SheetKind::Book {
|
||||||
name: row.try_get("name")?,
|
name: row.try_get("name")?,
|
||||||
composer_id: row.try_get("composer_id")?,
|
composer_id: row.try_get("composer_id")?,
|
||||||
pdf: Pdf::from_row(row)?,
|
pdf: parse_pdf_from_row(&row)?,
|
||||||
sheet_ids: todo!(),
|
sheet_ids: sheet_ids_from_string(row.try_get("sheet_ids").unwrap()),
|
||||||
},
|
},
|
||||||
SheetKindDiscriminants::BookSheet => SheetKind::BookSheet {
|
SheetKindDiscriminants::BookSheet => SheetKind::BookSheet {
|
||||||
|
name: row.try_get("name")?,
|
||||||
book_id: row.try_get("book_id")?,
|
book_id: row.try_get("book_id")?,
|
||||||
first_page: row.try_get("first_page")?,
|
first_page: row.try_get("first_page")?,
|
||||||
last_page: row.try_get("last_page")?,
|
last_page: row.try_get("last_page")?,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sheet_ids_from_string(s: String) -> Vec<i64> {
|
||||||
|
s.trim()
|
||||||
|
.split(",")
|
||||||
|
.map(|s| i64::from_str_radix(s, 10).unwrap())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_pdf_from_row(row: &SqliteRow) -> sqlx::Result<Pdf> {
|
||||||
|
// TODO: use get instead of try_get???
|
||||||
|
Ok(Pdf {
|
||||||
|
path: PathBuf::from(row.try_get::<String, _>("path").unwrap()),
|
||||||
|
file_size: row.try_get::<i64, _>("file_size")? as u64,
|
||||||
|
file_hash: row.try_get("file_hash")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -8,8 +8,9 @@ use relm4::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
database::Database,
|
database::Database,
|
||||||
ui::{mcdu::McduOutput, sheet_model::SheetModelType},
|
sheet::{I64DateTime, Sheet},
|
||||||
AppInitData,
|
sheet_dao,
|
||||||
|
ui::mcdu::McduOutput,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -20,13 +21,18 @@ use super::{
|
|||||||
pub struct AppModel {
|
pub struct AppModel {
|
||||||
database: Database,
|
database: Database,
|
||||||
mcdu: Controller<McduModel>,
|
mcdu: Controller<McduModel>,
|
||||||
sheets_and_files_listing: Controller<SheetListingModel>,
|
sheets_listing: Controller<SheetListingModel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AppInput {
|
pub enum AppInput {
|
||||||
SearchStarted(String),
|
SearchStarted(String),
|
||||||
SheetPressed(SheetModelType),
|
SheetPressed(Sheet),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppInitData {
|
||||||
|
pub sheets: Vec<Sheet>,
|
||||||
|
pub database: Database,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component(pub, async)]
|
#[relm4::component(pub, async)]
|
||||||
@ -49,7 +55,7 @@ impl AsyncComponent for AppModel {
|
|||||||
set_orientation: gtk::Orientation::Vertical,
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
gtk::ScrolledWindow {
|
gtk::ScrolledWindow {
|
||||||
model.sheets_and_files_listing.widget(),
|
model.sheets_listing.widget(),
|
||||||
set_vexpand: true,
|
set_vexpand: true,
|
||||||
set_hexpand: true,
|
set_hexpand: true,
|
||||||
},
|
},
|
||||||
@ -74,31 +80,19 @@ impl AsyncComponent for AppModel {
|
|||||||
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut orphan_files: Vec<SheetModelType> = init_data
|
let mut sheets = init_data.sheets;
|
||||||
.orphans
|
sheets.sort_by(|a, b| a.cmp(b).reverse());
|
||||||
.into_iter()
|
|
||||||
.map(|orphan| SheetModelType::Orphan { orphan })
|
|
||||||
.collect();
|
|
||||||
orphan_files.sort_by(|a, b| a.cmp(b).reverse());
|
|
||||||
|
|
||||||
let mut sheets_and_files: Vec<SheetModelType> = init_data
|
let sheets_listing = SheetListingModel::builder()
|
||||||
.sheets
|
.launch(sheets)
|
||||||
.into_iter()
|
|
||||||
.map(|sheet| SheetModelType::Sheet { sheet })
|
|
||||||
.chain(orphan_files)
|
|
||||||
.collect();
|
|
||||||
sheets_and_files.sort_by(|a, b| a.cmp(b).reverse());
|
|
||||||
|
|
||||||
let sheets_and_files_listing = SheetListingModel::builder()
|
|
||||||
.launch(sheets_and_files)
|
|
||||||
.forward(sender.input_sender(), |response| {
|
.forward(sender.input_sender(), |response| {
|
||||||
AppInput::SheetPressed(response.sheet_model_type)
|
AppInput::SheetPressed(response.sheet)
|
||||||
});
|
});
|
||||||
|
|
||||||
let model = AppModel {
|
let model = AppModel {
|
||||||
database: init_data.database,
|
database: init_data.database,
|
||||||
mcdu,
|
mcdu,
|
||||||
sheets_and_files_listing,
|
sheets_listing,
|
||||||
};
|
};
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
@ -112,30 +106,19 @@ impl AsyncComponent for AppModel {
|
|||||||
_sender: AsyncComponentSender<Self>,
|
_sender: AsyncComponentSender<Self>,
|
||||||
_root: &Self::Root,
|
_root: &Self::Root,
|
||||||
) {
|
) {
|
||||||
// AppInput::SheetPressed(sheet) => opener::open(sheet).unwrap(),
|
|
||||||
match message {
|
match message {
|
||||||
AppInput::SearchStarted(query) => {
|
AppInput::SearchStarted(query) => {
|
||||||
self.sheets_and_files_listing
|
self.sheets_listing
|
||||||
.emit(SheetListingInput::Query(query.clone()));
|
.emit(SheetListingInput::Query(query.clone()));
|
||||||
}
|
}
|
||||||
AppInput::SheetPressed(sheet_model_type) => {
|
AppInput::SheetPressed(sheet) => {
|
||||||
opener::open(sheet_model_type.get_path()).unwrap();
|
sheet.open_file(&self.database).await;
|
||||||
match sheet_model_type {
|
// TODO: updating does not work
|
||||||
SheetModelType::Orphan { mut orphan } => {
|
let mut sheet = sheet;
|
||||||
orphan.last_opened = Utc::now();
|
sheet.last_opened = I64DateTime(Utc::now());
|
||||||
self.database
|
sheet_dao::update_sheet_last_opened(&self.database, &sheet)
|
||||||
.update_orphan_last_opened(&orphan)
|
.await
|
||||||
.await
|
.unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
SheetModelType::Sheet { mut sheet } => {
|
|
||||||
sheet.last_opened = Utc::now();
|
|
||||||
self.database
|
|
||||||
.update_sheet_last_opened(&sheet)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ use relm4::factory::FactoryVecDeque;
|
|||||||
use relm4::RelmListBoxExt;
|
use relm4::RelmListBoxExt;
|
||||||
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
use relm4::{gtk, ComponentParts, ComponentSender, SimpleComponent};
|
||||||
|
|
||||||
use super::sheet_model::{OnQueryUpdate, SheetModel, SheetModelType};
|
use crate::sheet::Sheet;
|
||||||
|
|
||||||
|
use super::sheet_model::{OnQueryUpdate, SheetModel};
|
||||||
|
|
||||||
pub struct SheetListingModel {
|
pub struct SheetListingModel {
|
||||||
sheets: FactoryVecDeque<SheetModel>,
|
sheets: FactoryVecDeque<SheetModel>,
|
||||||
@ -18,12 +20,12 @@ pub enum SheetListingInput {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SheetModelSelected {
|
pub struct SheetModelSelected {
|
||||||
pub sheet_model_type: SheetModelType,
|
pub sheet: Sheet,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component(pub)]
|
#[relm4::component(pub)]
|
||||||
impl SimpleComponent for SheetListingModel {
|
impl SimpleComponent for SheetListingModel {
|
||||||
type Init = Vec<SheetModelType>;
|
type Init = Vec<Sheet>;
|
||||||
type Input = SheetListingInput;
|
type Input = SheetListingInput;
|
||||||
type Output = SheetModelSelected;
|
type Output = SheetModelSelected;
|
||||||
|
|
||||||
@ -65,10 +67,10 @@ impl SimpleComponent for SheetListingModel {
|
|||||||
self.sheets.broadcast(OnQueryUpdate { query });
|
self.sheets.broadcast(OnQueryUpdate { query });
|
||||||
}
|
}
|
||||||
SheetListingInput::ListBoxRowClicked(index) => {
|
SheetListingInput::ListBoxRowClicked(index) => {
|
||||||
let x = self.sheets.get(index as usize).unwrap();
|
let sheet_model = self.sheets.get(index as usize).unwrap();
|
||||||
sender
|
sender
|
||||||
.output(SheetModelSelected {
|
.output(SheetModelSelected {
|
||||||
sheet_model_type: x.sheet_model_type.clone(),
|
sheet: sheet_model.sheet.clone(),
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -3,47 +3,13 @@ use std::{cmp::Ordering, path::Path};
|
|||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use relm4::prelude::*;
|
use relm4::prelude::*;
|
||||||
|
|
||||||
use crate::sheet::{OrphanFile, Sheet};
|
use crate::sheet::Sheet;
|
||||||
|
|
||||||
use super::sheet_listing::SheetListingInput;
|
use super::sheet_listing::SheetListingInput;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum SheetModelType {
|
|
||||||
Sheet { sheet: Sheet },
|
|
||||||
Orphan { orphan: OrphanFile },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SheetModelType {
|
|
||||||
pub fn get_path(&self) -> &Path {
|
|
||||||
match self {
|
|
||||||
SheetModelType::Sheet { sheet } => sheet.path.as_path(),
|
|
||||||
SheetModelType::Orphan { orphan } => orphan.path.as_path(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Ord for SheetModelType {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
let self_last_opened = match self {
|
|
||||||
SheetModelType::Sheet { sheet } => sheet.last_opened,
|
|
||||||
SheetModelType::Orphan { orphan } => orphan.last_opened,
|
|
||||||
};
|
|
||||||
let other_last_opened = match other {
|
|
||||||
SheetModelType::Sheet { sheet } => sheet.last_opened,
|
|
||||||
SheetModelType::Orphan { orphan } => orphan.last_opened,
|
|
||||||
};
|
|
||||||
self_last_opened.cmp(&other_last_opened)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for SheetModelType {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SheetModel {
|
pub struct SheetModel {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub sheet_model_type: SheetModelType,
|
pub sheet: Sheet,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +23,7 @@ pub struct OnQueryUpdate {
|
|||||||
|
|
||||||
#[relm4::factory(pub)]
|
#[relm4::factory(pub)]
|
||||||
impl FactoryComponent for SheetModel {
|
impl FactoryComponent for SheetModel {
|
||||||
type Init = SheetModelType;
|
type Init = Sheet;
|
||||||
type ParentWidget = gtk::ListBox;
|
type ParentWidget = gtk::ListBox;
|
||||||
type CommandOutput = ();
|
type CommandOutput = ();
|
||||||
type ParentInput = SheetListingInput;
|
type ParentInput = SheetListingInput;
|
||||||
@ -81,20 +47,16 @@ 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.kind {
|
||||||
SheetModelType::Sheet { sheet } => sheet.name.to_string(),
|
crate::sheet::SheetKind::Sheet { name, .. } => name,
|
||||||
SheetModelType::Orphan { orphan } => orphan
|
crate::sheet::SheetKind::Orphan { pdf } => pdf.get_name(),
|
||||||
.path
|
crate::sheet::SheetKind::Book { name, .. } => name,
|
||||||
.file_name()
|
crate::sheet::SheetKind::BookSheet { name, .. } => name,
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SheetModel {
|
SheetModel {
|
||||||
label,
|
label: label.to_string(),
|
||||||
sheet_model_type: value,
|
sheet: value,
|
||||||
visible: true,
|
visible: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user