diff --git a/Cargo.toml b/Cargo.toml index 7d4a5ea..9643d8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ alphanumeric-sort = "1.5" linked-hash-map = { version = "0.5", features = ["serde_impl"] } atomicwrites = "0.4" plist = "1.5" -tokio = { version = "1.28", features = ["macros"] } +tokio = { version = "1.28", features = ["macros", "rt-multi-thread", "sync"] } time = { version = "0.3", features = ["serde", "serde-well-known"] } url = "2.4" unicode-normalization = "0.1" diff --git a/src-native/data.rs b/src-native/data.rs index 6e81a4e..9baffec 100644 --- a/src-native/data.rs +++ b/src-native/data.rs @@ -1,10 +1,9 @@ use crate::library::{Paths, load_library_json, open_library}; use crate::library_types::Library; use crate::tracks::Tag; -use anyhow::Context; +use anyhow::{Context, Result}; use atomicwrites::{AllowOverwrite, AtomicFile}; use dirs_next; -use napi::Result; use serde::Serialize; use sqlx::SqliteConnection; use std::env; @@ -71,7 +70,7 @@ impl Data { println!("Write: {}ms", now.elapsed().as_millis()); Ok(()) } - pub fn load( + pub async fn load( is_dev: bool, local_data_path: Option, library_path: Option, @@ -121,7 +120,7 @@ impl Data { logs_dir: path_to_string(app_log_dir()?), }; - let library_sqlite = open_library(&paths)?; + let library_sqlite = open_library(&paths).await?; let loaded_library_json = load_library_json(&library_dir.join("Library.json"))?.unwrap_or(Library::new()); diff --git a/src-native/data_js.rs b/src-native/data_js.rs index a240d79..9fdbd5c 100644 --- a/src-native/data_js.rs +++ b/src-native/data_js.rs @@ -1,7 +1,8 @@ use crate::data::{Data, app_log_dir, path_to_string}; use crate::library::Paths; -use napi::Result; +use anyhow::{Context, Result}; use std::fs; +use tokio::runtime::Runtime; #[napi(js_name = "load_data")] #[allow(dead_code)] @@ -27,7 +28,10 @@ pub fn load_data( fs::write(&file_path, log_msg).expect("Could not save crash log"); println!("Crash message written to {}", file_path.to_string_lossy()); })); - Data::load(is_dev, local_data_path, library_path)?; + let rt = Runtime::new().context("Error creating tokio runtime")?; + + rt.block_on(Data::load(is_dev, local_data_path, library_path))?; + return Ok(()); } diff --git a/src-native/itunes_import.rs b/src-native/itunes_import.rs index 17d2652..d9db993 100644 --- a/src-native/itunes_import.rs +++ b/src-native/itunes_import.rs @@ -568,7 +568,7 @@ fn import_playlist( id } -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct ImportStatus { pub errors: Vec, pub tracks_count: i64, diff --git a/src-native/lib.rs b/src-native/lib.rs index f4c4f20..a92e4fd 100644 --- a/src-native/lib.rs +++ b/src-native/lib.rs @@ -1,11 +1,7 @@ -#[cfg(feature = "napi-rs")] use anyhow::{Context, Result}; use mimalloc::MiMalloc; -#[cfg(feature = "napi-rs")] use serde::de::DeserializeOwned; -#[cfg(feature = "napi-rs")] use std::fs::File; -#[cfg(feature = "napi-rs")] use std::io::BufReader; use std::time::{SystemTime, UNIX_EPOCH}; @@ -13,31 +9,28 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[macro_use] extern crate napi_derive; -#[cfg(feature = "napi-rs")] mod data; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] mod data_js; pub mod db; pub mod filter; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] mod itunes_import; pub mod library; pub mod library_types; -#[cfg(feature = "napi-rs")] mod migrate; pub mod page; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] pub mod playlists; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] mod queue_state; pub mod sort; -#[cfg(feature = "napi-rs")] mod tracks; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] mod view_options; fn get_now_timestamp() -> i64 { @@ -48,7 +41,6 @@ fn get_now_timestamp() -> i64 { return timestamp; } -#[cfg(feature = "napi-rs")] fn sys_time_to_timestamp(sys_time: &SystemTime) -> i64 { let timestamp = match sys_time.duration_since(UNIX_EPOCH) { Ok(n) => n.as_millis() as i64, @@ -57,7 +49,6 @@ fn sys_time_to_timestamp(sys_time: &SystemTime) -> i64 { return timestamp; } -#[cfg(feature = "napi-rs")] fn str_to_option(s: String) -> Option { match s.as_str() { "" => None, @@ -65,7 +56,6 @@ fn str_to_option(s: String) -> Option { } } -#[cfg(feature = "napi-rs")] fn path_to_json(path: &str) -> Result where J: DeserializeOwned, diff --git a/src-native/library.rs b/src-native/library.rs index 1bed9f3..fcabb15 100644 --- a/src-native/library.rs +++ b/src-native/library.rs @@ -1,26 +1,19 @@ -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] use crate::data::Data; use crate::library_types::{ItemId, Library, SpecialTrackListName, TrackList, VersionedLibrary}; -#[cfg(feature = "napi-rs")] use crate::migrate::migrate_to_sqlite; use anyhow::{Context, Result, bail}; use linked_hash_map::LinkedHashMap; use serde_json::{Value, json}; -#[cfg(feature = "napi-rs")] use sqlx::{ConnectOptions, SqliteConnection, sqlite::SqliteConnectOptions}; use std::fs::File; use std::io::{ErrorKind, Read, Seek, SeekFrom}; use std::path::PathBuf; -#[cfg(feature = "napi-rs")] use std::time::Instant; -#[cfg(feature = "napi-rs")] use std::{fs::create_dir_all, path::Path}; -#[cfg(feature = "napi-rs")] -use tokio::runtime::Runtime; -#[cfg(feature = "napi-rs")] #[derive(Clone)] -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct Paths { pub path_separator: String, pub library_dir: String, @@ -34,7 +27,6 @@ pub struct Paths { pub queue_file: String, pub logs_dir: String, } -#[cfg(feature = "napi-rs")] impl Paths { fn ensure_dirs_exists(&self) -> Result<()> { create_dir_all(&self.library_dir)?; @@ -106,8 +98,7 @@ fn parse_old_versionless_library_json(library_file: &mut File) -> Result Result { +pub async fn open_library(paths: &Paths) -> Result { let now = Instant::now(); paths @@ -117,21 +108,19 @@ pub fn open_library(paths: &Paths) -> Result { let library_sqlite = &paths.library_sqlite; - let rt = Runtime::new().context("Error creating tokio runtime")?; - let exists = Path::new(&library_sqlite).exists(); if !exists { - rt.block_on(migrate_to_sqlite(paths))?; + migrate_to_sqlite(paths).await?; } - let mut connection = rt - .block_on( - SqliteConnectOptions::new() - .filename(&paths.library_sqlite) - .connect(), - ) + let mut connection = SqliteConnectOptions::new() + .filename(&paths.library_sqlite) + .connect() + .await .context("Error connecting to library database")?; - rt.block_on(sqlx::migrate!("./src-native/migrations").run(&mut connection)) + sqlx::migrate!("./src-native/migrations") + .run(&mut connection) + .await .map_err(|e| anyhow::anyhow!("{:?}", e)) .context("Could not run database migrations")?; @@ -149,7 +138,7 @@ pub enum TrackField { Bool, } -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[napi(js_name = "get_default_sort_desc")] #[allow(dead_code)] pub fn get_default_sort_desc(field: String) -> Result { @@ -208,7 +197,7 @@ pub fn get_track_field_type(field: &str) -> Result { return Ok(field); } -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[napi(js_name = "get_genres")] #[allow(dead_code)] pub fn get_genres() -> Vec { @@ -217,7 +206,7 @@ pub fn get_genres() -> Vec { genres.clone() } -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[napi(js_name = "get_artists")] #[allow(dead_code)] pub fn get_artists() -> Vec { diff --git a/src-native/library_types.rs b/src-native/library_types.rs index 1d676f5..9700c9f 100644 --- a/src-native/library_types.rs +++ b/src-native/library_types.rs @@ -1,9 +1,9 @@ #![allow(non_snake_case)] use crate::get_now_timestamp; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] use crate::library::Paths; -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] use crate::playlists::{delete_file, remove_from_all_playlists}; use anyhow::{Context, Result, bail}; use linked_hash_map::{Entry, LinkedHashMap}; @@ -141,7 +141,7 @@ impl Library { track_id_map.push(id.clone()); self.track_item_ids.insert(id, item_id); } - #[cfg(feature = "napi-rs")] + #[cfg(feature = "napi")] pub fn delete_track_and_file(&mut self, id: &TrackID, paths: &Paths) -> Result<()> { let file_path = { let track = self.get_track(id)?; diff --git a/src-native/page.rs b/src-native/page.rs index fca16a1..8df481d 100644 --- a/src-native/page.rs +++ b/src-native/page.rs @@ -1,15 +1,12 @@ -#[cfg(feature = "napi-rs")] use crate::data::Data; -use crate::db::{TrackListID, TrackListKind}; +use crate::db::TrackListKind; use crate::library::Paths; -#[cfg(feature = "napi-rs")] use crate::library_types::new_item_ids_from_track_ids; use crate::library_types::{ItemId, SpecialTrackListName}; use anyhow::Result; use futures_util::TryStreamExt; use serde::{Deserialize, Serialize}; use specta::Type; -#[cfg(feature = "napi-rs")] use sqlx::Connection; use sqlx::Row; use std::path::Path; @@ -481,7 +478,7 @@ fn search_tantivy( }; let order = if sort_desc { Order::Desc } else { Order::Asc }; - let limit = searcher.num_docs() as usize; + let limit = searcher.num_docs().try_into().unwrap(); let top_docs = TopDocs::with_limit(limit); println!("prepare search took {:?}", start_time.elapsed()); @@ -585,9 +582,13 @@ fn search_tantivy( Ok(track_ids) } -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[cfg_attr(feature = "napi", napi(js_name = "get_tracks_page"))] #[allow(dead_code)] +pub async fn get_tracks_page_js(options: TracksPageOptions) -> Result { + get_tracks_page(options).await +} + pub async fn get_tracks_page(options: TracksPageOptions) -> Result { let mut data = Data::get_async().await; let paths = data.paths.clone(); @@ -642,3 +643,33 @@ pub async fn get_tracks_page(options: TracksPageOptions) -> Result { }; Ok(tracks_page) } + +#[cfg(test)] +mod tests { + use crate::{ + data::Data, + library_types::SpecialTrackListName, + page::{TracksPageOptions, get_tracks_page}, + }; + use std::path::PathBuf; + + #[tokio::test] + async fn test_get_tracks_page() -> anyhow::Result<()> { + let library_path = PathBuf::from("./src-native/appdata/Library big"); + Data::load(true, None, Some(library_path.to_string_lossy().to_string())) + .await + .unwrap(); + let result = get_tracks_page(TracksPageOptions { + playlist_id: SpecialTrackListName::Root.get_id().to_string(), + sort_key: "name".to_string(), + sort_desc: false, + filter_query: "".to_string(), + group_album_tracks: false, + }) + .await?; + + println!("result: {:#?}", result.item_ids.len()); + + Ok(()) + } +} diff --git a/src-native/playlists.rs b/src-native/playlists.rs index 6ef935a..7524e4e 100644 --- a/src-native/playlists.rs +++ b/src-native/playlists.rs @@ -13,7 +13,7 @@ use std::path::PathBuf; #[cfg(target_os = "macos")] use trash::macos::TrashContextExtMacos; -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct TrackListDetails { pub id: String, pub name: String, diff --git a/src-native/queue_state.rs b/src-native/queue_state.rs index 8a60b5d..8fa3a47 100644 --- a/src-native/queue_state.rs +++ b/src-native/queue_state.rs @@ -6,7 +6,7 @@ use std::fs; use std::io::Write; #[derive(Serialize, Deserialize, Debug, Clone, Default)] -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct QueueItemState { #[serde(rename = "qId")] #[napi(js_name = "qId")] @@ -17,7 +17,7 @@ pub struct QueueItemState { } #[derive(Serialize, Deserialize, Debug, Clone, Default)] -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct QueueCurrentState { pub item: QueueItemState, #[napi(js_name = "from_auto_queue")] @@ -25,7 +25,7 @@ pub struct QueueCurrentState { } #[derive(Serialize, Deserialize, Debug, Clone, Default)] -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct QueueState { #[serde(default)] pub past: Vec, diff --git a/src-native/tracks/cover.rs b/src-native/tracks/cover.rs index 277e2fa..948a26c 100644 --- a/src-native/tracks/cover.rs +++ b/src-native/tracks/cover.rs @@ -5,6 +5,7 @@ use fast_image_resize::{IntoImageView, Resizer}; use image::codecs::jpeg::JpegEncoder; use image::codecs::png::PngEncoder; use image::{ImageEncoder, ImageFormat, ImageReader}; +#[cfg(feature = "napi")] use napi::bindgen_prelude::Buffer; use redb::{Database, TableDefinition}; use std::fs; @@ -52,7 +53,7 @@ fn init_cache_db(path: String) -> Result<()> { Ok(()) } -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[napi(js_name = "close_cache_db")] #[allow(dead_code)] pub async fn close_cache_db() -> napi::Result<()> { @@ -136,7 +137,7 @@ fn write_to_cache(cache_db: &Database, path: &str, value: CacheEntry) -> Result< } /// Returns `None` if the file does not have an image -#[cfg(feature = "napi-rs")] +#[cfg(feature = "napi")] #[napi(js_name = "read_small_cover_async")] #[allow(dead_code)] pub async fn read_small_cover_async( @@ -238,6 +239,7 @@ fn to_resized_image(image_bytes: Vec, max_size: u32) -> Result> { Ok(img_bytes) } +#[cfg(feature = "napi")] #[cfg_attr(feature = "napi-rs", napi(js_name = "read_cover_async"))] #[allow(dead_code)] pub async fn read_cover_async(file_path: String, index: u16) -> Result> { diff --git a/src-native/tracks/mod.rs b/src-native/tracks/mod.rs index 9325e2a..9bb4c97 100644 --- a/src-native/tracks/mod.rs +++ b/src-native/tracks/mod.rs @@ -3,6 +3,7 @@ use crate::get_now_timestamp; use crate::library::Paths; use crate::library_types::{ItemId, MsSinceUnixEpoch, TRACK_ID_MAP, Track, TrackID}; use anyhow::{Context, Result, bail}; +#[cfg(feature = "napi")] use napi::bindgen_prelude::{ArrayBuffer, Buffer}; use std::fs; use std::path::Path; @@ -14,7 +15,8 @@ mod tag; pub use tag::Tag; -#[napi(js_name = "get_track")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "get_track"))] #[allow(dead_code)] pub fn get_track(id: String) -> Result { let data = Data::get_blocking(); @@ -22,13 +24,14 @@ pub fn get_track(id: String) -> Result { Ok(track.clone()) } -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct KeyedTrack { pub id: TrackID, pub track: Track, } -#[napi(js_name = "get_track_by_item_id")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "get_track_by_item_id"))] #[allow(dead_code)] pub fn get_track_by_item_id(item_id: ItemId) -> Result { let data = Data::get_blocking(); @@ -41,7 +44,8 @@ pub fn get_track_by_item_id(item_id: ItemId) -> Result { }) } -#[napi(js_name = "get_track_ids")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "get_track_ids"))] #[allow(dead_code)] pub fn get_track_ids(item_ids: Vec) -> Vec { let id_map = TRACK_ID_MAP.read().unwrap(); @@ -52,7 +56,8 @@ pub fn get_track_ids(item_ids: Vec) -> Vec { track_ids.collect() } -#[napi(js_name = "track_exists")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "track_exists"))] #[allow(dead_code)] pub fn track_exists(id: String) -> bool { let data = Data::get_blocking(); @@ -60,7 +65,8 @@ pub fn track_exists(id: String) -> bool { tracks.contains_key(&id) } -#[napi(js_name = "add_play")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "add_play"))] #[allow(dead_code)] pub fn add_play(track_id: String) -> Result<()> { let mut data = Data::get_blocking(); @@ -77,7 +83,8 @@ pub fn add_play(track_id: String) -> Result<()> { Ok(()) } -#[napi(js_name = "add_skip")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "add_skip"))] #[allow(dead_code)] pub fn add_skip(track_id: String) -> Result<()> { let mut data = Data::get_blocking(); @@ -94,7 +101,8 @@ pub fn add_skip(track_id: String) -> Result<()> { Ok(()) } -#[napi(js_name = "add_play_time")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "add_play_time"))] #[allow(dead_code)] pub fn add_play_time(id: TrackID, start: MsSinceUnixEpoch, dur_ms: i64) -> Result<()> { let mut data = Data::get_blocking(); @@ -142,7 +150,8 @@ pub fn generate_filename(paths: &Paths, artist: &str, title: &str, ext: &str) -> return filename; } -#[napi(js_name = "import_file")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "import_file"))] #[allow(dead_code)] pub fn import_file(path: String, now: MsSinceUnixEpoch) -> Result<()> { let mut data = Data::get_blocking(); @@ -152,7 +161,8 @@ pub fn import_file(path: String, now: MsSinceUnixEpoch) -> Result<()> { Ok(()) } -#[napi(js_name = "load_tags")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "load_tags"))] #[allow(dead_code)] pub fn load_tags(track_id: String) -> Result<()> { let data = &mut *Data::get_blocking(); @@ -168,7 +178,8 @@ pub fn load_tags(track_id: String) -> Result<()> { Ok(()) } -#[napi(object)] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(object))] pub struct JsImage { pub index: i64, pub total_images: i64, @@ -176,7 +187,8 @@ pub struct JsImage { pub data: Buffer, } -#[napi(js_name = "get_image")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "get_image"))] #[allow(dead_code)] pub fn get_image(index: u32) -> Result> { let data = Data::get_blocking(); @@ -202,7 +214,8 @@ pub fn get_image(index: u32) -> Result> { Ok(Some(js_image)) } -#[napi(js_name = "set_image")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "set_image"))] #[allow(dead_code)] pub fn set_image(index: u32, path: String) -> Result<()> { let mut data = Data::get_blocking(); @@ -215,7 +228,8 @@ pub fn set_image(index: u32, path: String) -> Result<()> { Ok(()) } -#[napi(js_name = "set_image_data")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "set_image_data"))] #[allow(dead_code)] pub fn set_image_data(index: u32, bytes: ArrayBuffer) -> Result<()> { let mut data = Data::get_blocking(); @@ -227,7 +241,8 @@ pub fn set_image_data(index: u32, bytes: ArrayBuffer) -> Result<()> { Ok(()) } -#[napi(js_name = "remove_image")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "remove_image"))] #[allow(dead_code)] pub fn remove_image(index: u32) -> () { let mut data = Data::get_blocking(); @@ -239,7 +254,8 @@ pub fn remove_image(index: u32) -> () { }; } -#[napi(js_name = "update_track_info")] +#[cfg(feature = "napi-rs")] +#[cfg_attr(feature = "napi", napi(js_name = "update_track_info"))] #[allow(dead_code)] pub fn update_track_info(track_id: String, info: md::TrackMD) -> Result<()> { let data = &mut *Data::get_blocking(); diff --git a/src-native/view_options.rs b/src-native/view_options.rs index ccfa0c7..455349b 100644 --- a/src-native/view_options.rs +++ b/src-native/view_options.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::io::Write; #[derive(Serialize, Deserialize, Debug, Clone)] -#[napi(object)] +#[cfg_attr(feature = "napi", napi(object))] pub struct ViewOptions { pub shown_playlist_folders: Vec, /// Empty is treated as default