From 743b87fc7aea1aa90cfacee17cdac8ca818c5661 Mon Sep 17 00:00:00 2001 From: jakka Date: Thu, 12 Jun 2025 21:48:22 +0300 Subject: continued writing db logic. added files indexing --- src/files.rs | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/files.rs (limited to 'src/files.rs') diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..df596f8 --- /dev/null +++ b/src/files.rs @@ -0,0 +1,135 @@ +use anyhow::{Result, anyhow}; +use libsql::Connection; +use std::{ + fmt::Display, + path::{Path, PathBuf, absolute}, + time::UNIX_EPOCH, +}; +use tokio::{fs::read_dir, task::JoinSet}; + +use crate::db::Reencoder; + +#[derive(Debug)] +struct FileError { + file: PathBuf, + error: anyhow::Error, +} + +impl Display for FileError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "error: {}\t on file {}", + self.error, + self.file.to_string_lossy() + ) + } +} + +async fn handle_file(file: PathBuf, conn: Connection) -> Result<()> { + match conn.check_file(&file).await { + Ok(true) => { + let modtime = file + .metadata()? + .modified()? + .duration_since(UNIX_EPOCH)? + .as_secs(); + let db_time = conn.get_modtime(&file).await?; + if modtime != db_time { + if let Err(error) = conn.update_file(&file).await { + return Err(anyhow!(FileError { file, error })); + }; + } + return Ok(()); + } + Err(error) => return Err(anyhow!(FileError { file, error })), + _ => {} + } + + if let Err(error) = conn.insert_file(&file).await { + return Err(anyhow!(FileError { file, error })); + } + + Ok(()) +} + +pub async fn index_files_recursively(path: &Path, conn: &Connection) -> Result<()> { + if !path.is_dir() { + return Err(anyhow!("Invalid root directory")); + } + let abspath = absolute(path)?; + let mut tasks = JoinSet::new(); + let mut counter: i64 = 0; + + let mut dirs = vec![abspath]; + + while let Some(dir) = dirs.pop() { + let mut read_dir = read_dir(dir).await?; + + while let Some(entry) = read_dir.next_entry().await? { + let path = entry.path(); + if path.is_dir() { + dirs.push(path); + } else if path.is_file() { + if let Some(ext) = path.extension() { + if ext == "flac" { + let newconn = conn.clone(); + counter += 1; + tasks.spawn(async move { handle_file(path, newconn).await }); + } + } + } + print!("\rFiles found:\t{counter}") + } + } + + while let Some(task) = tasks.join_next().await { + match task { + Ok(Err(error)) => eprintln!("{error}"), + Err(error) => eprintln!("Error encountered:\t{}", error), + _ => {} + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use libsql::Builder; + + use super::*; + + async fn dummy_db(name: impl AsRef) -> Connection { + let conn = Builder::new_local(name) + .build() + .await + .unwrap() + .connect() + .unwrap(); + conn.execute("CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY, toencode BOOLEAN NOT NULL, modtime INTEGER)", ()).await.unwrap(); + conn + } + + #[tokio::test] + async fn test_lots_of_files() { + let conn = dummy_db("temp3.db").await; + index_files_recursively(Path::new("/mnt/Music"), &conn) + .await + .unwrap(); + println!( + "\n{}", + conn.query("SELECT COUNT(DISTINCT path) FROM flacs", ()) + .await + .unwrap() + .next() + .await + .unwrap() + .unwrap() + .get_value(0) + .unwrap() + .as_integer() + .unwrap() + ); + } +} -- cgit v1.3.1