110 Commits

Author SHA1 Message Date
phoenix 0e6438cf0d Updated rust (#95)
* Updated rust version in workflow

* Using variable in workflow file

* Updating packages

* cargo update

* Version bump
2026-04-05 17:14:50 -04:00
KD 00615532ce icarus_models version bump (#93)
* icarus_models version bump

* Build fix

* Removed method

* Some refactoring

* More cleanup

* Version bump

* Using standard version
2025-10-30 15:33:09 -04:00
KD abd16eeba2 tsk-79: Simplifying functions (#92)
* tsk-79: Changed return type of function and cleaned up other functions

* tsk-79: Version bump

* tsk-79: Code formatting
2025-10-27 12:42:42 -04:00
KD 5c9fcf2ec5 tsk-83: Add token code prior to action execution (#91)
* tsk-83: Code cleanup

* tsk-83: Added code to get token before an action begins

* tsk-83: Small fix

* tsk-83: Version bump

* tsk-83: Cleanup
2025-10-26 16:39:16 -04:00
KD 6bedab0d88 tsk-82: Refactor validate_args() method (#90)
* tsk-82: Refactored validate_flags() method

* tsk-82: Warning fix

* tsk-82: Code formatting

* tsk-82: Version bump

* tsk-82: Forgot this

* tsk-82: Fix
2025-10-24 20:51:33 -04:00
KD 686e0d8f1f tsk-81: Simplify downloading song (#89)
* tsk-81: Using method in Song to save to filesystem

* tsk-81: icaurs_models version bump

* tsk-81: Code formatting

* tsk-81: icarus_models version bump

* tsk-81: Version bump
2025-10-24 13:34:55 -04:00
KD 05a9134aaa tsk-80: Removing unused serde references (#88)
* tsk-80: Removing unused serde references

* tsk-80: Version bump

* tsk-80: Formatting code
2025-10-23 21:10:08 -04:00
KD 3cadac4efd tsk-78: Updating returned error on converting ostring to string (#87)
* tsk-78: Making error to get string from o string more robust

* tsk-78: Version bump
2025-10-23 16:52:26 -04:00
KD 2188c2b746 tsk-77: Print version of the software (#86)
* tsk-77: Added code to print version

* tsk-77: Removed print statement

* tsk-77: Code formatting

* tsk-77: Version bump

* tsk-77: Updated help
2025-10-22 21:40:12 -04:00
KD 9328f3d3da tsk-76: Remove songs directory (#85)
* tsk-76: Removed songs directory

* tsk-76: Version bump
2025-10-22 20:59:20 -04:00
KD ca13f101d7 icarus_models version bump (#84)
* icarus_models version bump

* icarus_models related changes

* Removing method

* Code formatting

* Warning fix

* Removing comments

* Version bump

* cargo update
2025-10-19 23:52:43 -04:00
KD 1e4ffc798e Use official icarus_models version (#75)
* icarus_models v0.7.0

* Version bump
2025-10-13 20:58:30 -04:00
KD ecd025346c tsk-70: Rust update (#74)
* Updated rust in workflow

* Updated rust in cargo

* icarus_models version bump

* Version bump

* Workflow changes
2025-10-12 17:06:57 -04:00
KD 74184fa022 Dependency update (#73)
* Cargo update

* Updated crates

* Removed unused file

* Code cleanup

* Version bump

* Removed commented code
2025-10-12 16:57:49 -04:00
KD c2a2ca049d icarus_models version bump (#71)
* icarus_models version bump

* icarus_models related changes

* Warning fixes

* Version bump

* Test fix
2025-10-11 20:05:34 -04:00
KD 157887bfac Merge pull request #52 from kdeng00/icarus_v2_support
Support for icarus v2 API
2025-08-30 13:23:15 -04:00
kdeng00 2b77bb978c Version bump 2025-08-30 13:18:12 -04:00
KD 206ebf03be Updated License (#69) 2025-08-30 13:17:29 -04:00
KD 0d7d57bf5f Minor refatoring (#68)
* Removed old upload method

* Removed upload commit action

* Version bump
2025-08-30 13:13:53 -04:00
KD 11b8cc21d3 Delete song (#67)
* Changed code to comply with async

* Able to delete song

* Updated readme

* Code formatting and cleanup

* Version bump
2025-08-30 13:06:04 -04:00
KD e109789b18 Retrieve songs (#66)
* Able to retrieve songs

* Updated readme

* Removed unused import

* Code formatting

* Version bump
2025-08-30 12:10:03 -04:00
KD ec0cbd7da0 Download song (#65)
* Got download functionality working

* Updated readme

* Code formatting

* Updated help

* Version bump
2025-08-30 11:52:04 -04:00
KD 809f27cf01 Upload song (#58)
* Queue song (#57)

* Added code to queue song

* Making functions async

* Able to queue song

* Cleanup

* Link user to queued song (#59)

* Renaming queue_song function

* Queued song linked to user

* Cleanup and code formatting

* Queue metadata (#60)

* Added code to queue metadata

* Code formatting

* Queue coverart (#61)

* Added queued coverart code

* Code formatting and cleanup

* Link queued coverart to queued song (#62)

* Added code to link queued coverart to queued song

* Code formatting

* Update queued song status (#63)

* Added function call to update queued song status

* Added skeleton method

* Added more code to the method

* Got it working

* Code formatting

* Multi upload (#64)

* Added comments

* Added TODO

* Got it working

* Updated documentation

* Version bump

* Code formatting and cleanup
2025-08-29 21:59:54 -04:00
KD 8a07718ce8 Token fetch (#56)
* Added print statement and TODO

* Refactoring token fetching code

Adding two APIs. One for auth and the other for the main app

* Fixed token code

* Code formatting

* Cleanup
2025-08-27 16:33:18 -04:00
KD 77cb5c8599 Icarus models bump (#55)
* icarus_models bump

* Fix after depdenency update
2025-08-27 15:07:44 -04:00
KD dbcfcfa109 Warning fixes (#54)
* More warning fixes

* Should be the last of the warning fixes

* Code cleanup

* Code formatting
2025-07-02 19:25:08 -04:00
KD 8b2b2f82e9 Update dependencies (#53)
* Removing new lines

* Updated reqwest, tokio, tokio-utils, and uuid

* Including Cargo.lock in source control

* Not sure how this got here

* Updated icarus_models

* Workflow change

* Removing unused workflow

* Github workflow fix

* Warning fixes

* Fixed what caused failed test

* Code cleanup and formatting
2025-07-02 12:26:38 -04:00
KD 8e6ddbc9df Rust std change (#51)
* Targeting rust 1.88

* Workflow changes
2025-06-30 20:49:15 -04:00
KD c8ba6606be Icarus models update (#50)
* Updated dependencies

* Saving changes

* More changes

* More changes

* Updated dependencies

* Updated readme

* Saving changes

* Cleanup

* Updated icarus_models

* Updated icarus_models
2025-04-10 21:04:58 -04:00
KD b7d55ebcf4 Targeting rust 1.86 (#49)
* Targeting rust 1.86

* Workflow changes
2025-04-05 17:06:49 -04:00
KD d5f95ddf9b Updated dependencies (#48)
* Updated dependencies

* Saving changes

* More changes

* More changes

* Updated dependencies

* Updated readme

* Saving changes

* Cleanup
2025-04-03 23:16:37 -04:00
KD 885f1db3af Removed Versioning section (#46) 2025-04-03 21:46:18 -04:00
KD d6f45c8279 Updated git cloning (#47) 2025-04-03 21:46:04 -04:00
KD 58a88b1abd Merge pull request #45 from kdeng00/version_bump
Version bump
2025-03-30 16:04:07 -04:00
KD 9b181733a6 Version bump 2025-03-30 16:01:36 -04:00
KD 219b95bb2f Updated icarus_models (#44)
* Updated icarus_models

* Constants and url changes

* Refactoring

* Removed functions

* Fixing tests
2025-03-30 16:00:06 -04:00
KD 24e066d8d9 Merge pull request #43 from kdeng00/version_bump
Version bump
2025-03-26 19:09:00 -04:00
KD ab08792ff4 Updated dependencies (#42)
* Updated dependencies

* Will update version in separate commit
2025-03-26 19:07:08 -04:00
phoenix 9799edef33 Version bump 2025-03-26 19:02:22 -04:00
KD 92eb31a687 Add test (#41)
* Code refactoring

* Cleanup

* Added test and moved test files

* Cleanup

* Removed test mod file

* Tests reorganizing

* Removed test file
2025-03-25 22:43:42 -04:00
KD aa946a0a05 Code refactoring (#40)
* Code refactoring

* Cleanup
2025-03-25 22:14:26 -04:00
KD 19d6f2dc33 Code cleanup (#39)
* Code cleanup

* Removed unused source file
2025-03-24 19:51:41 -04:00
KD 4824e6f33a Updated wav reference to flac (#38) 2025-03-24 19:51:28 -04:00
KD 64935ea772 Updated icarus-model (#33)
* Updated icarus-model

* Updated icarus-model

* Saving changes

* Code formatting

* Removing code

* Updated icarus-model

* Updated token secret

* Added host

* Fix build issue

* Fix build warnings

* Saving changes

* Saving changes

* Refactoring

* Migrating over icarus-model coverart

* Removed song module

* Another one

* Meta single upload is functional

* Cleanup

* More cleanup

* Added test files (#37)

* Added test files

Need to add a coverarg image file

* Updated test album file

* Added coverart

* Uploading meta is operational

* Code cleanup

* Moved function

* Added string module
2025-03-24 19:34:38 -04:00
KD de90b0d0a1 Merge pull request #34 from kdeng00/refactoring
Refactoring
2025-03-15 20:37:04 -04:00
KD 19b943eed8 Merge pull request #32 from kdeng00/warning_fix
Warning fix
2025-03-14 22:54:59 -04:00
phoenix 8c781398b4 Wasn't using MyError 2025-03-14 22:51:17 -04:00
KD c575d2e523 Refactoring (#31)
* Refactoring code

* Code formatting

* More changes

* Moved to icarus-models::user

* Some refactoring

* Updated icarus-models

* Removing constants

* Replaced with icarus-models constants

* Formatting

* Switched to constants from icarus-models:

* Replaced Token with AccessToken from icarus-models

* Updated icarus-models

* Formatting

* Removing code
2025-03-13 21:40:22 -04:00
phoenix 03ed3deae6 Removing code 2025-03-13 21:37:32 -04:00
phoenix b69c111b66 Formatting 2025-03-13 21:34:11 -04:00
phoenix 8d97d4eb70 Updated icarus-models 2025-03-13 21:33:22 -04:00
phoenix 2f00ce6abf Replaced Token with AccessToken from icarus-models 2025-03-13 21:26:37 -04:00
phoenix e3cd1f4018 Switched to constants from icarus-models: 2025-03-13 21:19:37 -04:00
phoenix a9f9ba8350 Formatting 2025-03-13 20:58:34 -04:00
phoenix df60bef381 Replaced with icarus-models constants 2025-03-13 20:58:19 -04:00
phoenix 038142eb8a Removing constants 2025-03-13 20:53:43 -04:00
phoenix 69b81bc15c Updated icarus-models 2025-03-13 20:50:13 -04:00
phoenix b2e246d175 Some refactoring 2025-03-13 20:49:29 -04:00
phoenix c76e7b71b5 Moved to icarus-models::user 2025-03-13 19:59:09 -04:00
phoenix b8dccca158 More changes 2025-03-13 19:39:53 -04:00
phoenix b52607b4e3 Code formatting 2025-03-13 19:37:51 -04:00
phoenix ac970a4302 Refactoring code 2025-03-13 19:12:49 -04:00
KD a372bfcc05 Added icarus-models (#30)
* Added icarus-models

* Updated workflow

* Added ci workflow for ssh keys

* More changes

* More changes. again

* More changes. again.agg

* Another one

* Using secrets

* Forgot dereference

* Added variables

* Checking regular variable

* Updating

* Repository secret

* Checking size

* Another one

* Another one

* Another one

* Another one

* Another one

* Cargo file fix

* Another one

* Reorder

* Addded another step

* Change

* Another step

* Remove step

* Remove step

* Move on up

* Remove

* Cleanup

* Cleanup
2025-03-12 20:58:29 -04:00
KD d22850b280 Update packages (#29)
* Updating packages

* Updated serde packages

* Updated packages
2025-03-12 20:58:12 -04:00
KD 4d170f6a85 Merge pull request #28 from kdeng00/refactoring
Removing fields not used
2025-03-10 20:11:25 -04:00
phoenix 7391b1db68 Removing fields not used 2025-03-10 20:08:04 -04:00
KD 772641a8c4 Targeting rust 2024 (#27) 2025-03-02 20:53:14 -05:00
KD 4c99f88baf Updating packages (#24)
* Updating packages

* Updated serde packages
2025-03-02 20:52:33 -05:00
KD c3718c5add Added constant for flac file extension (#25) 2025-02-18 20:09:49 -05:00
KD a4d026e41f Updating song model (#26) 2025-02-18 20:09:11 -05:00
KD 6aa6f548a9 Create rust-ubuntu-latest.yml (#23)
* Create rust-ubuntu-latest.yml

* Create rust-macos-latest

* Delete .github/workflows/rust.yml
2025-02-02 16:07:52 -05:00
KD 53b83a809c Changed id field of the Song model (#22)
* Changed id field of the Song model

* Updated Readme
2025-02-01 21:43:01 -05:00
Kun Deng ee4469b385 Language Migration (#21)
* Blank slate

* Added gitignore

* Updated gitignore

* Added rust code

* Added modules

* Fixed build issues

* rustfmt

* Updated models

* Added parsers module

* Updated utilities module

* Updated main.rs

* Updated api_parser and song model

* Updated icarus_action model

* Updated structs in managers module

* Updated main.rs

* More changes

* Saving changes

* Added new struct and methods

* More changes. Not functional yet

* Changed name of the project

* Removed cmake workflow

* Adding rust workflow

* Adding code to the syncers module

* Cargo formatting

* Continuing work

* Adding code to the syncers module

* Added dependencies

* Adding more code to the syncers module

* Added more code to the syncers module

It's not 100 percent done yet, I'll work on other modules

* Added more code to the managers module

* Added code to request a token

* Cargo formatting

* Adding more code to multi target upload

* Adding more multi-target upload code and formatted code

* Added more multi-upload code

* cargo fmt

* Got uploading functional and cargo fmt-ing

* Uploading multi target works, but is buggy

Not all of the songs are being uploaded

* Added single target upload

* Single target upload is functional but need to revisit multi-target upload

Multi-target upload is broken now. Needs to be revisited

* Fixed multi target upload

* Can now retrieve songs

* Downloading functionality is working

* Delete functionality is operational

* Updated README

* More cleanup

* Added test
2024-06-15 12:22:17 -04:00
Kun Deng 5e9e7bc46a Merge pull request #20 from kdeng00/vcpkg_building
Adding manifest file
2023-08-04 21:05:35 -04:00
kdeng00 5d02c47b86 Workflow fix 2023-08-04 21:03:02 -04:00
kdeng00 7abf5d5db2 Indentation issue 2023-08-04 21:01:15 -04:00
kdeng00 42dcbb87ed Adding build step to workflow 2023-08-04 20:59:43 -04:00
kdeng00 31b633e5f8 Cleanup 2023-08-04 20:56:51 -04:00
kdeng00 8788106352 Workflow changes 2023-08-04 20:51:23 -04:00
kdeng00 9957a7c6eb Updated workflow 2023-08-04 20:45:54 -04:00
kdeng00 ae3b18c21f Moved submodule directory
Moved where vcpkg is located to address GitHub Actions build failure
2023-08-04 20:41:47 -04:00
kdeng00 60b1d49ae6 Adding manifest file 2023-08-04 20:39:50 -04:00
kdeng00 1df7258993 Updated CMakeLists.txt
Updated the minimum version of cmake
2023-08-04 20:32:57 -04:00
kdeng00 68aa9f2245 Updated vcpkg
Updating vcpkg to version 2023.07.21
2023-08-04 20:30:35 -04:00
Kun Deng 8df10ed210 Update cmake.yml 2023-04-09 11:11:55 -04:00
Kun Deng e70a38d24b Create .github/workflows/cmake.yml 2023-04-09 11:03:48 -04:00
Kun Deng a593c5099d Merge pull request #19 from kdeng00/update_dependencies
Updated dependency
2023-04-09 10:58:49 -04:00
kdeng00 639bf24c97 Updated dependency
Updated the vcpkg dependency
2023-04-09 10:44:31 -04:00
Kun Deng a3a8084e69 Merge pull request #18 from kdeng00/update_doc
Update doc
2023-04-09 10:42:38 -04:00
kdeng00 bb1e8a42ef Update doc
Updating the README to include the recent releases
2023-04-09 10:39:50 -04:00
kdeng00 bc5112df38 Downgrading dependency
Changing to cmake 3.10.2 as the minimum required version to build the software. Decided to use the latest cmake version for Ubuntu 18.04. I use a rolling distro but want to make it accessible
2023-01-22 19:59:45 -05:00
Kun Deng c96ee39c6a Merge pull request #17 from kdeng00/cleanup
Cleanup
2022-08-28 10:53:23 -04:00
Kun Deng 389f41ecea Updates
Updated CMakeLists file, removed conan file, and have all windows configs targeting C++20
2022-08-28 10:53:00 -04:00
Kun Deng 10bf0fb0e1 Clean up and version change 2022-08-28 10:49:28 -04:00
Kun Deng 4429f7e766 Merge pull request #16 from kdeng00/bug_fix
Bug fix
2022-08-28 10:36:11 -04:00
Kun Deng e5d119d343 Bug fix 2022-08-28 10:34:27 -04:00
Kun Deng 1d88a3ccf6 Update README.md 2022-08-07 21:40:23 -04:00
Kun Deng 2768c1e351 Update README.md 2022-08-07 21:40:01 -04:00
Kun Deng 3955942988 Merge pull request #15 from kdeng00/v0.3
V0.3
2022-08-07 21:37:39 -04:00
Kun Deng bfd7a02970 Merge pull request #14 from kdeng00/vs
Vs
2022-08-07 21:35:55 -04:00
kdeng00 77acd15105 Able to debug with Visual Studio 2022-08-07 21:34:41 -04:00
kdeng00 435a0df941 Added build files
Added Visual studio build files
2022-08-07 21:15:54 -04:00
kdeng00 c624c4de51 Missing include 2022-08-07 21:15:07 -04:00
Kun Deng 1d6cd804be Merge pull request #13 from kdeng00/vcpkg
Vcpkg
2022-08-07 20:46:24 -04:00
kdeng00 2743543102 Clean up 2022-08-07 20:44:37 -04:00
kdeng00 3195011f99 vcpkg is operational 2022-08-07 20:42:11 -04:00
kdeng00 ed5ceedb44 vcpkg version
Version 2022.07.25
2022-08-07 20:04:57 -04:00
kdeng00 f70acba704 Added vcpkg 2022-08-07 20:04:02 -04:00
Kun Deng f224b5738b Merge pull request #12 from kdeng00/build_fix
Fixed build issue
2022-08-07 19:59:29 -04:00
kdeng00 c41cd9dbb7 Fixed build issue 2022-08-07 19:51:29 -04:00
76 changed files with 4348 additions and 2848 deletions
+39
View File
@@ -0,0 +1,39 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- name: Install Rust version
run: rustup install 1.94 && rustup default 1.94
- name: Debug secret
run: echo "${{ secrets.MYREPO_TOKEN }}" | head -c 10 ; echo "..."
- 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
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key
cargo build --release
env:
EXTREPO_KEY: ${{ secrets.MYREPO_TOKEN }}
- name: Run tests
run: cargo test --verbose
+9
View File
@@ -0,0 +1,9 @@
/.vs/*
/build
/x64
# Added by cargo
/target
.vscode/
View File
-87
View File
@@ -1,87 +0,0 @@
cmake_minimum_required(VERSION 3.14)
project(IcarusDownloadManager)
message(STATUS "Checking compiler flags for C++17 support.")
# Set C++17 support flags for various compilers
include(CheckCXXCompilerFlag)
if(WIN32)
message("Windows")
set(vs_ver 19.29.30138)
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL ${vs_ver})
message("Visual Studio version is at least ${vs_ver}")
set(CMAKE_CXX_STANDARD_COMPILE_OPTION "-std:c++latest")
set(CMAKE_CXX_EXTENSION_COMPILE_OPTION "-std:c++latest")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
else()
check_cxx_compiler_flag("-std=c++17" COMPILER_SUPPORTS_CXX17)
check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if(COMPILER_SUPPORTS_CXX17)
message(STATUS "C++17 is supported.")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -stdlib=libc++")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
endif()
elseif(COMPILER_SUPPORTS_CXX0X)
message(STATUS "C++0x is supported.")
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -stdlib=libc++")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
endif()
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++17 support. Please use a different C++ compiler.")
endif()
endif()
set(SOURCES
src/Main.cpp
src/Managers/ActionManager.cpp
src/Managers/CommitManager.cpp
src/Managers/FileManager.cpp
src/Managers/TokenManager.cpp
src/Managers/UserManager.cpp
src/Models/Song.cpp
src/Parsers/APIParser.cpp
src/Syncers/Delete.cpp
src/Syncers/Download.cpp
src/Syncers/RetrieveRecords.cpp
src/Syncers/Upload.cpp
src/Utilities/Conversions.cpp
)
set(IDM_INCLUDE_DIR
"${CMAKE_CURRENT_SOURCE_DIR}/include")
# conan
set(CONAN_BUILDINFO
${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
message("conan build info ${CONAN_BUILDINFO}")
include(${CONAN_BUILDINFO})
conan_basic_setup()
include_directories(${CPR_INCLUDE_DIRS} ${IDM_INCLUDE_DIR})
set(USE_SYSTEM_CURL OFF)
set(BUILD_CPR_TESTS OFF)
add_executable(icd ${SOURCES})
target_link_libraries(icd PUBLIC ${CONAN_LIBS})
Generated
+2455
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "icarus-dm"
version = "0.9.0"
rust-version = "1.94"
edition = "2024"
# 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.2", features = ["json", "blocking", "multipart", "stream"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
tokio = { version = "1.51", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["codec"] }
uuid = { version = "1.23", features = ["v4", "serde"] }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.10.0" }
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 Kun Deng Copyright (c) 2025 Kun Deng
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+31 -43
View File
@@ -1,74 +1,72 @@
# IcarusDownloadManager # IcarusDownloadManager
IcarusDownloadManager is a Linux CLI software client application that has the feature of uploading and downloading songs from the [Icarus](https://github.com/kdeng00/Icarus) Music Server. 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.
## Built With ## Built With
* C++ with C++17 features * Rust
* CMake * Cargo
* GCC >= 9 or Visual Studio >= 16 [2019] * futures
* [conan](https://github.com/conan-io/conan) * http
* [json](https://github.com/nlohmann/json) * reqwst
* [openssl](https://github.com/openssl/openssl) * serde
* [curl](https://github.com/curl/curl) * serde_json
* [cpr](https://github.com/libcpr/cpr) * tokio
* tokio-util
* icarus_models
### Getting Started ### Getting Started
Clone the repo
```BASH
git clone git@github.com:kdeng00/IcarusDownloadManager.git
```
Build the project: Build the project:
``` ```BASH
git clone --recursive https://github.com/kdeng00/IcarusDownloadManager cd IcarusDownloadManager
cargo build
mkdir build
cd build
conan install .. --build
cmake ..
cmake --build . --config release -j
``` ```
The program has been built and can be executed by the binary file *icd*. For information on how to use icd, merely execute the program without any command line arguments. 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 ### Downloading Song
```BASH ```BASH
icd download -u spacecadet -p stellar40 -h https://icarus.com -b 15 icarus-dm download -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -b e8407fc6-edd2-44c1-993f-08dd7324d91a
```
### Uploading Song
```BASH
icd upload -u spacecadet -p stellar40 -h https://icarus.com -s /path/of/song.mp3
``` ```
### Uploading Song with metadata ### Uploading Song with metadata
```BASH ```BASH
icd upload-meta -u spacecadet -p stellar40 -h https://icarus.com -s /path/of/song.mp3 -t 1 -m /path/to/metadata/config/collection.json -ca /path/to/cover/art/image.png 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
``` ```
### Uploading Song with metadata from directory ### Uploading Song with metadata from directory
```BASH ```BASH
icd upload-meta -u spacecadet -p stellar40 -h https://icarus.com -smca /path/where/songs/and/metadata/exists/ icarus-dm 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 ### Retrieving Song in json
```Bash ```Bash
icd retrieve -u spacecadet -p stellar40 -h https://icarus.com -rt songs icarus-dm retrieve -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -rt songs
``` ```
### Deleting Song ### Deleting Song
```BASH ```BASH
icd delete -u spacecadet -p stellar40 -h https://icarus.com -D 15 icarus-dm delete -u spacecadet -p stellar40 -h https://icarus.com -ha https://auth.icarus.com -D e8407fc6-edd2-44c1-993f-08dd7324d91a
``` ```
@@ -76,17 +74,7 @@ icd delete -u spacecadet -p stellar40 -h https://icarus.com -D 15
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests to the project. Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests to the project.
## Versioning
[v0.2.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.2.0)
[v0.1.1](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.1.1)
[v0.1.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/0.1.0)
## Authors
* **Kun Deng** - [kdeng00](https://github.com/kdeng00)
See also the list of [contributors](https://github.com/kdeng00/Icarus/graphs/contributors) who participated in this project.
## License ## License
-20
View File
@@ -1,20 +0,0 @@
[requires]
nlohmann_json/3.10.4
openssl/1.1.1l
libcurl/7.80.0
cpr/1.7.2
[generators]
cmake
[options]
openssl:shared=False
libcurl:shared=False
cpr:shared=False
libcurl:with_ssl=openssl
libcurl:with_ftp=False
libcurl:with_gopher=False
libcurl:with_imap=False
libcurl:with_pop3=False
libcurl:with_smb=False
libcurl:with_smtp=False
-93
View File
@@ -1,93 +0,0 @@
#ifndef ACTIONMANAGER_H_
#define ACTIONMANAGER_H_
#include<algorithm>
#include<string>
#include<string_view>
#include<array>
#include<vector>
#include"Models/Flags.h"
#include"Models/IcarusAction.h"
namespace Managers
{
class ActionManager
{
public:
ActionManager(char**, int);
Models::IcarusAction retrieveIcarusAction() const;
private:
constexpr std::array<const char*, 16> supportedFlags() noexcept
{
constexpr std::array<const char*, 16> allFlags{"-u", "-p", "-t", "-h", "-s",
"-sd", "-sr", "-d", "-D", "-b", "-rt", "-nc",
"-m", "-ca", "-smca", "-t"};
return allFlags;
}
constexpr std::array<const char*, 4> supportedActions() noexcept;
void initialize();
void validateFlags();
// Checks to see if the flag is valid
template<typename Str>
bool isValidFlag(const Str flag)
{
const auto flags = supportedFlags();
const auto i = std::find_if(flags.begin(), flags.end(), [&](const Str &f)
{
return f.compare(flag) == 0 ? true : false;
});
auto result = i != flags.end() ? true : false;
return result;
}
template<typename Str>
bool doesFlagHaveValue(const Str flag)
{
const auto flags = parsedFlags();
auto i = std::find_if(flags.begin(), flags.end(), [&](const Str &f)
{
return f.compare(flag) == 0 ? true : false;
});
if (i != flags.end())
{
if (++i != flags.end() && !isValidFlag<Str>(*i))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
void printAction() noexcept;
void printFlags() noexcept;
std::vector<std::string> parsedFlags();
std::string action;
std::vector<Models::Flags> flags;
char **params;
int paramCount;
};
}
#endif
-215
View File
@@ -1,215 +0,0 @@
#ifndef COMMITMANAGER_H_
#define COMMITMANAGER_H_
#include<map>
#include<iostream>
#include<string>
#include<string_view>
#include"Models/API.h"
#include"Models/IcarusAction.h"
#include"Models/Song.h"
#include"Models/Token.h"
#include"Utilities/Checks.h"
namespace Managers
{
class CommitManager
{
public:
CommitManager(Models::IcarusAction&);
void commitAction();
enum class RetrieveTypes
{
songs
};
// Used for parsing songs from the metadata file
class Album
{
public:
Album() = default;
void printInfo();
std::string album;
std::string albumArtist;
std::string genre;
int year;
int trackCount;
int discCount;
std::vector<Models::Song> songs;
};
private:
enum class ActionValues;
std::map<std::string, ActionValues> mapActions() noexcept;
void deleteSong();
void downloadSong();
void retrieveObjects();
void uploadSong();
Models::Token parseToken(Models::API);
// Uploads a single song. The song is constructed from a metadata file that contains
// information about the album the song is from. Also, the cover art of the song must
// be present.
//
// Expects
// * Song - mp3 file path
// * TrackID - track number to chose from when retrieving metadata. "1" and "1:1" are similar
// * Metadata - Source file containing metadata of the song
// * Cover art - path to image cover art
void uploadSongWithMetadata();
// Expects the song path, trackID, metadata file path, and cover path
void singTargetUpload(const std::string &songPath, const std::string &trackID,
const std::string &metaPath, const std::string &coverPath);
// Expects the source directory that contains songs, a metadata file, and cover image
// Disc and Track is retrieved from the filename if the filename conforms to a standard.
// If not, then the disc and track will default to 1
//
// Standards
// * track01.mp3 - Disc 1, Track 1
// * track05d02.mp3 - Disc 2, Track 5
void multiTargetUpload(const std::string &sourcePath);
// Standards
// * track01.mp3 - Disc 1, Track 1
// * track05d02.mp3 - Disc 2, Track 5
template<typename Song, typename Str>
void initializeDiscAndTrack(Song &song)
{
auto disc = 1;
auto track = 1;
// If 1 go with first standard, if 2 go with the second, if 0 then will default to 1 for disc and track
auto mode = 0;
const Str &songPath = song.songPath;
auto trd = song.songPath.find("trackd");
auto tr = song.songPath.find("track");
if (tr != Str::npos)
{
mode = 1;
}
if (trd != Str::npos)
{
mode = 2;
}
auto dl = [](char c, char t){ return c == t; };
auto d = Utilities::Checks::itemIterInContainer<char, Str>(songPath, 'd', dl);
auto k = Utilities::Checks::itemIterInContainer<char, Str>(songPath, 'k', dl);
auto dot = Utilities::Checks::itemIterInContainer<char, Str>(songPath, '.', dl);
switch(mode)
{
case 1:
{
if (k != songPath.end() && dot != songPath.end())
{
auto tStr = std::string(++k, dot);
std::cout << "TStr: " << tStr<<"\n";
if (Utilities::Checks::isNumber(tStr))
{
track = std::atoi(tStr.c_str());
}
}
break;
}
case 2:
{
if (k != songPath.end() && dot != songPath.end() && d != songPath.end())
{
auto tStr = std::string(++k, d);
auto dStr = std::string(++d, dot);
std::cout<<"DStr: "<<dStr<<" TStr: " << tStr<<"\n";
if (Utilities::Checks::isNumber(tStr))
{
track = std::atoi(tStr.c_str());
}
else if (Utilities::Checks::isNumber(dStr))
{
disc = std::atoi(dStr.c_str());
}
}
break;
}
}
song.disc = disc;
song.track = track;
}
template<typename Song, typename Str>
void parseDiscAndTrack(Song &song, const Str &trackID)
{
auto sep = [](char c, char t) { return c == t; };
auto separator = Utilities::Checks::itemIterInContainer<char, Str>(trackID, ':', sep);
if (separator != trackID.end())
{
auto dStr = Str(trackID.begin(), separator);
auto tStr = Str(++separator, trackID.end());
song.disc = std::atoi(dStr.c_str());
song.track = std::atoi(tStr.c_str());
}
else
{
auto isNumber = Utilities::Checks::isNumber(trackID);
if (isNumber)
{
song.track = std::atoi(trackID.c_str());
}
}
}
// Checks for the no confirm flag. Used when uploading songs from a directory
bool checkForNoConfirm()
{
for (const auto &arg: this->icaAction.flags)
{
if (arg.flag.compare("-nc") == 0)
{
return true;
}
}
return false;
}
Album retrieveMetadata(const std::string_view path);
std::string retrieveFileContent(const std::string_view path);
enum class ActionValues
{
deleteAct,
downloadAct,
retrieveAct,
uploadAct,
UPLOAD_SONG_WITH_METADATA // Uploads the song with metadata, including cover art
};
Models::IcarusAction icaAction;
};
}
#endif
-31
View File
@@ -1,31 +0,0 @@
#ifndef FILEMANAGER_H_
#define FILEMANAGER_H_
#include<string>
namespace Managers
{
class FileManager
{
public:
FileManager();
FileManager(std::string);
void saveFile(std::string);
void modifyFilePath(std::string);
char* retrieveFileBuffer() const;
int retrieveFileBufferLength() const;
private:
void readFile();
std::string filePath;
char* fileBuffer;
bool fileRead;
int fileBufferLength;
};
}
#endif
-25
View File
@@ -1,25 +0,0 @@
#ifndef TOKENMANAGER_H_
#define TOKENMANAGER_H_
#include"Models/API.h"
#include"Models/Token.h"
#include"Models/User.h"
namespace Managers
{
class TokenManager
{
public:
TokenManager(const Models::User&);
TokenManager(const Models::User&, Models::API&);
Models::Token requestToken();
private:
Models::API api;
Models::User user;
};
}
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef USERMANAGER_H_
#define USERMANAGER_H_
#include<iostream>
#include"Models/IcarusAction.h"
#include"Models/User.h"
namespace Managers
{
class UserManager
{
public:
UserManager(Models::User);
UserManager(const Models::IcarusAction);
Models::User retrieveUser() const;
private:
void parseUserFromActions();
Models::User user;
Models::IcarusAction icaAction;
};
}
#endif
-19
View File
@@ -1,19 +0,0 @@
#ifndef API_H_
#define API_H_
#include<string>
namespace Models
{
class API
{
public:
std::string url;
std::string endpoint;
std::string version;
};
}
#endif
-18
View File
@@ -1,18 +0,0 @@
#ifndef FLAGS_H_
#define FLAGS_H_
#include<string>
namespace Models
{
class Flags
{
public:
std::string flag;
std::string value;
};
}
#endif
-53
View File
@@ -1,53 +0,0 @@
#ifndef ICARUSACTION_H_
#define ICARUSACTION_H_
#include<string>
#include<algorithm>
#include<string_view>
#include<vector>
#include<iostream>
#include"Flags.h"
namespace Models
{
class IcarusAction
{
public:
std::string retrieveFlagValue(const std::string_view flag)
{
std::string value;
const auto fg = std::find_if(flags.begin(), flags.end(), [&](Flags f)
{
return f.flag.compare(flag) == 0 ? true : false;
});
if (fg != flags.end())
{
value.assign(fg->value);
}
return value;
}
void print_action_and_flags() noexcept
{
std::cout<<"Action: "<<this->action<<"\n";
std::cout<<"Flag count: "<<this->flags.size()<<"\n";
for (const auto &flag : this->flags)
{
std::cout<<"flag "<<flag.flag<<" value "<<flag.value<<"\n";
}
std::cout<<"\n";
}
std::string action;
std::vector<Flags> flags;
};
}
#endif
-90
View File
@@ -1,90 +0,0 @@
#ifndef SONG_H_
#define SONG_H_
#include <string>
#include <iostream>
#include <sstream>
namespace Models
{
class Song
{
public:
Song() = default;
void printInfo()
{
std::cout<<"Title: "<<this->title<<"\n";
std::cout<<"\n";
}
std::string song_path() noexcept
{
std::stringstream buffer;
buffer << this->directory;
const auto count = this->directory.size();
if (this->directory.at(count - 1) != '/' || this->directory.at(count - 1) != '\\')
{
buffer << "/";
}
buffer << this->filename;
return buffer.str();
}
int generate_filename_from_track()
{
auto result = 0;
std::stringstream buffer;
buffer << "track";
// NOTE: Multiple discs in one directory is not being addressed
if (this->track < 10)
{
buffer << "0";
}
buffer << this->track << ".mp3";
this->filename.assign(buffer.str());
return result;
}
std::string toMetadataJson();
int id;
std::string title;
std::string artist;
std::string album;
std::string genre;
int year;
int duration;
int track;
int disc;
std::string data;
[[deprecated("Use song_path() function instead")]]
std::string songPath;
std::string filename;
std::string directory;
};
class CoverArt
{
public:
int id;
std::string title;
std::string path;
};
}
#endif
-18
View File
@@ -1,18 +0,0 @@
#ifndef TOKEN_H_
#define TOKEN_H_
#include<string>
namespace Models
{
struct Token
{
std::string accessToken;
std::string tokenType;
int expiration;
};
}
#endif
-18
View File
@@ -1,18 +0,0 @@
#ifndef UPLOADFORM_H_
#define UPLOADFORM_H_
#include<string>
namespace Models
{
struct UploadForm
{
std::string url;
std::string filePath;
};
}
#endif
-17
View File
@@ -1,17 +0,0 @@
#ifndef USER_H_
#define USER_H_
#include<string>
namespace Models
{
struct User
{
std::string username;
std::string password;
};
}
#endif
-25
View File
@@ -1,25 +0,0 @@
#ifndef APIPARSER_H_
#define APIPARSER_H_
#include"Models/API.h"
#include"Models/IcarusAction.h"
namespace Parsers
{
class APIParser
{
public:
APIParser(Models::IcarusAction);
Models::API retrieveAPI() const;
private:
void parseAPI();
Models::API api;
Models::IcarusAction icaAct;
};
}
#endif
-25
View File
@@ -1,25 +0,0 @@
#ifndef DELETE_H_
#define DELETE_H_
#include"Models/API.h"
#include"Models/Song.h"
#include"Models/Token.h"
#include"SyncerBase.h"
namespace Syncers
{
class Delete : SyncerBase
{
public:
Delete(Models::API);
void deleteSong(const Models::Token, Models::Song);
private:
std::string retrieveUrl(Models::Song);
};
}
#endif
-33
View File
@@ -1,33 +0,0 @@
#ifndef DOWNLOAD_H_
#define DOWNLOAD_H_
#include<iostream>
#include<string>
#include"Models/API.h"
#include"Models/Song.h"
#include"Models/Token.h"
#include"SyncerBase.h"
namespace Syncers
{
class Download : SyncerBase
{
public:
Download();
Download(Models::API);
Download(std::string);
void downloadSong(const Models::Token token, Models::Song);
private:
std::string retrieveUrl(Models::Song);
std::string downloadFilePath;
void saveSong(Models::Song&);
};
}
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef RETRIEVERECORDS_H_
#define RETRIEVERECORDS_H_
#include "Managers/CommitManager.h"
#include "Models/API.h"
#include "Models/Token.h"
#include "Syncers/SyncerBase.h"
namespace Syncers
{
class RetrieveRecords: public SyncerBase
{
public:
RetrieveRecords();
RetrieveRecords(Models::API, Models::Token);
void retrieve(Managers::CommitManager::RetrieveTypes);
private:
void fetchSongs();
Models::API api;
Models::Token token;
};
}
#endif
-29
View File
@@ -1,29 +0,0 @@
#ifndef SYNCERBASE_H_
#define SYNCERBASE_H_
#include<string>
#include"Models/API.h"
namespace Syncers
{
class SyncerBase
{
protected:
Models::API api;
const int OK = 200;
const int UNAUTHORIZED = 401;
const int NOTFOUND = 404;
enum class Result
{
OK = 200,
UNAUTHORIZED = 401,
NOTFOUND = 404
};
};
}
#endif
-55
View File
@@ -1,55 +0,0 @@
#ifndef UPLOAD_H_
#define UPLOAD_H_
#include <filesystem>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "Managers/CommitManager.h"
#include "Managers/FileManager.h"
#include "Models/API.h"
#include "Models/Song.h"
#include "Models/Token.h"
#include "Models/UploadForm.h"
namespace fs = std::filesystem;
namespace Syncers
{
class Upload
{
public:
Upload() = default;
Upload(Models::API api, Models::Token token) : m_token(token), api(api)
{
this->api.endpoint = "song/data";
}
Models::Song uploadSong(Models::Song&);
void uploadSongsFromDirectory(const std::string&, const bool, bool);
void uploadSongWithMetadata(Managers::CommitManager::Album&, Models::Song&, Models::CoverArt&);
private:
Managers::FileManager fMgr;
Models::API api;
Models::Song song;
Models::Token m_token;
std::vector<Models::Song> retrieveAllSongsFromDirectory(const std::string&,
bool);
std::string retrieveUrl();
Models::Song retrieveSongPath(fs::directory_entry&);
void printSongDetails();
void printSongDetails(std::vector<Models::Song>&);
void printJsonData(const nlohmann::json&);
};
}
#endif
-36
View File
@@ -1,36 +0,0 @@
#ifndef ABOUTWINDOW_H_
#define ABOUTWINDOW_H_
#include<memory>
#include<QDialog>
#include<QDockWidget>
#include<QLabel>
#include<QPushButton>
#include<QWidget>
#include"UI/CommonWindow.h"
namespace UI
{
class AboutWindow: public QDialog, public CommonWindow
{
Q_OBJECT
public:
AboutWindow(QWidget* parent=0);
~AboutWindow() = default;
private:
void connections();
void setupWindow();
std::unique_ptr<QLabel> appName;
private slots:
void closeWindow();
};
}
#endif
-33
View File
@@ -1,33 +0,0 @@
#ifndef COMMONWINDOW_H_
#define COMMONWINDOW_H_
#include<memory>
#include<QDialog>
#include<QHBoxLayout>
#include<QVBoxLayout>
#include<QComboBox>
#include<QPushButton>
namespace UI
{
class CommonWindow
{
public:
CommonWindow() = default;
~CommonWindow() = default;
protected:
virtual void connections()=0;
std::unique_ptr<QComboBox> selectionBoxQt;
std::unique_ptr<QPushButton> actionButtonQt;
std::unique_ptr<QVBoxLayout> mainLayoutQt;
std::unique_ptr<QVBoxLayout> subLayoutOneQt;
std::unique_ptr<QVBoxLayout> subLayoutTwoQt;
int windowHeight, windowWidth;
};
}
#endif
-84
View File
@@ -1,84 +0,0 @@
#ifndef MAINWINDOW_H_
#define MAINWINDOW_H_
#include<iostream>
#include<memory>
#include<QAction>
#include<QComboBox>
#include<QDialog>
#include<QDockWidget>
#include<QLabel>
#include<QMenu>
#include<QMenuBar>
#include<QMainWindow>
#include<QPushButton>
#include<QStackedWidget>
#include<QTextEdit>
#include<QWidget>
#include"UI/CommonWindow.h"
#include"UI/AboutWindow.h"
namespace UI
{
class MainWindow: public QMainWindow, public CommonWindow
{
Q_OBJECT
public:
MainWindow();
~MainWindow() = default;
private:
void configureDownloadSection();
void configureUploadSection();
void configureWindowDimensions();
void configureWindowProperties();
void connections();
void createMenus();
void setupMainWidget();
void setupMainWindow();
void setupWindowLists();
std::unique_ptr<QStackedWidget> widgetStack;
std::unique_ptr<QVBoxLayout> stackLayout;
std::unique_ptr<QHBoxLayout> urlPortion;
std::unique_ptr<QHBoxLayout> songPathPortion;
std::unique_ptr<QWidget> mainWidgetQt;
std::unique_ptr<QWidget> uploadSongWidgetQt;
std::unique_ptr<QDockWidget> MainDockWidgetQt;
std::unique_ptr<QComboBox> windowComboBox;
std::unique_ptr<QPushButton> uploadSongQt;
std::unique_ptr<QTextEdit> urlQt;
std::unique_ptr<QTextEdit> sourceFilePathQt;
std::unique_ptr<QLabel> urlLabel;
std::unique_ptr<QLabel> songPath;
std::unique_ptr<QMenu> fileMenuQt;
std::unique_ptr<QMenu> editMenuQt;
std::unique_ptr<QMenu> helpMenuQt;
std::unique_ptr<QAction> closeApplicationQt;
std::unique_ptr<QAction> aboutApplicationQt;
std::unique_ptr<AboutWindow> aboutWindow;
signals:
private slots:
void uploadSong();
void exitApplication();
void displaySoftwareInformation();
void setCurrentIndex(int);
};
}
#endif
-68
View File
@@ -1,68 +0,0 @@
#ifndef CHECKS_H_
#define CHECKS_H_
#include<algorithm>
#include<cstdlib>
#include<ctype.h>
namespace Utilities
{
class Checks
{
public:
Checks() = delete;
static auto isNumber(const std::string &val)
{
return !val.empty() && std::find_if(val.begin(),
val.end(), [](char c)
{
return !std::isdigit(c);
}) == val.end();
}
// Note: Not implemented
template<typename Item, typename Container, typename Func>
static auto itemInContainer(const Container container, const Item item, Func func)
{
auto result = false;
auto i = std::find_if(container.begin(), container.end(), [&](Item i)
{
return func(item, i);
});
if (i != container.end())
{
result = true;
}
return result;
}
template<typename Item, typename Container, typename Func>
static auto itemIterInContainer(const Container &container, const Item &item, Func func)
{
auto result = false;
// std::cout<<container<<"\n";
auto ii = std::find_if(container.begin(), container.end(), [&](Item i)
{
// std::cout<<"iter "<<i<<" target "<<item<<"\n";
return func(i, item);
});
if (ii == container.end())
{
// std::cout<<item<<" not found in container\n";
ii = container.end();
}
return ii;
}
private:
};
}
#endif
-32
View File
@@ -1,32 +0,0 @@
#ifndef CONVERSIONS_H_
#define CONVERSIONS_H_
#include<memory>
#include<string>
namespace Utilities
{
class Conversions
{
public:
Conversions();
static void toLowerChar(char &c)
{
if (std::isalpha(c))
{
c = std::tolower(c);
}
}
void initializeValues();
template<typename T>
void printValue(T val);
private:
};
}
#endif
View File
-71
View File
@@ -1,71 +0,0 @@
#include<iostream>
#include<string>
#include"Managers/ActionManager.h"
#include"Managers/CommitManager.h"
using std::cin;
using std::cout;
using std::endl;
using std::string;
using Managers::ActionManager;
using Managers::CommitManager;
constexpr static auto IcarusDownloadManager_version()
{
return "v0.2.0";
}
void printHelp()
{
cout<<"icd [Action] [flag]\n\n";
cout<<"Actions\n";
cout<<"download\n";
cout<<"upload\n";
cout<<"retrieve\n";
cout<<"delete\n\n";
cout<<"Flags\n";
cout<<"Required for all actions\n";
cout<<"-u username\n";
cout<<"-p password\n";
cout<<"-h host\n\n";
cout<<"Required for upload\n";
cout<<"-s path of song\n";
cout<<"-sd directory where to search for songs to upload (Optional)\n";
cout<<"-sr directory where to recursively search for songs to upload (Optional)\n";
cout<<"-nc will not prompt the user when uploading from a directory\n\n";
cout<<"Required for download\n";
cout<<"-b song id\n";
cout<<"-d path to download song (Optional)\n\n";
cout<<"Required for retrieving records\n";
cout<<"-rt retrieve type (songs is only accepted)\n\n";
cout<<"Required for deleting a song\n";
cout<<"-D song id\n\n";
}
int main(int argc, char** argv)
{
if (argc < 2)
{
printHelp();
return -1;
}
ActionManager actMgr(argv, argc);
auto chosenAction = actMgr.retrieveIcarusAction();
chosenAction.print_action_and_flags();
CommitManager commitMgr(chosenAction);
commitMgr.commitAction();
return 0;
}
-118
View File
@@ -1,118 +0,0 @@
#include"Managers/ActionManager.h"
#include<algorithm>
#include<iostream>
#include<utility>
#include<cstring>
using std::cout;
using std::endl;
using std::string;
using std::string_view;
using std::vector;
using Models::Flags;
using Models::IcarusAction;
namespace Managers
{
#pragma region Constructors
ActionManager::ActionManager(char **param, int paramCount) :
params(std::move(param)), paramCount(paramCount)
{
initialize();
}
#pragma endregion
#pragma region Functions
IcarusAction ActionManager::retrieveIcarusAction() const
{
IcarusAction icarusAction;
icarusAction.flags = flags;
icarusAction.action = action;
return icarusAction;
}
void ActionManager::initialize()
{
validateFlags();
action = std::move(string{params[1]});
transform(action.begin(), action.end(),
action.begin(), ::tolower);
}
void ActionManager::validateFlags()
{
cout<<"Validating flags\n";
const auto flagVals = parsedFlags();
for (auto flag = flagVals.begin(); flag != flagVals.end(); ++flag)
{
Flags flg;
cout<<"Value: "<<*flag<<"\n";
if (isValidFlag<string>(*flag) && doesFlagHaveValue<string>(*flag))
{
cout<<"Flag has value\n";
flg.flag = *flag;
flg.value = *(++flag);
}
else if (isValidFlag<string>(*flag))
{
cout<<"Flag does not have a value\n";
flg.flag = *flag;
}
else
{
cout<<"Flag "<<*flag<<" is not valid"<<endl;
exit(1);
}
flags.emplace_back(std::move(flg));
}
}
vector<string> ActionManager::parsedFlags()
{
auto parsed = vector<string>();
for (auto i = 2; i < paramCount; ++i)
{
const std::string flag(std::move(*(params + i)));
parsed.push_back(std::move(flag));
}
return parsed;
}
#pragma region Testing
void ActionManager::printAction() noexcept
{
if (action.empty())
{
printf("Action is empty\n");
}
else
{
cout<<"Action is "<<action<<endl;
}
}
void ActionManager::printFlags() noexcept
{
cout<<"\nPrinting flags..."<<endl;
for (auto flag: flags)
{
cout<<"flag "<<flag.flag<<endl;
cout<<"value "<<flag.value<<endl;
}
}
#pragma endregion
#pragma endregion
}
-445
View File
@@ -1,445 +0,0 @@
#include"Managers/CommitManager.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <filesystem>
#include "nlohmann/json.hpp"
#include "Models/API.h"
#include "Models/Song.h"
#include "Models/Token.h"
#include "Parsers/APIParser.h"
#include "Syncers/Delete.h"
#include "Syncers/Download.h"
#include "Syncers/RetrieveRecords.h"
#include "Syncers/Upload.h"
#include "Managers/TokenManager.h"
#include "Managers/UserManager.h"
using std::cout;
using std::endl;
using std::map;
using std::string;
using Managers::TokenManager;
using Managers::UserManager;
using Models::API;
using Models::Song;
using Models::Token;
using Parsers::APIParser;
using Models::IcarusAction;
using Syncers::Delete;
using Syncers::Download;
using Syncers::RetrieveRecords;
using Syncers::Upload;
namespace filesystem = std::filesystem;
namespace Managers
{
#pragma region Constructors
CommitManager::CommitManager(IcarusAction& icaAct) : icaAction(std::move(icaAct))
{ }
#pragma endregion
#pragma region Functions
void CommitManager::commitAction()
{
auto action = icaAction.action;
cout<<"Commiting "<<action<<" action"<<endl;
switch (mapActions()[action])
{
case ActionValues::deleteAct:
deleteSong();
break;
case ActionValues::downloadAct:
downloadSong();
break;
case ActionValues::retrieveAct:
retrieveObjects();
break;
case ActionValues::uploadAct:
uploadSong();
break;
case ActionValues::UPLOAD_SONG_WITH_METADATA:
uploadSongWithMetadata();
break;
default:
break;
}
}
std::map<std::string, CommitManager::ActionValues>
CommitManager::mapActions() noexcept
{
const std::map<std::string, ActionValues> actions{
{"delete", ActionValues::deleteAct},
{"download", ActionValues::downloadAct},
{"retrieve", ActionValues::retrieveAct},
{"upload", ActionValues::uploadAct},
{"upload-meta", ActionValues::UPLOAD_SONG_WITH_METADATA}
};
return actions;
}
Token CommitManager::parseToken(API api)
{
cout<<"fetching token\n";
UserManager usrMgr{icaAction};
auto user = usrMgr.retrieveUser();
TokenManager tk{user, api};
return tk.requestToken();
}
void CommitManager::deleteSong()
{
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
Song song{};
for (auto arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-D") == 0)
{
song.id = atoi(value.c_str());
}
}
Delete del{api};
cout<<"Deleting song..."<<endl;
del.deleteSong(token, song);
}
void CommitManager::downloadSong()
{
cout<<"Starting downloading process..."<<endl;
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
Song song{};
for (auto arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-d") == 0)
{
song.songPath.assign(arg.value);
}
if (flag.compare("-b") == 0)
{
song.id = atoi(value.c_str());
}
}
Download dnld{api};
cout<<"downloading song"<<endl;
dnld.downloadSong(token, song);
}
void CommitManager::retrieveObjects()
{
cout<<"Starting retrieve process..."<<endl;
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
RetrieveTypes retrieveType;
for (auto arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-rt") == 0)
{
if (value.compare("songs") == 0)
{
retrieveType = RetrieveTypes::songs;
break;
}
}
}
RetrieveRecords songs{api, token};
songs.retrieve(retrieveType);
}
void CommitManager::uploadSong()
{
auto uploadSingleSong = true;
auto recursiveDirectory = false;
const auto noConfirm = checkForNoConfirm();
string songDirectory;
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
Song song;
for (auto& arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-s") == 0)
{
song.songPath.assign(arg.value);
}
else if (flag.compare("-sd") == 0)
{
songDirectory = value;
uploadSingleSong = false;
}
else if (flag.compare("-sr") == 0)
{
songDirectory = value;
uploadSingleSong = false;
recursiveDirectory = true;
}
}
Upload upld{api, token};
if (uploadSingleSong)
{
cout<<"Uploading song..."<<endl;
upld.uploadSong(song);
}
else
{
cout<<"Uploading songs from " << songDirectory << endl;
upld.uploadSongsFromDirectory(songDirectory, noConfirm, recursiveDirectory);
}
}
void CommitManager::uploadSongWithMetadata()
{
cout<<"Uploading single song with metadata\n\n";
// Either the set of "-s", "-m", "-ca", "-t" flags or "-smca" must exist with values
// in order to be valid but not both
const auto songPath = this->icaAction.retrieveFlagValue("-s");
const auto metadataPath = this->icaAction.retrieveFlagValue("-m");
const auto coverPath = this->icaAction.retrieveFlagValue("-ca");
const auto trackID = this->icaAction.retrieveFlagValue("-t");
const auto singleTarget = !songPath.empty() && !metadataPath.empty() &&
!coverPath.empty() && !trackID.empty() ? true : false;
const auto uni = this->icaAction.retrieveFlagValue("-smca");
const auto multiTarget = !uni.empty() ? true : false;
if (singleTarget && multiTarget)
{
cout<<"Cannot upload from source and directory\n";
return;
}
cout<<"Song path: "<<songPath<<"\n";
cout<<"TrackID: "<<trackID<<"\n";
cout<<"Metadata path: "<<metadataPath<<"\n";
cout<<"Cover Art path: "<<coverPath<<"\n";
if (singleTarget)
{
singTargetUpload(songPath, trackID, metadataPath, coverPath);
}
else if (multiTarget)
{
multiTargetUpload(uni);
}
}
void CommitManager::singTargetUpload(const std::string &songPath, const std::string &trackID,
const std::string &metaPath, const std::string &coverPath)
{
APIParser apiPrs(icaAction);
auto api = apiPrs.retrieveAPI();
const auto token = parseToken(api);
auto album = retrieveMetadata(metaPath);
album.printInfo();
Song song;
song.track = 1;
song.disc = 1;
cout<<"TrackID: "<<trackID<<"\n";
parseDiscAndTrack<Song, std::string>(song, trackID);
auto c = [](const Song &songA, const Song &songB) { return songA.track == songB.track && songA.disc == songB.disc; };
auto sng = Utilities::Checks::itemIterInContainer<Song, std::vector<Song>>(album.songs, song, c);
if (sng == album.songs.end())
{
cout<<"Not found with disc "<<song.disc<<" track "<<song.track<<"\n";
std::exit(-1);
}
song = *sng;
const auto p = fs::path(songPath);
song.directory = p.parent_path.string();
song.generate_filename_from_track();
Models::CoverArt cover;
cover.title = song.title;
cover.path = coverPath;
Upload up(api, token);
up.uploadSongWithMetadata(album, song, cover);
}
void CommitManager::multiTargetUpload(const std::string &sourcePath)
{
APIParser apiPrs(icaAction);
auto api = apiPrs.retrieveAPI();
const auto token = parseToken(api);
if (!fs::is_directory(sourcePath))
{
cout<<sourcePath<<" is not a directory\n";
std::exit(-1);
}
std::vector<Song> songs;
Models::CoverArt cover;
string metadataPath;
for (auto &p: fs::directory_iterator(sourcePath))
{
const auto &pp = p.path();
const auto stem = pp.stem();
const auto file = pp.filename();
const auto extension = pp.extension();
cout<<"Stem "<<stem<<" Extension "<<extension<<"\n";
if (extension.compare(".mp3") == 0)
{
Song song;
song.songPath = pp.string();
initializeDiscAndTrack<Song, std::string>(song);
songs.emplace_back(std::move(song));
}
else if (extension.compare(".jpg") == 0 || extension.compare(".png") == 0)
{
cover.path.assign(pp.string());
}
else if (extension.compare(".json") == 0)
{
metadataPath.assign(pp.string());
}
}
auto album = retrieveMetadata(metadataPath);
songs.clear();
songs.assign(album.songs.begin(), album.songs.end());
Upload up(api, token);
for (auto &song : songs)
{
up.uploadSongWithMetadata(album, song, cover);
}
}
#pragma region private
CommitManager::Album CommitManager::retrieveMetadata(const std::string_view path)
{
CommitManager::Album album;
const auto fileContent = retrieveFileContent(path);
cout<<"Parsing...\n";
auto serialized = nlohmann::json::parse(fileContent);
cout<<"Parsed\n";
album.album = serialized["album"].get<std::string>();
album.albumArtist = serialized["album_artist"].get<std::string>();
album.genre = serialized["genre"].get<std::string>();
album.year = serialized["year"].get<int>();
album.trackCount = serialized["track_count"].get<int>();
album.discCount = serialized["disc_count"].get<int>();
album.songs.reserve(album.trackCount);
for (auto &j : serialized["tracks"])
{
Song song;
song.title = j["title"].get<std::string>();
song.track = j["track"].get<int>();
song.disc = j["disc"].get<int>();
song.artist = j["artist"].get<std::string>();
song.album = album.album;
song.year = album.year;
song.genre = album.genre;
song.generate_filename_from_track();
const auto p = fs::path(path);
song.directory = p.parent_path().string();
album.songs.push_back(song);
}
return album;
}
string CommitManager::retrieveFileContent(const std::string_view path)
{
string path_str(path);
string value;
std::stringstream buffer;
std::fstream file(path_str, std::ios::in);
buffer<<file.rdbuf();
file.close();
value.assign(buffer.str());
return value;
}
#pragma endregion
void CommitManager::Album::printInfo()
{
std::cout<<"Album: "<<this->album<<"\n";
std::cout<<"Album Artist: "<<this->albumArtist<<"\n";
std::cout<<"Genre: "<<this->genre<<"\n";
std::cout<<"Year: "<<this->year<<"\n";
std::cout<<"Track count: "<<this->trackCount<<"\n";
std::cout<<"Disc count: "<<this->discCount<<"\n";
std::cout<<"\n";
}
#pragma region Functions
}
-73
View File
@@ -1,73 +0,0 @@
#include<iostream>
#include<fstream>
#include"Managers/FileManager.h"
using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
namespace Managers
{
#pragma region Constructors
FileManager::FileManager() {}
FileManager::FileManager(string filePath)
{
this->filePath = filePath;
readFile();
}
#pragma endregion
#pragma region Functions
void FileManager::saveFile(string newFilePath)
{
if (!fileRead)
readFile();
ofstream of{newFilePath, ofstream::binary};
of.write(fileBuffer, fileBufferLength);
of.close();
}
void FileManager::readFile()
{
ifstream is{filePath, ifstream::binary};
if (is)
{
is.seekg (0, is.end);
fileBufferLength = is.tellg();
is.seekg (0, is.beg);
fileBuffer = new char [fileBufferLength];
cout<< "Reading "<<fileBufferLength<<" characters... "<<endl;;
is.read (fileBuffer,fileBufferLength);
if (is)
cout<<"all characters read successfully.";
else
cout<<"error: only "<<is.gcount()<<" could be read";
cout<<endl;
is.close();
fileRead = true;
}
}
void FileManager::modifyFilePath(string filePath)
{
this->filePath = filePath;
}
char* FileManager::retrieveFileBuffer() const
{
return fileBuffer;
}
int FileManager::retrieveFileBufferLength() const { return fileBufferLength; }
#pragma endregion
}
-62
View File
@@ -1,62 +0,0 @@
#include"Managers/TokenManager.h"
#include<iostream>
#include<cpr/cpr.h>
#include<nlohmann/json.hpp>
using std::cout;
using std::endl;
using json = nlohmann::json;
using Managers::TokenManager;
using Models::API;
using Models::Token;
using Models::User;
namespace Managers
{
#pragma region Constructors
TokenManager::TokenManager(const User& user)
{
this->user = user;
}
TokenManager::TokenManager(const User& user, API& api)
{
this->user = user;
this->api = api;
this->api.endpoint = "api/" + api.version
+ "/login";
}
#pragma endregion
#pragma region Functions
Token TokenManager::requestToken()
{
Token token{};
json usrObj;
usrObj["username"] = user.username;
usrObj["password"] = user.password;
cout<<"Sending request for token"<<endl;
auto url = api.url + api.endpoint;
cout<<url<<endl;
auto r = cpr::Post(cpr::Url{url},
cpr::Body{usrObj.dump()},
cpr::Header{{"Content-Type", "application/json"}});
json res = json::parse(r.text);
token.accessToken = res["token"];
token.tokenType = res["token_type"];
cout<<"status code "<<r.status_code<<endl;
return token;
}
#pragma endregion
}
-55
View File
@@ -1,55 +0,0 @@
#include"Managers/UserManager.h"
#include<iostream>
#include<string>
#include<vector>
using std::string;
using std::vector;
using Models::IcarusAction;
using Models::User;
namespace Managers
{
#pragma region Constructors
UserManager::UserManager(User user)
{
this->user = user;
}
UserManager::UserManager(const IcarusAction icaAct)
{
this->icaAction = icaAct;
this->user = User{};
parseUserFromActions();
}
#pragma endregion
#pragma region Functions
User UserManager::retrieveUser() const
{
return user;
}
void UserManager::parseUserFromActions()
{
auto args = icaAction.flags;
for (auto arg : args)
{
auto flag = arg.flag;
if (flag.compare("-u") == 0)
{
user.username = arg.value;
}
if (flag.compare("-p") == 0)
{
user.password = arg.value;
}
}
}
#pragma endregion
}
-22
View File
@@ -1,22 +0,0 @@
#include "Models/Song.h"
#include "nlohmann/json.hpp"
using std::string;
namespace Models
{
string Song::toMetadataJson()
{
nlohmann::json s;
s["title"] = this->title;
s["artist"] = this->artist;
s["album"] = this->album;
s["genre"] = this->genre;
s["year"] = this->year;
s["track"] = this->track;
s["disc"] = this->disc;
return s.dump();
}
}
-53
View File
@@ -1,53 +0,0 @@
#include "Parsers/APIParser.h"
#include <iostream>
using std::cout;
using std::endl;
using Models::API;
using Models::IcarusAction;
namespace Parsers
{
#pragma region Constructors
APIParser::APIParser(IcarusAction icaAct) : icaAct(icaAct)
{
api = API{};
parseAPI();
}
#pragma endregion
#pragma region Functions
API APIParser::retrieveAPI() const
{
return api;
}
void APIParser::parseAPI()
{
auto flags = icaAct.flags;
cout << "Parsing api" << endl;
for (auto i =0; i < flags.size(); ++i)
{
auto arg = flags[i].flag;
auto value = flags[i].value;
if (arg.compare("-h") == 0)
{
api.url = (value[value.size()-1] == '/') ? value : value + "/";
break;
}
}
// TODO: For now I will hard code
// the api version since I am only
// on version 1
api.version = "v1";
}
#pragma endregion
}
-64
View File
@@ -1,64 +0,0 @@
#include "Syncers/Delete.h"
#include <exception>
#include <iostream>
#include <cpr/cpr.h>
using std::cout;
using std::endl;
using std::exception;
using std::string;
using Models::API;
using Models::Song;
using Models::Token;
namespace Syncers
{
#pragma region Constructors
Delete::Delete(API api)
{
this->api = api;
this->api.endpoint = "song/data";
}
#pragma endregion
#pragma region Functions
void Delete::deleteSong(const Token token, Song song)
{
try
{
auto url = retrieveUrl(song);
string auth{token.tokenType};
auth.append(" " + token.accessToken);
auto r = cpr::Delete(cpr::Url(url),
cpr::Header{{"authorization", auth}});
auto statusCode = r.status_code;
cout<<"Status code "<<statusCode<<endl;
}
catch (exception e)
{
auto msg = e.what();
cout<<msg<<endl;
}
cout<<"Finished"<<endl;
}
string Delete::retrieveUrl(Song song)
{
string url{api.url + "api/" + api.version + "/" +
api.endpoint + "/"};
url.append(std::to_string(song.id));
cout<<"url "<<url<<endl;
return url;
}
#pragma endregion
}
-93
View File
@@ -1,93 +0,0 @@
#include "Syncers/Download.h"
#include <exception>
#include <iostream>
#include <fstream>
#include <cpr/cpr.h>
using std::cout;
using std::endl;
using std::exception;
using std::ofstream;
using std::string;
using Models::API;
using Models::Song;
using Models::Token;
namespace Syncers
{
#pragma region Constructors
Download::Download() { }
Download::Download(API api)
{
this->api = api;
this->api.endpoint = "song/data";
}
Download::Download(string filePath)
{
downloadFilePath = filePath;
}
#pragma endregion
#pragma region Functions
void Download::downloadSong(const Token token, Song song)
{
try
{
string url = retrieveUrl(song);
song.songPath.append("track.mp3");
cout<<"song path "<<song.songPath<<endl;
string auth{token.tokenType};
auth.append(" " + token.accessToken);
auto r = cpr::Get(cpr::Url(url),
cpr::Header{{"Content-type", "audio/mpeg"},
{"Authorization", auth}});
int statusCode = r.status_code;
if (statusCode == OK) {
song.data = r.text;
saveSong(song);
}
cout<<"finsihed with status code "<<statusCode<<endl;
}
catch (exception e)
{
auto msg = e.what();
cout<<msg<<endl;
}
}
string Download::retrieveUrl(Song song)
{
string url{api.url + "api/" + api.version + "/" +
api.endpoint + "/"};
url.append(std::to_string(song.id));
cout<<"url "<<url<<endl;
return url;
}
void Download::saveSong(Song& song)
{
cout<<"\nSaving song to: "<<song.songPath<<endl;
int bufferLength = song.data.length();
const char *data = song.data.c_str();
cout<<"buff length "<<bufferLength<<endl;
ofstream saveSong{song.songPath, std::ios::binary};
saveSong.write(data, bufferLength);
saveSong.close();
}
#pragma endregion
}
-67
View File
@@ -1,67 +0,0 @@
#include "Syncers/RetrieveRecords.h"
#include <iostream>
#include <fstream>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
#include "Utilities/Conversions.h"
using std::cout;
using std::endl;
using std::ofstream;
using Managers::CommitManager;
using Models::API;
using Models::Token;
using Utilities::Conversions;
namespace Syncers
{
#pragma region Constructors
RetrieveRecords::RetrieveRecords() { }
RetrieveRecords::RetrieveRecords(API api, Token token)
: token(token), api(api) { }
#pragma endregion
#pragma region Functions
void RetrieveRecords::retrieve(CommitManager::RetrieveTypes type)
{
switch (type)
{
case CommitManager::RetrieveTypes::songs:
fetchSongs();
break;
default:
break;
}
}
void RetrieveRecords::fetchSongs()
{
cout<<"fetching songs"<<endl;
auto url = api.url + "api/" + api.version + "/" + "song";
auto auth = token.tokenType;
auth.append(" " + token.accessToken);
auto r = cpr::Get(cpr::Url{url},
cpr::Header{{"authorization", auth},
});
if (r.status_code != (int)Result::OK) {
cout<<"something went wrong\n";
cout<<"status code: "<<r.status_code<<endl;
cout<<"message: "<<r.text<<endl;
return;
}
auto songData = nlohmann::json::parse(r.text);
ofstream writeData{};
writeData.open("songs.json");
writeData<<songData.dump(4);
writeData.close();
}
#pragma endregion
}
-243
View File
@@ -1,243 +0,0 @@
#include <iostream>
#include <filesystem>
#include <exception>
#include "cpr/cpr.h"
#include "Syncers/Upload.h"
#include "Utilities/Conversions.h"
using std::cout;
using std::cin;
using std::endl;
using std::exception;
using std::string;
using json = nlohmann::json;
using Managers::FileManager;
using Models::API;
using Models::Song;
using Models::UploadForm;
using namespace cpr;
namespace Syncers
{
#pragma region Constructors
#pragma endregion
#pragma region Functions
Song Upload::uploadSong(Song& song)
{
try
{
auto url = retrieveUrl();
cout<<"url "<<url<<endl;
string auth{this->m_token.tokenType};
auth.append(" " + this->m_token.accessToken);
auto r = cpr::Post(cpr::Url{url},
cpr::Multipart{{"key", "small value"},
{"file", cpr::File{song.songPath}}},
cpr::Header{{"authorization", auth}}
);
cout << "status code: " << r.status_code<< std::endl;
cout << r.text << endl;
auto result = nlohmann::json::parse(r.text);
cout<<"Finished"<<endl;
song.id = result["id"].get<int>();
song.title = result["title"].get<std::string>();
song.artist = result["artist"].get<std::string>();
song.album = result["album"].get<std::string>();
song.genre = result["genre"].get<std::string>();
song.year = result["year"].get<int>();
song.duration = result["duration"].get<int>();
song.track = result["track"].get<int>();
return song;
}
catch (exception& e)
{
auto msg = e.what();
cout<<msg<<endl;
}
return song;
}
void Upload::uploadSongsFromDirectory(const std::string& directory,
const bool noConfirm, bool recursive = false)
{
try
{
auto songs = retrieveAllSongsFromDirectory(directory, recursive);
auto confirmUpload = true;
while (!noConfirm)
{
auto answer = 'a';
cout << "are you sure you want to upload " << songs.size() << " songs? [y/n]";
cin >> answer;
Utilities::Conversions::toLowerChar(answer);
if (answer == 'y' || answer == 'Y')
{
confirmUpload = true;
break;
}
}
cout << "uploading songs\n";
for (auto& song: songs)
{
song = uploadSong(song);
}
}
catch (exception& e)
{
cout<<e.what()<<endl;
}
}
void Upload::uploadSongWithMetadata(Managers::CommitManager::Album &album, Models::Song& song, Models::CoverArt &cover)
{
this->api.endpoint.assign("song/data/upload/with/data");
try
{
auto url = retrieveUrl();
cout << "url " << url << "\n";
string auth(this->m_token.tokenType);
auth.append(" " + this->m_token.accessToken);
nlohmann::json s;
s["title"] = song.title;
s["album"] = album.album;
s["album_artist"] = album.albumArtist;
s["artist"] = song.artist;
s["year"] = album.year;
s["genre"] = album.genre;
s["disc"] = song.disc;
s["track"] = song.track;
s["disc_count"] = album.discCount;
s["track_count"] = album.trackCount;
const auto meta = s.dump();
cout<<"\n\nMeta:\n"<<meta<<"\n";
cout << "Filepath: " << song.song_path() << "\n";
auto multipart = cpr::Multipart{{"cover", cpr::File{cover.path}},
{"metadata", meta},
{"file", cpr::File{song.song_path()}}};
auto r = cpr::Post(cpr::Url{url}, multipart,
cpr::Header{{"authorization", auth}}
);
cout << "status code: " << r.status_code<< std::endl;
cout << r.text << endl;
}
catch (exception &e)
{
auto msg = e.what();
cout<<"Error: "<<msg<<"\n";
}
}
std::vector<Song> Upload::retrieveAllSongsFromDirectory(const std::string& directory,
bool recursive)
{
std::vector<Song> allSongs;
if (recursive)
{
for (auto p: fs::recursive_directory_iterator(directory))
{
auto song = retrieveSongPath(p);
if (!song.songPath.empty())
allSongs.push_back(song);
}
}
else
{
for (auto p: fs::directory_iterator(directory))
{
auto song = retrieveSongPath(p);
if (!song.songPath.empty())
allSongs.push_back(song);
}
}
return allSongs;
}
string Upload::retrieveUrl()
{
const string url{api.url + "api/" + api.version + "/" +
api.endpoint};
return url;
}
Song Upload::retrieveSongPath(fs::directory_entry& dirEntry)
{
constexpr auto mp3Ext = ".mp3";
Song song;
if (fs::is_regular_file(dirEntry.path()))
{
const auto ext = dirEntry.path().extension().string();
if (ext.compare(mp3Ext) == 0)
{
cout << "found mp3 file" << endl;
song.songPath = dirEntry.path().string();
}
}
return song;
}
#pragma region Testing
void Upload::printSongDetails(std::vector<Song>& songs)
{
for (auto& song: songs)
{
cout<<"Song details: "<<endl;
cout<<"Id: "<<song.id<<endl;
cout<<"Title: "<<song.title<<endl;
cout<<"Artist: "<<song.artist<<endl;
cout<<"Album: "<<song.album<<endl;
cout<<"Genre: "<<song.genre<<endl;
cout<<"Year: "<<song.year<<endl;
cout<<"Duration: "<<song.duration<<endl;
cout<<"Path: "<<song.songPath<<endl;
}
}
void Upload::printJsonData(const json& obj)
{
cout<<endl<<endl<<"JSon data: "<<endl;
cout<<"id: "<<obj["id"]<<endl;
cout<<"title: "<<obj["title"]<<endl;
cout<<"artist: "<<obj["artist"]<<endl;
cout<<"album: "<<obj["album"]<<endl;
cout<<"genre: "<<obj["genre"]<<endl;
cout<<"year: "<<obj["year"]<<endl;
cout<<"duration: "<<obj["duration"]<<endl;
cout<<"song_data: "<<obj["song_data"]<<endl;
cout<<endl<<endl;
}
#pragma endregion
#pragma endregion
}
-52
View File
@@ -1,52 +0,0 @@
#include "UI/AboutWindow.h"
using std::unique_ptr;
namespace UI
{
#pragma region Constructors
AboutWindow::AboutWindow(QWidget* parent): QDialog(parent)
{
setupWindow();
}
#pragma endregion
#pragma region Functions
void AboutWindow::setupWindow()
{
windowWidth = 250;
windowHeight = 300;
mainLayoutQt = unique_ptr<QVBoxLayout>{new QVBoxLayout};
appName = unique_ptr<QLabel>{new QLabel(tr("IcarusDownloadManager"))};
actionButtonQt = unique_ptr<QPushButton>{new QPushButton(tr("Close"))};
mainLayoutQt->addWidget(appName.get());
mainLayoutQt->addWidget(actionButtonQt.get());
setFixedWidth(windowWidth);
setFixedHeight(windowHeight);
setLayout(mainLayoutQt.get());
setWindowTitle("About");
connections();
}
void AboutWindow::connections()
{
QObject::connect(actionButtonQt.get(), SIGNAL(clicked()), this,
SLOT(closeWindow()));
}
void AboutWindow::closeWindow()
{
this->hide();
}
#pragma endregion
}
-180
View File
@@ -1,180 +0,0 @@
#include "UI/MainWindow.h"
#include <iostream>
#include <string>
#include "Models/UploadForm.h"
#include "Syncers/Upload.h"
#include "Utilities/Conversions.h"
using std::cout;
using std::endl;
using std::string;
using std::unique_ptr;
using Models::UploadForm;
using Syncers::Upload;
namespace UI
{
#pragma region Constructors
MainWindow::MainWindow()
{
setupMainWindow();
aboutWindow = unique_ptr<AboutWindow>{new AboutWindow};
}
#pragma endregion
#pragma region Functions
void MainWindow::configureDownloadSection()
{
}
void MainWindow::configureUploadSection()
{
uploadSongQt = unique_ptr<QPushButton>{new QPushButton(tr("Upload"))};
urlQt = unique_ptr<QTextEdit>{new QTextEdit()};
sourceFilePathQt = unique_ptr<QTextEdit>{new QTextEdit()};
urlLabel = unique_ptr<QLabel>{new QLabel(tr("URL"))};
songPath = unique_ptr<QLabel>{new QLabel(tr("Song Path"))};
urlPortion = unique_ptr<QHBoxLayout>{new QHBoxLayout};
songPathPortion = unique_ptr<QHBoxLayout>{new QHBoxLayout};
urlPortion.get()->addWidget(urlLabel.get());
urlPortion.get()->addWidget(urlQt.get());
songPathPortion->addWidget(songPath.get());
songPathPortion->addWidget(sourceFilePathQt.get());
subLayoutOneQt = unique_ptr<QVBoxLayout>{new QVBoxLayout};
subLayoutOneQt.get()->addLayout(urlPortion.get());
subLayoutOneQt->addLayout(songPathPortion.get());
mainLayoutQt.get()->addLayout(subLayoutOneQt.get());
}
void MainWindow::configureWindowDimensions()
{
windowWidth = 450;
windowHeight = 450;
}
void MainWindow::configureWindowProperties()
{
setWindowTitle("IcarusDownloadManager");
setFixedHeight(windowHeight);
setFixedWidth(windowWidth);
}
void MainWindow::connections()
{
QObject::connect(uploadSongQt.get(), SIGNAL(clicked()), this, SLOT(uploadSong()));
QObject::connect(closeApplicationQt.get(), SIGNAL(triggered()), this,
SLOT(exitApplication()));
QObject::connect(aboutApplicationQt.get(), SIGNAL(triggered()), this,
SLOT(displaySoftwareInformation()));
QObject::connect(windowComboBox.get(), SIGNAL(activated(int)),
this, SLOT(setCurrentIndex(int)));
}
void MainWindow::createMenus()
{
fileMenuQt = unique_ptr<QMenu>{menuBar()->addMenu(tr("File"))};
editMenuQt = unique_ptr<QMenu>{menuBar()->addMenu(tr("Edit"))};
helpMenuQt = unique_ptr<QMenu>{menuBar()->addMenu(tr("Help"))};
closeApplicationQt = unique_ptr<QAction>{new QAction(new QObject(nullptr))};
closeApplicationQt->setText("Exit Application");
aboutApplicationQt = unique_ptr<QAction>{new QAction(new QObject(nullptr))};
aboutApplicationQt->setText("About");
fileMenuQt->addAction(closeApplicationQt.get());
helpMenuQt->addAction(aboutApplicationQt.get());
}
void MainWindow::setupMainWidget()
{
mainWidgetQt = unique_ptr<QWidget>{new QWidget};
windowComboBox = unique_ptr<QComboBox>{new QComboBox};
setupWindowLists();
stackLayout = unique_ptr<QVBoxLayout>{new QVBoxLayout};
stackLayout->addWidget(windowComboBox.get());
uploadSongWidgetQt = unique_ptr<QWidget>{new QWidget};
uploadSongWidgetQt->setLayout(mainLayoutQt.get());
stackLayout->addWidget(uploadSongWidgetQt.get());
mainWidgetQt->setLayout(stackLayout.get());
}
void MainWindow::setupMainWindow()
{
configureWindowDimensions();
mainLayoutQt = unique_ptr<QVBoxLayout>{new QVBoxLayout};
widgetStack = unique_ptr<QStackedWidget>{new QStackedWidget};
configureUploadSection();
setupMainWidget();
widgetStack->addWidget(mainWidgetQt.get());
MainDockWidgetQt = unique_ptr<QDockWidget>{new QDockWidget};
MainDockWidgetQt.get()->setWindowTitle(tr("Music Manager"));
MainDockWidgetQt->setWidget(widgetStack.get());
MainDockWidgetQt.get()->setFeatures(QDockWidget::NoDockWidgetFeatures);
setCentralWidget(MainDockWidgetQt.get());
createMenus();
configureWindowProperties();
connections();
}
void MainWindow::setupWindowLists()
{
windowComboBox->addItem(tr("Upload song"));
windowComboBox->addItem(tr("Download song"));
windowComboBox->addItem(tr("Display all songs"));
windowComboBox->addItem(tr("Display songs"));
}
void MainWindow::exitApplication()
{
exit(0);
}
void MainWindow::displaySoftwareInformation()
{
aboutWindow->show();
}
void MainWindow::setCurrentIndex(int index)
{
cout<<"index "<<index<<endl;
QString qText = windowComboBox->itemText(index);
auto cnvert = Utilities::Conversions(qText);
auto convertedStr = cnvert.convertQStringToString();
cout<<"item text"<<endl;
}
void MainWindow::uploadSong()
{
uploadSongQt->setEnabled(false);
string url = urlQt->toPlainText().toUtf8().constData();
string filePath = sourceFilePathQt->toPlainText().toUtf8().constData();
cout<<"URL endpoint: "<<url<<endl;
cout<<"Music file path: "<<filePath<<endl;
UploadForm formData{url, filePath};
Upload upld{formData};
upld.uploadSong();
uploadSongQt->setEnabled(true);
}
#pragma endregion
}
-26
View File
@@ -1,26 +0,0 @@
#include "Utilities/Conversions.h"
#include <iostream>
using std::string;
using std::unique_ptr;
namespace Utilities
{
Conversions::Conversions()
{
initializeValues();
}
void Conversions::initializeValues()
{
}
template <typename T>
void Conversions::printValue(T val)
{
std::cout<<"going to print value\n";
std::cout<<val<< "\n";
}
}
+40
View File
@@ -0,0 +1,40 @@
pub fn print_help() {
let msg: String = String::from(
r#"icd [Action] [flag]
Actions
download
upload-meta
retrieve
delete
Flags
Required for all actions
-u username
-p password
-h api host
-ha auth api host
Required for upload with metadata
-s path of song
-t track number
-m metadata filepath
-ca coverart filepath
-scma directory where songs, metadata, and cover art exists and will be uploaded (Optional)
Required for download
-b song id
-d path to download song (Optional)
Required for retrieving records
-rt retrieve type (songs is only accepted)
Required for deleting a song
-D song id
Print version
-v"#,
);
println!("{msg}");
}
+153
View File
@@ -0,0 +1,153 @@
mod help;
mod managers;
mod models;
mod parsers;
mod syncers;
mod utilities;
mod version;
use std::env;
#[tokio::main]
async fn main() {
let args: Vec<String> = env::args().collect();
let args_len = args.len() as i32;
if args_len == 1 {
help::print_help();
utilities::checks::exit_program(-1);
}
let mut act_mgr = managers::action_managers::ActionManager::default();
act_mgr.set_params(&args);
act_mgr.initialize();
let chosen_act = act_mgr.retrieve_icarus_action();
for act in &chosen_act.flags {
if act.flag == "-v" {
version::print_version();
utilities::checks::exit_program(-1);
}
}
chosen_act.print_action_and_flags();
let mut cmt_mgr = managers::commit_manager::CommitManager {
ica_action: chosen_act,
..Default::default()
};
cmt_mgr.commit_action().await;
}
#[cfg(test)]
mod tests {
use crate::managers;
#[test]
fn test_song_prep_upload() {
let meta_path = String::from("tests/sample2_tracks/album.json");
if !std::path::Path::new(&meta_path).exists() {
assert!(false, "File does not exists: {meta_path:?}");
}
match icarus_models::album::collection::parse_album(&meta_path) {
Ok(album) => {
for track in 1..3 {
let directory_path = std::path::Path::new(&meta_path);
let directory: String = directory_path.parent().unwrap().display().to_string();
let filename: String = if track < 10 {
String::from("track0")
+ &track.to_string()
+ icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
} else {
String::from("track")
+ &track.to_string()
+ icarus_models::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(_) => {
print!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
},
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
}
#[test]
fn test_minimum_action_and_args() {
let args: Vec<String> = vec![
"icarus-dm".to_string(),
"download".to_string(),
"-u".to_string(),
"jamborie".to_string(),
"-p".to_string(),
"somethingsecr3t!".to_string(),
"-h".to_string(),
"https://music-server.com".to_string(),
];
let arg_count = args.len() as i32;
let minimum_arg_count = 8;
assert!(arg_count >= minimum_arg_count);
let mut act_mgr = managers::action_managers::ActionManager {
action: String::new(),
flags: Vec::new(),
params: args,
param_count: arg_count,
};
act_mgr.initialize();
let ica = act_mgr.retrieve_icarus_action();
let action = &ica.action;
let flags = &ica.flags;
assert!(action != "");
assert!(flags.len() > 2);
assert!(
!(action != "download"
&& action != "upload"
&& action != "retrieve"
&& action != "upload-meta")
);
let mut found_count = 0;
let mut flags_already_read = Vec::new();
for flag in flags {
if flags_already_read.contains(&flag.flag) {
continue;
}
if flag.flag == "-u" {
found_count += 1;
flags_already_read.push(flag.flag.clone());
} else if flag.flag == "-p" {
found_count += 1;
flags_already_read.push(flag.flag.clone());
} else if flag.flag == "-h" {
found_count += 1;
flags_already_read.push(flag.flag.clone());
}
}
let all_flags_found = found_count == 3;
assert_eq!(found_count, 3, "Three flags are required: -u, -p, -h");
assert!(all_flags_found, "All flags have not been found");
}
}
+164
View File
@@ -0,0 +1,164 @@
use crate::{models, utilities};
#[derive(Debug)]
pub struct ActionManager {
pub action: String,
pub flags: Vec<models::flags::Flags>,
pub params: Vec<String>,
pub param_count: i32,
}
impl Default for ActionManager {
fn default() -> Self {
ActionManager {
action: String::new(),
flags: Vec::new(),
params: Vec::new(),
param_count: -1,
}
}
}
impl ActionManager {
pub fn retrieve_icarus_action(&self) -> models::icarus_action::IcarusAction {
models::icarus_action::IcarusAction {
flags: self.flags.clone(),
action: String::from(&self.action),
}
}
fn supported_flags(&self) -> Vec<String> {
vec![
String::from("-u"),
String::from("-p"),
String::from("-t"),
String::from("-h"),
String::from("-ha"),
String::from("-s"),
String::from("-sd"),
String::from("-sr"),
String::from("-d"),
String::from("-D"),
String::from("-b"),
String::from("-rt"),
String::from("-nc"),
String::from("-m"),
String::from("-ca"),
String::from("-smca"),
String::from("-t"),
String::from("-v"),
]
}
pub fn initialize(&mut self) {
self.validate_flags();
self.validate_action();
self.action = self.action.to_lowercase();
}
pub fn set_params(&mut self, args: &[String]) {
self.params = args.to_owned();
self.param_count = self.params.len() as i32;
}
fn validate_flags(&mut self) {
let flag_vals = self.parsed_flags();
let mut i = 0;
while i < flag_vals.len() {
let flag = &flag_vals[i];
let mut flg = models::flags::Flags::default();
if self.is_valid_flag(flag) {
if self.does_flag_have_value(flag) {
flg.flag = String::from(flag);
flg.value = String::from(&flag_vals[i + 1]);
i += 1;
} else {
flg.flag = String::from(flag);
}
} else {
utilities::checks::exit_program(-1);
}
self.flags.push(flg);
i += 1;
}
}
fn validate_action(&mut self) {
if self.params.len() >= 2 {
let act = &self.params[1];
self.action = String::from(act);
}
}
fn is_valid_flag(&self, flag: &String) -> bool {
let flags = self.supported_flags();
let mut found: bool = false;
for flg in &flags {
if flg == flag {
found = true;
break;
}
}
found
}
fn does_flag_have_value(&self, flag: &String) -> bool {
let flags_tmp = self.parsed_flags();
let mut i_found: i32 = -1;
for (i, item) in flags_tmp.iter().enumerate() {
let flg = &item;
if *flg == flag {
i_found = i as i32;
break;
}
}
if i_found >= 0 {
(i_found + 1) < flags_tmp.len().try_into().unwrap()
} else {
false
}
}
fn _print_action(&self) {
if self.action.is_empty() {
println!("Action is empty");
} else {
println!("Action is {}", self.action);
}
}
fn _print_flags(&self) {
println!("Printing flags...");
for flag in &self.flags {
println!("Flag {}", flag.flag);
println!("Value {}", flag.value);
}
}
fn parsed_flags(&self) -> Vec<String> {
let mut parsed: Vec<String> = Vec::new();
if self.param_count <= 2 {
let flag = String::from(&self.params[1]);
if flag == "-v" {
parsed.push(flag);
}
} else {
for i in 2..self.params.len() {
let flag = String::from(&self.params[i]);
parsed.push(flag);
}
}
parsed
}
}
+640
View File
@@ -0,0 +1,640 @@
use std::collections::HashMap;
use std::default::Default;
use std::fs::read_dir;
use std::io::Result;
use std::str::FromStr;
use crate::managers;
use crate::models::{self};
use crate::parsers;
use crate::syncers;
use crate::utilities;
#[derive(Debug, Default)]
pub struct CommitManager {
pub ica_action: models::icarus_action::IcarusAction,
pub api: models::api::Api,
pub auth_api: models::api::Api,
}
#[derive(Clone, Debug)]
enum ActionValues {
DeleteAct,
DownloadAct,
RetrieveAct,
UploadSongWithMetadata,
None,
}
#[derive(Clone, Debug)]
enum En {
ImageFile,
SongFile,
MetadataFile,
Other,
}
#[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 fn retrieve_song(
album: &icarus_models::album::collection::Album,
track: i32,
disc: i32,
directory: &str,
filename: &str,
) -> Result<icarus_models::song::Song> {
let mut found = false;
let mut song = icarus_models::song::Song::default();
for song_i in &album.tracks {
if song_i.track == track && song_i.disc == disc {
let track = song_i.clone();
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.disc = track.disc;
song.disc_count = album.disc_count;
song.duration = track.duration as i32;
song.genre = album.genre.clone();
song.title = track.title.clone();
song.year = album.year;
song.track = track.track;
song.track_count = album.track_count;
song.directory = directory.to_owned();
song.filename = filename.to_owned();
found = true;
break;
}
}
if found {
Ok(song)
} else {
Err(std::io::Error::other("Song not found"))
}
}
impl CommitManager {
pub async fn commit_action(&mut self) {
self.init_api().await;
let action = &self.ica_action.action;
println!("Committing {action} action");
let mapped_actions = &self.map_actions();
let token = self.parse_token(&self.auth_api).await;
match self.find_mapped_action(mapped_actions, action) {
ActionValues::DeleteAct => self.delete_song(&token).await,
ActionValues::DownloadAct => self.download_song(&token).await,
ActionValues::RetrieveAct => self.retrieve_object(&token).await,
ActionValues::UploadSongWithMetadata => self.upload_song_with_metadata(&token).await,
_ => {
println!("Nothing good here");
}
}
}
async fn init_api(&mut self) {
let mut prsr = parsers::api_parser::APIParser {
apis: vec![models::api::Api::default(), models::api::Api::default()],
ica_act: self.ica_action.clone(),
};
prsr.parse_api(parsers::api_parser::APIType::Main);
prsr.parse_api(parsers::api_parser::APIType::Auth);
self.api = prsr.retrieve_api(parsers::api_parser::APIType::Main);
self.auth_api = prsr.retrieve_api(parsers::api_parser::APIType::Auth);
}
fn find_mapped_action(
&self,
actions: &HashMap<String, ActionValues>,
action: &String,
) -> ActionValues {
for (key, act) in actions {
if key == action {
return act.clone();
}
}
ActionValues::None
}
fn map_actions(&self) -> HashMap<String, ActionValues> {
HashMap::from([
("download".to_string(), ActionValues::DownloadAct),
("download".to_string(), ActionValues::DownloadAct),
(
"upload-meta".to_string(),
ActionValues::UploadSongWithMetadata,
),
("retrieve".to_string(), ActionValues::RetrieveAct),
("delete".to_string(), ActionValues::DeleteAct),
])
}
async fn delete_song(&self, token: &icarus_models::token::AccessToken) {
println!("Deleting song");
let mut del = syncers::delete::Delete {
api: self.api.clone(),
};
let mut song = icarus_models::song::Song::default();
for arg in &self.ica_action.flags {
let flag = &arg.flag;
let value = &arg.value;
if flag == "-D" {
song.id = uuid::Uuid::from_str(value.as_str()).unwrap();
}
}
match del.delete_song(token, &song).await {
Ok((song, _coverart)) => {
println!("Song (Id {:?}) has been successfully deleted", song.id);
}
Err(er) => {
println!("Error {er:?}");
}
}
}
async fn download_song(&self, token: &icarus_models::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 {
id: uuid::Uuid::from_str(dwn.as_str()).unwrap(),
..Default::default()
};
match dwn_loader.download_song(token, &song).await {
Ok(o) => {
println!("Song downloaded");
song.data = o.as_bytes().to_vec();
song.directory = String::from(".");
song.filename = match icarus_models::song::generate_filename(
icarus_models::types::MusicType::FlacExtension,
true,
) {
Ok(filename) => filename,
Err(err) => {
eprintln!("Error generating song filename: {err:?}");
utilities::checks::exit_program(-3);
return;
}
};
match song.save_to_filesystem() {
Ok(_) => {
println!("Song saved");
}
Err(err) => {
eprintln!("Error saving song: {err:?}");
}
}
}
Err(er) => {
println!("Error {er:?}");
match er {
syncers::download::MyError::Request(error) => {
println!("Error: {error:?}");
}
syncers::download::MyError::Other(ss) => {
println!("Error: {ss:?}");
}
}
}
}
}
async fn retrieve_object(&self, token: &icarus_models::token::AccessToken) {
println!("Retrieving song");
let rt = self.ica_action.retrieve_flag_value(&String::from("-rt"));
if rt != "songs" {
panic!("Unsupported -rt: {rt}");
}
let mut repo = syncers::retrieve_records::RetrieveRecords {
api: self.api.clone(),
};
match repo.get_all_songs(token).await {
Ok(o) => {
println!("Songs");
println!("=====");
for song in o {
println!("Title: {:?}", song.title);
println!("Artist: {:?}", song.artist);
println!("Album: {:?}", song.album);
println!("Year: {:?}", song.year);
}
}
Err(er) => {
println!("Error: {er:?}");
}
}
}
async fn parse_token(&self, api: &models::api::Api) -> icarus_models::token::AccessToken {
println!("Fetching token");
let mut usr_mgr: managers::user_manager::UserManager =
managers::user_manager::UserManager {
user: icarus_models::user::User::default(),
ica_action: self.ica_action.clone(),
};
usr_mgr.parse_user_from_actions();
let usr = usr_mgr.retrieve_user();
let mut tok_mgr = managers::token_manager::TokenManager {
user: usr,
api: api.clone(),
};
tok_mgr.init();
let token = tok_mgr.request_token().await;
token.unwrap()
}
async fn upload_song_with_metadata(&mut self, token: &icarus_models::token::AccessToken) {
println!("Uplodaring song with metadara");
let songpath = self.ica_action.retrieve_flag_value(&String::from("-s"));
let metadata_path = self.ica_action.retrieve_flag_value(&String::from("-m"));
let coverpath = self.ica_action.retrieve_flag_value(&String::from("-ca"));
let track_id = self.ica_action.retrieve_flag_value(&String::from("-t"));
let single_target = !songpath.is_empty()
&& !metadata_path.is_empty()
&& !coverpath.is_empty()
&& !track_id.is_empty();
let uni = self.ica_action.retrieve_flag_value(&String::from("-smca"));
let multitarget = !uni.is_empty();
if single_target && multitarget {
println!("Cannot upload from source and directory");
panic!("What??");
}
if single_target {
println!("Song path: {songpath}");
println!("Track ID: {track_id}");
println!("metadata path: {metadata_path}");
println!("cover art path: {coverpath}");
let _ = self
.sing_target_upload(&songpath, &track_id, &metadata_path, &coverpath, token)
.await;
} else if multitarget {
let _ = self.multi_target_upload(&uni, token).await;
} else {
eprintln!("Single or Multi target has not been chosen");
}
}
/// Only uploading one song
async fn sing_target_upload(
&mut self,
songpath: &String,
track_id: &str,
meta_path: &String,
cover_path: &str,
token: &icarus_models::token::AccessToken,
) -> Result<()> {
let song_file = std::path::Path::new(&songpath);
if !song_file.exists() {
println!("Song file does not exist");
panic!("Error");
}
let pa = std::path::Path::new(&cover_path);
let mut cover_art = icarus_models::coverart::CoverArt {
directory: String::from(pa.parent().unwrap().to_str().unwrap()),
filename: String::from(pa.file_name().unwrap().to_str().unwrap()),
..Default::default()
};
let file_name = std::ffi::OsString::from(&song_file.file_name().unwrap());
match self.find_file_extension(&file_name) {
En::SongFile => match utilities::string::o_to_string(&file_name) {
Ok(s) => {
println!("file name: {file_name:?}");
match icarus_models::album::collection::parse_album(meta_path) {
Ok(album) => {
let filename = s.clone();
let directory = song_file.parent().unwrap().display().to_string();
let trck = i32::from_str(track_id).unwrap();
let mut s =
retrieve_song(&album, trck, 1, &directory, &filename).unwrap();
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();
let members = UploadSongMembers {
song: s,
coverart: cover_art,
token: token.clone(),
album,
};
match self.upload_song_process(&members).await {
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
Err(err) => {
println!("Error: {err:?}");
Err(std::io::Error::other(err.to_string()))
}
}
}
Err(er) => {
println!("Error: {er:?}");
Err(std::io::Error::other(er.to_string()))
}
},
_ => Err(std::io::Error::other("No sutitable file found".to_owned())),
}
}
/// Upload song to the queue to get processed
async fn upload_song_process(&self, data: &UploadSongMembers) -> Result<()> {
let mut up = syncers::upload::Upload::default();
let host = self.ica_action.retrieve_flag_value(&String::from("-h"));
up.set_api(&host);
let token = &data.token;
let song = &data.song;
let album = &data.album;
let coverart = &data.coverart;
println!("Queueing song");
let queued_song_id = match up.queue_song(token, song).await {
Ok(id) => id,
Err(err) => {
return Err(std::io::Error::other(err.to_string()));
}
};
println!("Queued song Id: {queued_song_id:?}");
match up.link_user_to_queued_song(token, &queued_song_id).await {
Ok(_) => {
println!("Queued song linked to user");
}
Err(err) => {
return Err(std::io::Error::other(err.to_string()));
}
}
let queued_metadata_id = match up.queue_metadata(token, album, song, &queued_song_id).await
{
Ok(id) => id,
Err(err) => {
return Err(std::io::Error::other(err.to_string()));
}
};
println!("Queued metadata Id: {queued_metadata_id:?}");
match up.queue_coverart(token, coverart).await {
Ok(id) => match up
.link_queued_song_to_queued_coverart(token, &queued_song_id, &id)
.await
{
Ok(_) => match up
.update_queued_song_status(token, &queued_song_id, "ready")
.await
{
Ok(_) => {
println!("Queued coverart Id: {id:?}");
println!("Linked queued song to queued coverart");
println!("Queued status updated");
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())),
}
}
fn get_songs(
&self,
metadata_path: &String,
source_directory: &str,
) -> Result<Vec<icarus_models::song::Song>> {
match icarus_models::album::collection::parse_album(metadata_path) {
Ok(albums) => {
let mut songs: Vec<icarus_models::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
} else {
"track".to_owned()
+ &track.track.to_string()
+ icarus_models::constants::file_extensions::audio::DEFAULTMUSICEXTENSION
};
songs.push(icarus_models::song::Song {
title: track.title.clone(),
artist: track.artist.clone(),
disc: track.disc,
track: track.track,
duration: track.duration as i32,
year: albums.year,
album_artist: albums.artist.clone(),
genre: albums.genre.clone(),
disc_count: albums.disc_count,
track_count: albums.track_count,
album: albums.title.clone(),
audio_type: String::from("FLAC"),
directory: source_directory.to_owned(),
filename: song_filename,
..Default::default()
});
}
Ok(songs)
}
Err(_) => Err(std::io::Error::other("Songs not retrieved")),
}
}
async fn multi_target_upload(
&mut self,
sourcepath: &String,
token: &icarus_models::token::AccessToken,
) -> std::io::Result<()> {
let directory_path = std::path::Path::new(&sourcepath);
if !directory_path.exists() {
panic!("Directory does not exist");
}
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(
&coverart_directory,
&coverart_filename,
);
let metadatapath = self.get_metadata_path(sourcepath).unwrap_or_default();
let mut up = syncers::upload::Upload::default();
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();
match self.get_songs(&metadatapath, sourcepath) {
Ok(sngs) => match icarus_models::album::collection::parse_album(&metadatapath) {
Ok(album) => {
for song in sngs {
let members = UploadSongMembers {
song,
coverart: cover_art.clone(),
token: token.clone(),
album: album.clone(),
};
match self.upload_song_process(&members).await {
Ok(o) => {
println!("Response: {o:?}");
}
Err(err) => {
eprintln!("Error: {err:?}");
return Err(err);
}
}
}
Ok(())
}
Err(err) => {
eprintln!("Error: {err:?}");
Err(std::io::Error::other(err.to_string()))
}
},
Err(error) => {
eprintln!("Error: {error:?}");
Err(std::io::Error::other(error.to_string()))
}
}
}
fn get_coverart_dir_and_filename(&self, directory: &str) -> Result<(String, String)> {
for entry in read_dir(std::path::Path::new(directory))? {
let entry = entry?;
let file_type = entry.file_type();
let file_name = entry.file_name();
println!("file type: {file_type:?}");
println!("file name: {file_name:?}");
if let En::ImageFile = self.find_file_extension(&file_name) {
let fname = utilities::string::o_to_string(&file_name);
return Ok((directory.to_string(), fname.unwrap()));
}
}
Err(std::io::Error::other(
"CoverArt directory and filename not found",
))
}
fn find_file_extension(&self, file_name: &std::ffi::OsString) -> En {
let file_name_str = Some(file_name.clone().into_string());
match file_name_str {
Some(string) => {
let a = string.unwrap();
let split = a.split(".");
let mut last_index = 0;
for _ in split.clone() {
last_index += 1;
}
let mut extension = String::new();
let mut index = 1;
for word in split {
if index == last_index {
extension = word.to_string();
break;
}
index += 1;
}
if extension == icarus_models::constants::file_extensions::audio::WAVEXTENSION[1..]
|| extension
== icarus_models::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..]
|| extension == "jpeg"
|| extension == "png"
{
return En::ImageFile;
}
}
_ => {
return En::Other;
}
}
En::Other
}
fn get_metadata_path(&self, directory_path: &String) -> Result<String> {
for entry in read_dir(std::path::Path::new(directory_path))? {
let entry = entry?;
let file_type = entry.file_type();
let file_name = entry.file_name();
println!("file type: {file_type:?}");
println!("file name: {file_name:?}");
if let En::MetadataFile = self.find_file_extension(&file_name) {
let directory_part = directory_path.clone();
let fname = utilities::string::o_to_string(&file_name);
return Ok(format!("{}/{}", directory_part, &fname.unwrap()));
}
}
Err(std::io::Error::other("Metadata path not found"))
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod action_managers;
pub mod commit_manager;
pub mod token_manager;
pub mod user_manager;
+86
View File
@@ -0,0 +1,86 @@
use std::default::Default;
use crate::models;
mod response {
pub mod token {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>,
}
}
}
pub struct TokenManager {
pub user: icarus_models::user::User,
pub api: models::api::Api,
}
impl Default for TokenManager {
fn default() -> Self {
let mut token = TokenManager {
user: icarus_models::user::User::default(),
api: models::api::Api::default(),
};
token.init();
token
}
}
impl TokenManager {
pub async fn request_token(&self) -> Result<icarus_models::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 {
user_id: uuid::Uuid::nil(),
username: String::new(),
token: String::new(),
token_type: String::new(),
expiration: -1,
message: String::new(),
};
let client = reqwest::Client::new();
let response = client.post(&url).json(&self.user).send().await.unwrap();
match response.status() {
reqwest::StatusCode::OK => {
// on success, parse our JSON to an APIResponse
match response.json::<response::token::Response>().await {
Ok(response) => {
let login_result = &response.data[0];
token.user_id = login_result.id;
token.username = login_result.username.clone();
token.token = login_result.token.clone();
token.token_type = login_result.token_type.clone();
token.expiration = login_result.expiration;
token.message = response.message;
Ok(token)
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
reqwest::StatusCode::UNAUTHORIZED => {
Err(std::io::Error::other("Need to grab a new token"))
}
other => {
panic!("Uh oh! Something unexpected happened: {other:?}");
}
}
}
pub fn init(&mut self) {
let api = &mut self.api;
api.version = String::from(crate::parsers::api_parser::API_VERSION);
api.endpoint = format!("api/{}/login", api.version);
}
pub fn retrieve_url(&self) -> String {
format!("{}{}", self.api.url, self.api.endpoint)
}
}
+37
View File
@@ -0,0 +1,37 @@
use std::default::Default;
use crate::models::{self};
#[derive(Debug, Default)]
pub struct UserManager {
pub user: icarus_models::user::User,
pub ica_action: models::icarus_action::IcarusAction,
}
impl UserManager {
pub fn retrieve_user(&self) -> icarus_models::user::User {
self.user.clone()
}
pub fn parse_user_from_actions(&mut self) {
let args = &self.ica_action.flags;
// Quit the loop when two are found
let mut amt: i32 = 0;
for arg in args {
let flag = &arg.flag;
if flag == "-u" {
self.user.username = String::from(&arg.value);
amt += 1;
} else if flag == "-p" {
self.user.password = String::from(&arg.value);
amt += 1;
}
if amt == 2 {
break;
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
use std::default::Default;
#[derive(Clone, Debug, Default)]
pub struct Api {
pub url: String,
pub endpoint: String,
pub version: String,
}
+7
View File
@@ -0,0 +1,7 @@
use std::default::Default;
#[derive(Clone, Debug, Default)]
pub struct Flags {
pub flag: String,
pub value: String,
}
+33
View File
@@ -0,0 +1,33 @@
use std::default::Default;
use crate::models;
#[derive(Clone, Debug, Default)]
pub struct IcarusAction {
pub action: String,
pub flags: Vec<models::flags::Flags>,
}
impl IcarusAction {
pub fn retrieve_flag_value(&self, flag: &String) -> String {
let mut val: String = String::new();
for f in self.flags.iter() {
if f.flag == *flag {
val = f.value.clone();
break;
}
}
val
}
pub fn print_action_and_flags(&self) {
println!("Action: {}", self.action);
println!("Flag count: {}", self.flags.len());
for flag in self.flags.iter() {
println!("flag {} value {}", flag.flag, flag.value);
}
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod api;
pub mod flags;
pub mod icarus_action;
+60
View File
@@ -0,0 +1,60 @@
use crate::models;
#[derive(Clone, Debug)]
pub struct APIParser {
pub apis: Vec<models::api::Api>,
pub ica_act: models::icarus_action::IcarusAction,
}
pub const API_VERSION: &str = "v2";
pub enum APIType {
Main,
Auth,
}
impl APIParser {
pub fn retrieve_api(&self, api_type: APIType) -> models::api::Api {
match api_type {
APIType::Main => self.apis[0].clone(),
APIType::Auth => self.apis[1].clone(),
}
}
pub fn parse_api(&mut self, api_type: APIType) {
let flags = self.ica_act.flags.clone();
println!("Parsing api");
for elem in flags {
let arg = elem.flag;
let value = elem.value;
match api_type {
APIType::Main => {
if arg == "-h" {
if value.chars().nth(value.len() - 1) == Some('/') {
self.apis[0].url = value;
} else {
self.apis[0].url = format!("{value}/");
}
break;
}
}
APIType::Auth => {
if arg == "-ha" {
if value.chars().nth(value.len() - 1) == Some('/') {
self.apis[1].url = value;
} else {
self.apis[1].url = format!("{value}/");
}
break;
}
}
}
}
for api in &mut self.apis {
api.version = String::from(API_VERSION);
}
}
}
+1
View File
@@ -0,0 +1 @@
pub mod api_parser;
+26
View File
@@ -0,0 +1,26 @@
use crate::models;
pub fn retrieve_url(api: &models::api::Api, with_id: bool, id: &uuid::Uuid) -> String {
if with_id {
retrieve_url_with_id(api, id)
} else {
retrieve_url_reg(api)
}
}
fn retrieve_url_reg(api: &models::api::Api) -> String {
format!("{}api/{}/{}/", api.url, api.version, api.endpoint)
}
fn retrieve_url_with_id(api: &models::api::Api, id: &uuid::Uuid) -> String {
format!("{}api/{}/{}/{}", api.url, api.version, api.endpoint, id)
}
pub async fn auth_header(
token: &icarus_models::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)),
Err(err) => Err(err),
}
}
+64
View File
@@ -0,0 +1,64 @@
use std::default::Default;
use reqwest;
use crate::models;
#[derive(Clone, Debug, Default)]
pub struct Delete {
pub api: models::api::Api,
}
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,
}
#[derive(Debug, serde::Deserialize)]
pub struct Response {
pub message: String,
pub data: Vec<SongAndCoverArt>,
}
}
}
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>
{
self.api.endpoint = "api/v2/song".to_owned();
let url = format!("{}{}/{}", self.api.url, self.api.endpoint, song.id);
println!("Url: {url:?}");
let client = reqwest::Client::builder().build().unwrap();
let access_token = token.bearer_token();
let response = client
.delete(&url)
.header(reqwest::header::AUTHORIZATION, access_token)
.send()
.await
.unwrap();
match response.status() {
reqwest::StatusCode::OK => {
println!("Success!");
match response.json::<response::delete_song::Response>().await {
Ok(resp) => {
println!("Response message: {:?}", resp.message);
let data = &resp.data[0];
Ok((data.song.clone(), data.coverart.clone()))
}
Err(er) => Err(std::io::Error::other(er.to_string())),
}
}
other => Err(std::io::Error::other(other.to_string())),
}
}
}
+50
View File
@@ -0,0 +1,50 @@
use std::default::Default;
use crate::models;
use crate::syncers;
#[derive(Default)]
pub struct Download {
pub api: models::api::Api,
}
#[derive(Debug)]
pub enum MyError {
Request(reqwest::Error),
Other(String),
}
impl Download {
pub async fn download_song(
&mut self,
token: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
) -> Result<String, MyError> {
self.api.endpoint = String::from("song/download");
let url = syncers::common::retrieve_url(&self.api, true, &song.id);
let access_token = token.bearer_token();
println!("Url: {url:?}");
let client = reqwest::Client::builder().build().unwrap();
match client
.get(&url)
.header(reqwest::header::AUTHORIZATION, &access_token)
.send()
.await
{
Ok(rep) => match rep.status() {
reqwest::StatusCode::OK => match rep.text().await {
Ok(e) => Ok(e),
Err(er) => Err(MyError::Other(er.to_string())),
},
reqwest::StatusCode::UNAUTHORIZED => {
Err(MyError::Other(String::from("Need to grab a new token")))
}
other => Err(MyError::Other(other.to_string())),
},
Err(er) => Err(MyError::Request(er)),
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod common;
pub mod delete;
pub mod download;
pub mod retrieve_records;
pub mod upload;
+57
View File
@@ -0,0 +1,57 @@
use std::default::Default;
use std::io::Error;
use crate::models;
#[derive(Default)]
pub struct RetrieveRecords {
pub api: models::api::Api,
}
mod response {
pub mod get_all_songs {
#[derive(Debug, serde::Deserialize)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::song::Song>,
}
}
}
impl RetrieveRecords {
pub async fn get_all_songs(
&mut self,
token: &icarus_models::token::AccessToken,
) -> Result<Vec<icarus_models::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();
println!("url: {url:?}");
let client = reqwest::Client::builder().build().unwrap();
let response = client
.get(&url)
.header(reqwest::header::AUTHORIZATION, access_token)
.send()
.await
.unwrap();
match response.status() {
reqwest::StatusCode::OK => {
// on success, parse our JSON to an API Response
match response.json::<response::get_all_songs::Response>().await {
Ok(parsed) => {
println!("Response message: {:?}", parsed.message);
Ok(parsed.data)
}
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
reqwest::StatusCode::UNAUTHORIZED => {
Err(std::io::Error::other("Need to grab a new token"))
}
other => Err(std::io::Error::other(other.to_string())),
}
}
}
+304
View File
@@ -0,0 +1,304 @@
use std::default::Default;
use http::HeaderValue;
use reqwest;
use crate::models;
use crate::syncers;
#[derive(Default)]
pub struct Upload {
pub api: models::api::Api,
}
mod response {
pub mod queue_song {
#[derive(Debug, serde::Deserialize)]
pub struct Response {
pub message: String,
pub data: Vec<uuid::Uuid>,
}
}
pub mod queue_metadata {
#[derive(Debug, serde::Deserialize)]
pub struct Response {
pub message: String,
pub data: Vec<uuid::Uuid>,
}
}
pub mod queue_coverart {
#[derive(Debug, serde::Deserialize)]
pub struct Response {
pub message: String,
pub data: Vec<uuid::Uuid>,
}
}
}
impl Upload {
pub async fn queue_song(
&self,
token: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
) -> Result<uuid::Uuid, reqwest::Error> {
let songpath = song.song_path().unwrap();
let file = tokio::fs::File::open(&songpath).await.unwrap();
let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new());
let file_body = reqwest::Body::wrap_stream(stream);
let form = reqwest::multipart::Form::new().part(
"file",
reqwest::multipart::Part::stream(file_body)
.file_name(song.filename.clone())
.mime_str("application/octet-stream")
.unwrap(),
);
let endpoint = String::from("api/v2/song/queue");
let url = format!("{}/{endpoint}", self.api.url);
println!("Url: {url:?}");
let mut headers = reqwest::header::HeaderMap::new();
let (auth, auth_val) = syncers::common::auth_header(token).await.unwrap();
headers.insert(auth, auth_val);
headers.insert("Accept", HeaderValue::from_str("*/*").unwrap());
headers.insert("Connection", HeaderValue::from_str("keep-alive").unwrap());
headers.insert("Cache-Control", HeaderValue::from_str("no-cache").unwrap());
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300))
.pool_idle_timeout(std::time::Duration::from_secs(30))
.build()
.unwrap();
match client
.post(url)
.headers(headers)
.multipart(form)
.send()
.await
{
Ok(response) => match response.json::<response::queue_song::Response>().await {
Ok(resp) => {
println!("Message: {:?}", resp.message);
Ok(resp.data[0])
}
Err(err) => Err(err),
},
Err(err) => Err(err),
}
}
pub async fn link_user_to_queued_song(
&self,
token: &icarus_models::token::AccessToken,
queued_song_id: &uuid::Uuid,
) -> Result<(), reqwest::Error> {
let endpoint = String::from("api/v2/song/queue/link");
let url = format!("{}/{endpoint}", self.api.url);
let mut headers = reqwest::header::HeaderMap::new();
let (auth, auth_val) = syncers::common::auth_header(token).await.unwrap();
headers.insert(auth, auth_val);
let payload = serde_json::json!({
"song_queue_id": queued_song_id,
"user_id": token.user_id
});
let client = reqwest::Client::builder().build().unwrap();
match client
.patch(url)
.headers(headers)
.json(&payload)
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
pub async fn queue_metadata(
&self,
token: &icarus_models::token::AccessToken,
album: &icarus_models::album::collection::Album,
song: &icarus_models::song::Song,
queued_song_id: &uuid::Uuid,
) -> Result<uuid::Uuid, reqwest::Error> {
let endpoint = String::from("api/v2/song/metadata/queue");
let url = format!("{}/{endpoint}", self.api.url);
let mut headers = reqwest::header::HeaderMap::new();
let (auth, auth_val) = syncers::common::auth_header(token).await.unwrap();
headers.insert(auth, auth_val);
let payload = serde_json::json!({
"song_queue_id": queued_song_id,
"title": song.title,
"artist": song.artist,
"album_artist": album.artist,
"album": album.title,
"genre": song.genre,
"track": song.track,
"track_count": album.track_count,
"disc": song.disc,
"disc_count": album.disc_count,
"year": album.year,
"duration": song.duration,
});
let client = reqwest::Client::builder().build().unwrap();
match client
.post(url)
.headers(headers)
.json(&payload)
.send()
.await
{
Ok(response) => match response.json::<response::queue_metadata::Response>().await {
Ok(resp) => {
println!("Message: {:?}", resp.message);
Ok(resp.data[0])
}
Err(err) => Err(err),
},
Err(err) => Err(err),
}
}
pub async fn queue_coverart(
&self,
token: &icarus_models::token::AccessToken,
coverart: &icarus_models::coverart::CoverArt,
) -> Result<uuid::Uuid, reqwest::Error> {
let coverartpath = coverart.get_path().unwrap();
let file = tokio::fs::File::open(&coverartpath).await.unwrap();
let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new());
let file_body = reqwest::Body::wrap_stream(stream);
let file_name = std::path::Path::new(&coverartpath)
.file_name()
.and_then(|name| name.to_str())
.map(String::from)
.unwrap_or("file".to_string());
let form = reqwest::multipart::Form::new().part(
"file",
reqwest::multipart::Part::stream(file_body)
.file_name(file_name)
.mime_str("application/octet-stream")
.unwrap(),
);
let endpoint = String::from("api/v2/coverart/queue");
let url = format!("{}/{endpoint}", self.api.url);
println!("Url: {url:?}");
let mut headers = reqwest::header::HeaderMap::new();
let (auth, auth_val) = syncers::common::auth_header(token).await.unwrap();
headers.insert(auth, auth_val);
headers.insert("Accept", HeaderValue::from_str("*/*").unwrap());
headers.insert("Connection", HeaderValue::from_str("keep-alive").unwrap());
headers.insert("Cache-Control", HeaderValue::from_str("no-cache").unwrap());
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300))
.pool_idle_timeout(std::time::Duration::from_secs(30))
.build()
.unwrap();
match client
.post(url)
.headers(headers)
.multipart(form)
.send()
.await
{
Ok(response) => match response.json::<response::queue_coverart::Response>().await {
Ok(resp) => {
println!("Message: {:?}", resp.message);
Ok(resp.data[0])
}
Err(err) => Err(err),
},
Err(err) => Err(err),
}
}
pub async fn link_queued_song_to_queued_coverart(
&self,
token: &icarus_models::token::AccessToken,
queued_song_id: &uuid::Uuid,
queued_coverart_id: &uuid::Uuid,
) -> Result<(), reqwest::Error> {
let endpoint = String::from("api/v2/coverart/queue/link");
let url = format!("{}/{endpoint}", self.api.url);
let mut headers = reqwest::header::HeaderMap::new();
let (auth, auth_val) = syncers::common::auth_header(token).await.unwrap();
headers.insert(auth, auth_val);
let payload = serde_json::json!({
"song_queue_id": queued_song_id,
"coverart_id": queued_coverart_id
});
let client = reqwest::Client::builder().build().unwrap();
match client
.patch(url)
.headers(headers)
.json(&payload)
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
pub async fn update_queued_song_status(
&self,
token: &icarus_models::token::AccessToken,
queued_song_id: &uuid::Uuid,
status: &str,
) -> Result<(), reqwest::Error> {
let endpoint = String::from("api/v2/song/queue");
let url = format!("{}/{endpoint}", self.api.url);
let mut headers = reqwest::header::HeaderMap::new();
let (auth, auth_val) = syncers::common::auth_header(token).await.unwrap();
headers.insert(auth, auth_val);
let payload = serde_json::json!({
"id": queued_song_id,
"status": status
});
let client = reqwest::Client::builder().build().unwrap();
match client
.patch(url)
.headers(headers)
.json(&payload)
.send()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
pub fn set_api(&mut self, host: &str) {
let api = models::api::Api {
url: host.to_owned(),
version: String::from(crate::parsers::api_parser::API_VERSION),
endpoint: String::new(),
};
self.api = api;
}
}
+3
View File
@@ -0,0 +1,3 @@
pub fn exit_program(code: i32) {
std::process::exit(code);
}
+2
View File
@@ -0,0 +1,2 @@
pub mod checks;
pub mod string;
+12
View File
@@ -0,0 +1,12 @@
pub fn o_to_string(val: &std::ffi::OsString) -> Result<std::string::String, std::io::Error> {
match val.clone().into_string() {
Ok(value) => Ok(value),
Err(err) => match err.into_string() {
Ok(res) => Err(std::io::Error::other(res.to_string())),
Err(err) => match err.to_str() {
Some(err) => Err(std::io::Error::other(err)),
None => Err(std::io::Error::other("Undefined error")),
},
},
}
}
+6
View File
@@ -0,0 +1,6 @@
pub fn print_version() {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
println!("{name:?} {version:?}");
}
+31
View File
@@ -0,0 +1,31 @@
{
"album": "Sample Tracks 2",
"album_artist": "KD",
"genre": "Country",
"year": 2025,
"track_count": 3,
"disc_count": 1,
"tracks": [
{
"title": "Uh huh",
"artist": "KD",
"disc": 1,
"track": 1,
"duration": 32
},
{
"title": "Bold and brash",
"artist": "KD",
"disc": 1,
"track": 2,
"duration": 37
},
{
"title": "How does this work?",
"artist": "KD",
"disc": 1,
"track": 3,
"duration": 46
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.