51 Commits
0.5.1 ... 0.6.2

Author SHA1 Message Date
eddf50483f Fixed some nullable types 2023-05-22 21:44:52 +02:00
a71d65e666 Fix negative sleep time 2023-05-22 21:41:11 +02:00
9a640aed27 Rewrote CoverDownload check if exists. 2023-05-22 21:38:44 +02:00
30b6c4680b Better Rate-Limits
Added Logger to DownloadClient
2023-05-22 21:38:23 +02:00
7b6253de0f Create Publication Folder at start of DownloadNewChapters 2023-05-22 21:37:30 +02:00
5aa3214ce5 TrangaTask.ToString() rewrite for logs-readability.
LogMessages only include class-name without path
2023-05-22 21:37:02 +02:00
9b70994f71 Adjusted RateLimit 2023-05-22 18:55:26 +02:00
93cf341f2d Fixed Publication.InternalId 2023-05-22 18:28:42 +02:00
01cb74c088 First attempt at #18 Rate Limits 2023-05-22 18:15:59 +02:00
ec480dffad Merge pull request 'closes #7' (#17) from Issue_7 into master
Reviewed-on: #17
2023-05-22 17:21:42 +02:00
b7014cbff5 Merge pull request 'fixes #14' (#16) from Issue_14_ChapterIsDownlaoded into master
Reviewed-on: #16
2023-05-22 17:21:19 +02:00
0cab921402 Merge pull request 'fixes #11' (#15) from Issue_11 into master
Reviewed-on: #15
2023-05-22 17:20:54 +02:00
0e0ba1796e closes #7 2023-05-22 17:20:07 +02:00
27d8565dc1 fixes #14 2023-05-22 17:09:47 +02:00
79dc44d707 fixes 11 2023-05-22 17:04:31 +02:00
bb6a0ad0d4 Merge pull request 'fixes #9' (#13) from Issue_9 into master
Reviewed-on: #13
2023-05-22 16:53:40 +02:00
43db463ba6 fixes #9 2023-05-22 16:52:52 +02:00
9eb8ddbc40 Changed Publication:
downloadUrl is now publicationId, internal to Connector
posterUrl is now a URL to the file, instead of an id
2023-05-22 16:45:55 +02:00
972cba69ec JsonIgnore
And better working directory stuff
2023-05-22 02:06:49 +02:00
962fe9529e Merge remote-tracking branch 'origin/master' 2023-05-22 01:53:36 +02:00
da1b0cb1cd Change to CommonApplicationFolder as applicationPath 2023-05-22 01:53:27 +02:00
7f88e57e47 Change to CommonApplicationFolder as applicationPath 2023-05-22 01:49:53 +02:00
8865bf284f Corrected applicationFolder in API 2023-05-22 01:42:53 +02:00
5fc2de5fcb logging 2023-05-22 01:20:32 +02:00
4bae223d95 Custom UniqueIdentifier. 2023-05-22 00:33:58 +02:00
0486168b43 AddMangaTaskToQueue Shortcut 2023-05-22 00:15:08 +02:00
b64ab5c6d4 Created TrangaSettings
Different files for settings, tasks, and known publications
Komga connector is stored in TrangaSettings
2023-05-22 00:13:24 +02:00
578fa5e6be JsonIgnore 2023-05-21 23:27:28 +02:00
4d33e78123 unused variable 2023-05-21 22:24:23 +02:00
52ac3e4e4e Proper Mapping for deleting and dequeueing 2023-05-21 22:24:12 +02:00
8b99a98e24 Merge pull request 'api-testing' (#5) from api-testing into master
Reviewed-on: #5
2023-05-21 22:04:06 +02:00
cf171d5c38 Bring CLI in line with new Methods 2023-05-21 22:02:35 +02:00
6d49b4b934 Swagger 2023-05-21 22:02:19 +02:00
b55d2a2d06 no duplicate keys 2023-05-21 22:02:05 +02:00
737eebf599 bring /settings/update in line with new methods 2023-05-21 22:01:56 +02:00
aef01b684c Fixed null on settings.komga 2023-05-21 22:01:40 +02:00
53bff61174 Added Swagger 2023-05-21 22:01:28 +02:00
431a602a40 Added Method UpdateSettings to SettingsData
Added Method UpdateSettings to TaskManager (to export data after update)
2023-05-21 22:01:04 +02:00
9afb81cee2 string and json 2023-05-21 21:24:18 +02:00
ea69b355b5 No duplicate keys 2023-05-21 21:24:04 +02:00
84dbc36bbf dont add duplicates 2023-05-21 21:23:51 +02:00
455c87b2e1 New API 2023-05-21 21:12:32 +02:00
df991e3da6 Remove APi for testing 2023-05-21 17:59:24 +02:00
13c96fd09a Create Appdata Directories for API 2023-05-21 16:51:14 +02:00
6f1a6a43ee API: Edit Settings 2023-05-21 16:49:55 +02:00
e2afc09c4a API: GetSettings 2023-05-21 16:46:34 +02:00
e9db7cfacc API: List Settings 2023-05-21 16:41:02 +02:00
755167c39a API: StartTask
API: Get Task Queue
API: Task Enqueue
API: Task Dequeue
2023-05-21 16:39:54 +02:00
1cff93fbac Use settings-file for API
Added API-call to list TaskTypes
Working? CreateTask API-call
Working? RemoveTask API-call
2023-05-21 16:23:35 +02:00
6c775d6e0c Moved check into if statement 2023-05-21 16:22:40 +02:00
876b1ab78b Added internalId to Publication 2023-05-21 16:22:14 +02:00
14 changed files with 464 additions and 248 deletions

View File

@ -52,7 +52,7 @@ public abstract class LoggerBase : TextWriter
public override string ToString() public override string ToString()
{ {
string dateTimeString = $"{logTime.ToShortDateString()} {logTime.ToLongTimeString()}"; string dateTimeString = $"{logTime.ToShortDateString()} {logTime.ToLongTimeString()}";
return $"[{dateTimeString}] {caller,30} | {value}"; return $"[{dateTimeString}] {caller.Split(new char[]{'.','+'}).Last(),15} | {value}";
} }
} }
} }

View File

@ -1,81 +1,110 @@
using System.Text.Json; using Logging;
using Tranga; using Tranga;
TaskManager taskManager = new (Directory.GetCurrentDirectory()); string applicationFolderPath =
var builder = WebApplication.CreateBuilder(args); Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga-API");
var app = builder.Build(); 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, "settings.json");
app.MapGet("/GetConnectors", () => JsonSerializer.Serialize(taskManager.GetAvailableConnectors().Values.ToArray())); Directory.CreateDirectory(applicationFolderPath);
Directory.CreateDirectory(logsFolderPath);
app.MapGet("/GetPublications", (string connectorName, string? publicationName) =>
{
Connector connector = taskManager.GetConnector(connectorName);
Publication[] publications;
if (publicationName is not null)
publications = connector.GetPublications(publicationName);
else
publications = connector.GetPublications();
return JsonSerializer.Serialize(publications);
});
app.MapGet("/ListTasks", () => JsonSerializer.Serialize(taskManager.GetAllTasks()));
app.MapGet("/CreateTask",
(TrangaTask.Task task, string? connectorName, string? publicationName, TimeSpan reoccurrence, string? language) =>
{
switch (task)
{
case TrangaTask.Task.UpdateKomgaLibrary:
taskManager.AddTask(TrangaTask.Task.UpdateKomgaLibrary, null, null, reoccurrence);
break;
case TrangaTask.Task.DownloadNewChapters:
try
{
Connector connector = taskManager.GetConnector(connectorName);
Publication[] publications;
if (publicationName is not null)
publications = connector.GetPublications(publicationName);
else
publications = connector.GetPublications();
Publication? publication = publications.FirstOrDefault(pub => pub.downloadUrl == publicationName);
if (publication is null)
JsonSerializer.Serialize($"Publication {publicationName} is unknown.");
taskManager.AddTask(TrangaTask.Task.DownloadNewChapters, connectorName, publication, reoccurrence, language ?? "");
return JsonSerializer.Serialize("Success");
}
catch (Exception e)
{
return JsonSerializer.Serialize(e.Message);
}
default: return JsonSerializer.Serialize("Not Implemented");
}
return JsonSerializer.Serialize("Not Implemented");
});
app.MapGet("/RemoveTask", (TrangaTask.Task task, string? connectorName, string? publicationName) =>
{
switch (task)
{
case TrangaTask.Task.UpdateKomgaLibrary:
taskManager.DeleteTask(TrangaTask.Task.UpdateKomgaLibrary, null, null);
return JsonSerializer.Serialize("Success");
case TrangaTask.Task.DownloadNewChapters:
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.downloadUrl == publicationName);
if (publication is null)
JsonSerializer.Serialize($"Publication {publicationName} is unknown.");
taskManager.DeleteTask(TrangaTask.Task.DownloadNewChapters, connectorName, publication);
return JsonSerializer.Serialize("Success");
default: return JsonSerializer.Serialize("Not Implemented"); 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.");
TrangaSettings settings;
if (File.Exists(settingsFilePath))
settings = TrangaSettings.LoadSettings(settingsFilePath);
else
settings = new TrangaSettings(Directory.GetCurrentDirectory(), applicationFolderPath, null);
TaskManager taskManager = new (settings, logger);
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddControllers().AddNewtonsoftJson();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwagger();
app.UseSwaggerUI();
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.MapDelete("/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.MapDelete("/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", () => taskManager.settings);
app.MapPost("/Settings/Update", (string? downloadLocation, string? komgaUrl, string? komgaAuth) => taskManager.UpdateSettings(downloadLocation, komgaUrl, komgaAuth) );
app.Run(); app.Run();

View File

@ -3,8 +3,8 @@
"windowsAuthentication": false, "windowsAuthentication": false,
"anonymousAuthentication": true, "anonymousAuthentication": true,
"iisExpress": { "iisExpress": {
"applicationUrl": "http://localhost:14826", "applicationUrl": "http://localhost:1716",
"sslPort": 44333 "sslPort": 44391
} }
}, },
"profiles": { "profiles": {
@ -12,7 +12,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "http://localhost:5119", "applicationUrl": "http://localhost:5177",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@ -21,7 +21,7 @@
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"applicationUrl": "https://localhost:7070;http://localhost:5119", "applicationUrl": "https://localhost:7036;http://localhost:5177",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@ -15,7 +15,14 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Tranga\Tranga.csproj" /> <ProjectReference Include="..\Tranga\Tranga.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.5" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
</Project> </Project>

View File

@ -1 +0,0 @@
[{"reoccurrence":"00:00:00","lastExecuted":"2023-05-19T17:34:40.5349215+02:00","connectorName":"MangaDex","task":0,"publication":{"sortName":null,"description":null,"tags":null,"posterUrl":null,"year":null,"originalLanguage":null,"status":null,"folderName":null,"downloadUrl":null},"language":"en"}]

View File

@ -14,10 +14,10 @@ public static class Tranga_Cli
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Tranga"); string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga");
string logsFolderPath = Path.Join(applicationFolderPath, "logs"); string logsFolderPath = Path.Join(applicationFolderPath, "logs");
string logFilePath = Path.Join(logsFolderPath, $"log-{DateTime.Now:dd-M-yyyy-HH-mm-ss}.txt"); string logFilePath = Path.Join(logsFolderPath, $"log-{DateTime.Now:dd-M-yyyy-HH-mm-ss}.txt");
string settingsFilePath = Path.Join(applicationFolderPath, "data.json"); string settingsFilePath = Path.Join(applicationFolderPath, "settings.json");
Directory.CreateDirectory(applicationFolderPath); Directory.CreateDirectory(applicationFolderPath);
Directory.CreateDirectory(logsFolderPath); Directory.CreateDirectory(logsFolderPath);
@ -28,11 +28,11 @@ public static class Tranga_Cli
Logger logger = new(new[] { Logger.LoggerType.FileLogger }, null, null, logFilePath); Logger logger = new(new[] { Logger.LoggerType.FileLogger }, null, null, logFilePath);
logger.WriteLine("Tranga_CLI", "Loading Taskmanager."); logger.WriteLine("Tranga_CLI", "Loading Taskmanager.");
TaskManager.SettingsData settings; TrangaSettings settings;
if (File.Exists(settingsFilePath)) if (File.Exists(settingsFilePath))
settings = TaskManager.LoadData(settingsFilePath); settings = TrangaSettings.LoadSettings(settingsFilePath);
else else
settings = new TaskManager.SettingsData(Directory.GetCurrentDirectory(), settingsFilePath, null, new HashSet<TrangaTask>()); settings = new TrangaSettings(Directory.GetCurrentDirectory(), applicationFolderPath, null);
logger.WriteLine("Tranga_CLI", "User Input"); logger.WriteLine("Tranga_CLI", "User Input");
@ -40,7 +40,7 @@ public static class Tranga_Cli
string? tmpPath = Console.ReadLine(); string? tmpPath = Console.ReadLine();
while(tmpPath is null) while(tmpPath is null)
tmpPath = Console.ReadLine(); tmpPath = Console.ReadLine();
if(tmpPath.Length > 0) if (tmpPath.Length > 0)
settings.downloadLocation = tmpPath; settings.downloadLocation = tmpPath;
Console.WriteLine($"Komga BaseURL [{settings.komga?.baseUrl}]:"); Console.WriteLine($"Komga BaseURL [{settings.komga?.baseUrl}]:");
@ -73,7 +73,7 @@ public static class Tranga_Cli
tmpPass += keyInfo.KeyChar; tmpPass += keyInfo.KeyChar;
} }
} while (key != ConsoleKey.Enter); } while (key != ConsoleKey.Enter);
settings.komga = new Komga(tmpUrl, tmpUser, tmpPass, logger); settings.komga = new Komga(tmpUrl, tmpUser, tmpPass, logger);
} }
@ -81,7 +81,7 @@ public static class Tranga_Cli
TaskMode(settings, logger); TaskMode(settings, logger);
} }
private static void TaskMode(TaskManager.SettingsData settings, Logger logger) private static void TaskMode(TrangaSettings settings, Logger logger)
{ {
TaskManager taskManager = new (settings, logger); TaskManager taskManager = new (settings, logger);
ConsoleKey selection = ConsoleKey.EraseEndOfFile; ConsoleKey selection = ConsoleKey.EraseEndOfFile;
@ -143,7 +143,7 @@ public static class Tranga_Cli
TailLog(logger); TailLog(logger);
Console.ReadKey(); Console.ReadKey();
break; break;
case ConsoleKey.M: case ConsoleKey.G:
RemoveTaskFromQueue(taskManager, logger); RemoveTaskFromQueue(taskManager, logger);
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
@ -153,6 +153,11 @@ public static class Tranga_Cli
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
break; break;
case ConsoleKey.M:
AddMangaTaskToQueue(taskManager, logger);
Console.WriteLine("Press any key.");
Console.ReadKey();
break;
} }
PrintMenu(taskManager, taskManager.settings.downloadLocation, logger); PrintMenu(taskManager, taskManager.settings.downloadLocation, logger);
} }
@ -186,8 +191,8 @@ public static class Tranga_Cli
Console.WriteLine(); Console.WriteLine();
Console.WriteLine($"{"C: Create Task",-30}{"L: List tasks",-30}{"B: Enqueue Task", -30}"); 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($"{"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}{"M: Remove Task from Queue", -30}"); Console.WriteLine($"{"E: Execute Task now",-30}{"R: List Running Tasks", -30}{"G: Remove Task from Queue", -30}");
Console.WriteLine(); Console.WriteLine($"{"M: New Download Manga Task",-30}{"", -30}{"", -30}");
Console.WriteLine($"{"",-30}{"F: Show Log",-30}{"Q: Exit",-30}"); Console.WriteLine($"{"",-30}{"F: Show Log",-30}{"Q: Exit",-30}");
} }
@ -204,8 +209,12 @@ public static class Tranga_Cli
$"{"",-5}{"Task",-20} | {"Last Executed",-20} | {"Reoccurrence",-12} | {"State",-10} | {"Connector",-15} | Publication/Manga"; $"{"",-5}{"Task",-20} | {"Last Executed",-20} | {"Reoccurrence",-12} | {"State",-10} | {"Connector",-15} | Publication/Manga";
Console.WriteLine(header); Console.WriteLine(header);
Console.WriteLine(new string('-', header.Length)); Console.WriteLine(new string('-', header.Length));
foreach(TrangaTask trangaTask in tasks) foreach (TrangaTask trangaTask in tasks)
Console.WriteLine($"{tIndex++:000}: {trangaTask}"); {
string[] taskSplit = trangaTask.ToString().Split(", ");
Console.WriteLine($"{tIndex++:000}: {taskSplit[0],-20} | {taskSplit[1],-20} | {taskSplit[2],-12} | {taskSplit[3],-10} | {(taskSplit.Length > 4 ? taskSplit[4] : ""),-15} | {(taskSplit.Length > 5 ? taskSplit[5] : "")}");
}
} }
private static TrangaTask? SelectTask(TrangaTask[] tasks, Logger logger) private static TrangaTask? SelectTask(TrangaTask[] tasks, Logger logger)
@ -239,7 +248,6 @@ public static class Tranga_Cli
try try
{ {
int selectedTaskIndex = Convert.ToInt32(selectedTask); int selectedTaskIndex = Convert.ToInt32(selectedTask);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
return tasks[selectedTaskIndex]; return tasks[selectedTaskIndex];
} }
catch (Exception e) catch (Exception e)
@ -250,6 +258,25 @@ public static class Tranga_Cli
return null; return null;
} }
private static void AddMangaTaskToQueue(TaskManager taskManager, Logger logger)
{
Console.Clear();
logger.WriteLine("Tranga_CLI", "Menu: Add Manga Download to queue");
Connector? connector = SelectConnector(taskManager.settings.downloadLocation, taskManager.GetAvailableConnectors().Values.ToArray(), logger);
if (connector is null)
return;
Publication? publication = SelectPublication(taskManager, connector!, logger);
if (publication is null)
return;
TimeSpan reoccurrence = SelectReoccurrence(logger);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
TrangaTask newTask = taskManager.AddTask(TrangaTask.Task.DownloadNewChapters, connector?.name, publication, reoccurrence, "en");
Console.WriteLine(newTask);
}
private static void AddTaskToQueue(TaskManager taskManager, Logger logger) private static void AddTaskToQueue(TaskManager taskManager, Logger logger)
{ {
@ -300,7 +327,7 @@ public static class Tranga_Cli
} }
} }
private static void CreateTask(TaskManager taskManager, TaskManager.SettingsData settings, Logger logger) private static void CreateTask(TaskManager taskManager, TrangaSettings settings, Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Creating Task"); logger.WriteLine("Tranga_CLI", "Menu: Creating Task");
TrangaTask.Task? tmpTask = SelectTaskType(logger); TrangaTask.Task? tmpTask = SelectTaskType(logger);
@ -319,7 +346,7 @@ public static class Tranga_Cli
Publication? publication = null; Publication? publication = null;
if (task != TrangaTask.Task.UpdatePublications && task != TrangaTask.Task.UpdateKomgaLibrary) if (task != TrangaTask.Task.UpdatePublications && task != TrangaTask.Task.UpdateKomgaLibrary)
{ {
publication = SelectPublication(connector!, logger); publication = SelectPublication(taskManager, connector!, logger);
if (publication is null) if (publication is null)
return; return;
} }
@ -443,7 +470,7 @@ public static class Tranga_Cli
return null; return null;
} }
private static Publication? SelectPublication(Connector connector, Logger logger) private static Publication? SelectPublication(TaskManager taskManager, Connector connector, Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Select Publication"); logger.WriteLine("Tranga_CLI", "Menu: Select Publication");
@ -452,7 +479,14 @@ public static class Tranga_Cli
Console.WriteLine("Publication search query (leave empty for all):"); Console.WriteLine("Publication search query (leave empty for all):");
string? query = Console.ReadLine(); string? query = Console.ReadLine();
Publication[] publications = connector.GetPublications(query ?? ""); Publication[] publications = taskManager.GetPublicationsFromConnector(connector, query ?? "");
if (publications.Length < 1)
{
logger.WriteLine("Tranga_CLI", "No publications returned");
Console.WriteLine($"No publications for query '{query}' returned;");
return null;
}
int pIndex = 0; int pIndex = 0;
Console.WriteLine("Publications:"); Console.WriteLine("Publications:");

View File

@ -4,10 +4,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga", ".\Tranga\Tranga.c
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-CLI", "Tranga-CLI\Tranga-CLI.csproj", "{4899E3B2-B259-479A-B43E-042D043E9501}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-CLI", "Tranga-CLI\Tranga-CLI.csproj", "{4899E3B2-B259-479A-B43E-042D043E9501}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-API", "Tranga-API\Tranga-API.csproj", "{6284C936-4E90-486B-BC46-0AFAD85AD8EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{415BE889-BB7D-426F-976F-8D977876A462}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{415BE889-BB7D-426F-976F-8D977876A462}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-API", "Tranga-API\Tranga-API.csproj", "{48F4E495-75BC-4402-8E03-DEC5B79D7E83}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -22,13 +22,13 @@ Global
{4899E3B2-B259-479A-B43E-042D043E9501}.Debug|Any CPU.Build.0 = Debug|Any CPU {4899E3B2-B259-479A-B43E-042D043E9501}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4899E3B2-B259-479A-B43E-042D043E9501}.Release|Any CPU.ActiveCfg = Release|Any CPU {4899E3B2-B259-479A-B43E-042D043E9501}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4899E3B2-B259-479A-B43E-042D043E9501}.Release|Any CPU.Build.0 = Release|Any CPU {4899E3B2-B259-479A-B43E-042D043E9501}.Release|Any CPU.Build.0 = Release|Any CPU
{6284C936-4E90-486B-BC46-0AFAD85AD8EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6284C936-4E90-486B-BC46-0AFAD85AD8EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6284C936-4E90-486B-BC46-0AFAD85AD8EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6284C936-4E90-486B-BC46-0AFAD85AD8EE}.Release|Any CPU.Build.0 = Release|Any CPU
{415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.Build.0 = Debug|Any CPU {415BE889-BB7D-426F-976F-8D977876A462}.Debug|Any CPU.Build.0 = Debug|Any CPU
{415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.ActiveCfg = Release|Any CPU {415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.ActiveCfg = Release|Any CPU
{415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal

View File

@ -12,15 +12,18 @@ namespace Tranga;
public abstract class Connector public abstract class Connector
{ {
internal string downloadLocation { get; } //Location of local files internal string downloadLocation { get; } //Location of local files
protected DownloadClient downloadClient { get; } protected DownloadClient downloadClient { get; init; }
protected Logger? logger; protected Logger? logger;
protected Connector(string downloadLocation, uint downloadDelay, Logger? logger) protected Connector(string downloadLocation, Logger? logger)
{ {
this.downloadLocation = downloadLocation; this.downloadLocation = downloadLocation;
this.downloadClient = new DownloadClient(downloadDelay);
this.logger = logger; this.logger = logger;
this.downloadClient = new DownloadClient(new Dictionary<byte, int>()
{
//RequestTypes for RateLimits
}, logger);
} }
public abstract string name { get; } //Name of the Connector (e.g. Website) public abstract string name { get; } //Name of the Connector (e.g. Website)
@ -85,6 +88,7 @@ public abstract class Connector
new XElement("Tags", string.Join(',',publication.tags)), new XElement("Tags", string.Join(',',publication.tags)),
new XElement("LanguageISO", publication.originalLanguage), new XElement("LanguageISO", publication.originalLanguage),
new XElement("Title", chapter.name), new XElement("Title", chapter.name),
new XElement("Writer", publication.author),
new XElement("Volume", chapter.volumeNumber), new XElement("Volume", chapter.volumeNumber),
new XElement("Number", chapter.chapterNumber) //TODO check if this is correct at some point new XElement("Number", chapter.chapterNumber) //TODO check if this is correct at some point
); );
@ -106,23 +110,24 @@ public abstract class Connector
/// <returns>Filepath</returns> /// <returns>Filepath</returns>
protected string CreateFullFilepath(Publication publication, Chapter chapter) protected string CreateFullFilepath(Publication publication, Chapter chapter)
{ {
return Path.Join(downloadLocation, publication.folderName, chapter.fileName); return Path.Join(downloadLocation, publication.folderName, $"{chapter.fileName}.cbz");
} }
/// <summary> /// <summary>
/// Downloads Image from URL and saves it to the given path(incl. fileName) /// Downloads Image from URL and saves it to the given path(incl. fileName)
/// </summary> /// </summary>
/// <param name="imageUrl"></param> /// <param name="imageUrl"></param>
/// <param name="fullPath"></param> /// <param name="fullPath"></param>
/// <param name="downloadClient">DownloadClient of the connector</param> /// <param name="downloadClient">DownloadClient of the connector</param>
protected static void DownloadImage(string imageUrl, string fullPath, DownloadClient downloadClient) /// <param name="requestType">Requesttype for ratelimit</param>
protected static void DownloadImage(string imageUrl, string fullPath, DownloadClient downloadClient, byte requestType)
{ {
DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(imageUrl); DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(imageUrl, requestType);
byte[] buffer = new byte[requestResult.result.Length]; byte[] buffer = new byte[requestResult.result.Length];
requestResult.result.ReadExactly(buffer, 0, buffer.Length); requestResult.result.ReadExactly(buffer, 0, buffer.Length);
File.WriteAllBytes(fullPath, buffer); File.WriteAllBytes(fullPath, buffer);
} }
/// <summary> /// <summary>
/// Downloads all Images from URLs, Compresses to zip(cbz) and saves. /// Downloads all Images from URLs, Compresses to zip(cbz) and saves.
/// </summary> /// </summary>
@ -130,17 +135,16 @@ public abstract class Connector
/// <param name="saveArchiveFilePath">Full path to save archive to (without file ending .cbz)</param> /// <param name="saveArchiveFilePath">Full path to save archive to (without file ending .cbz)</param>
/// <param name="downloadClient">DownloadClient of the connector</param> /// <param name="downloadClient">DownloadClient of the connector</param>
/// <param name="comicInfoPath">Path of the generate Chapter ComicInfo.xml, if it was generated</param> /// <param name="comicInfoPath">Path of the generate Chapter ComicInfo.xml, if it was generated</param>
protected static void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, DownloadClient downloadClient, Logger? logger, string? comicInfoPath = null) /// <param name="requestType">RequestType for RateLimits</param>
protected static void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, DownloadClient downloadClient, byte requestType, Logger? logger, string? comicInfoPath = null)
{ {
logger?.WriteLine("Connector", "Downloading Images"); logger?.WriteLine("Connector", "Downloading Images");
//Check if Publication Directory already exists //Check if Publication Directory already exists
string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar); string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray());
if (!Directory.Exists(directoryPath)) if (!Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath); Directory.CreateDirectory(directoryPath);
string fullPath = $"{saveArchiveFilePath}.cbz"; if (File.Exists(saveArchiveFilePath)) //Don't download twice.
if (File.Exists(fullPath)) //Don't download twice.
return; return;
//Create a temporary folder to store images //Create a temporary folder to store images
@ -152,7 +156,7 @@ public abstract class Connector
{ {
string[] split = imageUrl.Split('.'); string[] split = imageUrl.Split('.');
string extension = split[^1]; string extension = split[^1];
DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), downloadClient); DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), downloadClient, requestType);
} }
if(comicInfoPath is not null) if(comicInfoPath is not null)
@ -160,40 +164,72 @@ public abstract class Connector
logger?.WriteLine("Connector", "Creating archive"); logger?.WriteLine("Connector", "Creating archive");
//ZIP-it and ship-it //ZIP-it and ship-it
ZipFile.CreateFromDirectory(tempFolder, fullPath); ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
Directory.Delete(tempFolder, true); //Cleanup Directory.Delete(tempFolder, true); //Cleanup
} }
protected class DownloadClient protected class DownloadClient
{ {
private readonly TimeSpan _requestSpeed;
private DateTime _lastRequest;
private static readonly HttpClient Client = new(); private static readonly HttpClient Client = new();
private readonly Dictionary<byte, DateTime> _lastExecutedRateLimit;
private readonly Dictionary<byte, TimeSpan> _rateLimit;
private Logger? logger;
/// <summary> /// <summary>
/// Creates a httpClient /// Creates a httpClient
/// </summary> /// </summary>
/// <param name="delay">minimum delay between requests (to avoid spam)</param> /// <param name="delay">minimum delay between requests (to avoid spam)</param>
public DownloadClient(uint delay) /// <param name="rateLimitRequestsPerMinute">Rate limits for requests. byte is RequestType, int maximum requests per minute for RequestType</param>
public DownloadClient(Dictionary<byte, int> rateLimitRequestsPerMinute, Logger? logger)
{ {
_requestSpeed = TimeSpan.FromMilliseconds(delay); this.logger = logger;
_lastRequest = DateTime.Now.Subtract(_requestSpeed); _lastExecutedRateLimit = new();
_rateLimit = new();
foreach(KeyValuePair<byte, int> limit in rateLimitRequestsPerMinute)
_rateLimit.Add(limit.Key, TimeSpan.FromMinutes(1).Divide(limit.Value));
} }
/// <summary> /// <summary>
/// Request Webpage /// Request Webpage
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="requestType">For RateLimits: Same Endpoints use same type</param>
/// <returns>RequestResult with StatusCode and Stream of received data</returns> /// <returns>RequestResult with StatusCode and Stream of received data</returns>
public RequestResult MakeRequest(string url) public RequestResult MakeRequest(string url, byte requestType)
{ {
while((DateTime.Now - _lastRequest) < _requestSpeed) if (_rateLimit.TryGetValue(requestType, out TimeSpan value))
Thread.Sleep(10); _lastExecutedRateLimit.TryAdd(requestType, DateTime.Now.Subtract(value));
_lastRequest = DateTime.Now; else
{
logger?.WriteLine(this.GetType().ToString(), "RequestType not configured for rate-limit.");
return new RequestResult(HttpStatusCode.NotAcceptable, Stream.Null);
}
HttpRequestMessage requestMessage = new(HttpMethod.Get, url); TimeSpan rateLimitTimeout = _rateLimit[requestType]
HttpResponseMessage response = Client.Send(requestMessage); .Subtract(DateTime.Now.Subtract(_lastExecutedRateLimit[requestType]));
if(rateLimitTimeout > TimeSpan.Zero)
Thread.Sleep(rateLimitTimeout);
HttpResponseMessage? response = null;
while (response is null)
{
try
{
HttpRequestMessage requestMessage = new(HttpMethod.Get, url);
_lastExecutedRateLimit[requestType] = DateTime.Now;
response = Client.Send(requestMessage);
}
catch (HttpRequestException e)
{
logger?.WriteLine(this.GetType().ToString(), e.Message);
Thread.Sleep(_rateLimit[requestType] * 2);
}
}
Stream resultString = response.IsSuccessStatusCode ? response.Content.ReadAsStream() : Stream.Null; Stream resultString = response.IsSuccessStatusCode ? response.Content.ReadAsStream() : Stream.Null;
if (!response.IsSuccessStatusCode)
logger?.WriteLine(this.GetType().ToString(), $"Request-Error {response.StatusCode}: {response.ReasonPhrase}");
return new RequestResult(response.StatusCode, resultString); return new RequestResult(response.StatusCode, resultString);
} }

View File

@ -9,14 +9,26 @@ public class MangaDex : Connector
{ {
public override string name { get; } public override string name { get; }
public MangaDex(string downloadLocation, uint downloadDelay, Logger? logger) : base(downloadLocation, downloadDelay, logger) private enum RequestType : byte
{ {
name = "MangaDex"; Manga,
Feed,
AtHomeServer,
Cover,
Author
} }
public MangaDex(string downloadLocation, Logger? logger) : base(downloadLocation, 750, logger) public MangaDex(string downloadLocation, Logger? logger) : base(downloadLocation, logger)
{ {
name = "MangaDex"; name = "MangaDex";
this.downloadClient = new DownloadClient(new Dictionary<byte, int>()
{
{(byte)RequestType.Manga, 250},
{(byte)RequestType.Feed, 250},
{(byte)RequestType.AtHomeServer, 40},
{(byte)RequestType.Cover, 250},
{(byte)RequestType.Author, 250}
}, logger);
} }
public override Publication[] GetPublications(string publicationTitle = "") public override Publication[] GetPublications(string publicationTitle = "")
@ -31,7 +43,7 @@ public class MangaDex : Connector
//Request next Page //Request next Page
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest( downloadClient.MakeRequest(
$"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}"); $"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}", (byte)RequestType.Manga);
if (requestResult.statusCode != HttpStatusCode.OK) if (requestResult.statusCode != HttpStatusCode.OK)
break; break;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
@ -49,6 +61,8 @@ public class MangaDex : Connector
JsonObject manga = (JsonObject)mangeNode!; JsonObject manga = (JsonObject)mangeNode!;
JsonObject attributes = manga["attributes"]!.AsObject(); JsonObject attributes = manga["attributes"]!.AsObject();
string publicationId = manga["id"]!.GetValue<string>();
string title = attributes["title"]!.AsObject().ContainsKey("en") && attributes["title"]!["en"] is not null string title = attributes["title"]!.AsObject().ContainsKey("en") && attributes["title"]!["en"] is not null
? attributes["title"]!["en"]!.GetValue<string>() ? attributes["title"]!["en"]!.GetValue<string>()
: attributes["title"]![((IDictionary<string, JsonNode?>)attributes["title"]!.AsObject()).Keys.First()]!.GetValue<string>(); : attributes["title"]![((IDictionary<string, JsonNode?>)attributes["title"]!.AsObject()).Keys.First()]!.GetValue<string>();
@ -58,14 +72,12 @@ public class MangaDex : Connector
: null; : null;
JsonArray altTitlesObject = attributes["altTitles"]!.AsArray(); JsonArray altTitlesObject = attributes["altTitles"]!.AsArray();
string[,] altTitles = new string[altTitlesObject.Count, 2]; Dictionary<string, string> altTitlesDict = new();
int titleIndex = 0;
foreach (JsonNode? altTitleNode in altTitlesObject) foreach (JsonNode? altTitleNode in altTitlesObject)
{ {
JsonObject altTitleObject = (JsonObject)altTitleNode!; JsonObject altTitleObject = (JsonObject)altTitleNode!;
string key = ((IDictionary<string, JsonNode?>)altTitleObject).Keys.ToArray()[0]; string key = ((IDictionary<string, JsonNode?>)altTitleObject).Keys.ToArray()[0];
altTitles[titleIndex, 0] = key; altTitlesDict.TryAdd(key, altTitleObject[key]!.GetValue<string>());
altTitles[titleIndex++, 1] = altTitleObject[key]!.GetValue<string>();
} }
JsonArray tagsObject = attributes["tags"]!.AsArray(); JsonArray tagsObject = attributes["tags"]!.AsArray();
@ -77,23 +89,24 @@ public class MangaDex : Connector
tags.Add(tagObject["attributes"]!["name"]!["en"]!.GetValue<string>()); tags.Add(tagObject["attributes"]!["name"]!["en"]!.GetValue<string>());
} }
string? poster = null; string? posterId = null;
string? authorId = null;
if (manga.ContainsKey("relationships") && manga["relationships"] is not null) if (manga.ContainsKey("relationships") && manga["relationships"] is not null)
{ {
JsonArray relationships = manga["relationships"]!.AsArray(); JsonArray relationships = manga["relationships"]!.AsArray();
poster = relationships.FirstOrDefault(relationship => relationship!["type"]!.GetValue<string>() == "cover_art")!["id"]!.GetValue<string>(); posterId = relationships.FirstOrDefault(relationship => relationship!["type"]!.GetValue<string>() == "cover_art")!["id"]!.GetValue<string>();
authorId = relationships.FirstOrDefault(relationship => relationship!["type"]!.GetValue<string>() == "author")!["id"]!.GetValue<string>();
} }
string? coverUrl = GetCoverUrl(publicationId, posterId);
string? author = GetAuthor(authorId);
string[,]? links = null; Dictionary<string, string> linksDict = new();
if (attributes.ContainsKey("links") && attributes["links"] is not null) if (attributes.ContainsKey("links") && attributes["links"] is not null)
{ {
JsonObject linksObject = attributes["links"]!.AsObject(); JsonObject linksObject = attributes["links"]!.AsObject();
links = new string[linksObject.Count, 2];
int linkIndex = 0;
foreach (string key in ((IDictionary<string, JsonNode?>)linksObject).Keys) foreach (string key in ((IDictionary<string, JsonNode?>)linksObject).Keys)
{ {
links[linkIndex, 0] = key; linksDict.Add(key, linksObject[key]!.GetValue<string>());
links[linkIndex++, 1] = linksObject[key]!.GetValue<string>();
} }
} }
@ -107,17 +120,18 @@ public class MangaDex : Connector
string status = attributes["status"]!.GetValue<string>(); string status = attributes["status"]!.GetValue<string>();
Publication pub = new Publication( Publication pub = new (
title, title,
author,
description, description,
altTitles, altTitlesDict,
tags.ToArray(), tags.ToArray(),
poster, coverUrl,
links, linksDict,
year, year,
originalLanguage, originalLanguage,
status, status,
manga["id"]!.GetValue<string>() publicationId
); );
publications.Add(pub); //Add Publication (Manga) to result publications.Add(pub); //Add Publication (Manga) to result
} }
@ -139,7 +153,7 @@ public class MangaDex : Connector
//Request next "Page" //Request next "Page"
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest( downloadClient.MakeRequest(
$"https://api.mangadex.org/manga/{publication.downloadUrl}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}"); $"https://api.mangadex.org/manga/{publication.publicationId}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}", (byte)RequestType.Feed);
if (requestResult.statusCode != HttpStatusCode.OK) if (requestResult.statusCode != HttpStatusCode.OK)
break; break;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
@ -186,7 +200,7 @@ public class MangaDex : Connector
logger?.WriteLine(this.GetType().ToString(), $"Download Chapter {publication.sortName} {chapter.volumeNumber}-{chapter.chapterNumber}"); logger?.WriteLine(this.GetType().ToString(), $"Download Chapter {publication.sortName} {chapter.volumeNumber}-{chapter.chapterNumber}");
//Request URLs for Chapter-Images //Request URLs for Chapter-Images
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'"); downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer);
if (requestResult.statusCode != HttpStatusCode.OK) if (requestResult.statusCode != HttpStatusCode.OK)
return; return;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
@ -205,7 +219,47 @@ public class MangaDex : Connector
File.WriteAllText(comicInfoPath, CreateComicInfo(publication, chapter, logger)); File.WriteAllText(comicInfoPath, CreateComicInfo(publication, chapter, logger));
//Download Chapter-Images //Download Chapter-Images
DownloadChapterImages(imageUrls.ToArray(), CreateFullFilepath(publication, chapter), downloadClient, logger, comicInfoPath); DownloadChapterImages(imageUrls.ToArray(), CreateFullFilepath(publication, chapter), downloadClient, (byte)RequestType.AtHomeServer, logger, comicInfoPath);
}
private string? GetCoverUrl(string publicationId, string? posterId)
{
if (posterId is null)
{
logger?.WriteLine(this.GetType().ToString(), $"No posterId");
return null;
}
//Request information where to download Cover
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/cover/{posterId}", (byte)RequestType.Cover);
if (requestResult.statusCode != HttpStatusCode.OK)
return null;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
if (result is null)
return null;
string fileName = result["data"]!["attributes"]!["fileName"]!.GetValue<string>();
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
return coverUrl;
}
private string? GetAuthor(string? authorId)
{
if (authorId is null)
return null;
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/author/{authorId}", (byte)RequestType.Author);
if (requestResult.statusCode != HttpStatusCode.OK)
return null;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
if (result is null)
return null;
string author = result["data"]!["attributes"]!["name"]!.GetValue<string>();
return author;
} }
public override void DownloadCover(Publication publication) public override void DownloadCover(Publication publication)
@ -216,31 +270,26 @@ public class MangaDex : Connector
if(!Directory.Exists(publicationFolder)) if(!Directory.Exists(publicationFolder))
Directory.CreateDirectory(publicationFolder); Directory.CreateDirectory(publicationFolder);
DirectoryInfo dirInfo = new (publicationFolder); DirectoryInfo dirInfo = new (publicationFolder);
foreach(FileInfo fileInfo in dirInfo.EnumerateFiles()) if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover.")))
if (fileInfo.Name.Contains("cover.")) {
return; logger?.WriteLine(this.GetType().ToString(), $"Cover exists {publication.sortName}");
//Request information where to download Cover
DownloadClient.RequestResult requestResult =
downloadClient.MakeRequest($"https://api.mangadex.org/cover/{publication.posterUrl}");
if (requestResult.statusCode != HttpStatusCode.OK)
return; return;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); }
if (result is null)
if (publication.posterUrl is null || publication.posterUrl!.Contains("http"))
{
logger?.WriteLine(this.GetType().ToString(), $"No Poster-URL in publication");
return; return;
}
string fileName = result["data"]!["attributes"]!["fileName"]!.GetValue<string>();
string coverUrl = $"https://uploads.mangadex.org/covers/{publication.downloadUrl}/{fileName}";
//Get file-extension (jpg, png) //Get file-extension (jpg, png)
string[] split = coverUrl.Split('.'); string[] split = publication.posterUrl.Split('.');
string extension = split[^1]; string extension = split[^1];
string outFolderPath = Path.Join(downloadLocation, publication.folderName); string outFolderPath = Path.Join(downloadLocation, publication.folderName);
Directory.CreateDirectory(outFolderPath); Directory.CreateDirectory(outFolderPath);
//Download cover-Image //Download cover-Image
DownloadImage(coverUrl, Path.Join(downloadLocation, publication.folderName, $"cover.{extension}"), this.downloadClient); DownloadImage(publication.posterUrl, Path.Join(downloadLocation, publication.folderName, $"cover.{extension}"), this.downloadClient, (byte)RequestType.AtHomeServer);
} }
} }

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json; using System.Text;
using Newtonsoft.Json;
namespace Tranga; namespace Tranga;
@ -8,32 +9,36 @@ namespace Tranga;
public readonly struct Publication public readonly struct Publication
{ {
public string sortName { get; } public string sortName { get; }
// ReSharper disable UnusedAutoPropertyAccessor.Global we need it, trust public string? author { get; }
[JsonIgnore]public string[,] altTitles { get; } public Dictionary<string,string> altTitles { get; }
// ReSharper disable trice MemberCanBePrivate.Global, trust // ReSharper disable trice MemberCanBePrivate.Global, trust
public string? description { get; } public string? description { get; }
public string[] tags { get; } public string[] tags { get; }
public string? posterUrl { get; } public string? posterUrl { get; }
[JsonIgnore]public string[,]? links { get; } public Dictionary<string,string> links { get; }
public int? year { get; } public int? year { get; }
public string? originalLanguage { get; } public string? originalLanguage { get; }
public string status { get; } public string status { get; }
public string folderName { get; } public string folderName { get; }
public string downloadUrl { get; } public string publicationId { 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) public Publication(string sortName, string? author, string? description, Dictionary<string,string> altTitles, string[] tags, string? posterUrl, Dictionary<string,string>? links, int? year, string? originalLanguage, string status, string publicationId)
{ {
this.sortName = sortName; this.sortName = sortName;
this.author = author;
this.description = description; this.description = description;
this.altTitles = altTitles; this.altTitles = altTitles;
this.tags = tags; this.tags = tags;
this.posterUrl = posterUrl; this.posterUrl = posterUrl;
this.links = links; this.links = links ?? new Dictionary<string, string>();
this.year = year; this.year = year;
this.originalLanguage = originalLanguage; this.originalLanguage = originalLanguage;
this.status = status; this.status = status;
this.downloadUrl = downloadUrl; this.publicationId = publicationId;
this.folderName = string.Concat(sortName.Split(Path.GetInvalidPathChars().Concat(Path.GetInvalidFileNameChars()).ToArray())); this.folderName = string.Concat(sortName.Split(Path.GetInvalidPathChars().Concat(Path.GetInvalidFileNameChars()).ToArray()));
string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter));
this.internalId = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{onlyLowerLetters}{this.year}"));
} }
/// <returns>Serialized JSON String for series.json</returns> /// <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="chapterCollection">Current chapterCollection to update</param>
/// <param name="logger"></param> /// <param name="logger"></param>
/// <exception cref="ArgumentException">Is thrown when there is no Connector available with the name of the TrangaTask.connectorName</exception> /// <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. //Only execute task if it is not already being executed.
if (trangaTask.state == TrangaTask.ExecutionState.Running) if (trangaTask.state == TrangaTask.ExecutionState.Running)
@ -37,13 +37,13 @@ public static class TaskExecutor
switch (trangaTask.task) switch (trangaTask.task)
{ {
case TrangaTask.Task.DownloadNewChapters: case TrangaTask.Task.DownloadNewChapters:
DownloadNewChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, chapterCollection); DownloadNewChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
break; break;
case TrangaTask.Task.UpdateChapters: case TrangaTask.Task.UpdateChapters:
UpdateChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, chapterCollection); UpdateChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
break; break;
case TrangaTask.Task.UpdatePublications: case TrangaTask.Task.UpdatePublications:
UpdatePublications(connector!, chapterCollection); UpdatePublications(connector!, ref taskManager._chapterCollection);
break; break;
case TrangaTask.Task.UpdateKomgaLibrary: case TrangaTask.Task.UpdateKomgaLibrary:
UpdateKomgaLibrary(taskManager); UpdateKomgaLibrary(taskManager);
@ -75,7 +75,7 @@ public static class TaskExecutor
/// </summary> /// </summary>
/// <param name="connector">Connector to receive Publications from</param> /// <param name="connector">Connector to receive Publications from</param>
/// <param name="chapterCollection"></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(); Publication[] publications = connector.GetPublications();
foreach (Publication publication in publications) foreach (Publication publication in publications)
@ -90,15 +90,15 @@ public static class TaskExecutor
/// <param name="publication">Publication to check</param> /// <param name="publication">Publication to check</param>
/// <param name="language">Language to receive chapters for</param> /// <param name="language">Language to receive chapters for</param>
/// <param name="chapterCollection"></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); //Check if Publication already has a Folder
connector.DownloadCover(publication);
//Check if Publication already has a Folder and a series.json
string publicationFolder = Path.Join(connector.downloadLocation, publication.folderName); string publicationFolder = Path.Join(connector.downloadLocation, publication.folderName);
if(!Directory.Exists(publicationFolder)) if(!Directory.Exists(publicationFolder))
Directory.CreateDirectory(publicationFolder); Directory.CreateDirectory(publicationFolder);
List<Chapter> newChapters = UpdateChapters(connector, publication, language, ref chapterCollection);
connector.DownloadCover(publication);
string seriesInfoPath = Path.Join(publicationFolder, "series.json"); string seriesInfoPath = Path.Join(publicationFolder, "series.json");
if(!File.Exists(seriesInfoPath)) if(!File.Exists(seriesInfoPath))
@ -116,7 +116,7 @@ public static class TaskExecutor
/// <param name="language">Language to receive chapters for</param> /// <param name="language">Language to receive chapters for</param>
/// <param name="chapterCollection"></param> /// <param name="chapterCollection"></param>
/// <returns>List of Chapters that were previously not in collection</returns> /// <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(); List<Chapter> newChaptersList = new();
chapterCollection.TryAdd(publication, newChaptersList); //To ensure publication is actually in collection chapterCollection.TryAdd(publication, newChaptersList); //To ensure publication is actually in collection

View File

@ -10,30 +10,31 @@ namespace Tranga;
/// </summary> /// </summary>
public class TaskManager public class TaskManager
{ {
private readonly Dictionary<Publication, List<Chapter>> _chapterCollection = new(); public Dictionary<Publication, List<Chapter>> _chapterCollection = new();
private readonly HashSet<TrangaTask> _allTasks; private HashSet<TrangaTask> _allTasks;
private bool _continueRunning = true; private bool _continueRunning = true;
private readonly Connector[] _connectors; private readonly Connector[] _connectors;
private readonly Dictionary<Connector, List<TrangaTask>> _taskQueue = new(); private readonly Dictionary<Connector, List<TrangaTask>> _taskQueue = new();
public SettingsData settings { get; } public TrangaSettings settings { get; }
private Logger? logger { get; } private Logger? logger { get; }
public Komga? komga => settings.komga;
public Komga? komga { get; }
/// <param name="downloadFolderPath">Local path to save data (Manga) to</param> /// <param name="downloadFolderPath">Local path to save data (Manga) to</param>
/// <param name="settingsFilePath">Path to the settings file (data.json)</param> /// <param name="workingDirectory">Path to the working directory</param>
/// <param name="komgaBaseUrl">The Url of the Komga-instance that you want to update</param> /// <param name="komgaBaseUrl">The Url of the Komga-instance that you want to update</param>
/// <param name="komgaUsername">The Komga username</param> /// <param name="komgaUsername">The Komga username</param>
/// <param name="komgaPassword">The Komga password</param> /// <param name="komgaPassword">The Komga password</param>
/// <param name="logger"></param> /// <param name="logger"></param>
public TaskManager(string downloadFolderPath, string? settingsFilePath = null, string? komgaBaseUrl = null, string? komgaUsername = null, string? komgaPassword = null, Logger? logger = null) public TaskManager(string downloadFolderPath, string workingDirectory, string? komgaBaseUrl = null, string? komgaUsername = null, string? komgaPassword = null, Logger? logger = null)
{ {
this.logger = logger; this.logger = logger;
_allTasks = new HashSet<TrangaTask>(); _allTasks = new HashSet<TrangaTask>();
Komga? newKomga = null;
if (komgaBaseUrl != null && komgaUsername != null && komgaPassword != null) if (komgaBaseUrl != null && komgaUsername != null && komgaPassword != null)
this.komga = new Komga(komgaBaseUrl, komgaUsername, komgaPassword, logger); newKomga = new Komga(komgaBaseUrl, komgaUsername, komgaPassword, logger);
this.settings = new SettingsData(downloadFolderPath, settingsFilePath, this.komga, this._allTasks); this.settings = new TrangaSettings(downloadFolderPath, workingDirectory, newKomga);
ExportData(); ExportData();
this._connectors = new Connector[]{ new MangaDex(downloadFolderPath, logger) }; this._connectors = new Connector[]{ new MangaDex(downloadFolderPath, logger) };
@ -44,17 +45,27 @@ public class TaskManager
taskChecker.Start(); taskChecker.Start();
} }
public TaskManager(SettingsData settings, Logger? logger = null) public void UpdateSettings(string? downloadLocation, string? komgaUrl, string? komgaAuth)
{
Komga? komga = null;
if (komgaUrl is not null && komgaAuth is not null)
komga = new Komga(komgaUrl, komgaAuth, null);
settings.downloadLocation = downloadLocation ?? settings.downloadLocation;
settings.komga = komga ?? komga;
ExportData();
}
public TaskManager(TrangaSettings settings, Logger? logger = null)
{ {
this.logger = logger; this.logger = logger;
this._connectors = new Connector[]{ new MangaDex(settings.downloadLocation, logger) }; this._connectors = new Connector[]{ new MangaDex(settings.downloadLocation, logger) };
this.settings = settings;
ExportData();
foreach(Connector cConnector in this._connectors) foreach(Connector cConnector in this._connectors)
_taskQueue.Add(cConnector, new List<TrangaTask>()); _taskQueue.Add(cConnector, new List<TrangaTask>());
this.komga = settings.komga; _allTasks = new HashSet<TrangaTask>();
_allTasks = settings.allTasks;
this.settings = settings;
ImportData();
ExportData();
Thread taskChecker = new(TaskCheckerThread); Thread taskChecker = new(TaskCheckerThread);
taskChecker.Start(); taskChecker.Start();
} }
@ -107,7 +118,7 @@ public class TaskManager
logger?.WriteLine(this.GetType().ToString(), $"Forcing Execution: {task}"); logger?.WriteLine(this.GetType().ToString(), $"Forcing Execution: {task}");
Task t = new Task(() => Task t = new Task(() =>
{ {
TaskExecutor.Execute(this, task, this._chapterCollection, logger); TaskExecutor.Execute(this, task, logger);
}); });
t.Start(); t.Start();
} }
@ -125,8 +136,6 @@ public class TaskManager
string language = "") string language = "")
{ {
logger?.WriteLine(this.GetType().ToString(), $"Adding new Task {task} {connectorName} {publication?.sortName}"); logger?.WriteLine(this.GetType().ToString(), $"Adding new Task {task} {connectorName} {publication?.sortName}");
if (task != TrangaTask.Task.UpdateKomgaLibrary && connectorName is null)
throw new ArgumentException($"connectorName can not be null for task {task}");
TrangaTask newTask; TrangaTask newTask;
if (task == TrangaTask.Task.UpdateKomgaLibrary) if (task == TrangaTask.Task.UpdateKomgaLibrary)
@ -142,6 +151,9 @@ public class TaskManager
} }
else else
{ {
if(connectorName is null)
throw new ArgumentException($"connectorName can not be null for task {task}");
//Get appropriate Connector from available Connectors for TrangaTask //Get appropriate Connector from available Connectors for TrangaTask
Connector? connector = _connectors.FirstOrDefault(c => c.name == connectorName); Connector? connector = _connectors.FirstOrDefault(c => c.name == connectorName);
if (connector is null) if (connector is null)
@ -151,12 +163,14 @@ public class TaskManager
//Check if same task already exists //Check if same task already exists
if (!_allTasks.Any(trangaTask => trangaTask.task == task && trangaTask.connectorName == connector.name && if (!_allTasks.Any(trangaTask => trangaTask.task == task && trangaTask.connectorName == connector.name &&
trangaTask.publication?.downloadUrl == publication?.downloadUrl)) trangaTask.publication?.internalId == publication?.internalId))
{ {
if(task != TrangaTask.Task.UpdatePublications) if(task != TrangaTask.Task.UpdatePublications)
_chapterCollection.Add((Publication)publication!, new List<Chapter>()); _chapterCollection.TryAdd((Publication)publication!, new List<Chapter>());
_allTasks.Add(newTask); _allTasks.Add(newTask);
} }
else
logger?.WriteLine(this.GetType().ToString(), $"Publication already exists {publication?.internalId}");
} }
logger?.WriteLine(this.GetType().ToString(), $"Added new Task {newTask.ToString()}"); logger?.WriteLine(this.GetType().ToString(), $"Added new Task {newTask.ToString()}");
ExportData(); ExportData();
@ -176,18 +190,25 @@ public class TaskManager
if (task == TrangaTask.Task.UpdateKomgaLibrary) if (task == TrangaTask.Task.UpdateKomgaLibrary)
{ {
_allTasks.RemoveWhere(uTask => uTask.task == TrangaTask.Task.UpdateKomgaLibrary); _allTasks.RemoveWhere(uTask => uTask.task == TrangaTask.Task.UpdateKomgaLibrary);
logger?.WriteLine(this.GetType().ToString(), $"Removed Task {task}"); logger?.WriteLine(this.GetType().ToString(), $"Removed Task {task} from all Tasks.");
} }
else if (connectorName is null) else if (connectorName is null)
throw new ArgumentException($"connectorName can not be null for Task {task}"); throw new ArgumentException($"connectorName can not be null for Task {task}");
else else
{ {
foreach (List<TrangaTask> taskQueue in this._taskQueue.Values)
if(taskQueue.RemoveAll(trangaTask =>
trangaTask.task == task && trangaTask.connectorName == connectorName &&
trangaTask.publication?.internalId == publication?.internalId) > 0)
logger?.WriteLine(this.GetType().ToString(), $"Removed Task {task} {publication?.sortName} {publication?.internalId} from Queue.");
else
logger?.WriteLine(this.GetType().ToString(), $"Task {task} {publication?.sortName} {publication?.internalId} was not in Queue.");
if(_allTasks.RemoveWhere(trangaTask => if(_allTasks.RemoveWhere(trangaTask =>
trangaTask.task == task && trangaTask.connectorName == connectorName && trangaTask.task == task && trangaTask.connectorName == connectorName &&
trangaTask.publication?.downloadUrl == publication?.downloadUrl) > 0) trangaTask.publication?.internalId == publication?.internalId) > 0)
logger?.WriteLine(this.GetType().ToString(), $"Removed Task {task} {publication?.sortName} {publication?.downloadUrl}."); logger?.WriteLine(this.GetType().ToString(), $"Removed Task {task} {publication?.sortName} {publication?.internalId} from all Tasks.");
else else
logger?.WriteLine(this.GetType().ToString(), $"No Task {task} {publication?.sortName} {publication?.downloadUrl} could be found."); logger?.WriteLine(this.GetType().ToString(), $"No Task {task} {publication?.sortName} {publication?.internalId} could be found.");
} }
ExportData(); ExportData();
} }
@ -227,6 +248,17 @@ public class TaskManager
_allTasks.CopyTo(ret); _allTasks.CopyTo(ret);
return ret; return ret;
} }
public Publication[] GetPublicationsFromConnector(Connector connector, string? title = null)
{
Publication[] ret = connector.GetPublications(title ?? "");
foreach (Publication publication in ret)
{
if(!_chapterCollection.Any(pub => pub.Key.sortName == publication.sortName))
this._chapterCollection.TryAdd(publication, new List<Chapter>());
}
return ret;
}
/// <returns>All added Publications</returns> /// <returns>All added Publications</returns>
public Publication[] GetAllPublications() public Publication[] GetAllPublications()
@ -269,19 +301,25 @@ public class TaskManager
Environment.Exit(0); Environment.Exit(0);
} }
/// <summary> private void ImportData()
/// Loads stored data (settings, tasks) from file
/// </summary>
/// <param name="importFilePath">working directory, filename has to be data.json</param>
public static SettingsData LoadData(string importFilePath)
{ {
if (!File.Exists(importFilePath)) logger?.WriteLine(this.GetType().ToString(), "Importing Data");
return new SettingsData(Directory.GetCurrentDirectory(), null, null, new HashSet<TrangaTask>()); string buffer;
if (File.Exists(settings.tasksFilePath))
{
logger?.WriteLine(this.GetType().ToString(), $"Importing tasks from {settings.tasksFilePath}");
buffer = File.ReadAllText(settings.tasksFilePath);
this._allTasks = JsonConvert.DeserializeObject<HashSet<TrangaTask>>(buffer)!;
}
string toRead = File.ReadAllText(importFilePath); if (File.Exists(settings.knownPublicationsPath))
SettingsData data = JsonConvert.DeserializeObject<SettingsData>(toRead)!; {
logger?.WriteLine(this.GetType().ToString(), $"Importing known publications from {settings.knownPublicationsPath}");
return data; buffer = File.ReadAllText(settings.knownPublicationsPath);
Publication[] publications = JsonConvert.DeserializeObject<Publication[]>(buffer)!;
foreach (Publication publication in publications)
this._chapterCollection.TryAdd(publication, new List<Chapter>());
}
} }
/// <summary> /// <summary>
@ -289,27 +327,15 @@ public class TaskManager
/// </summary> /// </summary>
private void ExportData() private void ExportData()
{ {
logger?.WriteLine(this.GetType().ToString(), $"Exporting data to {settings.settingsFilePath}"); logger?.WriteLine(this.GetType().ToString(), $"Exporting settings to {settings.settingsFilePath}");
File.WriteAllText(settings.settingsFilePath, JsonConvert.SerializeObject(settings));
string serializedData = JsonConvert.SerializeObject(settings);
File.WriteAllText(settings.settingsFilePath, serializedData); logger?.WriteLine(this.GetType().ToString(), $"Exporting tasks to {settings.tasksFilePath}");
File.WriteAllText(settings.tasksFilePath, JsonConvert.SerializeObject(this._allTasks));
logger?.WriteLine(this.GetType().ToString(), $"Exporting known publications to {settings.knownPublicationsPath}");
File.WriteAllText(settings.knownPublicationsPath, JsonConvert.SerializeObject(this._chapterCollection.Keys.ToArray()));
} }
public class SettingsData
{
public string downloadLocation { get; set; }
public string settingsFilePath { get; }
public Komga? komga { get; set; }
public HashSet<TrangaTask> allTasks { get; }
public SettingsData(string downloadLocation, string? settingsFilePath, Komga? komga, HashSet<TrangaTask> allTasks)
{
this.settingsFilePath = settingsFilePath ??
Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Tranga", "data.json");
this.downloadLocation = downloadLocation;
this.komga = komga;
this.allTasks = allTasks;
}
}
} }

31
Tranga/TrangaSettings.cs Normal file
View File

@ -0,0 +1,31 @@
using Newtonsoft.Json;
namespace Tranga;
public class TrangaSettings
{
public string downloadLocation { get; set; }
public string workingDirectory { get; set; }
[JsonIgnore]public string settingsFilePath => Path.Join(workingDirectory, "settings.json");
[JsonIgnore]public string tasksFilePath => Path.Join(workingDirectory, "tasks.json");
[JsonIgnore]public string knownPublicationsPath => Path.Join(workingDirectory, "knownPublications.json");
public Komga? komga { get; set; }
public TrangaSettings(string downloadLocation, string workingDirectory, Komga? komga)
{
this.workingDirectory = workingDirectory;
this.downloadLocation = downloadLocation;
this.komga = komga;
}
public static TrangaSettings LoadSettings(string importFilePath)
{
if (!File.Exists(importFilePath))
return new TrangaSettings(Path.Join(Directory.GetCurrentDirectory(), "Downloads"), Directory.GetCurrentDirectory(), null);
string toRead = File.ReadAllText(importFilePath);
TrangaSettings settings = JsonConvert.DeserializeObject<TrangaSettings>(toRead)!;
return settings;
}
}

View File

@ -56,6 +56,6 @@ public class TrangaTask
public override string ToString() public override string ToString()
{ {
return $"{task,-20} | {lastExecuted,-20} | {reoccurrence,-12} | {state,-10} | {connectorName,-15} | {publication?.sortName}"; return $"{task}, {lastExecuted}, {reoccurrence}, {state} {(connectorName is not null ? $", {connectorName}" : "" )} {(publication is not null ? $", {publication?.sortName}": "")}";
} }
} }