From cdd2d94ba1355fb67a6c98d86416765435138cdb Mon Sep 17 00:00:00 2001 From: glax Date: Tue, 20 Jun 2023 23:15:56 +0200 Subject: [PATCH] Wrote my own Http-Server. ASP-NET can **** my **** and *** :) --- .../Tranga-API.csproj => API/API.csproj | 13 +- API/Dockerfile | 13 + API/Program.cs | 46 +++ API/RequestHandler.cs | 361 ++++++++++++++++++ API/Server.cs | 72 ++++ Dockerfile-base | 8 +- Tranga-API/Program.cs | 268 ------------- Tranga-API/Properties/launchSettings.json | 28 -- Tranga-API/appsettings.Development.json | 8 - Tranga-API/appsettings.json | 9 - Tranga-CLI/Tranga-CLI.csproj | 6 - Tranga.sln | 10 +- Tranga/TrangaTask.cs | 8 +- Website/apiConnector.js | 24 +- docker-compose.yaml | 4 +- 15 files changed, 522 insertions(+), 356 deletions(-) rename Tranga-API/Tranga-API.csproj => API/API.csproj (50%) create mode 100644 API/Dockerfile create mode 100644 API/Program.cs create mode 100644 API/RequestHandler.cs create mode 100644 API/Server.cs delete mode 100644 Tranga-API/Program.cs delete mode 100644 Tranga-API/Properties/launchSettings.json delete mode 100644 Tranga-API/appsettings.Development.json delete mode 100644 Tranga-API/appsettings.json diff --git a/Tranga-API/Tranga-API.csproj b/API/API.csproj similarity index 50% rename from Tranga-API/Tranga-API.csproj rename to API/API.csproj index 376581d..0622e9c 100644 --- a/Tranga-API/Tranga-API.csproj +++ b/API/API.csproj @@ -1,10 +1,10 @@ - + + Exe net7.0 - enable enable - Tranga_API + enable Linux @@ -15,14 +15,7 @@ - - - - - - - diff --git a/API/Dockerfile b/API/Dockerfile new file mode 100644 index 0000000..77ee58a --- /dev/null +++ b/API/Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 + +FROM mcr.microsoft.com/dotnet/sdk:7.0 as build-env +WORKDIR /src +COPY . /src/ +RUN dotnet restore API/API.csproj +RUN dotnet publish -c Release -o /publish + +FROM glax/tranga-base:dev as runtime +WORKDIR /publish +COPY --from=build-env /publish . +EXPOSE 6531 +ENTRYPOINT ["dotnet", "/publish/API.dll"] diff --git a/API/Program.cs b/API/Program.cs new file mode 100644 index 0000000..7cbc370 --- /dev/null +++ b/API/Program.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; +using Logging; +using Tranga; + +namespace API; + +public class Program +{ + public static void Main(string[] args) + { + string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga-API"); + string downloadFolderPath = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(applicationFolderPath, "Manga"); + string logsFolderPath = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/var/logs/Tranga" : Path.Join(applicationFolderPath, "logs"); + 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 = new(new[] { Logger.LoggerType.FileLogger, Logger.LoggerType.ConsoleLogger }, Console.Out, Console.Out.Encoding, logFilePath); + + logger.WriteLine("Tranga", "Loading settings."); + + TrangaSettings settings; + if (File.Exists(settingsFilePath)) + settings = TrangaSettings.LoadSettings(settingsFilePath, logger); + else + settings = new TrangaSettings(downloadFolderPath, applicationFolderPath, new HashSet(), new HashSet()); + + Directory.CreateDirectory(settings.workingDirectory); + Directory.CreateDirectory(settings.downloadLocation); + Directory.CreateDirectory(settings.coverImageCache); + + 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 server = new (6531, taskManager, logger); + foreach(NotificationManager nm in taskManager.settings.notificationManagers) + nm.SendNotification("Tranga-API", "Started Tranga-API"); + } +} + diff --git a/API/RequestHandler.cs b/API/RequestHandler.cs new file mode 100644 index 0000000..1778b91 --- /dev/null +++ b/API/RequestHandler.cs @@ -0,0 +1,361 @@ +using System.Net; +using System.Text.RegularExpressions; +using Tranga; +using Tranga.TrangaTasks; + +namespace 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?" }), + new(HttpMethod.Post, "/Tasks/CreateUpdateLibraryTask", new[] { "reoccurrenceTime" }), + 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-z]+=[A-z]+)+(&[A-z]+=[A-z]+)*"); + if (!queryRex.IsMatch(query)) + return ret; + query = query.Substring(1); + foreach(string kvpair in query.Split('&')) + ret.Add(kvpair.Split('=')[0], kvpair.Split('=')[1]); + 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); + 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 is null) + return; + _taskManager.AddTask(new MonitorPublicationTask(connectorName1, (Publication)publication1, TimeSpan.Parse(reoccurrenceTime1), language1 ?? "en")); + break; + case "/Tasks/CreateUpdateLibraryTask": + variables.TryGetValue("reoccurrenceTime", out string? reoccurrenceTime2); + if (reoccurrenceTime2 is null) + return; + _taskManager.AddTask(new UpdateLibrariesTask(TimeSpan.Parse(reoccurrenceTime2))); + 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.SearchChapters((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, _parent.logger, downloadLocation); + if (komgaUrl is not null && komgaAuth is not null && komgaUrl.Length > 5 && komgaAuth.Length > 0) + _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Komga, _parent.logger, 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, _parent.logger, kavitaUrl, kavitaUsername, + kavitaPassword); + if (gotifyUrl is not null && gotifyAppToken is not null && gotifyUrl.Length > 5 && gotifyAppToken.Length > 0) + _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Gotify, _parent.logger, gotifyUrl, gotifyAppToken); + if(lunaseaWebhook is not null && lunaseaWebhook.Length > 5) + _taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.LunaSea, _parent.logger, 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 _taskManager.GetPublicationsFromConnector(connector1, 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 _taskManager.GetNewChaptersList(connector2, (Publication)publication, language??"en").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("chapterSortNumber", out string? chapterSortNumber); + 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 && chapterSortNumber is not null) + { + task = _taskManager.GetTasksMatching(pTask, connectorName: connectorName4, internalId: publicationId, + chapterSortNumber: chapterSortNumber).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/API/Server.cs b/API/Server.cs new file mode 100644 index 0000000..191f8dc --- /dev/null +++ b/API/Server.cs @@ -0,0 +1,72 @@ +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using Logging; +using Newtonsoft.Json; +using Tranga; + +namespace API; + +public class Server +{ + private readonly HttpListener _listener = new (); + private readonly RequestHandler _requestHandler; + 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._listener.Prefixes.Add($"http://*:{port}/"); + this._requestHandler = new RequestHandler(taskManager, this); + Listen(); + } + + 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) + { + 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; + } + + _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"; + response.OutputStream.Write(content is not null + ? Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(content)) + : Array.Empty()); + response.OutputStream.Close(); + + } +} \ No newline at end of file diff --git a/Dockerfile-base b/Dockerfile-base index 24ff1aa..6c9c7f1 100644 --- a/Dockerfile-base +++ b/Dockerfile-base @@ -1,4 +1,8 @@ # syntax=docker/dockerfile:1 -FROM mcr.microsoft.com/dotnet/aspnet:7.0 as runtime +#FROM mcr.microsoft.com/dotnet/aspnet:7.0 as runtime +FROM mcr.microsoft.com/dotnet/runtime:7.0 as runtime WORKDIR /publish -RUN apt-get update && apt-get install -y libx11-6 libx11-xcb1 libatk1.0-0 libgtk-3-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 libxshmfence1 libnss3 +RUN apt-get update +RUN apt-get install -y libx11-6 libx11-xcb1 libatk1.0-0 libgtk-3-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 libxshmfence1 libnss3 +RUN apt-get autopurge -y +RUN apt-get autoclean -y \ No newline at end of file diff --git a/Tranga-API/Program.cs b/Tranga-API/Program.cs deleted file mode 100644 index 4213d59..0000000 --- a/Tranga-API/Program.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System.Runtime.InteropServices; -using Logging; -using Tranga; -using Tranga.TrangaTasks; - -string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga-API"); -string downloadFolderPath = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/Manga" : Path.Join(applicationFolderPath, "Manga"); -string logsFolderPath = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/var/logs/Tranga" : Path.Join(applicationFolderPath, "logs"); -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 = new(new[] { Logger.LoggerType.FileLogger, Logger.LoggerType.ConsoleLogger }, Console.Out, Console.Out.Encoding, logFilePath); - -logger.WriteLine("Tranga", "Loading settings."); - -TrangaSettings settings; -if (File.Exists(settingsFilePath)) - settings = TrangaSettings.LoadSettings(settingsFilePath, logger); -else - settings = new TrangaSettings(downloadFolderPath, applicationFolderPath, new HashSet(), new HashSet()); - -Directory.CreateDirectory(settings.workingDirectory); -Directory.CreateDirectory(settings.downloadLocation); -Directory.CreateDirectory(settings.coverImageCache); - -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); -foreach(NotificationManager nm in taskManager.settings.notificationManagers) - nm.SendNotification("Tranga-API", "Started Tranga-API"); - -var builder = WebApplication.CreateBuilder(args); -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); -builder.Services.AddControllers().AddNewtonsoftJson(); - -string corsHeader = "Tranga"; -builder.Services.AddCors(options => -{ - options.AddPolicy(name: corsHeader, - policy => - { - policy.AllowAnyOrigin(); - policy.WithMethods("GET", "POST", "DELETE"); - }); -}); - -var app = builder.Build(); -app.UseSwagger(); -app.UseSwaggerUI(); - -app.UseCors(corsHeader); - -app.MapGet("/Controllers/Get", () => taskManager.GetAvailableConnectors().Keys.ToArray()); - -app.MapGet("/Publications/GetKnown", (string? internalId) => -{ - if(internalId is null) - return taskManager.GetAllPublications(); - - return new [] { taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId) }; -}); - -app.MapGet("/Publications/GetFromConnector", (string connectorName, string title) => -{ - Connector? connector = taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value; - if (connector is null) - return Array.Empty(); - if(title.Length < 4) - return Array.Empty(); - return taskManager.GetPublicationsFromConnector(connector, title); -}); - -app.MapGet("/Publications/GetChapters", - (string connectorName, string internalId, string? onlyNew, string? onlyExisting, string? language) => - { - string[] yes = { "true", "yes", "1", "y" }; - bool newOnly = onlyNew is not null && yes.Contains(onlyNew); - bool existingOnly = onlyExisting is not null && yes.Contains(onlyExisting); - - Connector? connector = taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value; - if (connector is null) - return Array.Empty(); - Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId); - if (publication is null) - return Array.Empty(); - - if(newOnly) - return taskManager.GetNewChaptersList(connector, (Publication)publication, language??"en").ToArray(); - else if (existingOnly) - return taskManager.GetExistingChaptersList(connector, (Publication)publication, language ?? "en").ToArray(); - else - return connector.GetChapters((Publication)publication, language??"en"); -}); - -app.MapGet("/Tasks/GetTypes", () => Enum.GetNames(typeof(TrangaTask.Task))); - - -app.MapPost("/Tasks/CreateMonitorTask", - (string connectorName, string internalId, string reoccurrenceTime, string? language) => - { - Connector? connector = - taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value; - if (connector is null) - return; - Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId); - if (publication is null) - return; - taskManager.AddTask(new MonitorPublicationTask(connectorName, (Publication)publication, TimeSpan.Parse(reoccurrenceTime), language ?? "en")); - }); - -app.MapPost("/Tasks/CreateUpdateLibraryTask", (string reoccurrenceTime) => -{ - taskManager.AddTask(new UpdateLibrariesTask(TimeSpan.Parse(reoccurrenceTime))); -}); - -app.MapPost("/Tasks/CreateDownloadChaptersTask", (string connectorName, string internalId, string chapters, string? language) => { - - Connector? connector = - taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value; - if (connector is null) - return; - Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == internalId); - if (publication is null) - return; - - IEnumerable toDownload = connector.SearchChapters((Publication)publication, chapters, language ?? "en"); - foreach(Chapter chapter in toDownload) - taskManager.AddTask(new DownloadChapterTask(connectorName, (Publication)publication, chapter, "en")); -}); - -app.MapDelete("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) => -{ - TrangaTask.Task task = Enum.Parse(taskType); - foreach(TrangaTask tTask in taskManager.GetTasksMatching(task, connectorName, internalId: publicationId)) - taskManager.DeleteTask(tTask); -}); - -app.MapGet("/Tasks/Get", (string taskType, string? connectorName, string? searchString) => -{ - try - { - TrangaTask.Task task = Enum.Parse(taskType); - return taskManager.GetTasksMatching(task, connectorName:connectorName, searchString:searchString); - } - catch (ArgumentException) - { - return Array.Empty(); - } -}); - -app.MapGet("/Tasks/GetProgress", (string taskType, string connectorName, string publicationId, string? chapterSortNumber) => -{ - Connector? connector = - taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value; - if (connector is null) - return -1f; - try - { - TrangaTask? task = null; - TrangaTask.Task pTask = Enum.Parse(taskType); - if (pTask is TrangaTask.Task.MonitorPublication) - { - task = taskManager.GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault(); - }else if (pTask is TrangaTask.Task.DownloadChapter && chapterSortNumber is not null) - { - task = taskManager.GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId, - chapterSortNumber: chapterSortNumber).FirstOrDefault(); - } - if (task is null) - return -1f; - - return task.progress; - } - catch (ArgumentException) - { - return -1f; - } -}); - -app.MapPost("/Tasks/Start", (string taskType, string? connectorName, string? internalId) => -{ - try - { - TrangaTask.Task pTask = Enum.Parse(taskType); - TrangaTask? task = taskManager - .GetTasksMatching(pTask, connectorName: connectorName, internalId: internalId).FirstOrDefault(); - - if (task is null) - return; - taskManager.ExecuteTaskNow(task); - } - catch (ArgumentException) - { - return; - } - -}); - -app.MapGet("/Tasks/GetRunningTasks", - () => taskManager.GetAllTasks().Where(task => task.state is TrangaTask.ExecutionState.Running)); - -app.MapGet("/Queue/GetList", - () => taskManager.GetAllTasks().Where(task => task.state is TrangaTask.ExecutionState.Enqueued).OrderBy(task => task.nextExecution)); - -app.MapPost("/Queue/Enqueue", (string taskType, string? connectorName, string? publicationId) => -{ - try - { - TrangaTask.Task pTask = Enum.Parse(taskType); - TrangaTask? task = taskManager - .GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault(); - - if (task is null) - return; - taskManager.AddTaskToQueue(task); - } - catch (ArgumentException) - { - return; - } -}); - -app.MapDelete("/Queue/Dequeue", (string taskType, string? connectorName, string? publicationId) => -{ - try - { - TrangaTask.Task pTask = Enum.Parse(taskType); - TrangaTask? task = taskManager - .GetTasksMatching(pTask, connectorName: connectorName, internalId: publicationId).FirstOrDefault(); - - if (task is null) - return; - taskManager.RemoveTaskFromQueue(task); - } - catch (ArgumentException) - { - return; - } -}); - -app.MapGet("/Settings/Get", () => taskManager.settings); - -app.MapPost("/Settings/Update", - (string? downloadLocation, string? komgaUrl, string? komgaAuth, string? kavitaUrl, string? kavitaUsername, - string? kavitaPassword, string? gotifyUrl, string? gotifyAppToken, string? lunaseaWebhook) => - { - if (downloadLocation is not null && downloadLocation.Length > 0) - taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.DownloadLocation, logger, downloadLocation); - if (komgaUrl is not null && komgaAuth is not null && komgaUrl.Length > 5 && komgaAuth.Length > 0) - taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Komga, logger, 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, logger, kavitaUrl, kavitaUsername, - kavitaPassword); - if (gotifyUrl is not null && gotifyAppToken is not null && gotifyUrl.Length > 5 && gotifyAppToken.Length > 0) - taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.Gotify, logger, gotifyUrl, gotifyAppToken); - if(lunaseaWebhook is not null && lunaseaWebhook.Length > 5) - taskManager.settings.UpdateSettings(TrangaSettings.UpdateField.LunaSea, logger, lunaseaWebhook); - }); - -app.Run(); \ No newline at end of file diff --git a/Tranga-API/Properties/launchSettings.json b/Tranga-API/Properties/launchSettings.json deleted file mode 100644 index 255fff8..0000000 --- a/Tranga-API/Properties/launchSettings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:1716", - "sslPort": 44391 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5177" - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:7036;http://localhost:5177" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true - } - } -} diff --git a/Tranga-API/appsettings.Development.json b/Tranga-API/appsettings.Development.json deleted file mode 100644 index 0c208ae..0000000 --- a/Tranga-API/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/Tranga-API/appsettings.json b/Tranga-API/appsettings.json deleted file mode 100644 index 10f68b8..0000000 --- a/Tranga-API/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/Tranga-CLI/Tranga-CLI.csproj b/Tranga-CLI/Tranga-CLI.csproj index 93908b7..4d6dbf4 100644 --- a/Tranga-CLI/Tranga-CLI.csproj +++ b/Tranga-CLI/Tranga-CLI.csproj @@ -9,12 +9,6 @@ Linux - - - .dockerignore - - - diff --git a/Tranga.sln b/Tranga.sln index 7bf8e4f..97a12c6 100644 --- a/Tranga.sln +++ b/Tranga.sln @@ -6,7 +6,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-CLI", "Tranga-CLI\Tr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{415BE889-BB7D-426F-976F-8D977876A462}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-API", "Tranga-API\Tranga-API.csproj", "{48F4E495-75BC-4402-8E03-DEC5B79D7E83}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{A8AB1F5F-D174-49DC-AED2-0909B93BA7B6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,9 +26,9 @@ Global {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.Build.0 = Debug|Any CPU {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.ActiveCfg = Release|Any CPU {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.Build.0 = Release|Any CPU - {48F4E495-75BC-4402-8E03-DEC5B79D7E83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48F4E495-75BC-4402-8E03-DEC5B79D7E83}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48F4E495-75BC-4402-8E03-DEC5B79D7E83}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48F4E495-75BC-4402-8E03-DEC5B79D7E83}.Release|Any CPU.Build.0 = Release|Any CPU + {A8AB1F5F-D174-49DC-AED2-0909B93BA7B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8AB1F5F-D174-49DC-AED2-0909B93BA7B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8AB1F5F-D174-49DC-AED2-0909B93BA7B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8AB1F5F-D174-49DC-AED2-0909B93BA7B6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Tranga/TrangaTask.cs b/Tranga/TrangaTask.cs index f0c92f2..c418baf 100644 --- a/Tranga/TrangaTask.cs +++ b/Tranga/TrangaTask.cs @@ -26,11 +26,11 @@ public abstract class TrangaTask [Newtonsoft.Json.JsonIgnore] public TrangaTask? parentTask { get; set; } public string? parentTaskId { get; set; } [Newtonsoft.Json.JsonIgnore] protected HashSet childTasks { get; } - [Newtonsoft.Json.JsonIgnore] public double progress => GetProgress(); + public double progress => GetProgress(); [Newtonsoft.Json.JsonIgnore]public DateTime executionStarted { get; private set; } [Newtonsoft.Json.JsonIgnore]public DateTime lastChange { get; private set; } [Newtonsoft.Json.JsonIgnore]public DateTime executionApproximatelyFinished => progress != 0 ? lastChange.Add(GetRemainingTime()) : DateTime.MaxValue; - [Newtonsoft.Json.JsonIgnore]public TimeSpan executionApproximatelyRemaining => executionApproximatelyFinished.Subtract(DateTime.Now); + public TimeSpan executionApproximatelyRemaining => executionApproximatelyFinished.Subtract(DateTime.Now); [Newtonsoft.Json.JsonIgnore]public DateTime nextExecution => lastExecuted.Add(reoccurrence); public enum ExecutionState { Waiting, Enqueued, Running, Failed, Success } @@ -70,8 +70,6 @@ public abstract class TrangaTask { logger?.WriteLine(this.GetType().ToString(), $"Executing Task {this}"); this.state = ExecutionState.Running; - if(this.parentTask is not null) - this.parentTask.state = ExecutionState.Running; this.executionStarted = DateTime.Now; this.lastChange = DateTime.Now; HttpStatusCode statusCode = ExecuteTask(taskManager, logger, cancellationToken); @@ -93,8 +91,6 @@ public abstract class TrangaTask this.state = ExecutionState.Failed; this.lastExecuted = DateTime.MaxValue; } - if(this.parentTask is not null) - this.parentTask.state = ExecutionState.Waiting; logger?.WriteLine(this.GetType().ToString(), $"Finished Executing Task {this}"); } diff --git a/Website/apiConnector.js b/Website/apiConnector.js index e859229..d2fc230 100644 --- a/Website/apiConnector.js +++ b/Website/apiConnector.js @@ -43,60 +43,60 @@ function DeleteData(uri){ } async function GetAvailableControllers(){ - var uri = apiUri + "/Controllers/Get"; + var uri = apiUri + "/Connectors"; let json = await GetData(uri); return json; } async function GetPublicationFromConnector(connectorName, title){ - var uri = apiUri + `/Publications/GetFromConnector?connectorName=${connectorName}&title=${title}`; + var uri = apiUri + `/Publications/FromConnector?connectorName=${connectorName}&title=${title}`; let json = await GetData(uri); return json; } async function GetKnownPublications(){ - var uri = apiUri + "/Publications/GetKnown"; + var uri = apiUri + "/Publications/Known"; let json = await GetData(uri); return json; } async function GetPublication(internalId){ - var uri = apiUri + `/Publications/GetKnown?internalId=${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/GetChapters?internalId=${internalId}&connectorName=${connectorName}&onlyNew=${onlyNew}&language=${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/GetTypes"; + var uri = apiUri + "/Tasks/Types"; let json = await GetData(uri); return json; } async function GetRunningTasks(){ - var uri = apiUri + "/Tasks/GetRunningTasks"; + var uri = apiUri + "/Tasks/RunningTasks"; let json = await GetData(uri); return json; } async function GetDownloadTasks(){ - var uri = apiUri + "/Tasks/Get?taskType=DownloadNewChapters"; + var uri = apiUri + "/Tasks?taskType=DownloadNewChapters"; let json = await GetData(uri); return json; } async function GetSettings(){ - var uri = apiUri + "/Settings/Get"; + var uri = apiUri + "/Settings"; let json = await GetData(uri); return json; } async function GetKomgaTask(){ - var uri = apiUri + "/Tasks/Get?taskType=UpdateLibraries"; + var uri = apiUri + "/Tasks?taskType=UpdateLibraries"; let json = await GetData(uri); return json; } @@ -157,7 +157,7 @@ function UpdateLunaSea(lunaseaWebhook){ } function DeleteTask(taskType, connectorName, publicationId){ - var uri = apiUri + `/Tasks/Delete?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; + var uri = apiUri + `/Tasks?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; DeleteData(uri); } @@ -167,7 +167,7 @@ function DequeueTask(taskType, connectorName, publicationId){ } async function GetQueue(){ - var uri = apiUri + "/Queue/GetList"; + var uri = apiUri + "/Queue/List"; let json = await GetData(uri); return json; } \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 092a3ae..9dcf31f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,7 @@ services: - ./tranga:/usr/share/Tranga-API #1 when replacing ./tranga replace #2 with same value - ./Manga:/Manga ports: - - 6531:80 + - "6531:6531" restart: unless-stopped tranga-website: image: glax/tranga-website:latest @@ -15,7 +15,7 @@ services: volumes: - ./tranga/imageCache:/usr/share/nginx/html/imageCache:ro #2 when replacing Point to same value as #1/imageCache ports: - - 9555:80 + - "9555:80" depends_on: - tranga-api restart: unless-stopped \ No newline at end of file