From e499062fd5741824c465bf030af1408999610552 Mon Sep 17 00:00:00 2001 From: glax Date: Fri, 19 May 2023 20:22:13 +0200 Subject: [PATCH] Add more documentation --- Tranga/Connector.cs | 28 +++++++++++++++++++-- Tranga/Connectors/MangaDex.cs | 46 +++++++++++++++++++++++------------ Tranga/TaskExecutor.cs | 23 ++++++++++++++++++ Tranga/TaskManager.cs | 4 ++- 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/Tranga/Connector.cs b/Tranga/Connector.cs index 24bd845..51be4dd 100644 --- a/Tranga/Connector.cs +++ b/Tranga/Connector.cs @@ -62,6 +62,12 @@ public abstract class Connector File.WriteAllText(seriesInfoPath,publication.GetSeriesInfo()); } + /// + /// Downloads Image from URL and saves it to the given path(incl. fileName) + /// + /// + /// + /// DownloadClient of the connector protected static void DownloadImage(string imageUrl, string fullPath, DownloadClient downloadClient) { DownloadClient.RequestResult requestResult = downloadClient.MakeRequest(imageUrl); @@ -70,22 +76,31 @@ public abstract class Connector File.WriteAllBytes(fullPath, buffer); } + /// + /// Downloads all Images from URLs, Compresses to zip(cbz) and saves. + /// + /// List of URLs to download Images from + /// Full path to save archive to (without file ending .cbz) + /// DownloadClient of the connector protected static void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, DownloadClient downloadClient) { + //Check if Publication Directory already exists 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)) + if (File.Exists(fullPath)) //Don't download twice. return; + //Create a temporary folder to store images string tempFolder = Path.GetTempFileName(); File.Delete(tempFolder); Directory.CreateDirectory(tempFolder); int chapter = 0; + //Download all Images to temporary Folder foreach (string imageUrl in imageUrls) { string[] split = imageUrl.Split('.'); @@ -93,10 +108,10 @@ public abstract class Connector DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), downloadClient); } + //ZIP-it and ship-it ZipFile.CreateFromDirectory(tempFolder, fullPath); Directory.Delete(tempFolder); //Cleanup } - protected class DownloadClient { @@ -104,12 +119,21 @@ public abstract class Connector private DateTime _lastRequest; private static readonly HttpClient Client = new(); + /// + /// Creates a httpClient + /// + /// minimum delay between requests (to avoid spam) public DownloadClient(uint delay) { _requestSpeed = TimeSpan.FromMilliseconds(delay); _lastRequest = DateTime.Now.Subtract(_requestSpeed); } + /// + /// Request Webpage + /// + /// + /// RequestResult with StatusCode and Stream of received data public RequestResult MakeRequest(string url) { while((DateTime.Now - _lastRequest) < _requestSpeed) diff --git a/Tranga/Connectors/MangaDex.cs b/Tranga/Connectors/MangaDex.cs index 351f45d..5cd2662 100644 --- a/Tranga/Connectors/MangaDex.cs +++ b/Tranga/Connectors/MangaDex.cs @@ -20,24 +20,28 @@ public class MangaDex : Connector public override Publication[] GetPublications(string publicationTitle = "") { - const int limit = 100; - int offset = 0; - int total = int.MaxValue; + const int limit = 100; //How many values we want returned at once + int offset = 0; //"Page" + int total = int.MaxValue; //How many total results are there, is updated on first request HashSet publications = new(); - while (offset < total) + while (offset < total) //As long as we haven't requested all "Pages" { + //Request next Page 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(requestResult.result); + offset += limit; if (result is null) break; - total = result["total"]!.GetValue(); - JsonArray mangaInResult = result["data"]!.AsArray(); + total = result["total"]!.GetValue(); //Update the total number of Publications + + JsonArray mangaInResult = result["data"]!.AsArray(); //Manga-data-Array + //Loop each Manga and extract information from JSON foreach (JsonNode? mangeNode in mangaInResult) { JsonObject manga = (JsonObject)mangeNode!; @@ -113,7 +117,7 @@ public class MangaDex : Connector status, manga["id"]!.GetValue() ); - publications.Add(pub); + publications.Add(pub); //Add Publication (Manga) to result } } @@ -122,16 +126,17 @@ public class MangaDex : Connector public override Chapter[] GetChapters(Publication publication, string language = "") { - const int limit = 100; - int offset = 0; - string id = publication.downloadUrl; - int total = int.MaxValue; + const int limit = 100; //How many values we want returned at once + int offset = 0; //"Page" + int total = int.MaxValue; //How many total results are there, is updated on first request List chapters = new(); + //As long as we haven't requested all "Pages" while (offset < total) { + //Request next "Page" DownloadClient.RequestResult requestResult = downloadClient.MakeRequest( - $"https://api.mangadex.org/manga/{id}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}"); + $"https://api.mangadex.org/manga/{publication.downloadUrl}/feed?limit={limit}&offset={offset}&translatedLanguage%5B%5D={language}"); if (requestResult.statusCode != HttpStatusCode.OK) break; JsonObject? result = JsonSerializer.Deserialize(requestResult.result); @@ -142,6 +147,7 @@ public class MangaDex : Connector total = result["total"]!.GetValue(); JsonArray chaptersInResult = result["data"]!.AsArray(); + //Loop through all Chapters in result and extract information from JSON foreach (JsonNode? jsonNode in chaptersInResult) { JsonObject chapter = (JsonObject)jsonNode!; @@ -164,6 +170,7 @@ public class MangaDex : Connector } } + //Return Chapters ordered by Chapter-Number NumberFormatInfo chapterNumberFormatInfo = new() { NumberDecimalSeparator = "." @@ -173,6 +180,7 @@ public class MangaDex : Connector public override void DownloadChapter(Publication publication, Chapter chapter) { + //Request URLs for Chapter-Images DownloadClient.RequestResult requestResult = downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'"); if (requestResult.statusCode != HttpStatusCode.OK) @@ -184,22 +192,26 @@ public class MangaDex : Connector string baseUrl = result["baseUrl"]!.GetValue(); string hash = result["chapter"]!["hash"]!.GetValue(); JsonArray imageFileNames = result["chapter"]!["data"]!.AsArray(); + //Loop through all imageNames and construct urls (imageUrl) HashSet imageUrls = new(); foreach (JsonNode? image in imageFileNames) imageUrls.Add($"{baseUrl}/data/{hash}/{image!.GetValue()}"); + //Download Chapter-Images DownloadChapterImages(imageUrls.ToArray(), Path.Join(downloadLocation, publication.folderName, chapter.fileName), this.downloadClient); } public override void DownloadCover(Publication publication) { - string publicationPath = Path.Join(downloadLocation, publication.folderName); - Directory.CreateDirectory(publicationPath); - DirectoryInfo dirInfo = new (publicationPath); + //Check if Publication already has a Folder and cover + string publicationFolder = Path.Join(downloadLocation, publication.folderName); + Directory.CreateDirectory(publicationFolder); + DirectoryInfo dirInfo = new (publicationFolder); foreach(FileInfo fileInfo in dirInfo.EnumerateFiles()) if (fileInfo.Name.Contains("cover.")) return; + //Request information where to download Cover DownloadClient.RequestResult requestResult = downloadClient.MakeRequest($"https://api.mangadex.org/cover/{publication.posterUrl}"); if (requestResult.statusCode != HttpStatusCode.OK) @@ -211,11 +223,15 @@ public class MangaDex : Connector string fileName = result!["data"]!["attributes"]!["fileName"]!.GetValue(); string coverUrl = $"https://uploads.mangadex.org/covers/{publication.downloadUrl}/{fileName}"; + + //Get file-extension (jpg, png) string[] split = coverUrl.Split('.'); string extension = split[split.Length - 1]; string outFolderPath = Path.Join(downloadLocation, publication.folderName); Directory.CreateDirectory(outFolderPath); + + //Download cover-Image DownloadImage(coverUrl, Path.Join(downloadLocation, publication.folderName, $"cover.{extension}"), this.downloadClient); } } \ No newline at end of file diff --git a/Tranga/TaskExecutor.cs b/Tranga/TaskExecutor.cs index 4f55ef3..c4b0ff2 100644 --- a/Tranga/TaskExecutor.cs +++ b/Tranga/TaskExecutor.cs @@ -17,6 +17,7 @@ public static class TaskExecutor /// Is thrown when there is no Connector available with the name of the TrangaTask.connectorName public static void Execute(Connector[] connectors, TrangaTask trangaTask, Dictionary> chapterCollection) { + //Get Connector from list of available Connectors and the required Connector of the TrangaTask Connector? connector = connectors.FirstOrDefault(c => c.name == trangaTask.connectorName); if (connector is null) throw new ArgumentException($"Connector {trangaTask.connectorName} is not a known connector."); @@ -26,6 +27,7 @@ public static class TaskExecutor trangaTask.isBeingExecuted = true; trangaTask.lastExecuted = DateTime.Now; + //Call appropriate Method based on TrangaTask.Task switch (trangaTask.task) { case TrangaTask.Task.DownloadNewChapters: @@ -42,6 +44,11 @@ public static class TaskExecutor trangaTask.isBeingExecuted = false; } + /// + /// Updates the available Publications from a Connector (all of them) + /// + /// Connector to receive Publications from + /// private static void UpdatePublications(Connector connector, Dictionary> chapterCollection) { Publication[] publications = connector.GetPublications(); @@ -49,6 +56,14 @@ public static class TaskExecutor chapterCollection.TryAdd(publication, new List()); } + /// + /// Checks for new Chapters and Downloads new ones. + /// If no Chapters had been downloaded previously, download also cover and create series.json + /// + /// Connector to use + /// Publication to check + /// Language to receive chapters for + /// private static void DownloadNewChapters(Connector connector, Publication publication, string language, Dictionary> chapterCollection) { List newChapters = UpdateChapters(connector, publication, language, chapterCollection); @@ -58,6 +73,14 @@ public static class TaskExecutor connector.SaveSeriesInfo(publication); } + /// + /// Updates the available Chapters of a Publication + /// + /// Connector to use + /// Publication to check + /// Language to receive chapters for + /// + /// List of Chapters that were previously not in collection private static List UpdateChapters(Connector connector, Publication publication, string language, Dictionary> chapterCollection) { List newChaptersList = new(); diff --git a/Tranga/TaskManager.cs b/Tranga/TaskManager.cs index 6a510e1..9a94866 100644 --- a/Tranga/TaskManager.cs +++ b/Tranga/TaskManager.cs @@ -70,10 +70,12 @@ public class TaskManager public void AddTask(TrangaTask.Task task, string connectorName, Publication? publication, TimeSpan reoccurrence, string language = "") { + //Get appropriate Connector from available Connectors for TrangaTask Connector? connector = connectors.FirstOrDefault(c => c.name == connectorName); if (connector is null) throw new ArgumentException($"Connector {connectorName} is not a known connector."); + //Check if same task already exists if (!_allTasks.Any(trangaTask => trangaTask.task != task && trangaTask.connectorName != connector.name && trangaTask.publication?.downloadUrl != publication?.downloadUrl)) { @@ -142,7 +144,7 @@ public class TaskManager //Wait for tasks to finish while(_allTasks.Any(task => task.isBeingExecuted)) Thread.Sleep(10); - + Environment.Exit(0); } private HashSet ImportTasks(string importFolderPath)