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(HttpStatusCode.OK, null); - } -} \ No newline at end of file diff --git a/Tranga/Server/v2Miscellaneous.cs b/Tranga/Server/v2Miscellaneous.cs deleted file mode 100644 index 08ff29f..0000000 --- a/Tranga/Server/v2Miscellaneous.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Net; -using System.Text.RegularExpressions; - -namespace Tranga.Server; - -public partial class Server -{ - private ValueTuple GetV2LogFile(GroupCollection groups, Dictionary requestParameters) - { - if (logger is null || !File.Exists(logger?.logFilePath)) - { - return new ValueTuple(HttpStatusCode.NotFound, "Missing Logfile"); - } - - FileStream logFile = new (logger.logFilePath, FileMode.Open, FileAccess.Read); - FileStream content = new(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 0, FileOptions.DeleteOnClose); - logFile.Position = 0; - logFile.CopyTo(content); - content.Position = 0; - logFile.Dispose(); - return new ValueTuple(HttpStatusCode.OK, content); - } - - private ValueTuple GetV2Ping(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.Accepted, "Pong!"); - } - - private ValueTuple PostV2Ping(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.Accepted, "Pong!"); - } -} \ No newline at end of file diff --git a/Tranga/Server/v2NotificationConnectors.cs b/Tranga/Server/v2NotificationConnectors.cs deleted file mode 100644 index d6917fb..0000000 --- a/Tranga/Server/v2NotificationConnectors.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Net; -using System.Text.RegularExpressions; -using Tranga.NotificationConnectors; - -namespace Tranga.Server; - -public partial class Server -{ - private ValueTuple GetV2NotificationConnector(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, notificationConnectors); - } - - private ValueTuple GetV2NotificationConnectorTypes(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, - Enum.GetValues().ToDictionary(b => (byte)b, b => Enum.GetName(b))); - } - - private ValueTuple GetV2NotificationConnectorType(GroupCollection groups, Dictionary requestParameters) - { - if (groups.Count < 1 || - !Enum.TryParse(groups[1].Value, true, out NotificationConnector.NotificationConnectorType notificationConnectorType)) - { - return new ValueTuple(HttpStatusCode.NotFound, $"NotificationType {groups[1].Value} does not exist."); - } - - if(notificationConnectors.All(nc => nc.notificationConnectorType != notificationConnectorType)) - return new ValueTuple(HttpStatusCode.NotFound, $"NotificationType {Enum.GetName(notificationConnectorType)} not configured."); - else - return new ValueTuple(HttpStatusCode.OK, notificationConnectors.First(nc => nc.notificationConnectorType != notificationConnectorType)); - } - - private ValueTuple PostV2NotificationConnectorType(GroupCollection groups, Dictionary requestParameters) - { - if (groups.Count < 1 || - !Enum.TryParse(groups[1].Value, true, out NotificationConnector.NotificationConnectorType notificationConnectorType)) - { - return new ValueTuple(HttpStatusCode.NotFound, $"NotificationType {groups[1].Value} does not exist."); - } - - string? url; - switch (notificationConnectorType) - { - case NotificationConnector.NotificationConnectorType.Gotify: - if(!requestParameters.TryGetValue("url", out url)) - return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'url' missing."); - if(!requestParameters.TryGetValue("appToken", out string? appToken)) - return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'appToken' missing."); - Gotify gotify = new (this, url, appToken); - this.notificationConnectors.RemoveWhere(nc => - nc.notificationConnectorType == NotificationConnector.NotificationConnectorType.Gotify); - this.notificationConnectors.Add(gotify); - return new ValueTuple(HttpStatusCode.OK, gotify); - case NotificationConnector.NotificationConnectorType.LunaSea: - if(!requestParameters.TryGetValue("webhook", out string? webhook)) - return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'webhook' missing."); - LunaSea lunaSea = new (this, webhook); - this.notificationConnectors.RemoveWhere(nc => - nc.notificationConnectorType == NotificationConnector.NotificationConnectorType.LunaSea); - this.notificationConnectors.Add(lunaSea); - return new ValueTuple(HttpStatusCode.OK, lunaSea); - case NotificationConnector.NotificationConnectorType.Ntfy: - if(!requestParameters.TryGetValue("url", out 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."); - Ntfy ntfy = new(this, url, username, password, null); - this.notificationConnectors.RemoveWhere(nc => - nc.notificationConnectorType == NotificationConnector.NotificationConnectorType.Ntfy); - this.notificationConnectors.Add(ntfy); - return new ValueTuple(HttpStatusCode.OK, ntfy); - default: - return new ValueTuple(HttpStatusCode.MethodNotAllowed, $"NotificationType {Enum.GetName(notificationConnectorType)} is not supported."); - } - } - - private ValueTuple PostV2NotificationConnectorTypeTest(GroupCollection groups, Dictionary requestParameters) - { - if (groups.Count < 1 || - !Enum.TryParse(groups[1].Value, true, out NotificationConnector.NotificationConnectorType notificationConnectorType)) - { - return new ValueTuple(HttpStatusCode.NotFound, $"NotificationType {groups[1].Value} does not exist."); - } - - string? url; - switch (notificationConnectorType) - { - case NotificationConnector.NotificationConnectorType.Gotify: - if(!requestParameters.TryGetValue("url", out url)) - return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'url' missing."); - if(!requestParameters.TryGetValue("appToken", out string? appToken)) - return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'appToken' missing."); - Gotify gotify = new (this, url, appToken); - gotify.SendNotification("Tranga Test", "It was successful :3"); - return new ValueTuple(HttpStatusCode.OK, gotify); - case NotificationConnector.NotificationConnectorType.LunaSea: - if(!requestParameters.TryGetValue("webhook", out string? webhook)) - return new ValueTuple(HttpStatusCode.NotAcceptable, "Parameter 'webhook' missing."); - LunaSea lunaSea = new (this, webhook); - lunaSea.SendNotification("Tranga Test", "It was successful :3"); - return new ValueTuple(HttpStatusCode.OK, lunaSea); - case NotificationConnector.NotificationConnectorType.Ntfy: - if(!requestParameters.TryGetValue("url", out 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."); - Ntfy ntfy = new(this, url, username, password, null); - ntfy.SendNotification("Tranga Test", "It was successful :3"); - return new ValueTuple(HttpStatusCode.OK, ntfy); - default: - return new ValueTuple(HttpStatusCode.MethodNotAllowed, $"NotificationType {Enum.GetName(notificationConnectorType)} is not supported."); - } - } - - private ValueTuple DeleteV2NotificationConnectorType(GroupCollection groups, Dictionary requestParameters) - { - if (groups.Count < 1 || - !Enum.TryParse(groups[1].Value, true, out NotificationConnector.NotificationConnectorType notificationConnectorType)) - { - return new ValueTuple(HttpStatusCode.NotFound, $"NotificationType {groups[1].Value} does not exist."); - } - - if(notificationConnectors.All(nc => nc.notificationConnectorType != notificationConnectorType)) - return new ValueTuple(HttpStatusCode.NotFound, $"NotificationType {Enum.GetName(notificationConnectorType)} not configured."); - else - { - notificationConnectors.Remove(notificationConnectors.First(nc => nc.notificationConnectorType != notificationConnectorType)); - return new ValueTuple(HttpStatusCode.OK, null); - } - } -} \ No newline at end of file diff --git a/Tranga/Server/v2Settings.cs b/Tranga/Server/v2Settings.cs deleted file mode 100644 index 4e0212e..0000000 --- a/Tranga/Server/v2Settings.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.Net; -using System.Text.RegularExpressions; -using Tranga.MangaConnectors; - -namespace Tranga.Server; - -public partial class Server -{ - private ValueTuple GetV2Settings(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.AsJObject()); - } - - private ValueTuple GetV2SettingsUserAgent(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.userAgent); - } - - private ValueTuple PostV2SettingsUserAgent(GroupCollection groups, Dictionary requestParameters) - { - if (!requestParameters.TryGetValue("value", out string? userAgent)) - { - TrangaSettings.UpdateUserAgent(null); - return new ValueTuple(HttpStatusCode.Accepted, null); - } - else - { - TrangaSettings.UpdateUserAgent(userAgent); - return new ValueTuple(HttpStatusCode.OK, null); - } - } - - private ValueTuple GetV2SettingsRateLimitTypes(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, Enum.GetValues().ToDictionary(b =>(byte)b, b => Enum.GetName(b)) ); - } - - private ValueTuple GetV2SettingsRateLimit(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.requestLimits); - } - - private ValueTuple PostV2SettingsRateLimit(GroupCollection groups, Dictionary requestParameters) - { - foreach (KeyValuePair kv in requestParameters) - { - if(!Enum.TryParse(kv.Key, out RequestType requestType) || - !int.TryParse(kv.Value, out int requestsPerMinute)) - return new ValueTuple(HttpStatusCode.InternalServerError, null); - TrangaSettings.UpdateRateLimit(requestType, requestsPerMinute); - } - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.requestLimits); - } - - private ValueTuple GetV2SettingsRateLimitType(GroupCollection groups, Dictionary requestParameters) - { - if(groups.Count < 1 || - !Enum.TryParse(groups[1].Value, out RequestType requestType)) - return new ValueTuple(HttpStatusCode.NotFound, $"RequestType {groups[1].Value}"); - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.requestLimits[requestType]); - } - - private ValueTuple PostV2SettingsRateLimitType(GroupCollection groups, Dictionary requestParameters) - { - if(groups.Count < 1 || - !Enum.TryParse(groups[1].Value, out RequestType requestType)) - return new ValueTuple(HttpStatusCode.NotFound, $"RequestType {groups[1].Value}"); - if (!requestParameters.TryGetValue("value", out string? requestsPerMinuteStr) || - !int.TryParse(requestsPerMinuteStr, out int requestsPerMinute)) - return new ValueTuple(HttpStatusCode.InternalServerError, "Errors parsing requestsPerMinute"); - TrangaSettings.UpdateRateLimit(requestType, requestsPerMinute); - return new ValueTuple(HttpStatusCode.OK, null); - } - - private ValueTuple GetV2SettingsAprilFoolsMode(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.aprilFoolsMode); - } - - private ValueTuple PostV2SettingsAprilFoolsMode(GroupCollection groups, Dictionary requestParameters) - { - if (!requestParameters.TryGetValue("value", out string? trueFalseStr) || - !bool.TryParse(trueFalseStr, out bool trueFalse)) - return new ValueTuple(HttpStatusCode.InternalServerError, "Errors parsing 'value'"); - TrangaSettings.UpdateAprilFoolsMode(trueFalse); - return new ValueTuple(HttpStatusCode.OK, null); - } - - private ValueTuple GetV2SettingsCompressImages(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.compression); - } - - private ValueTuple PostV2SettingsCompressImages(GroupCollection groups, Dictionary requestParameters) - { - if (!requestParameters.TryGetValue("value", out string? valueStr) || - !int.TryParse(valueStr, out int value) - || value != int.Clamp(value, 1, 100)) - return new ValueTuple(HttpStatusCode.InternalServerError, "Errors parsing 'value'"); - TrangaSettings.UpdateCompressImages(value); - return new ValueTuple(HttpStatusCode.OK, null); - } - - private ValueTuple GetV2SettingsBwImages(GroupCollection groups, Dictionary requestParameters) - { - return new ValueTuple(HttpStatusCode.OK, TrangaSettings.bwImages); - } - - private ValueTuple PostV2SettingsBwImages(GroupCollection groups, Dictionary requestParameters) - { - if (!requestParameters.TryGetValue("value", out string? trueFalseStr) || - !bool.TryParse(trueFalseStr, out bool trueFalse)) - return new ValueTuple(HttpStatusCode.InternalServerError, "Errors parsing 'value'"); - TrangaSettings.UpdateBwImages(trueFalse); - return new ValueTuple(HttpStatusCode.OK, null); - } - - private ValueTuple PostV2SettingsDownloadLocation(GroupCollection groups, Dictionary requestParameters) - { - if (!requestParameters.TryGetValue("location", out string? folderPath)) - return new ValueTuple(HttpStatusCode.NotFound, "Missing Parameter 'location'"); - try - { - bool moveFiles = requestParameters.TryGetValue("moveFiles", out string? moveFilesStr) switch - { - false => true, - true => bool.Parse(moveFilesStr!) - }; - TrangaSettings.UpdateDownloadLocation(folderPath, moveFiles); - return new ValueTuple(HttpStatusCode.OK, null); - } - catch (FormatException) - { - return new ValueTuple(HttpStatusCode.InternalServerError, "Error Parsing Parameter 'moveFiles'"); - } - } -} \ No newline at end of file diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs deleted file mode 100644 index 2b6368b..0000000 --- a/Tranga/Tranga.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Logging; -using Tranga.Jobs; -using Tranga.MangaConnectors; - -namespace Tranga; - -public partial class Tranga : GlobalBase -{ - public bool keepRunning; - public JobBoss jobBoss; - private Server.Server _server; - - public Tranga(Logger? logger) : base(logger) - { - Log("\n\n _______ \n|_ _|.----..---.-..-----..-----..---.-.\n | | | _|| _ || || _ || _ |\n |___| |__| |___._||__|__||___ ||___._|\n |_____| \n\n"); - keepRunning = true; - foreach(DirectoryInfo dir in new DirectoryInfo(Path.GetTempPath()).GetDirectories("trangatemp"))//Cleanup old temp folders - dir.Delete(); - jobBoss = new(this, this._connectors); - StartJobBoss(); - this._server = new Server.Server(this); - string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"}; - SendNotifications("Tranga Started", emojis[Random.Shared.Next(0,emojis.Length-1)]); - Log(TrangaSettings.AsJObject().ToString()); - } - - public MangaConnector? GetConnector(string name) - { - foreach(MangaConnector mc in _connectors) - if (mc.name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) - return mc; - return null; - } - - public bool TryGetConnector(string name, out MangaConnector? connector) - { - connector = GetConnector(name); - return connector is not null; - } - - public List GetConnectors() - { - return _connectors.ToList(); - } - - public Manga? GetPublicationById(string internalId) => GetCachedManga(internalId); - - public bool TryGetPublicationById(string internalId, out Manga? manga) - { - manga = GetPublicationById(internalId); - return manga is not null; - } - - private void StartJobBoss() - { - Thread t = new (() => - { - while (keepRunning) - { - if(!TrangaSettings.aprilFoolsMode || !IsAprilFirst()) - jobBoss.CheckJobs(); - else - Log("April Fools Mode in Effect"); - Thread.Sleep(100); - } - }); - t.Start(); - } - - private bool IsAprilFirst() - { - //UTC 01 Apr +-12hrs - DateTime start = new DateTime(DateTime.Now.Year, 03, 31, 12, 0, 0, DateTimeKind.Utc); - DateTime end = new DateTime(DateTime.Now.Year, 04, 02, 12, 0, 0, DateTimeKind.Utc); - if (DateTime.UtcNow > start && DateTime.UtcNow < end) - return true; - return false; - } -} \ No newline at end of file diff --git a/Tranga/Tranga.csproj b/Tranga/Tranga.csproj deleted file mode 100644 index c5d37fd..0000000 --- a/Tranga/Tranga.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net8.0 - enable - enable - Exe - 12 - - - - - - - - - - - - - - - - - - .dockerignore - Dockerfile - - - - diff --git a/Tranga/TrangaArgs.cs b/Tranga/TrangaArgs.cs deleted file mode 100644 index f643d4a..0000000 --- a/Tranga/TrangaArgs.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Logging; -using GlaxArguments; - -namespace Tranga; - -public partial class Tranga : GlobalBase -{ - - public static void Main(string[] args) - { - Argument downloadLocation = new (new[] { "-d", "--downloadLocation" }, 1, "Directory to which downloaded Manga are saved"); - Argument workingDirectory = new (new[] { "-w", "--workingDirectory" }, 1, "Directory in which application-data is saved"); - Argument consoleLogger = new (new []{"-c", "--consoleLogger"}, 0, "Enables the consoleLogger"); - Argument fileLogger = new (new []{"-f", "--fileLogger"}, 0, "Enables the fileLogger"); - Argument fPath = new (new []{"-l", "--fPath"}, 1, "Log Folder Path"); - - Argument[] arguments = new[] - { - downloadLocation, - workingDirectory, - consoleLogger, - fileLogger, - fPath - }; - ArgumentFetcher fetcher = new (arguments); - Dictionary fetched = fetcher.Fetch(args); - - string? directoryPath = fetched.TryGetValue(fPath, out string[]? path) ? path[0] : null; - if (directoryPath is not null && !Directory.Exists(directoryPath)) - Directory.CreateDirectory(directoryPath); - - List enabledLoggers = new(); - if(fetched.ContainsKey(consoleLogger)) - enabledLoggers.Add(Logger.LoggerType.ConsoleLogger); - if (fetched.ContainsKey(fileLogger)) - enabledLoggers.Add(Logger.LoggerType.FileLogger); - Logger logger = new(enabledLoggers.ToArray(), Console.Out, Console.OutputEncoding, directoryPath); - - bool dlp = fetched.TryGetValue(downloadLocation, out string[]? downloadLocationPath); - bool wdp = fetched.TryGetValue(workingDirectory, out string[]? workingDirectoryPath); - - if (wdp) - TrangaSettings.LoadFromWorkingDirectory(workingDirectoryPath![0]); - else - TrangaSettings.CreateOrUpdate(); - if(dlp) - TrangaSettings.CreateOrUpdate(downloadDirectory: downloadLocationPath![0]); - - Tranga _ = new (logger); - } -} \ No newline at end of file diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs deleted file mode 100644 index dfa1590..0000000 --- a/Tranga/TrangaSettings.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System.Runtime.InteropServices; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Tranga.LibraryConnectors; -using Tranga.MangaConnectors; -using Tranga.NotificationConnectors; -using static System.IO.UnixFileMode; - -namespace Tranga; - -public static class TrangaSettings -{ - [JsonIgnore] internal static readonly string DefaultUserAgent = $"Tranga ({Enum.GetName(Environment.OSVersion.Platform)}; {(Environment.Is64BitOperatingSystem ? "x64" : "")}) / 1.0"; - public static string downloadLocation { get; private set; } = (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(Directory.GetCurrentDirectory(), "Downloads")); - public static string workingDirectory { get; private set; } = Path.Join(RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/usr/share" : Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "tranga-api"); - public static int apiPortNumber { get; private set; } = 6531; - public static string userAgent { get; private set; } = DefaultUserAgent; - public static bool bufferLibraryUpdates { get; private set; } = false; - public static bool bufferNotifications { get; private set; } = false; - public static int compression{ get; private set; } = 40; - public static bool bwImages { get; private set; } = false; - [JsonIgnore] public static string settingsFilePath => Path.Join(workingDirectory, "settings.json"); - [JsonIgnore] public static string libraryConnectorsFilePath => Path.Join(workingDirectory, "libraryConnectors.json"); - [JsonIgnore] public static string notificationConnectorsFilePath => Path.Join(workingDirectory, "notificationConnectors.json"); - [JsonIgnore] public static string jobsFolderPath => Path.Join(workingDirectory, "jobs"); - [JsonIgnore] public static string coverImageCache => Path.Join(workingDirectory, "imageCache"); - [JsonIgnore] public static string mangaCacheFolderPath => Path.Join(workingDirectory, "mangaCache"); - public static ushort? version { get; } = 2; - public static bool aprilFoolsMode { get; private set; } = true; - [JsonIgnore]internal static readonly Dictionary DefaultRequestLimits = new () - { - {RequestType.MangaInfo, 250}, - {RequestType.MangaDexFeed, 250}, - {RequestType.MangaDexImage, 40}, - {RequestType.MangaImage, 60}, - {RequestType.MangaCover, 250}, - {RequestType.Default, 60} - }; - - public static Dictionary requestLimits { get; set; } = DefaultRequestLimits; - - public static void LoadFromWorkingDirectory(string directory) - { - TrangaSettings.workingDirectory = directory; - if(File.Exists(settingsFilePath)) - Deserialize(File.ReadAllText(settingsFilePath)); - else return; - - Directory.CreateDirectory(downloadLocation); - Directory.CreateDirectory(workingDirectory); - ExportSettings(); - } - - public static void CreateOrUpdate(string? downloadDirectory = null, string? pWorkingDirectory = null, - int? pApiPortNumber = null, string? pUserAgent = null, bool? pAprilFoolsMode = null, - bool? pBufferLibraryUpdates = null, bool? pBufferNotifications = null, int? pCompression = null, bool? pbwImages = null) - { - if(pWorkingDirectory is null && File.Exists(settingsFilePath)) - LoadFromWorkingDirectory(workingDirectory); - downloadLocation = downloadDirectory ?? downloadLocation; - workingDirectory = pWorkingDirectory ?? workingDirectory; - apiPortNumber = pApiPortNumber ?? apiPortNumber; - userAgent = pUserAgent ?? userAgent; - aprilFoolsMode = pAprilFoolsMode ?? aprilFoolsMode; - bufferLibraryUpdates = pBufferLibraryUpdates ?? bufferLibraryUpdates; - bufferNotifications = pBufferNotifications ?? bufferNotifications; - compression = pCompression ?? compression; - bwImages = pbwImages ?? bwImages; - Directory.CreateDirectory(downloadLocation); - Directory.CreateDirectory(workingDirectory); - ExportSettings(); - } - - public static HashSet LoadLibraryConnectors(GlobalBase clone) - { - if (!File.Exists(libraryConnectorsFilePath)) - return new HashSet(); - return JsonConvert.DeserializeObject>(File.ReadAllText(libraryConnectorsFilePath), - new JsonSerializerSettings() - { - Converters = - { - new LibraryManagerJsonConverter(clone) - } - })!; - } - - public static HashSet LoadNotificationConnectors(GlobalBase clone) - { - if (!File.Exists(notificationConnectorsFilePath)) - return new HashSet(); - return JsonConvert.DeserializeObject>(File.ReadAllText(notificationConnectorsFilePath), - new JsonSerializerSettings() - { - Converters = - { - new NotificationManagerJsonConverter(clone) - } - })!; - } - - public static void UpdateAprilFoolsMode(bool enabled) - { - aprilFoolsMode = enabled; - ExportSettings(); - } - - public static void UpdateCompressImages(int value) - { - compression = int.Clamp(value, 1, 100); - ExportSettings(); - } - - public static void UpdateBwImages(bool enabled) - { - bwImages = enabled; - ExportSettings(); - } - - public static void UpdateDownloadLocation(string newPath, bool moveFiles = true) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - Directory.CreateDirectory(newPath, GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite); - else - Directory.CreateDirectory(newPath); - - if (moveFiles) - MoveContentsOfDirectoryTo(TrangaSettings.downloadLocation, newPath); - - TrangaSettings.downloadLocation = newPath; - ExportSettings(); - } - - public static void UpdateWorkingDirectory(string newPath) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - Directory.CreateDirectory(newPath, GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite); - else - Directory.CreateDirectory(newPath); - - MoveContentsOfDirectoryTo(TrangaSettings.workingDirectory, newPath); - - TrangaSettings.workingDirectory = newPath; - ExportSettings(); - } - - private static void MoveContentsOfDirectoryTo(string oldDir, string newDir) - { - string[] directoryPaths = Directory.GetDirectories(oldDir); - string[] filePaths = Directory.GetFiles(oldDir); - foreach (string file in filePaths) - { - string newPath = Path.Join(newDir, Path.GetFileName(file)); - File.Move(file, newPath, true); - } - foreach(string directory in directoryPaths) - { - string? dirName = Path.GetDirectoryName(directory); - if(dirName is null) - continue; - string newPath = Path.Join(newDir, dirName); - if(Directory.Exists(newPath)) - MoveContentsOfDirectoryTo(directory, newPath); - else - Directory.Move(directory, newPath); - } - } - - public static void UpdateUserAgent(string? customUserAgent) - { - userAgent = customUserAgent ?? DefaultUserAgent; - ExportSettings(); - } - - public static void UpdateRateLimit(RequestType requestType, int newLimit) - { - requestLimits[requestType] = newLimit; - ExportSettings(); - } - - public static void ResetRateLimits() - { - requestLimits = DefaultRequestLimits; - ExportSettings(); - } - - public static void ExportSettings() - { - if (File.Exists(settingsFilePath)) - { - while(GlobalBase.IsFileInUse(settingsFilePath, null)) - Thread.Sleep(100); - } - else - Directory.CreateDirectory(new FileInfo(settingsFilePath).DirectoryName!); - File.WriteAllText(settingsFilePath, Serialize()); - } - - public static JObject AsJObject() - { - JObject jobj = new JObject(); - jobj.Add("downloadLocation", JToken.FromObject(downloadLocation)); - jobj.Add("workingDirectory", JToken.FromObject(workingDirectory)); - jobj.Add("apiPortNumber", JToken.FromObject(apiPortNumber)); - jobj.Add("userAgent", JToken.FromObject(userAgent)); - jobj.Add("aprilFoolsMode", JToken.FromObject(aprilFoolsMode)); - jobj.Add("version", JToken.FromObject(version)); - jobj.Add("requestLimits", JToken.FromObject(requestLimits)); - jobj.Add("bufferLibraryUpdates", JToken.FromObject(bufferLibraryUpdates)); - jobj.Add("bufferNotifications", JToken.FromObject(bufferNotifications)); - jobj.Add("compression", JToken.FromObject(compression)); - jobj.Add("bwImages", JToken.FromObject(bwImages)); - return jobj; - } - - public static string Serialize() => AsJObject().ToString(); - - public static void Deserialize(string serialized) - { - JObject jobj = JObject.Parse(serialized); - if (jobj.TryGetValue("downloadLocation", out JToken? dl)) - downloadLocation = dl.Value()!; - if (jobj.TryGetValue("workingDirectory", out JToken? wd)) - workingDirectory = wd.Value()!; - if (jobj.TryGetValue("apiPortNumber", out JToken? apn)) - apiPortNumber = apn.Value(); - if (jobj.TryGetValue("userAgent", out JToken? ua)) - userAgent = ua.Value()!; - if (jobj.TryGetValue("aprilFoolsMode", out JToken? afm)) - aprilFoolsMode = afm.Value()!; - if (jobj.TryGetValue("requestLimits", out JToken? rl)) - requestLimits = rl.ToObject>()!; - if (jobj.TryGetValue("bufferLibraryUpdates", out JToken? blu)) - bufferLibraryUpdates = blu.Value()!; - if (jobj.TryGetValue("bufferNotifications", out JToken? bn)) - bufferNotifications = bn.Value()!; - if (jobj.TryGetValue("compression", out JToken? ci)) - compression = ci.Value()!; - if (jobj.TryGetValue("bwImages", out JToken? bwi)) - bwImages = bwi.Value()!; - } -} \ No newline at end of file