Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

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

api: expose audio library to gRPC, GraphQL and typescript

+1330 -197
+43
Cargo.lock
··· 4121 4121 ] 4122 4122 4123 4123 [[package]] 4124 + name = "lofty" 4125 + version = "0.21.1" 4126 + source = "registry+https://github.com/rust-lang/crates.io-index" 4127 + checksum = "c8bc4717ff10833a623b009e9254ae8667c7a59edc3cfb01c37aeeef4b6d54a7" 4128 + dependencies = [ 4129 + "byteorder", 4130 + "data-encoding", 4131 + "flate2", 4132 + "lofty_attr", 4133 + "log", 4134 + "ogg_pager", 4135 + "paste", 4136 + ] 4137 + 4138 + [[package]] 4139 + name = "lofty_attr" 4140 + version = "0.11.0" 4141 + source = "registry+https://github.com/rust-lang/crates.io-index" 4142 + checksum = "28bd4b9d8a5af74808932492521cdd272019b056f75fcc70056bd2c09fceb550" 4143 + dependencies = [ 4144 + "proc-macro2", 4145 + "quote", 4146 + "syn 2.0.77", 4147 + ] 4148 + 4149 + [[package]] 4124 4150 name = "log" 4125 4151 version = "0.4.22" 4126 4152 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4602 4628 checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" 4603 4629 dependencies = [ 4604 4630 "memchr", 4631 + ] 4632 + 4633 + [[package]] 4634 + name = "ogg_pager" 4635 + version = "0.6.1" 4636 + source = "registry+https://github.com/rust-lang/crates.io-index" 4637 + checksum = "87b0bef808533c5890ab77279538212efdbbbd9aa4ef1ccdfcfbf77a42f7e6fa" 4638 + dependencies = [ 4639 + "byteorder", 4605 4640 ] 4606 4641 4607 4642 [[package]] ··· 5587 5622 "deno_webidl", 5588 5623 "deno_websocket", 5589 5624 "reqwest", 5625 + "rockbox-library", 5590 5626 "rockbox-sys", 5627 + "sqlx", 5591 5628 "tokio", 5592 5629 ] 5593 5630 ··· 5602 5639 "async-graphql-actix-web", 5603 5640 "owo-colors 4.1.0", 5604 5641 "reqwest", 5642 + "rockbox-library", 5605 5643 "rockbox-sys", 5606 5644 "serde", 5645 + "sqlx", 5607 5646 "tokio", 5608 5647 ] 5609 5648 ··· 5615 5654 "chrono", 5616 5655 "cuid", 5617 5656 "futures", 5657 + "lofty", 5618 5658 "md5", 5619 5659 "owo-colors 4.1.0", 5620 5660 "rockbox-sys", 5661 + "serde", 5621 5662 "sqlx", 5622 5663 "tokio", 5623 5664 ] ··· 5629 5670 "owo-colors 5.0.0", 5630 5671 "prost 0.13.2", 5631 5672 "reqwest", 5673 + "rockbox-library", 5632 5674 "rockbox-sys", 5633 5675 "serde", 5676 + "sqlx", 5634 5677 "tokio", 5635 5678 "tonic", 5636 5679 "tonic-build",
+2
crates/ext/Cargo.toml
··· 16 16 deno_webidl = "0.168.0" 17 17 deno_websocket = "0.173.0" 18 18 reqwest = {version = "0.12.7", features = ["rustls-tls", "json"], default-features = false} 19 + rockbox-library = {path = "../library"} 19 20 rockbox-sys = {path = "../sys"} 21 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 20 22 tokio = {version = "1.36.0", features = ["full"]} 21 23 22 24 [build-dependencies]
+1
crates/ext/build.rs
··· 81 81 esm_entry_point = "ext:rockbox/src/bootstrap.js", 82 82 esm = [ 83 83 "src/browse/browse.js", 84 + "src/library/library.js", 84 85 "src/playback/playback.js", 85 86 "src/playlist/playlist.js", 86 87 "src/settings/settings.js",
+3
crates/ext/example.ts
··· 7 7 console.log(await rb.playlist.getCurrent()); 8 8 console.log(await rb.playlist.amount()); 9 9 console.log(await rb.browse.tree.getEntries("/")); 10 + console.log(await rb.library.album.getAlbums()); 11 + console.log(await rb.library.artist.getArtists()); 12 + console.log(await rb.library.track.getTracks());
+1
crates/ext/src/bootstrap.js
··· 1 1 import "ext:deno_console/01_console.js"; 2 2 import "ext:rockbox/src/browse/browse.js"; 3 + import "ext:rockbox/src/library/library.js"; 3 4 import "ext:rockbox/src/playback/playback.js"; 4 5 import "ext:rockbox/src/playlist/playlist.js"; 5 6 import "ext:rockbox/src/settings/settings.js";
+3
crates/ext/src/lib.rs
··· 5 5 use deno_ast::{MediaType, ParseParams}; 6 6 use deno_core::{error::AnyError, ModuleLoadResponse, ModuleSourceCode}; 7 7 use deno_net::NetPermissions; 8 + use library::rb_library; 8 9 use playback::rb_playback; 9 10 use playlist::rb_playlist; 10 11 use settings::rb_settings; ··· 13 14 14 15 pub mod browse; 15 16 pub mod dir; 17 + pub mod library; 16 18 pub mod playback; 17 19 pub mod playlist; 18 20 pub mod settings; ··· 161 163 startup_snapshot: Some(RUNTIME_SNAPSHOT), 162 164 extensions: vec![ 163 165 rb_browse::init_ops(), 166 + rb_library::init_ops(), 164 167 rb_playback::init_ops(), 165 168 rb_playlist::init_ops(), 166 169 rb_settings::init_ops(),
+18
crates/ext/src/library/library.js
··· 1 + const { ops } = Deno.core; 2 + 3 + const library = { 4 + album: { 5 + getAlbum: (id) => ops.op_get_album(id), 6 + getAlbums: () => ops.op_get_albums(), 7 + }, 8 + artist: { 9 + getArtist: (id) => ops.op_get_artist(id), 10 + getArtists: () => ops.op_get_artists(), 11 + }, 12 + track: { 13 + getTrack: (id) => ops.op_get_track(id), 14 + getTracks: () => ops.op_get_tracks(), 15 + }, 16 + }; 17 + 18 + globalThis.rb = { ...globalThis.rb, library };
+67
crates/ext/src/library/mod.rs
··· 1 + use deno_core::{error::AnyError, extension, op2}; 2 + use rockbox_library::{ 3 + create_connection_pool, 4 + entity::{album::Album, artist::Artist, track::Track}, 5 + repo, 6 + }; 7 + 8 + extension!( 9 + rb_library, 10 + ops = [ 11 + op_get_albums, 12 + op_get_artists, 13 + op_get_tracks, 14 + op_get_album, 15 + op_get_artist, 16 + op_get_track, 17 + ], 18 + esm = ["src/library/library.js"], 19 + ); 20 + 21 + #[op2(async)] 22 + #[serde] 23 + pub async fn op_get_albums() -> Result<Vec<Album>, AnyError> { 24 + let pool = create_connection_pool().await?; 25 + let albums = repo::album::all(pool).await?; 26 + Ok(albums) 27 + } 28 + 29 + #[op2(async)] 30 + #[serde] 31 + pub async fn op_get_artists() -> Result<Vec<Artist>, AnyError> { 32 + let pool = create_connection_pool().await?; 33 + let artists = repo::artist::all(pool).await?; 34 + Ok(artists) 35 + } 36 + 37 + #[op2(async)] 38 + #[serde] 39 + pub async fn op_get_tracks() -> Result<Vec<Track>, AnyError> { 40 + let pool = create_connection_pool().await?; 41 + let tracks = repo::track::all(pool).await?; 42 + Ok(tracks) 43 + } 44 + 45 + #[op2(async)] 46 + #[serde] 47 + pub async fn op_get_album(#[string] id: String) -> Result<Option<Album>, AnyError> { 48 + let pool = create_connection_pool().await?; 49 + let album = repo::album::find(pool, &id).await?; 50 + Ok(album) 51 + } 52 + 53 + #[op2(async)] 54 + #[serde] 55 + pub async fn op_get_artist(#[string] id: String) -> Result<Option<Artist>, AnyError> { 56 + let pool = create_connection_pool().await?; 57 + let artist = repo::artist::find(pool, &id).await?; 58 + Ok(artist) 59 + } 60 + 61 + #[op2(async)] 62 + #[serde] 63 + pub async fn op_get_track(#[string] id: String) -> Result<Option<Track>, AnyError> { 64 + let pool = create_connection_pool().await?; 65 + let track = repo::track::find(pool, &id).await?; 66 + Ok(track) 67 + }
+2
crates/graphql/Cargo.toml
··· 11 11 async-graphql-actix-web = "7.0.9" 12 12 owo-colors = "4.1.0" 13 13 reqwest = {version = "0.12.7", features = ["rustls-tls", "json"], default-features = false} 14 + rockbox-library = {path = "../library"} 14 15 rockbox-sys = {path = "../sys"} 15 16 serde = "1.0.210" 17 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 16 18 tokio = {version = "1.36.0", features = ["full"]}
-16
crates/graphql/src/schema/browse.rs
··· 7 7 8 8 #[Object] 9 9 impl BrowseQuery { 10 - async fn browse_id3(&self) -> String { 11 - "browse id3".to_string() 12 - } 13 - 14 - async fn tree_get_context(&self) -> String { 15 - "tree get context".to_string() 16 - } 17 - 18 10 async fn tree_get_entries(&self, ctx: &Context<'_>, path: String) -> Result<Vec<Entry>, Error> { 19 11 let client = ctx.data::<reqwest::Client>().unwrap(); 20 12 let url = format!("{}/tree_entries?q={}", rockbox_url(), path); 21 13 let response = client.get(&url).send().await?; 22 14 let response = response.json::<Vec<Entry>>().await?; 23 15 Ok(response) 24 - } 25 - 26 - async fn tree_get_entry_at(&self, ctx: &Context<'_>) -> Result<Entry, Error> { 27 - todo!() 28 - } 29 - 30 - async fn rockbox_browse(&self) -> String { 31 - "rockbox browse".to_string() 32 16 } 33 17 }
+49
crates/graphql/src/schema/library.rs
··· 1 + use async_graphql::*; 2 + use rockbox_library::repo; 3 + use sqlx::{Pool, Sqlite}; 4 + 5 + use crate::schema::objects::track::Track; 6 + 7 + use super::objects::{album::Album, artist::Artist}; 8 + 9 + #[derive(Default)] 10 + pub struct LibraryQuery; 11 + 12 + #[Object] 13 + impl LibraryQuery { 14 + async fn albums(&self, ctx: &Context<'_>) -> Result<Vec<Album>, Error> { 15 + let pool = ctx.data::<Pool<Sqlite>>()?; 16 + let results = repo::album::all(pool.clone()).await?; 17 + Ok(results.into_iter().map(Into::into).collect()) 18 + } 19 + 20 + async fn artists(&self, ctx: &Context<'_>) -> Result<Vec<Artist>, Error> { 21 + let pool = ctx.data::<Pool<Sqlite>>()?; 22 + let results = repo::artist::all(pool.clone()).await?; 23 + Ok(results.into_iter().map(Into::into).collect()) 24 + } 25 + 26 + async fn tracks(&self, ctx: &Context<'_>) -> Result<Vec<Track>, Error> { 27 + let pool = ctx.data::<Pool<Sqlite>>()?; 28 + let results = repo::track::all(pool.clone()).await?; 29 + Ok(results.into_iter().map(Into::into).collect()) 30 + } 31 + 32 + async fn album(&self, ctx: &Context<'_>, id: String) -> Result<Option<Album>, Error> { 33 + let pool = ctx.data::<Pool<Sqlite>>()?; 34 + let results = repo::album::find(pool.clone(), &id).await?; 35 + Ok(results.map(Into::into)) 36 + } 37 + 38 + async fn artist(&self, ctx: &Context<'_>, id: String) -> Result<Option<Artist>, Error> { 39 + let pool = ctx.data::<Pool<Sqlite>>()?; 40 + let results = repo::artist::find(pool.clone(), &id).await?; 41 + Ok(results.map(Into::into)) 42 + } 43 + 44 + async fn track(&self, ctx: &Context<'_>, id: String) -> Result<Option<Track>, Error> { 45 + let pool = ctx.data::<Pool<Sqlite>>()?; 46 + let results = repo::track::find(pool.clone(), &id).await?; 47 + Ok(results.map(Into::into)) 48 + } 49 + }
+1
crates/graphql/src/schema/mod.rs
··· 7 7 use system::SystemQuery; 8 8 9 9 pub mod browse; 10 + pub mod library; 10 11 pub mod metadata; 11 12 pub mod objects; 12 13 pub mod playback;
+58
crates/graphql/src/schema/objects/album.rs
··· 1 + use async_graphql::*; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Default, Clone, Serialize, Deserialize)] 5 + pub struct Album { 6 + pub id: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub year: u32, 10 + pub year_string: String, 11 + pub album_art: Option<String>, 12 + pub md5: String, 13 + } 14 + 15 + #[Object] 16 + impl Album { 17 + async fn id(&self) -> &str { 18 + &self.id 19 + } 20 + 21 + async fn title(&self) -> &str { 22 + &self.title 23 + } 24 + 25 + async fn artist(&self) -> &str { 26 + &self.artist 27 + } 28 + 29 + async fn year(&self) -> i32 { 30 + self.year as i32 31 + } 32 + 33 + async fn year_string(&self) -> &str { 34 + &self.year_string 35 + } 36 + 37 + async fn album_art(&self) -> Option<&str> { 38 + self.album_art.as_deref() 39 + } 40 + 41 + async fn md5(&self) -> &str { 42 + &self.md5 43 + } 44 + } 45 + 46 + impl From<rockbox_library::entity::album::Album> for Album { 47 + fn from(album: rockbox_library::entity::album::Album) -> Self { 48 + Self { 49 + id: album.id, 50 + title: album.title, 51 + artist: album.artist, 52 + year: album.year, 53 + year_string: album.year_string, 54 + album_art: album.album_art, 55 + md5: album.md5, 56 + } 57 + } 58 + }
+40
crates/graphql/src/schema/objects/artist.rs
··· 1 + use async_graphql::*; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Default, Clone, Serialize, Deserialize)] 5 + pub struct Artist { 6 + pub id: String, 7 + pub name: String, 8 + pub bio: Option<String>, 9 + pub image: Option<String>, 10 + } 11 + 12 + #[Object] 13 + impl Artist { 14 + async fn id(&self) -> &str { 15 + &self.id 16 + } 17 + 18 + async fn name(&self) -> &str { 19 + &self.name 20 + } 21 + 22 + async fn bio(&self) -> Option<&str> { 23 + self.bio.as_deref() 24 + } 25 + 26 + async fn image(&self) -> Option<&str> { 27 + self.image.as_deref() 28 + } 29 + } 30 + 31 + impl From<rockbox_library::entity::artist::Artist> for Artist { 32 + fn from(artist: rockbox_library::entity::artist::Artist) -> Self { 33 + Self { 34 + id: artist.id, 35 + name: artist.name, 36 + bio: artist.bio, 37 + image: artist.image, 38 + } 39 + } 40 + }
+2
crates/graphql/src/schema/objects/mod.rs
··· 1 + pub mod album; 2 + pub mod artist; 1 3 pub mod compressor_settings; 2 4 pub mod entry; 3 5 pub mod eq_band_setting;
+48
crates/graphql/src/schema/objects/track.rs
··· 4 4 5 5 #[derive(Default, Clone, Serialize)] 6 6 pub struct Track { 7 + pub id: Option<String>, 7 8 pub title: String, 8 9 pub artist: String, 9 10 pub album: String, ··· 25 26 pub length: u64, 26 27 pub elapsed: u64, 27 28 pub path: String, 29 + pub album_id: Option<String>, 30 + pub artist_id: Option<String>, 31 + pub genre_id: Option<String>, 28 32 } 29 33 30 34 #[Object] 31 35 impl Track { 36 + async fn id(&self) -> Option<&str> { 37 + self.id.as_deref() 38 + } 39 + 32 40 async fn title(&self) -> &str { 33 41 &self.title 34 42 } ··· 112 120 async fn path(&self) -> &str { 113 121 &self.path 114 122 } 123 + 124 + async fn album_id(&self) -> Option<&str> { 125 + self.album_id.as_deref() 126 + } 127 + 128 + async fn artist_id(&self) -> Option<&str> { 129 + self.artist_id.as_deref() 130 + } 131 + 132 + async fn genre_id(&self) -> Option<&str> { 133 + self.genre_id.as_deref() 134 + } 115 135 } 116 136 117 137 impl From<Mp3Entry> for Track { ··· 160 180 length, 161 181 elapsed, 162 182 path, 183 + ..Default::default() 184 + } 185 + } 186 + } 187 + 188 + impl From<rockbox_library::entity::track::Track> for Track { 189 + fn from(track: rockbox_library::entity::track::Track) -> Self { 190 + Self { 191 + id: Some(track.id), 192 + title: track.title, 193 + artist: track.artist, 194 + album: track.album, 195 + genre: track.genre.unwrap_or_default(), 196 + year_string: track.year_string.unwrap_or_default(), 197 + composer: track.composer, 198 + album_artist: track.album_artist, 199 + discnum: track.disc_number as i32, 200 + tracknum: track.track_number.unwrap_or_default() as i32, 201 + year: track.year.unwrap_or_default() as i32, 202 + bitrate: track.bitrate, 203 + frequency: track.frequency as u64, 204 + filesize: track.filesize as u64, 205 + length: track.length as u64, 206 + artist_id: Some(track.artist_id), 207 + album_id: Some(track.album_id), 208 + genre_id: Some(track.genre_id), 209 + path: track.path, 210 + ..Default::default() 163 211 } 164 212 } 165 213 }
+6 -1
crates/graphql/src/server.rs
··· 6 6 web::{self, Data}, 7 7 App, HttpRequest, HttpResponse, HttpServer, Result, 8 8 }; 9 + use anyhow::Error; 9 10 use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema}; 10 11 use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; 11 12 use owo_colors::OwoColorize; 13 + use rockbox_library::create_connection_pool; 12 14 use rockbox_sys::events::RockboxCommand; 13 15 14 16 use crate::{ ··· 46 48 )) 47 49 } 48 50 49 - pub async fn start(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> std::io::Result<()> { 51 + pub async fn start(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> Result<(), Error> { 50 52 let client = reqwest::Client::new(); 53 + let pool = create_connection_pool().await?; 51 54 let schema = Schema::build( 52 55 Query::default(), 53 56 Mutation::default(), ··· 55 58 ) 56 59 .data(cmd_tx) 57 60 .data(client) 61 + .data(pool) 58 62 .finish(); 59 63 let graphql_port = std::env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string()); 60 64 let addr = format!("{}:{}", "0.0.0.0", graphql_port); ··· 76 80 .bind(addr)? 77 81 .run() 78 82 .await 83 + .map_err(Error::new) 79 84 }
+3 -1
crates/library/Cargo.toml
··· 5 5 6 6 [dependencies] 7 7 anyhow = "1.0.89" 8 - chrono = "0.4.38" 8 + chrono = {version = "0.4.38", features = ["serde"]} 9 9 cuid = "1.3.3" 10 10 futures = "0.3.30" 11 + lofty = "0.21.1" 11 12 md5 = "0.7.0" 12 13 owo-colors = "4.1.0" 13 14 rockbox-sys = {path = "../sys"} 15 + serde = "1.0.210" 14 16 sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 15 17 tokio = {version = "1.36.0", features = ["full"]}
+8 -8
crates/library/migrations/20240923093823_create_tables.sql
··· 31 31 artist_id VARCHAR(255) NOT NULL, 32 32 album_id VARCHAR(255) NOT NULL, 33 33 genre_id VARCHAR(255), 34 - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 35 - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 34 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 35 + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 36 36 ); 37 37 38 38 CREATE TABLE IF NOT EXISTS artist ( ··· 55 55 description TEXT, 56 56 image VARCHAR(255), 57 57 folder_id VARCHAR(255), 58 - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 59 - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 58 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 59 + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 60 60 ); 61 61 62 62 CREATE TABLE IF NOT EXISTS playlist_tracks ( 63 63 id VARCHAR(255) PRIMARY KEY, 64 64 playlist_id VARCHAR(255) NOT NULL, 65 65 track_id VARCHAR(255) NOT NULL, 66 - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 66 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 67 67 ); 68 68 69 69 CREATE TABLE IF NOT EXISTS folder ( 70 70 id VARCHAR(255) PRIMARY KEY, 71 71 name VARCHAR(255) NOT NULL, 72 72 parent_id VARCHAR(255), 73 - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 74 - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 73 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 74 + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 75 75 ); 76 76 77 77 CREATE TABLE IF NOT EXISTS favourites ( 78 78 id VARCHAR(255) PRIMARY KEY, 79 79 track_id VARCHAR(255) NOT NULL, 80 - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 80 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 81 81 ); 82 82 83 83 CREATE TABLE IF NOT EXISTS artist_tracks (
+80
crates/library/src/album_art.rs
··· 1 + use std::io::Write; 2 + 3 + use anyhow::Error; 4 + use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor}; 5 + 6 + pub fn extract_and_save_album_cover(track_path: &str) -> Result<Option<String>, Error> { 7 + let tagged_file = match Probe::open(track_path) 8 + .expect("ERROR: Bad path provided!") 9 + .read() 10 + { 11 + Ok(tagged_file) => tagged_file, 12 + Err(e) => { 13 + println!("Error opening file: {}", e); 14 + return Ok(None); 15 + } 16 + }; 17 + 18 + let primary_tag = tagged_file.primary_tag(); 19 + let tag = match primary_tag { 20 + Some(tag) => tag, 21 + None => { 22 + println!("No tag found in file: {}", track_path); 23 + return Ok(None); 24 + } 25 + }; 26 + 27 + let pictures = tag.pictures(); 28 + if pictures.len() > 0 { 29 + let home = std::env::var("HOME")?; 30 + let covers_path = format!("{}/.config/rockbox.org/covers", home); 31 + std::fs::create_dir_all(&covers_path)?; 32 + let picture = &pictures[0]; 33 + 34 + if tag.album().is_none() { 35 + println!("No album found in file: {}", track_path); 36 + return Ok(None); 37 + } 38 + 39 + let album = md5::compute(tag.album().unwrap().as_bytes()); 40 + let filename = format!("{}/{:x}", covers_path, album); 41 + match picture.mime_type() { 42 + Some(lofty::picture::MimeType::Jpeg) => { 43 + let filename = format!("{}.jpg", filename); 44 + let mut file = std::fs::File::create(filename)?; 45 + file.write_all(picture.data())?; 46 + Ok(Some(format!("{:x}.jpg", album))) 47 + } 48 + Some(lofty::picture::MimeType::Png) => { 49 + let filename = format!("{}.png", filename); 50 + let mut file = std::fs::File::create(filename)?; 51 + file.write_all(picture.data())?; 52 + Ok(Some(format!("{:x}.png", album))) 53 + } 54 + Some(lofty::picture::MimeType::Bmp) => { 55 + let filename = format!("{}.bmp", filename); 56 + let mut file = std::fs::File::create(filename)?; 57 + file.write_all(picture.data())?; 58 + Ok(Some(format!("{:x}.bmp", album))) 59 + } 60 + Some(lofty::picture::MimeType::Gif) => { 61 + let filename = format!("{}.gif", filename); 62 + let mut file = std::fs::File::create(filename)?; 63 + file.write_all(picture.data())?; 64 + Ok(Some(format!("{:x}.gif", album))) 65 + } 66 + Some(lofty::picture::MimeType::Tiff) => { 67 + let filename = format!("{}.tiff", filename); 68 + let mut file = std::fs::File::create(filename)?; 69 + file.write_all(picture.data())?; 70 + Ok(Some(format!("{:x}.tiff", album))) 71 + } 72 + _ => { 73 + println!("Unsupported picture format"); 74 + Ok(None) 75 + } 76 + } 77 + } else { 78 + Ok(None) 79 + } 80 + }
+9 -6
crates/library/src/audio_scan.rs
··· 1 + use crate::album_art::extract_and_save_album_cover; 1 2 use crate::entity::album::Album; 2 3 use crate::entity::artist::Artist; 3 4 use crate::{entity::track::Track, repo}; 4 5 use anyhow::Error; 6 + use chrono::Utc; 5 7 use futures::future::BoxFuture; 6 8 use futures::stream::{FuturesUnordered, StreamExt}; 7 9 use owo_colors::OwoColorize; ··· 50 52 ); 51 53 let entry = rb::metadata::get_metadata(-1, path); 52 54 53 - let track_hash = format!("{:?}", md5::compute(entry.path.as_bytes())); 55 + let track_hash = format!("{:x}", md5::compute(entry.path.as_bytes())); 54 56 let artist_id = cuid::cuid1()?; 55 57 let album_id = cuid::cuid1()?; 56 58 let album_md5 = format!( 57 - "{:?}", 59 + "{:x}", 58 60 md5::compute(format!("{}{}", entry.albumartist, entry.album).as_bytes()) 59 61 ); 60 62 ··· 62 64 pool.clone(), 63 65 Track { 64 66 id: cuid::cuid1()?, 65 - path: entry.path, 67 + path: entry.path.clone(), 66 68 title: entry.title, 67 69 artist: entry.artist.clone(), 68 70 album: entry.album.clone(), ··· 81 83 filesize: entry.filesize as u32, 82 84 length: entry.length as u32, 83 85 md5: track_hash, 84 - created_at: chrono::Utc::now().timestamp_nanos_opt().unwrap() as i32, 85 - updated_at: chrono::Utc::now().timestamp_nanos_opt().unwrap() as i32, 86 + created_at: Utc::now(), 87 + updated_at: Utc::now(), 86 88 artist_id: artist_id.clone(), 87 89 album_id: album_id.clone(), 88 90 ..Default::default() ··· 90 92 ) 91 93 .await?; 92 94 95 + let album_art = extract_and_save_album_cover(&entry.path)?; 93 96 repo::album::save( 94 97 pool.clone(), 95 98 Album { ··· 98 101 artist: entry.albumartist.clone(), 99 102 year: entry.year as u32, 100 103 year_string: entry.year_string, 101 - album_art: None, 104 + album_art, 102 105 md5: album_md5, 103 106 }, 104 107 )
+3 -1
crates/library/src/entity/album.rs
··· 1 - #[derive(sqlx::FromRow, Default)] 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 2 4 pub struct Album { 3 5 pub id: String, 4 6 pub title: String,
+3 -1
crates/library/src/entity/artist.rs
··· 1 - #[derive(sqlx::FromRow, Default)] 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 2 4 pub struct Artist { 3 5 pub id: String, 4 6 pub name: String,
+8 -1
crates/library/src/entity/folder.rs
··· 1 - #[derive(sqlx::FromRow, Default)] 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 2 5 pub struct Folder { 3 6 pub id: String, 4 7 pub name: String, 5 8 pub parent_id: Option<String>, 9 + #[serde(with = "chrono::serde::ts_seconds")] 10 + pub created_at: DateTime<Utc>, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub updated_at: DateTime<Utc>, 6 13 }
+8 -3
crates/library/src/entity/playlist.rs
··· 1 - #[derive(sqlx::FromRow, Default)] 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 2 5 pub struct Playlist { 3 6 pub id: String, 4 7 pub name: String, 5 8 pub image: Option<String>, 6 9 pub description: Option<String>, 7 10 pub folder_id: Option<String>, 8 - pub created_at: i64, 9 - pub updated_at: i64, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub created_at: DateTime<Utc>, 13 + #[serde(with = "chrono::serde::ts_seconds")] 14 + pub updated_at: DateTime<Utc>, 10 15 }
+8 -3
crates/library/src/entity/track.rs
··· 1 - #[derive(sqlx::FromRow, Default)] 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 2 5 pub struct Track { 3 6 pub id: String, 4 7 pub path: String, ··· 21 24 pub artist_id: String, 22 25 pub album_id: String, 23 26 pub genre_id: String, 24 - pub created_at: i32, 25 - pub updated_at: i32, 27 + #[serde(with = "chrono::serde::ts_seconds")] 28 + pub created_at: DateTime<Utc>, 29 + #[serde(with = "chrono::serde::ts_seconds")] 30 + pub updated_at: DateTime<Utc>, 26 31 }
+1
crates/library/src/lib.rs
··· 2 2 3 3 use sqlx::{sqlite::SqliteConnectOptions, Error, Executor, Pool, Sqlite, SqlitePool}; 4 4 5 + pub mod album_art; 5 6 pub mod audio_scan; 6 7 pub mod entity; 7 8 pub mod repo;
+2
crates/rpc/Cargo.toml
··· 7 7 owo-colors = "5.0.0" 8 8 prost = "0.13.2" 9 9 reqwest = {version = "0.12.7", features = ["rustls-tls", "json"], default-features = false} 10 + rockbox-library = {path = "../library"} 10 11 rockbox-sys = {path = "../sys"} 11 12 serde = "1.0.210" 13 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 12 14 tokio = {version = "1.36.0", features = ["full"]} 13 15 tonic = "0.12.2" 14 16 tonic-reflection = "0.12.2"
+1
crates/rpc/build.rs
··· 5 5 .compile( 6 6 &[ 7 7 "proto/rockbox/v1alpha1/browse.proto", 8 + "proto/rockbox/v1alpha1/library.proto", 8 9 "proto/rockbox/v1alpha1/metadata.proto", 9 10 "proto/rockbox/v1alpha1/playback.proto", 10 11 "proto/rockbox/v1alpha1/playlist.proto",
+9 -24
crates/rpc/proto/rockbox/v1alpha1/browse.proto
··· 2 2 3 3 package rockbox.v1alpha1; 4 4 5 - message RockboxBrowseRequest { 6 - } 5 + message RockboxBrowseRequest {} 7 6 8 - message RockboxBrowseResponse { 9 - } 7 + message RockboxBrowseResponse {} 10 8 11 - message TreeGetContextRequest { 12 - } 9 + message TreeGetContextRequest {} 13 10 14 - message TreeGetContextResponse { 15 - } 11 + message TreeGetContextResponse {} 16 12 17 13 message TreeGetEntriesRequest { 18 14 string path = 1; ··· 29 25 repeated Entry entries = 1; 30 26 } 31 27 28 + message TreeGetEntryAtRequest {} 32 29 33 - message TreeGetEntryAtRequest { 34 - } 30 + message TreeGetEntryAtResponse {} 35 31 36 - message TreeGetEntryAtResponse { 37 - } 38 - 39 - message BrowseId3Request { 40 - 41 - } 42 - 43 - message BrowseId3Response { 32 + message BrowseId3Request {} 44 33 45 - } 34 + message BrowseId3Response {} 46 35 47 36 service BrowseService { 48 - rpc RockboxBrowse(RockboxBrowseRequest) returns (RockboxBrowseResponse); 49 - rpc TreeGetContext(TreeGetContextRequest) returns (TreeGetContextResponse); 50 37 rpc TreeGetEntries(TreeGetEntriesRequest) returns (TreeGetEntriesResponse); 51 - rpc TreeGetEntryAt(TreeGetEntryAtRequest) returns (TreeGetEntryAtResponse); 52 - rpc BrowseId3(BrowseId3Request) returns (BrowseId3Response); 53 - } 38 + }
+97
crates/rpc/proto/rockbox/v1alpha1/library.proto
··· 1 + syntax = "proto3"; 2 + 3 + package rockbox.v1alpha1; 4 + 5 + message Track { 6 + string id = 1; 7 + string path = 2; 8 + string title = 3; 9 + string artist = 4; 10 + string album = 5; 11 + string album_artist = 6; 12 + uint32 bitrate = 7; 13 + string composer = 8; 14 + uint32 disc_number = 9; 15 + uint32 filesize = 10; 16 + uint32 frequency = 11; 17 + uint32 length = 12; 18 + uint32 track_number = 13; 19 + uint32 year = 14; 20 + string year_string = 15; 21 + string genre = 16; 22 + string md5 = 17; 23 + optional string album_art = 18; 24 + optional string artist_id = 19; 25 + optional string album_id = 20; 26 + optional string genre_id = 21; 27 + string created_at = 22; 28 + string updated_at = 23; 29 + } 30 + 31 + message Artist { 32 + string id = 1; 33 + string name = 2; 34 + optional string bio = 3; 35 + optional string image = 4; 36 + } 37 + 38 + message Album { 39 + string id = 1; 40 + string title = 2; 41 + string artist = 3; 42 + uint32 year = 4; 43 + string year_string = 5; 44 + optional string album_art = 6; 45 + string md5 = 7; 46 + } 47 + 48 + message GetAlbumRequest { 49 + string id = 1; 50 + } 51 + 52 + message GetAlbumResponse { 53 + optional Album album = 1; 54 + } 55 + 56 + message GetArtistRequest { 57 + string id = 1; 58 + } 59 + 60 + message GetArtistResponse { 61 + optional Artist artist = 1; 62 + } 63 + 64 + message GetTrackRequest { 65 + string id = 1; 66 + } 67 + 68 + message GetTrackResponse { 69 + optional Track track = 1; 70 + } 71 + 72 + message GetAlbumsRequest {} 73 + 74 + message GetAlbumsResponse { 75 + repeated Album albums = 1; 76 + } 77 + 78 + message GetArtistsRequest {} 79 + 80 + message GetArtistsResponse { 81 + repeated Artist artists = 1; 82 + } 83 + 84 + message GetTracksRequest {} 85 + 86 + message GetTracksResponse { 87 + repeated Track tracks = 1; 88 + } 89 + 90 + service LibraryService { 91 + rpc GetAlbums(GetAlbumsRequest) returns (GetAlbumsResponse); 92 + rpc GetArtists(GetArtistsRequest) returns (GetArtistsResponse); 93 + rpc GetTracks(GetTracksRequest) returns (GetTracksResponse); 94 + rpc GetAlbum(GetAlbumRequest) returns (GetAlbumResponse); 95 + rpc GetArtist(GetArtistRequest) returns (GetArtistResponse); 96 + rpc GetTrack(GetTrackRequest) returns (GetTrackResponse); 97 + }
+585 -102
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 121 121 self.inner = self.inner.max_encoding_message_size(limit); 122 122 self 123 123 } 124 - pub async fn rockbox_browse( 124 + pub async fn tree_get_entries( 125 125 &mut self, 126 - request: impl tonic::IntoRequest<super::RockboxBrowseRequest>, 126 + request: impl tonic::IntoRequest<super::TreeGetEntriesRequest>, 127 127 ) -> std::result::Result< 128 - tonic::Response<super::RockboxBrowseResponse>, 128 + tonic::Response<super::TreeGetEntriesResponse>, 129 129 tonic::Status, 130 130 > { 131 131 self.inner ··· 139 139 })?; 140 140 let codec = tonic::codec::ProstCodec::default(); 141 141 let path = http::uri::PathAndQuery::from_static( 142 - "/rockbox.v1alpha1.BrowseService/RockboxBrowse", 142 + "/rockbox.v1alpha1.BrowseService/TreeGetEntries", 143 143 ); 144 144 let mut req = request.into_request(); 145 145 req.extensions_mut() 146 146 .insert( 147 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "RockboxBrowse"), 147 + GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetEntries"), 148 148 ); 149 149 self.inner.unary(req, path, codec).await 150 150 } 151 - pub async fn tree_get_context( 151 + } 152 + } 153 + /// Generated server implementations. 154 + pub mod browse_service_server { 155 + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 156 + use tonic::codegen::*; 157 + /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. 158 + #[async_trait] 159 + pub trait BrowseService: std::marker::Send + std::marker::Sync + 'static { 160 + async fn tree_get_entries( 161 + &self, 162 + request: tonic::Request<super::TreeGetEntriesRequest>, 163 + ) -> std::result::Result< 164 + tonic::Response<super::TreeGetEntriesResponse>, 165 + tonic::Status, 166 + >; 167 + } 168 + #[derive(Debug)] 169 + pub struct BrowseServiceServer<T> { 170 + inner: Arc<T>, 171 + accept_compression_encodings: EnabledCompressionEncodings, 172 + send_compression_encodings: EnabledCompressionEncodings, 173 + max_decoding_message_size: Option<usize>, 174 + max_encoding_message_size: Option<usize>, 175 + } 176 + impl<T> BrowseServiceServer<T> { 177 + pub fn new(inner: T) -> Self { 178 + Self::from_arc(Arc::new(inner)) 179 + } 180 + pub fn from_arc(inner: Arc<T>) -> Self { 181 + Self { 182 + inner, 183 + accept_compression_encodings: Default::default(), 184 + send_compression_encodings: Default::default(), 185 + max_decoding_message_size: None, 186 + max_encoding_message_size: None, 187 + } 188 + } 189 + pub fn with_interceptor<F>( 190 + inner: T, 191 + interceptor: F, 192 + ) -> InterceptedService<Self, F> 193 + where 194 + F: tonic::service::Interceptor, 195 + { 196 + InterceptedService::new(Self::new(inner), interceptor) 197 + } 198 + /// Enable decompressing requests with the given encoding. 199 + #[must_use] 200 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 201 + self.accept_compression_encodings.enable(encoding); 202 + self 203 + } 204 + /// Compress responses with the given encoding, if the client supports it. 205 + #[must_use] 206 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 207 + self.send_compression_encodings.enable(encoding); 208 + self 209 + } 210 + /// Limits the maximum size of a decoded message. 211 + /// 212 + /// Default: `4MB` 213 + #[must_use] 214 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 215 + self.max_decoding_message_size = Some(limit); 216 + self 217 + } 218 + /// Limits the maximum size of an encoded message. 219 + /// 220 + /// Default: `usize::MAX` 221 + #[must_use] 222 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 223 + self.max_encoding_message_size = Some(limit); 224 + self 225 + } 226 + } 227 + impl<T, B> tonic::codegen::Service<http::Request<B>> for BrowseServiceServer<T> 228 + where 229 + T: BrowseService, 230 + B: Body + std::marker::Send + 'static, 231 + B::Error: Into<StdError> + std::marker::Send + 'static, 232 + { 233 + type Response = http::Response<tonic::body::BoxBody>; 234 + type Error = std::convert::Infallible; 235 + type Future = BoxFuture<Self::Response, Self::Error>; 236 + fn poll_ready( 237 + &mut self, 238 + _cx: &mut Context<'_>, 239 + ) -> Poll<std::result::Result<(), Self::Error>> { 240 + Poll::Ready(Ok(())) 241 + } 242 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 243 + match req.uri().path() { 244 + "/rockbox.v1alpha1.BrowseService/TreeGetEntries" => { 245 + #[allow(non_camel_case_types)] 246 + struct TreeGetEntriesSvc<T: BrowseService>(pub Arc<T>); 247 + impl< 248 + T: BrowseService, 249 + > tonic::server::UnaryService<super::TreeGetEntriesRequest> 250 + for TreeGetEntriesSvc<T> { 251 + type Response = super::TreeGetEntriesResponse; 252 + type Future = BoxFuture< 253 + tonic::Response<Self::Response>, 254 + tonic::Status, 255 + >; 256 + fn call( 257 + &mut self, 258 + request: tonic::Request<super::TreeGetEntriesRequest>, 259 + ) -> Self::Future { 260 + let inner = Arc::clone(&self.0); 261 + let fut = async move { 262 + <T as BrowseService>::tree_get_entries(&inner, request) 263 + .await 264 + }; 265 + Box::pin(fut) 266 + } 267 + } 268 + let accept_compression_encodings = self.accept_compression_encodings; 269 + let send_compression_encodings = self.send_compression_encodings; 270 + let max_decoding_message_size = self.max_decoding_message_size; 271 + let max_encoding_message_size = self.max_encoding_message_size; 272 + let inner = self.inner.clone(); 273 + let fut = async move { 274 + let method = TreeGetEntriesSvc(inner); 275 + let codec = tonic::codec::ProstCodec::default(); 276 + let mut grpc = tonic::server::Grpc::new(codec) 277 + .apply_compression_config( 278 + accept_compression_encodings, 279 + send_compression_encodings, 280 + ) 281 + .apply_max_message_size_config( 282 + max_decoding_message_size, 283 + max_encoding_message_size, 284 + ); 285 + let res = grpc.unary(method, req).await; 286 + Ok(res) 287 + }; 288 + Box::pin(fut) 289 + } 290 + _ => { 291 + Box::pin(async move { 292 + Ok( 293 + http::Response::builder() 294 + .status(200) 295 + .header("grpc-status", tonic::Code::Unimplemented as i32) 296 + .header( 297 + http::header::CONTENT_TYPE, 298 + tonic::metadata::GRPC_CONTENT_TYPE, 299 + ) 300 + .body(empty_body()) 301 + .unwrap(), 302 + ) 303 + }) 304 + } 305 + } 306 + } 307 + } 308 + impl<T> Clone for BrowseServiceServer<T> { 309 + fn clone(&self) -> Self { 310 + let inner = self.inner.clone(); 311 + Self { 312 + inner, 313 + accept_compression_encodings: self.accept_compression_encodings, 314 + send_compression_encodings: self.send_compression_encodings, 315 + max_decoding_message_size: self.max_decoding_message_size, 316 + max_encoding_message_size: self.max_encoding_message_size, 317 + } 318 + } 319 + } 320 + /// Generated gRPC service name 321 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.BrowseService"; 322 + impl<T> tonic::server::NamedService for BrowseServiceServer<T> { 323 + const NAME: &'static str = SERVICE_NAME; 324 + } 325 + } 326 + #[derive(Clone, PartialEq, ::prost::Message)] 327 + pub struct Track { 328 + #[prost(string, tag = "1")] 329 + pub id: ::prost::alloc::string::String, 330 + #[prost(string, tag = "2")] 331 + pub path: ::prost::alloc::string::String, 332 + #[prost(string, tag = "3")] 333 + pub title: ::prost::alloc::string::String, 334 + #[prost(string, tag = "4")] 335 + pub artist: ::prost::alloc::string::String, 336 + #[prost(string, tag = "5")] 337 + pub album: ::prost::alloc::string::String, 338 + #[prost(string, tag = "6")] 339 + pub album_artist: ::prost::alloc::string::String, 340 + #[prost(uint32, tag = "7")] 341 + pub bitrate: u32, 342 + #[prost(string, tag = "8")] 343 + pub composer: ::prost::alloc::string::String, 344 + #[prost(uint32, tag = "9")] 345 + pub disc_number: u32, 346 + #[prost(uint32, tag = "10")] 347 + pub filesize: u32, 348 + #[prost(uint32, tag = "11")] 349 + pub frequency: u32, 350 + #[prost(uint32, tag = "12")] 351 + pub length: u32, 352 + #[prost(uint32, tag = "13")] 353 + pub track_number: u32, 354 + #[prost(uint32, tag = "14")] 355 + pub year: u32, 356 + #[prost(string, tag = "15")] 357 + pub year_string: ::prost::alloc::string::String, 358 + #[prost(string, tag = "16")] 359 + pub genre: ::prost::alloc::string::String, 360 + #[prost(string, tag = "17")] 361 + pub md5: ::prost::alloc::string::String, 362 + #[prost(string, optional, tag = "18")] 363 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 364 + #[prost(string, optional, tag = "19")] 365 + pub artist_id: ::core::option::Option<::prost::alloc::string::String>, 366 + #[prost(string, optional, tag = "20")] 367 + pub album_id: ::core::option::Option<::prost::alloc::string::String>, 368 + #[prost(string, optional, tag = "21")] 369 + pub genre_id: ::core::option::Option<::prost::alloc::string::String>, 370 + #[prost(string, tag = "22")] 371 + pub created_at: ::prost::alloc::string::String, 372 + #[prost(string, tag = "23")] 373 + pub updated_at: ::prost::alloc::string::String, 374 + } 375 + #[derive(Clone, PartialEq, ::prost::Message)] 376 + pub struct Artist { 377 + #[prost(string, tag = "1")] 378 + pub id: ::prost::alloc::string::String, 379 + #[prost(string, tag = "2")] 380 + pub name: ::prost::alloc::string::String, 381 + #[prost(string, optional, tag = "3")] 382 + pub bio: ::core::option::Option<::prost::alloc::string::String>, 383 + #[prost(string, optional, tag = "4")] 384 + pub image: ::core::option::Option<::prost::alloc::string::String>, 385 + } 386 + #[derive(Clone, PartialEq, ::prost::Message)] 387 + pub struct Album { 388 + #[prost(string, tag = "1")] 389 + pub id: ::prost::alloc::string::String, 390 + #[prost(string, tag = "2")] 391 + pub title: ::prost::alloc::string::String, 392 + #[prost(string, tag = "3")] 393 + pub artist: ::prost::alloc::string::String, 394 + #[prost(uint32, tag = "4")] 395 + pub year: u32, 396 + #[prost(string, tag = "5")] 397 + pub year_string: ::prost::alloc::string::String, 398 + #[prost(string, optional, tag = "6")] 399 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 400 + #[prost(string, tag = "7")] 401 + pub md5: ::prost::alloc::string::String, 402 + } 403 + #[derive(Clone, PartialEq, ::prost::Message)] 404 + pub struct GetAlbumRequest { 405 + #[prost(string, tag = "1")] 406 + pub id: ::prost::alloc::string::String, 407 + } 408 + #[derive(Clone, PartialEq, ::prost::Message)] 409 + pub struct GetAlbumResponse { 410 + #[prost(message, optional, tag = "1")] 411 + pub album: ::core::option::Option<Album>, 412 + } 413 + #[derive(Clone, PartialEq, ::prost::Message)] 414 + pub struct GetArtistRequest { 415 + #[prost(string, tag = "1")] 416 + pub id: ::prost::alloc::string::String, 417 + } 418 + #[derive(Clone, PartialEq, ::prost::Message)] 419 + pub struct GetArtistResponse { 420 + #[prost(message, optional, tag = "1")] 421 + pub artist: ::core::option::Option<Artist>, 422 + } 423 + #[derive(Clone, PartialEq, ::prost::Message)] 424 + pub struct GetTrackRequest { 425 + #[prost(string, tag = "1")] 426 + pub id: ::prost::alloc::string::String, 427 + } 428 + #[derive(Clone, PartialEq, ::prost::Message)] 429 + pub struct GetTrackResponse { 430 + #[prost(message, optional, tag = "1")] 431 + pub track: ::core::option::Option<Track>, 432 + } 433 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 434 + pub struct GetAlbumsRequest {} 435 + #[derive(Clone, PartialEq, ::prost::Message)] 436 + pub struct GetAlbumsResponse { 437 + #[prost(message, repeated, tag = "1")] 438 + pub albums: ::prost::alloc::vec::Vec<Album>, 439 + } 440 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 441 + pub struct GetArtistsRequest {} 442 + #[derive(Clone, PartialEq, ::prost::Message)] 443 + pub struct GetArtistsResponse { 444 + #[prost(message, repeated, tag = "1")] 445 + pub artists: ::prost::alloc::vec::Vec<Artist>, 446 + } 447 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 448 + pub struct GetTracksRequest {} 449 + #[derive(Clone, PartialEq, ::prost::Message)] 450 + pub struct GetTracksResponse { 451 + #[prost(message, repeated, tag = "1")] 452 + pub tracks: ::prost::alloc::vec::Vec<Track>, 453 + } 454 + /// Generated client implementations. 455 + pub mod library_service_client { 456 + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 457 + use tonic::codegen::*; 458 + use tonic::codegen::http::Uri; 459 + #[derive(Debug, Clone)] 460 + pub struct LibraryServiceClient<T> { 461 + inner: tonic::client::Grpc<T>, 462 + } 463 + impl LibraryServiceClient<tonic::transport::Channel> { 464 + /// Attempt to create a new client by connecting to a given endpoint. 465 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 466 + where 467 + D: TryInto<tonic::transport::Endpoint>, 468 + D::Error: Into<StdError>, 469 + { 470 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 471 + Ok(Self::new(conn)) 472 + } 473 + } 474 + impl<T> LibraryServiceClient<T> 475 + where 476 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 477 + T::Error: Into<StdError>, 478 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 479 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 480 + { 481 + pub fn new(inner: T) -> Self { 482 + let inner = tonic::client::Grpc::new(inner); 483 + Self { inner } 484 + } 485 + pub fn with_origin(inner: T, origin: Uri) -> Self { 486 + let inner = tonic::client::Grpc::with_origin(inner, origin); 487 + Self { inner } 488 + } 489 + pub fn with_interceptor<F>( 490 + inner: T, 491 + interceptor: F, 492 + ) -> LibraryServiceClient<InterceptedService<T, F>> 493 + where 494 + F: tonic::service::Interceptor, 495 + T::ResponseBody: Default, 496 + T: tonic::codegen::Service< 497 + http::Request<tonic::body::BoxBody>, 498 + Response = http::Response< 499 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 500 + >, 501 + >, 502 + <T as tonic::codegen::Service< 503 + http::Request<tonic::body::BoxBody>, 504 + >>::Error: Into<StdError> + std::marker::Send + std::marker::Sync, 505 + { 506 + LibraryServiceClient::new(InterceptedService::new(inner, interceptor)) 507 + } 508 + /// Compress requests with the given encoding. 509 + /// 510 + /// This requires the server to support it otherwise it might respond with an 511 + /// error. 512 + #[must_use] 513 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 514 + self.inner = self.inner.send_compressed(encoding); 515 + self 516 + } 517 + /// Enable decompressing responses. 518 + #[must_use] 519 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 520 + self.inner = self.inner.accept_compressed(encoding); 521 + self 522 + } 523 + /// Limits the maximum size of a decoded message. 524 + /// 525 + /// Default: `4MB` 526 + #[must_use] 527 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 528 + self.inner = self.inner.max_decoding_message_size(limit); 529 + self 530 + } 531 + /// Limits the maximum size of an encoded message. 532 + /// 533 + /// Default: `usize::MAX` 534 + #[must_use] 535 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 536 + self.inner = self.inner.max_encoding_message_size(limit); 537 + self 538 + } 539 + pub async fn get_albums( 152 540 &mut self, 153 - request: impl tonic::IntoRequest<super::TreeGetContextRequest>, 541 + request: impl tonic::IntoRequest<super::GetAlbumsRequest>, 154 542 ) -> std::result::Result< 155 - tonic::Response<super::TreeGetContextResponse>, 543 + tonic::Response<super::GetAlbumsResponse>, 156 544 tonic::Status, 157 545 > { 158 546 self.inner ··· 166 554 })?; 167 555 let codec = tonic::codec::ProstCodec::default(); 168 556 let path = http::uri::PathAndQuery::from_static( 169 - "/rockbox.v1alpha1.BrowseService/TreeGetContext", 557 + "/rockbox.v1alpha1.LibraryService/GetAlbums", 170 558 ); 171 559 let mut req = request.into_request(); 172 560 req.extensions_mut() 173 - .insert( 174 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetContext"), 175 - ); 561 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetAlbums")); 176 562 self.inner.unary(req, path, codec).await 177 563 } 178 - pub async fn tree_get_entries( 564 + pub async fn get_artists( 179 565 &mut self, 180 - request: impl tonic::IntoRequest<super::TreeGetEntriesRequest>, 566 + request: impl tonic::IntoRequest<super::GetArtistsRequest>, 181 567 ) -> std::result::Result< 182 - tonic::Response<super::TreeGetEntriesResponse>, 568 + tonic::Response<super::GetArtistsResponse>, 183 569 tonic::Status, 184 570 > { 185 571 self.inner ··· 193 579 })?; 194 580 let codec = tonic::codec::ProstCodec::default(); 195 581 let path = http::uri::PathAndQuery::from_static( 196 - "/rockbox.v1alpha1.BrowseService/TreeGetEntries", 582 + "/rockbox.v1alpha1.LibraryService/GetArtists", 197 583 ); 198 584 let mut req = request.into_request(); 199 585 req.extensions_mut() 200 586 .insert( 201 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetEntries"), 587 + GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetArtists"), 202 588 ); 203 589 self.inner.unary(req, path, codec).await 204 590 } 205 - pub async fn tree_get_entry_at( 591 + pub async fn get_tracks( 206 592 &mut self, 207 - request: impl tonic::IntoRequest<super::TreeGetEntryAtRequest>, 593 + request: impl tonic::IntoRequest<super::GetTracksRequest>, 208 594 ) -> std::result::Result< 209 - tonic::Response<super::TreeGetEntryAtResponse>, 595 + tonic::Response<super::GetTracksResponse>, 210 596 tonic::Status, 211 597 > { 212 598 self.inner ··· 220 606 })?; 221 607 let codec = tonic::codec::ProstCodec::default(); 222 608 let path = http::uri::PathAndQuery::from_static( 223 - "/rockbox.v1alpha1.BrowseService/TreeGetEntryAt", 609 + "/rockbox.v1alpha1.LibraryService/GetTracks", 224 610 ); 225 611 let mut req = request.into_request(); 226 612 req.extensions_mut() 227 - .insert( 228 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetEntryAt"), 229 - ); 613 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetTracks")); 614 + self.inner.unary(req, path, codec).await 615 + } 616 + pub async fn get_album( 617 + &mut self, 618 + request: impl tonic::IntoRequest<super::GetAlbumRequest>, 619 + ) -> std::result::Result< 620 + tonic::Response<super::GetAlbumResponse>, 621 + tonic::Status, 622 + > { 623 + self.inner 624 + .ready() 625 + .await 626 + .map_err(|e| { 627 + tonic::Status::new( 628 + tonic::Code::Unknown, 629 + format!("Service was not ready: {}", e.into()), 630 + ) 631 + })?; 632 + let codec = tonic::codec::ProstCodec::default(); 633 + let path = http::uri::PathAndQuery::from_static( 634 + "/rockbox.v1alpha1.LibraryService/GetAlbum", 635 + ); 636 + let mut req = request.into_request(); 637 + req.extensions_mut() 638 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetAlbum")); 230 639 self.inner.unary(req, path, codec).await 231 640 } 232 - pub async fn browse_id3( 641 + pub async fn get_artist( 233 642 &mut self, 234 - request: impl tonic::IntoRequest<super::BrowseId3Request>, 643 + request: impl tonic::IntoRequest<super::GetArtistRequest>, 235 644 ) -> std::result::Result< 236 - tonic::Response<super::BrowseId3Response>, 645 + tonic::Response<super::GetArtistResponse>, 237 646 tonic::Status, 238 647 > { 239 648 self.inner ··· 247 656 })?; 248 657 let codec = tonic::codec::ProstCodec::default(); 249 658 let path = http::uri::PathAndQuery::from_static( 250 - "/rockbox.v1alpha1.BrowseService/BrowseId3", 659 + "/rockbox.v1alpha1.LibraryService/GetArtist", 251 660 ); 252 661 let mut req = request.into_request(); 253 662 req.extensions_mut() 254 - .insert(GrpcMethod::new("rockbox.v1alpha1.BrowseService", "BrowseId3")); 663 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetArtist")); 664 + self.inner.unary(req, path, codec).await 665 + } 666 + pub async fn get_track( 667 + &mut self, 668 + request: impl tonic::IntoRequest<super::GetTrackRequest>, 669 + ) -> std::result::Result< 670 + tonic::Response<super::GetTrackResponse>, 671 + tonic::Status, 672 + > { 673 + self.inner 674 + .ready() 675 + .await 676 + .map_err(|e| { 677 + tonic::Status::new( 678 + tonic::Code::Unknown, 679 + format!("Service was not ready: {}", e.into()), 680 + ) 681 + })?; 682 + let codec = tonic::codec::ProstCodec::default(); 683 + let path = http::uri::PathAndQuery::from_static( 684 + "/rockbox.v1alpha1.LibraryService/GetTrack", 685 + ); 686 + let mut req = request.into_request(); 687 + req.extensions_mut() 688 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetTrack")); 255 689 self.inner.unary(req, path, codec).await 256 690 } 257 691 } 258 692 } 259 693 /// Generated server implementations. 260 - pub mod browse_service_server { 694 + pub mod library_service_server { 261 695 #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 262 696 use tonic::codegen::*; 263 - /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. 697 + /// Generated trait containing gRPC methods that should be implemented for use with LibraryServiceServer. 264 698 #[async_trait] 265 - pub trait BrowseService: std::marker::Send + std::marker::Sync + 'static { 266 - async fn rockbox_browse( 699 + pub trait LibraryService: std::marker::Send + std::marker::Sync + 'static { 700 + async fn get_albums( 701 + &self, 702 + request: tonic::Request<super::GetAlbumsRequest>, 703 + ) -> std::result::Result< 704 + tonic::Response<super::GetAlbumsResponse>, 705 + tonic::Status, 706 + >; 707 + async fn get_artists( 267 708 &self, 268 - request: tonic::Request<super::RockboxBrowseRequest>, 709 + request: tonic::Request<super::GetArtistsRequest>, 269 710 ) -> std::result::Result< 270 - tonic::Response<super::RockboxBrowseResponse>, 711 + tonic::Response<super::GetArtistsResponse>, 271 712 tonic::Status, 272 713 >; 273 - async fn tree_get_context( 714 + async fn get_tracks( 274 715 &self, 275 - request: tonic::Request<super::TreeGetContextRequest>, 716 + request: tonic::Request<super::GetTracksRequest>, 276 717 ) -> std::result::Result< 277 - tonic::Response<super::TreeGetContextResponse>, 718 + tonic::Response<super::GetTracksResponse>, 278 719 tonic::Status, 279 720 >; 280 - async fn tree_get_entries( 721 + async fn get_album( 281 722 &self, 282 - request: tonic::Request<super::TreeGetEntriesRequest>, 723 + request: tonic::Request<super::GetAlbumRequest>, 283 724 ) -> std::result::Result< 284 - tonic::Response<super::TreeGetEntriesResponse>, 725 + tonic::Response<super::GetAlbumResponse>, 285 726 tonic::Status, 286 727 >; 287 - async fn tree_get_entry_at( 728 + async fn get_artist( 288 729 &self, 289 - request: tonic::Request<super::TreeGetEntryAtRequest>, 730 + request: tonic::Request<super::GetArtistRequest>, 290 731 ) -> std::result::Result< 291 - tonic::Response<super::TreeGetEntryAtResponse>, 732 + tonic::Response<super::GetArtistResponse>, 292 733 tonic::Status, 293 734 >; 294 - async fn browse_id3( 735 + async fn get_track( 295 736 &self, 296 - request: tonic::Request<super::BrowseId3Request>, 737 + request: tonic::Request<super::GetTrackRequest>, 297 738 ) -> std::result::Result< 298 - tonic::Response<super::BrowseId3Response>, 739 + tonic::Response<super::GetTrackResponse>, 299 740 tonic::Status, 300 741 >; 301 742 } 302 743 #[derive(Debug)] 303 - pub struct BrowseServiceServer<T> { 744 + pub struct LibraryServiceServer<T> { 304 745 inner: Arc<T>, 305 746 accept_compression_encodings: EnabledCompressionEncodings, 306 747 send_compression_encodings: EnabledCompressionEncodings, 307 748 max_decoding_message_size: Option<usize>, 308 749 max_encoding_message_size: Option<usize>, 309 750 } 310 - impl<T> BrowseServiceServer<T> { 751 + impl<T> LibraryServiceServer<T> { 311 752 pub fn new(inner: T) -> Self { 312 753 Self::from_arc(Arc::new(inner)) 313 754 } ··· 358 799 self 359 800 } 360 801 } 361 - impl<T, B> tonic::codegen::Service<http::Request<B>> for BrowseServiceServer<T> 802 + impl<T, B> tonic::codegen::Service<http::Request<B>> for LibraryServiceServer<T> 362 803 where 363 - T: BrowseService, 804 + T: LibraryService, 364 805 B: Body + std::marker::Send + 'static, 365 806 B::Error: Into<StdError> + std::marker::Send + 'static, 366 807 { ··· 375 816 } 376 817 fn call(&mut self, req: http::Request<B>) -> Self::Future { 377 818 match req.uri().path() { 378 - "/rockbox.v1alpha1.BrowseService/RockboxBrowse" => { 819 + "/rockbox.v1alpha1.LibraryService/GetAlbums" => { 379 820 #[allow(non_camel_case_types)] 380 - struct RockboxBrowseSvc<T: BrowseService>(pub Arc<T>); 821 + struct GetAlbumsSvc<T: LibraryService>(pub Arc<T>); 381 822 impl< 382 - T: BrowseService, 383 - > tonic::server::UnaryService<super::RockboxBrowseRequest> 384 - for RockboxBrowseSvc<T> { 385 - type Response = super::RockboxBrowseResponse; 823 + T: LibraryService, 824 + > tonic::server::UnaryService<super::GetAlbumsRequest> 825 + for GetAlbumsSvc<T> { 826 + type Response = super::GetAlbumsResponse; 386 827 type Future = BoxFuture< 387 828 tonic::Response<Self::Response>, 388 829 tonic::Status, 389 830 >; 390 831 fn call( 391 832 &mut self, 392 - request: tonic::Request<super::RockboxBrowseRequest>, 833 + request: tonic::Request<super::GetAlbumsRequest>, 834 + ) -> Self::Future { 835 + let inner = Arc::clone(&self.0); 836 + let fut = async move { 837 + <T as LibraryService>::get_albums(&inner, request).await 838 + }; 839 + Box::pin(fut) 840 + } 841 + } 842 + let accept_compression_encodings = self.accept_compression_encodings; 843 + let send_compression_encodings = self.send_compression_encodings; 844 + let max_decoding_message_size = self.max_decoding_message_size; 845 + let max_encoding_message_size = self.max_encoding_message_size; 846 + let inner = self.inner.clone(); 847 + let fut = async move { 848 + let method = GetAlbumsSvc(inner); 849 + let codec = tonic::codec::ProstCodec::default(); 850 + let mut grpc = tonic::server::Grpc::new(codec) 851 + .apply_compression_config( 852 + accept_compression_encodings, 853 + send_compression_encodings, 854 + ) 855 + .apply_max_message_size_config( 856 + max_decoding_message_size, 857 + max_encoding_message_size, 858 + ); 859 + let res = grpc.unary(method, req).await; 860 + Ok(res) 861 + }; 862 + Box::pin(fut) 863 + } 864 + "/rockbox.v1alpha1.LibraryService/GetArtists" => { 865 + #[allow(non_camel_case_types)] 866 + struct GetArtistsSvc<T: LibraryService>(pub Arc<T>); 867 + impl< 868 + T: LibraryService, 869 + > tonic::server::UnaryService<super::GetArtistsRequest> 870 + for GetArtistsSvc<T> { 871 + type Response = super::GetArtistsResponse; 872 + type Future = BoxFuture< 873 + tonic::Response<Self::Response>, 874 + tonic::Status, 875 + >; 876 + fn call( 877 + &mut self, 878 + request: tonic::Request<super::GetArtistsRequest>, 393 879 ) -> Self::Future { 394 880 let inner = Arc::clone(&self.0); 395 881 let fut = async move { 396 - <T as BrowseService>::rockbox_browse(&inner, request).await 882 + <T as LibraryService>::get_artists(&inner, request).await 397 883 }; 398 884 Box::pin(fut) 399 885 } ··· 404 890 let max_encoding_message_size = self.max_encoding_message_size; 405 891 let inner = self.inner.clone(); 406 892 let fut = async move { 407 - let method = RockboxBrowseSvc(inner); 893 + let method = GetArtistsSvc(inner); 408 894 let codec = tonic::codec::ProstCodec::default(); 409 895 let mut grpc = tonic::server::Grpc::new(codec) 410 896 .apply_compression_config( ··· 420 906 }; 421 907 Box::pin(fut) 422 908 } 423 - "/rockbox.v1alpha1.BrowseService/TreeGetContext" => { 909 + "/rockbox.v1alpha1.LibraryService/GetTracks" => { 424 910 #[allow(non_camel_case_types)] 425 - struct TreeGetContextSvc<T: BrowseService>(pub Arc<T>); 911 + struct GetTracksSvc<T: LibraryService>(pub Arc<T>); 426 912 impl< 427 - T: BrowseService, 428 - > tonic::server::UnaryService<super::TreeGetContextRequest> 429 - for TreeGetContextSvc<T> { 430 - type Response = super::TreeGetContextResponse; 913 + T: LibraryService, 914 + > tonic::server::UnaryService<super::GetTracksRequest> 915 + for GetTracksSvc<T> { 916 + type Response = super::GetTracksResponse; 431 917 type Future = BoxFuture< 432 918 tonic::Response<Self::Response>, 433 919 tonic::Status, 434 920 >; 435 921 fn call( 436 922 &mut self, 437 - request: tonic::Request<super::TreeGetContextRequest>, 923 + request: tonic::Request<super::GetTracksRequest>, 438 924 ) -> Self::Future { 439 925 let inner = Arc::clone(&self.0); 440 926 let fut = async move { 441 - <T as BrowseService>::tree_get_context(&inner, request) 442 - .await 927 + <T as LibraryService>::get_tracks(&inner, request).await 443 928 }; 444 929 Box::pin(fut) 445 930 } ··· 450 935 let max_encoding_message_size = self.max_encoding_message_size; 451 936 let inner = self.inner.clone(); 452 937 let fut = async move { 453 - let method = TreeGetContextSvc(inner); 938 + let method = GetTracksSvc(inner); 454 939 let codec = tonic::codec::ProstCodec::default(); 455 940 let mut grpc = tonic::server::Grpc::new(codec) 456 941 .apply_compression_config( ··· 466 951 }; 467 952 Box::pin(fut) 468 953 } 469 - "/rockbox.v1alpha1.BrowseService/TreeGetEntries" => { 954 + "/rockbox.v1alpha1.LibraryService/GetAlbum" => { 470 955 #[allow(non_camel_case_types)] 471 - struct TreeGetEntriesSvc<T: BrowseService>(pub Arc<T>); 956 + struct GetAlbumSvc<T: LibraryService>(pub Arc<T>); 472 957 impl< 473 - T: BrowseService, 474 - > tonic::server::UnaryService<super::TreeGetEntriesRequest> 475 - for TreeGetEntriesSvc<T> { 476 - type Response = super::TreeGetEntriesResponse; 958 + T: LibraryService, 959 + > tonic::server::UnaryService<super::GetAlbumRequest> 960 + for GetAlbumSvc<T> { 961 + type Response = super::GetAlbumResponse; 477 962 type Future = BoxFuture< 478 963 tonic::Response<Self::Response>, 479 964 tonic::Status, 480 965 >; 481 966 fn call( 482 967 &mut self, 483 - request: tonic::Request<super::TreeGetEntriesRequest>, 968 + request: tonic::Request<super::GetAlbumRequest>, 484 969 ) -> Self::Future { 485 970 let inner = Arc::clone(&self.0); 486 971 let fut = async move { 487 - <T as BrowseService>::tree_get_entries(&inner, request) 488 - .await 972 + <T as LibraryService>::get_album(&inner, request).await 489 973 }; 490 974 Box::pin(fut) 491 975 } ··· 496 980 let max_encoding_message_size = self.max_encoding_message_size; 497 981 let inner = self.inner.clone(); 498 982 let fut = async move { 499 - let method = TreeGetEntriesSvc(inner); 983 + let method = GetAlbumSvc(inner); 500 984 let codec = tonic::codec::ProstCodec::default(); 501 985 let mut grpc = tonic::server::Grpc::new(codec) 502 986 .apply_compression_config( ··· 512 996 }; 513 997 Box::pin(fut) 514 998 } 515 - "/rockbox.v1alpha1.BrowseService/TreeGetEntryAt" => { 999 + "/rockbox.v1alpha1.LibraryService/GetArtist" => { 516 1000 #[allow(non_camel_case_types)] 517 - struct TreeGetEntryAtSvc<T: BrowseService>(pub Arc<T>); 1001 + struct GetArtistSvc<T: LibraryService>(pub Arc<T>); 518 1002 impl< 519 - T: BrowseService, 520 - > tonic::server::UnaryService<super::TreeGetEntryAtRequest> 521 - for TreeGetEntryAtSvc<T> { 522 - type Response = super::TreeGetEntryAtResponse; 1003 + T: LibraryService, 1004 + > tonic::server::UnaryService<super::GetArtistRequest> 1005 + for GetArtistSvc<T> { 1006 + type Response = super::GetArtistResponse; 523 1007 type Future = BoxFuture< 524 1008 tonic::Response<Self::Response>, 525 1009 tonic::Status, 526 1010 >; 527 1011 fn call( 528 1012 &mut self, 529 - request: tonic::Request<super::TreeGetEntryAtRequest>, 1013 + request: tonic::Request<super::GetArtistRequest>, 530 1014 ) -> Self::Future { 531 1015 let inner = Arc::clone(&self.0); 532 1016 let fut = async move { 533 - <T as BrowseService>::tree_get_entry_at(&inner, request) 534 - .await 1017 + <T as LibraryService>::get_artist(&inner, request).await 535 1018 }; 536 1019 Box::pin(fut) 537 1020 } ··· 542 1025 let max_encoding_message_size = self.max_encoding_message_size; 543 1026 let inner = self.inner.clone(); 544 1027 let fut = async move { 545 - let method = TreeGetEntryAtSvc(inner); 1028 + let method = GetArtistSvc(inner); 546 1029 let codec = tonic::codec::ProstCodec::default(); 547 1030 let mut grpc = tonic::server::Grpc::new(codec) 548 1031 .apply_compression_config( ··· 558 1041 }; 559 1042 Box::pin(fut) 560 1043 } 561 - "/rockbox.v1alpha1.BrowseService/BrowseId3" => { 1044 + "/rockbox.v1alpha1.LibraryService/GetTrack" => { 562 1045 #[allow(non_camel_case_types)] 563 - struct BrowseId3Svc<T: BrowseService>(pub Arc<T>); 1046 + struct GetTrackSvc<T: LibraryService>(pub Arc<T>); 564 1047 impl< 565 - T: BrowseService, 566 - > tonic::server::UnaryService<super::BrowseId3Request> 567 - for BrowseId3Svc<T> { 568 - type Response = super::BrowseId3Response; 1048 + T: LibraryService, 1049 + > tonic::server::UnaryService<super::GetTrackRequest> 1050 + for GetTrackSvc<T> { 1051 + type Response = super::GetTrackResponse; 569 1052 type Future = BoxFuture< 570 1053 tonic::Response<Self::Response>, 571 1054 tonic::Status, 572 1055 >; 573 1056 fn call( 574 1057 &mut self, 575 - request: tonic::Request<super::BrowseId3Request>, 1058 + request: tonic::Request<super::GetTrackRequest>, 576 1059 ) -> Self::Future { 577 1060 let inner = Arc::clone(&self.0); 578 1061 let fut = async move { 579 - <T as BrowseService>::browse_id3(&inner, request).await 1062 + <T as LibraryService>::get_track(&inner, request).await 580 1063 }; 581 1064 Box::pin(fut) 582 1065 } ··· 587 1070 let max_encoding_message_size = self.max_encoding_message_size; 588 1071 let inner = self.inner.clone(); 589 1072 let fut = async move { 590 - let method = BrowseId3Svc(inner); 1073 + let method = GetTrackSvc(inner); 591 1074 let codec = tonic::codec::ProstCodec::default(); 592 1075 let mut grpc = tonic::server::Grpc::new(codec) 593 1076 .apply_compression_config( ··· 621 1104 } 622 1105 } 623 1106 } 624 - impl<T> Clone for BrowseServiceServer<T> { 1107 + impl<T> Clone for LibraryServiceServer<T> { 625 1108 fn clone(&self) -> Self { 626 1109 let inner = self.inner.clone(); 627 1110 Self { ··· 634 1117 } 635 1118 } 636 1119 /// Generated gRPC service name 637 - pub const SERVICE_NAME: &str = "rockbox.v1alpha1.BrowseService"; 638 - impl<T> tonic::server::NamedService for BrowseServiceServer<T> { 1120 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.LibraryService"; 1121 + impl<T> tonic::server::NamedService for LibraryServiceServer<T> { 639 1122 const NAME: &'static str = SERVICE_NAME; 640 1123 } 641 1124 }
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

-28
crates/rpc/src/browse.rs
··· 17 17 18 18 #[tonic::async_trait] 19 19 impl BrowseService for Browse { 20 - async fn rockbox_browse( 21 - &self, 22 - request: tonic::Request<RockboxBrowseRequest>, 23 - ) -> Result<tonic::Response<RockboxBrowseResponse>, tonic::Status> { 24 - Ok(tonic::Response::new(RockboxBrowseResponse::default())) 25 - } 26 - 27 - async fn tree_get_context( 28 - &self, 29 - request: tonic::Request<TreeGetContextRequest>, 30 - ) -> Result<tonic::Response<TreeGetContextResponse>, tonic::Status> { 31 - Ok(tonic::Response::new(TreeGetContextResponse::default())) 32 - } 33 - 34 20 async fn tree_get_entries( 35 21 &self, 36 22 request: tonic::Request<TreeGetEntriesRequest>, ··· 52 38 .map(|entry| Entry::from(entry)) 53 39 .collect::<Vec<Entry>>(); 54 40 Ok(tonic::Response::new(TreeGetEntriesResponse { entries })) 55 - } 56 - 57 - async fn tree_get_entry_at( 58 - &self, 59 - request: tonic::Request<TreeGetEntryAtRequest>, 60 - ) -> Result<tonic::Response<TreeGetEntryAtResponse>, tonic::Status> { 61 - Ok(tonic::Response::new(TreeGetEntryAtResponse::default())) 62 - } 63 - 64 - async fn browse_id3( 65 - &self, 66 - request: tonic::Request<BrowseId3Request>, 67 - ) -> Result<tonic::Response<BrowseId3Response>, tonic::Status> { 68 - Ok(tonic::Response::new(BrowseId3Response::default())) 69 41 } 70 42 }
+58 -2
crates/rpc/src/lib.rs
··· 1 1 pub mod browse; 2 + pub mod library; 2 3 pub mod metadata; 3 4 pub mod playback; 4 5 pub mod playlist; ··· 17 18 user_settings::{CompressorSettings, EqBandSetting, ReplaygainSettings, UserSettings}, 18 19 }; 19 20 use v1alpha1::{ 20 - CurrentTrackResponse, Entry, GetGlobalSettingsResponse, GetGlobalStatusResponse, 21 - NextTrackResponse, 21 + Album, Artist, CurrentTrackResponse, Entry, GetGlobalSettingsResponse, 22 + GetGlobalStatusResponse, NextTrackResponse, Track, 22 23 }; 23 24 24 25 #[path = "rockbox.v1alpha1.rs"] ··· 650 651 attr, 651 652 time_write, 652 653 customaction, 654 + } 655 + } 656 + } 657 + 658 + impl From<rockbox_library::entity::artist::Artist> for Artist { 659 + fn from(artist: rockbox_library::entity::artist::Artist) -> Self { 660 + Self { 661 + id: artist.id, 662 + name: artist.name, 663 + bio: artist.bio, 664 + image: artist.image, 665 + } 666 + } 667 + } 668 + 669 + impl From<rockbox_library::entity::album::Album> for Album { 670 + fn from(album: rockbox_library::entity::album::Album) -> Self { 671 + Self { 672 + id: album.id, 673 + title: album.title, 674 + artist: album.artist, 675 + year: album.year, 676 + year_string: album.year_string, 677 + album_art: album.album_art, 678 + md5: album.md5, 679 + } 680 + } 681 + } 682 + 683 + impl From<rockbox_library::entity::track::Track> for Track { 684 + fn from(track: rockbox_library::entity::track::Track) -> Self { 685 + Self { 686 + id: track.id, 687 + path: track.path, 688 + title: track.title, 689 + artist: track.artist, 690 + album: track.album, 691 + album_artist: track.album_artist, 692 + bitrate: track.bitrate, 693 + composer: track.composer, 694 + disc_number: track.disc_number, 695 + filesize: track.filesize, 696 + frequency: track.frequency, 697 + length: track.length, 698 + track_number: track.track_number.unwrap_or_default(), 699 + year: track.year.unwrap_or_default(), 700 + year_string: track.year_string.unwrap_or_default(), 701 + genre: track.genre.unwrap_or_default(), 702 + md5: track.md5, 703 + album_art: track.album_art, 704 + artist_id: Some(track.artist_id), 705 + album_id: Some(track.album_id), 706 + genre_id: Some(track.genre_id), 707 + created_at: track.created_at.to_rfc3339(), 708 + updated_at: track.updated_at.to_rfc3339(), 653 709 } 654 710 } 655 711 }
+96
crates/rpc/src/library.rs
··· 1 + use rockbox_library::repo; 2 + use sqlx::Sqlite; 3 + 4 + use crate::api::rockbox::v1alpha1::{ 5 + library_service_server::LibraryService, GetAlbumRequest, GetAlbumResponse, GetAlbumsRequest, 6 + GetAlbumsResponse, GetArtistRequest, GetArtistResponse, GetArtistsRequest, GetArtistsResponse, 7 + GetTrackRequest, GetTrackResponse, GetTracksRequest, GetTracksResponse, 8 + }; 9 + 10 + pub struct Library { 11 + pool: sqlx::Pool<Sqlite>, 12 + } 13 + 14 + impl Library { 15 + pub fn new(pool: sqlx::Pool<Sqlite>) -> Self { 16 + Self { pool } 17 + } 18 + } 19 + 20 + #[tonic::async_trait] 21 + impl LibraryService for Library { 22 + async fn get_albums( 23 + &self, 24 + _request: tonic::Request<GetAlbumsRequest>, 25 + ) -> Result<tonic::Response<GetAlbumsResponse>, tonic::Status> { 26 + let albums = repo::album::all(self.pool.clone()) 27 + .await 28 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 29 + Ok(tonic::Response::new(GetAlbumsResponse { 30 + albums: albums.into_iter().map(|a| a.into()).collect(), 31 + })) 32 + } 33 + 34 + async fn get_artists( 35 + &self, 36 + _request: tonic::Request<GetArtistsRequest>, 37 + ) -> Result<tonic::Response<GetArtistsResponse>, tonic::Status> { 38 + let artists = repo::artist::all(self.pool.clone()) 39 + .await 40 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 41 + Ok(tonic::Response::new(GetArtistsResponse { 42 + artists: artists.into_iter().map(|a| a.into()).collect(), 43 + })) 44 + } 45 + 46 + async fn get_tracks( 47 + &self, 48 + _request: tonic::Request<GetTracksRequest>, 49 + ) -> Result<tonic::Response<GetTracksResponse>, tonic::Status> { 50 + let tracks = repo::track::all(self.pool.clone()) 51 + .await 52 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 53 + Ok(tonic::Response::new(GetTracksResponse { 54 + tracks: tracks.into_iter().map(|t| t.into()).collect(), 55 + })) 56 + } 57 + 58 + async fn get_album( 59 + &self, 60 + request: tonic::Request<GetAlbumRequest>, 61 + ) -> Result<tonic::Response<GetAlbumResponse>, tonic::Status> { 62 + let params = request.into_inner(); 63 + let album = repo::album::find(self.pool.clone(), &params.id) 64 + .await 65 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 66 + Ok(tonic::Response::new(GetAlbumResponse { 67 + album: album.map(|a| a.into()), 68 + })) 69 + } 70 + 71 + async fn get_artist( 72 + &self, 73 + request: tonic::Request<GetArtistRequest>, 74 + ) -> Result<tonic::Response<GetArtistResponse>, tonic::Status> { 75 + let params = request.into_inner(); 76 + let artist = repo::artist::find(self.pool.clone(), &params.id) 77 + .await 78 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 79 + Ok(tonic::Response::new(GetArtistResponse { 80 + artist: artist.map(|a| a.into()), 81 + })) 82 + } 83 + 84 + async fn get_track( 85 + &self, 86 + request: tonic::Request<GetTrackRequest>, 87 + ) -> Result<tonic::Response<GetTrackResponse>, tonic::Status> { 88 + let params = request.into_inner(); 89 + let track = repo::track::find(self.pool.clone(), &params.id) 90 + .await 91 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 92 + Ok(tonic::Response::new(GetTrackResponse { 93 + track: track.map(|t| t.into()), 94 + })) 95 + } 96 + }
+7
crates/rpc/src/server.rs
··· 3 3 use std::sync::{Arc, Mutex}; 4 4 5 5 use crate::api::rockbox::v1alpha1::browse_service_server::BrowseServiceServer; 6 + use crate::api::rockbox::v1alpha1::library_service_server::LibraryServiceServer; 6 7 use crate::api::rockbox::v1alpha1::playback_service_server::PlaybackServiceServer; 7 8 use crate::api::rockbox::v1alpha1::playlist_service_server::PlaylistServiceServer; 8 9 use crate::api::rockbox::v1alpha1::settings_service_server::SettingsServiceServer; 9 10 use crate::api::rockbox::v1alpha1::sound_service_server::SoundServiceServer; 10 11 use crate::api::rockbox::FILE_DESCRIPTOR_SET; 11 12 use crate::browse::Browse; 13 + use crate::library::Library; 12 14 use crate::playback::Playback; 13 15 use crate::playlist::Playlist; 14 16 use crate::settings::Settings; 15 17 use crate::sound::Sound; 16 18 use crate::system::System; 17 19 use owo_colors::OwoColorize; 20 + use rockbox_library::create_connection_pool; 18 21 use rockbox_sys::events::RockboxCommand; 19 22 use tonic::transport::Server; 20 23 ··· 37 40 ); 38 41 39 42 let client = reqwest::Client::new(); 43 + let pool = create_connection_pool().await?; 40 44 41 45 Server::builder() 42 46 .accept_http1(true) ··· 45 49 .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET) 46 50 .build_v1alpha()?, 47 51 ) 52 + .add_service(tonic_web::enable(LibraryServiceServer::new( 53 + Library::new(pool), 54 + ))) 48 55 .add_service(tonic_web::enable(PlaylistServiceServer::new( 49 56 Playlist::new(cmd_tx.clone(), client.clone()), 50 57 )))