55 Commits

Author SHA1 Message Date
KD 24e066d8d9 Merge pull request #43 from kdeng00/version_bump
Version bump
2025-03-26 19:09:00 -04:00
KD ab08792ff4 Updated dependencies (#42)
* Updated dependencies

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

* Cleanup

* Added test and moved test files

* Cleanup

* Removed test mod file

* Tests reorganizing

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

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

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

* Updated icarus-model

* Saving changes

* Code formatting

* Removing code

* Updated icarus-model

* Updated token secret

* Added host

* Fix build issue

* Fix build warnings

* Saving changes

* Saving changes

* Refactoring

* Migrating over icarus-model coverart

* Removed song module

* Another one

* Meta single upload is functional

* Cleanup

* More cleanup

* Added test files (#37)

* Added test files

Need to add a coverarg image file

* Updated test album file

* Added coverart

* Uploading meta is operational

* Code cleanup

* Moved function

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

* Code formatting

* More changes

* Moved to icarus-models::user

* Some refactoring

* Updated icarus-models

* Removing constants

* Replaced with icarus-models constants

* Formatting

* Switched to constants from icarus-models:

* Replaced Token with AccessToken from icarus-models

* Updated icarus-models

* Formatting

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

* Updated workflow

* Added ci workflow for ssh keys

* More changes

* More changes. again

* More changes. again.agg

* Another one

* Using secrets

* Forgot dereference

* Added variables

* Checking regular variable

* Updating

* Repository secret

* Checking size

* Another one

* Another one

* Another one

* Another one

* Another one

* Cargo file fix

* Another one

* Reorder

* Addded another step

* Change

* Another step

* Remove step

* Remove step

* Move on up

* Remove

* Cleanup

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

* Updated serde packages

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

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

* Create rust-macos-latest

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

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

* Added gitignore

* Updated gitignore

* Added rust code

* Added modules

* Fixed build issues

* rustfmt

* Updated models

* Added parsers module

* Updated utilities module

* Updated main.rs

* Updated api_parser and song model

* Updated icarus_action model

* Updated structs in managers module

* Updated main.rs

* More changes

* Saving changes

* Added new struct and methods

* More changes. Not functional yet

* Changed name of the project

* Removed cmake workflow

* Adding rust workflow

* Adding code to the syncers module

* Cargo formatting

* Continuing work

* Adding code to the syncers module

* Added dependencies

* Adding more code to the syncers module

* Added more code to the syncers module

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

* Added more code to the managers module

* Added code to request a token

* Cargo formatting

* Adding more code to multi target upload

* Adding more multi-target upload code and formatted code

* Added more multi-upload code

* cargo fmt

* Got uploading functional and cargo fmt-ing

* Uploading multi target works, but is buggy

Not all of the songs are being uploaded

* Added single target upload

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

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

* Fixed multi target upload

* Can now retrieve songs

* Downloading functionality is working

* Delete functionality is operational

* Updated README

* More cleanup

* Added test
2024-06-15 12:22:17 -04:00
Kun Deng 5e9e7bc46a Merge pull request #20 from kdeng00/vcpkg_building
Adding manifest file
2023-08-04 21:05:35 -04:00
kdeng00 5d02c47b86 Workflow fix 2023-08-04 21:03:02 -04:00
kdeng00 7abf5d5db2 Indentation issue 2023-08-04 21:01:15 -04:00
kdeng00 42dcbb87ed Adding build step to workflow 2023-08-04 20:59:43 -04:00
kdeng00 31b633e5f8 Cleanup 2023-08-04 20:56:51 -04:00
kdeng00 8788106352 Workflow changes 2023-08-04 20:51:23 -04:00
kdeng00 9957a7c6eb Updated workflow 2023-08-04 20:45:54 -04:00
kdeng00 ae3b18c21f Moved submodule directory
Moved where vcpkg is located to address GitHub Actions build failure
2023-08-04 20:41:47 -04:00
kdeng00 60b1d49ae6 Adding manifest file 2023-08-04 20:39:50 -04:00
kdeng00 1df7258993 Updated CMakeLists.txt
Updated the minimum version of cmake
2023-08-04 20:32:57 -04:00
kdeng00 68aa9f2245 Updated vcpkg
Updating vcpkg to version 2023.07.21
2023-08-04 20:30:35 -04:00
Kun Deng 8df10ed210 Update cmake.yml 2023-04-09 11:11:55 -04:00
Kun Deng e70a38d24b Create .github/workflows/cmake.yml 2023-04-09 11:03:48 -04:00
Kun Deng a593c5099d Merge pull request #19 from kdeng00/update_dependencies
Updated dependency
2023-04-09 10:58:49 -04:00
kdeng00 639bf24c97 Updated dependency
Updated the vcpkg dependency
2023-04-09 10:44:31 -04:00
Kun Deng a3a8084e69 Merge pull request #18 from kdeng00/update_doc
Update doc
2023-04-09 10:42:38 -04:00
kdeng00 bb1e8a42ef Update doc
Updating the README to include the recent releases
2023-04-09 10:39:50 -04:00
kdeng00 bc5112df38 Downgrading dependency
Changing to cmake 3.10.2 as the minimum required version to build the software. Decided to use the latest cmake version for Ubuntu 18.04. I use a rolling distro but want to make it accessible
2023-01-22 19:59:45 -05:00
79 changed files with 1790 additions and 3315 deletions
+22
View File
@@ -0,0 +1,22 @@
name: Rust
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
+39
View File
@@ -0,0 +1,39 @@
name: Rust
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust version
run: rustup install 1.85.0 && rustup default 1.85.0
- name: Debug secret
run: echo "${{ secrets.MYREPO_TOKEN }}" | head -c 10 ; echo "..."
- name: Build
run: |
mkdir -p ~/.ssh
echo "$EXTREPO_KEY" > ~/.ssh/gitlab_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key
ssh-keyscan git.kundeng.us ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLl/OZiKVDxwnyvMxa+rjKvDpKqTxH1GWuGuDPLmENGQMbTVulajZWr9x8Q1cotoJiHZkt7DA5vczcjB/4lwgWA= >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key
cargo build --release
env:
EXTREPO_KEY: ${{ secrets.MYREPO_TOKEN }}
- name: Run tests
run: cargo test --verbose
+7
View File
@@ -1,3 +1,10 @@
/.vs/* /.vs/*
/build /build
/x64 /x64
# Added by cargo
/target
.vscode/
Cargo.lock
-3
View File
@@ -1,3 +0,0 @@
[submodule "3rdparty/vcpkg"]
path = 3rdparty/vcpkg
url = https://github.com/microsoft/vcpkg
-1
Submodule 3rdparty/vcpkg deleted from f93ba152d5
-106
View File
@@ -1,106 +0,0 @@
cmake_minimum_required(VERSION 3.23.3)
set(VCPKG_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/vcpkg)
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)
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "icarus-dm"
version = "0.5.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = { version = "0.3.31" }
http = { version = "1.3.1" }
reqwest = { version = "0.12.15", features = ["json", "blocking", "multipart", "stream"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tokio = { version = "1.44.1", features = ["full"] }
tokio-util = { version = "0.7.14", features = ["codec"] }
icarus-models = { git = "ssh://git@git.kundeng.us/phoenix/icarus-models.git", tag = "v0.1.14" }
-99
View File
@@ -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
-31
View File
@@ -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
-165
View File
@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{12955990-643F-4A99-95E5-4752AFA179B0}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>$(ProjectDir)include;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<IncludePath>$(ProjectDir)include;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<Optimization>Disabled</Optimization>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<TargetMachine>MachineX86</TargetMachine>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<TargetMachine>MachineX86</TargetMachine>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\Main.cpp" />
<ClCompile Include="src\managers\ActionManager.cpp" />
<ClCompile Include="src\managers\CommitManager.cpp" />
<ClCompile Include="src\managers\FileManager.cpp" />
<ClCompile Include="src\managers\TokenManager.cpp" />
<ClCompile Include="src\managers\UserManager.cpp" />
<ClCompile Include="src\models\Song.cpp" />
<ClCompile Include="src\parsers\APIParser.cpp" />
<ClCompile Include="src\syncers\Delete.cpp" />
<ClCompile Include="src\syncers\Download.cpp" />
<ClCompile Include="src\syncers\RetrieveRecords.cpp" />
<ClCompile Include="src\syncers\Upload.cpp" />
<ClCompile Include="src\utilities\Conversions.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\managers\ActionManager.h" />
<ClInclude Include="include\managers\CommitManager.h" />
<ClInclude Include="include\managers\FileManager.h" />
<ClInclude Include="include\managers\TokenManager.h" />
<ClInclude Include="include\managers\UserManager.h" />
<ClInclude Include="include\models\API.h" />
<ClInclude Include="include\models\Flags.h" />
<ClInclude Include="include\models\IcarusAction.h" />
<ClInclude Include="include\models\Song.h" />
<ClInclude Include="include\models\Token.h" />
<ClInclude Include="include\models\UploadForm.h" />
<ClInclude Include="include\models\User.h" />
<ClInclude Include="include\parsers\APIParser.h" />
<ClInclude Include="include\syncers\Delete.h" />
<ClInclude Include="include\syncers\Download.h" />
<ClInclude Include="include\syncers\RetrieveRecords.h" />
<ClInclude Include="include\syncers\SyncerBase.h" />
<ClInclude Include="include\syncers\Upload.h" />
<ClInclude Include="include\utilities\Checks.h" />
<ClInclude Include="include\utilities\Conversions.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="IcarusDownloadManager.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
-120
View File
@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\Main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\managers\ActionManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\managers\CommitManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\managers\FileManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\managers\TokenManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\managers\UserManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\models\Song.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\parsers\APIParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\syncers\Delete.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\syncers\Download.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\syncers\RetrieveRecords.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\syncers\Upload.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\utilities\Conversions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\managers\ActionManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\managers\CommitManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\managers\FileManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\managers\TokenManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\managers\UserManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\API.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\Flags.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\IcarusAction.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\Song.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\Token.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\UploadForm.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\models\User.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\parsers\APIParser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\syncers\Delete.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\syncers\Download.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\syncers\RetrieveRecords.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\syncers\SyncerBase.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\syncers\Upload.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\utilities\Checks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\utilities\Conversions.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LocalDebuggerCommandArguments>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"</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LocalDebuggerCommandArguments>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"</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>
+22 -44
View File
@@ -1,86 +1,67 @@
# IcarusDownloadManager # IcarusDownloadManager
IcarusDownloadManager is a Linux CLI software client application that has the feature of uploading and downloading songs from the [Icarus](https://github.com/kdeng00/Icarus) Music Server. IcarusDownloadManager is a CLI software client application that has the feature of uploading and downloading songs from the [Icarus](https://github.com/kdeng00/Icarus) Music Server.
## Built With ## Built With
* C++ with C++20 features * Rust
* CMake * Cargo
* GCC >= 10 or Visual Studio >= 17 [2022] * futures
* [vcpkg](https://github.com/microsoft/vcpkg) * http
* [json](https://github.com/nlohmann/json) * reqwst
* [openssl](https://github.com/openssl/openssl) * serde
* [curl](https://github.com/curl/curl) * serde_json
* [cpr](https://github.com/libcpr/cpr) * tokio
* tokio-util
### Getting Started ### Getting Started
Clone the repo Clone the repo
``` ```BASH
git clone --recursive https://github.com/kdeng00/IcarusDownloadManager 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: Build the project:
``` ```BASH
cd IcarusDownloadManager cd IcarusDownloadManager
mkdir build cargo build
cd build
cmake ..
cmake --build . --config release -j
``` ```
The program has been built and can be executed by the binary file *icd*. For information on how to use icd, merely execute the program without any command line arguments. The program has been built and can be executed by the binary file *icarus-dm*. For information on how to use icarua-dm, merely execute the program without any command line arguments.
### Downloading Song ### Downloading Song
```BASH ```BASH
icd download -u spacecadet -p stellar40 -h https://icarus.com -b 15 icarus-dm download -u spacecadet -p stellar40 -h https://icarus.com -b 15
```
### Uploading Song
```BASH
icd upload -u spacecadet -p stellar40 -h https://icarus.com -s /path/of/song.mp3
``` ```
### Uploading Song with metadata ### Uploading Song with metadata
```BASH ```BASH
icd upload-meta -u spacecadet -p stellar40 -h https://icarus.com -s /path/of/song.mp3 -t 1 -m /path/to/metadata/config/collection.json -ca /path/to/cover/art/image.png icarus-dm upload-meta -u spacecadet -p stellar40 -h https://icarus.com -s /path/of/song.flac -t 1 -m /path/to/metadata/config/collection.json -ca /path/to/cover/art/image.png
``` ```
### Uploading Song with metadata from directory ### Uploading Song with metadata from directory
```BASH ```BASH
icd upload-meta -u spacecadet -p stellar40 -h https://icarus.com -smca /path/where/songs/and/metadata/exists/ icarus-dm upload-meta -u spacecadet -p stellar40 -h https://icarus.com -smca /path/where/songs/and/metadata/exists/
``` ```
### Retrieving Song in json ### Retrieving Song in json
```Bash ```Bash
icd retrieve -u spacecadet -p stellar40 -h https://icarus.com -rt songs icarus-dm retrieve -u spacecadet -p stellar40 -h https://icarus.com -rt songs
``` ```
### Deleting Song ### Deleting Song
```BASH ```BASH
icd delete -u spacecadet -p stellar40 -h https://icarus.com -D 15 icarus-dm delete -u spacecadet -p stellar40 -h https://icarus.com -D 15
``` ```
@@ -90,17 +71,14 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduc
## Versioning ## 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.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) [v0.2.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.2.0)
[v0.1.2](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.1.2)
[v0.1.1](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/v0.1.1) [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) [v0.1.0](https://github.com/kdeng00/IcarusDownloadManager/releases/tag/0.1.0)
## Authors
* **Kun Deng** - [kdeng00](https://github.com/kdeng00)
See also the list of [contributors](https://github.com/kdeng00/Icarus/graphs/contributors) who participated in this project.
## License ## License
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
-93
View File
@@ -1,93 +0,0 @@
#ifndef ACTIONMANAGER_H_
#define ACTIONMANAGER_H_
#include<algorithm>
#include<string>
#include<string_view>
#include<array>
#include<vector>
#include"Models/Flags.h"
#include"Models/IcarusAction.h"
namespace Managers
{
class ActionManager
{
public:
ActionManager(char**, int);
Models::IcarusAction retrieveIcarusAction() const;
private:
constexpr std::array<const char*, 16> supportedFlags() noexcept
{
constexpr std::array<const char*, 16> allFlags{"-u", "-p", "-t", "-h", "-s",
"-sd", "-sr", "-d", "-D", "-b", "-rt", "-nc",
"-m", "-ca", "-smca", "-t"};
return allFlags;
}
constexpr std::array<const char*, 4> supportedActions() noexcept;
void initialize();
void validateFlags();
// Checks to see if the flag is valid
template<typename Str>
bool isValidFlag(const Str flag)
{
const auto flags = supportedFlags();
const auto i = std::find_if(flags.begin(), flags.end(), [&](const Str &f)
{
return f.compare(flag) == 0 ? true : false;
});
auto result = i != flags.end() ? true : false;
return result;
}
template<typename Str>
bool doesFlagHaveValue(const Str flag)
{
const auto flags = parsedFlags();
auto i = std::find_if(flags.begin(), flags.end(), [&](const Str &f)
{
return f.compare(flag) == 0 ? true : false;
});
if (i != flags.end())
{
if (++i != flags.end() && !isValidFlag<Str>(*i))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
void printAction() noexcept;
void printFlags() noexcept;
std::vector<std::string> parsedFlags();
std::string action;
std::vector<Models::Flags> flags;
char **params;
int paramCount;
};
}
#endif
-220
View File
@@ -1,220 +0,0 @@
#ifndef COMMITMANAGER_H_
#define COMMITMANAGER_H_
#include<map>
#include<iostream>
#include<filesystem>
#include<string>
#include<string_view>
#include"Models/API.h"
#include"Models/IcarusAction.h"
#include"Models/Song.h"
#include"Models/Token.h"
#include"Utilities/Checks.h"
namespace Managers
{
class CommitManager
{
public:
CommitManager(Models::IcarusAction&);
void commitAction();
enum class RetrieveTypes
{
songs
};
// Used for parsing songs from the metadata file
class Album
{
public:
Album() = default;
void printInfo();
std::string album;
std::string albumArtist;
std::string genre;
int year;
int trackCount;
int discCount;
std::vector<Models::Song> songs;
};
private:
enum class ActionValues;
std::map<std::string, ActionValues> mapActions() noexcept;
void deleteSong();
void downloadSong();
void retrieveObjects();
void uploadSong();
Models::Token parseToken(Models::API);
// Uploads a single song. The song is constructed from a metadata file that contains
// information about the album the song is from. Also, the cover art of the song must
// be present.
//
// Expects
// * Song - mp3 file path
// * TrackID - track number to chose from when retrieving metadata. "1" and "1:1" are similar
// * Metadata - Source file containing metadata of the song
// * Cover art - path to image cover art
void uploadSongWithMetadata();
// Expects the song path, trackID, metadata file path, and cover path
void singTargetUpload(const std::string &songPath, const std::string &trackID,
const std::string &metaPath, const std::string &coverPath);
// Expects the source directory that contains songs, a metadata file, and cover image
// Disc and Track is retrieved from the filename if the filename conforms to a standard.
// If not, then the disc and track will default to 1
//
// Standards
// * track01.mp3 - Disc 1, Track 1
// * track05d02.mp3 - Disc 2, Track 5
void multiTargetUpload(const std::string &sourcePath);
// Standards
// * track01.mp3 - Disc 1, Track 1
// * track05d02.mp3 - Disc 2, Track 5
template<typename Song, typename Str>
void initializeDiscAndTrack(Song &song)
{
auto disc = 1;
auto track = 1;
// If 1 go with first standard, if 2 go with the second, if 0 then will default to 1 for disc and track
auto mode = 0;
const Str &songPath = song.songPath;
auto 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<char, Str>(filename, 'd', dl);
auto k = Utilities::Checks::itemIterInContainer<char, Str>(filename, 'k', dl);
auto dot = Utilities::Checks::itemIterInContainer<char, Str>(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: "<<dStr<<" TStr: " << tStr<<"\n";
if (Utilities::Checks::isNumber(tStr))
{
track = std::atoi(tStr.c_str());
}
else if (Utilities::Checks::isNumber(dStr))
{
disc = std::atoi(dStr.c_str());
}
}
break;
}
}
song.disc = disc;
song.track = track;
}
template<typename Song, typename Str>
void parseDiscAndTrack(Song &song, const Str &trackID)
{
auto sep = [](char c, char t) { return c == t; };
auto separator = Utilities::Checks::itemIterInContainer<char, Str>(trackID, ':', sep);
if (separator != trackID.end())
{
auto dStr = Str(trackID.begin(), separator);
auto tStr = Str(++separator, trackID.end());
song.disc = std::atoi(dStr.c_str());
song.track = std::atoi(tStr.c_str());
}
else
{
auto isNumber = Utilities::Checks::isNumber(trackID);
if (isNumber)
{
song.track = std::atoi(trackID.c_str());
}
}
}
// Checks for the no confirm flag. Used when uploading songs from a directory
bool checkForNoConfirm()
{
for (const auto &arg: this->icaAction.flags)
{
if (arg.flag.compare("-nc") == 0)
{
return true;
}
}
return false;
}
Album retrieveMetadata(const std::string_view path);
std::string retrieveFileContent(const std::string_view path);
enum class ActionValues
{
deleteAct,
downloadAct,
retrieveAct,
uploadAct,
UPLOAD_SONG_WITH_METADATA // Uploads the song with metadata, including cover art
};
Models::IcarusAction icaAction;
};
}
#endif
-31
View File
@@ -1,31 +0,0 @@
#ifndef FILEMANAGER_H_
#define FILEMANAGER_H_
#include<string>
namespace Managers
{
class FileManager
{
public:
FileManager();
FileManager(std::string);
void saveFile(std::string);
void modifyFilePath(std::string);
char* retrieveFileBuffer() const;
int retrieveFileBufferLength() const;
private:
void readFile();
std::string filePath;
char* fileBuffer;
bool fileRead;
int fileBufferLength;
};
}
#endif
-25
View File
@@ -1,25 +0,0 @@
#ifndef TOKENMANAGER_H_
#define TOKENMANAGER_H_
#include"Models/API.h"
#include"Models/Token.h"
#include"Models/User.h"
namespace Managers
{
class TokenManager
{
public:
TokenManager(const Models::User&);
TokenManager(const Models::User&, Models::API&);
Models::Token requestToken();
private:
Models::API api;
Models::User user;
};
}
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef USERMANAGER_H_
#define USERMANAGER_H_
#include<iostream>
#include"Models/IcarusAction.h"
#include"Models/User.h"
namespace Managers
{
class UserManager
{
public:
UserManager(Models::User);
UserManager(const Models::IcarusAction);
Models::User retrieveUser() const;
private:
void parseUserFromActions();
Models::User user;
Models::IcarusAction icaAction;
};
}
#endif
-19
View File
@@ -1,19 +0,0 @@
#ifndef API_H_
#define API_H_
#include<string>
namespace Models
{
class API
{
public:
std::string url;
std::string endpoint;
std::string version;
};
}
#endif
-18
View File
@@ -1,18 +0,0 @@
#ifndef FLAGS_H_
#define FLAGS_H_
#include<string>
namespace Models
{
class Flags
{
public:
std::string flag;
std::string value;
};
}
#endif
-53
View File
@@ -1,53 +0,0 @@
#ifndef ICARUSACTION_H_
#define ICARUSACTION_H_
#include<string>
#include<algorithm>
#include<string_view>
#include<vector>
#include<iostream>
#include"Flags.h"
namespace Models
{
class IcarusAction
{
public:
std::string retrieveFlagValue(const std::string_view flag)
{
std::string value;
const auto fg = std::find_if(flags.begin(), flags.end(), [&](Flags f)
{
return f.flag.compare(flag) == 0 ? true : false;
});
if (fg != flags.end())
{
value.assign(fg->value);
}
return value;
}
void print_action_and_flags() noexcept
{
std::cout<<"Action: "<<this->action<<"\n";
std::cout<<"Flag count: "<<this->flags.size()<<"\n";
for (const auto &flag : this->flags)
{
std::cout<<"flag "<<flag.flag<<" value "<<flag.value<<"\n";
}
std::cout<<"\n";
}
std::string action;
std::vector<Flags> flags;
};
}
#endif
-90
View File
@@ -1,90 +0,0 @@
#ifndef SONG_H_
#define SONG_H_
#include <string>
#include <iostream>
#include <sstream>
namespace Models
{
class Song
{
public:
Song() = default;
void printInfo()
{
std::cout<<"Title: "<<this->title<<"\n";
std::cout<<"\n";
}
std::string song_path() noexcept
{
std::stringstream buffer;
buffer << this->directory;
const auto count = this->directory.size();
if (this->directory.at(count - 1) != '/' || this->directory.at(count - 1) != '\\')
{
buffer << "/";
}
buffer << this->filename;
return buffer.str();
}
int generate_filename_from_track()
{
auto result = 0;
std::stringstream buffer;
buffer << "track";
// NOTE: Multiple discs in one directory is not being addressed
if (this->track < 10)
{
buffer << "0";
}
buffer << this->track << ".mp3";
this->filename.assign(buffer.str());
return result;
}
std::string toMetadataJson();
int id;
std::string title;
std::string artist;
std::string album;
std::string genre;
int year;
int duration;
int track;
int disc;
std::string data;
[[deprecated("Use song_path() function instead")]]
std::string songPath;
std::string filename;
std::string directory;
};
class CoverArt
{
public:
int id;
std::string title;
std::string path;
};
}
#endif
-18
View File
@@ -1,18 +0,0 @@
#ifndef TOKEN_H_
#define TOKEN_H_
#include<string>
namespace Models
{
struct Token
{
std::string accessToken;
std::string tokenType;
int expiration;
};
}
#endif
-18
View File
@@ -1,18 +0,0 @@
#ifndef UPLOADFORM_H_
#define UPLOADFORM_H_
#include<string>
namespace Models
{
struct UploadForm
{
std::string url;
std::string filePath;
};
}
#endif
-17
View File
@@ -1,17 +0,0 @@
#ifndef USER_H_
#define USER_H_
#include<string>
namespace Models
{
struct User
{
std::string username;
std::string password;
};
}
#endif
-25
View File
@@ -1,25 +0,0 @@
#ifndef APIPARSER_H_
#define APIPARSER_H_
#include"Models/API.h"
#include"Models/IcarusAction.h"
namespace Parsers
{
class APIParser
{
public:
APIParser(Models::IcarusAction);
Models::API retrieveAPI() const;
private:
void parseAPI();
Models::API api;
Models::IcarusAction icaAct;
};
}
#endif
-25
View File
@@ -1,25 +0,0 @@
#ifndef DELETE_H_
#define DELETE_H_
#include"Models/API.h"
#include"Models/Song.h"
#include"Models/Token.h"
#include"SyncerBase.h"
namespace Syncers
{
class Delete : SyncerBase
{
public:
Delete(Models::API);
void deleteSong(const Models::Token, Models::Song);
private:
std::string retrieveUrl(Models::Song);
};
}
#endif
-33
View File
@@ -1,33 +0,0 @@
#ifndef DOWNLOAD_H_
#define DOWNLOAD_H_
#include<iostream>
#include<string>
#include"Models/API.h"
#include"Models/Song.h"
#include"Models/Token.h"
#include"SyncerBase.h"
namespace Syncers
{
class Download : SyncerBase
{
public:
Download();
Download(Models::API);
Download(std::string);
void downloadSong(const Models::Token token, Models::Song);
private:
std::string retrieveUrl(Models::Song);
std::string downloadFilePath;
void saveSong(Models::Song&);
};
}
#endif
-28
View File
@@ -1,28 +0,0 @@
#ifndef RETRIEVERECORDS_H_
#define RETRIEVERECORDS_H_
#include "Managers/CommitManager.h"
#include "Models/API.h"
#include "Models/Token.h"
#include "Syncers/SyncerBase.h"
namespace Syncers
{
class RetrieveRecords: public SyncerBase
{
public:
RetrieveRecords();
RetrieveRecords(Models::API, Models::Token);
void retrieve(Managers::CommitManager::RetrieveTypes);
private:
void fetchSongs();
Models::API api;
Models::Token token;
};
}
#endif
-29
View File
@@ -1,29 +0,0 @@
#ifndef SYNCERBASE_H_
#define SYNCERBASE_H_
#include<string>
#include"Models/API.h"
namespace Syncers
{
class SyncerBase
{
protected:
Models::API api;
const int OK = 200;
const int UNAUTHORIZED = 401;
const int NOTFOUND = 404;
enum class Result
{
OK = 200,
UNAUTHORIZED = 401,
NOTFOUND = 404
};
};
}
#endif
-55
View File
@@ -1,55 +0,0 @@
#ifndef UPLOAD_H_
#define UPLOAD_H_
#include <filesystem>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "Managers/CommitManager.h"
#include "Managers/FileManager.h"
#include "Models/API.h"
#include "Models/Song.h"
#include "Models/Token.h"
#include "Models/UploadForm.h"
namespace fs = std::filesystem;
namespace Syncers
{
class Upload
{
public:
Upload() = default;
Upload(Models::API api, Models::Token token) : m_token(token), api(api)
{
this->api.endpoint = "song/data";
}
Models::Song uploadSong(Models::Song&);
void uploadSongsFromDirectory(const std::string&, const bool, bool);
void uploadSongWithMetadata(Managers::CommitManager::Album&, Models::Song&, Models::CoverArt&);
private:
Managers::FileManager fMgr;
Models::API api;
Models::Song song;
Models::Token m_token;
std::vector<Models::Song> retrieveAllSongsFromDirectory(const std::string&,
bool);
std::string retrieveUrl();
Models::Song retrieveSongPath(fs::directory_entry&);
void printSongDetails();
void printSongDetails(std::vector<Models::Song>&);
void printJsonData(const nlohmann::json&);
};
}
#endif
-36
View File
@@ -1,36 +0,0 @@
#ifndef ABOUTWINDOW_H_
#define ABOUTWINDOW_H_
#include<memory>
#include<QDialog>
#include<QDockWidget>
#include<QLabel>
#include<QPushButton>
#include<QWidget>
#include"UI/CommonWindow.h"
namespace UI
{
class AboutWindow: public QDialog, public CommonWindow
{
Q_OBJECT
public:
AboutWindow(QWidget* parent=0);
~AboutWindow() = default;
private:
void connections();
void setupWindow();
std::unique_ptr<QLabel> appName;
private slots:
void closeWindow();
};
}
#endif
-33
View File
@@ -1,33 +0,0 @@
#ifndef COMMONWINDOW_H_
#define COMMONWINDOW_H_
#include<memory>
#include<QDialog>
#include<QHBoxLayout>
#include<QVBoxLayout>
#include<QComboBox>
#include<QPushButton>
namespace UI
{
class CommonWindow
{
public:
CommonWindow() = default;
~CommonWindow() = default;
protected:
virtual void connections()=0;
std::unique_ptr<QComboBox> selectionBoxQt;
std::unique_ptr<QPushButton> actionButtonQt;
std::unique_ptr<QVBoxLayout> mainLayoutQt;
std::unique_ptr<QVBoxLayout> subLayoutOneQt;
std::unique_ptr<QVBoxLayout> subLayoutTwoQt;
int windowHeight, windowWidth;
};
}
#endif
-84
View File
@@ -1,84 +0,0 @@
#ifndef MAINWINDOW_H_
#define MAINWINDOW_H_
#include<iostream>
#include<memory>
#include<QAction>
#include<QComboBox>
#include<QDialog>
#include<QDockWidget>
#include<QLabel>
#include<QMenu>
#include<QMenuBar>
#include<QMainWindow>
#include<QPushButton>
#include<QStackedWidget>
#include<QTextEdit>
#include<QWidget>
#include"UI/CommonWindow.h"
#include"UI/AboutWindow.h"
namespace UI
{
class MainWindow: public QMainWindow, public CommonWindow
{
Q_OBJECT
public:
MainWindow();
~MainWindow() = default;
private:
void configureDownloadSection();
void configureUploadSection();
void configureWindowDimensions();
void configureWindowProperties();
void connections();
void createMenus();
void setupMainWidget();
void setupMainWindow();
void setupWindowLists();
std::unique_ptr<QStackedWidget> widgetStack;
std::unique_ptr<QVBoxLayout> stackLayout;
std::unique_ptr<QHBoxLayout> urlPortion;
std::unique_ptr<QHBoxLayout> songPathPortion;
std::unique_ptr<QWidget> mainWidgetQt;
std::unique_ptr<QWidget> uploadSongWidgetQt;
std::unique_ptr<QDockWidget> MainDockWidgetQt;
std::unique_ptr<QComboBox> windowComboBox;
std::unique_ptr<QPushButton> uploadSongQt;
std::unique_ptr<QTextEdit> urlQt;
std::unique_ptr<QTextEdit> sourceFilePathQt;
std::unique_ptr<QLabel> urlLabel;
std::unique_ptr<QLabel> songPath;
std::unique_ptr<QMenu> fileMenuQt;
std::unique_ptr<QMenu> editMenuQt;
std::unique_ptr<QMenu> helpMenuQt;
std::unique_ptr<QAction> closeApplicationQt;
std::unique_ptr<QAction> aboutApplicationQt;
std::unique_ptr<AboutWindow> aboutWindow;
signals:
private slots:
void uploadSong();
void exitApplication();
void displaySoftwareInformation();
void setCurrentIndex(int);
};
}
#endif
-67
View File
@@ -1,67 +0,0 @@
#ifndef CHECKS_H_
#define CHECKS_H_
#include<algorithm>
#include<string>
#include<cstdlib>
#include<ctype.h>
namespace Utilities
{
class Checks
{
public:
Checks() = delete;
static auto isNumber(const std::string &val)
{
return !val.empty() && std::find_if(val.begin(),
val.end(), [](char c)
{
return !std::isdigit(c);
}) == val.end();
}
// Note: Not implemented
template<typename Item, typename Container, typename Func>
static auto itemInContainer(const Container container, const Item item, Func func)
{
auto result = false;
auto i = std::find_if(container.begin(), container.end(), [&](Item i)
{
return func(item, i);
});
if (i != container.end())
{
result = true;
}
return result;
}
template<typename Item, typename Container, typename Func>
static auto itemIterInContainer(const Container &container, const Item &item, Func func)
{
auto result = false;
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
-32
View File
@@ -1,32 +0,0 @@
#ifndef CONVERSIONS_H_
#define CONVERSIONS_H_
#include<memory>
#include<string>
namespace Utilities
{
class Conversions
{
public:
Conversions();
static void toLowerChar(char &c)
{
if (std::isalpha(c))
{
c = std::tolower(c);
}
}
void initializeValues();
template<typename T>
void printValue(T val);
private:
};
}
#endif
-14
View File
@@ -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
-79
View File
@@ -1,79 +0,0 @@
#include<iostream>
#include<string>
#include"Managers/ActionManager.h"
#include"Managers/CommitManager.h"
using std::cin;
using std::cout;
using std::endl;
using std::string;
using Managers::ActionManager;
using Managers::CommitManager;
constexpr static auto IcarusDownloadManager_version()
{
return "v0.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;
}
-118
View File
@@ -1,118 +0,0 @@
#include"Managers/ActionManager.h"
#include<algorithm>
#include<iostream>
#include<utility>
#include<cstring>
using std::cout;
using std::endl;
using std::string;
using std::string_view;
using std::vector;
using Models::Flags;
using Models::IcarusAction;
namespace Managers
{
#pragma region Constructors
ActionManager::ActionManager(char **param, int paramCount) :
params(std::move(param)), paramCount(paramCount)
{
initialize();
}
#pragma endregion
#pragma region Functions
IcarusAction ActionManager::retrieveIcarusAction() const
{
IcarusAction icarusAction;
icarusAction.flags = flags;
icarusAction.action = action;
return icarusAction;
}
void ActionManager::initialize()
{
validateFlags();
action = std::move(string{params[1]});
transform(action.begin(), action.end(),
action.begin(), ::tolower);
}
void ActionManager::validateFlags()
{
cout<<"Validating flags\n";
const auto flagVals = parsedFlags();
for (auto flag = flagVals.begin(); flag != flagVals.end(); ++flag)
{
Flags flg;
cout<<"Value: "<<*flag<<"\n";
if (isValidFlag<string>(*flag) && doesFlagHaveValue<string>(*flag))
{
cout<<"Flag has value\n";
flg.flag = *flag;
flg.value = *(++flag);
}
else if (isValidFlag<string>(*flag))
{
cout<<"Flag does not have a value\n";
flg.flag = *flag;
}
else
{
cout<<"Flag "<<*flag<<" is not valid"<<endl;
exit(1);
}
flags.emplace_back(std::move(flg));
}
}
vector<string> ActionManager::parsedFlags()
{
auto parsed = vector<string>();
for (auto i = 2; i < paramCount; ++i)
{
const std::string flag(std::move(*(params + i)));
parsed.push_back(std::move(flag));
}
return parsed;
}
#pragma region Testing
void ActionManager::printAction() noexcept
{
if (action.empty())
{
printf("Action is empty\n");
}
else
{
cout<<"Action is "<<action<<endl;
}
}
void ActionManager::printFlags() noexcept
{
cout<<"\nPrinting flags..."<<endl;
for (auto flag: flags)
{
cout<<"flag "<<flag.flag<<endl;
cout<<"value "<<flag.value<<endl;
}
}
#pragma endregion
#pragma endregion
}
-457
View File
@@ -1,457 +0,0 @@
#include"Managers/CommitManager.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <filesystem>
#include "nlohmann/json.hpp"
#include "Models/API.h"
#include "Models/Song.h"
#include "Models/Token.h"
#include "Parsers/APIParser.h"
#include "Syncers/Delete.h"
#include "Syncers/Download.h"
#include "Syncers/RetrieveRecords.h"
#include "Syncers/Upload.h"
#include "Managers/TokenManager.h"
#include "Managers/UserManager.h"
using std::cout;
using std::endl;
using std::map;
using std::string;
using Managers::TokenManager;
using Managers::UserManager;
using Models::API;
using Models::Song;
using Models::Token;
using Parsers::APIParser;
using Models::IcarusAction;
using Syncers::Delete;
using Syncers::Download;
using Syncers::RetrieveRecords;
using Syncers::Upload;
namespace filesystem = std::filesystem;
namespace Managers
{
#pragma region Constructors
CommitManager::CommitManager(IcarusAction& icaAct) : icaAction(std::move(icaAct))
{ }
#pragma endregion
#pragma region Functions
void CommitManager::commitAction()
{
auto action = icaAction.action;
cout<<"Commiting "<<action<<" action"<<endl;
switch (mapActions()[action])
{
case ActionValues::deleteAct:
deleteSong();
break;
case ActionValues::downloadAct:
downloadSong();
break;
case ActionValues::retrieveAct:
retrieveObjects();
break;
case ActionValues::uploadAct:
uploadSong();
break;
case ActionValues::UPLOAD_SONG_WITH_METADATA:
uploadSongWithMetadata();
break;
default:
break;
}
}
std::map<std::string, CommitManager::ActionValues>
CommitManager::mapActions() noexcept
{
const std::map<std::string, ActionValues> actions{
{"delete", ActionValues::deleteAct},
{"download", ActionValues::downloadAct},
{"retrieve", ActionValues::retrieveAct},
{"upload", ActionValues::uploadAct},
{"upload-meta", ActionValues::UPLOAD_SONG_WITH_METADATA}
};
return actions;
}
Token CommitManager::parseToken(API api)
{
cout<<"fetching token\n";
UserManager usrMgr{icaAction};
auto user = usrMgr.retrieveUser();
TokenManager tk{user, api};
return tk.requestToken();
}
void CommitManager::deleteSong()
{
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
Song song{};
for (auto arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-D") == 0)
{
song.id = atoi(value.c_str());
}
}
Delete del{api};
cout<<"Deleting song..."<<endl;
del.deleteSong(token, song);
}
void CommitManager::downloadSong()
{
cout<<"Starting downloading process..."<<endl;
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
Song song{};
for (auto arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-d") == 0)
{
song.songPath.assign(arg.value);
}
if (flag.compare("-b") == 0)
{
song.id = atoi(value.c_str());
}
}
Download dnld{api};
cout<<"downloading song"<<endl;
dnld.downloadSong(token, song);
}
void CommitManager::retrieveObjects()
{
cout<<"Starting retrieve process..."<<endl;
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
RetrieveTypes retrieveType;
for (auto arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-rt") == 0)
{
if (value.compare("songs") == 0)
{
retrieveType = RetrieveTypes::songs;
break;
}
}
}
RetrieveRecords songs{api, token};
songs.retrieve(retrieveType);
}
void CommitManager::uploadSong()
{
auto uploadSingleSong = true;
auto recursiveDirectory = false;
const auto noConfirm = checkForNoConfirm();
string songDirectory;
APIParser apiPrs{icaAction};
auto api = apiPrs.retrieveAPI();
auto token = parseToken(api);
Song song;
for (auto& arg : icaAction.flags)
{
auto flag = arg.flag;
auto value = arg.value;
if (flag.compare("-s") == 0)
{
song.songPath.assign(arg.value);
}
else if (flag.compare("-sd") == 0)
{
songDirectory = value;
uploadSingleSong = false;
}
else if (flag.compare("-sr") == 0)
{
songDirectory = value;
uploadSingleSong = false;
recursiveDirectory = true;
}
}
Upload upld{api, token};
if (uploadSingleSong)
{
cout<<"Uploading song..."<<endl;
upld.uploadSong(song);
}
else
{
cout<<"Uploading songs from " << songDirectory << endl;
upld.uploadSongsFromDirectory(songDirectory, noConfirm, recursiveDirectory);
}
}
void CommitManager::uploadSongWithMetadata()
{
cout<<"Uploading single song with metadata\n\n";
// Either the set of "-s", "-m", "-ca", "-t" flags or "-smca" must exist with values
// in order to be valid but not both
const auto songPath = this->icaAction.retrieveFlagValue("-s");
const auto metadataPath = this->icaAction.retrieveFlagValue("-m");
const auto coverPath = this->icaAction.retrieveFlagValue("-ca");
const auto trackID = this->icaAction.retrieveFlagValue("-t");
const auto singleTarget = !songPath.empty() && !metadataPath.empty() &&
!coverPath.empty() && !trackID.empty() ? true : false;
const auto uni = this->icaAction.retrieveFlagValue("-smca");
const auto multiTarget = !uni.empty() ? true : false;
if (singleTarget && multiTarget)
{
cout<<"Cannot upload from source and directory\n";
return;
}
cout<<"Song path: "<<songPath<<"\n";
cout<<"TrackID: "<<trackID<<"\n";
cout<<"Metadata path: "<<metadataPath<<"\n";
cout<<"Cover Art path: "<<coverPath<<"\n";
if (singleTarget)
{
singTargetUpload(songPath, trackID, metadataPath, coverPath);
}
else if (multiTarget)
{
multiTargetUpload(uni);
}
}
void CommitManager::singTargetUpload(const std::string &songPath, const std::string &trackID,
const std::string &metaPath, const std::string &coverPath)
{
APIParser apiPrs(icaAction);
auto api = apiPrs.retrieveAPI();
const auto token = parseToken(api);
auto album = retrieveMetadata(metaPath);
album.printInfo();
Song song;
song.track = 1;
song.disc = 1;
cout<<"TrackID: "<<trackID<<"\n";
parseDiscAndTrack<Song, std::string>(song, trackID);
auto c = [](const Song &songA, const Song &songB) { return songA.track == songB.track && songA.disc == songB.disc; };
auto sng = Utilities::Checks::itemIterInContainer<Song, std::vector<Song>>(album.songs, song, c);
if (sng == album.songs.end())
{
cout<<"Not found with disc "<<song.disc<<" track "<<song.track<<"\n";
std::exit(-1);
}
song = *sng;
const auto p = filesystem::path(songPath);
song.directory = p.parent_path().string();
song.generate_filename_from_track();
Models::CoverArt cover;
cover.title = song.title;
cover.path = coverPath;
Upload up(api, token);
up.uploadSongWithMetadata(album, song, cover);
}
void CommitManager::multiTargetUpload(const std::string &sourcePath)
{
APIParser apiPrs(icaAction);
auto api = apiPrs.retrieveAPI();
const auto token = parseToken(api);
if (!fs::is_directory(sourcePath))
{
cout<<sourcePath<<" is not a directory\n";
std::exit(-1);
}
std::vector<Song> songs;
Models::CoverArt cover;
string metadataPath;
for (auto &p: fs::directory_iterator(sourcePath))
{
const auto &pp = p.path();
const auto stem = pp.stem();
const auto file = pp.filename();
const auto extension = pp.extension();
cout<<"Stem "<<stem<<" Extension "<<extension<<"\n";
auto validImg = [](const std::string& ext)
{
std::vector<string> 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, std::string>(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<std::string>();
album.albumArtist = serialized["album_artist"].get<std::string>();
album.genre = serialized["genre"].get<std::string>();
album.year = serialized["year"].get<int>();
album.trackCount = serialized["track_count"].get<int>();
album.discCount = serialized["disc_count"].get<int>();
album.songs.reserve(album.trackCount);
for (auto &j : serialized["tracks"])
{
Song song;
song.title = j["title"].get<std::string>();
song.track = j["track"].get<int>();
song.disc = j["disc"].get<int>();
song.artist = j["artist"].get<std::string>();
song.album = album.album;
song.year = album.year;
song.genre = album.genre;
song.generate_filename_from_track();
const auto p = fs::path(path);
song.directory = p.parent_path().string();
album.songs.push_back(song);
}
return album;
}
string CommitManager::retrieveFileContent(const std::string_view path)
{
string path_str(path);
string value;
std::stringstream buffer;
std::fstream file(path_str, std::ios::in);
buffer<<file.rdbuf();
file.close();
value.assign(buffer.str());
return value;
}
#pragma endregion
void CommitManager::Album::printInfo()
{
std::cout<<"Album: "<<this->album<<"\n";
std::cout<<"Album Artist: "<<this->albumArtist<<"\n";
std::cout<<"Genre: "<<this->genre<<"\n";
std::cout<<"Year: "<<this->year<<"\n";
std::cout<<"Track count: "<<this->trackCount<<"\n";
std::cout<<"Disc count: "<<this->discCount<<"\n";
std::cout<<"\n";
}
#pragma region Functions
}
-73
View File
@@ -1,73 +0,0 @@
#include<iostream>
#include<fstream>
#include"Managers/FileManager.h"
using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
namespace Managers
{
#pragma region Constructors
FileManager::FileManager() {}
FileManager::FileManager(string filePath)
{
this->filePath = filePath;
readFile();
}
#pragma endregion
#pragma region Functions
void FileManager::saveFile(string newFilePath)
{
if (!fileRead)
readFile();
ofstream of{newFilePath, ofstream::binary};
of.write(fileBuffer, fileBufferLength);
of.close();
}
void FileManager::readFile()
{
ifstream is{filePath, ifstream::binary};
if (is)
{
is.seekg (0, is.end);
fileBufferLength = is.tellg();
is.seekg (0, is.beg);
fileBuffer = new char [fileBufferLength];
cout<< "Reading "<<fileBufferLength<<" characters... "<<endl;;
is.read (fileBuffer,fileBufferLength);
if (is)
cout<<"all characters read successfully.";
else
cout<<"error: only "<<is.gcount()<<" could be read";
cout<<endl;
is.close();
fileRead = true;
}
}
void FileManager::modifyFilePath(string filePath)
{
this->filePath = filePath;
}
char* FileManager::retrieveFileBuffer() const
{
return fileBuffer;
}
int FileManager::retrieveFileBufferLength() const { return fileBufferLength; }
#pragma endregion
}
-62
View File
@@ -1,62 +0,0 @@
#include"Managers/TokenManager.h"
#include<iostream>
#include<cpr/cpr.h>
#include<nlohmann/json.hpp>
using std::cout;
using std::endl;
using json = nlohmann::json;
using Managers::TokenManager;
using Models::API;
using Models::Token;
using Models::User;
namespace Managers
{
#pragma region Constructors
TokenManager::TokenManager(const User& user)
{
this->user = user;
}
TokenManager::TokenManager(const User& user, API& api)
{
this->user = user;
this->api = api;
this->api.endpoint = "api/" + api.version
+ "/login";
}
#pragma endregion
#pragma region Functions
Token TokenManager::requestToken()
{
Token token{};
json usrObj;
usrObj["username"] = user.username;
usrObj["password"] = user.password;
cout<<"Sending request for token"<<endl;
auto url = api.url + api.endpoint;
cout<<url<<endl;
auto r = cpr::Post(cpr::Url{url},
cpr::Body{usrObj.dump()},
cpr::Header{{"Content-Type", "application/json"}});
json res = json::parse(r.text);
token.accessToken = res["token"];
token.tokenType = res["token_type"];
cout<<"status code "<<r.status_code<<endl;
return token;
}
#pragma endregion
}
-55
View File
@@ -1,55 +0,0 @@
#include"Managers/UserManager.h"
#include<iostream>
#include<string>
#include<vector>
using std::string;
using std::vector;
using Models::IcarusAction;
using Models::User;
namespace Managers
{
#pragma region Constructors
UserManager::UserManager(User user)
{
this->user = user;
}
UserManager::UserManager(const IcarusAction icaAct)
{
this->icaAction = icaAct;
this->user = User{};
parseUserFromActions();
}
#pragma endregion
#pragma region Functions
User UserManager::retrieveUser() const
{
return user;
}
void UserManager::parseUserFromActions()
{
auto args = icaAction.flags;
for (auto arg : args)
{
auto flag = arg.flag;
if (flag.compare("-u") == 0)
{
user.username = arg.value;
}
if (flag.compare("-p") == 0)
{
user.password = arg.value;
}
}
}
#pragma endregion
}
-22
View File
@@ -1,22 +0,0 @@
#include "Models/Song.h"
#include "nlohmann/json.hpp"
using std::string;
namespace Models
{
string Song::toMetadataJson()
{
nlohmann::json s;
s["title"] = this->title;
s["artist"] = this->artist;
s["album"] = this->album;
s["genre"] = this->genre;
s["year"] = this->year;
s["track"] = this->track;
s["disc"] = this->disc;
return s.dump();
}
}
-53
View File
@@ -1,53 +0,0 @@
#include "Parsers/APIParser.h"
#include <iostream>
using std::cout;
using std::endl;
using Models::API;
using Models::IcarusAction;
namespace Parsers
{
#pragma region Constructors
APIParser::APIParser(IcarusAction icaAct) : icaAct(icaAct)
{
api = API{};
parseAPI();
}
#pragma endregion
#pragma region Functions
API APIParser::retrieveAPI() const
{
return api;
}
void APIParser::parseAPI()
{
auto flags = icaAct.flags;
cout << "Parsing api" << endl;
for (auto i =0; i < flags.size(); ++i)
{
auto arg = flags[i].flag;
auto value = flags[i].value;
if (arg.compare("-h") == 0)
{
api.url = (value[value.size()-1] == '/') ? value : value + "/";
break;
}
}
// NOTE: For now I will hard code
// the api version since I am only
// on version 1
api.version = "v1";
}
#pragma endregion
}
-64
View File
@@ -1,64 +0,0 @@
#include "Syncers/Delete.h"
#include <exception>
#include <iostream>
#include <cpr/cpr.h>
using std::cout;
using std::endl;
using std::exception;
using std::string;
using Models::API;
using Models::Song;
using Models::Token;
namespace Syncers
{
#pragma region Constructors
Delete::Delete(API api)
{
this->api = api;
this->api.endpoint = "song/data";
}
#pragma endregion
#pragma region Functions
void Delete::deleteSong(const Token token, Song song)
{
try
{
auto url = retrieveUrl(song);
string auth{token.tokenType};
auth.append(" " + token.accessToken);
auto r = cpr::Delete(cpr::Url(url),
cpr::Header{{"authorization", auth}});
auto statusCode = r.status_code;
cout<<"Status code "<<statusCode<<endl;
}
catch (exception e)
{
auto msg = e.what();
cout<<msg<<endl;
}
cout<<"Finished"<<endl;
}
string Delete::retrieveUrl(Song song)
{
string url{api.url + "api/" + api.version + "/" +
api.endpoint + "/"};
url.append(std::to_string(song.id));
cout<<"url "<<url<<endl;
return url;
}
#pragma endregion
}
-93
View File
@@ -1,93 +0,0 @@
#include "Syncers/Download.h"
#include <exception>
#include <iostream>
#include <fstream>
#include <cpr/cpr.h>
using std::cout;
using std::endl;
using std::exception;
using std::ofstream;
using std::string;
using Models::API;
using Models::Song;
using Models::Token;
namespace Syncers
{
#pragma region Constructors
Download::Download() { }
Download::Download(API api)
{
this->api = api;
this->api.endpoint = "song/data";
}
Download::Download(string filePath)
{
downloadFilePath = filePath;
}
#pragma endregion
#pragma region Functions
void Download::downloadSong(const Token token, Song song)
{
try
{
string url = retrieveUrl(song);
song.songPath.append("track.mp3");
cout<<"song path "<<song.songPath<<endl;
string auth{token.tokenType};
auth.append(" " + token.accessToken);
auto r = cpr::Get(cpr::Url(url),
cpr::Header{{"Content-type", "audio/mpeg"},
{"Authorization", auth}});
int statusCode = r.status_code;
if (statusCode == OK) {
song.data = r.text;
saveSong(song);
}
cout<<"finsihed with status code "<<statusCode<<endl;
}
catch (exception e)
{
auto msg = e.what();
cout<<msg<<endl;
}
}
string Download::retrieveUrl(Song song)
{
string url{api.url + "api/" + api.version + "/" +
api.endpoint + "/"};
url.append(std::to_string(song.id));
cout<<"url "<<url<<endl;
return url;
}
void Download::saveSong(Song& song)
{
cout<<"\nSaving song to: "<<song.songPath<<endl;
int bufferLength = song.data.length();
const char *data = song.data.c_str();
cout<<"buff length "<<bufferLength<<endl;
ofstream saveSong{song.songPath, std::ios::binary};
saveSong.write(data, bufferLength);
saveSong.close();
}
#pragma endregion
}
-67
View File
@@ -1,67 +0,0 @@
#include "Syncers/RetrieveRecords.h"
#include <iostream>
#include <fstream>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
#include "Utilities/Conversions.h"
using std::cout;
using std::endl;
using std::ofstream;
using Managers::CommitManager;
using Models::API;
using Models::Token;
using Utilities::Conversions;
namespace Syncers
{
#pragma region Constructors
RetrieveRecords::RetrieveRecords() { }
RetrieveRecords::RetrieveRecords(API api, Token token)
: token(token), api(api) { }
#pragma endregion
#pragma region Functions
void RetrieveRecords::retrieve(CommitManager::RetrieveTypes type)
{
switch (type)
{
case CommitManager::RetrieveTypes::songs:
fetchSongs();
break;
default:
break;
}
}
void RetrieveRecords::fetchSongs()
{
cout<<"fetching songs"<<endl;
auto url = api.url + "api/" + api.version + "/" + "song";
auto auth = token.tokenType;
auth.append(" " + token.accessToken);
auto r = cpr::Get(cpr::Url{url},
cpr::Header{{"authorization", auth},
});
if (r.status_code != (int)Result::OK) {
cout<<"something went wrong\n";
cout<<"status code: "<<r.status_code<<endl;
cout<<"message: "<<r.text<<endl;
return;
}
auto songData = nlohmann::json::parse(r.text);
ofstream writeData{};
writeData.open("songs.json");
writeData<<songData.dump(4);
writeData.close();
}
#pragma endregion
}
-243
View File
@@ -1,243 +0,0 @@
#include <iostream>
#include <filesystem>
#include <exception>
#include "cpr/cpr.h"
#include "Syncers/Upload.h"
#include "Utilities/Conversions.h"
using std::cout;
using std::cin;
using std::endl;
using std::exception;
using std::string;
using json = nlohmann::json;
using Managers::FileManager;
using Models::API;
using Models::Song;
using Models::UploadForm;
using namespace cpr;
namespace Syncers
{
#pragma region Constructors
#pragma endregion
#pragma region Functions
Song Upload::uploadSong(Song& song)
{
try
{
auto url = retrieveUrl();
cout<<"url "<<url<<endl;
string auth{this->m_token.tokenType};
auth.append(" " + this->m_token.accessToken);
auto r = cpr::Post(cpr::Url{url},
cpr::Multipart{{"key", "small value"},
{"file", cpr::File{song.songPath}}},
cpr::Header{{"authorization", auth}}
);
cout << "status code: " << r.status_code<< std::endl;
cout << r.text << endl;
auto result = nlohmann::json::parse(r.text);
cout<<"Finished"<<endl;
song.id = result["id"].get<int>();
song.title = result["title"].get<std::string>();
song.artist = result["artist"].get<std::string>();
song.album = result["album"].get<std::string>();
song.genre = result["genre"].get<std::string>();
song.year = result["year"].get<int>();
song.duration = result["duration"].get<int>();
song.track = result["track"].get<int>();
return song;
}
catch (exception& e)
{
auto msg = e.what();
cout<<msg<<endl;
}
return song;
}
void Upload::uploadSongsFromDirectory(const std::string& directory,
const bool noConfirm, bool recursive = false)
{
try
{
auto songs = retrieveAllSongsFromDirectory(directory, recursive);
auto confirmUpload = true;
while (!noConfirm)
{
auto answer = 'a';
cout << "are you sure you want to upload " << songs.size() << " songs? [y/n]";
cin >> answer;
Utilities::Conversions::toLowerChar(answer);
if (answer == 'y' || answer == 'Y')
{
confirmUpload = true;
break;
}
}
cout << "uploading songs\n";
for (auto& song: songs)
{
song = uploadSong(song);
}
}
catch (exception& e)
{
cout<<e.what()<<endl;
}
}
void Upload::uploadSongWithMetadata(Managers::CommitManager::Album &album, Models::Song& song, Models::CoverArt &cover)
{
this->api.endpoint.assign("song/data/upload/with/data");
try
{
auto url = retrieveUrl();
cout << "url " << url << "\n";
string auth(this->m_token.tokenType);
auth.append(" " + this->m_token.accessToken);
nlohmann::json s;
s["title"] = song.title;
s["album"] = album.album;
s["album_artist"] = album.albumArtist;
s["artist"] = song.artist;
s["year"] = album.year;
s["genre"] = album.genre;
s["disc"] = song.disc;
s["track"] = song.track;
s["disc_count"] = album.discCount;
s["track_count"] = album.trackCount;
const auto meta = s.dump();
cout<<"\n\nMeta:\n"<<meta<<"\n";
cout << "Filepath: " << song.song_path() << "\n";
auto multipart = cpr::Multipart{{"cover", cpr::File{cover.path}},
{"metadata", meta},
{"file", cpr::File{song.song_path()}}};
auto r = cpr::Post(cpr::Url{url}, multipart,
cpr::Header{{"authorization", auth}}
);
cout << "status code: " << r.status_code<< std::endl;
cout << r.text << endl;
}
catch (exception &e)
{
auto msg = e.what();
cout<<"Error: "<<msg<<"\n";
}
}
std::vector<Song> Upload::retrieveAllSongsFromDirectory(const std::string& directory,
bool recursive)
{
std::vector<Song> allSongs;
if (recursive)
{
for (auto p: fs::recursive_directory_iterator(directory))
{
auto song = retrieveSongPath(p);
if (!song.songPath.empty())
allSongs.push_back(song);
}
}
else
{
for (auto p: fs::directory_iterator(directory))
{
auto song = retrieveSongPath(p);
if (!song.songPath.empty())
allSongs.push_back(song);
}
}
return allSongs;
}
string Upload::retrieveUrl()
{
const string url{api.url + "api/" + api.version + "/" +
api.endpoint};
return url;
}
Song Upload::retrieveSongPath(fs::directory_entry& dirEntry)
{
constexpr auto mp3Ext = ".mp3";
Song song;
if (fs::is_regular_file(dirEntry.path()))
{
const auto ext = dirEntry.path().extension().string();
if (ext.compare(mp3Ext) == 0)
{
cout << "found mp3 file" << endl;
song.songPath = dirEntry.path().string();
}
}
return song;
}
#pragma region Testing
void Upload::printSongDetails(std::vector<Song>& songs)
{
for (auto& song: songs)
{
cout<<"Song details: "<<endl;
cout<<"Id: "<<song.id<<endl;
cout<<"Title: "<<song.title<<endl;
cout<<"Artist: "<<song.artist<<endl;
cout<<"Album: "<<song.album<<endl;
cout<<"Genre: "<<song.genre<<endl;
cout<<"Year: "<<song.year<<endl;
cout<<"Duration: "<<song.duration<<endl;
cout<<"Path: "<<song.songPath<<endl;
}
}
void Upload::printJsonData(const json& obj)
{
cout<<endl<<endl<<"JSon data: "<<endl;
cout<<"id: "<<obj["id"]<<endl;
cout<<"title: "<<obj["title"]<<endl;
cout<<"artist: "<<obj["artist"]<<endl;
cout<<"album: "<<obj["album"]<<endl;
cout<<"genre: "<<obj["genre"]<<endl;
cout<<"year: "<<obj["year"]<<endl;
cout<<"duration: "<<obj["duration"]<<endl;
cout<<"song_data: "<<obj["song_data"]<<endl;
cout<<endl<<endl;
}
#pragma endregion
#pragma endregion
}
-52
View File
@@ -1,52 +0,0 @@
#include "UI/AboutWindow.h"
using std::unique_ptr;
namespace UI
{
#pragma region Constructors
AboutWindow::AboutWindow(QWidget* parent): QDialog(parent)
{
setupWindow();
}
#pragma endregion
#pragma region Functions
void AboutWindow::setupWindow()
{
windowWidth = 250;
windowHeight = 300;
mainLayoutQt = unique_ptr<QVBoxLayout>{new QVBoxLayout};
appName = unique_ptr<QLabel>{new QLabel(tr("IcarusDownloadManager"))};
actionButtonQt = unique_ptr<QPushButton>{new QPushButton(tr("Close"))};
mainLayoutQt->addWidget(appName.get());
mainLayoutQt->addWidget(actionButtonQt.get());
setFixedWidth(windowWidth);
setFixedHeight(windowHeight);
setLayout(mainLayoutQt.get());
setWindowTitle("About");
connections();
}
void AboutWindow::connections()
{
QObject::connect(actionButtonQt.get(), SIGNAL(clicked()), this,
SLOT(closeWindow()));
}
void AboutWindow::closeWindow()
{
this->hide();
}
#pragma endregion
}
-180
View File
@@ -1,180 +0,0 @@
#include "UI/MainWindow.h"
#include <iostream>
#include <string>
#include "Models/UploadForm.h"
#include "Syncers/Upload.h"
#include "Utilities/Conversions.h"
using std::cout;
using std::endl;
using std::string;
using std::unique_ptr;
using Models::UploadForm;
using Syncers::Upload;
namespace UI
{
#pragma region Constructors
MainWindow::MainWindow()
{
setupMainWindow();
aboutWindow = unique_ptr<AboutWindow>{new AboutWindow};
}
#pragma endregion
#pragma region Functions
void MainWindow::configureDownloadSection()
{
}
void MainWindow::configureUploadSection()
{
uploadSongQt = unique_ptr<QPushButton>{new QPushButton(tr("Upload"))};
urlQt = unique_ptr<QTextEdit>{new QTextEdit()};
sourceFilePathQt = unique_ptr<QTextEdit>{new QTextEdit()};
urlLabel = unique_ptr<QLabel>{new QLabel(tr("URL"))};
songPath = unique_ptr<QLabel>{new QLabel(tr("Song Path"))};
urlPortion = unique_ptr<QHBoxLayout>{new QHBoxLayout};
songPathPortion = unique_ptr<QHBoxLayout>{new QHBoxLayout};
urlPortion.get()->addWidget(urlLabel.get());
urlPortion.get()->addWidget(urlQt.get());
songPathPortion->addWidget(songPath.get());
songPathPortion->addWidget(sourceFilePathQt.get());
subLayoutOneQt = unique_ptr<QVBoxLayout>{new QVBoxLayout};
subLayoutOneQt.get()->addLayout(urlPortion.get());
subLayoutOneQt->addLayout(songPathPortion.get());
mainLayoutQt.get()->addLayout(subLayoutOneQt.get());
}
void MainWindow::configureWindowDimensions()
{
windowWidth = 450;
windowHeight = 450;
}
void MainWindow::configureWindowProperties()
{
setWindowTitle("IcarusDownloadManager");
setFixedHeight(windowHeight);
setFixedWidth(windowWidth);
}
void MainWindow::connections()
{
QObject::connect(uploadSongQt.get(), SIGNAL(clicked()), this, SLOT(uploadSong()));
QObject::connect(closeApplicationQt.get(), SIGNAL(triggered()), this,
SLOT(exitApplication()));
QObject::connect(aboutApplicationQt.get(), SIGNAL(triggered()), this,
SLOT(displaySoftwareInformation()));
QObject::connect(windowComboBox.get(), SIGNAL(activated(int)),
this, SLOT(setCurrentIndex(int)));
}
void MainWindow::createMenus()
{
fileMenuQt = unique_ptr<QMenu>{menuBar()->addMenu(tr("File"))};
editMenuQt = unique_ptr<QMenu>{menuBar()->addMenu(tr("Edit"))};
helpMenuQt = unique_ptr<QMenu>{menuBar()->addMenu(tr("Help"))};
closeApplicationQt = unique_ptr<QAction>{new QAction(new QObject(nullptr))};
closeApplicationQt->setText("Exit Application");
aboutApplicationQt = unique_ptr<QAction>{new QAction(new QObject(nullptr))};
aboutApplicationQt->setText("About");
fileMenuQt->addAction(closeApplicationQt.get());
helpMenuQt->addAction(aboutApplicationQt.get());
}
void MainWindow::setupMainWidget()
{
mainWidgetQt = unique_ptr<QWidget>{new QWidget};
windowComboBox = unique_ptr<QComboBox>{new QComboBox};
setupWindowLists();
stackLayout = unique_ptr<QVBoxLayout>{new QVBoxLayout};
stackLayout->addWidget(windowComboBox.get());
uploadSongWidgetQt = unique_ptr<QWidget>{new QWidget};
uploadSongWidgetQt->setLayout(mainLayoutQt.get());
stackLayout->addWidget(uploadSongWidgetQt.get());
mainWidgetQt->setLayout(stackLayout.get());
}
void MainWindow::setupMainWindow()
{
configureWindowDimensions();
mainLayoutQt = unique_ptr<QVBoxLayout>{new QVBoxLayout};
widgetStack = unique_ptr<QStackedWidget>{new QStackedWidget};
configureUploadSection();
setupMainWidget();
widgetStack->addWidget(mainWidgetQt.get());
MainDockWidgetQt = unique_ptr<QDockWidget>{new QDockWidget};
MainDockWidgetQt.get()->setWindowTitle(tr("Music Manager"));
MainDockWidgetQt->setWidget(widgetStack.get());
MainDockWidgetQt.get()->setFeatures(QDockWidget::NoDockWidgetFeatures);
setCentralWidget(MainDockWidgetQt.get());
createMenus();
configureWindowProperties();
connections();
}
void MainWindow::setupWindowLists()
{
windowComboBox->addItem(tr("Upload song"));
windowComboBox->addItem(tr("Download song"));
windowComboBox->addItem(tr("Display all songs"));
windowComboBox->addItem(tr("Display songs"));
}
void MainWindow::exitApplication()
{
exit(0);
}
void MainWindow::displaySoftwareInformation()
{
aboutWindow->show();
}
void MainWindow::setCurrentIndex(int index)
{
cout<<"index "<<index<<endl;
QString qText = windowComboBox->itemText(index);
auto cnvert = Utilities::Conversions(qText);
auto convertedStr = cnvert.convertQStringToString();
cout<<"item text"<<endl;
}
void MainWindow::uploadSong()
{
uploadSongQt->setEnabled(false);
string url = urlQt->toPlainText().toUtf8().constData();
string filePath = sourceFilePathQt->toPlainText().toUtf8().constData();
cout<<"URL endpoint: "<<url<<endl;
cout<<"Music file path: "<<filePath<<endl;
UploadForm formData{url, filePath};
Upload upld{formData};
upld.uploadSong();
uploadSongQt->setEnabled(true);
}
#pragma endregion
}
-26
View File
@@ -1,26 +0,0 @@
#include "Utilities/Conversions.h"
#include <iostream>
using std::string;
using std::unique_ptr;
namespace Utilities
{
Conversions::Conversions()
{
initializeValues();
}
void Conversions::initializeValues()
{
}
template <typename T>
void Conversions::printValue(T val)
{
std::cout<<"going to print value\n";
std::cout<<val<< "\n";
}
}
+36
View File
@@ -0,0 +1,36 @@
pub fn print_help() {
let msg: String = String::from(
r#"icd [Action] [flag]
Actions
download
upload-meta
retrieve
delete
Flags
Required for all actions
-u username
-p password
-h host
Required for upload with metadata
-s path of song
-t track number
-m metadata filepath
-ca coverart filepath
-scma directory where songs, metadata, and cover art exists and will be uploaded (Optional)
Required for download
-b song id
-d path to download song (Optional)
Required for retrieving records
-rt retrieve type (songs is only accepted)
Required for deleting a song
-D song id"#,
);
println!("{}", msg);
}
+146
View File
@@ -0,0 +1,146 @@
mod help;
mod managers;
mod models;
mod parsers;
mod syncers;
mod utilities;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let args_len = args.len() as i32;
if args_len == 1 {
help::print_help();
utilities::checks::exit_program(-1);
}
println!("Argument count: {}", args_len);
let mut act_mgr = managers::action_managers::ActionManager::default();
act_mgr.set_params(&args);
act_mgr.initialize();
let chosen_act = act_mgr.retrieve_icarus_action();
chosen_act.print_action_and_flags();
let mut cmt_mgr = managers::commit_manager::CommitManager {
ica_action: chosen_act,
};
cmt_mgr.commit_action();
}
#[cfg(test)]
mod tests {
use crate::managers;
#[test]
fn test_song_prep_upload() {
let meta_path = String::from("tests/sample2_tracks/album.json");
if !std::path::Path::new(&meta_path).exists() {
assert!(false, "File does not exists: {:?}", meta_path);
}
match icarus_models::album::collection::parse_album(&meta_path) {
Ok(album) => {
for track in 1..3 {
let directory_path = std::path::Path::new(&meta_path);
let directory: String = directory_path.parent().unwrap().display().to_string();
let filename: String = if track < 10 {
String::from("track0")
+ &track.to_string()
+ icarus_models::constants::DEFAULTMUSICEXTENSION
} else {
String::from("track")
+ &track.to_string()
+ icarus_models::constants::DEFAULTMUSICEXTENSION
};
let disc = 1;
match managers::commit_manager::retrieve_song(
&album, track, disc, &directory, &filename,
) {
Ok(song) => match song.to_data() {
Ok(_) => {
print!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
},
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
}
#[test]
fn test_minimum_action_and_args() {
let args: Vec<String> = vec![
"icarus-dm".to_string(),
"download".to_string(),
"-u".to_string(),
"jamborie".to_string(),
"-p".to_string(),
"somethingsecr3t!".to_string(),
"-h".to_string(),
"https://music-server.com".to_string(),
];
let arg_count = args.len() as i32;
let minimum_arg_count = 8;
assert!(arg_count >= minimum_arg_count);
let mut act_mgr = managers::action_managers::ActionManager {
action: String::new(),
flags: Vec::new(),
params: args,
param_count: arg_count,
};
act_mgr.initialize();
let ica = act_mgr.retrieve_icarus_action();
let action = &ica.action;
let flags = &ica.flags;
assert!(action != "");
assert!(flags.len() > 2);
assert!(
!(action != "download"
&& action != "upload"
&& action != "retrieve"
&& action != "upload-meta")
);
let mut found_count = 0;
let mut flags_already_read = Vec::new();
for flag in flags {
if flags_already_read.contains(&flag.flag) {
continue;
}
if flag.flag == "-u" {
found_count += 1;
flags_already_read.push(flag.flag.clone());
} else if flag.flag == "-p" {
found_count += 1;
flags_already_read.push(flag.flag.clone());
} else if flag.flag == "-h" {
found_count += 1;
flags_already_read.push(flag.flag.clone());
}
}
let all_flags_found = found_count == 3;
assert_eq!(found_count, 3, "Three flags are required: -u, -p, -h");
assert!(all_flags_found, "All flags have not been found");
}
}
+168
View File
@@ -0,0 +1,168 @@
use serde::{Deserialize, Serialize};
use crate::{models, utilities};
#[derive(Debug, Deserialize, Serialize)]
pub struct ActionManager {
pub action: String,
pub flags: Vec<models::flags::Flags>,
pub params: Vec<String>,
pub param_count: i32,
}
impl Default for ActionManager {
fn default() -> Self {
ActionManager {
action: String::new(),
flags: Vec::new(),
params: Vec::new(),
param_count: -1,
}
}
}
impl ActionManager {
pub fn retrieve_icarus_action(&self) -> models::icarus_action::IcarusAction {
return models::icarus_action::IcarusAction {
flags: self.flags.clone(),
action: String::from(&self.action),
};
}
fn supported_flags(&self) -> Vec<String> {
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();
}
pub fn set_params(&mut self, args: &Vec<String>) {
self.params = args.clone();
self.param_count = self.params.len() as i32;
}
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);
utilities::checks::exit_program(-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<String> {
let mut parsed: Vec<String> = Vec::new();
for i in 2..self.params.len() {
let flag = String::from(&self.params[i]);
parsed.push(flag);
}
return parsed;
}
}
+619
View File
@@ -0,0 +1,619 @@
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::{self};
use crate::parsers;
use crate::syncers;
use crate::utilities;
#[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,
}
#[derive(Clone, Debug)]
enum En {
ImageFile,
SongFile,
MetadataFile,
Other,
}
pub fn retrieve_song(
album: &icarus_models::album::collection::Album,
track: i32,
disc: i32,
directory: &String,
filename: &String,
) -> Result<icarus_models::song::Song> {
let mut found = false;
let mut song = icarus_models::song::Song::default();
for song_i in &album.tracks {
if song_i.track == track && song_i.disc == disc {
let track = song_i.clone();
song.album = album.title.clone();
song.album_artist = album.artist.clone();
song.artist = track.artist.clone();
song.audio_type = String::from(icarus_models::constants::DEFAULTMUSICEXTENSION);
song.disc = track.disc.clone();
song.disc_count = album.disc_count.clone();
song.duration = track.duration as i32;
song.genre = album.genre.clone();
song.title = track.title.clone();
song.year = album.year.clone();
song.track = track.track.clone();
song.track_count = album.track_count.clone();
song.directory = directory.clone();
song.filename = filename.clone();
found = true;
break;
}
}
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<String, ActionValues>,
action: &String,
) -> ActionValues {
for (key, act) in actions {
if key == action {
return act.clone();
}
}
return ActionValues::None;
}
fn map_actions(&self) -> HashMap<String, ActionValues> {
let actions: HashMap<String, ActionValues> = HashMap::from([
("download".to_string(), ActionValues::DownloadAct),
("download".to_string(), ActionValues::DownloadAct),
("upload".to_string(), ActionValues::UploadAct),
(
"upload-meta".to_string(),
ActionValues::UploadSongWithMetadata,
),
("retrieve".to_string(), ActionValues::RetrieveAct),
("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 = icarus_models::song::Song::default();
for arg in &self.ica_action.flags {
let flag = &arg.flag;
let value = &arg.value;
if flag == "-D" {
song.id = value.parse::<i32>().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 id: i32 = dwn.parse::<i32>().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);
println!("Message: {}", token.message);
let mut dwn_loader = syncers::download::Download { api: api.clone() };
let mut song = icarus_models::song::Song::default();
song.id = id;
let result_fut = dwn_loader.download_song(&token, &song);
match Runtime::new().unwrap().block_on(result_fut) {
Ok(o) => {
println!("Success");
let filename =
String::from("audio") + icarus_models::constants::DEFAULTMUSICEXTENSION;
let data = o.as_bytes();
let mut file = std::fs::File::create(filename).expect("Failed to save");
file.write_all(&data)
.expect("Failed to save downloaded song");
}
Err(er) => {
println!("Error {:?}", er);
match er {
syncers::download::MyError::Request(error) => {
println!("Error: {:?}", error);
}
syncers::download::MyError::Other(ss) => {
println!("Error: {:?}", ss);
}
}
}
}
}
fn retrieve_object(&self) {
println!("Retrieving 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);
match Runtime::new().unwrap().block_on(result_fut) {
Ok(o) => {
for song in o {
println!("Title: {:?}", song.title);
println!("Artist: {:?}", song.artist);
println!("Album: {:?}", song.album);
println!("Year: {:?}", song.year);
}
}
Err(er) => {
println!("Error: {:?}", er);
}
}
}
fn upload_song(&self) {
println!("Deleting song");
panic!("Not supported");
}
fn parse_token(&self, api: &models::api::API) -> icarus_models::token::AccessToken {
println!("Fetching token");
let mut usr_mgr: managers::user_manager::UserManager =
managers::user_manager::UserManager {
user: icarus_models::user::User::default(),
ica_action: self.ica_action.clone(),
};
usr_mgr.parse_user_from_actions();
let usr = usr_mgr.retrieve_user();
let mut tok_mgr = managers::token_manager::TokenManager {
user: usr,
api: api.clone(),
};
tok_mgr.init();
let token = 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);
println!("Token: {:?}", token.token);
let song_file = std::path::Path::new(&songpath);
if !song_file.exists() {
println!("Song file does not exist");
panic!("Error");
}
let mut cover_art = icarus_models::coverart::CoverArt {
id: 0,
title: String::new(),
path: cover_path.clone(),
data: Vec::new(),
};
let file_name = std::ffi::OsString::from(&song_file.file_name().unwrap());
match self.find_file_extension(&file_name) {
En::SongFile => match utilities::string::o_to_string(&file_name) {
Ok(s) => {
println!("file name: {:?}", file_name);
match icarus_models::album::collection::parse_album(&meta_path) {
Ok(album) => {
let filename = s.clone();
let directory = song_file.parent().unwrap().display().to_string();
let trck = i32::from_str(track_id).unwrap();
let mut s =
retrieve_song(&album, trck, 1, &directory, &filename).unwrap();
println!("Directory: {:?}", s.directory);
println!("Filename: {:?}", s.filename);
println!("Path: {:?}", s.song_path());
s.data = s.to_data().unwrap();
cover_art.data = 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);
match Runtime::new().unwrap().block_on(res) {
Ok(o) => {
println!("Successfully sent {:?}", o);
Ok(())
}
Err(er) => {
println!("Some error {:?}", er);
Err(std::io::Error::new(
std::io::ErrorKind::Other,
er.to_string(),
))
}
}
}
Err(err) => {
println!("Error: {:?}", err);
Err(std::io::Error::new(
std::io::ErrorKind::Other,
err.to_string(),
))
}
}
}
Err(er) => {
println!("Error: {:?}", er);
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
er.to_string(),
));
}
},
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"No sutitable file found".to_owned(),
));
}
}
}
fn get_songs(
&self,
metadata_path: &String,
source_directory: &String,
) -> Result<Vec<icarus_models::song::Song>> {
match icarus_models::album::collection::parse_album(metadata_path) {
Ok(albums) => {
let mut songs: Vec<icarus_models::song::Song> = Vec::new();
for track in &albums.tracks {
let filename = if track.track < 10 {
"track0".to_owned()
+ &track.track.to_string()
+ icarus_models::constants::DEFAULTMUSICEXTENSION
} else {
"track".to_owned()
+ &track.track.to_string()
+ icarus_models::constants::DEFAULTMUSICEXTENSION
};
songs.push(icarus_models::song::Song {
id: -1,
title: track.title.clone(),
artist: track.artist.clone(),
disc: track.disc.clone(),
track: track.track.clone(),
duration: track.duration.clone() as i32,
year: albums.year.clone(),
album_artist: albums.artist.clone(),
genre: albums.genre.clone(),
disc_count: albums.disc_count.clone(),
track_count: albums.track_count.clone(),
album: albums.title.clone(),
audio_type: String::from("FLAC"),
directory: source_directory.clone(),
filename: filename,
user_id: -1,
data: Vec::new(),
date_created: String::new(),
});
}
Ok(songs)
}
Err(_) => Ok(Vec::new()),
}
}
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 = icarus_models::coverart::CoverArt {
id: 0,
title: String::new(),
path: match self.get_cover_art_path(&sourcepath) {
Ok(o) => o,
Err(_) => String::new(),
},
data: Vec::new(),
};
let metadatapath = match self.get_metadata_path(&sourcepath) {
Ok(o) => o,
Err(_) => String::new(),
};
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 = cover_art.to_data().unwrap();
println!("");
match self.get_songs(&metadatapath, &sourcepath) {
Ok(sngs) => {
for song in sngs {
match Runtime::new()
.unwrap()
.block_on(up.upload_song_with_metadata(&token, &song, &cover_art))
{
Ok(o) => {
println!("Response: {:?}", o);
}
Err(err) => {
println!("Error: {:?}", err);
}
};
}
}
Err(error) => {
println!("Error: {:?}", error);
}
}
Ok(())
}
fn find_file_extension(&self, file_name: &std::ffi::OsString) -> En {
let file_name_str = Some(file_name.clone().into_string());
match file_name_str {
Some(string) => {
let a = string.unwrap();
let split = a.split(".");
let mut last_index = 0;
for _ in split.clone() {
last_index += 1;
}
let mut extension = String::new();
let mut index = 1;
for word in split {
if index == last_index {
extension = word.to_string();
break;
}
index += 1;
}
if extension == icarus_models::constants::WAVEXTENSION[1..]
|| extension == icarus_models::constants::FLACEXTENSION[1..]
{
return En::SongFile;
} else if extension == "json" {
return En::MetadataFile;
} else if extension == icarus_models::constants::JPGEXTENSION[1..]
|| extension == "jpeg"
|| extension == "png"
{
return En::ImageFile;
}
}
_ => {
return En::Other;
}
}
return En::Other;
}
fn get_cover_art_path(&self, directory_path: &String) -> Result<String> {
for entry in read_dir(std::path::Path::new(directory_path))? {
let entry = entry?;
let file_type = entry.file_type();
let file_name = entry.file_name();
println!("file type: {:?}", file_type);
println!("file name: {:?}", file_name);
match self.find_file_extension(&file_name) {
En::ImageFile => {
let directory_part = directory_path.clone();
let fname = utilities::string::o_to_string(&file_name);
let fullpath = directory_part + "/" + &fname.unwrap();
return Ok(fullpath);
}
_ => {}
}
}
Ok(String::new())
}
fn get_metadata_path(&self, directory_path: &String) -> Result<String> {
for entry in read_dir(std::path::Path::new(directory_path))? {
let entry = entry?;
let file_type = entry.file_type();
let file_name = entry.file_name();
println!("file type: {:?}", file_type);
println!("file name: {:?}", file_name);
match self.find_file_extension(&file_name) {
En::MetadataFile => {
let directory_part = directory_path.clone();
let fname = utilities::string::o_to_string(&file_name);
return Ok(directory_part + "/" + &fname.unwrap());
}
_ => {}
}
}
Ok(String::new())
}
fn _check_for_no_confirm(&self) -> bool {
for flag in self.ica_action.flags.iter() {
if flag.flag == "-nc" {
return true;
}
}
return false;
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod action_managers;
pub mod commit_manager;
pub mod token_manager;
pub mod user_manager;
+79
View File
@@ -0,0 +1,79 @@
use std::default::Default;
use crate::models;
pub struct TokenManager {
pub user: icarus_models::user::User,
pub api: models::api::API,
}
impl Default for TokenManager {
fn default() -> Self {
let mut token = TokenManager {
user: icarus_models::user::User::default(),
api: models::api::API::default(),
};
token.init();
return token;
}
}
impl TokenManager {
pub async fn request_token(&self) -> Result<icarus_models::token::AccessToken, std::io::Error> {
println!("Sending request for a token");
let url = self.retrieve_url();
println!("URL: {}", url);
let mut token = icarus_models::token::AccessToken {
user_id: -1,
username: String::new(),
token: String::new(),
token_type: String::new(),
expiration: -1,
message: String::new(),
};
let client = reqwest::Client::new();
let response = client.post(&url).json(&self.user).send().await.unwrap();
match response.status() {
reqwest::StatusCode::OK => {
// on success, parse our JSON to an APIResponse
let s = response.json::<icarus_models::token::AccessToken>().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);
return url;
}
}
+48
View File
@@ -0,0 +1,48 @@
use std::default::Default;
use serde::{Deserialize, Serialize};
use crate::models::{self};
#[derive(Debug, Deserialize, Serialize)]
pub struct UserManager {
pub user: icarus_models::user::User,
pub ica_action: models::icarus_action::IcarusAction,
}
impl Default for UserManager {
fn default() -> Self {
UserManager {
user: icarus_models::user::User::default(),
ica_action: models::icarus_action::IcarusAction::default(),
}
}
}
impl UserManager {
pub fn retrieve_user(&self) -> icarus_models::user::User {
return self.user.clone();
}
pub fn parse_user_from_actions(&mut self) {
let args = &self.ica_action.flags;
// Quit the loop when two are found
let mut amt: i32 = 0;
for arg in args {
let flag = &arg.flag;
if flag == "-u" {
self.user.username = String::from(&arg.value);
amt += 1;
} else if flag == "-p" {
self.user.password = String::from(&arg.value);
amt += 1;
}
if amt == 2 {
break;
}
}
}
}
+20
View File
@@ -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(),
}
}
}
+18
View File
@@ -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(),
}
}
}
+44
View File
@@ -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<models::flags::Flags>,
}
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);
}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod api;
pub mod flags;
pub mod icarus_action;
pub mod upload_form;
+18
View File
@@ -0,0 +1,18 @@
use std::default::Default;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct UploadForm {
pub url: Option<String>,
pub filepath: Option<String>,
}
impl Default for UploadForm {
fn default() -> Self {
UploadForm {
url: None,
filepath: None,
}
}
}
+34
View File
@@ -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();
}
}
+1
View File
@@ -0,0 +1 @@
pub mod api_parser;
+71
View File
@@ -0,0 +1,71 @@
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: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
) -> Result<icarus_models::song::Song, std::io::Error> {
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 = icarus_models::song::Song::default();
match response.status() {
reqwest::StatusCode::OK => {
println!("Success!");
let s = response.json::<icarus_models::song::Song>().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: &icarus_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.to_string();
return url;
}
}
+86
View File
@@ -0,0 +1,86 @@
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: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
) -> Result<String, MyError> {
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;
match response {
Ok(rep) => match rep.status() {
reqwest::StatusCode::OK => {
let data = rep.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);
}
},
Err(er) => {
return Err(MyError::Request(er));
}
}
return Err(MyError::Other(String::from("Error downloading")));
}
fn retrieve_url(&self, song: &icarus_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.to_string();
return url;
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod delete;
pub mod download;
pub mod retrieve_records;
pub mod upload;
+80
View File
@@ -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: &icarus_models::token::AccessToken,
) -> Result<Vec<icarus_models::song::Song>, Error> {
self.api.endpoint = String::from("song");
let mut songs: Vec<icarus_models::song::Song> = 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::<Vec<icarus_models::song::Song>>().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;
}
}
+127
View File
@@ -0,0 +1,127 @@
use std::default::Default;
use http::HeaderValue;
use reqwest;
use crate::models;
pub struct Upload {
pub api: models::api::API,
}
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: &icarus_models::token::AccessToken,
song: &icarus_models::song::Song,
cover: &icarus_models::coverart::CoverArt,
) -> Result<reqwest::Response, reqwest::Error> {
self.api.endpoint = String::from("song/data/upload/with/data");
let url = self.retrieve_url();
let access_token = token.bearer_token();
if url.is_empty() {
println!("Url is empty");
}
println!("Url: {}", url);
println!("Token: {}", access_token);
println!("Path: {:?}", song.song_path());
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(&song, &cover);
let client = reqwest::Client::builder().build().unwrap();
match client
.post(url)
.headers(headers)
.multipart(form)
.send()
.await
{
Ok(r) => {
return Ok(r);
}
Err(err) => {
return Err(err);
}
}
}
fn init_form(
&self,
song: &icarus_models::song::Song,
cover: &icarus_models::coverart::CoverArt,
) -> reqwest::multipart::Form {
let songpath = match song.song_path() {
Ok(s) => s,
Err(_) => String::new(),
};
let coverpath = cover.path.clone();
println!("Cover path: {:?}", coverpath);
let song_detail = match song.to_metadata_json(true) {
Ok(s) => s,
Err(_) => String::new(),
};
println!("\n{}\n", song_detail);
let mut song_filename = String::from("audio");
song_filename += icarus_models::constants::DEFAULTMUSICEXTENSION;
let mut cover_filename = String::from("cover");
cover_filename += icarus_models::constants::JPGEXTENSION;
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;
}
}
+32
View File
@@ -0,0 +1,32 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Checks {}
impl Checks {
pub fn _is_numeric(text: &String) -> bool {
text.parse::<f64>().is_ok()
}
pub fn _index_of_item_in_container<F>(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;
}
}
pub fn exit_program(code: i32) {
std::process::exit(code);
}
+2
View File
@@ -0,0 +1,2 @@
pub mod checks;
pub mod string;
+9
View File
@@ -0,0 +1,9 @@
pub fn o_to_string(val: &std::ffi::OsString) -> Result<std::string::String, std::io::Error> {
match val.clone().into_string() {
Ok(value) => Ok(value),
Err(_) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
String::from("Error"),
)),
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"album": "Sample Tracks 2",
"album_artist": "KD",
"genre": "Country",
"year": 2025,
"track_count": 3,
"disc_count": 1,
"tracks": [
{
"title": "Uh huh",
"artist": "KD",
"disc": 1,
"track": 1,
"duration": 32
},
{
"title": "Bold and brash",
"artist": "KD",
"disc": 1,
"track": 2,
"duration": 37
},
{
"title": "How does this work?",
"artist": "KD",
"disc": 1,
"track": 3,
"duration": 46
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

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