Fix DownloadMangaCover, Implement Manga/Cover endpoint

This commit is contained in:
Glax 2025-03-07 14:07:59 +01:00
parent 4075adfe6b
commit c679d7c677
4 changed files with 24 additions and 129 deletions

View File

@ -106,7 +106,7 @@ public class MangaController(PgsqlContext context) : Controller
Image image = Image.Load(m.CoverFileNameInCache);
using MemoryStream ms = new();
image.Save(ms, new JpegEncoder(){Quality = 100});
return File(ms, "image/jpeg");
return File(ms.GetBuffer(), "image/jpeg");
}
/// <summary>

View File

@ -11,125 +11,21 @@ using static System.IO.UnixFileMode;
namespace API.Schema.Jobs;
public class DownloadMangaCoverJob(string chapterId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
public class DownloadMangaCoverJob(string mangaId, string? parentJobId = null, ICollection<string>? dependsOnJobsIds = null)
: Job(TokenGen.CreateToken(typeof(DownloadMangaCoverJob)), JobType.DownloadMangaCoverJob, 0, parentJobId, dependsOnJobsIds)
{
[MaxLength(64)]
public string ChapterId { get; init; } = chapterId;
public Chapter? Chapter { get; init; }
public string MangaId { get; init; } = mangaId;
public Manga? Manga { get; init; }
protected override IEnumerable<Job> RunInternal(PgsqlContext context)
{
MangaConnector connector = Chapter.ParentManga?.MangaConnector ?? context.MangaConnectors.Find(context.Manga.Find(Chapter.ParentMangaId)?.MangaId)!;
DownloadChapterImages(Chapter, connector);
Manga? manga = Manga ?? context.Manga.Find(this.MangaId);
if (manga is null)
return [];
manga.CoverFileNameInCache = manga.SaveCoverImageToCache();
context.SaveChanges();
return [];
}
private bool DownloadChapterImages(Chapter chapter, MangaConnector connector)
{
string[] imageUrls = connector.GetChapterImageUrls(Chapter);
string saveArchiveFilePath = chapter.GetArchiveFilePath();
//Check if Publication Directory already exists
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
if (!Directory.Exists(directoryPath))
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
Directory.CreateDirectory(directoryPath,
UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute );
else
Directory.CreateDirectory(directoryPath);
if (File.Exists(saveArchiveFilePath)) //Don't download twice. Redownload
File.Delete(saveArchiveFilePath);
//Create a temporary folder to store images
string tempFolder = Directory.CreateTempSubdirectory("trangatemp").FullName;
int chapterNum = 0;
//Download all Images to temporary Folder
if (imageUrls.Length == 0)
{
Directory.Delete(tempFolder, true);
return false;
}
foreach (string imageUrl in imageUrls)
{
string extension = imageUrl.Split('.')[^1].Split('?')[0];
string imagePath = Path.Join(tempFolder, $"{chapterNum++}.{extension}");
bool status = DownloadImage(imageUrl, imagePath);
if (status is false)
return false;
}
CopyCoverFromCacheToDownloadLocation();
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
//ZIP-it and ship-it
ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(saveArchiveFilePath, UserRead | UserWrite | UserExecute | GroupRead | GroupWrite | GroupExecute | OtherRead | OtherExecute);
Directory.Delete(tempFolder, true); //Cleanup
return true;
}
private void ProcessImage(string imagePath)
{
if (!TrangaSettings.bwImages && TrangaSettings.compression == 100)
return;
using Image image = Image.Load(imagePath);
File.Delete(imagePath);
if(TrangaSettings.bwImages)
image.Mutate(i => i.ApplyProcessor(new AdaptiveThresholdProcessor()));
image.SaveAsJpeg(imagePath, new JpegEncoder()
{
Quality = TrangaSettings.compression
});
}
private void CopyCoverFromCacheToDownloadLocation(int? retries = 1)
{
//Check if Publication already has a Folder and cover
string publicationFolder = Chapter.ParentManga.CreatePublicationFolder();
DirectoryInfo dirInfo = new (publicationFolder);
if (dirInfo.EnumerateFiles().Any(info => info.Name.Contains("cover", StringComparison.InvariantCultureIgnoreCase)))
{
return;
}
string? fileInCache = Chapter.ParentManga.CoverFileNameInCache;
if (fileInCache is null || !File.Exists(fileInCache))
{
if (retries > 0 && Chapter.ParentManga.CoverUrl is not null)
{
Chapter.ParentManga.SaveCoverImageToCache();
CopyCoverFromCacheToDownloadLocation(--retries);
}
return;
}
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" );
File.Copy(fileInCache, newFilePath, true);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(newFilePath, GroupRead | GroupWrite | UserRead | UserWrite);
}
private bool DownloadImage(string imageUrl, string savePath)
{
HttpDownloadClient downloadClient = new();
RequestResult requestResult = downloadClient.MakeRequest(imageUrl, RequestType.MangaImage);
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300)
return false;
if (requestResult.result == Stream.Null)
return false;
FileStream fs = new (savePath, FileMode.Create);
requestResult.result.CopyTo(fs);
fs.Close();
ProcessImage(savePath);
return true;
}
}

View File

@ -58,7 +58,7 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
return [];
}
CopyCoverFromCacheToDownloadLocation(context, manga);
CopyCoverFromCacheToDownloadLocation(manga);
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
@ -88,7 +88,7 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
});
}
private void CopyCoverFromCacheToDownloadLocation(PgsqlContext context, Manga manga, int? retries = 1)
private void CopyCoverFromCacheToDownloadLocation(Manga manga)
{
//Check if Publication already has a Folder and cover
string publicationFolder = manga.CreatePublicationFolder();
@ -98,18 +98,10 @@ public class DownloadSingleChapterJob(string chapterId, string? parentJobId = nu
return;
}
string? fileInCache = manga.CoverFileNameInCache;
if (fileInCache is null || !File.Exists(fileInCache))
{
if (retries > 0)
{
manga.CoverFileNameInCache = manga.SaveCoverImageToCache();
context.SaveChanges();
CopyCoverFromCacheToDownloadLocation(context, manga, --retries);
}
string? fileInCache = manga.CoverFileNameInCache ?? manga.SaveCoverImageToCache();
if (fileInCache is null)
return;
}
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" );
File.Copy(fileInCache, newFilePath, true);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))

View File

@ -1,5 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Net;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using API.MangaDownloadClients;
@ -99,8 +99,11 @@ public class Manga
return mangaName;
}
internal string SaveCoverImageToCache()
internal string? SaveCoverImageToCache(int retries = 3)
{
if(retries < 0)
return null;
Regex urlRex = new (@"https?:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+))");
//https?:\/\/[a-zA-Z0-9-]+\.([a-zA-Z0-9-]+\.[a-zA-Z0-9]+)\/(?:.+\/)*(.+\.([a-zA-Z]+)) for only second level domains
Match match = urlRex.Match(CoverUrl);
@ -111,10 +114,14 @@ public class Manga
return saveImagePath;
RequestResult coverResult = new HttpDownloadClient().MakeRequest(CoverUrl, RequestType.MangaCover);
if (coverResult.statusCode is < HttpStatusCode.Accepted or >= HttpStatusCode.Ambiguous)
return SaveCoverImageToCache(--retries);
using MemoryStream ms = new();
coverResult.result.CopyTo(ms);
Directory.CreateDirectory(TrangaSettings.coverImageCache);
File.WriteAllBytes(saveImagePath, ms.ToArray());
return saveImagePath;
}