diff --git a/CLI/CLI.csproj b/CLI/CLI.csproj
deleted file mode 100644
index c905c4a..0000000
--- a/CLI/CLI.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- Exe
- net8.0
- enable
- enable
- 12
-
-
-
-
-
-
-
diff --git a/CLI/Program.cs b/CLI/Program.cs
deleted file mode 100644
index c349b33..0000000
--- a/CLI/Program.cs
+++ /dev/null
@@ -1,157 +0,0 @@
-using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
-using Logging;
-using Spectre.Console;
-using Spectre.Console.Cli;
-using Tranga;
-
-var app = new CommandApp();
-return app.Run(args);
-
-internal sealed class TrangaCli : Command
-{
- public sealed class Settings : CommandSettings
- {
- [Description("Directory to which downloaded Manga are saved")]
- [CommandOption("-d|--downloadLocation")]
- [DefaultValue(null)]
- public string? downloadLocation { get; init; }
-
- [Description("Directory in which application-data is saved")]
- [CommandOption("-w|--workingDirectory")]
- [DefaultValue(null)]
- public string? workingDirectory { get; init; }
-
- [Description("Enables the file-logger")]
- [CommandOption("-f")]
- [DefaultValue(null)]
- public bool? fileLogger { get; init; }
-
- [Description("Path to save logfile to")]
- [CommandOption("-l|--fPath")]
- [DefaultValue(null)]
- public string? fileLoggerPath { get; init; }
-
- [Description("Port on which to run API on")]
- [CommandOption("-p|--port")]
- [DefaultValue(null)]
- public int? apiPort { get; init; }
- }
-
- public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
- {
- List enabledLoggers = new();
- if(settings.fileLogger is true)
- enabledLoggers.Add(Logger.LoggerType.FileLogger);
-
- string? logFolderPath = settings.fileLoggerPath ?? "";
- Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, logFolderPath);
-
- if(settings.workingDirectory is not null)
- TrangaSettings.LoadFromWorkingDirectory(settings.workingDirectory);
- else
- TrangaSettings.CreateOrUpdate();
- if(settings.downloadLocation is not null)
- TrangaSettings.CreateOrUpdate(downloadDirectory: settings.downloadLocation);
-
- Tranga.Tranga? api = null;
-
- Thread trangaApi = new Thread(() =>
- {
- api = new(logger);
- });
- trangaApi.Start();
-
- HttpClient client = new();
-
- bool exit = false;
- while (!exit)
- {
- string menuSelect = AnsiConsole.Prompt(
- new SelectionPrompt()
- .Title("Menu")
- .PageSize(10)
- .MoreChoicesText("Up/Down")
- .AddChoices(new[]
- {
- "CustomRequest",
- "Log",
- "Exit"
- }));
-
- switch (menuSelect)
- {
- case "CustomRequest":
- HttpMethod requestMethod = AnsiConsole.Prompt(
- new SelectionPrompt()
- .Title("Request Type")
- .AddChoices(new[]
- {
- HttpMethod.Get,
- HttpMethod.Delete,
- HttpMethod.Post
- }));
- string requestPath = AnsiConsole.Prompt(
- new TextPrompt("Request Path:"));
- List> parameters = new();
- while (AnsiConsole.Confirm("Add Parameter?"))
- {
- string name = AnsiConsole.Ask("Parameter Name:");
- string value = AnsiConsole.Ask("Parameter Value:");
- parameters.Add(new ValueTuple(name, value));
- }
-
- string requestString = $"http://localhost:{TrangaSettings.apiPortNumber}/{requestPath}";
- if (parameters.Any())
- {
- requestString += "?";
- foreach (ValueTuple parameter in parameters)
- requestString += $"{parameter.Item1}={parameter.Item2}&";
- }
-
- HttpRequestMessage request = new (requestMethod, requestString);
- AnsiConsole.WriteLine($"Request: {request.Method} {request.RequestUri}");
- HttpResponseMessage response;
- if (AnsiConsole.Confirm("Send Request?"))
- response = client.Send(request);
- else break;
- AnsiConsole.WriteLine($"Response: {(int)response.StatusCode} {response.StatusCode}");
- AnsiConsole.WriteLine(response.Content.ReadAsStringAsync().Result);
- break;
- case "Log":
- List lines = logger.Tail(10).ToList();
- Rows rows = new Rows(lines.Select(line => new Text(line)));
-
- AnsiConsole.Live(rows).Start(context =>
- {
- bool running = true;
- while (running)
- {
- string[] newLines = logger.GetNewLines();
- if (newLines.Length > 0)
- {
- lines.AddRange(newLines);
- rows = new Rows(lines.Select(line => new Text(line)));
- context.UpdateTarget(rows);
- }
- Thread.Sleep(100);
- if (AnsiConsole.Console.Input.IsKeyAvailable())
- {
- AnsiConsole.Console.Input.ReadKey(true); //Do not process input
- running = false;
- }
- }
- });
- break;
- case "Exit":
- exit = true;
- break;
- }
- }
-
- if (api is not null)
- api.keepRunning = false;
-
- return 0;
- }
-}
\ No newline at end of file
diff --git a/Logging/FileLogger.cs b/Logging/FileLogger.cs
deleted file mode 100644
index b87e2b0..0000000
--- a/Logging/FileLogger.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.Text;
-
-namespace Logging;
-
-public class FileLogger : LoggerBase
-{
- internal string logFilePath { get; }
- private const int MaxNumberOfLogFiles = 5;
-
- public FileLogger(string logFilePath, Encoding? encoding = null) : base (encoding)
- {
- this.logFilePath = logFilePath;
-
- DirectoryInfo dir = Directory.CreateDirectory(new FileInfo(logFilePath).DirectoryName!);
-
- //Remove oldest logfile if more than MaxNumberOfLogFiles
- for (int fileCount = dir.EnumerateFiles().Count(); fileCount > MaxNumberOfLogFiles - 1; fileCount--) //-1 because we create own logfile later
- File.Delete(dir.EnumerateFiles().MinBy(file => file.LastWriteTime)!.FullName);
- }
-
- protected override void Write(LogMessage logMessage)
- {
- try
- {
- File.AppendAllText(logFilePath, logMessage.formattedMessage);
- }
- catch (Exception)
- {
- // ignored
- }
- }
-}
\ No newline at end of file
diff --git a/Logging/FormattedConsoleLogger.cs b/Logging/FormattedConsoleLogger.cs
deleted file mode 100644
index 2e920fe..0000000
--- a/Logging/FormattedConsoleLogger.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Text;
-
-namespace Logging;
-
-public class FormattedConsoleLogger : LoggerBase
-{
- private readonly TextWriter _stdOut;
- public FormattedConsoleLogger(TextWriter stdOut, Encoding? encoding = null) : base(encoding)
- {
- this._stdOut = stdOut;
- }
-
- protected override void Write(LogMessage message)
- {
- this._stdOut.Write(message.formattedMessage);
- }
-}
\ No newline at end of file
diff --git a/Logging/LogMessage.cs b/Logging/LogMessage.cs
deleted file mode 100644
index ac73c44..0000000
--- a/Logging/LogMessage.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace Logging;
-
-public readonly struct LogMessage
-{
- public DateTime logTime { get; }
- public string caller { get; }
- public string value { get; }
- public string formattedMessage => ToString();
-
- public LogMessage(DateTime messageTime, string caller, string value)
- {
- this.logTime = messageTime;
- this.caller = caller;
- this.value = value;
- }
-
- public override string ToString()
- {
- string dateTimeString = $"{logTime.ToShortDateString()} {logTime.ToLongTimeString()}.{logTime.Millisecond,-3}";
- string name = caller.Split(new char[] { '.', '+' }).Last();
- return $"[{dateTimeString}] {name.Substring(0, name.Length >= 13 ? 13 : name.Length),13} | {value}";
- }
-}
\ No newline at end of file
diff --git a/Logging/Logger.cs b/Logging/Logger.cs
deleted file mode 100644
index 66fd8e5..0000000
--- a/Logging/Logger.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace Logging;
-
-public class Logger : TextWriter
-{
- private static readonly string LogDirectoryPath = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
- ? "/var/log/tranga-api"
- : Path.Join(Directory.GetCurrentDirectory(), "logs");
- public string? logFilePath => _fileLogger?.logFilePath;
- public override Encoding Encoding { get; }
- public enum LoggerType
- {
- FileLogger,
- ConsoleLogger
- }
-
- private readonly FileLogger? _fileLogger;
- private readonly FormattedConsoleLogger? _formattedConsoleLogger;
- private readonly MemoryLogger _memoryLogger;
-
- public Logger(LoggerType[] enabledLoggers, TextWriter? stdOut, Encoding? encoding, string? logFolderPath)
- {
- this.Encoding = encoding ?? Encoding.UTF8;
- DateTime now = DateTime.Now;
- if(enabledLoggers.Contains(LoggerType.FileLogger) && (logFolderPath is null || logFolderPath == ""))
- {
- string filePath = Path.Join(LogDirectoryPath,
- $"{now.ToShortDateString()}_{now.Hour}-{now.Minute}-{now.Second}.log");
- _fileLogger = new FileLogger(filePath, encoding);
- }else if (enabledLoggers.Contains(LoggerType.FileLogger) && logFolderPath is not null)
- _fileLogger = new FileLogger(Path.Join(logFolderPath, $"{now.ToShortDateString()}_{now.Hour}-{now.Minute}-{now.Second}.log") , encoding);
-
-
- if (enabledLoggers.Contains(LoggerType.ConsoleLogger) && stdOut is not null)
- {
- _formattedConsoleLogger = new FormattedConsoleLogger(stdOut, encoding);
- }
- else if (enabledLoggers.Contains(LoggerType.ConsoleLogger) && stdOut is null)
- {
- _formattedConsoleLogger = null;
- throw new ArgumentException($"stdOut can not be null for LoggerType {LoggerType.ConsoleLogger}");
- }
- _memoryLogger = new MemoryLogger(encoding);
- WriteLine(GetType().ToString(), $"Logfile: {logFilePath}");
- }
-
- public void WriteLine(string caller, string? value)
- {
- value = value is null ? Environment.NewLine : string.Concat(value, Environment.NewLine);
-
- Write(caller, value);
- }
-
- public void Write(string caller, string? value)
- {
- if (value is null)
- return;
-
- _fileLogger?.Write(caller, value);
- _formattedConsoleLogger?.Write(caller, value);
- _memoryLogger.Write(caller, value);
- }
-
- public string[] Tail(uint? lines)
- {
- return _memoryLogger.Tail(lines);
- }
-
- public string[] GetNewLines()
- {
- return _memoryLogger.GetNewLines();
- }
-
- public string[] GetLog()
- {
- return _memoryLogger.GetLogMessages();
- }
-}
\ No newline at end of file
diff --git a/Logging/LoggerBase.cs b/Logging/LoggerBase.cs
deleted file mode 100644
index 0b86074..0000000
--- a/Logging/LoggerBase.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Text;
-
-namespace Logging;
-
-public abstract class LoggerBase : TextWriter
-{
- public override Encoding Encoding { get; }
-
- public LoggerBase(Encoding? encoding = null)
- {
- this.Encoding = encoding ?? Encoding.ASCII;
- }
-
- public void Write(string caller, string? value)
- {
- if (value is null)
- return;
-
- LogMessage message = new (DateTime.Now, caller, value);
-
- Write(message);
- }
-
- protected abstract void Write(LogMessage message);
-}
\ No newline at end of file
diff --git a/Logging/Logging.csproj b/Logging/Logging.csproj
deleted file mode 100644
index ccbcb6c..0000000
--- a/Logging/Logging.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- 12
-
-
-
diff --git a/Logging/MemoryLogger.cs b/Logging/MemoryLogger.cs
deleted file mode 100644
index 019600f..0000000
--- a/Logging/MemoryLogger.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System.Text;
-
-namespace Logging;
-
-public class MemoryLogger : LoggerBase
-{
- private readonly SortedList _logMessages = new();
- private int _lastLogMessageIndex = 0;
-
- public MemoryLogger(Encoding? encoding = null) : base(encoding)
- {
-
- }
-
- protected override void Write(LogMessage value)
- {
- lock (_logMessages)
- {
- _logMessages.Add(DateTime.Now, value);
- }
- }
-
- public string[] GetLogMessages()
- {
- return Tail(Convert.ToUInt32(_logMessages.Count));
- }
-
- public string[] Tail(uint? length)
- {
- int retLength;
- if (length is null || length > _logMessages.Count)
- retLength = _logMessages.Count;
- else
- retLength = (int)length;
-
- string[] ret = new string[retLength];
-
- for (int retIndex = 0; retIndex < ret.Length; retIndex++)
- {
- lock (_logMessages)
- {
- ret[retIndex] = _logMessages.GetValueAtIndex(_logMessages.Count - retLength + retIndex).ToString();
- }
- }
-
- _lastLogMessageIndex = _logMessages.Count - 1;
- return ret;
- }
-
- public string[] GetNewLines()
- {
- int logMessageCount = _logMessages.Count;
- List ret = new();
-
- int retIndex = 0;
- for (; retIndex < logMessageCount - _lastLogMessageIndex; retIndex++)
- {
- try
- {
- lock(_logMessages)
- {
- ret.Add(_logMessages.GetValueAtIndex(_lastLogMessageIndex + retIndex).ToString());
- }
- }
- catch (NullReferenceException)//Called when LogMessage has not finished writing
- {
- break;
- }
- }
-
- _lastLogMessageIndex = _lastLogMessageIndex + retIndex;
- return ret.ToArray();
- }
-}
\ No newline at end of file
diff --git a/Tranga.sln b/Tranga.sln
index babebcb..e4e14e6 100644
--- a/Tranga.sln
+++ b/Tranga.sln
@@ -1,11 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga", ".\Tranga\Tranga.csproj", "{545E81B9-D96B-4C8F-A97F-2C02414DE566}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{415BE889-BB7D-426F-976F-8D977876A462}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "CLI\CLI.csproj", "{4324C816-F9D2-468F-8ED6-397FE2F0DCB3}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{EDB07E7B-351F-4FCC-9AEF-777838E5551E}"
EndProject
Global
@@ -14,18 +8,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {545E81B9-D96B-4C8F-A97F-2C02414DE566}.Release|Any CPU.Build.0 = Release|Any CPU
- {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.Build.0 = Release|Any CPU
- {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4324C816-F9D2-468F-8ED6-397FE2F0DCB3}.Release|Any CPU.Build.0 = Release|Any CPU
{EDB07E7B-351F-4FCC-9AEF-777838E5551E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDB07E7B-351F-4FCC-9AEF-777838E5551E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDB07E7B-351F-4FCC-9AEF-777838E5551E}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Tranga/Chapter.cs b/Tranga/Chapter.cs
deleted file mode 100644
index 7622bd7..0000000
--- a/Tranga/Chapter.cs
+++ /dev/null
@@ -1,156 +0,0 @@
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-using System.Xml.Linq;
-using static System.IO.UnixFileMode;
-
-namespace Tranga;
-
-///
-/// Has to be Part of a publication
-/// Includes the Chapter-Name, -VolumeNumber, -ChapterNumber, the location of the chapter on the internet and the saveName of the local file.
-///
-public readonly struct Chapter : IComparable
-{
- // ReSharper disable once MemberCanBePrivate.Global
- public Manga parentManga { get; }
- public string? name { get; }
- public float volumeNumber { get; }
- public float chapterNumber { get; }
- public string url { get; }
- // ReSharper disable once MemberCanBePrivate.Global
- public string fileName { get; }
- public string? id { get; }
-
- private static readonly Regex LegalCharacters = new (@"([A-z]*[0-9]* *\.*-*,*\]*\[*'*\'*\)*\(*~*!*)*");
- private static readonly Regex IllegalStrings = new(@"(Vol(ume)?|Ch(apter)?)\.?", RegexOptions.IgnoreCase);
-
- public Chapter(Manga parentManga, string? name, string? volumeNumber, string chapterNumber, string url, string? id = null)
- : this(parentManga, name, float.Parse(volumeNumber??"0", GlobalBase.numberFormatDecimalPoint),
- float.Parse(chapterNumber, GlobalBase.numberFormatDecimalPoint), url, id)
- {
- }
-
- public Chapter(Manga parentManga, string? name, float? volumeNumber, float chapterNumber, string url, string? id = null)
- {
- this.parentManga = parentManga;
- this.name = name;
- this.volumeNumber = volumeNumber??0;
- this.chapterNumber = chapterNumber;
- this.url = url;
- this.id = id;
-
- string chapterVolNumStr = $"Vol.{this.volumeNumber} Ch.{chapterNumber}";
-
- if (name is not null && name.Length > 0)
- {
- string chapterName = IllegalStrings.Replace(string.Concat(LegalCharacters.Matches(name)), "");
- this.fileName = $"{chapterVolNumStr} - {chapterName}";
- }
- else
- this.fileName = chapterVolNumStr;
- }
-
- public override string ToString()
- {
- return $"Chapter {parentManga.sortName} {parentManga.internalId} {chapterNumber} {name}";
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is not Chapter)
- return false;
- return CompareTo(obj) == 0;
- }
-
- public int CompareTo(object? obj)
- {
- if(obj is not Chapter otherChapter)
- throw new ArgumentException($"{obj} can not be compared to {this}");
- return volumeNumber.CompareTo(otherChapter.volumeNumber) switch
- {
- <0 => -1,
- >0 => 1,
- _ => chapterNumber.CompareTo(otherChapter.chapterNumber)
- };
- }
-
- ///
- /// Checks if a chapter-archive is already present
- ///
- /// true if chapter is present
- internal bool CheckChapterIsDownloaded()
- {
- string mangaDirectory = Path.Join(TrangaSettings.downloadLocation, parentManga.folderName);
- if (!Directory.Exists(mangaDirectory))
- return false;
- FileInfo? mangaArchive = null;
- string markerPath = Path.Join(mangaDirectory, $".{id}");
- if (this.id is not null && File.Exists(markerPath))
- {
- if(File.Exists(File.ReadAllText(markerPath)))
- mangaArchive = new FileInfo(File.ReadAllText(markerPath));
- else
- File.Delete(markerPath);
- }
-
- if(mangaArchive is null)
- {
- FileInfo[] archives = new DirectoryInfo(mangaDirectory).GetFiles("*.cbz");
- Regex volChRex = new(@"(?:Vol(?:ume)?\.([0-9]+)\D*)?Ch(?:apter)?\.([0-9]+(?:\.[0-9]+)*)");
-
- Chapter t = this;
- mangaArchive = archives.FirstOrDefault(archive =>
- {
- Match m = volChRex.Match(archive.Name);
- if (m.Groups[1].Success)
- return m.Groups[1].Value == t.volumeNumber.ToString(GlobalBase.numberFormatDecimalPoint) &&
- m.Groups[2].Value == t.chapterNumber.ToString(GlobalBase.numberFormatDecimalPoint);
- else
- return m.Groups[2].Value == t.chapterNumber.ToString(GlobalBase.numberFormatDecimalPoint);
- });
- }
-
- string correctPath = GetArchiveFilePath();
- if(mangaArchive is not null && mangaArchive.FullName != correctPath)
- mangaArchive.MoveTo(correctPath, true);
- return (mangaArchive is not null);
- }
-
- public void CreateChapterMarker()
- {
- if (this.id is null)
- return;
- string path = Path.Join(TrangaSettings.downloadLocation, parentManga.folderName, $".{id}");
- File.WriteAllText(path, GetArchiveFilePath());
- File.SetAttributes(path, FileAttributes.Hidden);
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- File.SetUnixFileMode(path, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherExecute);
- }
-
- ///
- /// Creates full file path of chapter-archive
- ///
- /// Filepath
- internal string GetArchiveFilePath()
- {
- return Path.Join(TrangaSettings.downloadLocation, parentManga.folderName, $"{parentManga.folderName} - {this.fileName}.cbz");
- }
-
- ///
- /// Creates a string containing XML of publication and chapter.
- /// See ComicInfo.xml
- ///
- /// XML-string
- internal string GetComicInfoXmlString()
- {
- XElement comicInfo = new XElement("ComicInfo",
- new XElement("Tags", string.Join(',', parentManga.tags)),
- new XElement("LanguageISO", parentManga.originalLanguage),
- new XElement("Title", this.name),
- new XElement("Writer", string.Join(',', parentManga.authors)),
- new XElement("Volume", this.volumeNumber),
- new XElement("Number", this.chapterNumber)
- );
- return comicInfo.ToString();
- }
-}
\ No newline at end of file
diff --git a/Tranga/GlobalBase.cs b/Tranga/GlobalBase.cs
deleted file mode 100644
index 3825c21..0000000
--- a/Tranga/GlobalBase.cs
+++ /dev/null
@@ -1,202 +0,0 @@
-using System.Globalization;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Text.RegularExpressions;
-using Logging;
-using Newtonsoft.Json;
-using Tranga.LibraryConnectors;
-using Tranga.MangaConnectors;
-using Tranga.NotificationConnectors;
-
-namespace Tranga;
-
-public abstract class GlobalBase
-{
- [JsonIgnore]
- public Logger? logger { get; init; }
- protected HashSet notificationConnectors { get; init; }
- protected HashSet libraryConnectors { get; init; }
- private Dictionary cachedPublications { get; init; }
- protected HashSet _connectors;
- public static readonly NumberFormatInfo numberFormatDecimalPoint = new (){ NumberDecimalSeparator = "." };
- protected static readonly Regex baseUrlRex = new(@"https?:\/\/[0-9A-z\.-]+(:[0-9]+)?");
-
- protected GlobalBase(GlobalBase clone)
- {
- this.logger = clone.logger;
- this.notificationConnectors = clone.notificationConnectors;
- this.libraryConnectors = clone.libraryConnectors;
- this.cachedPublications = clone.cachedPublications;
- this._connectors = clone._connectors;
- }
-
- protected GlobalBase(Logger? logger)
- {
- this.logger = logger;
- this.notificationConnectors = TrangaSettings.LoadNotificationConnectors(this);
- this.libraryConnectors = TrangaSettings.LoadLibraryConnectors(this);
- this.cachedPublications = new();
- this._connectors = new();
- }
-
- protected Manga? GetCachedManga(string internalId)
- {
- return cachedPublications.TryGetValue(internalId, out Manga manga) switch
- {
- true => manga,
- _ => null
- };
- }
-
- protected IEnumerable GetAllCachedManga() => cachedPublications.Values;
-
- protected void AddMangaToCache(Manga manga)
- {
- if (!cachedPublications.TryAdd(manga.internalId, manga))
- {
- Log($"Overwriting Manga {manga.internalId}");
- cachedPublications[manga.internalId] = manga;
- }
- ExportManga();
- }
-
- protected void RemoveMangaFromCache(Manga manga) => RemoveMangaFromCache(manga.internalId);
-
- protected void RemoveMangaFromCache(string internalId)
- {
- cachedPublications.Remove(internalId);
- ExportManga();
- }
-
- internal void ImportManga()
- {
- string folder = TrangaSettings.mangaCacheFolderPath;
- Directory.CreateDirectory(folder);
-
- foreach (FileInfo fileInfo in new DirectoryInfo(folder).GetFiles())
- {
- string content = File.ReadAllText(fileInfo.FullName);
- try
- {
- Manga m = JsonConvert.DeserializeObject(content, new MangaConnectorJsonConverter(this, _connectors));
- this.cachedPublications.TryAdd(m.internalId, m);
- }
- catch (JsonException e)
- {
- Log($"Error parsing Manga {fileInfo.Name}:\n{e.Message}");
- }
- }
-
- }
-
- private static bool ExportRunning = false;
- private void ExportManga()
- {
- while (ExportRunning)
- Thread.Sleep(1);
- ExportRunning = true;
- string folder = TrangaSettings.mangaCacheFolderPath;
- Directory.CreateDirectory(folder);
- Manga[] copy = new Manga[cachedPublications.Values.Count];
- cachedPublications.Values.CopyTo(copy, 0);
- foreach (Manga manga in copy)
- {
- string content = JsonConvert.SerializeObject(manga, Formatting.Indented);
- string filePath = Path.Combine(folder, $"{manga.internalId}.json");
- File.WriteAllText(filePath, content, Encoding.UTF8);
- }
-
- foreach (FileInfo fileInfo in new DirectoryInfo(folder).GetFiles())
- {
- if(!cachedPublications.Keys.Any(key => fileInfo.Name.Substring(0, fileInfo.Name.LastIndexOf('.')).Equals(key)))
- fileInfo.Delete();
- }
-
- ExportRunning = false;
- }
-
- protected void Log(string message)
- {
- logger?.WriteLine(this.GetType().Name, message);
- }
-
- protected void Log(string fStr, params object?[] replace)
- {
- Log(string.Format(fStr, replace));
- }
-
- protected void SendNotifications(string title, string text, bool buffer = false)
- {
- foreach (NotificationConnector nc in notificationConnectors)
- nc.SendNotification(title, text, buffer);
- }
-
- protected void AddNotificationConnector(NotificationConnector notificationConnector)
- {
- Log($"Adding {notificationConnector}");
- notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnector.notificationConnectorType);
- notificationConnectors.Add(notificationConnector);
-
- while(IsFileInUse(TrangaSettings.notificationConnectorsFilePath))
- Thread.Sleep(100);
- Log("Exporting notificationConnectors");
- File.WriteAllText(TrangaSettings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors));
- }
-
- protected void DeleteNotificationConnector(NotificationConnector.NotificationConnectorType notificationConnectorType)
- {
- Log($"Removing {notificationConnectorType}");
- notificationConnectors.RemoveWhere(nc => nc.notificationConnectorType == notificationConnectorType);
- while(IsFileInUse(TrangaSettings.notificationConnectorsFilePath))
- Thread.Sleep(100);
- Log("Exporting notificationConnectors");
- File.WriteAllText(TrangaSettings.notificationConnectorsFilePath, JsonConvert.SerializeObject(notificationConnectors));
- }
-
- protected void UpdateLibraries()
- {
- foreach(LibraryConnector lc in libraryConnectors)
- lc.UpdateLibrary();
- }
-
- protected void AddLibraryConnector(LibraryConnector libraryConnector)
- {
- Log($"Adding {libraryConnector}");
- libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryConnector.libraryType);
- libraryConnectors.Add(libraryConnector);
-
- while(IsFileInUse(TrangaSettings.libraryConnectorsFilePath))
- Thread.Sleep(100);
- Log("Exporting libraryConnectors");
- File.WriteAllText(TrangaSettings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors, Formatting.Indented));
- }
-
- protected void DeleteLibraryConnector(LibraryConnector.LibraryType libraryType)
- {
- Log($"Removing {libraryType}");
- libraryConnectors.RemoveWhere(lc => lc.libraryType == libraryType);
- while(IsFileInUse(TrangaSettings.libraryConnectorsFilePath))
- Thread.Sleep(100);
- Log("Exporting libraryConnectors");
- File.WriteAllText(TrangaSettings.libraryConnectorsFilePath, JsonConvert.SerializeObject(libraryConnectors, Formatting.Indented));
- }
-
- protected bool IsFileInUse(string filePath) => IsFileInUse(filePath, this.logger);
-
- public static bool IsFileInUse(string filePath, Logger? logger)
- {
- if (!File.Exists(filePath))
- return false;
- try
- {
- using FileStream stream = new (filePath, FileMode.Open, FileAccess.Read, FileShare.None);
- stream.Close();
- return false;
- }
- catch (IOException)
- {
- logger?.WriteLine($"File is in use {filePath}");
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/Tranga/Jobs/DownloadChapter.cs b/Tranga/Jobs/DownloadChapter.cs
deleted file mode 100644
index 0092f6c..0000000
--- a/Tranga/Jobs/DownloadChapter.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System.Net;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Jobs;
-
-public class DownloadChapter : Job
-{
- public Chapter chapter { get; init; }
-
- public DownloadChapter(GlobalBase clone, Chapter chapter, DateTime lastExecution, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, lastExecution, parentJobId: parentJobId)
- {
- this.chapter = chapter;
- }
-
- public DownloadChapter(GlobalBase clone, Chapter chapter, string? parentJobId = null) : base(clone, JobType.DownloadChapterJob, parentJobId: parentJobId)
- {
- this.chapter = chapter;
- }
-
- protected override string GetId()
- {
- return $"{GetType()}-{chapter.parentManga.internalId}-{chapter.chapterNumber}";
- }
-
- public override string ToString()
- {
- return $"{id} Chapter: {chapter}";
- }
-
- protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss)
- {
- Task downloadTask = new(delegate
- {
- mangaConnector.CopyCoverFromCacheToDownloadLocation(chapter.parentManga);
- HttpStatusCode success = mangaConnector.DownloadChapter(chapter, this.progressToken);
- chapter.parentManga.UpdateLatestDownloadedChapter(chapter);
- if (success == HttpStatusCode.OK)
- {
- UpdateLibraries();
- SendNotifications("Chapter downloaded", $"{chapter.parentManga.sortName} - {chapter.chapterNumber}", true);
- }
- });
- downloadTask.Start();
- return Array.Empty();
- }
-
- protected override MangaConnector GetMangaConnector()
- {
- return chapter.parentManga.mangaConnector;
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is not DownloadChapter otherJob)
- return false;
- return otherJob.chapter.Equals(this.chapter);
- }
-}
\ No newline at end of file
diff --git a/Tranga/Jobs/DownloadNewChapters.cs b/Tranga/Jobs/DownloadNewChapters.cs
deleted file mode 100644
index d7edf96..0000000
--- a/Tranga/Jobs/DownloadNewChapters.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using Newtonsoft.Json;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Jobs;
-
-public class DownloadNewChapters : Job
-{
- public string mangaInternalId { get; set; }
- [JsonIgnore] private Manga? manga => GetCachedManga(mangaInternalId);
- public string translatedLanguage { get; init; }
-
- public DownloadNewChapters(GlobalBase clone, string mangaInternalId, DateTime lastExecution, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base(clone, JobType.DownloadNewChaptersJob, lastExecution, recurring, recurrence, parentJobId)
- {
- this.mangaInternalId = mangaInternalId;
- this.translatedLanguage = translatedLanguage;
- }
-
- public DownloadNewChapters(GlobalBase clone, MangaConnector connector, string mangaInternalId, bool recurring = false, TimeSpan? recurrence = null, string? parentJobId = null, string translatedLanguage = "en") : base (clone, JobType.DownloadNewChaptersJob, recurring, recurrence, parentJobId)
- {
- this.mangaInternalId = mangaInternalId;
- this.translatedLanguage = translatedLanguage;
- }
-
- protected override string GetId()
- {
- return $"{GetType()}-{mangaInternalId}";
- }
-
- public override string ToString()
- {
- return $"{id} Manga: {manga}";
- }
-
- protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss)
- {
- if (manga is null)
- {
- Log($"Manga {mangaInternalId} is missing! Can not execute job.");
- return Array.Empty();
- }
- manga.Value.SaveSeriesInfoJson();
- Chapter[] chapters = manga.Value.mangaConnector.GetNewChapters(manga.Value, this.translatedLanguage);
- this.progressToken.increments = chapters.Length;
- List jobs = new();
- manga.Value.mangaConnector.CopyCoverFromCacheToDownloadLocation(manga.Value);
- foreach (Chapter chapter in chapters)
- {
- DownloadChapter downloadChapterJob = new(this, chapter, parentJobId: this.id);
- jobs.Add(downloadChapterJob);
- }
- UpdateMetadata updateMetadataJob = new(this, mangaInternalId, parentJobId: this.id);
- jobs.Add(updateMetadataJob);
- progressToken.Complete();
- return jobs;
- }
-
- protected override MangaConnector GetMangaConnector()
- {
- if (manga is null)
- throw new Exception($"Missing Manga {mangaInternalId}");
- return manga.Value.mangaConnector;
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is not DownloadNewChapters otherJob)
- return false;
- return otherJob.mangaConnector == this.mangaConnector &&
- otherJob.manga?.publicationId == this.manga?.publicationId;
- }
-}
\ No newline at end of file
diff --git a/Tranga/Jobs/Job.cs b/Tranga/Jobs/Job.cs
deleted file mode 100644
index ac48b39..0000000
--- a/Tranga/Jobs/Job.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using Tranga.MangaConnectors;
-
-namespace Tranga.Jobs;
-
-public abstract class Job : GlobalBase
-{
- public ProgressToken progressToken { get; private set; }
- public bool recurring { get; init; }
- public TimeSpan? recurrenceTime { get; set; }
- public DateTime? lastExecution { get; private set; }
- public DateTime nextExecution => NextExecution();
- public string id => GetId();
- internal IEnumerable? subJobs { get; private set; }
- public string? parentJobId { get; init; }
- public enum JobType : byte { DownloadChapterJob = 0, DownloadNewChaptersJob = 1, UpdateMetaDataJob = 2, MonitorManga = 3 }
-
- public MangaConnector mangaConnector => GetMangaConnector();
-
- public JobType jobType;
-
- internal Job(GlobalBase clone, JobType jobType, bool recurring = false, TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
- {
- this.jobType = jobType;
- this.progressToken = new ProgressToken(0);
- this.recurring = recurring;
- if (recurring && recurrenceTime is null)
- throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided.");
- else if(recurring && recurrenceTime is not null)
- this.lastExecution = DateTime.Now.Subtract((TimeSpan)recurrenceTime);
- this.recurrenceTime = recurrenceTime ?? TimeSpan.Zero;
- this.parentJobId = parentJobId;
- }
-
- internal Job(GlobalBase clone, JobType jobType, DateTime lastExecution, bool recurring = false,
- TimeSpan? recurrenceTime = null, string? parentJobId = null) : base(clone)
- {
- this.jobType = jobType;
- this.progressToken = new ProgressToken(0);
- this.recurring = recurring;
- if (recurring && recurrenceTime is null)
- throw new ArgumentException("If recurrence is set to true, a recurrence time has to be provided.");
- this.lastExecution = lastExecution;
- this.recurrenceTime = recurrenceTime ?? TimeSpan.Zero;
- this.parentJobId = parentJobId;
- }
-
- protected abstract string GetId();
-
- public void AddSubJob(Job job)
- {
- subJobs ??= new List();
- subJobs = subJobs.Append(job);
- }
-
- private DateTime NextExecution()
- {
- if(recurrenceTime.HasValue && lastExecution.HasValue)
- return lastExecution.Value.Add(recurrenceTime.Value);
- if(recurrenceTime.HasValue && !lastExecution.HasValue)
- return DateTime.Now;
- return DateTime.MaxValue;
- }
-
- public void ResetProgress()
- {
- this.progressToken.increments -= progressToken.incrementsCompleted;
- this.lastExecution = DateTime.Now;
- this.progressToken.Waiting();
- }
-
- public void ExecutionEnqueue()
- {
- this.progressToken.increments -= progressToken.incrementsCompleted;
- this.progressToken.Standby();
- }
-
- public void Cancel()
- {
- Log($"Cancelling {this}");
- this.progressToken.cancellationRequested = true;
- this.progressToken.Cancel();
- this.lastExecution = DateTime.Now;
- if(subJobs is not null)
- foreach(Job subJob in subJobs)
- subJob.Cancel();
- }
-
- public IEnumerable ExecuteReturnSubTasks(JobBoss jobBoss)
- {
- progressToken.Start();
- subJobs = ExecuteReturnSubTasksInternal(jobBoss);
- lastExecution = DateTime.Now;
- return subJobs;
- }
-
- protected abstract IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss);
-
- protected abstract MangaConnector GetMangaConnector();
-}
\ No newline at end of file
diff --git a/Tranga/Jobs/JobBoss.cs b/Tranga/Jobs/JobBoss.cs
deleted file mode 100644
index c032f69..0000000
--- a/Tranga/Jobs/JobBoss.cs
+++ /dev/null
@@ -1,303 +0,0 @@
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-using Newtonsoft.Json;
-using Tranga.MangaConnectors;
-using static System.IO.UnixFileMode;
-
-namespace Tranga.Jobs;
-
-public class JobBoss : GlobalBase
-{
- public HashSet jobs { get; init; }
- private Dictionary> mangaConnectorJobQueue { get; init; }
-
- public JobBoss(GlobalBase clone, HashSet connectors) : base(clone)
- {
- this.jobs = new();
- LoadJobsList(connectors);
- this.mangaConnectorJobQueue = new();
- Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}");
- }
-
- public bool AddJob(Job job, string? jobFile = null)
- {
- if (ContainsJobLike(job))
- {
- Log($"Already Contains Job {job}");
- return false;
- }
- else
- {
- if (!this.jobs.Add(job))
- return false;
- Log($"Added {job}");
- UpdateJobFile(job, jobFile);
- }
- return true;
- }
-
- public void AddJobs(IEnumerable jobsToAdd)
- {
- foreach (Job job in jobsToAdd)
- AddJob(job);
- }
-
- ///
- /// Compares contents of the provided job and all current jobs
- /// Does not check if objects are the same
- ///
- public bool ContainsJobLike(Job job)
- {
- return this.jobs.Any(existingJob => existingJob.Equals(job));
- }
-
- public void RemoveJob(Job job)
- {
- Log($"Removing {job}");
- job.Cancel();
- this.jobs.Remove(job);
- if(job.subJobs is not null && job.subJobs.Any())
- RemoveJobs(job.subJobs);
- UpdateJobFile(job);
- }
-
- public void RemoveJobs(IEnumerable jobsToRemove)
- {
- List toRemove = jobsToRemove.ToList(); //Prevent multiple enumeration
- Log($"Removing {toRemove.Count()} jobs.");
- foreach (Job? job in toRemove)
- if(job is not null)
- RemoveJob(job);
- }
-
- public IEnumerable GetJobsLike(string? internalId = null, float? chapterNumber = null)
- {
- IEnumerable ret = this.jobs;
-
- if (internalId is not null && chapterNumber is not null)
- ret = ret.Where(jjob =>
- {
- if (jjob is not DownloadChapter job)
- return false;
- return job.chapter.parentManga.internalId == internalId &&
- job.chapter.chapterNumber.Equals(chapterNumber);
- });
- else if (internalId is not null)
- ret = ret.Where(jjob =>
- {
- if (jjob is not DownloadNewChapters job)
- return false;
- return job.mangaInternalId == internalId;
- });
- return ret;
- }
-
- public IEnumerable GetJobsLike(Manga? publication = null,
- Chapter? chapter = null)
- {
- if (chapter is not null)
- return GetJobsLike(chapter.Value.parentManga.internalId, chapter.Value.chapterNumber);
- else
- return GetJobsLike(publication?.internalId);
- }
-
- public Job? GetJobById(string jobId)
- {
- if (this.jobs.FirstOrDefault(jjob => jjob.id == jobId) is { } job)
- return job;
- return null;
- }
-
- public bool TryGetJobById(string jobId, out Job? job)
- {
- if (this.jobs.FirstOrDefault(jjob => jjob.id == jobId) is { } ret)
- {
- job = ret;
- return true;
- }
-
- job = null;
- return false;
- }
-
- private bool QueueContainsJob(Job job)
- {
- if (mangaConnectorJobQueue.TryAdd(job.mangaConnector, new Queue()))//If we can add the queue, there is certainly no job in it
- return true;
- return mangaConnectorJobQueue[job.mangaConnector].Contains(job);
- }
-
- public void AddJobToQueue(Job job)
- {
- Log($"Adding Job to Queue. {job}");
- if(!QueueContainsJob(job))
- mangaConnectorJobQueue[job.mangaConnector].Enqueue(job);
- job.ExecutionEnqueue();
- }
-
- private void AddJobsToQueue(IEnumerable newJobs)
- {
- foreach(Job job in newJobs)
- AddJobToQueue(job);
- }
-
- private void LoadJobsList(HashSet connectors)
- {
- Directory.CreateDirectory(TrangaSettings.jobsFolderPath);
- if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- File.SetUnixFileMode(TrangaSettings.jobsFolderPath, UserRead | UserWrite | UserExecute | GroupRead | OtherRead);
- if (!Directory.Exists(TrangaSettings.jobsFolderPath)) //No jobs to load
- return;
-
- //Load Manga-Files
- ImportManga();
-
- //Load json-job-files
- foreach (FileInfo file in Directory.GetFiles(TrangaSettings.jobsFolderPath, "*.json").Select(f => new FileInfo(f)))
- {
- Log($"Adding {file.Name}");
- Job? job = JsonConvert.DeserializeObject(File.ReadAllText(file.FullName),
- new JobJsonConverter(this, new MangaConnectorJsonConverter(this, connectors)));
- if (job is null)
- {
- string newName = file.FullName + ".failed";
- Log($"Failed loading file {file.Name}.\nMoving to {newName}");
- File.Move(file.FullName, newName);
- }
- else
- {
- Log($"Adding Job {job}");
- if (!AddJob(job, file.FullName)) //If we detect a duplicate, delete the file.
- {
- string path = string.Concat(file.FullName, ".duplicate");
- file.MoveTo(path);
- Log($"Duplicate detected or otherwise not able to add job to list.\nMoved job {job} to {path}");
- }
- }
- }
-
- //Connect jobs to parent-jobs and add Publications to cache
- foreach (Job job in this.jobs)
- {
- Log($"Loading Job {job}");
- Job? parentJob = this.jobs.FirstOrDefault(jjob => jjob.id == job.parentJobId);
- if (parentJob is not null)
- {
- parentJob.AddSubJob(job);
- Log($"Parent Job {parentJob}");
- }
- }
-
- string[] jobMangaInternalIds = this.jobs.Where(job => job is DownloadNewChapters)
- .Select(dnc => ((DownloadNewChapters)dnc).mangaInternalId).ToArray();
- jobMangaInternalIds = jobMangaInternalIds.Concat(
- this.jobs.Where(job => job is UpdateMetadata)
- .Select(dnc => ((UpdateMetadata)dnc).mangaInternalId)).ToArray();
- string[] internalIds = GetAllCachedManga().Select(m => m.internalId).ToArray();
-
- string[] extraneousIds = internalIds.Except(jobMangaInternalIds).ToArray();
- foreach (string internalId in extraneousIds)
- RemoveMangaFromCache(internalId);
-
- string[] coverFiles = Directory.GetFiles(TrangaSettings.coverImageCache);
- foreach(string fileName in coverFiles.Where(fileName => !GetAllCachedManga().Any(manga => manga.coverFileNameInCache == fileName)))
- File.Delete(fileName);
- string[] mangaFiles = Directory.GetFiles(TrangaSettings.mangaCacheFolderPath);
- foreach(string fileName in mangaFiles.Where(fileName => !GetAllCachedManga().Any(manga => fileName.Split('.')[0] == manga.internalId)))
- File.Delete(fileName);
- }
-
- internal void UpdateJobFile(Job job, string? oldFile = null)
- {
- string newJobFilePath = Path.Join(TrangaSettings.jobsFolderPath, $"{job.id}.json");
- string oldFilePath = oldFile??Path.Join(TrangaSettings.jobsFolderPath, $"{job.id}.json");
-
- //Delete old file
- if (File.Exists(oldFilePath))
- {
- Log($"Deleting Job-file {oldFilePath}");
- try
- {
- while(IsFileInUse(oldFilePath))
- Thread.Sleep(10);
- File.Delete(oldFilePath);
- }
- catch (Exception e)
- {
- Log($"Error deleting {oldFilePath} job {job.id}\n{e}");
- return; //Don't export a new file when we haven't actually deleted the old one
- }
- }
-
- //Export job (in new file) if it is still in our jobs list
- if (GetJobById(job.id) is not null)
- {
- Log($"Exporting Job {newJobFilePath}");
- string jobStr = JsonConvert.SerializeObject(job, Formatting.Indented);
- while(IsFileInUse(newJobFilePath))
- Thread.Sleep(10);
- File.WriteAllText(newJobFilePath, jobStr);
- if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- File.SetUnixFileMode(newJobFilePath, UserRead | UserWrite | GroupRead | OtherRead);
- }
- }
-
- private void UpdateAllJobFiles()
- {
- Log("Exporting Jobs");
- foreach (Job job in this.jobs)
- UpdateJobFile(job);
-
- //Remove files with jobs not in this.jobs-list
- Regex idRex = new (@"(.*)\.json");
- foreach (FileInfo file in new DirectoryInfo(TrangaSettings.jobsFolderPath).EnumerateFiles())
- {
- if (idRex.IsMatch(file.Name))
- {
- string id = idRex.Match(file.Name).Groups[1].Value;
- if (!this.jobs.Any(job => job.id == id))
- {
- try
- {
- file.Delete();
- }
- catch (Exception e)
- {
- Log(e.ToString());
- }
- }
- }
- }
- }
-
- public void CheckJobs()
- {
- AddJobsToQueue(jobs.Where(job => job.progressToken.state == ProgressToken.State.Waiting && job.nextExecution < DateTime.Now && !QueueContainsJob(job)).OrderBy(job => job.nextExecution));
- foreach (Queue jobQueue in mangaConnectorJobQueue.Values)
- {
- if(jobQueue.Count < 1)
- continue;
- Job queueHead = jobQueue.Peek();
- if (queueHead.progressToken.state is ProgressToken.State.Complete or ProgressToken.State.Cancelled)
- {
- if(!queueHead.recurring)
- RemoveJob(queueHead);
- else
- queueHead.ResetProgress();
- jobQueue.Dequeue();
- Log($"Next job in {jobs.MinBy(job => job.nextExecution)?.nextExecution.Subtract(DateTime.Now)} {jobs.MinBy(job => job.nextExecution)?.id}");
- }else if (queueHead.progressToken.state is ProgressToken.State.Standby)
- {
- Job eJob = jobQueue.Peek();
- Job[] subJobs = eJob.ExecuteReturnSubTasks(this).ToArray();
- UpdateJobFile(eJob);
- AddJobs(subJobs);
- AddJobsToQueue(subJobs);
- }else if (queueHead.progressToken.state is ProgressToken.State.Running && DateTime.Now.Subtract(queueHead.progressToken.lastUpdate) > TimeSpan.FromMinutes(5))
- {
- Log($"{queueHead} inactive for more than 5 minutes. Cancelling.");
- queueHead.Cancel();
- }
- }
- }
- }
\ No newline at end of file
diff --git a/Tranga/Jobs/JobJsonConverter.cs b/Tranga/Jobs/JobJsonConverter.cs
deleted file mode 100644
index 26a1a2a..0000000
--- a/Tranga/Jobs/JobJsonConverter.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Jobs;
-
-public class JobJsonConverter : JsonConverter
-{
- private GlobalBase _clone;
- private MangaConnectorJsonConverter _mangaConnectorJsonConverter;
-
- internal JobJsonConverter(GlobalBase clone, MangaConnectorJsonConverter mangaConnectorJsonConverter)
- {
- this._clone = clone;
- this._mangaConnectorJsonConverter = mangaConnectorJsonConverter;
- }
-
- public override bool CanConvert(Type objectType)
- {
- return (objectType == typeof(Job));
- }
-
- public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- JObject jo = JObject.Load(reader);
-
- if(!jo.ContainsKey("jobType"))
- throw new Exception();
-
- return Enum.Parse(jo["jobType"]!.Value().ToString()) switch
- {
- Job.JobType.UpdateMetaDataJob => new UpdateMetadata(_clone,
- jo.GetValue("mangaInternalId")!.Value()!,
- jo.GetValue("parentJobId")!.Value()),
- Job.JobType.DownloadChapterJob => new DownloadChapter(this._clone,
- jo.GetValue("chapter")!.ToObject(JsonSerializer.Create(new JsonSerializerSettings()
- {
- Converters = { this._mangaConnectorJsonConverter }
- })),
- DateTime.UnixEpoch,
- jo.GetValue("parentJobId")!.Value()),
- Job.JobType.DownloadNewChaptersJob => new DownloadNewChapters(this._clone,
- jo.GetValue("mangaInternalId")!.Value()!,
- jo.GetValue("lastExecution") is {} le
- ? le.ToObject()
- : DateTime.UnixEpoch,
- jo.GetValue("recurring")!.Value(),
- jo.GetValue("recurrenceTime")!.ToObject(),
- jo.GetValue("parentJobId")!.Value()),
- _ => throw new Exception()
- };
- }
-
- public override bool CanWrite => false;
-
- ///
- /// Don't call this
- ///
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
- {
- throw new Exception("Dont call this");
- }
-}
\ No newline at end of file
diff --git a/Tranga/Jobs/ProgressToken.cs b/Tranga/Jobs/ProgressToken.cs
deleted file mode 100644
index 3823ac2..0000000
--- a/Tranga/Jobs/ProgressToken.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-namespace Tranga.Jobs;
-
-public class ProgressToken
-{
- public bool cancellationRequested { get; set; }
- public int increments { get; set; }
- public int incrementsCompleted { get; set; }
- public float progress => GetProgress();
- public DateTime lastUpdate { get; private set; }
- public DateTime executionStarted { get; private set; }
- public TimeSpan timeRemaining => GetTimeRemaining();
-
- public enum State : byte { Running = 0, Complete = 1, Standby = 2, Cancelled = 3, Waiting = 4 }
- public State state { get; private set; }
-
- public ProgressToken(int increments)
- {
- this.cancellationRequested = false;
- this.increments = increments;
- this.incrementsCompleted = 0;
- this.state = State.Waiting;
- this.executionStarted = DateTime.UnixEpoch;
- this.lastUpdate = DateTime.UnixEpoch;
- }
-
- private float GetProgress()
- {
- if(increments > 0 && incrementsCompleted > 0)
- return incrementsCompleted / (float)increments;
- return 0;
- }
-
- private TimeSpan GetTimeRemaining()
- {
- if (increments > 0 && incrementsCompleted > 0)
- return DateTime.Now.Subtract(this.executionStarted).Divide(incrementsCompleted).Multiply(increments - incrementsCompleted);
- return TimeSpan.MaxValue;
- }
-
- public void Increment()
- {
- this.lastUpdate = DateTime.Now;
- this.incrementsCompleted++;
- if (incrementsCompleted > increments)
- state = State.Complete;
- }
-
- public void Standby()
- {
- this.lastUpdate = DateTime.Now;
- state = State.Standby;
- }
-
- public void Start()
- {
- this.lastUpdate = DateTime.Now;
- state = State.Running;
- this.executionStarted = DateTime.Now;
- }
-
- public void Complete()
- {
- this.lastUpdate = DateTime.Now;
- state = State.Complete;
- }
-
- public void Cancel()
- {
- this.lastUpdate = DateTime.Now;
- state = State.Cancelled;
- }
-
- public void Waiting()
- {
- this.lastUpdate = DateTime.Now;
- state = State.Waiting;
- }
-}
\ No newline at end of file
diff --git a/Tranga/Jobs/UpdateMetadata.cs b/Tranga/Jobs/UpdateMetadata.cs
deleted file mode 100644
index f122dc0..0000000
--- a/Tranga/Jobs/UpdateMetadata.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System.Text.Json.Serialization;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Jobs;
-
-public class UpdateMetadata : Job
-{
- public string mangaInternalId { get; set; }
- [JsonIgnore] private Manga? manga => GetCachedManga(mangaInternalId);
-
- public UpdateMetadata(GlobalBase clone, string mangaInternalId, string? parentJobId = null) : base(clone, JobType.UpdateMetaDataJob, parentJobId: parentJobId)
- {
- this.mangaInternalId = mangaInternalId;
- }
-
- protected override string GetId()
- {
- return $"{GetType()}-{mangaInternalId}";
- }
-
- public override string ToString()
- {
- return $"{id} Manga: {manga}";
- }
-
- protected override IEnumerable ExecuteReturnSubTasksInternal(JobBoss jobBoss)
- {
- if (manga is null)
- {
- Log($"Manga {mangaInternalId} is missing! Can not execute job.");
- return Array.Empty();
- }
-
- //Retrieve new Metadata
- Manga? possibleUpdatedManga = mangaConnector.GetMangaFromId(manga.Value.publicationId);
- if (possibleUpdatedManga is { } updatedManga)
- {
- if (updatedManga.Equals(this.manga)) //Check if anything changed
- {
- this.progressToken.Complete();
- return Array.Empty();
- }
-
- AddMangaToCache(manga.Value.WithMetadata(updatedManga));
- this.manga.Value.SaveSeriesInfoJson(true);
- this.mangaConnector.CopyCoverFromCacheToDownloadLocation((Manga)manga);
- this.progressToken.Complete();
- }
- else
- {
- Log($"Could not find Manga {manga}");
- this.progressToken.Cancel();
- return Array.Empty();
- }
- this.progressToken.Cancel();
- return Array.Empty();
- }
-
- protected override MangaConnector GetMangaConnector()
- {
- if (manga is null)
- throw new Exception($"Missing Manga {mangaInternalId}");
- return manga.Value.mangaConnector;
- }
-
- public override bool Equals(object? obj)
- {
-
- if (obj is not UpdateMetadata otherJob)
- return false;
- return otherJob.mangaConnector == this.mangaConnector &&
- otherJob.manga?.publicationId == this.manga?.publicationId;
- }
-}
\ No newline at end of file
diff --git a/Tranga/LibraryConnectors/Kavita.cs b/Tranga/LibraryConnectors/Kavita.cs
deleted file mode 100644
index 62150a0..0000000
--- a/Tranga/LibraryConnectors/Kavita.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System.Text.Json.Nodes;
-using Logging;
-using Newtonsoft.Json;
-using JsonSerializer = System.Text.Json.JsonSerializer;
-
-namespace Tranga.LibraryConnectors;
-
-public class Kavita : LibraryConnector
-{
-
- public Kavita(GlobalBase clone, string baseUrl, string username, string password) :
- base(clone, baseUrl, GetToken(baseUrl, username, password, clone.logger), LibraryType.Kavita)
- {
- }
-
- [JsonConstructor]
- public Kavita(GlobalBase clone, string baseUrl, string auth) : base(clone, baseUrl, auth, LibraryType.Kavita)
- {
- }
-
- public override string ToString()
- {
- return $"Kavita {baseUrl}";
- }
-
- private static string GetToken(string baseUrl, string username, string password, Logger? logger = null)
- {
- HttpClient client = new()
- {
- DefaultRequestHeaders =
- {
- { "Accept", "application/json" }
- }
- };
- HttpRequestMessage requestMessage = new ()
- {
- Method = HttpMethod.Post,
- RequestUri = new Uri($"{baseUrl}/api/Account/login"),
- Content = new StringContent($"{{\"username\":\"{username}\",\"password\":\"{password}\"}}", System.Text.Encoding.UTF8, "application/json")
- };
- try
- {
- HttpResponseMessage response = client.Send(requestMessage);
- logger?.WriteLine($"Kavita | GetToken {requestMessage.RequestUri} -> {response.StatusCode}");
- if (response.IsSuccessStatusCode)
- {
- JsonObject? result = JsonSerializer.Deserialize(response.Content.ReadAsStream());
- if (result is not null)
- return result["token"]!.GetValue();
- }
- else
- {
- logger?.WriteLine($"Kavita | {response.Content}");
- }
- }
- catch (HttpRequestException e)
- {
- logger?.WriteLine($"Kavita | Unable to retrieve token:\n\r{e}");
- }
- logger?.WriteLine("Kavita | Did not receive token.");
- return "";
- }
-
- protected override void UpdateLibraryInternal()
- {
- Log("Updating libraries.");
- foreach (KavitaLibrary lib in GetLibraries())
- NetClient.MakePost($"{baseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", auth, logger);
- }
-
- internal override bool Test()
- {
- foreach (KavitaLibrary lib in GetLibraries())
- if (NetClient.MakePost($"{baseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", auth, logger))
- return true;
- return false;
- }
-
- ///
- /// Fetches all libraries available to the user
- ///
- /// Array of KavitaLibrary
- private IEnumerable GetLibraries()
- {
- Log("Getting libraries.");
- Stream data = NetClient.MakeRequest($"{baseUrl}/api/Library/libraries", "Bearer", auth, logger);
- if (data == Stream.Null)
- {
- Log("No libraries returned");
- return Array.Empty();
- }
- JsonArray? result = JsonSerializer.Deserialize(data);
- if (result is null)
- {
- Log("No libraries returned");
- return Array.Empty();
- }
-
- List ret = new();
-
- foreach (JsonNode? jsonNode in result)
- {
- JsonObject? jObject = (JsonObject?)jsonNode;
- if(jObject is null)
- continue;
- int libraryId = jObject!["id"]!.GetValue();
- string libraryName = jObject["name"]!.GetValue();
- ret.Add(new KavitaLibrary(libraryId, libraryName));
- }
-
- return ret;
- }
-
- private struct KavitaLibrary
- {
- public int id { get; }
- // ReSharper disable once UnusedAutoPropertyAccessor.Local
- public string name { get; }
-
- public KavitaLibrary(int id, string name)
- {
- this.id = id;
- this.name = name;
- }
- }
-}
\ No newline at end of file
diff --git a/Tranga/LibraryConnectors/Komga.cs b/Tranga/LibraryConnectors/Komga.cs
deleted file mode 100644
index 2d6b2a1..0000000
--- a/Tranga/LibraryConnectors/Komga.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System.Text.Json.Nodes;
-using Newtonsoft.Json;
-using JsonSerializer = System.Text.Json.JsonSerializer;
-
-namespace Tranga.LibraryConnectors;
-
-///
-/// Provides connectivity to Komga-API
-/// Can fetch and update libraries
-///
-public class Komga : LibraryConnector
-{
- public Komga(GlobalBase clone, string baseUrl, string username, string password)
- : base(clone, baseUrl, Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}")), LibraryType.Komga)
- {
- }
-
- [JsonConstructor]
- public Komga(GlobalBase clone, string baseUrl, string auth) : base(clone, baseUrl, auth, LibraryType.Komga)
- {
- }
-
- public override string ToString()
- {
- return $"Komga {baseUrl}";
- }
-
- protected override void UpdateLibraryInternal()
- {
- Log("Updating libraries.");
- foreach (KomgaLibrary lib in GetLibraries())
- NetClient.MakePost($"{baseUrl}/api/v1/libraries/{lib.id}/scan", "Basic", auth, logger);
- }
-
- internal override bool Test()
- {
- foreach (KomgaLibrary lib in GetLibraries())
- if (NetClient.MakePost($"{baseUrl}/api/v1/libraries/{lib.id}/scan", "Basic", auth, logger))
- return true;
- return false;
- }
-
- ///
- /// Fetches all libraries available to the user
- ///
- /// Array of KomgaLibraries
- private IEnumerable GetLibraries()
- {
- Log("Getting Libraries");
- Stream data = NetClient.MakeRequest($"{baseUrl}/api/v1/libraries", "Basic", auth, logger);
- if (data == Stream.Null)
- {
- Log("No libraries returned");
- return Array.Empty();
- }
- JsonArray? result = JsonSerializer.Deserialize(data);
- if (result is null)
- {
- Log("No libraries returned");
- return Array.Empty();
- }
-
- HashSet ret = new();
-
- foreach (JsonNode? jsonNode in result)
- {
- var jObject = (JsonObject?)jsonNode;
- string libraryId = jObject!["id"]!.GetValue();
- string libraryName = jObject["name"]!.GetValue();
- ret.Add(new KomgaLibrary(libraryId, libraryName));
- }
-
- return ret;
- }
-
- private struct KomgaLibrary
- {
- public string id { get; }
- // ReSharper disable once UnusedAutoPropertyAccessor.Local
- public string name { get; }
-
- public KomgaLibrary(string id, string name)
- {
- this.id = id;
- this.name = name;
- }
- }
-}
\ No newline at end of file
diff --git a/Tranga/LibraryConnectors/LibraryConnector.cs b/Tranga/LibraryConnectors/LibraryConnector.cs
deleted file mode 100644
index 99320b0..0000000
--- a/Tranga/LibraryConnectors/LibraryConnector.cs
+++ /dev/null
@@ -1,144 +0,0 @@
-using System.Net;
-using System.Net.Http.Headers;
-using Logging;
-
-namespace Tranga.LibraryConnectors;
-
-public abstract class LibraryConnector : GlobalBase
-{
- public enum LibraryType : byte
- {
- Komga = 0,
- Kavita = 1
- }
-
- // ReSharper disable once UnusedAutoPropertyAccessor.Global
- public LibraryType libraryType { get; }
- public string baseUrl { get; }
- // ReSharper disable once MemberCanBeProtected.Global
- public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems
- private DateTime? _updateLibraryRequested = null;
- private readonly Thread? _libraryBufferThread = null;
- private const int NoChangeTimeout = 2, BiggestInterval = 20;
-
- protected LibraryConnector(GlobalBase clone, string baseUrl, string auth, LibraryType libraryType) : base(clone)
- {
- Log($"Creating libraryConnector {Enum.GetName(libraryType)}");
- if (!baseUrlRex.IsMatch(baseUrl))
- throw new ArgumentException("Base url does not match pattern");
- if(auth == "")
- throw new ArgumentNullException(nameof(auth), "Auth can not be empty");
- this.baseUrl = baseUrlRex.Match(baseUrl).Value;
- this.auth = auth;
- this.libraryType = libraryType;
-
- if (TrangaSettings.bufferLibraryUpdates)
- {
- _libraryBufferThread = new(CheckLibraryBuffer);
- _libraryBufferThread.Start();
- }
- }
-
- private void CheckLibraryBuffer()
- {
- while (true)
- {
- if (_updateLibraryRequested is not null && DateTime.Now.Subtract((DateTime)_updateLibraryRequested) > TimeSpan.FromMinutes(NoChangeTimeout)) //If no updates have been requested for NoChangeTimeout minutes, update library
- {
- UpdateLibraryInternal();
- _updateLibraryRequested = null;
- }
- Thread.Sleep(100);
- }
- }
-
- public void UpdateLibrary()
- {
- _updateLibraryRequested ??= DateTime.Now;
- if (!TrangaSettings.bufferLibraryUpdates)
- {
- UpdateLibraryInternal();
- return;
- }else if (_updateLibraryRequested is not null &&
- DateTime.Now.Subtract((DateTime)_updateLibraryRequested) > TimeSpan.FromMinutes(BiggestInterval)) //If the last update has been more than BiggestInterval minutes ago, update library
- {
- UpdateLibraryInternal();
- _updateLibraryRequested = null;
- }
- else if(_updateLibraryRequested is not null)
- {
- Log($"Buffering Library Updates (Updates in latest {((DateTime)_updateLibraryRequested).Add(TimeSpan.FromMinutes(BiggestInterval)).Subtract(DateTime.Now)} or {((DateTime)_updateLibraryRequested).Add(TimeSpan.FromMinutes(NoChangeTimeout)).Subtract(DateTime.Now)})");
- }
- }
-
- protected abstract void UpdateLibraryInternal();
- internal abstract bool Test();
-
- protected static class NetClient
- {
- public static Stream MakeRequest(string url, string authScheme, string auth, Logger? logger)
- {
- HttpClient client = new();
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authScheme, auth);
-
- HttpRequestMessage requestMessage = new ()
- {
- Method = HttpMethod.Get,
- RequestUri = new Uri(url)
- };
- try
- {
-
- HttpResponseMessage response = client.Send(requestMessage);
- logger?.WriteLine("LibraryManager.NetClient",
- $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
-
- if (response.StatusCode is HttpStatusCode.Unauthorized &&
- response.RequestMessage!.RequestUri!.AbsoluteUri != url)
- return MakeRequest(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);
- else if (response.IsSuccessStatusCode)
- return response.Content.ReadAsStream();
- else
- return Stream.Null;
- }
- catch (Exception e)
- {
- switch (e)
- {
- case HttpRequestException:
- logger?.WriteLine("LibraryManager.NetClient", $"Failed to make Request:\n\r{e}\n\rContinuing.");
- break;
- default:
- throw;
- }
- return Stream.Null;
- }
- }
-
- public static bool MakePost(string url, string authScheme, string auth, Logger? logger)
- {
- HttpClient client = new()
- {
- DefaultRequestHeaders =
- {
- { "Accept", "application/json" },
- { "Authorization", new AuthenticationHeaderValue(authScheme, auth).ToString() }
- }
- };
- HttpRequestMessage requestMessage = new ()
- {
- Method = HttpMethod.Post,
- RequestUri = new Uri(url)
- };
- HttpResponseMessage response = client.Send(requestMessage);
- logger?.WriteLine("LibraryManager.NetClient", $"POST {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}");
-
- if(response.StatusCode is HttpStatusCode.Unauthorized && response.RequestMessage!.RequestUri!.AbsoluteUri != url)
- return MakePost(response.RequestMessage!.RequestUri!.AbsoluteUri, authScheme, auth, logger);
- else if (response.IsSuccessStatusCode)
- return true;
- else
- return false;
- }
- }
-}
\ No newline at end of file
diff --git a/Tranga/LibraryConnectors/LibraryManagerJsonConverter.cs b/Tranga/LibraryConnectors/LibraryManagerJsonConverter.cs
deleted file mode 100644
index 23fbf3a..0000000
--- a/Tranga/LibraryConnectors/LibraryManagerJsonConverter.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace Tranga.LibraryConnectors;
-
-public class LibraryManagerJsonConverter : JsonConverter
-{
- private readonly GlobalBase _clone;
-
- internal LibraryManagerJsonConverter(GlobalBase clone)
- {
- this._clone = clone;
- }
-
- public override bool CanConvert(Type objectType)
- {
- return (objectType == typeof(LibraryConnector));
- }
-
- public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- JObject jo = JObject.Load(reader);
- if (jo["libraryType"]!.Value() == (byte)LibraryConnector.LibraryType.Komga)
- return new Komga(this._clone,
- jo.GetValue("baseUrl")!.Value()!,
- jo.GetValue("auth")!.Value()!);
-
- if (jo["libraryType"]!.Value() == (byte)LibraryConnector.LibraryType.Kavita)
- return new Kavita(this._clone,
- jo.GetValue("baseUrl")!.Value()!,
- jo.GetValue("auth")!.Value()!);
-
- throw new Exception();
- }
-
- public override bool CanWrite => false;
-
- ///
- /// Don't call this
- ///
- public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
- {
- throw new Exception("Dont call this");
- }
-}
\ No newline at end of file
diff --git a/Tranga/Manga.cs b/Tranga/Manga.cs
deleted file mode 100644
index ea7298a..0000000
--- a/Tranga/Manga.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Web;
-using Newtonsoft.Json;
-using Tranga.MangaConnectors;
-using static System.IO.UnixFileMode;
-
-namespace Tranga;
-
-///
-/// Contains information on a Publication (Manga)
-///
-public struct Manga
-{
- public string sortName { get; private set; }
- public List authors { get; private set; }
- // ReSharper disable once UnusedAutoPropertyAccessor.Global
- public Dictionary altTitles { get; private set; }
- // ReSharper disable once MemberCanBePrivate.Global
- public string? description { get; private set; }
- public string[] tags { get; private set; }
- // ReSharper disable once UnusedAutoPropertyAccessor.Global
- public string? coverUrl { get; private set; }
- public string? coverFileNameInCache { get; private set; }
- // ReSharper disable once UnusedAutoPropertyAccessor.Global
- public Dictionary links { get; }
- // ReSharper disable once MemberCanBePrivate.Global
- public int? year { get; private set; }
- public string? originalLanguage { get; }
- public ReleaseStatusByte releaseStatus { get; private set; }
- public enum ReleaseStatusByte : byte
- {
- Continuing = 0,
- Completed = 1,
- OnHiatus = 2,
- Cancelled = 3,
- Unreleased = 4
- };
- public string folderName { get; private set; }
- public string publicationId { get; }
- public string internalId { get; }
- public float ignoreChaptersBelow { get; set; }
- public float latestChapterDownloaded { get; set; }
- public float latestChapterAvailable { get; set; }
- public string websiteUrl { get; private set; }
- public MangaConnector mangaConnector { get; private set; }
-
- private static readonly Regex LegalCharacters = new (@"[A-Za-zÀ-ÖØ-öø-ÿ0-9 \.\-,'\'\)\(~!\+]*");
-
- [JsonConstructor]
- public Manga(MangaConnector mangaConnector, string sortName, List authors, string? description, Dictionary altTitles, string[] tags, string? coverUrl, string? coverFileNameInCache, Dictionary? links, int? year, string? originalLanguage, string publicationId, ReleaseStatusByte releaseStatus, string? websiteUrl, string? folderName = null, float? ignoreChaptersBelow = 0)
- {
- this.mangaConnector = mangaConnector;
- this.sortName = HttpUtility.HtmlDecode(sortName);
- this.authors = authors.Select(HttpUtility.HtmlDecode).ToList()!;
- this.description = HttpUtility.HtmlDecode(description);
- this.altTitles = altTitles.ToDictionary(a => HttpUtility.HtmlDecode(a.Key), a => HttpUtility.HtmlDecode(a.Value));
- this.tags = tags.Select(HttpUtility.HtmlDecode).ToArray()!;
- this.coverFileNameInCache = coverFileNameInCache;
- this.coverUrl = coverUrl;
- this.links = links ?? new Dictionary();
- this.year = year;
- this.originalLanguage = originalLanguage;
- this.publicationId = publicationId;
- this.folderName = folderName ?? string.Concat(LegalCharacters.Matches(HttpUtility.HtmlDecode(sortName)));
- while (this.folderName.EndsWith('.'))
- this.folderName = this.folderName.Substring(0, this.folderName.Length - 1);
- string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter));
- this.internalId = DateTime.Now.Ticks.ToString();
- this.ignoreChaptersBelow = ignoreChaptersBelow ?? 0f;
- this.latestChapterDownloaded = 0;
- this.latestChapterAvailable = 0;
- this.releaseStatus = releaseStatus;
- this.websiteUrl = websiteUrl??"";
- }
-
- public Manga WithMetadata(Manga newManga)
- {
- return this with
- {
- sortName = newManga.sortName,
- description = newManga.description,
- coverUrl = newManga.coverUrl,
- authors = authors.Union(newManga.authors).ToList(),
- altTitles = altTitles.UnionBy(newManga.altTitles, kv => kv.Key).ToDictionary(x => x.Key, x => x.Value),
- tags = tags.Union(newManga.tags).ToArray(),
- releaseStatus = newManga.releaseStatus,
- websiteUrl = newManga.websiteUrl,
- year = newManga.year,
- coverFileNameInCache = newManga.coverFileNameInCache
- };
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is not Manga compareManga)
- return false;
- return this.description == compareManga.description &&
- this.year == compareManga.year &&
- this.releaseStatus == compareManga.releaseStatus &&
- this.sortName == compareManga.sortName &&
- this.latestChapterAvailable.Equals(compareManga.latestChapterAvailable) &&
- this.authors.All(a => compareManga.authors.Contains(a)) &&
- (this.coverFileNameInCache??"").Equals(compareManga.coverFileNameInCache) &&
- (this.websiteUrl??"").Equals(compareManga.websiteUrl) &&
- this.tags.All(t => compareManga.tags.Contains(t));
- }
-
- public override string ToString()
- {
- return $"Publication {sortName} {internalId}";
- }
-
- public string CreatePublicationFolder(string downloadDirectory)
- {
- string publicationFolder = Path.Join(downloadDirectory, this.folderName);
- if(!Directory.Exists(publicationFolder))
- Directory.CreateDirectory(publicationFolder);
- if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- File.SetUnixFileMode(publicationFolder, GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute | UserRead | UserWrite | UserExecute);
- return publicationFolder;
- }
-
- public void MovePublicationFolder(string downloadDirectory, string newFolderName)
- {
- string oldPath = Path.Join(downloadDirectory, this.folderName);
- this.folderName = newFolderName;//Create new Path with the new folderName
- string newPath = CreatePublicationFolder(downloadDirectory);
- if (Directory.Exists(oldPath))
- {
- if (Directory.Exists(newPath)) //Move/Overwrite old Files, Delete old Directory
- {
- IEnumerable newPathFileNames = new DirectoryInfo(newPath).GetFiles().Select(fi => fi.Name);
- foreach(FileInfo fileInfo in new DirectoryInfo(oldPath).GetFiles().Where(fi => newPathFileNames.Contains(fi.Name) == false))
- File.Move(fileInfo.FullName, Path.Join(newPath, fileInfo.Name), true);
- Directory.Delete(oldPath);
- }else
- Directory.Move(oldPath, newPath);
- }
- }
-
- public void UpdateLatestDownloadedChapter(Chapter chapter)//TODO check files if chapters are all downloaded
- {
- float chapterNumber = Convert.ToSingle(chapter.chapterNumber, GlobalBase.numberFormatDecimalPoint);
- latestChapterDownloaded = latestChapterDownloaded < chapterNumber ? chapterNumber : latestChapterDownloaded;
- }
-
- public void SaveSeriesInfoJson(bool overwrite = false)
- {
- string publicationFolder = CreatePublicationFolder(TrangaSettings.downloadLocation);
- string seriesInfoPath = Path.Join(publicationFolder, "series.json");
- if(overwrite || (!overwrite && !File.Exists(seriesInfoPath)))
- File.WriteAllText(seriesInfoPath,this.GetSeriesInfoJson());
- if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- File.SetUnixFileMode(seriesInfoPath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite);
- }
-
- /// Serialized JSON String for series.json
- private string GetSeriesInfoJson()
- {
- SeriesInfo si = new (new Metadata(this));
- return System.Text.Json.JsonSerializer.Serialize(si);
- }
-
- //Only for series.json
- private struct SeriesInfo
- {
- // ReSharper disable once UnusedAutoPropertyAccessor.Local we need it, trust
- [JsonRequired]public Metadata metadata { get; }
- public SeriesInfo(Metadata metadata) => this.metadata = metadata;
- }
-
- //Only for series.json what an abomination, why are all the fields not-null????
- private struct Metadata
- {
- // ReSharper disable UnusedAutoPropertyAccessor.Local we need them all, trust me
- [JsonRequired] public string type { get; }
- [JsonRequired] public string publisher { get; }
- // ReSharper disable twice IdentifierTypo
- [JsonRequired] public int comicid { get; }
- [JsonRequired] public string booktype { get; }
- // ReSharper disable InconsistentNaming This one property is capitalized. Why?
- [JsonRequired] public string ComicImage { get; }
- [JsonRequired] public int total_issues { get; }
- [JsonRequired] public string publication_run { get; }
- [JsonRequired]public string name { get; }
- [JsonRequired]public string year { get; }
- [JsonRequired]public string status { get; }
- [JsonRequired]public string description_text { get; }
-
- public Metadata(Manga manga) : this(manga.sortName, manga.year.ToString() ?? string.Empty, manga.releaseStatus, manga.description ?? "")
- {
-
- }
-
- public Metadata(string name, string year, ReleaseStatusByte status, string description_text)
- {
- this.name = name;
- this.year = year;
- this.status = status switch
- {
- ReleaseStatusByte.Continuing => "Continuing",
- ReleaseStatusByte.Completed => "Ended",
- _ => Enum.GetName(status) ?? "Ended"
- };
- this.description_text = description_text;
-
- //kill it with fire, but otherwise Komga will not parse
- type = "Manga";
- publisher = "";
- comicid = 0;
- booktype = "";
- ComicImage = "";
- total_issues = 0;
- publication_run = "";
- }
- }
-}
\ No newline at end of file
diff --git a/Tranga/Server/RequestPath.cs b/Tranga/Server/RequestPath.cs
deleted file mode 100644
index cbf4d0d..0000000
--- a/Tranga/Server/RequestPath.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Net;
-using System.Text.RegularExpressions;
-
-namespace Tranga.Server;
-
-internal struct RequestPath
-{
- internal readonly string HttpMethod;
- internal readonly string RegexStr;
- internal readonly Func, ValueTuple> Method;
-
- public RequestPath(string httpHttpMethod, string regexStr,
- Func, ValueTuple> method)
- {
- this.HttpMethod = httpHttpMethod;
- this.RegexStr = regexStr + "(?:/?)";
- this.Method = method;
- }
-}
\ No newline at end of file
diff --git a/Tranga/Server/Server.cs b/Tranga/Server/Server.cs
deleted file mode 100644
index b7ce0f8..0000000
--- a/Tranga/Server/Server.cs
+++ /dev/null
@@ -1,269 +0,0 @@
-using System.Net;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Text.RegularExpressions;
-using Newtonsoft.Json;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.Metadata.Profiles.Exif;
-using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
-using ZstdSharp;
-
-namespace Tranga.Server;
-
-public partial class Server : GlobalBase, IDisposable
-{
- private readonly HttpListener _listener = new();
- private readonly Tranga _parent;
- private bool _running = true;
-
- private readonly List _apiRequestPaths;
-
- public Server(Tranga parent) : base(parent)
- {
- /*
- * Contains all valid Request Methods, Paths (with Regex Group Matching for specific Parameters) and Handling Methods
- */
- _apiRequestPaths = new List
- {
- new ("GET", @"/v2/Connector/Types", GetV2ConnectorTypes),
- new ("GET", @"/v2/Connector/([a-zA-Z]+)/GetManga", GetV2ConnectorConnectorNameGetManga),
- new ("GET", @"/v2/Mangas", GetV2Mangas),
- new ("GET", @"/v2/Manga/Search", GetV2MangaSearch),
- new ("GET", @"/v2/Manga", GetV2Manga),
- new ("GET", @"/v2/Manga/([-A-Za-z0-9]*={0,3})/Cover", GetV2MangaInternalIdCover),
- new ("GET", @"/v2/Manga/([-A-Za-z0-9]*={0,3})/Chapters", GetV2MangaInternalIdChapters),
- new ("GET", @"/v2/Manga/([-A-Za-z0-9]*={0,3})/Chapters/Latest", GetV2MangaInternalIdChaptersLatest),
- new ("POST", @"/v2/Manga/([-A-Za-z0-9]*={0,3})/ignoreChaptersBelow", PostV2MangaInternalIdIgnoreChaptersBelow),
- new ("POST", @"/v2/Manga/([-A-Za-z0-9]*={0,3})/moveFolder", PostV2MangaInternalIdMoveFolder),
- new ("GET", @"/v2/Manga/([-A-Za-z0-9]*={0,3})", GetV2MangaInternalId),
- new ("DELETE", @"/v2/Manga/([-A-Za-z0-9]*={0,3})", DeleteV2MangaInternalId),
- new ("GET", @"/v2/Jobs", GetV2Jobs),
- new ("GET", @"/v2/Jobs/Running", GetV2JobsRunning),
- new ("GET", @"/v2/Jobs/Waiting", GetV2JobsWaiting),
- new ("GET", @"/v2/Jobs/Monitoring", GetV2JobsMonitoring),
- new ("GET", @"/v2/Jobs/Standby", GetV2JobsStandby),
- new ("GET", @"/v2/Job/Types", GetV2JobTypes),
- new ("POST", @"/v2/Job/Create/([a-zA-Z]+)", PostV2JobCreateType),
- new ("GET", @"/v2/Job", GetV2Job),
- new ("GET", @"/v2/Job/([a-zA-Z\.]+-[-A-Za-z0-9+/]*={0,3}(?:-[0-9]+)?)/Progress", GetV2JobJobIdProgress),
- new ("POST", @"/v2/Job/([a-zA-Z\.]+-[-A-Za-z0-9+/]*={0,3}(?:-[0-9]+)?)/StartNow", PostV2JobJobIdStartNow),
- new ("POST", @"/v2/Job/([a-zA-Z\.]+-[-A-Za-z0-9+/]*={0,3}(?:-[0-9]+)?)/Cancel", PostV2JobJobIdCancel),
- new ("GET", @"/v2/Job/([a-zA-Z\.]+-[-A-Za-z0-9+/]*={0,3}(?:-[0-9]+)?)", GetV2JobJobId),
- new ("DELETE", @"/v2/Job/([a-zA-Z\.]+-[-A-Za-z0-9+/]*={0,3}(?:-[0-9]+)?)", DeleteV2JobJobId),
- new ("GET", @"/v2/Settings", GetV2Settings),
- new ("GET", @"/v2/Settings/UserAgent", GetV2SettingsUserAgent),
- new ("POST", @"/v2/Settings/UserAgent", PostV2SettingsUserAgent),
- new ("GET", @"/v2/Settings/RateLimit/Types", GetV2SettingsRateLimitTypes),
- new ("GET", @"/v2/Settings/RateLimit", GetV2SettingsRateLimit),
- new ("POST", @"/v2/Settings/RateLimit", PostV2SettingsRateLimit),
- new ("GET", @"/v2/Settings/RateLimit/([a-zA-Z]+)", GetV2SettingsRateLimitType),
- new ("POST", @"/v2/Settings/RateLimit/([a-zA-Z]+)", PostV2SettingsRateLimitType),
- new ("GET", @"/v2/Settings/AprilFoolsMode", GetV2SettingsAprilFoolsMode),
- new ("POST", @"/v2/Settings/AprilFoolsMode", PostV2SettingsAprilFoolsMode),
- new ("GET", @"/v2/Settings/CompressImages", GetV2SettingsCompressImages),
- new ("POST", @"/v2/Settings/CompressImages", PostV2SettingsCompressImages),
- new ("GET", @"/v2/Settings/BWImages", GetV2SettingsBwImages),
- new ("POST", @"/v2/Settings/BWImages", PostV2SettingsBwImages),
- new ("POST", @"/v2/Settings/DownloadLocation", PostV2SettingsDownloadLocation),
- new ("GET", @"/v2/LibraryConnector", GetV2LibraryConnector),
- new ("GET", @"/v2/LibraryConnector/Types", GetV2LibraryConnectorTypes),
- new ("GET", @"/v2/LibraryConnector/([a-zA-Z]+)", GetV2LibraryConnectorType),
- new ("POST", @"/v2/LibraryConnector/([a-zA-Z]+)", PostV2LibraryConnectorType),
- new ("POST", @"/v2/LibraryConnector/([a-zA-Z]+)/Test", PostV2LibraryConnectorTypeTest),
- new ("DELETE", @"/v2/LibraryConnector/([a-zA-Z]+)", DeleteV2LibraryConnectorType),
- new ("GET", @"/v2/NotificationConnector", GetV2NotificationConnector),
- new ("GET", @"/v2/NotificationConnector/Types", GetV2NotificationConnectorTypes),
- new ("GET", @"/v2/NotificationConnector/([a-zA-Z]+)", GetV2NotificationConnectorType),
- new ("POST", @"/v2/NotificationConnector/([a-zA-Z]+)", PostV2NotificationConnectorType),
- new ("POST", @"/v2/NotificationConnector/([a-zA-Z]+)/Test", PostV2NotificationConnectorTypeTest),
- new ("DELETE", @"/v2/NotificationConnector/([a-zA-Z]+)", DeleteV2NotificationConnectorType),
- new ("GET", @"/v2/LogFile", GetV2LogFile),
- new ("GET", @"/v2/Ping", GetV2Ping),
- new ("POST", @"/v2/Ping", PostV2Ping)
- };
-
- this._parent = parent;
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- this._listener.Prefixes.Add($"http://*:{TrangaSettings.apiPortNumber}/");
- else
- this._listener.Prefixes.Add($"http://localhost:{TrangaSettings.apiPortNumber}/");
- Thread listenThread = new(Listen);
- listenThread.Start();
- while(_parent.keepRunning && _running)
- Thread.Sleep(100);
- this.Dispose();
- }
-
- private void Listen()
- {
- this._listener.Start();
- foreach (string prefix in this._listener.Prefixes)
- Log($"Listening on {prefix}");
- while (this._listener.IsListening && _parent.keepRunning)
- {
- try
- {
- HttpListenerContext context = this._listener.GetContext();
- //Log($"{context.Request.HttpMethod} {context.Request.Url} {context.Request.UserAgent}");
- Task t = new(() =>
- {
- HandleRequest(context);
- });
- t.Start();
- }
- catch (HttpListenerException)
- {
-
- }
- }
- }
-
- private void HandleRequest(HttpListenerContext context)
- {
- HttpListenerRequest request = context.Request;
- HttpListenerResponse response = context.Response;
- if (request.HttpMethod == "OPTIONS")
- {
- SendResponse(HttpStatusCode.NoContent, response);//Response always contains all valid Request-Methods
- return;
- }
- if (request.Url!.LocalPath.Contains("favicon"))
- {
- SendResponse(HttpStatusCode.NoContent, response);
- return;
- }
- string path = Regex.Match(request.Url.LocalPath, @"\/[a-zA-Z0-9\.+/=-]+(\/[a-zA-Z0-9\.+/=-]+)*").Value; //Local Path
-
- if (!Regex.IsMatch(path, "/v2(/.*)?")) //Use only v2 API
- {
- SendResponse(HttpStatusCode.NotFound, response, "Use Version 2 API");
- return;
- }
-
- Dictionary requestVariables = GetRequestVariables(request.Url!.Query); //Variables in the URI
- Dictionary requestBody = GetRequestBody(request); //Variables in the JSON body
- Dictionary requestParams = requestVariables.UnionBy(requestBody, v => v.Key)
- .ToDictionary(kv => kv.Key, kv => kv.Value); //The actual variable used for the API
-
- ValueTuple responseMessage; //Used to respond to the HttpRequest
- if (_apiRequestPaths.Any(p => p.HttpMethod == request.HttpMethod && Regex.Match(path, p.RegexStr).Length == path.Length)) //Check if Request-Path is valid
- {
- RequestPath requestPath =
- _apiRequestPaths.First(p => p.HttpMethod == request.HttpMethod && Regex.Match(path, p.RegexStr).Length == path.Length);
- responseMessage =
- requestPath.Method.Invoke(Regex.Match(path, requestPath.RegexStr).Groups, requestParams); //Get HttpResponse content
- }
- else
- responseMessage = new ValueTuple(HttpStatusCode.MethodNotAllowed, "Unknown Request Path");
-
- SendResponse(responseMessage.Item1, response, responseMessage.Item2);
- }
-
- private Dictionary GetRequestVariables(string query)
- {
- Dictionary ret = new();
- Regex queryRex = new(@"\?{1}&?([A-z0-9-=]+=[A-z0-9-=]+)+(&[A-z0-9-=]+=[A-z0-9-=]+)*");
- if (!queryRex.IsMatch(query))
- return ret;
- query = query.Substring(1);
- foreach (string keyValuePair in query.Split('&').Where(str => str.Length >= 3))
- {
- string var = keyValuePair.Split('=')[0];
- string val = Regex.Replace(keyValuePair.Substring(var.Length + 1), "%20", " ");
- val = Regex.Replace(val, "%[0-9]{2}", "");
- ret.Add(var, val);
- }
- return ret;
- }
-
- private Dictionary GetRequestBody(HttpListenerRequest request)
- {
- if (!request.HasEntityBody)
- {
- //Nospam Log("No request body");
- return new Dictionary();
- }
- Stream body = request.InputStream;
- Encoding encoding = request.ContentEncoding;
- using StreamReader streamReader = new (body, encoding);
- try
- {
- Dictionary requestBody =
- JsonConvert.DeserializeObject>(streamReader.ReadToEnd())
- ?? new();
- return requestBody;
- }
- catch (JsonException e)
- {
- Log(e.Message);
- }
- return new Dictionary();
- }
-
- private void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null)
- {
- //Log($"Response: {statusCode} {content}");
- response.StatusCode = (int)statusCode;
- response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
- response.AddHeader("Access-Control-Allow-Methods", "GET, POST, DELETE");
- response.AddHeader("Access-Control-Max-Age", "1728000");
- response.AddHeader("Access-Control-Allow-Origin", "*");
- response.AddHeader("Content-Encoding", "zstd");
-
- using CompressionStream compressor = new (response.OutputStream, 5);
- try
- {
- if (content is Stream stream)
- {
- response.ContentType = "text/plain";
- response.AddHeader("Cache-Control", "private, no-store");
- stream.CopyTo(compressor);
- stream.Close();
- }else if (content is Image image)
- {
- response.ContentType = image.Metadata.DecodedImageFormat?.DefaultMimeType ?? PngFormat.Instance.DefaultMimeType;
- response.AddHeader("Cache-Control", "public, max-age=3600");
- response.AddHeader("Expires", $"{DateTime.Now.AddHours(1):ddd\\,\\ dd\\ MMM\\ yyyy\\ HH\\:mm\\:ss} GMT");
- string lastModifiedStr = "";
- if (image.Metadata.IptcProfile is not null)
- {
- DateTime date = DateTime.ParseExact(image.Metadata.IptcProfile.GetValues(IptcTag.CreatedDate).First().Value, "yyyyMMdd",null);
- DateTime time = DateTime.ParseExact(image.Metadata.IptcProfile.GetValues(IptcTag.CreatedTime).First().Value, "HHmmssK",null);
- lastModifiedStr = $"{date:ddd\\,\\ dd\\ MMM\\ yyyy} {time:HH\\:mm\\:ss} GMT";
- }else if (image.Metadata.ExifProfile is not null)
- {
- DateTime datetime = DateTime.ParseExact(image.Metadata.ExifProfile.Values.FirstOrDefault(value => value.Tag == ExifTag.DateTime)?.ToString() ?? "2000:01:01 01:01:01", "yyyy:MM:dd HH:mm:ss", null);
- lastModifiedStr = $"{datetime:ddd\\,\\ dd\\ MMM\\ yyyy\\ HH\\:mm\\:ss} GMT";
- }
- if(lastModifiedStr.Length>0)
- response.AddHeader("Last-Modified", lastModifiedStr);
- image.Save(compressor, image.Metadata.DecodedImageFormat ?? PngFormat.Instance);
- image.Dispose();
- }
- else
- {
- response.ContentType = "application/json";
- response.AddHeader("Cache-Control", "private, no-store");
- if(content is not null)
- new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content))).CopyTo(compressor);
- else
- compressor.Write(Array.Empty());
- }
-
- compressor.Flush();
- response.OutputStream.Close();
- }
- catch (HttpListenerException e)
- {
- Log(e.ToString());
- }
- }
-
-
- public void Dispose()
- {
- _running = false;
- ((IDisposable)_listener).Dispose();
- }
-}
\ No newline at end of file
diff --git a/Tranga/Server/v2Connector.cs b/Tranga/Server/v2Connector.cs
deleted file mode 100644
index 58fd354..0000000
--- a/Tranga/Server/v2Connector.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Net;
-using System.Text.RegularExpressions;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Server;
-
-public partial class Server
-{
- private ValueTuple GetV2ConnectorTypes(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.Accepted, _parent.GetConnectors());
- }
-
- private ValueTuple GetV2ConnectorConnectorNameGetManga(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.GetConnectors().Any(mangaConnector => mangaConnector.name == groups[1].Value)||
- !_parent.TryGetConnector(groups[1].Value, out MangaConnector? connector) ||
- connector is null)
- return new ValueTuple(HttpStatusCode.BadRequest, $"Connector '{groups[1].Value}' does not exist.");
-
- if (requestParameters.TryGetValue("title", out string? title))
- {
- return (HttpStatusCode.OK, connector.GetManga(title));
- }else if (requestParameters.TryGetValue("url", out string? url))
- {
- return (HttpStatusCode.OK, connector.GetMangaFromUrl(url));
- }else
- return new ValueTuple(HttpStatusCode.BadRequest, "Parameter 'title' or 'url' has to be set.");
- }
-}
\ No newline at end of file
diff --git a/Tranga/Server/v2Jobs.cs b/Tranga/Server/v2Jobs.cs
deleted file mode 100644
index a0eb9b4..0000000
--- a/Tranga/Server/v2Jobs.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System.Net;
-using System.Text.RegularExpressions;
-using Tranga.Jobs;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Server;
-
-public partial class Server
-{
- private ValueTuple GetV2Jobs(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, _parent.jobBoss.jobs.Select(job => job.id));
- }
-
- private ValueTuple GetV2JobsRunning(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, _parent.jobBoss.jobs
- .Where(job => job.progressToken.state is ProgressToken.State.Running)
- .Select(job => job.id));
- }
-
- private ValueTuple GetV2JobsWaiting(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, _parent.jobBoss.jobs
- .Where(job => job.progressToken.state is ProgressToken.State.Waiting)
- .Select(job => job.id));
- }
-
- private ValueTuple GetV2JobsStandby(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, _parent.jobBoss.jobs
- .Where(job => job.progressToken.state is ProgressToken.State.Standby)
- .Select(job => job.id));
- }
-
- private ValueTuple GetV2JobsMonitoring(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, _parent.jobBoss.jobs
- .Where(job => job.jobType is Job.JobType.DownloadNewChaptersJob)
- .Select(job => job.id));
- }
-
- private ValueTuple GetV2JobTypes(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK,
- Enum.GetValues().ToDictionary(b => (byte)b, b => Enum.GetName(b)));
- }
-
- private ValueTuple PostV2JobCreateType(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !Enum.TryParse(groups[1].Value, true, out Job.JobType jobType))
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"JobType {groups[1].Value} does not exist.");
- }
-
- string? mangaId;
- Manga? manga;
- switch (jobType)
- {
- case Job.JobType.MonitorManga:
- if(!requestParameters.TryGetValue("internalId", out mangaId) ||
- !_parent.TryGetPublicationById(mangaId, out manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, "'internalId' Parameter missing, or is not a valid ID.");
- if(!requestParameters.TryGetValue("interval", out string? intervalStr) ||
- !TimeSpan.TryParse(intervalStr, out TimeSpan interval))
- return new ValueTuple(HttpStatusCode.InternalServerError, "'interval' Parameter missing, or is not in correct format.");
- requestParameters.TryGetValue("language", out string? language);
- if (requestParameters.TryGetValue("customFolder", out string? folder))
- manga.Value.MovePublicationFolder(TrangaSettings.downloadLocation, folder);
- if (requestParameters.TryGetValue("startChapter", out string? startChapterStr) &&
- float.TryParse(startChapterStr, out float startChapter))
- {
- Manga manga1 = manga.Value;
- manga1.ignoreChaptersBelow = startChapter;
- }
-
- return _parent.jobBoss.AddJob(new DownloadNewChapters(this, ((Manga)manga).mangaConnector,
- ((Manga)manga).internalId, true, interval, language)) switch
- {
- true => new ValueTuple(HttpStatusCode.OK, null),
- false => new ValueTuple(HttpStatusCode.Conflict, "Job already exists."),
- };
- case Job.JobType.UpdateMetaDataJob:
- if(!requestParameters.TryGetValue("internalId", out mangaId) ||
- !_parent.TryGetPublicationById(mangaId, out manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, "InternalId Parameter missing, or is not a valid ID.");
- return _parent.jobBoss.AddJob(new UpdateMetadata(this, ((Manga)manga).internalId)) switch
- {
- true => new ValueTuple(HttpStatusCode.OK, null),
- false => new ValueTuple(HttpStatusCode.Conflict, "Job already exists."),
- };
- case Job.JobType.DownloadNewChaptersJob: //TODO
- case Job.JobType.DownloadChapterJob: //TODO
- default: return new ValueTuple(HttpStatusCode.MethodNotAllowed, $"JobType {Enum.GetName(jobType)} is not supported.");
- }
- }
-
- private ValueTuple GetV2JobJobId(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !_parent.jobBoss.TryGetJobById(groups[1].Value, out Job? job) ||
- job is null)
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"Job with ID: '{groups[1].Value}' does not exist.");
- }
- return new ValueTuple(HttpStatusCode.OK, job);
- }
-
- private ValueTuple DeleteV2JobJobId(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !_parent.jobBoss.TryGetJobById(groups[1].Value, out Job? job) ||
- job is null)
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"Job with ID: '{groups[1].Value}' does not exist.");
- }
-
- _parent.jobBoss.RemoveJob(job);
- return new ValueTuple(HttpStatusCode.OK, null);
- }
-
- private ValueTuple GetV2JobJobIdProgress(GroupCollection groups, Dictionary requestParameters)
- {
-
- if (groups.Count < 1 ||
- !_parent.jobBoss.TryGetJobById(groups[1].Value, out Job? job) ||
- job is null)
- {
- return new ValueTuple(HttpStatusCode.BadRequest, $"Job with ID: '{groups[1].Value}' does not exist.");
- }
- return new ValueTuple(HttpStatusCode.OK, job.progressToken);
- }
-
- private ValueTuple PostV2JobJobIdStartNow(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !_parent.jobBoss.TryGetJobById(groups[1].Value, out Job? job) ||
- job is null)
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"Job with ID: '{groups[1].Value}' does not exist.");
- }
- _parent.jobBoss.AddJobs(job.ExecuteReturnSubTasks(_parent.jobBoss));
- return new ValueTuple(HttpStatusCode.OK, null);
- }
-
- private ValueTuple PostV2JobJobIdCancel(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !_parent.jobBoss.TryGetJobById(groups[1].Value, out Job? job) ||
- job is null)
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"Job with ID: '{groups[1].Value}' does not exist.");
- }
- job.Cancel();
- return new ValueTuple(HttpStatusCode.OK, null);
- }
-
- private ValueTuple GetV2Job(GroupCollection groups, Dictionary requestParameters)
- {
- if(!requestParameters.TryGetValue("jobIds", out string? jobIdListStr))
- return new ValueTuple(HttpStatusCode.BadRequest, "Missing parameter 'jobIds'.");
- string[] jobIdList = jobIdListStr.Split(',');
- List ret = new();
- foreach (string jobId in jobIdList)
- {
- if(!_parent.jobBoss.TryGetJobById(jobId, out Job? job) || job is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Job with id '{jobId}' not found.");
- ret.Add(job);
- }
-
- return new ValueTuple(HttpStatusCode.OK, ret);
- }
-}
\ No newline at end of file
diff --git a/Tranga/Server/v2LibraryConnectors.cs b/Tranga/Server/v2LibraryConnectors.cs
deleted file mode 100644
index 85c2ee0..0000000
--- a/Tranga/Server/v2LibraryConnectors.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using System.Net;
-using System.Text.RegularExpressions;
-using Tranga.LibraryConnectors;
-
-namespace Tranga.Server;
-
-public partial class Server
-{
- private ValueTuple GetV2LibraryConnector(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, libraryConnectors);
- }
-
- private ValueTuple GetV2LibraryConnectorTypes(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK,
- Enum.GetValues().ToDictionary(b => (byte)b, b => Enum.GetName(b)));
- }
-
- private ValueTuple GetV2LibraryConnectorType(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !Enum.TryParse(groups[1].Value, true, out LibraryConnector.LibraryType libraryType))
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"LibraryType {groups[1].Value} does not exist.");
- }
-
- if(libraryConnectors.All(lc => lc.libraryType != libraryType))
- return new ValueTuple(HttpStatusCode.NotFound, $"LibraryType {Enum.GetName(libraryType)} not configured.");
- else
- return new ValueTuple(HttpStatusCode.OK, libraryConnectors.First(lc => lc.libraryType == libraryType));
- }
-
- private ValueTuple PostV2LibraryConnectorType(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !Enum.TryParse(groups[1].Value, true, out LibraryConnector.LibraryType libraryType))
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"LibraryType {groups[1].Value} does not exist.");
- }
-
- if(!requestParameters.TryGetValue("url", out string? url))
- return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'url' missing.");
- if(!requestParameters.TryGetValue("username", out string? username))
- return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'username' missing.");
- if(!requestParameters.TryGetValue("password", out string? password))
- return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'password' missing.");
-
- switch (libraryType)
- {
- case LibraryConnector.LibraryType.Kavita:
- Kavita kavita = new (this, url, username, password);
- libraryConnectors.RemoveWhere(lc => lc.libraryType == LibraryConnector.LibraryType.Kavita);
- libraryConnectors.Add(kavita);
- return new ValueTuple(HttpStatusCode.OK, kavita);
- case LibraryConnector.LibraryType.Komga:
- Komga komga = new (this, url, username, password);
- libraryConnectors.RemoveWhere(lc => lc.libraryType == LibraryConnector.LibraryType.Komga);
- libraryConnectors.Add(komga);
- return new ValueTuple(HttpStatusCode.OK, komga);
- default: return new ValueTuple(HttpStatusCode.MethodNotAllowed, $"LibraryType {Enum.GetName(libraryType)} is not supported.");
- }
- }
-
- private ValueTuple PostV2LibraryConnectorTypeTest(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !Enum.TryParse(groups[1].Value, true, out LibraryConnector.LibraryType libraryType))
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"LibraryType {groups[1].Value} does not exist.");
- }
-
- if(!requestParameters.TryGetValue("url", out string? url))
- return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'url' missing.");
- if(!requestParameters.TryGetValue("username", out string? username))
- return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'username' missing.");
- if(!requestParameters.TryGetValue("password", out string? password))
- return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'password' missing.");
-
- switch (libraryType)
- {
- case LibraryConnector.LibraryType.Kavita:
- Kavita kavita = new (this, url, username, password);
- return kavita.Test() switch
- {
- true => new ValueTuple(HttpStatusCode.OK, kavita),
- _ => new ValueTuple(HttpStatusCode.FailedDependency, kavita)
- };
- case LibraryConnector.LibraryType.Komga:
- Komga komga = new (this, url, username, password);
- return komga.Test() switch
- {
- true => new ValueTuple(HttpStatusCode.OK, komga),
- _ => new ValueTuple(HttpStatusCode.FailedDependency, komga)
- };
- default: return new ValueTuple(HttpStatusCode.MethodNotAllowed, $"LibraryType {Enum.GetName(libraryType)} is not supported.");
- }
- }
-
- private ValueTuple DeleteV2LibraryConnectorType(GroupCollection groups, Dictionary requestParameters)
- {
- if (groups.Count < 1 ||
- !Enum.TryParse(groups[1].Value, true, out LibraryConnector.LibraryType libraryType))
- {
- return new ValueTuple(HttpStatusCode.NotFound, $"LibraryType {groups[1].Value} does not exist.");
- }
-
- if(libraryConnectors.All(lc => lc.libraryType != libraryType))
- return new ValueTuple(HttpStatusCode.NotFound, $"LibraryType {Enum.GetName(libraryType)} not configured.");
- else
- {
- libraryConnectors.Remove(libraryConnectors.First(lc => lc.libraryType == libraryType));
- return new ValueTuple(HttpStatusCode.OK, null);
- }
- }
-}
\ No newline at end of file
diff --git a/Tranga/Server/v2Manga.cs b/Tranga/Server/v2Manga.cs
deleted file mode 100644
index cad38d6..0000000
--- a/Tranga/Server/v2Manga.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Processing;
-using System.Net;
-using System.Text.RegularExpressions;
-using SixLabors.ImageSharp.Processing.Processors.Transforms;
-using Tranga.Jobs;
-using Tranga.MangaConnectors;
-
-namespace Tranga.Server;
-
-public partial class Server
-{
- private ValueTuple GetV2Mangas(GroupCollection groups, Dictionary requestParameters)
- {
- return new ValueTuple(HttpStatusCode.OK, GetAllCachedManga().Select(m => m.internalId));
- }
-
- private ValueTuple GetV2Manga(GroupCollection groups, Dictionary requestParameters)
- {
- if(!requestParameters.TryGetValue("mangaIds", out string? mangaIdListStr))
- return new ValueTuple(HttpStatusCode.BadRequest, "Missing parameter 'mangaIds'.");
- string[] mangaIdList = mangaIdListStr.Split(',').Distinct().ToArray();
- List ret = new();
- foreach (string mangaId in mangaIdList)
- {
- if(!_parent.TryGetPublicationById(mangaId, out Manga? manga) || manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with id '{mangaId}' not found.");
- ret.Add(manga.Value);
- }
-
- return new ValueTuple(HttpStatusCode.OK, ret);
- }
-
- private ValueTuple GetV2MangaSearch(GroupCollection groups, Dictionary requestParameters)
- {
- if(!requestParameters.TryGetValue("title", out string? title))
- return new ValueTuple(HttpStatusCode.BadRequest, "Missing parameter 'title'.");
- List ret = new();
- List threads = new();
- foreach (MangaConnector mangaConnector in _connectors)
- {
- Thread t = new (() =>
- {
- ret.AddRange(mangaConnector.GetManga(title));
- });
- t.Start();
- threads.Add(t);
- }
- while(threads.Any(t => t.ThreadState is ThreadState.Running or ThreadState.WaitSleepJoin))
- Thread.Sleep(10);
-
- return new ValueTuple(HttpStatusCode.OK, ret);
- }
-
- private ValueTuple GetV2MangaInternalId(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
- return new ValueTuple(HttpStatusCode.OK, manga);
- }
-
- private ValueTuple DeleteV2MangaInternalId(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
- Job[] jobs = _parent.jobBoss.GetJobsLike(publication: manga).ToArray();
- _parent.jobBoss.RemoveJobs(jobs);
- RemoveMangaFromCache(groups[1].Value);
- return new ValueTuple(HttpStatusCode.OK, null);
- }
-
- private ValueTuple GetV2MangaInternalIdCover(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
- string filePath = manga.Value.coverFileNameInCache!;
- if(!File.Exists(filePath))
- return new ValueTuple(HttpStatusCode.NotFound, "Cover-File not found.");
-
- Image image = Image.Load(filePath);
- if (requestParameters.TryGetValue("dimensions", out string? dimensionsStr))
- {
- Regex dimensionsRex = new(@"([0-9]+)x([0-9]+)");
- if(!dimensionsRex.IsMatch(dimensionsStr))
- return new ValueTuple(HttpStatusCode.BadRequest, "Requested dimensions not in required format.");
- Match m = dimensionsRex.Match(dimensionsStr);
- int width = int.Parse(m.Groups[1].Value);
- int height = int.Parse(m.Groups[2].Value);
- double aspectRequested = (double)width / (double)height;
-
- double aspectCover = (double)image.Width / (double)image.Height;
-
- Size newSize = aspectRequested > aspectCover
- ? new Size(width, (width / image.Width) * image.Height)
- : new Size((height / image.Height) * image.Width, height);
-
- image.Mutate(x => x.Resize(newSize, CubicResampler.Robidoux, true));
- }
- return new ValueTuple(HttpStatusCode.OK, image);
- }
-
- private ValueTuple GetV2MangaInternalIdChapters(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
-
- Chapter[] chapters = requestParameters.TryGetValue("language", out string? parameter) switch
- {
- true => manga.Value.mangaConnector.GetChapters((Manga)manga, parameter),
- false => manga.Value.mangaConnector.GetChapters((Manga)manga)
- };
- return new ValueTuple(HttpStatusCode.OK, chapters);
- }
-
- private ValueTuple GetV2MangaInternalIdChaptersLatest(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
-
- float latest = requestParameters.TryGetValue("language", out string? parameter) switch
- {
- true => manga.Value.mangaConnector.GetChapters(manga.Value, parameter).Max().chapterNumber,
- false => manga.Value.mangaConnector.GetChapters(manga.Value).Max().chapterNumber
- };
- return new ValueTuple(HttpStatusCode.OK, latest);
- }
-
- private ValueTuple PostV2MangaInternalIdIgnoreChaptersBelow(GroupCollection groups, Dictionary requestParameters)
- {
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
- if (requestParameters.TryGetValue("startChapter", out string? startChapterStr) &&
- float.TryParse(startChapterStr, out float startChapter))
- {
- Manga manga1 = manga.Value;
- manga1.ignoreChaptersBelow = startChapter;
- return new ValueTuple(HttpStatusCode.OK, null);
- }else
- return new ValueTuple(HttpStatusCode.InternalServerError, "Parameter 'startChapter' missing, or failed to parse.");
- }
-
- private ValueTuple PostV2MangaInternalIdMoveFolder(GroupCollection groups, Dictionary requestParameters)
- {
-
- if(groups.Count < 1 ||
- !_parent.TryGetPublicationById(groups[1].Value, out Manga? manga) ||
- manga is null)
- return new ValueTuple(HttpStatusCode.NotFound, $"Manga with ID '{groups[1].Value} could not be found.'");
- if(!requestParameters.TryGetValue("location", out string? newFolder))
- return new ValueTuple(HttpStatusCode.BadRequest, "Parameter 'location' missing.");
- manga.Value.MovePublicationFolder(TrangaSettings.downloadLocation, newFolder);
- return new ValueTuple