···55description = "A tool to synchronize a music library to someplace else."
66license-file = "LICENSE"
7788+[features]
99+gui = []
1010+811[profile.release]
912strip = true # Automatically strip symbols from the binary.
1013opt-level = "z" # Optimize for size.
···5053infer = "0.19.0"
5154sysinfo = "0.37.2"
5255lofty = "0.22.4"
5353-gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "1a19df56caeb5db8520d478727a19c33264370f0" }
5454-gpui-component-assets = { git = "https://github.com/longbridge/gpui-component", rev = "1a19df56caeb5db8520d478727a19c33264370f0" }
5555-gpui = { git = "https://github.com/zed-industries/zed", rev = "b4d1ba7a" }
56565757+# Re-enable those and compile with "gui" feature when you're ready
5858+# to tackle GUI again.
5959+#
6060+# Removed because they make compiling slow asf.
6161+#
6262+# gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "1a19df56caeb5db8520d478727a19c33264370f0" }
6363+# gpui-component-assets = { git = "https://github.com/longbridge/gpui-component", rev = "1a19df56caeb5db8520d478727a19c33264370f0" }
6464+# gpui = { git = "https://github.com/zed-industries/zed", rev = "b4d1ba7a" }
57655858-[patch."git+https://github.com/zed-industries/zed"]
5959-gpui = { git = "https://github.com/zed-industries/zed?rev=b4d1ba7a" }
6666+6767+# [patch."git+https://github.com/zed-industries/zed"]
6868+# gpui = { git = "https://github.com/zed-industries/zed?rev=b4d1ba7a" }
+1-1
src/cmd/add.rs
···159159 let mut duplicate = 0;
160160161161 while let Ok(p) = paths.recv().await {
162162- let p = p?.clone();
162162+ let p = p.context(format!("yolo"))?.clone();
163163164164 let dc = dupe_checker.clone();
165165 if dc(&p, db)? {
+65-81
src/cmd/sync.rs
···1212use futures::TryStreamExt;
1313use futures::stream;
1414use std::collections::hash_set;
1515-use std::str::FromStr;
1515+use std::path::PathBuf;
1616+use std::sync::Arc;
1717+use std::sync::Mutex;
16181719#[derive(ClapArgs)]
1820pub struct Args {
···3335 #[arg(long, default_value_t = false)]
3436 pub dry_run: bool,
35373636- /// Instead of copying the files over to the specified destination, create an hardlink.
3737- #[arg(long, default_value_t = false)]
3838- pub link: bool,
3939-4038 /// Amount of threads to use for copy and transcoding, defaults to number of CPUs
4139 /// available.
4240 #[arg(long)]
···125123 &dest_dir,
126124 diff,
127125 filters.as_ref(),
128128- args.link,
129126 threads,
130127 args.ipod,
131128 )
···221218 dest_dir: &String,
222219 diff: Vec<String>,
223220 filters: Option<&Vec<crate::filter::ScriptRuntime>>,
224224- link: bool,
225221 threads: usize,
226222 ipod: bool,
227223) -> Result<()> {
···237233 )?;
238234239235 if ipod {
240240- return copy_ipod(dest_db, dest_dir, link, threads, tracks).await;
236236+ return copy_ipod(dest_db, dest_dir, threads, tracks).await;
241237 }
242238243239 stream::iter(tracks)
244240 .map(|track| {
245241 let trs = tr_settings.clone();
246246- async move { copy(&track, dest_db, dest_dir, trs, link).await }
242242+ let dest_path: PathBuf = track.storage_path(dest_dir).into();
243243+ async move { copy(&track, dest_db, dest_path, trs).await }
247244 })
248245 .buffer_unordered(threads)
249246 .try_for_each(|_| async move { Ok(()) })
···253250async fn copy_ipod(
254251 dest_db: &db::Instance,
255252 dest_dir: &String,
256256- link: bool,
257253 threads: usize,
258254 tracks: Vec<model::Track>,
259255) -> Result<()> {
260260- // copy single-threaded as iPods update the database when a file is added
261261- // on top of that, all tracks will be re-encoded to AAC 256kbit/s VBR.
262262- //
263263- // we encode in a temp directory, then copy the file over
264264-265265- let td = tempdir::TempDir::new("tunz_ipod_copy")?;
266266- let td = td.path().to_string_lossy().to_string();
267267-268256 let ipod_manager = gpod::Manager::new(dest_dir.clone())?;
269257270258 let transcoding = Some(ffmpeg::Quality {
···276264 let converted: Vec<_> = stream::iter(tracks)
277265 .map(|track| {
278266 let trs = transcoding.clone();
279279- let dest_dir = td.clone();
267267+ let ipm = ipod_manager.clone();
268268+280269 async move {
281281- copy(&track, dest_db, &dest_dir, trs, link).await?;
270270+ let mut fp: PathBuf = track.file_path.clone().into();
271271+ match track.is_lossless() {
272272+ true => {
273273+ fp.set_extension(ffmpeg::Codec::AAC.extension());
274274+ }
275275+ false => (),
276276+ }
277277+ let dest_path = ipm.path_for_file(fp.as_path()).unwrap();
278278+ let copied_track = copy(&track, dest_db, dest_path.clone(), trs).await?;
282279283283- let tr_track_path = std::path::PathBuf::from(
284284- &track.storage_path_with_extension(&dest_dir, &ffmpeg::Codec::AAC.extension()),
285285- );
286286-287287- Ok((track.clone(), tr_track_path))
280280+ Ok((copied_track, dest_path))
288281 }
289282 })
290283 .buffer_unordered(threads)
···293286294287 for (track, path) in converted {
295288 log::info!("copying {} to iPod...", track);
296296- ipod_manager.copy(
297297- &track,
298298- path.clone(),
299299- ffmpeg::Codec::AAC.extension(),
300300- 44100,
301301- 256,
302302- )?;
303303- std::fs::remove_file(path)?;
289289+ ipod_manager.copy(&track.clone(), path.clone(), track.extension, 44100, 256)?;
304290 }
305291306292 return Ok(());
···395381async fn copy(
396382 track: &model::Track,
397383 dest_db: &db::Instance,
398398- dest_dir: &String,
384384+ dest_fname: PathBuf,
399385 transcoding_settings: Option<ffmpeg::Quality>,
400400- link: bool,
401401-) -> Result<()> {
402402- let track_storage_path = track.storage_path(&dest_dir);
403403- let sp = std::path::Path::new(&track_storage_path);
386386+) -> Result<model::Track> {
387387+ // We replicate src database on dest 1:1
388388+ // We should create the same logical database (tracks with the same tags have the same hash)
389389+ // ignoring the format.
390390+ // When the copy is done we should return the newly-created track.
391391+392392+ let is_lossless = track.is_lossless();
393393+394394+ let sp = std::path::Path::new(&dest_fname);
404395405396 let parent = sp
406397 .parent()
407398 .with_context(|| "Cannot obtain base destination directory")?;
408399409409- // step 1: add an in-flight copy to the destination database
410400 let mut dest_track = track.clone();
411401 dest_track.file_state = crate::model::FileState::Copying;
412402 dest_db
···414404 .await
415405 .with_context(|| "Cannot insert in-progress copying track in destination database")?;
416406417417- // step 2: actually copy the track
418407 std::fs::create_dir_all(parent).with_context(|| {
419408 format!(
420409 "Cannot create destination directory tree {}",
···422411 )
423412 })?;
424413414414+ let track_dest_path = if let Some(ref ts) = transcoding_settings
415415+ && is_lossless
416416+ {
417417+ let mut dest_fname = dest_fname.clone();
418418+ dest_fname.set_extension(ts.codec.extension());
419419+ dest_fname
420420+ } else {
421421+ dest_fname
422422+ };
423423+425424 let opts = CopyOptions::new().overwrite(true);
426425427426 async_std::task::spawn_blocking({
428427 let track_for_thread = track.clone();
429429- let dest_dir_for_thread = dest_dir.clone();
430428 let ts_for_thread = transcoding_settings.clone();
431429432430 move || {
433431 if let Some(ts) = ts_for_thread
434434- && track_for_thread.is_lossless()
432432+ && is_lossless
435433 {
436436- log::info!("transcoding {} ({})", track_for_thread, ts);
434434+ log::info!("transcoding & copying {} ({})", track_for_thread, ts);
435435+ log::debug!("transcoding path: {}", track_dest_path.display());
436436+437437+ ffmpeg::convert(&track_for_thread, track_dest_path, &ts)?;
438438+ return Ok(());
439439+ }
437440438438- ffmpeg::convert(
439439- &track_for_thread,
440440- std::path::PathBuf::from_str(
441441- &track_for_thread.storage_path_with_extension(
442442- &dest_dir_for_thread,
443443- &ts.codec.extension(),
444444- ),
445445- )
446446- .unwrap(),
447447- &ts,
448448- )?;
449449- } else {
450450- log::info!("copying {}", track_for_thread);
441441+ log::info!("copying {}", track_for_thread);
442442+ log::debug!("YOOO {}", track_dest_path.display());
451443452452- if link {
453453- std::fs::hard_link(
454454- track_for_thread.file_path.clone(),
455455- track_storage_path.clone(),
456456- )?;
457457- } else {
458458- match copy_with_progress(
459459- track_for_thread.file_path.clone(),
460460- track_storage_path.clone(),
461461- &opts,
462462- |_| {},
463463- ) {
464464- std::result::Result::Ok(_) => {}
465465- Err(err) => {
466466- return Err(error::Error::CopyError(err)).with_context(|| {
467467- format!(
468468- "Cannot copy {} to {}",
469469- track_for_thread.file_path, track_storage_path
470470- )
471471- });
472472- }
473473- };
444444+ match copy_with_progress(
445445+ track_for_thread.file_path.clone(),
446446+ track_dest_path.clone(),
447447+ &opts,
448448+ |_| {},
449449+ ) {
450450+ std::result::Result::Ok(_) => {}
451451+ Err(err) => {
452452+ return Err(error::Error::CopyError(err)).with_context(|| {
453453+ format!(
454454+ "Cannot copy {} to {}",
455455+ track_for_thread.file_path,
456456+ track_dest_path.display()
457457+ )
458458+ });
474459 }
475475- }
460460+ };
476461477462 Ok(())
478463 }
479464 })
480465 .await?;
481466482482- // step 3: update the destination track with the new state
483467 dest_track.file_state = crate::model::FileState::Copied;
484468 dest_db
485469 .insert_track(&dest_track)
486470 .await
487471 .with_context(|| "Cannot insert copy finished track in destination database")?;
488472489489- Ok(())
473473+ Ok(track.clone())
490474}
···6677CREATE TABLE IF NOT EXISTS tracks (
88 id INTEGER PRIMARY KEY NOT NULL,
99- track_id TEXT NOT NULL,
99+ track_id TEXT NOT NULL UNIQUE,
1010 title TEXT NOT NULL,
1111 artist TEXT NOT NULL,
1212 album TEXT NOT NULL,
···15151616 // One day I may get back to this.
1717 // Today is not one of those days.
1818- //
1919- // if std::env::args().count() == 1 {
2020- // return gui::run().await;
2121- // }
1818+ #[cfg(feature = "gui")]
1919+ {
2020+ if std::env::args().count() == 1 {
2121+ return gui::run().await;
2222+ }
2323+ }
22242325 let c = cli::Cli::parse();
2426