From ee4469b385ec4be6f8a46e0a9f0f0b02e49d7311 Mon Sep 17 00:00:00 2001 From: Kun Deng Date: Sat, 15 Jun 2024 12:22:17 -0400 Subject: [PATCH] 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 --- .github/workflows/cmake.yml | 50 -- .github/workflows/rust.yml | 23 + .gitignore | 7 + .gitmodules | 3 - CMakeLists.txt | 108 ---- Cargo.toml | 18 + IcarusDownloadManager.rc | 99 ---- IcarusDownloadManager.sln | 31 -- IcarusDownloadManager.vcxproj | 165 ------ IcarusDownloadManager.vcxproj.filters | 120 ----- IcarusDownloadManager.vcxproj.user | 11 - README.md | 64 +-- include/Managers/ActionManager.h | 93 ---- include/Managers/CommitManager.h | 220 -------- include/Managers/FileManager.h | 31 -- include/Managers/TokenManager.h | 25 - include/Managers/UserManager.h | 28 - include/Models/API.h | 19 - include/Models/Flags.h | 18 - include/Models/IcarusAction.h | 53 -- include/Models/Song.h | 90 ---- include/Models/Token.h | 18 - include/Models/UploadForm.h | 18 - include/Models/User.h | 17 - include/Parsers/APIParser.h | 25 - include/Syncers/Delete.h | 25 - include/Syncers/Download.h | 33 -- include/Syncers/RetrieveRecords.h | 28 - include/Syncers/SyncerBase.h | 29 - include/Syncers/Upload.h | 55 -- include/UI/AboutWindow.h | 36 -- include/UI/CommonWindow.h | 33 -- include/UI/MainWindow.h | 84 --- include/Utilities/Checks.h | 67 --- include/Utilities/Conversions.h | 32 -- resource.h | 14 - src/Main.cpp | 79 --- src/Managers/ActionManager.cpp | 118 ---- src/Managers/CommitManager.cpp | 457 ---------------- src/Managers/FileManager.cpp | 73 --- src/Managers/TokenManager.cpp | 62 --- src/Managers/UserManager.cpp | 55 -- src/Models/Song.cpp | 22 - src/Parsers/APIParser.cpp | 53 -- src/Syncers/Delete.cpp | 64 --- src/Syncers/Download.cpp | 93 ---- src/Syncers/RetrieveRecords.cpp | 67 --- src/Syncers/Upload.cpp | 243 --------- src/UI/AboutWindow.cpp | 52 -- src/UI/MainWindow.cpp | 180 ------- src/Utilities/Conversions.cpp | 26 - src/constants/file_extensions.rs | 3 + src/constants/mod.rs | 1 + src/main.rs | 81 +++ src/managers/action_managers.rs | 221 ++++++++ src/managers/commit_manager.rs | 745 ++++++++++++++++++++++++++ src/managers/file_manager.rs | 64 +++ src/managers/mod.rs | 4 + src/managers/token_manager.rs | 73 +++ src/managers/user_manager.rs | 42 ++ src/models/api.rs | 20 + src/models/flags.rs | 18 + src/models/icarus_action.rs | 44 ++ src/models/mod.rs | 7 + src/models/song.rs | 171 ++++++ src/models/token.rs | 47 ++ src/models/upload_form.rs | 18 + src/models/user.rs | 24 + src/parsers/api_parser.rs | 34 ++ src/parsers/mod.rs | 1 + src/syncers/delete.rs | 72 +++ src/syncers/download.rs | 82 +++ src/syncers/mod.rs | 4 + src/syncers/retrieve_records.rs | 80 +++ src/syncers/upload.rs | 195 +++++++ src/utilities/checks.rs | 28 + src/utilities/mod.rs | 1 + vcpkg | 1 - vcpkg.json | 9 - 79 files changed, 2148 insertions(+), 3376 deletions(-) delete mode 100644 .github/workflows/cmake.yml create mode 100644 .github/workflows/rust.yml delete mode 100644 CMakeLists.txt create mode 100644 Cargo.toml delete mode 100644 IcarusDownloadManager.rc delete mode 100644 IcarusDownloadManager.sln delete mode 100644 IcarusDownloadManager.vcxproj delete mode 100644 IcarusDownloadManager.vcxproj.filters delete mode 100644 IcarusDownloadManager.vcxproj.user delete mode 100644 include/Managers/ActionManager.h delete mode 100644 include/Managers/CommitManager.h delete mode 100644 include/Managers/FileManager.h delete mode 100644 include/Managers/TokenManager.h delete mode 100644 include/Managers/UserManager.h delete mode 100644 include/Models/API.h delete mode 100644 include/Models/Flags.h delete mode 100644 include/Models/IcarusAction.h delete mode 100644 include/Models/Song.h delete mode 100644 include/Models/Token.h delete mode 100644 include/Models/UploadForm.h delete mode 100644 include/Models/User.h delete mode 100644 include/Parsers/APIParser.h delete mode 100644 include/Syncers/Delete.h delete mode 100644 include/Syncers/Download.h delete mode 100644 include/Syncers/RetrieveRecords.h delete mode 100644 include/Syncers/SyncerBase.h delete mode 100644 include/Syncers/Upload.h delete mode 100644 include/UI/AboutWindow.h delete mode 100644 include/UI/CommonWindow.h delete mode 100644 include/UI/MainWindow.h delete mode 100644 include/Utilities/Checks.h delete mode 100644 include/Utilities/Conversions.h delete mode 100644 resource.h delete mode 100644 src/Main.cpp delete mode 100644 src/Managers/ActionManager.cpp delete mode 100644 src/Managers/CommitManager.cpp delete mode 100644 src/Managers/FileManager.cpp delete mode 100644 src/Managers/TokenManager.cpp delete mode 100644 src/Managers/UserManager.cpp delete mode 100644 src/Models/Song.cpp delete mode 100644 src/Parsers/APIParser.cpp delete mode 100644 src/Syncers/Delete.cpp delete mode 100644 src/Syncers/Download.cpp delete mode 100644 src/Syncers/RetrieveRecords.cpp delete mode 100644 src/Syncers/Upload.cpp delete mode 100644 src/UI/AboutWindow.cpp delete mode 100644 src/UI/MainWindow.cpp delete mode 100644 src/Utilities/Conversions.cpp create mode 100644 src/constants/file_extensions.rs create mode 100644 src/constants/mod.rs create mode 100644 src/main.rs create mode 100644 src/managers/action_managers.rs create mode 100644 src/managers/commit_manager.rs create mode 100644 src/managers/file_manager.rs create mode 100644 src/managers/mod.rs create mode 100644 src/managers/token_manager.rs create mode 100644 src/managers/user_manager.rs create mode 100644 src/models/api.rs create mode 100644 src/models/flags.rs create mode 100644 src/models/icarus_action.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/song.rs create mode 100644 src/models/token.rs create mode 100644 src/models/upload_form.rs create mode 100644 src/models/user.rs create mode 100644 src/parsers/api_parser.rs create mode 100644 src/parsers/mod.rs create mode 100644 src/syncers/delete.rs create mode 100644 src/syncers/download.rs create mode 100644 src/syncers/mod.rs create mode 100644 src/syncers/retrieve_records.rs create mode 100644 src/syncers/upload.rs create mode 100644 src/utilities/checks.rs create mode 100644 src/utilities/mod.rs delete mode 160000 vcpkg delete mode 100644 vcpkg.json diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml deleted file mode 100644 index a2fcccc..0000000 --- a/.github/workflows/cmake.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: hosted-ninja_multi-vcpkg_submod-triplet-cacheoff -on: [push, workflow_dispatch] - -jobs: - job: - name: ${{ matrix.os }}-${{ github.workflow }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - include: - - os: windows-latest - triplet: x64-windows - - os: ubuntu-latest - triplet: x64-linux - - os: macos-latest - triplet: x64-osx - - env: - VCPKG_DEFAULT_TRIPLET: ${{ matrix.triplet }} - - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - - uses: lukka/get-cmake@latest - - name: dir - run: find $RUNNER_WORKSPACE - shell: bash - - - name: Setup vcpkg - uses: lukka/run-vcpkg@main - id: runvcpkg - with: - vcpkgJsonGlob: '**/cmakepresets/vcpkg.json' - - - name: List $RUNNER_WORKSPACE before build - run: find $RUNNER_WORKSPACE - shell: bash - - name: Prints output of run-vcpkg's action. - run: echo "root='${{ steps.runvcpkg.outputs.RUNVCPKG_VCPKG_ROOT_OUT }}', triplet='${{ steps.runvcpkg.outputs.RUNVCPKG_VCPKG_DEFAULT_TRIPLET_OUT }}' " - # - name: Build - # Build your program with the given configuration - # run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - - name: List $RUNNER_WORKSPACE after build - run: find $RUNNER_WORKSPACE - shell: bash diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..2bef732 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,23 @@ +name: Rust + +on: + push: + branches: [ "rust-lg" ] + pull_request: + branches: [ "rust-lg" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + diff --git a/.gitignore b/.gitignore index 59f00ad..450eff1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ /.vs/* /build /x64 + + +# Added by cargo + +/target +.vscode/ +Cargo.lock diff --git a/.gitmodules b/.gitmodules index 72d488d..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "3rdparty/vcpkg"] - path = vcpkg - url = https://github.com/microsoft/vcpkg diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 5294329..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,108 +0,0 @@ -cmake_minimum_required(VERSION 3.20.6) - -if(NOT DEFINED VCPKG_ROOT_PATH) - set(VCPKG_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/vcpkg) -endif() - -message("The vcpkg root path ${VCPKG_ROOT_PATH}") - -if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) - set(TOOLCHAIN_PATH ${VCPKG_ROOT_PATH}/scripts/buildsystems/vcpkg.cmake) - - set(CMAKE_TOOLCHAIN_FILE - "${TOOLCHAIN_PATH}" - CACHE STRING "Vcpkg toolchain file") - - message("Using default toolchain file") -endif() - - -set(SOFTWARE_DESCRIPTION - "A tool to interact with the Icarus Music streaming API") -set(SOFTWARE_VERSION - "0.3.2") - - -project(IcarusDownloadManager VERSION ${SOFTWARE_VERSION} DESCRIPTION ${SOFTWARE_DESCRIPTION} LANGUAGES CXX) - - -message(STATUS "Checking compiler flags for C++20 support.") - -# Set C++20 support flags for various compilers -include(CheckCXXCompilerFlag) - -if(WIN32) - message("Windows environment") - - if (MSVC_VERSION GREATER_EQUAL "1900") - message("Visual Studio version is at least ${vs_ver}") - - include(CheckCXXCompilerFlag) - CHECK_CXX_COMPILER_FLAG("/std:c++20" _cpp_latest_flag_supported) - if (_cpp_latest_flag_supported) - add_compile_options("/std:c++20") - endif() - - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - endif() -else() - check_cxx_compiler_flag("-std=c++20" COMPILER_SUPPORTS_CXX20) - check_cxx_compiler_flag("-std=c++0x" COMPILER_SUPPORTS_CXX0X) - - if(COMPILER_SUPPORTS_CXX20) - message(STATUS "C++20 is supported.") - - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -stdlib=libc++") - else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") - 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++20 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") - -include_directories(${IDM_INCLUDE_DIR}) - -set(USE_SYSTEM_CURL OFF) -set(BUILD_CPR_TESTS OFF) - -find_package(nlohmann_json CONFIG REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(CURL REQUIRED) -find_package(cpr CONFIG REQUIRED) - - -add_executable(icd ${SOURCES}) -target_link_libraries(icd PRIVATE nlohmann_json::nlohmann_json OpenSSL::SSL OpenSSL::Crypto CURL::libcurl cpr::cpr) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4f4458b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "icarus-dm" +version = "0.4.0" +edition = "2021" + + + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = { version = "0.3.30" } +http = { version = "1.1.0" } +reqwest = { version = "0.12.4", features = ["json", "blocking", "multipart", "stream"] } +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.115" +tokio = { version = "1.37", features = ["full"] } +tokio-util = { version = "0.7.11", features = ["codec"] } diff --git a/IcarusDownloadManager.rc b/IcarusDownloadManager.rc deleted file mode 100644 index 78870b7..0000000 --- a/IcarusDownloadManager.rc +++ /dev/null @@ -1,99 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,3,2,0 - PRODUCTVERSION 0,3,2,0 - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "FileDescription", "Tool to interact with Icarus API" - VALUE "FileVersion", "0.3.2.0" - VALUE "InternalName", "IcarusDo.exe" - VALUE "LegalCopyright", "Copyright (C) 2022" - VALUE "OriginalFilename", "IcarusDo.exe" - VALUE "ProductName", "IcarusDownloadManager" - VALUE "ProductVersion", "0.3.2.0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - diff --git a/IcarusDownloadManager.sln b/IcarusDownloadManager.sln deleted file mode 100644 index df5cd41..0000000 --- a/IcarusDownloadManager.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32630.192 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IcarusDownloadManager", "IcarusDownloadManager.vcxproj", "{12955990-643F-4A99-95E5-4752AFA179B0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {12955990-643F-4A99-95E5-4752AFA179B0}.Debug|x64.ActiveCfg = Debug|x64 - {12955990-643F-4A99-95E5-4752AFA179B0}.Debug|x64.Build.0 = Debug|x64 - {12955990-643F-4A99-95E5-4752AFA179B0}.Debug|x86.ActiveCfg = Debug|Win32 - {12955990-643F-4A99-95E5-4752AFA179B0}.Debug|x86.Build.0 = Debug|Win32 - {12955990-643F-4A99-95E5-4752AFA179B0}.Release|x64.ActiveCfg = Release|x64 - {12955990-643F-4A99-95E5-4752AFA179B0}.Release|x64.Build.0 = Release|x64 - {12955990-643F-4A99-95E5-4752AFA179B0}.Release|x86.ActiveCfg = Release|Win32 - {12955990-643F-4A99-95E5-4752AFA179B0}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {EA22B463-3EA6-4452-9AF1-D300C3BDE72D} - EndGlobalSection -EndGlobal diff --git a/IcarusDownloadManager.vcxproj b/IcarusDownloadManager.vcxproj deleted file mode 100644 index e16e870..0000000 --- a/IcarusDownloadManager.vcxproj +++ /dev/null @@ -1,165 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 17.0 - {12955990-643F-4A99-95E5-4752AFA179B0} - Win32Proj - 10.0 - - - - Application - true - v143 - - - Application - false - v143 - - - Application - true - v143 - NotSet - - - Application - false - v143 - NotSet - - - - - - - - - - - - - - - - - - - - - true - - - true - - - $(ProjectDir)include;$(IncludePath) - - - $(ProjectDir)include;$(IncludePath) - - - - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - MultiThreadedDebugDLL - Level3 - ProgramDatabase - Disabled - stdcpp20 - - - MachineX86 - true - Windows - - - - - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - MultiThreadedDLL - Level3 - ProgramDatabase - stdcpp20 - - - MachineX86 - true - Windows - true - true - - - - - stdcpp20 - - - - - stdcpp20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/IcarusDownloadManager.vcxproj.filters b/IcarusDownloadManager.vcxproj.filters deleted file mode 100644 index e61501d..0000000 --- a/IcarusDownloadManager.vcxproj.filters +++ /dev/null @@ -1,120 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - \ No newline at end of file diff --git a/IcarusDownloadManager.vcxproj.user b/IcarusDownloadManager.vcxproj.user deleted file mode 100644 index 43952bb..0000000 --- a/IcarusDownloadManager.vcxproj.user +++ /dev/null @@ -1,11 +0,0 @@ - - - - upload-meta -u kverse -p demolaugh -h http://localhost:5002 -smca "D:\OneDrive\media\music\mp3\bb_king\paying_the_cost_to_be_the_boss" - WindowsLocalDebugger - - - upload-meta -u kverse -p demolaugh -h http://localhost:5002 -smca "D:\OneDrive\media\music\mp3\bb_king\paying_the_cost_to_be_the_boss" - WindowsLocalDebugger - - \ No newline at end of file diff --git a/README.md b/README.md index 6eb57d6..6e9f2c5 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,67 @@ # 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 -* C++ with C++20 features -* CMake -* GCC >= 10 or Visual Studio >= 17 [2022] -* [vcpkg](https://github.com/microsoft/vcpkg) -* [json](https://github.com/nlohmann/json) -* [openssl](https://github.com/openssl/openssl) -* [curl](https://github.com/curl/curl) -* [cpr](https://github.com/libcpr/cpr) +* Rust +* Cargo +* futures +* http +* reqwst +* serde +* serde_json +* tokio +* tokio-util ### Getting Started Clone the repo -``` +```BASH git clone --recursive https://github.com/kdeng00/IcarusDownloadManager ``` -Install packages - -``` -vcpkg install nlohman-json -vcpkg install openssl -vcpkg install curl -vcpkg install cpr -``` - Build the project: -``` +```BASH cd IcarusDownloadManager -mkdir build -cd build - -cmake .. -cmake --build . --config release -j +cargo build ``` -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. +The program has been built and can be executed by the binary file *icarus-dm*. For information on how to use icarua-dm, merely execute the program without any command line arguments. ### Downloading Song ```BASH -icd download -u spacecadet -p stellar40 -h https://icarus.com -b 15 -``` - -### Uploading Song - -```BASH -icd upload -u spacecadet -p stellar40 -h https://icarus.com -s /path/of/song.mp3 +icarus-dm download -u spacecadet -p stellar40 -h https://icarus.com -b 15 ``` ### Uploading Song with metadata ```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 -s /path/of/song.mp3 -t 1 -m /path/to/metadata/config/collection.json -ca /path/to/cover/art/image.png ``` ### Uploading Song with metadata from directory ```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 -smca /path/where/songs/and/metadata/exists/ ``` - ### Retrieving Song in json ```Bash -icd retrieve -u spacecadet -p stellar40 -h https://icarus.com -rt songs +icarus-dm retrieve -u spacecadet -p stellar40 -h https://icarus.com -rt songs ``` ### Deleting Song ```BASH -icd delete -u spacecadet -p stellar40 -h https://icarus.com -D 15 +icarus-dm delete -u spacecadet -p stellar40 -h https://icarus.com -D 15 ``` @@ -90,6 +71,7 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduc ## Versioning +[v0.4.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.4.0) [v0.3.2](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.3.2) [v0.3.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.3.0) [v0.2.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.2.0) @@ -97,12 +79,6 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduc [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 This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/include/Managers/ActionManager.h b/include/Managers/ActionManager.h deleted file mode 100644 index 66bc3e1..0000000 --- a/include/Managers/ActionManager.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef ACTIONMANAGER_H_ -#define ACTIONMANAGER_H_ - -#include -#include -#include -#include -#include - -#include"Models/Flags.h" -#include"Models/IcarusAction.h" - -namespace Managers -{ - - - -class ActionManager -{ -public: - ActionManager(char**, int); - - Models::IcarusAction retrieveIcarusAction() const; -private: - constexpr std::array supportedFlags() noexcept - { - constexpr std::array allFlags{"-u", "-p", "-t", "-h", "-s", - "-sd", "-sr", "-d", "-D", "-b", "-rt", "-nc", - "-m", "-ca", "-smca", "-t"}; - - return allFlags; - } - constexpr std::array supportedActions() noexcept; - - void initialize(); - void validateFlags(); - // Checks to see if the flag is valid - template - 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 - 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(*i)) - { - return true; - } - else - { - return false; - } - } - else - { - return false; - } - } - - void printAction() noexcept; - void printFlags() noexcept; - - std::vector parsedFlags(); - - std::string action; - - std::vector flags; - - char **params; - int paramCount; -}; - -} - -#endif diff --git a/include/Managers/CommitManager.h b/include/Managers/CommitManager.h deleted file mode 100644 index d3827f2..0000000 --- a/include/Managers/CommitManager.h +++ /dev/null @@ -1,220 +0,0 @@ -#ifndef COMMITMANAGER_H_ -#define COMMITMANAGER_H_ - -#include -#include -#include -#include -#include - -#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 songs; - }; - -private: - enum class ActionValues; - - std::map 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 - 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 file = std::filesystem::path(songPath); - auto filename = file.filename().string();; - - 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(filename, 'd', dl); - auto k = Utilities::Checks::itemIterInContainer(filename, 'k', dl); - auto dot = Utilities::Checks::itemIterInContainer(filename, '.', dl); - - switch(mode) - { - case 1: - { - // if (k != songPath.end() && dot != songPath.end()) - if (k != filename.end() && dot != filename.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()) - if (k != filename.end() && dot != filename.end() && d != filename.end()) - { - auto tStr = std::string(++k, d); - auto dStr = std::string(++d, dot); - std::cout<<"DStr: "< - void parseDiscAndTrack(Song &song, const Str &trackID) - { - auto sep = [](char c, char t) { return c == t; }; - auto separator = Utilities::Checks::itemIterInContainer(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 diff --git a/include/Managers/FileManager.h b/include/Managers/FileManager.h deleted file mode 100644 index 675950c..0000000 --- a/include/Managers/FileManager.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef FILEMANAGER_H_ -#define FILEMANAGER_H_ - -#include - -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 diff --git a/include/Managers/TokenManager.h b/include/Managers/TokenManager.h deleted file mode 100644 index 4e42632..0000000 --- a/include/Managers/TokenManager.h +++ /dev/null @@ -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 diff --git a/include/Managers/UserManager.h b/include/Managers/UserManager.h deleted file mode 100644 index 423e938..0000000 --- a/include/Managers/UserManager.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef USERMANAGER_H_ -#define USERMANAGER_H_ - -#include - -#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 diff --git a/include/Models/API.h b/include/Models/API.h deleted file mode 100644 index 7c72fc2..0000000 --- a/include/Models/API.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef API_H_ -#define API_H_ - -#include - -namespace Models -{ - -class API -{ -public: - std::string url; - std::string endpoint; - std::string version; -}; - -} - -#endif diff --git a/include/Models/Flags.h b/include/Models/Flags.h deleted file mode 100644 index 96206f5..0000000 --- a/include/Models/Flags.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef FLAGS_H_ -#define FLAGS_H_ - -#include - -namespace Models -{ - -class Flags -{ -public: - std::string flag; - std::string value; -}; - -} - -#endif diff --git a/include/Models/IcarusAction.h b/include/Models/IcarusAction.h deleted file mode 100644 index 031886d..0000000 --- a/include/Models/IcarusAction.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef ICARUSACTION_H_ -#define ICARUSACTION_H_ - -#include -#include -#include -#include -#include - -#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: "<action<<"\n"; - std::cout<<"Flag count: "<flags.size()<<"\n"; - - for (const auto &flag : this->flags) - { - std::cout<<"flag "< flags; -}; - -} - -#endif diff --git a/include/Models/Song.h b/include/Models/Song.h deleted file mode 100644 index a66c787..0000000 --- a/include/Models/Song.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef SONG_H_ -#define SONG_H_ - -#include -#include -#include - - -namespace Models -{ - -class Song -{ -public: - Song() = default; - - void printInfo() - { - std::cout<<"Title: "<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 diff --git a/include/Models/Token.h b/include/Models/Token.h deleted file mode 100644 index eeb0338..0000000 --- a/include/Models/Token.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TOKEN_H_ -#define TOKEN_H_ - -#include - -namespace Models -{ - -struct Token -{ - std::string accessToken; - std::string tokenType; - int expiration; -}; - -} - -#endif diff --git a/include/Models/UploadForm.h b/include/Models/UploadForm.h deleted file mode 100644 index b65b957..0000000 --- a/include/Models/UploadForm.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef UPLOADFORM_H_ -#define UPLOADFORM_H_ - -#include - - -namespace Models -{ - -struct UploadForm -{ - std::string url; - std::string filePath; -}; - -} - -#endif diff --git a/include/Models/User.h b/include/Models/User.h deleted file mode 100644 index aed560d..0000000 --- a/include/Models/User.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef USER_H_ -#define USER_H_ - -#include - -namespace Models -{ - -struct User -{ - std::string username; - std::string password; -}; - -} - -#endif diff --git a/include/Parsers/APIParser.h b/include/Parsers/APIParser.h deleted file mode 100644 index 51aa22b..0000000 --- a/include/Parsers/APIParser.h +++ /dev/null @@ -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 diff --git a/include/Syncers/Delete.h b/include/Syncers/Delete.h deleted file mode 100644 index b927f64..0000000 --- a/include/Syncers/Delete.h +++ /dev/null @@ -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 diff --git a/include/Syncers/Download.h b/include/Syncers/Download.h deleted file mode 100644 index 7c0fcb6..0000000 --- a/include/Syncers/Download.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef DOWNLOAD_H_ -#define DOWNLOAD_H_ - -#include -#include - -#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 diff --git a/include/Syncers/RetrieveRecords.h b/include/Syncers/RetrieveRecords.h deleted file mode 100644 index 72fa807..0000000 --- a/include/Syncers/RetrieveRecords.h +++ /dev/null @@ -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 diff --git a/include/Syncers/SyncerBase.h b/include/Syncers/SyncerBase.h deleted file mode 100644 index 6948a42..0000000 --- a/include/Syncers/SyncerBase.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SYNCERBASE_H_ -#define SYNCERBASE_H_ - -#include - -#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 diff --git a/include/Syncers/Upload.h b/include/Syncers/Upload.h deleted file mode 100644 index 49b7ce1..0000000 --- a/include/Syncers/Upload.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef UPLOAD_H_ -#define UPLOAD_H_ - -#include -#include -#include - -#include - -#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 retrieveAllSongsFromDirectory(const std::string&, - bool); - - std::string retrieveUrl(); - - Models::Song retrieveSongPath(fs::directory_entry&); - - void printSongDetails(); - void printSongDetails(std::vector&); - void printJsonData(const nlohmann::json&); -}; - -} - -#endif diff --git a/include/UI/AboutWindow.h b/include/UI/AboutWindow.h deleted file mode 100644 index 15e5593..0000000 --- a/include/UI/AboutWindow.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ABOUTWINDOW_H_ -#define ABOUTWINDOW_H_ - -#include - -#include -#include -#include -#include -#include - -#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 appName; - -private slots: - void closeWindow(); -}; - -} - -#endif diff --git a/include/UI/CommonWindow.h b/include/UI/CommonWindow.h deleted file mode 100644 index f3a5413..0000000 --- a/include/UI/CommonWindow.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef COMMONWINDOW_H_ -#define COMMONWINDOW_H_ - -#include - -#include -#include -#include -#include -#include - - -namespace UI -{ - -class CommonWindow -{ -public: - CommonWindow() = default; - ~CommonWindow() = default; -protected: - virtual void connections()=0; - std::unique_ptr selectionBoxQt; - std::unique_ptr actionButtonQt; - std::unique_ptr mainLayoutQt; - std::unique_ptr subLayoutOneQt; - std::unique_ptr subLayoutTwoQt; - int windowHeight, windowWidth; -}; - -} - -#endif diff --git a/include/UI/MainWindow.h b/include/UI/MainWindow.h deleted file mode 100644 index 0460b1e..0000000 --- a/include/UI/MainWindow.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef MAINWINDOW_H_ -#define MAINWINDOW_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 widgetStack; - - std::unique_ptr stackLayout; - - std::unique_ptr urlPortion; - std::unique_ptr songPathPortion; - - std::unique_ptr mainWidgetQt; - std::unique_ptr uploadSongWidgetQt; - - std::unique_ptr MainDockWidgetQt; - - std::unique_ptr windowComboBox; - - std::unique_ptr uploadSongQt; - - std::unique_ptr urlQt; - std::unique_ptr sourceFilePathQt; - - std::unique_ptr urlLabel; - std::unique_ptr songPath; - - std::unique_ptr fileMenuQt; - std::unique_ptr editMenuQt; - std::unique_ptr helpMenuQt; - - std::unique_ptr closeApplicationQt; - std::unique_ptr aboutApplicationQt; - - std::unique_ptr aboutWindow; -signals: -private slots: - void uploadSong(); - void exitApplication(); - void displaySoftwareInformation(); - void setCurrentIndex(int); -}; - -} - -#endif diff --git a/include/Utilities/Checks.h b/include/Utilities/Checks.h deleted file mode 100644 index 8743e53..0000000 --- a/include/Utilities/Checks.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef CHECKS_H_ -#define CHECKS_H_ - -#include -#include -#include -#include - -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 - 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 - static auto itemIterInContainer(const Container &container, const Item &item, Func func) - { - auto result = false; - - auto ii = std::find_if(container.begin(), container.end(), [&](Item i) - { - return func(i, item); - }); - - if (ii == container.end()) - { - ii = container.end(); - } - - return ii; - } - private: - }; -} - -#endif - - - diff --git a/include/Utilities/Conversions.h b/include/Utilities/Conversions.h deleted file mode 100644 index 3bfa5a4..0000000 --- a/include/Utilities/Conversions.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef CONVERSIONS_H_ -#define CONVERSIONS_H_ - -#include -#include - -namespace Utilities -{ - -class Conversions -{ -public: - Conversions(); - - static void toLowerChar(char &c) - { - if (std::isalpha(c)) - { - c = std::tolower(c); - } - } - - void initializeValues(); - - template - void printValue(T val); -private: -}; - -} - -#endif diff --git a/resource.h b/resource.h deleted file mode 100644 index 3b3ded8..0000000 --- a/resource.h +++ /dev/null @@ -1,14 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by IcarusDownloadManager.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/src/Main.cpp b/src/Main.cpp deleted file mode 100644 index b9072ac..0000000 --- a/src/Main.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include - -#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.3.2"; -} - -void printHelp() -{ - cout<<"icd [Action] [flag]\n\n"; - - cout<<"Actions\n"; - cout<<"download\n"; - cout<<"upload\n"; - cout<<"upload-meta\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 upload with metadata\n"; - cout<<"-s path of song\n"; - cout<<"-t track number\n"; - cout<<"-m metadata filepath\n"; - cout<<"-ca coverart filepath\n"; - cout<<"-scma directory where songs, metadata, and cover art exists and will be uploaded (Optional)\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; -} diff --git a/src/Managers/ActionManager.cpp b/src/Managers/ActionManager.cpp deleted file mode 100644 index 27a6bfc..0000000 --- a/src/Managers/ActionManager.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include"Managers/ActionManager.h" - -#include -#include -#include -#include - -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(*flag) && doesFlagHaveValue(*flag)) - { - cout<<"Flag has value\n"; - flg.flag = *flag; - flg.value = *(++flag); - } - else if (isValidFlag(*flag)) - { - cout<<"Flag does not have a value\n"; - flg.flag = *flag; - } - else - { - cout<<"Flag "<<*flag<<" is not valid"< ActionManager::parsedFlags() -{ - auto parsed = vector(); - - 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 "< -#include -#include -#include - -#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 "< - CommitManager::mapActions() noexcept -{ - const std::map 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..."<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: "<(song, trackID); - - auto c = [](const Song &songA, const Song &songB) { return songA.track == songB.track && songA.disc == songB.disc; }; - auto sng = Utilities::Checks::itemIterInContainer>(album.songs, song, c); - - if (sng == album.songs.end()) - { - cout<<"Not found with disc "< 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 "< extensions; - extensions.reserve(3); - extensions.push_back(".jpg"); - extensions.push_back(".jpeg"); - extensions.push_back("."); - - return ext.compare(extensions[0]) == 0 || ext.compare(extensions[1]) == 0 || ext.compare(extensions[2]) == 0; - }; - - if (extension.compare(".mp3") == 0) - { - Song song; - song.songPath = pp.string(); - - initializeDiscAndTrack(song); - - songs.emplace_back(std::move(song)); - } - // else if (extension.compare(".jpg") == 0 || extension.compare(".png") == 0) - else if (validImg(extension.string())) - { - 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(); - album.albumArtist = serialized["album_artist"].get(); - album.genre = serialized["genre"].get(); - album.year = serialized["year"].get(); - album.trackCount = serialized["track_count"].get(); - album.discCount = serialized["disc_count"].get(); - album.songs.reserve(album.trackCount); - - for (auto &j : serialized["tracks"]) - { - Song song; - song.title = j["title"].get(); - song.track = j["track"].get(); - song.disc = j["disc"].get(); - song.artist = j["artist"].get(); - 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<album<<"\n"; - std::cout<<"Album Artist: "<albumArtist<<"\n"; - std::cout<<"Genre: "<genre<<"\n"; - std::cout<<"Year: "<year<<"\n"; - std::cout<<"Track count: "<trackCount<<"\n"; - std::cout<<"Disc count: "<discCount<<"\n"; - std::cout<<"\n"; -} - -#pragma region Functions - -} diff --git a/src/Managers/FileManager.cpp b/src/Managers/FileManager.cpp deleted file mode 100644 index a2c5618..0000000 --- a/src/Managers/FileManager.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include - -#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 "<filePath = filePath; -} - -char* FileManager::retrieveFileBuffer() const -{ - return fileBuffer; -} - -int FileManager::retrieveFileBufferLength() const { return fileBufferLength; } - -#pragma endregion - -} diff --git a/src/Managers/TokenManager.cpp b/src/Managers/TokenManager.cpp deleted file mode 100644 index 44f1725..0000000 --- a/src/Managers/TokenManager.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include"Managers/TokenManager.h" - -#include - -#include -#include - -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"< -#include -#include - -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 - -} diff --git a/src/Models/Song.cpp b/src/Models/Song.cpp deleted file mode 100644 index 49338a7..0000000 --- a/src/Models/Song.cpp +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/src/Parsers/APIParser.cpp b/src/Parsers/APIParser.cpp deleted file mode 100644 index 379877f..0000000 --- a/src/Parsers/APIParser.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "Parsers/APIParser.h" - -#include - -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; - } - } - - // NOTE: For now I will hard code - // the api version since I am only - // on version 1 - api.version = "v1"; -} -#pragma endregion - -} diff --git a/src/Syncers/Delete.cpp b/src/Syncers/Delete.cpp deleted file mode 100644 index 0bc9ed3..0000000 --- a/src/Syncers/Delete.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "Syncers/Delete.h" - -#include -#include - -#include - -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 "< -#include -#include - -#include - -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 "< -#include - -#include -#include - -#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"< -#include -#include - -#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 "<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"<(); - song.title = result["title"].get(); - song.artist = result["artist"].get(); - song.album = result["album"].get(); - song.genre = result["genre"].get(); - song.year = result["year"].get(); - song.duration = result["duration"].get(); - song.track = result["track"].get(); - - return song; - } - catch (exception& e) - { - auto msg = e.what(); - cout<> 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<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"< Upload::retrieveAllSongsFromDirectory(const std::string& directory, - bool recursive) -{ - std::vector 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& songs) -{ - for (auto& song: songs) - { - cout<<"Song details: "<{new QVBoxLayout}; - - appName = unique_ptr{new QLabel(tr("IcarusDownloadManager"))}; - actionButtonQt = unique_ptr{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 - -} diff --git a/src/UI/MainWindow.cpp b/src/UI/MainWindow.cpp deleted file mode 100644 index b44d6d3..0000000 --- a/src/UI/MainWindow.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "UI/MainWindow.h" - -#include -#include - -#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{new AboutWindow}; -} -#pragma endregion - - -#pragma region Functions -void MainWindow::configureDownloadSection() -{ -} -void MainWindow::configureUploadSection() -{ - uploadSongQt = unique_ptr{new QPushButton(tr("Upload"))}; - urlQt = unique_ptr{new QTextEdit()}; - sourceFilePathQt = unique_ptr{new QTextEdit()}; - - urlLabel = unique_ptr{new QLabel(tr("URL"))}; - songPath = unique_ptr{new QLabel(tr("Song Path"))}; - - urlPortion = unique_ptr{new QHBoxLayout}; - songPathPortion = unique_ptr{new QHBoxLayout}; - - urlPortion.get()->addWidget(urlLabel.get()); - urlPortion.get()->addWidget(urlQt.get()); - - songPathPortion->addWidget(songPath.get()); - songPathPortion->addWidget(sourceFilePathQt.get()); - - subLayoutOneQt = unique_ptr{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{menuBar()->addMenu(tr("File"))}; - editMenuQt = unique_ptr{menuBar()->addMenu(tr("Edit"))}; - helpMenuQt = unique_ptr{menuBar()->addMenu(tr("Help"))}; - - closeApplicationQt = unique_ptr{new QAction(new QObject(nullptr))}; - closeApplicationQt->setText("Exit Application"); - - aboutApplicationQt = unique_ptr{new QAction(new QObject(nullptr))}; - aboutApplicationQt->setText("About"); - - fileMenuQt->addAction(closeApplicationQt.get()); - helpMenuQt->addAction(aboutApplicationQt.get()); - -} -void MainWindow::setupMainWidget() -{ - mainWidgetQt = unique_ptr{new QWidget}; - - windowComboBox = unique_ptr{new QComboBox}; - setupWindowLists(); - - - stackLayout = unique_ptr{new QVBoxLayout}; - stackLayout->addWidget(windowComboBox.get()); - - uploadSongWidgetQt = unique_ptr{new QWidget}; - uploadSongWidgetQt->setLayout(mainLayoutQt.get()); - - stackLayout->addWidget(uploadSongWidgetQt.get()); - - mainWidgetQt->setLayout(stackLayout.get()); -} -void MainWindow::setupMainWindow() -{ - configureWindowDimensions(); - - mainLayoutQt = unique_ptr{new QVBoxLayout}; - widgetStack = unique_ptr{new QStackedWidget}; - - configureUploadSection(); - - setupMainWidget(); - - widgetStack->addWidget(mainWidgetQt.get()); - - MainDockWidgetQt = unique_ptr{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 "<itemText(index); - auto cnvert = Utilities::Conversions(qText); - auto convertedStr = cnvert.convertQStringToString(); - cout<<"item text"<setEnabled(false); - - string url = urlQt->toPlainText().toUtf8().constData(); - string filePath = sourceFilePathQt->toPlainText().toUtf8().constData(); - cout<<"URL endpoint: "<setEnabled(true); -} -#pragma endregion - -} diff --git a/src/Utilities/Conversions.cpp b/src/Utilities/Conversions.cpp deleted file mode 100644 index 92aa7bb..0000000 --- a/src/Utilities/Conversions.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "Utilities/Conversions.h" - -#include - -using std::string; -using std::unique_ptr; - -namespace Utilities -{ - -Conversions::Conversions() -{ - initializeValues(); -} - -void Conversions::initializeValues() -{ -} -template -void Conversions::printValue(T val) -{ - std::cout<<"going to print value\n"; - std::cout< = env::args().collect(); + + if args.len() == 1 { + print_help(); + exit_program(-1); + } + + let args_len = args.len() as i32; + + println!("Argument count: {}", args_len); + + let mut act_mgr = managers::action_managers::ActionManager { + action: String::new(), + flags: Vec::new(), + params: args, + param_count: args_len, + }; + act_mgr.initialize(); + + let chosen_act = act_mgr.retrieve_icarus_action(); + + chosen_act.print_action_and_flags(); + + let mut cmt_mgr = managers::commit_manager::CommitManager { + ica_action: chosen_act, + }; + + cmt_mgr.commit_action(); +} diff --git a/src/managers/action_managers.rs b/src/managers/action_managers.rs new file mode 100644 index 0000000..d92af04 --- /dev/null +++ b/src/managers/action_managers.rs @@ -0,0 +1,221 @@ +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Debug, Deserialize, Serialize)] +pub struct ActionManager { + pub action: String, + pub flags: Vec, + pub params: Vec, + pub param_count: i32, +} + +impl ActionManager { + pub fn retrieve_icarus_action(&self) -> models::icarus_action::IcarusAction { + return models::icarus_action::IcarusAction { + flags: self.flags.clone(), + action: String::from(&self.action), + }; + } + + fn supported_flags(&self) -> Vec { + return vec![ + String::from("-u"), + String::from("-p"), + String::from("-t"), + String::from("-h"), + 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"), + ]; + } + + pub fn initialize(&mut self) { + self.validate_flags(); + self.validate_action(); + self.action = self.action.to_lowercase(); + } + + fn validate_flags(&mut self) { + println!("Validating flags"); + + let flag_vals = self.parsed_flags(); + + let mut i = 0; + println!("Flag count: {}", flag_vals.len()); + + while i < flag_vals.len() { + let flag = &flag_vals[i]; + println!("Index: {} | Value: {}", i, flag); + + let mut flg = models::flags::Flags::default(); + + if self.is_valid_flag(flag) && self.does_flag_have_value(flag) { + println!("Flag has value"); + flg.flag = String::from(flag); + flg.value = String::from(&flag_vals[i + 1]); + + i = i + 1; + } else if self.is_valid_flag(flag) { + println!("Flag does not have a value"); + flg.flag = String::from(flag); + } else { + println!("Flag {} is not valid", flag); + std::process::exit(-1); + } + + self.flags.push(flg); + println!(""); + 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; + } + } + + return found; + } + + fn does_flag_have_value(&self, flag: &String) -> bool { + let flags_tmp = self.parsed_flags(); + + let mut i_found: i32 = -1; + + for i in 0..flags_tmp.len() { + let flg = &flags_tmp[i]; + if flg == flag { + i_found = i as i32; + break; + } + } + + if i_found >= 0 { + if (i_found + 1) < flags_tmp.len().try_into().unwrap() { + return true; + } else { + return false; + } + } else { + return false; + } + } + + fn _print_action(&self) { + if self.action.len() == 0 { + 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 { + let mut parsed: Vec = Vec::new(); + + for i in 2..self.params.len() { + let flag = String::from(&self.params[i]); + parsed.push(flag); + } + + return parsed; + } +} + +#[cfg(test)] +mod tests { + use crate::managers::action_managers::ActionManager; + + #[test] + fn minimum_action_and_args() { + let args: Vec = 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 = 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 all_flags_found = false; + 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()); + } + } + + 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"); + } +} diff --git a/src/managers/commit_manager.rs b/src/managers/commit_manager.rs new file mode 100644 index 0000000..400f0a3 --- /dev/null +++ b/src/managers/commit_manager.rs @@ -0,0 +1,745 @@ +use std::collections::HashMap; +use std::default::Default; +use std::fs::read_dir; +use std::io::{Result, Write}; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; +use tokio::runtime::Runtime; + +use crate::managers; +use crate::models::song::Album; +use crate::models::{self}; +use crate::syncers; +use crate::utilities; +use crate::{constants, parsers}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct CommitManager { + pub ica_action: models::icarus_action::IcarusAction, +} + +#[derive(Clone, Debug)] +enum ActionValues { + DeleteAct, + DownloadAct, + RetrieveAct, + UploadAct, + UploadSongWithMetadata, + None, +} + +enum _RetrieveTypes { + Songs, +} + +#[derive(Clone, Debug)] +enum En { + ImageFile, + SongFile, + MetadataFile, + Other, +} + +impl Album { + pub fn _print_info(&self) { + println!("Album: {}", self.title); + println!("Album Artist: {}", self.album_artist); + println!("Genre: {}", self.genre); + println!("Year: {}", self.year); + println!("Track Count: {}", self.track_count); + println!("Disc Count: {}\n", self.disc_count); + } + + pub fn retrieve_song(&self, track: i32, disc: i32) -> Result { + let mut found = false; + let mut song = models::song::Song::default(); + + for song_i in &self.songs { + if song_i.track.unwrap() == track && song_i.disc.unwrap() == disc { + song = song_i.clone(); + found = true; + } + } + + if found { + return Ok(song); + } + + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Song not found", + )); + } +} + +impl CommitManager { + pub fn commit_action(&mut self) { + let action = &self.ica_action.action; + println!("Committing {} action", action); + + let mapped_actions = &self.map_actions(); + let mapped_action = self.find_mapped_action(&mapped_actions, action); + + println!("{:?}", mapped_action); + + match mapped_action { + ActionValues::DeleteAct => self.delete_song(), + ActionValues::DownloadAct => self.download_song(), + ActionValues::RetrieveAct => self.retrieve_object(), + ActionValues::UploadAct => self.upload_song(), + ActionValues::UploadSongWithMetadata => self.upload_song_with_metadata(), + _ => { + println!("Nothing good here"); + } + } + } + + fn find_mapped_action( + &self, + actions: &HashMap, + action: &String, + ) -> ActionValues { + for (key, act) in actions { + if key == action { + return act.clone(); + } + } + + return ActionValues::None; + } + + fn map_actions(&self) -> HashMap { + let mut actions: HashMap = HashMap::new(); + actions.insert("download".to_string(), ActionValues::DownloadAct); + actions.insert("upload".to_string(), ActionValues::UploadAct); + actions.insert( + "upload-meta".to_string(), + ActionValues::UploadSongWithMetadata, + ); + actions.insert("retrieve".to_string(), ActionValues::RetrieveAct); + actions.insert("delete".to_string(), ActionValues::DeleteAct); + + return actions; + } + + fn delete_song(&self) { + let mut prsr = parsers::api_parser::APIParser { + ica_act: self.ica_action.clone(), + api: models::api::API::default(), + }; + prsr.parse_api(); + let api = prsr.retrieve_api(); + + let token = self.parse_token(&api); + + println!("Deleting song"); + + let mut song = models::song::Song::default(); + + for arg in &self.ica_action.flags { + let flag = &arg.flag; + let value = &arg.value; + + if flag == "-D" { + song.id = Some(value.parse::().unwrap()); + } + } + + let mut del = syncers::delete::Delete { api: api.clone() }; + + println!("Deleting song.."); + + let res_fut = del.delete_song(&token, &song); + let result = Runtime::new().unwrap().block_on(res_fut); + match result { + Ok(o) => { + println!("Song (Id {:?}) has been successfully deleted", o.id); + } + Err(er) => { + println!("Error {:?}", er); + } + } + } + + fn download_song(&self) { + println!("Deleting song"); + let dwn = self.ica_action.retrieve_flag_value(&String::from("-b")); + let num: i32 = dwn.parse::().unwrap(); + + let mut prsr = parsers::api_parser::APIParser { + api: models::api::API::default(), + ica_act: self.ica_action.clone(), + }; + prsr.parse_api(); + + let api = prsr.retrieve_api(); + let token = self.parse_token(&api); + + let mut dwn_loader = syncers::download::Download { api: api.clone() }; + let mut song = models::song::Song::default(); + song.id = Some(num); + let result_fut = dwn_loader.download_song(&token, &song); + let result = Runtime::new().unwrap().block_on(result_fut); + match result { + Ok(o) => { + println!("Success"); + let mut filename = String::from("audio"); + filename += constants::file_extensions::WAV_FILE_EXTENSION; + let data = o.as_bytes(); + let mut file = std::fs::File::create(filename).expect("Failed to save"); + file.write_all(&data).expect("ff"); + } + Err(er) => { + println!("Error {:?}", er); + } + } + } + + fn retrieve_object(&self) { + println!("Deleting song"); + let rt = self.ica_action.retrieve_flag_value(&String::from("-rt")); + + if rt != "songs" { + panic!("Unsupported -rt: {}", rt); + } + + let mut prsr = parsers::api_parser::APIParser { + api: models::api::API::default(), + ica_act: self.ica_action.clone(), + }; + prsr.parse_api(); + + let api = prsr.retrieve_api(); + let token = self.parse_token(&api); + let mut repo = syncers::retrieve_records::RetrieveRecords { api: api.clone() }; + let result_fut = repo.get_all_songs(&token); + + let result = Runtime::new().unwrap().block_on(result_fut); + match result { + Ok(o) => { + for son in o { + son.print_info(); + } + } + Err(er) => { + println!("Error: {:?}", er); + } + } + } + + fn upload_song(&self) { + println!("Deleting song"); + panic!("Not supported"); + } + + fn parse_token(&self, api: &models::api::API) -> models::token::Token { + println!("Fetching token"); + + let mut usr_mgr: managers::user_manager::UserManager = + managers::user_manager::UserManager { + user: models::user::User { + username: String::new(), + password: String::new(), + }, + 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 = Runtime::new().unwrap().block_on(tok_mgr.request_token()); + + return token.unwrap(); + } + + fn upload_song_with_metadata(&mut self) { + 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.len() > 0 + && metadata_path.len() > 0 + && coverpath.len() > 0 + && track_id.len() > 0; + + let uni = self.ica_action.retrieve_flag_value(&String::from("-smca")); + let multitarget = uni.len() > 0; + + 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); + } else if multitarget { + let _ = self.multi_target_upload(&uni); + } else { + println!("Single or Multi target has not been chosen"); + } + } + + fn sing_target_upload( + &mut self, + songpath: &String, + track_id: &String, + meta_path: &String, + cover_path: &String, + ) -> Result<()> { + let mut prsr = parsers::api_parser::APIParser { + api: models::api::API::default(), + ica_act: self.ica_action.clone(), + }; + prsr.parse_api(); + + let api = prsr.retrieve_api(); + let token = self.parse_token(&api); + + let song_file = std::path::Path::new(&songpath); + + if !song_file.exists() { + println!("Song file does not exist"); + panic!("Error"); + } + + let mut cover_art = models::song::CoverArt::default(); + let mut song = models::song::Song::default(); + let mut filenames = Vec::new(); + let mut fp = String::new(); + let mut dir = String::new(); + + let entry = &song_file; + + let file_name = std::ffi::OsString::from(entry.file_name().unwrap()); + + println!("file name: {:?}", file_name); + + match self.find_file_extension(&file_name) { + En::ImageFile => {} + En::MetadataFile => {} + En::SongFile => { + let fname = self.o_to_string(&file_name); + + match fname { + Ok(s) => { + filenames.push(s.clone()); + fp = s.clone(); + dir = song_file.parent().unwrap().display().to_string(); + song.filepath = Some(s.clone()); + song.directory = Some(dir.clone()); + self.initialize_disc_and_track(&mut song); + } + Err(er) => println!("Error: {:?}", er), + } + } + _ => {} + } + + cover_art.path = Some(cover_path.clone()); + + let album = self.retrieve_metadata(&meta_path); + let trck = i32::from_str(track_id).unwrap(); + let mut s = album.retrieve_song(trck, 1).unwrap(); + s.filepath = Some(fp); + s.directory = Some(dir); + s.genre = Some(album.genre.clone()); + s.year = Some(album.year.clone()); + s.album = Some(album.title.clone()); + s.data = Some(s.to_data().unwrap()); + + cover_art.data = Some(cover_art.to_data().unwrap()); + + let mut up = syncers::upload::Upload::default(); + let host = self.ica_action.retrieve_flag_value(&String::from("-h")); + up.set_api(&host); + + let res = up.upload_song_with_metadata(&token, &s, &cover_art, &album); + let tken = Runtime::new().unwrap().block_on(res); + + match &tken { + Ok(o) => { + println!("Successfully sent {:?}", o); + } + Err(er) => { + println!("Some error {:?}", er); + } + } + + Ok(()) + } + + fn multi_target_upload(&mut self, sourcepath: &String) -> std::io::Result<()> { + let mut prsr = parsers::api_parser::APIParser { + api: models::api::API::default(), + ica_act: self.ica_action.clone(), + }; + prsr.parse_api(); + let api = prsr.retrieve_api(); + let token = self.parse_token(&api); + + let directory_path = std::path::Path::new(&sourcepath); + + if !directory_path.exists() { + panic!("Directory does not exist"); + } + + let mut cover_art = models::song::CoverArt::default(); + let mut songs: Vec = Vec::new(); + let mut filenames: Vec = Vec::new(); + let mut metadatapath: String = String::new(); + + // iterate files in metadatapath + let path = std::path::Path::new(directory_path); + + for entry in read_dir(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); + + match self.find_file_extension(&file_name) { + En::ImageFile => { + let directory_part = sourcepath.clone(); + let fname = self.o_to_string(&file_name); + let fullpath = directory_part + "/" + &fname.unwrap(); + cover_art.path = Some(fullpath); + } + En::MetadataFile => { + let directory_part = sourcepath.clone(); + let fname = self.o_to_string(&file_name); + metadatapath = directory_part + "/" + &fname.unwrap(); + } + En::SongFile => { + let mut song = models::song::Song::default(); + let fname = self.o_to_string(&file_name); + + match fname { + Ok(s) => { + filenames.push(s.clone()); + song.filepath = Some(s.clone()); + song.directory = Some(sourcepath.clone()); + song.data = Some(song.to_data().unwrap()); + self.initialize_disc_and_track(&mut song); + } + Err(er) => println!("Error: {:?}", er), + } + + songs.push(song) + } + _ => {} + } + } + + filenames.sort(); + + let mut album = self.retrieve_metadata(&metadatapath); + + self.song_parsing(&mut album, &sourcepath, &filenames); + + 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 = Some(cover_art.to_data().unwrap()); + + println!(""); + + for sng in &mut album.songs { + match sng.data { + Some(_) => {} + None => { + sng.data = Some(sng.to_data().unwrap()); + } + }; + } + + for song in &album.songs { + // Upload each song + println!("Sending song..."); + let res = up.upload_song_with_metadata(&token, &song, &cover_art, &album); + let tken = Runtime::new().unwrap().block_on(res); + + match &tken { + Ok(o) => { + println!("Successfully sent {:?}", o); + } + Err(er) => { + println!("Some error {:?}", er); + } + } + + println!(""); + } + + Ok(()) + } + + // Makes sure the elements in album.songs is populated + fn song_parsing( + &self, + album: &mut models::song::Album, + directory: &String, + filenames: &Vec, + ) { + // Apply directory + for song in &mut album.songs { + let dir = &song.directory; + match dir { + Some(s) => println!("{}", s), + None => { + song.directory = Some(directory.clone()); + } + } + } + + // Apply filename + let mut index = 0; + for song in &mut album.songs { + let filename = filenames[index].clone(); + song.filepath = Some(filename); + index += 1; + } + + for song in &mut album.songs { + match &mut song.album { + Some(_) => {} + None => { + song.album = Some(album.title.clone()); + } + } + + match &mut song.genre { + Some(_) => {} + None => { + song.genre = Some(album.genre.clone()); + } + } + + match &mut song.year { + Some(_) => {} + None => { + song.year = Some(album.year.clone()); + } + } + } + } + + 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 == "wav" || extension == "flac" { + return En::SongFile; + } else if extension == "json" { + return En::MetadataFile; + } else if extension == "jpg" || extension == "jpeg" || extension == "png" { + return En::ImageFile; + } + } + _ => { + return En::Other; + } + } + + return En::Other; + } + + fn o_to_string(&self, val: &std::ffi::OsString) -> Result { + let res = val.clone().into_string(); + return match res { + Ok(sss) => Ok(sss), + Err(_) => Ok(String::from("Error")), + }; + } + + // Standards + // * track01.cdda.wav - Disc 1, Track 1 + // * track02d02.cdda.wav - Disc 2, Track 2 + fn initialize_disc_and_track(&mut self, song: &mut models::song::Song) { + let mut disc = 1; + let mut track = 1; + let mut mode = 0; + let filename = + & as Clone>::clone(&song.filepath).unwrap(); + + let trd = filename.contains("trackd"); + let tr = filename.contains("track"); + + if tr { + mode = 1; + } + + if trd { + mode = 2; + } + + let dl = |a: &char, b: &char| -> bool { + return a == b; + }; + let d = utilities::checks::Checks::index_of_item_in_container(&filename, &'d', dl); + let k = utilities::checks::Checks::index_of_item_in_container(&filename, &'k', dl); + let dot = utilities::checks::Checks::index_of_item_in_container(&filename, &'.', dl); + let end = filename.len() as i32; + + match mode { + 1 => { + if k != end && dot != end { + let st = k + 1; + let ed = dot - 1; + let mut t: String = String::new(); + let mut index = 0; + for a in filename.chars() { + if index >= st && index <= ed { + t.push(a); + } + + index += 1; + } + + if utilities::checks::Checks::is_numeric(&t) { + track = t.parse::().unwrap(); + } + disc = 1 + } + } + 2 => { + if k != end && dot != end && d != end { + let st = k + 1; + let ed = dot - 1; + let mut t: String = String::new(); + let mut index = 0; + for a in filename.chars() { + if index <= ed { + t.push(a); + } else if index >= st { + t.push(a); + } + + index += 1; + } + + if utilities::checks::Checks::is_numeric(&t) { + track = t.parse::().unwrap(); + } + + let sst = d + 1; + let eed = dot; + let mut d_s = String::new(); + index = 0; + for a in filename.chars() { + if index <= eed { + d_s.push(a); + } else if index >= sst { + d_s.push(a); + } + + index += 1; + } + + if utilities::checks::Checks::is_numeric(&d_s) { + track = d_s.parse::().unwrap(); + } + } + } + _ => println!(""), + } + + song.disc = Some(disc); + song.track = Some(track); + } + + fn _parse_disc_and_track(&self, song: &mut models::song::Song, track_id: &String) { + let sep = |_a: &char, _b: &char| -> bool { + return false; + }; + + let index = utilities::checks::Checks::index_of_item_in_container(track_id, &':', sep); + + if index == -1 { + let mut d_str: String = String::new(); + let t_str = String::new(); + + for c in track_id.chars().skip(0).take(index as usize) { + d_str.push(c); + } + + let start = index + 1; + let end = track_id.len() - 1; + + for c in track_id.chars().skip(start as usize).take(end as usize) { + d_str.push(c); + } + + song.disc = Some(d_str.parse::().unwrap()); + song.track = Some(t_str.parse::().unwrap()); + } else { + if utilities::checks::Checks::is_numeric(track_id) { + song.track = Some(track_id.parse::().unwrap()); + } + } + } + + fn _check_for_no_confirm(&self) -> bool { + for flag in self.ica_action.flags.iter() { + if flag.flag == "-nc" { + return true; + } + } + return false; + } + + fn retrieve_metadata(&self, path: &String) -> Album { + let content = self.retrieve_file_content(&path); + let val = content.unwrap(); + + let converted = serde_json::from_str(&val); + + match &converted { + Ok(_) => println!("Good!"), + Err(er) => println!("Error {:?}", er), + } + return converted.unwrap(); + } + + fn retrieve_file_content(&self, path: &String) -> Result { + return std::fs::read_to_string(path); + } +} diff --git a/src/managers/file_manager.rs b/src/managers/file_manager.rs new file mode 100644 index 0000000..b76a4f4 --- /dev/null +++ b/src/managers/file_manager.rs @@ -0,0 +1,64 @@ +use std::default::Default; + + +pub struct FileManager { + pub filepath: String, + pub filebuffer: String, + pub file_read: bool, + pub file_buffer_length: i32, +} + +impl Default for FileManager { + fn default() -> Self { + FileManager { + filepath: String::new(), + filebuffer: String::new(), + file_read: false, + file_buffer_length: -1 + } + } +} + + +impl FileManager { + // TODO: Implement + pub fn init(&mut self) { + self.read_file() + } + + pub fn save_file(&mut self, filepath: &String) { + if !self.file_read { + self.read_file(); + } + + let mut file = File::open(filepath)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + self.file_buffer_length = buffer.len(); + self.filebuffer = String::from_utf8(buffer); // Assuming UTF-8 encoding + } + + pub fn modify_file_path(&mut self, file: &String) { + self.filepath = file; + } + + pub fn retrieve_file_buffer(&self) -> String { + self.filebuffer; + } + + pub fn retrieve_file_length(&self) -> i32 { + self.file_buffer_length; + } + + fn read_file(&mut self) -> Vec { + let mut file = File::open(self.filepath)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + self.file_buffer_length = buffer.len(); + self.filebuffer = String::from_utf8(buffer); // Assuming UTF-8 encoding + self.file_read = true; + + return buffer; + } +} + diff --git a/src/managers/mod.rs b/src/managers/mod.rs new file mode 100644 index 0000000..dbe307c --- /dev/null +++ b/src/managers/mod.rs @@ -0,0 +1,4 @@ +pub mod action_managers; +pub mod commit_manager; +pub mod token_manager; +pub mod user_manager; diff --git a/src/managers/token_manager.rs b/src/managers/token_manager.rs new file mode 100644 index 0000000..8ba8a1d --- /dev/null +++ b/src/managers/token_manager.rs @@ -0,0 +1,73 @@ +use std::default::Default; + +use crate::models; + +pub struct TokenManager { + pub user: models::user::User, + pub api: models::api::API, +} + +impl Default for TokenManager { + fn default() -> Self { + let mut token = TokenManager { + user: models::user::User::default(), + api: models::api::API::default(), + }; + + token.init(); + + return token; + } +} + +impl TokenManager { + pub async fn request_token(&self) -> Result { + println!("Sending request for a token"); + + let url = self.retrieve_url(); + + println!("URL: {}", url); + + let mut token = models::token::Token::default(); + + 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 + let s = response.json::().await; + match s { + // + Ok(parsed) => { + token = parsed; + } + Err(_) => println!("Hm, the response didn't match the shape we expected."), + }; + } + reqwest::StatusCode::UNAUTHORIZED => { + println!("Need to grab a new token"); + } + other => { + panic!("Uh oh! Something unexpected happened: {:?}", other); + } + } + + return Ok(token); + } + + pub fn init(&mut self) { + let api = &mut self.api; + api.version = String::from("v1"); + api.endpoint = String::from(format!("api/{}/login", api.version)); + } + + pub fn retrieve_url(&self) -> String { + let api = &self.api; + let mut url = String::from(&api.url); + url += &String::from(&api.endpoint); + url += &String::from("/"); + + return url; + } +} diff --git a/src/managers/user_manager.rs b/src/managers/user_manager.rs new file mode 100644 index 0000000..61607e2 --- /dev/null +++ b/src/managers/user_manager.rs @@ -0,0 +1,42 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +use crate::models::{self}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct UserManager { + pub user: models::user::User, + pub ica_action: models::icarus_action::IcarusAction, +} + +impl Default for UserManager { + fn default() -> Self { + UserManager { + user: models::user::User::default(), + ica_action: models::icarus_action::IcarusAction::default(), + } + } +} + +impl UserManager { + pub fn retrieve_user(&self) -> models::user::User { + return self.user.clone(); + } + + pub fn parse_user_from_actions(&mut self) { + let args = &self.ica_action.flags; + + for arg in args { + let flag = &arg.flag; + + if flag == "-u" { + self.user.username = String::from(&arg.value); + } + + if flag == "-p" { + self.user.password = String::from(&arg.value); + } + } + } +} diff --git a/src/models/api.rs b/src/models/api.rs new file mode 100644 index 0000000..aeb9269 --- /dev/null +++ b/src/models/api.rs @@ -0,0 +1,20 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct API { + pub url: String, + pub endpoint: String, + pub version: String, +} + +impl Default for API { + fn default() -> Self { + API { + url: String::new(), + endpoint: String::new(), + version: String::new(), + } + } +} diff --git a/src/models/flags.rs b/src/models/flags.rs new file mode 100644 index 0000000..d51f2b3 --- /dev/null +++ b/src/models/flags.rs @@ -0,0 +1,18 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Flags { + pub flag: String, + pub value: String, +} + +impl Default for Flags { + fn default() -> Self { + Flags { + flag: String::new(), + value: String::new(), + } + } +} diff --git a/src/models/icarus_action.rs b/src/models/icarus_action.rs new file mode 100644 index 0000000..4ef6aad --- /dev/null +++ b/src/models/icarus_action.rs @@ -0,0 +1,44 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +use crate::models; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct IcarusAction { + pub action: String, + pub flags: Vec, +} + +impl Default for IcarusAction { + fn default() -> Self { + IcarusAction { + action: String::new(), + flags: Vec::new(), + } + } +} + +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; + } + } + + return 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); + } + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..986c861 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,7 @@ +pub mod api; +pub mod flags; +pub mod icarus_action; +pub mod song; +pub mod token; +pub mod upload_form; +pub mod user; diff --git a/src/models/song.rs b/src/models/song.rs new file mode 100644 index 0000000..7568e45 --- /dev/null +++ b/src/models/song.rs @@ -0,0 +1,171 @@ +use std::default::Default; +use std::io::Read; + +use serde::{Deserialize, Serialize}; + +use crate::constants; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Song { + #[serde(alias = "song_id")] + pub id: Option, + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub duration: Option, + pub track: Option, + pub disc: Option, + pub data: Option>, + pub filepath: Option, + pub directory: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Album { + #[serde(alias = "album")] + pub title: String, + pub album_artist: String, + pub genre: String, + pub year: i32, + pub track_count: i32, + pub disc_count: i32, + #[serde(alias = "tracks")] + pub songs: Vec, +} + +impl Default for Album { + fn default() -> Self { + Album { + title: String::new(), + album_artist: String::new(), + genre: String::new(), + year: 0, + track_count: 0, + disc_count: 0, + songs: Vec::new(), + } + } +} + +impl Default for Song { + fn default() -> Self { + Song { + id: None, + title: None, + artist: None, + album: None, + genre: None, + year: None, + duration: None, + track: None, + disc: None, + data: None, + filepath: None, + directory: None, + } + } +} + +impl Song { + pub fn print_info(&self) { + println!("Title: {:?}", self.title); + println!("Artist: {:?}", self.artist); + } + + pub fn song_path(&self) -> String { + let directory = + & as Clone>::clone(&self.directory).unwrap(); + + let mut buffer: String = directory.to_string(); + let count = buffer.len(); + + if buffer.chars().nth(count - 1) != Some('/') { + buffer += "/"; + } + + let filename = + & as Clone>::clone(&self.filepath).unwrap(); + buffer += filename; + + return buffer; + } + + pub fn to_data(&self) -> Result, std::io::Error> { + let path = self.song_path(); + println!("Converting song to data"); + println!("Path: {:?}", path); + + let mut file = std::fs::File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + if buffer.len() == 0 { + println!("Why is it empty?"); + } + + Ok(buffer) + } + + // if 1 - wav, if 0 - mp3, anything else defaults to wav + pub fn _generate_filename_from_track(&mut self, i_type: i32) -> i32 { + let mut filename: String = String::new(); + if self.track.unwrap() < 10 { + filename += "0"; + } + + filename += &self.track.unwrap().to_string(); + + if i_type == 0 { + filename += constants::file_extensions::_MP3_FILE_EXTENSION; + } else { + filename += constants::file_extensions::WAV_FILE_EXTENSION; + } + + self.filepath = Some(filename); + + return 0; + } + + pub fn _to_metadata_json(&self) -> Result { + return serde_json::to_string_pretty(&self); + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CoverArt { + pub id: Option, + pub title: Option, + pub path: Option, + pub data: Option>, +} + +impl Default for CoverArt { + fn default() -> Self { + CoverArt { + id: None, + title: None, + path: None, + data: None, + } + } +} + +impl CoverArt { + pub fn to_data(&self) -> Result, std::io::Error> { + let mut path: String = String::new(); + match &self.path { + Some(val) => { + path = String::from(val); + } + None => { + (); + } + } + + let mut file = std::fs::File::open(path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + Ok(buffer) + } +} diff --git a/src/models/token.rs b/src/models/token.rs new file mode 100644 index 0000000..e975fa2 --- /dev/null +++ b/src/models/token.rs @@ -0,0 +1,47 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct Token { + #[serde(alias = "user_id")] + pub user_id: i32, + #[serde(alias = "username")] + pub username: Option, + #[serde(alias = "token")] + pub access_token: Option, + #[serde(alias = "token_type")] + pub token_type: Option, + #[serde(alias = "expiration")] + pub expiration: Option, + #[serde(alias = "message")] + pub message: Option, +} + +impl Default for Token { + fn default() -> Self { + Token { + user_id: -1, + username: None, + access_token: None, + token_type: None, + expiration: None, + message: None, + } + } +} + +impl Token { + pub fn bearer_token(&self) -> String { + let mut token: String = String::from("Bearer "); + + match &self.access_token { + Some(tok) => { + token += tok; + } + None => {} + } + + return token; + } +} diff --git a/src/models/upload_form.rs b/src/models/upload_form.rs new file mode 100644 index 0000000..ef90173 --- /dev/null +++ b/src/models/upload_form.rs @@ -0,0 +1,18 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct UploadForm { + pub url: Option, + pub filepath: Option, +} + +impl Default for UploadForm { + fn default() -> Self { + UploadForm { + url: None, + filepath: None, + } + } +} diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..46a37c0 --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,24 @@ +use std::default::Default; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct User { + pub username: String, + pub password: String, +} + +impl Default for User { + fn default() -> Self { + User { + username: String::new(), + password: String::new(), + } + } +} + +impl User { + pub fn _to_json(&self) -> Result { + return serde_json::to_string_pretty(&self); + } +} diff --git a/src/parsers/api_parser.rs b/src/parsers/api_parser.rs new file mode 100644 index 0000000..7a8396f --- /dev/null +++ b/src/parsers/api_parser.rs @@ -0,0 +1,34 @@ +use crate::models; + +#[derive(Clone, Debug)] +pub struct APIParser { + pub api: models::api::API, + pub ica_act: models::icarus_action::IcarusAction, +} + +impl APIParser { + pub fn retrieve_api(&self) -> models::api::API { + return self.api.clone(); + } + + pub fn parse_api(&mut self) { + let flags = self.ica_act.flags.clone(); + println!("Parsing api"); + + for elem in flags { + let arg = elem.flag; + let value = elem.value; + + if arg == "-h" { + if value.chars().nth(value.len() - 1) == Some('/') { + self.api.url = value; + } else { + self.api.url = value + "/"; + } + break; + } + } + + self.api.version = "v1".to_string(); + } +} diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs new file mode 100644 index 0000000..35820cf --- /dev/null +++ b/src/parsers/mod.rs @@ -0,0 +1 @@ +pub mod api_parser; diff --git a/src/syncers/delete.rs b/src/syncers/delete.rs new file mode 100644 index 0000000..8ae73f2 --- /dev/null +++ b/src/syncers/delete.rs @@ -0,0 +1,72 @@ +use std::default::Default; + +use reqwest; + +use crate::models; + +#[derive(Clone, Debug)] +pub struct Delete { + pub api: models::api::API, +} + +impl Default for Delete { + fn default() -> Self { + Delete { + api: models::api::API::default(), + } + } +} + +impl Delete { + pub async fn delete_song( + &mut self, + token: &models::token::Token, + song: &models::song::Song, + ) -> Result { + self.api.endpoint = "song/data/delete".to_owned(); + let url = self.retrieve_url(&song); + 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(); + let mut sng = models::song::Song::default(); + + match response.status() { + reqwest::StatusCode::OK => { + println!("Success!"); + let s = response.json::().await; + match s { + // + Ok(parsed) => { + sng = parsed; + } + Err(er) => { + println!("Error {:?}", er); + } + }; + } + other => { + panic!("Issue occurred: {:?}", other); + } + } + + return Ok(sng); + } + + fn retrieve_url(&self, song: &models::song::Song) -> String { + let api = &self.api; + let mut url: String = String::from(&api.url); + url += &String::from("api/"); + url += &String::from(&api.version); + url += &String::from("/"); + url += &String::from(&api.endpoint); + url += &String::from("/"); + url += &song.id.unwrap().to_string(); + + return url; + } +} diff --git a/src/syncers/download.rs b/src/syncers/download.rs new file mode 100644 index 0000000..39f0c12 --- /dev/null +++ b/src/syncers/download.rs @@ -0,0 +1,82 @@ +use std::default::Default; + +use crate::models; + +pub struct Download { + pub api: models::api::API, +} + +impl Default for Download { + fn default() -> Self { + Download { + api: models::api::API::default(), + } + } +} + +#[derive(Debug)] +pub enum MyError { + _Request(reqwest::Error), + Other(String), +} + +impl Download { + pub async fn download_song( + &mut self, + token: &models::token::Token, + song: &models::song::Song, + ) -> Result { + self.api.endpoint = String::from("song/data/download"); + let url = self.retrieve_url(&song); + let access_token = token.bearer_token(); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::AUTHORIZATION, + http::header::HeaderValue::from_str(&access_token.clone()).unwrap(), + ); + + 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 => { + let data = response.text(); + match data.await { + Ok(e) => { + return Ok(e); + } + Err(er) => { + println!("Error {:?}", er); + } + } + } + reqwest::StatusCode::UNAUTHORIZED => { + println!("Need to grab a new token"); + } + other => { + panic!("Uh oh! Something unexpected happened: {:?}", other); + } + } + + return Err(MyError::Other(String::from("Error downloading"))); + } + + fn retrieve_url(&self, song: &models::song::Song) -> String { + let api = &self.api; + let mut url: String = String::from(&api.url); + url += &String::from("api/"); + url += &String::from(&api.version); + url += &String::from("/"); + url += &String::from(&api.endpoint); + url += &String::from("/"); + url += &song.id.unwrap().to_string(); + + return url; + } +} diff --git a/src/syncers/mod.rs b/src/syncers/mod.rs new file mode 100644 index 0000000..cb8e326 --- /dev/null +++ b/src/syncers/mod.rs @@ -0,0 +1,4 @@ +pub mod delete; +pub mod download; +pub mod retrieve_records; +pub mod upload; diff --git a/src/syncers/retrieve_records.rs b/src/syncers/retrieve_records.rs new file mode 100644 index 0000000..b312cb4 --- /dev/null +++ b/src/syncers/retrieve_records.rs @@ -0,0 +1,80 @@ +use std::default::Default; +use std::io::Error; + +use crate::models; + +pub struct RetrieveRecords { + pub api: models::api::API, +} + +impl Default for RetrieveRecords { + fn default() -> Self { + RetrieveRecords { + api: models::api::API::default(), + } + } +} + +impl RetrieveRecords { + pub async fn get_all_songs( + &mut self, + token: &models::token::Token, + ) -> Result, Error> { + self.api.endpoint = String::from("song"); + let mut songs: Vec = Vec::new(); + let url = self.retrieve_url(); + let access_token = token.bearer_token(); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::AUTHORIZATION, + http::header::HeaderValue::from_str(&access_token.clone()).unwrap(), + ); + headers.insert( + reqwest::header::CONTENT_TYPE, + http::header::HeaderValue::from_static("application/json"), + ); + + 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 APIResponse + let s = response.json::>().await; + match s { + // + Ok(parsed) => { + songs = parsed; + } + Err(_) => println!("Hm, the response didn't match the shape we expected."), + }; + } + reqwest::StatusCode::UNAUTHORIZED => { + println!("Need to grab a new token"); + } + other => { + panic!("Uh oh! Something unexpected happened: {:?}", other); + } + } + + return Ok(songs); + } + + fn retrieve_url(&self) -> String { + let api = &self.api; + let mut url: String = String::from(&api.url); + url += &String::from("api/"); + url += &String::from(&api.version); + url += &String::from("/"); + url += &String::from(&api.endpoint); + url += &String::from("/"); + + return url; + } +} diff --git a/src/syncers/upload.rs b/src/syncers/upload.rs new file mode 100644 index 0000000..bb8b65b --- /dev/null +++ b/src/syncers/upload.rs @@ -0,0 +1,195 @@ +use std::default::Default; + +use http::HeaderMap; +use http::HeaderValue; +use reqwest; +use reqwest::multipart::Form; +use serde::{Deserialize, Serialize}; + +use crate::constants; +use crate::models; + +pub struct Upload { + pub api: models::api::API, +} + +#[derive(Debug, Deserialize, Serialize)] +struct Song { + title: String, + album: String, + artist: String, + album_artist: String, + year: i32, + genre: String, + duration: i32, + track: i32, + track_count: i32, + disc: i32, + disc_count: i32, + #[serde(skip_serializing)] + songpath: String, +} + +impl Song { + pub fn to_metadata_json(&self) -> Result { + return serde_json::to_string_pretty(&self); + } +} + +impl Default for Upload { + fn default() -> Self { + Upload { + api: models::api::API::default(), + } + } +} + +impl Upload { + pub async fn upload_song_with_metadata( + &mut self, + token: &models::token::Token, + song: &models::song::Song, + cover: &models::song::CoverArt, + album: &models::song::Album, + ) -> Result { + self.api.endpoint = String::from("song/data/upload/with/data"); + let url = self.retrieve_url(); + let mut new_song = self.initialize_song(&song, &album); + new_song.songpath = song.song_path(); + let access_token = token.bearer_token(); + + if url.is_empty() { + println!("Url is empty"); + } + + println!("Url: {}", url); + println!("Token: {}", access_token); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::AUTHORIZATION, + HeaderValue::from_str(&access_token.clone()).unwrap(), + ); + headers.insert(reqwest::header::ACCEPT, HeaderValue::from_static("*/*")); + + let form = self.init_form(&new_song, &cover); + let client = reqwest::Client::builder().build().unwrap(); + let response = client + .post(url) + .headers(headers) + .multipart(form) + .send() + .await; + let response_text = response.unwrap(); + + println!("Something was sent"); + println!("{:?}", response_text); + + return Ok(response_text); + } + + fn _initialize_form( + &self, + song_raw_data: Vec, + cover_raw_data: Vec, + song_detail: String, + ) -> Form { + let mut headers = HeaderMap::new(); + headers.insert( + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("application/octet-stream"), + ); + + let file = reqwest::multipart::Part::bytes(song_raw_data).headers(headers); + let mut headers_i = HeaderMap::new(); + headers_i.insert( + http::header::CONTENT_TYPE, + http::HeaderValue::from_static("image/jpeg"), + ); + + let cover = reqwest::multipart::Part::bytes(cover_raw_data).headers(headers_i); + + let mut song_filename = String::from("audio"); + song_filename += constants::file_extensions::WAV_FILE_EXTENSION; + let mut cover_filename = String::from("cover"); + cover_filename += constants::file_extensions::JPG_FILE_EXTENSION; + + return reqwest::multipart::Form::new() + .part("cover", cover.file_name(cover_filename)) + .text("metadata", song_detail) + .part("file", file.file_name(song_filename)); + } + + fn init_form(&self, song: &Song, cover: &models::song::CoverArt) -> reqwest::multipart::Form { + let songpath = song.songpath.clone(); + let coverpath = cover.path.clone().unwrap(); + let song_detail = song.to_metadata_json().unwrap(); + + println!("\n{}\n", song_detail); + + let mut song_filename = String::from("audio"); + song_filename += constants::file_extensions::WAV_FILE_EXTENSION; + let mut cover_filename = String::from("cover"); + cover_filename += constants::file_extensions::JPG_FILE_EXTENSION; + + let form = reqwest::multipart::Form::new() + .part( + "file", + reqwest::multipart::Part::bytes(std::fs::read(songpath).unwrap()) + .file_name(song_filename), + ) + .part( + "cover", + reqwest::multipart::Part::bytes(std::fs::read(coverpath).unwrap()) + .file_name(cover_filename), + ) + .text("metadata", song_detail); + + return form; + } + + pub fn set_api(&mut self, host: &String) { + let mut api = models::api::API::default(); + api.url = host.clone(); + api.version = String::from("v1"); + self.api = api; + } + + fn retrieve_url(&self) -> String { + let api = &self.api; + let mut buffer = api.url.clone(); + let count = buffer.len(); + + if buffer.chars().nth(count - 1) != Some('/') { + buffer += "/"; + } + + let mut url: String = String::from(&buffer); + url += &String::from("api/"); + url += &String::from(&api.version); + url += &String::from("/"); + url += &String::from(&api.endpoint); + + return url; + } + + fn initialize_song(&self, song: &models::song::Song, album: &models::song::Album) -> Song { + let dur = song.duration.clone().unwrap(); + println!("Duration: {}", dur); + + return Song { + title: String::from(&song.title.clone().unwrap()), + album: album.title.clone(), + artist: String::from(&song.artist.clone().unwrap().clone()), + album_artist: album.album_artist.clone(), + year: album.year.clone(), + genre: album.genre.clone(), + duration: f64::round(dur) as i32, + track: (song.track.clone().unwrap()), + track_count: album.track_count.clone(), + disc: song.disc.clone().unwrap(), + disc_count: album.disc_count.clone(), + songpath: String::new(), + }; + } +} diff --git a/src/utilities/checks.rs b/src/utilities/checks.rs new file mode 100644 index 0000000..96537ad --- /dev/null +++ b/src/utilities/checks.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct Checks {} + +impl Checks { + pub fn is_numeric(text: &String) -> bool { + text.parse::().is_ok() + } + + pub fn index_of_item_in_container(container: &String, item: &char, func: F) -> i32 + where + F: Fn(&char, &char) -> bool, + { + let mut index = -1; + + for c in container.chars() { + if func(&c, item) { + index += 1; + break; + } + + index += 1; + } + + return index; + } +} diff --git a/src/utilities/mod.rs b/src/utilities/mod.rs new file mode 100644 index 0000000..f6b5329 --- /dev/null +++ b/src/utilities/mod.rs @@ -0,0 +1 @@ +pub mod checks; diff --git a/vcpkg b/vcpkg deleted file mode 160000 index 9d47b24..0000000 --- a/vcpkg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9d47b24eacbd1cd94f139457ef6cd35e5d92cc84 diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index 8d277db..0000000 --- a/vcpkg.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "dependencies": [ - "nlohmann-json", - "curl", - "openssl", - "cpr" - ] -}