[READ-ONLY] Mirror of https://github.com/probablykasper/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Move errors to anyhow

Kasper (Sep 26, 2024, 9:46 AM +0200) d6e1a67d b3e23097

+238 -391
+6 -11
src-native/data.rs
··· 2 2 use crate::library::{load_library, Paths}; 3 3 use crate::library_types::Library; 4 4 use crate::tracks::Tag; 5 - use crate::UniResult; 5 + use anyhow::{Context, Result}; 6 6 use atomicwrites::{AllowOverwrite, AtomicFile}; 7 7 use dirs_next; 8 - use napi::Result; 9 8 use serde::Serialize; 10 9 use std::collections::HashSet; 11 10 use std::env; ··· 34 33 now = Instant::now(); 35 34 let file_path = &self.paths.library_json; 36 35 let af = AtomicFile::new(file_path, AllowOverwrite); 37 - let result = af.write(|f| f.write_all(&json)); 38 - match result { 39 - Ok(_) => {} 40 - Err(err) => throw!("Error saving: {err}"), 41 - } 36 + af.write(|f| f.write_all(&json)).context("Error saving")?; 42 37 println!("Write: {}ms", now.elapsed().as_millis()); 43 38 Ok(()) 44 39 } ··· 46 41 is_dev: bool, 47 42 local_data_path: Option<String>, 48 43 library_path: Option<String>, 49 - ) -> UniResult<Data> { 44 + ) -> Result<Data> { 50 45 if is_dev { 51 46 println!("Starting in dev mode"); 52 47 } ··· 61 56 local_data_dir = appdata_dev.join("LocalData/space.kasper.ferrum"); 62 57 } else { 63 58 library_dir = dirs_next::audio_dir() 64 - .ok_or("Music folder not found")? 59 + .context("Music folder not found")? 65 60 .join("Ferrum"); 66 61 cache_dir = dirs_next::cache_dir() 67 - .ok_or("Cache folder not found")? 62 + .context("Cache folder not found")? 68 63 .join("space.kasper.ferrum"); 69 64 local_data_dir = dirs_next::data_local_dir() 70 - .ok_or("Local data folder not found")? 65 + .context("Local data folder not found")? 71 66 .join("space.kasper.ferrum"); 72 67 }; 73 68 let paths = Paths {
+5 -4
src-native/data_js.rs
··· 1 1 use crate::data::Data; 2 - use napi::{Env, JsUndefined, Result}; 2 + use anyhow::{Context, Result}; 3 + use napi::{Env, JsUndefined}; 3 4 4 5 pub fn get_data(env: &Env) -> Result<&mut Data> { 5 - let data = env.get_instance_data::<Data>()?.ok_or(nerr!("No data"))?; 6 + let data = env.get_instance_data::<Data>()?.context("No data")?; 6 7 return Ok(data); 7 8 } 8 9 ··· 43 44 44 45 #[napi(js_name = "save")] 45 46 #[allow(dead_code)] 46 - pub fn save(env: Env) -> Result<JsUndefined> { 47 + pub fn save(env: Env) -> napi::Result<JsUndefined> { 47 48 let data: &mut Data = get_data(&env)?; 48 49 data.save()?; 49 - return env.get_undefined(); 50 + env.get_undefined() 50 51 }
+29 -43
src-native/itunes_import.rs
··· 5 5 }; 6 6 use crate::tracks::generate_filename; 7 7 use crate::tracks::import::{read_file_metadata, FileType}; 8 + use anyhow::{bail, Context, Result}; 8 9 use lofty::file::{AudioFile, TaggedFileExt}; 9 - use napi::{Env, Result}; 10 + use napi::Env; 10 11 use serde::{Deserialize, Serialize}; 11 12 use std::collections::HashMap; 12 13 use std::fs; ··· 61 62 continue; 62 63 } 63 64 64 - let track: XmlTrack = match plist::from_value(&value) { 65 - Ok(track) => track, 66 - Err(e) => throw!("Could not read track with id \"{key}\": {e}"), 67 - }; 65 + let track: XmlTrack = plist::from_value(&value) 66 + .with_context(|| format!("Could not read track with id \"{key}"))?; 68 67 tracks.insert(key, track); 69 68 } 70 69 let mut playlists = Vec::new(); ··· 75 74 .and_then(|v| v.as_string()) 76 75 .unwrap_or_default() 77 76 .to_string(); 78 - let playlist: XmlPlaylist = match plist::from_value(value) { 79 - Ok(playlist) => playlist, 80 - Err(e) => throw!("Could not read playlist \"{name}\": {e}"), 81 - }; 77 + let playlist: XmlPlaylist = plist::from_value(value) 78 + .with_context(|| format!("Could not read playlist \"{name}\""))?; 82 79 playlists.push(playlist); 83 80 } 84 81 Ok(XmlLibraryProps { tracks, playlists }) ··· 96 93 for xml_playlist in playlists { 97 94 if xml_playlist.distinguished_kind == Some(4) { 98 95 if xml_music_playlist.is_some() { 99 - throw!("Found two iTunes-generated Music playlists"); 96 + bail!("Found two iTunes-generated Music playlists"); 100 97 } else { 101 98 xml_music_playlist = Some(xml_playlist); 102 99 } 103 100 } 104 101 } 105 - xml_music_playlist.ok_or(nerr!("No Music playlist found")) 102 + xml_music_playlist.context("No Music playlist found") 106 103 } 107 104 fn take_importable_playlists(&mut self) -> Vec<XmlPlaylist> { 108 105 let playlists = std::mem::take(&mut self.playlists); ··· 310 307 } 311 308 312 309 fn parse_file_url(value: &str) -> Result<PathBuf> { 313 - let file_url = match url::Url::parse(value) { 314 - Ok(url) => url, 315 - Err(e) => throw!("Invalid track location: {}", e), 316 - }; 310 + let file_url = url::Url::parse(value).context("Invalid track location")?; 317 311 match file_url.scheme() { 318 312 "file" => {} 319 - _ => throw!("Invalid track location scheme: {}", value), 313 + _ => bail!("Invalid track location scheme: {}", value), 320 314 } 321 315 match file_url.to_file_path() { 322 316 Ok(path) => Ok(path), 323 - Err(()) => throw!("Invalid track location host: {}", value), 317 + Err(()) => bail!("Invalid track location host: {}", value), 324 318 } 325 319 } 326 320 ··· 330 324 start_time: i64, 331 325 tracks_dir: &Path, 332 326 ) -> Result<(PathBuf, Track)> { 333 - let xml_location = match xml_track.location { 334 - Some(ref location) => location, 335 - None => throw!("Missing track location"), 336 - }; 327 + let xml_location = xml_track.location.context("Missing track location")?; 337 328 if xml_track.track_type != Some("File".to_string()) { 338 - return Err(nerr!( 329 + bail!( 339 330 "Track with type {}, expected \"File\"", 340 331 xml_track.track_type.as_deref().unwrap_or("unknown"), 341 - )); 332 + ); 342 333 } 343 334 344 335 // Unlike "Skip Date" etc, "Play Date" is a non-UTC Mac HFS+ timestamp, but ··· 347 338 348 339 let skip = CountInfo::new(xml_track.skip_count, xml_track.skip_date); 349 340 350 - let xml_track_path = parse_file_url(xml_location)?; 341 + let xml_track_path = parse_file_url(&xml_location)?; 351 342 352 343 // this will also checks if the file exists 353 344 let file_md = read_file_metadata(&xml_track_path)?; 354 345 355 - let tagged_file = match lofty::read_from_path(&xml_track_path) { 356 - Ok(tagged_file) => tagged_file, 357 - Err(e) => throw!("Failed to read file information: {}", e), 358 - }; 346 + let tagged_file = 347 + lofty::read_from_path(&xml_track_path).context("Failed to read file information")?; 359 348 let audio_properties = tagged_file.properties(); 360 349 361 350 let file_type = FileType::from_path(&xml_track_path)?; ··· 376 365 duration: audio_properties.duration().as_secs_f64(), 377 366 bitrate: audio_properties 378 367 .audio_bitrate() 379 - .ok_or(nerr!("Unknown bitrate"))? 368 + .context("Unknown bitrate")? 380 369 .into(), 381 370 sampleRate: audio_properties 382 371 .audio_bitrate() 383 - .ok_or(nerr!("Unknown sample rate"))? 372 + .context("Unknown sample rate")? 384 373 .into(), 385 374 file: filename, 386 375 dateModified: datetime_to_timestamp_millis(xml_track.date_modified), ··· 436 425 let float: f32 = volume_adjustment.into(); 437 426 let vol = (float / 2.55).round() as i8; 438 427 if vol < -100 || vol > 100 { 439 - throw!("Invalid volume adjustment: {}", volume_adjustment); 428 + bail!("Invalid volume adjustment: {}", volume_adjustment); 440 429 } 441 430 Some(vol) 442 431 } ··· 599 588 #[napi] 600 589 impl ItunesImport { 601 590 #[napi(factory)] 602 - pub fn new(env: Env) -> Result<Self> { 591 + pub fn new(env: Env) -> napi::Result<Self> { 603 592 let data = get_data(&env)?; 604 593 Ok(Self { 605 594 new_library: Some(data.library.clone()).into(), ··· 607 596 }) 608 597 } 609 598 #[napi] 610 - pub async fn start(&self, path: String, tracks_dir: String) -> Result<ImportStatus> { 611 - import_itunes(self, path, tracks_dir).await 599 + pub async fn start(&self, path: String, tracks_dir: String) -> napi::Result<ImportStatus> { 600 + Ok(import_itunes(self, path, tracks_dir).await?) 612 601 } 613 602 #[napi] 614 - pub fn finish(&mut self, env: Env) -> Result<()> { 603 + pub fn finish(&mut self, env: Env) -> napi::Result<()> { 615 604 let data = get_data(&env)?; 616 605 let itunes_track_paths = &mut *self.itunes_track_paths.lock().unwrap(); 617 606 for (itunes_path, ferrum_file) in itunes_track_paths { 618 607 let new_path = data.paths.tracks_dir.join(ferrum_file); 619 - fs::copy(itunes_path, new_path).or(Err(nerr!("Error copying file")))?; 608 + fs::copy(itunes_path, new_path).context("Error copying file")?; 620 609 } 621 610 let new_library = &mut self.new_library.lock().unwrap(); 622 - data.library = new_library.take().ok_or(nerr!("Not initialized"))?; 611 + data.library = new_library.take().context("Not initialized")?; 623 612 Ok(()) 624 613 } 625 614 } ··· 632 621 let new_library_lock = &mut *itunes_import.new_library.lock().unwrap(); 633 622 let mut library = match new_library_lock { 634 623 Some(library) => library, 635 - None => throw!("Not initialized"), 624 + None => bail!("Not initialized"), 636 625 }; 637 626 let mut itunes_track_paths = itunes_import.itunes_track_paths.lock().unwrap(); 638 627 let original_tracks_count = library.get_tracks().len(); 639 628 let original_tracklists_count = library.trackLists.len(); 640 - let xml_lib: XmlLibrary = match plist::from_file(path) { 641 - Ok(book) => book, 642 - Err(e) => throw!("Unable to parse: {e}"), 643 - }; 629 + let xml_lib: XmlLibrary = plist::from_file(path).context("Unable to parse")?; 644 630 let mut errors = Vec::new(); 645 631 let start_time = get_now_timestamp(); 646 632 ··· 667 653 let xml_track = xml 668 654 .tracks 669 655 .remove(&xml_id) 670 - .ok_or(nerr!("Track with id {} not found", playlist_item.track_id))?; 656 + .with_context(|| format!("Track with id {} not found", playlist_item.track_id))?; 671 657 let artist_title = xml_track.artist_title(); 672 658 673 659 if matches!(xml_track.name.as_deref(), Some("") | None) {
+4 -21
src-native/lib.rs
··· 1 + use anyhow::{Context, Result}; 1 2 use serde::de::DeserializeOwned; 2 3 use std::fs::File; 3 4 use std::io::BufReader; 4 5 use std::mem; 5 6 use std::path::PathBuf; 6 7 use std::time::{SystemTime, UNIX_EPOCH}; 7 - 8 - macro_rules! nerr { 9 - ($($arg:tt)*) => { 10 - napi::Error::from_reason(format!($($arg)*).to_owned()) 11 - } 12 - } 13 - 14 - macro_rules! throw { 15 - ($($arg:tt)*) => { 16 - return crate::UniResult::Err(format!($($arg)*).into()).map_err(|e| e.into()) 17 - } 18 - } 19 8 20 9 #[macro_use] 21 10 extern crate napi_derive; ··· 85 74 } 86 75 } 87 76 88 - fn path_to_json<J>(path: PathBuf) -> UniResult<J> 77 + fn path_to_json<J>(path: PathBuf) -> Result<J> 89 78 where 90 79 J: DeserializeOwned, 91 80 { 92 - let file = match File::open(path) { 93 - Ok(f) => f, 94 - Err(err) => throw!("Error opening file: {}", err), 95 - }; 81 + let file = File::open(path).context("Error opening file")?; 96 82 let reader = BufReader::new(file); 97 - let json = match serde_json::from_reader(reader) { 98 - Ok(json) => json, 99 - Err(err) => throw!("Error parsing file: {:?}", err), 100 - }; 83 + let json = serde_json::from_reader(reader).context("Error parsing file")?; 101 84 Ok(json) 102 85 }
+44 -54
src-native/library.rs
··· 1 1 use crate::library_types::{Library, VersionedLibrary}; 2 - use crate::UniResult; 2 + use anyhow::{bail, Context, Result}; 3 3 use serde::{Deserialize, Serialize}; 4 4 use serde_json::{json, Value}; 5 5 use std::fs::{create_dir_all, File}; 6 - use std::io::{Error, ErrorKind, Read}; 6 + use std::io::{ErrorKind, Read}; 7 7 use std::path::PathBuf; 8 8 use std::time::Instant; 9 9 ··· 17 17 pub local_data_dir: PathBuf, 18 18 } 19 19 impl Paths { 20 - fn ensure_dirs_exists(&self) -> Result<(), Error> { 20 + fn ensure_dirs_exists(&self) -> Result<()> { 21 21 create_dir_all(&self.library_dir)?; 22 22 create_dir_all(&self.tracks_dir)?; 23 23 create_dir_all(&self.local_data_dir)?; ··· 26 26 } 27 27 } 28 28 29 - pub fn load_library(paths: &Paths) -> UniResult<Library> { 29 + pub fn load_library(paths: &Paths) -> Result<Library> { 30 30 let mut now = Instant::now(); 31 31 32 - match paths.ensure_dirs_exists() { 33 - Ok(_) => {} 34 - Err(err) => throw!("Error ensuring folder exists: {}", err), 35 - }; 32 + paths 33 + .ensure_dirs_exists() 34 + .context("Error ensuring folder exists")?; 36 35 println!( 37 36 "Loading library at path: {}", 38 37 paths.library_dir.to_string_lossy() 39 38 ); 40 39 41 - let versioned_library = match File::open(&paths.library_json) { 42 - Ok(mut file) => { 43 - let mut json_str = String::new(); 44 - match file.read_to_string(&mut json_str) { 45 - Ok(_) => {} 46 - Err(err) => throw!("Error reading library file: {}", err), 47 - }; 48 - println!("Read library: {}ms", now.elapsed().as_millis()); 49 - now = Instant::now(); 40 + let mut library_file = match File::open(&paths.library_json) { 41 + Ok(file) => file, 42 + Err(err) => match err.kind() { 43 + ErrorKind::NotFound => return Ok(Library::new()), 44 + _ => return Err(err).context("Error opening library file"), 45 + }, 46 + }; 50 47 51 - let mut value: Value = match serde_json::from_str(&mut json_str) { 52 - Ok(library) => library, 53 - Err(err) => throw!("Error parsing library file: {:?}", err), 54 - }; 55 - // Migrate version number to string 56 - if let Some(obj) = value.as_object_mut() { 57 - if let Some(version_field) = obj.get_mut("version") { 58 - if let Some(version) = version_field.as_number() { 59 - if version.as_u64() == Some(1) { 60 - *version_field = json!("1"); 61 - } else if version.as_u64() == Some(2) { 62 - *version_field = json!("2"); 63 - } 64 - } 48 + let mut json_str = String::new(); 49 + library_file 50 + .read_to_string(&mut json_str) 51 + .context("Error reading library file")?; 52 + println!("Read library: {}ms", now.elapsed().as_millis()); 53 + now = Instant::now(); 54 + 55 + let mut value: Value = 56 + serde_json::from_str(&mut json_str).context("Error parsing library file")?; 57 + // Migrate version number to string 58 + if let Some(obj) = value.as_object_mut() { 59 + if let Some(version_field) = obj.get_mut("version") { 60 + if let Some(version) = version_field.as_number() { 61 + if version.as_u64() == Some(1) { 62 + *version_field = json!("1"); 63 + } else if version.as_u64() == Some(2) { 64 + *version_field = json!("2"); 65 65 } 66 66 } 67 + } 68 + } 67 69 68 - let versioned_library: VersionedLibrary = match serde_json::from_value(value) { 69 - Ok(library) => library, 70 - Err(err) => throw!("Error parsing library file: {:?}", err), 71 - }; 72 - 73 - println!("Parse library: {}ms", now.elapsed().as_millis()); 74 - versioned_library 75 - } 76 - Err(err) => match err.kind() { 77 - ErrorKind::NotFound => { 78 - return Ok(Library::new()); 79 - } 80 - _err_kind => throw!("Error opening library file: {}", err), 81 - }, 82 - }; 70 + let versioned_library: VersionedLibrary = 71 + serde_json::from_value(value).context("Error parsing library file")?; 72 + println!("Parse library: {}ms", now.elapsed().as_millis()); 83 73 84 74 let library = versioned_library.upgrade().init_libary(); 85 75 println!("Initialized library: {}ms", now.elapsed().as_millis()); ··· 98 88 99 89 #[napi(js_name = "get_default_sort_desc")] 100 90 #[allow(dead_code)] 101 - pub fn get_default_sort_desc(field: String) -> bool { 91 + pub fn get_default_sort_desc(field: String) -> Result<bool> { 102 92 if field == "index" { 103 - return true; 93 + return Ok(true); 104 94 } 105 - let field = get_track_field_type(&field); 95 + let field = get_track_field_type(&field)?; 106 96 let desc = match field { 107 - Some(TrackField::String) => false, 97 + TrackField::String => false, 108 98 _ => true, 109 99 }; 110 - return desc; 100 + Ok(desc) 111 101 } 112 102 113 - pub fn get_track_field_type(field: &str) -> Option<TrackField> { 103 + pub fn get_track_field_type(field: &str) -> Result<TrackField> { 114 104 let field = match field { 115 105 "size" => TrackField::I64, 116 106 "duration" => TrackField::F64, ··· 149 139 "playCount" => TrackField::U32, 150 140 "skipCount" => TrackField::U32, 151 141 "volume" => TrackField::I8, 152 - _ => return None, 142 + _ => bail!("Field type not found for {}", field), 153 143 }; 154 - return Some(field); 144 + return Ok(field); 155 145 }
+18 -21
src-native/library_types.rs
··· 1 1 #![allow(non_snake_case)] 2 2 3 + use crate::get_now_timestamp; 3 4 use crate::playlists::{delete_file, remove_from_all_playlists}; 4 - use crate::{get_now_timestamp, UniResult}; 5 + use anyhow::{bail, Context, Result}; 5 6 use linked_hash_map::{Entry, LinkedHashMap}; 6 7 use nanoid::nanoid; 7 8 use serde::{Deserialize, Serialize}; ··· 131 132 track_id_map.push(id.clone()); 132 133 self.track_item_ids.insert(id, item_id); 133 134 } 134 - pub fn delete_track_and_file(&mut self, id: &TrackID, tracks_dir: &PathBuf) -> UniResult<()> { 135 + pub fn delete_track_and_file(&mut self, id: &TrackID, tracks_dir: &PathBuf) -> Result<()> { 135 136 let file_path = { 136 137 let track = self.get_track(id)?; 137 138 tracks_dir.join(&track.file) 138 139 }; 139 140 if !file_path.exists() { 140 - throw!("File does not exist: {}", file_path.to_string_lossy()); 141 + bail!("File does not exist: {}", file_path.to_string_lossy()); 141 142 } 142 143 143 144 remove_from_all_playlists(self, id); ··· 191 192 children: Vec::new(), 192 193 } 193 194 } 194 - pub fn get_track(&self, id: &TrackID) -> UniResult<&Track> { 195 - match self.get_tracks().get(id) { 196 - Some(track) => Ok(track), 197 - None => throw!("Track with ID {} not found", id), 198 - } 195 + pub fn get_track(&self, id: &TrackID) -> Result<&Track> { 196 + self.get_tracks() 197 + .get(id) 198 + .context("Track with ID {} not found") 199 199 } 200 - pub fn get_track_mut(&mut self, id: &TrackID) -> UniResult<&mut Track> { 201 - match self.tracks.get_mut(id) { 202 - Some(track) => Ok(track), 203 - None => throw!("Track with ID {} not found", id), 204 - } 200 + pub fn get_track_mut(&mut self, id: &TrackID) -> Result<&mut Track> { 201 + self.tracks 202 + .get_mut(id) 203 + .context("Track with ID {} not found") 205 204 } 206 - pub fn get_tracklist(&self, id: &str) -> UniResult<&TrackList> { 207 - let tracklist = self.trackLists.get(id); 208 - Ok(tracklist.ok_or("Playlist ID not found")?) 205 + pub fn get_tracklist(&self, id: &str) -> Result<&TrackList> { 206 + self.trackLists.get(id).context("Playlist ID not found") 209 207 } 210 - pub fn get_tracklist_mut(&mut self, id: &str) -> UniResult<&mut TrackList> { 211 - let tracklist = self.trackLists.get_mut(id); 212 - Ok(tracklist.ok_or("Playlist ID not found")?) 208 + pub fn get_tracklist_mut(&mut self, id: &str) -> Result<&mut TrackList> { 209 + self.trackLists.get_mut(id).context("Playlist ID not found") 213 210 } 214 - pub fn get_root_tracklist_mut(&mut self) -> UniResult<&mut Special> { 211 + pub fn get_root_tracklist_mut(&mut self) -> Result<&mut Special> { 215 212 let tracklist = self.trackLists.get_mut("root"); 216 213 match tracklist { 217 214 Some(TrackList::Special(special)) => match special.name { 218 215 SpecialTrackListName::Root => Ok(special), 219 216 }, 220 - _ => throw!("Root playlist not found"), 217 + _ => bail!("Root playlist not found"), 221 218 } 222 219 } 223 220 pub fn get_parent_id(&self, id: &str) -> Option<String> {
+45 -48
src-native/playlists.rs
··· 4 4 get_track_ids_from_item_ids, new_item_ids_from_track_ids, ItemId, Library, 5 5 SpecialTrackListName, TrackID, TrackList, TRACK_ID_MAP, 6 6 }; 7 - use crate::{str_to_option, UniResult}; 8 - use napi::{Env, JsUnknown, Result}; 7 + use crate::str_to_option; 8 + use anyhow::{bail, Context, Result}; 9 + use napi::{Env, JsUnknown}; 9 10 use std::collections::{HashMap, HashSet}; 10 11 use std::path::PathBuf; 11 12 ··· 60 61 pub fn get_track_list(id: String, env: Env) -> Result<JsUnknown> { 61 62 let data: &mut Data = get_data(&env)?; 62 63 let tracklist = data.library.get_tracklist(&id)?; 63 - env.to_js_value(&tracklist) 64 + Ok(env.to_js_value(&tracklist)?) 64 65 } 65 66 66 67 fn get_child_ids_recursive( ··· 71 72 let folder_children = match library.trackLists.get(id) { 72 73 Some(TrackList::Playlist(_)) => return Ok(()), 73 74 Some(TrackList::Folder(folder)) => folder.children.clone(), 74 - Some(TrackList::Special(_)) => throw!("Cannot delete special track list"), 75 - None => throw!("No track list with id {}", id), 75 + Some(TrackList::Special(_)) => bail!("Cannot delete special track list"), 76 + None => bail!("No track list with id {}", id), 76 77 }; 77 78 for child_id in &folder_children { 78 79 get_child_ids_recursive(library, &child_id, ids)?; ··· 80 81 for new_id in folder_children { 81 82 let was_new = ids.insert(new_id.clone()); 82 83 if !was_new { 83 - throw!("Duplicate track list id {new_id}"); 84 + bail!("Duplicate track list id {new_id}"); 84 85 } 85 86 } 86 87 Ok(()) 87 88 } 88 89 89 90 fn remove_child_id(library: &mut Library, parent_id: &String, child_id: &String) -> Result<()> { 90 - let parent = match library.trackLists.get_mut(parent_id) { 91 - Some(tracklist) => tracklist, 92 - None => throw!("Parent id {parent_id} not found"), 93 - }; 91 + let parent = library 92 + .trackLists 93 + .get_mut(parent_id) 94 + .with_context(|| format!("Parent id {parent_id} not found"))?; 94 95 let children = match parent { 95 - TrackList::Playlist(_) => throw!("Parent id {parent_id} not found"), 96 + TrackList::Playlist(_) => bail!("Parent id {parent_id} not found"), 96 97 TrackList::Folder(folder) => folder.children.clone(), 97 98 TrackList::Special(special) => match special.name { 98 99 SpecialTrackListName::Root => special.children.clone(), ··· 104 105 .filter(|id| id != child_id) 105 106 .collect(); 106 107 match children.len() - new_children.len() { 107 - 0 => throw!("Parent id {child_id} does not contain child"), 108 + 0 => bail!("Parent id {child_id} does not contain child"), 108 109 1 => (), 109 - _ => throw!("Child id {child_id} found multiple times"), 110 + _ => bail!("Child id {child_id} found multiple times"), 110 111 }; 111 112 match parent { 112 113 TrackList::Playlist(_) => panic!(), ··· 123 124 #[allow(dead_code)] 124 125 pub fn delete_track_list(id: String, env: Env) -> Result<()> { 125 126 let data: &mut Data = get_data(&env)?; 126 - let parent_id = data 127 - .library 128 - .get_parent_id(&id) 129 - .ok_or(nerr!("No parent found"))?; 127 + let parent_id = data.library.get_parent_id(&id).context("No parent found")?; 130 128 131 129 let mut ids = HashSet::new(); 132 130 ids.insert(id.clone()); 133 131 get_child_ids_recursive(&mut data.library, &id, &mut ids)?; 134 132 135 133 if ids.contains(&parent_id) { 136 - throw!("Parent id {parent_id} contains itself"); 134 + bail!("Parent id {parent_id} contains itself"); 137 135 } 138 136 remove_child_id(&mut data.library, &parent_id, &id)?; 139 137 for id in &ids { ··· 148 146 let data: &mut Data = get_data(&env)?; 149 147 let playlist = match data.library.get_tracklist_mut(&playlist_id)? { 150 148 TrackList::Playlist(playlist) => playlist, 151 - TrackList::Folder(_) => throw!("Cannot add track to folder"), 152 - TrackList::Special(_) => throw!("Cannot add track to special playlist"), 149 + TrackList::Folder(_) => bail!("Cannot add track to folder"), 150 + TrackList::Special(_) => bail!("Cannot add track to special playlist"), 153 151 }; 154 152 let mut new_item_ids = new_item_ids_from_track_ids(&track_ids); 155 153 playlist.tracks.append(&mut new_item_ids); ··· 163 161 let mut track_ids: HashSet<String> = HashSet::from_iter(ids); 164 162 let playlist = match data.library.get_tracklist_mut(&playlist_id)? { 165 163 TrackList::Playlist(playlist) => playlist, 166 - _ => throw!("Cannot check if folder/special contains track"), 164 + _ => bail!("Cannot check if folder/special contains track"), 167 165 }; 168 166 for track in &playlist.get_track_ids() { 169 167 if track_ids.contains(track) { ··· 180 178 let data: &mut Data = get_data(&env)?; 181 179 let playlist = match data.library.get_tracklist_mut(&playlist_id)? { 182 180 TrackList::Playlist(playlist) => playlist, 183 - _ => throw!("Cannot remove track from non-playlist"), 181 + _ => bail!("Cannot remove track from non-playlist"), 184 182 }; 185 183 let items_to_remove: HashSet<ItemId> = item_ids.into_iter().collect(); 186 184 ··· 204 202 } 205 203 } 206 204 207 - pub fn delete_file(path: &PathBuf) -> UniResult<()> { 205 + pub fn delete_file(path: &PathBuf) -> Result<()> { 208 206 #[allow(unused_mut)] 209 207 let mut trash_context = trash::TrashContext::new(); 210 208 211 209 #[cfg(target_os = "macos")] 212 210 trash_context.set_delete_method(trash::macos::DeleteMethod::NsFileManager); 213 211 214 - match trash_context.delete(&path) { 215 - Ok(_) => Ok(()), 216 - Err(_) => throw!("Failed moving file to trash: {}", path.to_string_lossy()), 217 - } 212 + trash_context 213 + .delete(&path) 214 + .with_context(|| format!("Failed moving file to trash: {}", path.to_string_lossy())) 218 215 } 219 216 220 217 #[napi(js_name = "delete_tracks_with_item_ids")] ··· 252 249 } 253 250 }; 254 251 255 - let parent = match library.trackLists.get_mut(&parent_id) { 256 - Some(parent) => parent, 257 - None => throw!("Parent not found"), 258 - }; 252 + let parent = library 253 + .trackLists 254 + .get_mut(&parent_id) 255 + .context("Parent not found")?; 259 256 260 257 match parent { 261 - TrackList::Playlist(_) => throw!("Parent cannot be playlist"), 258 + TrackList::Playlist(_) => bail!("Parent cannot be playlist"), 262 259 TrackList::Folder(folder) => { 263 260 folder.children.push(list.id().to_string()); 264 261 library.trackLists.insert(list.id().to_string(), list); ··· 280 277 let data: &mut Data = get_data(&env)?; 281 278 282 279 match data.library.trackLists.get_mut(&id) { 283 - Some(TrackList::Special(_)) => throw!("Cannot edit special playlists"), 280 + Some(TrackList::Special(_)) => bail!("Cannot edit special playlists"), 284 281 Some(TrackList::Playlist(playlist)) => { 285 282 playlist.name = name; 286 283 playlist.description = str_to_option(description); ··· 289 286 folder.name = name; 290 287 folder.description = str_to_option(description); 291 288 } 292 - None => throw!("Playlist not found"), 289 + None => bail!("Playlist not found"), 293 290 }; 294 291 295 292 return Ok(()); 296 293 } 297 294 298 - fn get_all_tracklist_children(data: &Data, playlist_id: &str) -> UniResult<Vec<TrackID>> { 295 + fn get_all_tracklist_children(data: &Data, playlist_id: &str) -> Result<Vec<TrackID>> { 299 296 let direct_children = match data.library.get_tracklist(playlist_id)? { 300 297 TrackList::Folder(folder) => &folder.children, 301 298 TrackList::Special(special) => &special.children, ··· 320 317 fn get_children_if_user_editable<'a>( 321 318 library: &'a mut Library, 322 319 id: &'a str, 323 - ) -> UniResult<&'a mut Vec<String>> { 320 + ) -> Result<&'a mut Vec<String>> { 324 321 let children = match library.trackLists.get_mut(id) { 325 322 Some(TrackList::Folder(folder)) => &mut folder.children, 326 323 Some(TrackList::Special(special)) => match special.name { 327 324 SpecialTrackListName::Root => &mut special.children, 328 325 }, 329 - None => throw!("Attempted to move from/to non-existant folder"), 330 - _ => throw!("Attempted to move from/to non-folder"), 326 + None => bail!("Attempted to move from/to non-existant folder"), 327 + _ => bail!("Attempted to move from/to non-folder"), 331 328 }; 332 329 Ok(children) 333 330 } ··· 344 341 let data: &mut Data = get_data(&env)?; 345 342 346 343 match data.library.trackLists.get(&id) { 347 - Some(TrackList::Special(_)) => throw!("Cannot move special playlist"), 348 - None => throw!("List not found"), 344 + Some(TrackList::Special(_)) => bail!("Cannot move special playlist"), 345 + None => bail!("List not found"), 349 346 _ => {} 350 347 }; 351 348 ··· 353 350 get_children_if_user_editable(&mut data.library, &to_id)?; 354 351 355 352 if id == to_id { 356 - throw!("Cannot move playlist into itself"); 353 + bail!("Cannot move playlist into itself"); 357 354 } 358 355 359 356 let from_id_children = get_all_tracklist_children(&data, &id)?; 360 357 if from_id_children.contains(&to_id) { 361 - throw!("Cannot move playlist to a child of itself"); 358 + bail!("Cannot move playlist to a child of itself"); 362 359 } 363 360 364 361 let children = get_children_if_user_editable(&mut data.library, &from_id)?; 365 - let i = match children.iter().position(|child_id| child_id == &id) { 366 - None => throw!("Could not find playlist"), 367 - Some(i) => i, 368 - }; 362 + let i = children 363 + .iter() 364 + .position(|child_id| child_id == &id) 365 + .context("Could not find playlist")?; 369 366 children.remove(i); 370 367 371 368 let to_folder_children = get_children_if_user_editable(&mut data.library, &to_id)?; ··· 391 388 let data: &mut Data = get_data(&env)?; 392 389 let playlist = match data.library.get_tracklist_mut(&playlist_id)? { 393 390 TrackList::Playlist(playlist) => playlist, 394 - _ => return Err(nerr!("Cannot rearrange tracks in non-playlist")), 391 + _ => bail!("Cannot rearrange tracks in non-playlist"), 395 392 }; 396 393 397 394 let item_ids_set: HashSet<ItemId> = item_ids.iter().cloned().collect(); ··· 416 413 Ok(()) 417 414 } 418 415 419 - pub fn get_tracklist_item_ids(library: &Library, playlist_id: &str) -> UniResult<Vec<ItemId>> { 416 + pub fn get_tracklist_item_ids(library: &Library, playlist_id: &str) -> Result<Vec<ItemId>> { 420 417 match library.get_tracklist(playlist_id)? { 421 418 TrackList::Playlist(playlist) => Ok(playlist.tracks.clone()), 422 419 TrackList::Folder(folder) => {
+3 -6
src-native/sort.rs
··· 2 2 use crate::library_types::{ItemId, Library, Track, TRACK_ID_MAP}; 3 3 use crate::page::TracksPageOptions; 4 4 use crate::playlists::get_tracklist_item_ids; 5 - use crate::UniResult; 6 5 use alphanumeric_sort::compare_str; 6 + use anyhow::Result; 7 7 use std::cmp::Ordering; 8 8 use std::time::Instant; 9 9 ··· 86 86 } 87 87 } 88 88 89 - pub fn sort(options: TracksPageOptions, library: &Library) -> UniResult<Vec<ItemId>> { 89 + pub fn sort(options: TracksPageOptions, library: &Library) -> Result<Vec<ItemId>> { 90 90 let now = Instant::now(); 91 91 92 92 let id_map = TRACK_ID_MAP.read().unwrap(); ··· 104 104 } 105 105 106 106 let tracks = library.get_tracks(); 107 - let field = match get_track_field_type(&options.sort_key) { 108 - Some(field) => field, 109 - None => throw!("Field type not found for {}", options.sort_key), 110 - }; 107 + let field = get_track_field_type(&options.sort_key)?; 111 108 let subsort_field = options.group_album_tracks 112 109 && match options.sort_key.as_str() { 113 110 "dateAdded" | "albumName" | "comments" | "genre" | "year" | "artist" => true,
+5 -6
src-native/tracks/cover.rs
··· 1 1 use super::Tag; 2 - use anyhow::Context; 2 + use anyhow::{anyhow, bail, Context, Result}; 3 3 use fast_image_resize::images::Image; 4 4 use fast_image_resize::{IntoImageView, Resizer}; 5 5 use image::codecs::jpeg::JpegEncoder; ··· 7 7 use image::{ImageEncoder, ImageFormat, ImageReader}; 8 8 use lazy_static::lazy_static; 9 9 use napi::bindgen_prelude::Buffer; 10 - use napi::Result; 11 10 use redb::{Database, TableDefinition}; 12 11 use std::fs; 13 12 use std::io::{BufWriter, Cursor}; ··· 121 120 path: String, 122 121 index: u16, 123 122 cache_db_path: String, 124 - ) -> Result<Option<Buffer>> { 123 + ) -> napi::Result<Option<Buffer>> { 125 124 if path == "" { 126 - throw!("path must not be empty") 125 + return Err(anyhow!("path must not be empty").into()); 127 126 } else if cache_db_path == "" { 128 - throw!("cache_db_path must not be empty") 127 + return Err(anyhow!("cache_db_path must not be empty").into()); 129 128 } 130 129 131 130 init_cache_db(cache_db_path)?; ··· 210 209 .into_inner() 211 210 .context("Error getting inner img buffer")? 212 211 } 213 - _ => throw!("Unsupported image type"), 212 + _ => bail!("Unsupported image type"), 214 213 }; 215 214 Ok(img_bytes) 216 215 }
+20 -28
src-native/tracks/import.rs
··· 1 1 use crate::data::Data; 2 2 use crate::library_types::Track; 3 + use crate::sys_time_to_timestamp; 3 4 use crate::tracks::generate_filename; 4 - use crate::{sys_time_to_timestamp, UniResult}; 5 + use anyhow::{bail, Context, Result}; 5 6 use lofty::file::{AudioFile, TaggedFileExt}; 6 7 use lofty::tag::{Accessor, ItemKey, TagExt}; 7 8 use std::fs; ··· 14 15 Opus, 15 16 } 16 17 impl FileType { 17 - pub fn from_path(path: &Path) -> UniResult<Self> { 18 + pub fn from_path(path: &Path) -> Result<Self> { 18 19 let ext = path.extension().unwrap_or_default().to_string_lossy(); 19 20 match ext.as_ref() { 20 21 "mp3" => Ok(FileType::Mp3), 21 22 "m4a" => Ok(FileType::M4a), 22 23 "opus" => Ok(FileType::Opus), 23 - _ => throw!("Unsupported file extension {}", ext), 24 + _ => bail!("Unsupported file extension {}", ext), 24 25 } 25 26 } 26 - pub fn from_lofty_file_type(lofty_type: lofty::file::FileType) -> UniResult<Self> { 27 + pub fn from_lofty_file_type(lofty_type: lofty::file::FileType) -> Result<Self> { 27 28 match lofty_type { 28 29 lofty::file::FileType::Mpeg => Ok(FileType::Mp3), 29 30 lofty::file::FileType::Mp4 => Ok(FileType::M4a), 30 31 lofty::file::FileType::Opus => Ok(FileType::Opus), 31 - _ => throw!("Unsupported file type {:?}", lofty_type), 32 + _ => bail!("Unsupported file type {:?}", lofty_type), 32 33 } 33 34 } 34 35 pub fn file_extension(&self) -> &'static str { ··· 45 46 } 46 47 } 47 48 48 - pub fn read_file_metadata(path: &Path) -> UniResult<fs::Metadata> { 49 + pub fn read_file_metadata(path: &Path) -> Result<fs::Metadata> { 49 50 match std::fs::metadata(path) { 50 51 Ok(file_md) => Ok(file_md), 51 52 Err(err) => match err.kind() { 52 - std::io::ErrorKind::NotFound => throw!("File does not exist"), 53 - _ => throw!("Unable to access file: {}", err), 53 + std::io::ErrorKind::NotFound => bail!("File does not exist"), 54 + _ => bail!("Unable to access file: {}", err), 54 55 }, 55 56 } 56 57 } 57 58 58 - pub fn import(data: &Data, track_path: &Path, now: i64) -> UniResult<Track> { 59 + pub fn import(data: &Data, track_path: &Path, now: i64) -> Result<Track> { 59 60 let file_md = read_file_metadata(track_path)?; 60 61 61 62 let mut date_modified = match file_md.modified() { ··· 63 64 Err(_) => now, 64 65 }; 65 66 66 - let probe = match lofty::probe::Probe::open(track_path) { 67 - Ok(f) => { 68 - let parse_options = lofty::config::ParseOptions::new() 69 - .read_properties(true) 70 - .parsing_mode(lofty::config::ParsingMode::Strict); 71 - f.options(parse_options) 72 - } 73 - Err(e) => throw!("File does not exist: {}", e), 74 - }; 75 - let mut tagged_file = match probe.read() { 76 - Ok(f) => f, 77 - Err(e) => throw!("Unable to read file: {}", e), 78 - }; 67 + let parse_options = lofty::config::ParseOptions::new() 68 + .read_properties(true) 69 + .parsing_mode(lofty::config::ParsingMode::Strict); 70 + let probe = lofty::probe::Probe::open(track_path) 71 + .context("File does not exist")? 72 + .options(parse_options); 73 + let mut tagged_file = probe.read().context("Unable to read file")?; 79 74 let properties = tagged_file.properties().clone(); 80 75 81 76 let mut tag_changed = false; ··· 108 103 let filename = generate_filename(tracks_dir, &artist, &title, extension); 109 104 let dest_path = tracks_dir.join(&filename); 110 105 111 - match fs::copy(track_path, &dest_path) { 112 - Ok(_) => (), 113 - Err(e) => throw!("Error copying file: {e}"), 114 - }; 106 + fs::copy(track_path, &dest_path).context("Error copying file")?; 115 107 println!( 116 108 "{} -> {}", 117 109 track_path.to_string_lossy(), ··· 119 111 ); 120 112 121 113 if tag_changed { 122 - println!("Writing:::::::"); 114 + println!("Writing tag to imported file"); 123 115 match tag.save_to_path(&dest_path, lofty::config::WriteOptions::default()) { 124 116 Ok(_) => (), 125 - Err(e) => throw!("Unable to tag file {}: {e}", dest_path.to_string_lossy()), 117 + Err(e) => bail!("Unable to tag file {}: {e}", dest_path.to_string_lossy()), 126 118 }; 127 119 // manually set date_modified because the date_modified doens't seem to 128 120 // immediately update after tag.write_to_path().
+12 -40
src-native/tracks/md.rs
··· 1 1 #![allow(non_snake_case)] 2 2 3 - use super::tag::SetInfoError; 4 3 use super::{generate_filename, Tag}; 5 4 use crate::library_types::Track; 6 - use crate::{get_now_timestamp, str_to_option, UniResult}; 5 + use crate::{get_now_timestamp, str_to_option}; 6 + use anyhow::{bail, Context, Result}; 7 7 use serde::{Deserialize, Serialize}; 8 8 use std::fs; 9 9 use std::path::PathBuf; ··· 36 36 track: &mut Track, 37 37 tag: &mut Tag, 38 38 new_info: TrackMD, 39 - ) -> UniResult<()> { 39 + ) -> Result<()> { 40 40 let old_path = tracks_dir.join(&track.file); 41 41 if !old_path.exists() { 42 - throw!("File does not exist: {}", track.file); 42 + bail!("File does not exist: {}", track.file); 43 43 } 44 44 let ext = old_path.extension().unwrap_or_default().to_string_lossy(); 45 45 ··· 95 95 // year 96 96 let new_year_i32 = match new_info.year.as_ref() { 97 97 "" => None, 98 - value => match value.parse() { 99 - Ok(n) => Some(n), 100 - Err(_) => throw!("Invalid year"), 101 - }, 98 + value => Some(value.parse().context("Invalid year")?), 102 99 }; 103 100 let new_year_i64 = new_year_i32.map(i64::from); 104 101 match new_year_i32 { ··· 109 106 // track_number, track_count 110 107 let new_track_number: Option<u32> = match new_info.trackNum.as_ref() { 111 108 "" => None, 112 - value => match value.parse() { 113 - Ok(n) => Some(n), 114 - Err(_) => throw!("Invalid track number"), 115 - }, 109 + value => Some(value.parse().context("Invalid track number")?), 116 110 }; 117 111 let new_track_count: Option<u32> = match new_info.trackCount.as_ref() { 118 112 "" => None, 119 - value => match value.parse() { 120 - Ok(n) => Some(n), 121 - Err(_) => throw!("Invalid track count"), 122 - }, 113 + value => Some(value.parse().context("Invalid track count")?), 123 114 }; 124 - match tag.set_track_info(new_track_number, new_track_count) { 125 - Ok(()) => {} 126 - // don't set tag at all if number is required 127 - Err(SetInfoError::NumberRequired) => tag.set_track_info(None, None)?, 128 - Err(e) => Err(e)?, 129 - } 115 + tag.set_track_info(new_track_number, new_track_count); 130 116 131 117 // disc_number, disc_count 132 118 let new_disc_number: Option<u32> = match new_info.discNum.as_ref() { 133 119 "" => None, 134 - value => match value.parse() { 135 - Ok(n) => Some(n), 136 - Err(_) => throw!("Invalid disc number"), 137 - }, 120 + value => Some(value.parse().context("Invalid disc number")?), 138 121 }; 139 122 let new_disc_count: Option<u32> = match new_info.discCount.as_ref() { 140 123 "" => None, 141 - value => match value.parse() { 142 - Ok(n) => Some(n), 143 - Err(_) => throw!("Invalid disc count"), 144 - }, 124 + value => Some(value.parse().context("Invalid disc count")?), 145 125 }; 146 - match tag.set_disc_info(new_disc_number, new_disc_count) { 147 - Ok(()) => {} 148 - // don't set tag at all if number is required 149 - Err(SetInfoError::NumberRequired) => tag.set_disc_info(None, None)?, 150 - Err(e) => Err(e)?, 151 - }; 126 + tag.set_disc_info(new_disc_number, new_disc_count); 152 127 153 128 let new_bpm: Option<u16> = match new_info.bpm.as_ref() { 154 129 "" => None, 155 - value => match value.parse() { 156 - Ok(n) => Some(n), 157 - Err(_) => throw!("Invalid bpm"), 158 - }, 130 + value => Some(value.parse().context("Invalid bpm")?), 159 131 }; 160 132 match new_bpm { 161 133 None => tag.remove_bpm(),
+17 -19
src-native/tracks/mod.rs
··· 2 2 use crate::data_js::get_data; 3 3 use crate::get_now_timestamp; 4 4 use crate::library_types::{ItemId, MsSinceUnixEpoch, Track, TrackID, TRACK_ID_MAP}; 5 - use napi::{Env, JsArrayBuffer, JsBuffer, JsObject, Result, Task}; 5 + use anyhow::{anyhow, bail, Context, Result}; 6 + use napi::{Env, JsArrayBuffer, JsBuffer, JsObject, Task}; 6 7 use std::fs; 7 8 use std::path::{Path, PathBuf}; 8 9 ··· 102 103 pub fn add_play_time(id: TrackID, start: MsSinceUnixEpoch, dur_ms: i64, env: Env) -> Result<()> { 103 104 let data: &mut Data = get_data(&env)?; 104 105 let tracks = data.library.get_tracks(); 105 - tracks.get(&id).ok_or(nerr!("Track ID not found"))?; 106 + tracks.get(&id).context("Track ID not found")?; 106 107 data.library.playTime.push((id, start, dur_ms)); 107 108 Ok(()) 108 109 } ··· 112 113 impl Task for ReadCover { 113 114 type Output = Vec<u8>; 114 115 type JsValue = JsBuffer; 115 - fn compute(&mut self) -> Result<Self::Output> { 116 + fn compute(&mut self) -> napi::Result<Self::Output> { 116 117 let path = &self.0; 117 118 let index = self.1; 118 119 ··· 120 121 let image = match tag.get_image_consume(index)? { 121 122 Some(image) => image, 122 123 None => { 123 - return Err(nerr!("No image")); 124 + return Err(anyhow!("No image").into()); 124 125 } 125 126 }; 126 127 127 128 Ok(image.data) 128 129 } 129 - fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> { 130 + fn resolve(&mut self, env: Env, output: Self::Output) -> napi::Result<Self::JsValue> { 130 131 let result = env.create_buffer_copy(output)?; 131 132 return Ok(result.into_raw()); 132 133 } 133 134 } 134 135 #[napi(js_name = "read_cover_async", ts_return_type = "Promise<ArrayBuffer>")] 135 136 #[allow(dead_code)] 136 - pub fn read_cover_async(track_id: String, index: u16, env: Env) -> Result<JsObject> { 137 + pub fn read_cover_async(track_id: String, index: u16, env: Env) -> napi::Result<JsObject> { 137 138 let data: &mut Data = get_data(&env)?; 138 139 let track = id_to_track(&env, &track_id)?; 139 140 let tracks_dir = &data.paths.tracks_dir; ··· 239 240 pub fn set_image(index: u32, path_str: String, env: Env) -> Result<()> { 240 241 let data: &mut Data = get_data(&env)?; 241 242 let path = data.paths.tracks_dir.join(path_str); 242 - match &mut data.current_tag { 243 - Some(tag) => { 244 - let new_bytes = match fs::read(&path) { 245 - Ok(b) => b, 246 - Err(e) => throw!("Error reading that file: {}", e), 247 - }; 248 - tag.set_image(index as usize, new_bytes)?; 249 - } 250 - None => throw!("No tag loaded"), 243 + let tag = match &mut data.current_tag { 244 + Some(tag) => tag, 245 + None => bail!("No tag loaded"), 251 246 }; 247 + let new_bytes = fs::read(&path).context("Error reading that file")?; 248 + tag.set_image(index as usize, new_bytes)?; 252 249 Ok(()) 253 250 } 254 251 ··· 257 254 pub fn set_image_data(index: u32, bytes: JsArrayBuffer, env: Env) -> Result<()> { 258 255 let bytes: Vec<u8> = bytes.into_value()?.to_vec(); 259 256 let data: &mut Data = get_data(&env)?; 260 - match &mut data.current_tag { 261 - Some(tag) => tag.set_image(index as usize, bytes)?, 262 - None => throw!("No tag loaded"), 257 + let tag = match &mut data.current_tag { 258 + Some(tag) => tag, 259 + None => bail!("No tag loaded"), 263 260 }; 261 + tag.set_image(index as usize, bytes)?; 264 262 Ok(()) 265 263 } 266 264 ··· 285 283 286 284 let tag = match &mut data.current_tag { 287 285 Some(tag) => tag, 288 - None => throw!("No tag loaded"), 286 + None => bail!("No tag loaded"), 289 287 }; 290 288 md::update_track_info(&data.paths.tracks_dir, track, tag, info)?; 291 289
+23 -78
src-native/tracks/tag.rs
··· 1 - use crate::{UniError, UniResult}; 1 + use anyhow::{bail, Context, Result}; 2 2 use lofty::picture::{MimeType, Picture}; 3 3 use lofty::tag::ItemKey; 4 4 use lofty::{file::TaggedFileExt, tag::Accessor, tag::TagExt}; 5 5 use std::io::Cursor; 6 6 use std::path::{Path, PathBuf}; 7 7 8 - pub enum SetInfoError { 9 - NumberRequired, 10 - Other(String), 11 - } 12 - impl ToString for SetInfoError { 13 - fn to_string(self: &SetInfoError) -> String { 14 - match self { 15 - SetInfoError::NumberRequired => "Number required".to_string(), 16 - SetInfoError::Other(s) => s.to_string(), 17 - } 18 - } 19 - } 20 - impl From<SetInfoError> for napi::Error { 21 - fn from(err: SetInfoError) -> napi::Error { 22 - napi::Error::from_reason(err.to_string()) 23 - } 24 - } 25 - impl From<SetInfoError> for UniError { 26 - fn from(err: SetInfoError) -> UniError { 27 - err.to_string().into() 28 - } 29 - } 30 - 31 8 pub struct Image { 32 9 pub data: Vec<u8>, 33 10 } ··· 43 20 tag: lofty::tag::Tag, 44 21 } 45 22 impl Tag { 46 - pub fn read_from_path(path: &PathBuf) -> UniResult<Tag> { 23 + pub fn read_from_path(path: &PathBuf) -> Result<Tag> { 47 24 if !path.exists() { 48 - throw!("File does not exist: {}", path.to_string_lossy()); 25 + bail!("File does not exist: {}", path.to_string_lossy()); 49 26 } 50 27 let ext = path.extension().unwrap_or_default().to_string_lossy(); 51 28 52 29 let tag = match ext.as_ref() { 53 30 "mp3" | "m4a" | "opus" => { 54 - let probe = match lofty::probe::Probe::open(path) { 55 - Ok(f) => { 56 - let parse_options = lofty::config::ParseOptions::new() 57 - .read_properties(false) 58 - .parsing_mode(lofty::config::ParsingMode::Strict); 59 - f.options(parse_options) 60 - } 61 - Err(e) => throw!("File does not exist: {}", e), 62 - }; 31 + let parse_options = lofty::config::ParseOptions::new() 32 + .read_properties(false) 33 + .parsing_mode(lofty::config::ParsingMode::Strict); 34 + let probe = lofty::probe::Probe::open(path) 35 + .context("File does not exist")? 36 + .options(parse_options); 63 37 64 - let mut tagged_file = match probe.read() { 65 - Ok(f) => f, 66 - Err(e) => throw!("Unable to read file: {}", e), 67 - }; 38 + let mut tagged_file = probe.read().context("Unable to read file")?; 68 39 69 40 let tag = match tagged_file.remove(tagged_file.primary_tag_type()) { 70 41 Some(t) => t.clone(), ··· 73 44 74 45 Tag { tag } 75 46 } 76 - _ => throw!("Unsupported file extension: {}", ext), 47 + _ => bail!("Unsupported file extension: {}", ext), 77 48 }; 78 49 Ok(tag) 79 50 } 80 - pub fn write_to_path(&mut self, path: &Path) -> UniResult<()> { 81 - match self 82 - .tag 51 + pub fn write_to_path(&mut self, path: &Path) -> Result<()> { 52 + self.tag 83 53 .save_to_path(path, lofty::config::WriteOptions::default()) 84 - { 85 - Ok(_) => {} 86 - Err(e) => throw!("Unable to tag file: {}", e), 87 - }; 88 - Ok(()) 54 + .context("Unable to tag file") 89 55 } 90 56 pub fn remove_title(&mut self) { 91 57 self.tag.remove_title() ··· 143 109 let u = if value < 0 { 0 } else { value as u32 }; 144 110 self.tag.set_year(u); 145 111 } 146 - /// For some tag types, `total` cannot exist without `number`. In those 147 - /// cases, `total` is assumed to be `None`. 148 - pub fn set_track_info( 149 - &mut self, 150 - number: Option<u32>, 151 - total: Option<u32>, 152 - ) -> Result<(), SetInfoError> { 112 + pub fn set_track_info(&mut self, number: Option<u32>, total: Option<u32>) -> () { 153 113 match number { 154 114 Some(number) => self.tag.set_track(number), 155 115 None => self.tag.remove_track(), ··· 158 118 Some(total) => self.tag.set_track_total(total), 159 119 None => self.tag.remove_track_total(), 160 120 } 161 - 162 - Ok(()) 163 121 } 164 - /// For some tag types, `total` cannot exist without `number`. In those 165 - /// cases, `total` is assumed to be `None`. 166 - pub fn set_disc_info( 167 - &mut self, 168 - number: Option<u32>, 169 - total: Option<u32>, 170 - ) -> Result<(), SetInfoError> { 122 + pub fn set_disc_info(&mut self, number: Option<u32>, total: Option<u32>) -> () { 171 123 match number { 172 124 Some(number) => self.tag.set_disk(number), 173 125 None => self.tag.remove_disk(), ··· 176 128 Some(total) => self.tag.set_disk_total(total), 177 129 None => self.tag.remove_disk_total(), 178 130 } 179 - 180 - Ok(()) 181 131 } 182 132 pub fn remove_bpm(&mut self) { 183 133 self.tag.remove_key(&ItemKey::Bpm); ··· 195 145 pub fn set_comment(&mut self, value: &str) { 196 146 self.tag.set_comment(value.to_string()) 197 147 } 198 - pub fn set_image(&mut self, index: usize, data: Vec<u8>) -> UniResult<()> { 148 + pub fn set_image(&mut self, index: usize, data: Vec<u8>) -> Result<()> { 199 149 let mut reader = Cursor::new(data); 200 - let picture = match lofty::picture::Picture::from_reader(&mut reader) { 201 - Ok(picture) => picture, 202 - Err(e) => throw!("Unable to read picture: {}", e), 203 - }; 150 + let picture = 151 + lofty::picture::Picture::from_reader(&mut reader).context("Unable to read picture")?; 204 152 match picture.mime_type() { 205 153 Some(lofty::picture::MimeType::Png | lofty::picture::MimeType::Jpeg) => { 206 154 self.tag.set_picture(index, picture); 207 155 } 208 - _ => throw!("Unsupported picture type"), 156 + _ => bail!("Unsupported picture type"), 209 157 } 210 158 211 159 Ok(()) 212 160 } 213 - pub fn get_image_ref(&self, index: usize) -> UniResult<Option<ImageRef>> { 161 + pub fn get_image_ref(&self, index: usize) -> Result<Option<ImageRef>> { 214 162 let pictures = self.tag.pictures(); 215 163 match pictures.get(index) { 216 164 Some(pic) => { ··· 219 167 index: index.try_into().expect("usize conv"), 220 168 total_images: pictures.len().try_into().expect("usize conv"), 221 169 data, 222 - mime_type: match pic.mime_type() { 223 - Some(mime_type) => mime_type.clone(), 224 - _ => throw!("No mime type"), 225 - }, 170 + mime_type: pic.mime_type().context("No mime type")?.clone(), 226 171 })) 227 172 } 228 173 None => Ok(None), 229 174 } 230 175 } 231 - pub fn get_image_consume(mut self, index: usize) -> UniResult<Option<Image>> { 176 + pub fn get_image_consume(mut self, index: usize) -> Result<Option<Image>> { 232 177 if self.tag.picture_count() <= index.try_into().expect("usize conv") { 233 178 return Ok(None); 234 179 }
+7 -12
src-native/view_options.rs
··· 1 1 use crate::data::Data; 2 2 use crate::data_js::get_data; 3 3 use crate::library::Paths; 4 - use crate::{path_to_json, UniResult}; 4 + use crate::path_to_json; 5 + use anyhow::{Context, Result}; 5 6 use atomicwrites::AtomicFile; 6 7 use atomicwrites::OverwriteBehavior::AllowOverwrite; 7 - use napi::{Env, Result}; 8 + use napi::Env; 8 9 use serde::{Deserialize, Serialize}; 9 10 use std::io::Write; 10 11 ··· 25 26 }, 26 27 } 27 28 } 28 - pub fn save(&self, paths: &Paths) -> UniResult<()> { 29 - let json_str = match serde_json::to_string(self) { 30 - Ok(json_str) => json_str, 31 - Err(_) => throw!("Error saving view.json"), 32 - }; 29 + pub fn save(&self, paths: &Paths) -> Result<()> { 30 + let json_str = serde_json::to_string(self).context("Error saving view.json")?; 33 31 let file_path = paths.local_data_dir.join("view.json"); 34 32 let af = AtomicFile::new(file_path, AllowOverwrite); 35 - let result = af.write(|f| f.write_all(json_str.as_bytes())); 36 - match result { 37 - Ok(_) => {} 38 - Err(_) => throw!("Error writing view.json"), 39 - }; 33 + af.write(|f| f.write_all(json_str.as_bytes())) 34 + .context("Error writing view.json")?; 40 35 Ok(()) 41 36 } 42 37 }