use std::path::{Path, PathBuf}; use strum::{EnumMessage, IntoEnumIterator}; use strum_macros::{EnumDiscriminants, EnumIter, EnumString}; use chrono::{DateTime, NaiveDateTime, Utc}; use sqlx::{sqlite::SqliteRow, Row, SqlitePool}; use crate::{ database::Database, sheet::{I64DateTime, Pdf, Sheet, SheetKind, SheetKindDiscriminants}, }; pub async fn insert_file_as_orphan( database: &Database, file: impl AsRef, ) -> sqlx::Result { let pdf = Pdf::try_from(file.as_ref().to_path_buf()).unwrap(); let last_opened = DateTime::::default(); let result = sqlx::query( " INSERT INTO orphans (path, file_size, file_hash, last_opened) VALUES ($1, $2, $3, $4) ", ) .bind(pdf.path.to_str().unwrap().to_string()) .bind(pdf.file_size as i32) .bind(pdf.file_hash.clone()) .bind(last_opened.timestamp()) .execute(&database.connection) .await .unwrap(); let id = result.last_insert_rowid(); Ok(Sheet { id, last_opened: I64DateTime(last_opened), kind: SheetKind::Orphan { pdf }, }) } pub async fn find_path_of_book(database: &Database, book_id: &i64) -> sqlx::Result { sqlx::query("SELECT path FROM books WHERE id = $1") .bind(book_id) .map(|row: SqliteRow| PathBuf::try_from(row.try_get::("path").unwrap()).unwrap()) .fetch_one(&database.connection) .await } pub async fn update_sheet_path(database: &Database, sheet: &Sheet) -> sqlx::Result<()> { if let Some(path) = sheet.try_get_path() { let sheet_kind = SheetKindDiscriminants::from(&sheet.kind); let table = sheet_kind.get_database_table_name(); 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!( "UPDATE {} SET last_opened = $1 WHERE id = $2", table )) .bind(i64::from(&sheet.last_opened)) .bind(sheet.id) .execute(&database.connection) .await .map(|_| ()) } pub async fn fetch_all_sheets(database: &Database) -> sqlx::Result> { let mut sheets: Vec = Vec::new(); for kind in SheetKindDiscriminants::iter() { let table = kind.get_database_table_name(); let mut sheets_of_kind = sqlx::query(&format!("SELECT * FROM {}", table)) .map(|row: SqliteRow| Sheet { id: row.try_get("id").unwrap(), last_opened: I64DateTime::try_from(row.try_get::("last_opened").unwrap()) .unwrap(), kind: parse_kind_from_row(kind, row).unwrap(), }) .fetch_all(&database.connection) .await?; sheets.append(&mut sheets_of_kind); } Ok(sheets) } fn parse_kind_from_row(kind: SheetKindDiscriminants, row: SqliteRow) -> sqlx::Result { Ok(match kind { SheetKindDiscriminants::Sheet => SheetKind::Sheet { name: row.try_get("name")?, composer_id: row.try_get("composer_id")?, pdf: parse_pdf_from_row(&row)?, }, SheetKindDiscriminants::Orphan => SheetKind::Orphan { pdf: parse_pdf_from_row(&row)?, }, SheetKindDiscriminants::Book => SheetKind::Book { name: row.try_get("name")?, composer_id: row.try_get("composer_id")?, pdf: parse_pdf_from_row(&row)?, sheet_ids: sheet_ids_from_string(row.try_get("sheet_ids").unwrap()), }, SheetKindDiscriminants::BookSheet => SheetKind::BookSheet { name: row.try_get("name")?, book_id: row.try_get("book_id")?, first_page: row.try_get("first_page")?, last_page: row.try_get("last_page")?, }, }) } fn sheet_ids_from_string(s: String) -> Vec { s.trim() .split(",") .map(|s| i64::from_str_radix(s, 10).unwrap()) .collect() } fn parse_pdf_from_row(row: &SqliteRow) -> sqlx::Result { // TODO: use get instead of try_get??? Ok(Pdf { path: PathBuf::from(row.try_get::("path").unwrap()), file_size: row.try_get::("file_size")? as u64, file_hash: row.try_get("file_hash")?, }) }