From bbe18a9dfd92f6ff66817fe667e1fe4f82684c2b Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 16:28:33 +0900
Subject: [PATCH 01/21] Switch to .NET 7 and Update dependencies
---
.gitignore | 5 ++++-
Mappings/ContractToGalleryResultModelMapping.cs | 2 +-
asuka.csproj | 17 +++++++++--------
3 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/.gitignore b/.gitignore
index e653105..b004b30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+# Mac
+.DS_Store
+
# User-specific files
*.rsuser
*.suo
@@ -359,4 +362,4 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
diff --git a/Mappings/ContractToGalleryResultModelMapping.cs b/Mappings/ContractToGalleryResultModelMapping.cs
index 0e83b6a..8759b54 100644
--- a/Mappings/ContractToGalleryResultModelMapping.cs
+++ b/Mappings/ContractToGalleryResultModelMapping.cs
@@ -7,7 +7,7 @@ public static class ContractToGalleryResultModelMapping
{
public static GalleryResult ToGalleryResult(this GalleryResponse response)
{
- return new()
+ return new GalleryResult
{
Id = response.Id,
MediaId = response.MediaId,
diff --git a/asuka.csproj b/asuka.csproj
index d80cb41..07cec4c 100644
--- a/asuka.csproj
+++ b/asuka.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net7.0
1.2.0.0
1.2.0.0
appicon.ico
@@ -17,22 +17,23 @@
AppIcon.png
D:\Code\Projects\asuka\LICENSE
+ 11
-
-
-
-
-
-
+
+
+
+
+
+
-
+
From 2650f0a8e24eabcf77cfb9699bbeea85c177fdad Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 19:45:15 +0900
Subject: [PATCH 02/21] Refactor services and components into proper folder
structure
---
.../Options}/ConfigureOptions.cs | 0
.../Options}/FileCommandOptions.cs | 32 +--
.../Options}/GetOptions.cs | 38 +--
.../Options}/ICommonOptions.cs | 44 +--
.../Options}/IRequiresInputOption.cs | 22 +-
.../Options}/RandomOptions.cs | 24 +-
.../Options}/RecommendOptions.cs | 24 +-
.../Options}/SearchOptions.cs | 90 +++---
.../Parsers}/ConfigureCommand.cs | 0
.../Parsers}/FileCommandService.cs | 178 ++++++------
.../Parsers}/GetCommandService.cs | 112 ++++----
.../Parsers}/IConfigureCommand.cs | 0
.../Parsers}/IFileCommandService.cs | 20 +-
.../Parsers}/IGetCommandService.cs | 20 +-
.../Parsers}/IRandomCommandService.cs | 20 +-
.../Parsers}/IRecommendCommandService.cs | 20 +-
.../Parsers}/ISearchCommandService.cs | 20 +-
.../Parsers}/RandomCommandService.cs | 110 +++----
.../Parsers}/RecommendCommandService.cs | 128 ++++-----
.../Parsers}/SearchCommandService.cs | 168 +++++------
{Api => Core/Api}/IGalleryApi.cs | 42 +--
{Api => Core/Api}/IGalleryImage.cs | 22 +-
{Api => Core/Api}/Queries/SearchQuery.cs | 30 +-
.../Responses/GalleryImageObjectResponse.cs | 20 +-
.../Api}/Responses/GalleryImageResponse.cs | 30 +-
.../Api}/Responses/GalleryListResponse.cs | 20 +-
.../Api}/Responses/GalleryResponse.cs | 56 ++--
.../Api}/Responses/GallerySearchResponse.cs | 24 +-
.../Api}/Responses/GalleryTagResponse.cs | 42 +--
.../Api}/Responses/GalleryTitleResponse.cs | 30 +-
.../Compression}/IPackArchiveToCbz.cs | 0
.../Compression}/PackArchiveToCbz.cs | 0
.../Downloader}/DownloadService.cs | 270 +++++++++---------
.../Downloader}/IDownloadService.cs | 30 +-
.../InternalTypes/DownloadResult.cs | 10 +
.../InternalTypes/FetchImageParameter.cs | 3 +
.../Downloader/InternalTypes/PrepareResult.cs | 8 +
...ontractToGalleryImageResultModelMapping.cs | 68 ++---
.../ContractToGalleryResultModelMapping.cs | 60 ++--
.../ContractToGalleryTagResultModelMapping.cs | 36 +--
.../ContractToUserSelectedModelMapping.cs | 48 ++--
{Models => Core/Models}/GalleryImageResult.cs | 14 +-
{Models => Core/Models}/GalleryResult.cs | 38 +--
{Models => Core/Models}/GalleryTitleResult.cs | 16 +-
{Models => Core/Models}/TachiyomiDetails.cs | 0
.../Requests}/GalleryRequestService.cs | 98 +++----
.../Requests}/IGalleryRequestService.cs | 28 +-
Installers/ConfigureServices.cs | 99 -------
Installers/IInstaller.cs | 6 +
Installers/InstallServices.cs | 32 +++
Installers/InstallerExtension.cs | 6 +
Installers/Refit/ConfigureRefitService.cs | 0
Output/ProgressService/IProgressService.cs | 0
.../ProgressBarConfiguration.cs | 42 +--
Output/ProgressService/ProgressService.cs | 6 +
Output/{ => Writer}/ConsoleWriter.cs | 100 +++----
Output/{ => Writer}/IConsoleWriter.cs | 26 +-
57 files changed, 1201 insertions(+), 1229 deletions(-)
rename {CommandOptions => Commandline/Options}/ConfigureOptions.cs (100%)
rename {CommandOptions => Commandline/Options}/FileCommandOptions.cs (96%)
rename {CommandOptions => Commandline/Options}/GetOptions.cs (96%)
rename {CommandOptions => Commandline/Options}/ICommonOptions.cs (96%)
rename {CommandOptions => Commandline/Options}/IRequiresInputOption.cs (95%)
rename {CommandOptions => Commandline/Options}/RandomOptions.cs (96%)
rename {CommandOptions => Commandline/Options}/RecommendOptions.cs (96%)
rename {CommandOptions => Commandline/Options}/SearchOptions.cs (96%)
rename {CommandParsers => Commandline/Parsers}/ConfigureCommand.cs (100%)
rename {CommandParsers => Commandline/Parsers}/FileCommandService.cs (96%)
rename {CommandParsers => Commandline/Parsers}/GetCommandService.cs (96%)
rename {CommandParsers => Commandline/Parsers}/IConfigureCommand.cs (100%)
rename {CommandParsers => Commandline/Parsers}/IFileCommandService.cs (95%)
rename {CommandParsers => Commandline/Parsers}/IGetCommandService.cs (95%)
rename {CommandParsers => Commandline/Parsers}/IRandomCommandService.cs (95%)
rename {CommandParsers => Commandline/Parsers}/IRecommendCommandService.cs (95%)
rename {CommandParsers => Commandline/Parsers}/ISearchCommandService.cs (95%)
rename {CommandParsers => Commandline/Parsers}/RandomCommandService.cs (96%)
rename {CommandParsers => Commandline/Parsers}/RecommendCommandService.cs (96%)
rename {CommandParsers => Commandline/Parsers}/SearchCommandService.cs (96%)
rename {Api => Core/Api}/IGalleryApi.cs (96%)
rename {Api => Core/Api}/IGalleryImage.cs (95%)
rename {Api => Core/Api}/Queries/SearchQuery.cs (94%)
rename {Api => Core/Api}/Responses/GalleryImageObjectResponse.cs (95%)
rename {Api => Core/Api}/Responses/GalleryImageResponse.cs (94%)
rename {Api => Core/Api}/Responses/GalleryListResponse.cs (95%)
rename {Api => Core/Api}/Responses/GalleryResponse.cs (96%)
rename {Api => Core/Api}/Responses/GallerySearchResponse.cs (95%)
rename {Api => Core/Api}/Responses/GalleryTagResponse.cs (95%)
rename {Api => Core/Api}/Responses/GalleryTitleResponse.cs (95%)
rename {Compression => Core/Compression}/IPackArchiveToCbz.cs (100%)
rename {Compression => Core/Compression}/PackArchiveToCbz.cs (100%)
rename {Downloader => Core/Downloader}/DownloadService.cs (97%)
rename {Downloader => Core/Downloader}/IDownloadService.cs (95%)
create mode 100644 Core/Downloader/InternalTypes/DownloadResult.cs
create mode 100644 Core/Downloader/InternalTypes/FetchImageParameter.cs
create mode 100644 Core/Downloader/InternalTypes/PrepareResult.cs
rename {Mappings => Core/Mappings}/ContractToGalleryImageResultModelMapping.cs (96%)
rename {Mappings => Core/Mappings}/ContractToGalleryResultModelMapping.cs (97%)
rename {Mappings => Core/Mappings}/ContractToGalleryTagResultModelMapping.cs (96%)
rename {Mappings => Core/Mappings}/ContractToUserSelectedModelMapping.cs (96%)
rename {Models => Core/Models}/GalleryImageResult.cs (95%)
rename {Models => Core/Models}/GalleryResult.cs (97%)
rename {Models => Core/Models}/GalleryTitleResult.cs (95%)
rename {Models => Core/Models}/TachiyomiDetails.cs (100%)
rename {Services => Core/Requests}/GalleryRequestService.cs (91%)
rename {Services => Core/Requests}/IGalleryRequestService.cs (86%)
delete mode 100644 Installers/ConfigureServices.cs
create mode 100644 Installers/IInstaller.cs
create mode 100644 Installers/InstallServices.cs
create mode 100644 Installers/InstallerExtension.cs
create mode 100644 Installers/Refit/ConfigureRefitService.cs
create mode 100644 Output/ProgressService/IProgressService.cs
rename {Utils => Output/ProgressService}/ProgressBarConfiguration.cs (96%)
create mode 100644 Output/ProgressService/ProgressService.cs
rename Output/{ => Writer}/ConsoleWriter.cs (96%)
rename Output/{ => Writer}/IConsoleWriter.cs (96%)
diff --git a/CommandOptions/ConfigureOptions.cs b/Commandline/Options/ConfigureOptions.cs
similarity index 100%
rename from CommandOptions/ConfigureOptions.cs
rename to Commandline/Options/ConfigureOptions.cs
diff --git a/CommandOptions/FileCommandOptions.cs b/Commandline/Options/FileCommandOptions.cs
similarity index 96%
rename from CommandOptions/FileCommandOptions.cs
rename to Commandline/Options/FileCommandOptions.cs
index 751f154..8e631d4 100644
--- a/CommandOptions/FileCommandOptions.cs
+++ b/Commandline/Options/FileCommandOptions.cs
@@ -1,16 +1,16 @@
-using CommandLine;
-
-namespace asuka.CommandOptions;
-
-[Verb("file", HelpText = "Download galleries from text file")]
-public record FileCommandOptions : ICommonOptions
-{
- [Option('f', "file",
- Required = true,
- HelpText = "Path to text file to read.")]
- public string FilePath { get; init; }
-
- public bool Pack { get; init; }
- public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
-}
+using CommandLine;
+
+namespace asuka.CommandOptions;
+
+[Verb("file", HelpText = "Download galleries from text file")]
+public record FileCommandOptions : ICommonOptions
+{
+ [Option('f', "file",
+ Required = true,
+ HelpText = "Path to text file to read.")]
+ public string FilePath { get; init; }
+
+ public bool Pack { get; init; }
+ public string Output { get; init; }
+ public bool UseTachiyomiLayout { get; init; }
+}
diff --git a/CommandOptions/GetOptions.cs b/Commandline/Options/GetOptions.cs
similarity index 96%
rename from CommandOptions/GetOptions.cs
rename to Commandline/Options/GetOptions.cs
index 4ecfb4c..442564d 100644
--- a/CommandOptions/GetOptions.cs
+++ b/Commandline/Options/GetOptions.cs
@@ -1,19 +1,19 @@
-using CommandLine;
-
-namespace asuka.CommandOptions;
-
-[Verb("get", HelpText = "Download a Single Gallery from URL.")]
-public record GetOptions : IRequiresInputOption, ICommonOptions
-{
- public int Input { get; init; }
-
- [Option('r', "readonly",
- Default = false,
- Required = false,
- HelpText = "View the information only")]
- public bool ReadOnly { get; init; }
-
- public bool Pack { get; init; }
- public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
-}
+using CommandLine;
+
+namespace asuka.CommandOptions;
+
+[Verb("get", HelpText = "Download a Single Gallery from URL.")]
+public record GetOptions : IRequiresInputOption, ICommonOptions
+{
+ public int Input { get; init; }
+
+ [Option('r', "readonly",
+ Default = false,
+ Required = false,
+ HelpText = "View the information only")]
+ public bool ReadOnly { get; init; }
+
+ public bool Pack { get; init; }
+ public string Output { get; init; }
+ public bool UseTachiyomiLayout { get; init; }
+}
diff --git a/CommandOptions/ICommonOptions.cs b/Commandline/Options/ICommonOptions.cs
similarity index 96%
rename from CommandOptions/ICommonOptions.cs
rename to Commandline/Options/ICommonOptions.cs
index 20b2093..1b2c0ee 100644
--- a/CommandOptions/ICommonOptions.cs
+++ b/Commandline/Options/ICommonOptions.cs
@@ -1,22 +1,22 @@
-using CommandLine;
-
-namespace asuka.CommandOptions;
-
-public interface ICommonOptions
-{
- [Option('p', "pack",
- Default = false,
- Required = false,
- HelpText = "Pack downloaded gallery into single CBZ archive")]
- bool Pack { get; init; }
-
- [Option('o', "output",
- Required = false,
- HelpText = "Destination path for gallery download")]
- string Output { get; init; }
-
- [Option("useTachiyomiLayout",
- Required = false,
- HelpText = "Uses Tachiyomi Folder Structure for Download")]
- bool UseTachiyomiLayout { get; init; }
-}
+using CommandLine;
+
+namespace asuka.CommandOptions;
+
+public interface ICommonOptions
+{
+ [Option('p', "pack",
+ Default = false,
+ Required = false,
+ HelpText = "Pack downloaded gallery into single CBZ archive")]
+ bool Pack { get; init; }
+
+ [Option('o', "output",
+ Required = false,
+ HelpText = "Destination path for gallery download")]
+ string Output { get; init; }
+
+ [Option("useTachiyomiLayout",
+ Required = false,
+ HelpText = "Uses Tachiyomi Folder Structure for Download")]
+ bool UseTachiyomiLayout { get; init; }
+}
diff --git a/CommandOptions/IRequiresInputOption.cs b/Commandline/Options/IRequiresInputOption.cs
similarity index 95%
rename from CommandOptions/IRequiresInputOption.cs
rename to Commandline/Options/IRequiresInputOption.cs
index a84821d..ccd2ddf 100644
--- a/CommandOptions/IRequiresInputOption.cs
+++ b/Commandline/Options/IRequiresInputOption.cs
@@ -1,11 +1,11 @@
-using CommandLine;
-
-namespace asuka.CommandOptions;
-
-public interface IRequiresInputOption
-{
- [Option('i', "input",
- Required = true,
- HelpText = "Input Numeric Code")]
- int Input { get; init; }
-}
+using CommandLine;
+
+namespace asuka.CommandOptions;
+
+public interface IRequiresInputOption
+{
+ [Option('i', "input",
+ Required = true,
+ HelpText = "Input Numeric Code")]
+ int Input { get; init; }
+}
diff --git a/CommandOptions/RandomOptions.cs b/Commandline/Options/RandomOptions.cs
similarity index 96%
rename from CommandOptions/RandomOptions.cs
rename to Commandline/Options/RandomOptions.cs
index 8b6363d..ce69618 100644
--- a/CommandOptions/RandomOptions.cs
+++ b/Commandline/Options/RandomOptions.cs
@@ -1,12 +1,12 @@
-using CommandLine;
-using CommandLine.Text;
-
-namespace asuka.CommandOptions;
-
-[Verb("random", HelpText = "Randomly pick a gallery.")]
-public record RandomOptions : ICommonOptions
-{
- public bool Pack { get; init; }
- public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
-}
+using CommandLine;
+using CommandLine.Text;
+
+namespace asuka.CommandOptions;
+
+[Verb("random", HelpText = "Randomly pick a gallery.")]
+public record RandomOptions : ICommonOptions
+{
+ public bool Pack { get; init; }
+ public string Output { get; init; }
+ public bool UseTachiyomiLayout { get; init; }
+}
diff --git a/CommandOptions/RecommendOptions.cs b/Commandline/Options/RecommendOptions.cs
similarity index 96%
rename from CommandOptions/RecommendOptions.cs
rename to Commandline/Options/RecommendOptions.cs
index 14b81f8..0c303f6 100644
--- a/CommandOptions/RecommendOptions.cs
+++ b/Commandline/Options/RecommendOptions.cs
@@ -1,12 +1,12 @@
-using CommandLine;
-
-namespace asuka.CommandOptions;
-
-[Verb("recommend", HelpText = "Download recommendation from the gallery URL.")]
-public record RecommendOptions : ICommonOptions, IRequiresInputOption
-{
- public int Input { get; init; }
- public bool Pack { get; init; }
- public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
-}
+using CommandLine;
+
+namespace asuka.CommandOptions;
+
+[Verb("recommend", HelpText = "Download recommendation from the gallery URL.")]
+public record RecommendOptions : ICommonOptions, IRequiresInputOption
+{
+ public int Input { get; init; }
+ public bool Pack { get; init; }
+ public string Output { get; init; }
+ public bool UseTachiyomiLayout { get; init; }
+}
diff --git a/CommandOptions/SearchOptions.cs b/Commandline/Options/SearchOptions.cs
similarity index 96%
rename from CommandOptions/SearchOptions.cs
rename to Commandline/Options/SearchOptions.cs
index 64b321c..0eafbb1 100644
--- a/CommandOptions/SearchOptions.cs
+++ b/Commandline/Options/SearchOptions.cs
@@ -1,45 +1,45 @@
-using System.Collections.Generic;
-using asuka.Api.Queries;
-using CommandLine;
-
-namespace asuka.CommandOptions;
-
-[Verb("search", HelpText = "Search something in the gallery")]
-public record SearchOptions : ICommonOptions
-{
- [Option('q', "queries",
- Required = false,
- HelpText = "Search queries.")]
- public IEnumerable Queries { get; init; }
-
- [Option('e', "exclude",
- Required = false,
- HelpText = "Exclude something on search.")]
- public IEnumerable Exclude { get; init; }
-
- [Option("page",
- Required = true,
- Default = 1,
- HelpText = "Page to view its contents")]
- public int Page { get; init; }
-
- [Option('d', "dateRange",
- Required = false,
- HelpText = "Specify uploaded date to search")]
- public IEnumerable DateRange { get; init; }
-
- [Option("pageRange",
- Required = false,
- HelpText = "Specify page range of the gallery")]
- public IEnumerable PageRange { get; init; }
-
- [Option("sort",
- Required = false,
- Default = "date",
- HelpText = "Sort options")]
- public string Sort { get; init; }
-
- public bool Pack { get; init; }
- public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
-}
+using System.Collections.Generic;
+using asuka.Api.Queries;
+using CommandLine;
+
+namespace asuka.CommandOptions;
+
+[Verb("search", HelpText = "Search something in the gallery")]
+public record SearchOptions : ICommonOptions
+{
+ [Option('q', "queries",
+ Required = false,
+ HelpText = "Search queries.")]
+ public IEnumerable Queries { get; init; }
+
+ [Option('e', "exclude",
+ Required = false,
+ HelpText = "Exclude something on search.")]
+ public IEnumerable Exclude { get; init; }
+
+ [Option("page",
+ Required = true,
+ Default = 1,
+ HelpText = "Page to view its contents")]
+ public int Page { get; init; }
+
+ [Option('d', "dateRange",
+ Required = false,
+ HelpText = "Specify uploaded date to search")]
+ public IEnumerable DateRange { get; init; }
+
+ [Option("pageRange",
+ Required = false,
+ HelpText = "Specify page range of the gallery")]
+ public IEnumerable PageRange { get; init; }
+
+ [Option("sort",
+ Required = false,
+ Default = "date",
+ HelpText = "Sort options")]
+ public string Sort { get; init; }
+
+ public bool Pack { get; init; }
+ public string Output { get; init; }
+ public bool UseTachiyomiLayout { get; init; }
+}
diff --git a/CommandParsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs
similarity index 100%
rename from CommandParsers/ConfigureCommand.cs
rename to Commandline/Parsers/ConfigureCommand.cs
diff --git a/CommandParsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs
similarity index 96%
rename from CommandParsers/FileCommandService.cs
rename to Commandline/Parsers/FileCommandService.cs
index 347b8d9..641fd97 100644
--- a/CommandParsers/FileCommandService.cs
+++ b/Commandline/Parsers/FileCommandService.cs
@@ -1,89 +1,89 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Output;
-using asuka.Services;
-using asuka.Utils;
-using ShellProgressBar;
-
-namespace asuka.CommandParsers;
-
-public class FileCommandService : IFileCommandService
-{
- private readonly IGalleryRequestService _api;
- private readonly IConsoleWriter _console;
- private readonly IDownloadService _download;
- private readonly IConfigurationManager _configurationManager;
-
- public FileCommandService(IGalleryRequestService api, IConsoleWriter console, IDownloadService download, IConfigurationManager configurationManager)
- {
- _api = api;
- _console = console;
- _download = download;
- _configurationManager = configurationManager;
- }
-
- public async Task RunAsync(FileCommandOptions opts)
- {
- if (!File.Exists(opts.FilePath))
- {
- _console.ErrorLine("File doesn't exist.");
- return;
- }
-
- if (IsFileExceedingToFileSizeLimit(opts.FilePath))
- {
- _console.ErrorLine("The file size is exceeding 5MB file size limit.");
- return;
- }
-
- var textFile = await File.ReadAllLinesAsync(opts.FilePath, Encoding.UTF8)
- .ConfigureAwait(false);
- var validUrls = FilterValidUrls(textFile);
-
- if (validUrls.Count == 0)
- {
- _console.ErrorLine("No valid URLs found.");
- return;
- }
-
- using var progress = new ProgressBar(
- validUrls.Count,
- "downloading from text file...",
- ProgressBarConfiguration.BarOption);
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
-
- foreach (var url in validUrls)
- {
- var code = Regex.Match(url, @"\d+").Value;
- var response = await _api.FetchSingleAsync(code);
-
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
- progress.Tick();
- }
- }
-
- private static IReadOnlyList FilterValidUrls(IEnumerable urls)
- {
- return urls.Where(url =>
- {
- const string pattern = @"^http(s)?:\/\/(nhentai\.net)\b([//g]*)\b([\d]{1,6})\/?$";
- var regexp = new Regex(pattern, RegexOptions.IgnoreCase);
-
- return regexp.IsMatch(url);
- }).ToList();
- }
-
- private static bool IsFileExceedingToFileSizeLimit(string inputFile)
- {
- var fileSize = new FileInfo(inputFile).Length;
- return fileSize > 5242880;
- }
-}
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using asuka.CommandOptions;
+using asuka.Configuration;
+using asuka.Downloader;
+using asuka.Output;
+using asuka.Services;
+using asuka.Utils;
+using ShellProgressBar;
+
+namespace asuka.CommandParsers;
+
+public class FileCommandService : IFileCommandService
+{
+ private readonly IGalleryRequestService _api;
+ private readonly IConsoleWriter _console;
+ private readonly IDownloadService _download;
+ private readonly IConfigurationManager _configurationManager;
+
+ public FileCommandService(IGalleryRequestService api, IConsoleWriter console, IDownloadService download, IConfigurationManager configurationManager)
+ {
+ _api = api;
+ _console = console;
+ _download = download;
+ _configurationManager = configurationManager;
+ }
+
+ public async Task RunAsync(FileCommandOptions opts)
+ {
+ if (!File.Exists(opts.FilePath))
+ {
+ _console.ErrorLine("File doesn't exist.");
+ return;
+ }
+
+ if (IsFileExceedingToFileSizeLimit(opts.FilePath))
+ {
+ _console.ErrorLine("The file size is exceeding 5MB file size limit.");
+ return;
+ }
+
+ var textFile = await File.ReadAllLinesAsync(opts.FilePath, Encoding.UTF8)
+ .ConfigureAwait(false);
+ var validUrls = FilterValidUrls(textFile);
+
+ if (validUrls.Count == 0)
+ {
+ _console.ErrorLine("No valid URLs found.");
+ return;
+ }
+
+ using var progress = new ProgressBar(
+ validUrls.Count,
+ "downloading from text file...",
+ ProgressBarConfiguration.BarOption);
+
+ var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+
+ foreach (var url in validUrls)
+ {
+ var code = Regex.Match(url, @"\d+").Value;
+ var response = await _api.FetchSingleAsync(code);
+
+ await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
+ progress.Tick();
+ }
+ }
+
+ private static IReadOnlyList FilterValidUrls(IEnumerable urls)
+ {
+ return urls.Where(url =>
+ {
+ const string pattern = @"^http(s)?:\/\/(nhentai\.net)\b([//g]*)\b([\d]{1,6})\/?$";
+ var regexp = new Regex(pattern, RegexOptions.IgnoreCase);
+
+ return regexp.IsMatch(url);
+ }).ToList();
+ }
+
+ private static bool IsFileExceedingToFileSizeLimit(string inputFile)
+ {
+ var fileSize = new FileInfo(inputFile).Length;
+ return fileSize > 5242880;
+ }
+}
diff --git a/CommandParsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs
similarity index 96%
rename from CommandParsers/GetCommandService.cs
rename to Commandline/Parsers/GetCommandService.cs
index a819b85..8616dbd 100644
--- a/CommandParsers/GetCommandService.cs
+++ b/Commandline/Parsers/GetCommandService.cs
@@ -1,56 +1,56 @@
-using System;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using FluentValidation;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Output;
-using asuka.Services;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public class GetCommandService : IGetCommandService
-{
- private readonly IGalleryRequestService _api;
- private readonly IValidator _validator;
- private readonly IDownloadService _download;
- private readonly IConsoleWriter _console;
- private readonly IConfigurationManager _configurationManager;
-
- public GetCommandService(
- IGalleryRequestService api,
- IValidator validator,
- IDownloadService download,
- IConsoleWriter console,
- IConfigurationManager configurationManager)
- {
- _api = api;
- _validator = validator;
- _download = download;
- _console = console;
- _configurationManager = configurationManager;
- }
-
- public async Task RunAsync(GetOptions opts)
- {
- var validationResult = await _validator.ValidateAsync(opts);
- if (!validationResult.IsValid)
- {
- _console.ValidationErrors(validationResult.Errors);
- return;
- }
-
- var response = await _api.FetchSingleAsync(opts.Input.ToString());
- _console.WriteLine(response.ToReadable());
-
- if (opts.ReadOnly)
- {
- return;
- }
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, null);
- }
-}
+using System;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using FluentValidation;
+using asuka.CommandOptions;
+using asuka.Configuration;
+using asuka.Downloader;
+using asuka.Output;
+using asuka.Services;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public class GetCommandService : IGetCommandService
+{
+ private readonly IGalleryRequestService _api;
+ private readonly IValidator _validator;
+ private readonly IDownloadService _download;
+ private readonly IConsoleWriter _console;
+ private readonly IConfigurationManager _configurationManager;
+
+ public GetCommandService(
+ IGalleryRequestService api,
+ IValidator validator,
+ IDownloadService download,
+ IConsoleWriter console,
+ IConfigurationManager configurationManager)
+ {
+ _api = api;
+ _validator = validator;
+ _download = download;
+ _console = console;
+ _configurationManager = configurationManager;
+ }
+
+ public async Task RunAsync(GetOptions opts)
+ {
+ var validationResult = await _validator.ValidateAsync(opts);
+ if (!validationResult.IsValid)
+ {
+ _console.ValidationErrors(validationResult.Errors);
+ return;
+ }
+
+ var response = await _api.FetchSingleAsync(opts.Input.ToString());
+ _console.WriteLine(response.ToReadable());
+
+ if (opts.ReadOnly)
+ {
+ return;
+ }
+
+ var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+ await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, null);
+ }
+}
diff --git a/CommandParsers/IConfigureCommand.cs b/Commandline/Parsers/IConfigureCommand.cs
similarity index 100%
rename from CommandParsers/IConfigureCommand.cs
rename to Commandline/Parsers/IConfigureCommand.cs
diff --git a/CommandParsers/IFileCommandService.cs b/Commandline/Parsers/IFileCommandService.cs
similarity index 95%
rename from CommandParsers/IFileCommandService.cs
rename to Commandline/Parsers/IFileCommandService.cs
index 387277c..9d7fd13 100644
--- a/CommandParsers/IFileCommandService.cs
+++ b/Commandline/Parsers/IFileCommandService.cs
@@ -1,10 +1,10 @@
-using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public interface IFileCommandService
-{
- Task RunAsync(FileCommandOptions opts);
-}
+using System.Threading.Tasks;
+using asuka.CommandOptions;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public interface IFileCommandService
+{
+ Task RunAsync(FileCommandOptions opts);
+}
diff --git a/CommandParsers/IGetCommandService.cs b/Commandline/Parsers/IGetCommandService.cs
similarity index 95%
rename from CommandParsers/IGetCommandService.cs
rename to Commandline/Parsers/IGetCommandService.cs
index 5e5dbcc..b3bd708 100644
--- a/CommandParsers/IGetCommandService.cs
+++ b/Commandline/Parsers/IGetCommandService.cs
@@ -1,10 +1,10 @@
-using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public interface IGetCommandService
-{
- Task RunAsync(GetOptions opts);
-}
+using System.Threading.Tasks;
+using asuka.CommandOptions;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public interface IGetCommandService
+{
+ Task RunAsync(GetOptions opts);
+}
diff --git a/CommandParsers/IRandomCommandService.cs b/Commandline/Parsers/IRandomCommandService.cs
similarity index 95%
rename from CommandParsers/IRandomCommandService.cs
rename to Commandline/Parsers/IRandomCommandService.cs
index 95d87ca..19c18aa 100644
--- a/CommandParsers/IRandomCommandService.cs
+++ b/Commandline/Parsers/IRandomCommandService.cs
@@ -1,10 +1,10 @@
-using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public interface IRandomCommandService
-{
- Task RunAsync(RandomOptions opts);
-}
+using System.Threading.Tasks;
+using asuka.CommandOptions;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public interface IRandomCommandService
+{
+ Task RunAsync(RandomOptions opts);
+}
diff --git a/CommandParsers/IRecommendCommandService.cs b/Commandline/Parsers/IRecommendCommandService.cs
similarity index 95%
rename from CommandParsers/IRecommendCommandService.cs
rename to Commandline/Parsers/IRecommendCommandService.cs
index 2dd924e..6055da5 100644
--- a/CommandParsers/IRecommendCommandService.cs
+++ b/Commandline/Parsers/IRecommendCommandService.cs
@@ -1,10 +1,10 @@
-using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public interface IRecommendCommandService
-{
- Task RunAsync(RecommendOptions opts);
-}
+using System.Threading.Tasks;
+using asuka.CommandOptions;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public interface IRecommendCommandService
+{
+ Task RunAsync(RecommendOptions opts);
+}
diff --git a/CommandParsers/ISearchCommandService.cs b/Commandline/Parsers/ISearchCommandService.cs
similarity index 95%
rename from CommandParsers/ISearchCommandService.cs
rename to Commandline/Parsers/ISearchCommandService.cs
index 55bd02f..28289b3 100644
--- a/CommandParsers/ISearchCommandService.cs
+++ b/Commandline/Parsers/ISearchCommandService.cs
@@ -1,10 +1,10 @@
-using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public interface ISearchCommandService
-{
- Task RunAsync(SearchOptions opts);
-}
+using System.Threading.Tasks;
+using asuka.CommandOptions;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public interface ISearchCommandService
+{
+ Task RunAsync(SearchOptions opts);
+}
diff --git a/CommandParsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
similarity index 96%
rename from CommandParsers/RandomCommandService.cs
rename to Commandline/Parsers/RandomCommandService.cs
index 2f317c8..8ffaa40 100644
--- a/CommandParsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -1,55 +1,55 @@
-using System;
-using System.Threading.Tasks;
-using Sharprompt;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Output;
-using asuka.Services;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public class RandomCommandService : IRandomCommandService
-{
- private readonly IDownloadService _download;
- private readonly IGalleryRequestService _api;
- private readonly IConsoleWriter _console;
- private readonly IConfigurationManager _configurationManager;
-
- public RandomCommandService(
- IDownloadService download,
- IGalleryRequestService api,
- IConsoleWriter console,
- IConfigurationManager configurationManager)
- {
- _download = download;
- _api = api;
- _console = console;
- _configurationManager = configurationManager;
- }
-
- public async Task RunAsync(RandomOptions opts)
- {
- var totalNumbers = await _api.GetTotalGalleryCountAsync();
-
- while (true)
- {
- var randomCode = new Random().Next(1, totalNumbers);
- var response = await _api.FetchSingleAsync(randomCode.ToString());
-
- _console.WriteLine(response.ToReadable());
-
- var prompt = Prompt.Confirm("Are you sure to download this one?", true);
- if (!prompt)
- {
- await Task.Delay(1000).ConfigureAwait(false);
- continue;
- }
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, null);
- break;
- }
- }
-}
+using System;
+using System.Threading.Tasks;
+using Sharprompt;
+using asuka.CommandOptions;
+using asuka.Configuration;
+using asuka.Downloader;
+using asuka.Output;
+using asuka.Services;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public class RandomCommandService : IRandomCommandService
+{
+ private readonly IDownloadService _download;
+ private readonly IGalleryRequestService _api;
+ private readonly IConsoleWriter _console;
+ private readonly IConfigurationManager _configurationManager;
+
+ public RandomCommandService(
+ IDownloadService download,
+ IGalleryRequestService api,
+ IConsoleWriter console,
+ IConfigurationManager configurationManager)
+ {
+ _download = download;
+ _api = api;
+ _console = console;
+ _configurationManager = configurationManager;
+ }
+
+ public async Task RunAsync(RandomOptions opts)
+ {
+ var totalNumbers = await _api.GetTotalGalleryCountAsync();
+
+ while (true)
+ {
+ var randomCode = new Random().Next(1, totalNumbers);
+ var response = await _api.FetchSingleAsync(randomCode.ToString());
+
+ _console.WriteLine(response.ToReadable());
+
+ var prompt = Prompt.Confirm("Are you sure to download this one?", true);
+ if (!prompt)
+ {
+ await Task.Delay(1000).ConfigureAwait(false);
+ continue;
+ }
+
+ var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+ await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, null);
+ break;
+ }
+ }
+}
diff --git a/CommandParsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs
similarity index 96%
rename from CommandParsers/RecommendCommandService.cs
rename to Commandline/Parsers/RecommendCommandService.cs
index c3ed219..fd8d9ac 100644
--- a/CommandParsers/RecommendCommandService.cs
+++ b/Commandline/Parsers/RecommendCommandService.cs
@@ -1,64 +1,64 @@
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-using FluentValidation;
-using ShellProgressBar;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Mappings;
-using asuka.Output;
-using asuka.Services;
-using asuka.Utils;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public class RecommendCommandService : IRecommendCommandService
-{
- private readonly IValidator _validator;
- private readonly IGalleryRequestService _api;
- private readonly IDownloadService _download;
- private readonly IConsoleWriter _console;
- private readonly IConfigurationManager _configurationManager;
-
- public RecommendCommandService(
- IValidator validator,
- IGalleryRequestService api,
- IDownloadService download,
- IConsoleWriter console,
- IConfigurationManager configurationManager)
- {
- _validator = validator;
- _api = api;
- _download = download;
- _console = console;
- _configurationManager = configurationManager;
- }
-
- public async Task RunAsync(RecommendOptions opts)
- {
- var validator = await _validator.ValidateAsync(opts);
- if (!validator.IsValid)
- {
- _console.ValidationErrors(validator.Errors);
- return;
- }
-
- var responses = await _api.FetchRecommendedAsync(opts.Input.ToString());
-
- var selection = responses.FilterByUserSelected();
-
- // Initialise the Progress bar.
- using var progress = new ProgressBar(selection.Count, $"[task] recommend from id: {opts.Input}",
- ProgressBarConfiguration.BarOption);
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
-
- foreach (var response in selection)
- {
-
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
- progress.Tick();
- }
- }
-}
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using FluentValidation;
+using ShellProgressBar;
+using asuka.CommandOptions;
+using asuka.Configuration;
+using asuka.Downloader;
+using asuka.Mappings;
+using asuka.Output;
+using asuka.Services;
+using asuka.Utils;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public class RecommendCommandService : IRecommendCommandService
+{
+ private readonly IValidator _validator;
+ private readonly IGalleryRequestService _api;
+ private readonly IDownloadService _download;
+ private readonly IConsoleWriter _console;
+ private readonly IConfigurationManager _configurationManager;
+
+ public RecommendCommandService(
+ IValidator validator,
+ IGalleryRequestService api,
+ IDownloadService download,
+ IConsoleWriter console,
+ IConfigurationManager configurationManager)
+ {
+ _validator = validator;
+ _api = api;
+ _download = download;
+ _console = console;
+ _configurationManager = configurationManager;
+ }
+
+ public async Task RunAsync(RecommendOptions opts)
+ {
+ var validator = await _validator.ValidateAsync(opts);
+ if (!validator.IsValid)
+ {
+ _console.ValidationErrors(validator.Errors);
+ return;
+ }
+
+ var responses = await _api.FetchRecommendedAsync(opts.Input.ToString());
+
+ var selection = responses.FilterByUserSelected();
+
+ // Initialise the Progress bar.
+ using var progress = new ProgressBar(selection.Count, $"[task] recommend from id: {opts.Input}",
+ ProgressBarConfiguration.BarOption);
+
+ var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+
+ foreach (var response in selection)
+ {
+
+ await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
+ progress.Tick();
+ }
+ }
+}
diff --git a/CommandParsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs
similarity index 96%
rename from CommandParsers/SearchCommandService.cs
rename to Commandline/Parsers/SearchCommandService.cs
index 3a6e861..79af539 100644
--- a/CommandParsers/SearchCommandService.cs
+++ b/Commandline/Parsers/SearchCommandService.cs
@@ -1,84 +1,84 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using FluentValidation;
-using ShellProgressBar;
-using asuka.Api.Queries;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Mappings;
-using asuka.Output;
-using asuka.Services;
-using asuka.Utils;
-using Microsoft.Extensions.Configuration;
-
-namespace asuka.CommandParsers;
-
-public class SearchCommandService : ISearchCommandService
-{
- private readonly IGalleryRequestService _api;
- private readonly IValidator _validator;
- private readonly IConsoleWriter _console;
- private readonly IDownloadService _download;
- private readonly IConfigurationManager _configurationManager;
-
- public SearchCommandService(
- IGalleryRequestService api,
- IValidator validator,
- IConsoleWriter console,
- IDownloadService download,
- IConfigurationManager configurationManager)
- {
- _api = api;
- _validator = validator;
- _console = console;
- _download = download;
- _configurationManager = configurationManager;
- }
-
- public async Task RunAsync(SearchOptions opts)
- {
- var validationResult = await _validator.ValidateAsync(opts);
- if (!validationResult.IsValid)
- {
- _console.ValidationErrors(validationResult.Errors);
- return;
- }
-
- // Construct search query
- var searchQueries = new List();
- searchQueries.AddRange(opts.Queries);
- searchQueries.AddRange(opts.Exclude.Select(q => $"-{q}"));
- searchQueries.AddRange(opts.DateRange.Select(d => $"uploaded:{d}"));
- searchQueries.AddRange(opts.PageRange.Select(p => $"pages:{p}"));
-
- var query = new SearchQuery
- {
- Queries = string.Join(" ", searchQueries),
- PageNumber = opts.Page,
- Sort = opts.Sort
- };
-
- var responses = await _api.SearchAsync(query);
- if (responses.Count < 1)
- {
- _console.ErrorLine("No results found.");
- return;
- }
-
- var selection = responses.FilterByUserSelected();
-
- // Initialise the Progress bar.
- using var progress = new ProgressBar(selection.Count, $"[task] search download",
- ProgressBarConfiguration.BarOption);
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
-
- foreach (var response in selection)
- {
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
- progress.Tick();
- }
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using FluentValidation;
+using ShellProgressBar;
+using asuka.Api.Queries;
+using asuka.CommandOptions;
+using asuka.Configuration;
+using asuka.Downloader;
+using asuka.Mappings;
+using asuka.Output;
+using asuka.Services;
+using asuka.Utils;
+using Microsoft.Extensions.Configuration;
+
+namespace asuka.CommandParsers;
+
+public class SearchCommandService : ISearchCommandService
+{
+ private readonly IGalleryRequestService _api;
+ private readonly IValidator _validator;
+ private readonly IConsoleWriter _console;
+ private readonly IDownloadService _download;
+ private readonly IConfigurationManager _configurationManager;
+
+ public SearchCommandService(
+ IGalleryRequestService api,
+ IValidator validator,
+ IConsoleWriter console,
+ IDownloadService download,
+ IConfigurationManager configurationManager)
+ {
+ _api = api;
+ _validator = validator;
+ _console = console;
+ _download = download;
+ _configurationManager = configurationManager;
+ }
+
+ public async Task RunAsync(SearchOptions opts)
+ {
+ var validationResult = await _validator.ValidateAsync(opts);
+ if (!validationResult.IsValid)
+ {
+ _console.ValidationErrors(validationResult.Errors);
+ return;
+ }
+
+ // Construct search query
+ var searchQueries = new List();
+ searchQueries.AddRange(opts.Queries);
+ searchQueries.AddRange(opts.Exclude.Select(q => $"-{q}"));
+ searchQueries.AddRange(opts.DateRange.Select(d => $"uploaded:{d}"));
+ searchQueries.AddRange(opts.PageRange.Select(p => $"pages:{p}"));
+
+ var query = new SearchQuery
+ {
+ Queries = string.Join(" ", searchQueries),
+ PageNumber = opts.Page,
+ Sort = opts.Sort
+ };
+
+ var responses = await _api.SearchAsync(query);
+ if (responses.Count < 1)
+ {
+ _console.ErrorLine("No results found.");
+ return;
+ }
+
+ var selection = responses.FilterByUserSelected();
+
+ // Initialise the Progress bar.
+ using var progress = new ProgressBar(selection.Count, $"[task] search download",
+ ProgressBarConfiguration.BarOption);
+
+ var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+
+ foreach (var response in selection)
+ {
+ await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
+ progress.Tick();
+ }
+ }
+}
diff --git a/Api/IGalleryApi.cs b/Core/Api/IGalleryApi.cs
similarity index 96%
rename from Api/IGalleryApi.cs
rename to Core/Api/IGalleryApi.cs
index 01b0a51..9e7ca15 100644
--- a/Api/IGalleryApi.cs
+++ b/Core/Api/IGalleryApi.cs
@@ -1,21 +1,21 @@
-using System.Threading.Tasks;
-using asuka.Api.Queries;
-using asuka.Api.Responses;
-using Refit;
-
-namespace asuka.Api;
-
-public interface IGalleryApi
-{
- [Get("/api/gallery/{code}")]
- Task FetchSingle(string code);
-
- [Get("/api/gallery/{code}/related")]
- Task FetchRecommended(string code);
-
- [Get("/api/galleries/all?page=1")]
- Task FetchAll();
-
- [Get("/api/galleries/search")]
- Task SearchGallery(SearchQuery queries);
-}
+using System.Threading.Tasks;
+using asuka.Api.Queries;
+using asuka.Api.Responses;
+using Refit;
+
+namespace asuka.Api;
+
+public interface IGalleryApi
+{
+ [Get("/api/gallery/{code}")]
+ Task FetchSingle(string code);
+
+ [Get("/api/gallery/{code}/related")]
+ Task FetchRecommended(string code);
+
+ [Get("/api/galleries/all?page=1")]
+ Task FetchAll();
+
+ [Get("/api/galleries/search")]
+ Task SearchGallery(SearchQuery queries);
+}
diff --git a/Api/IGalleryImage.cs b/Core/Api/IGalleryImage.cs
similarity index 95%
rename from Api/IGalleryImage.cs
rename to Core/Api/IGalleryImage.cs
index 7516c95..abdbb84 100644
--- a/Api/IGalleryImage.cs
+++ b/Core/Api/IGalleryImage.cs
@@ -1,11 +1,11 @@
-using System.Net.Http;
-using System.Threading.Tasks;
-using Refit;
-
-namespace asuka.Api;
-
-public interface IGalleryImage
-{
- [Get("/galleries/{mediaId}/{filename}")]
- Task GetImage(string mediaId, string filename);
-}
+using System.Net.Http;
+using System.Threading.Tasks;
+using Refit;
+
+namespace asuka.Api;
+
+public interface IGalleryImage
+{
+ [Get("/galleries/{mediaId}/{filename}")]
+ Task GetImage(string mediaId, string filename);
+}
diff --git a/Api/Queries/SearchQuery.cs b/Core/Api/Queries/SearchQuery.cs
similarity index 94%
rename from Api/Queries/SearchQuery.cs
rename to Core/Api/Queries/SearchQuery.cs
index 9a0dd94..d325a78 100644
--- a/Api/Queries/SearchQuery.cs
+++ b/Core/Api/Queries/SearchQuery.cs
@@ -1,15 +1,15 @@
-using Refit;
-
-namespace asuka.Api.Queries;
-
-public record SearchQuery
-{
- [AliasAs("query")]
- public string Queries { get; init; }
-
- [AliasAs("page")]
- public int PageNumber { get; init; }
-
- [AliasAs("sort")]
- public string Sort { get; init; }
-}
+using Refit;
+
+namespace asuka.Api.Queries;
+
+public record SearchQuery
+{
+ [AliasAs("query")]
+ public string Queries { get; init; }
+
+ [AliasAs("page")]
+ public int PageNumber { get; init; }
+
+ [AliasAs("sort")]
+ public string Sort { get; init; }
+}
diff --git a/Api/Responses/GalleryImageObjectResponse.cs b/Core/Api/Responses/GalleryImageObjectResponse.cs
similarity index 95%
rename from Api/Responses/GalleryImageObjectResponse.cs
rename to Core/Api/Responses/GalleryImageObjectResponse.cs
index 04b2421..7e0bdfd 100644
--- a/Api/Responses/GalleryImageObjectResponse.cs
+++ b/Core/Api/Responses/GalleryImageObjectResponse.cs
@@ -1,10 +1,10 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GalleryImageObjectResponse
-{
- [JsonProperty("pages")]
- public IReadOnlyList Images { get; init; }
-}
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GalleryImageObjectResponse
+{
+ [JsonProperty("pages")]
+ public IReadOnlyList Images { get; init; }
+}
diff --git a/Api/Responses/GalleryImageResponse.cs b/Core/Api/Responses/GalleryImageResponse.cs
similarity index 94%
rename from Api/Responses/GalleryImageResponse.cs
rename to Core/Api/Responses/GalleryImageResponse.cs
index fa37f20..7439078 100644
--- a/Api/Responses/GalleryImageResponse.cs
+++ b/Core/Api/Responses/GalleryImageResponse.cs
@@ -1,15 +1,15 @@
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GalleryImageResponse
-{
- [JsonProperty("t")]
- public string Type { get; init; }
-
- [JsonProperty("h")]
- public int Height { get; init; }
-
- [JsonProperty("w")]
- public int Width { get; init; }
-}
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GalleryImageResponse
+{
+ [JsonProperty("t")]
+ public string Type { get; init; }
+
+ [JsonProperty("h")]
+ public int Height { get; init; }
+
+ [JsonProperty("w")]
+ public int Width { get; init; }
+}
diff --git a/Api/Responses/GalleryListResponse.cs b/Core/Api/Responses/GalleryListResponse.cs
similarity index 95%
rename from Api/Responses/GalleryListResponse.cs
rename to Core/Api/Responses/GalleryListResponse.cs
index 60adb67..b206026 100644
--- a/Api/Responses/GalleryListResponse.cs
+++ b/Core/Api/Responses/GalleryListResponse.cs
@@ -1,10 +1,10 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GalleryListResponse
-{
- [JsonProperty("result")]
- public IReadOnlyList Result { get; init; }
-}
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GalleryListResponse
+{
+ [JsonProperty("result")]
+ public IReadOnlyList Result { get; init; }
+}
diff --git a/Api/Responses/GalleryResponse.cs b/Core/Api/Responses/GalleryResponse.cs
similarity index 96%
rename from Api/Responses/GalleryResponse.cs
rename to Core/Api/Responses/GalleryResponse.cs
index 60e6525..43c1c7b 100644
--- a/Api/Responses/GalleryResponse.cs
+++ b/Core/Api/Responses/GalleryResponse.cs
@@ -1,28 +1,28 @@
-using System.Collections.Generic;
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GalleryResponse
-{
- [JsonProperty("id")]
- public int Id { get; init; }
-
- [JsonProperty("media_id")]
- public int MediaId { get; init; }
-
- [JsonProperty("title")]
- public GalleryTitleResponse Title { get; init; }
-
- [JsonProperty("images")]
- public GalleryImageObjectResponse Images { get; init; }
-
- [JsonProperty("tags")]
- public IReadOnlyList Tags { get; init; }
-
- [JsonProperty("num_pages")]
- public int TotalPages { get; init; }
-
- [JsonProperty("num_favorites")]
- public int TotalFavorites { get; init; }
-}
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GalleryResponse
+{
+ [JsonProperty("id")]
+ public int Id { get; init; }
+
+ [JsonProperty("media_id")]
+ public int MediaId { get; init; }
+
+ [JsonProperty("title")]
+ public GalleryTitleResponse Title { get; init; }
+
+ [JsonProperty("images")]
+ public GalleryImageObjectResponse Images { get; init; }
+
+ [JsonProperty("tags")]
+ public IReadOnlyList Tags { get; init; }
+
+ [JsonProperty("num_pages")]
+ public int TotalPages { get; init; }
+
+ [JsonProperty("num_favorites")]
+ public int TotalFavorites { get; init; }
+}
diff --git a/Api/Responses/GallerySearchResponse.cs b/Core/Api/Responses/GallerySearchResponse.cs
similarity index 95%
rename from Api/Responses/GallerySearchResponse.cs
rename to Core/Api/Responses/GallerySearchResponse.cs
index f42302b..47d782e 100644
--- a/Api/Responses/GallerySearchResponse.cs
+++ b/Core/Api/Responses/GallerySearchResponse.cs
@@ -1,12 +1,12 @@
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GallerySearchResponse : GalleryListResponse
-{
- [JsonProperty("num_pages")]
- public int TotalPages { get; init; }
-
- [JsonProperty("per_page")]
- public int TotalItemsPerPage { get; init; }
-}
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GallerySearchResponse : GalleryListResponse
+{
+ [JsonProperty("num_pages")]
+ public int TotalPages { get; init; }
+
+ [JsonProperty("per_page")]
+ public int TotalItemsPerPage { get; init; }
+}
diff --git a/Api/Responses/GalleryTagResponse.cs b/Core/Api/Responses/GalleryTagResponse.cs
similarity index 95%
rename from Api/Responses/GalleryTagResponse.cs
rename to Core/Api/Responses/GalleryTagResponse.cs
index f5998d4..c53e174 100644
--- a/Api/Responses/GalleryTagResponse.cs
+++ b/Core/Api/Responses/GalleryTagResponse.cs
@@ -1,21 +1,21 @@
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GalleryTagResponse
-{
- [JsonProperty("id")]
- public int Id { get; init; }
-
- [JsonProperty("type")]
- public string Type { get; init; }
-
- [JsonProperty("name")]
- public string Name { get; init; }
-
- [JsonProperty("url")]
- public string Url { get; init; }
-
- [JsonProperty("count")]
- public int Count { get; init; }
-}
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GalleryTagResponse
+{
+ [JsonProperty("id")]
+ public int Id { get; init; }
+
+ [JsonProperty("type")]
+ public string Type { get; init; }
+
+ [JsonProperty("name")]
+ public string Name { get; init; }
+
+ [JsonProperty("url")]
+ public string Url { get; init; }
+
+ [JsonProperty("count")]
+ public int Count { get; init; }
+}
diff --git a/Api/Responses/GalleryTitleResponse.cs b/Core/Api/Responses/GalleryTitleResponse.cs
similarity index 95%
rename from Api/Responses/GalleryTitleResponse.cs
rename to Core/Api/Responses/GalleryTitleResponse.cs
index 8ec6127..fdc905d 100644
--- a/Api/Responses/GalleryTitleResponse.cs
+++ b/Core/Api/Responses/GalleryTitleResponse.cs
@@ -1,15 +1,15 @@
-using Newtonsoft.Json;
-
-namespace asuka.Api.Responses;
-
-public record GalleryTitleResponse
-{
- [JsonProperty("japanese")]
- public string Japanese { get; init; }
-
- [JsonProperty("english")]
- public string English { get; init; }
-
- [JsonProperty("pretty")]
- public string Pretty { get; init; }
-}
+using Newtonsoft.Json;
+
+namespace asuka.Api.Responses;
+
+public record GalleryTitleResponse
+{
+ [JsonProperty("japanese")]
+ public string Japanese { get; init; }
+
+ [JsonProperty("english")]
+ public string English { get; init; }
+
+ [JsonProperty("pretty")]
+ public string Pretty { get; init; }
+}
diff --git a/Compression/IPackArchiveToCbz.cs b/Core/Compression/IPackArchiveToCbz.cs
similarity index 100%
rename from Compression/IPackArchiveToCbz.cs
rename to Core/Compression/IPackArchiveToCbz.cs
diff --git a/Compression/PackArchiveToCbz.cs b/Core/Compression/PackArchiveToCbz.cs
similarity index 100%
rename from Compression/PackArchiveToCbz.cs
rename to Core/Compression/PackArchiveToCbz.cs
diff --git a/Downloader/DownloadService.cs b/Core/Downloader/DownloadService.cs
similarity index 97%
rename from Downloader/DownloadService.cs
rename to Core/Downloader/DownloadService.cs
index 449732d..342b16b 100644
--- a/Downloader/DownloadService.cs
+++ b/Core/Downloader/DownloadService.cs
@@ -1,135 +1,135 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using ShellProgressBar;
-using asuka.Api;
-using asuka.Compression;
-using asuka.Models;
-using asuka.Output;
-using asuka.Utils;
-using Newtonsoft.Json;
-
-namespace asuka.Downloader;
-
-public class DownloadService : IDownloadService
-{
- private readonly IGalleryImage _api;
- private readonly IPackArchiveToCbz _packer;
- private int _taskId;
- private string _destinationPath;
- private string _folderName;
-
- public DownloadService(IGalleryImage api, IPackArchiveToCbz packer)
- {
- _api = api;
- _packer = packer;
- }
-
- public async Task DownloadAsync(GalleryResult result,
- string outputPath,
- bool pack,
- bool useTachiyomiFolderLayout,
- IProgressBar progress)
- {
- // Prepare the download.
- await PrepareAsync(result, outputPath, useTachiyomiFolderLayout).ConfigureAwait(false);
-
- // If the progress is null, we create a new one.
- var progressTheme = ProgressBarConfiguration.BarOption;
- var progressInitialTitle = $"[queued] id: {_taskId}";
-
- using var bar = progress == null
- ? new ProgressBar(result.TotalPages, progressInitialTitle, progressTheme)
- : (IProgressBar) progress.Spawn(result.TotalPages, progressInitialTitle, progressTheme);
-
- using var throttler = new SemaphoreSlim(2);
- var taskList = new List();
-
- foreach (var page in result.Images)
- {
- await throttler.WaitAsync().ConfigureAwait(false);
-
- var referenceBar = bar;
- var referenceThrottler = throttler;
- taskList.Add(Task.Run(async () =>
- {
- await FetchImageAsync(result.MediaId, page, referenceBar).ConfigureAwait(false);
- referenceThrottler.Release();
- }));
- }
-
- await Task.WhenAll(taskList).ConfigureAwait(false);
-
- if (pack)
- {
- var imageFiles = Directory.GetFiles(_destinationPath);
- await _packer.RunAsync(_folderName, imageFiles, $"{_destinationPath}.cbz", bar);
- }
- }
-
- private static string SantizeFolderName(string folderName)
- {
- var illegalRegex = new string(Path.GetInvalidFileNameChars()) + ".";
- var regex = new Regex($"[{Regex.Escape(illegalRegex)}]");
- var multiSpacingRegex = new Regex("[ ]{2,}");
-
- folderName = regex.Replace(folderName, "");
- folderName = folderName.Trim();
- folderName = multiSpacingRegex.Replace(folderName, "");
-
- return folderName;
- }
-
- private async Task PrepareAsync(GalleryResult result, string outputPath, bool useTachiyomiFolderLayout)
- {
- _taskId = result.Id;
- _destinationPath = Environment.CurrentDirectory;
- if (!string.IsNullOrEmpty(outputPath))
- {
- _destinationPath = outputPath;
- }
-
- var galleryTitle = string.IsNullOrEmpty(result.Title.Japanese)
- ? (string.IsNullOrEmpty(result.Title.English) ? result.Title.Pretty : result.Title.English)
- : result.Title.Japanese;
-
- var folderName = SantizeFolderName($"{result.Id} - {galleryTitle}");
- _folderName = folderName;
-
- var mangaRootPath = Path.Join(_destinationPath, folderName);
- _destinationPath = useTachiyomiFolderLayout ? Path.Join(mangaRootPath, "ch1") : mangaRootPath;
- if (!Directory.Exists(_destinationPath))
- {
- Directory.CreateDirectory(_destinationPath);
- }
-
- var metadataPath = Path.Combine(mangaRootPath, "info.txt");
- await File.WriteAllTextAsync(metadataPath, result.ToReadable())
- .ConfigureAwait(false);
-
- // Generate Tachiyomi details.json
- if (useTachiyomiFolderLayout)
- {
- var tachiyomiMetadataPath = Path.Combine(mangaRootPath, "details.json");
- var tachiyomiMetadata = JsonConvert
- .SerializeObject(result.ToTachiyomiMetadata(), Formatting.Indented);
- await File.WriteAllTextAsync(tachiyomiMetadataPath, tachiyomiMetadata)
- .ConfigureAwait(false);
- }
- }
-
- private async Task FetchImageAsync(int mediaId, GalleryImageResult page, IProgressBar bar)
- {
- var image = await _api.GetImage(mediaId.ToString(), page.ServerFilename);
- var imageContents = await image.ReadAsByteArrayAsync();
-
- var filePath = Path.Combine(_destinationPath, page.Filename);
- await File.WriteAllBytesAsync(filePath, imageContents)
- .ConfigureAwait(false);
-
- bar.Tick($"[downloading] id: {_taskId}");
- }
-}
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using ShellProgressBar;
+using asuka.Api;
+using asuka.Compression;
+using asuka.Models;
+using asuka.Output;
+using asuka.Utils;
+using Newtonsoft.Json;
+
+namespace asuka.Downloader;
+
+public class DownloadService : IDownloadService
+{
+ private readonly IGalleryImage _api;
+ private readonly IPackArchiveToCbz _packer;
+ private int _taskId;
+ private string _destinationPath;
+ private string _folderName;
+
+ public DownloadService(IGalleryImage api, IPackArchiveToCbz packer)
+ {
+ _api = api;
+ _packer = packer;
+ }
+
+ public async Task DownloadAsync(GalleryResult result,
+ string outputPath,
+ bool pack,
+ bool useTachiyomiFolderLayout,
+ IProgressBar progress)
+ {
+ // Prepare the download.
+ await PrepareAsync(result, outputPath, useTachiyomiFolderLayout).ConfigureAwait(false);
+
+ // If the progress is null, we create a new one.
+ var progressTheme = ProgressBarConfiguration.BarOption;
+ var progressInitialTitle = $"[queued] id: {_taskId}";
+
+ using var bar = progress == null
+ ? new ProgressBar(result.TotalPages, progressInitialTitle, progressTheme)
+ : (IProgressBar) progress.Spawn(result.TotalPages, progressInitialTitle, progressTheme);
+
+ using var throttler = new SemaphoreSlim(2);
+ var taskList = new List();
+
+ foreach (var page in result.Images)
+ {
+ await throttler.WaitAsync().ConfigureAwait(false);
+
+ var referenceBar = bar;
+ var referenceThrottler = throttler;
+ taskList.Add(Task.Run(async () =>
+ {
+ await FetchImageAsync(result.MediaId, page, referenceBar).ConfigureAwait(false);
+ referenceThrottler.Release();
+ }));
+ }
+
+ await Task.WhenAll(taskList).ConfigureAwait(false);
+
+ if (pack)
+ {
+ var imageFiles = Directory.GetFiles(_destinationPath);
+ await _packer.RunAsync(_folderName, imageFiles, $"{_destinationPath}.cbz", bar);
+ }
+ }
+
+ private static string SantizeFolderName(string folderName)
+ {
+ var illegalRegex = new string(Path.GetInvalidFileNameChars()) + ".";
+ var regex = new Regex($"[{Regex.Escape(illegalRegex)}]");
+ var multiSpacingRegex = new Regex("[ ]{2,}");
+
+ folderName = regex.Replace(folderName, "");
+ folderName = folderName.Trim();
+ folderName = multiSpacingRegex.Replace(folderName, "");
+
+ return folderName;
+ }
+
+ private async Task PrepareAsync(GalleryResult result, string outputPath, bool useTachiyomiFolderLayout)
+ {
+ _taskId = result.Id;
+ _destinationPath = Environment.CurrentDirectory;
+ if (!string.IsNullOrEmpty(outputPath))
+ {
+ _destinationPath = outputPath;
+ }
+
+ var galleryTitle = string.IsNullOrEmpty(result.Title.Japanese)
+ ? (string.IsNullOrEmpty(result.Title.English) ? result.Title.Pretty : result.Title.English)
+ : result.Title.Japanese;
+
+ var folderName = SantizeFolderName($"{result.Id} - {galleryTitle}");
+ _folderName = folderName;
+
+ var mangaRootPath = Path.Join(_destinationPath, folderName);
+ _destinationPath = useTachiyomiFolderLayout ? Path.Join(mangaRootPath, "ch1") : mangaRootPath;
+ if (!Directory.Exists(_destinationPath))
+ {
+ Directory.CreateDirectory(_destinationPath);
+ }
+
+ var metadataPath = Path.Combine(mangaRootPath, "info.txt");
+ await File.WriteAllTextAsync(metadataPath, result.ToReadable())
+ .ConfigureAwait(false);
+
+ // Generate Tachiyomi details.json
+ if (useTachiyomiFolderLayout)
+ {
+ var tachiyomiMetadataPath = Path.Combine(mangaRootPath, "details.json");
+ var tachiyomiMetadata = JsonConvert
+ .SerializeObject(result.ToTachiyomiMetadata(), Formatting.Indented);
+ await File.WriteAllTextAsync(tachiyomiMetadataPath, tachiyomiMetadata)
+ .ConfigureAwait(false);
+ }
+ }
+
+ private async Task FetchImageAsync(int mediaId, GalleryImageResult page, IProgressBar bar)
+ {
+ var image = await _api.GetImage(mediaId.ToString(), page.ServerFilename);
+ var imageContents = await image.ReadAsByteArrayAsync();
+
+ var filePath = Path.Combine(_destinationPath, page.Filename);
+ await File.WriteAllBytesAsync(filePath, imageContents)
+ .ConfigureAwait(false);
+
+ bar.Tick($"[downloading] id: {_taskId}");
+ }
+}
diff --git a/Downloader/IDownloadService.cs b/Core/Downloader/IDownloadService.cs
similarity index 95%
rename from Downloader/IDownloadService.cs
rename to Core/Downloader/IDownloadService.cs
index 983dbca..c82d16d 100644
--- a/Downloader/IDownloadService.cs
+++ b/Core/Downloader/IDownloadService.cs
@@ -1,15 +1,15 @@
-using System.Threading.Tasks;
-using asuka.Models;
-using ShellProgressBar;
-
-namespace asuka.Downloader;
-
-public interface IDownloadService
-{
- Task DownloadAsync(
- GalleryResult result,
- string outputPath,
- bool pack,
- bool useTachiyomiFolderLayout,
- IProgressBar progress);
-}
+using System.Threading.Tasks;
+using asuka.Models;
+using ShellProgressBar;
+
+namespace asuka.Downloader;
+
+public interface IDownloadService
+{
+ Task DownloadAsync(
+ GalleryResult result,
+ string outputPath,
+ bool pack,
+ bool useTachiyomiFolderLayout,
+ IProgressBar progress);
+}
diff --git a/Core/Downloader/InternalTypes/DownloadResult.cs b/Core/Downloader/InternalTypes/DownloadResult.cs
new file mode 100644
index 0000000..966b8c9
--- /dev/null
+++ b/Core/Downloader/InternalTypes/DownloadResult.cs
@@ -0,0 +1,10 @@
+using System.Collections.Generic;
+
+namespace asuka.Core.Downloader;
+
+public record DownloadResult
+{
+ public string FolderName { get; init; }
+ public IList ImageFiles { get; init; }
+ public string DestinationPath { get; init; }
+}
diff --git a/Core/Downloader/InternalTypes/FetchImageParameter.cs b/Core/Downloader/InternalTypes/FetchImageParameter.cs
new file mode 100644
index 0000000..bf4e973
--- /dev/null
+++ b/Core/Downloader/InternalTypes/FetchImageParameter.cs
@@ -0,0 +1,3 @@
+namespace asuka.Core.Downloader.InternalTypes;
+
+public record FetchImageParameter();
\ No newline at end of file
diff --git a/Core/Downloader/InternalTypes/PrepareResult.cs b/Core/Downloader/InternalTypes/PrepareResult.cs
new file mode 100644
index 0000000..61f515a
--- /dev/null
+++ b/Core/Downloader/InternalTypes/PrepareResult.cs
@@ -0,0 +1,8 @@
+namespace asuka.Core.Downloader;
+
+internal record PrepareResult
+{
+ public string FolderName { get; init; }
+ public int Id { get; init; }
+ public string DestinationPath { get; init; }
+}
diff --git a/Mappings/ContractToGalleryImageResultModelMapping.cs b/Core/Mappings/ContractToGalleryImageResultModelMapping.cs
similarity index 96%
rename from Mappings/ContractToGalleryImageResultModelMapping.cs
rename to Core/Mappings/ContractToGalleryImageResultModelMapping.cs
index d557761..5f2173e 100644
--- a/Mappings/ContractToGalleryImageResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryImageResultModelMapping.cs
@@ -1,34 +1,34 @@
-using System.Collections.Generic;
-using System.Linq;
-using asuka.Api.Responses;
-using asuka.Models;
-
-namespace asuka.Mappings;
-
-public static class ContractToGalleryImageResultModelMapping
-{
- public static IReadOnlyList ToGalleryImageResult(
- this IReadOnlyList response)
- {
- return response.Select((value, index) =>
- {
- var extension = value.Type switch
- {
- "j" => ".jpg",
- "p" => ".png",
- "g" => ".gif",
- _ => ""
- };
-
- var pageNumber = index + 1;
- var pageNumberFormatted = pageNumber.ToString($"D{response.Count.ToString().Length}");
- var filename = $"{pageNumberFormatted}{extension}";
-
- return new GalleryImageResult
- {
- ServerFilename = $"{pageNumber}{extension}",
- Filename = filename
- };
- }).ToList();
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using asuka.Api.Responses;
+using asuka.Models;
+
+namespace asuka.Mappings;
+
+public static class ContractToGalleryImageResultModelMapping
+{
+ public static IReadOnlyList ToGalleryImageResult(
+ this IReadOnlyList response)
+ {
+ return response.Select((value, index) =>
+ {
+ var extension = value.Type switch
+ {
+ "j" => ".jpg",
+ "p" => ".png",
+ "g" => ".gif",
+ _ => ""
+ };
+
+ var pageNumber = index + 1;
+ var pageNumberFormatted = pageNumber.ToString($"D{response.Count.ToString().Length}");
+ var filename = $"{pageNumberFormatted}{extension}";
+
+ return new GalleryImageResult
+ {
+ ServerFilename = $"{pageNumber}{extension}",
+ Filename = filename
+ };
+ }).ToList();
+ }
+}
diff --git a/Mappings/ContractToGalleryResultModelMapping.cs b/Core/Mappings/ContractToGalleryResultModelMapping.cs
similarity index 97%
rename from Mappings/ContractToGalleryResultModelMapping.cs
rename to Core/Mappings/ContractToGalleryResultModelMapping.cs
index 8759b54..519d904 100644
--- a/Mappings/ContractToGalleryResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryResultModelMapping.cs
@@ -1,30 +1,30 @@
-using asuka.Api.Responses;
-using asuka.Models;
-
-namespace asuka.Mappings;
-
-public static class ContractToGalleryResultModelMapping
-{
- public static GalleryResult ToGalleryResult(this GalleryResponse response)
- {
- return new GalleryResult
- {
- Id = response.Id,
- MediaId = response.MediaId,
- Title = new GalleryTitleResult
- {
- Japanese = response.Title.Japanese,
- English = response.Title.English,
- Pretty = response.Title.Pretty
- },
- Images = response.Images.Images.ToGalleryImageResult(),
- Artists = response.Tags.GetTagByGroup("artist"),
- Parodies = response.Tags.GetTagByGroup("parody"),
- Characters = response.Tags.GetTagByGroup("character"),
- Tags = response.Tags.GetTagByGroup("tag"),
- Categories = response.Tags.GetTagByGroup("category"),
- Languages = response.Tags.GetTagByGroup("language"),
- TotalPages = response.TotalPages
- };
- }
-}
+using asuka.Api.Responses;
+using asuka.Models;
+
+namespace asuka.Mappings;
+
+public static class ContractToGalleryResultModelMapping
+{
+ public static GalleryResult ToGalleryResult(this GalleryResponse response)
+ {
+ return new GalleryResult
+ {
+ Id = response.Id,
+ MediaId = response.MediaId,
+ Title = new GalleryTitleResult
+ {
+ Japanese = response.Title.Japanese,
+ English = response.Title.English,
+ Pretty = response.Title.Pretty
+ },
+ Images = response.Images.Images.ToGalleryImageResult(),
+ Artists = response.Tags.GetTagByGroup("artist"),
+ Parodies = response.Tags.GetTagByGroup("parody"),
+ Characters = response.Tags.GetTagByGroup("character"),
+ Tags = response.Tags.GetTagByGroup("tag"),
+ Categories = response.Tags.GetTagByGroup("category"),
+ Languages = response.Tags.GetTagByGroup("language"),
+ TotalPages = response.TotalPages
+ };
+ }
+}
diff --git a/Mappings/ContractToGalleryTagResultModelMapping.cs b/Core/Mappings/ContractToGalleryTagResultModelMapping.cs
similarity index 96%
rename from Mappings/ContractToGalleryTagResultModelMapping.cs
rename to Core/Mappings/ContractToGalleryTagResultModelMapping.cs
index e2a24ba..504fa31 100644
--- a/Mappings/ContractToGalleryTagResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryTagResultModelMapping.cs
@@ -1,18 +1,18 @@
-using System.Collections.Generic;
-using System.Linq;
-using asuka.Api.Responses;
-
-namespace asuka.Mappings;
-
-public static class ContractToGalleryTagResultModelMapping
-{
- public static IReadOnlyList GetTagByGroup(
- this IEnumerable response,
- string filter)
- {
- return response
- .Where(x => x.Type == filter)
- .Select(x => x.Name)
- .ToList();
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using asuka.Api.Responses;
+
+namespace asuka.Mappings;
+
+public static class ContractToGalleryTagResultModelMapping
+{
+ public static IReadOnlyList GetTagByGroup(
+ this IEnumerable response,
+ string filter)
+ {
+ return response
+ .Where(x => x.Type == filter)
+ .Select(x => x.Name)
+ .ToList();
+ }
+}
diff --git a/Mappings/ContractToUserSelectedModelMapping.cs b/Core/Mappings/ContractToUserSelectedModelMapping.cs
similarity index 96%
rename from Mappings/ContractToUserSelectedModelMapping.cs
rename to Core/Mappings/ContractToUserSelectedModelMapping.cs
index 3844f77..e0baba8 100644
--- a/Mappings/ContractToUserSelectedModelMapping.cs
+++ b/Core/Mappings/ContractToUserSelectedModelMapping.cs
@@ -1,24 +1,24 @@
-using System.Collections.Generic;
-using System.Linq;
-using Sharprompt;
-using asuka.Models;
-
-namespace asuka.Mappings;
-
-public static class ContractToUserSelectedModelMapping
-{
- public static IReadOnlyList FilterByUserSelected(
- this IReadOnlyList response)
- {
- var selection = Prompt.MultiSelect("Select to download", response, response.Count,
- textSelector: (result) =>
- {
- var title = string.IsNullOrEmpty(result.Title.Japanese)
- ? (string.IsNullOrEmpty(result.Title.English) ? result.Title.Pretty : result.Title.English)
- : result.Title.Japanese;
- return title;
- });
-
- return selection.ToList();
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using Sharprompt;
+using asuka.Models;
+
+namespace asuka.Mappings;
+
+public static class ContractToUserSelectedModelMapping
+{
+ public static IReadOnlyList FilterByUserSelected(
+ this IReadOnlyList response)
+ {
+ var selection = Prompt.MultiSelect("Select to download", response, response.Count,
+ textSelector: (result) =>
+ {
+ var title = string.IsNullOrEmpty(result.Title.Japanese)
+ ? (string.IsNullOrEmpty(result.Title.English) ? result.Title.Pretty : result.Title.English)
+ : result.Title.Japanese;
+ return title;
+ });
+
+ return selection.ToList();
+ }
+}
diff --git a/Models/GalleryImageResult.cs b/Core/Models/GalleryImageResult.cs
similarity index 95%
rename from Models/GalleryImageResult.cs
rename to Core/Models/GalleryImageResult.cs
index 9571efc..dfae02c 100644
--- a/Models/GalleryImageResult.cs
+++ b/Core/Models/GalleryImageResult.cs
@@ -1,7 +1,7 @@
-namespace asuka.Models;
-
-public record GalleryImageResult
-{
- public string ServerFilename { get; init; }
- public string Filename { get; init; }
-}
+namespace asuka.Models;
+
+public record GalleryImageResult
+{
+ public string ServerFilename { get; init; }
+ public string Filename { get; init; }
+}
diff --git a/Models/GalleryResult.cs b/Core/Models/GalleryResult.cs
similarity index 97%
rename from Models/GalleryResult.cs
rename to Core/Models/GalleryResult.cs
index 29d607e..a8a2724 100644
--- a/Models/GalleryResult.cs
+++ b/Core/Models/GalleryResult.cs
@@ -1,19 +1,19 @@
-using System.Collections.Generic;
-
-namespace asuka.Models;
-
-public record GalleryResult
-{
- public int Id { get; init; }
- public int MediaId { get; init; }
- public GalleryTitleResult Title { get; init; }
- public IReadOnlyList Images { get; init; }
- public IReadOnlyList Artists { get; init; }
- public IReadOnlyList Parodies { get; init; }
- public IReadOnlyList Characters { get; init; }
- public IReadOnlyList Tags { get; init; }
- public IReadOnlyList Categories { get; init; }
- public IReadOnlyList Languages { get; init; }
- public IReadOnlyList Groups { get; init; }
- public int TotalPages { get; init; }
-}
+using System.Collections.Generic;
+
+namespace asuka.Models;
+
+public record GalleryResult
+{
+ public int Id { get; init; }
+ public int MediaId { get; init; }
+ public GalleryTitleResult Title { get; init; }
+ public IReadOnlyList Images { get; init; }
+ public IReadOnlyList Artists { get; init; }
+ public IReadOnlyList Parodies { get; init; }
+ public IReadOnlyList Characters { get; init; }
+ public IReadOnlyList Tags { get; init; }
+ public IReadOnlyList Categories { get; init; }
+ public IReadOnlyList Languages { get; init; }
+ public IReadOnlyList Groups { get; init; }
+ public int TotalPages { get; init; }
+}
diff --git a/Models/GalleryTitleResult.cs b/Core/Models/GalleryTitleResult.cs
similarity index 95%
rename from Models/GalleryTitleResult.cs
rename to Core/Models/GalleryTitleResult.cs
index 9d2ef69..b52a4f8 100644
--- a/Models/GalleryTitleResult.cs
+++ b/Core/Models/GalleryTitleResult.cs
@@ -1,8 +1,8 @@
-namespace asuka.Models;
-
-public record GalleryTitleResult
-{
- public string Japanese { get; init; }
- public string English { get; init; }
- public string Pretty { get; init; }
-}
+namespace asuka.Models;
+
+public record GalleryTitleResult
+{
+ public string Japanese { get; init; }
+ public string English { get; init; }
+ public string Pretty { get; init; }
+}
diff --git a/Models/TachiyomiDetails.cs b/Core/Models/TachiyomiDetails.cs
similarity index 100%
rename from Models/TachiyomiDetails.cs
rename to Core/Models/TachiyomiDetails.cs
diff --git a/Services/GalleryRequestService.cs b/Core/Requests/GalleryRequestService.cs
similarity index 91%
rename from Services/GalleryRequestService.cs
rename to Core/Requests/GalleryRequestService.cs
index 78f3de6..24f1f3c 100644
--- a/Services/GalleryRequestService.cs
+++ b/Core/Requests/GalleryRequestService.cs
@@ -1,49 +1,49 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using asuka.Api;
-using asuka.Api.Queries;
-using asuka.Mappings;
-using asuka.Models;
-
-namespace asuka.Services;
-
-public class GalleryRequestService : IGalleryRequestService
-{
- private readonly IGalleryApi _api;
-
- public GalleryRequestService(IGalleryApi api)
- {
- _api = api;
- }
-
- public async Task FetchSingleAsync(string code)
- {
- var result = await _api.FetchSingle(code)
- .ConfigureAwait(false);
- return result.ToGalleryResult();
- }
-
- public async Task> FetchRecommendedAsync(string code)
- {
- var result = await _api.FetchRecommended(code)
- .ConfigureAwait(false);
- return result.Result.Select(x => x.ToGalleryResult()).ToList();
- }
-
- public async Task> SearchAsync(SearchQuery query)
- {
- var result = await _api.SearchGallery(query)
- .ConfigureAwait(false);
- return result.Result.Select(x => x.ToGalleryResult()).ToList();
- }
-
- public async Task GetTotalGalleryCountAsync()
- {
- var result = await _api.FetchAll()
- .ConfigureAwait(false);
- var id = result.Result[0].Id;
-
- return id;
- }
-}
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using asuka.Api;
+using asuka.Api.Queries;
+using asuka.Core.Mappings;
+using asuka.Core.Models;
+
+namespace asuka.Core.Api;
+
+public class GalleryRequestService : IGalleryRequestService
+{
+ private readonly IGalleryApi _api;
+
+ public GalleryRequestService(IGalleryApi api)
+ {
+ _api = api;
+ }
+
+ public async Task FetchSingleAsync(string code)
+ {
+ var result = await _api.FetchSingle(code)
+ .ConfigureAwait(false);
+ return result.ToGalleryResult();
+ }
+
+ public async Task> FetchRecommendedAsync(string code)
+ {
+ var result = await _api.FetchRecommended(code)
+ .ConfigureAwait(false);
+ return result.Result.Select(x => x.ToGalleryResult()).ToList();
+ }
+
+ public async Task> SearchAsync(SearchQuery query)
+ {
+ var result = await _api.SearchGallery(query)
+ .ConfigureAwait(false);
+ return result.Result.Select(x => x.ToGalleryResult()).ToList();
+ }
+
+ public async Task GetTotalGalleryCountAsync()
+ {
+ var result = await _api.FetchAll()
+ .ConfigureAwait(false);
+ var id = result.Result[0].Id;
+
+ return id;
+ }
+}
diff --git a/Services/IGalleryRequestService.cs b/Core/Requests/IGalleryRequestService.cs
similarity index 86%
rename from Services/IGalleryRequestService.cs
rename to Core/Requests/IGalleryRequestService.cs
index df1ee4b..230c265 100644
--- a/Services/IGalleryRequestService.cs
+++ b/Core/Requests/IGalleryRequestService.cs
@@ -1,14 +1,14 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using asuka.Api.Queries;
-using asuka.Models;
-
-namespace asuka.Services;
-
-public interface IGalleryRequestService
-{
- Task FetchSingleAsync(string url);
- Task> FetchRecommendedAsync(string url);
- Task> SearchAsync(SearchQuery query);
- Task GetTotalGalleryCountAsync();
-}
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using asuka.Api.Queries;
+using asuka.Core.Models;
+
+namespace asuka.Core.Api;
+
+public interface IGalleryRequestService
+{
+ Task FetchSingleAsync(string url);
+ Task> FetchRecommendedAsync(string url);
+ Task> SearchAsync(SearchQuery query);
+ Task GetTotalGalleryCountAsync();
+}
diff --git a/Installers/ConfigureServices.cs b/Installers/ConfigureServices.cs
deleted file mode 100644
index 1dfb707..0000000
--- a/Installers/ConfigureServices.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System;
-using System.Drawing;
-using System.Net.Http;
-using asuka.Api;
-using asuka.CommandParsers;
-using asuka.Compression;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Output;
-using asuka.Services;
-using FluentValidation;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Polly;
-using Polly.Contrib.WaitAndRetry;
-using Refit;
-using ConfigurationManager = asuka.Configuration.ConfigurationManager;
-
-namespace asuka.Installers;
-
-public static class ConfigureServices
-{
- public static void InstallServices(this IServiceCollection services)
- {
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddValidatorsFromAssemblyContaining();
- }
-
- public static void InstallRefitServices(this IServiceCollection services, IConfiguration configuration)
- {
- var cookies = new CloudflareBypass();
-
- var cloudflareClearance = cookies.GetCookieByName("cf_clearance");
- var csrfToken = cookies.GetCookieByName("csrftoken");
-
- if (cloudflareClearance == null || csrfToken == null)
- {
- Colorful.Console.WriteLine("WARNING: Cookies are unset! Your request might fail.", Color.Red);
- }
-
- var configureRefit = new RefitSettings
- {
- ContentSerializer = new NewtonsoftJsonContentSerializer(),
- HttpMessageHandlerFactory = () =>
- {
- var handler = new HttpClientHandler();
-
- if (cloudflareClearance != null)
- {
- handler.CookieContainer.Add(cloudflareClearance);
- }
-
- if (csrfToken != null)
- {
- handler.CookieContainer.Add(csrfToken);
- }
-
- return handler;
- }
- };
-
- services.AddRefitClient(configureRefit)
- .AddTransientHttpErrorPolicy(ConfigureErrorPolicyBuilder)
- .ConfigureHttpClient(httpClient =>
- {
- httpClient.BaseAddress = new Uri(configuration["ApiBaseAddress"]);
- httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(cookies.UserAgent);
- });
- services.AddRefitClient()
- .AddTransientHttpErrorPolicy(ConfigureErrorPolicyBuilder)
- .ConfigureHttpClient(httpClient =>
- {
- httpClient.BaseAddress = new Uri(configuration["ImageBaseAddress"]);
- httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(cookies.UserAgent);
- });
- }
-
- private static IAsyncPolicy ConfigureErrorPolicyBuilder(
- PolicyBuilder builder)
- {
- var delay = Backoff.DecorrelatedJitterBackoffV2(
- TimeSpan.FromSeconds(1), 5);
- return builder.WaitAndRetryAsync(delay, (_, span) =>
- {
- Colorful.Console.WriteLine($"Retrying in {span.Seconds}");
- });
- }
-}
diff --git a/Installers/IInstaller.cs b/Installers/IInstaller.cs
new file mode 100644
index 0000000..64ec29e
--- /dev/null
+++ b/Installers/IInstaller.cs
@@ -0,0 +1,6 @@
+namespace asuka.Installers;
+
+public interface IInstaller
+{
+
+}
\ No newline at end of file
diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs
new file mode 100644
index 0000000..8f2be33
--- /dev/null
+++ b/Installers/InstallServices.cs
@@ -0,0 +1,32 @@
+using asuka.Commandline.Parsers;
+using asuka.Configuration;
+using asuka.Core.Api;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Output.Writer;
+using FluentValidation;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using ConfigurationManager = asuka.Configuration.ConfigurationManager;
+
+namespace asuka.Installers;
+
+public class ServiceCollection
+{
+ public void InstallServices(IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddValidatorsFromAssemblyContaining();
+ }
+}
diff --git a/Installers/InstallerExtension.cs b/Installers/InstallerExtension.cs
new file mode 100644
index 0000000..c9a3cd0
--- /dev/null
+++ b/Installers/InstallerExtension.cs
@@ -0,0 +1,6 @@
+namespace asuka.Installers;
+
+public class InstallerExtension
+{
+
+}
\ No newline at end of file
diff --git a/Installers/Refit/ConfigureRefitService.cs b/Installers/Refit/ConfigureRefitService.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Output/ProgressService/IProgressService.cs b/Output/ProgressService/IProgressService.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Utils/ProgressBarConfiguration.cs b/Output/ProgressService/ProgressBarConfiguration.cs
similarity index 96%
rename from Utils/ProgressBarConfiguration.cs
rename to Output/ProgressService/ProgressBarConfiguration.cs
index 70c2604..093ad90 100644
--- a/Utils/ProgressBarConfiguration.cs
+++ b/Output/ProgressService/ProgressBarConfiguration.cs
@@ -1,21 +1,21 @@
-using System;
-using ShellProgressBar;
-
-namespace asuka.Utils;
-
-public static class ProgressBarConfiguration
-{
- public static readonly ProgressBarOptions BarOption = new()
- {
- ForegroundColor = ConsoleColor.Yellow,
- ForegroundColorDone = ConsoleColor.Green,
- ProgressCharacter = '-'
- };
-
- public static readonly ProgressBarOptions CompressOption = new()
- {
- ForegroundColor = ConsoleColor.DarkRed,
- ForegroundColorDone = ConsoleColor.Green,
- ProgressCharacter = '#'
- };
-}
+using System;
+using ShellProgressBar;
+
+namespace asuka.Utils;
+
+public static class ProgressBarConfiguration
+{
+ public static readonly ProgressBarOptions BarOption = new()
+ {
+ ForegroundColor = ConsoleColor.Yellow,
+ ForegroundColorDone = ConsoleColor.Green,
+ ProgressCharacter = '-'
+ };
+
+ public static readonly ProgressBarOptions CompressOption = new()
+ {
+ ForegroundColor = ConsoleColor.DarkRed,
+ ForegroundColorDone = ConsoleColor.Green,
+ ProgressCharacter = '#'
+ };
+}
diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs
new file mode 100644
index 0000000..06bd6f8
--- /dev/null
+++ b/Output/ProgressService/ProgressService.cs
@@ -0,0 +1,6 @@
+namespace asuka.Output.ProgressService;
+
+public class ProgressService
+{
+
+}
\ No newline at end of file
diff --git a/Output/ConsoleWriter.cs b/Output/Writer/ConsoleWriter.cs
similarity index 96%
rename from Output/ConsoleWriter.cs
rename to Output/Writer/ConsoleWriter.cs
index c1d58b4..90197e5 100644
--- a/Output/ConsoleWriter.cs
+++ b/Output/Writer/ConsoleWriter.cs
@@ -1,50 +1,50 @@
-using System.Collections.Generic;
-using System.Drawing;
-using asuka.Configuration;
-using FluentValidation.Results;
-using Console = Colorful.Console;
-
-namespace asuka.Output;
-
-public class ConsoleWriter : IConsoleWriter
-{
- private readonly IConfigurationManager _configurationManager;
-
- public ConsoleWriter(IConfigurationManager configurationManager)
- {
- _configurationManager = configurationManager;
- }
-
- private Color GetColor(Color forWhiteTheme, Color forDarkTheme)
- {
- return _configurationManager.Values.ConsoleTheme == "dark" ? forDarkTheme : forWhiteTheme;
- }
-
- public void WriteLine(object message)
- {
- Console.WriteLine(message, GetColor(Color.Red, Color.Aqua));
- }
-
- public void WarningLine(object message)
- {
- Console.WriteLine(message, GetColor(Color.Blue, Color.Yellow));
- }
-
- public void ErrorLine(string message)
- {
- Console.WriteLine(message, GetColor(Color.Teal, Color.IndianRed));
- }
-
- public void SuccessLine(string message)
- {
- Console.WriteLine(message, GetColor(Color.Purple, Color.Green));
- }
-
- public void ValidationErrors(IEnumerable errors)
- {
- foreach (var error in errors)
- {
- ErrorLine($"{error.ErrorCode}: {error.ErrorMessage}");
- }
- }
-}
+using System.Collections.Generic;
+using System.Drawing;
+using asuka.Configuration;
+using FluentValidation.Results;
+using Console = Colorful.Console;
+
+namespace asuka.Output;
+
+public class ConsoleWriter : IConsoleWriter
+{
+ private readonly IConfigurationManager _configurationManager;
+
+ public ConsoleWriter(IConfigurationManager configurationManager)
+ {
+ _configurationManager = configurationManager;
+ }
+
+ private Color GetColor(Color forWhiteTheme, Color forDarkTheme)
+ {
+ return _configurationManager.Values.ConsoleTheme == "dark" ? forDarkTheme : forWhiteTheme;
+ }
+
+ public void WriteLine(object message)
+ {
+ Console.WriteLine(message, GetColor(Color.Red, Color.Aqua));
+ }
+
+ public void WarningLine(object message)
+ {
+ Console.WriteLine(message, GetColor(Color.Blue, Color.Yellow));
+ }
+
+ public void ErrorLine(string message)
+ {
+ Console.WriteLine(message, GetColor(Color.Teal, Color.IndianRed));
+ }
+
+ public void SuccessLine(string message)
+ {
+ Console.WriteLine(message, GetColor(Color.Purple, Color.Green));
+ }
+
+ public void ValidationErrors(IEnumerable errors)
+ {
+ foreach (var error in errors)
+ {
+ ErrorLine($"{error.ErrorCode}: {error.ErrorMessage}");
+ }
+ }
+}
diff --git a/Output/IConsoleWriter.cs b/Output/Writer/IConsoleWriter.cs
similarity index 96%
rename from Output/IConsoleWriter.cs
rename to Output/Writer/IConsoleWriter.cs
index 72f402b..f40a8e9 100644
--- a/Output/IConsoleWriter.cs
+++ b/Output/Writer/IConsoleWriter.cs
@@ -1,13 +1,13 @@
-using System.Collections.Generic;
-using FluentValidation.Results;
-
-namespace asuka.Output;
-
-public interface IConsoleWriter
-{
- void WriteLine(object message);
- void WarningLine(object message);
- void ErrorLine(string message);
- void SuccessLine(string message);
- void ValidationErrors(IEnumerable errors);
-}
+using System.Collections.Generic;
+using FluentValidation.Results;
+
+namespace asuka.Output;
+
+public interface IConsoleWriter
+{
+ void WriteLine(object message);
+ void WarningLine(object message);
+ void ErrorLine(string message);
+ void SuccessLine(string message);
+ void ValidationErrors(IEnumerable errors);
+}
From 7b1f550c7514a1f9f71317039edb7ea4768f1582 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 19:49:05 +0900
Subject: [PATCH 03/21] Refactor services into proper folder structure
- Fixed workflows to use .NET 7 images
---
.circleci/config.yml | 2 +-
.github/workflows/dotnet.yml | 2 +-
AsukaApplication.cs | 38 +++-----
Commandline/Options/ConfigureOptions.cs | 2 +-
Commandline/Options/FileCommandOptions.cs | 2 +-
Commandline/Options/GetOptions.cs | 2 +-
Commandline/Options/ICommonOptions.cs | 2 +-
Commandline/Options/IRequiresInputOption.cs | 2 +-
Commandline/Options/RandomOptions.cs | 3 +-
Commandline/Options/RecommendOptions.cs | 2 +-
Commandline/Options/SearchOptions.cs | 3 +-
Commandline/Parsers/ConfigureCommand.cs | 6 +-
Commandline/Parsers/FileCommandService.cs | 62 +++++++------
Commandline/Parsers/GetCommandService.cs | 32 ++++---
Commandline/Parsers/IConfigureCommand.cs | 4 +-
Commandline/Parsers/IFileCommandService.cs | 5 +-
Commandline/Parsers/IGetCommandService.cs | 5 +-
Commandline/Parsers/IRandomCommandService.cs | 5 +-
.../Parsers/IRecommendCommandService.cs | 5 +-
Commandline/Parsers/ISearchCommandService.cs | 5 +-
Commandline/Parsers/RandomCommandService.cs | 27 +++---
.../Parsers/RecommendCommandService.cs | 43 ++++-----
Commandline/Parsers/SearchCommandService.cs | 44 +++++----
Core/Api/IGalleryApi.cs | 6 +-
Core/Api/IGalleryImage.cs | 2 +-
Core/Api/Queries/SearchQuery.cs | 2 +-
.../Responses/GalleryImageObjectResponse.cs | 2 +-
Core/Api/Responses/GalleryImageResponse.cs | 2 +-
Core/Api/Responses/GalleryListResponse.cs | 2 +-
Core/Api/Responses/GalleryResponse.cs | 2 +-
Core/Api/Responses/GallerySearchResponse.cs | 2 +-
Core/Api/Responses/GalleryTagResponse.cs | 2 +-
Core/Api/Responses/GalleryTitleResponse.cs | 2 +-
Core/Compression/IPackArchiveToCbz.cs | 7 +-
Core/Compression/PackArchiveToCbz.cs | 23 +++--
Core/Downloader/DownloadService.cs | 93 ++++++++++---------
Core/Downloader/IDownloadService.cs | 12 +--
.../InternalTypes/DownloadResult.cs | 2 +-
.../InternalTypes/FetchImageParameter.cs | 10 +-
.../Downloader/InternalTypes/PrepareResult.cs | 2 +-
...ontractToGalleryImageResultModelMapping.cs | 6 +-
.../ContractToGalleryResultModelMapping.cs | 6 +-
.../ContractToGalleryTagResultModelMapping.cs | 4 +-
.../ContractToUserSelectedModelMapping.cs | 4 +-
Core/Models/GalleryImageResult.cs | 2 +-
Core/Models/GalleryResult.cs | 2 +-
Core/Models/GalleryTitleResult.cs | 2 +-
Core/Models/TachiyomiDetails.cs | 2 +-
Core/Requests/GalleryRequestService.cs | 6 +-
Core/Requests/IGalleryRequestService.cs | 4 +-
Installers/IInstaller.cs | 7 +-
Installers/InstallServices.cs | 28 +++---
Installers/InstallerExtension.cs | 21 ++++-
Installers/Refit/ConfigureRefitService.cs | 80 ++++++++++++++++
LICENSE | 4 +-
Output/DisplayInformation.cs | 2 +-
Output/ProgressService/IProgressService.cs | 11 +++
.../ProgressBarConfiguration.cs | 2 +-
Output/ProgressService/ProgressService.cs | 31 ++++++-
Output/Writer/ConsoleWriter.cs | 2 +-
Output/Writer/IConsoleWriter.cs | 2 +-
Program.cs | 3 +-
Validators/ConfigurationValidator.cs | 2 +-
Validators/RequiresInputOptionValidator.cs | 2 +-
Validators/SearchValidator.cs | 2 +-
appsettings.json | 6 +-
asuka.csproj | 4 +
67 files changed, 447 insertions(+), 276 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e2dce20..b940072 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -6,7 +6,7 @@
jobs:
build:
docker:
- - image: mcr.microsoft.com/dotnet/sdk:6.0
+ - image: mcr.microsoft.com/dotnet/sdk:7.0
steps:
- checkout
- run:
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 16a5a79..bdf0740 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 6.0.x
+ dotnet-version: 7.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
diff --git a/AsukaApplication.cs b/AsukaApplication.cs
index f5e1d30..53d7b71 100644
--- a/AsukaApplication.cs
+++ b/AsukaApplication.cs
@@ -1,10 +1,9 @@
-using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using asuka.Commandline.Options;
+using asuka.Commandline.Parsers;
using CommandLine;
-using asuka.CommandOptions;
-using asuka.CommandParsers;
-using asuka.Output;
+using asuka.Output.Writer;
namespace asuka;
@@ -38,25 +37,16 @@ public class AsukaApplication
public async Task RunAsync(IEnumerable args)
{
- try
- {
- var parser = Parser.Default
- .ParseArguments(args);
- await parser.MapResult(
- async (GetOptions opts) => { await _getCommand.RunAsync(opts); },
- async (RecommendOptions opts) => { await _recommendCommand.RunAsync(opts); },
- async (SearchOptions opts) => { await _searchCommand.RunAsync(opts); },
- async (RandomOptions opts) => { await _randomCommand.RunAsync(opts); },
- async (FileCommandOptions opts) => { await _fileCommand.RunAsync(opts); },
- async (ConfigureOptions opts) => { await _configureCommand.RunAsync(opts); },
- _ => Task.FromResult(1));
- _console.SuccessLine("Task completed.");
- }
- catch (Exception err)
- {
- _console.ErrorLine($"An exception occured. Error: {err.Message}");
- _console.ErrorLine("Full Error:");
- _console.WriteLine(err);
- }
+ var parser = Parser.Default
+ .ParseArguments(args);
+ await parser.MapResult(
+ async (GetOptions opts) => { await _getCommand.RunAsync(opts); },
+ async (RecommendOptions opts) => { await _recommendCommand.RunAsync(opts); },
+ async (SearchOptions opts) => { await _searchCommand.RunAsync(opts); },
+ async (RandomOptions opts) => { await _randomCommand.RunAsync(opts); },
+ async (FileCommandOptions opts) => { await _fileCommand.RunAsync(opts); },
+ async (ConfigureOptions opts) => { await _configureCommand.RunAsync(opts); },
+ _ => Task.FromResult(1));
+ _console.SuccessLine("Task completed.");
}
}
diff --git a/Commandline/Options/ConfigureOptions.cs b/Commandline/Options/ConfigureOptions.cs
index 0231853..ba3202a 100644
--- a/Commandline/Options/ConfigureOptions.cs
+++ b/Commandline/Options/ConfigureOptions.cs
@@ -1,6 +1,6 @@
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
[Verb("config", HelpText = "Configure the client")]
public record ConfigureOptions
diff --git a/Commandline/Options/FileCommandOptions.cs b/Commandline/Options/FileCommandOptions.cs
index 8e631d4..c76803a 100644
--- a/Commandline/Options/FileCommandOptions.cs
+++ b/Commandline/Options/FileCommandOptions.cs
@@ -1,6 +1,6 @@
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
[Verb("file", HelpText = "Download galleries from text file")]
public record FileCommandOptions : ICommonOptions
diff --git a/Commandline/Options/GetOptions.cs b/Commandline/Options/GetOptions.cs
index 442564d..515a4ef 100644
--- a/Commandline/Options/GetOptions.cs
+++ b/Commandline/Options/GetOptions.cs
@@ -1,6 +1,6 @@
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
[Verb("get", HelpText = "Download a Single Gallery from URL.")]
public record GetOptions : IRequiresInputOption, ICommonOptions
diff --git a/Commandline/Options/ICommonOptions.cs b/Commandline/Options/ICommonOptions.cs
index 1b2c0ee..e92c8ff 100644
--- a/Commandline/Options/ICommonOptions.cs
+++ b/Commandline/Options/ICommonOptions.cs
@@ -1,6 +1,6 @@
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
public interface ICommonOptions
{
diff --git a/Commandline/Options/IRequiresInputOption.cs b/Commandline/Options/IRequiresInputOption.cs
index ccd2ddf..42cce75 100644
--- a/Commandline/Options/IRequiresInputOption.cs
+++ b/Commandline/Options/IRequiresInputOption.cs
@@ -1,6 +1,6 @@
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
public interface IRequiresInputOption
{
diff --git a/Commandline/Options/RandomOptions.cs b/Commandline/Options/RandomOptions.cs
index ce69618..af90109 100644
--- a/Commandline/Options/RandomOptions.cs
+++ b/Commandline/Options/RandomOptions.cs
@@ -1,7 +1,6 @@
using CommandLine;
-using CommandLine.Text;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
[Verb("random", HelpText = "Randomly pick a gallery.")]
public record RandomOptions : ICommonOptions
diff --git a/Commandline/Options/RecommendOptions.cs b/Commandline/Options/RecommendOptions.cs
index 0c303f6..8c37f50 100644
--- a/Commandline/Options/RecommendOptions.cs
+++ b/Commandline/Options/RecommendOptions.cs
@@ -1,6 +1,6 @@
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
[Verb("recommend", HelpText = "Download recommendation from the gallery URL.")]
public record RecommendOptions : ICommonOptions, IRequiresInputOption
diff --git a/Commandline/Options/SearchOptions.cs b/Commandline/Options/SearchOptions.cs
index 0eafbb1..76581ab 100644
--- a/Commandline/Options/SearchOptions.cs
+++ b/Commandline/Options/SearchOptions.cs
@@ -1,8 +1,7 @@
using System.Collections.Generic;
-using asuka.Api.Queries;
using CommandLine;
-namespace asuka.CommandOptions;
+namespace asuka.Commandline.Options;
[Verb("search", HelpText = "Search something in the gallery")]
public record SearchOptions : ICommonOptions
diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs
index a2e4ff4..2c86425 100644
--- a/Commandline/Parsers/ConfigureCommand.cs
+++ b/Commandline/Parsers/ConfigureCommand.cs
@@ -1,12 +1,12 @@
-using System.Text.Json.Serialization;
using System.Threading.Tasks;
-using asuka.CommandOptions;
+using asuka.Commandline.Options;
using asuka.Configuration;
using asuka.Output;
+using asuka.Output.Writer;
using FluentValidation;
using Newtonsoft.Json;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public class ConfigureCommand : IConfigureCommand
{
diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs
index 641fd97..f6fbaeb 100644
--- a/Commandline/Parsers/FileCommandService.cs
+++ b/Commandline/Parsers/FileCommandService.cs
@@ -4,29 +4,35 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Output;
-using asuka.Services;
-using asuka.Utils;
-using ShellProgressBar;
+using asuka.Commandline.Options;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Core.Requests;
+using asuka.Output.ProgressService;
+using asuka.Output.Writer;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
-public class FileCommandService : IFileCommandService
+public partial class FileCommandService : IFileCommandService
{
private readonly IGalleryRequestService _api;
private readonly IConsoleWriter _console;
private readonly IDownloadService _download;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IProgressService _progressService;
+ private readonly IPackArchiveToCbz _pack;
- public FileCommandService(IGalleryRequestService api, IConsoleWriter console, IDownloadService download, IConfigurationManager configurationManager)
+ public FileCommandService(
+ IGalleryRequestService api,
+ IConsoleWriter console,
+ IDownloadService download,
+ IProgressService progressService,
+ IPackArchiveToCbz pack)
{
_api = api;
_console = console;
_download = download;
- _configurationManager = configurationManager;
+ _progressService = progressService;
+ _pack = pack;
}
public async Task RunAsync(FileCommandOptions opts)
@@ -53,32 +59,28 @@ public async Task RunAsync(FileCommandOptions opts)
return;
}
- using var progress = new ProgressBar(
- validUrls.Count,
- "downloading from text file...",
- ProgressBarConfiguration.BarOption);
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+ _progressService.CreateMasterProgress(validUrls.Count, "downloading from text file...");
+ var progress = _progressService.GetMasterProgress();
foreach (var url in validUrls)
{
- var code = Regex.Match(url, @"\d+").Value;
+ var code = NumericRegex().Match(url).Value;
var response = await _api.FetchSingleAsync(code);
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
+ var result = await _download.DownloadAsync(response, opts.Output);
+
+ if (opts.Pack)
+ {
+ var destination = result.DestinationPath[..^1] + ".cbz";
+ await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ }
progress.Tick();
}
}
private static IReadOnlyList FilterValidUrls(IEnumerable urls)
{
- return urls.Where(url =>
- {
- const string pattern = @"^http(s)?:\/\/(nhentai\.net)\b([//g]*)\b([\d]{1,6})\/?$";
- var regexp = new Regex(pattern, RegexOptions.IgnoreCase);
-
- return regexp.IsMatch(url);
- }).ToList();
+ return urls.Where(url => WebUrlRegex().IsMatch(url)).ToList();
}
private static bool IsFileExceedingToFileSizeLimit(string inputFile)
@@ -86,4 +88,10 @@ private static bool IsFileExceedingToFileSizeLimit(string inputFile)
var fileSize = new FileInfo(inputFile).Length;
return fileSize > 5242880;
}
+
+ [GeneratedRegex("\\d+")]
+ private static partial Regex NumericRegex();
+
+ [GeneratedRegex("^http(s)?:\\/\\/(nhentai\\.net)\\b([//g]*)\\b([\\d]{1,6})\\/?$", RegexOptions.IgnoreCase, "en-JP")]
+ private static partial Regex WebUrlRegex();
}
diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs
index 8616dbd..3e2a6b1 100644
--- a/Commandline/Parsers/GetCommandService.cs
+++ b/Commandline/Parsers/GetCommandService.cs
@@ -1,15 +1,13 @@
-using System;
-using System.Text.RegularExpressions;
using System.Threading.Tasks;
-using FluentValidation;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
+using asuka.Commandline.Options;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Core.Requests;
using asuka.Output;
-using asuka.Services;
-using Microsoft.Extensions.Configuration;
+using asuka.Output.Writer;
+using FluentValidation;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public class GetCommandService : IGetCommandService
{
@@ -17,20 +15,20 @@ public class GetCommandService : IGetCommandService
private readonly IValidator _validator;
private readonly IDownloadService _download;
private readonly IConsoleWriter _console;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IPackArchiveToCbz _pack;
public GetCommandService(
IGalleryRequestService api,
IValidator validator,
IDownloadService download,
IConsoleWriter console,
- IConfigurationManager configurationManager)
+ IPackArchiveToCbz pack)
{
_api = api;
_validator = validator;
_download = download;
_console = console;
- _configurationManager = configurationManager;
+ _pack = pack;
}
public async Task RunAsync(GetOptions opts)
@@ -49,8 +47,12 @@ public async Task RunAsync(GetOptions opts)
{
return;
}
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, null);
+
+ var result = await _download.DownloadAsync(response, opts.Output);
+ if (opts.Pack)
+ {
+ var destination = result.DestinationPath[..^1] + ".cbz";
+ await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ }
}
}
diff --git a/Commandline/Parsers/IConfigureCommand.cs b/Commandline/Parsers/IConfigureCommand.cs
index 351aa9c..23fa243 100644
--- a/Commandline/Parsers/IConfigureCommand.cs
+++ b/Commandline/Parsers/IConfigureCommand.cs
@@ -1,7 +1,7 @@
using System.Threading.Tasks;
-using asuka.CommandOptions;
+using asuka.Commandline.Options;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public interface IConfigureCommand
{
diff --git a/Commandline/Parsers/IFileCommandService.cs b/Commandline/Parsers/IFileCommandService.cs
index 9d7fd13..1bbb664 100644
--- a/Commandline/Parsers/IFileCommandService.cs
+++ b/Commandline/Parsers/IFileCommandService.cs
@@ -1,8 +1,7 @@
using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
+using asuka.Commandline.Options;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public interface IFileCommandService
{
diff --git a/Commandline/Parsers/IGetCommandService.cs b/Commandline/Parsers/IGetCommandService.cs
index b3bd708..42fb1e0 100644
--- a/Commandline/Parsers/IGetCommandService.cs
+++ b/Commandline/Parsers/IGetCommandService.cs
@@ -1,8 +1,7 @@
using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
+using asuka.Commandline.Options;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public interface IGetCommandService
{
diff --git a/Commandline/Parsers/IRandomCommandService.cs b/Commandline/Parsers/IRandomCommandService.cs
index 19c18aa..40d41cd 100644
--- a/Commandline/Parsers/IRandomCommandService.cs
+++ b/Commandline/Parsers/IRandomCommandService.cs
@@ -1,8 +1,7 @@
using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
+using asuka.Commandline.Options;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public interface IRandomCommandService
{
diff --git a/Commandline/Parsers/IRecommendCommandService.cs b/Commandline/Parsers/IRecommendCommandService.cs
index 6055da5..c60d97c 100644
--- a/Commandline/Parsers/IRecommendCommandService.cs
+++ b/Commandline/Parsers/IRecommendCommandService.cs
@@ -1,8 +1,7 @@
using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
+using asuka.Commandline.Options;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public interface IRecommendCommandService
{
diff --git a/Commandline/Parsers/ISearchCommandService.cs b/Commandline/Parsers/ISearchCommandService.cs
index 28289b3..3d2552a 100644
--- a/Commandline/Parsers/ISearchCommandService.cs
+++ b/Commandline/Parsers/ISearchCommandService.cs
@@ -1,8 +1,7 @@
using System.Threading.Tasks;
-using asuka.CommandOptions;
-using Microsoft.Extensions.Configuration;
+using asuka.Commandline.Options;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public interface ISearchCommandService
{
diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
index 8ffaa40..1be4b4d 100644
--- a/Commandline/Parsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -1,32 +1,33 @@
using System;
using System.Threading.Tasks;
-using Sharprompt;
-using asuka.CommandOptions;
+using asuka.Commandline.Options;
using asuka.Configuration;
-using asuka.Downloader;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Core.Requests;
using asuka.Output;
-using asuka.Services;
-using Microsoft.Extensions.Configuration;
+using asuka.Output.Writer;
+using Sharprompt;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public class RandomCommandService : IRandomCommandService
{
private readonly IDownloadService _download;
private readonly IGalleryRequestService _api;
private readonly IConsoleWriter _console;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IPackArchiveToCbz _pack;
public RandomCommandService(
IDownloadService download,
IGalleryRequestService api,
IConsoleWriter console,
- IConfigurationManager configurationManager)
+ IPackArchiveToCbz pack)
{
_download = download;
_api = api;
_console = console;
- _configurationManager = configurationManager;
+ _pack = pack;
}
public async Task RunAsync(RandomOptions opts)
@@ -47,8 +48,12 @@ public async Task RunAsync(RandomOptions opts)
continue;
}
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, null);
+ var result = await _download.DownloadAsync(response, opts.Output);
+ if (opts.Pack)
+ {
+ var destination = result.DestinationPath[..^1] + ".cbz";
+ await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ }
break;
}
}
diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs
index fd8d9ac..db7d7b1 100644
--- a/Commandline/Parsers/RecommendCommandService.cs
+++ b/Commandline/Parsers/RecommendCommandService.cs
@@ -1,17 +1,14 @@
-using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using asuka.Commandline.Options;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Core.Mappings;
+using asuka.Core.Requests;
+using asuka.Output.ProgressService;
+using asuka.Output.Writer;
using FluentValidation;
-using ShellProgressBar;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Mappings;
-using asuka.Output;
-using asuka.Services;
-using asuka.Utils;
-using Microsoft.Extensions.Configuration;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public class RecommendCommandService : IRecommendCommandService
{
@@ -19,20 +16,23 @@ public class RecommendCommandService : IRecommendCommandService
private readonly IGalleryRequestService _api;
private readonly IDownloadService _download;
private readonly IConsoleWriter _console;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IProgressService _progressService;
+ private readonly IPackArchiveToCbz _pack;
public RecommendCommandService(
IValidator validator,
IGalleryRequestService api,
IDownloadService download,
IConsoleWriter console,
- IConfigurationManager configurationManager)
+ IProgressService progressService,
+ IPackArchiveToCbz pack)
{
_validator = validator;
_api = api;
_download = download;
_console = console;
- _configurationManager = configurationManager;
+ _progressService = progressService;
+ _pack = pack;
}
public async Task RunAsync(RecommendOptions opts)
@@ -45,19 +45,20 @@ public async Task RunAsync(RecommendOptions opts)
}
var responses = await _api.FetchRecommendedAsync(opts.Input.ToString());
-
var selection = responses.FilterByUserSelected();
// Initialise the Progress bar.
- using var progress = new ProgressBar(selection.Count, $"[task] recommend from id: {opts.Input}",
- ProgressBarConfiguration.BarOption);
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
+ _progressService.CreateMasterProgress(selection.Count, $"[task] recommend from id: {opts.Input}");
+ var progress = _progressService.GetMasterProgress();
foreach (var response in selection)
{
-
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
+ var result = await _download.DownloadAsync(response, opts.Output);
+ if (opts.Pack)
+ {
+ var destination = result.DestinationPath[..^1] + ".cbz";
+ await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ }
progress.Tick();
}
}
diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs
index 79af539..7150c03 100644
--- a/Commandline/Parsers/SearchCommandService.cs
+++ b/Commandline/Parsers/SearchCommandService.cs
@@ -1,19 +1,17 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using asuka.Commandline.Options;
+using asuka.Core.Api.Queries;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Core.Mappings;
+using asuka.Core.Requests;
+using asuka.Output.ProgressService;
+using asuka.Output.Writer;
using FluentValidation;
-using ShellProgressBar;
-using asuka.Api.Queries;
-using asuka.CommandOptions;
-using asuka.Configuration;
-using asuka.Downloader;
-using asuka.Mappings;
-using asuka.Output;
-using asuka.Services;
-using asuka.Utils;
-using Microsoft.Extensions.Configuration;
-namespace asuka.CommandParsers;
+namespace asuka.Commandline.Parsers;
public class SearchCommandService : ISearchCommandService
{
@@ -21,20 +19,23 @@ public class SearchCommandService : ISearchCommandService
private readonly IValidator _validator;
private readonly IConsoleWriter _console;
private readonly IDownloadService _download;
- private readonly IConfigurationManager _configurationManager;
+ private readonly IProgressService _progressService;
+ private readonly IPackArchiveToCbz _pack;
public SearchCommandService(
IGalleryRequestService api,
IValidator validator,
IConsoleWriter console,
IDownloadService download,
- IConfigurationManager configurationManager)
+ IProgressService progressService,
+ IPackArchiveToCbz pack)
{
_api = api;
_validator = validator;
_console = console;
_download = download;
- _configurationManager = configurationManager;
+ _progressService = progressService;
+ _pack = pack;
}
public async Task RunAsync(SearchOptions opts)
@@ -70,14 +71,17 @@ public async Task RunAsync(SearchOptions opts)
var selection = responses.FilterByUserSelected();
// Initialise the Progress bar.
- using var progress = new ProgressBar(selection.Count, $"[task] search download",
- ProgressBarConfiguration.BarOption);
-
- var useTachiyomiLayout = opts.UseTachiyomiLayout || _configurationManager.Values.UseTachiyomiLayout;
-
+ _progressService.CreateMasterProgress(selection.Count, "[task] search download");
+ var progress = _progressService.GetMasterProgress();
+
foreach (var response in selection)
{
- await _download.DownloadAsync(response, opts.Output, opts.Pack, useTachiyomiLayout, progress);
+ var result = await _download.DownloadAsync(response, opts.Output);
+ if (opts.Pack)
+ {
+ var destination = result.DestinationPath[..^1] + ".cbz";
+ await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ }
progress.Tick();
}
}
diff --git a/Core/Api/IGalleryApi.cs b/Core/Api/IGalleryApi.cs
index 9e7ca15..8538c84 100644
--- a/Core/Api/IGalleryApi.cs
+++ b/Core/Api/IGalleryApi.cs
@@ -1,9 +1,9 @@
using System.Threading.Tasks;
-using asuka.Api.Queries;
-using asuka.Api.Responses;
+using asuka.Core.Api.Queries;
+using asuka.Core.Api.Responses;
using Refit;
-namespace asuka.Api;
+namespace asuka.Core.Api;
public interface IGalleryApi
{
diff --git a/Core/Api/IGalleryImage.cs b/Core/Api/IGalleryImage.cs
index abdbb84..76329c2 100644
--- a/Core/Api/IGalleryImage.cs
+++ b/Core/Api/IGalleryImage.cs
@@ -2,7 +2,7 @@
using System.Threading.Tasks;
using Refit;
-namespace asuka.Api;
+namespace asuka.Core.Api;
public interface IGalleryImage
{
diff --git a/Core/Api/Queries/SearchQuery.cs b/Core/Api/Queries/SearchQuery.cs
index d325a78..96e0edb 100644
--- a/Core/Api/Queries/SearchQuery.cs
+++ b/Core/Api/Queries/SearchQuery.cs
@@ -1,6 +1,6 @@
using Refit;
-namespace asuka.Api.Queries;
+namespace asuka.Core.Api.Queries;
public record SearchQuery
{
diff --git a/Core/Api/Responses/GalleryImageObjectResponse.cs b/Core/Api/Responses/GalleryImageObjectResponse.cs
index 7e0bdfd..431e7ac 100644
--- a/Core/Api/Responses/GalleryImageObjectResponse.cs
+++ b/Core/Api/Responses/GalleryImageObjectResponse.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GalleryImageObjectResponse
{
diff --git a/Core/Api/Responses/GalleryImageResponse.cs b/Core/Api/Responses/GalleryImageResponse.cs
index 7439078..0e9d2db 100644
--- a/Core/Api/Responses/GalleryImageResponse.cs
+++ b/Core/Api/Responses/GalleryImageResponse.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GalleryImageResponse
{
diff --git a/Core/Api/Responses/GalleryListResponse.cs b/Core/Api/Responses/GalleryListResponse.cs
index b206026..d820d09 100644
--- a/Core/Api/Responses/GalleryListResponse.cs
+++ b/Core/Api/Responses/GalleryListResponse.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GalleryListResponse
{
diff --git a/Core/Api/Responses/GalleryResponse.cs b/Core/Api/Responses/GalleryResponse.cs
index 43c1c7b..949a586 100644
--- a/Core/Api/Responses/GalleryResponse.cs
+++ b/Core/Api/Responses/GalleryResponse.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GalleryResponse
{
diff --git a/Core/Api/Responses/GallerySearchResponse.cs b/Core/Api/Responses/GallerySearchResponse.cs
index 47d782e..8c470e1 100644
--- a/Core/Api/Responses/GallerySearchResponse.cs
+++ b/Core/Api/Responses/GallerySearchResponse.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GallerySearchResponse : GalleryListResponse
{
diff --git a/Core/Api/Responses/GalleryTagResponse.cs b/Core/Api/Responses/GalleryTagResponse.cs
index c53e174..637b59c 100644
--- a/Core/Api/Responses/GalleryTagResponse.cs
+++ b/Core/Api/Responses/GalleryTagResponse.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GalleryTagResponse
{
diff --git a/Core/Api/Responses/GalleryTitleResponse.cs b/Core/Api/Responses/GalleryTitleResponse.cs
index fdc905d..db66645 100644
--- a/Core/Api/Responses/GalleryTitleResponse.cs
+++ b/Core/Api/Responses/GalleryTitleResponse.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
-namespace asuka.Api.Responses;
+namespace asuka.Core.Api.Responses;
public record GalleryTitleResponse
{
diff --git a/Core/Compression/IPackArchiveToCbz.cs b/Core/Compression/IPackArchiveToCbz.cs
index 2330829..db36a57 100644
--- a/Core/Compression/IPackArchiveToCbz.cs
+++ b/Core/Compression/IPackArchiveToCbz.cs
@@ -1,9 +1,10 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using ShellProgressBar;
-namespace asuka.Compression;
+namespace asuka.Core.Compression;
public interface IPackArchiveToCbz
{
- Task RunAsync(string folderName, string[] imageFiles, string output, IProgressBar parentBar);
+ Task RunAsync(string folderName, IList imageFiles, string output);
}
diff --git a/Core/Compression/PackArchiveToCbz.cs b/Core/Compression/PackArchiveToCbz.cs
index cdad880..bd2e376 100644
--- a/Core/Compression/PackArchiveToCbz.cs
+++ b/Core/Compression/PackArchiveToCbz.cs
@@ -1,19 +1,30 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
-using asuka.Utils;
+using asuka.Output.ProgressService;
using ShellProgressBar;
-namespace asuka.Compression;
+namespace asuka.Core.Compression;
public class PackArchiveToCbz : IPackArchiveToCbz
{
- public async Task RunAsync(string folderName, string[] imageFiles, string output, IProgressBar parentBar)
- {
- var progressTheme = ProgressBarConfiguration.CompressOption;
- var childBar = parentBar.Spawn(imageFiles.Length, "compressing...", progressTheme);
+ private readonly IProgressService _progressService;
+ public PackArchiveToCbz(IProgressService progressService)
+ {
+ _progressService = progressService;
+ }
+
+ public async Task RunAsync(string folderName, IList imageFiles, string output)
+ {
+ if (string.IsNullOrEmpty(output))
+ {
+ return;
+ }
+
+ var childBar = _progressService.NestToMaster(imageFiles.Count, $"compressing...: {output}");
await ValidateArchive(output, childBar).ConfigureAwait(false);
var fileMode = File.Exists(output) ? FileMode.Open : FileMode.Create;
diff --git a/Core/Downloader/DownloadService.cs b/Core/Downloader/DownloadService.cs
index 342b16b..b6ac887 100644
--- a/Core/Downloader/DownloadService.cs
+++ b/Core/Downloader/DownloadService.cs
@@ -4,46 +4,39 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using ShellProgressBar;
-using asuka.Api;
-using asuka.Compression;
-using asuka.Models;
+using asuka.Configuration;
+using asuka.Core.Api;
+using asuka.Core.Downloader.InternalTypes;
+using asuka.Core.Models;
using asuka.Output;
-using asuka.Utils;
+using asuka.Output.ProgressService;
using Newtonsoft.Json;
+using ShellProgressBar;
-namespace asuka.Downloader;
+namespace asuka.Core.Downloader;
public class DownloadService : IDownloadService
{
private readonly IGalleryImage _api;
- private readonly IPackArchiveToCbz _packer;
- private int _taskId;
- private string _destinationPath;
- private string _folderName;
+ private readonly IProgressService _progress;
+ private readonly IConfigurationManager _configurationManager;
- public DownloadService(IGalleryImage api, IPackArchiveToCbz packer)
+ public DownloadService(IGalleryImage api, IProgressService progress, IConfigurationManager configurationManager)
{
_api = api;
- _packer = packer;
+ _progress = progress;
+ _configurationManager = configurationManager;
}
- public async Task DownloadAsync(GalleryResult result,
- string outputPath,
- bool pack,
- bool useTachiyomiFolderLayout,
- IProgressBar progress)
+ public async Task DownloadAsync(GalleryResult result, string outputPath)
{
+ var useTachiyomiFolderLayout = _configurationManager.Values.UseTachiyomiLayout;
// Prepare the download.
- await PrepareAsync(result, outputPath, useTachiyomiFolderLayout).ConfigureAwait(false);
+ var prepare = await PrepareAsync(result, outputPath, useTachiyomiFolderLayout).ConfigureAwait(false);
// If the progress is null, we create a new one.
- var progressTheme = ProgressBarConfiguration.BarOption;
- var progressInitialTitle = $"[queued] id: {_taskId}";
-
- using var bar = progress == null
- ? new ProgressBar(result.TotalPages, progressInitialTitle, progressTheme)
- : (IProgressBar) progress.Spawn(result.TotalPages, progressInitialTitle, progressTheme);
+ var progressInitialTitle = $"[queued] id: {prepare.Id}";
+ var bar = _progress.NestToMaster(result.TotalPages, progressInitialTitle);
using var throttler = new SemaphoreSlim(2);
var taskList = new List();
@@ -56,18 +49,29 @@ public DownloadService(IGalleryImage api, IPackArchiveToCbz packer)
var referenceThrottler = throttler;
taskList.Add(Task.Run(async () =>
{
- await FetchImageAsync(result.MediaId, page, referenceBar).ConfigureAwait(false);
+ var param = new FetchImageParameter
+ {
+ DestinationPath = prepare.DestinationPath,
+ MediaId = result.MediaId,
+ Page = page,
+ TaskId = prepare.Id
+ };
+
+ await FetchImageAsync(param, referenceBar).ConfigureAwait(false);
referenceThrottler.Release();
}));
}
await Task.WhenAll(taskList).ConfigureAwait(false);
- if (pack)
+ var destination = useTachiyomiFolderLayout
+ ? Path.GetFullPath($"{prepare.DestinationPath}/../") : prepare.DestinationPath;
+ return new DownloadResult
{
- var imageFiles = Directory.GetFiles(_destinationPath);
- await _packer.RunAsync(_folderName, imageFiles, $"{_destinationPath}.cbz", bar);
- }
+ FolderName = useTachiyomiFolderLayout ? Path.GetFullPath($"{prepare.FolderName}/../") : prepare.FolderName,
+ ImageFiles = Directory.GetFiles(destination),
+ DestinationPath = destination
+ };
}
private static string SantizeFolderName(string folderName)
@@ -83,13 +87,12 @@ private static string SantizeFolderName(string folderName)
return folderName;
}
- private async Task PrepareAsync(GalleryResult result, string outputPath, bool useTachiyomiFolderLayout)
+ private async Task PrepareAsync(GalleryResult result, string outputPath, bool useTachiyomiFolderLayout)
{
- _taskId = result.Id;
- _destinationPath = Environment.CurrentDirectory;
+ var destinationPath = Environment.CurrentDirectory;
if (!string.IsNullOrEmpty(outputPath))
{
- _destinationPath = outputPath;
+ destinationPath = outputPath;
}
var galleryTitle = string.IsNullOrEmpty(result.Title.Japanese)
@@ -97,13 +100,12 @@ private async Task PrepareAsync(GalleryResult result, string outputPath, bool us
: result.Title.Japanese;
var folderName = SantizeFolderName($"{result.Id} - {galleryTitle}");
- _folderName = folderName;
- var mangaRootPath = Path.Join(_destinationPath, folderName);
- _destinationPath = useTachiyomiFolderLayout ? Path.Join(mangaRootPath, "ch1") : mangaRootPath;
- if (!Directory.Exists(_destinationPath))
+ var mangaRootPath = Path.Join(destinationPath, folderName);
+ destinationPath = useTachiyomiFolderLayout ? Path.Join(mangaRootPath, "ch1") : mangaRootPath;
+ if (!Directory.Exists(destinationPath))
{
- Directory.CreateDirectory(_destinationPath);
+ Directory.CreateDirectory(destinationPath);
}
var metadataPath = Path.Combine(mangaRootPath, "info.txt");
@@ -119,17 +121,24 @@ await File.WriteAllTextAsync(metadataPath, result.ToReadable())
await File.WriteAllTextAsync(tachiyomiMetadataPath, tachiyomiMetadata)
.ConfigureAwait(false);
}
+
+ return new PrepareResult
+ {
+ FolderName = folderName,
+ Id = result.Id,
+ DestinationPath = destinationPath
+ };
}
- private async Task FetchImageAsync(int mediaId, GalleryImageResult page, IProgressBar bar)
+ private async Task FetchImageAsync(FetchImageParameter data, IProgressBar bar)
{
- var image = await _api.GetImage(mediaId.ToString(), page.ServerFilename);
+ var image = await _api.GetImage(data.MediaId.ToString(), data.Page.ServerFilename);
var imageContents = await image.ReadAsByteArrayAsync();
- var filePath = Path.Combine(_destinationPath, page.Filename);
+ var filePath = Path.Combine(data.DestinationPath, data.Page.Filename);
await File.WriteAllBytesAsync(filePath, imageContents)
.ConfigureAwait(false);
- bar.Tick($"[downloading] id: {_taskId}");
+ bar.Tick($"[downloading] id: {data.TaskId}");
}
}
diff --git a/Core/Downloader/IDownloadService.cs b/Core/Downloader/IDownloadService.cs
index c82d16d..5e78b4e 100644
--- a/Core/Downloader/IDownloadService.cs
+++ b/Core/Downloader/IDownloadService.cs
@@ -1,15 +1,11 @@
using System.Threading.Tasks;
-using asuka.Models;
+using asuka.Core.Downloader.InternalTypes;
+using asuka.Core.Models;
using ShellProgressBar;
-namespace asuka.Downloader;
+namespace asuka.Core.Downloader;
public interface IDownloadService
{
- Task DownloadAsync(
- GalleryResult result,
- string outputPath,
- bool pack,
- bool useTachiyomiFolderLayout,
- IProgressBar progress);
+ Task DownloadAsync(GalleryResult result, string outputPath);
}
diff --git a/Core/Downloader/InternalTypes/DownloadResult.cs b/Core/Downloader/InternalTypes/DownloadResult.cs
index 966b8c9..75f286f 100644
--- a/Core/Downloader/InternalTypes/DownloadResult.cs
+++ b/Core/Downloader/InternalTypes/DownloadResult.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace asuka.Core.Downloader;
+namespace asuka.Core.Downloader.InternalTypes;
public record DownloadResult
{
diff --git a/Core/Downloader/InternalTypes/FetchImageParameter.cs b/Core/Downloader/InternalTypes/FetchImageParameter.cs
index bf4e973..09413fb 100644
--- a/Core/Downloader/InternalTypes/FetchImageParameter.cs
+++ b/Core/Downloader/InternalTypes/FetchImageParameter.cs
@@ -1,3 +1,11 @@
+using asuka.Core.Models;
+
namespace asuka.Core.Downloader.InternalTypes;
-public record FetchImageParameter();
\ No newline at end of file
+internal record FetchImageParameter
+{
+ internal int MediaId { get; init; }
+ internal GalleryImageResult Page { get; init; }
+ internal int TaskId { get; init; }
+ internal string DestinationPath { get; init; }
+}
diff --git a/Core/Downloader/InternalTypes/PrepareResult.cs b/Core/Downloader/InternalTypes/PrepareResult.cs
index 61f515a..c72d158 100644
--- a/Core/Downloader/InternalTypes/PrepareResult.cs
+++ b/Core/Downloader/InternalTypes/PrepareResult.cs
@@ -1,4 +1,4 @@
-namespace asuka.Core.Downloader;
+namespace asuka.Core.Downloader.InternalTypes;
internal record PrepareResult
{
diff --git a/Core/Mappings/ContractToGalleryImageResultModelMapping.cs b/Core/Mappings/ContractToGalleryImageResultModelMapping.cs
index 5f2173e..03d804f 100644
--- a/Core/Mappings/ContractToGalleryImageResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryImageResultModelMapping.cs
@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Linq;
-using asuka.Api.Responses;
-using asuka.Models;
+using asuka.Core.Api.Responses;
+using asuka.Core.Models;
-namespace asuka.Mappings;
+namespace asuka.Core.Mappings;
public static class ContractToGalleryImageResultModelMapping
{
diff --git a/Core/Mappings/ContractToGalleryResultModelMapping.cs b/Core/Mappings/ContractToGalleryResultModelMapping.cs
index 519d904..6b6b322 100644
--- a/Core/Mappings/ContractToGalleryResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryResultModelMapping.cs
@@ -1,7 +1,7 @@
-using asuka.Api.Responses;
-using asuka.Models;
+using asuka.Core.Api.Responses;
+using asuka.Core.Models;
-namespace asuka.Mappings;
+namespace asuka.Core.Mappings;
public static class ContractToGalleryResultModelMapping
{
diff --git a/Core/Mappings/ContractToGalleryTagResultModelMapping.cs b/Core/Mappings/ContractToGalleryTagResultModelMapping.cs
index 504fa31..ddcef50 100644
--- a/Core/Mappings/ContractToGalleryTagResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryTagResultModelMapping.cs
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
-using asuka.Api.Responses;
+using asuka.Core.Api.Responses;
-namespace asuka.Mappings;
+namespace asuka.Core.Mappings;
public static class ContractToGalleryTagResultModelMapping
{
diff --git a/Core/Mappings/ContractToUserSelectedModelMapping.cs b/Core/Mappings/ContractToUserSelectedModelMapping.cs
index e0baba8..20c917f 100644
--- a/Core/Mappings/ContractToUserSelectedModelMapping.cs
+++ b/Core/Mappings/ContractToUserSelectedModelMapping.cs
@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Linq;
+using asuka.Core.Models;
using Sharprompt;
-using asuka.Models;
-namespace asuka.Mappings;
+namespace asuka.Core.Mappings;
public static class ContractToUserSelectedModelMapping
{
diff --git a/Core/Models/GalleryImageResult.cs b/Core/Models/GalleryImageResult.cs
index dfae02c..323cc11 100644
--- a/Core/Models/GalleryImageResult.cs
+++ b/Core/Models/GalleryImageResult.cs
@@ -1,4 +1,4 @@
-namespace asuka.Models;
+namespace asuka.Core.Models;
public record GalleryImageResult
{
diff --git a/Core/Models/GalleryResult.cs b/Core/Models/GalleryResult.cs
index a8a2724..81c2cdd 100644
--- a/Core/Models/GalleryResult.cs
+++ b/Core/Models/GalleryResult.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace asuka.Models;
+namespace asuka.Core.Models;
public record GalleryResult
{
diff --git a/Core/Models/GalleryTitleResult.cs b/Core/Models/GalleryTitleResult.cs
index b52a4f8..c63c643 100644
--- a/Core/Models/GalleryTitleResult.cs
+++ b/Core/Models/GalleryTitleResult.cs
@@ -1,4 +1,4 @@
-namespace asuka.Models;
+namespace asuka.Core.Models;
public record GalleryTitleResult
{
diff --git a/Core/Models/TachiyomiDetails.cs b/Core/Models/TachiyomiDetails.cs
index a84be5e..3d7d989 100644
--- a/Core/Models/TachiyomiDetails.cs
+++ b/Core/Models/TachiyomiDetails.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
-namespace asuka.Models;
+namespace asuka.Core.Models;
public record TachiyomiDetails
{
diff --git a/Core/Requests/GalleryRequestService.cs b/Core/Requests/GalleryRequestService.cs
index 24f1f3c..aec8ebb 100644
--- a/Core/Requests/GalleryRequestService.cs
+++ b/Core/Requests/GalleryRequestService.cs
@@ -1,12 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using asuka.Api;
-using asuka.Api.Queries;
+using asuka.Core.Api;
+using asuka.Core.Api.Queries;
using asuka.Core.Mappings;
using asuka.Core.Models;
-namespace asuka.Core.Api;
+namespace asuka.Core.Requests;
public class GalleryRequestService : IGalleryRequestService
{
diff --git a/Core/Requests/IGalleryRequestService.cs b/Core/Requests/IGalleryRequestService.cs
index 230c265..03ea12d 100644
--- a/Core/Requests/IGalleryRequestService.cs
+++ b/Core/Requests/IGalleryRequestService.cs
@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using asuka.Api.Queries;
+using asuka.Core.Api.Queries;
using asuka.Core.Models;
-namespace asuka.Core.Api;
+namespace asuka.Core.Requests;
public interface IGalleryRequestService
{
diff --git a/Installers/IInstaller.cs b/Installers/IInstaller.cs
index 64ec29e..b5c5121 100644
--- a/Installers/IInstaller.cs
+++ b/Installers/IInstaller.cs
@@ -1,6 +1,9 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
namespace asuka.Installers;
public interface IInstaller
{
-
-}
\ No newline at end of file
+ void ConfigureService(IServiceCollection services, IConfiguration configuration);
+}
diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs
index 8f2be33..c226520 100644
--- a/Installers/InstallServices.cs
+++ b/Installers/InstallServices.cs
@@ -1,8 +1,9 @@
using asuka.Commandline.Parsers;
using asuka.Configuration;
-using asuka.Core.Api;
using asuka.Core.Compression;
using asuka.Core.Downloader;
+using asuka.Core.Requests;
+using asuka.Output.ProgressService;
using asuka.Output.Writer;
using FluentValidation;
using Microsoft.Extensions.Configuration;
@@ -11,22 +12,25 @@
namespace asuka.Installers;
-public class ServiceCollection
+public class InstallServices : IInstaller
{
- public void InstallServices(IServiceCollection services, IConfiguration configuration)
+ public void ConfigureService(IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
- services.AddSingleton();
+ services.AddScoped();
+ services.AddScoped();
+
+ // Command parsers
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
services.AddValidatorsFromAssemblyContaining();
}
}
diff --git a/Installers/InstallerExtension.cs b/Installers/InstallerExtension.cs
index c9a3cd0..c7172b8 100644
--- a/Installers/InstallerExtension.cs
+++ b/Installers/InstallerExtension.cs
@@ -1,6 +1,21 @@
+using System;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
namespace asuka.Installers;
-public class InstallerExtension
+public static class InstallerExtension
{
-
-}
\ No newline at end of file
+ public static void InstallServicesInAssembly(this IServiceCollection serviceCollection,
+ IConfiguration configuration)
+ {
+ var installers = typeof(Program).Assembly.ExportedTypes
+ .Where(x => typeof(IInstaller).IsAssignableFrom(x) && !x.IsInterface && !x.IsInterface)
+ .Select(Activator.CreateInstance)
+ .Cast()
+ .ToList();
+
+ installers.ForEach(installer => installer.ConfigureService(serviceCollection, configuration));
+ }
+}
diff --git a/Installers/Refit/ConfigureRefitService.cs b/Installers/Refit/ConfigureRefitService.cs
index e69de29..92d6087 100644
--- a/Installers/Refit/ConfigureRefitService.cs
+++ b/Installers/Refit/ConfigureRefitService.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Drawing;
+using System.Net.Http;
+using asuka.Configuration;
+using asuka.Core.Api;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Polly;
+using Polly.Contrib.WaitAndRetry;
+using Refit;
+
+namespace asuka.Installers.Refit;
+
+public class ConfigureRefitService : IInstaller
+{
+ public void ConfigureService(IServiceCollection services, IConfiguration configuration)
+ {
+ var cookies = new CloudflareBypass();
+
+ var cloudflareClearance = cookies.GetCookieByName("cf_clearance");
+ var csrfToken = cookies.GetCookieByName("csrftoken");
+
+ if (cloudflareClearance == null || csrfToken == null)
+ {
+ Colorful.Console.WriteLine("WARNING: Cookies are unset! Your request might fail.", Color.Red);
+ }
+
+ var configureRefit = new RefitSettings
+ {
+ ContentSerializer = new NewtonsoftJsonContentSerializer(),
+ HttpMessageHandlerFactory = () =>
+ {
+ var handler = new HttpClientHandler();
+
+ if (cloudflareClearance != null)
+ {
+ handler.CookieContainer.Add(cloudflareClearance);
+ }
+
+ if (csrfToken != null)
+ {
+ handler.CookieContainer.Add(csrfToken);
+ }
+
+ return handler;
+ }
+ };
+
+ var apiBaseAddress = configuration.GetSection("BaseAddresses")["ApiBaseAddress"]
+ ?? "https://nhentai.net";
+ var imageBaseAddress = configuration.GetSection("BaseAddresses")["ImageBaseAddress"]
+ ?? "https://i.nhentai.net";
+
+ services.AddRefitClient(configureRefit)
+ .AddTransientHttpErrorPolicy(ConfigureErrorPolicyBuilder)
+ .ConfigureHttpClient(httpClient =>
+ {
+ httpClient.BaseAddress = new Uri(apiBaseAddress);
+ httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(cookies.UserAgent);
+ });
+ services.AddRefitClient()
+ .AddTransientHttpErrorPolicy(ConfigureErrorPolicyBuilder)
+ .ConfigureHttpClient(httpClient =>
+ {
+ httpClient.BaseAddress = new Uri(imageBaseAddress);
+ httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(cookies.UserAgent);
+ });
+ }
+
+ private static IAsyncPolicy ConfigureErrorPolicyBuilder(
+ PolicyBuilder builder)
+ {
+ var delay = Backoff.DecorrelatedJitterBackoffV2(
+ TimeSpan.FromSeconds(1), 5);
+ return builder.WaitAndRetryAsync(delay, (_, span) =>
+ {
+ Colorful.Console.WriteLine($"Retrying in {span.Seconds}");
+ });
+ }
+}
diff --git a/LICENSE b/LICENSE
index 186a9f7..55ac0bb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
-Copyright 2021 Aiko Fujimoto
+Copyright 2023 Aiko Fujimoto
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Output/DisplayInformation.cs b/Output/DisplayInformation.cs
index 50f92d5..9b7cc36 100644
--- a/Output/DisplayInformation.cs
+++ b/Output/DisplayInformation.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Text;
-using asuka.Models;
+using asuka.Core.Models;
namespace asuka.Output;
diff --git a/Output/ProgressService/IProgressService.cs b/Output/ProgressService/IProgressService.cs
index e69de29..2d40e63 100644
--- a/Output/ProgressService/IProgressService.cs
+++ b/Output/ProgressService/IProgressService.cs
@@ -0,0 +1,11 @@
+using ShellProgressBar;
+
+namespace asuka.Output.ProgressService;
+
+public interface IProgressService
+{
+ void CreateMasterProgress(int totalTicks, string title);
+ IProgressBar NestToMaster(int totalTicks, string title);
+ IProgressBar GetMasterProgress();
+ bool HasMasterProgress();
+}
diff --git a/Output/ProgressService/ProgressBarConfiguration.cs b/Output/ProgressService/ProgressBarConfiguration.cs
index 093ad90..2f7b5ea 100644
--- a/Output/ProgressService/ProgressBarConfiguration.cs
+++ b/Output/ProgressService/ProgressBarConfiguration.cs
@@ -1,7 +1,7 @@
using System;
using ShellProgressBar;
-namespace asuka.Utils;
+namespace asuka.Output.ProgressService;
public static class ProgressBarConfiguration
{
diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs
index 06bd6f8..f2bce84 100644
--- a/Output/ProgressService/ProgressService.cs
+++ b/Output/ProgressService/ProgressService.cs
@@ -1,6 +1,31 @@
+using ShellProgressBar;
+
namespace asuka.Output.ProgressService;
-public class ProgressService
+public class ProgressService : IProgressService
{
-
-}
\ No newline at end of file
+ private IProgressBar _progressBar;
+
+ public void CreateMasterProgress(int totalTicks, string title)
+ {
+ _progressBar = new ProgressBar(totalTicks, title, ProgressBarConfiguration.BarOption);
+ }
+
+ public IProgressBar GetMasterProgress()
+ {
+ return _progressBar;
+ }
+
+ public bool HasMasterProgress()
+ {
+ return _progressBar is not null;
+ }
+
+ public IProgressBar NestToMaster(int totalTicks, string title)
+ {
+ if (HasMasterProgress()) return _progressBar.Spawn(totalTicks, title, ProgressBarConfiguration.BarOption);
+
+ CreateMasterProgress(totalTicks, title);
+ return GetMasterProgress();
+ }
+}
diff --git a/Output/Writer/ConsoleWriter.cs b/Output/Writer/ConsoleWriter.cs
index 90197e5..b90f979 100644
--- a/Output/Writer/ConsoleWriter.cs
+++ b/Output/Writer/ConsoleWriter.cs
@@ -4,7 +4,7 @@
using FluentValidation.Results;
using Console = Colorful.Console;
-namespace asuka.Output;
+namespace asuka.Output.Writer;
public class ConsoleWriter : IConsoleWriter
{
diff --git a/Output/Writer/IConsoleWriter.cs b/Output/Writer/IConsoleWriter.cs
index f40a8e9..7bea40d 100644
--- a/Output/Writer/IConsoleWriter.cs
+++ b/Output/Writer/IConsoleWriter.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using FluentValidation.Results;
-namespace asuka.Output;
+namespace asuka.Output.Writer;
public interface IConsoleWriter
{
diff --git a/Program.cs b/Program.cs
index 5013ac4..55abbf2 100644
--- a/Program.cs
+++ b/Program.cs
@@ -8,8 +8,7 @@
.Build();
var services = new ServiceCollection();
-services.InstallServices();
-services.InstallRefitServices(configuration);
+services.InstallServicesInAssembly(configuration);
var serviceProvider = services.BuildServiceProvider();
var app = serviceProvider.GetRequiredService();
diff --git a/Validators/ConfigurationValidator.cs b/Validators/ConfigurationValidator.cs
index 0d481d4..b93c243 100644
--- a/Validators/ConfigurationValidator.cs
+++ b/Validators/ConfigurationValidator.cs
@@ -1,5 +1,5 @@
using System.IO;
-using asuka.CommandOptions;
+using asuka.Commandline.Options;
using FluentValidation;
namespace asuka.Validators;
diff --git a/Validators/RequiresInputOptionValidator.cs b/Validators/RequiresInputOptionValidator.cs
index 69dcd54..dacf5a4 100644
--- a/Validators/RequiresInputOptionValidator.cs
+++ b/Validators/RequiresInputOptionValidator.cs
@@ -1,5 +1,5 @@
+using asuka.Commandline.Options;
using FluentValidation;
-using asuka.CommandOptions;
namespace asuka.Validators;
diff --git a/Validators/SearchValidator.cs b/Validators/SearchValidator.cs
index 908a864..016f62d 100644
--- a/Validators/SearchValidator.cs
+++ b/Validators/SearchValidator.cs
@@ -1,5 +1,5 @@
+using asuka.Commandline.Options;
using FluentValidation;
-using asuka.CommandOptions;
namespace asuka.Validators;
diff --git a/appsettings.json b/appsettings.json
index 28bbcad..ad1a693 100644
--- a/appsettings.json
+++ b/appsettings.json
@@ -1,4 +1,6 @@
{
- "ApiBaseAddress": "https://nhentai.net",
- "ImageBaseAddress": "https://i.nhentai.net"
+ "BaseAddresses": {
+ "ApiBaseAddress": "https://nhentai.net",
+ "ImageBaseAddress": "https://i.nhentai.net"
+ }
}
diff --git a/asuka.csproj b/asuka.csproj
index 07cec4c..164ef24 100644
--- a/asuka.csproj
+++ b/asuka.csproj
@@ -58,4 +58,8 @@
+
+
+
+
From 7524a335c1350c312c48214685f47bc9171c6181 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 21:00:59 +0900
Subject: [PATCH 04/21] Fix issue for compression with nested folders
---
Commandline/Parsers/FileCommandService.cs | 3 +-
Commandline/Parsers/GetCommandService.cs | 3 +-
Commandline/Parsers/RandomCommandService.cs | 3 +-
.../Parsers/RecommendCommandService.cs | 3 +-
Commandline/Parsers/SearchCommandService.cs | 3 +-
Core/Compression/IPackArchiveToCbz.cs | 2 +-
Core/Compression/PackArchiveToCbz.cs | 100 ++++--------------
Core/Downloader/DownloadService.cs | 5 +-
.../InternalTypes/DownloadResult.cs | 5 +-
Output/ProgressService/IProgressService.cs | 1 +
Output/ProgressService/ProgressService.cs | 5 +
11 files changed, 37 insertions(+), 96 deletions(-)
diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs
index f6fbaeb..fa1d705 100644
--- a/Commandline/Parsers/FileCommandService.cs
+++ b/Commandline/Parsers/FileCommandService.cs
@@ -71,8 +71,7 @@ public async Task RunAsync(FileCommandOptions opts)
if (opts.Pack)
{
- var destination = result.DestinationPath[..^1] + ".cbz";
- await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
}
progress.Tick();
}
diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs
index 3e2a6b1..c3cf251 100644
--- a/Commandline/Parsers/GetCommandService.cs
+++ b/Commandline/Parsers/GetCommandService.cs
@@ -51,8 +51,7 @@ public async Task RunAsync(GetOptions opts)
var result = await _download.DownloadAsync(response, opts.Output);
if (opts.Pack)
{
- var destination = result.DestinationPath[..^1] + ".cbz";
- await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
}
}
}
diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
index 1be4b4d..acf9d13 100644
--- a/Commandline/Parsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -51,8 +51,7 @@ public async Task RunAsync(RandomOptions opts)
var result = await _download.DownloadAsync(response, opts.Output);
if (opts.Pack)
{
- var destination = result.DestinationPath[..^1] + ".cbz";
- await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
}
break;
}
diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs
index db7d7b1..d36a354 100644
--- a/Commandline/Parsers/RecommendCommandService.cs
+++ b/Commandline/Parsers/RecommendCommandService.cs
@@ -56,8 +56,7 @@ public async Task RunAsync(RecommendOptions opts)
var result = await _download.DownloadAsync(response, opts.Output);
if (opts.Pack)
{
- var destination = result.DestinationPath[..^1] + ".cbz";
- await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
}
progress.Tick();
}
diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs
index 7150c03..65fe6fb 100644
--- a/Commandline/Parsers/SearchCommandService.cs
+++ b/Commandline/Parsers/SearchCommandService.cs
@@ -79,8 +79,7 @@ public async Task RunAsync(SearchOptions opts)
var result = await _download.DownloadAsync(response, opts.Output);
if (opts.Pack)
{
- var destination = result.DestinationPath[..^1] + ".cbz";
- await _pack.RunAsync(result.FolderName, result.ImageFiles, destination);
+ await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
}
progress.Tick();
}
diff --git a/Core/Compression/IPackArchiveToCbz.cs b/Core/Compression/IPackArchiveToCbz.cs
index db36a57..dafd79a 100644
--- a/Core/Compression/IPackArchiveToCbz.cs
+++ b/Core/Compression/IPackArchiveToCbz.cs
@@ -6,5 +6,5 @@ namespace asuka.Core.Compression;
public interface IPackArchiveToCbz
{
- Task RunAsync(string folderName, IList imageFiles, string output);
+ Task RunAsync(string targetFolder, string output, IProgressBar bar);
}
diff --git a/Core/Compression/PackArchiveToCbz.cs b/Core/Compression/PackArchiveToCbz.cs
index bd2e376..3658d48 100644
--- a/Core/Compression/PackArchiveToCbz.cs
+++ b/Core/Compression/PackArchiveToCbz.cs
@@ -1,7 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
+using System.IO;
using System.IO.Compression;
+using System.Linq;
using System.Threading.Tasks;
using asuka.Output.ProgressService;
using ShellProgressBar;
@@ -16,93 +15,36 @@ public PackArchiveToCbz(IProgressService progressService)
{
_progressService = progressService;
}
-
- public async Task RunAsync(string folderName, IList imageFiles, string output)
- {
- if (string.IsNullOrEmpty(output))
- {
- return;
- }
-
- var childBar = _progressService.NestToMaster(imageFiles.Count, $"compressing...: {output}");
- await ValidateArchive(output, childBar).ConfigureAwait(false);
-
- var fileMode = File.Exists(output) ? FileMode.Open : FileMode.Create;
- var zipMode = File.Exists(output) ? ZipArchiveMode.Update : ZipArchiveMode.Create;
-
- await using var archiveToOpen = new FileStream(output, fileMode);
- using var archive = new ZipArchive(archiveToOpen, zipMode);
- archive.CreateEntry($"{folderName}\\");
-
- foreach (var image in imageFiles)
- {
- await AddOrUpdate(archive, folderName, image, zipMode)
- .ConfigureAwait(false);
- childBar.Tick();
- }
- }
-
- private static async Task AddOrUpdate(ZipArchive archive, string folderEntry, string imageFile, ZipArchiveMode mode)
+ public async Task RunAsync(string targetFolder, string output, IProgressBar bar)
{
- var fileName = Path.GetFileName(imageFile);
- var entryName = $"{folderEntry}\\{fileName}";
-
- if (mode == ZipArchiveMode.Create)
+ if (string.IsNullOrEmpty(output))
{
- await WriteEntry(archive, entryName, imageFile)
- .ConfigureAwait(false);
return;
}
- var archiveEntry = archive.GetEntry(entryName);
- archiveEntry?.Delete();
-
- await WriteEntry(archive, entryName, imageFile).ConfigureAwait(false);
- }
-
- private static async Task WriteEntry(ZipArchive archive, string entryName, string inputFile)
- {
- var entry = archive.CreateEntry(entryName);
- await using var writer = new BinaryWriter(entry.Open());
-
- var fileContents = await File.ReadAllBytesAsync(inputFile)
- .ConfigureAwait(false);
- writer.Write(fileContents);
- }
+ var files = Directory.GetFiles(targetFolder, "*.*", SearchOption.AllDirectories)
+ .Select(x => Path.GetRelativePath(output, x))
+ .ToList();
+ var childBar = _progressService.HookToInstance(bar, files.Count, $"compressing...");
- private static async Task ValidateArchive(string inputArchive, IProgressBar childBar)
- {
- if (!File.Exists(inputArchive))
- {
- return;
- }
+ // Delete if file exists.
+ if (File.Exists(output)) File.Delete(output);
- await Task.Run(() =>
+ var destination = $"{targetFolder[..^1]}.cbz";
+ await using var archiveToOpen = new FileStream(destination, FileMode.Create);
+ using var archive = new ZipArchive(archiveToOpen, ZipArchiveMode.Create);
+
+ // Recursively look for the files in the target directory.
+ foreach (var file in files)
{
- if (IsArchiveValid(inputArchive))
- {
- return;
- }
-
- childBar.WriteErrorLine("Archive appears to be invalid. Deleting...");
- // Deletion might fail.
- File.Delete(inputArchive);
- }).ConfigureAwait(false);
- }
+ var entry = archive.CreateEntry(file);
- private static bool IsArchiveValid(string inputArchive)
- {
- try
- {
- using var zipFile = ZipFile.OpenRead(inputArchive);
- _ = zipFile.Entries;
+ await using var writer = new BinaryWriter(entry.Open());
+ var fileData = await File.ReadAllBytesAsync(Path.Combine(output, file));
- return true;
- }
- catch (Exception)
- {
- return false;
+ writer.Write(fileData);
+ childBar.Tick($"compressing: {destination}");
}
}
}
diff --git a/Core/Downloader/DownloadService.cs b/Core/Downloader/DownloadService.cs
index b6ac887..282ea33 100644
--- a/Core/Downloader/DownloadService.cs
+++ b/Core/Downloader/DownloadService.cs
@@ -68,9 +68,8 @@ public async Task DownloadAsync(GalleryResult result, string out
? Path.GetFullPath($"{prepare.DestinationPath}/../") : prepare.DestinationPath;
return new DownloadResult
{
- FolderName = useTachiyomiFolderLayout ? Path.GetFullPath($"{prepare.FolderName}/../") : prepare.FolderName,
- ImageFiles = Directory.GetFiles(destination),
- DestinationPath = destination
+ DestinationPath = destination,
+ ProgressBar = bar
};
}
diff --git a/Core/Downloader/InternalTypes/DownloadResult.cs b/Core/Downloader/InternalTypes/DownloadResult.cs
index 75f286f..85e3a1b 100644
--- a/Core/Downloader/InternalTypes/DownloadResult.cs
+++ b/Core/Downloader/InternalTypes/DownloadResult.cs
@@ -1,10 +1,9 @@
-using System.Collections.Generic;
+using ShellProgressBar;
namespace asuka.Core.Downloader.InternalTypes;
public record DownloadResult
{
- public string FolderName { get; init; }
- public IList ImageFiles { get; init; }
public string DestinationPath { get; init; }
+ public IProgressBar ProgressBar { get; init; }
}
diff --git a/Output/ProgressService/IProgressService.cs b/Output/ProgressService/IProgressService.cs
index 2d40e63..c95dd0b 100644
--- a/Output/ProgressService/IProgressService.cs
+++ b/Output/ProgressService/IProgressService.cs
@@ -8,4 +8,5 @@ public interface IProgressService
IProgressBar NestToMaster(int totalTicks, string title);
IProgressBar GetMasterProgress();
bool HasMasterProgress();
+ IProgressBar HookToInstance(IProgressBar bar, int totalTicks, string title);
}
diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs
index f2bce84..8eefbba 100644
--- a/Output/ProgressService/ProgressService.cs
+++ b/Output/ProgressService/ProgressService.cs
@@ -28,4 +28,9 @@ public IProgressBar NestToMaster(int totalTicks, string title)
CreateMasterProgress(totalTicks, title);
return GetMasterProgress();
}
+
+ public IProgressBar HookToInstance(IProgressBar bar, int totalTicks, string title)
+ {
+ return bar.Spawn(totalTicks, title, ProgressBarConfiguration.BarOption);
+ }
}
From b8346805096154935130ccc41336e2cde81d0149 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 21:20:01 +0900
Subject: [PATCH 05/21] Skip files when is already present
---
Core/Downloader/DownloadService.cs | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/Core/Downloader/DownloadService.cs b/Core/Downloader/DownloadService.cs
index 282ea33..78a4c64 100644
--- a/Core/Downloader/DownloadService.cs
+++ b/Core/Downloader/DownloadService.cs
@@ -45,7 +45,6 @@ public async Task DownloadAsync(GalleryResult result, string out
{
await throttler.WaitAsync().ConfigureAwait(false);
- var referenceBar = bar;
var referenceThrottler = throttler;
taskList.Add(Task.Run(async () =>
{
@@ -57,7 +56,7 @@ public async Task DownloadAsync(GalleryResult result, string out
TaskId = prepare.Id
};
- await FetchImageAsync(param, referenceBar).ConfigureAwait(false);
+ await FetchImageAsync(param, bar).ConfigureAwait(false);
referenceThrottler.Release();
}));
}
@@ -131,10 +130,16 @@ await File.WriteAllTextAsync(tachiyomiMetadataPath, tachiyomiMetadata)
private async Task FetchImageAsync(FetchImageParameter data, IProgressBar bar)
{
+ var filePath = Path.Combine(data.DestinationPath, data.Page.Filename);
+ if (File.Exists(filePath))
+ {
+ bar.Tick($"[skipped existing] id: {data.TaskId}");
+ return;
+ }
+
var image = await _api.GetImage(data.MediaId.ToString(), data.Page.ServerFilename);
var imageContents = await image.ReadAsByteArrayAsync();
-
- var filePath = Path.Combine(data.DestinationPath, data.Page.Filename);
+
await File.WriteAllBytesAsync(filePath, imageContents)
.ConfigureAwait(false);
From 32fc11d295f525d126636d3e37dda09a0746f291 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 21:30:53 +0900
Subject: [PATCH 06/21] Use LINQ to transform data into a Task
---
Core/Downloader/DownloadService.cs | 41 +++++++++++++++---------------
1 file changed, 21 insertions(+), 20 deletions(-)
diff --git a/Core/Downloader/DownloadService.cs b/Core/Downloader/DownloadService.cs
index 78a4c64..f19f182 100644
--- a/Core/Downloader/DownloadService.cs
+++ b/Core/Downloader/DownloadService.cs
@@ -1,6 +1,6 @@
using System;
-using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -38,28 +38,29 @@ public async Task DownloadAsync(GalleryResult result, string out
var progressInitialTitle = $"[queued] id: {prepare.Id}";
var bar = _progress.NestToMaster(result.TotalPages, progressInitialTitle);
- using var throttler = new SemaphoreSlim(2);
- var taskList = new List();
-
- foreach (var page in result.Images)
- {
- await throttler.WaitAsync().ConfigureAwait(false);
-
- var referenceThrottler = throttler;
- taskList.Add(Task.Run(async () =>
+ var throttler = new SemaphoreSlim(2);
+ var taskList = result.Images
+ .Select(x => Task.Run(async () =>
{
- var param = new FetchImageParameter
+ await throttler.WaitAsync().ConfigureAwait(false);
+
+ try
+ {
+ var param = new FetchImageParameter
+ {
+ DestinationPath = prepare.DestinationPath,
+ MediaId = result.MediaId,
+ Page = x,
+ TaskId = prepare.Id
+ };
+
+ await FetchImageAsync(param, bar).ConfigureAwait(false);
+ }
+ finally
{
- DestinationPath = prepare.DestinationPath,
- MediaId = result.MediaId,
- Page = page,
- TaskId = prepare.Id
- };
-
- await FetchImageAsync(param, bar).ConfigureAwait(false);
- referenceThrottler.Release();
+ throttler.Release();
+ }
}));
- }
await Task.WhenAll(taskList).ConfigureAwait(false);
From 41a4f4499a5669f2dba3d8d174243118ee6da587 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sun, 15 Jan 2023 21:43:23 +0900
Subject: [PATCH 07/21] Remove unused dependencies
---
Commandline/Parsers/ConfigureCommand.cs | 1 -
Commandline/Parsers/RandomCommandService.cs | 1 -
2 files changed, 2 deletions(-)
diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs
index 2c86425..a60fd1f 100644
--- a/Commandline/Parsers/ConfigureCommand.cs
+++ b/Commandline/Parsers/ConfigureCommand.cs
@@ -1,7 +1,6 @@
using System.Threading.Tasks;
using asuka.Commandline.Options;
using asuka.Configuration;
-using asuka.Output;
using asuka.Output.Writer;
using FluentValidation;
using Newtonsoft.Json;
diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
index acf9d13..608ef65 100644
--- a/Commandline/Parsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -1,7 +1,6 @@
using System;
using System.Threading.Tasks;
using asuka.Commandline.Options;
-using asuka.Configuration;
using asuka.Core.Compression;
using asuka.Core.Downloader;
using asuka.Core.Requests;
From bca457e21722452ec30d8acc956856526f57d7e0 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Tue, 24 Jan 2023 03:03:53 +0900
Subject: [PATCH 08/21] Refactor command line parser
---
AsukaApplication.cs | 43 +++++++------------
Commandline/CommandLineParserFactory.cs | 36 ++++++++++++++++
Commandline/CommandLineParserTokens.cs | 11 +++++
Commandline/ICommandLineParser.cs | 8 ++++
Commandline/ICommandLineParserFactory.cs | 6 +++
Commandline/Parsers/ConfigureCommand.cs | 5 ++-
Commandline/Parsers/FileCommandService.cs | 5 ++-
Commandline/Parsers/GetCommandService.cs | 5 ++-
Commandline/Parsers/IConfigureCommand.cs | 9 ----
Commandline/Parsers/IFileCommandService.cs | 9 ----
Commandline/Parsers/IGetCommandService.cs | 9 ----
Commandline/Parsers/IRandomCommandService.cs | 9 ----
.../Parsers/IRecommendCommandService.cs | 9 ----
Commandline/Parsers/ISearchCommandService.cs | 9 ----
Commandline/Parsers/RandomCommandService.cs | 5 ++-
.../Parsers/RecommendCommandService.cs | 5 ++-
Commandline/Parsers/SearchCommandService.cs | 5 ++-
Installers/InstallServices.cs | 16 ++++---
asuka.csproj | 4 --
19 files changed, 104 insertions(+), 104 deletions(-)
create mode 100644 Commandline/CommandLineParserFactory.cs
create mode 100644 Commandline/CommandLineParserTokens.cs
create mode 100644 Commandline/ICommandLineParser.cs
create mode 100644 Commandline/ICommandLineParserFactory.cs
delete mode 100644 Commandline/Parsers/IConfigureCommand.cs
delete mode 100644 Commandline/Parsers/IFileCommandService.cs
delete mode 100644 Commandline/Parsers/IGetCommandService.cs
delete mode 100644 Commandline/Parsers/IRandomCommandService.cs
delete mode 100644 Commandline/Parsers/IRecommendCommandService.cs
delete mode 100644 Commandline/Parsers/ISearchCommandService.cs
diff --git a/AsukaApplication.cs b/AsukaApplication.cs
index 53d7b71..0a91be6 100644
--- a/AsukaApplication.cs
+++ b/AsukaApplication.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
+using asuka.Commandline;
using asuka.Commandline.Options;
-using asuka.Commandline.Parsers;
using CommandLine;
using asuka.Output.Writer;
@@ -9,30 +9,13 @@ namespace asuka;
public class AsukaApplication
{
- private readonly IGetCommandService _getCommand;
- private readonly IRecommendCommandService _recommendCommand;
- private readonly ISearchCommandService _searchCommand;
- private readonly IRandomCommandService _randomCommand;
- private readonly IFileCommandService _fileCommand;
private readonly IConsoleWriter _console;
- private readonly IConfigureCommand _configureCommand;
+ private readonly ICommandLineParserFactory _command;
- public AsukaApplication(
- IGetCommandService getCommand,
- IRecommendCommandService recommendCommand,
- IConsoleWriter console,
- ISearchCommandService searchCommand,
- IRandomCommandService randomCommand,
- IFileCommandService fileCommand,
- IConfigureCommand configureCommand)
+ public AsukaApplication(IConsoleWriter console, ICommandLineParserFactory command)
{
- _getCommand = getCommand;
- _recommendCommand = recommendCommand;
_console = console;
- _searchCommand = searchCommand;
- _randomCommand = randomCommand;
- _fileCommand = fileCommand;
- _configureCommand = configureCommand;
+ _command = command;
}
public async Task RunAsync(IEnumerable args)
@@ -40,13 +23,19 @@ public async Task RunAsync(IEnumerable args)
var parser = Parser.Default
.ParseArguments(args);
await parser.MapResult(
- async (GetOptions opts) => { await _getCommand.RunAsync(opts); },
- async (RecommendOptions opts) => { await _recommendCommand.RunAsync(opts); },
- async (SearchOptions opts) => { await _searchCommand.RunAsync(opts); },
- async (RandomOptions opts) => { await _randomCommand.RunAsync(opts); },
- async (FileCommandOptions opts) => { await _fileCommand.RunAsync(opts); },
- async (ConfigureOptions opts) => { await _configureCommand.RunAsync(opts); },
+ async (GetOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Get); },
+ async (RecommendOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Recommend); },
+ async (SearchOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Search); },
+ async (RandomOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Random); },
+ async (FileCommandOptions opts) => { await RunCommand(opts, CommandLineParserTokens.File); },
+ async (ConfigureOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Configure); },
_ => Task.FromResult(1));
_console.SuccessLine("Task completed.");
}
+
+ private async Task RunCommand(object opts, CommandLineParserTokens token)
+ {
+ var service = _command.GetInstance(token);
+ await service.RunAsync(opts);
+ }
}
diff --git a/Commandline/CommandLineParserFactory.cs b/Commandline/CommandLineParserFactory.cs
new file mode 100644
index 0000000..28efc51
--- /dev/null
+++ b/Commandline/CommandLineParserFactory.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using asuka.Commandline.Parsers;
+
+namespace asuka.Commandline;
+
+public class CommandLineParserFactory : ICommandLineParserFactory
+{
+ private readonly IEnumerable _parsers;
+
+ public CommandLineParserFactory(IEnumerable parsers)
+ {
+ _parsers = parsers;
+ }
+
+ public ICommandLineParser GetInstance(CommandLineParserTokens token)
+ {
+ return token switch
+ {
+ CommandLineParserTokens.Configure => GetService(typeof(ConfigureCommand)),
+ CommandLineParserTokens.File => GetService(typeof(FileCommandService)),
+ CommandLineParserTokens.Get => GetService(typeof(GetCommandService)),
+ CommandLineParserTokens.Random => GetService(typeof(RandomCommandService)),
+ CommandLineParserTokens.Recommend => GetService(typeof(RecommendCommandService)),
+ CommandLineParserTokens.Search => GetService(typeof(SearchCommandService)),
+ _ => throw new ArgumentOutOfRangeException(nameof(token), token, null)
+ };
+ }
+
+ private ICommandLineParser GetService(Type type)
+ {
+ return _parsers.FirstOrDefault(x => x.GetType() == type)!;
+ }
+}
diff --git a/Commandline/CommandLineParserTokens.cs b/Commandline/CommandLineParserTokens.cs
new file mode 100644
index 0000000..ab69557
--- /dev/null
+++ b/Commandline/CommandLineParserTokens.cs
@@ -0,0 +1,11 @@
+namespace asuka.Commandline;
+
+public enum CommandLineParserTokens
+{
+ Configure,
+ File,
+ Get,
+ Random,
+ Recommend,
+ Search
+}
diff --git a/Commandline/ICommandLineParser.cs b/Commandline/ICommandLineParser.cs
new file mode 100644
index 0000000..0c3d388
--- /dev/null
+++ b/Commandline/ICommandLineParser.cs
@@ -0,0 +1,8 @@
+using System.Threading.Tasks;
+
+namespace asuka.Commandline;
+
+public interface ICommandLineParser
+{
+ public Task RunAsync(object options);
+}
diff --git a/Commandline/ICommandLineParserFactory.cs b/Commandline/ICommandLineParserFactory.cs
new file mode 100644
index 0000000..2c35295
--- /dev/null
+++ b/Commandline/ICommandLineParserFactory.cs
@@ -0,0 +1,6 @@
+namespace asuka.Commandline;
+
+public interface ICommandLineParserFactory
+{
+ public ICommandLineParser GetInstance(CommandLineParserTokens token);
+}
diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs
index a60fd1f..fbeb8b7 100644
--- a/Commandline/Parsers/ConfigureCommand.cs
+++ b/Commandline/Parsers/ConfigureCommand.cs
@@ -7,7 +7,7 @@
namespace asuka.Commandline.Parsers;
-public class ConfigureCommand : IConfigureCommand
+public class ConfigureCommand : ICommandLineParser
{
private readonly IConfigurationManager _configurationManager;
private readonly IConsoleWriter _consoleWriter;
@@ -28,8 +28,9 @@ private void ListAllConfigurationValues()
_consoleWriter.WriteLine($"UseTachiyomiLayout: {_configurationManager.Values.UseTachiyomiLayout}");
}
- public async Task RunAsync(ConfigureOptions opts)
+ public async Task RunAsync(object options)
{
+ var opts = (ConfigureOptions)options;
var validation = await _validator.ValidateAsync(opts);
if (!validation.IsValid)
{
diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs
index fa1d705..7f09c64 100644
--- a/Commandline/Parsers/FileCommandService.cs
+++ b/Commandline/Parsers/FileCommandService.cs
@@ -13,7 +13,7 @@
namespace asuka.Commandline.Parsers;
-public partial class FileCommandService : IFileCommandService
+public partial class FileCommandService : ICommandLineParser
{
private readonly IGalleryRequestService _api;
private readonly IConsoleWriter _console;
@@ -35,8 +35,9 @@ public partial class FileCommandService : IFileCommandService
_pack = pack;
}
- public async Task RunAsync(FileCommandOptions opts)
+ public async Task RunAsync(object options)
{
+ var opts = (FileCommandOptions)options;
if (!File.Exists(opts.FilePath))
{
_console.ErrorLine("File doesn't exist.");
diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs
index c3cf251..e031816 100644
--- a/Commandline/Parsers/GetCommandService.cs
+++ b/Commandline/Parsers/GetCommandService.cs
@@ -9,7 +9,7 @@
namespace asuka.Commandline.Parsers;
-public class GetCommandService : IGetCommandService
+public class GetCommandService : ICommandLineParser
{
private readonly IGalleryRequestService _api;
private readonly IValidator _validator;
@@ -31,8 +31,9 @@ public class GetCommandService : IGetCommandService
_pack = pack;
}
- public async Task RunAsync(GetOptions opts)
+ public async Task RunAsync(object options)
{
+ var opts = (GetOptions)options;
var validationResult = await _validator.ValidateAsync(opts);
if (!validationResult.IsValid)
{
diff --git a/Commandline/Parsers/IConfigureCommand.cs b/Commandline/Parsers/IConfigureCommand.cs
deleted file mode 100644
index 23fa243..0000000
--- a/Commandline/Parsers/IConfigureCommand.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Commandline.Options;
-
-namespace asuka.Commandline.Parsers;
-
-public interface IConfigureCommand
-{
- Task RunAsync(ConfigureOptions opts);
-}
diff --git a/Commandline/Parsers/IFileCommandService.cs b/Commandline/Parsers/IFileCommandService.cs
deleted file mode 100644
index 1bbb664..0000000
--- a/Commandline/Parsers/IFileCommandService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Commandline.Options;
-
-namespace asuka.Commandline.Parsers;
-
-public interface IFileCommandService
-{
- Task RunAsync(FileCommandOptions opts);
-}
diff --git a/Commandline/Parsers/IGetCommandService.cs b/Commandline/Parsers/IGetCommandService.cs
deleted file mode 100644
index 42fb1e0..0000000
--- a/Commandline/Parsers/IGetCommandService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Commandline.Options;
-
-namespace asuka.Commandline.Parsers;
-
-public interface IGetCommandService
-{
- Task RunAsync(GetOptions opts);
-}
diff --git a/Commandline/Parsers/IRandomCommandService.cs b/Commandline/Parsers/IRandomCommandService.cs
deleted file mode 100644
index 40d41cd..0000000
--- a/Commandline/Parsers/IRandomCommandService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Commandline.Options;
-
-namespace asuka.Commandline.Parsers;
-
-public interface IRandomCommandService
-{
- Task RunAsync(RandomOptions opts);
-}
diff --git a/Commandline/Parsers/IRecommendCommandService.cs b/Commandline/Parsers/IRecommendCommandService.cs
deleted file mode 100644
index c60d97c..0000000
--- a/Commandline/Parsers/IRecommendCommandService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Commandline.Options;
-
-namespace asuka.Commandline.Parsers;
-
-public interface IRecommendCommandService
-{
- Task RunAsync(RecommendOptions opts);
-}
diff --git a/Commandline/Parsers/ISearchCommandService.cs b/Commandline/Parsers/ISearchCommandService.cs
deleted file mode 100644
index 3d2552a..0000000
--- a/Commandline/Parsers/ISearchCommandService.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Commandline.Options;
-
-namespace asuka.Commandline.Parsers;
-
-public interface ISearchCommandService
-{
- Task RunAsync(SearchOptions opts);
-}
diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
index 608ef65..c6aeda6 100644
--- a/Commandline/Parsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -10,7 +10,7 @@
namespace asuka.Commandline.Parsers;
-public class RandomCommandService : IRandomCommandService
+public class RandomCommandService : ICommandLineParser
{
private readonly IDownloadService _download;
private readonly IGalleryRequestService _api;
@@ -29,8 +29,9 @@ public class RandomCommandService : IRandomCommandService
_pack = pack;
}
- public async Task RunAsync(RandomOptions opts)
+ public async Task RunAsync(object options)
{
+ var opts = (RandomOptions)options;
var totalNumbers = await _api.GetTotalGalleryCountAsync();
while (true)
diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs
index d36a354..10b26d6 100644
--- a/Commandline/Parsers/RecommendCommandService.cs
+++ b/Commandline/Parsers/RecommendCommandService.cs
@@ -10,7 +10,7 @@
namespace asuka.Commandline.Parsers;
-public class RecommendCommandService : IRecommendCommandService
+public class RecommendCommandService : ICommandLineParser
{
private readonly IValidator _validator;
private readonly IGalleryRequestService _api;
@@ -35,8 +35,9 @@ public class RecommendCommandService : IRecommendCommandService
_pack = pack;
}
- public async Task RunAsync(RecommendOptions opts)
+ public async Task RunAsync(object options)
{
+ var opts = (RecommendOptions)options;
var validator = await _validator.ValidateAsync(opts);
if (!validator.IsValid)
{
diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs
index 65fe6fb..aa1f1c9 100644
--- a/Commandline/Parsers/SearchCommandService.cs
+++ b/Commandline/Parsers/SearchCommandService.cs
@@ -13,7 +13,7 @@
namespace asuka.Commandline.Parsers;
-public class SearchCommandService : ISearchCommandService
+public class SearchCommandService : ICommandLineParser
{
private readonly IGalleryRequestService _api;
private readonly IValidator _validator;
@@ -38,8 +38,9 @@ public class SearchCommandService : ISearchCommandService
_pack = pack;
}
- public async Task RunAsync(SearchOptions opts)
+ public async Task RunAsync(object options)
{
+ var opts = (SearchOptions)options;
var validationResult = await _validator.ValidateAsync(opts);
if (!validationResult.IsValid)
{
diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs
index c226520..b8fa643 100644
--- a/Installers/InstallServices.cs
+++ b/Installers/InstallServices.cs
@@ -1,3 +1,4 @@
+using asuka.Commandline;
using asuka.Commandline.Parsers;
using asuka.Configuration;
using asuka.Core.Compression;
@@ -22,15 +23,16 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu
services.AddSingleton();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// Command parsers
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
services.AddValidatorsFromAssemblyContaining();
}
}
diff --git a/asuka.csproj b/asuka.csproj
index 164ef24..07cec4c 100644
--- a/asuka.csproj
+++ b/asuka.csproj
@@ -58,8 +58,4 @@
-
-
-
-
From 2d1d285bac061cfd4c1917d51adca4faba413fe7 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Fri, 27 Jan 2023 13:30:32 +0900
Subject: [PATCH 09/21] Refactor downloader
---
Commandline/CommandLineParserFactory.cs | 1 -
Commandline/Parsers/FileCommandService.cs | 19 ++-
Commandline/Parsers/GetCommandService.cs | 25 ++-
Commandline/Parsers/RandomCommandService.cs | 22 ++-
.../Parsers/RecommendCommandService.cs | 17 +-
Commandline/Parsers/SearchCommandService.cs | 17 +-
Core/Downloader/DownloadService.cs | 149 ------------------
Core/Downloader/Downloader.cs | 141 +++++++++++++++++
Core/Downloader/IDownloadService.cs | 11 --
Core/Downloader/IDownloader.cs | 13 ++
.../InternalTypes/DownloadResult.cs | 9 --
.../InternalTypes/FetchImageParameter.cs | 11 --
.../Downloader/InternalTypes/PrepareResult.cs | 8 -
Core/Utilities/PathUtils.cs | 106 +++++++++++++
Installers/InstallServices.cs | 2 +-
15 files changed, 340 insertions(+), 211 deletions(-)
delete mode 100644 Core/Downloader/DownloadService.cs
create mode 100644 Core/Downloader/Downloader.cs
delete mode 100644 Core/Downloader/IDownloadService.cs
create mode 100644 Core/Downloader/IDownloader.cs
delete mode 100644 Core/Downloader/InternalTypes/DownloadResult.cs
delete mode 100644 Core/Downloader/InternalTypes/FetchImageParameter.cs
delete mode 100644 Core/Downloader/InternalTypes/PrepareResult.cs
create mode 100644 Core/Utilities/PathUtils.cs
diff --git a/Commandline/CommandLineParserFactory.cs b/Commandline/CommandLineParserFactory.cs
index 28efc51..d45f9b8 100644
--- a/Commandline/CommandLineParserFactory.cs
+++ b/Commandline/CommandLineParserFactory.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using asuka.Commandline.Parsers;
diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs
index 7f09c64..85801ea 100644
--- a/Commandline/Parsers/FileCommandService.cs
+++ b/Commandline/Parsers/FileCommandService.cs
@@ -17,14 +17,14 @@ public partial class FileCommandService : ICommandLineParser
{
private readonly IGalleryRequestService _api;
private readonly IConsoleWriter _console;
- private readonly IDownloadService _download;
+ private readonly IDownloader _download;
private readonly IProgressService _progressService;
private readonly IPackArchiveToCbz _pack;
public FileCommandService(
IGalleryRequestService api,
IConsoleWriter console,
- IDownloadService download,
+ IDownloader download,
IProgressService progressService,
IPackArchiveToCbz pack)
{
@@ -68,11 +68,22 @@ public async Task RunAsync(object options)
var code = NumericRegex().Match(url).Value;
var response = await _api.FetchSingleAsync(code);
- var result = await _download.DownloadAsync(response, opts.Output);
+ await _download.Initialize(response, opts.Output, 1);
+ // Create progress bar
+ var internalProgress = _progressService.NestToMaster(response.TotalPages, $"downloading: {response.Id}");
+ _download.OnImageDownload = () =>
+ {
+ internalProgress.Tick($"downloading: {response.Id}");
+ };
+
+ // Start downloading
+ await _download.Start();
+
+ // If --pack option is specified, compresss the file into cbz
if (opts.Pack)
{
- await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
+ await _pack.RunAsync(_download.DownloadRoot, opts.Output, internalProgress);
}
progress.Tick();
}
diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs
index e031816..49ced9c 100644
--- a/Commandline/Parsers/GetCommandService.cs
+++ b/Commandline/Parsers/GetCommandService.cs
@@ -4,6 +4,7 @@
using asuka.Core.Downloader;
using asuka.Core.Requests;
using asuka.Output;
+using asuka.Output.ProgressService;
using asuka.Output.Writer;
using FluentValidation;
@@ -13,21 +14,24 @@ public class GetCommandService : ICommandLineParser
{
private readonly IGalleryRequestService _api;
private readonly IValidator _validator;
- private readonly IDownloadService _download;
+ private readonly IDownloader _download;
private readonly IConsoleWriter _console;
+ private readonly IProgressService _progress;
private readonly IPackArchiveToCbz _pack;
public GetCommandService(
IGalleryRequestService api,
IValidator validator,
- IDownloadService download,
+ IDownloader download,
IConsoleWriter console,
+ IProgressService progress,
IPackArchiveToCbz pack)
{
_api = api;
_validator = validator;
_download = download;
_console = console;
+ _progress = progress;
_pack = pack;
}
@@ -48,11 +52,22 @@ public async Task RunAsync(object options)
{
return;
}
-
- var result = await _download.DownloadAsync(response, opts.Output);
+
+ await _download.Initialize(response, opts.Output, 1);
+
+ _progress.CreateMasterProgress(response.TotalPages, $"downloading: {response.Id}");
+
+ var progress = _progress.GetMasterProgress();
+ _download.OnImageDownload = () =>
+ {
+ progress.Tick();
+ };
+
+ await _download.Start();
+
if (opts.Pack)
{
- await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
+ await _pack.RunAsync(_download.DownloadRoot, opts.Output, progress);
}
}
}
diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
index c6aeda6..11bfc3c 100644
--- a/Commandline/Parsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -5,6 +5,7 @@
using asuka.Core.Downloader;
using asuka.Core.Requests;
using asuka.Output;
+using asuka.Output.ProgressService;
using asuka.Output.Writer;
using Sharprompt;
@@ -12,20 +13,23 @@ namespace asuka.Commandline.Parsers;
public class RandomCommandService : ICommandLineParser
{
- private readonly IDownloadService _download;
+ private readonly IDownloader _download;
private readonly IGalleryRequestService _api;
private readonly IConsoleWriter _console;
+ private readonly IProgressService _progress;
private readonly IPackArchiveToCbz _pack;
public RandomCommandService(
- IDownloadService download,
+ IDownloader download,
IGalleryRequestService api,
IConsoleWriter console,
+ IProgressService progress,
IPackArchiveToCbz pack)
{
_download = download;
_api = api;
_console = console;
+ _progress = progress;
_pack = pack;
}
@@ -48,10 +52,20 @@ public async Task RunAsync(object options)
continue;
}
- var result = await _download.DownloadAsync(response, opts.Output);
+ await _download.Initialize(response, opts.Output, 1);
+
+ _progress.CreateMasterProgress(response.TotalPages, $"downloading random id: {response.Id}");
+ var progress = _progress.GetMasterProgress();
+ _download.OnImageDownload = () =>
+ {
+ progress.Tick();
+ };
+
+ await _download.Start();
+
if (opts.Pack)
{
- await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
+ await _pack.RunAsync(_download.DownloadRoot, opts.Output, progress);
}
break;
}
diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs
index 10b26d6..0d1b3df 100644
--- a/Commandline/Parsers/RecommendCommandService.cs
+++ b/Commandline/Parsers/RecommendCommandService.cs
@@ -14,7 +14,7 @@ public class RecommendCommandService : ICommandLineParser
{
private readonly IValidator _validator;
private readonly IGalleryRequestService _api;
- private readonly IDownloadService _download;
+ private readonly IDownloader _download;
private readonly IConsoleWriter _console;
private readonly IProgressService _progressService;
private readonly IPackArchiveToCbz _pack;
@@ -22,7 +22,7 @@ public class RecommendCommandService : ICommandLineParser
public RecommendCommandService(
IValidator validator,
IGalleryRequestService api,
- IDownloadService download,
+ IDownloader download,
IConsoleWriter console,
IProgressService progressService,
IPackArchiveToCbz pack)
@@ -54,10 +54,19 @@ public async Task RunAsync(object options)
foreach (var response in selection)
{
- var result = await _download.DownloadAsync(response, opts.Output);
+ await _download.Initialize(response, opts.Output, 1);
+
+ var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}");
+ _download.OnImageDownload = () =>
+ {
+ innerProgress.Tick();
+ };
+
+ await _download.Start();
+
if (opts.Pack)
{
- await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
+ await _pack.RunAsync(_download.DownloadRoot, opts.Output, innerProgress);
}
progress.Tick();
}
diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs
index aa1f1c9..c071af8 100644
--- a/Commandline/Parsers/SearchCommandService.cs
+++ b/Commandline/Parsers/SearchCommandService.cs
@@ -18,7 +18,7 @@ public class SearchCommandService : ICommandLineParser
private readonly IGalleryRequestService _api;
private readonly IValidator _validator;
private readonly IConsoleWriter _console;
- private readonly IDownloadService _download;
+ private readonly IDownloader _download;
private readonly IProgressService _progressService;
private readonly IPackArchiveToCbz _pack;
@@ -26,7 +26,7 @@ public class SearchCommandService : ICommandLineParser
IGalleryRequestService api,
IValidator validator,
IConsoleWriter console,
- IDownloadService download,
+ IDownloader download,
IProgressService progressService,
IPackArchiveToCbz pack)
{
@@ -77,10 +77,19 @@ public async Task RunAsync(object options)
foreach (var response in selection)
{
- var result = await _download.DownloadAsync(response, opts.Output);
+ await _download.Initialize(response, opts.Output, 1);
+
+ var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}");
+ _download.OnImageDownload = () =>
+ {
+ innerProgress.Tick();
+ };
+
+ await _download.Start();
+
if (opts.Pack)
{
- await _pack.RunAsync(result.DestinationPath, opts.Output, result.ProgressBar);
+ await _pack.RunAsync(_download.DownloadRoot, opts.Output, innerProgress);
}
progress.Tick();
}
diff --git a/Core/Downloader/DownloadService.cs b/Core/Downloader/DownloadService.cs
deleted file mode 100644
index f19f182..0000000
--- a/Core/Downloader/DownloadService.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using asuka.Configuration;
-using asuka.Core.Api;
-using asuka.Core.Downloader.InternalTypes;
-using asuka.Core.Models;
-using asuka.Output;
-using asuka.Output.ProgressService;
-using Newtonsoft.Json;
-using ShellProgressBar;
-
-namespace asuka.Core.Downloader;
-
-public class DownloadService : IDownloadService
-{
- private readonly IGalleryImage _api;
- private readonly IProgressService _progress;
- private readonly IConfigurationManager _configurationManager;
-
- public DownloadService(IGalleryImage api, IProgressService progress, IConfigurationManager configurationManager)
- {
- _api = api;
- _progress = progress;
- _configurationManager = configurationManager;
- }
-
- public async Task DownloadAsync(GalleryResult result, string outputPath)
- {
- var useTachiyomiFolderLayout = _configurationManager.Values.UseTachiyomiLayout;
- // Prepare the download.
- var prepare = await PrepareAsync(result, outputPath, useTachiyomiFolderLayout).ConfigureAwait(false);
-
- // If the progress is null, we create a new one.
- var progressInitialTitle = $"[queued] id: {prepare.Id}";
- var bar = _progress.NestToMaster(result.TotalPages, progressInitialTitle);
-
- var throttler = new SemaphoreSlim(2);
- var taskList = result.Images
- .Select(x => Task.Run(async () =>
- {
- await throttler.WaitAsync().ConfigureAwait(false);
-
- try
- {
- var param = new FetchImageParameter
- {
- DestinationPath = prepare.DestinationPath,
- MediaId = result.MediaId,
- Page = x,
- TaskId = prepare.Id
- };
-
- await FetchImageAsync(param, bar).ConfigureAwait(false);
- }
- finally
- {
- throttler.Release();
- }
- }));
-
- await Task.WhenAll(taskList).ConfigureAwait(false);
-
- var destination = useTachiyomiFolderLayout
- ? Path.GetFullPath($"{prepare.DestinationPath}/../") : prepare.DestinationPath;
- return new DownloadResult
- {
- DestinationPath = destination,
- ProgressBar = bar
- };
- }
-
- private static string SantizeFolderName(string folderName)
- {
- var illegalRegex = new string(Path.GetInvalidFileNameChars()) + ".";
- var regex = new Regex($"[{Regex.Escape(illegalRegex)}]");
- var multiSpacingRegex = new Regex("[ ]{2,}");
-
- folderName = regex.Replace(folderName, "");
- folderName = folderName.Trim();
- folderName = multiSpacingRegex.Replace(folderName, "");
-
- return folderName;
- }
-
- private async Task PrepareAsync(GalleryResult result, string outputPath, bool useTachiyomiFolderLayout)
- {
- var destinationPath = Environment.CurrentDirectory;
- if (!string.IsNullOrEmpty(outputPath))
- {
- destinationPath = outputPath;
- }
-
- var galleryTitle = string.IsNullOrEmpty(result.Title.Japanese)
- ? (string.IsNullOrEmpty(result.Title.English) ? result.Title.Pretty : result.Title.English)
- : result.Title.Japanese;
-
- var folderName = SantizeFolderName($"{result.Id} - {galleryTitle}");
-
- var mangaRootPath = Path.Join(destinationPath, folderName);
- destinationPath = useTachiyomiFolderLayout ? Path.Join(mangaRootPath, "ch1") : mangaRootPath;
- if (!Directory.Exists(destinationPath))
- {
- Directory.CreateDirectory(destinationPath);
- }
-
- var metadataPath = Path.Combine(mangaRootPath, "info.txt");
- await File.WriteAllTextAsync(metadataPath, result.ToReadable())
- .ConfigureAwait(false);
-
- // Generate Tachiyomi details.json
- if (useTachiyomiFolderLayout)
- {
- var tachiyomiMetadataPath = Path.Combine(mangaRootPath, "details.json");
- var tachiyomiMetadata = JsonConvert
- .SerializeObject(result.ToTachiyomiMetadata(), Formatting.Indented);
- await File.WriteAllTextAsync(tachiyomiMetadataPath, tachiyomiMetadata)
- .ConfigureAwait(false);
- }
-
- return new PrepareResult
- {
- FolderName = folderName,
- Id = result.Id,
- DestinationPath = destinationPath
- };
- }
-
- private async Task FetchImageAsync(FetchImageParameter data, IProgressBar bar)
- {
- var filePath = Path.Combine(data.DestinationPath, data.Page.Filename);
- if (File.Exists(filePath))
- {
- bar.Tick($"[skipped existing] id: {data.TaskId}");
- return;
- }
-
- var image = await _api.GetImage(data.MediaId.ToString(), data.Page.ServerFilename);
- var imageContents = await image.ReadAsByteArrayAsync();
-
- await File.WriteAllBytesAsync(filePath, imageContents)
- .ConfigureAwait(false);
-
- bar.Tick($"[downloading] id: {data.TaskId}");
- }
-}
diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs
new file mode 100644
index 0000000..19b4281
--- /dev/null
+++ b/Core/Downloader/Downloader.cs
@@ -0,0 +1,141 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using asuka.Configuration;
+using asuka.Core.Api;
+using asuka.Core.Models;
+using asuka.Core.Utilities;
+using asuka.Output;
+using Newtonsoft.Json;
+
+namespace asuka.Core.Downloader;
+
+internal record DownloadTaskDetails
+{
+ internal GalleryResult Result { get; init; }
+ internal string ChapterPath { get; init; }
+ internal string ChapterRoot { get; init; }
+}
+
+internal record FetchImageParameter
+{
+ internal string DestinationPath { get; init; }
+ internal int MediaId { get; init; }
+ internal GalleryImageResult Page { get; init; }
+}
+
+public class Downloader : IDownloader
+{
+ private readonly IGalleryImage _api;
+ private readonly IConfigurationManager _configurationManager;
+ private DownloadTaskDetails _details;
+
+ public Downloader(IGalleryImage api, IConfigurationManager configurationManager)
+ {
+ _api = api;
+ _configurationManager = configurationManager;
+ }
+
+ private static string GetTitle(GalleryTitleResult result)
+ {
+ if (!string.IsNullOrEmpty(result.Japanese)) return result.Japanese;
+ if (!string.IsNullOrEmpty(result.English)) return result.English;
+ if (!string.IsNullOrEmpty(result.Pretty)) return result.Pretty;
+
+ return "Unknown title";
+ }
+
+ private async Task WriteMetadata(string output, GalleryResult result, int chapter = 0)
+ {
+ if (_configurationManager.Values.UseTachiyomiLayout && chapter != -1)
+ {
+ var metaPath = Path.Combine(output, "details.json");
+ var metadata = JsonConvert
+ .SerializeObject(result.ToTachiyomiMetadata(), Formatting.Indented);
+
+ await File.WriteAllTextAsync(metaPath, metadata).ConfigureAwait(false);
+ return;
+ }
+
+ var metadataPath = Path.Combine(output, "info.txt");
+ await File.WriteAllTextAsync(metadataPath, result.ToReadable())
+ .ConfigureAwait(false);
+ }
+
+ public async Task Initialize(GalleryResult result, string outputPath, int chapter = -1)
+ {
+ // Override given chapter when using the layout is false.
+ var chapterNumber = _configurationManager.Values.UseTachiyomiLayout ? chapter : -1;
+ var destination = PathUtils.NormalizeJoin(outputPath, GetTitle(result.Title), chapterNumber);
+ if (!Directory.Exists(destination.ChapterPath))
+ {
+ Directory.CreateDirectory(destination.ChapterPath!);
+ }
+
+ await WriteMetadata(destination.ChapterRoot, result, chapter);
+
+ _details = new DownloadTaskDetails
+ {
+ ChapterPath = destination.ChapterPath,
+ ChapterRoot = destination.ChapterRoot,
+ Result = result
+ };
+ }
+
+ public Action OnImageDownload { get; set; } = () => { };
+ public string DownloadRoot => _details.ChapterRoot;
+
+ public async Task Start()
+ {
+ // Break when necessary.
+ if (_details is null)
+ {
+ throw new NullReferenceException("Data required for download task is missing.");
+ }
+
+ var throttler = new SemaphoreSlim(2);
+ var taskList = _details.Result.Images
+ .Select(x => Task.Run(async () =>
+ {
+ await throttler.WaitAsync().ConfigureAwait(false);
+
+ try
+ {
+ var param = new FetchImageParameter
+ {
+ DestinationPath = _details.ChapterPath,
+ MediaId = _details.Result.MediaId,
+ Page = x
+ };
+
+ await DownloadImage(param).ConfigureAwait(false);
+ }
+ finally
+ {
+ throttler.Release();
+ }
+ }));
+
+ await Task.WhenAll(taskList).ConfigureAwait(false);
+ }
+
+ private async Task DownloadImage(FetchImageParameter data)
+ {
+ var filePath = Path.Combine(data.DestinationPath, data.Page.Filename);
+ if (File.Exists(filePath))
+ {
+ OnImageDownload();
+ return;
+ }
+
+ var image = await _api.GetImage(data.MediaId.ToString(), data.Page.ServerFilename);
+ var imageData = await image.ReadAsByteArrayAsync();
+
+ await File.WriteAllBytesAsync(filePath, imageData)
+ .ConfigureAwait(false);
+
+ OnImageDownload();
+ }
+}
diff --git a/Core/Downloader/IDownloadService.cs b/Core/Downloader/IDownloadService.cs
deleted file mode 100644
index 5e78b4e..0000000
--- a/Core/Downloader/IDownloadService.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Threading.Tasks;
-using asuka.Core.Downloader.InternalTypes;
-using asuka.Core.Models;
-using ShellProgressBar;
-
-namespace asuka.Core.Downloader;
-
-public interface IDownloadService
-{
- Task DownloadAsync(GalleryResult result, string outputPath);
-}
diff --git a/Core/Downloader/IDownloader.cs b/Core/Downloader/IDownloader.cs
new file mode 100644
index 0000000..6d5d5fd
--- /dev/null
+++ b/Core/Downloader/IDownloader.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Threading.Tasks;
+using asuka.Core.Models;
+
+namespace asuka.Core.Downloader;
+
+public interface IDownloader
+{
+ public Action OnImageDownload { set; }
+ public string DownloadRoot { get; }
+ Task Start();
+ Task Initialize(GalleryResult result, string outputPath, int chapter = -1);
+}
diff --git a/Core/Downloader/InternalTypes/DownloadResult.cs b/Core/Downloader/InternalTypes/DownloadResult.cs
deleted file mode 100644
index 85e3a1b..0000000
--- a/Core/Downloader/InternalTypes/DownloadResult.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using ShellProgressBar;
-
-namespace asuka.Core.Downloader.InternalTypes;
-
-public record DownloadResult
-{
- public string DestinationPath { get; init; }
- public IProgressBar ProgressBar { get; init; }
-}
diff --git a/Core/Downloader/InternalTypes/FetchImageParameter.cs b/Core/Downloader/InternalTypes/FetchImageParameter.cs
deleted file mode 100644
index 09413fb..0000000
--- a/Core/Downloader/InternalTypes/FetchImageParameter.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using asuka.Core.Models;
-
-namespace asuka.Core.Downloader.InternalTypes;
-
-internal record FetchImageParameter
-{
- internal int MediaId { get; init; }
- internal GalleryImageResult Page { get; init; }
- internal int TaskId { get; init; }
- internal string DestinationPath { get; init; }
-}
diff --git a/Core/Downloader/InternalTypes/PrepareResult.cs b/Core/Downloader/InternalTypes/PrepareResult.cs
deleted file mode 100644
index c72d158..0000000
--- a/Core/Downloader/InternalTypes/PrepareResult.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace asuka.Core.Downloader.InternalTypes;
-
-internal record PrepareResult
-{
- public string FolderName { get; init; }
- public int Id { get; init; }
- public string DestinationPath { get; init; }
-}
diff --git a/Core/Utilities/PathUtils.cs b/Core/Utilities/PathUtils.cs
new file mode 100644
index 0000000..fcc2ffd
--- /dev/null
+++ b/Core/Utilities/PathUtils.cs
@@ -0,0 +1,106 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace asuka.Core.Utilities;
+
+public struct NormalizeJoinDetails
+{
+ public string ChapterRoot { get; init; }
+ public string ChapterPath { get; init; }
+}
+
+public static class PathUtils
+{
+ ///
+ /// Instead of removing known illegal file path characters, we can substitute them to their fixed-width
+ /// alternatives.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static NormalizeJoinDetails NormalizeJoin(string outputPath, string folderName, int chapter = -1)
+ {
+ #region Dictionaries of Character conversion
+ var characterMap = new Dictionary
+ {
+ {
+ "#", "#"
+ },
+ {
+ "%", "%"
+ },
+ {
+ "&", "&"
+ },
+ {
+ "{", "{"
+ },
+ {
+ "}", "}"
+ },
+ {
+ "\\", "/"
+ },
+ {
+ "<", "<"
+ },
+ {
+ ">", ">"
+ },
+ {
+ "*", "*"
+ },
+ {
+ "?", "?"
+ },
+ {
+ "/", "/"
+ },
+ {
+ "$", "$"
+ },
+ {
+ "!", "!"
+ },
+ {
+ "'", "'"
+ },
+ {
+ "\"", """
+ },
+ {
+ ":", ":"
+ },
+ {
+ "@", "@"
+ },
+ {
+ "+", "+"
+ },
+ {
+ "`", "`"
+ },
+ {
+ "|", "|"
+ },
+ {
+ "=", "="
+ }
+ };
+ #endregion
+
+ var normalizedFolderName = $"{folderName}";
+ normalizedFolderName = characterMap
+ .Aggregate(normalizedFolderName, (current, pair) => current.Replace(pair.Key, pair.Value));
+
+ var chapterRoot = Path.Combine(outputPath, chapter == -1 ? normalizedFolderName : $"{normalizedFolderName}");
+
+ return new NormalizeJoinDetails
+ {
+ ChapterPath = chapter == -1 ? chapterRoot : Path.Combine(chapterRoot, $"ch{chapter}"),
+ ChapterRoot = chapterRoot
+ };
+ }
+}
diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs
index b8fa643..f481c73 100644
--- a/Installers/InstallServices.cs
+++ b/Installers/InstallServices.cs
@@ -21,7 +21,7 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
- services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
From 0108d86715e0e33a00a63a2543468efb4c7db4ef Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sat, 28 Jan 2023 15:59:18 +0900
Subject: [PATCH 10/21] Remove Newtonsoft.Json as dependency
Use built-in System.Text.Json as a default serializer/deserializer for Json.
---
AsukaApplication.cs | 14 +++++++--
Commandline/Parsers/ConfigureCommand.cs | 4 +--
Configuration/ConfigurationData.cs | 10 +++----
Configuration/ConfigurationManager.cs | 11 +++----
Configuration/CookieDump.cs | 22 +++++++-------
.../Responses/GalleryImageObjectResponse.cs | 6 ++--
Core/Api/Responses/GalleryImageResponse.cs | 14 ++++-----
Core/Api/Responses/GalleryListResponse.cs | 6 ++--
Core/Api/Responses/GalleryResponse.cs | 30 +++++++++----------
Core/Api/Responses/GallerySearchResponse.cs | 10 +++----
Core/Api/Responses/GalleryTagResponse.cs | 20 ++++++-------
Core/Api/Responses/GalleryTitleResponse.cs | 14 ++++-----
Core/Downloader/Downloader.cs | 7 +++--
.../ContractToGalleryResultModelMapping.cs | 8 ++++-
Core/Models/TachiyomiDetails.cs | 14 ++++-----
Installers/Refit/ConfigureRefitService.cs | 9 +++++-
asuka.csproj | 2 --
17 files changed, 112 insertions(+), 89 deletions(-)
diff --git a/AsukaApplication.cs b/AsukaApplication.cs
index 0a91be6..f01902f 100644
--- a/AsukaApplication.cs
+++ b/AsukaApplication.cs
@@ -29,13 +29,23 @@ public async Task RunAsync(IEnumerable args)
async (RandomOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Random); },
async (FileCommandOptions opts) => { await RunCommand(opts, CommandLineParserTokens.File); },
async (ConfigureOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Configure); },
- _ => Task.FromResult(1));
- _console.SuccessLine("Task completed.");
+ async (SeriesCreatorCommandOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Series); },
+ errors =>
+ {
+ foreach (var error in errors)
+ {
+ _console.ErrorLine($"An error occured. Type: {error.Tag}");
+ }
+
+ return Task.FromResult(1);
+ });
}
private async Task RunCommand(object opts, CommandLineParserTokens token)
{
var service = _command.GetInstance(token);
await service.RunAsync(opts);
+
+ _console.SuccessLine("Task completed.");
}
}
diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs
index fbeb8b7..dc60ec0 100644
--- a/Commandline/Parsers/ConfigureCommand.cs
+++ b/Commandline/Parsers/ConfigureCommand.cs
@@ -1,9 +1,9 @@
+using System.Text.Json;
using System.Threading.Tasks;
using asuka.Commandline.Options;
using asuka.Configuration;
using asuka.Output.Writer;
using FluentValidation;
-using Newtonsoft.Json;
namespace asuka.Commandline.Parsers;
@@ -22,7 +22,7 @@ public ConfigureCommand(IValidator validator, IConfigurationMa
private void ListAllConfigurationValues()
{
- _consoleWriter.WriteLine($"Cookies: {JsonConvert.SerializeObject(_configurationManager.Values.Cookies)}");
+ _consoleWriter.WriteLine($"Cookies: {JsonSerializer.Serialize(_configurationManager.Values.Cookies)}");
_consoleWriter.WriteLine($"User Agent: {_configurationManager.Values.UserAgent}");
_consoleWriter.WriteLine($"Theme: {_configurationManager.Values.ConsoleTheme}");
_consoleWriter.WriteLine($"UseTachiyomiLayout: {_configurationManager.Values.UseTachiyomiLayout}");
diff --git a/Configuration/ConfigurationData.cs b/Configuration/ConfigurationData.cs
index 891f664..d8187f0 100644
--- a/Configuration/ConfigurationData.cs
+++ b/Configuration/ConfigurationData.cs
@@ -1,18 +1,18 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Configuration;
public class ConfigurationData
{
- [JsonProperty("cookies")]
+ [JsonPropertyName("cookies")]
public CookieDump[] Cookies { get; set; }
- [JsonProperty("user_agent")]
+ [JsonPropertyName("user_agent")]
public string UserAgent { get; set; }
- [JsonProperty("use_tachiyomi_layout")]
+ [JsonPropertyName("use_tachiyomi_layout")]
public bool UseTachiyomiLayout { get; set; }
- [JsonProperty("console_theme")]
+ [JsonPropertyName("console_theme")]
public string ConsoleTheme { get; set; }
}
diff --git a/Configuration/ConfigurationManager.cs b/Configuration/ConfigurationManager.cs
index 24a3de5..15b0f8c 100644
--- a/Configuration/ConfigurationManager.cs
+++ b/Configuration/ConfigurationManager.cs
@@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Text;
+using System.Text.Json;
using System.Threading.Tasks;
-using Newtonsoft.Json;
namespace asuka.Configuration;
@@ -23,7 +23,7 @@ public ConfigurationManager()
}
var data = File.ReadAllText(configPath, Encoding.UTF8);
- Configuration = JsonConvert.DeserializeObject(data);
+ Configuration = JsonSerializer.Deserialize(data);
}
public async Task SetCookiesAsync(string path)
@@ -34,7 +34,7 @@ public async Task SetCookiesAsync(string path)
}
var cookies = await File.ReadAllTextAsync(path, Encoding.UTF8);
- var cookieData = JsonConvert.DeserializeObject(cookies);
+ var cookieData = JsonSerializer.Deserialize(cookies);
if (cookieData == null) return;
@@ -72,8 +72,9 @@ private async Task FlushAsync()
{
var configPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
".asuka/config.json");
-
- var data = JsonConvert.SerializeObject(Configuration);
+
+ var serializerOptions = new JsonSerializerOptions { WriteIndented = true };
+ var data = JsonSerializer.Serialize(Configuration, serializerOptions);
await File.WriteAllTextAsync(configPath, data);
}
}
diff --git a/Configuration/CookieDump.cs b/Configuration/CookieDump.cs
index 9ebfe67..eb206c9 100644
--- a/Configuration/CookieDump.cs
+++ b/Configuration/CookieDump.cs
@@ -1,21 +1,21 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Configuration;
public record CookieDump
{
- [JsonProperty("domain")]
- public string Domain { get; init; }
+ [JsonPropertyName("domain")]
+ public string Domain { get; set; }
- [JsonProperty("httpOnly")]
- public bool HttpOnly { get; init; }
+ [JsonPropertyName("httpOnly")]
+ public bool HttpOnly { get; set; }
- [JsonProperty("secure")]
- public bool Secure { get; init; }
+ [JsonPropertyName("secure")]
+ public bool Secure { get; set; }
- [JsonProperty("name")]
- public string Name { get; init; }
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
- [JsonProperty("value")]
- public string Value { get; init; }
+ [JsonPropertyName("value")]
+ public string Value { get; set; }
}
diff --git a/Core/Api/Responses/GalleryImageObjectResponse.cs b/Core/Api/Responses/GalleryImageObjectResponse.cs
index 431e7ac..da211a1 100644
--- a/Core/Api/Responses/GalleryImageObjectResponse.cs
+++ b/Core/Api/Responses/GalleryImageObjectResponse.cs
@@ -1,10 +1,10 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GalleryImageObjectResponse
{
- [JsonProperty("pages")]
- public IReadOnlyList Images { get; init; }
+ [JsonPropertyName("pages")]
+ public IReadOnlyList Images { get; set; }
}
diff --git a/Core/Api/Responses/GalleryImageResponse.cs b/Core/Api/Responses/GalleryImageResponse.cs
index 0e9d2db..ed84d89 100644
--- a/Core/Api/Responses/GalleryImageResponse.cs
+++ b/Core/Api/Responses/GalleryImageResponse.cs
@@ -1,15 +1,15 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GalleryImageResponse
{
- [JsonProperty("t")]
- public string Type { get; init; }
+ [JsonPropertyName("t")]
+ public string Type { get; set; }
- [JsonProperty("h")]
- public int Height { get; init; }
+ [JsonPropertyName("h")]
+ public int Height { get; set; }
- [JsonProperty("w")]
- public int Width { get; init; }
+ [JsonPropertyName("w")]
+ public int Width { get; set; }
}
diff --git a/Core/Api/Responses/GalleryListResponse.cs b/Core/Api/Responses/GalleryListResponse.cs
index d820d09..4509b3f 100644
--- a/Core/Api/Responses/GalleryListResponse.cs
+++ b/Core/Api/Responses/GalleryListResponse.cs
@@ -1,10 +1,10 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GalleryListResponse
{
- [JsonProperty("result")]
- public IReadOnlyList Result { get; init; }
+ [JsonPropertyName("result")]
+ public IReadOnlyList Result { get; set; }
}
diff --git a/Core/Api/Responses/GalleryResponse.cs b/Core/Api/Responses/GalleryResponse.cs
index 949a586..f43ef53 100644
--- a/Core/Api/Responses/GalleryResponse.cs
+++ b/Core/Api/Responses/GalleryResponse.cs
@@ -1,28 +1,28 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GalleryResponse
{
- [JsonProperty("id")]
- public int Id { get; init; }
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
- [JsonProperty("media_id")]
- public int MediaId { get; init; }
+ [JsonPropertyName("media_id")]
+ public string MediaId { get; set; }
- [JsonProperty("title")]
- public GalleryTitleResponse Title { get; init; }
+ [JsonPropertyName("title")]
+ public GalleryTitleResponse Title { get; set; }
- [JsonProperty("images")]
- public GalleryImageObjectResponse Images { get; init; }
+ [JsonPropertyName("images")]
+ public GalleryImageObjectResponse Images { get; set; }
- [JsonProperty("tags")]
- public IReadOnlyList Tags { get; init; }
+ [JsonPropertyName("tags")]
+ public IReadOnlyList Tags { get; set; }
- [JsonProperty("num_pages")]
- public int TotalPages { get; init; }
+ [JsonPropertyName("num_pages")]
+ public int TotalPages { get; set; }
- [JsonProperty("num_favorites")]
- public int TotalFavorites { get; init; }
+ [JsonPropertyName("num_favorites")]
+ public int TotalFavorites { get; set; }
}
diff --git a/Core/Api/Responses/GallerySearchResponse.cs b/Core/Api/Responses/GallerySearchResponse.cs
index 8c470e1..1d22d71 100644
--- a/Core/Api/Responses/GallerySearchResponse.cs
+++ b/Core/Api/Responses/GallerySearchResponse.cs
@@ -1,12 +1,12 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GallerySearchResponse : GalleryListResponse
{
- [JsonProperty("num_pages")]
- public int TotalPages { get; init; }
+ [JsonPropertyName("num_pages")]
+ public int TotalPages { get; set; }
- [JsonProperty("per_page")]
- public int TotalItemsPerPage { get; init; }
+ [JsonPropertyName("per_page")]
+ public int TotalItemsPerPage { get; set; }
}
diff --git a/Core/Api/Responses/GalleryTagResponse.cs b/Core/Api/Responses/GalleryTagResponse.cs
index 637b59c..4af1c33 100644
--- a/Core/Api/Responses/GalleryTagResponse.cs
+++ b/Core/Api/Responses/GalleryTagResponse.cs
@@ -1,21 +1,21 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GalleryTagResponse
{
- [JsonProperty("id")]
+ [JsonPropertyName("id")]
public int Id { get; init; }
- [JsonProperty("type")]
- public string Type { get; init; }
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
- [JsonProperty("name")]
- public string Name { get; init; }
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
- [JsonProperty("url")]
- public string Url { get; init; }
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
- [JsonProperty("count")]
- public int Count { get; init; }
+ [JsonPropertyName("count")]
+ public int Count { get; set; }
}
diff --git a/Core/Api/Responses/GalleryTitleResponse.cs b/Core/Api/Responses/GalleryTitleResponse.cs
index db66645..53d95ca 100644
--- a/Core/Api/Responses/GalleryTitleResponse.cs
+++ b/Core/Api/Responses/GalleryTitleResponse.cs
@@ -1,15 +1,15 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Api.Responses;
public record GalleryTitleResponse
{
- [JsonProperty("japanese")]
- public string Japanese { get; init; }
+ [JsonPropertyName("japanese")]
+ public string Japanese { get; set; }
- [JsonProperty("english")]
- public string English { get; init; }
+ [JsonPropertyName("english")]
+ public string English { get; set; }
- [JsonProperty("pretty")]
- public string Pretty { get; init; }
+ [JsonPropertyName("pretty")]
+ public string Pretty { get; set; }
}
diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs
index 19b4281..2bdaa5c 100644
--- a/Core/Downloader/Downloader.cs
+++ b/Core/Downloader/Downloader.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using asuka.Configuration;
@@ -8,7 +9,6 @@
using asuka.Core.Models;
using asuka.Core.Utilities;
using asuka.Output;
-using Newtonsoft.Json;
namespace asuka.Core.Downloader;
@@ -52,8 +52,9 @@ private async Task WriteMetadata(string output, GalleryResult result, int chapte
if (_configurationManager.Values.UseTachiyomiLayout && chapter != -1)
{
var metaPath = Path.Combine(output, "details.json");
- var metadata = JsonConvert
- .SerializeObject(result.ToTachiyomiMetadata(), Formatting.Indented);
+ var serializerOptions = new JsonSerializerOptions { WriteIndented = true };
+ var metadata = JsonSerializer
+ .Serialize(result.ToTachiyomiMetadata(), serializerOptions);
await File.WriteAllTextAsync(metaPath, metadata).ConfigureAwait(false);
return;
diff --git a/Core/Mappings/ContractToGalleryResultModelMapping.cs b/Core/Mappings/ContractToGalleryResultModelMapping.cs
index 6b6b322..1673788 100644
--- a/Core/Mappings/ContractToGalleryResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryResultModelMapping.cs
@@ -5,12 +5,18 @@ namespace asuka.Core.Mappings;
public static class ContractToGalleryResultModelMapping
{
+ private static int ParseNumber(string number)
+ {
+ var parse = int.TryParse(number, out var result);
+ return parse ? result : 0;
+ }
+
public static GalleryResult ToGalleryResult(this GalleryResponse response)
{
return new GalleryResult
{
Id = response.Id,
- MediaId = response.MediaId,
+ MediaId = ParseNumber(response.MediaId),
Title = new GalleryTitleResult
{
Japanese = response.Title.Japanese,
diff --git a/Core/Models/TachiyomiDetails.cs b/Core/Models/TachiyomiDetails.cs
index 3d7d989..a0a3be6 100644
--- a/Core/Models/TachiyomiDetails.cs
+++ b/Core/Models/TachiyomiDetails.cs
@@ -1,25 +1,25 @@
using System.Collections.Generic;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace asuka.Core.Models;
public record TachiyomiDetails
{
- [JsonProperty("title")]
+ [JsonPropertyName("title")]
public string Title { get; init; }
- [JsonProperty("author")]
+ [JsonPropertyName("author")]
public string Author { get; init; }
- [JsonProperty("artist")]
+ [JsonPropertyName("artist")]
public string Artist { get; init; }
- [JsonProperty("description")]
+ [JsonPropertyName("description")]
public string Description { get; init; }
- [JsonProperty("genre")]
+ [JsonPropertyName("genre")]
public IReadOnlyList Genres { get; init; }
- [JsonProperty("status")]
+ [JsonPropertyName("status")]
public string Status { get; init; } = "2";
}
diff --git a/Installers/Refit/ConfigureRefitService.cs b/Installers/Refit/ConfigureRefitService.cs
index 92d6087..0ec26d0 100644
--- a/Installers/Refit/ConfigureRefitService.cs
+++ b/Installers/Refit/ConfigureRefitService.cs
@@ -1,6 +1,7 @@
using System;
using System.Drawing;
using System.Net.Http;
+using System.Text.Json;
using asuka.Configuration;
using asuka.Core.Api;
using Microsoft.Extensions.Configuration;
@@ -25,9 +26,15 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu
Colorful.Console.WriteLine("WARNING: Cookies are unset! Your request might fail.", Color.Red);
}
+ var contentSerializerSettings = new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
var configureRefit = new RefitSettings
{
- ContentSerializer = new NewtonsoftJsonContentSerializer(),
+ ContentSerializer = new SystemTextJsonContentSerializer(contentSerializerSettings),
HttpMessageHandlerFactory = () =>
{
var handler = new HttpClientHandler();
diff --git a/asuka.csproj b/asuka.csproj
index 07cec4c..7cf2d16 100644
--- a/asuka.csproj
+++ b/asuka.csproj
@@ -28,11 +28,9 @@
-
-
From 603eef5082c306eef17d4ead84cf816bf651e534 Mon Sep 17 00:00:00 2001
From: fumiichan <35658068+fumiichan@users.noreply.github.com>
Date: Sat, 28 Jan 2023 23:25:56 +0900
Subject: [PATCH 11/21] Implement series creation
---
AsukaApplication.cs | 2 +-
Commandline/CommandLineParserFactory.cs | 1 +
Commandline/CommandLineParserTokens.cs | 3 +-
Commandline/Options/FileCommandOptions.cs | 1 -
Commandline/Options/GetOptions.cs | 1 -
Commandline/Options/ICommonOptions.cs | 5 -
Commandline/Options/RandomOptions.cs | 1 -
Commandline/Options/RecommendOptions.cs | 1 -
Commandline/Options/SearchOptions.cs | 1 -
.../Options/SeriesCreatorCommandOptions.cs | 14 ++
Commandline/Parsers/ConfigureCommand.cs | 13 +-
Commandline/Parsers/FileCommandService.cs | 4 +-
Commandline/Parsers/GetCommandService.cs | 4 +-
Commandline/Parsers/RandomCommandService.cs | 4 +-
.../Parsers/RecommendCommandService.cs | 4 +-
Commandline/Parsers/SearchCommandService.cs | 4 +-
.../Parsers/SeriesCreatorCommandService.cs | 133 ++++++++++++++
Configuration/ConfigurationManager.cs | 17 +-
Configuration/IConfigurationManager.cs | 11 +-
Core/Api/Responses/GalleryResponse.cs | 4 +-
Core/Downloader/Downloader.cs | 43 +++--
Core/Downloader/IDownloader.cs | 5 +-
.../ContractToGalleryResultModelMapping.cs | 8 +-
Core/Models/GallerySeriesChaptersModel.cs | 12 ++
Core/Models/GallerySeriesModel.cs | 19 ++
Core/Utilities/PathUtils.cs | 162 +++++++++---------
Installers/InstallServices.cs | 3 +-
Validators/SeriesCreatorValidator.cs | 18 ++
28 files changed, 348 insertions(+), 150 deletions(-)
create mode 100644 Commandline/Options/SeriesCreatorCommandOptions.cs
create mode 100644 Commandline/Parsers/SeriesCreatorCommandService.cs
create mode 100644 Core/Models/GallerySeriesChaptersModel.cs
create mode 100644 Core/Models/GallerySeriesModel.cs
create mode 100644 Validators/SeriesCreatorValidator.cs
diff --git a/AsukaApplication.cs b/AsukaApplication.cs
index f01902f..57b647e 100644
--- a/AsukaApplication.cs
+++ b/AsukaApplication.cs
@@ -21,7 +21,7 @@ public AsukaApplication(IConsoleWriter console, ICommandLineParserFactory comman
public async Task RunAsync(IEnumerable args)
{
var parser = Parser.Default
- .ParseArguments(args);
+ .ParseArguments(args);
await parser.MapResult(
async (GetOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Get); },
async (RecommendOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Recommend); },
diff --git a/Commandline/CommandLineParserFactory.cs b/Commandline/CommandLineParserFactory.cs
index d45f9b8..8e327b4 100644
--- a/Commandline/CommandLineParserFactory.cs
+++ b/Commandline/CommandLineParserFactory.cs
@@ -24,6 +24,7 @@ public ICommandLineParser GetInstance(CommandLineParserTokens token)
CommandLineParserTokens.Random => GetService(typeof(RandomCommandService)),
CommandLineParserTokens.Recommend => GetService(typeof(RecommendCommandService)),
CommandLineParserTokens.Search => GetService(typeof(SearchCommandService)),
+ CommandLineParserTokens.Series => GetService(typeof(SeriesCreatorCommandService)),
_ => throw new ArgumentOutOfRangeException(nameof(token), token, null)
};
}
diff --git a/Commandline/CommandLineParserTokens.cs b/Commandline/CommandLineParserTokens.cs
index ab69557..406bf79 100644
--- a/Commandline/CommandLineParserTokens.cs
+++ b/Commandline/CommandLineParserTokens.cs
@@ -7,5 +7,6 @@ public enum CommandLineParserTokens
Get,
Random,
Recommend,
- Search
+ Search,
+ Series
}
diff --git a/Commandline/Options/FileCommandOptions.cs b/Commandline/Options/FileCommandOptions.cs
index c76803a..47551ca 100644
--- a/Commandline/Options/FileCommandOptions.cs
+++ b/Commandline/Options/FileCommandOptions.cs
@@ -12,5 +12,4 @@ public record FileCommandOptions : ICommonOptions
public bool Pack { get; init; }
public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
}
diff --git a/Commandline/Options/GetOptions.cs b/Commandline/Options/GetOptions.cs
index 515a4ef..b337363 100644
--- a/Commandline/Options/GetOptions.cs
+++ b/Commandline/Options/GetOptions.cs
@@ -15,5 +15,4 @@ public record GetOptions : IRequiresInputOption, ICommonOptions
public bool Pack { get; init; }
public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
}
diff --git a/Commandline/Options/ICommonOptions.cs b/Commandline/Options/ICommonOptions.cs
index e92c8ff..75d423c 100644
--- a/Commandline/Options/ICommonOptions.cs
+++ b/Commandline/Options/ICommonOptions.cs
@@ -14,9 +14,4 @@ public interface ICommonOptions
Required = false,
HelpText = "Destination path for gallery download")]
string Output { get; init; }
-
- [Option("useTachiyomiLayout",
- Required = false,
- HelpText = "Uses Tachiyomi Folder Structure for Download")]
- bool UseTachiyomiLayout { get; init; }
}
diff --git a/Commandline/Options/RandomOptions.cs b/Commandline/Options/RandomOptions.cs
index af90109..19ea0ca 100644
--- a/Commandline/Options/RandomOptions.cs
+++ b/Commandline/Options/RandomOptions.cs
@@ -7,5 +7,4 @@ public record RandomOptions : ICommonOptions
{
public bool Pack { get; init; }
public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
}
diff --git a/Commandline/Options/RecommendOptions.cs b/Commandline/Options/RecommendOptions.cs
index 8c37f50..6983606 100644
--- a/Commandline/Options/RecommendOptions.cs
+++ b/Commandline/Options/RecommendOptions.cs
@@ -8,5 +8,4 @@ public record RecommendOptions : ICommonOptions, IRequiresInputOption
public int Input { get; init; }
public bool Pack { get; init; }
public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
}
diff --git a/Commandline/Options/SearchOptions.cs b/Commandline/Options/SearchOptions.cs
index 76581ab..09e4483 100644
--- a/Commandline/Options/SearchOptions.cs
+++ b/Commandline/Options/SearchOptions.cs
@@ -40,5 +40,4 @@ public record SearchOptions : ICommonOptions
public bool Pack { get; init; }
public string Output { get; init; }
- public bool UseTachiyomiLayout { get; init; }
}
diff --git a/Commandline/Options/SeriesCreatorCommandOptions.cs b/Commandline/Options/SeriesCreatorCommandOptions.cs
new file mode 100644
index 0000000..0ea257e
--- /dev/null
+++ b/Commandline/Options/SeriesCreatorCommandOptions.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using CommandLine;
+
+namespace asuka.Commandline.Options;
+
+[Verb("series", HelpText = "Construct series")]
+public record SeriesCreatorCommandOptions: ICommonOptions
+{
+ [Option('a', "array", HelpText = "Create a seres from series of ids", Required = true)]
+ public IEnumerable FromList { get; set; }
+
+ public bool Pack { get; init; }
+ public string Output { get; init; }
+}
diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs
index dc60ec0..d4593e6 100644
--- a/Commandline/Parsers/ConfigureCommand.cs
+++ b/Commandline/Parsers/ConfigureCommand.cs
@@ -46,29 +46,32 @@ public async Task RunAsync(object options)
if (opts.ResetConfig)
{
- await _configurationManager.ResetAsync();
+ _configurationManager.Reset();
+ await _configurationManager.Flush();
_consoleWriter.SuccessLine("Configuration has been reset.");
return;
}
if (!string.IsNullOrEmpty(opts.SetDefaultCookies))
{
- await _configurationManager.SetCookiesAsync(opts.SetDefaultCookies);
+ await _configurationManager.SetCookies(opts.SetDefaultCookies);
}
if (!string.IsNullOrEmpty(opts.SetUserAgent))
{
- await _configurationManager.SetUserAgentAsync(opts.SetUserAgent);
+ _configurationManager.SetUserAgent(opts.SetUserAgent);
}
if (opts.UseTachiyomiLayoutToggle == bool.FalseString || opts.UseTachiyomiLayoutToggle == bool.TrueString)
{
- await _configurationManager.ToggleTachiyomiLayoutAsync(bool.Parse(opts.UseTachiyomiLayoutToggle));
+ _configurationManager.ToggleTachiyomiLayout(bool.Parse(opts.UseTachiyomiLayoutToggle));
}
if (!string.IsNullOrEmpty(opts.Theme))
{
- await _configurationManager.ChangeColourThemeAsync(opts.Theme);
+ _configurationManager.ChangeColourTheme(opts.Theme);
}
+
+ await _configurationManager.Flush();
}
}
diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs
index 85801ea..fe28429 100644
--- a/Commandline/Parsers/FileCommandService.cs
+++ b/Commandline/Parsers/FileCommandService.cs
@@ -68,7 +68,8 @@ public async Task RunAsync(object options)
var code = NumericRegex().Match(url).Value;
var response = await _api.FetchSingleAsync(code);
- await _download.Initialize(response, opts.Output, 1);
+ _download.CreateSeries(response.Title, opts.Output);
+ _download.CreateChapter(response, 1);
// Create progress bar
var internalProgress = _progressService.NestToMaster(response.TotalPages, $"downloading: {response.Id}");
@@ -79,6 +80,7 @@ public async Task RunAsync(object options)
// Start downloading
await _download.Start();
+ await _download.Finalize();
// If --pack option is specified, compresss the file into cbz
if (opts.Pack)
diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs
index 49ced9c..9cbb685 100644
--- a/Commandline/Parsers/GetCommandService.cs
+++ b/Commandline/Parsers/GetCommandService.cs
@@ -53,7 +53,8 @@ public async Task RunAsync(object options)
return;
}
- await _download.Initialize(response, opts.Output, 1);
+ _download.CreateSeries(response.Title, opts.Output);
+ _download.CreateChapter(response, 1);
_progress.CreateMasterProgress(response.TotalPages, $"downloading: {response.Id}");
@@ -64,6 +65,7 @@ public async Task RunAsync(object options)
};
await _download.Start();
+ await _download.Finalize();
if (opts.Pack)
{
diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs
index 11bfc3c..8ee0dbc 100644
--- a/Commandline/Parsers/RandomCommandService.cs
+++ b/Commandline/Parsers/RandomCommandService.cs
@@ -52,7 +52,8 @@ public async Task RunAsync(object options)
continue;
}
- await _download.Initialize(response, opts.Output, 1);
+ _download.CreateSeries(response.Title, opts.Output);
+ _download.CreateChapter(response, 1);
_progress.CreateMasterProgress(response.TotalPages, $"downloading random id: {response.Id}");
var progress = _progress.GetMasterProgress();
@@ -62,6 +63,7 @@ public async Task RunAsync(object options)
};
await _download.Start();
+ await _download.Finalize();
if (opts.Pack)
{
diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs
index 0d1b3df..19eca57 100644
--- a/Commandline/Parsers/RecommendCommandService.cs
+++ b/Commandline/Parsers/RecommendCommandService.cs
@@ -54,7 +54,8 @@ public async Task RunAsync(object options)
foreach (var response in selection)
{
- await _download.Initialize(response, opts.Output, 1);
+ _download.CreateSeries(response.Title, opts.Output);
+ _download.CreateChapter(response, 1);
var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}");
_download.OnImageDownload = () =>
@@ -63,6 +64,7 @@ public async Task RunAsync(object options)
};
await _download.Start();
+ await _download.Finalize();
if (opts.Pack)
{
diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs
index c071af8..9296843 100644
--- a/Commandline/Parsers/SearchCommandService.cs
+++ b/Commandline/Parsers/SearchCommandService.cs
@@ -77,7 +77,8 @@ public async Task RunAsync(object options)
foreach (var response in selection)
{
- await _download.Initialize(response, opts.Output, 1);
+ _download.CreateSeries(response.Title, opts.Output);
+ _download.CreateChapter(response, 1);
var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}");
_download.OnImageDownload = () =>
@@ -86,6 +87,7 @@ public async Task RunAsync(object options)
};
await _download.Start();
+ await _download.Finalize();
if (opts.Pack)
{
diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs
new file mode 100644
index 0000000..8914fc1
--- /dev/null
+++ b/Commandline/Parsers/SeriesCreatorCommandService.cs
@@ -0,0 +1,133 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using asuka.Commandline.Options;
+using asuka.Configuration;
+using asuka.Core.Compression;
+using asuka.Core.Downloader;
+using asuka.Core.Models;
+using asuka.Core.Requests;
+using asuka.Output.ProgressService;
+using asuka.Output.Writer;
+using FluentValidation;
+
+namespace asuka.Commandline.Parsers;
+
+public class SeriesCreatorCommandService : ICommandLineParser
+{
+ private readonly IGalleryRequestService _api;
+ private readonly IConsoleWriter _console;
+ private readonly IDownloader _downloader;
+ private readonly IProgressService _progress;
+ private readonly IPackArchiveToCbz _pack;
+ private readonly IConfigurationManager _config;
+ private readonly IValidator _validator;
+
+ public SeriesCreatorCommandService(
+ IGalleryRequestService api,
+ IConsoleWriter console,
+ IDownloader downloader,
+ IProgressService progress,
+ IPackArchiveToCbz pack,
+ IConfigurationManager config,
+ IValidator validator)
+ {
+ _api = api;
+ _console = console;
+ _downloader = downloader;
+ _progress = progress;
+ _pack = pack;
+ _config = config;
+ _validator = validator;
+ }
+
+ private async Task DownloadTask(IList results)
+ {
+ _progress.CreateMasterProgress(results.Count, "downloading series");
+ var masterProgress = _progress.GetMasterProgress();
+
+ for (var i = 0; i < results.Count; i++)
+ {
+ var chapter = results[i];
+ try
+ {
+ _downloader.CreateChapter(chapter, i + 1);
+
+ var innerProgress = _progress.NestToMaster(chapter.TotalPages, $"downloading chapter {i + 1}");
+ _downloader.OnImageDownload = () =>
+ {
+ innerProgress.Tick();
+ };
+
+ await _downloader.Start();
+ }
+ finally
+ {
+ masterProgress.Tick();
+ }
+ }
+
+ return _downloader.DownloadRoot;
+ }
+
+ private async Task> GetChapterInformation(IEnumerable ids)
+ {
+ // Queue list of chapters.
+ var chapters = new List();
+ foreach (var chapter in ids)
+ {
+ try
+ {
+ var galleryResponse = await _api.FetchSingleAsync(chapter);
+ chapters.Add(galleryResponse);
+ }
+ catch
+ {
+ _console.WarningLine($"Skipping: {chapter} because of an error.");
+ }
+ }
+
+ return chapters;
+ }
+
+ private async Task HandleArrayTask(IList codes, string output, bool pack)
+ {
+ // Queue list of chapters.
+ var chapters = await GetChapterInformation(codes);
+
+ // If there's no chapters (due to likely most of them failed to fetch metadata)
+ // Quit immediately.
+ if (chapters.Count <= 0)
+ {
+ _console.SuccessLine("Nothing to do. Quitting...");
+ return;
+ }
+
+ _downloader.CreateSeries(chapters[0].Title, output);
+ var destinationPath = await DownloadTask(chapters);
+ await _downloader.Finalize(chapters[0]);
+
+ if (pack)
+ {
+ await _pack.RunAsync(destinationPath, output, _progress.GetMasterProgress());
+ }
+ }
+
+ public async Task RunAsync(object options)
+ {
+ var opts = (SeriesCreatorCommandOptions)options;
+
+ var validationResult = await _validator.ValidateAsync(opts);
+ if (!validationResult.IsValid)
+ {
+ _console.ValidationErrors(validationResult.Errors);
+ return;
+ }
+
+ // Temporarily enable tachiyomi folder layout
+ _config.ToggleTachiyomiLayout(true);
+
+ var list = opts.FromList.ToList();
+ await HandleArrayTask(list, opts.Output, opts.Pack);
+ }
+}
diff --git a/Configuration/ConfigurationManager.cs b/Configuration/ConfigurationManager.cs
index 15b0f8c..880727b 100644
--- a/Configuration/ConfigurationManager.cs
+++ b/Configuration/ConfigurationManager.cs
@@ -26,7 +26,7 @@ public ConfigurationManager()
Configuration = JsonSerializer.Deserialize(data);
}
- public async Task SetCookiesAsync(string path)
+ public async Task SetCookies(string path)
{
if (!File.Exists(path))
{
@@ -39,36 +39,31 @@ public async Task SetCookiesAsync(string path)
if (cookieData == null) return;
Configuration.Cookies = cookieData;
- await FlushAsync();
}
- public async Task SetUserAgentAsync(string userAgent)
+ public void SetUserAgent(string userAgent)
{
Configuration.UserAgent = userAgent;
- await FlushAsync();
}
- public async Task ToggleTachiyomiLayoutAsync(bool value)
+ public void ToggleTachiyomiLayout(bool value)
{
Configuration.UseTachiyomiLayout = value;
- await FlushAsync();
}
- public async Task ChangeColourThemeAsync(string value)
+ public void ChangeColourTheme(string value)
{
Configuration.ConsoleTheme = value;
- await FlushAsync();
}
- public async Task ResetAsync()
+ public void Reset()
{
Configuration = new ConfigurationData();
- await FlushAsync();
}
public ConfigurationData Values => Configuration;
- private async Task FlushAsync()
+ public async Task Flush()
{
var configPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
".asuka/config.json");
diff --git a/Configuration/IConfigurationManager.cs b/Configuration/IConfigurationManager.cs
index b61ea72..d8a1ea9 100644
--- a/Configuration/IConfigurationManager.cs
+++ b/Configuration/IConfigurationManager.cs
@@ -4,10 +4,11 @@ namespace asuka.Configuration;
public interface IConfigurationManager
{
- Task SetCookiesAsync(string path);
- Task SetUserAgentAsync(string userAgent);
- Task ToggleTachiyomiLayoutAsync(bool value);
- Task ResetAsync();
- Task ChangeColourThemeAsync(string value);
+ Task SetCookies(string path);
+ void SetUserAgent(string userAgent);
+ void ToggleTachiyomiLayout(bool value);
+ void Reset();
+ void ChangeColourTheme(string value);
+ Task Flush();
ConfigurationData Values { get; }
}
diff --git a/Core/Api/Responses/GalleryResponse.cs b/Core/Api/Responses/GalleryResponse.cs
index f43ef53..d35f53d 100644
--- a/Core/Api/Responses/GalleryResponse.cs
+++ b/Core/Api/Responses/GalleryResponse.cs
@@ -6,10 +6,12 @@ namespace asuka.Core.Api.Responses;
public record GalleryResponse
{
[JsonPropertyName("id")]
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public int Id { get; set; }
[JsonPropertyName("media_id")]
- public string MediaId { get; set; }
+ [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
+ public int MediaId { get; set; }
[JsonPropertyName("title")]
public GalleryTitleResponse Title { get; set; }
diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs
index 2bdaa5c..25c50b1 100644
--- a/Core/Downloader/Downloader.cs
+++ b/Core/Downloader/Downloader.cs
@@ -14,8 +14,8 @@ namespace asuka.Core.Downloader;
internal record DownloadTaskDetails
{
- internal GalleryResult Result { get; init; }
- internal string ChapterPath { get; init; }
+ internal GalleryResult Result { get; set; }
+ internal string ChapterPath { get; set; }
internal string ChapterRoot { get; init; }
}
@@ -47,9 +47,9 @@ private static string GetTitle(GalleryTitleResult result)
return "Unknown title";
}
- private async Task WriteMetadata(string output, GalleryResult result, int chapter = 0)
+ private async Task WriteMetadata(string output, GalleryResult result)
{
- if (_configurationManager.Values.UseTachiyomiLayout && chapter != -1)
+ if (_configurationManager.Values.UseTachiyomiLayout)
{
var metaPath = Path.Combine(output, "details.json");
var serializerOptions = new JsonSerializerOptions { WriteIndented = true };
@@ -65,26 +65,28 @@ await File.WriteAllTextAsync(metadataPath, result.ToReadable())
.ConfigureAwait(false);
}
- public async Task Initialize(GalleryResult result, string outputPath, int chapter = -1)
+ public void CreateSeries(GalleryTitleResult title, string outputPath)
{
- // Override given chapter when using the layout is false.
- var chapterNumber = _configurationManager.Values.UseTachiyomiLayout ? chapter : -1;
- var destination = PathUtils.NormalizeJoin(outputPath, GetTitle(result.Title), chapterNumber);
- if (!Directory.Exists(destination.ChapterPath))
- {
- Directory.CreateDirectory(destination.ChapterPath!);
- }
-
- await WriteMetadata(destination.ChapterRoot, result, chapter);
-
+ var destination = PathUtils.NormalizeJoin(outputPath, GetTitle(title));
_details = new DownloadTaskDetails
{
- ChapterPath = destination.ChapterPath,
- ChapterRoot = destination.ChapterRoot,
- Result = result
+ ChapterRoot = destination
};
}
+ public void CreateChapter(GalleryResult result, int chapter = -1)
+ {
+ _details.ChapterPath = _configurationManager.Values.UseTachiyomiLayout && chapter > 0
+ ? Path.Combine(_details.ChapterRoot, $"ch{chapter}")
+ : _details.ChapterRoot;
+ _details.Result = result;
+
+ if (!Directory.Exists(_details.ChapterPath))
+ {
+ Directory.CreateDirectory(_details.ChapterPath!);
+ }
+ }
+
public Action OnImageDownload { get; set; } = () => { };
public string DownloadRoot => _details.ChapterRoot;
@@ -121,6 +123,11 @@ public async Task Start()
await Task.WhenAll(taskList).ConfigureAwait(false);
}
+
+ public async Task Finalize(GalleryResult result = null)
+ {
+ await WriteMetadata(_details.ChapterRoot, result ?? _details.Result);
+ }
private async Task DownloadImage(FetchImageParameter data)
{
diff --git a/Core/Downloader/IDownloader.cs b/Core/Downloader/IDownloader.cs
index 6d5d5fd..c319dd9 100644
--- a/Core/Downloader/IDownloader.cs
+++ b/Core/Downloader/IDownloader.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using asuka.Core.Models;
@@ -8,6 +9,8 @@ public interface IDownloader
{
public Action OnImageDownload { set; }
public string DownloadRoot { get; }
+ void CreateSeries(GalleryTitleResult title, string outputPath);
+ void CreateChapter(GalleryResult result, int chapter = -1);
Task Start();
- Task Initialize(GalleryResult result, string outputPath, int chapter = -1);
+ Task Finalize(GalleryResult result = null);
}
diff --git a/Core/Mappings/ContractToGalleryResultModelMapping.cs b/Core/Mappings/ContractToGalleryResultModelMapping.cs
index 1673788..6b6b322 100644
--- a/Core/Mappings/ContractToGalleryResultModelMapping.cs
+++ b/Core/Mappings/ContractToGalleryResultModelMapping.cs
@@ -5,18 +5,12 @@ namespace asuka.Core.Mappings;
public static class ContractToGalleryResultModelMapping
{
- private static int ParseNumber(string number)
- {
- var parse = int.TryParse(number, out var result);
- return parse ? result : 0;
- }
-
public static GalleryResult ToGalleryResult(this GalleryResponse response)
{
return new GalleryResult
{
Id = response.Id,
- MediaId = ParseNumber(response.MediaId),
+ MediaId = response.MediaId,
Title = new GalleryTitleResult
{
Japanese = response.Title.Japanese,
diff --git a/Core/Models/GallerySeriesChaptersModel.cs b/Core/Models/GallerySeriesChaptersModel.cs
new file mode 100644
index 0000000..e0fc5a1
--- /dev/null
+++ b/Core/Models/GallerySeriesChaptersModel.cs
@@ -0,0 +1,12 @@
+using System.Text.Json.Serialization;
+
+namespace asuka.Core.Models;
+
+public record GallerySeriesChaptersModel
+{
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ [JsonPropertyName("title")]
+ public string Title { get; set; }
+}
diff --git a/Core/Models/GallerySeriesModel.cs b/Core/Models/GallerySeriesModel.cs
new file mode 100644
index 0000000..36007bd
--- /dev/null
+++ b/Core/Models/GallerySeriesModel.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace asuka.Core.Models;
+
+public record GallerySeriesModel
+{
+ [JsonPropertyName("title")]
+ public string Title { get; set; }
+
+ [JsonPropertyName("artist")]
+ public string Artist { get; set; }
+
+ [JsonPropertyName("genres")]
+ public IReadOnlyList Genres { get; set; }
+
+ [JsonPropertyName("chapters")]
+ public IReadOnlyList Chapters { get; set; }
+}
diff --git a/Core/Utilities/PathUtils.cs b/Core/Utilities/PathUtils.cs
index fcc2ffd..c413838 100644
--- a/Core/Utilities/PathUtils.cs
+++ b/Core/Utilities/PathUtils.cs
@@ -4,103 +4,97 @@
namespace asuka.Core.Utilities;
-public struct NormalizeJoinDetails
-{
- public string ChapterRoot { get; init; }
- public string ChapterPath { get; init; }
-}
-
public static class PathUtils
{
+ #region Dictionaries of Character conversion
+ private static readonly Dictionary SymbolDictionary = new Dictionary
+ {
+ {
+ "#", "#"
+ },
+ {
+ "%", "%"
+ },
+ {
+ "&", "&"
+ },
+ {
+ "{", "{"
+ },
+ {
+ "}", "}"
+ },
+ {
+ "\\", "/"
+ },
+ {
+ "<", "<"
+ },
+ {
+ ">", ">"
+ },
+ {
+ "*", "*"
+ },
+ {
+ "?", "?"
+ },
+ {
+ "/", "/"
+ },
+ {
+ "$", "$"
+ },
+ {
+ "!", "!"
+ },
+ {
+ "'", "'"
+ },
+ {
+ "\"", """
+ },
+ {
+ ":", ":"
+ },
+ {
+ "@", "@"
+ },
+ {
+ "+", "+"
+ },
+ {
+ "`", "`"
+ },
+ {
+ "|", "|"
+ },
+ {
+ "=", "="
+ }
+ };
+ #endregion
///
/// Instead of removing known illegal file path characters, we can substitute them to their fixed-width
/// alternatives.
///
///
///
- ///
///
- public static NormalizeJoinDetails NormalizeJoin(string outputPath, string folderName, int chapter = -1)
+ public static string NormalizeJoin(string outputPath, string folderName)
{
- #region Dictionaries of Character conversion
- var characterMap = new Dictionary
- {
- {
- "#", "#"
- },
- {
- "%", "%"
- },
- {
- "&", "&"
- },
- {
- "{", "{"
- },
- {
- "}", "}"
- },
- {
- "\\", "/"
- },
- {
- "<", "<"
- },
- {
- ">", ">"
- },
- {
- "*", "*"
- },
- {
- "?", "?"
- },
- {
- "/", "/"
- },
- {
- "$", "$"
- },
- {
- "!", "!"
- },
- {
- "'", "'"
- },
- {
- "\"", """
- },
- {
- ":", ":"
- },
- {
- "@", "@"
- },
- {
- "+", "+"
- },
- {
- "`", "`"
- },
- {
- "|", "|"
- },
- {
- "=", "="
- }
- };
- #endregion
+ var normalizedFolderName = NormalizeName(folderName);
+ var chapterRoot = Path.Combine(outputPath, normalizedFolderName);
+ return chapterRoot;
+ }
+
+ public static string NormalizeName(string folderName)
+ {
var normalizedFolderName = $"{folderName}";
- normalizedFolderName = characterMap
+ normalizedFolderName = SymbolDictionary
.Aggregate(normalizedFolderName, (current, pair) => current.Replace(pair.Key, pair.Value));
- var chapterRoot = Path.Combine(outputPath, chapter == -1 ? normalizedFolderName : $"{normalizedFolderName}");
-
- return new NormalizeJoinDetails
- {
- ChapterPath = chapter == -1 ? chapterRoot : Path.Combine(chapterRoot, $"ch{chapter}"),
- ChapterRoot = chapterRoot
- };
+ return normalizedFolderName;
}
}
diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs
index f481c73..17b2f4e 100644
--- a/Installers/InstallServices.cs
+++ b/Installers/InstallServices.cs
@@ -23,7 +23,7 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu
services.AddSingleton();
services.AddScoped();
services.AddScoped();
- services.AddScoped