Track media server songs list
This commit is contained in:
parent
d217dd0b81
commit
9c689d73d6
12
.idea/dataSources.xml
Normal file
12
.idea/dataSources.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="postgres@localhost" uuid="84798454-83c1-4f0a-870a-8e649a07b493">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/navidrome-playlists.iml" filepath="$PROJECT_DIR$/.idea/navidrome-playlists.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
13
.idea/navidrome-playlists.iml
Normal file
13
.idea/navidrome-playlists.iml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="CPP_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/navidrome/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/navidrome/target" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -11,18 +11,20 @@
|
|||||||
<component name="CargoProjects">
|
<component name="CargoProjects">
|
||||||
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
|
||||||
<cargoProject FILE="$PROJECT_DIR$/navidrome/Cargo.toml" />
|
<cargoProject FILE="$PROJECT_DIR$/navidrome/Cargo.toml" />
|
||||||
<cargoProject FILE="$PROJECT_DIR$/listenbrainz/Cargo.toml" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Structure">
|
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Structure">
|
||||||
<change afterPath="$PROJECT_DIR$/src/daemon.rs" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/navidrome/src/client/library.rs" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/listenbrainz.rs" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/daemon/mod.rs" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/models.rs" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/daemon/update_tracks.rs" afterDir="false" />
|
||||||
<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$/listenbrainz/Cargo.toml" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/navidrome/src/client/builder.rs" beforeDir="false" afterPath="$PROJECT_DIR$/navidrome/src/client/builder.rs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/listenbrainz/src/lib.rs" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/navidrome/src/client/mod.rs" beforeDir="false" afterPath="$PROJECT_DIR$/navidrome/src/client/mod.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/navidrome/src/client/playlists.rs" beforeDir="false" afterPath="$PROJECT_DIR$/navidrome/src/client/playlists.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/navidrome/src/models/navidrome.rs" beforeDir="false" afterPath="$PROJECT_DIR$/navidrome/src/models/navidrome.rs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/daemon.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/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
@ -56,12 +58,13 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
|
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
"RunOnceActivity.cidr.known.project.marker": "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",
|
||||||
"last_opened_file_path": "/home/jude/listenbrainz-rs",
|
"last_opened_file_path": "/home/jude/Documents/navidrome-playlists/src/daemon",
|
||||||
"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)",
|
||||||
@ -72,18 +75,50 @@
|
|||||||
"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",
|
||||||
"settings.editor.selected.configurable": "language.rust.rustfmt",
|
"settings.editor.selected.configurable": "language.rust.cargo.check",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
},
|
||||||
|
"keyToStringList": {
|
||||||
|
"DatabaseDriversLRU": [
|
||||||
|
"postgresql"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}]]></component>
|
}]]></component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/src/daemon" />
|
||||||
|
<recent name="$PROJECT_DIR$/navidrome/src/client" />
|
||||||
|
</key>
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/src/daemon" />
|
||||||
<recent name="$PROJECT_DIR$/navidrome/src/models" />
|
<recent name="$PROJECT_DIR$/navidrome/src/models" />
|
||||||
</key>
|
</key>
|
||||||
</component>
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="Run" type="CargoCommandRunConfiguration" factoryName="Cargo Command" nameIsGenerated="true">
|
||||||
|
<option name="command" value="run" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<option name="emulateTerminal" value="false" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<envs>
|
||||||
|
<env name="DATABASE_URL" value="postgres:///navidrome-playlists" />
|
||||||
|
<env name="NAVIDROME_BASE" value="https://navidrome.jellypro.xyz" />
|
||||||
|
<env name="NAVIDROME_TOKEN" value="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG0iOnRydWUsImV4cCI6MTY5NDM2MjkyNywiaWF0IjoxNjk0MjcyNzE1LCJpc3MiOiJORCIsInN1YiI6Imp1ZGUiLCJ1aWQiOiIyZjljNTFiMi03MThmLTRiZmYtYjhkYi03MzE0ODdmZmFhYmIifQ.XNpfkjJI7wXq4EzGM-s7dQAXyCBAyN4Dmy4frbRMXPU" />
|
||||||
|
</envs>
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
<component name="RustProjectSettings">
|
<component name="RustProjectSettings">
|
||||||
<option name="toolchainHomeDirectory" value="$PROJECT_DIR$/../.cargo/bin" />
|
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" />
|
||||||
<option name="version" value="2" />
|
|
||||||
</component>
|
</component>
|
||||||
<component name="RustfmtProjectSettings">
|
<component name="RustfmtProjectSettings">
|
||||||
<option name="runRustfmtOnSave" value="true" />
|
<option name="runRustfmtOnSave" value="true" />
|
||||||
@ -100,6 +135,8 @@
|
|||||||
<workItem from="1691949248703" duration="4525000" />
|
<workItem from="1691949248703" duration="4525000" />
|
||||||
<workItem from="1692003763542" duration="5103000" />
|
<workItem from="1692003763542" duration="5103000" />
|
||||||
<workItem from="1692042976149" duration="10055000" />
|
<workItem from="1692042976149" duration="10055000" />
|
||||||
|
<workItem from="1694271920428" duration="9295000" />
|
||||||
|
<workItem from="1694285959491" duration="137000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Structure">
|
<task id="LOCAL-00001" summary="Structure">
|
||||||
<created>1692008860369</created>
|
<created>1692008860369</created>
|
||||||
@ -118,4 +155,8 @@
|
|||||||
<MESSAGE value="Structure" />
|
<MESSAGE value="Structure" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Structure" />
|
<option name="LAST_COMMIT_MESSAGE" value="Structure" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
|
<expand />
|
||||||
|
<select />
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
73
Cargo.lock
generated
73
Cargo.lock
generated
@ -35,6 +35,21 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.73"
|
version = "0.1.73"
|
||||||
@ -194,6 +209,21 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@ -670,6 +700,29 @@ dependencies = [
|
|||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -855,11 +908,22 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "navidrome"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "navidrome-playlists"
|
name = "navidrome-playlists"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"navidrome",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
@ -2062,6 +2126,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
@ -11,6 +11,7 @@ tokio = { version = "1.0", features = ["full"] }
|
|||||||
sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "uuid"] }
|
sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "uuid"] }
|
||||||
reqwest = { version = "0.11.18", features = ["json"] }
|
reqwest = { version = "0.11.18", features = ["json"] }
|
||||||
serde = { version = "1.0.183", features = ["derive"] }
|
serde = { version = "1.0.183", features = ["derive"] }
|
||||||
|
navidrome = { path = "navidrome" }
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
depends = "$auto"
|
depends = "$auto"
|
||||||
|
@ -1,2 +1,59 @@
|
|||||||
struct NavidromeBuilder {
|
use crate::client::Navidrome;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
|
||||||
|
pub struct NavidromeBuilder {
|
||||||
|
base: Option<String>,
|
||||||
|
token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NavidromeBuilderError {
|
||||||
|
MissingParam,
|
||||||
|
Reqwest(reqwest::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for NavidromeBuilderError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for NavidromeBuilderError {}
|
||||||
|
|
||||||
|
impl NavidromeBuilder {
|
||||||
|
pub fn new() -> NavidromeBuilder {
|
||||||
|
return NavidromeBuilder {
|
||||||
|
base: None,
|
||||||
|
token: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base(&mut self, base: impl ToString) -> &mut NavidromeBuilder {
|
||||||
|
self.base = Some(base.to_string());
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn token(&mut self, token: impl ToString) -> &mut NavidromeBuilder {
|
||||||
|
self.token = Some(token.to_string());
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> Result<Navidrome, NavidromeBuilderError> {
|
||||||
|
let client = reqwest::ClientBuilder::new()
|
||||||
|
.build()
|
||||||
|
.map_err(|e| NavidromeBuilderError::Reqwest(e))?;
|
||||||
|
|
||||||
|
return Ok(Navidrome {
|
||||||
|
base: self
|
||||||
|
.base
|
||||||
|
.clone()
|
||||||
|
.ok_or(NavidromeBuilderError::MissingParam)?,
|
||||||
|
http: client,
|
||||||
|
token: self
|
||||||
|
.token
|
||||||
|
.clone()
|
||||||
|
.ok_or(NavidromeBuilderError::MissingParam)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
22
navidrome/src/client/library.rs
Normal file
22
navidrome/src/client/library.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use crate::client::{MediaResult as Result, Navidrome, QualifyPath};
|
||||||
|
use crate::models::generic::Playlist;
|
||||||
|
use crate::models::navidrome::{NavidromePlaylist, NavidromeTrack};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Library {
|
||||||
|
async fn tracks(&self) -> Result<Vec<NavidromeTrack>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Library for Navidrome {
|
||||||
|
async fn tracks(&self) -> Result<Vec<NavidromeTrack>> {
|
||||||
|
self.http
|
||||||
|
.get(self.path("api/song"))
|
||||||
|
.header("X-Nd-Authorization", format!("Bearer {}", self.token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<Vec<NavidromeTrack>>()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
|
pub mod library;
|
||||||
pub mod playlists;
|
pub mod playlists;
|
||||||
|
|
||||||
trait QualifyPath {
|
trait QualifyPath {
|
||||||
fn path(&self, path: &str) -> String;
|
fn path(&self, path: impl Display) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MediaResult<U> = Result<U, reqwest::Error>;
|
pub type MediaResult<U> = Result<U, reqwest::Error>;
|
||||||
@ -14,7 +17,7 @@ pub struct Navidrome {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl QualifyPath for Navidrome {
|
impl QualifyPath for Navidrome {
|
||||||
fn path(&self, path: &str) -> String {
|
fn path(&self, path: impl Display) -> String {
|
||||||
format!("{}/{}", self.base, path)
|
format!("{}/{}", self.base, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::client::{MediaResult as Result, Navidrome, QualifyPath};
|
use crate::client::{MediaResult as Result, Navidrome, QualifyPath};
|
||||||
use crate::models::generic::Playlist;
|
use crate::models::generic::Playlist;
|
||||||
use crate::models::navidrome::NavidromePlaylist;
|
use crate::models::navidrome::{NavidromePlaylist, NavidromeTrack};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Playlists {
|
pub trait Playlists {
|
||||||
async fn playlists(&self) -> Result<Vec<Playlist>>;
|
async fn playlists(&self) -> Result<Vec<Playlist>>;
|
||||||
async fn playlist(&self, id: String) -> Result<Playlist>;
|
async fn playlist(&self, id: String) -> Result<Playlist>;
|
||||||
|
async fn tracks(&self, id: String) -> Result<Vec<NavidromeTrack>>;
|
||||||
async fn create_playlist(&self) -> Result<Playlist>;
|
async fn create_playlist(&self) -> Result<Playlist>;
|
||||||
async fn delete_playlist(&self) -> Result<()>;
|
async fn delete_playlist(&self) -> Result<()>;
|
||||||
async fn update_playlists(&self) -> Result<()>;
|
async fn update_playlists(&self) -> Result<()>;
|
||||||
@ -26,7 +27,24 @@ impl Playlists for Navidrome {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn playlist(&self, id: String) -> Result<Playlist> {
|
async fn playlist(&self, id: String) -> Result<Playlist> {
|
||||||
todo!()
|
self.http
|
||||||
|
.get(self.path(format!("api/playlist/{}", id)))
|
||||||
|
.header("X-Nd-Authorization", format!("Bearer {}", self.token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<NavidromePlaylist>()
|
||||||
|
.await
|
||||||
|
.map(|pv| (&pv).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tracks(&self, id: String) -> Result<Vec<NavidromeTrack>> {
|
||||||
|
self.http
|
||||||
|
.get(self.path(format!("api/playlist/{}/tracks", id)))
|
||||||
|
.header("X-Nd-Authorization", format!("Bearer {}", self.token))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<Vec<NavidromeTrack>>()
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_playlist(&self) -> Result<Playlist> {
|
async fn create_playlist(&self) -> Result<Playlist> {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use crate::models::generic::Playlist;
|
use crate::models::generic::Playlist;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::iter::Iterator;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct LoginResponse {
|
struct LoginResponse {
|
||||||
@ -34,8 +33,6 @@ pub(crate) struct NavidromePlaylist {
|
|||||||
owner_id: String,
|
owner_id: String,
|
||||||
#[serde(rename = "ownerName")]
|
#[serde(rename = "ownerName")]
|
||||||
owner_name: String,
|
owner_name: String,
|
||||||
#[serde(skip)] // todo fix
|
|
||||||
rules: Option<NavidromePlaylistRule>,
|
|
||||||
#[serde(rename = "songCount")]
|
#[serde(rename = "songCount")]
|
||||||
song_count: u64,
|
song_count: u64,
|
||||||
sync: bool,
|
sync: bool,
|
||||||
@ -54,5 +51,20 @@ impl From<&NavidromePlaylist> for Playlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Clone)]
|
||||||
struct NavidromePlaylistRule {}
|
pub struct NavidromeTrack {
|
||||||
|
title: String,
|
||||||
|
artist: String,
|
||||||
|
#[serde(rename = "albumArtist")]
|
||||||
|
album_artist: String,
|
||||||
|
album: String,
|
||||||
|
#[serde(rename = "albumId")]
|
||||||
|
album_id: String,
|
||||||
|
#[serde(rename = "artistId")]
|
||||||
|
artist_id: String,
|
||||||
|
id: String,
|
||||||
|
#[serde(rename = "mbzTrackId")]
|
||||||
|
pub musicbrainz_track_id: Option<String>,
|
||||||
|
#[serde(rename = "mbzReleaseTrackId")]
|
||||||
|
musicbrainz_release_track_id: Option<String>,
|
||||||
|
}
|
||||||
|
5
src/daemon/mod.rs
Normal file
5
src/daemon/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod update_playlists;
|
||||||
|
pub mod update_tracks;
|
||||||
|
|
||||||
|
pub use update_playlists::update_playlists_daemon;
|
||||||
|
pub use update_tracks::update_tracks_daemon;
|
@ -5,6 +5,8 @@ use std::time::Duration;
|
|||||||
use tokio::time::{interval, MissedTickBehavior};
|
use tokio::time::{interval, MissedTickBehavior};
|
||||||
|
|
||||||
pub async fn update_playlists_daemon() -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn update_playlists_daemon() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Playlist daemon starting...");
|
||||||
|
|
||||||
let database = PgPool::connect(&env::var("DATABASE_URL").unwrap())
|
let database = PgPool::connect(&env::var("DATABASE_URL").unwrap())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
33
src/daemon/update_tracks.rs
Normal file
33
src/daemon/update_tracks.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use navidrome::{
|
||||||
|
client::{library::Library, Navidrome},
|
||||||
|
models::navidrome::NavidromeTrack,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tokio::time::{interval, MissedTickBehavior};
|
||||||
|
|
||||||
|
pub async fn update_tracks_daemon(
|
||||||
|
media_client: &Navidrome,
|
||||||
|
tracks_map: &mut Arc<RwLock<HashMap<String, NavidromeTrack>>>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Tracks daemon starting...");
|
||||||
|
|
||||||
|
let mut ticker = interval(Duration::from_secs(30));
|
||||||
|
ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut tracks_map = tracks_map.write().await;
|
||||||
|
media_client.tracks().await?.iter().for_each(|t| {
|
||||||
|
if let Some(mb_id) = &t.musicbrainz_track_id {
|
||||||
|
tracks_map.insert(mb_id.clone(), t.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Found {} tracks", tracks_map.len());
|
||||||
|
|
||||||
|
ticker.tick().await;
|
||||||
|
}
|
||||||
|
}
|
40
src/main.rs
40
src/main.rs
@ -2,20 +2,54 @@ mod daemon;
|
|||||||
mod listenbrainz;
|
mod listenbrainz;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
use crate::daemon::update_playlists_daemon;
|
use navidrome::{
|
||||||
|
client::{builder::NavidromeBuilder, library::Library},
|
||||||
|
models::navidrome::NavidromeTrack,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::daemon::{update_playlists_daemon, update_tracks_daemon};
|
||||||
use crate::listenbrainz::StatsRange;
|
use crate::listenbrainz::StatsRange;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct TrackState {
|
||||||
|
tracks: Arc<RwLock<HashMap<String, NavidromeTrack>>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let database = PgPool::connect(&env::var("DATABASE_URL")?).await?;
|
let database = PgPool::connect(&env::var("DATABASE_URL")?).await?;
|
||||||
sqlx::migrate!().run(&database).await?;
|
sqlx::migrate!().run(&database).await?;
|
||||||
|
|
||||||
|
let media_client = NavidromeBuilder::new()
|
||||||
|
.base(env::var("NAVIDROME_BASE")?)
|
||||||
|
.token(env::var("NAVIDROME_TOKEN")?)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let mut tracks = Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
|
let state = TrackState {
|
||||||
|
tracks: tracks.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
update_playlists_daemon().await.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
update_tracks_daemon(&media_client, &mut tracks)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
let app = Router::new().route("/", get(index)).with_state(database);
|
let app = Router::new().route("/", get(index)).with_state(database);
|
||||||
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
@ -23,10 +57,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
update_playlists_daemon().await.unwrap();
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user