Add details for building

This commit is contained in:
jude 2024-04-16 20:37:42 +01:00
parent 39dad2820d
commit 06ccea56d1
8 changed files with 355 additions and 23 deletions

10
.idea/workspace.xml generated
View File

@ -13,8 +13,14 @@
</component>
<component name="ChangeListManager">
<list default="true" id="52900e09-9584-4b6c-95ff-fbd4ed5d8b2c" name="Changes" comment="Add interface package. Start adding auth stuff for refreshing tokens.">
<change afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change afterPath="$PROJECT_DIR$/etc/playlistd.toml" 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.toml" beforeDir="false" afterPath="$PROJECT_DIR$/Cargo.toml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Containerfile" beforeDir="false" afterPath="$PROJECT_DIR$/Containerfile" 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" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -107,7 +113,7 @@
<env name="SUBSONIC_BASE" value="https://navidrome.jellypro.xyz/rest" />
<env name="SUBSONIC_PASSWORD" value="u7P4xQYZEvC9pFva" />
<env name="SUBSONIC_USERNAME" value="jude" />
<env name="UPDATE_INTERVAL" value="240" />
<env name="DAEMON_INTERVAL" value="240" />
</envs>
<option name="emulateTerminal" value="false" />
<option name="channel" value="DEFAULT" />
@ -153,7 +159,7 @@
<workItem from="1710677603495" duration="13803000" />
<workItem from="1710872266453" duration="225000" />
<workItem from="1713283733149" duration="180000" />
<workItem from="1713283935337" duration="7223000" />
<workItem from="1713283935337" duration="11994000" />
</task>
<task id="LOCAL-00001" summary="Structure">
<created>1692008860369</created>

251
Cargo.lock generated
View File

@ -208,12 +208,61 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "config"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be"
dependencies = [
"async-trait",
"convert_case",
"json5",
"lazy_static",
"nom",
"pathdiff",
"ron",
"rust-ini",
"serde",
"serde_json",
"toml",
"yaml-rust",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -269,6 +318,12 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -302,6 +357,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "dlv-list"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
"const-random",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -549,6 +613,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
[[package]]
name = "hashbrown"
version = "0.14.3"
@ -565,7 +635,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown",
"hashbrown 0.14.3",
]
[[package]]
@ -774,7 +844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.14.3",
]
[[package]]
@ -807,6 +877,17 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -839,6 +920,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
@ -944,6 +1031,7 @@ name = "navidrome-playlists"
version = "0.1.0"
dependencies = [
"axum",
"config",
"getrandom",
"md5",
"reqwest",
@ -1091,6 +1179,16 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "ordered-multimap"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
dependencies = [
"dlv-list",
"hashbrown 0.13.2",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -1120,6 +1218,12 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@ -1135,6 +1239,51 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.51",
]
[[package]]
name = "pest_meta"
version = "2.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "pin-project"
version = "1.1.4"
@ -1297,6 +1446,18 @@ dependencies = [
"winreg",
]
[[package]]
name = "ron"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
dependencies = [
"base64",
"bitflags 2.4.2",
"serde",
"serde_derive",
]
[[package]]
name = "rsa"
version = "0.9.6"
@ -1317,6 +1478,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-ini"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -1436,6 +1607,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1849,6 +2029,15 @@ dependencies = [
"syn 2.0.51",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -1929,6 +2118,40 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tower"
version = "0.4.13"
@ -2001,6 +2224,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicode-bidi"
version = "0.3.15"
@ -2302,6 +2531,15 @@ version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
[[package]]
name = "winnow"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
@ -2312,6 +2550,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zerocopy"
version = "0.7.32"

View File

@ -15,9 +15,16 @@ uuid = { version = "1.7", features = ["v4", "serde"] }
md5 = "0.7.0"
getrandom = "0.2.12"
thiserror = "1.0.58"
config = "0.14.0"
[package.metadata.deb]
depends = "$auto"
assets = [
["etc/playlistd.toml", "etc/playlistd.toml", "600"],
]
conf-files = [
"/etc/playlistd.toml",
]
[package.metadata.deb.systemd-units]
unit-scripts = "systemd"

View File

@ -4,6 +4,6 @@ ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo \
PATH=/usr/local/cargo/bin:$PATH
RUN apt update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y gcc gcc-multilib cmake pkg-config libssl-dev curl mysql-client-8.0 npm
RUN apt update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y gcc gcc-multilib cmake pkg-config libssl-dev curl
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain nightly
RUN cargo install cargo-deb

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# playlistd
A daemon and API for automatically updating Subsonic playlists from Listenbrainz data
*Note: I've not actually tested this against Subsonic, only Navidrome*
## Building & running
This is a standard Rust project and should be uncomplicated to build and run. It does read some
environment variables:
* `SUBSONIC_BASE`, your Subsonic instance's API base. For navidrome users, this
is `<protocol>://<host>/rest`
* `SUBSONIC_USERNAME`, your Subsonic username
* `SUBSONIC_PASSWORD`, your Subsonic password
* `DATABASE_URL`, the URL for a Postgres database
* `DAEMON_INTERVAL`, how often to update playlists
For deploying, these can be configured in `/etc/playlistd.toml`.
### Container build (Ubuntu 20.04)
`podman run --rm --network=host -v "$PWD":/mnt -w /mnt -e "DATABASE_URL=postgres://jude@127.0.0.1/navidrome-playlists" playlistd cargo deb`
## How to use
### Tracked playlist
A tracked playlist is a single Subsonic playlist that tracks data from Listenbrainz. For example,
a playlist that contains your top 15 songs from the past week.
These will work best when your music library is well organised with Musicbrainz metadata.
You can create a tracked playlist by inserting a row into the `tracked_playlist` table.
### Static playlist
TODO, not yet implemented

15
etc/playlistd.toml Normal file
View File

@ -0,0 +1,15 @@
[subsonic]
# The API base URL
base = "http://localhost/rest"
# Your subsonic username
username = "admin"
# Your subsonic password
password = ""
[daemon]
# How often to refresh playlists (in seconds)
interval = 240
[database]
# URL of database to use
url = "postgres:///navidrome-playlists"

View File

@ -1,9 +1,8 @@
use crate::listenbrainz;
use crate::listenbrainz::StatsRange;
use crate::models::TrackedPlaylist;
use crate::subsonic::Subsonic;
use crate::{listenbrainz, CONFIG};
use sqlx::postgres::PgPool;
use std::env;
use std::time::Duration;
use thiserror::Error;
use tokio::time::{interval, MissedTickBehavior};
@ -21,11 +20,19 @@ 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())
.await
.unwrap();
let database = PgPool::connect(
CONFIG
.get()
.unwrap()
.get::<String>("database.url")?
.as_str(),
)
.await
.unwrap();
let mut ticker = interval(Duration::from_secs(env::var("UPDATE_INTERVAL")?.parse()?));
let mut ticker = interval(Duration::from_secs(
CONFIG.get().unwrap().get::<u64>("daemon.interval")?,
));
ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
loop {

View File

@ -11,28 +11,40 @@ use crate::subsonic::SubsonicBuilder;
use axum::extract::State;
use axum::routing::{get, put};
use axum::{extract, Json, Router};
use config::Config;
use serde::Serialize;
use sqlx::postgres::PgPool;
use sqlx::Error;
use std::collections::HashMap;
use std::env;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::sync::OnceLock;
#[derive(Clone)]
struct TrackState {
tracks: Arc<RwLock<HashMap<String, String>>>,
}
pub static CONFIG: OnceLock<Config> = OnceLock::new();
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let database = PgPool::connect(&env::var("DATABASE_URL")?).await?;
CONFIG
.set(
Config::builder()
.add_source(config::File::with_name("/etc/playlistd").required(false))
.add_source(config::Environment::default().separator("_"))
.build()
.unwrap(),
)
.unwrap();
let database = PgPool::connect(
CONFIG
.get()
.unwrap()
.get::<String>("database.url")?
.as_str(),
)
.await?;
sqlx::migrate!().run(&database).await?;
let media_client = SubsonicBuilder::new()
.base(env::var("SUBSONIC_BASE")?)
.username(env::var("SUBSONIC_USERNAME")?)
.password(env::var("SUBSONIC_PASSWORD")?)
.base(CONFIG.get().unwrap().get::<String>("subsonic.base")?)
.username(CONFIG.get().unwrap().get::<String>("subsonic.username")?)
.password(CONFIG.get().unwrap().get::<String>("subsonic.password")?)
.build()?;
tokio::spawn(async move {