···5555lofty = "0.22.4"
5656gtk4 = { version = "0.10.3", features = ["v4_20"] }
5757libadwaita = { version = "0.8.1", features = ["v1_6"] }
5858+async-trait = "0.1.89"
58595960# Re-enable those and compile with "gui" feature when you're ready
6061# to tackle GUI again.
+3-3
src/cmd/default_filter.rhai
···11/*
22Hi!
3344-This is the filter file, in here you can write Rhai code to filter out tracks
44+This is the filter file, in here you can write Rhai code to filter out tracks
55during the sync process.
6677-This file must contain the `filter(track)` function, which will be evaluated for
77+This file must contain the `filter(track)` function, which will be evaluated for
88every candidate copy track: return `true` to copy the track.
991010If you want to match any String to a regex, call `regex_match(pattern, your_string)`.
···1515fn filter(track) {
1616 // add your filtering code here!
17171818- true
1818+ false
1919}
+5-21
src/cmd/filter.rs
···11use crate::{cmd::error, db, filter};
22-use anyhow::{anyhow, Context, Result};
22+use anyhow::{Context, Result, anyhow};
33use clap::Args as ClapArgs;
4455const DEFAULT_FILTER: &'static str = include_str!("default_filter.rhai");
6677#[derive(ClapArgs)]
88pub struct Args {
99- /// Path where tunz database is stored.
1010- #[arg(long)]
1111- pub destination: Option<String>,
99+ /// Directory in which tunz will store its local database.
1010+ #[arg(short, long, default_value_t = db::default_database_dir().to_str().unwrap().to_owned())]
1111+ pub database_path: String,
12121313 /// Read existing filters off the database.
1414 #[arg(long)]
···2121 pub file: Option<String>,
2222}
23232424-impl Args {
2525- pub fn validate(&self) -> Result<()> {
2626- if let None = self.destination {
2727- return Err(anyhow!(error::Error::ValidationError(
2828- "missing destination".to_owned(),
2929- )));
3030- };
3131-3232- Ok(())
3333- }
3434-}
3535-3624pub async fn run(args: Args) -> Result<()> {
3737- args.validate()?;
3838-3939- let destination = args.destination.unwrap();
4040-4141- let dest_db = db::Instance::new(&destination, true).await?;
2525+ let dest_db = db::Instance::new(&args.database_path, false).await?;
42264327 let existing_filter = dest_db
4428 .filter()
+34-91
src/cmd/sync.rs
···11+use std::path::PathBuf;
22+use std::str::FromStr;
33+14use crate::cmd::*;
25use crate::db;
66+use crate::device;
77+use crate::model;
38use crate::sync;
99+use crate::sync::filter_tracks;
410use anyhow::Ok;
511use anyhow::anyhow;
612use anyhow::{Context, Result};
···1925 /// Do not delete from destination tracks that are not contained in the local database instance.
2026 #[arg(long, default_value_t = false)]
2127 pub no_delete: bool,
2222-2323- /// Do not attempt to copy any file or write any change on destination database, just
2424- /// print what tracks would be copied over.
2525- #[arg(long, default_value_t = false)]
2626- pub dry_run: bool,
27282829 /// Amount of threads to use for copy and transcoding, defaults to number of CPUs
2930 /// available.
···6263 };
63646465 let dest_dir = args.destination.unwrap();
6666+ let dest_path = PathBuf::from_str(&dest_dir).unwrap();
6767+6868+ let dest = match args.ipod {
6969+ true => device::IPod::new(dest_path).await?,
7070+ false => device::Disk::new(dest_path).await?,
7171+ };
65726673 let local_db = db::Instance::new(&args.database_path, false)
6774 .await
6875 .with_context(|| "Cannot open local database instance")?;
69767070- let dest_db = db::Instance::new(&dest_dir.clone(), true)
7171- .await
7272- .with_context(|| "Cannot open destination database instance")?;
7373-7474- let diff = db::diff(&local_db, &dest_db)
7575- .await
7676- .with_context(|| "Cannot calculate difference between local and destination databases")?;
7777-7878- let mut reverse_diff = db::diff(&dest_db, &local_db)
7979- .await
8080- .with_context(|| "Cannot calculate difference between destination and local databases")?;
8181-8282- let raw_filter = local_db
7777+ let filters = match local_db
8378 .filter()
8479 .await
8585- .with_context(|| "Could not fetch filters.")?;
8686-8787- let filters = match raw_filter {
8080+ .with_context(|| "Could not fetch filters.")?
8181+ {
8882 Some(raw_filter) => Some(
8983 crate::filter::evaluate(vec![raw_filter])
9084 .with_context(|| "Could not evaluate filters")?,
···9286 None => None,
9387 };
94889595- // find any filtered tracks that were already copied
9696- reverse_diff.append(
9797- &mut sync::diff_databases(&local_db, &dest_db, filters.as_ref(), false).await?,
9898- );
8989+ let local_tracks = local_db.tracks_by_state(model::FileState::Copied).await?;
9990100100- // now filter out all tracks to copy by using the filters
101101- let diff = sync::filter_tracks_by_id(filters.as_ref(), &local_db, diff).await?;
9191+ // filter out tracks that we should ignore
9292+ let local_tracks = filter_tracks(local_tracks, filters.as_ref(), !args.no_delete)?;
9393+9494+ let dest_tracks = dest.list_by_state(model::FileState::Copied).await?;
9595+9696+ // tracks that are on local but not on dest
9797+ let diff = db::diff_vec(local_tracks.clone(), dest_tracks.clone())
9898+ .await
9999+ .with_context(|| "Cannot calculate difference between local and destination databases")?;
102100103103- if args.dry_run {
104104- dry_run_copy(&local_db, &dest_dir, diff, filters.as_ref()).await?;
105105- dry_run_delete(&dest_db, &dest_dir, reverse_diff, filters.as_ref()).await?;
106106- return Ok(());
107107- }
101101+ // tracks that are on dest but not on local
102102+ let reverse_diff = db::diff_vec(dest_tracks.clone(), local_tracks.clone())
103103+ .await
104104+ .with_context(|| "Cannot calculate difference between destination and local databases")?;
108105109106 if !args.no_delete {
110110- sync::run_delete(&dest_db, &dest_dir, reverse_diff, filters.as_ref()).await?
107107+ for track in reverse_diff {
108108+ log::info!("deleting {}", track);
109109+ dest.delete(track).await?;
110110+ }
111111 }
112112113113 log::info!("using {} threads", threads);
114114115115- sync::run_copy(
116116- &local_db,
117117- &dest_db,
118118- &dest_dir,
119119- diff,
120120- filters.as_ref(),
121121- threads,
122122- args.ipod,
123123- )
124124- .await?;
125125-126126- Ok(())
127127-}
128128-129129-async fn dry_run_copy(
130130- local_db: &db::Instance,
131131- dest_dir: &String,
132132- diff: Vec<String>,
133133- filters: Option<&Vec<crate::filter::ScriptRuntime>>,
134134-) -> Result<()> {
135135- let tracks = sync::filter_tracks(
136136- local_db
137137- .tracks_by_id(diff)
138138- .await
139139- .with_context(|| "Cannot get tracks from local database")?,
140140- filters,
141141- false,
142142- )?;
143143-144144- for track in tracks {
145145- let track_storage_path = track.storage_path(&dest_dir);
146146-147147- log::info!("Will copy {} to {}", track.file_path, track_storage_path);
148148- }
115115+ sync::run_copy(dest, diff, threads).await?;
149116150117 Ok(())
151118}
152152-153153-async fn dry_run_delete(
154154- dest_db: &db::Instance,
155155- dest_dir: &String,
156156- diff: Vec<String>,
157157- filters: Option<&Vec<crate::filter::ScriptRuntime>>,
158158-) -> Result<()> {
159159- let tracks = sync::filter_tracks(
160160- dest_db
161161- .tracks_by_id(diff)
162162- .await
163163- .with_context(|| "Cannot get tracks from destination database")?,
164164- filters,
165165- true,
166166- )?;
167167-168168- for track in tracks {
169169- let track_storage_path = track.storage_path(&dest_dir);
170170-171171- log::info!("Will delete {}", track_storage_path)
172172- }
173173-174174- Ok(())
175175-}