This commit is contained in:
glax 2023-05-21 21:12:32 +02:00
parent df991e3da6
commit 455c87b2e1
11 changed files with 265 additions and 27 deletions

20
Tranga-API/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Tranga-API/Tranga-API.csproj", "Tranga-API/"]
RUN dotnet restore "Tranga-API/Tranga-API.csproj"
COPY . .
WORKDIR "/src/Tranga-API"
RUN dotnet build "Tranga-API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Tranga-API.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Tranga-API.dll"]

122
Tranga-API/Program.cs Normal file
View File

@ -0,0 +1,122 @@
using Logging;
using Tranga;
string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Tranga-API");
string logsFolderPath = Path.Join(applicationFolderPath, "logs");
string logFilePath = Path.Join(logsFolderPath, $"log-{DateTime.Now:dd-M-yyyy-HH-mm-ss}.txt");
string settingsFilePath = Path.Join(applicationFolderPath, "data.json");
Directory.CreateDirectory(applicationFolderPath);
Directory.CreateDirectory(logsFolderPath);
Console.WriteLine($"Logfile-Path: {logFilePath}");
Console.WriteLine($"Settings-File-Path: {settingsFilePath}");
Logger logger = new(new[] { Logger.LoggerType.FileLogger }, null, null, logFilePath);
logger.WriteLine("Tranga_CLI", "Loading Taskmanager.");
TaskManager.SettingsData settings;
if (File.Exists(settingsFilePath))
settings = TaskManager.LoadData(settingsFilePath);
else
settings = new TaskManager.SettingsData(Directory.GetCurrentDirectory(), settingsFilePath, null, new HashSet<TrangaTask>());
TaskManager taskManager = new (settings, logger);
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers().AddNewtonsoftJson();
var app = builder.Build();
app.MapGet("/GetAvailableControllers", () => taskManager.GetAvailableConnectors());
app.MapGet("/GetKnownPublications", () => taskManager.GetAllPublications());
app.MapGet("/GetPublicationsFromConnector", (string connectorName, string title) =>
{
Connector? connector = taskManager.GetAvailableConnectors().FirstOrDefault(con => con.Key == connectorName).Value;
if (connector is null)
return Array.Empty<Publication>();
if(title.Length < 4)
return Array.Empty<Publication>();
return taskManager.GetPublicationsFromConnector(connector, title);
});
app.MapGet("/Tasks/GetTaskTypes", () => Enum.GetNames(typeof(TrangaTask.Task)));
app.MapPost("/Tasks/Create", (string taskType, string? connectorName, string? publicationId, string reoccurrenceTime, string? language) =>
{
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == publicationId);
TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType);
taskManager.AddTask(task, connectorName, publication, TimeSpan.Parse(reoccurrenceTime), language??"");
});
app.MapPost("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) =>
{
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == publicationId);
TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType);
taskManager.DeleteTask(task, connectorName, publication);
});
app.MapGet("/Tasks/GetList", () => taskManager.GetAllTasks());
app.MapPost("/Tasks/Start", (string taskType, string? connectorName, string? publicationId) =>
{
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager.GetAllTasks().FirstOrDefault(tTask =>
tTask.task == pTask && tTask.publication?.internalId == publicationId && tTask.connectorName == connectorName);
if (task is null)
return;
taskManager.ExecuteTaskNow(task);
});
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));
app.MapPost("/Queue/Enqueue", (string taskType, string? connectorName, string? publicationId) =>
{
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager.GetAllTasks().FirstOrDefault(tTask =>
tTask.task == pTask && tTask.publication?.internalId == publicationId && tTask.connectorName == connectorName);
if (task is null)
return;
taskManager.AddTaskToQueue(task);
});
app.MapPost("/Queue/Dequeue", (string taskType, string? connectorName, string? publicationId) =>
{
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager.GetAllTasks().FirstOrDefault(tTask =>
tTask.task == pTask && tTask.publication?.internalId == publicationId && tTask.connectorName == connectorName);
if (task is null)
return;
taskManager.RemoveTaskFromQueue(task);
});
app.MapGet("/Settings/Get", () => new Settings(taskManager.settings));
app.MapPost("/Setting/Update", (string? downloadLocation, string? komgaUrl, string? komgaAuth) =>
{
if(downloadLocation is not null)
taskManager.settings.downloadLocation = downloadLocation;
if(komgaUrl is not null && komgaAuth is not null)
taskManager.settings.komga = new Komga(komgaUrl, komgaAuth, logger);
});
app.Run();
class Settings
{
public string downloadLocation;
public Komga? komga;
public Settings(TaskManager.SettingsData settings)
{
this.downloadLocation = settings.downloadLocation;
this.komga = komga;
}
}

View File

@ -0,0 +1,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:1716",
"sslPort": 44391
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5177",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7036;http://localhost:5177",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Tranga_API</RootNamespace>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Tranga\Tranga.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.5" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -6,6 +6,8 @@ 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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -24,5 +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
EndGlobalSection
EndGlobal

View File

@ -58,14 +58,12 @@ public class MangaDex : Connector
: null;
JsonArray altTitlesObject = attributes["altTitles"]!.AsArray();
string[,] altTitles = new string[altTitlesObject.Count, 2];
int titleIndex = 0;
Dictionary<string, string> altTitlesDict = new();
foreach (JsonNode? altTitleNode in altTitlesObject)
{
JsonObject altTitleObject = (JsonObject)altTitleNode!;
string key = ((IDictionary<string, JsonNode?>)altTitleObject).Keys.ToArray()[0];
altTitles[titleIndex, 0] = key;
altTitles[titleIndex++, 1] = altTitleObject[key]!.GetValue<string>();
altTitlesDict.Add(key, altTitleObject[key]!.GetValue<string>());
}
JsonArray tagsObject = attributes["tags"]!.AsArray();
@ -84,16 +82,14 @@ public class MangaDex : Connector
poster = relationships.FirstOrDefault(relationship => relationship!["type"]!.GetValue<string>() == "cover_art")!["id"]!.GetValue<string>();
}
Dictionary<string, string> linksDict = new();
string[,]? links = null;
if (attributes.ContainsKey("links") && attributes["links"] is not null)
{
JsonObject linksObject = attributes["links"]!.AsObject();
links = new string[linksObject.Count, 2];
int linkIndex = 0;
foreach (string key in ((IDictionary<string, JsonNode?>)linksObject).Keys)
{
links[linkIndex, 0] = key;
links[linkIndex++, 1] = linksObject[key]!.GetValue<string>();
linksDict.Add(key, linksObject[key]!.GetValue<string>());
}
}
@ -110,14 +106,13 @@ public class MangaDex : Connector
Publication pub = new Publication(
title,
description,
altTitles,
altTitlesDict,
tags.ToArray(),
poster,
links,
linksDict,
year,
originalLanguage,
status,
manga["id"]!.GetValue<string>(),
manga["id"]!.GetValue<string>()
);
publications.Add(pub); //Add Publication (Manga) to result

View File

@ -9,34 +9,38 @@ public readonly struct Publication
{
public string sortName { get; }
// ReSharper disable UnusedAutoPropertyAccessor.Global we need it, trust
[JsonIgnore]public string[,] altTitles { get; }
[JsonIgnore]public Dictionary<string,string> altTitles { get; }
// ReSharper disable trice MemberCanBePrivate.Global, trust
public string? description { get; }
public string[] tags { get; }
public string? posterUrl { get; }
[JsonIgnore]public string[,]? links { get; }
[JsonIgnore]public Dictionary<string,string> links { get; }
public int? year { get; }
public string? originalLanguage { get; }
public string status { get; }
public string folderName { get; }
public string downloadUrl { get; }
public string internalId { get; }
public Publication(string sortName, string? description, string[,] altTitles, string[] tags, string? posterUrl, string[,]? links, int? year, string? originalLanguage, string status, string downloadUrl, string internalId)
public readonly struct ValueTuple
{
}
public Publication(string sortName, string? description, Dictionary<string,string> altTitles, string[] tags, string? posterUrl, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string downloadUrl)
{
this.sortName = sortName;
this.description = description;
this.altTitles = altTitles;
this.tags = tags;
this.posterUrl = posterUrl;
this.links = links;
this.links = links ?? new Dictionary<string, string>();
this.year = year;
this.originalLanguage = originalLanguage;
this.status = status;
this.downloadUrl = downloadUrl;
this.folderName = string.Concat(sortName.Split(Path.GetInvalidPathChars().Concat(Path.GetInvalidFileNameChars()).ToArray()));
this.internalId = internalId;
this.internalId = Guid.NewGuid().ToString();
}
/// <returns>Serialized JSON String for series.json</returns>

View File

@ -17,7 +17,7 @@ public static class TaskExecutor
/// <param name="chapterCollection">Current chapterCollection to update</param>
/// <param name="logger"></param>
/// <exception cref="ArgumentException">Is thrown when there is no Connector available with the name of the TrangaTask.connectorName</exception>
public static void Execute(TaskManager taskManager, TrangaTask trangaTask, Dictionary<Publication, List<Chapter>> chapterCollection, Logger? logger)
public static void Execute(TaskManager taskManager, TrangaTask trangaTask, Logger? logger)
{
//Only execute task if it is not already being executed.
if (trangaTask.state == TrangaTask.ExecutionState.Running)
@ -37,13 +37,13 @@ public static class TaskExecutor
switch (trangaTask.task)
{
case TrangaTask.Task.DownloadNewChapters:
DownloadNewChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, chapterCollection);
DownloadNewChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
break;
case TrangaTask.Task.UpdateChapters:
UpdateChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, chapterCollection);
UpdateChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
break;
case TrangaTask.Task.UpdatePublications:
UpdatePublications(connector!, chapterCollection);
UpdatePublications(connector!, ref taskManager._chapterCollection);
break;
case TrangaTask.Task.UpdateKomgaLibrary:
UpdateKomgaLibrary(taskManager);
@ -75,7 +75,7 @@ public static class TaskExecutor
/// </summary>
/// <param name="connector">Connector to receive Publications from</param>
/// <param name="chapterCollection"></param>
private static void UpdatePublications(Connector connector, Dictionary<Publication, List<Chapter>> chapterCollection)
private static void UpdatePublications(Connector connector, ref Dictionary<Publication, List<Chapter>> chapterCollection)
{
Publication[] publications = connector.GetPublications();
foreach (Publication publication in publications)
@ -90,9 +90,9 @@ public static class TaskExecutor
/// <param name="publication">Publication to check</param>
/// <param name="language">Language to receive chapters for</param>
/// <param name="chapterCollection"></param>
private static void DownloadNewChapters(Connector connector, Publication publication, string language, Dictionary<Publication, List<Chapter>> chapterCollection)
private static void DownloadNewChapters(Connector connector, Publication publication, string language, ref Dictionary<Publication, List<Chapter>> chapterCollection)
{
List<Chapter> newChapters = UpdateChapters(connector, publication, language, chapterCollection);
List<Chapter> newChapters = UpdateChapters(connector, publication, language, ref chapterCollection);
connector.DownloadCover(publication);
//Check if Publication already has a Folder and a series.json
@ -116,7 +116,7 @@ public static class TaskExecutor
/// <param name="language">Language to receive chapters for</param>
/// <param name="chapterCollection"></param>
/// <returns>List of Chapters that were previously not in collection</returns>
private static List<Chapter> UpdateChapters(Connector connector, Publication publication, string language, Dictionary<Publication, List<Chapter>> chapterCollection)
private static List<Chapter> UpdateChapters(Connector connector, Publication publication, string language, ref Dictionary<Publication, List<Chapter>> chapterCollection)
{
List<Chapter> newChaptersList = new();
chapterCollection.TryAdd(publication, newChaptersList); //To ensure publication is actually in collection

View File

@ -10,7 +10,7 @@ namespace Tranga;
/// </summary>
public class TaskManager
{
private readonly Dictionary<Publication, List<Chapter>> _chapterCollection = new();
public Dictionary<Publication, List<Chapter>> _chapterCollection = new();
private readonly HashSet<TrangaTask> _allTasks;
private bool _continueRunning = true;
private readonly Connector[] _connectors;
@ -106,7 +106,7 @@ public class TaskManager
logger?.WriteLine(this.GetType().ToString(), $"Forcing Execution: {task}");
Task t = new Task(() =>
{
TaskExecutor.Execute(this, task, this._chapterCollection, logger);
TaskExecutor.Execute(this, task, logger);
});
t.Start();
}
@ -227,6 +227,14 @@ public class TaskManager
_allTasks.CopyTo(ret);
return ret;
}
public Publication[] GetPublicationsFromConnector(Connector connector, string? title = null)
{
Publication[] ret = connector.GetPublications(title ?? "");
foreach(Publication publication in ret)
this._chapterCollection.TryAdd(publication, new List<Chapter>());
return ret;
}
/// <returns>All added Publications</returns>
public Publication[] GetAllPublications()
@ -290,8 +298,11 @@ public class TaskManager
private void ExportData()
{
logger?.WriteLine(this.GetType().ToString(), $"Exporting data to {settings.settingsFilePath}");
string serializedData = JsonConvert.SerializeObject(settings);
File.WriteAllText(settings.settingsFilePath, serializedData);
}