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" - ] -}