diff options
Diffstat (limited to 'src/db.rs')
| -rw-r--r-- | src/db.rs | 139 |
1 files changed, 98 insertions, 41 deletions
@@ -1,4 +1,4 @@ -use anyhow::{Ok, Result, anyhow}; +use anyhow::{Result, anyhow}; use directories::BaseDirs; use futures_util::StreamExt; use libsql::{Builder, Connection, params}; @@ -6,22 +6,19 @@ use std::{ ffi::OsStr, fmt::Display, path::{Path, absolute}, + time::{Duration, UNIX_EPOCH}, }; -use crate::flac::{get_vendor, CURRENT_VENDOR}; +use crate::flac::{CURRENT_VENDOR, get_vendor}; -pub async fn open_db() -> Result<Connection> { - let conn = if let Some(base_dir) = BaseDirs::new() { - let db_name = Path::new(base_dir.data_dir()).join("reencoder.db"); - Builder::new_local(db_name).build().await?.connect()? - } else { - return Err(anyhow!("Failed to locate data directory")); - }; - - conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, toencode BOOLEAN NOT NULL)", ()).await?; - - Ok(conn) -} +const TABLE_CREATE: &str = "CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, toencode BOOLEAN NOT NULL, modtime INTEGER)"; +const ADD_NEW_ITEM: &str = "INSERT INTO flacs (path, toencode, modtime) VALUES (?1, ?2, ?3)"; +const REPLACE_ITEM: &str = "REPLACE INTO flacs (path, toencode, modtime) VALUES (?1, ?2, ?3)"; +const TOENCODE_QUERY: &str = "SELECT path FROM flacs WHERE toencode"; +const CHECK_FILE: &str = "SELECT exists(SELECT 1 FROM flacs WHERE path = ?1)"; +const FETCH_MODTIME: &str = "SELECT modtime FROM flacs WHERE path = ?1"; +const FETCH_FILES: &str = "SELECT path FROM flacs"; +const REMOVE_FILE: &str = "DELETE FROM flac WHERE path = ?1"; #[derive(Debug)] pub enum Errors { @@ -40,19 +37,25 @@ pub trait Reencoder { async fn insert_file(&self, filename: &impl AsRef<OsStr>) -> Result<()>; async fn update_file(&self, filename: &impl AsRef<OsStr>) -> Result<()>; async fn get_files_toencode(&self) -> Result<Vec<String>>; - async fn get_files_indexed(&self) -> Result<Vec<String>>; + async fn check_file(&self, filename: &impl AsRef<OsStr>) -> Result<bool>; + async fn get_modtime(&self, filename: &impl AsRef<OsStr>) -> Result<u64>; + async fn clean_files(&self) -> Result<()>; } impl Reencoder for Connection { async fn insert_file(&self, filename: &impl AsRef<OsStr>) -> Result<()> { - let file = Path::new(filename); - let toencode = !matches!(get_vendor(file).as_str(), CURRENT_VENDOR); + let abs_filename = absolute(Path::new(filename))?; + let toencode = !matches!(get_vendor(&abs_filename)?.as_str(), CURRENT_VENDOR); - let abs_filename = absolute(file)?; + let modtime = abs_filename + .metadata()? + .modified()? + .duration_since(UNIX_EPOCH)? + .as_secs(); self.execute( - "INSERT INTO flacs (path, toencode) VALUES (?1, ?2)", - params![abs_filename.to_str().unwrap(), toencode], + ADD_NEW_ITEM, + params![abs_filename.to_str().unwrap(), toencode, modtime], ) .await?; @@ -62,9 +65,15 @@ impl Reencoder for Connection { async fn update_file(&self, filename: &impl AsRef<OsStr>) -> Result<()> { let abs_filename = absolute(Path::new(filename))?; + let modtime = abs_filename + .metadata()? + .modified()? + .duration_since(UNIX_EPOCH)? + .as_secs(); + self.execute( - "REPLACE INTO flacs (path, toencode) VALUES (?1, ?2)", - params![abs_filename.to_str().unwrap(), false], + REPLACE_ITEM, + params![abs_filename.to_str().unwrap(), false, modtime], ) .await?; @@ -72,9 +81,7 @@ impl Reencoder for Connection { } async fn get_files_toencode(&self) -> Result<Vec<String>> { - let rows = self - .query("SELECT path FROM flacs WHERE toencode", ()) - .await?; + let rows = self.query(TOENCODE_QUERY, ()).await?; if rows.column_count() == 0 { return Err(anyhow!(Errors::EmptyQuery)); }; @@ -88,24 +95,73 @@ impl Reencoder for Connection { Ok(filenames) } - async fn get_files_indexed(&self) -> Result<Vec<String>> { - let rows = self - .query("SELECT path FROM flacs", ()) - .await?; - if rows.column_count() == 0 { - return Err(anyhow!(Errors::EmptyQuery)); - }; + async fn check_file(&self, filename: &impl AsRef<OsStr>) -> Result<bool> { + let abs_filename = absolute(Path::new(filename))?; - let filenames = rows - .into_stream() - .map(|row| row.unwrap().get_str(0).unwrap().to_string()) - .collect::<Vec<String>>() - .await; + if let Some(row) = self + .query(CHECK_FILE, params!(abs_filename.to_str().unwrap())) + .await? + .next() + .await? + { + Ok(matches!(row.get_value(0)?, libsql::Value::Integer(1))) + } else { + Err(anyhow!("database error")) + } + } - Ok(filenames) + async fn get_modtime(&self, filename: &impl AsRef<OsStr>) -> Result<u64> { + let abs_filename = absolute(Path::new(filename))?; + + if let Some(row) = self + .query(FETCH_MODTIME, params!(abs_filename.to_str().unwrap())) + .await? + .next() + .await? + { + if let Some(sec) = row.get_value(0)?.as_integer() { + Ok(Duration::from_secs(*sec as u64).as_secs()) + } else { + Ok(Duration::from_secs(0).as_secs()) + } + } else { + Err(anyhow!("database error")) + } + } + + async fn clean_files(&self) -> Result<()> { + let mut tasks = tokio::task::JoinSet::new(); + while let Ok(Some(row)) = self.query(FETCH_FILES, ()).await?.next().await { + let path = absolute(Path::new(row.get_str(0)?))?; + let conn = self.clone(); + tasks.spawn(async move { + if !path.exists() { + let _ = conn + .execute(REMOVE_FILE, params!(path.to_str().unwrap())) + .await; + } + }); + } + + tasks.join_all().await; + + Ok(()) } } +pub async fn open_db() -> Result<Connection> { + let conn = if let Some(base_dir) = BaseDirs::new() { + let db_name = Path::new(base_dir.data_dir()).join("reencoder.db"); + Builder::new_local(db_name).build().await?.connect()? + } else { + return Err(anyhow!("Failed to locate data directory")); + }; + + conn.execute(TABLE_CREATE, ()).await?; + + Ok(conn) +} + #[cfg(test)] mod tests { use super::*; @@ -117,7 +173,7 @@ mod tests { .unwrap() .connect() .unwrap(); - conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, toencode BOOLEAN NOT NULL)", ()).await.unwrap(); + conn.execute(TABLE_CREATE, ()).await.unwrap(); conn } @@ -145,10 +201,11 @@ mod tests { let _ = conn .execute( - "REPLACE INTO flacs (path,, toencode) VALUES (?1, ?2)", + REPLACE_ITEM, params![ absolute(Path::new("16bit.flac")).unwrap().to_str(), - true + true, + "" ], ) .await; |
