summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db.rs219
-rw-r--r--src/files.rs132
-rw-r--r--src/main.rs50
3 files changed, 219 insertions, 182 deletions
diff --git a/src/db.rs b/src/db.rs
index ba14c1a..a899ac3 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -1,16 +1,15 @@
+use crate::flac::{CURRENT_VENDOR, get_vendor};
use anyhow::{Result, anyhow};
use directories::BaseDirs;
-use rusqlite::{Connection, params};
use std::{
path::{Path, PathBuf},
time::UNIX_EPOCH,
};
-
-use crate::flac::{CURRENT_VENDOR, get_vendor};
+use turso::{Connection, params};
const TABLE_CREATE: &str = "CREATE TABLE IF NOT EXISTS flacs (path TEXT PRIMARY KEY UNIQUE, toencode BOOLEAN NOT NULL, modtime INTEGER)";
-const ADD_ITEM: &str = "INSERT INTO flacs (path, toencode, modtime) VALUES (?1, ?2, ?3)";
-const UPDATE_ITEM: &str = "UPDATE flacs SET toencode = ?2, modtime = ?3 WHERE path = ?1";
+const ADD_FILE: &str = "INSERT INTO flacs (path, toencode, modtime) VALUES (?1, ?2, ?3)";
+const UPDATE_FILE: &str = "UPDATE flacs SET toencode = ?2, modtime = ?3 WHERE path = ?1";
const TOENCODE_PATHS: &str = "SELECT path FROM flacs WHERE toencode";
const TOENCODE_NUMBER: &str = "SELECT COUNT(*) from flacs WHERE toencode";
const CHECK_FILE: &str = "SELECT exists(SELECT 1 FROM flacs WHERE path = ?1)";
@@ -20,20 +19,25 @@ const DEDUPE_DB: &str =
"DELETE FROM flacs WHERE rowid NOT IN (SELECT MAX(rowid) FROM flacs GROUP BY path)";
const GET_MODTIME: &str = "SELECT modtime FROM flacs WHERE path = ?1";
-pub(crate) fn init_connection(path: Option<&PathBuf>) -> Result<Connection> {
- let conn = if let Some(file) = path {
- Connection::open(file)?
+pub(crate) async fn init_db(path: Option<&PathBuf>) -> Result<turso::Database> {
+ let db = if let Some(file) = path {
+ turso::Builder::new_local(file.to_str().unwrap())
+ .build()
+ .await?
} else if let Some(base_dir) = BaseDirs::new() {
- let file = Path::new(base_dir.data_dir()).join("reencoder.db");
- Connection::open(file)?
+ let file = base_dir.data_dir().join("reencoder.db");
+ turso::Builder::new_local(file.to_str().unwrap())
+ .build()
+ .await?
} else {
return Err(anyhow!("Failed to locate data directory"));
};
- conn.execute(TABLE_CREATE, ())?;
- Ok(conn)
+ let conn = db.connect()?;
+ conn.execute(TABLE_CREATE, ()).await?;
+ Ok(db)
}
-pub(crate) fn insert_file(conn: &Connection, filename: &Path) -> Result<()> {
+pub(crate) async fn insert_file(conn: &Connection, filename: &Path) -> Result<()> {
let toencode = !matches!(get_vendor(filename)?.as_str(), CURRENT_VENDOR);
let modtime = filename
@@ -43,14 +47,15 @@ pub(crate) fn insert_file(conn: &Connection, filename: &Path) -> Result<()> {
.as_secs();
conn.execute(
- ADD_ITEM,
+ ADD_FILE,
params![filename.to_str().unwrap(), toencode, modtime],
- )?;
+ )
+ .await?;
Ok(())
}
-pub(crate) fn update_file(conn: &Connection, filename: &Path) -> Result<()> {
+pub(crate) async fn update_file(conn: &Connection, filename: &Path) -> Result<()> {
let modtime = filename
.metadata()?
.modified()?
@@ -58,70 +63,74 @@ pub(crate) fn update_file(conn: &Connection, filename: &Path) -> Result<()> {
.as_secs();
conn.execute(
- UPDATE_ITEM,
+ UPDATE_FILE,
params![filename.to_str().unwrap(), false, modtime],
- )?;
+ )
+ .await?;
Ok(())
}
-pub(crate) fn check_file(conn: &Connection, filename: &Path) -> Result<bool> {
- if conn.query_one(CHECK_FILE, params!(filename.to_str().unwrap()), |row| {
- let num: bool = row.get(0)?;
- Ok(num)
- })? {
- Ok(true)
- } else {
- Ok(false)
- }
+pub(crate) async fn check_file(conn: &Connection, filename: &Path) -> Result<bool> {
+ Ok(conn
+ .query(CHECK_FILE, params!(filename.to_str().unwrap()))
+ .await?
+ .next()
+ .await?
+ .unwrap()
+ .get::<bool>(0)?)
}
-pub(crate) fn init_clean_files(conn: &Connection) -> Result<Vec<PathBuf>, rusqlite::Error> {
- conn.execute(DEDUPE_DB, ())?;
- let mut stmt = conn.prepare(FETCH_FILES)?;
- let mut rows = stmt.query(())?;
+pub(crate) async fn init_clean_files(conn: &Connection) -> Result<Vec<PathBuf>, turso::Error> {
+ conn.execute(DEDUPE_DB, ()).await?;
+ let mut rows = conn.query(FETCH_FILES, ()).await?;
let mut files = Vec::new();
- while let Ok(Some(row)) = rows.next() {
+ while let Ok(Some(row)) = rows.next().await {
let path: String = row.get(0)?;
files.push(PathBuf::from(path));
}
+
Ok(files)
}
-pub(crate) fn remove_file(conn: &Connection, filename: &Path) -> Result<()> {
- conn.execute(REMOVE_FILE, params!(filename.to_str().unwrap()))?;
+pub(crate) async fn remove_file(conn: &Connection, filename: &Path) -> Result<()> {
+ conn.execute(REMOVE_FILE, params!(filename.to_str().unwrap()))
+ .await?;
Ok(())
}
-pub(crate) fn get_toencode_files(conn: &Connection) -> Result<Vec<PathBuf>, rusqlite::Error> {
- let mut stmt = conn.prepare(TOENCODE_PATHS)?;
- let mut rows = stmt.query(())?;
+pub(crate) async fn get_toencode_files(conn: &Connection) -> Result<Vec<PathBuf>, turso::Error> {
+ let mut rows = conn.query(TOENCODE_PATHS, ()).await?;
let mut files: Vec<PathBuf> = Vec::new();
- while let Ok(Some(row)) = rows.next() {
+ while let Ok(Some(row)) = rows.next().await {
let path: String = row.get(0)?;
files.push(PathBuf::from(path));
}
+
Ok(files)
}
-pub(crate) fn get_toencode_number(conn: &Connection) -> Result<u64, rusqlite::Error> {
- conn.query_one(TOENCODE_NUMBER, (), |row| {
- let num: u64 = row.get(0)?;
- Ok(num)
- })
+pub(crate) async fn get_toencode_number(conn: &Connection) -> Result<u64, turso::Error> {
+ conn.query(TOENCODE_NUMBER, ())
+ .await?
+ .next()
+ .await?
+ .unwrap()
+ .get::<u64>(0)
}
-pub(crate) fn get_modtime(conn: &Connection, file: &Path) -> Result<u64> {
- Ok(
- conn.query_one(GET_MODTIME, params![file.to_str().unwrap()], |row| {
- let modtime: u64 = row.get(0)?;
- Ok(modtime)
- })?,
- )
+pub(crate) async fn get_modtime(conn: &Connection, file: &Path) -> Result<u64> {
+ Ok(conn
+ .query(GET_MODTIME, params![file.to_str().unwrap()])
+ .await?
+ .next()
+ .await?
+ .unwrap()
+ .get::<u64>(0)?)
}
-pub(crate) fn vacuum(conn: &Connection) -> Result<()> {
- conn.execute("VACUUM", ())?;
+pub(crate) async fn vacuum(conn: &Connection) -> Result<()> {
+ conn.execute("VACUUM", ()).await?;
Ok(())
}
@@ -129,9 +138,11 @@ pub(crate) fn vacuum(conn: &Connection) -> Result<()> {
mod tests {
use super::*;
+ use macro_rules_attribute::apply;
+ use smol_macros::{Executor, test};
- #[test]
- fn check_localfiles() {
+ #[apply(test!)]
+ async fn check_localfiles(ex: &Executor<'_>) {
let dbname = PathBuf::from("temp1.db");
let filenames = [
"./samples/16bit.flac",
@@ -139,61 +150,69 @@ mod tests {
"./samples/32bit.flac",
];
let mut counter = 0;
- let conn = init_connection(Some(&dbname)).unwrap();
- for file in filenames {
- let filename = PathBuf::from(file);
- insert_file(&conn, &filename).unwrap();
- }
- let mut stmt = conn.prepare(TOENCODE_PATHS).unwrap();
- let mut returned = stmt.query(()).unwrap();
-
- while let Ok(Some(_)) = returned.next() {
- counter += 1
- }
- std::fs::remove_file(dbname).unwrap();
- assert!(counter == 0)
+ ex.spawn(async move {
+ let db = init_db(Some(&dbname)).await.unwrap();
+ let conn = db.connect().unwrap();
+ for file in filenames {
+ let path = PathBuf::from(file);
+ insert_file(&conn, &path).await.unwrap();
+ }
+ let mut returned = conn.query(TOENCODE_PATHS, ()).await.unwrap();
+ while let Ok(Some(_)) = returned.next().await {
+ counter += 1
+ }
+ std::fs::remove_file(dbname).unwrap();
+ assert!(counter == 0)
+ })
+ .await;
}
- #[test]
- fn check_update() {
+ #[apply(test!)]
+ async fn check_update(ex: &Executor<'_>) {
let dbname = PathBuf::from("temp2.db");
let filenames = [
"./samples/16bit.flac",
"./samples/24bit.flac",
"./samples/32bit.flac",
];
- let conn = init_connection(Some(&dbname)).unwrap();
- for file in filenames {
- insert_file(&conn, &Path::new(file).canonicalize().unwrap()).unwrap();
- }
-
- conn.execute(
- UPDATE_ITEM,
- params![
- Path::new("./samples/16bit.flac")
- .canonicalize()
- .unwrap()
- .to_str()
- .unwrap(),
- true,
- ""
- ],
- )
- .unwrap();
+ ex.spawn(async move {
+ let db = init_db(Some(&dbname)).await.unwrap();
+ let conn = db.connect().unwrap();
+ for file in filenames {
+ insert_file(&conn, &Path::new(file).canonicalize().unwrap())
+ .await
+ .unwrap();
+ }
+ conn.execute(
+ UPDATE_FILE,
+ params![
+ Path::new("./samples/16bit.flac")
+ .canonicalize()
+ .unwrap()
+ .to_str()
+ .unwrap(),
+ true,
+ ""
+ ],
+ )
+ .await
+ .unwrap();
- update_file(
- &conn,
- &Path::new("./samples/16bit.flac").canonicalize().unwrap(),
- )
- .unwrap();
+ update_file(
+ &conn,
+ &Path::new("./samples/16bit.flac").canonicalize().unwrap(),
+ )
+ .await
+ .unwrap();
- let mut stmt = conn.prepare(TOENCODE_PATHS).unwrap();
- let mut returned = stmt.query(()).unwrap();
- let mut counter = 0;
- while let Ok(Some(_)) = returned.next() {
- counter += 1
- }
- std::fs::remove_file(dbname).unwrap();
- assert!(counter == 0)
+ let mut returned = conn.query(TOENCODE_PATHS, ()).await.unwrap();
+ let mut counter = 0;
+ while let Ok(Some(_)) = returned.next().await {
+ counter += 1
+ }
+ std::fs::remove_file(dbname).unwrap();
+ assert!(counter == 0)
+ })
+ .await;
}
}
diff --git a/src/files.rs b/src/files.rs
index 63c87fa..e3a31ca 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -3,19 +3,19 @@ use crate::flac::handle_encode;
use anyhow::{Result, anyhow};
#[cfg(not(test))]
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
-use rusqlite::Connection;
use std::{
error::Error,
fmt::Display,
path::{Path, PathBuf},
sync::{
- Arc, Mutex,
+ Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc,
},
thread::{self, sleep},
time::{Duration, UNIX_EPOCH},
};
+use turso::Connection;
use walkdir::WalkDir;
#[cfg(not(test))]
@@ -51,26 +51,26 @@ impl Display for FileError {
impl Error for FileError {}
-fn handle_file(file: &Path, conn: &Connection) -> Result<()> {
- if db::check_file(conn, file)? {
+async fn handle_file(file: &Path, conn: &Connection) -> Result<()> {
+ if db::check_file(conn, file).await? {
let modtime = file
.metadata()?
.modified()?
.duration_since(UNIX_EPOCH)?
.as_secs();
- let db_modtime = db::get_modtime(conn, file)?;
+ let db_modtime = db::get_modtime(conn, file).await?;
if modtime != db_modtime {
- db::update_file(conn, file)?;
+ db::update_file(conn, file).await?;
}
return Ok(());
}
- db::insert_file(conn, file)?;
+ db::insert_file(conn, file).await?;
Ok(())
}
-pub(crate) fn index_files_recursively(
+pub(crate) async fn index_files_recursively(
path: &Path,
conn: &Connection,
handler: Arc<AtomicBool>,
@@ -92,6 +92,8 @@ pub(crate) fn index_files_recursively(
let newhandler = handler.clone();
+ let newhandler = handler.clone();
+
#[allow(unused_variables)]
s.spawn(move || {
for entry in WalkDir::new(&abspath) {
@@ -108,8 +110,6 @@ pub(crate) fn index_files_recursively(
let _ = filesend.send(path.to_owned());
#[cfg(not(test))]
newbar.inc_length(1);
- }
- }
} else {
break;
}
@@ -120,7 +120,7 @@ pub(crate) fn index_files_recursively(
&& handler.load(Ordering::SeqCst)
{
#[allow(unused_variables)]
- if let Err(error) = handle_file(&path, conn) {
+ if let Err(error) = smol::block_on( async {handle_file(&path, conn)}).await {
#[cfg(not(test))]
bar.println(format!("{}", FileError::new(&path, error)));
} else {
@@ -141,22 +141,20 @@ pub(crate) fn index_files_recursively(
Ok(())
}
-pub(crate) fn reencode_files(
- conn: Connection,
+pub async fn reencode_files(
+ conn: &Connection,
handler: Arc<AtomicBool>,
threads: usize,
) -> Result<()> {
#[cfg(not(test))]
let bar = ProgressBar::with_draw_target(
- Some(db::get_toencode_number(&conn)?),
+ Some(db::get_toencode_number(conn).await?),
ProgressDrawTarget::stdout_with_hz(60),
)
.with_style(ProgressStyle::with_template(BAR_TEMPLATE)?.progress_chars("#>-"))
.with_message("Reencoding");
- let mut files = db::get_toencode_files(&conn)?.into_iter();
-
- let lock = Arc::new(Mutex::new(conn));
+ let mut files = db::get_toencode_files(conn).await?.into_iter();
let thread_counter = Arc::new(AtomicUsize::new(0));
@@ -177,17 +175,19 @@ pub(crate) fn reencode_files(
thread_counter.fetch_add(1, Ordering::Relaxed);
let handler = handler.clone();
- let lock = lock.clone();
#[cfg(not(test))]
let bar = bar.clone();
let thread_counter = thread_counter.clone();
+ let conn = conn.clone();
s.spawn(move || {
match handle_encode(&file, handler) {
Err(error) => eprintln!("{}", FileError::new(&file, error)),
Ok(false) => {
- if let Err(error) = db::update_file(&lock.lock().unwrap(), &file) {
- eprintln!("{}", FileError::new(&file, error));
+ if let Err(error) =
+ smol::block_on(async { db::update_file(&conn, &file).await })
+ {
+ eprintln!("{}", FileError::new(&file, error))
}
#[cfg(not(test))]
bar.inc(1)
@@ -210,8 +210,8 @@ pub(crate) fn reencode_files(
Ok(())
}
-pub(crate) fn clean_files(conn: &Connection, handler: Arc<AtomicBool>) -> Result<()> {
- let files = db::init_clean_files(conn)?;
+pub async fn clean_files(conn: &Connection, handler: Arc<AtomicBool>) -> Result<()> {
+ let files = db::init_clean_files(conn).await?;
#[cfg(not(test))]
let spinner = ProgressBar::with_draw_target(None, ProgressDrawTarget::stdout_with_hz(60))
@@ -222,17 +222,20 @@ pub(crate) fn clean_files(conn: &Connection, handler: Arc<AtomicBool>) -> Result
files.iter().for_each(|file| {
#[allow(clippy::collapsible_if)]
if handler.load(Ordering::SeqCst) && !file.exists() {
- if let Err(error) = db::remove_file(conn, file) {
+ #[cfg(not(test))]
+ let spinner = spinner.clone();
+ if let Err(error) = smol::block_on(async { db::remove_file(conn, file).await }) {
eprintln!("{}", FileError::new(file, error))
};
#[cfg(not(test))]
spinner.inc(1);
}
});
+
#[cfg(not(test))]
spinner.finish();
- db::vacuum(conn)?;
+ db::vacuum(conn).await?;
Ok(())
}
@@ -240,52 +243,65 @@ pub(crate) fn clean_files(conn: &Connection, handler: Arc<AtomicBool>) -> Result
#[cfg(test)]
mod tests {
use super::*;
+ use macro_rules_attribute::apply;
+ use smol_macros::{Executor, test};
- #[test]
- fn test_index_lots_of_files() {
+ #[apply(test!)]
+ async fn test_index_lots_of_files(ex: &Executor<'_>) {
let dbname = PathBuf::from("temp3.db");
let handler = Arc::new(AtomicBool::new(true));
- let conn = db::init_connection(Some(&dbname)).unwrap();
- index_files_recursively(Path::new("./testfiles"), &conn, handler).unwrap();
- std::fs::remove_file(dbname).unwrap();
+ ex.spawn(async {
+ let db = db::init_db(Some(&dbname)).await.unwrap();
+ let conn = db.connect().unwrap();
+ index_files_recursively(Path::new("./testfiles"), &conn, handler).unwrap();
+ std::fs::remove_file(dbname).unwrap();
+ })
+ .await
}
- #[test]
- fn test_clean_files() {
+ #[apply(test!)]
+ async fn test_clean_files(ex: &Executor<'_>) {
let dbname = PathBuf::from("temp4.db");
let handler = Arc::new(AtomicBool::new(true));
- let conn = db::init_connection(Some(&dbname)).unwrap();
- let filenames = [
- "./samples/16bit.flac",
- "./samples/24bit.flac",
- "./samples/32bit.flac",
- "./samples/nonexisting.flac",
- ];
- std::fs::copy("./samples/32bit.flac", "./samples/nonexisting.flac").unwrap();
- for file in filenames {
- let filename = PathBuf::from(file);
- db::insert_file(&conn, &filename).unwrap();
- }
+ ex.spawn(async {
+ let db = db::init_db(Some(&dbname)).await.unwrap();
+ let conn = db.connect().unwrap();
+ let filenames = [
+ "./samples/16bit.flac",
+ "./samples/24bit.flac",
+ "./samples/32bit.flac",
+ "./samples/nonexisting.flac",
+ ];
+ std::fs::copy("./samples/32bit.flac", "./samples/nonexisting.flac").unwrap();
+ for file in filenames {
+ let filename = PathBuf::from(file);
+ db::insert_file(&conn, &filename).await.unwrap();
+ }
- std::fs::remove_file("./samples/nonexisting.flac").unwrap();
+ std::fs::remove_file("./samples/nonexisting.flac").unwrap();
- clean_files(&conn, handler).unwrap();
- let counter = db::init_clean_files(&conn).unwrap().len();
- std::fs::remove_file(dbname).unwrap();
- assert!(counter == 3)
+ clean_files(&conn, handler).await.unwrap();
+ let counter = db::init_clean_files(&conn).await.unwrap().len();
+ std::fs::remove_file(dbname).unwrap();
+ assert!(counter == 3)
+ })
+ .await;
}
- #[test]
- fn test_reencode_lots_of_files() {
+ #[apply(test!)]
+ async fn test_reencode_lots_of_files(ex: &Executor<'_>) {
let dbname = PathBuf::from("temp5.db");
let handler = Arc::new(AtomicBool::new(true));
- let conn = db::init_connection(Some(&dbname)).unwrap();
- let temp = handler.clone();
- index_files_recursively(Path::new("./testfiles"), &conn, temp).unwrap();
- println!("\n{}", db::get_toencode_number(&conn).unwrap());
- reencode_files(conn, handler, 4).unwrap();
- let conn = db::init_connection(Some(&dbname)).unwrap();
- println!("\n{}", db::get_toencode_number(&conn).unwrap());
- std::fs::remove_file(dbname).unwrap();
+ ex.spawn(async {
+ let db = db::init_db(Some(&dbname)).await.unwrap();
+ let conn = db.connect().unwrap();
+ let temp = handler.clone();
+ index_files_recursively(Path::new("./testfiles"), &conn, temp).unwrap();
+ println!("\n{}", db::get_toencode_number(&conn).await.unwrap());
+ reencode_files(&conn, handler, 4).await.unwrap();
+ println!("\n{}", db::get_toencode_number(&conn).await.unwrap());
+ std::fs::remove_file(dbname).unwrap();
+ })
+ .await;
}
}
diff --git a/src/main.rs b/src/main.rs
index de3da62..e9543f8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,6 @@
-mod db;
-mod files;
-mod flac;
+pub(crate) mod db;
+pub(crate) mod files;
+pub(crate) mod flac;
use anyhow::Result;
use clap::{Arg, ArgAction, Command, ValueHint, command, value_parser};
use clap_complete::{Generator, Shell, generate};
@@ -90,30 +90,32 @@ fn main() -> Result<()> {
r.store(false, Ordering::SeqCst);
})?;
- let conn = db::init_connection(args.get_one::<PathBuf>("db"))?;
+ smol::block_on(async move {
+ let path = args.get_one::<PathBuf>("db");
+ let db = db::init_db(path).await?;
- let path = args.get_one::<PathBuf>("path");
+ if path.is_none() && !args.get_flag("clean") && !args.get_flag("doit") {
+ let count = db::get_toencode_number(&db.connect()?).await?;
+ println!("Files to reencode:\t{}", style(count).green());
+ return Ok(());
+ }
- if path.is_none() && !args.get_flag("clean") && !args.get_flag("doit") {
- let count = db::get_toencode_number(&conn)?;
- println!("Files to reencode:\t{}", style(count).green());
- return Ok(());
- }
+ if let Some(realpath) = path {
+ let hanlder = running.clone();
+ files::index_files_recursively(realpath, &db.connect()?, hanlder)?;
+ }
- if let Some(realpath) = path {
- let hanlder = running.clone();
- files::index_files_recursively(realpath, &conn, hanlder)?;
- }
+ if args.get_flag("clean") {
+ let handler = running.clone();
+ files::clean_files(&db.connect()?, handler).await?;
+ }
- if args.get_flag("clean") {
- let handler = running.clone();
- files::clean_files(&conn, handler)?;
- }
+ if args.get_flag("doit") {
+ let hanlder = running.clone();
+ let threads = *args.get_one::<usize>("threads").unwrap();
+ files::reencode_files(&db.connect()?, hanlder, threads).await?;
+ }
- if args.get_flag("doit") {
- let hanlder = running.clone();
- let threads = *args.get_one::<usize>("threads").unwrap();
- files::reencode_files(conn, hanlder, threads)?;
- }
- Ok::<(), anyhow::Error>(())
+ Ok::<(), anyhow::Error>(())
+ })
}