Tranga/API/Schema/Manga.cs
Glax 5b03befbf1
Some checks failed
Docker Image CI / build (push) Has been cancelled
#168 Multiple Base-Paths (Libraries) Support
2025-03-16 16:48:19 +01:00

186 lines
7.1 KiB
C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using API.MangaDownloadClients;
using API.Schema.Jobs;
using API.Schema.MangaConnectors;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using static System.IO.UnixFileMode;
namespace API.Schema;
[PrimaryKey("MangaId")]
public class Manga
{
[StringLength(64)]
[Required]
public string MangaId { get; init; }
[StringLength(128)]
[Required]
public string IdOnConnectorSite { get; init; }
[StringLength(256)]
[Required]
public string Name { get; internal set; }
[Required]
public string Description { get; internal set; }
[Url]
[StringLength(256)]
[Required]
public string WebsiteUrl { get; internal set; }
[JsonIgnore]
[Url]
public string CoverUrl { get; internal set; }
[JsonIgnore]
public string? CoverFileNameInCache { get; internal set; }
[Required]
public uint Year { get; internal set; }
[StringLength(8)]
[Required]
public string? OriginalLanguage { get; internal set; }
[Required]
public MangaReleaseStatus ReleaseStatus { get; internal set; }
[StringLength(256)]
[Required]
public string DirectoryName { get; private set; }
public LocalLibrary? Library { get; internal set; }
[JsonIgnore]
[NotMapped]
public string LibraryPath => Library is null ? TrangaSettings.downloadLocation : Library.BasePath;
[JsonIgnore]
[NotMapped]
public string FullDirectoryPath => Path.Join(LibraryPath, DirectoryName);
[Required]
public float IgnoreChapterBefore { get; internal set; }
[StringLength(64)]
[Required]
public string MangaConnectorId { get; private set; }
[JsonIgnore] public MangaConnector? MangaConnector { get; private set; }
[JsonIgnore] public ICollection<Author>? Authors { get; internal set; }
[NotMapped]
[StringLength(64)]
[Required]
public IEnumerable<string> AuthorIds => Authors?.Select(a => a.AuthorId) ?? [];
[JsonIgnore] public ICollection<MangaTag>? MangaTags { get; internal set; }
[NotMapped]
[StringLength(64)]
[Required]
public IEnumerable<string> Tags => MangaTags?.Select(t => t.Tag) ?? [];
[JsonIgnore] public ICollection<Link>? Links { get; internal set; }
[NotMapped]
[StringLength(64)]
[Required]
public IEnumerable<string> LinkIds => Links?.Select(l => l.LinkId) ?? [];
[JsonIgnore] public ICollection<MangaAltTitle>? AltTitles { get; internal set; }
[NotMapped]
[StringLength(64)]
[Required]
public IEnumerable<string> AltTitleIds => AltTitles?.Select(a => a.AltTitleId) ?? [];
public Manga(string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl,
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
float ignoreChapterBefore, MangaConnector mangaConnector, ICollection<Author> authors,
ICollection<MangaTag> mangaTags, ICollection<Link> links, ICollection<MangaAltTitle> altTitles,
LocalLibrary? library = null)
: this(idOnConnectorSite, name, description, websiteUrl, coverUrl, coverFileNameInCache, year, originalLanguage,
releaseStatus, ignoreChapterBefore, mangaConnector.Name)
{
this.Authors = authors;
this.MangaTags = mangaTags;
this.Links = links;
this.AltTitles = altTitles;
this.Library = library;
}
public Manga(string idOnConnectorSite, string name, string description, string websiteUrl, string coverUrl,
string? coverFileNameInCache, uint year, string? originalLanguage, MangaReleaseStatus releaseStatus,
float ignoreChapterBefore, string mangaConnectorId)
{
MangaId = TokenGen.CreateToken(typeof(Manga), mangaConnectorId, idOnConnectorSite);
IdOnConnectorSite = idOnConnectorSite;
Name = name;
Description = description;
WebsiteUrl = websiteUrl;
CoverUrl = coverUrl;
CoverFileNameInCache = coverFileNameInCache;
Year = year;
OriginalLanguage = originalLanguage;
ReleaseStatus = releaseStatus;
IgnoreChapterBefore = ignoreChapterBefore;
MangaConnectorId = mangaConnectorId;
DirectoryName = BuildFolderName(name);
}
public MoveFileOrFolderJob UpdateFolderName(string downloadLocation, string newName)
{
string oldName = this.DirectoryName;
this.DirectoryName = newName;
return new MoveFileOrFolderJob(Path.Join(downloadLocation, oldName), Path.Join(downloadLocation, this.DirectoryName));
}
internal void UpdateWithInfo(Manga other)
{
this.Name = other.Name;
this.Year = other.Year;
this.Description = other.Description;
this.CoverUrl = other.CoverUrl;
this.OriginalLanguage = other.OriginalLanguage;
this.Authors = other.Authors;
this.Links = other.Links;
this.MangaTags = other.MangaTags;
this.AltTitles = other.AltTitles;
this.ReleaseStatus = other.ReleaseStatus;
}
private static string BuildFolderName(string mangaName)
{
return mangaName;
}
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);
string filename = $"{match.Groups[1].Value}-{MangaId}.{match.Groups[3].Value}";
string saveImagePath = Path.Join(TrangaSettings.coverImageCache, filename);
if (File.Exists(saveImagePath))
return saveImagePath;
RequestResult coverResult = new HttpDownloadClient().MakeRequest(CoverUrl, RequestType.MangaCover, $"https://{match.Groups[1].Value}");
if (coverResult.statusCode is < HttpStatusCode.OK 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;
}
public string CreatePublicationFolder()
{
string publicationFolder = Path.Join(LibraryPath, this.DirectoryName);
if(!Directory.Exists(publicationFolder))
Directory.CreateDirectory(publicationFolder);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
File.SetUnixFileMode(publicationFolder, GroupRead | GroupWrite | GroupExecute | OtherRead | OtherWrite | OtherExecute | UserRead | UserWrite | UserExecute);
return publicationFolder;
}
//TODO onchanges create job to update metadata files in archives, etc.
}