33 Commits

Author SHA1 Message Date
e72efa3731 Corrected string 2023-05-21 02:18:39 +02:00
597eedb6d4 Added menu to show loglines 2023-05-21 02:17:38 +02:00
8829132046 Cleanup code 2023-05-21 02:13:19 +02:00
32467191f6 Added New CLI Options to list enqueued task and view last 20 loglines 2023-05-21 02:11:47 +02:00
fe52d2c3b5 Always create and use MemoryLogger 2023-05-21 02:10:32 +02:00
554f6b4acc TaskCheckerThread new logic 2023-05-21 01:58:24 +02:00
9d0fc18051 Delete old data.json 2023-05-21 01:58:07 +02:00
e02b00e0ef Better/More logging 2023-05-21 01:57:56 +02:00
06a8e4e895 Make caller right aligned 2023-05-21 01:57:18 +02:00
a557f8cab5 Export Data when starting new task 2023-05-20 23:12:15 +02:00
e564be08f5 Search query length now at least 4 characters 2023-05-20 23:08:16 +02:00
b8bf7bdf30 "Fixed" Issue with Filelogger, where program would crash if file could not be written 2023-05-20 22:56:05 +02:00
d6af014cb7 string 2023-05-20 22:43:39 +02:00
2dcaaf4d66 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	Tranga/TaskManager.cs
2023-05-20 22:21:26 +02:00
e3ec5420c0 Fixed bug for enqueued tasks constantly being triggered to execute 2023-05-20 22:21:00 +02:00
5d66bce5b6 Fixed bug for enqueued tasks constantly being triggered to execute 2023-05-20 22:15:31 +02:00
07ae4af209 Changed Log message to long timestring 2023-05-20 22:13:25 +02:00
d62b0bdf34 Changed Logger to accept string as caller
Added Logger to all relevant methods
2023-05-20 22:10:24 +02:00
a367ebb230 Use Logger to log CLI-Inputs 2023-05-20 21:48:08 +02:00
4d3861d31b Created Logger 2023-05-20 21:47:54 +02:00
497ec74b9a Readme 2023-05-20 17:48:53 +02:00
18b5d17994 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	Tranga-CLI/Tranga_Cli.cs
2023-05-20 17:19:17 +02:00
1916018fba Some work on API-side 2023-05-20 17:18:22 +02:00
a6a2d20981 More fancy CLI 2023-05-20 16:35:45 +02:00
1449292e53 More fancy CLI 2023-05-20 16:35:08 +02:00
67f3695be8 CLI: When listing Task add headers for values 2023-05-20 16:27:30 +02:00
086d72565a Formatting of trangaTask string with fixed-with instead of tabs 2023-05-20 16:23:25 +02:00
e54e83c2ae Moved "Press any key" 2023-05-20 16:15:17 +02:00
73f19c3989 Clear console when aborting. 2023-05-20 16:13:19 +02:00
2c84688925 Rewrote menu structure
You can now exit menus with q
2023-05-20 16:12:15 +02:00
a58f113d14 Add ability to abort when selecting task in menu to ExecuteNow or Remove 2023-05-20 15:58:35 +02:00
fcb1848a93 Renamed SelectTask to SelectTaskType to avoid confusion 2023-05-20 15:58:02 +02:00
337111d833 Remove DownloadNow mode 2023-05-20 15:57:35 +02:00
17 changed files with 616 additions and 234 deletions

26
Logging/FileLogger.cs Normal file
View File

@ -0,0 +1,26 @@
using System.Text;
using System.Text.Json.Serialization;
namespace Logging;
public class FileLogger : LoggerBase
{
private string logFilePath { get; }
public FileLogger(string logFilePath, TextWriter? stdOut, Encoding? encoding = null) : base (stdOut, encoding)
{
this.logFilePath = logFilePath;
}
protected override void Write(LogMessage logMessage)
{
try
{
File.AppendAllText(logFilePath, logMessage.ToString());
}
catch (Exception e)
{
stdOut?.WriteLine(e);
}
}
}

View File

@ -0,0 +1,17 @@
using System.Text;
namespace Logging;
public class FormattedConsoleLogger : LoggerBase
{
public FormattedConsoleLogger(TextWriter? stdOut, Encoding? encoding = null) : base(stdOut, encoding)
{
}
protected override void Write(LogMessage message)
{
//Nothing to do yet
}
}

58
Logging/Logger.cs Normal file
View File

@ -0,0 +1,58 @@
using System.Net.Mime;
using System.Text;
namespace Logging;
public class Logger : TextWriter
{
public override Encoding Encoding { get; }
public enum LoggerType
{
FileLogger,
ConsoleLogger
}
private FileLogger? _fileLogger;
private FormattedConsoleLogger? _formattedConsoleLogger;
private MemoryLogger _memoryLogger;
private TextWriter? stdOut;
public Logger(LoggerType[] enabledLoggers, TextWriter? stdOut, Encoding? encoding, string? logFilePath)
{
this.Encoding = encoding ?? Encoding.ASCII;
this.stdOut = stdOut ?? null;
if (enabledLoggers.Contains(LoggerType.FileLogger) && logFilePath is not null)
_fileLogger = new FileLogger(logFilePath, null, encoding);
else
{
_fileLogger = null;
throw new ArgumentException($"logFilePath can not be null for LoggerType {LoggerType.FileLogger}");
}
_formattedConsoleLogger = enabledLoggers.Contains(LoggerType.ConsoleLogger) ? new FormattedConsoleLogger(null, encoding) : null;
_memoryLogger = new MemoryLogger(null, encoding);
}
public void WriteLine(string caller, string? value)
{
value = value is null ? Environment.NewLine : string.Concat(value, Environment.NewLine);
Write(caller, value);
}
public void Write(string caller, string? value)
{
if (value is null)
return;
_fileLogger?.Write(caller, value);
_formattedConsoleLogger?.Write(caller, value);
_memoryLogger.Write(caller, value);
stdOut?.Write(value);
}
public string[] Tail(uint? lines)
{
return _memoryLogger.Tail(lines);
}
}

58
Logging/LoggerBase.cs Normal file
View File

@ -0,0 +1,58 @@
using System.Text;
namespace Logging;
public abstract class LoggerBase : TextWriter
{
public override Encoding Encoding { get; }
protected TextWriter? stdOut { get; }
public LoggerBase(TextWriter? stdOut, Encoding? encoding = null)
{
this.Encoding = encoding ?? Encoding.ASCII;
this.stdOut = stdOut;
}
public void WriteLine(string caller, string? value)
{
value = value is null ? Environment.NewLine : string.Join(value, Environment.NewLine);
LogMessage message = new LogMessage(DateTime.Now, caller, value);
Write(message);
}
public void Write(string caller, string? value)
{
if (value is null)
return;
LogMessage message = new LogMessage(DateTime.Now, caller, value);
stdOut?.Write(message.ToString());
Write(message);
}
protected abstract void Write(LogMessage message);
public class LogMessage
{
public DateTime logTime { get; }
public string caller { get; }
public string value { get; }
public LogMessage(DateTime now, string caller, string value)
{
this.logTime = now;
this.caller = caller;
this.value = value;
}
public override string ToString()
{
string dateTimeString = $"{logTime.ToShortDateString()} {logTime.ToLongTimeString()}";
return $"[{dateTimeString}] {caller,30} | {value}";
}
}
}

9
Logging/Logging.csproj Normal file
View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

39
Logging/MemoryLogger.cs Normal file
View File

@ -0,0 +1,39 @@
using System.Text;
namespace Logging;
public class MemoryLogger : LoggerBase
{
private SortedList<DateTime, LogMessage> logMessages = new();
public MemoryLogger(TextWriter? stdOut, Encoding? encoding = null) : base(stdOut, encoding)
{
}
protected override void Write(LogMessage value)
{
logMessages.Add(value.logTime, value);
}
public string[] GetLogMessage()
{
return Tail(Convert.ToUInt32(logMessages.Count));
}
public string[] Tail(uint? length)
{
int retLength;
if (length is null || length > logMessages.Count)
retLength = logMessages.Count;
else
retLength = (int)length;
string[] ret = new string[retLength];
for (int logMessageIndex = logMessages.Count - retLength; logMessageIndex < logMessages.Count; logMessageIndex++)
ret[logMessageIndex + retLength - logMessages.Count] = logMessages.GetValueAtIndex(logMessageIndex).ToString();
return ret;
}
}

View File

@ -56,11 +56,9 @@ and automatically start updates in [Komga](https://komga.org/) to import them.
### Inspiration: ### Inspiration:
Because [Kaizoku](https://github.com/oae/kaizoku) was relying on [mangal](https://github.com/metafates/mangal) on hasn't Because [Kaizoku](https://github.com/oae/kaizoku) was relying on [mangal](https://github.com/metafates/mangal) and mangal
received bugfixes for it's issues with Titles not showing up, or throwing errors because of illegal characters, there hasn't received bugfixes for it's issues with Titles not showing up, or throwing errors because of illegal characters,
were no alternatives for automatic downloads. there were no alternatives for automatic downloads. However [Kaizoku](https://github.com/oae/kaizoku) certainly had a great Web-UI.
[Kaizoku](https://github.com/oae/kaizoku) certainly had a great Web-UI, but just wasn't working.
That is why I wanted to create my own project, in a language I understand, and that I am able to maintain myself. That is why I wanted to create my own project, in a language I understand, and that I am able to maintain myself.
@ -135,8 +133,6 @@ Distributed under the GNU GPLv3 License. See `LICENSE.txt` for more information
<!-- ACKNOWLEDGMENTS --> <!-- ACKNOWLEDGMENTS -->
## Acknowledgments ## Acknowledgments
Use this space to list resources you find helpful and would like to give credit to. I've included a few of my favorites to kick things off!
* [Choose an Open Source License](https://choosealicense.com) * [Choose an Open Source License](https://choosealicense.com)
* [Font Awesome](https://fontawesome.com) * [Font Awesome](https://fontawesome.com)
* [Best-README-Template](https://github.com/othneildrew/Best-README-Template/tree/master) * [Best-README-Template](https://github.com/othneildrew/Best-README-Template/tree/master)

View File

@ -1,20 +1,19 @@
using System.Text.Json; using System.Text.Json;
using Tranga; using Tranga;
using Tranga.Connectors;
TaskManager taskManager = new TaskManager(Directory.GetCurrentDirectory()); TaskManager taskManager = new (Directory.GetCurrentDirectory());
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var app = builder.Build(); var app = builder.Build();
app.MapGet("/GetConnectors", () => JsonSerializer.Serialize(taskManager.GetAvailableConnectors().Values.ToArray())); app.MapGet("/GetConnectors", () => JsonSerializer.Serialize(taskManager.GetAvailableConnectors().Values.ToArray()));
app.MapGet("/GetPublications", (string connectorName, string? title) => app.MapGet("/GetPublications", (string connectorName, string? publicationName) =>
{ {
Connector connector = taskManager.GetConnector(connectorName); Connector connector = taskManager.GetConnector(connectorName);
Publication[] publications; Publication[] publications;
if (title is not null) if (publicationName is not null)
publications = connector.GetPublications(title); publications = connector.GetPublications(publicationName);
else else
publications = connector.GetPublications(); publications = connector.GetPublications();
@ -24,26 +23,59 @@ app.MapGet("/GetPublications", (string connectorName, string? title) =>
app.MapGet("/ListTasks", () => JsonSerializer.Serialize(taskManager.GetAllTasks())); app.MapGet("/ListTasks", () => JsonSerializer.Serialize(taskManager.GetAllTasks()));
app.MapGet("/CreateTask", app.MapGet("/CreateTask",
(TrangaTask.Task task, string connectorName, string? publicationName, TimeSpan reoccurrence, string language) => (TrangaTask.Task task, string? connectorName, string? publicationName, TimeSpan reoccurrence, string? language) =>
{ {
Publication? publication = switch (task)
taskManager.GetAllPublications().FirstOrDefault(pub => pub.downloadUrl == publicationName); {
if (publication is null) case TrangaTask.Task.UpdateKomgaLibrary:
JsonSerializer.Serialize($"Publication {publicationName} is unknown."); taskManager.AddTask(TrangaTask.Task.UpdateKomgaLibrary, null, null, reoccurrence);
break;
taskManager.AddTask(task, connectorName, publication, reoccurrence, language); case TrangaTask.Task.DownloadNewChapters:
JsonSerializer.Serialize("Success"); 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 connector, string? publicationName) => app.MapGet("/RemoveTask", (TrangaTask.Task task, string? connectorName, string? publicationName) =>
{ {
Publication? publication = switch (task)
taskManager.GetAllPublications().FirstOrDefault(pub => pub.downloadUrl == publicationName); {
if (publication is null) case TrangaTask.Task.UpdateKomgaLibrary:
JsonSerializer.Serialize($"Publication {publicationName} is unknown."); taskManager.RemoveTask(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.RemoveTask(TrangaTask.Task.DownloadNewChapters, connectorName, publication);
return JsonSerializer.Serialize("Success");
taskManager.RemoveTask(task, connector, publication); default: return JsonSerializer.Serialize("Not Implemented");
JsonSerializer.Serialize("Success"); }
}); });
app.Run(); app.Run();

View File

@ -1,6 +1,6 @@
using System.Globalization; using System.Globalization;
using Logging;
using Tranga; using Tranga;
using Tranga.Connectors;
namespace Tranga_CLI; namespace Tranga_CLI;
@ -14,6 +14,10 @@ public static class Tranga_Cli
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
Logger logger = new(new[] { Logger.LoggerType.FileLogger }, null, null,
Path.Join(Directory.GetCurrentDirectory(), $"log-{DateTime.Now:dd-M-yyyy-HH-mm-ss}.txt"));
logger.WriteLine("Tranga_CLI", "Loading Settings.");
TaskManager.SettingsData settings; TaskManager.SettingsData settings;
string settingsPath = Path.Join(Directory.GetCurrentDirectory(), "data.json"); string settingsPath = Path.Join(Directory.GetCurrentDirectory(), "data.json");
if (File.Exists(settingsPath)) if (File.Exists(settingsPath))
@ -22,6 +26,7 @@ public static class Tranga_Cli
settings = new TaskManager.SettingsData(Directory.GetCurrentDirectory(), null, new HashSet<TrangaTask>()); settings = new TaskManager.SettingsData(Directory.GetCurrentDirectory(), null, new HashSet<TrangaTask>());
logger.WriteLine("Tranga_CLI", "User Input");
Console.WriteLine($"Output folder path [{settings.downloadLocation}]:"); Console.WriteLine($"Output folder path [{settings.downloadLocation}]:");
string? tmpPath = Console.ReadLine(); string? tmpPath = Console.ReadLine();
while(tmpPath is null) while(tmpPath is null)
@ -60,119 +65,66 @@ public static class Tranga_Cli
} }
} while (key != ConsoleKey.Enter); } while (key != ConsoleKey.Enter);
settings.komga = new Komga(tmpUrl, tmpUser, tmpPass); settings.komga = new Komga(tmpUrl, tmpUser, tmpPass, logger);
} }
//For now only TaskManager mode
/*
Console.Write("Mode (D: Interactive only, T: TaskManager):");
ConsoleKeyInfo mode = Console.ReadKey();
while (mode.Key != ConsoleKey.D && mode.Key != ConsoleKey.T)
mode = Console.ReadKey();
Console.WriteLine();
if(mode.Key == ConsoleKey.D) logger.WriteLine("Tranga_CLI", "Loaded.");
DownloadNow(settings); TaskMode(settings, logger);
else if (mode.Key == ConsoleKey.T)
TaskMode(settings);*/
TaskMode(settings);
} }
private static void TaskMode(TaskManager.SettingsData settings) private static void TaskMode(TaskManager.SettingsData settings, Logger logger)
{ {
TaskManager taskManager = new TaskManager(settings); TaskManager taskManager = new (settings, logger);
ConsoleKey selection = ConsoleKey.NoName; ConsoleKey selection = PrintMenu(taskManager, settings.downloadLocation, logger);
int menu = 0; while (selection != ConsoleKey.Q)
while (selection != ConsoleKey.Escape && selection != ConsoleKey.Q)
{ {
switch (menu) switch (selection)
{ {
case 1: case ConsoleKey.L:
PrintTasks(taskManager.GetAllTasks()); PrintTasks(taskManager.GetAllTasks(), logger);
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
menu = 0;
break; break;
case 2: case ConsoleKey.C:
TrangaTask.Task task = SelectTask(); CreateTask(taskManager, settings, logger);
Connector? connector = null;
if(task != TrangaTask.Task.UpdateKomgaLibrary)
connector = SelectConnector(settings.downloadLocation, taskManager.GetAvailableConnectors().Values.ToArray());
Publication? publication = null;
if(task != TrangaTask.Task.UpdatePublications && task != TrangaTask.Task.UpdateKomgaLibrary)
publication = SelectPublication(connector!);
TimeSpan reoccurrence = SelectReoccurrence();
TrangaTask newTask = taskManager.AddTask(task, connector?.name, publication, reoccurrence, "en");
Console.WriteLine(newTask);
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
menu = 0;
break; break;
case 3: case ConsoleKey.D:
RemoveTask(taskManager); RemoveTask (taskManager, logger);
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
menu = 0;
break; break;
case 4: case ConsoleKey.E:
ExecuteTaskNow(taskManager); ExecuteTaskNow(taskManager, logger);
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
menu = 0;
break; break;
case 5: case ConsoleKey.S:
Console.WriteLine("Search-Query (Name):"); SearchTasks(taskManager, logger);
string? query = Console.ReadLine();
while (query is null || query.Length < 1)
query = Console.ReadLine();
PrintTasks(taskManager.GetAllTasks().Where(qTask =>
qTask.ToString().ToLower().Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray());
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
menu = 0;
break;
case 6:
PrintTasks(taskManager.GetAllTasks().Where(eTask => eTask.state == TrangaTask.ExecutionState.Running).ToArray());
Console.WriteLine("Press any key.");
Console.ReadKey();
menu = 0;
break; break;
default: case ConsoleKey.R:
selection = Menu(taskManager, settings.downloadLocation); PrintTasks(taskManager.GetAllTasks().Where(eTask => eTask.state == TrangaTask.ExecutionState.Running).ToArray(), logger);
switch (selection) Console.WriteLine("Press any key.");
{ Console.ReadKey();
case ConsoleKey.L: break;
menu = 1; case ConsoleKey.K:
break; PrintTasks(taskManager.GetAllTasks().Where(qTask => qTask.state is TrangaTask.ExecutionState.Enqueued).ToArray(), logger);
case ConsoleKey.C: Console.WriteLine("Press any key.");
menu = 2; Console.ReadKey();
break; break;
case ConsoleKey.D: case ConsoleKey.F:
menu = 3; ShowLastLoglines(logger);
break; Console.WriteLine("Press any key.");
case ConsoleKey.E: Console.ReadKey();
menu = 4;
break;
case ConsoleKey.U:
menu = 0;
break;
case ConsoleKey.S:
menu = 5;
break;
case ConsoleKey.R:
menu = 6;
break;
default:
menu = 0;
break;
}
break; break;
} }
selection = PrintMenu(taskManager, settings.downloadLocation, logger);
} }
logger.WriteLine("Tranga_CLI", "Exiting.");
if (taskManager.GetAllTasks().Any(task => task.state == TrangaTask.ExecutionState.Running)) if (taskManager.GetAllTasks().Any(task => task.state == TrangaTask.ExecutionState.Running))
{ {
Console.WriteLine("Force quit (Even with running tasks?) y/N"); Console.WriteLine("Force quit (Even with running tasks?) y/N");
@ -185,83 +137,197 @@ public static class Tranga_Cli
taskManager.Shutdown(false); taskManager.Shutdown(false);
} }
private static ConsoleKey Menu(TaskManager taskManager, string folderPath) private static ConsoleKey PrintMenu(TaskManager taskManager, string folderPath, Logger logger)
{ {
int taskCount = taskManager.GetAllTasks().Length; int taskCount = taskManager.GetAllTasks().Length;
int taskRunningCount = taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Running); int taskRunningCount = taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Running);
int taskEnqueuedCount = int taskEnqueuedCount =
taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Enqueued); taskManager.GetAllTasks().Count(task => task.state == TrangaTask.ExecutionState.Enqueued);
Console.Clear(); Console.Clear();
Console.WriteLine($"Download Folder: {folderPath} Tasks (Running/Queue/Total): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}"); Console.WriteLine($"Download Folder: {folderPath}");
Console.WriteLine("U: Update this Screen"); Console.WriteLine($"Tasks (Running/Queue/Total)): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}");
Console.WriteLine("L: List tasks");
Console.WriteLine("C: Create Task");
Console.WriteLine("D: Delete Task");
Console.WriteLine("E: Execute Task now");
Console.WriteLine("S: Search Task");
Console.WriteLine("R: Running Tasks");
Console.WriteLine("Q: Exit");
ConsoleKey selection = Console.ReadKey().Key;
Console.WriteLine(); Console.WriteLine();
Console.WriteLine($"{"C: Create Task",-30}{"L: List tasks",-30}");
Console.WriteLine($"{"D: Delete Task",-30}{"R: List Running Tasks", -30}");
Console.WriteLine($"{"E: Execute Task now",-30}{"S: Search Tasks", -30}");
Console.WriteLine($"{"",-30}{"K: List Task Queue", -30}");
//Console.WriteLine();
Console.WriteLine($"{"F: Show Log",-30}{"",-30}");
Console.WriteLine($"{"U: Update this Screen",-30}{"Q: Exit",-30}");
ConsoleKey selection = Console.ReadKey().Key;
logger.WriteLine("Tranga_CLI", $"Menu selection: {selection}");
return selection; return selection;
} }
private static void PrintTasks(TrangaTask[] tasks) private static void PrintTasks(TrangaTask[] tasks, Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Printing Tasks");
int taskCount = tasks.Length; int taskCount = tasks.Length;
int taskRunningCount = tasks.Count(task => task.state == TrangaTask.ExecutionState.Running); int taskRunningCount = tasks.Count(task => task.state == TrangaTask.ExecutionState.Running);
int taskEnqueuedCount = tasks.Count(task => task.state == TrangaTask.ExecutionState.Enqueued); int taskEnqueuedCount = tasks.Count(task => task.state == TrangaTask.ExecutionState.Enqueued);
Console.Clear(); Console.Clear();
int tIndex = 0; int tIndex = 0;
Console.WriteLine($"Tasks (Running/Queue/Total): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}"); Console.WriteLine($"Tasks (Running/Queue/Total): {taskRunningCount}/{taskEnqueuedCount}/{taskCount}");
string header =
$"{"",-5}{"Task",-20} | {"Last Executed",-20} | {"Reoccurrence",-12} | {"State",-10} | {"Connector",-15} | Publication/Manga";
Console.WriteLine(header);
Console.WriteLine(new string('-', header.Length));
foreach(TrangaTask trangaTask in tasks) foreach(TrangaTask trangaTask in tasks)
Console.WriteLine($"{tIndex++:000}: {trangaTask}"); Console.WriteLine($"{tIndex++:000}: {trangaTask}");
} }
private static void ExecuteTaskNow(TaskManager taskManager) private static void ShowLastLoglines(Logger logger)
{ {
Console.Clear();
logger.WriteLine("Tranga_CLI", "Menu: Show Log-lines");
Console.WriteLine("Enter q to abort");
Console.WriteLine($"Number of lines:");
string? chosenNumber = Console.ReadLine();
while(chosenNumber is null || chosenNumber.Length < 1)
chosenNumber = Console.ReadLine();
if (chosenNumber.Length == 1 && chosenNumber.ToLower() == "q")
{
Console.Clear();
Console.WriteLine("aborted.");
logger.WriteLine("Tranga_CLI", "aborted");
return;
}
try
{
string[] lines = logger.Tail(Convert.ToUInt32(chosenNumber));
Console.Clear();
foreach (string message in lines)
Console.Write(message);
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message}");
logger.WriteLine("Tranga_CLI", e.Message);
}
}
private static void CreateTask(TaskManager taskManager, TaskManager.SettingsData settings, Logger logger)
{
logger.WriteLine("Tranga_CLI", "Menu: Creating Task");
TrangaTask.Task? tmpTask = SelectTaskType(logger);
if (tmpTask is null)
return;
TrangaTask.Task task = (TrangaTask.Task)tmpTask!;
Connector? connector = null;
if (task != TrangaTask.Task.UpdateKomgaLibrary)
{
connector = SelectConnector(settings.downloadLocation, taskManager.GetAvailableConnectors().Values.ToArray(), logger);
if (connector is null)
return;
}
Publication? publication = null;
if (task != TrangaTask.Task.UpdatePublications && task != TrangaTask.Task.UpdateKomgaLibrary)
{
publication = SelectPublication(connector!, logger);
if (publication is null)
return;
}
TimeSpan reoccurrence = SelectReoccurrence(logger);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
TrangaTask newTask = taskManager.AddTask(task, connector?.name, publication, reoccurrence, "en");
Console.WriteLine(newTask);
}
private static void ExecuteTaskNow(TaskManager taskManager, Logger logger)
{
logger.WriteLine("Tranga_CLI", "Menu: Executing Task");
TrangaTask[] tasks = taskManager.GetAllTasks(); TrangaTask[] tasks = taskManager.GetAllTasks();
if (tasks.Length < 1) if (tasks.Length < 1)
{ {
Console.Clear(); Console.Clear();
Console.WriteLine("There are no available Tasks."); Console.WriteLine("There are no available Tasks.");
logger.WriteLine("Tranga_CLI", "No available Tasks.");
return; return;
} }
PrintTasks(tasks); PrintTasks(tasks, logger);
logger.WriteLine("Tranga_CLI", "Selecting Task to Execute");
Console.WriteLine("Enter q to abort");
Console.WriteLine($"Select Task (0-{tasks.Length - 1}):"); Console.WriteLine($"Select Task (0-{tasks.Length - 1}):");
string? selectedTask = Console.ReadLine(); string? selectedTask = Console.ReadLine();
while(selectedTask is null || selectedTask.Length < 1) while(selectedTask is null || selectedTask.Length < 1)
selectedTask = Console.ReadLine(); selectedTask = Console.ReadLine();
int selectedTaskIndex = Convert.ToInt32(selectedTask);
taskManager.ExecuteTaskNow(tasks[selectedTaskIndex]); if (selectedTask.Length == 1 && selectedTask.ToLower() == "q")
{
Console.Clear();
Console.WriteLine("aborted.");
logger.WriteLine("Tranga_CLI", "aborted");
return;
}
try
{
int selectedTaskIndex = Convert.ToInt32(selectedTask);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
taskManager.ExecuteTaskNow(tasks[selectedTaskIndex]);
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message}");
logger.WriteLine("Tranga_CLI", e.Message);
}
} }
private static void RemoveTask(TaskManager taskManager) private static void RemoveTask(TaskManager taskManager, Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Remove Task");
TrangaTask[] tasks = taskManager.GetAllTasks(); TrangaTask[] tasks = taskManager.GetAllTasks();
if (tasks.Length < 1) if (tasks.Length < 1)
{ {
Console.Clear(); Console.Clear();
Console.WriteLine("There are no available Tasks."); Console.WriteLine("There are no available Tasks.");
logger.WriteLine("Tranga_CLI", "No available Tasks");
return; return;
} }
PrintTasks(tasks); PrintTasks(tasks, logger);
logger.WriteLine("Tranga_CLI", "Selecting Task");
Console.WriteLine("Enter q to abort");
Console.WriteLine($"Select Task (0-{tasks.Length - 1}):"); Console.WriteLine($"Select Task (0-{tasks.Length - 1}):");
string? selectedTask = Console.ReadLine(); string? selectedTask = Console.ReadLine();
while(selectedTask is null || selectedTask.Length < 1) while(selectedTask is null || selectedTask.Length < 1)
selectedTask = Console.ReadLine(); selectedTask = Console.ReadLine();
int selectedTaskIndex = Convert.ToInt32(selectedTask);
taskManager.RemoveTask(tasks[selectedTaskIndex].task, tasks[selectedTaskIndex].connectorName, tasks[selectedTaskIndex].publication); if (selectedTask.Length == 1 && selectedTask.ToLower() == "q")
{
Console.Clear();
Console.WriteLine("aborted.");
logger.WriteLine("Tranga_CLI", "aborted.");
return;
}
try
{
int selectedTaskIndex = Convert.ToInt32(selectedTask);
logger.WriteLine("Tranga_CLI", "Sending Task to TaskManager");
taskManager.RemoveTask(tasks[selectedTaskIndex].task, tasks[selectedTaskIndex].connectorName, tasks[selectedTaskIndex].publication);
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message}");
logger.WriteLine("Tranga_CLI", e.Message);
}
} }
private static TrangaTask.Task SelectTask() private static TrangaTask.Task? SelectTaskType(Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Select TaskType");
Console.Clear(); Console.Clear();
string[] taskNames = Enum.GetNames<TrangaTask.Task>(); string[] taskNames = Enum.GetNames<TrangaTask.Task>();
@ -269,64 +335,87 @@ public static class Tranga_Cli
Console.WriteLine("Available Tasks:"); Console.WriteLine("Available Tasks:");
foreach (string taskName in taskNames) foreach (string taskName in taskNames)
Console.WriteLine($"{tIndex++}: {taskName}"); Console.WriteLine($"{tIndex++}: {taskName}");
Console.WriteLine("Enter q to abort");
Console.WriteLine($"Select Task (0-{taskNames.Length - 1}):"); Console.WriteLine($"Select Task (0-{taskNames.Length - 1}):");
string? selectedTask = Console.ReadLine(); string? selectedTask = Console.ReadLine();
while(selectedTask is null || selectedTask.Length < 1) while(selectedTask is null || selectedTask.Length < 1)
selectedTask = Console.ReadLine(); selectedTask = Console.ReadLine();
int selectedTaskIndex = Convert.ToInt32(selectedTask);
string selectedTaskName = taskNames[selectedTaskIndex]; if (selectedTask.Length == 1 && selectedTask.ToLower() == "q")
return Enum.Parse<TrangaTask.Task>(selectedTaskName); {
Console.Clear();
Console.WriteLine("aborted.");
logger.WriteLine("Tranga_CLI", "aborted.");
return null;
}
try
{
int selectedTaskIndex = Convert.ToInt32(selectedTask);
string selectedTaskName = taskNames[selectedTaskIndex];
return Enum.Parse<TrangaTask.Task>(selectedTaskName);
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message}");
logger.WriteLine("Tranga_CLI", e.Message);
}
return null;
} }
private static TimeSpan SelectReoccurrence() private static TimeSpan SelectReoccurrence(Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Select Reoccurrence");
Console.WriteLine("Select reoccurrence Timer (Format hh:mm:ss):"); Console.WriteLine("Select reoccurrence Timer (Format hh:mm:ss):");
return TimeSpan.Parse(Console.ReadLine()!, new CultureInfo("en-US")); return TimeSpan.Parse(Console.ReadLine()!, new CultureInfo("en-US"));
} }
private static void DownloadNow(TaskManager.SettingsData settings) private static Connector? SelectConnector(string folderPath, Connector[] connectors, Logger logger)
{
Connector connector = SelectConnector(settings.downloadLocation, new Connector[]{new MangaDex(settings.downloadLocation)});
Publication publication = SelectPublication(connector);
Chapter[] downloadChapters = SelectChapters(connector, publication);
if (downloadChapters.Length > 0)
{
connector.DownloadCover(publication);
connector.SaveSeriesInfo(publication);
}
foreach (Chapter chapter in downloadChapters)
{
Console.WriteLine($"Downloading {publication.sortName} V{chapter.volumeNumber}C{chapter.chapterNumber}");
connector.DownloadChapter(publication, chapter);
}
}
private static Connector SelectConnector(string folderPath, Connector[] connectors)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Select Connector");
Console.Clear(); Console.Clear();
int cIndex = 0; int cIndex = 0;
Console.WriteLine("Connectors:"); Console.WriteLine("Connectors:");
foreach (Connector connector in connectors) foreach (Connector connector in connectors)
Console.WriteLine($"{cIndex++}: {connector.name}"); Console.WriteLine($"{cIndex++}: {connector.name}");
Console.WriteLine("Enter q to abort");
Console.WriteLine($"Select Connector (0-{connectors.Length - 1}):"); Console.WriteLine($"Select Connector (0-{connectors.Length - 1}):");
string? selectedConnector = Console.ReadLine(); string? selectedConnector = Console.ReadLine();
while(selectedConnector is null || selectedConnector.Length < 1) while(selectedConnector is null || selectedConnector.Length < 1)
selectedConnector = Console.ReadLine(); selectedConnector = Console.ReadLine();
int selectedConnectorIndex = Convert.ToInt32(selectedConnector);
if (selectedConnector.Length == 1 && selectedConnector.ToLower() == "q")
{
Console.Clear();
Console.WriteLine("aborted.");
logger.WriteLine("Tranga_CLI", "aborted.");
return null;
}
return connectors[selectedConnectorIndex]; try
{
int selectedConnectorIndex = Convert.ToInt32(selectedConnector);
return connectors[selectedConnectorIndex];
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message}");
logger.WriteLine("Tranga_CLI", e.Message);
}
return null;
} }
private static Publication SelectPublication(Connector connector) private static Publication? SelectPublication(Connector connector, Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Select Publication");
Console.Clear(); Console.Clear();
Console.WriteLine($"Connector: {connector.name}"); Console.WriteLine($"Connector: {connector.name}");
Console.WriteLine("Publication search query (leave empty for all):"); Console.WriteLine("Publication search query (leave empty for all):");
@ -338,52 +427,45 @@ public static class Tranga_Cli
Console.WriteLine("Publications:"); Console.WriteLine("Publications:");
foreach(Publication publication in publications) foreach(Publication publication in publications)
Console.WriteLine($"{pIndex++}: {publication.sortName}"); Console.WriteLine($"{pIndex++}: {publication.sortName}");
Console.WriteLine($"Select publication to Download (0-{publications.Length - 1}):");
string? selected = Console.ReadLine(); Console.WriteLine("Enter q to abort");
while(selected is null || selected.Length < 1) Console.WriteLine($"Select publication to Download (0-{publications.Length - 1}):");
selected = Console.ReadLine();
return publications[Convert.ToInt32(selected)]; string? selectedPublication = Console.ReadLine();
while(selectedPublication is null || selectedPublication.Length < 1)
selectedPublication = Console.ReadLine();
if (selectedPublication.Length == 1 && selectedPublication.ToLower() == "q")
{
Console.Clear();
Console.WriteLine("aborted.");
logger.WriteLine("Tranga_CLI", "aborted.");
return null;
}
try
{
int selectedPublicationIndex = Convert.ToInt32(selectedPublication);
return publications[selectedPublicationIndex];
}
catch (Exception e)
{
Console.WriteLine($"Exception: {e.Message}");
logger.WriteLine("Tranga_CLI", e.Message);
}
return null;
} }
private static Chapter[] SelectChapters(Connector connector, Publication publication) private static void SearchTasks(TaskManager taskManager, Logger logger)
{ {
logger.WriteLine("Tranga_CLI", "Menu: Search task");
Console.Clear(); Console.Clear();
Console.WriteLine($"Connector: {connector.name} Publication: {publication.sortName}"); Console.WriteLine("Enter search query:");
Chapter[] chapters = connector.GetChapters(publication, "en"); string? query = Console.ReadLine();
while (query is null || query.Length < 4)
int cIndex = 0; query = Console.ReadLine();
Console.WriteLine("Chapters:"); PrintTasks(taskManager.GetAllTasks().Where(qTask =>
foreach (Chapter ch in chapters) qTask.ToString().ToLower().Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray(), logger);
{
string name = cIndex.ToString();
if (ch.name is not null && ch.name.Length > 0)
name = ch.name;
else if (ch.chapterNumber is not null && ch.chapterNumber.Length > 0)
name = ch.chapterNumber;
Console.WriteLine($"{cIndex++}: {name}");
}
Console.WriteLine($"Select Chapters to download (0-{chapters.Length - 1}) [range x-y or 'a' for all]: ");
string? selected = Console.ReadLine();
while(selected is null || selected.Length < 1)
selected = Console.ReadLine();
int start = 0;
int end;
if (selected == "a")
end = chapters.Length - 1;
else if (selected.Contains('-'))
{
string[] split = selected.Split('-');
start = Convert.ToInt32(split[0]);
end = Convert.ToInt32(split[1]);
}
else
{
start = Convert.ToInt32(selected);
end = Convert.ToInt32(selected);
}
return chapters.Skip(start).Take((end + 1)-start).ToArray();
} }
} }

View File

@ -6,6 +6,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-CLI", "Tranga-CLI\Tr
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-API", "Tranga-API\Tranga-API.csproj", "{6284C936-4E90-486B-BC46-0AFAD85AD8EE}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tranga-API", "Tranga-API\Tranga-API.csproj", "{6284C936-4E90-486B-BC46-0AFAD85AD8EE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging", "Logging\Logging.csproj", "{415BE889-BB7D-426F-976F-8D977876A462}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -24,5 +26,9 @@ Global
{6284C936-4E90-486B-BC46-0AFAD85AD8EE}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{6284C936-4E90-486B-BC46-0AFAD85AD8EE}.Release|Any CPU.Build.0 = 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.Build.0 = Debug|Any CPU
{415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.ActiveCfg = Release|Any CPU
{415BE889-BB7D-426F-976F-8D977876A462}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -1,6 +1,7 @@
using System.IO.Compression; using System.IO.Compression;
using System.Net; using System.Net;
using System.Xml.Linq; using System.Xml.Linq;
using Logging;
namespace Tranga; namespace Tranga;
@ -13,10 +14,13 @@ 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; }
protected Connector(string downloadLocation, uint downloadDelay) protected Logger? logger;
protected Connector(string downloadLocation, uint downloadDelay, Logger? logger)
{ {
this.downloadLocation = downloadLocation; this.downloadLocation = downloadLocation;
this.downloadClient = new DownloadClient(downloadDelay); this.downloadClient = new DownloadClient(downloadDelay);
this.logger = 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)
@ -58,6 +62,7 @@ public abstract class Connector
/// <param name="publication">Publication to save series.json for</param> /// <param name="publication">Publication to save series.json for</param>
public void SaveSeriesInfo(Publication publication) public void SaveSeriesInfo(Publication publication)
{ {
logger?.WriteLine(this.GetType().ToString(), $"Saving series.json for {publication.sortName}");
//Check if Publication already has a Folder and a series.json //Check if Publication already has a Folder and a series.json
string publicationFolder = Path.Join(downloadLocation, publication.folderName); string publicationFolder = Path.Join(downloadLocation, publication.folderName);
if(!Directory.Exists(publicationFolder)) if(!Directory.Exists(publicationFolder))
@ -73,8 +78,9 @@ public abstract class Connector
/// See ComicInfo.xml /// See ComicInfo.xml
/// </summary> /// </summary>
/// <returns>XML-string</returns> /// <returns>XML-string</returns>
protected static string CreateComicInfo(Publication publication, Chapter chapter) protected static string CreateComicInfo(Publication publication, Chapter chapter, Logger? logger)
{ {
logger?.WriteLine("Connector", $"Creating ComicInfo.Xml for {publication.sortName} Chapter {chapter.sortNumber}");
XElement comicInfo = new XElement("ComicInfo", XElement comicInfo = new XElement("ComicInfo",
new XElement("Tags", string.Join(',',publication.tags)), new XElement("Tags", string.Join(',',publication.tags)),
new XElement("LanguageISO", publication.originalLanguage), new XElement("LanguageISO", publication.originalLanguage),
@ -124,8 +130,9 @@ 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, string? comicInfoPath = null) protected static void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, DownloadClient downloadClient, Logger? logger, string? comicInfoPath = null)
{ {
logger?.WriteLine("Connector", "Downloading Images");
//Check if Publication Directory already exists //Check if Publication Directory already exists
string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar); string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar);
string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray()); string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray());
@ -151,6 +158,7 @@ public abstract class Connector
if(comicInfoPath is not null) if(comicInfoPath is not null)
File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml")); File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml"));
logger?.WriteLine("Connector", "Creating archive");
//ZIP-it and ship-it //ZIP-it and ship-it
ZipFile.CreateFromDirectory(tempFolder, fullPath); ZipFile.CreateFromDirectory(tempFolder, fullPath);
Directory.Delete(tempFolder, true); //Cleanup Directory.Delete(tempFolder, true); //Cleanup

View File

@ -2,24 +2,26 @@
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Logging;
namespace Tranga.Connectors; namespace Tranga.Connectors;
public class MangaDex : Connector public class MangaDex : Connector
{ {
public override string name { get; } public override string name { get; }
public MangaDex(string downloadLocation, uint downloadDelay) : base(downloadLocation, downloadDelay) public MangaDex(string downloadLocation, uint downloadDelay, Logger? logger) : base(downloadLocation, downloadDelay, logger)
{ {
name = "MangaDex"; name = "MangaDex";
} }
public MangaDex(string downloadLocation) : base(downloadLocation, 750) public MangaDex(string downloadLocation, Logger? logger) : base(downloadLocation, 750, logger)
{ {
name = "MangaDex"; name = "MangaDex";
} }
public override Publication[] GetPublications(string publicationTitle = "") public override Publication[] GetPublications(string publicationTitle = "")
{ {
logger?.WriteLine(this.GetType().ToString(), $"Getting Publications (title={publicationTitle})");
const int limit = 100; //How many values we want returned at once const int limit = 100; //How many values we want returned at once
int offset = 0; //"Page" int offset = 0; //"Page"
int total = int.MaxValue; //How many total results are there, is updated on first request int total = int.MaxValue; //How many total results are there, is updated on first request
@ -126,6 +128,7 @@ public class MangaDex : Connector
public override Chapter[] GetChapters(Publication publication, string language = "") public override Chapter[] GetChapters(Publication publication, string language = "")
{ {
logger?.WriteLine(this.GetType().ToString(), $"Getting Chapters {publication.sortName} (language={language})");
const int limit = 100; //How many values we want returned at once const int limit = 100; //How many values we want returned at once
int offset = 0; //"Page" int offset = 0; //"Page"
int total = int.MaxValue; //How many total results are there, is updated on first request int total = int.MaxValue; //How many total results are there, is updated on first request
@ -180,6 +183,7 @@ public class MangaDex : Connector
public override void DownloadChapter(Publication publication, Chapter chapter) public override void DownloadChapter(Publication publication, Chapter chapter)
{ {
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'");
@ -198,14 +202,15 @@ public class MangaDex : Connector
imageUrls.Add($"{baseUrl}/data/{hash}/{image!.GetValue<string>()}"); imageUrls.Add($"{baseUrl}/data/{hash}/{image!.GetValue<string>()}");
string comicInfoPath = Path.GetTempFileName(); string comicInfoPath = Path.GetTempFileName();
File.WriteAllText(comicInfoPath, CreateComicInfo(publication, chapter)); File.WriteAllText(comicInfoPath, CreateComicInfo(publication, chapter, logger));
//Download Chapter-Images //Download Chapter-Images
DownloadChapterImages(imageUrls.ToArray(), CreateFullFilepath(publication, chapter), downloadClient, comicInfoPath); DownloadChapterImages(imageUrls.ToArray(), CreateFullFilepath(publication, chapter), downloadClient, logger, comicInfoPath);
} }
public override void DownloadCover(Publication publication) public override void DownloadCover(Publication publication)
{ {
logger?.WriteLine(this.GetType().ToString(), $"Download cover {publication.sortName}");
//Check if Publication already has a Folder and cover //Check if Publication already has a Folder and cover
string publicationFolder = Path.Join(downloadLocation, publication.folderName); string publicationFolder = Path.Join(downloadLocation, publication.folderName);
if(!Directory.Exists(publicationFolder)) if(!Directory.Exists(publicationFolder))

View File

@ -1,5 +1,6 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using JsonSerializer = System.Text.Json.JsonSerializer; using JsonSerializer = System.Text.Json.JsonSerializer;
@ -13,23 +14,27 @@ public class Komga
{ {
public string baseUrl { get; } public string baseUrl { get; }
public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems public string auth { get; } //Base64 encoded, if you use your password everywhere, you have problems
private Logger? logger;
/// <param name="baseUrl">Base-URL of Komga instance, no trailing slashes(/)</param> /// <param name="baseUrl">Base-URL of Komga instance, no trailing slashes(/)</param>
/// <param name="username">Komga Username</param> /// <param name="username">Komga Username</param>
/// <param name="password">Komga password, will be base64 encoded. yea</param> /// <param name="password">Komga password, will be base64 encoded. yea</param>
public Komga(string baseUrl, string username, string password) public Komga(string baseUrl, string username, string password, Logger? logger)
{ {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.auth = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}")); this.auth = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}"));
this.logger = logger;
} }
/// <param name="baseUrl">Base-URL of Komga instance, no trailing slashes(/)</param> /// <param name="baseUrl">Base-URL of Komga instance, no trailing slashes(/)</param>
/// <param name="auth">Base64 string of username and password (username):(password)</param> /// <param name="auth">Base64 string of username and password (username):(password)</param>
[JsonConstructor] [JsonConstructor]
public Komga(string baseUrl, string auth) public Komga(string baseUrl, string auth, Logger? logger)
{ {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.auth = auth; this.auth = auth;
this.logger = logger;
} }
/// <summary> /// <summary>
@ -38,6 +43,7 @@ public class Komga
/// <returns>Array of KomgaLibraries</returns> /// <returns>Array of KomgaLibraries</returns>
public KomgaLibrary[] GetLibraries() public KomgaLibrary[] GetLibraries()
{ {
logger?.WriteLine(this.GetType().ToString(), $"Getting Libraries");
Stream data = NetClient.MakeRequest($"{baseUrl}/api/v1/libraries", auth); Stream data = NetClient.MakeRequest($"{baseUrl}/api/v1/libraries", auth);
JsonArray? result = JsonSerializer.Deserialize<JsonArray>(data); JsonArray? result = JsonSerializer.Deserialize<JsonArray>(data);
if (result is null) if (result is null)
@ -63,6 +69,7 @@ public class Komga
/// <returns>true if successful</returns> /// <returns>true if successful</returns>
public bool UpdateLibrary(string libraryId) public bool UpdateLibrary(string libraryId)
{ {
logger?.WriteLine(this.GetType().ToString(), $"Updating Libraries");
return NetClient.MakePost($"{baseUrl}/api/v1/libraries/{libraryId}/scan", auth); return NetClient.MakePost($"{baseUrl}/api/v1/libraries/{libraryId}/scan", auth);
} }

View File

@ -1,4 +1,6 @@
namespace Tranga; using Logging;
namespace Tranga;
/// <summary> /// <summary>
/// Executes TrangaTasks /// Executes TrangaTasks
@ -13,13 +15,18 @@ public static class TaskExecutor
/// <param name="taskManager">Parent</param> /// <param name="taskManager">Parent</param>
/// <param name="trangaTask">Task to execute</param> /// <param name="trangaTask">Task to execute</param>
/// <param name="chapterCollection">Current chapterCollection to update</param> /// <param name="chapterCollection">Current chapterCollection to update</param>
/// <param name="logger"></param>
/// <exception cref="ArgumentException">Is thrown when there is no Connector available with the name of the TrangaTask.connectorName</exception> /// <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) public static void Execute(TaskManager taskManager, TrangaTask trangaTask, Dictionary<Publication, List<Chapter>> chapterCollection, 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)
{
logger?.WriteLine("TaskExecutor", $"Task already running {trangaTask}");
return; return;
}
trangaTask.state = TrangaTask.ExecutionState.Running; trangaTask.state = TrangaTask.ExecutionState.Running;
logger?.WriteLine("TaskExecutor", $"Starting Task {trangaTask}");
//Connector is not needed for all tasks //Connector is not needed for all tasks
Connector? connector = null; Connector? connector = null;
@ -42,9 +49,10 @@ public static class TaskExecutor
UpdateKomgaLibrary(taskManager); UpdateKomgaLibrary(taskManager);
break; break;
} }
trangaTask.state = TrangaTask.ExecutionState.Waiting; logger?.WriteLine("TaskExecutor", $"Task finished! {trangaTask}");
trangaTask.lastExecuted = DateTime.Now; trangaTask.lastExecuted = DateTime.Now;
trangaTask.state = TrangaTask.ExecutionState.Waiting;
} }
/// <summary> /// <summary>

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json; using Logging;
using Newtonsoft.Json;
using Tranga.Connectors; using Tranga.Connectors;
namespace Tranga; namespace Tranga;
@ -15,20 +16,23 @@ public class TaskManager
private readonly Connector[] _connectors; private readonly Connector[] _connectors;
private Dictionary<Connector, List<TrangaTask>> tasksToExecute = new(); private Dictionary<Connector, List<TrangaTask>> tasksToExecute = new();
private string downloadLocation { get; } private string downloadLocation { get; }
private Logger? logger { get; }
public Komga? komga { get; private set; } public Komga? komga { get; }
/// <param name="folderPath">Local path to save data (Manga) to</param> /// <param name="folderPath">Local path to save data (Manga) to</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>
public TaskManager(string folderPath, string? komgaBaseUrl = null, string? komgaUsername = null, string? komgaPassword = null) /// <param name="logger"></param>
public TaskManager(string folderPath, string? komgaBaseUrl = null, string? komgaUsername = null, string? komgaPassword = null, Logger? logger = null)
{ {
this.logger = logger;
this.downloadLocation = folderPath; this.downloadLocation = folderPath;
if (komgaBaseUrl != null && komgaUsername != null && komgaPassword != null) if (komgaBaseUrl != null && komgaUsername != null && komgaPassword != null)
this.komga = new Komga(komgaBaseUrl, komgaUsername, komgaPassword); this.komga = new Komga(komgaBaseUrl, komgaUsername, komgaPassword, logger);
this._connectors = new Connector[]{ new MangaDex(folderPath) }; this._connectors = new Connector[]{ new MangaDex(folderPath, logger) };
foreach(Connector cConnector in this._connectors) foreach(Connector cConnector in this._connectors)
tasksToExecute.Add(cConnector, new List<TrangaTask>()); tasksToExecute.Add(cConnector, new List<TrangaTask>());
_allTasks = new HashSet<TrangaTask>(); _allTasks = new HashSet<TrangaTask>();
@ -37,9 +41,10 @@ public class TaskManager
taskChecker.Start(); taskChecker.Start();
} }
public TaskManager(SettingsData settings) public TaskManager(SettingsData settings, Logger? logger = null)
{ {
this._connectors = new Connector[]{ new MangaDex(settings.downloadLocation) }; this.logger = logger;
this._connectors = new Connector[]{ new MangaDex(settings.downloadLocation, logger) };
foreach(Connector cConnector in this._connectors) foreach(Connector cConnector in this._connectors)
tasksToExecute.Add(cConnector, new List<TrangaTask>()); tasksToExecute.Add(cConnector, new List<TrangaTask>());
this.downloadLocation = settings.downloadLocation; this.downloadLocation = settings.downloadLocation;
@ -55,13 +60,16 @@ public class TaskManager
/// </summary> /// </summary>
private void TaskCheckerThread() private void TaskCheckerThread()
{ {
logger?.WriteLine(this.GetType().ToString(), "Starting TaskCheckerThread.");
while (_continueRunning) while (_continueRunning)
{ {
//Check if previous tasks have finished and execute new tasks //Check if previous tasks have finished and execute new tasks
foreach (KeyValuePair<Connector, List<TrangaTask>> connectorTaskQueue in tasksToExecute) foreach (KeyValuePair<Connector, List<TrangaTask>> connectorTaskQueue in tasksToExecute)
{ {
connectorTaskQueue.Value.RemoveAll(task => task.state == TrangaTask.ExecutionState.Waiting); if(connectorTaskQueue.Value.RemoveAll(task => task.state == TrangaTask.ExecutionState.Waiting) > 0)
if (connectorTaskQueue.Value.Count > 0 && !connectorTaskQueue.Value.All(task => task.state is TrangaTask.ExecutionState.Running or TrangaTask.ExecutionState.Waiting)) ExportData(Directory.GetCurrentDirectory());
if (connectorTaskQueue.Value.Count > 0 && connectorTaskQueue.Value.All(task => task.state is TrangaTask.ExecutionState.Enqueued))
ExecuteTaskNow(connectorTaskQueue.Value.First()); ExecuteTaskNow(connectorTaskQueue.Value.First());
} }
@ -74,6 +82,7 @@ public class TaskManager
ExecuteTaskNow(task); ExecuteTaskNow(task);
else else
{ {
logger?.WriteLine(this.GetType().ToString(), $"Task due: {task}");
tasksToExecute[GetConnector(task.connectorName!)].Add(task); tasksToExecute[GetConnector(task.connectorName!)].Add(task);
} }
} }
@ -90,9 +99,10 @@ public class TaskManager
if (!this._allTasks.Contains(task)) if (!this._allTasks.Contains(task))
return; return;
logger?.WriteLine(this.GetType().ToString(), $"Forcing Execution: {task}");
Task t = new Task(() => Task t = new Task(() =>
{ {
TaskExecutor.Execute(this, task, this._chapterCollection); TaskExecutor.Execute(this, task, this._chapterCollection, logger);
}); });
t.Start(); t.Start();
} }
@ -109,6 +119,7 @@ public class TaskManager
public TrangaTask AddTask(TrangaTask.Task task, string? connectorName, Publication? publication, TimeSpan reoccurrence, public TrangaTask AddTask(TrangaTask.Task task, string? connectorName, Publication? publication, TimeSpan reoccurrence,
string language = "") string language = "")
{ {
logger?.WriteLine(this.GetType().ToString(), $"Adding new Task {task} {connectorName} {publication?.sortName}");
if (task != TrangaTask.Task.UpdateKomgaLibrary && connectorName is null) if (task != TrangaTask.Task.UpdateKomgaLibrary && 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}");
@ -142,6 +153,7 @@ public class TaskManager
_allTasks.Add(newTask); _allTasks.Add(newTask);
} }
} }
logger?.WriteLine(this.GetType().ToString(), $"Added new Task {newTask.ToString()}");
ExportData(Directory.GetCurrentDirectory()); ExportData(Directory.GetCurrentDirectory());
return newTask; return newTask;
@ -155,14 +167,23 @@ public class TaskManager
/// <param name="publication">Publication that was used</param> /// <param name="publication">Publication that was used</param>
public void RemoveTask(TrangaTask.Task task, string? connectorName, Publication? publication) public void RemoveTask(TrangaTask.Task task, string? connectorName, Publication? publication)
{ {
logger?.WriteLine(this.GetType().ToString(), $"Removing Task {task} {publication?.sortName}");
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}");
}
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
_allTasks.RemoveWhere(trangaTask => {
if(_allTasks.RemoveWhere(trangaTask =>
trangaTask.task == task && trangaTask.connectorName == connectorName && trangaTask.task == task && trangaTask.connectorName == connectorName &&
trangaTask.publication?.downloadUrl == publication?.downloadUrl); trangaTask.publication?.downloadUrl == publication?.downloadUrl) > 0)
logger?.WriteLine(this.GetType().ToString(), $"Removed Task {task} {publication?.sortName} {publication?.downloadUrl}.");
else
logger?.WriteLine(this.GetType().ToString(), $"No Task {task} {publication?.sortName} {publication?.downloadUrl} could be found.");
}
ExportData(Directory.GetCurrentDirectory()); ExportData(Directory.GetCurrentDirectory());
} }
@ -191,8 +212,10 @@ public class TaskManager
/// </summary> /// </summary>
/// <param name="connectorName">Connector-name (exact)</param> /// <param name="connectorName">Connector-name (exact)</param>
/// <exception cref="Exception">If Connector is not available</exception> /// <exception cref="Exception">If Connector is not available</exception>
public Connector GetConnector(string connectorName) public Connector GetConnector(string? connectorName)
{ {
if(connectorName is null)
throw new Exception($"connectorName can not be null");
Connector? ret = this._connectors.FirstOrDefault(connector => connector.name == connectorName); Connector? ret = this._connectors.FirstOrDefault(connector => connector.name == connectorName);
if (ret is null) if (ret is null)
throw new Exception($"Connector {connectorName} is not an available Connector."); throw new Exception($"Connector {connectorName} is not an available Connector.");
@ -205,6 +228,7 @@ public class TaskManager
/// <param name="force">If force is true, tasks are aborted.</param> /// <param name="force">If force is true, tasks are aborted.</param>
public void Shutdown(bool force = false) public void Shutdown(bool force = false)
{ {
logger?.WriteLine(this.GetType().ToString(), $"Shutting down (forced={force})");
_continueRunning = false; _continueRunning = false;
ExportData(Directory.GetCurrentDirectory()); ExportData(Directory.GetCurrentDirectory());
@ -214,6 +238,7 @@ public class TaskManager
//Wait for tasks to finish //Wait for tasks to finish
while(_allTasks.Any(task => task.state is TrangaTask.ExecutionState.Running or TrangaTask.ExecutionState.Enqueued)) while(_allTasks.Any(task => task.state is TrangaTask.ExecutionState.Running or TrangaTask.ExecutionState.Enqueued))
Thread.Sleep(10); Thread.Sleep(10);
logger?.WriteLine(this.GetType().ToString(), "Tasks finished. Bye!");
Environment.Exit(0); Environment.Exit(0);
} }
@ -239,10 +264,12 @@ public class TaskManager
/// <param name="exportFolderPath">Folder path, filename will be data.json</param> /// <param name="exportFolderPath">Folder path, filename will be data.json</param>
private void ExportData(string exportFolderPath) private void ExportData(string exportFolderPath)
{ {
logger?.WriteLine(this.GetType().ToString(), $"Exporting data to data.json");
SettingsData data = new SettingsData(this.downloadLocation, this.komga, this._allTasks); SettingsData data = new SettingsData(this.downloadLocation, this.komga, this._allTasks);
string exportPath = Path.Join(exportFolderPath, "data.json"); string exportPath = Path.Join(exportFolderPath, "data.json");
string serializedData = JsonConvert.SerializeObject(data); string serializedData = JsonConvert.SerializeObject(data);
File.Delete(exportPath);
File.WriteAllText(exportPath, serializedData); File.WriteAllText(exportPath, serializedData);
} }

View File

@ -10,4 +10,8 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
</ItemGroup>
</Project> </Project>

View File

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