From 84b5dae6ab30d076c41616e139ad07896097a49b Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:53:35 +0900 Subject: [PATCH 1/8] Update to .net 8 and its dependencies --- .circleci/config.yml | 2 +- .github/workflows/dotnet.yml | 2 +- Commandline/Parsers/ConfigureCommand.cs | 16 ++++++++-------- .../Parsers/SeriesCreatorCommandService.cs | 4 ++-- ...igurationManager.cs => AppConfigManager.cs} | 4 ++-- ...gurationManager.cs => IAppConfigManager.cs} | 2 +- Core/Downloader/Downloader.cs | 10 +++++----- Installers/InstallServices.cs | 4 +--- Output/ProgressService/ProgressService.cs | 8 ++++---- Output/Writer/ConsoleWriter.cs | 8 ++++---- asuka.csproj | 18 +++++++++--------- 11 files changed, 38 insertions(+), 40 deletions(-) rename Configuration/{ConfigurationManager.cs => AppConfigManager.cs} (96%) rename Configuration/{IConfigurationManager.cs => IAppConfigManager.cs} (87%) diff --git a/.circleci/config.yml b/.circleci/config.yml index b940072..c336075 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ jobs: build: docker: - - image: mcr.microsoft.com/dotnet/sdk:7.0 + - image: mcr.microsoft.com/dotnet/sdk:8.0 steps: - checkout - run: diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bdf0740..81dc74d 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: 7.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs index 224f810..3b58778 100644 --- a/Commandline/Parsers/ConfigureCommand.cs +++ b/Commandline/Parsers/ConfigureCommand.cs @@ -8,13 +8,13 @@ namespace asuka.Commandline.Parsers; public class ConfigureCommand : ICommandLineParser { - private readonly IConfigurationManager _configurationManager; + private readonly IAppConfigManager _appConfigManager; private readonly IConsoleWriter _consoleWriter; private readonly IValidator _validator; - public ConfigureCommand(IValidator validator, IConfigurationManager configurationManager, IConsoleWriter consoleWriter) + public ConfigureCommand(IValidator validator, IAppConfigManager appConfigManager, IConsoleWriter consoleWriter) { - _configurationManager = configurationManager; + _appConfigManager = appConfigManager; _consoleWriter = consoleWriter; _validator = validator; } @@ -31,15 +31,15 @@ public async Task RunAsync(object options) if (opts.SetConfigMode) { - _configurationManager.SetValue(opts.Key, opts.Value); - await _configurationManager.Flush(); + _appConfigManager.SetValue(opts.Key, opts.Value); + await _appConfigManager.Flush(); return; } if (opts.ReadConfigMode) { - var configValue = _configurationManager.GetValue(opts.Key); + var configValue = _appConfigManager.GetValue(opts.Key); _consoleWriter.WriteLine($"{opts.Key} = {configValue}"); return; @@ -47,7 +47,7 @@ public async Task RunAsync(object options) if (opts.ListConfigMode) { - var keyValuePairs = _configurationManager.GetAllValues(); + var keyValuePairs = _appConfigManager.GetAllValues(); foreach (var (key, value) in keyValuePairs) { @@ -59,7 +59,7 @@ public async Task RunAsync(object options) if (opts.ResetConfig) { - await _configurationManager.Reset(); + await _appConfigManager.Reset(); } } } diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index 0f521c8..82a83eb 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -20,7 +20,7 @@ public class SeriesCreatorCommandService : ICommandLineParser private readonly IDownloader _downloader; private readonly IProgressService _progress; private readonly IPackArchiveToCbz _pack; - private readonly IConfigurationManager _config; + private readonly IAppConfigManager _config; private readonly IValidator _validator; public SeriesCreatorCommandService( @@ -29,7 +29,7 @@ public SeriesCreatorCommandService( IDownloader downloader, IProgressService progress, IPackArchiveToCbz pack, - IConfigurationManager config, + IAppConfigManager config, IValidator validator) { _api = api; diff --git a/Configuration/ConfigurationManager.cs b/Configuration/AppConfigManager.cs similarity index 96% rename from Configuration/ConfigurationManager.cs rename to Configuration/AppConfigManager.cs index 9f6bdbc..e0fe006 100644 --- a/Configuration/ConfigurationManager.cs +++ b/Configuration/AppConfigManager.cs @@ -8,11 +8,11 @@ namespace asuka.Configuration; -public class ConfigurationManager : IConfigurationManager +public class AppConfigManager : IAppConfigManager { private Dictionary _config; - public ConfigurationManager() + public AppConfigManager() { var configRoot = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".asuka"); var configPath = Path.Join(configRoot, "config.conf"); diff --git a/Configuration/IConfigurationManager.cs b/Configuration/IAppConfigManager.cs similarity index 87% rename from Configuration/IConfigurationManager.cs rename to Configuration/IAppConfigManager.cs index 75eaed6..9caf3d7 100644 --- a/Configuration/IConfigurationManager.cs +++ b/Configuration/IAppConfigManager.cs @@ -3,7 +3,7 @@ namespace asuka.Configuration; -public interface IConfigurationManager +public interface IAppConfigManager { void SetValue(string key, string value); string GetValue(string key); diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs index 9d5586d..e0c4d4c 100644 --- a/Core/Downloader/Downloader.cs +++ b/Core/Downloader/Downloader.cs @@ -29,13 +29,13 @@ internal record FetchImageParameter public class Downloader : IDownloader { private readonly IGalleryImage _api; - private readonly IConfigurationManager _configurationManager; + private readonly IAppConfigManager _appConfigManager; private DownloadTaskDetails _details; - public Downloader(IGalleryImage api, IConfigurationManager configurationManager) + public Downloader(IGalleryImage api, IAppConfigManager appConfigManager) { _api = api; - _configurationManager = configurationManager; + _appConfigManager = appConfigManager; } private static string GetTitle(GalleryTitleResult result) @@ -53,7 +53,7 @@ private static string GetTitle(GalleryTitleResult result) private async Task WriteMetadata(string output, GalleryResult result) { - if (_configurationManager.GetValue("layout.tachiyomi") == "yes") + if (_appConfigManager.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.GetValue("layout.tachiyomi") == "yes" && chapter > 0 + _details.ChapterPath = _appConfigManager.GetValue("layout.tachiyomi") == "yes" && chapter > 0 ? Path.Combine(_details.ChapterRoot, $"ch{chapter}") : _details.ChapterRoot; _details.Result = result; diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs index c7e5f62..810db84 100644 --- a/Installers/InstallServices.cs +++ b/Installers/InstallServices.cs @@ -5,12 +5,10 @@ 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; using Microsoft.Extensions.DependencyInjection; -using ConfigurationManager = asuka.Configuration.ConfigurationManager; namespace asuka.Installers; @@ -24,7 +22,7 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu services.AddSingleton(); services.AddScoped(); services.AddScoped(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); // Command parsers diff --git a/Output/ProgressService/ProgressService.cs b/Output/ProgressService/ProgressService.cs index 149f7b6..67e33d7 100644 --- a/Output/ProgressService/ProgressService.cs +++ b/Output/ProgressService/ProgressService.cs @@ -6,17 +6,17 @@ namespace asuka.Output.ProgressService; public class ProgressService : IProgressService { private IProgressProvider _progressBar; - private readonly IConfigurationManager _configuration; + private readonly IAppConfigManager _appConfig; - public ProgressService(IConfigurationManager configuration) + public ProgressService(IAppConfigManager appConfig) { - _configuration = configuration; + _appConfig = appConfig; } public void CreateMasterProgress(int totalTicks, string title) { _progressBar = ProgressProviderFactory - .GetProvider(_configuration.GetValue("tui.progress"), totalTicks, title, ProgressBarConfiguration.BarOption); + .GetProvider(_appConfig.GetValue("tui.progress"), totalTicks, title, ProgressBarConfiguration.BarOption); } public IProgressProvider GetMasterProgress() diff --git a/Output/Writer/ConsoleWriter.cs b/Output/Writer/ConsoleWriter.cs index f820e58..d8c993e 100644 --- a/Output/Writer/ConsoleWriter.cs +++ b/Output/Writer/ConsoleWriter.cs @@ -8,16 +8,16 @@ namespace asuka.Output.Writer; public class ConsoleWriter : IConsoleWriter { - private readonly IConfigurationManager _configurationManager; + private readonly IAppConfigManager _appConfigManager; - public ConsoleWriter(IConfigurationManager configurationManager) + public ConsoleWriter(IAppConfigManager appConfigManager) { - _configurationManager = configurationManager; + _appConfigManager = appConfigManager; } private Color GetColor(Color forWhiteTheme, Color forDarkTheme) { - return _configurationManager.GetValue("color.theme") == "dark" ? forDarkTheme : forWhiteTheme; + return _appConfigManager.GetValue("color.theme") == "dark" ? forDarkTheme : forWhiteTheme; } public void WriteLine(object message) diff --git a/asuka.csproj b/asuka.csproj index 7761364..0a5e365 100644 --- a/asuka.csproj +++ b/asuka.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 1.2.0.0 1.2.0.0 appicon.ico @@ -17,19 +17,19 @@ AppIcon.png D:\Code\Projects\asuka\LICENSE - 11 - disable + latestmajor + enable - - - - - - + + + + + + From f59da272cf034a49c6d3bbcdc92d28327d16a9a9 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:04:17 +0900 Subject: [PATCH 2/8] Use keyed services feature --- AsukaApplication.cs | 52 ------------------------ Commandline/CommandLineParserFactory.cs | 37 ----------------- Commandline/CommandLineParserTokens.cs | 13 ------ Commandline/ICommandLineParserFactory.cs | 6 --- Installers/InstallServices.cs | 18 ++++---- Program.cs | 51 +++++++++++++++++++++-- 6 files changed, 56 insertions(+), 121 deletions(-) delete mode 100644 AsukaApplication.cs delete mode 100644 Commandline/CommandLineParserFactory.cs delete mode 100644 Commandline/CommandLineParserTokens.cs delete mode 100644 Commandline/ICommandLineParserFactory.cs diff --git a/AsukaApplication.cs b/AsukaApplication.cs deleted file mode 100644 index f43b954..0000000 --- a/AsukaApplication.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using asuka.Commandline; -using asuka.Commandline.Options; -using CommandLine; -using asuka.Output.Writer; - -namespace asuka; - -public class AsukaApplication -{ - private readonly IConsoleWriter _console; - private readonly ICommandLineParserFactory _command; - - public AsukaApplication(IConsoleWriter console, ICommandLineParserFactory command) - { - _console = console; - _command = command; - } - - public async Task RunAsync(IEnumerable args) - { - var parser = Parser.Default - .ParseArguments(args); - await parser.MapResult( - 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); }, - async (SeriesCreatorCommandOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Series); }, - async (CookieConfigureOptions opts) => { await RunCommand(opts, CommandLineParserTokens.Cookie); }, - 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/CommandLineParserFactory.cs b/Commandline/CommandLineParserFactory.cs deleted file mode 100644 index 4f38771..0000000 --- a/Commandline/CommandLineParserFactory.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -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)), - CommandLineParserTokens.Series => GetService(typeof(SeriesCreatorCommandService)), - CommandLineParserTokens.Cookie => GetService(typeof(CookieConfigureService)), - _ => 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 deleted file mode 100644 index 33acc0b..0000000 --- a/Commandline/CommandLineParserTokens.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace asuka.Commandline; - -public enum CommandLineParserTokens -{ - Configure, - File, - Get, - Random, - Recommend, - Search, - Series, - Cookie -} diff --git a/Commandline/ICommandLineParserFactory.cs b/Commandline/ICommandLineParserFactory.cs deleted file mode 100644 index 2c35295..0000000 --- a/Commandline/ICommandLineParserFactory.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace asuka.Commandline; - -public interface ICommandLineParserFactory -{ - public ICommandLineParser GetInstance(CommandLineParserTokens token); -} diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs index 810db84..edeca2d 100644 --- a/Installers/InstallServices.cs +++ b/Installers/InstallServices.cs @@ -16,7 +16,6 @@ public class InstallServices : IInstaller { public void ConfigureService(IServiceCollection services, IConfiguration configuration) { - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -26,15 +25,14 @@ public void ConfigureService(IServiceCollection services, IConfiguration configu services.AddSingleton(); // Command parsers - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddKeyedScoped("Cmd_Get"); + services.AddKeyedScoped("Cmd_Recommend"); + services.AddKeyedScoped("Cmd_Search"); + services.AddKeyedScoped("Cmd_Random"); + services.AddKeyedScoped("Cmd_File"); + services.AddKeyedScoped("Cmd_Configure"); + services.AddKeyedScoped("Cmd_Series"); + services.AddKeyedScoped("Cmd_Cookie"); services.AddValidatorsFromAssemblyContaining(); } } diff --git a/Program.cs b/Program.cs index 55abbf2..27e8648 100644 --- a/Program.cs +++ b/Program.cs @@ -1,7 +1,11 @@ -using asuka; +using System; +using System.Threading.Tasks; +using asuka.Commandline; +using asuka.Commandline.Options; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using asuka.Installers; +using CommandLine; var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") @@ -11,5 +15,46 @@ services.InstallServicesInAssembly(configuration); var serviceProvider = services.BuildServiceProvider(); -var app = serviceProvider.GetRequiredService(); -await app.RunAsync(args); +async Task RunCommand(object options, string serviceKey) +{ + var service = serviceProvider.GetKeyedService($"Cmd_{serviceKey}"); + if (service == null) + { + Console.WriteLine($"Unknown command"); + return; + } + + await service.RunAsync(options); +} + +var parser = Parser.Default + .ParseArguments(args); + +try +{ + await parser.MapResult( + async (GetOptions opts) => { await RunCommand(opts, "Get"); }, + async (RecommendOptions opts) => { await RunCommand(opts, "Recommend"); }, + async (SearchOptions opts) => { await RunCommand(opts, "Search"); }, + async (RandomOptions opts) => { await RunCommand(opts, "Random"); }, + async (FileCommandOptions opts) => { await RunCommand(opts, "File"); }, + async (ConfigureOptions opts) => { await RunCommand(opts, "Configure"); }, + async (SeriesCreatorCommandOptions opts) => { await RunCommand(opts, "Series"); }, + async (CookieConfigureOptions opts) => { await RunCommand(opts, "Cookie"); }, + errors => + { + foreach (var error in errors) + { + Console.WriteLine($"An error occured. Type: {error.Tag}"); + } + + return Task.FromResult(1); + }); + + return 0; +} +catch (Exception e) +{ + Console.WriteLine($"An error occured. Message: {e.Message}"); + return 1; +} From 415eff92bbd9817cbac844eb1d3633a88d8a3058 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Thu, 21 Dec 2023 16:21:55 +0900 Subject: [PATCH 3/8] Use tachiyomi layout by default --- .../Parsers/SeriesCreatorCommandService.cs | 7 ------ Configuration/AppConfigManager.cs | 3 --- Core/Downloader/Downloader.cs | 22 +++++-------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index 82a83eb..6a13ff9 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -2,7 +2,6 @@ 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; @@ -20,7 +19,6 @@ public class SeriesCreatorCommandService : ICommandLineParser private readonly IDownloader _downloader; private readonly IProgressService _progress; private readonly IPackArchiveToCbz _pack; - private readonly IAppConfigManager _config; private readonly IValidator _validator; public SeriesCreatorCommandService( @@ -29,7 +27,6 @@ public SeriesCreatorCommandService( IDownloader downloader, IProgressService progress, IPackArchiveToCbz pack, - IAppConfigManager config, IValidator validator) { _api = api; @@ -37,7 +34,6 @@ public SeriesCreatorCommandService( _downloader = downloader; _progress = progress; _pack = pack; - _config = config; _validator = validator; } @@ -124,9 +120,6 @@ public async Task RunAsync(object options) return; } - // Temporarily enable tachiyomi folder layout - _config.SetValue("layout.tachiyomi", "yes"); - var list = opts.FromList.ToList(); await HandleArrayTask(list, opts.Output, opts.Pack); } diff --git a/Configuration/AppConfigManager.cs b/Configuration/AppConfigManager.cs index e0fe006..d2fb029 100644 --- a/Configuration/AppConfigManager.cs +++ b/Configuration/AppConfigManager.cs @@ -36,9 +36,6 @@ private Dictionary GetDefaults() { "colors.theme", "dark" }, - { - "layout.tachiyomi", "yes" - }, { "tui.progress", "progress" } diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs index e0c4d4c..3462ecf 100644 --- a/Core/Downloader/Downloader.cs +++ b/Core/Downloader/Downloader.cs @@ -53,20 +53,12 @@ private static string GetTitle(GalleryTitleResult result) private async Task WriteMetadata(string output, GalleryResult result) { - if (_appConfigManager.GetValue("layout.tachiyomi") == "yes") - { - var metaPath = Path.Combine(output, "details.json"); - var serializerOptions = new JsonSerializerOptions { WriteIndented = true }; - var metadata = JsonSerializer - .Serialize(result.ToTachiyomiMetadata(), serializerOptions); + var metaPath = Path.Combine(output, "details.json"); + var serializerOptions = new JsonSerializerOptions { WriteIndented = true }; + var metadata = JsonSerializer + .Serialize(result.ToTachiyomiMetadata(), serializerOptions); - await File.WriteAllTextAsync(metaPath, metadata).ConfigureAwait(false); - return; - } - - var metadataPath = Path.Combine(output, "info.txt"); - await File.WriteAllTextAsync(metadataPath, result.ToReadable()) - .ConfigureAwait(false); + await File.WriteAllTextAsync(metaPath, metadata).ConfigureAwait(false); } public void CreateSeries(GalleryTitleResult title, string outputPath) @@ -80,9 +72,7 @@ public void CreateSeries(GalleryTitleResult title, string outputPath) public void CreateChapter(GalleryResult result, int chapter) { - _details.ChapterPath = _appConfigManager.GetValue("layout.tachiyomi") == "yes" && chapter > 0 - ? Path.Combine(_details.ChapterRoot, $"ch{chapter}") - : _details.ChapterRoot; + _details.ChapterPath = Path.Combine(_details.ChapterRoot, $"ch{chapter}"); _details.Result = result; if (!Directory.Exists(_details.ChapterPath)) From 6a4fe2f3e7c25bcc34240a088e9f87bbc7ed2aa8 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:25:46 +0900 Subject: [PATCH 4/8] Refactor command parsers --- Commandline/Parsers/ConfigureCommand.cs | 13 +- Commandline/Parsers/CookieConfigureService.cs | 30 ++-- Commandline/Parsers/FileCommandService.cs | 75 ++++----- Commandline/Parsers/GetCommandService.cs | 61 +++---- Commandline/Parsers/RandomCommandService.cs | 60 +++---- .../Parsers/RecommendCommandService.cs | 65 ++++---- Commandline/Parsers/SearchCommandService.cs | 68 ++++---- .../Parsers/SeriesCreatorCommandService.cs | 105 ++++-------- Core/Compression/Compress.cs | 61 +++++++ Core/Compression/IPackArchiveToCbz.cs | 13 -- Core/Compression/PackArchiveToCbz.cs | 55 ------- Core/Downloader/DownloadBuilder.cs | 89 ++++++++++ Core/Downloader/Downloader.cs | 153 ------------------ Core/Downloader/IDownloader.cs | 17 -- Core/Extensions/GalleryResultExtensions.cs | 63 ++++++++ .../Extensions/GalleryTitleResultExtension.cs | 38 +++++ Core/Extensions/TitleLanguages.cs | 19 +++ Core/Output/Progress/IProgressProvider.cs | 47 ++++++ Core/Output/Progress/StealthProgressBar.cs | 39 +++++ Core/Output/Progress/TextProgressBar.cs | 82 ++++++++++ Downloader/SeriesDownloaderBuilder.cs | 69 ++++++++ Installers/InstallServices.cs | 10 +- Installers/Refit/ConfigureRefitService.cs | 23 ++- Output/DisplayInformation.cs | 63 -------- Output/Progress/IProgressFactory.cs | 9 ++ Output/Progress/ProgressFactory.cs | 39 +++++ Output/Progress/ProgressTypes.cs | 8 + Output/Progress/ShellProgressBarOptions.cs | 14 ++ Output/Progress/ShellProgressBarWrapper.cs | 56 +++++++ Output/ProgressService/IProgressService.cs | 12 -- .../ProgressBarConfiguration.cs | 21 --- .../ProgressProviderFactory.cs | 16 -- Output/ProgressService/ProgressService.cs | 47 ------ .../Providers/ExternalProgressProvider.cs | 44 ----- .../Providers/IProgressProvider.cs | 10 -- .../Providers/TextProgressProvider.cs | 63 -------- .../Providers/VoidProgressProvider.cs | 25 --- .../Providers/Wrappers/IProgressWrapper.cs | 10 -- .../Providers/Wrappers/ProgressWrapper.cs | 53 ------ Output/ValidationErrorExtensions.cs | 16 ++ Output/Writer/ConsoleWriter.cs | 50 ------ Output/Writer/IConsoleWriter.cs | 13 -- asuka.csproj | 1 - 43 files changed, 893 insertions(+), 932 deletions(-) create mode 100644 Core/Compression/Compress.cs delete mode 100644 Core/Compression/IPackArchiveToCbz.cs delete mode 100644 Core/Compression/PackArchiveToCbz.cs create mode 100644 Core/Downloader/DownloadBuilder.cs delete mode 100644 Core/Downloader/Downloader.cs delete mode 100644 Core/Downloader/IDownloader.cs create mode 100644 Core/Extensions/GalleryResultExtensions.cs create mode 100644 Core/Extensions/GalleryTitleResultExtension.cs create mode 100644 Core/Extensions/TitleLanguages.cs create mode 100644 Core/Output/Progress/IProgressProvider.cs create mode 100644 Core/Output/Progress/StealthProgressBar.cs create mode 100644 Core/Output/Progress/TextProgressBar.cs create mode 100644 Downloader/SeriesDownloaderBuilder.cs delete mode 100644 Output/DisplayInformation.cs create mode 100644 Output/Progress/IProgressFactory.cs create mode 100644 Output/Progress/ProgressFactory.cs create mode 100644 Output/Progress/ProgressTypes.cs create mode 100644 Output/Progress/ShellProgressBarOptions.cs create mode 100644 Output/Progress/ShellProgressBarWrapper.cs delete mode 100644 Output/ProgressService/IProgressService.cs delete mode 100644 Output/ProgressService/ProgressBarConfiguration.cs delete mode 100644 Output/ProgressService/ProgressProviderFactory.cs delete mode 100644 Output/ProgressService/ProgressService.cs delete mode 100644 Output/ProgressService/Providers/ExternalProgressProvider.cs delete mode 100644 Output/ProgressService/Providers/IProgressProvider.cs delete mode 100644 Output/ProgressService/Providers/TextProgressProvider.cs delete mode 100644 Output/ProgressService/Providers/VoidProgressProvider.cs delete mode 100644 Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs delete mode 100644 Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs create mode 100644 Output/ValidationErrorExtensions.cs delete mode 100644 Output/Writer/ConsoleWriter.cs delete mode 100644 Output/Writer/IConsoleWriter.cs diff --git a/Commandline/Parsers/ConfigureCommand.cs b/Commandline/Parsers/ConfigureCommand.cs index 3b58778..ed912cc 100644 --- a/Commandline/Parsers/ConfigureCommand.cs +++ b/Commandline/Parsers/ConfigureCommand.cs @@ -1,7 +1,8 @@ +using System; using System.Threading.Tasks; using asuka.Commandline.Options; using asuka.Configuration; -using asuka.Output.Writer; +using asuka.Output; using FluentValidation; namespace asuka.Commandline.Parsers; @@ -9,13 +10,11 @@ namespace asuka.Commandline.Parsers; public class ConfigureCommand : ICommandLineParser { private readonly IAppConfigManager _appConfigManager; - private readonly IConsoleWriter _consoleWriter; private readonly IValidator _validator; - public ConfigureCommand(IValidator validator, IAppConfigManager appConfigManager, IConsoleWriter consoleWriter) + public ConfigureCommand(IValidator validator, IAppConfigManager appConfigManager) { _appConfigManager = appConfigManager; - _consoleWriter = consoleWriter; _validator = validator; } @@ -25,7 +24,7 @@ public async Task RunAsync(object options) var validation = await _validator.ValidateAsync(opts); if (!validation.IsValid) { - _consoleWriter.ValidationErrors(validation.Errors); + validation.Errors.PrintValidationExceptions(); return; } @@ -40,7 +39,7 @@ public async Task RunAsync(object options) if (opts.ReadConfigMode) { var configValue = _appConfigManager.GetValue(opts.Key); - _consoleWriter.WriteLine($"{opts.Key} = {configValue}"); + Console.WriteLine($"{opts.Key} = {configValue}"); return; } @@ -51,7 +50,7 @@ public async Task RunAsync(object options) foreach (var (key, value) in keyValuePairs) { - _consoleWriter.WriteLine($"{key} = {value}"); + Console.WriteLine($"{key} = {value}"); } return; diff --git a/Commandline/Parsers/CookieConfigureService.cs b/Commandline/Parsers/CookieConfigureService.cs index e065867..97fc5fb 100644 --- a/Commandline/Parsers/CookieConfigureService.cs +++ b/Commandline/Parsers/CookieConfigureService.cs @@ -1,10 +1,11 @@ +using System; 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 asuka.Output; using FluentValidation; namespace asuka.Commandline.Parsers; @@ -12,16 +13,13 @@ 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; } @@ -32,7 +30,7 @@ public async Task RunAsync(object options) var validationResult = await _validator.ValidateAsync(opts); if (!validationResult.IsValid) { - _console.ValidationErrors(validationResult.Errors); + validationResult.Errors.PrintValidationExceptions(); return; } @@ -40,10 +38,24 @@ public async Task RunAsync(object options) 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"); + if (cookieData != null) + { + var cloudflare = cookieData.FirstOrDefault(x => x.Name == "cf_clearance"); + var csrf = cookieData.FirstOrDefault(x => x.Name == "csrftoken"); + + if (cloudflare != null && csrf != null) + { + await _requestConfigurator.ApplyCookies(cloudflare, csrf); + } + + if (!string.IsNullOrEmpty(opts.UserAgent)) + { + await _requestConfigurator.ApplyUserAgent(opts.UserAgent); + } - await _requestConfigurator.ApplyCookies(cloudflare, csrf); - await _requestConfigurator.ApplyUserAgent(opts.UserAgent); + return; + } + + Console.WriteLine("An error occured at reading the cookie you provided."); } } diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs index 9f5c87c..4f0904e 100644 --- a/Commandline/Parsers/FileCommandService.cs +++ b/Commandline/Parsers/FileCommandService.cs @@ -1,38 +1,35 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using asuka.Api; using asuka.Commandline.Options; using asuka.Core.Compression; using asuka.Core.Downloader; +using asuka.Core.Extensions; using asuka.Core.Requests; -using asuka.Output.ProgressService; -using asuka.Output.Writer; +using asuka.Core.Utilities; +using asuka.Output.Progress; namespace asuka.Commandline.Parsers; public class FileCommandService : ICommandLineParser { private readonly IGalleryRequestService _api; - private readonly IConsoleWriter _console; - private readonly IDownloader _download; - private readonly IProgressService _progressService; - private readonly IPackArchiveToCbz _pack; + private readonly IGalleryImage _apiImage; + private readonly IProgressFactory _progress; public FileCommandService( IGalleryRequestService api, - IConsoleWriter console, - IDownloader download, - IProgressService progressService, - IPackArchiveToCbz pack) + IGalleryImage apiImage, + IProgressFactory progress) { _api = api; - _console = console; - _download = download; - _progressService = progressService; - _pack = pack; + _apiImage = apiImage; + _progress = progress; } public async Task RunAsync(object options) @@ -40,13 +37,13 @@ public async Task RunAsync(object options) var opts = (FileCommandOptions)options; if (!File.Exists(opts.FilePath)) { - _console.ErrorLine("File doesn't exist."); + Console.WriteLine("File doesn't exist."); return; } if (IsFileExceedingToFileSizeLimit(opts.FilePath)) { - _console.ErrorLine("The file size is exceeding 5MB file size limit."); + Console.WriteLine("The file size is exceeding 5MB file size limit."); return; } @@ -56,38 +53,42 @@ public async Task RunAsync(object options) if (validUrls.Count == 0) { - _console.ErrorLine("No valid URLs found."); + Console.WriteLine("No valid URLs found."); return; } - _progressService.CreateMasterProgress(validUrls.Count, "downloading from text file..."); - var progress = _progressService.GetMasterProgress(); + var mainProgress = _progress.Create(validUrls.Count, + $"Downloading from text file..."); foreach (var url in validUrls) { var code = new Regex("\\d+").Match(url).Value; var response = await _api.FetchSingleAsync(code); - _download.CreateSeries(response.Title, opts.Output); - _download.CreateChapter(response, 1); - - // Create progress bar - var internalProgress = _progressService.NestToMaster(response.TotalPages, $"downloading: {response.Id}"); - _download.SetOnImageDownload = () => + var childProgress = mainProgress.Spawn(response.TotalPages, + $"Downloading: {response.Title.GetTitle()}")!; + + var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var downloader = new DownloadBuilder(response, 1) { - internalProgress.Tick($"downloading: {response.Id}"); + Request = _apiImage, + Output = output, + OnEachComplete = _ => + { + childProgress.Tick(); + }, + OnComplete = async gallery => + { + await gallery.WriteMetadata(Path.Combine(output, "details.json")); + if (opts.Pack) + { + await Compress.ToCbz(output, childProgress); + } + } }; - // Start downloading - await _download.Start(); - await _download.Final(); - - // If --pack option is specified, compresss the file into cbz - if (opts.Pack) - { - await _pack.RunAsync(_download.DownloadRoot, opts.Output, internalProgress); - } - progress.Tick(); + await downloader.Start(); + mainProgress.Tick(); } } diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs index b4c9b9a..58af890 100644 --- a/Commandline/Parsers/GetCommandService.cs +++ b/Commandline/Parsers/GetCommandService.cs @@ -1,69 +1,72 @@ +using System; +using System.IO; using System.Threading.Tasks; +using asuka.Api; using asuka.Commandline.Options; using asuka.Core.Compression; using asuka.Core.Downloader; +using asuka.Core.Extensions; using asuka.Core.Requests; +using asuka.Core.Utilities; using asuka.Output; -using asuka.Output.ProgressService; -using asuka.Output.Writer; +using asuka.Output.Progress; using FluentValidation; namespace asuka.Commandline.Parsers; public class GetCommandService : ICommandLineParser { + private readonly IGalleryImage _apiImage; private readonly IGalleryRequestService _api; private readonly IValidator _validator; - private readonly IDownloader _download; - private readonly IConsoleWriter _console; - private readonly IProgressService _progress; - private readonly IPackArchiveToCbz _pack; + private readonly IProgressFactory _progress; public GetCommandService( + IGalleryImage apiImage, IGalleryRequestService api, IValidator validator, - IDownloader download, - IConsoleWriter console, - IProgressService progress, - IPackArchiveToCbz pack) + IProgressFactory progress) { + _apiImage = apiImage; _api = api; _validator = validator; - _download = download; - _console = console; _progress = progress; - _pack = pack; } private async Task DownloadTask(int input, bool pack, bool readOnly, string outputPath) { var response = await _api.FetchSingleAsync(input.ToString()); - _console.WriteLine(response.ToReadable()); + Console.WriteLine(response.ToFormattedText()); // Don't download. if (readOnly) { return; } - - _download.CreateSeries(response.Title, outputPath); - _download.CreateChapter(response, 1); - _progress.CreateMasterProgress(response.TotalPages, $"downloading: {response.Id}"); + var mainProgress = _progress.Create(response.TotalPages, + $"Downloading Manga: {response.Title.GetTitle()}"); - var progress = _progress.GetMasterProgress(); - _download.SetOnImageDownload = () => + var output = Path.Combine(outputPath, PathUtils.NormalizeName(response.Title.GetTitle())); + var downloader = new DownloadBuilder(response, 1) { - progress.Tick(); + Request = _apiImage, + Output = output, + OnEachComplete = _ => + { + mainProgress.Tick(); + }, + OnComplete = async gallery => + { + await gallery.WriteMetadata(Path.Combine(outputPath, "details.json")); + if (pack) + { + await Compress.ToCbz(output, mainProgress); + } + } }; - await _download.Start(); - await _download.Final(); - - if (pack) - { - await _pack.RunAsync(_download.DownloadRoot, outputPath, progress); - } + await downloader.Start(); } public async Task RunAsync(object options) @@ -72,7 +75,7 @@ public async Task RunAsync(object options) var validationResult = await _validator.ValidateAsync(opts); if (!validationResult.IsValid) { - _console.ValidationErrors(validationResult.Errors); + validationResult.Errors.PrintValidationExceptions(); return; } diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs index 6d12499..44d918b 100644 --- a/Commandline/Parsers/RandomCommandService.cs +++ b/Commandline/Parsers/RandomCommandService.cs @@ -1,36 +1,32 @@ using System; +using System.IO; using System.Threading.Tasks; +using asuka.Api; using asuka.Commandline.Options; using asuka.Core.Compression; using asuka.Core.Downloader; +using asuka.Core.Extensions; using asuka.Core.Requests; -using asuka.Output; -using asuka.Output.ProgressService; -using asuka.Output.Writer; +using asuka.Core.Utilities; +using asuka.Output.Progress; using Sharprompt; namespace asuka.Commandline.Parsers; public class RandomCommandService : ICommandLineParser { - private readonly IDownloader _download; private readonly IGalleryRequestService _api; - private readonly IConsoleWriter _console; - private readonly IProgressService _progress; - private readonly IPackArchiveToCbz _pack; + private readonly IGalleryImage _apiImage; + private readonly IProgressFactory _progress; public RandomCommandService( - IDownloader download, IGalleryRequestService api, - IConsoleWriter console, - IProgressService progress, - IPackArchiveToCbz pack) + IGalleryImage apiImage, + IProgressFactory progress) { - _download = download; _api = api; - _console = console; + _apiImage = apiImage; _progress = progress; - _pack = pack; } public async Task RunAsync(object options) @@ -43,7 +39,7 @@ public async Task RunAsync(object options) var randomCode = new Random().Next(1, totalNumbers); var response = await _api.FetchSingleAsync(randomCode.ToString()); - _console.WriteLine(response.ToReadable()); + Console.WriteLine(response.ToFormattedText()); var prompt = Prompt.Confirm("Are you sure to download this one?", true); if (!prompt) @@ -51,24 +47,30 @@ public async Task RunAsync(object options) await Task.Delay(1000).ConfigureAwait(false); continue; } - - _download.CreateSeries(response.Title, opts.Output); - _download.CreateChapter(response, 1); - _progress.CreateMasterProgress(response.TotalPages, $"downloading random id: {response.Id}"); - var progress = _progress.GetMasterProgress(); - _download.SetOnImageDownload = () => + var mainProgress = _progress.Create(response.TotalPages, + $"Downloading Manga: {response.Title.GetTitle()}"); + + var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var downloader = new DownloadBuilder(response, 1) { - progress.Tick(); + Output = output, + Request = _apiImage, + OnEachComplete = _ => + { + mainProgress.Tick(); + }, + OnComplete = async data => + { + await data.WriteMetadata(Path.Combine(output, "details.json")); + if (opts.Pack) + { + await Compress.ToCbz(output, mainProgress); + } + } }; - await _download.Start(); - await _download.Final(); - - if (opts.Pack) - { - await _pack.RunAsync(_download.DownloadRoot, opts.Output, progress); - } + await downloader.Start(); break; } } diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs index 29c2f90..9818b77 100644 --- a/Commandline/Parsers/RecommendCommandService.cs +++ b/Commandline/Parsers/RecommendCommandService.cs @@ -1,11 +1,15 @@ +using System.IO; using System.Threading.Tasks; +using asuka.Api; using asuka.Commandline.Options; using asuka.Core.Compression; using asuka.Core.Downloader; +using asuka.Core.Extensions; using asuka.Core.Mappings; using asuka.Core.Requests; -using asuka.Output.ProgressService; -using asuka.Output.Writer; +using asuka.Core.Utilities; +using asuka.Output; +using asuka.Output.Progress; using FluentValidation; namespace asuka.Commandline.Parsers; @@ -14,25 +18,19 @@ public class RecommendCommandService : ICommandLineParser { private readonly IValidator _validator; private readonly IGalleryRequestService _api; - private readonly IDownloader _download; - private readonly IConsoleWriter _console; - private readonly IProgressService _progressService; - private readonly IPackArchiveToCbz _pack; + private readonly IGalleryImage _apiImage; + private readonly IProgressFactory _progress; public RecommendCommandService( IValidator validator, IGalleryRequestService api, - IDownloader download, - IConsoleWriter console, - IProgressService progressService, - IPackArchiveToCbz pack) + IGalleryImage apiImage, + IProgressFactory progress) { _validator = validator; _api = api; - _download = download; - _console = console; - _progressService = progressService; - _pack = pack; + _apiImage = apiImage; + _progress = progress; } public async Task RunAsync(object options) @@ -41,7 +39,7 @@ public async Task RunAsync(object options) var validator = await _validator.ValidateAsync(opts); if (!validator.IsValid) { - _console.ValidationErrors(validator.Errors); + validator.Errors.PrintValidationExceptions(); return; } @@ -49,28 +47,33 @@ public async Task RunAsync(object options) var selection = responses.FilterByUserSelected(); // Initialise the Progress bar. - _progressService.CreateMasterProgress(selection.Count, $"[task] recommend from id: {opts.Input}"); - var progress = _progressService.GetMasterProgress(); + var mainProgress = _progress.Create(selection.Count, $"Downloading {opts.Input} recommendations..."); foreach (var response in selection) { - _download.CreateSeries(response.Title, opts.Output); - _download.CreateChapter(response, 1); - - var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}"); - _download.SetOnImageDownload = () => + var childProgress = mainProgress + .Spawn(response.TotalPages, $"Downloading {response.Title.GetTitle()}")!; + var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var downloader = new DownloadBuilder(response, 1) { - innerProgress.Tick(); + Request = _apiImage, + Output = output, + OnEachComplete = _ => + { + childProgress.Tick(); + }, + OnComplete = async data => + { + await data.WriteMetadata(Path.Combine(output, "details.json")); + if (opts.Pack) + { + await Compress.ToCbz(output, childProgress); + } + } }; - await _download.Start(); - await _download.Final(); - - if (opts.Pack) - { - await _pack.RunAsync(_download.DownloadRoot, opts.Output, innerProgress); - } - progress.Tick(); + await downloader.Start(); + mainProgress.Tick(); } } } diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs index 0db4d89..208002e 100644 --- a/Commandline/Parsers/SearchCommandService.cs +++ b/Commandline/Parsers/SearchCommandService.cs @@ -1,14 +1,19 @@ +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; +using asuka.Api; using asuka.Commandline.Options; using asuka.Api.Queries; using asuka.Core.Compression; using asuka.Core.Downloader; +using asuka.Core.Extensions; using asuka.Core.Mappings; using asuka.Core.Requests; -using asuka.Output.ProgressService; -using asuka.Output.Writer; +using asuka.Core.Utilities; +using asuka.Output; +using asuka.Output.Progress; using FluentValidation; namespace asuka.Commandline.Parsers; @@ -16,26 +21,20 @@ namespace asuka.Commandline.Parsers; public class SearchCommandService : ICommandLineParser { private readonly IGalleryRequestService _api; + private readonly IGalleryImage _apiImage; private readonly IValidator _validator; - private readonly IConsoleWriter _console; - private readonly IDownloader _download; - private readonly IProgressService _progressService; - private readonly IPackArchiveToCbz _pack; + private readonly IProgressFactory _progress; public SearchCommandService( IGalleryRequestService api, + IGalleryImage apiImage, IValidator validator, - IConsoleWriter console, - IDownloader download, - IProgressService progressService, - IPackArchiveToCbz pack) + IProgressFactory progress) { _api = api; + _apiImage = apiImage; _validator = validator; - _console = console; - _download = download; - _progressService = progressService; - _pack = pack; + _progress = progress; } public async Task RunAsync(object options) @@ -44,7 +43,7 @@ public async Task RunAsync(object options) var validationResult = await _validator.ValidateAsync(opts); if (!validationResult.IsValid) { - _console.ValidationErrors(validationResult.Errors); + validationResult.Errors.PrintValidationExceptions(); return; } @@ -65,35 +64,40 @@ public async Task RunAsync(object options) var responses = await _api.SearchAsync(query); if (responses.Count < 1) { - _console.ErrorLine("No results found."); + Console.WriteLine("No results found."); return; } var selection = responses.FilterByUserSelected(); // Initialise the Progress bar. - _progressService.CreateMasterProgress(selection.Count, "[task] search download"); - var progress = _progressService.GetMasterProgress(); + var mainProgress = _progress.Create(selection.Count, "Downloading found results..."); foreach (var response in selection) { - _download.CreateSeries(response.Title, opts.Output); - _download.CreateChapter(response, 1); - - var innerProgress = _progressService.NestToMaster(response.TotalPages, $"downloading id: {response.Id}"); - _download.SetOnImageDownload = () => + var childProgress = mainProgress + .Spawn(response.TotalPages, $"Downloading {response.Title.GetTitle()}")!; + var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var downloader = new DownloadBuilder(response, 1) { - innerProgress.Tick(); + Output = output, + Request = _apiImage, + OnEachComplete = _ => + { + childProgress.Tick(); + }, + OnComplete = async data => + { + await data.WriteMetadata(Path.Combine(output, "details.json")); + if (opts.Pack) + { + await Compress.ToCbz(output, childProgress); + } + } }; - await _download.Start(); - await _download.Final(); - - if (opts.Pack) - { - await _pack.RunAsync(_download.DownloadRoot, opts.Output, innerProgress); - } - progress.Tick(); + await downloader.Start(); + mainProgress.Tick(); } } } diff --git a/Commandline/Parsers/SeriesCreatorCommandService.cs b/Commandline/Parsers/SeriesCreatorCommandService.cs index 6a13ff9..47178fa 100644 --- a/Commandline/Parsers/SeriesCreatorCommandService.cs +++ b/Commandline/Parsers/SeriesCreatorCommandService.cs @@ -1,13 +1,13 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using asuka.Api; using asuka.Commandline.Options; -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 asuka.Downloader; +using asuka.Output; +using asuka.Output.Progress; using FluentValidation; namespace asuka.Commandline.Parsers; @@ -15,98 +15,47 @@ 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 IGalleryImage _apiImage; private readonly IValidator _validator; + private readonly IProgressFactory _progress; public SeriesCreatorCommandService( IGalleryRequestService api, - IConsoleWriter console, - IDownloader downloader, - IProgressService progress, - IPackArchiveToCbz pack, - IValidator validator) + IGalleryImage apiImage, + IValidator validator, + IProgressFactory progress) { _api = api; - _console = console; - _downloader = downloader; - _progress = progress; - _pack = pack; + _apiImage = apiImage; _validator = validator; + _progress = progress; } - private async Task DownloadTask(IList results) + private async Task HandleArrayTask(IList codes, string output, bool pack) { - _progress.CreateMasterProgress(results.Count, "downloading series"); - var masterProgress = _progress.GetMasterProgress(); - - for (var i = 0; i < results.Count; i++) + var mainProgressBar = _progress.Create(codes.Count, $"Downloading series..."); + var downloader = new SeriesDownloaderBuilder() { - var chapter = results[i]; - try - { - _downloader.CreateChapter(chapter, i + 1); - - var innerProgress = _progress.NestToMaster(chapter.TotalPages, $"downloading chapter {i + 1}"); - _downloader.SetOnImageDownload = () => - { - 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) + GalleryImage = _apiImage, + Output = output, + Progress = mainProgressBar, + Pack = pack, + }; + + foreach (var code in codes) { try { - var galleryResponse = await _api.FetchSingleAsync(chapter); - chapters.Add(galleryResponse); + var galleryResponse = await _api.FetchSingleAsync(code); + downloader.AddChapter(galleryResponse); } catch { - _console.WarningLine($"Skipping: {chapter} because of an error."); + Console.WriteLine($"Skipping: {code} 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.Final(chapters[0]); - - if (pack) - { - await _pack.RunAsync(destinationPath, output, _progress.GetMasterProgress()); - } + await downloader.Start(); } public async Task RunAsync(object options) @@ -116,7 +65,7 @@ public async Task RunAsync(object options) var validationResult = await _validator.ValidateAsync(opts); if (!validationResult.IsValid) { - _console.ValidationErrors(validationResult.Errors); + validationResult.Errors.PrintValidationExceptions(); return; } diff --git a/Core/Compression/Compress.cs b/Core/Compression/Compress.cs new file mode 100644 index 0000000..9c1cc38 --- /dev/null +++ b/Core/Compression/Compress.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; +using asuka.Core.Output.Progress; + +namespace asuka.Core.Compression; + +public static class Compress +{ + private class FilePath + { + public required string Relative { get; init; } + public required string Full { get; init; } + } + + public static async Task ToCbz(string folder, IProgressProvider progress) + { + var directory = new DirectoryInfo(folder); + var parent = Directory.GetParent(directory.FullName) + ?? new DirectoryInfo(Directory.GetDirectoryRoot(directory.FullName)); + + var files = directory.EnumerateFiles("*.*", SearchOption.AllDirectories) + .Select(x => new FilePath + { + Relative = Path.GetRelativePath(parent.FullName, x.FullName), + Full = x.FullName + }) + .ToList(); + var outputFile = Path.Combine(parent.FullName, directory.Name + ".cbz"); + + // Delete existing file + if (File.Exists(outputFile)) + { + File.Delete(outputFile); + } + + var bar = progress.Spawn(files.Count, $"Compressing: {directory.Name}.cbz"); + + try + { + await using var stream = new FileStream(outputFile, FileMode.Create); + using var archive = new ZipArchive(stream, ZipArchiveMode.Create); + + foreach (var file in files) + { + var entry = archive.CreateEntry(file.Relative); + + await using var writer = new BinaryWriter(entry.Open()); + var data = await File.ReadAllBytesAsync(file.Full); + + writer.Write(data); + bar!.Tick(); + } + } + catch + { + bar!.Stop("Failed to compress due to an exception."); + } + } +} diff --git a/Core/Compression/IPackArchiveToCbz.cs b/Core/Compression/IPackArchiveToCbz.cs deleted file mode 100644 index bac44e7..0000000 --- a/Core/Compression/IPackArchiveToCbz.cs +++ /dev/null @@ -1,13 +0,0 @@ -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, IProgressProvider bar); -} diff --git a/Core/Compression/PackArchiveToCbz.cs b/Core/Compression/PackArchiveToCbz.cs deleted file mode 100644 index 6121a1d..0000000 --- a/Core/Compression/PackArchiveToCbz.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.IO; -using System.IO.Compression; -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; - -public class PackArchiveToCbz : IPackArchiveToCbz -{ - private readonly IProgressService _progressService; - - public PackArchiveToCbz(IProgressService progressService) - { - _progressService = progressService; - } - - public async Task RunAsync(string targetFolder, string output, IProgressProvider bar) - { - if (string.IsNullOrEmpty(output)) - { - return; - } - - var files = Directory.GetFiles(targetFolder, "*.*", SearchOption.AllDirectories) - .Select(x => Path.GetRelativePath(output, x)) - .ToList(); - var childBar = _progressService.HookToInstance(bar, files.Count, $"compressing..."); - - // Delete if file exists. - if (File.Exists(output)) - { - File.Delete(output); - } - - 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) - { - var entry = archive.CreateEntry(file); - - await using var writer = new BinaryWriter(entry.Open()); - var fileData = await File.ReadAllBytesAsync(Path.Combine(output, file)); - - writer.Write(fileData); - childBar.Tick($"compressing: {destination}"); - } - } -} diff --git a/Core/Downloader/DownloadBuilder.cs b/Core/Downloader/DownloadBuilder.cs new file mode 100644 index 0000000..4bdc2f8 --- /dev/null +++ b/Core/Downloader/DownloadBuilder.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using asuka.Api; +using asuka.Core.Models; + +namespace asuka.Core.Downloader; + +public class DownloadStatus +{ + public bool Success { get; set; } + public required string FileName { get; set; } +} + +public class DownloadBuilder +{ + private readonly GalleryResult _chapter; + private readonly int _chapterId; + + public required IGalleryImage Request { get; init; } + public required string Output { get; init; } + public Action? OnEachComplete { get; init; } + public Func? OnComplete { get; init; } + + public DownloadBuilder(GalleryResult chapter, int chapterId) + { + _chapter = chapter; + _chapterId = chapterId; + } + + public async Task Start() + { + var chapterPath = Path.Combine(Output, $"ch{_chapterId}"); + if (!Directory.Exists(chapterPath)) + { + Directory.CreateDirectory(chapterPath); + } + + var semaphore = new SemaphoreSlim(2); + var tasks = _chapter.Images + .Select(x => Task.Run(async () => + { + await semaphore.WaitAsync().ConfigureAwait(false); + + var filePath = Path.Combine(chapterPath, x.Filename); + await DownloadImage(filePath, _chapter.MediaId.ToString(), x.ServerFilename); + + semaphore.Release(); + })); + + await Task.WhenAll(tasks) + .ConfigureAwait(false); + + OnComplete?.Invoke(_chapter); + } + + private async Task DownloadImage(string outputPath, string mediaId, string serverFileName) + { + var status = new DownloadStatus + { + Success = true, + FileName = Path.GetFileName(outputPath) + }; + + try + { + if (File.Exists(outputPath)) + { + OnEachComplete?.Invoke(status); + return; + } + + var image = await Request.GetImage(mediaId, serverFileName); + var data = await image.ReadAsByteArrayAsync(); + + await File.WriteAllBytesAsync(outputPath, data) + .ConfigureAwait(false); + + OnEachComplete?.Invoke(status); + } + catch + { + status.Success = false; + OnEachComplete?.Invoke(status); + } + } +} diff --git a/Core/Downloader/Downloader.cs b/Core/Downloader/Downloader.cs deleted file mode 100644 index 3462ecf..0000000 --- a/Core/Downloader/Downloader.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using asuka.Configuration; -using asuka.Api; -using asuka.Core.Models; -using asuka.Core.Utilities; -using asuka.Output; - -namespace asuka.Core.Downloader; - -internal record DownloadTaskDetails -{ - internal GalleryResult Result { get; set; } - internal string ChapterPath { get; set; } - 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 IAppConfigManager _appConfigManager; - private DownloadTaskDetails _details; - - public Downloader(IGalleryImage api, IAppConfigManager appConfigManager) - { - _api = api; - _appConfigManager = appConfigManager; - } - - private static string GetTitle(GalleryTitleResult result) - { - 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) - { - var metaPath = Path.Combine(output, "details.json"); - var serializerOptions = new JsonSerializerOptions { WriteIndented = true }; - var metadata = JsonSerializer - .Serialize(result.ToTachiyomiMetadata(), serializerOptions); - - await File.WriteAllTextAsync(metaPath, metadata).ConfigureAwait(false); - } - - public void CreateSeries(GalleryTitleResult title, string outputPath) - { - var destination = PathUtils.NormalizeJoin(outputPath, GetTitle(title)); - _details = new DownloadTaskDetails - { - ChapterRoot = destination - }; - } - - public void CreateChapter(GalleryResult result, int chapter) - { - _details.ChapterPath = Path.Combine(_details.ChapterRoot, $"ch{chapter}"); - _details.Result = result; - - if (!Directory.Exists(_details.ChapterPath)) - { - Directory.CreateDirectory(_details.ChapterPath!); - } - } - - public void CreateChapter(GalleryResult result) - { - CreateChapter(result, 1); - } - - public Action SetOnImageDownload { private get; set; } = () => { }; - public string DownloadRoot => _details.ChapterRoot; - - public async Task Start() - { - // Break when necessary. - if (_details is null) - { - return; - } - - 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); - } - - public async Task Final() - { - await WriteMetadata(_details.ChapterRoot, _details.Result); - } - - public async Task Final(GalleryResult result) - { - await WriteMetadata(_details.ChapterRoot, result); - } - - private async Task DownloadImage(FetchImageParameter data) - { - var filePath = Path.Combine(data.DestinationPath, data.Page.Filename); - if (File.Exists(filePath)) - { - SetOnImageDownload(); - return; - } - - var image = await _api.GetImage(data.MediaId.ToString(), data.Page.ServerFilename); - var imageData = await image.ReadAsByteArrayAsync(); - - await File.WriteAllBytesAsync(filePath, imageData) - .ConfigureAwait(false); - - SetOnImageDownload(); - } -} diff --git a/Core/Downloader/IDownloader.cs b/Core/Downloader/IDownloader.cs deleted file mode 100644 index d2eecdb..0000000 --- a/Core/Downloader/IDownloader.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Threading.Tasks; -using asuka.Core.Models; - -namespace asuka.Core.Downloader; - -public interface IDownloader -{ - public Action SetOnImageDownload { set; } - public string DownloadRoot { get; } - void CreateSeries(GalleryTitleResult title, string outputPath); - void CreateChapter(GalleryResult result); - void CreateChapter(GalleryResult result, int chapter); - Task Start(); - Task Final(); - Task Final(GalleryResult result); -} diff --git a/Core/Extensions/GalleryResultExtensions.cs b/Core/Extensions/GalleryResultExtensions.cs new file mode 100644 index 0000000..131d260 --- /dev/null +++ b/Core/Extensions/GalleryResultExtensions.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using asuka.Core.Models; +using asuka.Output; + +namespace asuka.Core.Extensions; + +public static class GalleryResultExtensions +{ + public static async Task WriteMetadata(this GalleryResult result, string destination) + { + var serializerOptions = new JsonSerializerOptions { WriteIndented = true }; + var metadata = JsonSerializer + .Serialize(CreateTachiyomiDetails(result), serializerOptions); + + await File.WriteAllTextAsync(destination, metadata).ConfigureAwait(false); + } + + public static string ToFormattedText(this GalleryResult result) + { + var builder = new StringBuilder(); + + builder.AppendLine("Title ========================================="); + builder.AppendLine($"Japanese: {result.Title.Japanese}"); + builder.AppendLine($"English: {result.Title.English}"); + builder.AppendLine($"Pretty: {result.Title.Pretty}"); + + builder.AppendLine("Tags =========================================="); + builder.AppendLine($"Artists: {SafeJoin(result.Artists)}"); + builder.AppendLine($"Parodies: {SafeJoin(result.Parodies)}"); + builder.AppendLine($"Characters: {SafeJoin(result.Characters)}"); + builder.AppendLine($"Categories: {SafeJoin(result.Categories)}"); + builder.AppendLine($"Groups: {SafeJoin(result.Groups)}"); + builder.AppendLine($"Tags: {SafeJoin(result.Tags)}"); + builder.AppendLine($"Language: {SafeJoin(result.Languages)}"); + + builder.AppendLine("==============================================="); + builder.AppendLine($"Total Pages: {result.TotalPages}"); + builder.AppendLine($"URL: https://nhentai.net/g/{result.Id}\n"); + + return builder.ToString(); + } + + private static TachiyomiDetails CreateTachiyomiDetails(GalleryResult result) + { + return new TachiyomiDetails + { + Title = result.Title.GetTitle(), + Author = SafeJoin(result.Artists), + Artist = SafeJoin(result.Artists), + Description = $"Source: https://nhentai.net/g/{result.Id}", + Genres = result.Tags + }; + } + + private static string SafeJoin(IEnumerable? strings) + { + return strings == null ? string.Empty : string.Join(", ", strings); + } +} \ No newline at end of file diff --git a/Core/Extensions/GalleryTitleResultExtension.cs b/Core/Extensions/GalleryTitleResultExtension.cs new file mode 100644 index 0000000..181d954 --- /dev/null +++ b/Core/Extensions/GalleryTitleResultExtension.cs @@ -0,0 +1,38 @@ +using asuka.Core.Models; + +namespace asuka.Core.Extensions; + +public static class GalleryTitleResultExtension +{ + public static string GetTitle(this GalleryTitleResult title) + { + if (!string.IsNullOrEmpty(title.Japanese)) + { + return title.Japanese; + } + if (!string.IsNullOrEmpty(title.English)) + { + return title.English; + } + return !string.IsNullOrEmpty(title.Pretty) ? title.Pretty : "Unknown title"; + } + + public static string? GetTitleByLanguage(this GalleryTitleResult title, TitleLanguages language) + { + if (!string.IsNullOrEmpty(title.Japanese) && language == TitleLanguages.Japanese) + { + return title.Japanese; + } + if (!string.IsNullOrEmpty(title.English) && language == TitleLanguages.English) + { + return title.English; + } + + if (!string.IsNullOrEmpty(title.Pretty) && language == TitleLanguages.Pretty) + { + return title.Pretty; + } + + return null; + } +} \ No newline at end of file diff --git a/Core/Extensions/TitleLanguages.cs b/Core/Extensions/TitleLanguages.cs new file mode 100644 index 0000000..92f394b --- /dev/null +++ b/Core/Extensions/TitleLanguages.cs @@ -0,0 +1,19 @@ +namespace asuka.Core.Extensions; + +public enum TitleLanguages +{ + /// + /// Official title written in native language. This can be either in Japanese or + /// others + /// + Japanese, + /// + /// Translated title + /// + English, + /// + /// Either english or japanese or official title written in native language without + /// other tags + /// + Pretty +} \ No newline at end of file diff --git a/Core/Output/Progress/IProgressProvider.cs b/Core/Output/Progress/IProgressProvider.cs new file mode 100644 index 0000000..f26e7f7 --- /dev/null +++ b/Core/Output/Progress/IProgressProvider.cs @@ -0,0 +1,47 @@ +namespace asuka.Core.Output.Progress; + +public interface IProgressProvider +{ + /// + /// Increments progress by 1 + /// + void Tick(); + + /// + /// Increments progress and changes the maximum progress to a new one. + /// + /// + void Tick(int newMaxTicks); + + /// + /// Increments progress and changes the text on the progress. + /// + /// + void Tick(string message); + + /// + /// Increments progress and changes the maximum progress and the text. + /// + /// + /// + void Tick(int newMaxTicks, string message); + + /// + /// Stops the progress bar. + /// + void Stop(); + + /// + /// Stops the progress with a message + /// + /// + void Stop(string message); + + /// + /// Spawns child progressbar. + /// + /// + /// + /// Child progressbar + IProgressProvider? Spawn(int maxTicks, string message); +} \ No newline at end of file diff --git a/Core/Output/Progress/StealthProgressBar.cs b/Core/Output/Progress/StealthProgressBar.cs new file mode 100644 index 0000000..4418ae0 --- /dev/null +++ b/Core/Output/Progress/StealthProgressBar.cs @@ -0,0 +1,39 @@ +namespace asuka.Core.Output.Progress; + +public class StealthProgressBar : IProgressProvider +{ + public void Tick() + { + // Progress is left empty to print nothing. + } + + public void Tick(int newMaxTicks) + { + // Progress is left empty to print nothing. + } + + public void Tick(string message) + { + // Progress is left empty to print nothing. + } + + public void Tick(int newMaxTicks, string message) + { + // Progress is left empty to print nothing. + } + + public void Stop() + { + // Progress is left empty to print nothing. + } + + public void Stop(string message) + { + // Progress is left empty to print nothing. + } + + public IProgressProvider Spawn(int maxTicks, string message) + { + return new StealthProgressBar(); + } +} \ No newline at end of file diff --git a/Core/Output/Progress/TextProgressBar.cs b/Core/Output/Progress/TextProgressBar.cs new file mode 100644 index 0000000..b18523f --- /dev/null +++ b/Core/Output/Progress/TextProgressBar.cs @@ -0,0 +1,82 @@ +using System; + +namespace asuka.Core.Output.Progress; + +public class TextProgressBar : IProgressProvider +{ + private readonly int _spacing; + private int _progress; + private int _maxTicks; + private string _title; + private bool _stopped; + + public TextProgressBar(int maxTicks, string title) + { + _maxTicks = maxTicks; + _title = title; + } + + private TextProgressBar(int spacing, int maxTicks, string title) + { + _spacing = spacing; + _maxTicks = maxTicks; + _title = title; + } + + public void Tick() + { + if (_stopped) + { + return; + } + + _progress++; + var print = $"{_title} : {_progress} out of {_maxTicks}"; + Console.WriteLine(print.PadLeft(print.Length + _spacing), ' '); + } + + public void Tick(int newMaxTicks) + { + _maxTicks = newMaxTicks; + Tick(); + } + + public void Tick(string message) + { + _title = message; + Tick(); + } + + public void Tick(int newMaxTicks, string message) + { + _maxTicks = newMaxTicks; + _title = message; + Tick(); + } + + public void Stop() + { + _stopped = true; + var print = $"{_title} : {_progress} out of {_maxTicks}"; + Console.WriteLine(print.PadLeft(print.Length + _spacing), ' '); + } + + public void Stop(string message) + { + _stopped = true; + _title = message; + + var print = $"{_title} : {_progress} out of {_maxTicks}"; + Console.WriteLine(print.PadLeft(print.Length + _spacing), ' '); + } + + public IProgressProvider? Spawn(int maxTicks, string message) + { + if (_stopped) + { + return null; + } + + return new TextProgressBar(_spacing + 2, maxTicks, message); + } +} diff --git a/Downloader/SeriesDownloaderBuilder.cs b/Downloader/SeriesDownloaderBuilder.cs new file mode 100644 index 0000000..93aa559 --- /dev/null +++ b/Downloader/SeriesDownloaderBuilder.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using asuka.Api; +using asuka.Core.Compression; +using asuka.Core.Downloader; +using asuka.Core.Extensions; +using asuka.Core.Models; +using asuka.Core.Output.Progress; +using asuka.Core.Utilities; + +namespace asuka.Downloader; + +public class SeriesDownloaderBuilder +{ + private readonly List _chapters; + + public required string Output { get; init; } + public required IGalleryImage GalleryImage { get; init; } + public required IProgressProvider Progress { get; init; } + public int StartingChapter { get; init; } = 1; + public bool Pack { get; init; } + + public SeriesDownloaderBuilder() + { + _chapters = new List(); + } + + public void AddChapter(GalleryResult chapter) + { + _chapters.Add(chapter); + } + + public async Task Start() + { + if (_chapters.Count <= 0) + { + return; + } + + var output = Path.Combine(Output, PathUtils.NormalizeName(_chapters[0].Title.GetTitle())); + + for (var i = 0; i < _chapters.Count; i++) + { + var childProgress = Progress + .Spawn(_chapters[i].TotalPages, $"Downloading chapter {StartingChapter + i}")!; + var downloader = new DownloadBuilder(_chapters[i], StartingChapter + i) + { + Output = output, + Request = GalleryImage, + OnEachComplete = _ => + { + childProgress.Tick(); + } + }; + + await downloader.Start(); + Progress.Tick(); + } + + // Write tachiyomi metadata + await _chapters[0].WriteMetadata(Path.Combine(output, "details.json")); + + if (Pack) + { + await Compress.ToCbz(output, Progress); + } + } +} diff --git a/Installers/InstallServices.cs b/Installers/InstallServices.cs index edeca2d..c0fb835 100644 --- a/Installers/InstallServices.cs +++ b/Installers/InstallServices.cs @@ -1,11 +1,8 @@ using asuka.Commandline; using asuka.Commandline.Parsers; using asuka.Configuration; -using asuka.Core.Compression; -using asuka.Core.Downloader; using asuka.Core.Requests; -using asuka.Output.ProgressService; -using asuka.Output.Writer; +using asuka.Output.Progress; using FluentValidation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -16,13 +13,10 @@ public class InstallServices : IInstaller { public void ConfigureService(IServiceCollection services, IConfiguration configuration) { - services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Command parsers services.AddKeyedScoped("Cmd_Get"); diff --git a/Installers/Refit/ConfigureRefitService.cs b/Installers/Refit/ConfigureRefitService.cs index 71d2752..ea5a7fc 100644 --- a/Installers/Refit/ConfigureRefitService.cs +++ b/Installers/Refit/ConfigureRefitService.cs @@ -81,11 +81,11 @@ private static IAsyncPolicy ConfigureErrorPolicyBuilder( TimeSpan.FromSeconds(1), 5); return builder.WaitAndRetryAsync(delay, (_, span) => { - Colorful.Console.WriteLine($"Retrying in {span.Seconds}"); + Console.WriteLine($"Retrying in {span.Seconds}"); }); } - private static Cookie CreateCookieFromConfig(IConfiguration configuration, string name) + private static Cookie? CreateCookieFromConfig(IConfiguration configuration, string name) { var option = configuration .GetSection("RequestOptions") @@ -97,11 +97,22 @@ private static Cookie CreateCookieFromConfig(IConfiguration configuration, strin return null; } - return new Cookie(option.GetValue("Name"), option.GetValue("Value")) + var cookieName = option.GetValue("Name"); + var cookieValue = option.GetValue("Value"); + var cookieDomain = option.GetValue("Domain"); + var cookieHttpOnlyFlag = option.GetValue("HttpOnly"); + var cookieSecureFlag = option.GetValue("Secure"); + + if (string.IsNullOrEmpty(cookieName) || string.IsNullOrEmpty(cookieValue) || string.IsNullOrEmpty(cookieDomain)) + { + return null; + } + + return new Cookie(cookieName, cookieValue) { - Domain = option.GetValue("Domain"), - HttpOnly = option.GetValue("HttpOnly"), - Secure = option.GetValue("Secure") + Domain = cookieDomain, + HttpOnly = cookieHttpOnlyFlag, + Secure = cookieSecureFlag }; } } diff --git a/Output/DisplayInformation.cs b/Output/DisplayInformation.cs deleted file mode 100644 index 9b7cc36..0000000 --- a/Output/DisplayInformation.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using asuka.Core.Models; - -namespace asuka.Output; - -public static class DisplayInformation -{ - public static string ToReadable(this GalleryResult result) - { - var builder = new StringBuilder(); - - builder.AppendLine("Title ========================================="); - builder.AppendLine($"Japanese: {result.Title.Japanese}"); - builder.AppendLine($"English: {result.Title.English}"); - builder.AppendLine($"Pretty: {result.Title.Pretty}"); - - builder.AppendLine("Tags =========================================="); - builder.AppendLine($"Artists: {SafeJoin(result.Artists)}"); - builder.AppendLine($"Parodies: {SafeJoin(result.Parodies)}"); - builder.AppendLine($"Characters: {SafeJoin(result.Characters)}"); - builder.AppendLine($"Categories: {SafeJoin(result.Categories)}"); - builder.AppendLine($"Groups: {SafeJoin(result.Groups)}"); - builder.AppendLine($"Tags: {SafeJoin(result.Tags)}"); - builder.AppendLine($"Language: {SafeJoin(result.Languages)}"); - - builder.AppendLine("==============================================="); - builder.AppendLine($"Total Pages: {result.TotalPages}"); - builder.AppendLine($"URL: https://nhentai.net/g/{result.Id}\n"); - - return builder.ToString(); - } - - public static TachiyomiDetails ToTachiyomiMetadata(this GalleryResult result) - { - var metadata = new TachiyomiDetails - { - Title = GetTitle(result.Title), - Author = SafeJoin(result.Artists), - Artist = SafeJoin(result.Artists), - Description = $"Source: https://nhentai.net/g/{result.Id}", - Genres = result.Tags - }; - - return metadata; - } - - private static string GetTitle(GalleryTitleResult titles) - { - // By default use the Pretty name, if not present then use English - // if not then Japanese - if (!string.IsNullOrEmpty(titles.Pretty)) - { - return titles.Pretty; - } - return !string.IsNullOrEmpty(titles.English) ? titles.English : titles.Japanese; - } - - private static string SafeJoin(IEnumerable strings) - { - return strings == null ? string.Empty : string.Join(", ", strings); - } -} diff --git a/Output/Progress/IProgressFactory.cs b/Output/Progress/IProgressFactory.cs new file mode 100644 index 0000000..1931472 --- /dev/null +++ b/Output/Progress/IProgressFactory.cs @@ -0,0 +1,9 @@ +using asuka.Core.Output.Progress; + +namespace asuka.Output.Progress; + +public interface IProgressFactory +{ + IProgressProvider Create(int maxTicks, string message); + IProgressProvider Create(ProgressTypes type, int maxTicks, string message); +} diff --git a/Output/Progress/ProgressFactory.cs b/Output/Progress/ProgressFactory.cs new file mode 100644 index 0000000..bb3d238 --- /dev/null +++ b/Output/Progress/ProgressFactory.cs @@ -0,0 +1,39 @@ +using asuka.Configuration; +using asuka.Core.Output.Progress; + +namespace asuka.Output.Progress; + +public class ProgressFactory : IProgressFactory +{ + private readonly IAppConfigManager _config; + + public ProgressFactory(IAppConfigManager config) + { + _config = config; + } + + public IProgressProvider Create(int maxTicks, string message) + { + return _config.GetValue("tui.progress") switch + { + "stealth" => Create(ProgressTypes.Stealth, maxTicks, message), + "text" => Create(ProgressTypes.Text, maxTicks, message), + _ => Create(ProgressTypes.Progress, maxTicks, message) + }; + } + + public IProgressProvider Create(ProgressTypes type, int maxTicks, string message) + { + if (type == ProgressTypes.Stealth) + { + return new StealthProgressBar(); + } + + if (type == ProgressTypes.Text) + { + return new TextProgressBar(maxTicks, message); + } + + return new ShellProgressBarWrapper(maxTicks, message); + } +} \ No newline at end of file diff --git a/Output/Progress/ProgressTypes.cs b/Output/Progress/ProgressTypes.cs new file mode 100644 index 0000000..ff68b1e --- /dev/null +++ b/Output/Progress/ProgressTypes.cs @@ -0,0 +1,8 @@ +namespace asuka.Output.Progress; + +public enum ProgressTypes +{ + Progress, + Text, + Stealth +} \ No newline at end of file diff --git a/Output/Progress/ShellProgressBarOptions.cs b/Output/Progress/ShellProgressBarOptions.cs new file mode 100644 index 0000000..5946f94 --- /dev/null +++ b/Output/Progress/ShellProgressBarOptions.cs @@ -0,0 +1,14 @@ +using System; +using ShellProgressBar; + +namespace asuka.Output.Progress; + +public class ShellProgressBarOptions +{ + public static ProgressBarOptions Options => new ProgressBarOptions + { + ForegroundColor = ConsoleColor.Yellow, + ForegroundColorDone = ConsoleColor.Green, + ProgressCharacter = '-' + }; +} diff --git a/Output/Progress/ShellProgressBarWrapper.cs b/Output/Progress/ShellProgressBarWrapper.cs new file mode 100644 index 0000000..190e809 --- /dev/null +++ b/Output/Progress/ShellProgressBarWrapper.cs @@ -0,0 +1,56 @@ +using asuka.Core.Output.Progress; +using ShellProgressBar; + +namespace asuka.Output.Progress; + +public class ShellProgressBarWrapper : IProgressProvider +{ + private readonly IProgressBar _progress; + + public ShellProgressBarWrapper(int maxTicks, string message) + { + _progress = new ProgressBar(maxTicks, message, ShellProgressBarOptions.Options); + } + + private ShellProgressBarWrapper(IProgressBar progress, int maxTicks, string message) + { + _progress = progress.Spawn(maxTicks, message, ShellProgressBarOptions.Options); + } + + public void Tick() + { + _progress.Tick(); + } + + public void Tick(int newMaxTicks) + { + _progress.Tick(newMaxTicks); + } + + public void Tick(string message) + { + _progress.Tick(message); + } + + public void Tick(int newMaxTicks, string message) + { + _progress.Tick(newMaxTicks, message); + } + + public void Stop() + { + var progress = _progress.AsProgress(); + progress.Report(1); + } + + public void Stop(string message) + { + _progress.Message = message; + Stop(); + } + + public IProgressProvider Spawn(int maxTicks, string message) + { + return new ShellProgressBarWrapper(_progress, maxTicks, message); + } +} diff --git a/Output/ProgressService/IProgressService.cs b/Output/ProgressService/IProgressService.cs deleted file mode 100644 index d3f7fbc..0000000 --- a/Output/ProgressService/IProgressService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using asuka.Output.ProgressService.Providers; - -namespace asuka.Output.ProgressService; - -public interface IProgressService -{ - void CreateMasterProgress(int totalTicks, string title); - IProgressProvider NestToMaster(int totalTicks, string title); - IProgressProvider GetMasterProgress(); - bool HasMasterProgress(); - IProgressProvider HookToInstance(IProgressProvider bar, int totalTicks, string title); -} diff --git a/Output/ProgressService/ProgressBarConfiguration.cs b/Output/ProgressService/ProgressBarConfiguration.cs deleted file mode 100644 index 2f7b5ea..0000000 --- a/Output/ProgressService/ProgressBarConfiguration.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using ShellProgressBar; - -namespace asuka.Output.ProgressService; - -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/ProgressProviderFactory.cs b/Output/ProgressService/ProgressProviderFactory.cs deleted file mode 100644 index 80c6c75..0000000 --- a/Output/ProgressService/ProgressProviderFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 67e33d7..0000000 --- a/Output/ProgressService/ProgressService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using asuka.Configuration; -using asuka.Output.ProgressService.Providers; - -namespace asuka.Output.ProgressService; - -public class ProgressService : IProgressService -{ - private IProgressProvider _progressBar; - private readonly IAppConfigManager _appConfig; - - public ProgressService(IAppConfigManager appConfig) - { - _appConfig = appConfig; - } - - public void CreateMasterProgress(int totalTicks, string title) - { - _progressBar = ProgressProviderFactory - .GetProvider(_appConfig.GetValue("tui.progress"), totalTicks, title, ProgressBarConfiguration.BarOption); - } - - public IProgressProvider GetMasterProgress() - { - return _progressBar; - } - - public bool HasMasterProgress() - { - return _progressBar is not null; - } - - public IProgressProvider NestToMaster(int totalTicks, string title) - { - if (HasMasterProgress()) - { - return _progressBar.Spawn(totalTicks, title, ProgressBarConfiguration.BarOption); - } - - CreateMasterProgress(totalTicks, title); - return GetMasterProgress(); - } - - 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 deleted file mode 100644 index 8b3e085..0000000 --- a/Output/ProgressService/Providers/ExternalProgressProvider.cs +++ /dev/null @@ -1,44 +0,0 @@ -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() - { - _progress.Tick(); - } - - public void Tick(string message) - { - _progress.Tick(message); - } - - public void Tick(int newTickCount) - { - _progress.Tick(newTickCount); - } - - public void Tick(int newTickCount, string message) - { - _progress.Tick(newTickCount, message); - } -} diff --git a/Output/ProgressService/Providers/IProgressProvider.cs b/Output/ProgressService/Providers/IProgressProvider.cs deleted file mode 100644 index 2b5691b..0000000 --- a/Output/ProgressService/Providers/IProgressProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace asuka.Output.ProgressService.Providers; - -public interface IProgressProvider -{ - IProgressProvider Spawn(int maxTicks, string title, object options); - void Tick(); - void Tick(string message); - void Tick(int newTickCount); - void Tick(int newTickCount, string message); -} diff --git a/Output/ProgressService/Providers/TextProgressProvider.cs b/Output/ProgressService/Providers/TextProgressProvider.cs deleted file mode 100644 index 0d530a1..0000000 --- a/Output/ProgressService/Providers/TextProgressProvider.cs +++ /dev/null @@ -1,63 +0,0 @@ -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() - { - Tick(null); - } - - public void Tick(string message) - { - _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) - { - Tick(newTickCount, null); - } - - public void Tick(int newTickCount, string message) - { - _maxTick = newTickCount; - Tick(message); - } -} diff --git a/Output/ProgressService/Providers/VoidProgressProvider.cs b/Output/ProgressService/Providers/VoidProgressProvider.cs deleted file mode 100644 index cc7860e..0000000 --- a/Output/ProgressService/Providers/VoidProgressProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace asuka.Output.ProgressService.Providers; - -public class VoidProgressProvider : IProgressProvider -{ - public IProgressProvider Spawn(int maxTicks, string title, object options) - { - return new VoidProgressProvider(); - } - - public void Tick() - { - } - - public void Tick(string message) - { - } - - public void Tick(int newTickCount) - { - } - - public void Tick(int newTickCount, string message) - { - } -} diff --git a/Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs b/Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs deleted file mode 100644 index a7daee7..0000000 --- a/Output/ProgressService/Providers/Wrappers/IProgressWrapper.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace asuka.Output.ProgressService.Providers.Wrappers; - -public interface IProgressWrapper -{ - IProgressWrapper Spawn(int maxTicks, string title, object options); - void Tick(); - void Tick(string message); - void Tick(int newTickCount); - void Tick(int newTickCount, string message); -} diff --git a/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs b/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs deleted file mode 100644 index 1f9c58e..0000000 --- a/Output/ProgressService/Providers/Wrappers/ProgressWrapper.cs +++ /dev/null @@ -1,53 +0,0 @@ -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) - { - _progress.Tick(newTickCount); - } - - public void Tick(int newTickCount, string message) - { - _progress.Tick(newTickCount, message); - } - - public void Tick() - { - _progress.Tick(); - } - - public void Tick(string message) - { - _progress.Tick(message); - } -} diff --git a/Output/ValidationErrorExtensions.cs b/Output/ValidationErrorExtensions.cs new file mode 100644 index 0000000..9739687 --- /dev/null +++ b/Output/ValidationErrorExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using FluentValidation.Results; + +namespace asuka.Output; + +public static class ValidationErrorExtensions +{ + public static void PrintValidationExceptions(this List failures) + { + foreach (var failure in failures) + { + Console.WriteLine(failure.ErrorMessage); + } + } +} diff --git a/Output/Writer/ConsoleWriter.cs b/Output/Writer/ConsoleWriter.cs deleted file mode 100644 index d8c993e..0000000 --- a/Output/Writer/ConsoleWriter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; -using asuka.Configuration; -using FluentValidation.Results; -using Console = Colorful.Console; - -namespace asuka.Output.Writer; - -public class ConsoleWriter : IConsoleWriter -{ - private readonly IAppConfigManager _appConfigManager; - - public ConsoleWriter(IAppConfigManager appConfigManager) - { - _appConfigManager = appConfigManager; - } - - private Color GetColor(Color forWhiteTheme, Color forDarkTheme) - { - return _appConfigManager.GetValue("color.theme") == "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/Writer/IConsoleWriter.cs b/Output/Writer/IConsoleWriter.cs deleted file mode 100644 index 7bea40d..0000000 --- a/Output/Writer/IConsoleWriter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using FluentValidation.Results; - -namespace asuka.Output.Writer; - -public interface IConsoleWriter -{ - void WriteLine(object message); - void WarningLine(object message); - void ErrorLine(string message); - void SuccessLine(string message); - void ValidationErrors(IEnumerable errors); -} diff --git a/asuka.csproj b/asuka.csproj index 0a5e365..7e34a68 100644 --- a/asuka.csproj +++ b/asuka.csproj @@ -22,7 +22,6 @@ - From ad09fde5634ab9825ef62e50acd9a9867ffa96ad Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:34:55 +0900 Subject: [PATCH 5/8] [skip ci] bump to 1.3.0.0 --- asuka.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/asuka.csproj b/asuka.csproj index 7e34a68..45315b3 100644 --- a/asuka.csproj +++ b/asuka.csproj @@ -3,15 +3,15 @@ Exe net8.0 - 1.2.0.0 - 1.2.0.0 + 1.3.0.0 + 1.3.0.0 appicon.ico Release;Debug x64 - 1.2.0 + 1.3.0 Aiko Fujimoto Cross Platform nhentai downloader - Copyright 2021 Aiko Fujimoto + Copyright 2023 Aiko Fujimoto Aiko Fujimoto https://github.com/aikoofujimotoo/asuka AppIcon.png From efdd1b7cd5a68aad5d4857639c2e1ce1228e8719 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sat, 23 Dec 2023 02:39:58 +0900 Subject: [PATCH 6/8] Add another normalization rules in folder name --- Core/Utilities/PathUtils.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/Utilities/PathUtils.cs b/Core/Utilities/PathUtils.cs index c413838..c8bca8e 100644 --- a/Core/Utilities/PathUtils.cs +++ b/Core/Utilities/PathUtils.cs @@ -71,6 +71,12 @@ public static class PathUtils }, { "=", "=" + }, + { + ".", "。" + }, + { + "\t", "_" } }; #endregion From 060ccd829a01bb2403421f611ec31b7693978618 Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sat, 23 Dec 2023 02:52:06 +0900 Subject: [PATCH 7/8] Fix regressions Fixes the following regressions: - details.json not being written on get command - Download fails when output option is not specified --- Commandline/Parsers/FileCommandService.cs | 2 +- Commandline/Parsers/GetCommandService.cs | 4 ++-- Commandline/Parsers/RandomCommandService.cs | 2 +- Commandline/Parsers/RecommendCommandService.cs | 2 +- Commandline/Parsers/SearchCommandService.cs | 2 +- Core/Utilities/PathUtils.cs | 11 ++++++++--- Downloader/SeriesDownloaderBuilder.cs | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Commandline/Parsers/FileCommandService.cs b/Commandline/Parsers/FileCommandService.cs index 4f0904e..4b1ac8e 100644 --- a/Commandline/Parsers/FileCommandService.cs +++ b/Commandline/Parsers/FileCommandService.cs @@ -68,7 +68,7 @@ public async Task RunAsync(object options) var childProgress = mainProgress.Spawn(response.TotalPages, $"Downloading: {response.Title.GetTitle()}")!; - var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var output = PathUtils.Join(opts.Output, response.Title.GetTitle()); var downloader = new DownloadBuilder(response, 1) { Request = _apiImage, diff --git a/Commandline/Parsers/GetCommandService.cs b/Commandline/Parsers/GetCommandService.cs index 58af890..a16957a 100644 --- a/Commandline/Parsers/GetCommandService.cs +++ b/Commandline/Parsers/GetCommandService.cs @@ -47,7 +47,7 @@ private async Task DownloadTask(int input, bool pack, bool readOnly, string outp var mainProgress = _progress.Create(response.TotalPages, $"Downloading Manga: {response.Title.GetTitle()}"); - var output = Path.Combine(outputPath, PathUtils.NormalizeName(response.Title.GetTitle())); + var output = PathUtils.Join(outputPath, response.Title.GetTitle()); var downloader = new DownloadBuilder(response, 1) { Request = _apiImage, @@ -58,7 +58,7 @@ private async Task DownloadTask(int input, bool pack, bool readOnly, string outp }, OnComplete = async gallery => { - await gallery.WriteMetadata(Path.Combine(outputPath, "details.json")); + await gallery.WriteMetadata(Path.Combine(output, "details.json")); if (pack) { await Compress.ToCbz(output, mainProgress); diff --git a/Commandline/Parsers/RandomCommandService.cs b/Commandline/Parsers/RandomCommandService.cs index 44d918b..ff0d2b9 100644 --- a/Commandline/Parsers/RandomCommandService.cs +++ b/Commandline/Parsers/RandomCommandService.cs @@ -51,7 +51,7 @@ public async Task RunAsync(object options) var mainProgress = _progress.Create(response.TotalPages, $"Downloading Manga: {response.Title.GetTitle()}"); - var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var output = PathUtils.Join(opts.Output, response.Title.GetTitle()); var downloader = new DownloadBuilder(response, 1) { Output = output, diff --git a/Commandline/Parsers/RecommendCommandService.cs b/Commandline/Parsers/RecommendCommandService.cs index 9818b77..2c48921 100644 --- a/Commandline/Parsers/RecommendCommandService.cs +++ b/Commandline/Parsers/RecommendCommandService.cs @@ -53,7 +53,7 @@ public async Task RunAsync(object options) { var childProgress = mainProgress .Spawn(response.TotalPages, $"Downloading {response.Title.GetTitle()}")!; - var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var output = PathUtils.Join(opts.Output, response.Title.GetTitle()); var downloader = new DownloadBuilder(response, 1) { Request = _apiImage, diff --git a/Commandline/Parsers/SearchCommandService.cs b/Commandline/Parsers/SearchCommandService.cs index 208002e..c3eb956 100644 --- a/Commandline/Parsers/SearchCommandService.cs +++ b/Commandline/Parsers/SearchCommandService.cs @@ -77,7 +77,7 @@ public async Task RunAsync(object options) { var childProgress = mainProgress .Spawn(response.TotalPages, $"Downloading {response.Title.GetTitle()}")!; - var output = Path.Combine(opts.Output, PathUtils.NormalizeName(response.Title.GetTitle())); + var output = PathUtils.Join(opts.Output, response.Title.GetTitle()); var downloader = new DownloadBuilder(response, 1) { Output = output, diff --git a/Core/Utilities/PathUtils.cs b/Core/Utilities/PathUtils.cs index c8bca8e..5a6ea51 100644 --- a/Core/Utilities/PathUtils.cs +++ b/Core/Utilities/PathUtils.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -87,15 +88,19 @@ public static class PathUtils /// /// /// - public static string NormalizeJoin(string outputPath, string folderName) + public static string Join(string outputPath, string folderName) { + var destinationPath = string.IsNullOrEmpty(outputPath) + ? Environment.CurrentDirectory + : outputPath; + var normalizedFolderName = NormalizeName(folderName); - var chapterRoot = Path.Combine(outputPath, normalizedFolderName); + var chapterRoot = Path.Combine(destinationPath, normalizedFolderName); return chapterRoot; } - public static string NormalizeName(string folderName) + private static string NormalizeName(string folderName) { var normalizedFolderName = $"{folderName}"; normalizedFolderName = SymbolDictionary diff --git a/Downloader/SeriesDownloaderBuilder.cs b/Downloader/SeriesDownloaderBuilder.cs index 93aa559..cf7ba58 100644 --- a/Downloader/SeriesDownloaderBuilder.cs +++ b/Downloader/SeriesDownloaderBuilder.cs @@ -38,7 +38,7 @@ public async Task Start() return; } - var output = Path.Combine(Output, PathUtils.NormalizeName(_chapters[0].Title.GetTitle())); + var output = PathUtils.Join(Output, _chapters[0].Title.GetTitle()); for (var i = 0; i < _chapters.Count; i++) { From 33524d542437ee165b19fb3bbc89d2f28555cdeb Mon Sep 17 00:00:00 2001 From: fumiichan <35658068+fumiichan@users.noreply.github.com> Date: Sat, 23 Dec 2023 03:12:10 +0900 Subject: [PATCH 8/8] Address null checks --- Api/Queries/SearchQuery.cs | 4 ++-- Api/Responses/GalleryImageObjectResponse.cs | 3 ++- Api/Responses/GalleryImageResponse.cs | 1 + Api/Responses/GalleryListResponse.cs | 1 + Api/Responses/GalleryResponse.cs | 5 +++-- Api/Responses/GalleryTagResponse.cs | 1 + Api/Responses/GalleryTitleResponse.cs | 1 + Commandline/Options/ConfigureOptions.cs | 1 + Commandline/Options/CookieConfigureOptions.cs | 1 + Commandline/Options/FileCommandOptions.cs | 1 + Commandline/Options/GetOptions.cs | 1 + Commandline/Options/RandomOptions.cs | 1 + Commandline/Options/RecommendOptions.cs | 1 + Commandline/Options/SearchOptions.cs | 1 + .../Options/SeriesCreatorCommandOptions.cs | 1 + Configuration/AppConfigManager.cs | 2 +- Configuration/ApplicationSettingsModel.cs | 22 +++++++++---------- Configuration/CookieDump.cs | 8 +++---- Configuration/RequestConfigurator.cs | 4 +++- .../ContractToGalleryResultModelMapping.cs | 2 ++ .../ContractToUserSelectedModelMapping.cs | 9 ++------ Core/Models/GalleryImageResult.cs | 4 ++-- Core/Models/GalleryResult.cs | 18 +++++++-------- Core/Models/GallerySeriesChaptersModel.cs | 12 ---------- Core/Models/GallerySeriesModel.cs | 19 ---------------- Core/Models/GalleryTitleResult.cs | 6 ++--- Core/Models/TachiyomiDetails.cs | 10 ++++----- 27 files changed, 61 insertions(+), 79 deletions(-) delete mode 100644 Core/Models/GallerySeriesChaptersModel.cs delete mode 100644 Core/Models/GallerySeriesModel.cs diff --git a/Api/Queries/SearchQuery.cs b/Api/Queries/SearchQuery.cs index d325a78..91f6bd5 100644 --- a/Api/Queries/SearchQuery.cs +++ b/Api/Queries/SearchQuery.cs @@ -5,11 +5,11 @@ namespace asuka.Api.Queries; public record SearchQuery { [AliasAs("query")] - public string Queries { get; init; } + public required string Queries { get; init; } [AliasAs("page")] public int PageNumber { get; init; } [AliasAs("sort")] - public string Sort { get; init; } + public required string Sort { get; init; } } diff --git a/Api/Responses/GalleryImageObjectResponse.cs b/Api/Responses/GalleryImageObjectResponse.cs index f85ccd6..8abda65 100644 --- a/Api/Responses/GalleryImageObjectResponse.cs +++ b/Api/Responses/GalleryImageObjectResponse.cs @@ -3,8 +3,9 @@ namespace asuka.Api.Responses; +#nullable disable public record GalleryImageObjectResponse { [JsonPropertyName("pages")] - public IReadOnlyList Images { get; set; } + public IReadOnlyList Images { get; set; } } diff --git a/Api/Responses/GalleryImageResponse.cs b/Api/Responses/GalleryImageResponse.cs index 96dbd7a..b26652e 100644 --- a/Api/Responses/GalleryImageResponse.cs +++ b/Api/Responses/GalleryImageResponse.cs @@ -2,6 +2,7 @@ namespace asuka.Api.Responses; +#nullable disable public record GalleryImageResponse { [JsonPropertyName("t")] diff --git a/Api/Responses/GalleryListResponse.cs b/Api/Responses/GalleryListResponse.cs index 2de0de4..f6b1480 100644 --- a/Api/Responses/GalleryListResponse.cs +++ b/Api/Responses/GalleryListResponse.cs @@ -3,6 +3,7 @@ namespace asuka.Api.Responses; +#nullable disable public record GalleryListResponse { [JsonPropertyName("result")] diff --git a/Api/Responses/GalleryResponse.cs b/Api/Responses/GalleryResponse.cs index 0e153d0..811dc90 100644 --- a/Api/Responses/GalleryResponse.cs +++ b/Api/Responses/GalleryResponse.cs @@ -3,6 +3,7 @@ namespace asuka.Api.Responses; +#nullable disable public record GalleryResponse { [JsonPropertyName("id")] @@ -14,10 +15,10 @@ public record GalleryResponse public int MediaId { get; set; } [JsonPropertyName("title")] - public GalleryTitleResponse Title { get; set; } + public required GalleryTitleResponse Title { get; set; } [JsonPropertyName("images")] - public GalleryImageObjectResponse Images { get; set; } + public required GalleryImageObjectResponse Images { get; set; } [JsonPropertyName("tags")] public IReadOnlyList Tags { get; set; } diff --git a/Api/Responses/GalleryTagResponse.cs b/Api/Responses/GalleryTagResponse.cs index a48af67..62e3393 100644 --- a/Api/Responses/GalleryTagResponse.cs +++ b/Api/Responses/GalleryTagResponse.cs @@ -2,6 +2,7 @@ namespace asuka.Api.Responses; +#nullable disable public record GalleryTagResponse { [JsonPropertyName("id")] diff --git a/Api/Responses/GalleryTitleResponse.cs b/Api/Responses/GalleryTitleResponse.cs index da5f662..ba3f145 100644 --- a/Api/Responses/GalleryTitleResponse.cs +++ b/Api/Responses/GalleryTitleResponse.cs @@ -2,6 +2,7 @@ namespace asuka.Api.Responses; +#nullable disable public record GalleryTitleResponse { [JsonPropertyName("japanese")] diff --git a/Commandline/Options/ConfigureOptions.cs b/Commandline/Options/ConfigureOptions.cs index 4177925..0663d5c 100644 --- a/Commandline/Options/ConfigureOptions.cs +++ b/Commandline/Options/ConfigureOptions.cs @@ -2,6 +2,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("config", HelpText = "Configure the client")] public record ConfigureOptions { diff --git a/Commandline/Options/CookieConfigureOptions.cs b/Commandline/Options/CookieConfigureOptions.cs index fe66643..4072c65 100644 --- a/Commandline/Options/CookieConfigureOptions.cs +++ b/Commandline/Options/CookieConfigureOptions.cs @@ -2,6 +2,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("cookie", HelpText = "Configure cookies and User Agent")] public record CookieConfigureOptions { diff --git a/Commandline/Options/FileCommandOptions.cs b/Commandline/Options/FileCommandOptions.cs index 47551ca..d5305e5 100644 --- a/Commandline/Options/FileCommandOptions.cs +++ b/Commandline/Options/FileCommandOptions.cs @@ -2,6 +2,7 @@ namespace asuka.Commandline.Options; +#nullable disable [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 787750c..e1968a6 100644 --- a/Commandline/Options/GetOptions.cs +++ b/Commandline/Options/GetOptions.cs @@ -3,6 +3,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("get", HelpText = "Download a Single Gallery from URL.")] public record GetOptions : ICommonOptions { diff --git a/Commandline/Options/RandomOptions.cs b/Commandline/Options/RandomOptions.cs index 19ea0ca..f03d15a 100644 --- a/Commandline/Options/RandomOptions.cs +++ b/Commandline/Options/RandomOptions.cs @@ -2,6 +2,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("random", HelpText = "Randomly pick a gallery.")] public record RandomOptions : ICommonOptions { diff --git a/Commandline/Options/RecommendOptions.cs b/Commandline/Options/RecommendOptions.cs index eedec9e..a848a3e 100644 --- a/Commandline/Options/RecommendOptions.cs +++ b/Commandline/Options/RecommendOptions.cs @@ -2,6 +2,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("recommend", HelpText = "Download recommendation from the gallery URL.")] public record RecommendOptions : ICommonOptions { diff --git a/Commandline/Options/SearchOptions.cs b/Commandline/Options/SearchOptions.cs index 09e4483..2105147 100644 --- a/Commandline/Options/SearchOptions.cs +++ b/Commandline/Options/SearchOptions.cs @@ -3,6 +3,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("search", HelpText = "Search something in the gallery")] public record SearchOptions : ICommonOptions { diff --git a/Commandline/Options/SeriesCreatorCommandOptions.cs b/Commandline/Options/SeriesCreatorCommandOptions.cs index 0ea257e..313bba6 100644 --- a/Commandline/Options/SeriesCreatorCommandOptions.cs +++ b/Commandline/Options/SeriesCreatorCommandOptions.cs @@ -3,6 +3,7 @@ namespace asuka.Commandline.Options; +#nullable disable [Verb("series", HelpText = "Construct series")] public record SeriesCreatorCommandOptions: ICommonOptions { diff --git a/Configuration/AppConfigManager.cs b/Configuration/AppConfigManager.cs index d2fb029..3797aa4 100644 --- a/Configuration/AppConfigManager.cs +++ b/Configuration/AppConfigManager.cs @@ -78,7 +78,7 @@ public void SetValue(string key, string value) public string GetValue(string key) { - return _config.TryGetValue(key, out var data) ? data : null; + return _config.GetValueOrDefault(key) ?? ""; } public IReadOnlyList<(string, string)> GetAllValues() diff --git a/Configuration/ApplicationSettingsModel.cs b/Configuration/ApplicationSettingsModel.cs index 5f7bacb..ac5d51f 100644 --- a/Configuration/ApplicationSettingsModel.cs +++ b/Configuration/ApplicationSettingsModel.cs @@ -2,33 +2,33 @@ namespace asuka.Configuration; public record CookieMetadata { - public string Name { get; set; } - public string Value { get; set; } - public string Domain { get; set; } + 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 CookieMetadata CloudflareClearance { get; set; } = new(); + public CookieMetadata CsrfToken { get; set; } = new(); } public record RequestOptions { - public CookieStore Cookies { get; set; } - public string UserAgent { get; set; } + public CookieStore Cookies { get; set; } = new(); + public string UserAgent { get; set; } = ""; } public record Addresses { - public string ApiBaseAddress { get; set; } - public string ImageBaseAddress { get; set; } + public string ApiBaseAddress { get; set; } = ""; + public string ImageBaseAddress { get; set; } = ""; } public record ApplicationSettingsModel { - public Addresses BaseAddresses { get; set; } - public RequestOptions RequestOptions { get; set; } + public Addresses BaseAddresses { get; set; } = new(); + public RequestOptions RequestOptions { get; init; } = new(); } diff --git a/Configuration/CookieDump.cs b/Configuration/CookieDump.cs index eb206c9..3a68e9f 100644 --- a/Configuration/CookieDump.cs +++ b/Configuration/CookieDump.cs @@ -4,8 +4,8 @@ namespace asuka.Configuration; public record CookieDump { - [JsonPropertyName("domain")] - public string Domain { get; set; } + [JsonPropertyName("domain")] + public string Domain { get; set; } = ""; [JsonPropertyName("httpOnly")] public bool HttpOnly { get; set; } @@ -14,8 +14,8 @@ public record CookieDump public bool Secure { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string Name { get; set; } = ""; [JsonPropertyName("value")] - public string Value { get; set; } + public string Value { get; set; } = ""; } diff --git a/Configuration/RequestConfigurator.cs b/Configuration/RequestConfigurator.cs index 61617ba..932b308 100644 --- a/Configuration/RequestConfigurator.cs +++ b/Configuration/RequestConfigurator.cs @@ -18,7 +18,9 @@ public RequestConfigurator() private async Task ReadSettings() { var file = await File.ReadAllTextAsync(_appSettingsPath); - return JsonSerializer.Deserialize(file); + var config = JsonSerializer.Deserialize(file); + + return config ?? new ApplicationSettingsModel(); } private async Task WriteSettings(ApplicationSettingsModel settings) diff --git a/Core/Mappings/ContractToGalleryResultModelMapping.cs b/Core/Mappings/ContractToGalleryResultModelMapping.cs index 4570012..3cb6990 100644 --- a/Core/Mappings/ContractToGalleryResultModelMapping.cs +++ b/Core/Mappings/ContractToGalleryResultModelMapping.cs @@ -23,6 +23,8 @@ public static GalleryResult ToGalleryResult(this GalleryResponse response) Characters = response.Tags.GetTagByGroup("character"), Tags = response.Tags.GetTagByGroup("tag"), Categories = response.Tags.GetTagByGroup("category"), + // Not sure about this one. + Groups = response.Tags.GetTagByGroup("group"), Languages = response.Tags.GetTagByGroup("language"), TotalPages = response.TotalPages }; diff --git a/Core/Mappings/ContractToUserSelectedModelMapping.cs b/Core/Mappings/ContractToUserSelectedModelMapping.cs index 20c917f..e09af52 100644 --- a/Core/Mappings/ContractToUserSelectedModelMapping.cs +++ b/Core/Mappings/ContractToUserSelectedModelMapping.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using asuka.Core.Extensions; using asuka.Core.Models; using Sharprompt; @@ -11,13 +12,7 @@ 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; - }); + textSelector: result => result.Title.GetTitle()); return selection.ToList(); } diff --git a/Core/Models/GalleryImageResult.cs b/Core/Models/GalleryImageResult.cs index 323cc11..56bdef7 100644 --- a/Core/Models/GalleryImageResult.cs +++ b/Core/Models/GalleryImageResult.cs @@ -2,6 +2,6 @@ namespace asuka.Core.Models; public record GalleryImageResult { - public string ServerFilename { get; init; } - public string Filename { get; init; } + public required string ServerFilename { get; init; } + public required string Filename { get; init; } } diff --git a/Core/Models/GalleryResult.cs b/Core/Models/GalleryResult.cs index 81c2cdd..5b5b274 100644 --- a/Core/Models/GalleryResult.cs +++ b/Core/Models/GalleryResult.cs @@ -6,14 +6,14 @@ 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 required GalleryTitleResult Title { get; init; } + public required IReadOnlyList Images { get; init; } + public required IReadOnlyList Artists { get; init; } + public required IReadOnlyList Parodies { get; init; } + public required IReadOnlyList Characters { get; init; } + public required IReadOnlyList Tags { get; init; } + public required IReadOnlyList Categories { get; init; } + public required IReadOnlyList Languages { get; init; } + public required IReadOnlyList Groups { get; init; } public int TotalPages { get; init; } } diff --git a/Core/Models/GallerySeriesChaptersModel.cs b/Core/Models/GallerySeriesChaptersModel.cs deleted file mode 100644 index e0fc5a1..0000000 --- a/Core/Models/GallerySeriesChaptersModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 36007bd..0000000 --- a/Core/Models/GallerySeriesModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -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/Models/GalleryTitleResult.cs b/Core/Models/GalleryTitleResult.cs index c63c643..77f28e2 100644 --- a/Core/Models/GalleryTitleResult.cs +++ b/Core/Models/GalleryTitleResult.cs @@ -2,7 +2,7 @@ namespace asuka.Core.Models; public record GalleryTitleResult { - public string Japanese { get; init; } - public string English { get; init; } - public string Pretty { get; init; } + public string? Japanese { get; init; } + public string? English { get; init; } + public string? Pretty { get; init; } } diff --git a/Core/Models/TachiyomiDetails.cs b/Core/Models/TachiyomiDetails.cs index a0a3be6..23d66bd 100644 --- a/Core/Models/TachiyomiDetails.cs +++ b/Core/Models/TachiyomiDetails.cs @@ -6,19 +6,19 @@ namespace asuka.Core.Models; public record TachiyomiDetails { [JsonPropertyName("title")] - public string Title { get; init; } + public required string Title { get; init; } [JsonPropertyName("author")] - public string Author { get; init; } + public required string Author { get; init; } [JsonPropertyName("artist")] - public string Artist { get; init; } + public required string Artist { get; init; } [JsonPropertyName("description")] - public string Description { get; init; } + public required string Description { get; init; } [JsonPropertyName("genre")] - public IReadOnlyList Genres { get; init; } + public required IReadOnlyList Genres { get; init; } [JsonPropertyName("status")] public string Status { get; init; } = "2";