10 Commits
v0.1 ... v0.2

7 changed files with 111 additions and 30 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
Has a interactive CLI-Version as well as API-Version (no documentation yet).
Only one Connector so far: MangaDex.org (Timeout between requests 750ms)
Can automatically download new Chapters every given time-period.

View File

@ -37,7 +37,7 @@ public static class Tranga_Cli
switch (menu) switch (menu)
{ {
case 1: case 1:
PrintTasks(taskManager); PrintTasks(taskManager.GetAllTasks());
Console.WriteLine("Press any key."); Console.WriteLine("Press any key.");
Console.ReadKey(); Console.ReadKey();
menu = 0; menu = 0;
@ -61,8 +61,14 @@ public static class Tranga_Cli
Console.ReadKey(); Console.ReadKey();
menu = 0; menu = 0;
break; break;
case 4:
ExecuteTask(taskManager);
Console.WriteLine("Press any key.");
Console.ReadKey();
menu = 0;
break;
default: default:
selection = Menu(folderPath); selection = Menu(taskManager, folderPath);
switch (selection) switch (selection)
{ {
case ConsoleKey.L: case ConsoleKey.L:
@ -74,6 +80,9 @@ public static class Tranga_Cli
case ConsoleKey.D: case ConsoleKey.D:
menu = 3; menu = 3;
break; break;
case ConsoleKey.E:
menu = 4;
break;
default: default:
menu = 0; menu = 0;
break; break;
@ -81,40 +90,65 @@ public static class Tranga_Cli
break; break;
} }
} }
taskManager.Shutdown();
if (taskManager.GetAllTasks().Any(task => task.isBeingExecuted))
{
Console.WriteLine("Force quit (Even with running tasks?) y/N");
selection = Console.ReadKey().Key;
taskManager.Shutdown(selection == ConsoleKey.Y);
}else
taskManager.Shutdown(false);
} }
private static ConsoleKey Menu(string folderPath) private static ConsoleKey Menu(TaskManager taskManager, string folderPath)
{ {
int taskCount = taskManager.GetAllTasks().Length;
int taskRunningCount = taskManager.GetAllTasks().Count(task => task.isBeingExecuted);
Console.Clear(); Console.Clear();
Console.WriteLine($"Download Folder: {folderPath}"); Console.WriteLine($"Download Folder: {folderPath} Tasks (Running/Total): {taskRunningCount}/{taskCount}");
Console.WriteLine("Select Option:"); Console.WriteLine("Select Option:");
Console.WriteLine("L: List tasks"); Console.WriteLine("L: List tasks");
Console.WriteLine("C: Create Task"); Console.WriteLine("C: Create Task");
Console.WriteLine("D: Delete Task"); Console.WriteLine("D: Delete Task");
Console.WriteLine("E: Execute Task now");
Console.WriteLine("Q: Exit with saving"); Console.WriteLine("Q: Exit with saving");
ConsoleKey selection = Console.ReadKey().Key; ConsoleKey selection = Console.ReadKey().Key;
Console.WriteLine(); Console.WriteLine();
return selection; return selection;
} }
private static int PrintTasks(TaskManager taskManager) private static void PrintTasks(TrangaTask[] tasks)
{ {
int taskCount = tasks.Length;
int taskRunningCount = tasks.Count(task => task.isBeingExecuted);
Console.Clear(); Console.Clear();
TrangaTask[] tasks = taskManager.GetAllTasks();
int tIndex = 0; int tIndex = 0;
Console.WriteLine("Tasks:"); Console.WriteLine($"Tasks (Running/Total): {taskRunningCount}/{taskCount}");
foreach(TrangaTask trangaTask in tasks) foreach(TrangaTask trangaTask in tasks)
Console.WriteLine($"{tIndex++}: {trangaTask.task} - {trangaTask.reoccurrence} - {trangaTask.publication?.sortName} - {trangaTask.connectorName}"); Console.WriteLine($"{tIndex++}: {trangaTask.task} - {trangaTask.reoccurrence} - {trangaTask.publication?.sortName} - {trangaTask.connectorName} - {trangaTask.lastExecuted} - {(trangaTask.isBeingExecuted ? "Running" : "Waiting")}");
return tasks.Length; }
private static void ExecuteTask(TaskManager taskManager)
{
TrangaTask[] tasks = taskManager.GetAllTasks();
PrintTasks(tasks);
Console.WriteLine($"Select Task (0-{tasks.Length - 1}):");
string? selectedTask = Console.ReadLine();
while(selectedTask is null || selectedTask.Length < 1)
selectedTask = Console.ReadLine();
int selectedTaskIndex = Convert.ToInt32(selectedTask);
taskManager.ExecuteTaskNow(tasks[selectedTaskIndex]);
} }
private static void RemoveTask(TaskManager taskManager) private static void RemoveTask(TaskManager taskManager)
{ {
int length = PrintTasks(taskManager);
TrangaTask[] tasks = taskManager.GetAllTasks(); TrangaTask[] tasks = taskManager.GetAllTasks();
Console.WriteLine($"Select Task (0-{length - 1}):"); PrintTasks(tasks);
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)

View File

@ -20,6 +20,15 @@ public abstract class Connector
protected void DownloadChapter(string[] imageUrls, string saveArchiveFilePath) protected void DownloadChapter(string[] imageUrls, string saveArchiveFilePath)
{ {
string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar);
string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray());
if (!Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath);
string fullPath = $"{saveArchiveFilePath}.cbz";
if (File.Exists(fullPath))
return;
string tempFolder = Path.GetTempFileName(); string tempFolder = Path.GetTempFileName();
File.Delete(tempFolder); File.Delete(tempFolder);
Directory.CreateDirectory(tempFolder); Directory.CreateDirectory(tempFolder);
@ -31,14 +40,7 @@ public abstract class Connector
string extension = split[split.Length - 1]; string extension = split[split.Length - 1];
DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}")); DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"));
} }
string[] splitPath = saveArchiveFilePath.Split(Path.DirectorySeparatorChar);
string directoryPath = Path.Combine(splitPath.Take(splitPath.Length - 1).ToArray());
if (!Directory.Exists(directoryPath))
Directory.CreateDirectory(directoryPath);
string fullPath = $"{saveArchiveFilePath}.cbz";
File.Delete(fullPath);
ZipFile.CreateFromDirectory(tempFolder, fullPath); ZipFile.CreateFromDirectory(tempFolder, fullPath);
} }

View File

@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.Net;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
@ -21,7 +22,11 @@ public class MangaDex : Connector
HashSet<Publication> publications = new(); HashSet<Publication> publications = new();
while (offset < total) while (offset < total)
{ {
DownloadClient.RequestResult requestResult = _downloadClient.MakeRequest($"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}"); DownloadClient.RequestResult requestResult =
_downloadClient.MakeRequest(
$"https://api.mangadex.org/manga?limit={limit}&title={publicationTitle}&offset={offset}");
if (requestResult.statusCode != HttpStatusCode.OK)
break;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
offset += limit; offset += limit;
if (result is null) if (result is null)
@ -121,7 +126,10 @@ public class MangaDex : Connector
while (offset < total) while (offset < total)
{ {
DownloadClient.RequestResult requestResult = DownloadClient.RequestResult requestResult =
_downloadClient.MakeRequest($"https://api.mangadex.org/manga/{id}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}"); _downloadClient.MakeRequest(
$"https://api.mangadex.org/manga/{id}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}");
if (requestResult.statusCode != HttpStatusCode.OK)
break;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
offset += limit; offset += limit;
@ -163,6 +171,8 @@ public class MangaDex : Connector
{ {
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'");
if (requestResult.statusCode != HttpStatusCode.OK)
return;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
if (result is null) if (result is null)
return; return;
@ -188,12 +198,16 @@ public class MangaDex : Connector
public override void DownloadCover(Publication publication) public override void DownloadCover(Publication publication)
{ {
string publicationPath = Path.Join(downloadLocation, publication.folderName); string publicationPath = Path.Join(downloadLocation, publication.folderName);
DirectoryInfo dirInfo = new DirectoryInfo(publicationPath); Directory.CreateDirectory(publicationPath);
DirectoryInfo dirInfo = new (publicationPath);
foreach(FileInfo fileInfo in dirInfo.EnumerateFiles()) foreach(FileInfo fileInfo in dirInfo.EnumerateFiles())
if (fileInfo.Name.Contains("cover.")) if (fileInfo.Name.Contains("cover."))
return; return;
DownloadClient.RequestResult requestResult = _downloadClient.MakeRequest($"https://api.mangadex.org/cover/{publication.posterUrl}"); DownloadClient.RequestResult requestResult =
_downloadClient.MakeRequest($"https://api.mangadex.org/cover/{publication.posterUrl}");
if (requestResult.statusCode != HttpStatusCode.OK)
return;
JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result); JsonObject? result = JsonSerializer.Deserialize<JsonObject>(requestResult.result);
if (result is null) if (result is null)
return; return;

View File

@ -7,6 +7,10 @@ public static class TaskExecutor
Connector? connector = connectors.FirstOrDefault(c => c.name == trangaTask.connectorName); Connector? connector = connectors.FirstOrDefault(c => c.name == trangaTask.connectorName);
if (connector is null) if (connector is null)
throw new ArgumentException($"Connector {trangaTask.connectorName} is not a known connector."); throw new ArgumentException($"Connector {trangaTask.connectorName} is not a known connector.");
if (trangaTask.isBeingExecuted)
return;
trangaTask.isBeingExecuted = true;
trangaTask.lastExecuted = DateTime.Now; trangaTask.lastExecuted = DateTime.Now;
switch (trangaTask.task) switch (trangaTask.task)
@ -21,6 +25,8 @@ public static class TaskExecutor
UpdatePublications(connector, chapterCollection); UpdatePublications(connector, chapterCollection);
break; break;
} }
trangaTask.isBeingExecuted = false;
} }
private static void UpdatePublications(Connector connector, Dictionary<Publication, List<Chapter>> chapterCollection) private static void UpdatePublications(Connector connector, Dictionary<Publication, List<Chapter>> chapterCollection)

View File

@ -34,6 +34,15 @@ public class TaskManager
} }
} }
public void ExecuteTaskNow(TrangaTask task)
{
Task t = new Task(() =>
{
TaskExecutor.Execute(this.connectors, task, this._chapterCollection);
});
t.Start();
}
public void AddTask(TrangaTask.Task task, string connectorName, Publication? publication, TimeSpan reoccurrence, public void AddTask(TrangaTask.Task task, string connectorName, Publication? publication, TimeSpan reoccurrence,
string language = "") string language = "")
{ {
@ -44,6 +53,8 @@ public class TaskManager
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?.downloadUrl != publication?.downloadUrl))
{ {
if(task != TrangaTask.Task.UpdatePublications)
_chapterCollection.Add((Publication)publication!, new List<Chapter>());
_allTasks.Add(new TrangaTask(connector.name, task, publication, reoccurrence, language)); _allTasks.Add(new TrangaTask(connector.name, task, publication, reoccurrence, language));
ExportTasks(Directory.GetCurrentDirectory()); ExportTasks(Directory.GetCurrentDirectory());
} }
@ -74,13 +85,21 @@ public class TaskManager
return this._chapterCollection.Keys.ToArray(); return this._chapterCollection.Keys.ToArray();
} }
public void Shutdown() public void Shutdown(bool force = false)
{ {
_continueRunning = false; _continueRunning = false;
ExportTasks(Directory.GetCurrentDirectory()); ExportTasks(Directory.GetCurrentDirectory());
if(force)
Environment.Exit(_allTasks.Count(task => task.isBeingExecuted));
//Wait for tasks to finish
while(_allTasks.Any(task => task.isBeingExecuted))
Thread.Sleep(10);
} }
public HashSet<TrangaTask> ImportTasks(string importFolderPath) private HashSet<TrangaTask> ImportTasks(string importFolderPath)
{ {
string filePath = Path.Join(importFolderPath, "tasks.json"); string filePath = Path.Join(importFolderPath, "tasks.json");
if (!File.Exists(filePath)) if (!File.Exists(filePath))
@ -96,7 +115,7 @@ public class TaskManager
return importTasks.ToHashSet(); return importTasks.ToHashSet();
} }
public void ExportTasks(string exportFolderPath) private void ExportTasks(string exportFolderPath)
{ {
string filePath = Path.Join(exportFolderPath, "tasks.json"); string filePath = Path.Join(exportFolderPath, "tasks.json");
string toWrite = JsonConvert.SerializeObject(_allTasks.ToArray()); string toWrite = JsonConvert.SerializeObject(_allTasks.ToArray());

View File

@ -1,4 +1,6 @@
namespace Tranga; using Newtonsoft.Json;
namespace Tranga;
public class TrangaTask public class TrangaTask
{ {
@ -8,6 +10,7 @@ public class TrangaTask
public Task task { get; } public Task task { get; }
public Publication? publication { get; } public Publication? publication { get; }
public string language { get; } public string language { get; }
[JsonIgnore]public bool isBeingExecuted { get; set; }
public TrangaTask(string connectorName, Task task, Publication? publication, TimeSpan reoccurrence, string language = "") public TrangaTask(string connectorName, Task task, Publication? publication, TimeSpan reoccurrence, string language = "")
{ {