Compare commits

...
16 Commits
Author SHA1 Message Date
phoenix 7fba068ade Update deps (#4)
songparser / Check (push) Successful in 2m7s
songparser / Clippy (push) Successful in 45s
songparser / Rustfmt (push) Successful in 1m45s
songparser / build (push) Successful in 2m37s
Reviewed-on: #4
2026-08-15 10:53:14 -04:00
phoenix 4da7cc7b1c Dependency name change (#3)
songparser / Check (push) Successful in 4m36s
songparser / build (push) Successful in 2m30s
songparser / Rustfmt (push) Successful in 3m40s
songparser / Clippy (push) Successful in 5m16s
Reviewed-on: #3
2026-07-21 15:30:18 -04:00
phoenix 9372b8ea07 Update (#2)
songparser / Check (push) Successful in 2m3s
songparser / Rustfmt (push) Successful in 52s
songparser / build (push) Successful in 2m0s
songparser / Clippy (push) Successful in 2m12s
Reviewed-on: #2
2026-07-07 22:49:06 -04:00
phoenix 02cc833cd9 Update rust (#1)
Rust Build / Check (push) Successful in 1m2s
Rust Build / Rustfmt (push) Successful in 57s
Rust Build / Test Suite (push) Successful in 1m9s
Rust Build / Clippy (push) Successful in 1m22s
Rust Build / build (push) Successful in 2m6s
Rust Build / Check (pull_request) Successful in 53s
Rust Build / Rustfmt (pull_request) Successful in 43s
Rust Build / Test Suite (pull_request) Successful in 1m35s
Rust Build / Clippy (pull_request) Successful in 2m26s
Rust Build / build (pull_request) Successful in 2m33s
Reviewed-on: #1
2026-05-22 11:21:58 -04:00
phoenix 6a905c7ec6 Update rust (#72)
Reviewed-on: #72
Co-authored-by: phoenix <mail@kundeng.us>
Co-committed-by: phoenix <mail@kundeng.us>
2026-04-05 17:32:13 -04:00
phoenix 36c079fd87 tsk-68: Saving file_type from CoverArt (#70)
Closes #68

Reviewed-on: #70
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-30 20:25:38 +00:00
phoenix 73eedf829d icarus_models version bump (#69)
Reviewed-on: #69
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-30 19:36:06 +00:00
phoenix 9b6ccb032f Refactoring (#67)
Reviewed-on: #67
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-26 19:49:30 +00:00
phoenix 8a8f3c1075 tsk-45: Root directory check (#66)
Closes #45

Reviewed-on: #66
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-23 20:18:05 +00:00
phoenix 113416432b tsk-54: Make Song file random when updating queued song (#65)
Closes #54

Reviewed-on: #65
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 18:09:48 +00:00
phoenix bd351b1157 tsk-62: Use QueuedSong and QueuedCoverArt to replace references to path of Song and CoverArt (#64)
Closes #62

Reviewed-on: #64
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 17:42:00 +00:00
phoenix 106867e5ee tsk-56: Address hard coded file extensions (#63)
Closes #56

Reviewed-on: #63
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 16:34:11 +00:00
phoenix a2128f99e6 tsk-57 (#61)
Closes #57

Reviewed-on: #61
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 16:23:13 +00:00
phoenix 5636f82b29 coverArtQueue change (#60)
Reviewed-on: #60
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 02:43:45 +00:00
phoenix 3bb7fb1010 tsk-55: Utilize the expiration methods in icarus_models (#59)
Closes #55

Reviewed-on: #59
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-20 17:45:05 +00:00
phoenix 98f1c8bdf6 icarus_models version bump (#58)
Reviewed-on: #58
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-20 03:54:37 +00:00
20 changed files with 1428 additions and 1298 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
ROOT_DIRECTORY=/usr/local/bin ROOT_DIRECTORY=/usr/local/bin
ICARUS_BASE_API_URL=http://api:3000 SOARICARUS_BASE_API_URL=http://api:8000
ICARUS_AUTH_BASE_API_URL=http://auth_api:3000 SOARICARUS_AUTH_BASE_API_URL=http://auth_api:8001
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
+4
View File
@@ -0,0 +1,4 @@
ROOT_DIRECTORY=/tmp
SOARICARUS_BASE_API_URL=http://localhost:8000
SOARICARUS_AUTH_BASE_API_URL=http://localhost:8001
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
-4
View File
@@ -1,4 +0,0 @@
ROOT_DIRECTORY=/home/songparser/mydata
ICARUS_BASE_API_URL=http://localhost:3000
ICARUS_AUTH_BASE_API_URL=http://localhost:3001
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
+2 -2
View File
@@ -10,14 +10,14 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 # Important for git describe --tags fetch-depth: 0 # Important for git describe --tags
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.90.0 toolchain: 1.97
components: cargo components: cargo
- name: Extract Version from Cargo.toml - name: Extract Version from Cargo.toml
+17 -30
View File
@@ -1,24 +1,27 @@
name: Rust Build name: songparser
on: on:
push: push:
branches: branches:
- main - main
- devel
pull_request: pull_request:
branches: branches:
- main - main
- devel
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.90.0 toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key
@@ -28,33 +31,16 @@ jobs:
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/gitlab_deploy_key
cargo check cargo check
test:
name: Test Suite
runs-on: ubuntu-24.04
needs: setup_ssh
steps:
- uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.90.0
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key
cargo test
fmt: fmt:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup_ssh needs: setup_ssh
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.90.0 toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
@@ -70,10 +56,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup_ssh needs: setup_ssh
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.90.0 toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy - run: rustup component add clippy
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
@@ -89,10 +76,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: setup_ssh needs: setup_ssh
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.90.0 toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key
@@ -101,4 +89,3 @@ jobs:
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/gitlab_deploy_key
cargo build --release cargo build --release
-39
View File
@@ -1,39 +0,0 @@
stages:
- build
- test
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- mv "$SSH_DEPLOY_KEY" gitlab_deploy_key # copies the file from the variable.
- chmod 600 gitlab_deploy_key
- ssh-add gitlab_deploy_key
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host gitlab.com\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- echo "$KNOWN_HOSTS" >> ~/.ssh/known_hosts
build:
stage: build
image: rust:1.85
script:
- cargo build --release
artifacts:
paths:
- target/release/songparser
expire_in: 1 week
cache:
key: "cargo-cache"
paths:
- target/
- ~/.cargo/
test:
stage: test
image: rust:latest
script:
- cargo test
cache:
key: "cargo-cache"
paths:
- target/
- ~/.cargo/
Generated
+752 -592
View File
File diff suppressed because it is too large Load Diff
+15 -13
View File
@@ -1,18 +1,20 @@
[package] [package]
name = "songparser" name = "songparser"
version = "0.4.1" version = "0.6.2"
edition = "2024" edition = "2024"
rust-version = "1.90" rust-version = "1.95"
license = "MIT"
description = "Service to parse songs queued for processing"
[dependencies] [dependencies]
tokio = { version = "1.47.1", features = ["full"] } tokio = { version = "1.53.1", features = ["full"] }
futures = { version = "0.3.31" } futures = { version = "0.3.34" }
reqwest = { version = "0.12.23", features = ["json", "stream", "multipart"] } reqwest = { version = "0.13.4", features = ["json", "stream", "multipart"] }
serde = { version = "1.0.228", features = ["derive"] } openssl = { version = "0.10.81", features = ["vendored"] }
serde_json = { version = "1.0.145" } serde = { version = "1.0.229", features = ["derive"] }
time = { version = "0.3.44", features = ["macros", "serde"] } serde_json = { version = "1.0.151" }
uuid = { version = "1.18.1", features = ["v4", "serde"] } time = { version = "0.3.55", features = ["macros", "serde"] }
rand = { version = "0.9.2" } uuid = { version = "1.24.1", features = ["v4", "serde"] }
icarus_meta = { git = "ssh://git@git.kundeng.us/phoenix/icarus_meta.git", tag = "v0.4.0" } simeta = { git = "ssh://git@git.kundeng.us/phoenix/simeta.git", tag = "v0.6.2" }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.7.0" } simodels = { git = "ssh://git@git.kundeng.us/phoenix/simodels.git", tag = "v0.11.4" }
icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.5.0" } sienvy = { git = "ssh://git@git.kundeng.us/phoenix/sienvy.git", tag = "v0.8.1" }
+3 -29
View File
@@ -1,63 +1,37 @@
# Stage 1: Build the application FROM rust:1.97 as builder
# Use a specific Rust version for reproducibility. Choose one that matches your development environment.
# Using slim variant for smaller base image
FROM rust:1.90 as builder
# Set the working directory inside the container
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Install build dependencies if needed (e.g., git for cloning)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl3 \ pkg-config libssl3 \
ca-certificates \ ca-certificates \
openssh-client git \ openssh-client git \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# << --- ADD HOST KEY HERE --- >>
# Replace 'yourgithost.com' with the actual hostname (e.g., github.com)
RUN mkdir -p -m 0700 ~/.ssh && \ RUN mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
# Copy Cargo manifests
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
# Build *only* dependencies to leverage Docker cache
# This dummy build caches dependencies as a separate layer
RUN --mount=type=ssh mkdir src && \ RUN --mount=type=ssh mkdir src && \
echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \ echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \
cargo build --release --quiet && \ cargo build --release --quiet && \
rm -rf src target/release/deps/songparser* # Clean up dummy build artifacts (replace songparser) rm -rf src target/release/deps/songparser*
# Copy the actual source code
COPY src ./src COPY src ./src
# If you have other directories like `templates` or `static`, copy them too
COPY .env ./.env COPY .env ./.env
# << --- SSH MOUNT ADDED HERE --- >>
# Build *only* dependencies to leverage Docker cache
# This dummy build caches dependencies as a separate layer
# Mount the SSH agent socket for this command
RUN --mount=type=ssh \ RUN --mount=type=ssh \
cargo build --release --quiet cargo build --release --quiet
# Stage 2: Create the final, smaller runtime image FROM debian:trixie-slim
# Use a minimal base image like debian-slim or even distroless for security/size
FROM ubuntu:24.04
# Install runtime dependencies if needed (e.g., SSL certificates)
RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /usr/local/bin WORKDIR /usr/local/bin
# Copy the compiled binary from the builder stage
# Replace 'songparser' with the actual name of your binary (usually the crate name)
COPY --from=builder /usr/src/app/target/release/songparser . COPY --from=builder /usr/src/app/target/release/songparser .
# Copy other necessary files like .env (if used for runtime config) or static assets
# It's generally better to configure via environment variables in Docker though
COPY --from=builder /usr/src/app/.env . COPY --from=builder /usr/src/app/.env .
# Set the command to run your application
# Ensure this matches the binary name copied above
CMD ["./songparser"] CMD ["./songparser"]
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Kun Deng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3 -3
View File
@@ -1,4 +1,4 @@
# Songparser # songparser
A service that edits the metadata of a queued song and populates it with data. A service that edits the metadata of a queued song and populates it with data.
@@ -11,10 +11,10 @@ it can be changed.
The `SERVICE_PASSPHRASE` env variable should not be changed, but it could be changed. The The `SERVICE_PASSPHRASE` env variable should not be changed, but it could be changed. The
value for this variable should match the value in the `passphrase` table. This would be value for this variable should match the value in the `passphrase` table. This would be
found in the `icarus_auth` project. found in the `soaricarus_auth` project.
Ensure that the URLs for the two APIs are correctly set for the respective env variables Ensure that the URLs for the two APIs are correctly set for the respective env variables
`ICARUS_BASE_API_URL` for Icarus API and `ICARUS_AUTH_BASE_API_URL` for `icarus_auth`. `SOARICARUS_BASE_API_URL` for soaricarus API and `SOARICARUS_AUTH_BASE_API_URL` for `soaricarus_auth`.
If the values are properly set, next is to build the image. The docker image should be If the values are properly set, next is to build the image. The docker image should be
built from the main icarus web API. built from the main icarus web API.
+4 -5
View File
@@ -1,12 +1,11 @@
version: '3.8' # Use a recent version version: '3.8' # Use a recent version
services: services:
# Your Rust Application Service
songparser: songparser:
build: # Tells docker-compose to build the Dockerfile in the current directory build:
context: . context: .
ssh: ["default"] # Uses host's SSH agent ssh: ["default"]
container_name: songparser # Optional: Give the container a specific name container_name: songparser
env_file: env_file:
- .env - .env
restart: unless-stopped # Optional: Restart policy restart: unless-stopped
+23 -38
View File
@@ -1,5 +1,4 @@
pub mod fetch_next_queue_item { pub mod fetch_next_queue_item {
pub async fn fetch_next_queue_item( pub async fn fetch_next_queue_item(
app: &crate::config::App, app: &crate::config::App,
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
@@ -12,9 +11,7 @@ pub mod fetch_next_queue_item {
} }
pub mod response { pub mod response {
use serde::{Deserialize, Serialize}; #[derive(Debug, serde::Deserialize, serde::Serialize)]
#[derive(Debug, Deserialize, Serialize)]
pub struct QueueItem { pub struct QueueItem {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub filename: String, pub filename: String,
@@ -22,7 +19,7 @@ pub mod fetch_next_queue_item {
pub user_id: uuid::Uuid, pub user_id: uuid::Uuid,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SongQueueItem { pub struct SongQueueItem {
pub message: String, pub message: String,
pub data: Vec<QueueItem>, pub data: Vec<QueueItem>,
@@ -78,14 +75,9 @@ pub mod get_metadata_queue {
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let endpoint = String::from("api/v2/song/metadata/queue"); let endpoint = String::from("api/v2/song/metadata/queue");
let api_url = format!("{}/{endpoint}", app.uri); let api_url = format!("{}/{endpoint}?song_queue_id={song_queue_id}", app.uri);
let (key, header) = super::auth_header(app).await; let (key, header) = super::auth_header(app).await;
client client.get(api_url).header(key, header).send().await
.get(api_url)
.query(&[("song_queue_id", song_queue_id)])
.header(key, header)
.send()
.await
} }
pub mod response { pub mod response {
@@ -131,14 +123,9 @@ pub mod get_coverart_queue {
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let endpoint = String::from("api/v2/coverart/queue"); let endpoint = String::from("api/v2/coverart/queue");
let api_url = format!("{}/{endpoint}", app.uri); let api_url = format!("{}/{endpoint}?song_queue_id={song_queue_id}", app.uri);
let (key, header) = super::auth_header(app).await; let (key, header) = super::auth_header(app).await;
client client.get(api_url).header(key, header).send().await
.get(api_url)
.query(&[("song_queue_id", song_queue_id)])
.header(key, header)
.send()
.await
} }
pub async fn get_data( pub async fn get_data(
@@ -158,6 +145,7 @@ pub mod get_coverart_queue {
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct CoverArtQueue { pub struct CoverArtQueue {
pub id: uuid::Uuid, pub id: uuid::Uuid,
pub file_type: String,
pub song_queue_id: uuid::Uuid, pub song_queue_id: uuid::Uuid,
} }
@@ -174,7 +162,7 @@ pub mod service_token {
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>, pub data: Vec<simodels::login_result::LoginResult>,
} }
} }
} }
@@ -184,7 +172,7 @@ pub mod refresh_token {
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>, pub data: Vec<simodels::login_result::LoginResult>,
} }
} }
} }
@@ -192,21 +180,19 @@ pub mod refresh_token {
pub mod update_queued_song { pub mod update_queued_song {
pub async fn update_queued_song( pub async fn update_queued_song(
app: &crate::config::App, app: &crate::config::App,
song_path: &String, queued_song: &crate::queued_item::QueuedSong,
song_queue_id: &uuid::Uuid,
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
let client = reqwest::Client::builder().build()?; let client = reqwest::Client::builder().build()?;
println!("Song path: {song_path:?}"); println!("Queued song path: {:?}", queued_song.path);
// TODO: Make the filename random
let form = reqwest::multipart::Form::new().part( let form = reqwest::multipart::Form::new().part(
"file", "file",
reqwest::multipart::Part::bytes(std::fs::read(song_path).unwrap()) reqwest::multipart::Part::bytes(std::fs::read(&queued_song.path).unwrap())
.file_name("track01.flac"), .file_name(queued_song.song.filename.clone()),
); );
let url = format!("{}/api/v2/song/queue/{song_queue_id}", app.uri); let url = format!("{}/api/v2/song/queue/{}", app.uri, queued_song.id);
println!("Url: {url:?}"); println!("Url: {url:?}");
let (key, header) = crate::api::auth_header(app).await; let (key, header) = crate::api::auth_header(app).await;
@@ -267,21 +253,20 @@ pub mod create_song {
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::song::Song>, pub data: Vec<simodels::song::Song>,
} }
} }
} }
pub mod create_coverart { pub mod create_coverart {
pub async fn create( pub async fn create(
app: &crate::config::App, app: &crate::config::App,
song_id: &uuid::Uuid, song: &simodels::song::Song,
coverart_queue_id: &uuid::Uuid, queued_coverart: &crate::queued_item::QueuedCoverArt,
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
let client = reqwest::Client::builder().build()?; let client = reqwest::Client::builder().build()?;
let url = format!("{}/api/v2/coverart", app.uri); let url = format!("{}/api/v2/coverart", app.uri);
let payload = get_payload(song_id, coverart_queue_id); let payload = get_payload(&song.id, &queued_coverart.id);
let (key, header) = crate::api::auth_header(app).await; let (key, header) = crate::api::auth_header(app).await;
let request = client.post(url).json(&payload).header(key, header); let request = client.post(url).json(&payload).header(key, header);
@@ -299,7 +284,7 @@ pub mod create_coverart {
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::coverart::CoverArt>, pub data: Vec<simodels::coverart::CoverArt>,
} }
} }
} }
@@ -308,12 +293,12 @@ pub mod wipe_data {
pub mod song_queue { pub mod song_queue {
pub async fn wipe_data( pub async fn wipe_data(
app: &crate::config::App, app: &crate::config::App,
song_queue_id: &uuid::Uuid, queued_song: &crate::queued_item::QueuedSong,
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
let client = reqwest::Client::builder().build()?; let client = reqwest::Client::builder().build()?;
let url = format!("{}/api/v2/song/queue/data/wipe", app.uri); let url = format!("{}/api/v2/song/queue/data/wipe", app.uri);
let payload = serde_json::json!({ let payload = serde_json::json!({
"song_queue_id": song_queue_id "song_queue_id": queued_song.id
}); });
let (key, header) = crate::api::auth_header(app).await; let (key, header) = crate::api::auth_header(app).await;
let request = client.patch(url).json(&payload).header(key, header); let request = client.patch(url).json(&payload).header(key, header);
@@ -332,12 +317,12 @@ pub mod wipe_data {
pub mod coverart_queue { pub mod coverart_queue {
pub async fn wipe_data( pub async fn wipe_data(
app: &crate::config::App, app: &crate::config::App,
coverart_queue_id: &uuid::Uuid, queued_coverart: &crate::queued_item::QueuedCoverArt,
) -> Result<reqwest::Response, reqwest::Error> { ) -> Result<reqwest::Response, reqwest::Error> {
let client = reqwest::Client::builder().build()?; let client = reqwest::Client::builder().build()?;
let url = format!("{}/api/v2/coverart/queue/data/wipe", app.uri); let url = format!("{}/api/v2/coverart/queue/data/wipe", app.uri);
let payload = serde_json::json!({ let payload = serde_json::json!({
"coverart_queue_id": coverart_queue_id "coverart_queue_id": queued_coverart.id
}); });
let (key, header) = crate::api::auth_header(app).await; let (key, header) = crate::api::auth_header(app).await;
let request = client.patch(url).json(&payload).header(key, header); let request = client.patch(url).json(&payload).header(key, header);
+57
View File
@@ -0,0 +1,57 @@
pub async fn get_token(
app: &crate::config::App,
) -> Result<simodels::login_result::LoginResult, std::io::Error> {
let client = reqwest::Client::new();
let endpoint = String::from("api/v2/service/login");
let api_url = format!("{}/{endpoint}", app.auth_uri);
let payload = serde_json::json!({
"passphrase": sienvy::environment::get_service_passphrase().value,
});
match client.post(api_url).json(&payload).send().await {
Ok(response) => match response
.json::<crate::api::service_token::response::Response>()
.await
{
Ok(resp) => {
if resp.data.is_empty() {
Err(std::io::Error::other(String::from("No token returned")))
} else {
Ok(resp.data[0].clone())
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
pub async fn get_refresh_token(
app: &crate::config::App,
) -> Result<simodels::login_result::LoginResult, std::io::Error> {
let client = reqwest::Client::new();
let endpoint = String::from("api/v2/token/refresh");
let api_url = format!("{}/{endpoint}", app.auth_uri);
let payload = serde_json::json!({
"access_token": app.token.token
});
match client.post(api_url).json(&payload).send().await {
Ok(response) => match response
.json::<crate::api::refresh_token::response::Response>()
.await
{
Ok(resp) => {
if resp.data.is_empty() {
Err(std::io::Error::other(String::from("No token returned")))
} else {
Ok(resp.data[0].clone())
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
+18 -1
View File
@@ -2,5 +2,22 @@
pub struct App { pub struct App {
pub uri: String, pub uri: String,
pub auth_uri: String, pub auth_uri: String,
pub token: icarus_models::login_result::LoginResult, pub token: simodels::login_result::LoginResult,
pub root_directory: String,
}
impl App {
pub fn does_root_directory_exists(&self) -> bool {
let path = std::path::Path::new(&self.root_directory);
if path.exists() { path.is_dir() } else { false }
}
}
pub async fn initialize_app_config() -> App {
App {
uri: sienvy::environment::get_soaricarus_base_api_url().value,
auth_uri: sienvy::environment::get_soaricarus_auth_base_api_url().value,
root_directory: sienvy::environment::get_root_directory().value,
..Default::default()
}
} }
+28 -540
View File
@@ -1,27 +1,37 @@
pub mod api; pub mod api;
pub mod auth;
pub mod config; pub mod config;
pub mod metadata;
pub mod parser;
pub mod queue;
pub mod queued_item;
pub mod util; pub mod util;
pub const SECONDS_TO_SLEEP: u64 = 5; pub const SECONDS_TO_SLEEP: u64 = 5;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut app = config::App { let mut app = config::initialize_app_config().await;
uri: icarus_envy::environment::get_icarus_base_api_url()
.await
.value,
auth_uri: icarus_envy::environment::get_icarus_auth_base_api_url()
.await
.value,
..Default::default()
};
println!("Base URL: {:?}", app.uri); println!("Base URL: {:?}", app.uri);
println!("Auth URL: {:?}", app.auth_uri); println!("Auth URL: {:?}", app.auth_uri);
match auth::get_token(&app).await { if !app.does_root_directory_exists() {
Ok(login_result) => { eprintln!("Root directory does not exist");
app.token = login_result; println!("Attempting to create directory");
let path = std::path::Path::new(&app.root_directory);
match std::fs::create_dir(path) {
Ok(_) => {
println!("Directory created");
} }
Err(err) => {
eprintln!("Error creating directory: {err:?}");
std::process::exit(-1);
}
}
}
app.token = match auth::get_token(&app).await {
Ok(login_result) => login_result,
Err(err) => { Err(err) => {
eprintln!("Error: {err:?}"); eprintln!("Error: {err:?}");
std::process::exit(-1); std::process::exit(-1);
@@ -29,9 +39,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}; };
loop { loop {
println!("Token: {:?}", app.token); if app.token.token_expired() {
if auth::did_token_expire(&app.token).await {
println!("Token expired"); println!("Token expired");
app.token = match auth::get_refresh_token(&app).await { app.token = match auth::get_refresh_token(&app).await {
Ok(login_result) => login_result, Ok(login_result) => login_result,
@@ -47,7 +55,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Token did not expire"); println!("Token did not expire");
} }
match is_queue_empty(&app).await { match queue::is_queue_empty(&app).await {
Ok((empty, song_queue_item)) => { Ok((empty, song_queue_item)) => {
if !empty { if !empty {
println!("Queue is not empty"); println!("Queue is not empty");
@@ -56,17 +64,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let song_queue_id = song_queue_item.data[0].id; let song_queue_id = song_queue_item.data[0].id;
let user_id = song_queue_item.data[0].user_id; let user_id = song_queue_item.data[0].user_id;
match some_work(&app, &song_queue_id, &user_id).await { match parser::some_work(&app, &song_queue_id, &user_id).await {
Ok(( Ok((song, coverart, _metadata, queued_song, queued_coverart)) => {
song, match queue::wipe_data_from_queues(&app, &queued_song, &queued_coverart)
coverart,
(song_queue_id, _song_queue_path),
(coverart_queue_id, _coverart_queue_path),
)) => {
match wipe_data_from_queues(&app, &song_queue_id, &coverart_queue_id)
.await .await
{ {
Ok(_) => match cleanup(&song, &coverart).await { Ok(_) => match parser::cleanup(&song, &coverart).await {
Ok(_) => { Ok(_) => {
println!("Successful cleanup"); println!("Successful cleanup");
} }
@@ -96,518 +99,3 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio::time::sleep(tokio::time::Duration::from_secs(SECONDS_TO_SLEEP)).await; tokio::time::sleep(tokio::time::Duration::from_secs(SECONDS_TO_SLEEP)).await;
} }
} }
mod auth {
pub async fn get_token(
app: &crate::config::App,
) -> Result<icarus_models::login_result::LoginResult, std::io::Error> {
let client = reqwest::Client::new();
let endpoint = String::from("api/v2/service/login");
let api_url = format!("{}/{endpoint}", app.auth_uri);
let payload = serde_json::json!({
"passphrase": icarus_envy::environment::get_service_passphrase().await.value,
});
match client.post(api_url).json(&payload).send().await {
Ok(response) => match response
.json::<crate::api::service_token::response::Response>()
.await
{
Ok(resp) => {
if resp.data.is_empty() {
Err(std::io::Error::other(String::from("No token returned")))
} else {
Ok(resp.data[0].clone())
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
// TODO: Might want to put the functionality within icarus_models at some point
pub async fn did_token_expire(login_result: &icarus_models::login_result::LoginResult) -> bool {
let current_time = time::OffsetDateTime::now_utc();
let expire_time =
time::OffsetDateTime::from_unix_timestamp(login_result.expiration).unwrap();
current_time > expire_time
}
pub async fn get_refresh_token(
app: &crate::config::App,
) -> Result<icarus_models::login_result::LoginResult, std::io::Error> {
let client = reqwest::Client::new();
let endpoint = String::from("api/v2/token/refresh");
let api_url = format!("{}/{endpoint}", app.auth_uri);
let payload = serde_json::json!({
"access_token": app.token.token
});
match client.post(api_url).json(&payload).send().await {
Ok(response) => match response
.json::<crate::api::refresh_token::response::Response>()
.await
{
Ok(resp) => {
if resp.data.is_empty() {
Err(std::io::Error::other(String::from("No token returned")))
} else {
Ok(resp.data[0].clone())
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
}
async fn wipe_data_from_queues(
app: &config::App,
song_queue_id: &uuid::Uuid,
coverart_queue_id: &uuid::Uuid,
) -> Result<(), std::io::Error> {
match api::wipe_data::song_queue::wipe_data(app, song_queue_id).await {
Ok(response) => match response
.json::<api::wipe_data::song_queue::response::Response>()
.await
{
Ok(_resp) => {
match api::wipe_data::coverart_queue::wipe_data(app, coverart_queue_id).await {
Ok(inner_response) => match inner_response
.json::<api::wipe_data::coverart_queue::response::Response>()
.await
{
Ok(_inner_resp) => {
println!("Wiped data from CoverArt queue");
println!("Resp: {_inner_resp:?}");
Ok(())
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
async fn cleanup(
song: &icarus_models::song::Song,
coverart: &icarus_models::coverart::CoverArt,
) -> Result<(), std::io::Error> {
match song.remove_from_filesystem() {
Ok(_) => {}
Err(err) => {
eprintln!("Error: Problem cleaning up SongQueue files {err:?}");
}
}
match coverart.remove_from_filesystem() {
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
async fn is_queue_empty(
app: &config::App,
) -> Result<(bool, api::fetch_next_queue_item::response::SongQueueItem), reqwest::Error> {
match api::fetch_next_queue_item::fetch_next_queue_item(app).await {
Ok(response) => {
match response
.json::<api::fetch_next_queue_item::response::SongQueueItem>()
.await
{
Ok(response) => {
if response.data.is_empty() {
Ok((true, response))
} else {
Ok((false, response))
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
async fn some_work(
app: &crate::config::App,
song_queue_id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> Result<
(
icarus_models::song::Song,
icarus_models::coverart::CoverArt,
(uuid::Uuid, String),
(uuid::Uuid, String),
),
std::io::Error,
> {
match prep_song(app, song_queue_id).await {
Ok(((song_directory, song_filename), coverart_queue_path, metadata, coverart_queue_id)) => {
let mut song_queue_path: String = String::new();
let p = std::path::Path::new(&song_directory);
let sp = p.join(&song_filename);
song_queue_path.push_str(sp.to_str().unwrap_or_default());
match apply_metadata(&song_queue_path, &coverart_queue_path, &metadata).await {
Ok(_applied) => {
match api::update_queued_song::update_queued_song(
app,
&song_queue_path,
song_queue_id,
)
.await
{
Ok(response) => {
match response
.json::<api::update_queued_song::response::Response>()
.await
{
Ok(_inner_response) => {
println!("Response: {_inner_response:?}");
// TODO: Place this somewhere else
let song_type = String::from("flac");
match api::create_song::create(
app, &metadata, user_id, &song_type,
)
.await
{
Ok(response) => match response
.json::<api::create_song::response::Response>()
.await
{
Ok(resp) => {
println!("Response: {resp:?}");
let mut song = resp.data[0].clone();
song.directory = song_directory;
song.filename = song_filename;
match api::create_coverart::create(app, &song.id, &coverart_queue_id).await {
Ok(response) => match response.json::<api::create_coverart::response::Response>().await {
Ok(resp) => {
println!("CoverArt sent and successfully parsed response");
println!("json: {resp:?}");
let mut coverart = resp.data[0].clone();
coverart.path = coverart_queue_path.clone();
Ok((song.clone(), coverart.clone(), (metadata.song_queue_id, song_queue_path), (coverart_queue_id, coverart_queue_path)))
}
Err(err) => {
Err(std::io::Error::other(err.to_string()))
}
}
Err(err) => {
Err(std::io::Error::other(err.to_string()))
}
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
async fn prep_song(
app: &crate::config::App,
song_queue_id: &uuid::Uuid,
) -> Result<
(
(String, String),
String,
api::get_metadata_queue::response::Metadata,
uuid::Uuid,
),
reqwest::Error,
> {
match api::fetch_song_queue_data::get_data(app, song_queue_id).await {
Ok(response) => {
// Process data here...
match api::parsing::parse_response_into_bytes(response).await {
Ok(song_bytes) => {
let (song_directory, song_filename) =
generate_song_queue_dir_and_filename().await;
let song = icarus_models::song::Song {
directory: song_directory,
filename: song_filename,
data: song_bytes,
..Default::default()
};
let songpath = song.song_path().unwrap_or_default();
let song_queue_path = match song.save_to_filesystem() {
Ok(_) => std::path::Path::new(&songpath),
Err(_err) => std::path::Path::new(""),
};
println!("Saved at: {song_queue_path:?}");
match api::get_metadata_queue::get(app, song_queue_id).await {
Ok(response) => {
match response
.json::<api::get_metadata_queue::response::Response>()
.await
{
Ok(response) => {
let id = &response.data[0].id;
let created_at = &response.data[0].created_at;
let metadata = &response.data[0].metadata;
println!("Id: {id:?}");
println!("Metadata: {metadata:?}");
println!("Created at: {created_at:?}");
println!("Getting coverart queue");
match api::get_coverart_queue::get(app, song_queue_id).await {
Ok(response) => {
match response.json::<api::get_coverart_queue::response::Response>().await {
Ok(response) => {
let coverart_queue_id = &response.data[0].id;
println!("Coverart queue Id: {coverart_queue_id:?}");
match api::get_coverart_queue::get_data(app, coverart_queue_id).await {
Ok(response) => match api::parsing::parse_response_into_bytes(response).await {
Ok(coverart_queue_bytes) => {
let (directory, filename) = generate_coverart_queue_dir_and_filename().await;
let coverart = icarus_models::coverart::CoverArt {
path: directory + "/" + &filename,
data: coverart_queue_bytes,
..Default::default()
};
coverart.save_to_filesystem().unwrap();
let coverart_queue_path = std::path::Path::new(&coverart.path);
println!("Saved coverart queue file at: {coverart_queue_path:?}");
let c_path = util::path_buf_to_string(coverart_queue_path);
Ok(((song.directory, song.filename), c_path, metadata.clone(), *coverart_queue_id))
}
Err(err) => {
Err(err)
}
}
Err(err) => {
Err(err)
}
}
}
Err(err) => {
Err(err)
}
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
// TODO: Consider having something like this in icarus_models
pub async fn generate_song_queue_dir_and_filename() -> (String, String) {
let mut song = icarus_models::song::Song::default();
song.filename = icarus_models::song::generate_filename(
icarus_models::types::MusicTypes::FlacExtension,
true,
);
song.directory = icarus_envy::environment::get_root_directory().await.value;
(song.directory, song.filename)
}
// TODO: Consider having something like this in icarus_models
pub async fn generate_coverart_queue_dir_and_filename() -> (String, String) {
use rand::Rng;
let mut filename: String = String::new();
let filename_len = 10;
let some_chars: String = String::from("abcdefghij0123456789");
let mut rng = rand::rng();
for _i in 0..filename_len {
let random_number: i32 = rng.random_range(0..=19);
let index = random_number as usize;
let rando_char = some_chars.chars().nth(index);
if let Some(c) = rando_char {
filename.push(c);
}
}
// TODO: Do not hard code the file extension
filename += ".jpeg";
// TODO: Consider separating song and coverart when saving to the filesystem
let directory = icarus_envy::environment::get_root_directory().await.value;
(directory, filename)
}
pub async fn apply_metadata(
song_queue_path: &String,
coverart_queue_path: &String,
metadata: &api::get_metadata_queue::response::Metadata,
) -> Result<bool, std::io::Error> {
// Apply metadata fields
let types = icarus_meta::types::all_metadata_types();
for t in types {
match t {
icarus_meta::types::Type::Album => {
let meta_type =
icarus_meta::types::MetadataType::from_string(metadata.album.clone());
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::AlbumArtist => {
let meta_type =
icarus_meta::types::MetadataType::from_string(metadata.album_artist.clone());
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::Artist => {
let meta_type =
icarus_meta::types::MetadataType::from_string(metadata.artist.clone());
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::Date => {
// TODO: Do something about this discrepancy
let meta_type =
icarus_meta::types::MetadataType::from_string(metadata.year.to_string());
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::Disc => {
let meta_type = icarus_meta::types::MetadataType::from_int(metadata.disc);
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::Genre => {
let meta_type =
icarus_meta::types::MetadataType::from_string(metadata.genre.clone());
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::Title => {
let meta_type =
icarus_meta::types::MetadataType::from_string(metadata.title.clone());
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::Track => {
let meta_type = icarus_meta::types::MetadataType::from_int(metadata.track);
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::TrackCount => {
let meta_type = icarus_meta::types::MetadataType::from_int(metadata.track_count);
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
icarus_meta::types::Type::DiscCount => {
let meta_type = icarus_meta::types::MetadataType::from_int(metadata.disc_count);
match icarus_meta::meta::metadata::set_meta_value(t, song_queue_path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
}
}
// Apply coverart
match icarus_meta::meta::coverart::contains_coverart(song_queue_path) {
Ok((exists, size)) => {
if exists {
println!("Coverart exists: {size:?} size");
match icarus_meta::meta::coverart::remove_coverart(song_queue_path) {
Ok(_data) => {}
Err(err) => {
return Err(err);
}
}
}
match icarus_meta::meta::coverart::set_coverart(song_queue_path, coverart_queue_path) {
Ok(_data) => {
if _data.is_empty() {
println!("There was an issue");
Ok(false)
} else {
println!("Success in applying coverart to song");
Ok(true)
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
+135
View File
@@ -0,0 +1,135 @@
/// Applies metadata to the queued song
pub async fn apply_metadata(
queued_song: &crate::queued_item::QueuedSong,
queued_coverart: &crate::queued_item::QueuedCoverArt,
metadata: &crate::api::get_metadata_queue::response::Metadata,
) -> Result<bool, std::io::Error> {
// Apply metadata fields
let types = simeta::types::all_metadata_types();
for t in types {
match t {
simeta::types::Type::Album => {
let meta_type = simeta::types::MetadataType::from_string(metadata.album.clone());
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::AlbumArtist => {
let meta_type =
simeta::types::MetadataType::from_string(metadata.album_artist.clone());
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::Artist => {
let meta_type = simeta::types::MetadataType::from_string(metadata.artist.clone());
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::Date => {
// TODO: Do something about this discrepancy
let meta_type = simeta::types::MetadataType::from_string(metadata.year.to_string());
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::Disc => {
let meta_type = simeta::types::MetadataType::from_int(metadata.disc);
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::Genre => {
let meta_type = simeta::types::MetadataType::from_string(metadata.genre.clone());
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::Title => {
let meta_type = simeta::types::MetadataType::from_string(metadata.title.clone());
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::Track => {
let meta_type = simeta::types::MetadataType::from_int(metadata.track);
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::TrackCount => {
let meta_type = simeta::types::MetadataType::from_int(metadata.track_count);
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
simeta::types::Type::DiscCount => {
let meta_type = simeta::types::MetadataType::from_int(metadata.disc_count);
match simeta::meta::metadata::set_meta_value(t, &queued_song.path, meta_type) {
Ok(_) => {}
Err(_err) => {
return Err(_err);
}
}
}
}
}
// Apply coverart
match simeta::meta::coverart::contains_coverart(&queued_song.path) {
Ok((exists, size)) => {
if exists {
println!("Coverart exists: {size:?} size");
match simeta::meta::coverart::remove_coverart(&queued_song.path) {
Ok(_data) => {}
Err(err) => {
return Err(err);
}
}
}
match simeta::meta::coverart::set_coverart(&queued_song.path, &queued_coverart.path) {
Ok(_data) => {
if _data.is_empty() {
println!("There was an issue");
Ok(false)
} else {
println!("Success in applying coverart to song");
Ok(true)
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
+271
View File
@@ -0,0 +1,271 @@
pub async fn some_work(
app: &crate::config::App,
song_queue_id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> Result<
(
simodels::song::Song,
simodels::coverart::CoverArt,
crate::api::get_metadata_queue::response::Metadata,
crate::queued_item::QueuedSong,
crate::queued_item::QueuedCoverArt,
),
std::io::Error,
> {
match prep_song(app, song_queue_id).await {
Ok((queued_song, queued_coverart, metadata)) => {
println!("Prepping song");
match crate::metadata::apply_metadata(&queued_song, &queued_coverart, &metadata).await {
Ok(_applied) => {
match crate::api::update_queued_song::update_queued_song(app, &queued_song)
.await
{
Ok(response) => {
match response
.json::<crate::api::update_queued_song::response::Response>()
.await
{
Ok(_inner_response) => {
println!("Updated queued song");
println!("Response: {_inner_response:?}");
let song_type =
String::from(simeta::detection::song::constants::FLAC_TYPE);
match crate::api::create_song::create(
app, &metadata, user_id, &song_type,
)
.await
{
Ok(response) => match response
.json::<crate::api::create_song::response::Response>()
.await
{
Ok(resp) => {
println!("Response: {resp:?}");
let mut song = resp.data[0].clone();
song.directory = queued_song.song.directory.clone();
song.filename = queued_song.song.filename.clone();
match crate::api::create_coverart::create(app, &song, &queued_coverart).await {
Ok(response) => match response.json::<crate::api::create_coverart::response::Response>().await {
Ok(resp) => {
println!("CoverArt sent and successfully parsed response");
println!("json: {resp:?}");
let mut coverart = resp.data[0].clone();
coverart.directory = queued_coverart.coverart.directory.clone();
coverart.filename = queued_coverart.coverart.filename.clone();
Ok((song.clone(), coverart.clone(), metadata, queued_song.clone(), queued_coverart.clone()))
}
Err(err) => {
Err(std::io::Error::other(err.to_string()))
}
}
Err(err) => {
Err(std::io::Error::other(err.to_string()))
}
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
pub async fn prep_song(
app: &crate::config::App,
song_queue_id: &uuid::Uuid,
) -> Result<
(
crate::queued_item::QueuedSong,
crate::queued_item::QueuedCoverArt,
crate::api::get_metadata_queue::response::Metadata,
),
reqwest::Error,
> {
match crate::api::fetch_song_queue_data::get_data(app, song_queue_id).await {
Ok(response) => {
// Process data here...
match crate::api::parsing::parse_response_into_bytes(response).await {
Ok(song_bytes) => {
let song = simodels::song::Song {
directory: sienvy::environment::get_root_directory().value,
filename: simodels::song::generate_filename(
simodels::types::MusicType::FlacExtension,
true,
)
.unwrap(),
data: song_bytes,
..Default::default()
};
let songpath = song.song_path().unwrap_or_default();
let queued_song: crate::queued_item::QueuedSong =
match song.save_to_filesystem() {
Ok(_) => crate::queued_item::QueuedSong {
id: *song_queue_id,
song,
path: songpath,
},
Err(err) => {
eprintln!("Error: {err:?}");
crate::queued_item::QueuedSong {
..Default::default()
}
}
};
println!("Saved at: {:?}", queued_song.path);
match crate::api::get_metadata_queue::get(app, &queued_song.id).await {
Ok(response) => {
match response
.json::<crate::api::get_metadata_queue::response::Response>()
.await
{
Ok(response) => {
let bod = &response.data[0];
match process_coverart(app, &queued_song.id, bod).await {
Ok(qc) => Ok((queued_song, qc, bod.metadata.clone())),
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
pub async fn process_coverart(
app: &crate::config::App,
queued_song_id: &uuid::Uuid,
queued_item: &crate::api::get_metadata_queue::response::QueueItem,
) -> Result<crate::queued_item::QueuedCoverArt, reqwest::Error> {
let id = queued_item.id;
let created_at = queued_item.created_at;
let metadata = &queued_item.metadata;
println!("Id: {id:?}");
println!("Metadata: {metadata:?}");
println!("Created at: {created_at:?}");
println!("Getting coverart queue");
match crate::api::get_coverart_queue::get(app, queued_song_id).await {
Ok(response) => {
match response
.json::<crate::api::get_coverart_queue::response::Response>()
.await
{
Ok(response) => {
let coverart_queue = &response.data[0];
let coverart_queue_id = coverart_queue.id;
println!("Coverart queue Id: {coverart_queue_id:?}");
match crate::api::get_coverart_queue::get_data(app, &coverart_queue_id).await {
Ok(response) => {
match crate::api::parsing::parse_response_into_bytes(response).await {
Ok(coverart_queue_bytes) => {
let queued_coverart = init_queued_coverart(
&coverart_queue_id,
&coverart_queue.file_type,
coverart_queue_bytes,
)
.await;
println!(
"Saved coverart queue file at: {:?}",
queued_coverart.path
);
println!(
"Queued CoverArt file type: {:?}",
queued_coverart.coverart.file_type
);
Ok(queued_coverart)
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
async fn init_queued_coverart(
coverart_queue_id: &uuid::Uuid,
file_type: &str,
bytes: Vec<u8>,
) -> crate::queued_item::QueuedCoverArt {
// TODO: Consider separating song and coverart when saving to the filesystem
let covart_type = if file_type == simeta::detection::coverart::constants::PNG_TYPE {
simodels::types::CoverArtType::PngExtension
} else if file_type == simeta::detection::coverart::constants::JPEG_TYPE {
simodels::types::CoverArtType::JpegExtension
} else if file_type == simeta::detection::coverart::constants::JPG_TYPE {
simodels::types::CoverArtType::JpgExtension
} else {
simodels::types::CoverArtType::None
};
let coverart = simodels::coverart::CoverArt {
directory: sienvy::environment::get_root_directory().value,
filename: match simodels::coverart::generate_filename(covart_type, true) {
Ok(filename) => filename,
Err(err) => {
eprintln!("Error generating CoverArt filename: {err:?}");
panic!("Error initializing queued CoverArt");
}
},
file_type: String::from(file_type),
data: bytes,
..Default::default()
};
coverart.save_to_filesystem().unwrap();
let coverart_queue_fs_path = coverart.get_path().unwrap();
crate::queued_item::QueuedCoverArt {
id: *coverart_queue_id,
coverart,
path: coverart_queue_fs_path,
}
}
pub async fn cleanup(
song: &simodels::song::Song,
coverart: &simodels::coverart::CoverArt,
) -> Result<(), std::io::Error> {
match song.remove_from_filesystem() {
Ok(_) => {}
Err(err) => {
eprintln!("Error: Problem cleaning up SongQueue files {err:?}");
}
}
match coverart.remove_from_filesystem() {
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
+60
View File
@@ -0,0 +1,60 @@
pub async fn wipe_data_from_queues(
app: &crate::config::App,
queued_song: &crate::queued_item::QueuedSong,
queued_coverart: &crate::queued_item::QueuedCoverArt,
) -> Result<(), std::io::Error> {
match crate::api::wipe_data::song_queue::wipe_data(app, queued_song).await {
Ok(response) => match response
.json::<crate::api::wipe_data::song_queue::response::Response>()
.await
{
Ok(_resp) => {
match crate::api::wipe_data::coverart_queue::wipe_data(app, queued_coverart).await {
Ok(inner_response) => match inner_response
.json::<crate::api::wipe_data::coverart_queue::response::Response>()
.await
{
Ok(_inner_resp) => {
println!("Wiped data from CoverArt queue");
println!("Resp: {_inner_resp:?}");
Ok(())
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
Err(err) => Err(std::io::Error::other(err.to_string())),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
pub async fn is_queue_empty(
app: &crate::config::App,
) -> Result<
(
bool,
crate::api::fetch_next_queue_item::response::SongQueueItem,
),
reqwest::Error,
> {
match crate::api::fetch_next_queue_item::fetch_next_queue_item(app).await {
Ok(response) => {
match response
.json::<crate::api::fetch_next_queue_item::response::SongQueueItem>()
.await
{
Ok(response) => {
if response.data.is_empty() {
Ok((true, response))
} else {
Ok((false, response))
}
}
Err(err) => Err(err),
}
}
Err(err) => Err(err),
}
}
+13
View File
@@ -0,0 +1,13 @@
#[derive(Clone, Debug, Default)]
pub struct QueuedSong {
pub id: uuid::Uuid,
pub song: simodels::song::Song,
pub path: String,
}
#[derive(Clone, Debug, Default)]
pub struct QueuedCoverArt {
pub id: uuid::Uuid,
pub coverart: simodels::coverart::CoverArt,
pub path: String,
}