4 Commits
Author SHA1 Message Date
phoenix f32f6812b3 Adding openssl (#104)
sidm / build (push) Successful in 3m26s
Reviewed-on: #104
2026-07-21 15:32:48 -04:00
phoenix 68c0b06280 Dependency name change (#103)
sidm / build (push) Successful in 1m36s
Reviewed-on: #103
2026-07-16 13:47:33 -04:00
phoenix 592c5aaf60 Update rust (#102)
Rust / build (push) Successful in 3m25s
Reviewed-on: phoenix/idm#102
2026-07-10 19:29:05 -04:00
phoenix a8ed05c278 tsk-100: Name change (#101)
Rust / build (push) Successful in 1m44s
Rust / build (pull_request) Successful in 3m10s
From `icarus_dm` -> `idm`.

Closes #100

Reviewed-on: phoenix/icarus_dm#101
2026-07-10 19:03:11 -04:00
13 changed files with 296 additions and 674 deletions
@@ -1,4 +1,4 @@
name: Rust
name: sidm
on:
push:
@@ -15,19 +15,17 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- name: Install Rust version
run: rustup install 1.95 && rustup default 1.95
- name: Debug secret
run: echo "${{ secrets.MYREPO_TOKEN }}" | head -c 10 ; echo "..."
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- name: Build
run: |
mkdir -p ~/.ssh
echo "$EXTREPO_KEY" > ~/.ssh/gitlab_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key
ssh-keyscan ${{ vars.MYHOST }}= >> ~/.ssh/known_hosts
ssh-keyscan ${{ secrets.MYHOST }}= >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key
Generated
+199 -555
View File
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -1,17 +1,19 @@
[package]
name = "icarus-dm"
version = "0.10.0"
name = "sidm"
version = "0.10.1"
rust-version = "1.95"
edition = "2024"
license = "MIT"
description = "CLI tool to download and upload music from soaricarus"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = { version = "0.3.32" }
http = { version = "1.4.0" }
reqwest = { version = "0.13.3", features = ["json", "blocking", "multipart", "stream"] }
http = { version = "1.4.2" }
reqwest = { version = "0.13.4", features = ["json", "blocking", "multipart", "stream"] }
openssl = { version = "0.10.81", features = ["vendored"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
tokio = { version = "1.52", features = ["full"] }
serde_json = { version = "1.0.150" }
tokio = { version = "1.52.4", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["codec"] }
uuid = { version = "1.23.1", features = ["v4", "serde"] }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.11.1" }
uuid = { version = "1.23.4", features = ["v4", "serde"] }
simodels = { git = "ssh://git@git.kundeng.us/phoenix/simodels.git", tag = "v0.11.3-main-fe9d101bd0-111" }
+9 -27
View File
@@ -1,72 +1,54 @@
# IcarusDownloadManager
# sidm
IcarusDownloadManager is a CLI software client application that has the feature of uploading and downloading songs from the [Icarus](https://github.com/kdeng00/Icarus) Music Server.
sidm (SoarIcarusDownloadManager) is a CLI software client application that has the feature of uploading and downloading songs from the [soaricarus_api](https://git.kundeng.us/phoenix/soaricarus_api) Music Server.
## Built With
* Rust
* Cargo
* futures
* http
* reqwst
* serde
* serde_json
* tokio
* tokio-util
* icarus_models
### Getting Started
Clone the repo
```BASH
git clone git@github.com:kdeng00/IcarusDownloadManager.git
git clone git@git.kundeng.us:phoenix/sidm.git
```
Build the project:
```BASH
cd IcarusDownloadManager
cd sidm
cargo build
```
Even though this project is open source, there are some libraries that are closed source (may be opened later).
In order to successfully build it, your ssh public key would be needed to add to the closed libraries. If you
have interest, something could be worked out to provide access.
The program has been built and can be executed by the binary file *icarus-dm*. For information on how to use icarua-dm, merely execute the program without any command line arguments.
### Downloading Song
```BASH
icarus-dm download -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -b e8407fc6-edd2-44c1-993f-08dd7324d91a
sidm download -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -b e8407fc6-edd2-44c1-993f-08dd7324d91a
```
### Uploading Song with metadata
```BASH
icarus-dm upload-meta -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -s /path/of/song.flac -t 1 -m /path/to/metadata/config/collection.json -ca /path/to/cover/art/image.png
sidm upload-meta -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -s /path/of/song.flac -t 1 -m /path/to/metadata/config/collection.json -ca /path/to/cover/art/image.png
```
### Uploading Song with metadata from directory
```BASH
icarus-dm upload-meta -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -smca /path/where/songs/and/metadata/exists/
sidm upload-meta -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -smca /path/where/songs/and/metadata/exists/
```
### Retrieving Song in json
```Bash
icarus-dm retrieve -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -rt songs
sidm retrieve -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -rt songs
```
### Deleting Song
```BASH
icarus-dm delete -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -D e8407fc6-edd2-44c1-993f-08dd7324d91a
sidm delete -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -D e8407fc6-edd2-44c1-993f-08dd7324d91a
```
+5 -5
View File
@@ -51,7 +51,7 @@ mod tests {
assert!(false, "File does not exists: {meta_path:?}");
}
match icarus_models::album::collection::parse_album(&meta_path) {
match simodels::album::collection::parse_album(&meta_path) {
Ok(album) => {
for track in 1..3 {
let directory_path = std::path::Path::new(&meta_path);
@@ -59,17 +59,17 @@ mod tests {
let filename: String = if track < 10 {
String::from("track0")
+ &track.to_string()
+ icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
+ simodels::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
} else {
String::from("track")
+ &track.to_string()
+ icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
+ simodels::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
};
let disc = 1;
match managers::commit_manager::retrieve_song(
&album, track, disc, &directory, &filename,
) {
Ok(song) => match icarus_models::song::io::to_data(&song) {
Ok(song) => match simodels::song::io::to_data(&song) {
Ok(_) => {
print!("Success");
}
@@ -92,7 +92,7 @@ mod tests {
#[test]
fn test_minimum_action_and_args() {
let args: Vec<String> = vec![
"icarus-dm".to_string(),
"idm".to_string(),
"download".to_string(),
"-u".to_string(),
"jamborie".to_string(),
+37 -40
View File
@@ -36,21 +36,21 @@ enum En {
#[derive(Debug)]
struct UploadSongMembers {
pub song: icarus_models::song::Song,
pub coverart: icarus_models::coverart::CoverArt,
pub token: icarus_models::token::AccessToken,
pub album: icarus_models::album::collection::Album,
pub song: simodels::song::Song,
pub coverart: simodels::coverart::CoverArt,
pub token: simodels::token::AccessToken,
pub album: simodels::album::collection::Album,
}
pub fn retrieve_song(
album: &icarus_models::album::collection::Album,
album: &simodels::album::collection::Album,
track: i32,
disc: i32,
directory: &str,
filename: &str,
) -> Result<icarus_models::song::Song> {
) -> Result<simodels::song::Song> {
let mut found = false;
let mut song = icarus_models::song::Song::default();
let mut song = simodels::song::Song::default();
for song_i in &album.tracks {
if song_i.track == track && song_i.disc == disc {
@@ -58,9 +58,8 @@ pub fn retrieve_song(
song.album = album.title.clone();
song.album_artist = album.artist.clone();
song.artist = track.artist.clone();
song.audio_type = String::from(
icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION,
);
song.audio_type =
String::from(simodels::constants::file_extensions::audio::DEFAULTMUSICEXTENSION);
song.disc = track.disc;
song.disc_count = album.disc_count;
song.duration = track.duration as i32;
@@ -143,13 +142,13 @@ impl CommitManager {
])
}
async fn delete_song(&self, token: &icarus_models::token::AccessToken) {
async fn delete_song(&self, token: &simodels::token::AccessToken) {
println!("Deleting song");
let mut del = syncers::delete::Delete {
api: self.api.clone(),
};
let mut song = icarus_models::song::Song::default();
let mut song = simodels::song::Song::default();
for arg in &self.ica_action.flags {
let flag = &arg.flag;
@@ -170,13 +169,13 @@ impl CommitManager {
}
}
async fn download_song(&self, token: &icarus_models::token::AccessToken) {
async fn download_song(&self, token: &simodels::token::AccessToken) {
println!("Downloading song");
let dwn = self.ica_action.retrieve_flag_value(&String::from("-b"));
let mut dwn_loader = syncers::download::Download {
api: self.api.clone(),
};
let mut song = icarus_models::song::Song {
let mut song = simodels::song::Song {
id: uuid::Uuid::from_str(dwn.as_str()).unwrap(),
..Default::default()
};
@@ -187,8 +186,8 @@ impl CommitManager {
song.data = o.as_bytes().to_vec();
song.directory = String::from(".");
song.filename = match icarus_models::song::generate_filename(
icarus_models::types::MusicType::FlacExtension,
song.filename = match simodels::song::generate_filename(
simodels::types::MusicType::FlacExtension,
true,
) {
Ok(filename) => filename,
@@ -221,7 +220,7 @@ impl CommitManager {
}
}
async fn retrieve_object(&self, token: &icarus_models::token::AccessToken) {
async fn retrieve_object(&self, token: &simodels::token::AccessToken) {
println!("Retrieving song");
let rt = self.ica_action.retrieve_flag_value(&String::from("-rt"));
@@ -250,12 +249,12 @@ impl CommitManager {
}
}
async fn parse_token(&self, api: &models::api::Api) -> icarus_models::token::AccessToken {
async fn parse_token(&self, api: &models::api::Api) -> simodels::token::AccessToken {
println!("Fetching token");
let mut usr_mgr: managers::user_manager::UserManager =
managers::user_manager::UserManager {
user: icarus_models::user::User::default(),
user: simodels::user::User::default(),
ica_action: self.ica_action.clone(),
};
usr_mgr.parse_user_from_actions();
@@ -272,7 +271,7 @@ impl CommitManager {
token.unwrap()
}
async fn upload_song_with_metadata(&mut self, token: &icarus_models::token::AccessToken) {
async fn upload_song_with_metadata(&mut self, token: &simodels::token::AccessToken) {
println!("Uplodaring song with metadara");
let songpath = self.ica_action.retrieve_flag_value(&String::from("-s"));
@@ -316,7 +315,7 @@ impl CommitManager {
track_id: &str,
meta_path: &String,
cover_path: &str,
token: &icarus_models::token::AccessToken,
token: &simodels::token::AccessToken,
) -> Result<()> {
let song_file = std::path::Path::new(&songpath);
@@ -327,7 +326,7 @@ impl CommitManager {
let pa = std::path::Path::new(&cover_path);
let mut cover_art = icarus_models::coverart::CoverArt {
let mut cover_art = simodels::coverart::CoverArt {
directory: String::from(pa.parent().unwrap().to_str().unwrap()),
filename: String::from(pa.file_name().unwrap().to_str().unwrap()),
..Default::default()
@@ -339,7 +338,7 @@ impl CommitManager {
Ok(s) => {
println!("file name: {file_name:?}");
match icarus_models::album::collection::parse_album(meta_path) {
match simodels::album::collection::parse_album(meta_path) {
Ok(album) => {
let filename = s.clone();
let directory = song_file.parent().unwrap().display().to_string();
@@ -349,9 +348,8 @@ impl CommitManager {
println!("Directory: {:?}", s.directory);
println!("Filename: {:?}", s.filename);
println!("Path: {:?}", s.song_path());
s.data = icarus_models::song::io::to_data(&s).unwrap();
cover_art.data =
icarus_models::coverart::io::to_data(&cover_art).unwrap();
s.data = simodels::song::io::to_data(&s).unwrap();
cover_art.data = simodels::coverart::io::to_data(&cover_art).unwrap();
let members = UploadSongMembers {
song: s,
@@ -448,23 +446,23 @@ impl CommitManager {
&self,
metadata_path: &String,
source_directory: &str,
) -> Result<Vec<icarus_models::song::Song>> {
match icarus_models::album::collection::parse_album(metadata_path) {
) -> Result<Vec<simodels::song::Song>> {
match simodels::album::collection::parse_album(metadata_path) {
Ok(albums) => {
let mut songs: Vec<icarus_models::song::Song> = Vec::new();
let mut songs: Vec<simodels::song::Song> = Vec::new();
for track in &albums.tracks {
let song_filename = if track.track < 10 {
"track0".to_owned()
+ &track.track.to_string()
+ icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
+ simodels::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
} else {
"track".to_owned()
+ &track.track.to_string()
+ icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
+ simodels::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
};
songs.push(icarus_models::song::Song {
songs.push(simodels::song::Song {
title: track.title.clone(),
artist: track.artist.clone(),
disc: track.disc,
@@ -491,7 +489,7 @@ impl CommitManager {
async fn multi_target_upload(
&mut self,
sourcepath: &String,
token: &icarus_models::token::AccessToken,
token: &simodels::token::AccessToken,
) -> std::io::Result<()> {
let directory_path = std::path::Path::new(&sourcepath);
@@ -501,7 +499,7 @@ impl CommitManager {
let (coverart_directory, coverart_filename) =
self.get_coverart_dir_and_filename(sourcepath).unwrap();
let mut cover_art = icarus_models::coverart::init::init_coverart_dir_and_filename(
let mut cover_art = simodels::coverart::init::init_coverart_dir_and_filename(
&coverart_directory,
&coverart_filename,
);
@@ -511,10 +509,10 @@ impl CommitManager {
let host = self.ica_action.retrieve_flag_value(&String::from("-h"));
up.set_api(&host);
cover_art.data = icarus_models::coverart::io::to_data(&cover_art).unwrap();
cover_art.data = simodels::coverart::io::to_data(&cover_art).unwrap();
match self.get_songs(&metadatapath, sourcepath) {
Ok(sngs) => match icarus_models::album::collection::parse_album(&metadatapath) {
Ok(sngs) => match simodels::album::collection::parse_album(&metadatapath) {
Ok(album) => {
for song in sngs {
let members = UploadSongMembers {
@@ -595,15 +593,14 @@ impl CommitManager {
index += 1;
}
if extension == icarus_models::constants::file_extensions::audio::WAVEXTENSION[1..]
|| extension
== icarus_models::constants::file_extensions::audio::FLACEXTENSION[1..]
if extension == simodels::constants::file_extensions::audio::WAVEXTENSION[1..]
|| extension == simodels::constants::file_extensions::audio::FLACEXTENSION[1..]
{
return En::SongFile;
} else if extension == "json" {
return En::MetadataFile;
} else if extension
== icarus_models::constants::file_extensions::image::JPGEXTENSION[1..]
== simodels::constants::file_extensions::image::JPGEXTENSION[1..]
|| extension == "jpeg"
|| extension == "png"
{
+5 -5
View File
@@ -7,20 +7,20 @@ mod response {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>,
pub data: Vec<simodels::login_result::LoginResult>,
}
}
}
pub struct TokenManager {
pub user: icarus_models::user::User,
pub user: simodels::user::User,
pub api: models::api::Api,
}
impl Default for TokenManager {
fn default() -> Self {
let mut token = TokenManager {
user: icarus_models::user::User::default(),
user: simodels::user::User::default(),
api: models::api::Api::default(),
};
token.init();
@@ -30,13 +30,13 @@ impl Default for TokenManager {
}
impl TokenManager {
pub async fn request_token(&self) -> Result<icarus_models::token::AccessToken, std::io::Error> {
pub async fn request_token(&self) -> Result<simodels::token::AccessToken, std::io::Error> {
println!("Sending request for a token");
let url = self.retrieve_url();
println!("URL: {url}");
let mut token = icarus_models::token::AccessToken {
let mut token = simodels::token::AccessToken {
user_id: uuid::Uuid::nil(),
username: String::new(),
token: String::new(),
+2 -2
View File
@@ -4,12 +4,12 @@ use crate::models::{self};
#[derive(Debug, Default)]
pub struct UserManager {
pub user: icarus_models::user::User,
pub user: simodels::user::User,
pub ica_action: models::icarus_action::IcarusAction,
}
impl UserManager {
pub fn retrieve_user(&self) -> icarus_models::user::User {
pub fn retrieve_user(&self) -> simodels::user::User {
self.user.clone()
}
+1 -1
View File
@@ -17,7 +17,7 @@ fn retrieve_url_with_id(api: &models::api::Api, id: &uuid::Uuid) -> String {
}
pub async fn auth_header(
token: &icarus_models::token::AccessToken,
token: &simodels::token::AccessToken,
) -> Result<(http::HeaderName, http::HeaderValue), http::header::InvalidHeaderValue> {
match http::HeaderValue::from_str(&token.bearer_token()) {
Ok(auth_value) => Ok((reqwest::header::AUTHORIZATION, auth_value)),
+5 -6
View File
@@ -13,8 +13,8 @@ mod response {
pub mod delete_song {
#[derive(Debug, serde::Deserialize)]
pub struct SongAndCoverArt {
pub song: icarus_models::song::Song,
pub coverart: icarus_models::coverart::CoverArt,
pub song: simodels::song::Song,
pub coverart: simodels::coverart::CoverArt,
}
#[derive(Debug, serde::Deserialize)]
@@ -28,10 +28,9 @@ mod response {
impl Delete {
pub async fn delete_song(
&mut self,
token: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
) -> Result<(icarus_models::song::Song, icarus_models::coverart::CoverArt), std::io::Error>
{
token: &simodels::token::AccessToken,
song: &simodels::song::Song,
) -> Result<(simodels::song::Song, simodels::coverart::CoverArt), std::io::Error> {
self.api.endpoint = "api/v2/song".to_owned();
let url = format!("{}{}/{}", self.api.url, self.api.endpoint, song.id);
println!("Url: {url:?}");
+2 -2
View File
@@ -17,8 +17,8 @@ pub enum MyError {
impl Download {
pub async fn download_song(
&mut self,
token: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
token: &simodels::token::AccessToken,
song: &simodels::song::Song,
) -> Result<String, MyError> {
self.api.endpoint = String::from("song/download");
let url = syncers::common::retrieve_url(&self.api, true, &song.id);
+3 -3
View File
@@ -13,7 +13,7 @@ mod response {
#[derive(Debug, serde::Deserialize)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::song::Song>,
pub data: Vec<simodels::song::Song>,
}
}
}
@@ -21,8 +21,8 @@ mod response {
impl RetrieveRecords {
pub async fn get_all_songs(
&mut self,
token: &icarus_models::token::AccessToken,
) -> Result<Vec<icarus_models::song::Song>, Error> {
token: &simodels::token::AccessToken,
) -> Result<Vec<simodels::song::Song>, Error> {
self.api.endpoint = String::from("api/v2/song/all");
let url = format!("{}{}", self.api.url, self.api.endpoint);
let access_token = token.bearer_token();
+10 -10
View File
@@ -40,8 +40,8 @@ mod response {
impl Upload {
pub async fn queue_song(
&self,
token: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
token: &simodels::token::AccessToken,
song: &simodels::song::Song,
) -> Result<uuid::Uuid, reqwest::Error> {
let songpath = song.song_path().unwrap();
let file = tokio::fs::File::open(&songpath).await.unwrap();
@@ -92,7 +92,7 @@ impl Upload {
pub async fn link_user_to_queued_song(
&self,
token: &icarus_models::token::AccessToken,
token: &simodels::token::AccessToken,
queued_song_id: &uuid::Uuid,
) -> Result<(), reqwest::Error> {
let endpoint = String::from("api/v2/song/queue/link");
@@ -123,9 +123,9 @@ impl Upload {
pub async fn queue_metadata(
&self,
token: &icarus_models::token::AccessToken,
album: &icarus_models::album::collection::Album,
song: &icarus_models::song::Song,
token: &simodels::token::AccessToken,
album: &simodels::album::collection::Album,
song: &simodels::song::Song,
queued_song_id: &uuid::Uuid,
) -> Result<uuid::Uuid, reqwest::Error> {
let endpoint = String::from("api/v2/song/metadata/queue");
@@ -173,8 +173,8 @@ impl Upload {
pub async fn queue_coverart(
&self,
token: &icarus_models::token::AccessToken,
coverart: &icarus_models::coverart::CoverArt,
token: &simodels::token::AccessToken,
coverart: &simodels::coverart::CoverArt,
) -> Result<uuid::Uuid, reqwest::Error> {
let coverartpath = coverart.get_path().unwrap();
let file = tokio::fs::File::open(&coverartpath).await.unwrap();
@@ -231,7 +231,7 @@ impl Upload {
pub async fn link_queued_song_to_queued_coverart(
&self,
token: &icarus_models::token::AccessToken,
token: &simodels::token::AccessToken,
queued_song_id: &uuid::Uuid,
queued_coverart_id: &uuid::Uuid,
) -> Result<(), reqwest::Error> {
@@ -263,7 +263,7 @@ impl Upload {
pub async fn update_queued_song_status(
&self,
token: &icarus_models::token::AccessToken,
token: &simodels::token::AccessToken,
queued_song_id: &uuid::Uuid,
status: &str,
) -> Result<(), reqwest::Error> {