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(); + services.AddSingleton(); // Command parsers services.AddScoped(); @@ -32,6 +32,7 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddValidatorsFromAssemblyContaining(); } diff --git a/Validators/SeriesCreatorValidator.cs b/Validators/SeriesCreatorValidator.cs new file mode 100644 index 0000000..a9c878b --- /dev/null +++ b/Validators/SeriesCreatorValidator.cs @@ -0,0 +1,18 @@ +using System.Linq; +using asuka.Commandline.Options; +using FluentValidation; + +namespace asuka.Validators; + +public class SeriesCreatorValidator : AbstractValidator +{ + public SeriesCreatorValidator() + { + When(opts => opts.FromList.Any(), () => + { + RuleForEach(opts => opts.FromList) + .Matches(@"^\d{1,6}$") + .WithMessage("One or more elements on this list contains invalid Ids."); + }); + } +} From 7a28a37bacbff20a736999f7f105b24b9c32fbf7 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sat, 28 Jan 2023 23:46:08 +0900 Subject: [PATCH 12/21] Apply code recommendations --- Commandline/Parsers/FileCommandService.cs | 16 +++----- Commandline/Parsers/GetCommandService.cs | 4 +- Commandline/Parsers/RandomCommandService.cs | 4 +- .../Parsers/RecommendCommandService.cs | 4 +- Commandline/Parsers/SearchCommandService.cs | 4 +- .../Parsers/SeriesCreatorCommandService.cs | 4 +- Core/Compression/PackArchiveToCbz.cs | 5 ++- Core/Downloader/Downloader.cs | 38 +++++++++++++------ Core/Downloader/IDownloader.cs | 8 ++-- Output/ProgressService/ProgressService.cs | 5 ++- 10 files changed, 54 insertions(+), 38 deletions(-) diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs index fe28429..9f5c87c 100644 --- a/Commandline/Parsers/FileCommandService.cs +++ b/Commandline/Parsers/FileCommandService.cs @@ -13,7 +13,7 @@ namespace asuka.Commandline.Parsers; -public partial class FileCommandService : ICommandLineParser +public class FileCommandService : ICommandLineParser { private readonly IGalleryRequestService _api; private readonly IConsoleWriter _console; @@ -65,7 +65,7 @@ public async Task RunAsync(object options) foreach (var url in validUrls) { - var code = NumericRegex().Match(url).Value; + var code = new Regex("\\d+").Match(url).Value; var response = await _api.FetchSingleAsync(code); _download.CreateSeries(response.Title, opts.Output); @@ -73,14 +73,14 @@ public async Task RunAsync(object options) // Create progress bar var internalProgress = _progressService.NestToMaster(response.TotalPages, $"downloading: {response.Id}"); - _download.OnImageDownload = () => + _download.SetOnImageDownload = () => { internalProgress.Tick($"downloading: {response.Id}"); }; // Start downloading await _download.Start(); - await _download.Finalize(); + await _download.Final(); // If --pack option is specified, compresss the file into cbz if (opts.Pack) @@ -93,7 +93,7 @@ public async Task RunAsync(object options) private static IReadOnlyList FilterValidUrls(IEnumerable urls) { - return urls.Where(url => WebUrlRegex().IsMatch(url)).ToList(); + return urls.Where(url => new Regex("^http(s)?:\\/\\/(nhentai\\.net)\\b([//g]*)\\b([\\d]{1,6})\\/?$").IsMatch(url)).ToList(); } private static bool IsFileExceedingToFileSizeLimit(string inputFile) @@ -101,10 +101,4 @@ 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 9cbb685..64a7c11 100644 --- a/Commandline/Parsers/GetCommandService.cs +++ b/Commandline/Parsers/GetCommandService.cs @@ -59,13 +59,13 @@ public async Task RunAsync(object options) _progress.CreateMasterProgress(response.TotalPages, $"downloading: {response.Id}"); var progress = _progress.GetMasterProgress(); - _download.OnImageDownload = () => + _download.SetOnImageDownload = () => { progress.Tick(); }; await _download.Start(); - await _download.Finalize(); + await _download.Final(); if (opts.Pack) { diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs index 8ee0dbc..6d12499 100644 --- a/Commandline/Parsers/RandomCommandService.cs +++ b/Commandline/Parsers/RandomCommandService.cs @@ -57,13 +57,13 @@ public async Task RunAsync(object options) _progress.CreateMasterProgress(response.TotalPages, $"downloading random id: {response.Id}"); var progress = _progress.GetMasterProgress(); - _download.OnImageDownload = () => + _download.SetOnImageDownload = () => { progress.Tick(); }; await _download.Start(); - await _download.Finalize(); + await _download.Final(); if (opts.Pack) { diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs index 19eca57..7b1141e 100644 --- a/Commandline/Parsers/RecommendCommandService.cs +++ b/Commandline/Parsers/RecommendCommandService.cs @@ -58,13 +58,13 @@ public async Task RunAsync(object options) _download.CreateChapter(response, 1); var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}"); - _download.OnImageDownload = () => + _download.SetOnImageDownload = () => { innerProgress.Tick(); }; await _download.Start(); - await _download.Finalize(); + await _download.Final(); if (opts.Pack) { diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs index 9296843..5928e9b 100644 --- a/Commandline/Parsers/SearchCommandService.cs +++ b/Commandline/Parsers/SearchCommandService.cs @@ -81,13 +81,13 @@ public async Task RunAsync(object options) _download.CreateChapter(response, 1); var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}"); - _download.OnImageDownload = () => + _download.SetOnImageDownload = () => { innerProgress.Tick(); }; await _download.Start(); - await _download.Finalize(); + await _download.Final(); if (opts.Pack) { diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index 8914fc1..fc597a3 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -54,7 +54,7 @@ private async Task DownloadTask(IList results) _downloader.CreateChapter(chapter, i + 1); var innerProgress = _progress.NestToMaster(chapter.TotalPages, $"downloading chapter {i + 1}"); - _downloader.OnImageDownload = () => + _downloader.SetOnImageDownload = () => { innerProgress.Tick(); }; @@ -105,7 +105,7 @@ private async Task HandleArrayTask(IList codes, string output, bool pack _downloader.CreateSeries(chapters[0].Title, output); var destinationPath = await DownloadTask(chapters); - await _downloader.Finalize(chapters[0]); + await _downloader.Final(chapters[0]); if (pack) { diff --git a/Core/Compression/PackArchiveToCbz.cs b/Core/Compression/PackArchiveToCbz.cs index 3658d48..a5ff71f 100644 --- a/Core/Compression/PackArchiveToCbz.cs +++ b/Core/Compression/PackArchiveToCbz.cs @@ -29,7 +29,10 @@ public async Task RunAsync(string targetFolder, string output, IProgressBar bar) var childBar = _progressService.HookToInstance(bar, files.Count, $"compressing..."); // Delete if file exists. - if (File.Exists(output)) File.Delete(output); + if (File.Exists(output)) + { + File.Delete(output); + } var destination = $"{targetFolder[..^1]}.cbz"; await using var archiveToOpen = new FileStream(destination, FileMode.Create); diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs index 25c50b1..2f0c84b 100644 --- a/Core/Downloader/Downloader.cs +++ b/Core/Downloader/Downloader.cs @@ -40,11 +40,15 @@ public Downloader(IGalleryImage api, IConfigurationManager 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"; + if (!string.IsNullOrEmpty(result.Japanese)) + { + return result.Japanese; + } + if (!string.IsNullOrEmpty(result.English)) + { + return result.English; + } + return !string.IsNullOrEmpty(result.Pretty) ? result.Pretty : "Unknown title"; } private async Task WriteMetadata(string output, GalleryResult result) @@ -74,7 +78,7 @@ public void CreateSeries(GalleryTitleResult title, string outputPath) }; } - public void CreateChapter(GalleryResult result, int chapter = -1) + public void CreateChapter(GalleryResult result, int chapter) { _details.ChapterPath = _configurationManager.Values.UseTachiyomiLayout && chapter > 0 ? Path.Combine(_details.ChapterRoot, $"ch{chapter}") @@ -87,7 +91,12 @@ public void CreateChapter(GalleryResult result, int chapter = -1) } } - public Action OnImageDownload { get; set; } = () => { }; + public void CreateChapter(GalleryResult result) + { + CreateChapter(result, 1); + } + + public Action SetOnImageDownload { get; set; } = () => { }; public string DownloadRoot => _details.ChapterRoot; public async Task Start() @@ -95,7 +104,7 @@ public async Task Start() // Break when necessary. if (_details is null) { - throw new NullReferenceException("Data required for download task is missing."); + throw new Exception("Data required for download task is missing."); } var throttler = new SemaphoreSlim(2); @@ -124,9 +133,14 @@ public async Task Start() await Task.WhenAll(taskList).ConfigureAwait(false); } - public async Task Finalize(GalleryResult result = null) + public async Task Final() + { + await WriteMetadata(_details.ChapterRoot, _details.Result); + } + + public async Task Final(GalleryResult result) { - await WriteMetadata(_details.ChapterRoot, result ?? _details.Result); + await WriteMetadata(_details.ChapterRoot, result); } private async Task DownloadImage(FetchImageParameter data) @@ -134,7 +148,7 @@ private async Task DownloadImage(FetchImageParameter data) var filePath = Path.Combine(data.DestinationPath, data.Page.Filename); if (File.Exists(filePath)) { - OnImageDownload(); + SetOnImageDownload(); return; } @@ -144,6 +158,6 @@ private async Task DownloadImage(FetchImageParameter data) await File.WriteAllBytesAsync(filePath, imageData) .ConfigureAwait(false); - OnImageDownload(); + SetOnImageDownload(); } } diff --git a/Core/Downloader/IDownloader.cs b/Core/Downloader/IDownloader.cs index c319dd9..0ed2634 100644 --- a/Core/Downloader/IDownloader.cs +++ b/Core/Downloader/IDownloader.cs @@ -7,10 +7,12 @@ namespace asuka.Core.Downloader; public interface IDownloader { - public Action OnImageDownload { set; } + public Action SetOnImageDownload { set; } public string DownloadRoot { get; } void CreateSeries(GalleryTitleResult title, string outputPath); - void CreateChapter(GalleryResult result, int chapter = -1); + void CreateChapter(GalleryResult result); + void CreateChapter(GalleryResult result, int chapter); Task Start(); - Task Finalize(GalleryResult result = null); + Task Final(); + Task Final(GalleryResult result); } diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs index 8eefbba..1390f9d 100644 --- a/Output/ProgressService/ProgressService.cs +++ b/Output/ProgressService/ProgressService.cs @@ -23,7 +23,10 @@ public bool HasMasterProgress() public IProgressBar NestToMaster(int totalTicks, string title) { - if (HasMasterProgress()) return _progressBar.Spawn(totalTicks, title, ProgressBarConfiguration.BarOption); + if (HasMasterProgress()) + { + return _progressBar.Spawn(totalTicks, title, ProgressBarConfiguration.BarOption); + } CreateMasterProgress(totalTicks, title); return GetMasterProgress(); From 269b6553713263ba459878deaa1e1a6e26094ba8 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 29 Jan 2023 02:44:12 +0900 Subject: [PATCH 13/21] Use appsettings.json to store cookies and user agent config So that the Refit configuration would only involve IConfiguration and won't touch the configuration services as these config is mainly involved in requesting. --- AsukaApplication.cs | 3 +- Commandline/CommandLineParserFactory.cs | 1 + Commandline/CommandLineParserTokens.cs | 3 +- Commandline/Options/CookieConfigureOptions.cs | 13 +++ Commandline/Parsers/ConfigureCommand.cs | 10 --- Commandline/Parsers/CookieConfigureService.cs | 49 ++++++++++++ Configuration/ApplicationSettingsModel.cs | 34 ++++++++ Configuration/CloudflareBypass.cs | 28 ------- Configuration/ConfigurationManager.cs | 20 ----- Configuration/IConfigurationManager.cs | 2 - Configuration/IRequestConfigurator.cs | 10 +++ Configuration/RequestConfigurator.cs | 79 +++++++++++++++++++ Installers/InstallServices.cs | 2 + Installers/Refit/ConfigureRefitService.cs | 58 +++++++++----- Validators/CookieConfiguratorValidator.cs | 18 +++++ appsettings.json | 19 +++++ asuka.csproj | 2 + 17 files changed, 270 insertions(+), 81 deletions(-) create mode 100644 Commandline/Options/CookieConfigureOptions.cs create mode 100644 Commandline/Parsers/CookieConfigureService.cs create mode 100644 Configuration/ApplicationSettingsModel.cs delete mode 100644 Configuration/CloudflareBypass.cs create mode 100644 Configuration/IRequestConfigurator.cs create mode 100644 Configuration/RequestConfigurator.cs create mode 100644 Validators/CookieConfiguratorValidator.cs diff --git a/AsukaApplication.cs b/AsukaApplication.cs index 57b647e..f43b954 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); }, @@ -30,6 +30,7 @@ public async Task RunAsync(IEnumerable args) async (FileCommandOptions opts) => { await RunCommand(opts, CommandLineParserTokens.File); }, async (ConfigureOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Configure); }, async (SeriesCreatorCommandOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Series); }, + async (CookieConfigureOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Cookie); }, errors => { foreach (var error in errors) diff --git a/Commandline/CommandLineParserFactory.cs b/Commandline/CommandLineParserFactory.cs index 8e327b4..4f38771 100644 --- a/Commandline/CommandLineParserFactory.cs +++ b/Commandline/CommandLineParserFactory.cs @@ -25,6 +25,7 @@ public ICommandLineParser GetInstance(CommandLineParserTokens token) CommandLineParserTokens.Recommend => GetService(typeof(RecommendCommandService)), CommandLineParserTokens.Search => GetService(typeof(SearchCommandService)), CommandLineParserTokens.Series => GetService(typeof(SeriesCreatorCommandService)), + CommandLineParserTokens.Cookie => GetService(typeof(CookieConfigureService)), _ => throw new ArgumentOutOfRangeException(nameof(token), token, null) }; } diff --git a/Commandline/CommandLineParserTokens.cs b/Commandline/CommandLineParserTokens.cs index 406bf79..33acc0b 100644 --- a/Commandline/CommandLineParserTokens.cs +++ b/Commandline/CommandLineParserTokens.cs @@ -8,5 +8,6 @@ public enum CommandLineParserTokens Random, Recommend, Search, - Series + Series, + Cookie } diff --git a/Commandline/Options/CookieConfigureOptions.cs b/Commandline/Options/CookieConfigureOptions.cs new file mode 100644 index 0000000..fe66643 --- /dev/null +++ b/Commandline/Options/CookieConfigureOptions.cs @@ -0,0 +1,13 @@ +using CommandLine; + +namespace asuka.Commandline.Options; + +[Verb("cookie", HelpText = "Configure cookies and User Agent")] +public record CookieConfigureOptions +{ + [Option('c', "cookie", HelpText = "Read cookies from a text file dump", Required = true)] + public string CookieFile { get; init; } + + [Option('u', "userAgent", HelpText = "Set user agent", Required = true)] + public string UserAgent { get; init; } +} diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs index d4593e6..ee204a9 100644 --- a/Commandline/Parsers/ConfigureCommand.cs +++ b/Commandline/Parsers/ConfigureCommand.cs @@ -51,16 +51,6 @@ public async Task RunAsync(object options) _consoleWriter.SuccessLine("Configuration has been reset."); return; } - - if (!string.IsNullOrEmpty(opts.SetDefaultCookies)) - { - await _configurationManager.SetCookies(opts.SetDefaultCookies); - } - - if (!string.IsNullOrEmpty(opts.SetUserAgent)) - { - _configurationManager.SetUserAgent(opts.SetUserAgent); - } if (opts.UseTachiyomiLayoutToggle == bool.FalseString || opts.UseTachiyomiLayoutToggle == bool.TrueString) { diff --git a/Commandline/Parsers/CookieConfigureService.cs b/Commandline/Parsers/CookieConfigureService.cs new file mode 100644 index 0000000..e065867 --- /dev/null +++ b/Commandline/Parsers/CookieConfigureService.cs @@ -0,0 +1,49 @@ +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using asuka.Commandline.Options; +using asuka.Configuration; +using asuka.Output.Writer; +using FluentValidation; + +namespace asuka.Commandline.Parsers; + +public class CookieConfigureService : ICommandLineParser +{ + private readonly IRequestConfigurator _requestConfigurator; + private readonly IConsoleWriter _console; + private readonly IValidator _validator; + + public CookieConfigureService( + IRequestConfigurator requestConfigurator, + IConsoleWriter console, + IValidator validator) + { + _requestConfigurator = requestConfigurator; + _console = console; + _validator = validator; + } + + public async Task RunAsync(object options) + { + var opts = (CookieConfigureOptions)options; + + var validationResult = await _validator.ValidateAsync(opts); + if (!validationResult.IsValid) + { + _console.ValidationErrors(validationResult.Errors); + return; + } + + // Read cookies + var file = await File.ReadAllTextAsync(opts.CookieFile); + var cookieData = JsonSerializer.Deserialize(file); + + var cloudflare = cookieData.FirstOrDefault(x => x.Name == "cf_clearance"); + var csrf = cookieData.FirstOrDefault(x => x.Name == "csrftoken"); + + await _requestConfigurator.ApplyCookies(cloudflare, csrf); + await _requestConfigurator.ApplyUserAgent(opts.UserAgent); + } +} diff --git a/Configuration/ApplicationSettingsModel.cs b/Configuration/ApplicationSettingsModel.cs new file mode 100644 index 0000000..5f7bacb --- /dev/null +++ b/Configuration/ApplicationSettingsModel.cs @@ -0,0 +1,34 @@ +namespace asuka.Configuration; + +public record CookieMetadata +{ + public string Name { get; set; } + public string Value { get; set; } + public string Domain { get; set; } + public bool HttpOnly { get; set; } + public bool Secure { get; set; } +} + +public record CookieStore +{ + public CookieMetadata CloudflareClearance { get; set; } + public CookieMetadata CsrfToken { get; set; } +} + +public record RequestOptions +{ + public CookieStore Cookies { get; set; } + public string UserAgent { get; set; } +} + +public record Addresses +{ + public string ApiBaseAddress { get; set; } + public string ImageBaseAddress { get; set; } +} + +public record ApplicationSettingsModel +{ + public Addresses BaseAddresses { get; set; } + public RequestOptions RequestOptions { get; set; } +} diff --git a/Configuration/CloudflareBypass.cs b/Configuration/CloudflareBypass.cs deleted file mode 100644 index 3d02b72..0000000 --- a/Configuration/CloudflareBypass.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using System.Net; - -namespace asuka.Configuration; - -public class CloudflareBypass : ConfigurationManager -{ - public Cookie GetCookieByName(string name) - { - var cookie = Configuration.Cookies?.FirstOrDefault(x => x.Name == name); - - if (cookie == null) - { - return null; - } - - return new Cookie(cookie.Name, cookie.Value) - { - Domain = cookie.Domain, - HttpOnly = cookie.HttpOnly, - Secure = cookie.Secure - }; - } - - public string UserAgent => !string.IsNullOrEmpty(Configuration.UserAgent) - ? Configuration.UserAgent - : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"; -} diff --git a/Configuration/ConfigurationManager.cs b/Configuration/ConfigurationManager.cs index 880727b..69f790d 100644 --- a/Configuration/ConfigurationManager.cs +++ b/Configuration/ConfigurationManager.cs @@ -26,26 +26,6 @@ public ConfigurationManager() Configuration = JsonSerializer.Deserialize(data); } - public async Task SetCookies(string path) - { - if (!File.Exists(path)) - { - throw new FileNotFoundException("Cookie file specified cannot be found."); - } - - var cookies = await File.ReadAllTextAsync(path, Encoding.UTF8); - var cookieData = JsonSerializer.Deserialize(cookies); - - if (cookieData == null) return; - - Configuration.Cookies = cookieData; - } - - public void SetUserAgent(string userAgent) - { - Configuration.UserAgent = userAgent; - } - public void ToggleTachiyomiLayout(bool value) { Configuration.UseTachiyomiLayout = value; diff --git a/Configuration/IConfigurationManager.cs b/Configuration/IConfigurationManager.cs index d8a1ea9..ff94477 100644 --- a/Configuration/IConfigurationManager.cs +++ b/Configuration/IConfigurationManager.cs @@ -4,8 +4,6 @@ namespace asuka.Configuration; public interface IConfigurationManager { - Task SetCookies(string path); - void SetUserAgent(string userAgent); void ToggleTachiyomiLayout(bool value); void Reset(); void ChangeColourTheme(string value); diff --git a/Configuration/IRequestConfigurator.cs b/Configuration/IRequestConfigurator.cs new file mode 100644 index 0000000..15645ec --- /dev/null +++ b/Configuration/IRequestConfigurator.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace asuka.Configuration; + +public interface IRequestConfigurator +{ + Task ApplyCookies(CookieDump clearance, CookieDump csrf); + Task ApplyUserAgent(string userAgent); + Task ChangeBaseAddresses(string apiEndpoint, string imageEndpoint); +} diff --git a/Configuration/RequestConfigurator.cs b/Configuration/RequestConfigurator.cs new file mode 100644 index 0000000..61617ba --- /dev/null +++ b/Configuration/RequestConfigurator.cs @@ -0,0 +1,79 @@ +using System.IO; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; + +namespace asuka.Configuration; + +public class RequestConfigurator : IRequestConfigurator +{ + private readonly string _appSettingsPath; + + public RequestConfigurator() + { + var assemblyDir = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location)!; + _appSettingsPath = Path.Combine(assemblyDir, "appsettings.json"); + } + + private async Task ReadSettings() + { + var file = await File.ReadAllTextAsync(_appSettingsPath); + return JsonSerializer.Deserialize(file); + } + + private async Task WriteSettings(ApplicationSettingsModel settings) + { + var jsonConfig = JsonSerializer.Serialize(settings, new JsonSerializerOptions + { + WriteIndented = true + }); + await File.WriteAllTextAsync(_appSettingsPath, jsonConfig); + } + + public async Task ApplyCookies(CookieDump clearance, CookieDump csrf) + { + var config = await ReadSettings(); + + config.RequestOptions.Cookies.CloudflareClearance = new CookieMetadata + { + Name = clearance.Name, + Domain = clearance.Domain, + HttpOnly = clearance.HttpOnly, + Secure = clearance.Secure, + Value = clearance.Value + }; + + config.RequestOptions.Cookies.CsrfToken = new CookieMetadata + { + Name = csrf.Name, + Domain = csrf.Domain, + HttpOnly = csrf.HttpOnly, + Secure = csrf.Secure, + Value = csrf.Value + }; + + await WriteSettings(config); + } + + public async Task ApplyUserAgent(string userAgent) + { + var config = await ReadSettings(); + + config.RequestOptions.UserAgent = userAgent; + + await WriteSettings(config); + } + + public async Task ChangeBaseAddresses(string apiEndpoint, string imageEndpoint) + { + var config = await ReadSettings(); + + config.BaseAddresses = new Addresses + { + ApiBaseAddress = apiEndpoint, + ImageBaseAddress = imageEndpoint + }; + + await WriteSettings(config); + } +} diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs index 17b2f4e..324a943 100644 --- a/Installers/InstallServices.cs +++ b/Installers/InstallServices.cs @@ -24,6 +24,7 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu services.AddScoped(); services.AddScoped(); services.AddSingleton(); + services.AddSingleton(); // Command parsers services.AddScoped(); @@ -33,6 +34,7 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddValidatorsFromAssemblyContaining(); } diff --git a/Installers/Refit/ConfigureRefitService.cs b/Installers/Refit/ConfigureRefitService.cs index 0ec26d0..85c43ac 100644 --- a/Installers/Refit/ConfigureRefitService.cs +++ b/Installers/Refit/ConfigureRefitService.cs @@ -1,8 +1,7 @@ using System; -using System.Drawing; +using System.Net; using System.Net.Http; using System.Text.Json; -using asuka.Configuration; using asuka.Core.Api; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -16,22 +15,21 @@ 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 contentSerializerSettings = new JsonSerializerOptions { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + var cloudflare = CreateCookieFromConfig(configuration, "CloudflareClearance"); + var csrf = CreateCookieFromConfig(configuration, "CsrfToken"); + + // Warn about cookies. + if (cloudflare == null || csrf == null) + { + Console.WriteLine("Cookies might be unset! Run \"asuka cookie\" command to set your cookies!"); + } + var configureRefit = new RefitSettings { ContentSerializer = new SystemTextJsonContentSerializer(contentSerializerSettings), @@ -39,14 +37,14 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu { var handler = new HttpClientHandler(); - if (cloudflareClearance != null) + if (cloudflare != null) { - handler.CookieContainer.Add(cloudflareClearance); + handler.CookieContainer.Add(cloudflare); } - if (csrfToken != null) + if (csrf != null) { - handler.CookieContainer.Add(csrfToken); + handler.CookieContainer.Add(csrf); } return handler; @@ -57,20 +55,22 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu ?? "https://nhentai.net"; var imageBaseAddress = configuration.GetSection("BaseAddresses")["ImageBaseAddress"] ?? "https://i.nhentai.net"; - + var userAgent = configuration.GetSection("RequestOptions") + .GetValue("UserAgent"); + services.AddRefitClient(configureRefit) .AddTransientHttpErrorPolicy(ConfigureErrorPolicyBuilder) .ConfigureHttpClient(httpClient => { httpClient.BaseAddress = new Uri(apiBaseAddress); - httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(cookies.UserAgent); + httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); }); services.AddRefitClient() .AddTransientHttpErrorPolicy(ConfigureErrorPolicyBuilder) .ConfigureHttpClient(httpClient => { httpClient.BaseAddress = new Uri(imageBaseAddress); - httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(cookies.UserAgent); + httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(userAgent); }); } @@ -84,4 +84,24 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu Colorful.Console.WriteLine($"Retrying in {span.Seconds}"); }); } + + private static Cookie CreateCookieFromConfig(IConfiguration configuration, string name) + { + var option = configuration + .GetSection("RequestOptions") + .GetSection("Cookies") + .GetSection(name); + + if (string.IsNullOrEmpty(option.GetValue("Name"))) + { + return null; + } + + return new Cookie(option.GetValue("Name"), option.GetValue("Value")) + { + Domain = option.GetValue("Domain"), + HttpOnly = option.GetValue("HttpOnly"), + Secure = option.GetValue("Secure") + }; + } } diff --git a/Validators/CookieConfiguratorValidator.cs b/Validators/CookieConfiguratorValidator.cs new file mode 100644 index 0000000..0ea13b0 --- /dev/null +++ b/Validators/CookieConfiguratorValidator.cs @@ -0,0 +1,18 @@ +using System.IO; +using asuka.Commandline.Options; +using FluentValidation; + +namespace asuka.Validators; + +public class CookieConfiguratorValidator : AbstractValidator +{ + public CookieConfiguratorValidator() + { + When(opts => !string.IsNullOrEmpty(opts.CookieFile), () => + { + RuleFor(opts => opts.CookieFile) + .Must(File.Exists) + .WithMessage("Your cookie file cannot be found!"); + }); + } +} diff --git a/appsettings.json b/appsettings.json index ad1a693..958aae5 100644 --- a/appsettings.json +++ b/appsettings.json @@ -2,5 +2,24 @@ "BaseAddresses": { "ApiBaseAddress": "https://nhentai.net", "ImageBaseAddress": "https://i.nhentai.net" + }, + "RequestOptions": { + "Cookies": { + "CloudflareClearance": { + "Name": "", + "Value": "", + "Domain": "", + "HttpOnly": false, + "Secure": false + }, + "CsrfToken": { + "Name": "", + "Value": "", + "Domain": "", + "HttpOnly": false, + "Secure": false + } + }, + "UserAgent": "" } } diff --git a/asuka.csproj b/asuka.csproj index 7cf2d16..2c30dc0 100644 --- a/asuka.csproj +++ b/asuka.csproj @@ -18,6 +18,7 @@ D:\Code\Projects\asuka\LICENSE 11 + disable @@ -25,6 +26,7 @@ + From 9f95194f12822d87d8b354863345111e4ac81074 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 29 Jan 2023 20:53:35 +0900 Subject: [PATCH 14/21] Implement new configuration style Configuration will no longer use JSON and instead will use linux-style conf file configuration like what git does. Configuration has modes, use --set or -s flag to tell the application if you want to set values. Use --read or -r flag to read a specific key value or --list or -l flag to list all values set. To reset you may use --reset flag. --- Commandline/Options/ConfigureOptions.cs | 22 +++--- Commandline/Parsers/ConfigureCommand.cs | 42 +++++----- .../Parsers/SeriesCreatorCommandService.cs | 2 +- Configuration/ConfigurationData.cs | 18 ----- Configuration/ConfigurationManager.cs | 76 +++++++++++++++---- Configuration/IConfigurationManager.cs | 9 ++- Core/Downloader/Downloader.cs | 6 +- Core/Downloader/IDownloader.cs | 1 - Installers/Refit/ConfigureRefitService.cs | 8 +- Output/Writer/ConsoleWriter.cs | 4 +- Validators/ConfigurationValidator.cs | 27 +++---- 11 files changed, 118 insertions(+), 97 deletions(-) delete mode 100644 Configuration/ConfigurationData.cs diff --git a/Commandline/Options/ConfigureOptions.cs b/Commandline/Options/ConfigureOptions.cs index ba3202a..4177925 100644 --- a/Commandline/Options/ConfigureOptions.cs +++ b/Commandline/Options/ConfigureOptions.cs @@ -5,21 +5,21 @@ namespace asuka.Commandline.Options; [Verb("config", HelpText = "Configure the client")] public record ConfigureOptions { - [Option('c', "setDefaultCookies", HelpText = "Sets/Updates the cookies")] - public string SetDefaultCookies { get; init; } + [Option('s', "set", HelpText = "Set value")] + public bool SetConfigMode { get; init; } - [Option('u', "setUserAgent", HelpText = "Set default User Agent")] - public string SetUserAgent { get; init; } + [Option('r', "read", HelpText = "Read configuration")] + public bool ReadConfigMode { get; init; } - [Option('l', "useTachiyomiLayout", HelpText = "Toggle Tachiyomi Layout")] - public string UseTachiyomiLayoutToggle { get; init; } + [Option('l', "list", HelpText = "List all configuration values")] + public bool ListConfigMode { get; init; } - [Option('t', "theme", HelpText = "Use a colour scheme according to your console background. Supported values are light and dark")] - public string Theme { get; init; } + [Option('k', "key", HelpText = "Configuration to set/read")] + public string Key { get; init; } + + [Option('v', "value", HelpText = "New value")] + public string Value { get; init; } [Option("reset", HelpText = "Reset configuration values")] public bool ResetConfig { get; init; } - - [Option("list", HelpText = "List values of configuration")] - public bool JustList { get; init; } } diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs index ee204a9..224f810 100644 --- a/Commandline/Parsers/ConfigureCommand.cs +++ b/Commandline/Parsers/ConfigureCommand.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using System.Threading.Tasks; using asuka.Commandline.Options; using asuka.Configuration; @@ -20,14 +19,6 @@ public ConfigureCommand(IValidator validator, IConfigurationMa _validator = validator; } - private void ListAllConfigurationValues() - { - _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}"); - } - public async Task RunAsync(object options) { var opts = (ConfigureOptions)options; @@ -37,31 +28,38 @@ public async Task RunAsync(object options) _consoleWriter.ValidationErrors(validation.Errors); return; } - - if (opts.JustList) + + if (opts.SetConfigMode) { - ListAllConfigurationValues(); + _configurationManager.SetValue(opts.Key, opts.Value); + await _configurationManager.Flush(); + return; } - if (opts.ResetConfig) + if (opts.ReadConfigMode) { - _configurationManager.Reset(); - await _configurationManager.Flush(); - _consoleWriter.SuccessLine("Configuration has been reset."); + var configValue = _configurationManager.GetValue(opts.Key); + _consoleWriter.WriteLine($"{opts.Key} = {configValue}"); + return; } - if (opts.UseTachiyomiLayoutToggle == bool.FalseString || opts.UseTachiyomiLayoutToggle == bool.TrueString) + if (opts.ListConfigMode) { - _configurationManager.ToggleTachiyomiLayout(bool.Parse(opts.UseTachiyomiLayoutToggle)); + var keyValuePairs = _configurationManager.GetAllValues(); + + foreach (var (key, value) in keyValuePairs) + { + _consoleWriter.WriteLine($"{key} = {value}"); + } + + return; } - if (!string.IsNullOrEmpty(opts.Theme)) + if (opts.ResetConfig) { - _configurationManager.ChangeColourTheme(opts.Theme); + await _configurationManager.Reset(); } - - await _configurationManager.Flush(); } } diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index fc597a3..0f521c8 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -125,7 +125,7 @@ public async Task RunAsync(object options) } // Temporarily enable tachiyomi folder layout - _config.ToggleTachiyomiLayout(true); + _config.SetValue("layout.tachiyomi", "yes"); var list = opts.FromList.ToList(); await HandleArrayTask(list, opts.Output, opts.Pack); diff --git a/Configuration/ConfigurationData.cs b/Configuration/ConfigurationData.cs deleted file mode 100644 index d8187f0..0000000 --- a/Configuration/ConfigurationData.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; - -namespace asuka.Configuration; - -public class ConfigurationData -{ - [JsonPropertyName("cookies")] - public CookieDump[] Cookies { get; set; } - - [JsonPropertyName("user_agent")] - public string UserAgent { get; set; } - - [JsonPropertyName("use_tachiyomi_layout")] - public bool UseTachiyomiLayout { get; set; } - - [JsonPropertyName("console_theme")] - public string ConsoleTheme { get; set; } -} diff --git a/Configuration/ConfigurationManager.cs b/Configuration/ConfigurationManager.cs index 69f790d..4483a8d 100644 --- a/Configuration/ConfigurationManager.cs +++ b/Configuration/ConfigurationManager.cs @@ -1,55 +1,99 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; -using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace asuka.Configuration; public class ConfigurationManager : IConfigurationManager { - protected ConfigurationData Configuration; + private Dictionary _config; public ConfigurationManager() { var configRoot = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".asuka"); - var configPath = Path.Join(configRoot, "config.json"); + var configPath = Path.Join(configRoot, "config.conf"); if (!File.Exists(configPath)) { Directory.CreateDirectory(configRoot); - Configuration = new ConfigurationData(); + _config = GetDefaults(); + return; } var data = File.ReadAllText(configPath, Encoding.UTF8); - Configuration = JsonSerializer.Deserialize(data); + _config = ReadConfiguration(data); + } + + private Dictionary GetDefaults() + { + return new Dictionary + { + { + "colors.theme", "dark" + }, + { + "layout.tachiyomi", "yes" + } + }; + } + + private Dictionary ReadConfiguration(string fileData) + { + var config = fileData.Split("\n"); + + var dict = new Dictionary(); + foreach (var value in config) + { + var regex = new Regex("^([a-z1-9.]+)=([a-z1-9])+$"); + if (!regex.IsMatch(value)) + { + continue; + } + + var configValue = value.Split('='); + dict.Add(configValue[0], configValue[1]); + } + + return dict; } - public void ToggleTachiyomiLayout(bool value) + public void SetValue(string key, string value) { - Configuration.UseTachiyomiLayout = value; + _config[key] = value; } - public void ChangeColourTheme(string value) + public string GetValue(string key) { - Configuration.ConsoleTheme = value; + return _config.TryGetValue(key, out var data) ? data : null; } - public void Reset() + public IReadOnlyList<(string, string)> GetAllValues() { - Configuration = new ConfigurationData(); + return _config.Select(x => (x.Key, x.Value)).ToList(); } - public ConfigurationData Values => Configuration; + public async Task Reset() + { + _config = GetDefaults(); + await Flush(); + } public async Task Flush() { var configPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - ".asuka/config.json"); + ".asuka/config.conf"); + + var stringBuilder = new StringBuilder(); + foreach (var (key, value) in _config) + { + stringBuilder.Append($"{key}={value}\n"); + } - var serializerOptions = new JsonSerializerOptions { WriteIndented = true }; - var data = JsonSerializer.Serialize(Configuration, serializerOptions); - await File.WriteAllTextAsync(configPath, data); + await File.WriteAllTextAsync(configPath, stringBuilder.ToString()); } } diff --git a/Configuration/IConfigurationManager.cs b/Configuration/IConfigurationManager.cs index ff94477..75eaed6 100644 --- a/Configuration/IConfigurationManager.cs +++ b/Configuration/IConfigurationManager.cs @@ -1,12 +1,13 @@ +using System.Collections.Generic; using System.Threading.Tasks; namespace asuka.Configuration; public interface IConfigurationManager { - void ToggleTachiyomiLayout(bool value); - void Reset(); - void ChangeColourTheme(string value); + void SetValue(string key, string value); + string GetValue(string key); + IReadOnlyList<(string, string)> GetAllValues(); + Task Reset(); Task Flush(); - ConfigurationData Values { get; } } diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs index 2f0c84b..49eddce 100644 --- a/Core/Downloader/Downloader.cs +++ b/Core/Downloader/Downloader.cs @@ -53,7 +53,7 @@ private static string GetTitle(GalleryTitleResult result) private async Task WriteMetadata(string output, GalleryResult result) { - if (_configurationManager.Values.UseTachiyomiLayout) + if (_configurationManager.GetValue("layout.tachiyomi") == "yes") { var metaPath = Path.Combine(output, "details.json"); var serializerOptions = new JsonSerializerOptions { WriteIndented = true }; @@ -80,7 +80,7 @@ public void CreateSeries(GalleryTitleResult title, string outputPath) public void CreateChapter(GalleryResult result, int chapter) { - _details.ChapterPath = _configurationManager.Values.UseTachiyomiLayout && chapter > 0 + _details.ChapterPath = _configurationManager.GetValue("layout.tachiyomi") == "yes" && chapter > 0 ? Path.Combine(_details.ChapterRoot, $"ch{chapter}") : _details.ChapterRoot; _details.Result = result; @@ -96,7 +96,7 @@ public void CreateChapter(GalleryResult result) CreateChapter(result, 1); } - public Action SetOnImageDownload { get; set; } = () => { }; + public Action SetOnImageDownload { private get; set; } = () => { }; public string DownloadRoot => _details.ChapterRoot; public async Task Start() diff --git a/Core/Downloader/IDownloader.cs b/Core/Downloader/IDownloader.cs index 0ed2634..d2eecdb 100644 --- a/Core/Downloader/IDownloader.cs +++ b/Core/Downloader/IDownloader.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using asuka.Core.Models; diff --git a/Installers/Refit/ConfigureRefitService.cs b/Installers/Refit/ConfigureRefitService.cs index 85c43ac..dc2d136 100644 --- a/Installers/Refit/ConfigureRefitService.cs +++ b/Installers/Refit/ConfigureRefitService.cs @@ -51,10 +51,10 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu } }; - var apiBaseAddress = configuration.GetSection("BaseAddresses")["ApiBaseAddress"] - ?? "https://nhentai.net"; - var imageBaseAddress = configuration.GetSection("BaseAddresses")["ImageBaseAddress"] - ?? "https://i.nhentai.net"; + var apiBaseAddress = configuration.GetSection("BaseAddresses") + .GetValue("ApiBaseAddress") ?? "https://nhentai.net"; + var imageBaseAddress = configuration.GetSection("BaseAddresses") + .GetValue("ImageBaseAddress") ?? "https://i.nhentai.net"; var userAgent = configuration.GetSection("RequestOptions") .GetValue("UserAgent"); diff --git a/Output/Writer/ConsoleWriter.cs b/Output/Writer/ConsoleWriter.cs index b90f979..f820e58 100644 --- a/Output/Writer/ConsoleWriter.cs +++ b/Output/Writer/ConsoleWriter.cs @@ -17,9 +17,9 @@ public ConsoleWriter(IConfigurationManager configurationManager) private Color GetColor(Color forWhiteTheme, Color forDarkTheme) { - return _configurationManager.Values.ConsoleTheme == "dark" ? forDarkTheme : forWhiteTheme; + return _configurationManager.GetValue("color.theme") == "dark" ? forDarkTheme : forWhiteTheme; } - + public void WriteLine(object message) { Console.WriteLine(message, GetColor(Color.Red, Color.Aqua)); diff --git a/Validators/ConfigurationValidator.cs b/Validators/ConfigurationValidator.cs index b93c243..1a4b333 100644 --- a/Validators/ConfigurationValidator.cs +++ b/Validators/ConfigurationValidator.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.Text.RegularExpressions; using asuka.Commandline.Options; using FluentValidation; @@ -8,25 +8,22 @@ public class ConfigurationValidator : AbstractValidator { public ConfigurationValidator() { - When(opts => !string.IsNullOrEmpty(opts.Theme), () => + When(opts => opts.ReadConfigMode, () => { - RuleFor(opts => opts.Theme) - .Matches(@"(light|dark)") - .WithMessage("Invalid Configuration value. Must be either light or dark"); + RuleFor(opts => opts.Key) + .Must(x => !string.IsNullOrEmpty(x)) + .WithMessage("Invalid key"); }); - When(opts => !string.IsNullOrEmpty(opts.UseTachiyomiLayoutToggle), () => + When(opts => opts.SetConfigMode, () => { - RuleFor(opts => opts.UseTachiyomiLayoutToggle) - .Matches(@"(True|False)") - .WithMessage("Invalid values. Use True or False as values. (Case sensitive)"); - }); + RuleFor(opts => opts.Key) + .Must(x => !string.IsNullOrEmpty(x)) + .WithMessage("Invalid key."); - When(opts => !string.IsNullOrEmpty(opts.SetDefaultCookies), () => - { - RuleFor(opts => opts.SetDefaultCookies) - .Must(File.Exists) - .WithMessage("The Cookie file you specified cannot be found."); + RuleFor(opts => opts.Value) + .Must(x => !string.IsNullOrEmpty(x)) + .WithMessage("Invalid value"); }); } } From aa9d521db6b6cdac8fd088feedfd76012cfb6f22 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 29 Jan 2023 22:27:36 +0900 Subject: [PATCH 15/21] [skip ci] Update README and remove USAGE.md --- README.md | 11 +--- docs/USAGE.md | 150 -------------------------------------------------- 2 files changed, 3 insertions(+), 158 deletions(-) delete mode 100644 docs/USAGE.md diff --git a/README.md b/README.md index 63e453f..826d510 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ![The Banner](docs/banner.png) [![Maintenance](https://badgen.net/badge/maintained%3F/yes/green)](https://github.com/aikoofujimotoo/asuka/graphs/commit-activity) -[![CircleCI](https://circleci.com/gh/aikoofujimotoo/asuka.svg?style=shield&circle-token=488813c48d642cdb1ff63cdb2483fdab55df8c19)](https://circleci.com/gh/aikoofujimotoo/asuka) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/fd7d1abe2865463c93e091fc1f205dbe)](https://www.codacy.com/gh/aikoofujimotoo/asuka/dashboard?utm_source=github.com&utm_medium=referral&utm_content=aikoofujimotoo/asuka&utm_campaign=Badge_Grade) [![Age Rating](https://badgen.net/badge/age%20rating/18+/red)](https://en.wikipedia.org/wiki/Age_of_majority) [![MIT license](https://badgen.net/badge/license/MIT/green)](LICENSE) @@ -10,24 +9,20 @@ Cross-platform nhentai downloader on Console. ## Requirements -- [.NET 6.0 Runtime](https://dotnet.microsoft.com/download/dotnet/6.0) +- [.NET 7.0 Runtime](https://dotnet.microsoft.com/download/dotnet/7.0) - For supported platforms check [here](https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md)*. - *Releases supports x64 Operating Systems only. You cannot use this on x86 or ARM. Check Compiling from Source section for compiling builds for these platforms.* ## Usage -By running `asuka --help` you mostly see everything you need to know how to use the client. - -**Before using this, run `asuka config --setDefaultCookies ` to set cookies. Without this your requests will fail. To obtain `cookies.txt` file, you have to install [EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg) extension and export the cookies and save it on a text file. Then, set your user agent by finding out your [User Agent](https://www.whatismybrowser.com/detect/what-is-my-user-agent/) and use `asuka config -u ""` to set it.** - -If you want in-depth explanation and examples, see [here](docs/USAGE.md). +[Getting Started](https://github.com/fumiichan/asuka/wiki/Getting-Started) ## Compiling from Source ### What do I need -- [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) +- [.NET 7.0 SDK](https://dotnet.microsoft.com/download/dotnet/7.0) ### Compiling diff --git a/docs/USAGE.md b/docs/USAGE.md deleted file mode 100644 index 55ff336..0000000 --- a/docs/USAGE.md +++ /dev/null @@ -1,150 +0,0 @@ -# Usage - -## Basic Usage - -Asuka has 5 main Commands: - -- `get` is used to retrieve and download manga either by code or a collection of links in a text file. -- `recommend` is used to get recommendations on specific manga. -- `search` is used to search a manga. -- `file` is used to download entire list of manga in a text file. -- `random` is used to get really a random manga. - -### `get` - -Usage: - -```text -asuka get <-i|--input > [-r|--readonly] [-p|--pack] [-o|--output ] -``` - -| Option | Required? | Description | -|-----------------------------|-----------|----------------------------------------------------| -| `-i \| --input ` | Yes | Specify a gallery code to download. | -| `-r \| --readonly` | No | View the information only. | -| `-p \| --pack` | No | Pack the downloaded doujinshi as CBZ archive | -| `-o \| --output ` | No | Path to save the downloaded doujinshi. | -| `--useTachiyomiLayout` | No | Use Tachiyomi Folder Structure | - -**Usage Examples** - -```text -asuka get -i 177013 -``` - -You need to add the links seperated by a new line. - -#### Get the information only - -```text -asuka get -i https://nhentai.net/g/177013 -r -``` - -#### Pack the downloaded archive - -```text -asuka get -i https://nhentai.net/g/177013 -p -``` - -**Remarks** - -- `--readonly` option supports on single link only. - -### `recommend` - -Usage: - -```text -asuka recommend <-i|--input > [-p|--pack] [-o|--output ] -``` - -| Option | Required? | Description | -|-------------------------|-------------|----------------------------------------------------| -| `-i \| --input ` | Yes | Specify a gallery code to download. | -| `-p \| --pack` | No | Pack the downloaded doujinshi as CBZ archive | -| `--useTachiyomiLayout` | No | Use Tachiyomi Folder Structure | -| `-o \| --output ` | No | Path to save the downloaded doujinshi. | - -**Usage Examples** - -Usage is the same as on the `get` command except that it doesn't accept file paths as arguments. - -### `search` - -Usage: - -```text -asuka search [options] -``` - -| Option | Required? | Description | -|------------------------------|-----------|------------------------------------------------------| -| `-q \| --query ` | No | Your search query | -| `-e \| --exclude ` | No | Exclude tags in your search | -| `--pageRange ` | No | Filter for specifying minimum page count in gallery. | -| `--dateRange ` | No | Filter for specifying exact timeframe | -| `-p \| --page ` | Yes | Page Number. | -| `--pack` | No | Pack the downloaded manga as CBZ archive | -| `--sort ` | No | Sort results. (default: `date`) | -| `-o \| --output ` | No | Path to save the downloaded doujinshi. | -| `--useTachiyomiLayout` | No | Use Tachiyomi Folder Structure | - -**Sort Options:** - -| Sort | Description | -|------------------------------|---------------------------------------------------------| -| `date` | Sorts result from newest to oldest. | -| `popular-week` | Sorts result from most popular to less popular (Weekly) | -| `popular-today` | Sorts result from most popular to less popular (Today) | -| `popular` | Sorts result from most popular to less popular | - -**Finer Queries:** - -| Filter | Type | Description | Examples | -|-------------------|---------------|---------------------------------------------|----------------------------------------------| -| `category` | `string` | Specify the manga category | `category:manga` | -| `artist` | `string` | Specify artist to filter | `artist:shindol` | -| `parody` | `string` | Specify parody of the manga | `parody:"to love ru"` | -| `tag` | `string` | Specify a tag | `tag:"wholesome"` | -| `character` | `string` | Specify a character in the manga | `character:astolfo` | -| `language` | `string` | Specify language of the manga | `language:english` | -| `group` | `string` | Specify the group (or circle) of the manga | `group:poyopoyosky` | - -**Remarks** - -- Use `--exclude` option to exclude tags instead of adding dashes (`-`) before queries. -- `--dateRange` and `--pageRange` supports operators such as `>` `>=` `<` and `<=`. -- `--dateRange` supports following date units (Ex: `--dateRange ">2d" "<=5d"`: - - `h` for hours - - `d` for days - - `w` for weeks - - `m` for months - - `y` for years - -### `file` - -Usage: - -```text -asuka file <-f|--file > [options] -``` - -| Option | Required? | Description | -|------------------------------|-----------|------------------------------------------------------| -| `-f \| --file ` | Yes | Path to the text file with list of URLs | -| `--pack` | No | Pack the downloaded manga as CBZ archive | -| `-o \| --output ` | No | Path to save the downloaded doujinshi. | -| `--useTachiyomiLayout` | No | Use Tachiyomi Folder Structure | - -### `random` - -Usage: - -```text -asuka random [-p|--pack] [-o|--output ] -``` - -| Option | Required? | Description | -|----------------------------|-----------|----------------------------------------------| -| `--pack` | No | Pack the downloaded manga as CBZ archive | -| `-o \| --output ` | No | Path to save the downloaded doujinshi. | From d95b597134dc51f2d1b74481683e47d85d01dfe3 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 5 Mar 2023 20:19:01 +0900 Subject: [PATCH 16/21] Support multiple IDs in get command now --- Commandline/Options/GetOptions.cs | 7 ++-- Commandline/Parsers/GetCommandService.cs | 43 ++++++++++++++---------- Validators/GetValidator.cs | 15 +++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 Validators/GetValidator.cs diff --git a/Commandline/Options/GetOptions.cs b/Commandline/Options/GetOptions.cs index b337363..debb45f 100644 --- a/Commandline/Options/GetOptions.cs +++ b/Commandline/Options/GetOptions.cs @@ -3,9 +3,12 @@ namespace asuka.Commandline.Options; [Verb("get", HelpText = "Download a Single Gallery from URL.")] -public record GetOptions : IRequiresInputOption, ICommonOptions +public record GetOptions : ICommonOptions { - public int Input { get; init; } + [Option('i', "input", + Required = true, + HelpText = "Input Numeric Code(s)")] + public int[] Input { get; init; } [Option('r', "readonly", Default = false, diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs index 64a7c11..b4c9b9a 100644 --- a/Commandline/Parsers/GetCommandService.cs +++ b/Commandline/Parsers/GetCommandService.cs @@ -13,7 +13,7 @@ namespace asuka.Commandline.Parsers; public class GetCommandService : ICommandLineParser { private readonly IGalleryRequestService _api; - private readonly IValidator _validator; + private readonly IValidator _validator; private readonly IDownloader _download; private readonly IConsoleWriter _console; private readonly IProgressService _progress; @@ -21,7 +21,7 @@ public class GetCommandService : ICommandLineParser public GetCommandService( IGalleryRequestService api, - IValidator validator, + IValidator validator, IDownloader download, IConsoleWriter console, IProgressService progress, @@ -35,25 +35,18 @@ public class GetCommandService : ICommandLineParser _pack = pack; } - public async Task RunAsync(object options) + private async Task DownloadTask(int input, bool pack, bool readOnly, string outputPath) { - var opts = (GetOptions)options; - var validationResult = await _validator.ValidateAsync(opts); - if (!validationResult.IsValid) - { - _console.ValidationErrors(validationResult.Errors); - return; - } - - var response = await _api.FetchSingleAsync(opts.Input.ToString()); + var response = await _api.FetchSingleAsync(input.ToString()); _console.WriteLine(response.ToReadable()); - if (opts.ReadOnly) + // Don't download. + if (readOnly) { return; } - - _download.CreateSeries(response.Title, opts.Output); + + _download.CreateSeries(response.Title, outputPath); _download.CreateChapter(response, 1); _progress.CreateMasterProgress(response.TotalPages, $"downloading: {response.Id}"); @@ -67,9 +60,25 @@ public async Task RunAsync(object options) await _download.Start(); await _download.Final(); - if (opts.Pack) + if (pack) + { + await _pack.RunAsync(_download.DownloadRoot, outputPath, progress); + } + } + + public async Task RunAsync(object options) + { + var opts = (GetOptions)options; + var validationResult = await _validator.ValidateAsync(opts); + if (!validationResult.IsValid) + { + _console.ValidationErrors(validationResult.Errors); + return; + } + + foreach (var code in opts.Input) { - await _pack.RunAsync(_download.DownloadRoot, opts.Output, progress); + await DownloadTask(code, opts.Pack, opts.ReadOnly, opts.Output); } } } diff --git a/Validators/GetValidator.cs b/Validators/GetValidator.cs new file mode 100644 index 0000000..80add34 --- /dev/null +++ b/Validators/GetValidator.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices.JavaScript; +using asuka.Commandline.Options; +using FluentValidation; + +namespace asuka.Validators; + +public class GetValidator : AbstractValidator +{ + public GetValidator() + { + RuleForEach(opts => opts.Input) + .Must(x => x > 0) + .WithMessage("IDs must not be lower than 0."); + } +} From 0d757331af366332fad57b013fb456ba897be669 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 5 Mar 2023 20:21:18 +0900 Subject: [PATCH 17/21] Update dependencies --- asuka.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/asuka.csproj b/asuka.csproj index 2c30dc0..ed4172d 100644 --- a/asuka.csproj +++ b/asuka.csproj @@ -24,12 +24,12 @@ - - - + + + - + From 947f71ecb18b4d09167f30fd521384b71c15f7f3 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 5 Mar 2023 20:26:59 +0900 Subject: [PATCH 18/21] Refactor Command-line options --- Commandline/Options/IRequiresInputOption.cs | 11 -------- Commandline/Options/RecommendOptions.cs | 5 +++- .../Parsers/RecommendCommandService.cs | 4 +-- ...{GetValidator.cs => GetOptionValidator.cs} | 2 +- ...dator.cs => RecommendedOptionValidator.cs} | 28 +++++++++---------- 5 files changed, 21 insertions(+), 29 deletions(-) delete mode 100644 Commandline/Options/IRequiresInputOption.cs rename Validators/{GetValidator.cs => GetOptionValidator.cs} (91%) rename Validators/{RequiresInputOptionValidator.cs => RecommendedOptionValidator.cs} (61%) diff --git a/Commandline/Options/IRequiresInputOption.cs b/Commandline/Options/IRequiresInputOption.cs deleted file mode 100644 index 42cce75..0000000 --- a/Commandline/Options/IRequiresInputOption.cs +++ /dev/null @@ -1,11 +0,0 @@ -using CommandLine; - -namespace asuka.Commandline.Options; - -public interface IRequiresInputOption -{ - [Option('i', "input", - Required = true, - HelpText = "Input Numeric Code")] - int Input { get; init; } -} diff --git a/Commandline/Options/RecommendOptions.cs b/Commandline/Options/RecommendOptions.cs index 6983606..eedec9e 100644 --- a/Commandline/Options/RecommendOptions.cs +++ b/Commandline/Options/RecommendOptions.cs @@ -3,8 +3,11 @@ namespace asuka.Commandline.Options; [Verb("recommend", HelpText = "Download recommendation from the gallery URL.")] -public record RecommendOptions : ICommonOptions, IRequiresInputOption +public record RecommendOptions : ICommonOptions { + [Option('i', "input", + Required = true, + HelpText = "Input Numeric Code")] public int Input { get; init; } public bool Pack { get; init; } public string Output { get; init; } diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs index 7b1141e..29c2f90 100644 --- a/Commandline/Parsers/RecommendCommandService.cs +++ b/Commandline/Parsers/RecommendCommandService.cs @@ -12,7 +12,7 @@ namespace asuka.Commandline.Parsers; public class RecommendCommandService : ICommandLineParser { - private readonly IValidator _validator; + private readonly IValidator _validator; private readonly IGalleryRequestService _api; private readonly IDownloader _download; private readonly IConsoleWriter _console; @@ -20,7 +20,7 @@ public class RecommendCommandService : ICommandLineParser private readonly IPackArchiveToCbz _pack; public RecommendCommandService( - IValidator validator, + IValidator validator, IGalleryRequestService api, IDownloader download, IConsoleWriter console, diff --git a/Validators/GetValidator.cs b/Validators/GetOptionValidator.cs similarity index 91% rename from Validators/GetValidator.cs rename to Validators/GetOptionValidator.cs index 80add34..25eaa5e 100644 --- a/Validators/GetValidator.cs +++ b/Validators/GetOptionValidator.cs @@ -9,7 +9,7 @@ public class GetValidator : AbstractValidator public GetValidator() { RuleForEach(opts => opts.Input) - .Must(x => x > 0) + .GreaterThan(0) .WithMessage("IDs must not be lower than 0."); } } diff --git a/Validators/RequiresInputOptionValidator.cs b/Validators/RecommendedOptionValidator.cs similarity index 61% rename from Validators/RequiresInputOptionValidator.cs rename to Validators/RecommendedOptionValidator.cs index dacf5a4..1959755 100644 --- a/Validators/RequiresInputOptionValidator.cs +++ b/Validators/RecommendedOptionValidator.cs @@ -1,14 +1,14 @@ -using asuka.Commandline.Options; -using FluentValidation; - -namespace asuka.Validators; - -public class RequiresInputOptionValidator : AbstractValidator -{ - public RequiresInputOptionValidator() - { - RuleFor(opts => opts.Input) - .GreaterThan(0) - .WithMessage("Enter a valid gallery code."); - } -} +using asuka.Commandline.Options; +using FluentValidation; + +namespace asuka.Validators; + +public class RecommendedOptionValidator : AbstractValidator +{ + public RecommendedOptionValidator() + { + RuleFor(opts => opts.Input) + .GreaterThan(0) + .WithMessage("Enter a valid gallery code."); + } +} From bbfbd46803aab2b61d1df9a1719bd8ee44b83f6d Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 5 Mar 2023 21:13:59 +0900 Subject: [PATCH 19/21] Implement progress bar configuration --- Commandline/Parsers/FileCommandService.cs | 1 + Commandline/Parsers/GetCommandService.cs | 1 + Commandline/Parsers/RandomCommandService.cs | 1 + .../Parsers/RecommendCommandService.cs | 1 + Commandline/Parsers/SearchCommandService.cs | 1 + .../Parsers/SeriesCreatorCommandService.cs | 1 + Configuration/ConfigurationManager.cs | 12 +++++ Core/Compression/IPackArchiveToCbz.cs | 5 +- Core/Compression/PackArchiveToCbz.cs | 4 +- Installers/InstallServices.cs | 1 + Output/ProgressService/IProgressService.cs | 9 ++-- .../ProgressProviderFactory.cs | 17 ++++++ Output/ProgressService/ProgressService.cs | 20 +++++-- .../Providers/ExternalProgressProvider.cs | 35 ++++++++++++ .../Providers/IProgressProvider.cs | 8 +++ .../Providers/TextProgressProvider.cs | 53 +++++++++++++++++++ .../Providers/VoidProgressProvider.cs | 19 +++++++ .../Providers/Wrappers/IProgressWrapper.cs | 8 +++ .../Providers/Wrappers/ProgressWrapper.cs | 45 ++++++++++++++++ 19 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 Output/ProgressService/ProgressProviderFactory.cs create mode 100644 Output/ProgressService/Providers/ExternalProgressProvider.cs create mode 100644 Output/ProgressService/Providers/IProgressProvider.cs create mode 100644 Output/ProgressService/Providers/TextProgressProvider.cs create mode 100644 Output/ProgressService/Providers/VoidProgressProvider.cs create mode 100644 Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs create mode 100644 Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs index 9f5c87c..304c73a 100644 --- a/Commandline/Parsers/FileCommandService.cs +++ b/Commandline/Parsers/FileCommandService.cs @@ -9,6 +9,7 @@ using asuka.Core.Downloader; using asuka.Core.Requests; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; namespace asuka.Commandline.Parsers; diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs index b4c9b9a..138c9f7 100644 --- a/Commandline/Parsers/GetCommandService.cs +++ b/Commandline/Parsers/GetCommandService.cs @@ -5,6 +5,7 @@ using asuka.Core.Requests; using asuka.Output; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs index 6d12499..fabefa3 100644 --- a/Commandline/Parsers/RandomCommandService.cs +++ b/Commandline/Parsers/RandomCommandService.cs @@ -6,6 +6,7 @@ using asuka.Core.Requests; using asuka.Output; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using Sharprompt; diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs index 29c2f90..c9ccc2c 100644 --- a/Commandline/Parsers/RecommendCommandService.cs +++ b/Commandline/Parsers/RecommendCommandService.cs @@ -5,6 +5,7 @@ using asuka.Core.Mappings; using asuka.Core.Requests; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs index 5928e9b..cf859f1 100644 --- a/Commandline/Parsers/SearchCommandService.cs +++ b/Commandline/Parsers/SearchCommandService.cs @@ -8,6 +8,7 @@ using asuka.Core.Mappings; using asuka.Core.Requests; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index 0f521c8..fc8040e 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -8,6 +8,7 @@ using asuka.Core.Models; using asuka.Core.Requests; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Configuration/ConfigurationManager.cs b/Configuration/ConfigurationManager.cs index 4483a8d..9f6bdbc 100644 --- a/Configuration/ConfigurationManager.cs +++ b/Configuration/ConfigurationManager.cs @@ -38,6 +38,9 @@ public ConfigurationManager() }, { "layout.tachiyomi", "yes" + }, + { + "tui.progress", "progress" } }; } @@ -58,6 +61,15 @@ public ConfigurationManager() var configValue = value.Split('='); dict.Add(configValue[0], configValue[1]); } + + // Ensure we populate all configuraiton options + foreach (var value in GetDefaults()) + { + if (!dict.ContainsKey(value.Key)) + { + dict.Add(value.Key, value.Value); + } + } return dict; } diff --git a/Core/Compression/IPackArchiveToCbz.cs b/Core/Compression/IPackArchiveToCbz.cs index dafd79a..bac44e7 100644 --- a/Core/Compression/IPackArchiveToCbz.cs +++ b/Core/Compression/IPackArchiveToCbz.cs @@ -1,10 +1,13 @@ using System.Collections.Generic; using System.Threading.Tasks; +using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers; +using asuka.Output.ProgressService.Providers.Wrappers; using ShellProgressBar; namespace asuka.Core.Compression; public interface IPackArchiveToCbz { - Task RunAsync(string targetFolder, string output, IProgressBar bar); + Task RunAsync(string targetFolder, string output, IProgressProvider bar); } diff --git a/Core/Compression/PackArchiveToCbz.cs b/Core/Compression/PackArchiveToCbz.cs index a5ff71f..6121a1d 100644 --- a/Core/Compression/PackArchiveToCbz.cs +++ b/Core/Compression/PackArchiveToCbz.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Threading.Tasks; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers; +using asuka.Output.ProgressService.Providers.Wrappers; using ShellProgressBar; namespace asuka.Core.Compression; @@ -16,7 +18,7 @@ public PackArchiveToCbz(IProgressService progressService) _progressService = progressService; } - public async Task RunAsync(string targetFolder, string output, IProgressBar bar) + public async Task RunAsync(string targetFolder, string output, IProgressProvider bar) { if (string.IsNullOrEmpty(output)) { diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs index 324a943..c7e5f62 100644 --- a/Installers/InstallServices.cs +++ b/Installers/InstallServices.cs @@ -5,6 +5,7 @@ using asuka.Core.Downloader; using asuka.Core.Requests; using asuka.Output.ProgressService; +using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; using Microsoft.Extensions.Configuration; diff --git a/Output/ProgressService/IProgressService.cs b/Output/ProgressService/IProgressService.cs index c95dd0b..0fa2baf 100644 --- a/Output/ProgressService/IProgressService.cs +++ b/Output/ProgressService/IProgressService.cs @@ -1,12 +1,13 @@ -using ShellProgressBar; +using asuka.Output.ProgressService.Providers; +using asuka.Output.ProgressService.Providers.Wrappers; namespace asuka.Output.ProgressService; public interface IProgressService { void CreateMasterProgress(int totalTicks, string title); - IProgressBar NestToMaster(int totalTicks, string title); - IProgressBar GetMasterProgress(); + IProgressProvider NestToMaster(int totalTicks, string title); + IProgressProvider GetMasterProgress(); bool HasMasterProgress(); - IProgressBar HookToInstance(IProgressBar bar, int totalTicks, string title); + IProgressProvider HookToInstance(IProgressProvider bar, int totalTicks, string title); } diff --git a/Output/ProgressService/ProgressProviderFactory.cs b/Output/ProgressService/ProgressProviderFactory.cs new file mode 100644 index 0000000..d0aab6d --- /dev/null +++ b/Output/ProgressService/ProgressProviderFactory.cs @@ -0,0 +1,17 @@ +using asuka.Configuration; +using asuka.Output.ProgressService.Providers; + +namespace asuka.Output.ProgressService; + +public static class ProgressProviderFactory +{ + public static IProgressProvider GetProvider(string tuiOption, int maxTicks, string title, object options) + { + return tuiOption switch + { + "stealth" => new VoidProgressProvider(), + "text" => new TextProgressProvider(maxTicks, title), + _ => new ExternalProgressProvider(maxTicks, title, options) + }; + } +} diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs index 1390f9d..d4f9a3d 100644 --- a/Output/ProgressService/ProgressService.cs +++ b/Output/ProgressService/ProgressService.cs @@ -1,17 +1,27 @@ +using asuka.Configuration; +using asuka.Output.ProgressService.Providers; +using asuka.Output.ProgressService.Providers.Wrappers; using ShellProgressBar; namespace asuka.Output.ProgressService; public class ProgressService : IProgressService { - private IProgressBar _progressBar; + private IProgressProvider _progressBar; + private readonly IConfigurationManager _configuration; + + public ProgressService(IConfigurationManager configuration) + { + _configuration = configuration; + } public void CreateMasterProgress(int totalTicks, string title) { - _progressBar = new ProgressBar(totalTicks, title, ProgressBarConfiguration.BarOption); + _progressBar = ProgressProviderFactory + .GetProvider(_configuration.GetValue("tui.progress"), totalTicks, title, ProgressBarConfiguration.BarOption); } - public IProgressBar GetMasterProgress() + public IProgressProvider GetMasterProgress() { return _progressBar; } @@ -21,7 +31,7 @@ public bool HasMasterProgress() return _progressBar is not null; } - public IProgressBar NestToMaster(int totalTicks, string title) + public IProgressProvider NestToMaster(int totalTicks, string title) { if (HasMasterProgress()) { @@ -32,7 +42,7 @@ public IProgressBar NestToMaster(int totalTicks, string title) return GetMasterProgress(); } - public IProgressBar HookToInstance(IProgressBar bar, int totalTicks, string title) + public IProgressProvider HookToInstance(IProgressProvider bar, int totalTicks, string title) { return bar.Spawn(totalTicks, title, ProgressBarConfiguration.BarOption); } diff --git a/Output/ProgressService/Providers/ExternalProgressProvider.cs b/Output/ProgressService/Providers/ExternalProgressProvider.cs new file mode 100644 index 0000000..e79643b --- /dev/null +++ b/Output/ProgressService/Providers/ExternalProgressProvider.cs @@ -0,0 +1,35 @@ +using asuka.Configuration; +using asuka.Output.ProgressService.Providers.Wrappers; +using ShellProgressBar; + +namespace asuka.Output.ProgressService.Providers; + +public class ExternalProgressProvider : IProgressProvider +{ + private readonly IProgressWrapper _progress; + + public ExternalProgressProvider(int maxTicks, string title, object options) + { + _progress = new ProgressWrapper(maxTicks, title, (ProgressBarOptions)options); + } + + private ExternalProgressProvider(IProgressWrapper wrapper) + { + _progress = wrapper; + } + + public IProgressProvider Spawn(int maxTicks, string title, object options) + { + return new ExternalProgressProvider(_progress.Spawn(maxTicks, title, options)); + } + + public void Tick(string message = null) + { + _progress.Tick(message); + } + + public void Tick(int newTickCount, string message = null) + { + _progress.Tick(newTickCount, message); + } +} diff --git a/Output/ProgressService/Providers/IProgressProvider.cs b/Output/ProgressService/Providers/IProgressProvider.cs new file mode 100644 index 0000000..a2e17f8 --- /dev/null +++ b/Output/ProgressService/Providers/IProgressProvider.cs @@ -0,0 +1,8 @@ +namespace asuka.Output.ProgressService.Providers; + +public interface IProgressProvider +{ + IProgressProvider Spawn(int maxTicks, string title, object options); + void Tick(string message = null); + void Tick(int newTickCount, string message = null); +} diff --git a/Output/ProgressService/Providers/TextProgressProvider.cs b/Output/ProgressService/Providers/TextProgressProvider.cs new file mode 100644 index 0000000..ac4d85f --- /dev/null +++ b/Output/ProgressService/Providers/TextProgressProvider.cs @@ -0,0 +1,53 @@ +using System; + +namespace asuka.Output.ProgressService.Providers; + +public class TextProgressProvider : IProgressProvider +{ + private readonly Guid _id; + private int _maxTick = 0; + private int _progress = 0; + private readonly string _title; + private readonly bool _isChild; + private readonly int _parentId; + + public TextProgressProvider(int maxTick, string title) + { + _maxTick = maxTick; + _title = title; + _isChild = false; + _id = Guid.NewGuid(); + } + + private TextProgressProvider(int maxTick, string title, int parentId) + { + _maxTick = maxTick; + _title = title; + _parentId = parentId; + _isChild = true; + _id = Guid.NewGuid(); + } + + public IProgressProvider Spawn(int maxTicks, string title, object options) + { + return new TextProgressProvider(maxTicks, title, _parentId); + } + + public void Tick(string message = null) + { + _progress += 1; + if (_isChild) + { + Console.WriteLine($"[Progress][Parent: {_parentId}] {message ?? _title} : {_progress} out of {_maxTick}"); + return; + } + + Console.WriteLine($"[Progress] {message ?? _title} : {_progress} out of {_maxTick}"); + } + + public void Tick(int newTickCount, string message = null) + { + _maxTick = newTickCount; + Tick(message); + } +} diff --git a/Output/ProgressService/Providers/VoidProgressProvider.cs b/Output/ProgressService/Providers/VoidProgressProvider.cs new file mode 100644 index 0000000..c75b824 --- /dev/null +++ b/Output/ProgressService/Providers/VoidProgressProvider.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices.JavaScript; + +namespace asuka.Output.ProgressService.Providers; + +public class VoidProgressProvider : IProgressProvider +{ + public IProgressProvider Spawn(int maxTicks, string title, object options) + { + return new VoidProgressProvider(); + } + + public void Tick(string message = null) + { + } + + public void Tick(int newTickCount, string message = null) + { + } +} diff --git a/Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs b/Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs new file mode 100644 index 0000000..ffb0d57 --- /dev/null +++ b/Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs @@ -0,0 +1,8 @@ +namespace asuka.Output.ProgressService.Providers.Wrappers; + +public interface IProgressWrapper +{ + IProgressWrapper Spawn(int maxTicks, string title, object options); + void Tick(string message = null); + void Tick(int newTickCount, string message = null); +} diff --git a/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs b/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs new file mode 100644 index 0000000..a7724d3 --- /dev/null +++ b/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs @@ -0,0 +1,45 @@ +using ShellProgressBar; + +namespace asuka.Output.ProgressService.Providers.Wrappers; + +public class ProgressWrapper : IProgressWrapper +{ + private readonly IProgressBar _progress; + + /// + /// Initialize a Progress Wrapper with IProgressBar instance + /// + /// + /// + private ProgressWrapper(IProgressBar progress) + { + _progress = progress; + } + + /// + /// Initialize a Progress Wrapper with ProgressBar required parameters + /// + /// + /// + /// + /// + public ProgressWrapper(int maxTicks, string title, ProgressBarOptions options) + { + _progress = new ProgressBar(maxTicks, title, options); + } + + public IProgressWrapper Spawn(int maxTicks, string title, object options) + { + return new ProgressWrapper(_progress.Spawn(maxTicks, title, (ProgressBarOptions)options)); + } + + public void Tick(int newTickCount, string message = null) + { + _progress.Tick(newTickCount, message); + } + + public void Tick(string message = null) + { + _progress.Tick(message); + } +} From baae25ed9b2258f180bae718d28132a8d3dc85c2 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 5 Mar 2023 23:47:16 +0900 Subject: [PATCH 20/21] Fix documentation and unused deps --- Commandline/Parsers/FileCommandService.cs | 1 - Commandline/Parsers/GetCommandService.cs | 1 - Commandline/Parsers/RandomCommandService.cs | 1 - Commandline/Parsers/RecommendCommandService.cs | 1 - Commandline/Parsers/SearchCommandService.cs | 1 - Commandline/Parsers/SeriesCreatorCommandService.cs | 1 - Output/ProgressService/IProgressService.cs | 1 - Output/ProgressService/ProgressProviderFactory.cs | 1 - Output/ProgressService/ProgressService.cs | 2 -- Output/ProgressService/Providers/ExternalProgressProvider.cs | 1 - Output/ProgressService/Providers/VoidProgressProvider.cs | 2 -- Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs | 2 -- 12 files changed, 15 deletions(-) diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs index 304c73a..9f5c87c 100644 --- a/Commandline/Parsers/FileCommandService.cs +++ b/Commandline/Parsers/FileCommandService.cs @@ -9,7 +9,6 @@ using asuka.Core.Downloader; using asuka.Core.Requests; using asuka.Output.ProgressService; -using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; namespace asuka.Commandline.Parsers; diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs index 138c9f7..b4c9b9a 100644 --- a/Commandline/Parsers/GetCommandService.cs +++ b/Commandline/Parsers/GetCommandService.cs @@ -5,7 +5,6 @@ using asuka.Core.Requests; using asuka.Output; using asuka.Output.ProgressService; -using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs index fabefa3..6d12499 100644 --- a/Commandline/Parsers/RandomCommandService.cs +++ b/Commandline/Parsers/RandomCommandService.cs @@ -6,7 +6,6 @@ using asuka.Core.Requests; using asuka.Output; using asuka.Output.ProgressService; -using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using Sharprompt; diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs index c9ccc2c..29c2f90 100644 --- a/Commandline/Parsers/RecommendCommandService.cs +++ b/Commandline/Parsers/RecommendCommandService.cs @@ -5,7 +5,6 @@ using asuka.Core.Mappings; using asuka.Core.Requests; using asuka.Output.ProgressService; -using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs index cf859f1..5928e9b 100644 --- a/Commandline/Parsers/SearchCommandService.cs +++ b/Commandline/Parsers/SearchCommandService.cs @@ -8,7 +8,6 @@ using asuka.Core.Mappings; using asuka.Core.Requests; using asuka.Output.ProgressService; -using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index fc8040e..0f521c8 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -8,7 +8,6 @@ using asuka.Core.Models; using asuka.Core.Requests; using asuka.Output.ProgressService; -using asuka.Output.ProgressService.Providers.Wrappers; using asuka.Output.Writer; using FluentValidation; diff --git a/Output/ProgressService/IProgressService.cs b/Output/ProgressService/IProgressService.cs index 0fa2baf..d3f7fbc 100644 --- a/Output/ProgressService/IProgressService.cs +++ b/Output/ProgressService/IProgressService.cs @@ -1,5 +1,4 @@ using asuka.Output.ProgressService.Providers; -using asuka.Output.ProgressService.Providers.Wrappers; namespace asuka.Output.ProgressService; diff --git a/Output/ProgressService/ProgressProviderFactory.cs b/Output/ProgressService/ProgressProviderFactory.cs index d0aab6d..80c6c75 100644 --- a/Output/ProgressService/ProgressProviderFactory.cs +++ b/Output/ProgressService/ProgressProviderFactory.cs @@ -1,4 +1,3 @@ -using asuka.Configuration; using asuka.Output.ProgressService.Providers; namespace asuka.Output.ProgressService; diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs index d4f9a3d..149f7b6 100644 --- a/Output/ProgressService/ProgressService.cs +++ b/Output/ProgressService/ProgressService.cs @@ -1,7 +1,5 @@ using asuka.Configuration; using asuka.Output.ProgressService.Providers; -using asuka.Output.ProgressService.Providers.Wrappers; -using ShellProgressBar; namespace asuka.Output.ProgressService; diff --git a/Output/ProgressService/Providers/ExternalProgressProvider.cs b/Output/ProgressService/Providers/ExternalProgressProvider.cs index e79643b..206ab70 100644 --- a/Output/ProgressService/Providers/ExternalProgressProvider.cs +++ b/Output/ProgressService/Providers/ExternalProgressProvider.cs @@ -1,4 +1,3 @@ -using asuka.Configuration; using asuka.Output.ProgressService.Providers.Wrappers; using ShellProgressBar; diff --git a/Output/ProgressService/Providers/VoidProgressProvider.cs b/Output/ProgressService/Providers/VoidProgressProvider.cs index c75b824..315676b 100644 --- a/Output/ProgressService/Providers/VoidProgressProvider.cs +++ b/Output/ProgressService/Providers/VoidProgressProvider.cs @@ -1,5 +1,3 @@ -using System.Runtime.InteropServices.JavaScript; - namespace asuka.Output.ProgressService.Providers; public class VoidProgressProvider : IProgressProvider diff --git a/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs b/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs index a7724d3..c26c7b4 100644 --- a/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs +++ b/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs @@ -9,7 +9,6 @@ public class ProgressWrapper : IProgressWrapper /// /// Initialize a Progress Wrapper with IProgressBar instance /// - /// /// private ProgressWrapper(IProgressBar progress) { @@ -19,7 +18,6 @@ private ProgressWrapper(IProgressBar progress) /// /// Initialize a Progress Wrapper with ProgressBar required parameters /// - /// /// /// /// From 2efd105a66b408287f148317443532054d0af507 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sun, 12 Mar 2023 18:50:14 +0900 Subject: [PATCH 21/21] Use IEnumerable instead of int[] --- Commandline/Options/GetOptions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Commandline/Options/GetOptions.cs b/Commandline/Options/GetOptions.cs index debb45f..787750c 100644 --- a/Commandline/Options/GetOptions.cs +++ b/Commandline/Options/GetOptions.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using CommandLine; namespace asuka.Commandline.Options; @@ -8,7 +9,7 @@ public record GetOptions : ICommonOptions [Option('i', "input", Required = true, HelpText = "Input Numeric Code(s)")] - public int[] Input { get; init; } + public IEnumerable Input { get; init; } [Option('r', "readonly", Default = false,