diff --git a/.github/workflows/docker-image-cuttingedge.yml b/.github/workflows/docker-image-cuttingedge.yml index b36acd2..42c6f76 100644 --- a/.github/workflows/docker-image-cuttingedge.yml +++ b/.github/workflows/docker-image-cuttingedge.yml @@ -42,17 +42,4 @@ jobs: pull: true push: true tags: | - glax/tranga-api:cuttingedge - - # https://github.com/docker/build-push-action#multi-platform-image - - name: Build and push Website - uses: docker/build-push-action@v4.1.1 - with: - context: ./Website - file: ./Website/Dockerfile - #platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6 - platforms: linux/amd64 - pull: true - push: true - tags: | - glax/tranga-website:cuttingedge + glax/tranga-api:cuttingedge \ No newline at end of file diff --git a/.github/workflows/docker-image-master.yml b/.github/workflows/docker-image-master.yml index 995c8a2..710587b 100644 --- a/.github/workflows/docker-image-master.yml +++ b/.github/workflows/docker-image-master.yml @@ -44,17 +44,4 @@ jobs: pull: true push: true tags: | - glax/tranga-api:latest - - # https://github.com/docker/build-push-action#multi-platform-image - - name: Build and push Website - uses: docker/build-push-action@v4.1.1 - with: - context: ./Website - file: ./Website/Dockerfile - #platforms: linux/amd64,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6 - platforms: linux/amd64 - pull: true - push: true - tags: | - glax/tranga-website:latest + glax/tranga-api:latest \ No newline at end of file diff --git a/Tranga/API/RequestHandler.cs b/Tranga/API/RequestHandler.cs deleted file mode 100644 index 62f7f5c..0000000 --- a/Tranga/API/RequestHandler.cs +++ /dev/null @@ -1,365 +0,0 @@ -using System.Globalization; -using System.Net; -using System.Text.RegularExpressions; -using Tranga; -using Tranga.Connectors; -using Tranga.TrangaTasks; - -namespace Tranga.API; - -public class RequestHandler -{ - private TaskManager _taskManager; - private Server _parent; - - private List> _validRequestPaths = new() - { - new(HttpMethod.Get, "/", Array.Empty()), - new(HttpMethod.Get, "/Connectors", Array.Empty()), - new(HttpMethod.Get, "/Publications/Known", new[] { "internalId?" }), - new(HttpMethod.Get, "/Publications/FromConnector", new[] { "connectorName", "title" }), - new(HttpMethod.Get, "/Publications/Chapters", - new[] { "connectorName", "internalId", "onlyNew?", "onlyExisting?", "language?" }), - new(HttpMethod.Get, "/Tasks/Types", Array.Empty()), - new(HttpMethod.Post, "/Tasks/CreateMonitorTask", - new[] { "connectorName", "internalId", "reoccurrenceTime", "language?", "ignoreChaptersBelow?" }), - new(HttpMethod.Post, "/Tasks/CreateDownloadChaptersTask", - new[] { "connectorName", "internalId", "chapters", "language?" }), - new(HttpMethod.Get, "/Tasks", new[] { "taskType", "connectorName?", "publicationId?" }), - new(HttpMethod.Delete, "/Tasks", new[] { "taskType", "connectorName?", "searchString?" }), - new(HttpMethod.Get, "/Tasks/Progress", - new[] { "taskType", "connectorName", "publicationId", "chapterSortNumber?" }), - new(HttpMethod.Post, "/Tasks/Start", new[] { "taskType", "connectorName?", "internalId?" }), - new(HttpMethod.Get, "/Tasks/RunningTasks", Array.Empty()), - new(HttpMethod.Get, "/Queue/List", Array.Empty()), - new(HttpMethod.Post, "/Queue/Enqueue", new[] { "taskType", "connectorName?", "publicationId?" }), - new(HttpMethod.Delete, "/Queue/Dequeue", new[] { "taskType", "connectorName?", "publicationId?" }), - new(HttpMethod.Get, "/Settings", Array.Empty()), - new(HttpMethod.Post, "/Settings/Update", new[] - { - "downloadLocation?", "komgaUrl?", "komgaAuth?", "kavitaUrl?", "kavitaUsername?", - "kavitaPassword?", "gotifyUrl?", "gotifyAppToken?", "lunaseaWebhook?" - }) - }; - - public RequestHandler(TaskManager taskManager, Server parent) - { - this._taskManager = taskManager; - this._parent = parent; - } - - internal void HandleRequest(HttpListenerRequest request, HttpListenerResponse response) - { - string requestPath = request.Url!.LocalPath; - if (requestPath.Contains("favicon")) - { - _parent.SendResponse(HttpStatusCode.NoContent, response); - return; - } - if (!this._validRequestPaths.Any(path => path.Item1.Method == request.HttpMethod && path.Item2 == requestPath)) - { - _parent.SendResponse(HttpStatusCode.BadRequest, response); - return; - } - Dictionary variables = GetRequestVariables(request.Url!.Query); - object? responseObject = null; - switch (request.HttpMethod) - { - case "GET": - responseObject = this.HandleGet(requestPath, variables); - break; - case "POST": - this.HandlePost(requestPath, variables); - break; - case "DELETE": - this.HandleDelete(requestPath, variables); - break; - } - _parent.SendResponse(HttpStatusCode.OK, response, responseObject); - } - - 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 kvpair in query.Split('&').Where(str => str.Length >= 3)) - { - string var = kvpair.Split('=')[0]; - string val = Regex.Replace(kvpair.Substring(var.Length + 1), "%20", " "); - val = Regex.Replace(val, "%[0-9]{2}", ""); - ret.Add(var, val); - } - return ret; - } - - private void HandleDelete(string requestPath, Dictionary variables) - { - switch (requestPath) - { - case "/Tasks": - variables.TryGetValue("taskType", out string? taskType1); - variables.TryGetValue("connectorName", out string? connectorName1); - variables.TryGetValue("publicationId", out string? publicationId1); - if(taskType1 is null) - return; - - try - { - TrangaTask.Task task = Enum.Parse(taskType1); - foreach(TrangaTask tTask in _taskManager.GetTasksMatching(task, connectorName1, internalId: publicationId1)) - _taskManager.DeleteTask(tTask); - } - catch (ArgumentException) - { - return; - } - break; - case "/Queue/Dequeue": - variables.TryGetValue("taskType", out string? taskType2); - variables.TryGetValue("connectorName", out string? connectorName2); - variables.TryGetValue("publicationId", out string? publicationId2); - if(taskType2 is null) - return; - - try - { - TrangaTask.Task pTask = Enum.Parse(taskType2); - TrangaTask? task = _taskManager - .GetTasksMatching(pTask, connectorName: connectorName2, internalId: publicationId2).FirstOrDefault(); - - if (task is null) - return; - _taskManager.RemoveTaskFromQueue(task); - } - catch (ArgumentException) - { - return; - } - break; - } - } - - private void HandlePost(string requestPath, Dictionary variables) - { - switch (requestPath) - { - - case "/Tasks/CreateMonitorTask": - variables.TryGetValue("connectorName", out string? connectorName1); - variables.TryGetValue("internalId", out string? internalId1); - variables.TryGetValue("reoccurrenceTime", out string? reoccurrenceTime1); - variables.TryGetValue("language", out string? language1); - variables.TryGetValue("ignoreChaptersBelow", out string? minChapter); - if (connectorName1 is null || internalId1 is null || reoccurrenceTime1 is null) - return; - Connector? connector1 = - _taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName1).Value; - if (connector1 is null) - return; - Publication? publication1 = _taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId1); - if (!publication1.HasValue) - return; - Publication pPublication1 = (Publication)publication1; - if (minChapter is not null) - pPublication1.ignoreChaptersBelow = float.Parse(minChapter,new NumberFormatInfo() { NumberDecimalSeparator = "." }); - _taskManager.AddTask(new MonitorPublicationTask(connectorName1, pPublication1, TimeSpan.Parse(reoccurrenceTime1), language1 ?? "en")); - break; - case "/Tasks/CreateDownloadChaptersTask": - variables.TryGetValue("connectorName", out string? connectorName2); - variables.TryGetValue("internalId", out string? internalId2); - variables.TryGetValue("chapters", out string? chapters); - variables.TryGetValue("language", out string? language2); - if (connectorName2 is null || internalId2 is null || chapters is null) - return; - Connector? connector2 = - _taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName2).Value; - if (connector2 is null) - return; - Publication? publication2 = _taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId2); - if (publication2 is null) - return; - - IEnumerable toDownload = connector2.SelectChapters((Publication)publication2, chapters, language2 ?? "en"); - foreach(Chapter chapter in toDownload) - _taskManager.AddTask(new DownloadChapterTask(connectorName2, (Publication)publication2, chapter, "en")); - break; - case "/Tasks/Start": - variables.TryGetValue("taskType", out string? taskType1); - variables.TryGetValue("connectorName", out string? connectorName3); - variables.TryGetValue("internalId", out string? internalId3); - if (taskType1 is null) - return; - try - { - TrangaTask.Task pTask = Enum.Parse(taskType1); - TrangaTask? task = _taskManager - .GetTasksMatching(pTask, connectorName: connectorName3, internalId: internalId3).FirstOrDefault(); - - if (task is null) - return; - _taskManager.ExecuteTaskNow(task); - } - catch (ArgumentException) - { - return; - } - break; - case "/Queue/Enqueue": - variables.TryGetValue("taskType", out string? taskType2); - variables.TryGetValue("connectorName", out string? connectorName4); - variables.TryGetValue("publicationId", out string? publicationId); - if (taskType2 is null) - return; - try - { - TrangaTask.Task pTask = Enum.Parse(taskType2); - TrangaTask? task = _taskManager - .GetTasksMatching(pTask, connectorName: connectorName4, internalId: publicationId).FirstOrDefault(); - - if (task is null) - return; - _taskManager.AddTaskToQueue(task); - } - catch (ArgumentException) - { - return; - } - break; - case "/Settings/Update": - variables.TryGetValue("downloadLocation", out string? downloadLocation); - variables.TryGetValue("komgaUrl", out string? komgaUrl); - variables.TryGetValue("komgaAuth", out string? komgaAuth); - variables.TryGetValue("kavitaUrl", out string? kavitaUrl); - variables.TryGetValue("kavitaUsername", out string? kavitaUsername); - variables.TryGetValue("kavitaPassword", out string? kavitaPassword); - variables.TryGetValue("gotifyUrl", out string? gotifyUrl); - variables.TryGetValue("gotifyAppToken", out string? gotifyAppToken); - variables.TryGetValue("lunaseaWebhook", out string? lunaseaWebhook); - - if (downloadLocation is not null && downloadLocation.Length > 0) - _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.DownloadLocation, downloadLocation); - if (komgaUrl is not null && komgaAuth is not null && komgaUrl.Length > 5 && komgaAuth.Length > 0) - _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Komga, komgaUrl, komgaAuth); - if (kavitaUrl is not null && kavitaPassword is not null && kavitaUsername is not null && kavitaUrl.Length > 5 && - kavitaUsername.Length > 0 && kavitaPassword.Length > 0) - _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Kavita, kavitaUrl, kavitaUsername, - kavitaPassword); - if (gotifyUrl is not null && gotifyAppToken is not null && gotifyUrl.Length > 5 && gotifyAppToken.Length > 0) - _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Gotify, gotifyUrl, gotifyAppToken); - if(lunaseaWebhook is not null && lunaseaWebhook.Length > 5) - _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.LunaSea, lunaseaWebhook); - break; - } - } - - private object? HandleGet(string requestPath, Dictionary variables) - { - switch (requestPath) - { - case "/Connectors": - return this._taskManager.GetAvailableConnectors().Keys.ToArray(); - case "/Publications/Known": - variables.TryGetValue("internalId", out string? internalId1); - if(internalId1 is null) - return _taskManager.GetAllPublications(); - return new [] { _taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId1) }; - case "/Publications/FromConnector": - variables.TryGetValue("connectorName", out string? connectorName1); - variables.TryGetValue("title", out string? title); - if (connectorName1 is null || title is null) - return null; - Connector? connector1 = _taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName1).Value; - if (connector1 is null) - return null; - if(title.Length < 4) - return null; - return connector1.GetPublications(ref _taskManager.collection, title); - case "/Publications/Chapters": - string[] yes = { "true", "yes", "1", "y" }; - variables.TryGetValue("connectorName", out string? connectorName2); - variables.TryGetValue("internalId", out string? internalId2); - variables.TryGetValue("onlyNew", out string? onlyNew); - variables.TryGetValue("onlyExisting", out string? onlyExisting); - variables.TryGetValue("language", out string? language); - if (connectorName2 is null || internalId2 is null) - return null; - bool newOnly = onlyNew is not null && yes.Contains(onlyNew); - bool existingOnly = onlyExisting is not null && yes.Contains(onlyExisting); - - Connector? connector2 = _taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName2).Value; - if (connector2 is null) - return null; - Publication? publication = _taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId2); - if (publication is null) - return null; - - if(newOnly) - return connector2.GetNewChaptersList((Publication)publication, language??"en", ref _taskManager.collection).ToArray(); - else if (existingOnly) - return _taskManager.GetExistingChaptersList(connector2, (Publication)publication, language ?? "en").ToArray(); - else - return connector2.GetChapters((Publication)publication, language??"en"); - case "/Tasks/Types": - return Enum.GetNames(typeof(TrangaTask.Task)); - case "/Tasks": - variables.TryGetValue("taskType", out string? taskType1); - variables.TryGetValue("connectorName", out string? connectorName3); - variables.TryGetValue("searchString", out string? searchString); - if (taskType1 is null) - return null; - try - { - TrangaTask.Task task = Enum.Parse(taskType1); - return _taskManager.GetTasksMatching(task, connectorName:connectorName3, searchString:searchString); - } - catch (ArgumentException) - { - return null; - } - case "/Tasks/Progress": - variables.TryGetValue("taskType", out string? taskType2); - variables.TryGetValue("connectorName", out string? connectorName4); - variables.TryGetValue("publicationId", out string? publicationId); - variables.TryGetValue("chapterNumber", out string? chapterNumber); - if (taskType2 is null || connectorName4 is null || publicationId is null) - return null; - Connector? connector = - _taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName4).Value; - if (connector is null) - return null; - try - { - TrangaTask? task = null; - TrangaTask.Task pTask = Enum.Parse(taskType2); - if (pTask is TrangaTask.Task.MonitorPublication) - { - task = _taskManager.GetTasksMatching(pTask, connectorName: connectorName4, internalId: publicationId).FirstOrDefault(); - }else if (pTask is TrangaTask.Task.DownloadChapter && chapterNumber is not null) - { - task = _taskManager.GetTasksMatching(pTask, connectorName: connectorName4, internalId: publicationId, - chapterNumber: chapterNumber).FirstOrDefault(); - } - if (task is null) - return null; - - return task.progress; - } - catch (ArgumentException) - { - return null; - } - case "/Tasks/RunningTasks": - return _taskManager.GetAllTasks().Where(task => task.state is TrangaTask.ExecutionState.Running); - case "/Queue/List": - return _taskManager.GetAllTasks().Where(task => task.state is TrangaTask.ExecutionState.Enqueued).OrderBy(task => task.nextExecution); - case "/Settings": - return _taskManager.settings; - case "/": - default: - return this._validRequestPaths; - } - } -} \ No newline at end of file diff --git a/Tranga/API/Server.cs b/Tranga/API/Server.cs deleted file mode 100644 index 8d19bc4..0000000 --- a/Tranga/API/Server.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Net; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using Logging; -using Newtonsoft.Json; -using Tranga; - -namespace Tranga.API; - -public class Server -{ - private readonly HttpListener _listener = new (); - private readonly RequestHandler _requestHandler; - private readonly TaskManager _taskManager; - internal readonly Logger? logger; - - private readonly Regex _validUrl = - new (@"https?:\/\/(www\.)?[-A-z0-9]{1,256}(\.[-a-zA-Z0-9]{1,6})?(:[0-9]{1,5})?(\/{1}[A-z0-9()@:%_\+.~#?&=]+)*\/?"); - public Server(int port, TaskManager taskManager, Logger? logger = null) - { - this.logger = logger; - this._taskManager = taskManager; - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - this._listener.Prefixes.Add($"http://*:{port}/"); - else - this._listener.Prefixes.Add($"http://localhost:{port}/"); - this._requestHandler = new RequestHandler(taskManager, this); - Thread listenThread = new Thread(Listen); - listenThread.Start(); - } - - private void Listen() - { - this._listener.Start(); - foreach (string prefix in this._listener.Prefixes) - this.logger?.WriteLine(this.GetType().ToString(), $"Listening on {prefix}"); - while (this._listener.IsListening && _taskManager._continueRunning) - { - HttpListenerContext context = this._listener.GetContextAsync().Result; - Task t = new (() => - { - HandleContext(context); - }); - t.Start(); - } - } - - private void HandleContext(HttpListenerContext context) - { - HttpListenerRequest request = context.Request; - HttpListenerResponse response = context.Response; - //logger?.WriteLine(this.GetType().ToString(), $"New request: {request.HttpMethod} {request.Url}"); - - if (!_validUrl.IsMatch(request.Url!.ToString())) - { - SendResponse(HttpStatusCode.BadRequest, response); - return; - } - - if (request.HttpMethod == "OPTIONS") - { - SendResponse(HttpStatusCode.OK, response); - } - else - { - _requestHandler.HandleRequest(request, response); - } - } - - internal void SendResponse(HttpStatusCode statusCode, HttpListenerResponse response, object? content = null) - { - //logger?.WriteLine(this.GetType().ToString(), $"Sending response: {statusCode}"); - 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.AppendHeader("Access-Control-Allow-Origin", "*"); - response.ContentType = "application/json"; - try - { - response.OutputStream.Write(content is not null - ? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content)) - : Array.Empty()); - response.OutputStream.Close(); - } - catch (HttpListenerException) - { - - } - } -} \ No newline at end of file diff --git a/Tranga/CommonObjects.cs b/Tranga/CommonObjects.cs deleted file mode 100644 index 0d9c5f4..0000000 --- a/Tranga/CommonObjects.cs +++ /dev/null @@ -1,125 +0,0 @@ -using Logging; -using Newtonsoft.Json; -using Tranga.LibraryManagers; -using Tranga.NotificationManagers; - -namespace Tranga; - -public class CommonObjects -{ - public HashSet libraryManagers { get; init; } - public HashSet notificationManagers { get; init; } - [JsonIgnore]public Logger? logger { get; set; } - [JsonIgnore]private string settingsFilePath { get; init; } - - public CommonObjects(HashSet? libraryManagers, HashSet? notificationManagers, Logger? logger, string settingsFilePath) - { - this.libraryManagers = libraryManagers??new(); - this.notificationManagers = notificationManagers??new(); - this.logger = logger; - this.settingsFilePath = settingsFilePath; - } - - public static CommonObjects LoadSettings(string settingsFilePath, Logger? logger) - { - if (!File.Exists(settingsFilePath)) - return new CommonObjects(null, null, logger, settingsFilePath); - - string toRead = File.ReadAllText(settingsFilePath); - TrangaSettings.SettingsJsonObject settings = JsonConvert.DeserializeObject( - toRead, - new JsonSerializerSettings - { - Converters = - { - new NotificationManager.NotificationManagerJsonConverter(), - new LibraryManager.LibraryManagerJsonConverter() - } - })!; - - if(settings.co is null) - return new CommonObjects(null, null, logger, settingsFilePath); - - if (logger is not null) - { - settings.co.logger = logger; - foreach (LibraryManager lm in settings.co.libraryManagers) - lm.AddLogger(logger); - foreach(NotificationManager nm in settings.co.notificationManagers) - nm.AddLogger(logger); - } - - return settings.co; - } - - public void ExportSettings() - { - TrangaSettings.SettingsJsonObject? settings = null; - if (File.Exists(settingsFilePath)) - { - bool inUse = true; - while (inUse) - { - try - { - using FileStream stream = new (settingsFilePath, FileMode.Open, FileAccess.Read, FileShare.None); - stream.Close(); - inUse = false; - } - catch (IOException) - { - inUse = true; - Thread.Sleep(50); - } - } - string toRead = File.ReadAllText(settingsFilePath); - settings = JsonConvert.DeserializeObject(toRead, - new JsonSerializerSettings - { - Converters = - { - new NotificationManager.NotificationManagerJsonConverter(), - new LibraryManager.LibraryManagerJsonConverter() - } - }); - } - settings = new TrangaSettings.SettingsJsonObject(settings?.ts, this); - File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(settings)); - } - - public void UpdateSettings(TrangaSettings.UpdateField field, params string[] values) - { - switch (field) - { - case TrangaSettings.UpdateField.Komga: - if (values.Length != 2) - return; - libraryManagers.RemoveWhere(lm => lm.GetType() == typeof(Komga)); - libraryManagers.Add(new Komga(values[0], values[1], this.logger)); - break; - case TrangaSettings.UpdateField.Kavita: - if (values.Length != 3) - return; - libraryManagers.RemoveWhere(lm => lm.GetType() == typeof(Kavita)); - libraryManagers.Add(new Kavita(values[0], values[1], values[2], this.logger)); - break; - case TrangaSettings.UpdateField.Gotify: - if (values.Length != 2) - return; - notificationManagers.RemoveWhere(nm => nm.GetType() == typeof(Gotify)); - Gotify newGotify = new(values[0], values[1], this.logger); - notificationManagers.Add(newGotify); - newGotify.SendNotification("Success!", "Gotify was added to Tranga!"); - break; - case TrangaSettings.UpdateField.LunaSea: - if(values.Length != 1) - return; - notificationManagers.RemoveWhere(nm => nm.GetType() == typeof(LunaSea)); - LunaSea newLunaSea = new(values[0], this.logger); - notificationManagers.Add(newLunaSea); - newLunaSea.SendNotification("Success!", "LunaSea was added to Tranga!"); - break; - } - ExportSettings(); - } -} \ No newline at end of file diff --git a/Tranga/Connectors/Connector.cs b/Tranga/Connectors/Connector.cs index 997aeec..224a23e 100644 --- a/Tranga/Connectors/Connector.cs +++ b/Tranga/Connectors/Connector.cs @@ -3,7 +3,6 @@ using System.IO.Compression; using System.Net; using System.Runtime.InteropServices; using System.Text.RegularExpressions; -using Tranga.TrangaTasks; using static System.IO.UnixFileMode; namespace Tranga.Connectors; @@ -12,37 +11,25 @@ namespace Tranga.Connectors; /// Base-Class for all Connectors /// Provides some methods to be used by all Connectors, as well as a DownloadClient /// -public abstract class Connector +public abstract class Connector : TBaseObject { - protected CommonObjects commonObjects; - protected TrangaSettings settings { get; } internal DownloadClient downloadClient { get; init; } = null!; - - protected Connector(TrangaSettings settings, CommonObjects commonObjects) + + protected Connector(TBaseObject clone) : base(clone) { - this.settings = settings; - this.commonObjects = commonObjects; if (!Directory.Exists(settings.coverImageCache)) Directory.CreateDirectory(settings.coverImageCache); } public abstract string name { get; } //Name of the Connector (e.g. Website) - public Publication[] GetPublications(ref HashSet publicationCollection, string publicationTitle = "") - { - Publication[] ret = GetPublicationsInternal(publicationTitle); - foreach (Publication p in ret) - publicationCollection.Add(p); - return ret; - } - /// /// Returns all Publications with the given string. /// If the string is empty or null, returns all Publication of the Connector /// /// Search-Query /// Publications matching the query - protected abstract Publication[] GetPublicationsInternal(string publicationTitle = ""); + protected abstract Publication[] GetPublications(string publicationTitle = ""); /// /// Returns all Chapters of the publication in the provided language. @@ -62,14 +49,15 @@ public abstract class Connector /// List of Chapters that were previously not in collection public List GetNewChaptersList(Publication publication, string language, ref HashSet collection) { + Log($"Getting new Chapters for {publication}"); Chapter[] newChapters = this.GetChapters(publication, language); collection.Add(publication); NumberFormatInfo decimalPoint = new (){ NumberDecimalSeparator = "." }; - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Checking for duplicates"); + Log($"Checking for duplicates {publication}"); List newChaptersList = newChapters.Where(nChapter => float.Parse(nChapter.chapterNumber, decimalPoint) > publication.ignoreChaptersBelow && !nChapter.CheckChapterIsDownloaded(settings.downloadLocation)).ToList(); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"{newChaptersList.Count} new chapters."); + Log($"{newChaptersList.Count} new chapters. {publication}"); return newChaptersList; } @@ -153,9 +141,8 @@ public abstract class Connector /// /// Publication that contains Chapter /// Chapter with Images to retrieve - /// Will be used for progress-tracking /// - public abstract HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, DownloadChapterTask parentTask, CancellationToken? cancellationToken = null); + public abstract HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, CancellationToken? cancellationToken = null); /// /// Copies the already downloaded cover from cache to downloadLocation @@ -163,19 +150,19 @@ public abstract class Connector /// Publication to retrieve Cover for public void CopyCoverFromCacheToDownloadLocation(Publication publication) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Cloning cover {publication.sortName} -> {publication.internalId}"); + Log($"Copy cover {publication}"); //Check if Publication already has a Folder and cover string publicationFolder = publication.CreatePublicationFolder(settings.downloadLocation); DirectoryInfo dirInfo = new (publicationFolder); if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover", StringComparison.InvariantCultureIgnoreCase))) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Cover exists {publication.sortName}"); + Log($"Cover exists {publication}"); return; } string fileInCache = Path.Join(settings.coverImageCache, publication.coverFileNameInCache); string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" ); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Cloning cover {fileInCache} -> {newFilePath}"); + Log($"Cloning cover {fileInCache} -> {newFilePath}"); File.Copy(fileInCache, newFilePath, true); if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | OtherRead | OtherWrite | UserRead | UserWrite); @@ -204,16 +191,15 @@ public abstract class Connector /// /// List of URLs to download Images from /// Full path to save archive to (without file ending .cbz) - /// Used for progress tracking /// Path of the generate Chapter ComicInfo.xml, if it was generated /// RequestType for RateLimits /// Used in http request header /// - protected HttpStatusCode DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, byte requestType, DownloadChapterTask parentTask, string? comicInfoPath = null, string? referrer = null, CancellationToken? cancellationToken = null) + protected HttpStatusCode DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, byte requestType, string? comicInfoPath = null, string? referrer = null, CancellationToken? cancellationToken = null) { if (cancellationToken?.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; - commonObjects.logger?.WriteLine("Connector", $"Downloading Images for {saveArchiveFilePath}"); + Log($"Downloading Images for {saveArchiveFilePath}"); //Check if Publication Directory already exists string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!; if (!Directory.Exists(directoryPath)) @@ -231,11 +217,10 @@ public abstract class Connector { string[] split = imageUrl.Split('.'); string extension = split[^1]; - commonObjects.logger?.WriteLine("Connector", $"Downloading Image {chapter + 1:000}/{imageUrls.Length:000} {parentTask.publication.sortName} {parentTask.publication.internalId} Vol.{parentTask.chapter.volumeNumber} Ch.{parentTask.chapter.chapterNumber} {parentTask.progress:P2}"); + Log($"Downloading image {chapter + 1:000}/{imageUrls.Length:000}"); //TODO HttpStatusCode status = DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), requestType, referrer); if ((int)status < 200 || (int)status >= 300) return status; - parentTask.IncrementProgress(1.0 / imageUrls.Length); if (cancellationToken?.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; } @@ -243,7 +228,7 @@ public abstract class Connector if(comicInfoPath is not null) File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml")); - commonObjects.logger?.WriteLine("Connector", $"Creating archive {saveArchiveFilePath}"); + Log($"Creating archive {saveArchiveFilePath}"); //ZIP-it and ship-it ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath); if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) @@ -265,7 +250,7 @@ public abstract class Connector using MemoryStream ms = new(); coverResult.result.CopyTo(ms); File.WriteAllBytes(saveImagePath, ms.ToArray()); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Saving image to {saveImagePath}"); + Log($"Saving cover to {saveImagePath}"); return filename; } } \ No newline at end of file diff --git a/Tranga/DownloadClient.cs b/Tranga/Connectors/DownloadClient.cs similarity index 79% rename from Tranga/DownloadClient.cs rename to Tranga/Connectors/DownloadClient.cs index 2951598..1012983 100644 --- a/Tranga/DownloadClient.cs +++ b/Tranga/Connectors/DownloadClient.cs @@ -1,9 +1,8 @@ using System.Net; -using Logging; -namespace Tranga; +namespace Tranga.Connectors; -internal class DownloadClient +internal class DownloadClient : TBaseObject { private static readonly HttpClient Client = new() { @@ -12,17 +11,9 @@ internal class DownloadClient private readonly Dictionary _lastExecutedRateLimit; private readonly Dictionary _rateLimit; - // ReSharper disable once InconsistentNaming - private readonly Logger? logger; - /// - /// Creates a httpClient - /// - /// Rate limits for requests. byte is RequestType, int maximum requests per minute for RequestType - /// - public DownloadClient(Dictionary rateLimitRequestsPerMinute, Logger? logger) + public DownloadClient(Dictionary rateLimitRequestsPerMinute, TBaseObject clone) : base(clone) { - this.logger = logger; _lastExecutedRateLimit = new(); _rateLimit = new(); foreach(KeyValuePair limit in rateLimitRequestsPerMinute) @@ -42,7 +33,7 @@ internal class DownloadClient _lastExecutedRateLimit.TryAdd(requestType, DateTime.Now.Subtract(value)); else { - logger?.WriteLine(this.GetType().ToString(), "RequestType not configured for rate-limit."); + Log("RequestType not configured for rate-limit."); return new RequestResult(HttpStatusCode.NotAcceptable, Stream.Null); } @@ -65,14 +56,13 @@ internal class DownloadClient } catch (HttpRequestException e) { - logger?.WriteLine(this.GetType().ToString(), e.Message); - logger?.WriteLine(this.GetType().ToString(), $"Waiting {_rateLimit[requestType] * 2}... Retrying."); + Log("Exception:\n\t{0}\n\tWaiting {1} before retrying.", e.Message, _rateLimit[requestType] * 2); Thread.Sleep(_rateLimit[requestType] * 2); } } if (!response.IsSuccessStatusCode) { - logger?.WriteLine(this.GetType().ToString(), $"Request-Error {response.StatusCode}: {response.ReasonPhrase}"); + Log($"Request-Error {response.StatusCode}: {response.ReasonPhrase}"); return new RequestResult(response.StatusCode, Stream.Null); } diff --git a/Tranga/Connectors/MangaDex.cs b/Tranga/Connectors/MangaDex.cs index b63f7a3..be953d5 100644 --- a/Tranga/Connectors/MangaDex.cs +++ b/Tranga/Connectors/MangaDex.cs @@ -2,7 +2,6 @@ using System.Net; using System.Text.Json; using System.Text.Json.Nodes; -using Tranga.TrangaTasks; namespace Tranga.Connectors; public class MangaDex : Connector @@ -18,7 +17,7 @@ public class MangaDex : Connector Author, } - public MangaDex(TrangaSettings settings, CommonObjects commonObjects) : base(settings, commonObjects) + public MangaDex(TBaseObject clone) : base(clone) { name = "MangaDex"; this.downloadClient = new DownloadClient(new Dictionary() @@ -28,12 +27,12 @@ public class MangaDex : Connector {(byte)RequestType.AtHomeServer, 40}, {(byte)RequestType.CoverUrl, 250}, {(byte)RequestType.Author, 250} - }, commonObjects.logger); + }, clone); } - protected override Publication[] GetPublicationsInternal(string publicationTitle = "") + protected override Publication[] GetPublications(string publicationTitle = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={publicationTitle})"); + Log($"Searching Publications. Term=\"{publicationTitle}\""); const int limit = 100; //How many values we want returned at once int offset = 0; //"Page" int total = int.MaxValue; //How many total results are there, is updated on first request @@ -59,7 +58,7 @@ public class MangaDex : Connector //Loop each Manga and extract information from JSON foreach (JsonNode? mangeNode in mangaInResult) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting publication data. {++loadedPublicationData}/{total}"); + Log($"Getting publication data. {++loadedPublicationData}/{total}"); JsonObject manga = (JsonObject)mangeNode!; JsonObject attributes = manga["attributes"]!.AsObject(); @@ -146,13 +145,13 @@ public class MangaDex : Connector } } - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Done getting publications (title={publicationTitle})"); + Log($"Retrieved {publications.Count} publications."); return publications.ToArray(); } public override Chapter[] GetChapters(Publication publication, string language = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Chapters for {publication.sortName} {publication.internalId} (language={language})"); + Log($"Getting chapters {publication}"); const int limit = 100; //How many values we want returned at once int offset = 0; //"Page" int total = int.MaxValue; //How many total results are there, is updated on first request @@ -199,19 +198,16 @@ public class MangaDex : Connector } //Return Chapters ordered by Chapter-Number - NumberFormatInfo chapterNumberFormatInfo = new() - { - NumberDecimalSeparator = "." - }; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Done getting {chapters.Count} Chapters for {publication.internalId}"); + NumberFormatInfo chapterNumberFormatInfo = new() { NumberDecimalSeparator = "." }; + Log($"Got {chapters.Count} chapters. {publication}"); return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray(); } - public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, DownloadChapterTask parentTask, CancellationToken? cancellationToken = null) + public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, CancellationToken? cancellationToken = null) { if (cancellationToken?.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Downloading Chapter-Info {publication.sortName} {publication.internalId} {chapter.volumeNumber}-{chapter.chapterNumber}"); + Log($"Retrieving chapter-info {chapter} {publication}"); //Request URLs for Chapter-Images DownloadClient.RequestResult requestResult = downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer); @@ -233,15 +229,15 @@ public class MangaDex : Connector File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); //Download Chapter-Images - return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), (byte)RequestType.AtHomeServer, parentTask, comicInfoPath, cancellationToken:cancellationToken); + return DownloadChapterImages(imageUrls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), (byte)RequestType.AtHomeServer, comicInfoPath, cancellationToken:cancellationToken); } private string? GetCoverUrl(string publicationId, string? posterId) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting CoverUrl for {publicationId}"); + Log($"Getting CoverUrl for Publication {publicationId}"); if (posterId is null) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"No posterId, aborting"); + Log("No cover."); return null; } @@ -257,12 +253,13 @@ public class MangaDex : Connector string fileName = result["data"]!["attributes"]!["fileName"]!.GetValue(); string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}"; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Got Cover-Url for {publicationId} -> {coverUrl}"); + Log($"Cover-Url {publicationId} -> {coverUrl}"); return coverUrl; } private List GetAuthors(IEnumerable authorIds) { + Log("Retrieving authors."); List ret = new(); foreach (string authorId in authorIds) { @@ -276,7 +273,7 @@ public class MangaDex : Connector string authorName = result["data"]!["attributes"]!["name"]!.GetValue(); ret.Add(authorName); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Got author {authorId} -> {authorName}"); + Log($"Got author {authorId} -> {authorName}"); } return ret; } diff --git a/Tranga/Connectors/MangaKatana.cs b/Tranga/Connectors/MangaKatana.cs index 6205d38..3fec584 100644 --- a/Tranga/Connectors/MangaKatana.cs +++ b/Tranga/Connectors/MangaKatana.cs @@ -2,7 +2,6 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; -using Tranga.TrangaTasks; namespace Tranga.Connectors; @@ -10,18 +9,18 @@ public class MangaKatana : Connector { public override string name { get; } - public MangaKatana(TrangaSettings settings, CommonObjects commonObjects) : base(settings, commonObjects) + public MangaKatana(TBaseObject clone) : base(clone) { this.name = "MangaKatana"; this.downloadClient = new DownloadClient(new Dictionary() { {1, 60} - }, commonObjects.logger); + }, clone); } - protected override Publication[] GetPublicationsInternal(string publicationTitle = "") + protected override Publication[] GetPublications(string publicationTitle = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={publicationTitle})"); + Log($"Searching Publications. Term=\"{publicationTitle}\""); string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*").Where(m => m.Value.Length > 0)).ToLower(); string requestUrl = $"https://mangakatana.com/?search={sanitizedTitle}&search_by=book_name"; DownloadClient.RequestResult requestResult = @@ -38,7 +37,9 @@ public class MangaKatana : Connector return new [] { ParseSinglePublicationFromHtml(requestResult.result, requestResult.redirectedToUrl.Split('/')[^1]) }; } - return ParsePublicationsFromHtml(requestResult.result); + Publication[] publications = ParsePublicationsFromHtml(requestResult.result); + Log($"Retrieved {publications.Length} publications."); + return publications; } private Publication[] ParsePublicationsFromHtml(Stream html) @@ -137,7 +138,7 @@ public class MangaKatana : Connector public override Chapter[] GetChapters(Publication publication, string language = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Chapters for {publication.sortName} {publication.internalId} (language={language})"); + Log($"Getting chapters {publication}"); string requestUrl = $"https://mangakatana.com/manga/{publication.publicationId}"; // Leaving this in for verification if the page exists DownloadClient.RequestResult requestResult = @@ -151,7 +152,7 @@ public class MangaKatana : Connector NumberDecimalSeparator = "." }; List chapters = ParseChaptersFromHtml(publication, requestUrl); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Done getting Chapters for {publication.internalId}"); + Log($"Got {chapters.Count} chapters. {publication}"); return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray(); } @@ -180,11 +181,11 @@ public class MangaKatana : Connector return ret; } - public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, DownloadChapterTask parentTask, CancellationToken? cancellationToken = null) + public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, CancellationToken? cancellationToken = null) { if (cancellationToken?.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Downloading Chapter-Info {publication.sortName} {publication.internalId} {chapter.volumeNumber}-{chapter.chapterNumber}"); + Log($"Retrieving chapter-info {chapter} {publication}"); string requestUrl = chapter.url; // Leaving this in to check if the page exists DownloadClient.RequestResult requestResult = @@ -197,7 +198,7 @@ public class MangaKatana : Connector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, parentTask, comicInfoPath, "https://mangakatana.com/", cancellationToken); + return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://mangakatana.com/", cancellationToken); } private string[] ParseImageUrlsFromHtml(string mangaUrl) diff --git a/Tranga/Connectors/Manganato.cs b/Tranga/Connectors/Manganato.cs index cfc9791..26e1a42 100644 --- a/Tranga/Connectors/Manganato.cs +++ b/Tranga/Connectors/Manganato.cs @@ -2,26 +2,25 @@ using System.Net; using System.Text.RegularExpressions; using HtmlAgilityPack; -using Tranga.TrangaTasks; namespace Tranga.Connectors; public class Manganato : Connector { public override string name { get; } - - public Manganato(TrangaSettings settings, CommonObjects commonObjects) : base(settings, commonObjects) + + public Manganato(TBaseObject clone) : base(clone) { this.name = "Manganato"; this.downloadClient = new DownloadClient(new Dictionary() { {1, 60} - }, commonObjects.logger); + }, clone); } - protected override Publication[] GetPublicationsInternal(string publicationTitle = "") + protected override Publication[] GetPublications(string publicationTitle = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={publicationTitle})"); + Log($"Searching Publications. Term=\"{publicationTitle}\""); string sanitizedTitle = string.Join('_', Regex.Matches(publicationTitle, "[A-z]*")).ToLower(); string requestUrl = $"https://manganato.com/search/story/{sanitizedTitle}"; DownloadClient.RequestResult requestResult = @@ -29,7 +28,9 @@ public class Manganato : Connector if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300) return Array.Empty(); - return ParsePublicationsFromHtml(requestResult.result); + Publication[] publications = ParsePublicationsFromHtml(requestResult.result); + Log($"Retrieved {publications.Length} publications."); + return publications; } private Publication[] ParsePublicationsFromHtml(Stream html) @@ -125,7 +126,7 @@ public class Manganato : Connector public override Chapter[] GetChapters(Publication publication, string language = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Chapters for {publication.sortName} {publication.internalId} (language={language})"); + Log($"Getting chapters {publication}"); string requestUrl = $"https://chapmanganato.com/{publication.publicationId}"; DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(requestUrl, 1); @@ -138,7 +139,7 @@ public class Manganato : Connector NumberDecimalSeparator = "." }; List chapters = ParseChaptersFromHtml(publication, requestResult.result); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Done getting Chapters for {publication.internalId}"); + Log($"Got {chapters.Count} chapters. {publication}"); return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray(); } @@ -167,11 +168,11 @@ public class Manganato : Connector return ret; } - public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, DownloadChapterTask parentTask, CancellationToken? cancellationToken = null) + public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, CancellationToken? cancellationToken = null) { if (cancellationToken?.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Downloading Chapter-Info {publication.sortName} {publication.internalId} {chapter.volumeNumber}-{chapter.chapterNumber}"); + Log($"Retrieving chapter-info {chapter} {publication}"); string requestUrl = chapter.url; DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(requestUrl, 1); @@ -183,7 +184,7 @@ public class Manganato : Connector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, parentTask, comicInfoPath, "https://chapmanganato.com/", cancellationToken); + return DownloadChapterImages(imageUrls, chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, "https://chapmanganato.com/", cancellationToken); } private string[] ParseImageUrlsFromHtml(Stream html) diff --git a/Tranga/Connectors/Mangasee.cs b/Tranga/Connectors/Mangasee.cs index ea9de48..acd3567 100644 --- a/Tranga/Connectors/Mangasee.cs +++ b/Tranga/Connectors/Mangasee.cs @@ -5,7 +5,6 @@ using System.Xml.Linq; using HtmlAgilityPack; using Newtonsoft.Json; using PuppeteerSharp; -using Tranga.TrangaTasks; namespace Tranga.Connectors; @@ -15,13 +14,13 @@ public class Mangasee : Connector private IBrowser? _browser; private const string ChromiumVersion = "1154303"; - public Mangasee(TrangaSettings settings, CommonObjects commonObjects) : base(settings, commonObjects) + public Mangasee(TBaseObject clone) : base(clone) { this.name = "Mangasee"; this.downloadClient = new DownloadClient(new Dictionary() { { 1, 60 } - }, commonObjects.logger); + }, clone); Task d = new Task(DownloadBrowser); d.Start(); @@ -34,31 +33,29 @@ public class Mangasee : Connector browserFetcher.Remove(rev); if (!browserFetcher.LocalRevisions().Contains(ChromiumVersion)) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Downloading headless browser"); + Log("Downloading headless browser"); DateTime last = DateTime.Now.Subtract(TimeSpan.FromSeconds(5)); browserFetcher.DownloadProgressChanged += (_, args) => { double currentBytes = Convert.ToDouble(args.BytesReceived) / Convert.ToDouble(args.TotalBytesToReceive); if (args.TotalBytesToReceive == args.BytesReceived) + Log("Browser downloaded."); + else if (DateTime.Now > last.AddSeconds(1)) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Browser downloaded."); - } - else if (DateTime.Now > last.AddSeconds(5)) - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Browser download progress: {currentBytes:P2}"); + Log($"Browser download progress: {currentBytes:P2}"); last = DateTime.Now; } }; if (!browserFetcher.CanDownloadAsync(ChromiumVersion).Result) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Can't download browser version {ChromiumVersion}"); - return; + Log($"Can't download browser version {ChromiumVersion}"); + throw new Exception(); } await browserFetcher.DownloadAsync(ChromiumVersion); } - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Starting browser."); + Log("Starting Browser."); this._browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true, @@ -71,9 +68,9 @@ public class Mangasee : Connector }); } - protected override Publication[] GetPublicationsInternal(string publicationTitle = "") + protected override Publication[] GetPublications(string publicationTitle = "") { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={publicationTitle})"); + Log($"Searching Publications. Term=\"{publicationTitle}\""); string requestUrl = $"https://mangasee123.com/_search.php"; DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(requestUrl, 1); @@ -98,7 +95,7 @@ public class Mangasee : Connector queryFiltered = queryFiltered.Where(item => item.Value >= publicationTitle.Split(' ').Length - 1) .ToDictionary(item => item.Key, item => item.Value); - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Got {queryFiltered.Count} Publications (title={publicationTitle})"); + Log($"Retrieved {queryFiltered.Count} publications."); HashSet ret = new(); List orderedFiltered = @@ -111,7 +108,7 @@ public class Mangasee : Connector downloadClient.MakeRequest($"https://mangasee123.com/manga/{orderedItem.i}", 1); if ((int)requestResult.statusCode >= 200 || (int)requestResult.statusCode < 300) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Retrieving Publication info: {orderedItem.s} {index++}/{orderedFiltered.Count}"); + Log($"Retrieving Publication info: {orderedItem.s} {index++}/{orderedFiltered.Count}"); ret.Add(ParseSinglePublicationFromHtml(requestResult.result, orderedItem.s, orderedItem.i, orderedItem.a)); } } @@ -216,9 +213,10 @@ public class Mangasee : Connector public override Chapter[] GetChapters(Publication publication, string language = "") { + Log($"Getting chapters {publication}"); XDocument doc = XDocument.Load($"https://mangasee123.com/rss/{publication.publicationId}.xml"); XElement[] chapterItems = doc.Descendants("item").ToArray(); - List ret = new(); + List chapters = new(); foreach (XElement chapter in chapterItems) { string volumeNumber = "1"; @@ -227,7 +225,7 @@ public class Mangasee : Connector string url = chapter.Descendants("link").First().Value; url = url.Replace(Regex.Matches(url,"(-page-[0-9])")[0].ToString(),""); - ret.Add(new Chapter(publication, "", volumeNumber, chapterNumber, url)); + chapters.Add(new Chapter(publication, "", volumeNumber, chapterNumber, url)); } //Return Chapters ordered by Chapter-Number @@ -235,23 +233,23 @@ public class Mangasee : Connector { NumberDecimalSeparator = "." }; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Done getting Chapters for {publication.internalId}"); - return ret.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray(); + Log($"Got {chapters.Count} chapters. {publication}"); + return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray(); } - public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, DownloadChapterTask parentTask, CancellationToken? cancellationToken = null) + public override HttpStatusCode DownloadChapter(Publication publication, Chapter chapter, CancellationToken? cancellationToken = null) { if (cancellationToken?.IsCancellationRequested ?? false) return HttpStatusCode.RequestTimeout; while (this._browser is null && !(cancellationToken?.IsCancellationRequested??false)) { - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Waiting for headless browser to download..."); + Log("Waiting for headless browser to download..."); Thread.Sleep(1000); } if (cancellationToken?.IsCancellationRequested??false) return HttpStatusCode.RequestTimeout; - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Downloading Chapter-Info {publication.sortName} {publication.internalId} {chapter.volumeNumber}-{chapter.chapterNumber}"); + Log($"Retrieving chapter-info {chapter} {publication}"); IPage page = _browser!.NewPageAsync().Result; IResponse response = page.GoToAsync(chapter.url).Result; if (response.Ok) @@ -268,7 +266,7 @@ public class Mangasee : Connector string comicInfoPath = Path.GetTempFileName(); File.WriteAllText(comicInfoPath, chapter.GetComicInfoXmlString()); - return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, parentTask, comicInfoPath, cancellationToken:cancellationToken); + return DownloadChapterImages(urls.ToArray(), chapter.GetArchiveFilePath(settings.downloadLocation), 1, comicInfoPath, cancellationToken:cancellationToken); } return response.Status; } diff --git a/Tranga/LibraryManagers/Kavita.cs b/Tranga/LibraryConnectors/Kavita.cs similarity index 80% rename from Tranga/LibraryManagers/Kavita.cs rename to Tranga/LibraryConnectors/Kavita.cs index 04cbe67..e670eb4 100644 --- a/Tranga/LibraryManagers/Kavita.cs +++ b/Tranga/LibraryConnectors/Kavita.cs @@ -1,19 +1,19 @@ using System.Text.Json.Nodes; -using Logging; using Newtonsoft.Json; using JsonSerializer = System.Text.Json.JsonSerializer; -namespace Tranga.LibraryManagers; +namespace Tranga.LibraryConnectors; -public class Kavita : LibraryManager +public class Kavita : LibraryConnector { - public Kavita(string baseUrl, string username, string password, Logger? logger) : base(baseUrl, GetToken(baseUrl, username, password), logger, LibraryType.Kavita) + public Kavita(string baseUrl, string username, string password, TBaseObject clone) : + base(baseUrl, GetToken(baseUrl, username, password), LibraryType.Kavita, clone) { } [JsonConstructor] - public Kavita(string baseUrl, string auth, Logger? logger) : base(baseUrl, auth, logger, LibraryType.Kavita) + public Kavita(string baseUrl, string auth, TBaseObject clone) : base(baseUrl, auth, LibraryType.Kavita, clone) { } @@ -42,7 +42,7 @@ public class Kavita : LibraryManager public override void UpdateLibrary() { - logger?.WriteLine(this.GetType().ToString(), $"Updating Libraries"); + Log("Updating libraries."); foreach (KavitaLibrary lib in GetLibraries()) NetClient.MakePost($"{baseUrl}/api/Library/scan?libraryId={lib.id}", "Bearer", auth, logger); } @@ -53,17 +53,17 @@ public class Kavita : LibraryManager /// Array of KavitaLibrary private IEnumerable GetLibraries() { - logger?.WriteLine(this.GetType().ToString(), $"Getting Libraries"); + Log("Getting libraries."); Stream data = NetClient.MakeRequest($"{baseUrl}/api/Library", "Bearer", auth, logger); if (data == Stream.Null) { - logger?.WriteLine(this.GetType().ToString(), $"No libraries returned"); + Log("No libraries returned"); return Array.Empty(); } JsonArray? result = JsonSerializer.Deserialize(data); if (result is null) { - logger?.WriteLine(this.GetType().ToString(), $"No libraries returned"); + Log("No libraries returned"); return Array.Empty(); } diff --git a/Tranga/LibraryManagers/Komga.cs b/Tranga/LibraryConnectors/Komga.cs similarity index 75% rename from Tranga/LibraryManagers/Komga.cs rename to Tranga/LibraryConnectors/Komga.cs index 545ec91..669b203 100644 --- a/Tranga/LibraryManagers/Komga.cs +++ b/Tranga/LibraryConnectors/Komga.cs @@ -1,29 +1,28 @@ using System.Text.Json.Nodes; -using Logging; using Newtonsoft.Json; using JsonSerializer = System.Text.Json.JsonSerializer; -namespace Tranga.LibraryManagers; +namespace Tranga.LibraryConnectors; /// /// Provides connectivity to Komga-API /// Can fetch and update libraries /// -public class Komga : LibraryManager +public class Komga : LibraryConnector { - public Komga(string baseUrl, string username, string password, Logger? logger) - : base(baseUrl, Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}")), logger, LibraryType.Komga) + public Komga(string baseUrl, string username, string password, TBaseObject clone) + : base(baseUrl, Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}")), LibraryType.Komga, clone) { } [JsonConstructor] - public Komga(string baseUrl, string auth, Logger? logger) : base(baseUrl, auth, logger, LibraryType.Komga) + public Komga(string baseUrl, string auth, TBaseObject clone) : base(baseUrl, auth, LibraryType.Komga, clone) { } public override void UpdateLibrary() { - logger?.WriteLine(this.GetType().ToString(), $"Updating Libraries"); + Log("Updating libraries."); foreach (KomgaLibrary lib in GetLibraries()) NetClient.MakePost($"{baseUrl}/api/v1/libraries/{lib.id}/scan", "Basic", auth, logger); } @@ -34,17 +33,17 @@ public class Komga : LibraryManager /// Array of KomgaLibraries private IEnumerable GetLibraries() { - logger?.WriteLine(this.GetType().ToString(), $"Getting Libraries"); + Log("Getting Libraries"); Stream data = NetClient.MakeRequest($"{baseUrl}/api/v1/libraries", "Basic", auth, logger); if (data == Stream.Null) { - logger?.WriteLine(this.GetType().ToString(), $"No libraries returned"); + Log("No libraries returned"); return Array.Empty(); } JsonArray? result = JsonSerializer.Deserialize(data); if (result is null) { - logger?.WriteLine(this.GetType().ToString(), $"No libraries returned"); + Log("No libraries returned"); return Array.Empty(); } diff --git a/Tranga/LibraryManagers/LibraryManager.cs b/Tranga/LibraryConnectors/LibraryConnector.cs similarity index 58% rename from Tranga/LibraryManagers/LibraryManager.cs rename to Tranga/LibraryConnectors/LibraryConnector.cs index 88e7b98..165304e 100644 --- a/Tranga/LibraryManagers/LibraryManager.cs +++ b/Tranga/LibraryConnectors/LibraryConnector.cs @@ -1,12 +1,10 @@ using System.Net; using System.Net.Http.Headers; using Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -namespace Tranga.LibraryManagers; +namespace Tranga.LibraryConnectors; -public abstract class LibraryManager +public abstract class LibraryConnector : TBaseObject { public enum LibraryType : byte { @@ -19,26 +17,15 @@ public abstract class LibraryManager public string baseUrl { get; } // ReSharper disable once MemberCanBeProtected.Global public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems - protected Logger? logger; - /// Base-URL of Komga instance, no trailing slashes(/) - /// Base64 string of username and password (username):(password) - /// - /// - protected LibraryManager(string baseUrl, string auth, Logger? logger, LibraryType libraryType) + protected LibraryConnector(string baseUrl, string auth, LibraryType libraryType, TBaseObject clone) : base(clone) { this.baseUrl = baseUrl; this.auth = auth; - this.logger = logger; this.libraryType = libraryType; } public abstract void UpdateLibrary(); - public void AddLogger(Logger newLogger) - { - this.logger = newLogger; - } - protected static class NetClient { public static Stream MakeRequest(string url, string authScheme, string auth, Logger? logger) @@ -52,7 +39,7 @@ public abstract class LibraryManager RequestUri = new Uri(url) }; HttpResponseMessage response = client.Send(requestMessage); - logger?.WriteLine("LibraryManager", $"GET {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); + 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); @@ -78,7 +65,7 @@ public abstract class LibraryManager RequestUri = new Uri(url) }; HttpResponseMessage response = client.Send(requestMessage); - logger?.WriteLine("LibraryManager", $"POST {url} -> {(int)response.StatusCode}: {response.ReasonPhrase}"); + 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); @@ -88,34 +75,4 @@ public abstract class LibraryManager return false; } } - - public class LibraryManagerJsonConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return (objectType == typeof(LibraryManager)); - } - - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - JObject jo = JObject.Load(reader); - if (jo["libraryType"]!.Value() == (Int64)LibraryType.Komga) - return jo.ToObject(serializer)!; - - if (jo["libraryType"]!.Value() == (Int64)LibraryType.Kavita) - return jo.ToObject(serializer)!; - - 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/LibraryConnectors/LibraryManagerJsonConverter.cs b/Tranga/LibraryConnectors/LibraryManagerJsonConverter.cs new file mode 100644 index 0000000..f8a4d11 --- /dev/null +++ b/Tranga/LibraryConnectors/LibraryManagerJsonConverter.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Tranga.LibraryConnectors; + +public class LibraryManagerJsonConverter : JsonConverter +{ + 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() == (Int64)LibraryConnector.LibraryType.Komga) + return jo.ToObject(serializer)!; + + if (jo["libraryType"]!.Value() == (Int64)LibraryConnector.LibraryType.Kavita) + return jo.ToObject(serializer)!; + + 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/Migrator.cs b/Tranga/Migrator.cs deleted file mode 100644 index 0ac0e91..0000000 --- a/Tranga/Migrator.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Text.Json.Nodes; -using Logging; -using Newtonsoft.Json; -using Tranga.LibraryManagers; -using Tranga.NotificationManagers; -using Tranga.TrangaTasks; - -namespace Tranga; - -public static class Migrator -{ - internal static readonly ushort CurrentVersion = 17; - public static void Migrate(string settingsFilePath, Logger? logger) - { - if (!File.Exists(settingsFilePath)) - return; - JsonNode settingsNode = JsonNode.Parse(File.ReadAllText(settingsFilePath))!; - ushort version = settingsNode["version"] is not null - ? settingsNode["version"]!.GetValue() - : settingsNode["ts"]!["version"]!.GetValue(); - logger?.WriteLine("Migrator", $"Migrating {version} -> {CurrentVersion}"); - switch (version) - { - case 15: - MoveToCommonObjects(settingsFilePath, logger); - TrangaSettings.SettingsJsonObject sjo = JsonConvert.DeserializeObject(File.ReadAllText(settingsFilePath))!; - RemoveUpdateLibraryTask(sjo.ts!, logger); - break; - case 16: - MoveToCommonObjects(settingsFilePath, logger); - break; - } - - TrangaSettings.SettingsJsonObject sjo2 = JsonConvert.DeserializeObject( - File.ReadAllText(settingsFilePath), - new JsonSerializerSettings - { - Converters = - { - new TrangaTask.TrangaTaskJsonConverter(), - new NotificationManager.NotificationManagerJsonConverter(), - new LibraryManager.LibraryManagerJsonConverter() - } - })!; - sjo2.ts!.version = CurrentVersion; - sjo2.ts!.ExportSettings(); - } - - private static void RemoveUpdateLibraryTask(TrangaSettings settings, Logger? logger) - { - if (!File.Exists(settings.tasksFilePath)) - return; - - logger?.WriteLine("Migrator", "Removing old/deprecated UpdateLibraryTasks (v16)"); - string tasksJsonString = File.ReadAllText(settings.tasksFilePath); - HashSet tasks = JsonConvert.DeserializeObject>(tasksJsonString, - new JsonSerializerSettings { Converters = { new TrangaTask.TrangaTaskJsonConverter() } })!; - tasks.RemoveWhere(t => t.task == TrangaTask.Task.UpdateLibraries); - File.WriteAllText(settings.tasksFilePath, JsonConvert.SerializeObject(tasks)); - } - - public static void MoveToCommonObjects(string settingsFilePath, Logger? logger) - { - if (!File.Exists(settingsFilePath)) - return; - - logger?.WriteLine("Migrator", "Moving Settings to commonObjects-structure (v17)"); - JsonNode node = JsonNode.Parse(File.ReadAllText(settingsFilePath))!; - TrangaSettings ts = new( - node["downloadLocation"]!.GetValue(), - node["workingDirectory"]!.GetValue()); - JsonArray libraryManagers = node["libraryManagers"]!.AsArray(); - logger?.WriteLine("Migrator", $"\tGot {libraryManagers.Count} libraryManagers."); - JsonNode? komgaNode = libraryManagers.FirstOrDefault(lm => lm["libraryType"].GetValue() == (byte)LibraryManager.LibraryType.Komga); - JsonNode? kavitaNode = libraryManagers.FirstOrDefault(lm => lm["libraryType"].GetValue() == (byte)LibraryManager.LibraryType.Kavita); - HashSet lms = new(); - if (komgaNode is not null) - lms.Add(new Komga(komgaNode["baseUrl"]!.GetValue(), komgaNode["auth"]!.GetValue(), null)); - if (kavitaNode is not null) - lms.Add(new Kavita(kavitaNode["baseUrl"]!.GetValue(), kavitaNode["auth"]!.GetValue(), null)); - - JsonArray notificationManagers = node["notificationManagers"]!.AsArray(); - logger?.WriteLine("Migrator", $"\tGot {notificationManagers.Count} notificationManagers."); - JsonNode? gotifyNode = notificationManagers.FirstOrDefault(nm => - nm["notificationManagerType"].GetValue() == (byte)NotificationManager.NotificationManagerType.Gotify); - JsonNode? lunaSeaNode = notificationManagers.FirstOrDefault(nm => - nm["notificationManagerType"].GetValue() == (byte)NotificationManager.NotificationManagerType.LunaSea); - HashSet nms = new(); - if (gotifyNode is not null) - nms.Add(new Gotify(gotifyNode["endpoint"]!.GetValue(), gotifyNode["appToken"]!.GetValue())); - if (lunaSeaNode is not null) - nms.Add(new LunaSea(lunaSeaNode["id"]!.GetValue())); - - CommonObjects co = new (lms, nms, logger, settingsFilePath); - - TrangaSettings.SettingsJsonObject sjo = new(ts, co); - File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(sjo)); - } -} \ No newline at end of file diff --git a/Tranga/NotificationManagers/Gotify.cs b/Tranga/NotificationConnectors/Gotify.cs similarity index 82% rename from Tranga/NotificationManagers/Gotify.cs rename to Tranga/NotificationConnectors/Gotify.cs index a823665..ea06e5d 100644 --- a/Tranga/NotificationManagers/Gotify.cs +++ b/Tranga/NotificationConnectors/Gotify.cs @@ -1,10 +1,9 @@ using System.Text; -using Logging; using Newtonsoft.Json; -namespace Tranga.NotificationManagers; +namespace Tranga.NotificationConnectors; -public class Gotify : NotificationManager +public class Gotify : NotificationConnector { public string endpoint { get; } // ReSharper disable once MemberCanBePrivate.Global @@ -12,7 +11,7 @@ public class Gotify : NotificationManager private readonly HttpClient _client = new(); [JsonConstructor] - public Gotify(string endpoint, string appToken, Logger? logger = null) : base(NotificationManagerType.Gotify, logger) + public Gotify(string endpoint, string appToken, TBaseObject clone) : base(NotificationManagerType.Gotify, clone) { this.endpoint = endpoint; this.appToken = appToken; @@ -20,7 +19,7 @@ public class Gotify : NotificationManager public override void SendNotification(string title, string notificationText) { - logger?.WriteLine(this.GetType().ToString(), $"Sending notification: {title} - {notificationText}"); + Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, notificationText); HttpRequestMessage request = new(HttpMethod.Post, $"{endpoint}/message"); request.Headers.Add("X-Gotify-Key", this.appToken); diff --git a/Tranga/NotificationManagers/LunaSea.cs b/Tranga/NotificationConnectors/LunaSea.cs similarity index 73% rename from Tranga/NotificationManagers/LunaSea.cs rename to Tranga/NotificationConnectors/LunaSea.cs index aef14c5..07e7347 100644 --- a/Tranga/NotificationManagers/LunaSea.cs +++ b/Tranga/NotificationConnectors/LunaSea.cs @@ -1,24 +1,23 @@ using System.Text; -using Logging; using Newtonsoft.Json; -namespace Tranga.NotificationManagers; +namespace Tranga.NotificationConnectors; -public class LunaSea : NotificationManager +public class LunaSea : NotificationConnector { // ReSharper disable once MemberCanBePrivate.Global public string id { get; init; } private readonly HttpClient _client = new(); [JsonConstructor] - public LunaSea(string id, Logger? logger = null) : base(NotificationManagerType.LunaSea, logger) + public LunaSea(string id, TBaseObject clone) : base(NotificationManagerType.LunaSea, clone) { this.id = id; } public override void SendNotification(string title, string notificationText) { - logger?.WriteLine(this.GetType().ToString(), $"Sending notification: {title} - {notificationText}"); + Log($"Sending notification: {title} - {notificationText}"); MessageData message = new(title, notificationText); HttpRequestMessage request = new(HttpMethod.Post, $"https://notify.lunasea.app/v1/custom/{id}"); request.Content = new StringContent(JsonConvert.SerializeObject(message, Formatting.None), Encoding.UTF8, "application/json"); @@ -26,7 +25,7 @@ public class LunaSea : NotificationManager if (!response.IsSuccessStatusCode) { StreamReader sr = new (response.Content.ReadAsStream()); - logger?.WriteLine(this.GetType().ToString(), $"{response.StatusCode}: {sr.ReadToEnd()}"); + Log($"{response.StatusCode}: {sr.ReadToEnd()}"); } } diff --git a/Tranga/NotificationConnectors/NotificationConnector.cs b/Tranga/NotificationConnectors/NotificationConnector.cs new file mode 100644 index 0000000..aa77a78 --- /dev/null +++ b/Tranga/NotificationConnectors/NotificationConnector.cs @@ -0,0 +1,15 @@ +namespace Tranga.NotificationConnectors; + +public abstract class NotificationConnector : TBaseObject +{ + public NotificationManagerType notificationManagerType; + + protected NotificationConnector(NotificationManagerType notificationManagerType, TBaseObject clone) : base(clone) + { + this.notificationManagerType = notificationManagerType; + } + + public enum NotificationManagerType : byte { Gotify = 0, LunaSea = 1 } + + public abstract void SendNotification(string title, string notificationText); +} \ No newline at end of file diff --git a/Tranga/NotificationConnectors/NotificationManagerJsonConverter.cs b/Tranga/NotificationConnectors/NotificationManagerJsonConverter.cs new file mode 100644 index 0000000..a85a226 --- /dev/null +++ b/Tranga/NotificationConnectors/NotificationManagerJsonConverter.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Tranga.NotificationConnectors; + +public class NotificationManagerJsonConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return (objectType == typeof(NotificationConnector)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, + JsonSerializer serializer) + { + JObject jo = JObject.Load(reader); + if (jo["notificationManagerType"]!.Value() == (byte)NotificationConnector.NotificationManagerType.Gotify) + return jo.ToObject(serializer)!; + else if (jo["notificationManagerType"]!.Value() == (byte)NotificationConnector.NotificationManagerType.LunaSea) + return jo.ToObject(serializer)!; + + 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/NotificationManagers/NotificationManager.cs b/Tranga/NotificationManagers/NotificationManager.cs deleted file mode 100644 index 7c65063..0000000 --- a/Tranga/NotificationManagers/NotificationManager.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Tranga.NotificationManagers; - -public abstract class NotificationManager -{ - protected Logger? logger; - public NotificationManagerType notificationManagerType; - - protected NotificationManager(NotificationManagerType notificationManagerType, Logger? logger = null) - { - this.notificationManagerType = notificationManagerType; - this.logger = logger; - } - - public enum NotificationManagerType : byte { Gotify = 0, LunaSea = 1 } - - public abstract void SendNotification(string title, string notificationText); - - public void AddLogger(Logger pLogger) - { - this.logger = pLogger; - } - - public class NotificationManagerJsonConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return (objectType == typeof(NotificationManager)); - } - - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, - JsonSerializer serializer) - { - JObject jo = JObject.Load(reader); - if (jo["notificationManagerType"]!.Value() == (byte)NotificationManagerType.Gotify) - return jo.ToObject(serializer)!; - else if (jo["notificationManagerType"]!.Value() == (byte)NotificationManagerType.LunaSea) - return jo.ToObject(serializer)!; - - 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/Publication.cs b/Tranga/Publication.cs index 330e7c0..05eb213 100644 --- a/Tranga/Publication.cs +++ b/Tranga/Publication.cs @@ -33,7 +33,7 @@ public struct Publication public string internalId { get; } public float ignoreChaptersBelow { get; set; } - private static readonly Regex LegalCharacters = new Regex(@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*"); + private static readonly Regex LegalCharacters = new (@"[A-Z]*[a-z]*[0-9]* *\.*-*,*'*\'*\)*\(*~*!*"); [JsonConstructor] public Publication(string sortName, List authors, string? description, Dictionary altTitles, string[] tags, string? posterUrl, string? coverFileNameInCache, Dictionary? links, int? year, string? originalLanguage, string status, string publicationId, string? folderName = null, float? ignoreChaptersBelow = 0) diff --git a/Tranga/TBaseObject.cs b/Tranga/TBaseObject.cs new file mode 100644 index 0000000..8f95e3c --- /dev/null +++ b/Tranga/TBaseObject.cs @@ -0,0 +1,68 @@ +using Logging; +using Tranga.LibraryConnectors; +using Tranga.NotificationConnectors; + +namespace Tranga; + +public class TBaseObject +{ + protected Logger? logger { get; init; } + protected TrangaSettings settings { get; init; } + protected HashSet notificationConnectors { get; init; } + protected HashSet libraryConnectors { get; init; } + + public TBaseObject(TBaseObject clone) + { + this.logger = clone.logger; + this.settings = clone.settings; + this.notificationConnectors = clone.notificationConnectors; + this.libraryConnectors = clone.libraryConnectors; + } + + public TBaseObject(Logger? logger, TrangaSettings settings, HashSet notificationConnectors, HashSet libraryConnectors) + { + this.logger = logger; + this.settings = settings; + this.notificationConnectors = notificationConnectors; + this.libraryConnectors = libraryConnectors; + } + + 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 bool IsFileInUse(string filePath) + { + if (!File.Exists(filePath)) + return false; + try + { + using FileStream stream = new (filePath, FileMode.Open, FileAccess.Read, FileShare.None); + stream.Close(); + return false; + } + catch (IOException) + { + Log($"File is in use {filePath}"); + return true; + } + } + + protected void SendNotification(string title, string message) + { + foreach(NotificationConnector nc in notificationConnectors) + nc.SendNotification(title, message); + } + + protected void UpdateLibraries() + { + foreach (LibraryConnector libraryConnector in libraryConnectors) + libraryConnector.UpdateLibrary(); + } +} \ No newline at end of file diff --git a/Tranga/TaskManager.cs b/Tranga/TaskManager.cs deleted file mode 100644 index 518c5ad..0000000 --- a/Tranga/TaskManager.cs +++ /dev/null @@ -1,385 +0,0 @@ -using Newtonsoft.Json; -using Tranga.Connectors; -using Tranga.TrangaTasks; - -namespace Tranga; - -/// -/// Manages all TrangaTasks. -/// Provides a Threaded environment to execute Tasks, and still manage the Task-Collection -/// -public class TaskManager -{ - public HashSet collection = new(); - private HashSet _allTasks = new(); - private readonly Dictionary _runningTasks = new (); - public bool _continueRunning = true; - private readonly Connector[] _connectors; - public TrangaSettings settings { get; } - public CommonObjects commonObjects { get; init; } - - public TaskManager(TrangaSettings settings, Logging.Logger? logger) - { - commonObjects = CommonObjects.LoadSettings(settings.settingsFilePath, logger); - commonObjects.logger?.WriteLine(this.GetType().ToString(), value: "\n"+ - @"-----------------------------------------------------------------"+"\n"+ - @" |¯¯¯¯¯¯|°|¯¯¯¯¯¯\ /¯¯¯¯¯¯| |¯¯¯\|¯¯¯| /¯¯¯¯¯¯\' /¯¯¯¯¯¯| "+"\n"+ - @" | | | x <|' / ! | | '| | (/¯¯¯\° / ! | "+ "\n"+ - @" ¯|__|¯ |__|\\__\\ /___/¯|_'| |___|\\__| \\_____/' /___/¯|_'| "+ "\n"+ - @"-----------------------------------------------------------------"); - this._connectors = new Connector[] - { - new MangaDex(settings, commonObjects), - new Manganato(settings, commonObjects), - new Mangasee(settings, commonObjects), - new MangaKatana(settings, commonObjects) - }; - - this.settings = settings; - ImportData(); - ExportDataAndSettings(); - Thread taskChecker = new(TaskCheckerThread); - taskChecker.Start(); - } - - /// - /// Runs continuously until shutdown. - /// Checks if tasks have to be executed (time elapsed) - /// - private void TaskCheckerThread() - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Starting TaskCheckerThread."); - int waitingTasksCount = _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting); - while (_continueRunning) - { - foreach (TrangaTask waitingButExecute in _allTasks.Where(taskQuery => - taskQuery.nextExecution < DateTime.Now && - taskQuery.state is TrangaTask.ExecutionState.Waiting)) - { - waitingButExecute.state = TrangaTask.ExecutionState.Enqueued; - } - - foreach (TrangaTask enqueuedTask in _allTasks.Where(enqueuedTask => enqueuedTask.state is TrangaTask.ExecutionState.Enqueued).OrderBy(enqueuedTask => enqueuedTask.nextExecution)) - { - switch (enqueuedTask.task) - { - case TrangaTask.Task.DownloadChapter: - case TrangaTask.Task.MonitorPublication: - if (!_allTasks.Any(taskQuery => - { - if (taskQuery.state is not TrangaTask.ExecutionState.Running) return false; - switch (taskQuery) - { - case DownloadChapterTask dct when enqueuedTask is DownloadChapterTask eDct && dct.connectorName == eDct.connectorName: - case MonitorPublicationTask mpt when enqueuedTask is MonitorPublicationTask eMpt && mpt.connectorName == eMpt.connectorName: - return true; - default: - return false; - } - })) - { - ExecuteTaskNow(enqueuedTask); - } - break; - case TrangaTask.Task.UpdateLibraries: - ExecuteTaskNow(enqueuedTask); - break; - } - } - - foreach (TrangaTask timedOutTask in _runningTasks.Keys - .Where(taskQuery => taskQuery.lastChange < DateTime.Now.Subtract(TimeSpan.FromMinutes(3)))) - { - _runningTasks[timedOutTask].Cancel(); - timedOutTask.state = TrangaTask.ExecutionState.Failed; - } - - foreach (TrangaTask finishedTask in _allTasks - .Where(taskQuery => taskQuery.state is TrangaTask.ExecutionState.Success).ToArray()) - { - if(finishedTask is DownloadChapterTask) - { - DeleteTask(finishedTask); - finishedTask.state = TrangaTask.ExecutionState.Success; - } - else - { - finishedTask.state = TrangaTask.ExecutionState.Waiting; - this._runningTasks.Remove(finishedTask); - } - } - - foreach (TrangaTask failedTask in _allTasks.Where(taskQuery => - taskQuery.state is TrangaTask.ExecutionState.Failed).ToArray()) - { - DeleteTask(failedTask); - TrangaTask newTask = failedTask.Clone(); - failedTask.parentTask?.AddChildTask(newTask); - AddTask(newTask); - } - - if(waitingTasksCount != _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting)) - ExportDataAndSettings(); - waitingTasksCount = _allTasks.Count(task => task.state is TrangaTask.ExecutionState.Waiting); - Thread.Sleep(1000); - } - } - - /// - /// Forces the execution of a given task - /// - /// Task to execute - public void ExecuteTaskNow(TrangaTask task) - { - task.state = TrangaTask.ExecutionState.Running; - CancellationTokenSource cToken = new (); - Task t = new(() => - { - task.Execute(this, cToken.Token); - }, cToken.Token); - _runningTasks.Add(task, cToken); - t.Start(); - } - - public void AddTask(TrangaTask newTask) - { - switch (newTask.task) - { - case TrangaTask.Task.UpdateLibraries: - //Only one UpdateKomgaLibrary Task - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Replacing old {newTask.task}-Task."); - if (GetTasksMatching(newTask).FirstOrDefault() is { } exists) - _allTasks.Remove(exists); - _allTasks.Add(newTask); - ExportDataAndSettings(); - break; - default: - if (!GetTasksMatching(newTask).Any()) - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Adding new Task {newTask}"); - _allTasks.Add(newTask); - ExportDataAndSettings(); - } - else - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Task already exists {newTask}"); - break; - } - } - - public void DeleteTask(TrangaTask removeTask) - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Removing Task {removeTask}"); - if(_allTasks.Contains(removeTask)) - _allTasks.Remove(removeTask); - removeTask.parentTask?.RemoveChildTask(removeTask); - if (_runningTasks.ContainsKey(removeTask)) - { - _runningTasks[removeTask].Cancel(); - _runningTasks.Remove(removeTask); - } - foreach(TrangaTask childTask in removeTask.childTasks) - DeleteTask(childTask); - ExportDataAndSettings(); - } - - // ReSharper disable once MemberCanBePrivate.Global - public IEnumerable GetTasksMatching(TrangaTask mTask) - { - switch (mTask.task) - { - case TrangaTask.Task.UpdateLibraries: - return GetTasksMatching(TrangaTask.Task.UpdateLibraries); - case TrangaTask.Task.DownloadChapter: - DownloadChapterTask dct = (DownloadChapterTask)mTask; - return GetTasksMatching(TrangaTask.Task.DownloadChapter, connectorName: dct.connectorName, - internalId: dct.publication.internalId, chapterNumber: dct.chapter.chapterNumber); - case TrangaTask.Task.MonitorPublication: - MonitorPublicationTask mpt = (MonitorPublicationTask)mTask; - return GetTasksMatching(TrangaTask.Task.MonitorPublication, connectorName: mpt.connectorName, - internalId: mpt.publication.internalId); - } - return Array.Empty(); - } - - public IEnumerable GetTasksMatching(TrangaTask.Task taskType, string? connectorName = null, string? searchString = null, string? internalId = null, string? chapterNumber = null) - { - switch (taskType) - { - case TrangaTask.Task.MonitorPublication: - if(connectorName is null) - return _allTasks.Where(tTask => tTask.task == taskType); - GetConnector(connectorName);//Name check - if (searchString is not null) - { - return _allTasks.Where(mTask => - mTask is MonitorPublicationTask mpt && mpt.connectorName == connectorName && - mpt.ToString().Contains(searchString, StringComparison.InvariantCultureIgnoreCase)); - } - else if (internalId is not null) - { - return _allTasks.Where(mTask => - mTask is MonitorPublicationTask mpt && mpt.connectorName == connectorName && - mpt.publication.internalId == internalId); - } - else - return _allTasks.Where(tTask => - tTask is MonitorPublicationTask mpt && mpt.connectorName == connectorName); - - case TrangaTask.Task.DownloadChapter: - if(connectorName is null) - return _allTasks.Where(tTask => tTask.task == taskType); - GetConnector(connectorName);//Name check - if (searchString is not null) - { - return _allTasks.Where(mTask => - mTask is DownloadChapterTask dct && dct.connectorName == connectorName && - dct.ToString().Contains(searchString, StringComparison.InvariantCultureIgnoreCase)); - } - else if (internalId is not null && chapterNumber is not null) - { - return _allTasks.Where(mTask => - mTask is DownloadChapterTask dct && dct.connectorName == connectorName && - dct.publication.internalId == internalId && - dct.chapter.chapterNumber == chapterNumber); - } - else - return _allTasks.Where(mTask => - mTask is DownloadChapterTask dct && dct.connectorName == connectorName); - - default: - return Array.Empty(); - } - } - - /// - /// Removes a Task from the queue - /// - /// - public void RemoveTaskFromQueue(TrangaTask task) - { - task.lastExecuted = DateTime.Now; - task.state = TrangaTask.ExecutionState.Waiting; - } - - /// - /// Sets last execution time to start of time - /// Let taskManager handle enqueuing - /// - /// - public void AddTaskToQueue(TrangaTask task) - { - task.lastExecuted = DateTime.UnixEpoch; - } - - /// All available Connectors - public Dictionary GetAvailableConnectors() - { - return this._connectors.ToDictionary(connector => connector.name, connector => connector); - } - - /// All TrangaTasks in task-collection - public TrangaTask[] GetAllTasks() - { - TrangaTask[] ret = new TrangaTask[_allTasks.Count]; - _allTasks.CopyTo(ret); - return ret; - } - - /// All added Publications - public Publication[] GetAllPublications() - { - return this.collection.ToArray(); - } - - public List GetExistingChaptersList(Connector connector, Publication publication, string language) - { - Chapter[] newChapters = connector.GetChapters(publication, language); - return newChapters.Where(nChapter => nChapter.CheckChapterIsDownloaded(settings.downloadLocation)).ToList(); - } - - /// - /// Return Connector with given Name - /// - /// Connector-name (exact) - /// If Connector is not available - public Connector GetConnector(string? connectorName) - { - if(connectorName is null) - throw new Exception($"connectorName can not be null"); - Connector? ret = this._connectors.FirstOrDefault(connector => connector.name == connectorName); - if (ret is null) - throw new Exception($"Connector {connectorName} is not an available Connector."); - return ret; - } - - /// - /// Shuts down the taskManager. - /// - /// If force is true, tasks are aborted. - public void Shutdown(bool force = false) - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Shutting down (forced={force})"); - _continueRunning = false; - ExportDataAndSettings(); - - if(force) - Environment.Exit(_allTasks.Count(task => task.state is TrangaTask.ExecutionState.Enqueued or TrangaTask.ExecutionState.Running)); - - //Wait for tasks to finish - while(_allTasks.Any(task => task.state is TrangaTask.ExecutionState.Running or TrangaTask.ExecutionState.Enqueued)) - Thread.Sleep(10); - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Tasks finished. Bye!"); - Environment.Exit(0); - } - - private void ImportData() - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), "Importing Data"); - if (File.Exists(settings.tasksFilePath)) - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Importing tasks from {settings.tasksFilePath}"); - string buffer = File.ReadAllText(settings.tasksFilePath); - this._allTasks = JsonConvert.DeserializeObject>(buffer, new JsonSerializerSettings() { Converters = { new TrangaTask.TrangaTaskJsonConverter() } })!; - } - - foreach (TrangaTask task in this._allTasks.Where(tTask => tTask.parentTaskId is not null).ToArray()) - { - TrangaTask? parentTask = this._allTasks.FirstOrDefault(pTask => pTask.taskId == task.parentTaskId); - if (parentTask is not null) - { - this.DeleteTask(task); - parentTask.lastExecuted = DateTime.UnixEpoch; - } - } - } - - /// - /// Exports data (settings, tasks) to file - /// - private void ExportDataAndSettings() - { - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Exporting settings to {settings.settingsFilePath}"); - settings.ExportSettings(); - - commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Exporting tasks to {settings.tasksFilePath}"); - while(IsFileInUse(settings.tasksFilePath)) - Thread.Sleep(50); - File.WriteAllText(settings.tasksFilePath, JsonConvert.SerializeObject(this._allTasks)); - } - - private bool IsFileInUse(string path) - { - if (!File.Exists(path)) - return false; - try - { - using FileStream stream = new (path, FileMode.Open, FileAccess.Read, FileShare.None); - stream.Close(); - } - catch (IOException) - { - return true; - } - return false; - } -} \ No newline at end of file diff --git a/Tranga/Tranga.cs b/Tranga/Tranga.cs deleted file mode 100644 index 997a787..0000000 --- a/Tranga/Tranga.cs +++ /dev/null @@ -1,580 +0,0 @@ -using System.Globalization; -using System.Runtime.InteropServices; -using Logging; -using Tranga.API; -using Tranga.Connectors; -using Tranga.NotificationManagers; -using Tranga.TrangaTasks; - -namespace Tranga; - -public static class Tranga -{ - public static void Main(string[] args) - { - bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga-API"); - - string downloadFolderPath = isLinux ? "/Manga" : Path.Join(applicationFolderPath, "Manga"); - string logsFolderPath = isLinux ? "/var/log/Tranga" : Path.Join(applicationFolderPath, "log"); - string logFilePath = Path.Join(logsFolderPath, $"log-{DateTime.Now:dd-M-yyyy-HH-mm-ss}.txt"); - string settingsFilePath = Path.Join(applicationFolderPath, "settings.json"); - - - Directory.CreateDirectory(logsFolderPath); - Logger logger = isLinux - ? new Logger(new[] { Logger.LoggerType.FileLogger, Logger.LoggerType.ConsoleLogger }, Console.Out, Console.Out.Encoding, logFilePath) - : new Logger(new[] { Logger.LoggerType.FileLogger }, Console.Out, Console.Out.Encoding, logFilePath); - - logger.WriteLine("Tranga",value: "\n"+ - "-------------------------------------------\n"+ - " Starting Tranga-API\n"+ - "-------------------------------------------"); - logger.WriteLine("Tranga", "Migrating..."); - Migrator.Migrate(settingsFilePath, logger); - - TrangaSettings settings; - if (File.Exists(settingsFilePath)) - { - logger.WriteLine("Tranga", $"Loading settings {settingsFilePath}"); - settings = TrangaSettings.LoadSettings(settingsFilePath); - } - else - { - settings = new TrangaSettings(downloadFolderPath, applicationFolderPath); - settings.version = Migrator.CurrentVersion; - } - - Directory.CreateDirectory(settings.workingDirectory); - Directory.CreateDirectory(settings.downloadLocation); - Directory.CreateDirectory(settings.coverImageCache); - - logger.WriteLine("Tranga", $"Is Linux: {isLinux}"); - logger.WriteLine("Tranga",$"Application-Folder: {settings.workingDirectory}"); - logger.WriteLine("Tranga",$"Settings-File-Path: {settings.settingsFilePath}"); - logger.WriteLine("Tranga",$"Download-Folder-Path: {settings.downloadLocation}"); - logger.WriteLine("Tranga",$"Logfile-Path: {logFilePath}"); - logger.WriteLine("Tranga",$"Image-Cache-Path: {settings.coverImageCache}"); - - logger.WriteLine("Tranga", "Loading Taskmanager."); - TaskManager taskManager = new (settings, logger); - - Server _ = new (6531, taskManager); - foreach(NotificationManager nm in taskManager.commonObjects.notificationManagers) - nm.SendNotification("Tranga-API", "Started Tranga-API"); - - if(!isLinux) - TaskMode(taskManager, logger); - } - - private static void TaskMode(TaskManager taskManager, Logger logger) - { - ConsoleKey selection = ConsoleKey.EraseEndOfFile; - PrintMenu(taskManager, taskManager.settings.downloadLocation); - while (selection != ConsoleKey.Q) - { - int taskCount = taskManager.GetAllTasks().Length; - int taskRunningCount = taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Running); - int taskEnqueuedCount = - taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Enqueued); - Console.SetCursorPosition(0,1); - Console.WriteLine($"Tasks (Running/Queue/Total)): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}"); - - if (Console.KeyAvailable) - { - selection = Console.ReadKey().Key; - switch (selection) - { - case ConsoleKey.L: - while (!Console.KeyAvailable) - { - PrintTasks(taskManager.GetAllTasks(), logger); - Console.WriteLine("Press any key."); - Thread.Sleep(500); - } - Console.ReadKey(); - break; - case ConsoleKey.C: - CreateTask(taskManager); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - case ConsoleKey.D: - DeleteTask(taskManager); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - case ConsoleKey.E: - ExecuteTaskNow(taskManager); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - case ConsoleKey.S: - SearchTasks(taskManager); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - case ConsoleKey.R: - while (!Console.KeyAvailable) - { - PrintTasks( - taskManager.GetAllTasks().Where(eTask => eTask.state == TrangaTask.ExecutionState.Running) - .ToArray(), logger); - Console.WriteLine("Press any key."); - Thread.Sleep(500); - } - Console.ReadKey(); - break; - case ConsoleKey.K: - while (!Console.KeyAvailable) - { - PrintTasks( - taskManager.GetAllTasks() - .Where(qTask => qTask.state is TrangaTask.ExecutionState.Enqueued) - .ToArray(), logger); - Console.WriteLine("Press any key."); - Thread.Sleep(500); - } - Console.ReadKey(); - break; - case ConsoleKey.F: - TailLog(logger); - Console.ReadKey(); - break; - case ConsoleKey.G: - RemoveTaskFromQueue(taskManager, logger); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - case ConsoleKey.B: - AddTaskToQueue(taskManager, logger); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - case ConsoleKey.M: - AddMangaTaskToQueue(taskManager, logger); - Console.WriteLine("Press any key."); - Console.ReadKey(); - break; - } - PrintMenu(taskManager, taskManager.settings.downloadLocation); - } - Thread.Sleep(200); - } - - logger.WriteLine("Tranga_CLI", "Exiting."); - Console.Clear(); - Console.WriteLine("Exiting."); - if (taskManager.GetAllTasks().Any(task => task.state == TrangaTask.ExecutionState.Running)) - { - Console.WriteLine("Force quit (Even with running tasks?) y/N"); - selection = Console.ReadKey().Key; - while(selection != ConsoleKey.Y && selection != ConsoleKey.N) - selection = Console.ReadKey().Key; - taskManager.Shutdown(selection == ConsoleKey.Y); - }else - // ReSharper disable once RedundantArgumentDefaultValue Better readability - taskManager.Shutdown(false); - } - - private static void PrintMenu(TaskManager taskManager, string folderPath) - { - int taskCount = taskManager.GetAllTasks().Length; - int taskRunningCount = taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Running); - int taskEnqueuedCount = - taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Enqueued); - Console.Clear(); - Console.WriteLine($"Download Folder: {folderPath}"); - Console.WriteLine($"Tasks (Running/Queue/Total)): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}"); - Console.WriteLine(); - Console.WriteLine($"{"C: Create Task",-30}{"L: List tasks",-30}{"B: Enqueue Task", -30}"); - Console.WriteLine($"{"D: Delete Task",-30}{"S: Search Tasks", -30}{"K: List Task Queue", -30}"); - Console.WriteLine($"{"E: Execute Task now",-30}{"R: List Running Tasks", -30}{"G: Remove Task from Queue", -30}"); - Console.WriteLine($"{"M: New Download Manga Task",-30}{"", -30}{"", -30}"); - Console.WriteLine($"{"",-30}{"F: Show Log",-30}{"Q: Exit",-30}"); - } - - private static void PrintTasks(TrangaTask[] tasks, Logger? logger) - { - logger?.WriteLine("Tranga_CLI", "Printing Tasks"); - int taskCount = tasks.Length; - int taskRunningCount = tasks.Count(task => task.state == TrangaTask.ExecutionState.Running); - int taskEnqueuedCount = tasks.Count(task => task.state == TrangaTask.ExecutionState.Enqueued); - Console.Clear(); - int tIndex = 0; - Console.WriteLine($"Tasks (Running/Queue/Total): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}"); - string header = - $"{"",-5}{"Task",-20} | {"Last Executed",-20} | {"Reoccurrence",-12} | {"State",-10} | {"Progress",-9} | {"Finished",-20} | {"Remaining",-12} | {"Connector",-15} | Publication/Manga "; - Console.WriteLine(header); - Console.WriteLine(new string('-', header.Length)); - foreach (TrangaTask trangaTask in tasks) - { - string[] taskSplit = trangaTask.ToString().Split(", "); - Console.WriteLine($"{tIndex++:000}: {taskSplit[0],-20} | {taskSplit[1],-20} | {taskSplit[2],-12} | {taskSplit[3],-10} | {taskSplit[4],-9} | {taskSplit[5],-20} | {taskSplit[6][..12],-12} | {(taskSplit.Length > 7 ? taskSplit[7] : ""),-15} | {(taskSplit.Length > 8 ? taskSplit[8] : "")} {(taskSplit.Length > 9 ? taskSplit[9] : "")} {(taskSplit.Length > 10 ? taskSplit[10] : "")}"); - } - - } - - private static TrangaTask[] SelectTasks(TrangaTask[] tasks, Logger? logger) - { - logger?.WriteLine("Tranga_CLI", "Menu: Select task"); - if (tasks.Length < 1) - { - Console.Clear(); - Console.WriteLine("There are no available Tasks."); - logger?.WriteLine("Tranga_CLI", "No available Tasks."); - return Array.Empty(); - } - PrintTasks(tasks, logger); - - logger?.WriteLine("Tranga_CLI", "Selecting Task to Remove (from queue)"); - Console.WriteLine("Enter q to abort"); - Console.WriteLine($"Select Task(s) (0-{tasks.Length - 1}):"); - - string? selectedTask = Console.ReadLine(); - while(selectedTask is null || selectedTask.Length < 1) - selectedTask = Console.ReadLine(); - - if (selectedTask.Length == 1 && selectedTask.ToLower() == "q") - { - Console.Clear(); - Console.WriteLine("aborted."); - logger?.WriteLine("Tranga_CLI", "aborted"); - return Array.Empty(); - } - - if (selectedTask.Contains('-')) - { - int start = Convert.ToInt32(selectedTask.Split('-')[0]); - int end = Convert.ToInt32(selectedTask.Split('-')[1]); - return tasks[start..end]; - } - else - { - int selectedTaskIndex = Convert.ToInt32(selectedTask); - return new[] { tasks[selectedTaskIndex] }; - } - } - - private static void AddMangaTaskToQueue(TaskManager taskManager, Logger logger) - { - Console.Clear(); - logger.WriteLine("Tranga_CLI", "Menu: Add Manga Download to queue"); - - Connector? connector = SelectConnector(taskManager.GetAvailableConnectors().Values.ToArray(), logger); - if (connector is null) - return; - - Publication? publication = SelectPublication(taskManager, connector); - if (publication is null) - return; - - TimeSpan reoccurrence = SelectReoccurrence(logger); - logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager"); - TrangaTask nTask = new MonitorPublicationTask(connector.name, (Publication)publication, reoccurrence, "en"); - taskManager.AddTask(nTask); - Console.WriteLine(nTask); - } - - private static void AddTaskToQueue(TaskManager taskManager, Logger logger) - { - Console.Clear(); - logger.WriteLine("Tranga_CLI", "Menu: Add Task to queue"); - - TrangaTask[] tasks = taskManager.GetAllTasks().Where(rTask => - rTask.state is not TrangaTask.ExecutionState.Enqueued and not TrangaTask.ExecutionState.Running).ToArray(); - - TrangaTask[] selectedTasks = SelectTasks(tasks, logger); - logger.WriteLine("Tranga_CLI", $"Sending {selectedTasks.Length} Tasks to TaskManager"); - foreach(TrangaTask task in selectedTasks) - taskManager.AddTaskToQueue(task); - } - - private static void RemoveTaskFromQueue(TaskManager taskManager, Logger logger) - { - Console.Clear(); - logger.WriteLine("Tranga_CLI", "Menu: Remove Task from queue"); - - TrangaTask[] tasks = taskManager.GetAllTasks().Where(rTask => rTask.state is TrangaTask.ExecutionState.Enqueued).ToArray(); - - TrangaTask[] selectedTasks = SelectTasks(tasks, logger); - logger.WriteLine("Tranga_CLI", $"Sending {selectedTasks.Length} Tasks to TaskManager"); - foreach(TrangaTask task in selectedTasks) - taskManager.RemoveTaskFromQueue(task); - } - - private static void TailLog(Logger logger) - { - logger.WriteLine("Tranga_CLI", "Menu: Show Log-lines"); - Console.Clear(); - - string[] lines = logger.Tail(20); - foreach (string message in lines) - Console.Write(message); - - while (!Console.KeyAvailable) - { - string[] newLines = logger.GetNewLines(); - foreach(string message in newLines) - Console.Write(message); - Thread.Sleep(40); - } - } - - private static void CreateTask(TaskManager taskManager) - { - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "Menu: Creating Task"); - TrangaTask.Task? tmpTask = SelectTaskType(taskManager.commonObjects.logger); - if (tmpTask is null) - return; - TrangaTask.Task task = (TrangaTask.Task)tmpTask; - - Connector? connector = null; - if (task != TrangaTask.Task.UpdateLibraries) - { - connector = SelectConnector(taskManager.GetAvailableConnectors().Values.ToArray(), taskManager.commonObjects.logger); - if (connector is null) - return; - } - - Publication? publication = null; - if (task != TrangaTask.Task.UpdateLibraries) - { - publication = SelectPublication(taskManager, connector!); - if (publication is null) - return; - } - - if (task is TrangaTask.Task.MonitorPublication) - { - TimeSpan reoccurrence = SelectReoccurrence(taskManager.commonObjects.logger); - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "Sending Task to TaskManager"); - - TrangaTask newTask = new MonitorPublicationTask(connector!.name, (Publication)publication!, reoccurrence, "en"); - taskManager.AddTask(newTask); - Console.WriteLine(newTask); - }else if (task is TrangaTask.Task.DownloadChapter) - { - foreach (Chapter chapter in SelectChapters(connector!, (Publication)publication!, taskManager.commonObjects.logger)) - { - TrangaTask newTask = new DownloadChapterTask(connector!.name, (Publication)publication, chapter, "en"); - taskManager.AddTask(newTask); - Console.WriteLine(newTask); - } - } - } - - private static void ExecuteTaskNow(TaskManager taskManager) - { - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "Menu: Executing Task"); - TrangaTask[] tasks = taskManager.GetAllTasks().Where(nTask => nTask.state is not TrangaTask.ExecutionState.Running).ToArray(); - - TrangaTask[] selectedTasks = SelectTasks(tasks, taskManager.commonObjects.logger); - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", $"Sending {selectedTasks.Length} Tasks to TaskManager"); - foreach(TrangaTask task in selectedTasks) - taskManager.ExecuteTaskNow(task); - } - - private static void DeleteTask(TaskManager taskManager) - { - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "Menu: Delete Task"); - TrangaTask[] tasks = taskManager.GetAllTasks(); - - TrangaTask[] selectedTasks = SelectTasks(tasks, taskManager.commonObjects.logger); - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", $"Sending {selectedTasks.Length} Tasks to TaskManager"); - foreach(TrangaTask task in selectedTasks) - taskManager.DeleteTask(task); - } - - private static TrangaTask.Task? SelectTaskType(Logger? logger) - { - logger?.WriteLine("Tranga_CLI", "Menu: Select TaskType"); - Console.Clear(); - string[] taskNames = Enum.GetNames(); - - int tIndex = 0; - Console.WriteLine("Available Tasks:"); - foreach (string taskName in taskNames) - Console.WriteLine($"{tIndex++}: {taskName}"); - - Console.WriteLine("Enter q to abort"); - Console.WriteLine($"Select Task (0-{taskNames.Length - 1}):"); - - string? selectedTask = Console.ReadLine(); - while(selectedTask is null || selectedTask.Length < 1) - selectedTask = Console.ReadLine(); - - if (selectedTask.Length == 1 && selectedTask.ToLower() == "q") - { - Console.Clear(); - Console.WriteLine("aborted."); - logger?.WriteLine("Tranga_CLI", "aborted."); - return null; - } - - try - { - int selectedTaskIndex = Convert.ToInt32(selectedTask); - string selectedTaskName = taskNames[selectedTaskIndex]; - return Enum.Parse(selectedTaskName); - } - catch (Exception e) - { - Console.WriteLine($"Exception: {e.Message}"); - logger?.WriteLine("Tranga_CLI", e.Message); - } - - return null; - } - - private static TimeSpan SelectReoccurrence(Logger? logger) - { - logger?.WriteLine("Tranga_CLI", "Menu: Select Reoccurrence"); - Console.WriteLine("Select reoccurrence Timer (Format hh:mm:ss):"); - return TimeSpan.Parse(Console.ReadLine()!, new CultureInfo("en-US")); - } - - private static Chapter[] SelectChapters(Connector connector, Publication publication, Logger? logger) - { - logger?.WriteLine("Tranga_CLI", "Menu: Select Chapters"); - Chapter[] availableChapters = connector.GetChapters(publication, "en"); - int cIndex = 0; - Console.WriteLine("Chapters:"); - - System.Text.StringBuilder sb = new(); - foreach(Chapter chapter in availableChapters) - { - sb.Append($"{cIndex++}: "); - - if(string.IsNullOrWhiteSpace(chapter.volumeNumber) == false) - { - sb.Append($"Vol.{chapter.volumeNumber} "); - } - - if(string.IsNullOrWhiteSpace(chapter.chapterNumber) == false) - { - sb.Append($"Ch.{chapter.chapterNumber} "); - } - - if(string.IsNullOrWhiteSpace(chapter.name) == false) - { - sb.Append($" - {chapter.name}"); - } - - Console.WriteLine(sb.ToString()); - sb.Clear(); - } - - Console.WriteLine("Enter q to abort"); - Console.WriteLine($"Select Chapter(s):"); - - string? selectedChapters = Console.ReadLine(); - while(selectedChapters is null || selectedChapters.Length < 1) - selectedChapters = Console.ReadLine(); - - return connector.SelectChapters(publication, selectedChapters); - } - - private static Connector? SelectConnector(Connector[] connectors, Logger? logger) - { - logger?.WriteLine("Tranga_CLI", "Menu: Select Connector"); - Console.Clear(); - - int cIndex = 0; - Console.WriteLine("Connectors:"); - foreach (Connector connector in connectors) - Console.WriteLine($"{cIndex++}: {connector.name}"); - - Console.WriteLine("Enter q to abort"); - Console.WriteLine($"Select Connector (0-{connectors.Length - 1}):"); - - string? selectedConnector = Console.ReadLine(); - while(selectedConnector is null || selectedConnector.Length < 1) - selectedConnector = Console.ReadLine(); - - if (selectedConnector.Length == 1 && selectedConnector.ToLower() == "q") - { - Console.Clear(); - Console.WriteLine("aborted."); - logger?.WriteLine("Tranga_CLI", "aborted."); - return null; - } - - try - { - int selectedConnectorIndex = Convert.ToInt32(selectedConnector); - return connectors[selectedConnectorIndex]; - } - catch (Exception e) - { - Console.WriteLine($"Exception: {e.Message}"); - logger?.WriteLine("Tranga_CLI", e.Message); - } - - return null; - } - - private static Publication? SelectPublication(TaskManager taskManager, Connector connector) - { - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "Menu: Select Publication"); - - Console.Clear(); - Console.WriteLine($"Connector: {connector.name}"); - Console.WriteLine("Publication search query (leave empty for all):"); - string? query = Console.ReadLine(); - - Publication[] publications = connector.GetPublications(ref taskManager.collection, query ?? ""); - - if (publications.Length < 1) - { - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "No publications returned"); - Console.WriteLine($"No publications for query '{query}' returned;"); - return null; - } - - int pIndex = 0; - Console.WriteLine("Publications:"); - foreach(Publication publication in publications) - Console.WriteLine($"{pIndex++}: {publication.sortName}"); - - Console.WriteLine("Enter q to abort"); - Console.WriteLine($"Select publication to Download (0-{publications.Length - 1}):"); - - string? selectedPublication = Console.ReadLine(); - while(selectedPublication is null || selectedPublication.Length < 1) - selectedPublication = Console.ReadLine(); - - if (selectedPublication.Length == 1 && selectedPublication.ToLower() == "q") - { - Console.Clear(); - Console.WriteLine("aborted."); - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "aborted."); - return null; - } - - try - { - int selectedPublicationIndex = Convert.ToInt32(selectedPublication); - return publications[selectedPublicationIndex]; - } - catch (Exception e) - { - Console.WriteLine($"Exception: {e.Message}"); - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", e.Message); - } - - return null; - } - - private static void SearchTasks(TaskManager taskManager) - { - taskManager.commonObjects.logger?.WriteLine("Tranga_CLI", "Menu: Search task"); - Console.Clear(); - Console.WriteLine("Enter search query:"); - string? query = Console.ReadLine(); - while (query is null || query.Length < 4) - query = Console.ReadLine(); - PrintTasks(taskManager.GetAllTasks().Where(qTask => - qTask.ToString().ToLower().Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray(), taskManager.commonObjects.logger); - } -} \ No newline at end of file diff --git a/Tranga/TrangaSettings.cs b/Tranga/TrangaSettings.cs index a37de07..2eda41d 100644 --- a/Tranga/TrangaSettings.cs +++ b/Tranga/TrangaSettings.cs @@ -1,6 +1,7 @@ -using Newtonsoft.Json; -using Tranga.LibraryManagers; -using Tranga.NotificationManagers; +using Logging; +using Newtonsoft.Json; +using Tranga.LibraryConnectors; +using Tranga.NotificationConnectors; namespace Tranga; @@ -13,30 +14,38 @@ public class TrangaSettings [JsonIgnore] public string coverImageCache => Path.Join(workingDirectory, "imageCache"); public ushort? version { get; set; } - public TrangaSettings(string downloadLocation, string workingDirectory) + public TrangaSettings(string? downloadLocation = null, string? workingDirectory = null) { + downloadLocation ??= Path.Join(Directory.GetCurrentDirectory(), "Downloads"); + workingDirectory ??= Directory.GetCurrentDirectory(); if (downloadLocation.Length < 1 || workingDirectory.Length < 1) throw new ArgumentException("Download-location and working-directory paths can not be empty!"); this.workingDirectory = workingDirectory; this.downloadLocation = downloadLocation; } - public static TrangaSettings LoadSettings(string importFilePath) + public static TrangaSettings LoadSettings(string importFilePath, Logger? logger) { if (!File.Exists(importFilePath)) - return new TrangaSettings(Path.Join(Directory.GetCurrentDirectory(), "Downloads"), Directory.GetCurrentDirectory()); + return new TrangaSettings(); string toRead = File.ReadAllText(importFilePath); - SettingsJsonObject settings = JsonConvert.DeserializeObject(toRead, - new JsonSerializerSettings { Converters = { new NotificationManager.NotificationManagerJsonConverter(), new LibraryManager.LibraryManagerJsonConverter() } })!; - return settings.ts ?? new TrangaSettings(Path.Join(Directory.GetCurrentDirectory(), "Downloads"), Directory.GetCurrentDirectory()); + TrangaSettings? settings = JsonConvert.DeserializeObject(File.ReadAllText(importFilePath), + new JsonSerializerSettings + { + Converters = + { + new NotificationManagerJsonConverter(), + new LibraryManagerJsonConverter() + } + }); + return settings ?? new TrangaSettings(); } public void ExportSettings() { - SettingsJsonObject? settings = null; - if (File.Exists(settingsFilePath)) + while (File.Exists(settingsFilePath)) { bool inUse = true; while (inUse) @@ -49,49 +58,10 @@ public class TrangaSettings } catch (IOException) { - inUse = true; - Thread.Sleep(50); + Thread.Sleep(100); } } - string toRead = File.ReadAllText(settingsFilePath); - settings = JsonConvert.DeserializeObject(toRead, - new JsonSerializerSettings - { - Converters = - { - new NotificationManager.NotificationManagerJsonConverter(), - new LibraryManager.LibraryManagerJsonConverter() - } - }); - } - settings = new SettingsJsonObject(this, settings?.co); - File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(settings)); - } - - public void UpdateSettings(UpdateField field, params string[] values) - { - switch (field) - { - case UpdateField.DownloadLocation: - if (values.Length != 1) - return; - this.downloadLocation = values[0]; - break; - } - ExportSettings(); - } - - public enum UpdateField { DownloadLocation, Komga, Kavita, Gotify, LunaSea} - - internal class SettingsJsonObject - { - public TrangaSettings? ts { get; } - public CommonObjects? co { get; } - - public SettingsJsonObject(TrangaSettings? ts, CommonObjects? co) - { - this.ts = ts; - this.co = co; } + File.WriteAllText(settingsFilePath, JsonConvert.SerializeObject(this)); } } \ No newline at end of file diff --git a/Tranga/TrangaTasks/DownloadChapterTask.cs b/Tranga/TrangaTasks/DownloadChapterTask.cs deleted file mode 100644 index 8134be6..0000000 --- a/Tranga/TrangaTasks/DownloadChapterTask.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Net; -using Tranga.Connectors; -using Tranga.NotificationManagers; -using Tranga.LibraryManagers; - -namespace Tranga.TrangaTasks; - -public class DownloadChapterTask : TrangaTask -{ - public string connectorName { get; } - public Publication publication { get; } - // ReSharper disable once MemberCanBePrivate.Global - public string language { get; } - public Chapter chapter { get; } - - private double _dctProgress; - - public DownloadChapterTask(string connectorName, Publication publication, Chapter chapter, string language = "en", MonitorPublicationTask? parentTask = null) : base(Task.DownloadChapter, TimeSpan.Zero, parentTask) - { - this.chapter = chapter; - this.connectorName = connectorName; - this.publication = publication; - this.language = language; - } - - protected override HttpStatusCode ExecuteTask(TaskManager taskManager, CancellationToken? cancellationToken = null) - { - if (cancellationToken?.IsCancellationRequested ?? false) - return HttpStatusCode.RequestTimeout; - Connector connector = taskManager.GetConnector(this.connectorName); - connector.CopyCoverFromCacheToDownloadLocation(this.publication); - HttpStatusCode downloadSuccess = connector.DownloadChapter(this.publication, this.chapter, this, cancellationToken); - if ((int)downloadSuccess >= 200 && (int)downloadSuccess < 300) - { - foreach(NotificationManager nm in taskManager.commonObjects.notificationManagers) - nm.SendNotification("Chapter downloaded", $"{this.publication.sortName} {this.chapter.chapterNumber} {this.chapter.name}"); - - foreach (LibraryManager lm in taskManager.commonObjects.libraryManagers) - lm.UpdateLibrary(); - } - return downloadSuccess; - } - - public override TrangaTask Clone() - { - return new DownloadChapterTask(this.connectorName, this.publication, this.chapter, - this.language, (MonitorPublicationTask?)this.parentTask); - } - - protected override double GetProgress() - { - return _dctProgress; - } - - internal void IncrementProgress(double amount) - { - this._dctProgress += amount; - this.lastChange = DateTime.Now; - if(this.parentTask is not null) - this.parentTask.lastChange = DateTime.Now; - } - - public override string ToString() - { - return $"{base.ToString()}, {connectorName}, {publication.sortName} {publication.internalId}, Vol.{chapter.volumeNumber} Ch.{chapter.chapterNumber}"; - } -} \ No newline at end of file diff --git a/Tranga/TrangaTasks/MonitorPublicationTask.cs b/Tranga/TrangaTasks/MonitorPublicationTask.cs deleted file mode 100644 index 5319759..0000000 --- a/Tranga/TrangaTasks/MonitorPublicationTask.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Net; -using Tranga.Connectors; - -namespace Tranga.TrangaTasks; - -public class MonitorPublicationTask : TrangaTask -{ - public string connectorName { get; } - public Publication publication { get; } - // ReSharper disable once MemberCanBePrivate.Global - public string language { get; } - public MonitorPublicationTask(string connectorName, Publication publication, TimeSpan reoccurrence, string language = "en") : base(Task.MonitorPublication, reoccurrence) - { - this.connectorName = connectorName; - this.publication = publication; - this.language = language; - } - - protected override HttpStatusCode ExecuteTask(TaskManager taskManager, CancellationToken? cancellationToken = null) - { - if (cancellationToken?.IsCancellationRequested ?? false) - return HttpStatusCode.RequestTimeout; - Connector connector = taskManager.GetConnector(this.connectorName); - - //Check if Publication already has a Folder - publication.CreatePublicationFolder(taskManager.settings.downloadLocation); - List newChapters = connector.GetNewChaptersList(publication, language, ref taskManager.collection); - - connector.CopyCoverFromCacheToDownloadLocation(publication); - - publication.SaveSeriesInfoJson(taskManager.settings.downloadLocation); - - foreach (Chapter newChapter in newChapters) - { - DownloadChapterTask newTask = new (this.connectorName, publication, newChapter, this.language, this); - this.childTasks.Add(newTask); - newTask.state = ExecutionState.Enqueued; - taskManager.AddTask(newTask); - } - - return HttpStatusCode.OK; - } - - public override TrangaTask Clone() - { - return new MonitorPublicationTask(this.connectorName, this.publication, this.reoccurrence, - this.language); - } - - protected override double GetProgress() - { - if (this.childTasks.Count > 0) - return this.childTasks.Sum(ct => ct.progress) / childTasks.Count; - return 1; - } - - public override string ToString() - { - return $"{base.ToString()}, {connectorName}, {publication.sortName} {publication.internalId}"; - } -} \ No newline at end of file diff --git a/Tranga/TrangaTasks/TrangaTask.cs b/Tranga/TrangaTasks/TrangaTask.cs deleted file mode 100644 index 189884c..0000000 --- a/Tranga/TrangaTasks/TrangaTask.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Net; -using System.Text.Json.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using JsonConverter = Newtonsoft.Json.JsonConverter; - -namespace Tranga.TrangaTasks; - -/// -/// Stores information on Task, when implementing new Tasks also update the serializer -/// -[JsonDerivedType(typeof(MonitorPublicationTask), 2)] -[JsonDerivedType(typeof(UpdateLibrariesTask), 3)] -[JsonDerivedType(typeof(DownloadChapterTask), 4)] -public abstract class TrangaTask -{ - // ReSharper disable once MemberCanBeProtected.Global - public TimeSpan reoccurrence { get; } - public DateTime lastExecuted { get; set; } - [Newtonsoft.Json.JsonIgnore] public ExecutionState state { get; set; } - public Task task { get; } - public string taskId { get; init; } - [Newtonsoft.Json.JsonIgnore] public TrangaTask? parentTask { get; set; } - public string? parentTaskId { get; set; } - [Newtonsoft.Json.JsonIgnore] internal HashSet childTasks { get; } - public double progress => GetProgress(); - // ReSharper disable once MemberCanBePrivate.Global - [Newtonsoft.Json.JsonIgnore]public DateTime executionStarted { get; private set; } - [Newtonsoft.Json.JsonIgnore]public DateTime lastChange { get; internal set; } - // ReSharper disable once MemberCanBePrivate.Global - [Newtonsoft.Json.JsonIgnore]public DateTime executionApproximatelyFinished => lastChange.Add(GetRemainingTime()); - // ReSharper disable once MemberCanBePrivate.Global - public TimeSpan executionApproximatelyRemaining => executionApproximatelyFinished.Subtract(DateTime.Now); - [Newtonsoft.Json.JsonIgnore]public DateTime nextExecution => lastExecuted.Add(reoccurrence); - - public enum ExecutionState { Waiting, Enqueued, Running, Failed, Success } - - protected TrangaTask(Task task, TimeSpan reoccurrence, TrangaTask? parentTask = null) - { - this.reoccurrence = reoccurrence; - this.lastExecuted = DateTime.Now.Subtract(reoccurrence); - this.task = task; - this.executionStarted = DateTime.UnixEpoch; - this.lastChange = DateTime.MaxValue; - this.taskId = Convert.ToBase64String(BitConverter.GetBytes(new Random().Next())); - this.childTasks = new(); - this.parentTask = parentTask; - this.parentTaskId = parentTask?.taskId; - } - - /// - /// BL for concrete Tasks - /// - /// - /// - protected abstract HttpStatusCode ExecuteTask(TaskManager taskManager, CancellationToken? cancellationToken = null); - - public abstract TrangaTask Clone(); - - protected abstract double GetProgress(); - - /// - /// Execute the task - /// - /// Should be the parent taskManager - /// - public void Execute(TaskManager taskManager, CancellationToken? cancellationToken = null) - { - taskManager.commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Executing Task {this}"); - this.state = ExecutionState.Running; - this.executionStarted = DateTime.Now; - this.lastChange = DateTime.Now; - if(parentTask is not null && parentTask.childTasks.All(ct => ct.state is ExecutionState.Waiting or ExecutionState.Failed)) - parentTask.executionStarted = DateTime.Now; - - HttpStatusCode statusCode = ExecuteTask(taskManager, cancellationToken); - - if ((int)statusCode >= 200 && (int)statusCode < 300) - { - this.lastExecuted = DateTime.Now; - this.state = ExecutionState.Success; - } - else - { - this.state = ExecutionState.Failed; - this.lastExecuted = DateTime.MaxValue; - } - - if (this is DownloadChapterTask) - taskManager.DeleteTask(this); - - taskManager.commonObjects.logger?.WriteLine(this.GetType().ToString(), $"Finished Executing Task {this}"); - } - - public void AddChildTask(TrangaTask childTask) - { - this.childTasks.Add(childTask); - } - - public void RemoveChildTask(TrangaTask childTask) - { - this.childTasks.Remove(childTask); - } - - private TimeSpan GetRemainingTime() - { - if(progress == 0 || state is ExecutionState.Enqueued or ExecutionState.Waiting or ExecutionState.Failed || lastChange == DateTime.MaxValue) - return DateTime.MaxValue.Subtract(lastChange).Subtract(TimeSpan.FromHours(1)); - TimeSpan elapsed = lastChange.Subtract(executionStarted); - return elapsed.Divide(progress).Multiply(1 - progress); - } - - public enum Task : byte - { - MonitorPublication = 2, - UpdateLibraries = 3, - DownloadChapter = 4, - } - - public override string ToString() - { - return $"{task}, {lastExecuted}, {reoccurrence}, {state}, {progress:P2}, {executionApproximatelyFinished}, {executionApproximatelyRemaining}"; - } - - public class TrangaTaskJsonConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(TrangaTask); - } - - public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - JObject jo = JObject.Load(reader); - if (jo["task"]!.Value() == (Int64)Task.MonitorPublication) - return jo.ToObject(serializer)!; - - if (jo["task"]!.Value() == (Int64)Task.UpdateLibraries) - return jo.ToObject(serializer)!; - - if (jo["task"]!.Value() == (Int64)Task.DownloadChapter) - return jo.ToObject(serializer)!; - - 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/TrangaTasks/UpdateLibrariesTask.cs b/Tranga/TrangaTasks/UpdateLibrariesTask.cs deleted file mode 100644 index e7ecb02..0000000 --- a/Tranga/TrangaTasks/UpdateLibrariesTask.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Net; - -namespace Tranga.TrangaTasks; - -/// -/// LEGACY DEPRECATED -/// -public class UpdateLibrariesTask : TrangaTask -{ - public UpdateLibrariesTask(TimeSpan reoccurrence) : base(Task.UpdateLibraries, reoccurrence) - { - } - - protected override HttpStatusCode ExecuteTask(TaskManager taskManager, CancellationToken? cancellationToken = null) - { - return HttpStatusCode.BadRequest; - } - - public override TrangaTask Clone() - { - return new UpdateLibrariesTask(this.reoccurrence); - } - - protected override double GetProgress() - { - return 1; - } -} \ No newline at end of file diff --git a/Website/Dockerfile b/Website/Dockerfile deleted file mode 100644 index d178671..0000000 --- a/Website/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM nginx:alpine3.17-slim -COPY . /usr/share/nginx/html -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/Website/apiConnector.js b/Website/apiConnector.js deleted file mode 100644 index 560d3a5..0000000 --- a/Website/apiConnector.js +++ /dev/null @@ -1,168 +0,0 @@ -let apiUri = `http://${window.location.host.split(':')[0]}:6531` - -if(getCookie("apiUri") != ""){ - apiUri = getCookie("apiUri"); -} -function getCookie(cname) { - let name = cname + "="; - let decodedCookie = decodeURIComponent(document.cookie); - let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return ""; -} - -async function GetData(uri){ - let request = await fetch(uri, { - method: 'GET', - headers: { - 'Accept': 'application/json' - } - }); - let json = await request.json(); - return json; -} - -function PostData(uri){ - fetch(uri, { - method: 'POST' - }); -} - -function DeleteData(uri){ - fetch(uri, { - method: 'DELETE' - }); -} - -async function GetAvailableControllers(){ - var uri = apiUri + "/Connectors"; - let json = await GetData(uri); - return json; -} - -async function GetPublicationFromConnector(connectorName, title){ - var uri = apiUri + `/Publications/FromConnector?connectorName=${connectorName}&title=${title}`; - let json = await GetData(uri); - return json; -} - -async function GetKnownPublications(){ - var uri = apiUri + "/Publications/Known"; - let json = await GetData(uri); - return json; -} - -async function GetPublication(internalId){ - var uri = apiUri + `/Publications/Known?internalId=${internalId}`; - let json = await GetData(uri); - return json; -} - -async function GetChapters(internalId, connectorName, onlyNew, language){ - var uri = apiUri + `/Publications/Chapters?internalId=${internalId}&connectorName=${connectorName}&onlyNew=${onlyNew}&language=${language}`; - let json = await GetData(uri); - return json; -} - -async function GetTaskTypes(){ - var uri = apiUri + "/Tasks/Types"; - let json = await GetData(uri); - return json; -} -async function GetRunningTasks(){ - var uri = apiUri + "/Tasks/RunningTasks"; - let json = await GetData(uri); - return json; -} - -async function GetDownloadTasks(){ - var uri = apiUri + "/Tasks?taskType=MonitorPublication"; - let json = await GetData(uri); - return json; -} - -async function GetSettings(){ - var uri = apiUri + "/Settings"; - let json = await GetData(uri); - return json; -} - -async function GetKomgaTask(){ - var uri = apiUri + "/Tasks?taskType=UpdateLibraries"; - let json = await GetData(uri); - return json; -} - -function CreateMonitorTask(connectorName, internalId, reoccurrence, language){ - var uri = apiUri + `/Tasks/CreateMonitorTask?connectorName=${connectorName}&internalId=${internalId}&reoccurrenceTime=${reoccurrence}&language=${language}`; - PostData(uri); -} - -function CreateDownloadChaptersTask(connectorName, internalId, chapters, language){ - var uri = apiUri + `/Tasks/CreateDownloadChaptersTask?connectorName=${connectorName}&internalId=${internalId}&chapters=${chapters}&language=${language}`; - PostData(uri); -} - -function StartTask(taskType, connectorName, internalId){ - var uri = apiUri + `/Tasks/Start?taskType=${taskType}&connectorName=${connectorName}&internalId=${internalId}`; - PostData(uri); -} - -function EnqueueTask(taskType, connectorName, publicationId){ - var uri = apiUri + `/Queue/Enqueue?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; - PostData(uri); -} - -function UpdateDownloadLocation(downloadLocation){ - var uri = apiUri + "/Settings/Update?" - uri += "&downloadLocation="+downloadLocation; - PostData(uri); -} - -function UpdateKomga(komgaUrl, komgaAuth){ - var uri = apiUri + "/Settings/Update?" - uri += `&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`; - PostData(uri); -} - -function UpdateKavita(kavitaUrl, kavitaUser, kavitaPass){ - var uri = apiUri + "/Settings/Update?" - uri += `&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUser}&kavitaPassword=${kavitaPass}`; - PostData(uri); -} - -function UpdateGotify(gotifyUrl, gotifyAppToken){ - var uri = apiUri + "/Settings/Update?" - uri += `&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`; - PostData(uri); -} - -function UpdateLunaSea(lunaseaWebhook){ - var uri = apiUri + "/Settings/Update?" - uri += `&lunaseaWebhook=${lunaseaWebhook}`; - PostData(uri); -} - -function DeleteTask(taskType, connectorName, publicationId){ - var uri = apiUri + `/Tasks?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; - DeleteData(uri); -} - -function DequeueTask(taskType, connectorName, publicationId){ - var uri = apiUri + `/Queue/Dequeue?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; - DeleteData(uri); -} - -async function GetQueue(){ - var uri = apiUri + "/Queue/List"; - let json = await GetData(uri); - return json; -} \ No newline at end of file diff --git a/Website/favicon.ico b/Website/favicon.ico deleted file mode 100644 index ffb44eb..0000000 Binary files a/Website/favicon.ico and /dev/null differ diff --git a/Website/index.html b/Website/index.html deleted file mode 100644 index d6e9667..0000000 --- a/Website/index.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - Tranga - - - - - - - - website image is Blahaj - Tranga - - - - - - settingscog - - - -
-

+

-
- - cover - - MangaDex - Tensei Pandemic - - -
- - - - - Select Publication - -
- - -
-
- -
- -
-
-
-
- - - - - Create Task: Monitor Publication - -
- Run every - hours - minutes - -
-
-
-
- - - - - Create Task: Download Chapter(s) - -
- -
-
- -
-
-
-
- - - - - cover - - Tensei Pandemic - - Imamura Hinata - Imamura Hinata is a high school boy with a cute appearance. - Since his trauma with the first love, he wanted to be more manly than anybody else. But one day he woke up to something different… - The total opposite of his ideal male body! - Pandemic love comedy! - - - Start Task ▶️ - Delete Task ❌ - Monitor ➕ - Download Chapter ➕ - - - - - - - - - Settings - -
-

Download Location:

- -
-
-

API-URI

- -
-
- Komga -
Configured: ✅❌
- - - -
-
- Kavita -
Configured: ✅❌
- - - -
-
- Gotify -
Configured: ✅❌
- - -
-
- LunaSea -
Configured: ✅❌
- -
-
- - -
-
-
-
- - - - - Task Progress - - - - - -
-
-
- running
0
-
-
- queue
0
-
-

Made with Blåhaj 🦈

-
-
- - - - - \ No newline at end of file diff --git a/Website/interaction.js b/Website/interaction.js deleted file mode 100644 index e6695db..0000000 --- a/Website/interaction.js +++ /dev/null @@ -1,525 +0,0 @@ -let publications = []; -let tasks = []; -let toEditId; - -const searchBox = document.querySelector("#searchbox"); -const searchPublicationQuery = document.querySelector("#searchPublicationQuery"); -const selectPublication = document.querySelector("#taskSelectOutput"); -const connectorSelect = document.querySelector("#connectors"); -const settingsPopup = document.querySelector("#settingsPopup"); -const settingsCog = document.querySelector("#settingscog"); -const selectRecurrence = document.querySelector("#selectReccurrence"); -const tasksContent = document.querySelector("content"); -const selectPublicationPopup = document.querySelector("#selectPublicationPopup"); -const createMonitorTaskButton = document.querySelector("#createMonitorTaskButton"); -const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterTaskButton"); -const createMonitorTaskPopup = document.querySelector("#createMonitorTaskPopup"); -const createDownloadChaptersTask = document.querySelector("#createDownloadChaptersTask"); -const chapterOutput = document.querySelector("#chapterOutput"); -const selectedChapters = document.querySelector("#selectedChapters"); -const publicationViewerPopup = document.querySelector("#publicationViewerPopup"); -const publicationViewerWindow = document.querySelector("publication-viewer"); -const publicationViewerDescription = document.querySelector("#publicationViewerDescription"); -const publicationViewerName = document.querySelector("#publicationViewerName"); -const publicationViewerTags = document.querySelector("#publicationViewerTags"); -const publicationViewerAuthor = document.querySelector("#publicationViewerAuthor"); -const pubviewcover = document.querySelector("#pubviewcover"); -const publicationDelete = document.querySelector("publication-delete"); -const publicationTaskStart = document.querySelector("publication-starttask"); -const settingDownloadLocation = document.querySelector("#downloadLocation"); -const settingKomgaUrl = document.querySelector("#komgaUrl"); -const settingKomgaUser = document.querySelector("#komgaUsername"); -const settingKomgaPass = document.querySelector("#komgaPassword"); -const settingKavitaUrl = document.querySelector("#kavitaUrl"); -const settingKavitaUser = document.querySelector("#kavitaUsername"); -const settingKavitaPass = document.querySelector("#kavitaPassword"); -const settingGotifyUrl = document.querySelector("#gotifyUrl"); -const settingGotifyAppToken = document.querySelector("#gotifyAppToken"); -const settingLunaseaWebhook = document.querySelector("#lunaseaWebhook"); -const libraryUpdateTime = document.querySelector("#libraryUpdateTime"); -const settingKomgaConfigured = document.querySelector("#komgaConfigured"); -const settingKavitaConfigured = document.querySelector("#kavitaConfigured"); -const settingGotifyConfigured = document.querySelector("#gotifyConfigured"); -const settingLunaseaConfigured = document.querySelector("#lunaseaConfigured"); -const settingApiUri = document.querySelector("#settingApiUri"); -const tagTasksRunning = document.querySelector("#tasksRunningTag"); -const tagTasksQueued = document.querySelector("#tasksQueuedTag"); -const downloadTasksPopup = document.querySelector("#downloadTasksPopup"); -const downloadTasksOutput = downloadTasksPopup.querySelector("popup-content"); - -searchbox.addEventListener("keyup", (event) => FilterResults()); -settingsCog.addEventListener("click", () => OpenSettings()); -document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => settingsPopup.style.display = "none"); -document.querySelector("#blurBackgroundTaskPopup").addEventListener("click", () => selectPublicationPopup.style.display = "none"); -document.querySelector("#blurBackgroundPublicationPopup").addEventListener("click", () => HidePublicationPopup()); -document.querySelector("#blurBackgroundCreateMonitorTaskPopup").addEventListener("click", () => createMonitorTaskPopup.style.display = "none"); -document.querySelector("#blurBackgroundCreateDownloadChaptersTask").addEventListener("click", () => createDownloadChaptersTask.style.display = "none"); -document.querySelector("#blurBackgroundTasksQueuePopup").addEventListener("click", () => downloadTasksPopup.style.display = "none"); -selectedChapters.addEventListener("keypress", (event) => { - if(event.key === "Enter"){ - DownloadChapterTaskClick(); - } -}) -publicationDelete.addEventListener("click", () => DeleteTaskClick()); -createMonitorTaskButton.addEventListener("click", () => { - HidePublicationPopup(); - createMonitorTaskPopup.style.display = "block"; -}); -createDownloadChapterTaskButton.addEventListener("click", () => { - HidePublicationPopup(); - OpenDownloadChapterTaskPopup(); -}); -publicationTaskStart.addEventListener("click", () => StartTaskClick()); -searchPublicationQuery.addEventListener("keypress", (event) => { - if(event.key === "Enter"){ - NewSearch(); - } -}); - - -let availableConnectors; -GetAvailableControllers() - .then(json => availableConnectors = json) - .then(json => - json.forEach(connector => { - var option = document.createElement('option'); - option.value = connector; - option.innerText = connector; - connectorSelect.appendChild(option); - }) - ); - - -function NewSearch(){ - //Disable inputs - connectorSelect.disabled = true; - searchPublicationQuery.disabled = true; - //Waitcursor - document.body.style.cursor = "wait"; - - //Empty previous results - selectPublication.replaceChildren(); - GetPublicationFromConnector(connectorSelect.value, searchPublicationQuery.value) - .then(json => - json.forEach(publication => { - var option = CreatePublication(publication, connectorSelect.value); - option.addEventListener("click", (mouseEvent) => { - ShowPublicationViewerWindow(publication.internalId, mouseEvent, true); - }); - selectPublication.appendChild(option); - } - )) - .then(() => { - //Re-enable inputs - connectorSelect.disabled = false; - searchPublicationQuery.disabled = false; - //Cursor - document.body.style.cursor = "initial"; - }); -} - -//Returns a new "Publication" Item to display in the tasks section -function CreatePublication(publication, connector){ - var publicationElement = document.createElement('publication'); - publicationElement.setAttribute("id", publication.internalId); - var img = document.createElement('img'); - img.src = `imageCache/${publication.coverFileNameInCache}`; - publicationElement.appendChild(img); - var info = document.createElement('publication-information'); - var connectorName = document.createElement('connector-name'); - connectorName.innerText = connector; - connectorName.className = "pill"; - info.appendChild(connectorName); - var publicationName = document.createElement('publication-name'); - publicationName.innerText = publication.sortName; - info.appendChild(publicationName); - publicationElement.appendChild(info); - if(publications.filter(pub => pub.internalId === publication.internalId) < 1) - publications.push(publication); - return publicationElement; -} - -function AddMonitorTask(){ - var hours = document.querySelector("#hours").value; - var minutes = document.querySelector("#minutes").value; - CreateMonitorTask(connectorSelect.value, toEditId, `${hours}:${minutes}:00`, "en"); - HidePublicationPopup(); - createMonitorTaskPopup.style.display = "none"; - selectPublicationPopup.style.display = "none"; -} - -function OpenDownloadChapterTaskPopup(){ - selectedChapters.value = ""; - chapterOutput.replaceChildren(); - createDownloadChaptersTask.style.display = "block"; - GetChapters(toEditId, connectorSelect.value, true, "en").then((json) => { - var i = 0; - json.forEach(chapter => { - var chapterDom = document.createElement("div"); - var indexDom = document.createElement("span"); - indexDom.className = "index"; - indexDom.innerText = i++; - chapterDom.appendChild(indexDom); - - var volDom = document.createElement("span"); - volDom.className = "vol"; - volDom.innerText = chapter.volumeNumber; - chapterDom.appendChild(volDom); - - var chDom = document.createElement("span"); - chDom.className = "ch"; - chDom.innerText = chapter.chapterNumber; - chapterDom.appendChild(chDom); - - var titleDom = document.createElement("span"); - titleDom.innerText = chapter.name; - chapterDom.appendChild(titleDom); - chapterOutput.appendChild(chapterDom); - }); - }); -} - -function DownloadChapterTaskClick(){ - CreateDownloadChaptersTask(connectorSelect.value, toEditId, selectedChapters.value, "en"); - HidePublicationPopup(); - createDownloadChaptersTask.style.display = "none"; - selectPublicationPopup.style.display = "none"; -} - -function DeleteTaskClick(){ - taskToDelete = tasks.filter(tTask => tTask.publication.internalId === toEditId)[0]; - DeleteTask("MonitorPublication", taskToDelete.connectorName, toEditId); - HidePublicationPopup(); -} - -function StartTaskClick(){ - var toEditTask = tasks.filter(task => task.publication.internalId == toEditId)[0]; - StartTask("MonitorPublication", toEditTask.connectorName, toEditId); - HidePublicationPopup(); -} - -function ResetContent(){ - //Delete everything - tasksContent.replaceChildren(); - - //Add "Add new Task" Button - var add = document.createElement("div"); - add.setAttribute("id", "addPublication") - var plus = document.createElement("p"); - plus.innerText = "+"; - add.appendChild(plus); - add.addEventListener("click", () => ShowNewTaskWindow()); - tasksContent.appendChild(add); -} -function ShowPublicationViewerWindow(publicationId, event, add){ - //Show popup - publicationViewerPopup.style.display = "block"; - - //Set position to mouse-position - if(event.clientY < window.innerHeight - publicationViewerWindow.offsetHeight) - publicationViewerWindow.style.top = `${event.clientY}px`; - else - publicationViewerWindow.style.top = `${event.clientY - publicationViewerWindow.offsetHeight}px`; - - if(event.clientX < window.innerWidth - publicationViewerWindow.offsetWidth) - publicationViewerWindow.style.left = `${event.clientX}px`; - else - publicationViewerWindow.style.left = `${event.clientX - publicationViewerWindow.offsetWidth}px`; - - //Edit information inside the window - var publication = publications.filter(pub => pub.internalId === publicationId)[0]; - publicationViewerName.innerText = publication.sortName; - publicationViewerTags.innerText = publication.tags.join(", "); - publicationViewerDescription.innerText = publication.description; - publicationViewerAuthor.innerText = publication.authors.join(','); - pubviewcover.src = `imageCache/${publication.coverFileNameInCache}`; - toEditId = publicationId; - - //Check what action should be listed - if(add){ - createMonitorTaskButton.style.display = "initial"; - createDownloadChapterTaskButton.style.display = "initial"; - publicationDelete.style.display = "none"; - publicationTaskStart.style.display = "none"; - } - else{ - createMonitorTaskButton.style.display = "none"; - createDownloadChapterTaskButton.style.display = "none"; - publicationDelete.style.display = "initial"; - publicationTaskStart.style.display = "initial"; - } -} - -function HidePublicationPopup(){ - publicationViewerPopup.style.display = "none"; -} - -function ShowNewTaskWindow(){ - selectPublication.replaceChildren(); - searchPublicationQuery.value = ""; - selectPublicationPopup.style.display = "flex"; -} - - -const fadeIn = [ - { opacity: "0" }, - { opacity: "1" } -]; - -const fadeInTiming = { - duration: 50, - iterations: 1, - fill: "forwards" -} - -function OpenSettings(){ - GetSettingsClick(); - settingsPopup.style.display = "flex"; -} - -function GetSettingsClick(){ - settingApiUri.value = ""; - settingKomgaUrl.value = ""; - settingKomgaUser.value = ""; - settingKomgaPass.value = ""; - settingKomgaConfigured.innerText = "❌"; - settingKavitaUrl.value = ""; - settingKavitaUser.value = ""; - settingKavitaPass.value = ""; - settingKavitaConfigured.innerText = "❌"; - settingGotifyUrl.value = ""; - settingGotifyAppToken.value = ""; - settingGotifyConfigured.innerText = "❌"; - settingLunaseaWebhook.value = ""; - settingLunaseaConfigured.innerText = "❌"; - - settingApiUri.placeholder = apiUri; - - GetSettings().then(json => { - settingDownloadLocation.innerText = json.downloadLocation; - json.libraryManagers.forEach(lm => { - if(lm.libraryType == 0){ - settingKomgaUrl.placeholder = lm.baseUrl; - settingKomgaUser.placeholder = "User"; - settingKomgaPass.placeholder = "***"; - settingKomgaConfigured.innerText = "✅"; - } else if(lm.libraryType == 1){ - settingKavitaUrl.placeholder = lm.baseUrl; - settingKavitaUser.placeholder = "User"; - settingKavitaPass.placeholder = "***"; - settingKavitaConfigured.innerText = "✅"; - } - }); - json.notificationManagers.forEach(nm => { - if(nm.notificationManagerType == 0){ - settingGotifyConfigured.innerText = "✅"; - } else if(nm.notificationManagerType == 1){ - settingLunaseaConfigured.innerText = "✅"; - } - }); - }); - - GetKomgaTask().then(json => { - if(json.length > 0) - libraryUpdateTime.value = json[0].reoccurrence; - }); -} - -function UpdateLibrarySettings(){ - if(settingKomgaUrl.value != "" && settingKomgaUser.value != "" && settingKomgaPass.value != ""){ - var auth = utf8_to_b64(`${settingKomgaUser.value}:${settingKomgaPass.value}`); - console.log(auth); - UpdateKomga(settingKomgaUrl.value, auth); - } - - if(settingKavitaUrl.value != "" && settingKavitaUser.value != "" && settingKavitaPass.value != ""){ - UpdateKavita(settingKavitaUrl.value, settingKavitaUser.value, settingKavitaPass.value); - } - - if(settingGotifyUrl.value != "" && settingGotifyAppToken.value != ""){ - UpdateGotify(settingGotifyUrl.value, settingGotifyAppToken.value); - } - - if(settingLunaseaWebhook.value != ""){ - UpdateLunaSea(settingLunaseaWebhook.value); - } - - if(settingApiUri.value != ""){ - apiUri = settingApiUri.value; - document.cookie = `apiUri=${apiUri};`; - } - - setTimeout(() => GetSettingsClick(), 200); -} - -function utf8_to_b64( str ) { - return window.btoa(unescape(encodeURIComponent( str ))); -} - -function FilterResults(){ - if(searchBox.value.length > 0){ - tasksContent.childNodes.forEach(publication => { - publication.childNodes.forEach(item => { - if(item.nodeName.toLowerCase() == "publication-information"){ - item.childNodes.forEach(information => { - if(information.nodeName.toLowerCase() == "publication-name"){ - if(!information.textContent.toLowerCase().includes(searchBox.value.toLowerCase())){ - publication.style.display = "none"; - }else{ - publication.style.display = "initial"; - } - } - }); - } - }); - }); - }else{ - tasksContent.childNodes.forEach(publication => publication.style.display = "initial"); - } -} - -function ShowTasksQueue(){ - - downloadTasksOutput.replaceChildren(); - GetRunningTasks() - .then(json => { - tagTasksRunning.innerText = json.length; - json.forEach(task => { - if(task.task == 2 || task.task == 4) { - downloadTasksOutput.appendChild(CreateProgressChild(task)); - document.querySelector(`#progress${GetValidSelector(task.taskId)}`).value = task.progress; - var finishedHours = task.executionApproximatelyRemaining.split(':')[0]; - var finishedMinutes = task.executionApproximatelyRemaining.split(':')[1]; - var finishedSeconds = task.executionApproximatelyRemaining.split(':')[2].split('.')[0]; - document.querySelector(`#progressStr${GetValidSelector(task.taskId)}`).innerText = `${finishedHours}:${finishedMinutes}:${finishedSeconds}`; - } - }); - }); - - GetQueue() - .then(json => { - tagTasksQueued.innerText = json.length; - json.forEach(task => { - downloadTasksOutput.appendChild(CreateProgressChild(task)); - }); - }); - downloadTasksPopup.style.display = "flex"; -} - -function CreateProgressChild(task){ - var child = document.createElement("div"); - var img = document.createElement('img'); - img.src = `imageCache/${task.publication.coverFileNameInCache}`; - child.appendChild(img); - - var name = document.createElement("span"); - name.innerText = task.publication.sortName; - name.className = "pubTitle"; - child.appendChild(name); - - - var progress = document.createElement("progress"); - progress.id = `progress${GetValidSelector(task.taskId)}`; - child.appendChild(progress); - - var progressStr = document.createElement("span"); - progressStr.innerText = " \t∞"; - progressStr.className = "progressStr"; - progressStr.id = `progressStr${GetValidSelector(task.taskId)}`; - child.appendChild(progressStr); - - if(task.chapter != undefined){ - var chapterNumber = document.createElement("span"); - chapterNumber.className = "chapterNumber"; - chapterNumber.innerText = `Vol.${task.chapter.volumeNumber} Ch.${task.chapter.chapterNumber}`; - child.appendChild(chapterNumber); - - var chapterName = document.createElement("span"); - chapterName.className = "chapterName"; - chapterName.innerText = task.chapter.name; - child.appendChild(chapterName); - } - - - return child; -} - -//Resets the tasks shown -ResetContent(); -downloadTasksOutput.replaceChildren(); -//Get Tasks and show them -GetDownloadTasks() - .then(json => json.forEach(task => { - var publication = CreatePublication(task.publication, task.connectorName); - publication.addEventListener("click", (event) => ShowPublicationViewerWindow(task.publication.internalId, event, false)); - tasksContent.appendChild(publication); - tasks.push(task); - })); - -GetRunningTasks() - .then(json => { - tagTasksRunning.innerText = json.length; - json.forEach(task => { - downloadTasksOutput.appendChild(CreateProgressChild(task)); - }); - }); - -GetQueue() - .then(json => { - tagTasksQueued.innerText = json.length; - json.forEach(task => { - downloadTasksOutput.appendChild(CreateProgressChild(task)); - }); - }) - -setInterval(() => { - //Tasks from API - var cTasks = []; - GetDownloadTasks() - .then(json => json.forEach(task => cTasks.push(task))) - .then(() => { - //Only update view if tasks-amount has changed - if(tasks.length != cTasks.length) { - //Resets the tasks shown - ResetContent(); - //Add all currenttasks to view - cTasks.forEach(task => { - var publication = CreatePublication(task.publication, task.connectorName); - publication.addEventListener("click", (event) => ShowPublicationViewerWindow(task.publication.internalId, event, false)); - tasksContent.appendChild(publication); - }) - - tasks = cTasks; - } - } - ); - - GetRunningTasks() - .then(json => { - tagTasksRunning.innerText = json.length; - }); - - GetQueue() - .then(json => { - tagTasksQueued.innerText = json.length; - }); -}, 1000); - -setInterval(() => { - GetRunningTasks().then((json) => { - json.forEach(task => { - if(task.task == 2 || task.task == 4){ - document.querySelector(`#progress${GetValidSelector(task.taskId)}`).value = task.progress; - var finishedHours = task.executionApproximatelyRemaining.split(':')[0]; - var finishedMinutes = task.executionApproximatelyRemaining.split(':')[1]; - var finishedSeconds = task.executionApproximatelyRemaining.split(':')[2].split('.')[0]; - document.querySelector(`#progressStr${GetValidSelector(task.taskId)}`).innerText = `${finishedHours}:${finishedMinutes}:${finishedSeconds}`; - } - }); - }); -},500); - -function GetValidSelector(str){ - var clean = [...str.matchAll(/[a-zA-Z0-9]*-*_*/g)]; - return clean.join(''); -} \ No newline at end of file diff --git a/Website/media/blahaj.png b/Website/media/blahaj.png deleted file mode 100644 index dbbff93..0000000 Binary files a/Website/media/blahaj.png and /dev/null differ diff --git a/Website/media/close-x.svg b/Website/media/close-x.svg deleted file mode 100644 index fc8cc4d..0000000 --- a/Website/media/close-x.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Website/media/queue.svg b/Website/media/queue.svg deleted file mode 100644 index 30b9620..0000000 --- a/Website/media/queue.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/Website/media/running.svg b/Website/media/running.svg deleted file mode 100644 index ddde0a6..0000000 --- a/Website/media/running.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Website/media/settings-cogwheel.svg b/Website/media/settings-cogwheel.svg deleted file mode 100644 index 7e61388..0000000 --- a/Website/media/settings-cogwheel.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Website/media/tasks.svg b/Website/media/tasks.svg deleted file mode 100644 index 6e64e66..0000000 --- a/Website/media/tasks.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Website/style.css b/Website/style.css deleted file mode 100644 index f7114ed..0000000 --- a/Website/style.css +++ /dev/null @@ -1,603 +0,0 @@ -:root{ - --background-color: #030304; - --second-background-color: #fff; - --primary-color: #f5a9b8; - --secondary-color: #5bcefa; - --accent-color: #fff; - --topbar-height: 60px; - box-sizing: border-box; -} - -body{ - padding: 0; - margin: 0; - height: 100vh; - background-color: var(--background-color); - font-family: "Inter", sans-serif; - overflow-x: hidden; -} - -wrapper { - display: flex; - flex-flow: column; - flex-wrap: nowrap; - height: 100vh; -} - -background-placeholder{ - background-color: var(--second-background-color); - opacity: 1; - position: absolute; - width: 100%; - height: 100%; - border-radius: 0 0 5px 0; - z-index: -1; -} - -topbar { - display: flex; - align-items: center; - height: var(--topbar-height); - background-color: var(--secondary-color); - z-index: 100; - box-shadow: 0 0 20px black; -} - -titlebox { - position: relative; - display: flex; - margin: 0 0 0 40px; - height: 100%; - align-items:center; - justify-content:center; -} - -titlebox span{ - cursor: default; - font-size: 24pt; - font-weight: bold; - background: linear-gradient(150deg, var(--primary-color), var(--accent-color)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - margin-left: 20px; -} - -titlebox img { - height: 100%; - margin-right: 10px; - cursor: grab; -} - -spacer{ - flex-grow: 1; -} - -searchdiv{ - display: block; - margin: 0 10px 0 0; -} - -#searchbox { - padding: 3px 10px; - border: 0; - border-radius: 4px; - font-size: 14pt; - width: 250px; -} - -#settingscog { - cursor: pointer; - margin: 0px 30px; - height: 50%; - filter: invert(100%) sepia(0%) saturate(7465%) hue-rotate(115deg) brightness(116%) contrast(101%); -} - -viewport { - position: relative; - display: flex; - flex-flow: row; - flex-wrap: nowrap; - flex-grow: 1; - height: 100%; - overflow-y: scroll; -} - -footer { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - width: 100%; - height: 40px; - align-items: center; - justify-content: center; - background-color: var(--primary-color); - align-content: center; -} - -footer > div { - height: 100%; - margin: 0 30px; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - cursor: pointer; -} - -footer > div > *{ - height: 40%; - margin: 0 5px; -} - -#madeWith { - flex-grow: 1; - text-align: right; - margin-right: 20px; - cursor: url("media/blahaj.png"), grab; -} - -content { - position: relative; - flex-grow: 1; - border-radius: 5px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: start; - align-content: start; -} - -#settingsPopup{ - z-index: 10; -} - -#settingsPopup popup-content{ - flex-direction: column; - align-items: start; - margin: 15px 10px; -} - -#settingsPopup popup-content > * { - margin: 5px 10px; -} - -#settingsPopup popup-content .title { - font-weight: bolder; -} - -#addPublication { - cursor: pointer; - background-color: var(--secondary-color); - width: 180px; - height: 300px; - border-radius: 5px; - margin: 10px 10px; - padding: 15px 20px; - position: relative; -} - -#addPublication p{ - width: 100%; - text-align: center; - font-size: 150pt; - vertical-align: middle; - line-height: 300px; - margin: 0; - color: var(--accent-color); -} - -.pill { - flex-grow: 0; - height: 14pt; - font-size: 12pt; - border-radius: 9pt; - background-color: var(--primary-color); - padding: 2pt 17px; - color: black; -} - -publication{ - cursor: pointer; - background-color: var(--secondary-color); - width: 180px; - height: 300px; - border-radius: 5px; - margin: 10px 10px; - padding: 15px 20px; - position: relative; -} - -publication::after{ - content: ''; - position: absolute; - left: 0; top: 0; - border-radius: 5px; - width: 100%; height: 100%; - background: linear-gradient(rgba(0,0,0,0.8), rgba(0, 0, 0, 0.7),rgba(0, 0, 0, 0.2)); -} - -publication-information { - display: flex; - flex-direction: column; - justify-content: start; -} - -publication-information * { - z-index: 1; - color: var(--accent-color); -} - -connector-name{ - width: fit-content; - margin: 10px 0; -} - -publication-name{ - width: fit-content; - font-size: 16pt; - font-weight: bold; -} - -publication img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 0; - border-radius: 5px; -} - -popup{ - display: none; - width: 100%; - min-height: 100%; - top: 0; - left: 0; - position: fixed; - z-index: 2; - flex-direction: column; -} - - -popup popup-window { - position: absolute; - z-index: 3; - left: 25%; - top: 100px; - width: 50%; - display: flex; - flex-direction: column; - background-color: var(--second-background-color); - border-radius: 3px; - overflow: hidden; -} - -popup popup-window popup-title { - height: 30px; - font-size: 14pt; - font-weight: bolder; - padding: 5px 10px; - margin: 0; - display: flex; - align-items: center; - background-color: var(--primary-color); - color: var(--accent-color) -} - -popup popup-window popup-content{ - margin: 15px 10px; - display: flex; - align-items: center; - justify-content: space-evenly; -} - -popup popup-window popup-content div > * { - margin: 2px 3px 0 0; -} - -popup popup-window popup-content input, select { - padding: 3px 4px; - width: 130px; - border: 1px solid lightgrey; - background-color: var(--accent-color); - border-radius: 3px; -} - -#selectPublicationPopup publication { - width: 150px; - height: 250px; -} - -#createTaskPopup { - z-index: 7; -} - -#createTaskPopup input { - height: 30px; - width: 200px; -} - -#createMonitorTaskPopup, #createDownloadChaptersTask { - z-index: 9; -} - -#createMonitorTaskPopup input[type="number"] { - width: 40px; -} - -#createDownloadChaptersTask popup-content { - flex-direction: column; - align-items: start; -} - -#createDownloadChaptersTask popup-content > * { - margin: 3px 0; -} - -#createDownloadChaptersTask #chapterOutput { - max-height: 50vh; - overflow-y: scroll; -} - -#createDownloadChaptersTask #chapterOutput .index{ - display: inline-block; - width: 25px; -} - -#createDownloadChaptersTask #chapterOutput .index::after{ - content: ':'; -} - -#createDownloadChaptersTask #chapterOutput .vol::before{ - content: 'Vol.'; -} - -#createDownloadChaptersTask #chapterOutput .vol{ - display: inline-block; - width: 45px; -} - -#createDownloadChaptersTask #chapterOutput .ch::before{ - content: 'Ch.'; -} - -#createDownloadChaptersTask #chapterOutput .ch { - display: inline-block; - width: 60px; -} - -#downloadTasksPopup popup-window { - left: 0; - top: 80px; - margin: 0 0 0 10px; - height: calc(100vh - 140px); - width: 400px; - max-width: 95vw; - overflow-y: scroll; -} - -#downloadTasksPopup popup-content { - flex-direction: column; - align-items: start; - margin: 5px; -} - -#downloadTasksPopup popup-content > div { - display: block; - height: 80px; - position: relative; - margin: 5px 0; -} - -#downloadTasksPopup popup-content > div > img { - display: block; - position: absolute; - height: 100%; - width: 60px; - left: 0; - top: 0; - object-fit: cover; - border-radius: 4px; -} - -#downloadTasksPopup popup-content > div > span { - display: block; - position: absolute; - width: max-content; -} - -#downloadTasksPopup popup-content > div > .pubTitle { - left: 70px; - top: 0; -} - -#downloadTasksPopup popup-content > div > .chapterName { - left: 70px; - top: 28pt; -} - -#downloadTasksPopup popup-content > div > .chapterNumber { - left: 70px; - top: 14pt; -} - -#downloadTasksPopup popup-content > div > progress { - display: block; - position: absolute; - left: 150px; - bottom: 0; - width: 200px; -} - -#downloadTasksPopup popup-content > div > .progressStr { - display: block; - position: absolute; - left: 70px; - bottom: 0; - width: 70px; -} - -blur-background { - width: 100%; - height: 100%; - position: absolute; - left: 0; - background-color: black; - opacity: 0.5; -} - -#taskSelectOutput{ - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: start; - align-content: start; - max-height: 70vh; - overflow-y: scroll; -} - -#publicationViewerPopup{ - z-index: 5; -} - -publication-viewer{ - display: block; - width: 450px; - position: absolute; - top: 200px; - left: 400px; - background-color: var(--accent-color); - border-radius: 5px; - overflow: hidden; - padding: 15px; -} - -publication-viewer::after{ - content: ''; - position: absolute; - left: 0; top: 0; - border-radius: 5px; - width: 100%; - height: 100%; - background: rgba(0,0,0,0.8); - backdrop-filter: blur(3px); -} - -publication-viewer img { - position: absolute; - left: 0; - top: 0; - height: 100%; - width: 100%; - object-fit: cover; - border-radius: 5px; - z-index: 0; -} - -publication-viewer publication-information > * { - margin: 5px 0; -} - -publication-viewer publication-information publication-name { - width: initial; - overflow-x: scroll; - white-space: nowrap; - scrollbar-width: none; -} - -publication-viewer publication-information publication-tags::before { - content: "Tags"; - display: block; - font-weight: bolder; -} - -publication-viewer publication-information publication-tags { - overflow-x: scroll; - white-space: nowrap; - scrollbar-width: none; -} - -publication-viewer publication-information publication-author::before { - content: "Author: "; - font-weight: bolder; -} - -publication-viewer publication-information publication-description::before { - content: "Description"; - display: block; - font-weight: bolder; -} - -publication-viewer publication-information publication-description { - font-size: 12pt; - margin: 5px 0; - height: 145px; - overflow-x: scroll; -} - -publication-viewer publication-information publication-interactions { - display: flex; - flex-direction: row; - justify-content: end; - align-items: start; - width: 100%; -} - -publication-viewer publication-information publication-interactions > * { - margin: 0 10px; - font-size: 16pt; - cursor: pointer; -} - -publication-viewer publication-information publication-interactions publication-starttask { - color: var(--secondary-color); -} - -publication-viewer publication-information publication-interactions publication-delete { - color: red; -} - -publication-viewer publication-information publication-interactions publication-add { - color: limegreen; -} - -footer-tag-popup { - display: none; - padding: 2px 4px; - position: fixed; - bottom: 58px; - left: 20px; - background-color: var(--second-background-color); - z-index: 8; - border-radius: 5px; - max-height: 400px; -} - -footer-tag-content{ - position: relative; - max-height: 400px; - display: flex; - flex-direction: column; - flex-wrap: nowrap; - overflow-y: scroll; -} - -footer-tag-content > * { - margin: 2px 5px; -} - -footer-tag-popup::before{ - content: ""; - width: 0; - height: 0; - position: absolute; - border-right: 10px solid var(--second-background-color); - border-left: 10px solid transparent; - border-top: 10px solid var(--second-background-color); - border-bottom: 10px solid transparent; - left: 0; - bottom: -17px; - border-radius: 0 0 0 5px; -} \ No newline at end of file diff --git a/screenshots/addtask.png b/screenshots/addtask.png deleted file mode 100644 index ebaf431..0000000 Binary files a/screenshots/addtask.png and /dev/null differ diff --git a/screenshots/overview.png b/screenshots/overview.png deleted file mode 100644 index 012b0b8..0000000 Binary files a/screenshots/overview.png and /dev/null differ diff --git a/screenshots/progress.png b/screenshots/progress.png deleted file mode 100644 index 728aec7..0000000 Binary files a/screenshots/progress.png and /dev/null differ diff --git a/screenshots/publication-description.png b/screenshots/publication-description.png deleted file mode 100644 index d852b00..0000000 Binary files a/screenshots/publication-description.png and /dev/null differ diff --git a/screenshots/settings.png b/screenshots/settings.png deleted file mode 100644 index 14039e0..0000000 Binary files a/screenshots/settings.png and /dev/null differ