30 Commits
0.6 ... 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
10 changed files with 313 additions and 178 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,11 +1,11 @@
using Logging; using Logging;
using Tranga; using Tranga;
string applicationFolderPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Tranga-API"); string applicationFolderPath =
Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Tranga-API");
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);
@ -16,11 +16,11 @@ Console.WriteLine($"Settings-File-Path: {settingsFilePath}");
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);
TaskManager taskManager = new (settings, logger); TaskManager taskManager = new (settings, logger);
@ -58,7 +58,7 @@ app.MapPost("/Tasks/Create", (string taskType, string? connectorName, string? pu
taskManager.AddTask(task, connectorName, publication, TimeSpan.Parse(reoccurrenceTime), language??""); taskManager.AddTask(task, connectorName, publication, TimeSpan.Parse(reoccurrenceTime), language??"");
}); });
app.MapPost("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) => app.MapDelete("/Tasks/Delete", (string taskType, string? connectorName, string? publicationId) =>
{ {
Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == publicationId); Publication? publication = taskManager.GetAllPublications().FirstOrDefault(pub => pub.internalId == publicationId);
TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task task = Enum.Parse<TrangaTask.Task>(taskType);
@ -93,7 +93,7 @@ app.MapPost("/Queue/Enqueue", (string taskType, string? connectorName, string? p
taskManager.AddTaskToQueue(task); taskManager.AddTaskToQueue(task);
}); });
app.MapPost("/Queue/Dequeue", (string taskType, string? connectorName, string? publicationId) => app.MapDelete("/Queue/Dequeue", (string taskType, string? connectorName, string? publicationId) =>
{ {
TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType); TrangaTask.Task pTask = Enum.Parse<TrangaTask.Task>(taskType);
TrangaTask? task = taskManager.GetAllTasks().FirstOrDefault(tTask => TrangaTask? task = taskManager.GetAllTasks().FirstOrDefault(tTask =>
@ -103,20 +103,8 @@ app.MapPost("/Queue/Dequeue", (string taskType, string? connectorName, string? p
taskManager.RemoveTaskFromQueue(task); taskManager.RemoveTaskFromQueue(task);
}); });
app.MapGet("/Settings/Get", () => new Settings(taskManager.settings)); app.MapGet("/Settings/Get", () => taskManager.settings);
app.MapPost("/Settings/Update", (string? downloadLocation, string? komgaUrl, string? komgaAuth) => taskManager.UpdateSettings(downloadLocation, komgaUrl, komgaAuth) ); app.MapPost("/Settings/Update", (string? downloadLocation, string? komgaUrl, string? komgaAuth) => taskManager.UpdateSettings(downloadLocation, komgaUrl, komgaAuth) );
app.Run(); app.Run();
class Settings
{
public string downloadLocation { get; }
public Komga? komga { get; }
public Settings(TaskManager.SettingsData settings)
{
this.downloadLocation = settings.downloadLocation;
this.komga = settings.komga;
}
}

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,8 +40,8 @@ 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.UpdateSettings(pDownloadLocation: tmpPath, null); settings.downloadLocation = tmpPath;
Console.WriteLine($"Komga BaseURL [{settings.komga?.baseUrl}]:"); Console.WriteLine($"Komga BaseURL [{settings.komga?.baseUrl}]:");
string? tmpUrl = Console.ReadLine(); string? tmpUrl = Console.ReadLine();
@ -74,14 +74,14 @@ public static class Tranga_Cli
} }
} while (key != ConsoleKey.Enter); } while (key != ConsoleKey.Enter);
settings.UpdateSettings(null, new Komga(tmpUrl, tmpUser, tmpPass, logger)); settings.komga = new Komga(tmpUrl, tmpUser, tmpPass, logger);
} }
logger.WriteLine("Tranga_CLI", "Loaded."); logger.WriteLine("Tranga_CLI", "Loaded.");
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

@ -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>();
@ -75,15 +89,18 @@ 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);
Dictionary<string, string> linksDict = new(); Dictionary<string, string> linksDict = new();
string[,]? links = null;
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();
@ -103,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,
altTitlesDict, altTitlesDict,
tags.ToArray(), tags.ToArray(),
poster, coverUrl,
linksDict, 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
} }
@ -135,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);
@ -182,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);
@ -201,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)
@ -212,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,28 +9,24 @@ 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 Dictionary<string,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 Dictionary<string,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 string internalId { get; }
public readonly struct ValueTuple 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)
{
}
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.sortName = sortName;
this.author = author;
this.description = description; this.description = description;
this.altTitles = altTitles; this.altTitles = altTitles;
this.tags = tags; this.tags = tags;
@ -38,9 +35,10 @@ public readonly struct Publication
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()));
this.internalId = Guid.NewGuid().ToString(); 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

@ -92,13 +92,13 @@ public static class TaskExecutor
/// <param name="chapterCollection"></param> /// <param name="chapterCollection"></param>
private static void DownloadNewChapters(Connector connector, Publication publication, string language, ref 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, ref 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))

View File

@ -11,28 +11,30 @@ namespace Tranga;
public class TaskManager public class TaskManager
{ {
public 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 { get; } public Komga? komga => settings.komga;
/// <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) };
@ -48,21 +50,22 @@ public class TaskManager
Komga? komga = null; Komga? komga = null;
if (komgaUrl is not null && komgaAuth is not null) if (komgaUrl is not null && komgaAuth is not null)
komga = new Komga(komgaUrl, komgaAuth, null); komga = new Komga(komgaUrl, komgaAuth, null);
settings.UpdateSettings(downloadLocation, komga); settings.downloadLocation = downloadLocation ?? settings.downloadLocation;
settings.komga = komga ?? komga;
ExportData(); ExportData();
} }
public TaskManager(SettingsData settings, Logger? logger = null) 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();
} }
@ -160,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.TryAdd((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();
@ -185,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();
} }
@ -289,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>
@ -309,38 +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));
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}");
string serializedData = JsonConvert.SerializeObject(settings); File.WriteAllText(settings.knownPublicationsPath, JsonConvert.SerializeObject(this._chapterCollection.Keys.ToArray()));
File.WriteAllText(settings.settingsFilePath, serializedData);
} }
public class SettingsData
{
public string downloadLocation { get; private set; }
public string settingsFilePath { get; }
public Komga? komga { get; private 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;
}
public void UpdateSettings(string? pDownloadLocation, Komga? pKomga)
{
if(pDownloadLocation is not null)
this.downloadLocation = pDownloadLocation;
if(pKomga is not null)
this.komga = pKomga;
}
}
} }

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}": "")}";
} }
} }