Replace axum with Rocket
Axum is shit and doesn't work
This commit is contained in:
parent
ace0f4927a
commit
996504373e
@ -16,6 +16,10 @@
|
|||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.lock" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/daemon/update_playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/daemon/update_playlists.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/models.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/models.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/subsonic.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/subsonic.rs" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -52,42 +56,42 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"Cargo.Run.executor": "Run",
|
"Cargo.Run.executor": "Run",
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
"RunOnceActivity.rust.reset.selective.auto.import": "true",
|
"RunOnceActivity.rust.reset.selective.auto.import": "true",
|
||||||
"WebServerToolWindowFactoryState": "false",
|
"WebServerToolWindowFactoryState": "false",
|
||||||
"cf.first.check.clang-format": "false",
|
"cf.first.check.clang-format": "false",
|
||||||
"cidr.known.project.marker": "true",
|
"cidr.known.project.marker": "true",
|
||||||
"git-widget-placeholder": "master",
|
"git-widget-placeholder": "master",
|
||||||
"last_opened_file_path": "/home/jude/Documents/navidrome-playlists",
|
"last_opened_file_path": "/home/jude/Documents/navidrome-playlists",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
|
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
|
||||||
"org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon": "",
|
"org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon": "",
|
||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/client/mod.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/client/mod.rs": "true",
|
||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/client/playlists.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/client/playlists.rs": "true",
|
||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/lib.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/lib.rs": "true",
|
||||||
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/models.rs": "true",
|
"org.rust.disableDetachedFileInspection/home/jude/navidrome-playlists/navidrome/src/models.rs": "true",
|
||||||
"org.rust.first.attach.projects": "true",
|
"org.rust.first.attach.projects": "true",
|
||||||
"settings.editor.selected.configurable": "language.rust.cargo.check",
|
"settings.editor.selected.configurable": "language.rust.cargo.check",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
"DatabaseDriversLRU": [
|
"DatabaseDriversLRU": [
|
||||||
"postgresql"
|
"postgresql"
|
||||||
],
|
],
|
||||||
"com.intellij.ide.scratch.LRUPopupBuilder$1/SQL Dialect": [
|
"com.intellij.ide.scratch.LRUPopupBuilder$1/SQL Dialect": [
|
||||||
"PostgreSQL"
|
"PostgreSQL"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
<recent name="$PROJECT_DIR$/src/daemon" />
|
<recent name="$PROJECT_DIR$/src/daemon" />
|
||||||
@ -154,7 +158,10 @@
|
|||||||
<workItem from="1710677603495" duration="13803000" />
|
<workItem from="1710677603495" duration="13803000" />
|
||||||
<workItem from="1710872266453" duration="225000" />
|
<workItem from="1710872266453" duration="225000" />
|
||||||
<workItem from="1713283733149" duration="180000" />
|
<workItem from="1713283733149" duration="180000" />
|
||||||
<workItem from="1713283935337" duration="12890000" />
|
<workItem from="1713283935337" duration="13390000" />
|
||||||
|
<workItem from="1713372315382" duration="4924000" />
|
||||||
|
<workItem from="1713385358427" duration="2705000" />
|
||||||
|
<workItem from="1713465993328" duration="1489000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Structure">
|
<task id="LOCAL-00001" summary="Structure">
|
||||||
<created>1692008860369</created>
|
<created>1692008860369</created>
|
||||||
|
1166
Cargo.lock
generated
1166
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@ license = "AGPL-3.0 only"
|
|||||||
description = "Subsonic playlist daemon"
|
description = "Subsonic playlist daemon"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7", features = ["json"] }
|
rocket = { version = "0.5.0", features = ["json"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid"] }
|
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "uuid"] }
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
@ -17,6 +17,7 @@ md5 = "0.7.0"
|
|||||||
getrandom = "0.2.12"
|
getrandom = "0.2.12"
|
||||||
thiserror = "1.0.58"
|
thiserror = "1.0.58"
|
||||||
config = "0.14.0"
|
config = "0.14.0"
|
||||||
|
log = "0.4.21"
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
depends = "$auto"
|
depends = "$auto"
|
||||||
|
@ -2,6 +2,7 @@ use crate::listenbrainz::StatsRange;
|
|||||||
use crate::models::TrackedPlaylist;
|
use crate::models::TrackedPlaylist;
|
||||||
use crate::subsonic::Subsonic;
|
use crate::subsonic::Subsonic;
|
||||||
use crate::{listenbrainz, CONFIG};
|
use crate::{listenbrainz, CONFIG};
|
||||||
|
use log::error;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@ -16,7 +17,7 @@ pub enum UpdatePlaylistError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_playlists_daemon(
|
pub async fn update_playlists_daemon(
|
||||||
media_client: &Subsonic,
|
media_client: impl AsRef<Subsonic> + Clone,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("Playlist daemon starting...");
|
println!("Playlist daemon starting...");
|
||||||
|
|
||||||
@ -43,7 +44,8 @@ pub async fn update_playlists_daemon(
|
|||||||
match records_res {
|
match records_res {
|
||||||
Ok(records) => {
|
Ok(records) => {
|
||||||
for record in records {
|
for record in records {
|
||||||
update_playlist(media_client, record).await.unwrap(); // todo handle me
|
// todo handle me
|
||||||
|
update_playlist(media_client.clone(), record).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +59,7 @@ pub async fn update_playlists_daemon(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn update_playlist(
|
async fn update_playlist(
|
||||||
media_client: &Subsonic,
|
media_client: impl AsRef<Subsonic>,
|
||||||
playlist: TrackedPlaylist,
|
playlist: TrackedPlaylist,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
match playlist.playlist_id {
|
match playlist.playlist_id {
|
||||||
@ -72,6 +74,7 @@ async fn update_playlist(
|
|||||||
let mut tracks = vec![];
|
let mut tracks = vec![];
|
||||||
for recording in recordings {
|
for recording in recordings {
|
||||||
let search_results = media_client
|
let search_results = media_client
|
||||||
|
.as_ref()
|
||||||
.song_search(format!(
|
.song_search(format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
recording.track_name, recording.artist_name
|
recording.track_name, recording.artist_name
|
||||||
@ -91,7 +94,7 @@ async fn update_playlist(
|
|||||||
tracks.push(track.id);
|
tracks.push(track.id);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
println!(
|
error!(
|
||||||
"Couldn't find matching track for {} - {}",
|
"Couldn't find matching track for {} - {}",
|
||||||
recording.track_name, recording.artist_name
|
recording.track_name, recording.artist_name
|
||||||
)
|
)
|
||||||
@ -99,8 +102,12 @@ async fn update_playlist(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let playlist = media_client.get_playlist(playlist_id.to_string()).await?;
|
let playlist = media_client
|
||||||
|
.as_ref()
|
||||||
|
.get_playlist(playlist_id.to_string())
|
||||||
|
.await?;
|
||||||
media_client
|
media_client
|
||||||
|
.as_ref()
|
||||||
.update_playlist(
|
.update_playlist(
|
||||||
playlist_id.to_string(),
|
playlist_id.to_string(),
|
||||||
tracks,
|
tracks,
|
||||||
|
111
src/main.rs
111
src/main.rs
@ -6,16 +6,16 @@ mod subsonic;
|
|||||||
mod track;
|
mod track;
|
||||||
|
|
||||||
use crate::daemon::update_playlists_daemon;
|
use crate::daemon::update_playlists_daemon;
|
||||||
use crate::models::{PartialTrackedPlaylist, TrackedPlaylist};
|
use crate::models::{CreateTrackedPlaylist, PartialTrackedPlaylist, TrackedPlaylist};
|
||||||
use crate::subsonic::SubsonicBuilder;
|
use crate::subsonic::{Subsonic, SubsonicBuilder};
|
||||||
use axum::extract::State;
|
|
||||||
use axum::routing::{get, put};
|
|
||||||
use axum::{extract, Json, Router};
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
|
use rocket::serde::json::{json, Json};
|
||||||
|
use rocket::{get, post, routes, serde::json::Value as JsonValue, State};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
use sqlx::Error;
|
use std::str::FromStr;
|
||||||
use std::sync::OnceLock;
|
use std::sync::{Arc, OnceLock};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub static CONFIG: OnceLock<Config> = OnceLock::new();
|
pub static CONFIG: OnceLock<Config> = OnceLock::new();
|
||||||
|
|
||||||
@ -41,23 +41,32 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.await?;
|
.await?;
|
||||||
sqlx::migrate!().run(&database).await?;
|
sqlx::migrate!().run(&database).await?;
|
||||||
|
|
||||||
let media_client = SubsonicBuilder::new()
|
let media_client = Arc::new(
|
||||||
.base(CONFIG.get().unwrap().get::<String>("subsonic.base")?)
|
SubsonicBuilder::new()
|
||||||
.username(CONFIG.get().unwrap().get::<String>("subsonic.username")?)
|
.base(CONFIG.get().unwrap().get::<String>("subsonic.base")?)
|
||||||
.password(CONFIG.get().unwrap().get::<String>("subsonic.password")?)
|
.username(CONFIG.get().unwrap().get::<String>("subsonic.username")?)
|
||||||
.build()?;
|
.password(CONFIG.get().unwrap().get::<String>("subsonic.password")?)
|
||||||
|
.build()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _media_client = media_client.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
update_playlists_daemon(&media_client).await.unwrap();
|
update_playlists_daemon(_media_client).await.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
let app = Router::new()
|
rocket::build()
|
||||||
.route("/playlists", get(all_playlists))
|
.manage(media_client)
|
||||||
.route("/playlists", put(create_tracking_playlist))
|
.manage(database)
|
||||||
.with_state(database);
|
.mount(
|
||||||
|
"/",
|
||||||
let listener = tokio::net::TcpListener::bind("localhost:3000").await?;
|
routes![
|
||||||
axum::serve(listener, app).await?;
|
create_tracking_playlist,
|
||||||
|
add_tracking_playlist,
|
||||||
|
all_playlists,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.launch()
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -67,8 +76,16 @@ struct JsonError {
|
|||||||
error: String,
|
error: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for JsonError {
|
impl From<sqlx::Error> for JsonError {
|
||||||
fn from(value: Error) -> Self {
|
fn from(value: sqlx::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
error: value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<uuid::Error> for JsonError {
|
||||||
|
fn from(value: uuid::Error) -> Self {
|
||||||
Self {
|
Self {
|
||||||
error: value.to_string(),
|
error: value.to_string(),
|
||||||
}
|
}
|
||||||
@ -81,19 +98,55 @@ enum Response<T> {
|
|||||||
Error(JsonError),
|
Error(JsonError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/create", data = "<create_playlist>")]
|
||||||
async fn create_tracking_playlist(
|
async fn create_tracking_playlist(
|
||||||
State(pool): State<PgPool>,
|
media_client: &State<Arc<Subsonic>>,
|
||||||
extract::Json(partial): extract::Json<PartialTrackedPlaylist>,
|
pool: &State<PgPool>,
|
||||||
|
create_playlist: Json<CreateTrackedPlaylist>,
|
||||||
) -> Json<Response<TrackedPlaylist>> {
|
) -> Json<Response<TrackedPlaylist>> {
|
||||||
match partial.record(&pool).await {
|
match media_client
|
||||||
|
.create_playlist(create_playlist.playlist_name.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(created_playlist) => match Uuid::from_str(&created_playlist.id) {
|
||||||
|
Ok(playlist_id) => {
|
||||||
|
let partial = PartialTrackedPlaylist {
|
||||||
|
playlist_id,
|
||||||
|
playlist_size: create_playlist.playlist_size,
|
||||||
|
tracking_user: create_playlist.tracking_user.clone(),
|
||||||
|
tracking_type: create_playlist.tracking_type.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match partial.record(pool.inner()).await {
|
||||||
|
Ok(playlist) => Json(Response::Success(playlist)),
|
||||||
|
Err(e) => Json(Response::Error(e.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => Json(Response::Error(e.into())),
|
||||||
|
},
|
||||||
|
|
||||||
|
Err(_) => Json(Response::Error(JsonError {
|
||||||
|
error: "Couldn't create Subsonic playlist".to_string(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/add", data = "<playlist>")]
|
||||||
|
async fn add_tracking_playlist(
|
||||||
|
pool: &State<PgPool>,
|
||||||
|
playlist: Json<PartialTrackedPlaylist>,
|
||||||
|
) -> Json<Response<TrackedPlaylist>> {
|
||||||
|
match playlist.record(pool.inner()).await {
|
||||||
Ok(playlist) => Json(Response::Success(playlist)),
|
Ok(playlist) => Json(Response::Success(playlist)),
|
||||||
Err(e) => Json(Response::Error(e.into())),
|
Err(e) => Json(Response::Error(e.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn all_playlists(State(pool): State<PgPool>) -> Json<Vec<TrackedPlaylist>> {
|
#[get("/")]
|
||||||
match TrackedPlaylist::all(&pool).await {
|
async fn all_playlists(pool: &State<PgPool>) -> JsonValue {
|
||||||
Ok(playlists) => Json(playlists),
|
match TrackedPlaylist::all(pool.inner()).await {
|
||||||
Err(_) => Json(vec![]),
|
Ok(playlists) => json!(playlists),
|
||||||
|
Err(_) => json!([]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,17 @@ use sqlx::types::Uuid as UuidType;
|
|||||||
use sqlx::{Executor, Postgres};
|
use sqlx::{Executor, Postgres};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CreateTrackedPlaylist {
|
||||||
|
pub playlist_name: String,
|
||||||
|
pub playlist_size: i32,
|
||||||
|
pub tracking_user: Option<String>,
|
||||||
|
pub tracking_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PartialTrackedPlaylist {
|
pub struct PartialTrackedPlaylist {
|
||||||
pub playlist_id: Option<Uuid>,
|
pub playlist_id: Uuid,
|
||||||
pub playlist_size: i32,
|
pub playlist_size: i32,
|
||||||
pub tracking_user: Option<String>,
|
pub tracking_user: Option<String>,
|
||||||
pub tracking_type: Option<String>,
|
pub tracking_type: Option<String>,
|
||||||
|
@ -2,9 +2,11 @@ use crate::playlist::Playlist;
|
|||||||
use crate::track::Track;
|
use crate::track::Track;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Subsonic {
|
pub struct Subsonic {
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
@ -34,11 +36,24 @@ struct GetPlaylistResponse {
|
|||||||
playlist: Playlist,
|
playlist: Playlist,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreatePlaylistResponse = GetPlaylistResponse;
|
||||||
|
|
||||||
struct AuthParams {
|
struct AuthParams {
|
||||||
salt: String,
|
salt: String,
|
||||||
hash: String,
|
hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum SubsonicError {
|
||||||
|
Reqwest(reqwest::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SubsonicError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Subsonic {
|
impl Subsonic {
|
||||||
fn auth_params(&self) -> AuthParams {
|
fn auth_params(&self) -> AuthParams {
|
||||||
let salt = Uuid::new_v4().simple().to_string();
|
let salt = Uuid::new_v4().simple().to_string();
|
||||||
@ -50,10 +65,7 @@ impl Subsonic {
|
|||||||
AuthParams { salt, hash }
|
AuthParams { salt, hash }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn song_search(
|
pub async fn song_search(&self, query: String) -> Result<Vec<Track>, SubsonicError> {
|
||||||
&self,
|
|
||||||
query: String,
|
|
||||||
) -> Result<Vec<Track>, Box<dyn std::error::Error>> {
|
|
||||||
let auth_params = self.auth_params();
|
let auth_params = self.auth_params();
|
||||||
let query_params = [
|
let query_params = [
|
||||||
("query", query.as_str()),
|
("query", query.as_str()),
|
||||||
@ -72,18 +84,17 @@ impl Subsonic {
|
|||||||
.get(format!("{}/search3", self.base))
|
.get(format!("{}/search3", self.base))
|
||||||
.query(&query_params)
|
.query(&query_params)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?
|
||||||
.json::<SubsonicResponse<SearchResponse>>()
|
.json::<SubsonicResponse<SearchResponse>>()
|
||||||
.await?
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?
|
||||||
.subsonic_response
|
.subsonic_response
|
||||||
.search_results
|
.search_results
|
||||||
.song)
|
.song)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_playlist(
|
pub async fn get_playlist(&self, playlist_id: String) -> Result<Playlist, SubsonicError> {
|
||||||
&self,
|
|
||||||
playlist_id: String,
|
|
||||||
) -> Result<Playlist, Box<dyn std::error::Error>> {
|
|
||||||
let auth_params = self.auth_params();
|
let auth_params = self.auth_params();
|
||||||
|
|
||||||
let get_params = [
|
let get_params = [
|
||||||
@ -101,9 +112,38 @@ impl Subsonic {
|
|||||||
.get(format!("{}/getPlaylist", self.base))
|
.get(format!("{}/getPlaylist", self.base))
|
||||||
.query(&get_params)
|
.query(&get_params)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?
|
||||||
.json::<SubsonicResponse<GetPlaylistResponse>>()
|
.json::<SubsonicResponse<GetPlaylistResponse>>()
|
||||||
.await?
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?
|
||||||
|
.subsonic_response
|
||||||
|
.playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_playlist(&self, playlist_name: String) -> Result<Playlist, SubsonicError> {
|
||||||
|
let auth_params = self.auth_params();
|
||||||
|
|
||||||
|
let get_params = [
|
||||||
|
("u", self.username.to_string()),
|
||||||
|
("s", auth_params.salt.to_string()),
|
||||||
|
("t", auth_params.hash.to_string()),
|
||||||
|
("v", String::from("16")),
|
||||||
|
("c", String::from("playlistd")),
|
||||||
|
("f", String::from("json")),
|
||||||
|
("name", playlist_name),
|
||||||
|
];
|
||||||
|
|
||||||
|
Ok(self
|
||||||
|
.client
|
||||||
|
.get(format!("{}/createPlaylist", self.base))
|
||||||
|
.query(&get_params)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?
|
||||||
|
.json::<SubsonicResponse<CreatePlaylistResponse>>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?
|
||||||
.subsonic_response
|
.subsonic_response
|
||||||
.playlist)
|
.playlist)
|
||||||
}
|
}
|
||||||
@ -113,7 +153,7 @@ impl Subsonic {
|
|||||||
playlist_id: String,
|
playlist_id: String,
|
||||||
add: Vec<String>,
|
add: Vec<String>,
|
||||||
remove: Vec<usize>,
|
remove: Vec<usize>,
|
||||||
) -> Result<Playlist, Box<dyn std::error::Error>> {
|
) -> Result<Playlist, SubsonicError> {
|
||||||
let auth_params = self.auth_params();
|
let auth_params = self.auth_params();
|
||||||
|
|
||||||
let mut update_query = vec![
|
let mut update_query = vec![
|
||||||
@ -138,7 +178,8 @@ impl Subsonic {
|
|||||||
.get(format!("{}/updatePlaylist", self.base))
|
.get(format!("{}/updatePlaylist", self.base))
|
||||||
.query(&update_query)
|
.query(&update_query)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(|e| SubsonicError::Reqwest(e))?;
|
||||||
|
|
||||||
self.get_playlist(playlist_id).await
|
self.get_playlist(playlist_id).await
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user