[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.

Remove FerrumStatus

Kasper (Sep 16, 2025, 8:40 AM +0200) 48d674ca 91b36c0b

+41 -79
+2 -7
ferrum-addon/addon.d.ts
··· 23 23 /** Returns the deleted track lists, including folder children */ 24 24 export declare function delete_track_list(id: string): void 25 25 26 - export declare function delete_tracks_with_item_ids(itemIds: Array<ItemId>): FerrumStatus 27 - 28 - export type FerrumStatus = 29 - | { type: 'Ok' } 30 - | { type: 'FileDeletionError', field0: string } 31 - | { type: 'SaveError', field0: string } 26 + export declare function delete_tracks_with_item_ids(itemIds: Array<ItemId>): void 32 27 33 28 export interface Folder { 34 29 id: TrackListID ··· 134 129 135 130 export declare function remove_image(index: number): void 136 131 137 - export declare function save(): FerrumStatus 132 + export declare function save(): void 138 133 139 134 export declare function save_view_options(viewOptions: ViewOptions, filePath: string): Promise<void> 140 135
+3 -6
src-native/data_js.rs
··· 1 - use crate::FerrumStatus; 2 1 use crate::data::Data; 3 2 use crate::library::Paths; 4 3 use anyhow::Result; ··· 66 65 67 66 #[napi(js_name = "save")] 68 67 #[allow(dead_code)] 69 - pub fn save(env: Env) -> FerrumStatus { 68 + pub fn save(env: Env) -> Result<()> { 70 69 let data: &mut Data = get_data(&env); 71 - match data.save() { 72 - Ok(_) => FerrumStatus::Ok, 73 - Err(err) => FerrumStatus::SaveError(err.to_string()), 74 - } 70 + data.save()?; 71 + Ok(()) 75 72 }
-15
src-native/lib.rs
··· 19 19 mod tracks; 20 20 mod view_options; 21 21 22 - #[napi] 23 - pub enum FerrumStatus { 24 - Ok, 25 - FileDeletionError(String), 26 - SaveError(String), 27 - } 28 - impl FerrumStatus { 29 - pub fn is_err(&self) -> bool { 30 - match self { 31 - FerrumStatus::Ok => false, 32 - _ => true, 33 - } 34 - } 35 - } 36 - 37 22 fn get_now_timestamp() -> i64 { 38 23 let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) { 39 24 Ok(n) => n.as_millis() as i64,
+5 -10
src-native/library_types.rs
··· 1 1 #![allow(non_snake_case)] 2 2 3 + use crate::get_now_timestamp; 3 4 use crate::library::Paths; 4 5 use crate::playlists::{delete_file, remove_from_all_playlists}; 5 - use crate::{FerrumStatus, get_now_timestamp}; 6 6 use anyhow::{Context, Result, bail}; 7 7 use linked_hash_map::{Entry, LinkedHashMap}; 8 8 use nanoid::nanoid; ··· 138 138 track_id_map.push(id.clone()); 139 139 self.track_item_ids.insert(id, item_id); 140 140 } 141 - pub fn delete_track_and_file(&mut self, id: &TrackID, paths: &Paths) -> Result<FerrumStatus> { 141 + pub fn delete_track_and_file(&mut self, id: &TrackID, paths: &Paths) -> Result<()> { 142 142 let file_path = { 143 143 let track = self.get_track(id)?; 144 144 paths.get_track_file_path(&track.file) 145 145 }; 146 146 if !file_path.exists() { 147 - return Ok(FerrumStatus::FileDeletionError(format!( 148 - "File does not exist: {}", 149 - file_path.to_string_lossy() 150 - ))); 147 + bail!("File does not exist: {}", file_path.to_string_lossy()); 151 148 } 152 149 153 150 remove_from_all_playlists(self, id); ··· 157 154 self.track_item_ids 158 155 .remove(id) 159 156 .expect("Track ID not found when deleting (2)"); 160 - Ok(match delete_file(&file_path) { 161 - Ok(_) => FerrumStatus::Ok, 162 - Err(err) => FerrumStatus::FileDeletionError(err.to_string()), 163 - }) 157 + delete_file(&file_path)?; 158 + Ok(()) 164 159 } 165 160 pub fn generate_id(&self) -> String { 166 161 let alphabet: [char; 32] = [
+4 -7
src-native/playlists.rs
··· 4 4 ItemId, Library, SpecialTrackListName, TRACK_ID_MAP, TrackID, TrackList, 5 5 get_track_ids_from_item_ids, new_item_ids_from_track_ids, 6 6 }; 7 - use crate::{FerrumStatus, str_to_option}; 7 + use crate::str_to_option; 8 8 use anyhow::{Context, Result, bail}; 9 9 use linked_hash_map::LinkedHashMap; 10 10 use napi::{Env, Unknown}; ··· 216 216 217 217 #[napi(js_name = "delete_tracks_with_item_ids")] 218 218 #[allow(dead_code)] 219 - pub fn delete_tracks_with_item_ids(item_ids: Vec<ItemId>, env: Env) -> Result<FerrumStatus> { 219 + pub fn delete_tracks_with_item_ids(item_ids: Vec<ItemId>, env: Env) -> Result<()> { 220 220 let data: &mut Data = get_data(&env); 221 221 let library = &mut data.library; 222 222 let track_ids = get_track_ids_from_item_ids(&item_ids); 223 223 for track_id in &track_ids { 224 - let status = library.delete_track_and_file(track_id, &data.paths)?; 225 - if status.is_err() { 226 - return Ok(status); 227 - } 224 + library.delete_track_and_file(track_id, &data.paths)?; 228 225 } 229 - return Ok(FerrumStatus::Ok); 226 + return Ok(()); 230 227 } 231 228 232 229 #[napi(js_name = "new_playlist")]
+27 -34
src/lib/data.ts
··· 12 12 import { queue } from './queue' 13 13 import { current_playlist_id } from '@/components/TrackList.svelte' 14 14 import { navigate } from './router' 15 - import { call_sync, error_popup, get_error_message, strict_call } from './error' 15 + import { call_sync, get_error_message, strict_call } from './error' 16 16 17 17 export const is_dev = window.is_dev 18 18 export const local_data_path = window.local_data_path ··· 80 80 save() 81 81 } 82 82 export function delete_tracks_with_item_ids(item_ids: ItemId[]) { 83 - const result = strict_call((addon) => addon.delete_tracks_with_item_ids(item_ids)) 84 - if (result.type === 'FileDeletionError') { 85 - // Gracefully handle 86 - error_popup(result.field0) 87 - } 88 - tracklist_updated.emit() 89 - queue.removeDeleted() 90 - save() 83 + return call_sync((addon) => addon.delete_tracks_with_item_ids(item_ids)).on_success(() => { 84 + tracklist_updated.emit() 85 + queue.removeDeleted() 86 + save() 87 + }) 91 88 } 92 89 export type PlaylistInfo = { 93 90 name: string ··· 147 144 } 148 145 149 146 export function get_default_sort_desc(field: string) { 150 - return strict_call((data) => data.get_default_sort_desc(field)) 147 + return strict_call((addon) => addon.get_default_sort_desc(field)) 151 148 } 152 149 153 150 export function get_track(id: TrackID) { 154 - return strict_call((data) => data.get_track(id)) 151 + return strict_call((addon) => addon.get_track(id)) 155 152 } 156 153 export function get_track_by_item_id(item_id: ItemId) { 157 - return strict_call((data) => data.get_track_by_item_id(item_id)) 154 + return strict_call((addon) => addon.get_track_by_item_id(item_id)) 158 155 } 159 156 export function get_track_ids(item_ids: ItemId[]) { 160 - return strict_call((data) => data.get_track_ids(item_ids)) 157 + return strict_call((addon) => addon.get_track_ids(item_ids)) 161 158 } 162 159 export function get_tracks_page(options: TracksPageOptions) { 163 - return strict_call((data) => data.get_tracks_page(options)) 160 + return strict_call((addon) => addon.get_tracks_page(options)) 164 161 } 165 162 export function track_exists(id: TrackID) { 166 - return strict_call((data) => data.track_exists(id)) 163 + return strict_call((addon) => addon.track_exists(id)) 167 164 } 168 165 export function get_track_list(id: TrackListID) { 169 - return strict_call((data) => data.get_track_list(id)) 166 + return strict_call((addon) => addon.get_track_list(id)) 170 167 } 171 168 export function delete_track_list(id: TrackListID) { 172 - strict_call((data) => data.delete_track_list(id)) 169 + strict_call((addon) => addon.delete_track_list(id)) 173 170 if (id === get(current_playlist_id)) { 174 171 navigate('/playlist/root') 175 172 } ··· 177 174 save() 178 175 } 179 176 export function save() { 180 - const result = strict_call((addon) => addon.save()) 181 - if (result.type === 'SaveError') { 182 - // Gracefully handle 183 - error_popup(result.field0) 184 - } 177 + return call_sync((addon) => addon.save()) 185 178 } 186 179 export function add_play(id: TrackID) { 187 - return call_sync((data) => data.add_play(id)).on_success(() => { 180 + return call_sync((addon) => addon.add_play(id)).on_success(() => { 188 181 tracklist_updated.emit() 189 182 save() 190 183 }) 191 184 } 192 185 export function add_skip(id: TrackID) { 193 - return call_sync((data) => data.add_skip(id)).on_success(() => { 186 + return call_sync((addon) => addon.add_skip(id)).on_success(() => { 194 187 tracklist_updated.emit() 195 188 save() 196 189 }) 197 190 } 198 191 export function add_play_time(id: TrackID, start_time: MsSinceUnixEpoch, duration_ms: number) { 199 - return call_sync((data) => data.add_play_time(id, start_time, duration_ms)).on_success(() => { 192 + return call_sync((addon) => addon.add_play_time(id, start_time, duration_ms)).on_success(() => { 200 193 save() 201 194 }) 202 195 } ··· 204 197 return inner_addon.read_cover_async(file_path, index) 205 198 } 206 199 export function update_track_info(id: TrackID, md: TrackMd) { 207 - strict_call((data) => data.update_track_info(id, md)) 200 + strict_call((addon) => addon.update_track_info(id, md)) 208 201 tracks_updated.emit() 209 202 save() 210 203 } 211 204 export function load_tags(id: TrackID) { 212 - return call_sync((data) => data.load_tags(id)) 205 + return call_sync((addon) => addon.load_tags(id)) 213 206 } 214 207 export function get_image(index: number) { 215 - return call_sync((data) => data.get_image(index)) 208 + return call_sync((addon) => addon.get_image(index)) 216 209 } 217 210 export function set_image(index: number, path: string) { 218 - return call_sync((data) => data.set_image(index, path)) 211 + return call_sync((addon) => addon.set_image(index, path)) 219 212 } 220 213 export function set_image_data(index: number, bytes: ArrayBuffer) { 221 - return call_sync((data) => data.set_image_data(index, bytes)) 214 + return call_sync((addon) => addon.set_image_data(index, bytes)) 222 215 } 223 216 export function remove_image(index: number) { 224 - return call_sync((data) => data.remove_image(index)) 217 + return call_sync((addon) => addon.remove_image(index)) 225 218 } 226 - export let view_options = strict_call((data) => data.load_view_options()) 219 + export let view_options = strict_call((addon) => addon.load_view_options()) 227 220 export function save_view_options(options: ViewOptions) { 228 221 view_options = options 229 - return call_sync((data) => data.save_view_options(view_options, paths.viewOptionsFile)) 222 + return call_sync((addon) => addon.save_view_options(view_options, paths.viewOptionsFile)) 230 223 } 231 224 232 225 export const filter = writable('') ··· 250 243 return strict_call((addon) => addon.get_genres()) 251 244 } 252 245 export function move_tracks(playlist_id: TrackListID, indexes: ItemId[], to_index: number) { 253 - return call_sync((data) => data.move_tracks(playlist_id, indexes, to_index)).on_success(() => { 246 + return call_sync((addon) => addon.move_tracks(playlist_id, indexes, to_index)).on_success(() => { 254 247 tracklist_updated.emit() 255 248 save() 256 249 })