diff --git a/Cargo.lock b/Cargo.lock index d52c9e3..44c925e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -38,21 +50,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" version = "1.0.93" @@ -248,12 +245,14 @@ dependencies = [ "gtk4", "http-cache", "http-cache-reqwest", + "image", "libadwaita", "oo7", "rand", "reqwest", "reqwest-middleware", "serde", + "serde_json", "tokio", "tracing", "tracing-subscriber", @@ -268,6 +267,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "av-data" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca67ba5d317924c02180c576157afd54babe48a76ebc66ce6d34bb8ba08308e" +dependencies = [ + "byte-slice-cast", + "bytes", + "num-derive", + "num-rational", + "num-traits", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -310,7 +322,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools", @@ -324,12 +336,27 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitreader" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886559b1e163d56c765bc3a985febb4eee8009f625244511d8ee3c432e08c066" +dependencies = [ + "cfg-if", +] + [[package]] name = "block" version = "0.1.6" @@ -373,6 +400,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytemuck" version = "1.19.0" @@ -385,6 +418,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.8.0" @@ -424,7 +463,7 @@ version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7fa699e1d7ae691001a811dda5ef0e3e42e1d4119b26426352989df9e94e3e6" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cairo-sys-rs", "glib", "libc", @@ -438,7 +477,7 @@ checksum = "428290f914b9b86089f60f5d8a9f6e440508e1bcff23b25afd51502b0a2da88f" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -468,6 +507,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-expr" version = "0.17.1" @@ -496,13 +545,8 @@ version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", "num-traits", "serde", - "wasm-bindgen", - "windows-targets", ] [[package]] @@ -596,6 +640,28 @@ dependencies = [ "typenum", ] +[[package]] +name = "dav1d" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4b54a40baf633a71c6f0fb49494a7e4ee7bc26f3e727212b6cb915aa1ea1e1" +dependencies = [ + "av-data", + "bitflags 2.6.0", + "dav1d-sys", + "static_assertions", +] + +[[package]] +name = "dav1d-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ecb1c5e8f4dc438eedc1b534a54672fb0e0a56035dae6b50162787bd2c50e95" +dependencies = [ + "libc", + "system-deps 6.2.2", +] + [[package]] name = "deranged" version = "0.3.11" @@ -706,12 +772,30 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible_collections" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "fdeflate" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +dependencies = [ + "simd-adler32", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -871,7 +955,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -903,7 +987,7 @@ dependencies = [ "libc", "pango-sys", "pkg-config", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -981,7 +1065,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.3", "windows-sys 0.52.0", ] @@ -991,7 +1075,7 @@ version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd3e4ee7998ab5a135d900db56930cc19ad16681adf245daff54f618b9d5e1" dependencies = [ - "bitflags", + "bitflags 2.6.0", "futures-channel", "futures-core", "futures-executor", @@ -1035,7 +1119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b1827e8621fc42c0dfb228e5d57ff6a71f9699e666ece8113f979ad87c2de" dependencies = [ "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -1052,7 +1136,7 @@ checksum = "a4c674d2ff8478cf0ec29d2be730ed779fef54415a2fb4b565c52def62696462" dependencies = [ "glib-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -1075,7 +1159,7 @@ dependencies = [ "glib-sys", "libc", "pkg-config", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -1106,7 +1190,7 @@ dependencies = [ "graphene-sys", "libc", "pango-sys", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -1158,7 +1242,7 @@ dependencies = [ "gsk4-sys", "libc", "pango-sys", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -1180,6 +1264,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.1" @@ -1386,29 +1479,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.52.0", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "icu_collections" version = "1.5.0" @@ -1548,6 +1618,33 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "dav1d", + "image-webp", + "mp4parse", + "num-traits", + "png", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -1555,7 +1652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.1", ] [[package]] @@ -1635,7 +1732,7 @@ dependencies = [ "gtk4-sys", "libc", "pango-sys", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -1776,6 +1873,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -1790,13 +1888,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mp4parse" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a35203d3c6ce92d5251c77520acb2e57108c88728695aa883f70023624c570" +dependencies = [ + "bitreader", + "byteorder", + "fallible_collections", + "log", + "num-traits", + "static_assertions", +] + [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", @@ -1880,6 +1992,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2040,7 +2163,7 @@ dependencies = [ "glib-sys", "gobject-sys", "libc", - "system-deps", + "system-deps 7.0.3", ] [[package]] @@ -2094,6 +2217,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "png" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.7.4" @@ -2152,6 +2288,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quinn" version = "0.11.6" @@ -2397,11 +2539,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2481,9 +2623,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -2494,7 +2636,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2638,6 +2780,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -2735,13 +2883,26 @@ dependencies = [ "syn", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr 0.15.8", + "heck", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "system-deps" version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ - "cfg-expr", + "cfg-expr 0.17.1", "heck", "pkg-config", "toml", @@ -3270,16 +3431,7 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core 0.58.0", - "windows-targets", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ + "windows-core", "windows-targets", ] @@ -3706,6 +3858,21 @@ dependencies = [ "syn", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "4.2.0" diff --git a/Cargo.toml b/Cargo.toml index 878d77b..e6a74d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" adw = { version = "0.7.0", package = "libadwaita", features = ["v1_6"] } async-channel = "2.3.1" bytes = "1.8.0" -chrono = { version = "0.4.38", features = ["serde"] } +chrono = { version = "0.4.38", features = ["serde", "std"], default-features = false } color-thief = "0.2.2" event-listener = "5.3.1" futures = "0.3.31" @@ -19,6 +19,12 @@ http-cache = { version = "0.20.0", default-features = false, features = [ "manager-cacache", ] } http-cache-reqwest = "0.15.0" +image = { version = "0.25.5", default-features = false, features = [ + "avif-native", + "jpeg", + "png", + "webp", +] } oo7 = "0.3.3" rand = "0.8" reqwest = { version = "0.12.9", default-features = false, features = [ @@ -28,8 +34,13 @@ reqwest = { version = "0.12.9", default-features = false, features = [ "http2", "rustls-tls-native-roots", ] } -reqwest-middleware = { version = "0.4.0", features = ["json", "http2", "charset"] } +reqwest-middleware = { version = "0.4.0", features = [ + "json", + "http2", + "charset", +] } serde = { version = "1.0.214", features = ["derive"] } +serde_json = "1.0.133" tokio = { version = "1", features = ["rt-multi-thread"] } tracing = { version = "0.1.40", default-features = false, features = [ "attributes", diff --git a/src/model/song.rs b/src/model/song.rs index e72816e..c872371 100644 --- a/src/model/song.rs +++ b/src/model/song.rs @@ -98,12 +98,12 @@ impl Song { *thumbnail_loading = Some(glib::spawn_future_local(async move { let _permit = SEM.acquire().await.unwrap(); - let bytes = match api - .cover_art(&id, Some(50 * scale_factor)) // see pixel-size in - // play_queue_song.blp + let pixbuf = match api + // see pixel-size in play_queue_song.blp + .cover_art(&id, Some(50 * scale_factor)) .await { - Ok(bytes) => bytes, + Ok(pixbuf) => pixbuf, Err(err) => { event!( Level::ERROR, @@ -113,13 +113,14 @@ impl Song { } }; event!(target: "audrey::thumbnail_loader", Level::DEBUG, id = id, "fetched thumbnail"); + let song = match song_weak.upgrade() { None => return, Some(song) => song, }; - song.set_thumbnail( - gdk::Texture::from_bytes(&glib::Bytes::from_owned(bytes)).unwrap(), - ); + + let texture = gdk::Texture::for_pixbuf(&pixbuf); + song.set_thumbnail(texture); })); } } diff --git a/src/subsonic.rs b/src/subsonic.rs index 4d3f67e..676d103 100644 --- a/src/subsonic.rs +++ b/src/subsonic.rs @@ -3,8 +3,9 @@ pub mod schema; mod album_list; pub use album_list::AlbumListType; -use adw::glib; use bytes::Bytes; +use gtk::glib; +use image::ImageReader; use rand::Rng; use tracing::{event, Level}; @@ -23,6 +24,8 @@ fn runtime() -> &'static tokio::runtime::Runtime { pub enum Error { UrlParseError(url::ParseError), ReqwestError(reqwest_middleware::Error), + JsonDecodeError(serde_json::Error), + ImageDecodeError(image::error::ImageError), SubsonicError(schema::Error), OtherError(&'static str), } @@ -33,6 +36,8 @@ impl fmt::Display for Error { match self { Self::UrlParseError(err) => fmt::Display::fmt(err, f), Self::ReqwestError(err) => fmt::Display::fmt(err, f), + Self::ImageDecodeError(err) => fmt::Display::fmt(err, f), + Self::JsonDecodeError(err) => fmt::Display::fmt(err, f), Self::SubsonicError(err) => fmt::Display::fmt(&err.message, f), Self::OtherError(err) => fmt::Display::fmt(err, f), } @@ -63,6 +68,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: serde_json::Error) -> Self { + Self::JsonDecodeError(err) + } +} + #[derive(Clone)] pub struct Client { client: reqwest_middleware::ClientWithMiddleware, @@ -123,10 +134,7 @@ impl Client { let http_cache = Cache(HttpCache { mode: CacheMode::default(), - manager: CACacheManager { - path: cache_dir, - ..Default::default() - }, + manager: CACacheManager { path: cache_dir }, options: HttpCacheOptions { cache_options: Some(CacheOptions { ignore_cargo_cult: true, @@ -147,10 +155,7 @@ impl Client { Ok(Client { client, base_url }) } - async fn send( - &self, - request: reqwest_middleware::RequestBuilder, - ) -> Result { + async fn send(&self, request: reqwest_middleware::RequestBuilder) -> Result { // FIXME: is an entire channel per request overkill? maybe pool them? let (sender, receiver) = async_channel::bounded(1); @@ -161,16 +166,27 @@ impl Client { let future = request.send(); runtime().spawn(async move { // wrap this logic in a fn so we can use ? - async fn perform( + async fn perform( response: Result, - ) -> Result, reqwest_middleware::Error> { + ) -> Result { let response = response?.error_for_status()?; let xcache = response.headers()[http_cache::XCACHE].to_str().ok(); - event!(target: "audrey::http_request", Level::DEBUG, url=response.url().as_str(), status=response.status().as_str(), cache_status=xcache); + let content_type = response.headers()[reqwest::header::CONTENT_TYPE] + .to_str() + .ok(); + event!( + name: "response", + target: "audrey::http", + Level::DEBUG, + url=response.url().as_str(), + status=response.status().as_str(), + cache_status=xcache, + content_type=content_type + ); response - .json::>() + .bytes() .await - .map_err(|e| reqwest_middleware::Error::Reqwest(e)) + .map_err(|e| Error::ReqwestError(reqwest_middleware::Error::Reqwest(e))) } sender @@ -179,16 +195,10 @@ impl Client { .expect("could not send subsonic response back to the main loop"); }); - let response = receiver + receiver .recv() .await - .expect("could not receive subsonic response from tokio")? - .subsonic_response; - - match response { - schema::SubsonicResponse::Ok { inner } => Ok(inner), - schema::SubsonicResponse::Failed { error } => Err(Error::SubsonicError(error)), - } + .expect("could not receive subsonic response from tokio") } fn url(&self, path: &[&str], query: &[(&str, &str)]) -> url::Url { @@ -201,22 +211,40 @@ impl Client { url } - async fn get( + async fn get_bytes(&self, url: url::Url) -> Result { + self.send(self.client.get(url)).await + } + + async fn get_json( &self, - path: &[&str], - query: &[(&str, &str)], + url: url::Url, ) -> Result { - self.send(self.client.get(self.url(path, query))).await + let bytes = self.get_bytes(url).await?; + serde_json::from_slice::(&bytes).map_err(Error::JsonDecodeError) + } + + async fn get_subsonic( + &self, + url: url::Url, + ) -> Result { + let response = self + .get_json::>(url) + .await?; + + match response.subsonic_response { + schema::SubsonicResponse::Ok { inner } => Ok(inner), + schema::SubsonicResponse::Failed { error } => Err(Error::SubsonicError(error)), + } } pub async fn ping(&self) -> Result<(), Error> { - self.get(&["rest", "ping"], &[]).await + let url = self.url(&["rest", "ping"], &[]); + self.get_bytes(url).await.map(|_| ()) } pub async fn random_songs(&self, size: u32) -> Result, Error> { - self.get::( - &["rest", "getRandomSongs"], - &[("size", &size.to_string())], + self.get_subsonic::( + self.url(&["rest", "getRandomSongs"], &[("size", &size.to_string())]), ) .await .map(|response| response.random_songs.song) @@ -232,53 +260,40 @@ impl Client { } } - pub async fn cover_art(&self, id: &str, size: Option) -> Result { - let (sender, receiver) = async_channel::bounded(1); + pub async fn cover_art( + &self, + id: &str, + size: Option, + ) -> Result { let url = self.cover_art_url(id, size); + let cover_art_bytes = self.get_bytes(url).await?; + let image = ImageReader::new(std::io::BufReader::new(std::io::Cursor::new( + cover_art_bytes, + ))) + .with_guessed_format() + .map_err(|e| Error::ImageDecodeError(image::ImageError::IoError(e)))? + .decode() + .map_err(Error::ImageDecodeError)?; - event!(target: "audrey::http_request_cover_art", Level::DEBUG, url = url.as_str()); + event!(Level::DEBUG, "loaded image: {:?}", image.color()); + let width = image.width(); + let height = image.height(); + // 8bpc rgba -> 32 -> 4 bytes + let stride = width * 4; + // gtk only supports 8bpc; maybe in the future we should handle hdr >8bpc too + let image = image.into_rgba8().into_vec(); - let future = self.client.get(url).send(); - runtime().spawn(async move { - async fn perform( - response: Result, - ) -> Result { - let response = response?.error_for_status()?; - let xcache = response.headers()[http_cache::XCACHE].to_str().ok(); - event!(target: "audrey::http_request_cover_art", Level::DEBUG, url=response.url().as_str(), status=response.status().as_str(), cache_status=xcache); - if response.headers()[reqwest::header::CONTENT_TYPE] - .to_str() - .expect("bad content type") - .starts_with("text/json") - { - match response - .json::>() - .await? - .subsonic_response - { - schema::SubsonicResponse::Failed { error } => { - Err(Error::SubsonicError(error)) - } - _ => panic!("unlimited error but no error"), - } - } else { - Ok(response.bytes().await?) - } - } - - if let Err(async_channel::SendError(_)) = sender.send(perform(future.await).await).await - { - event!( - Level::INFO, - "could not send cover art bytes to main loop (task cancelled?)" - ); - } - }); - - receiver - .recv() - .await - .expect("could not receive cover art bytes from tokio") + let pixbuf = gtk::gdk_pixbuf::Pixbuf::from_bytes( + &glib::Bytes::from_owned(image), + gtk::gdk_pixbuf::Colorspace::Rgb, + true, + 8, + // TODO: bubble up these to fail image loads instead of crashing + width.try_into().unwrap(), + height.try_into().unwrap(), + stride.try_into().unwrap(), + ); + Ok(pixbuf) } pub fn stream_url(&self, id: &str) -> url::Url { diff --git a/src/subsonic/album_list.rs b/src/subsonic/album_list.rs index b3ca0a2..d29d632 100644 --- a/src/subsonic/album_list.rs +++ b/src/subsonic/album_list.rs @@ -24,14 +24,14 @@ impl Client { ) -> Result, Error> { match type_ { AlbumListType::Newest => { - self.get::( + self.get_subsonic::(self.url( &["rest", "getAlbumList2"], &[ ("type", "newest"), ("size", &size.to_string()), ("offset", &offset.to_string()), ], - ) + )) .await } diff --git a/src/ui/window.rs b/src/ui/window.rs index 37d87eb..77157ce 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -663,11 +663,11 @@ mod imp { .loading_cover_handle .replace(Some(glib::spawn_future_local(async move { let api = window.imp().api.borrow().as_ref().unwrap().clone(); - let bytes = match api + let pixbuf = match api .cover_art(&song_id, None) // full size .await { - Ok(bytes) => bytes, + Ok(pixbuf) => pixbuf, Err(err) => { event!( Level::ERROR, @@ -679,13 +679,9 @@ mod imp { match window.song() { Some(song) if song.id() == song_id => { - let loader = gtk::gdk_pixbuf::PixbufLoader::new(); - loader.write(&bytes).unwrap(); - loader.close().unwrap(); - let pixbuf = loader.pixbuf().unwrap(); - let texture = gdk::Texture::for_pixbuf(&pixbuf); - window.set_playing_cover_art(Some(&texture.clone().into())); + + window.set_playing_cover_art(Some(&texture.into())); // from amberol let palette = color_thief::get_palette(