summaryrefslogtreecommitdiff
path: root/src/files.rs
diff options
context:
space:
mode:
authorjakka <jakkadoujin@gmail.com>2025-06-12 21:48:22 +0300
committerjakka <jakkadoujin@gmail.com>2025-06-12 21:48:22 +0300
commit743b87fc7aea1aa90cfacee17cdac8ca818c5661 (patch)
tree0b1edf14b68a242a964b6a6704fb55c53c66dc4e /src/files.rs
parent382e526f76ada0ca58f560a1217cb9a597ba3945 (diff)
continued writing db logic. added files indexing
Diffstat (limited to 'src/files.rs')
-rw-r--r--src/files.rs135
1 files changed, 135 insertions, 0 deletions
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<Path>) -> 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()
+ );
+ }
+}