Custom Chapter-Naming scheme

https://github.com/C9Glax/tranga/issues/92
This commit is contained in:
Glax 2025-03-29 20:33:58 +01:00
parent c94c55300c
commit 9350de0ae9
3 changed files with 160 additions and 40 deletions

View File

@ -1,5 +1,6 @@
using API.MangaDownloadClients;
using API.Schema;
using API.Schema.Jobs;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
@ -199,4 +200,67 @@ public class SettingsController(PgsqlContext context) : Controller
TrangaSettings.UpdateAprilFoolsMode(enabled);
return Ok();
}
/// <summary>
/// Gets the Chapter Naming Scheme
/// </summary>
/// <remarks>
/// Placeholders:
/// %M Manga Name
/// %V Volume
/// %C Chapter
/// %T Title
/// %A Author (first in list)
/// %I Chapter Internal ID
/// %i Manga Internal ID
/// %Y Year (Manga)
///
/// ?_(...) replace _ with a value from above:
/// Everything inside the braces will only be added if the value of %_ is not null
/// </remarks>
/// <response code="200"></response>
[HttpGet("ChapterNamingScheme")]
[ProducesResponseType<string>(Status200OK, "text/plain")]
public IActionResult GetCustomNamingScheme()
{
return Ok(TrangaSettings.chapterNamingScheme);
}
/// <summary>
/// Sets the Chapter Naming Scheme
/// </summary>
/// <remarks>
/// Placeholders:
/// %M Manga Name
/// %V Volume
/// %C Chapter
/// %T Title
/// %A Author (first in list)
/// %I Chapter Internal ID
/// %i Manga Internal ID
/// %Y Year (Manga)
///
/// ?_(...) replace _ with a value from above:
/// Everything inside the braces will only be added if the value of %_ is not null
/// </remarks>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpPatch("ChapterNamingScheme")]
[ProducesResponseType(Status200OK)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult SetCustomNamingScheme([FromBody]string namingScheme)
{
try
{
TrangaSettings.UpdateChapterNamingScheme(namingScheme);
MoveFileOrFolderJob[] newJobs =
context.Chapters.Where(c => c.Downloaded).Select(c => c.UpdateArchiveFileName()).Where(x => x != null).ToArray()!;
context.Jobs.AddRange(newJobs);
return Ok();
}
catch (Exception e)
{
return StatusCode(500, e);
}
}
}

View File

@ -1,5 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using API.Schema.Jobs;
using Microsoft.EntityFrameworkCore;
@ -86,7 +88,7 @@ public class Chapter : IComparable<Chapter>
return UpdateArchiveFileName();
}
private MoveFileOrFolderJob? UpdateArchiveFileName()
internal MoveFileOrFolderJob? UpdateArchiveFileName()
{
string? oldPath = FullArchiveFilePath;
if (oldPath is null)
@ -106,9 +108,76 @@ public class Chapter : IComparable<Chapter>
return File.Exists(path);
}
/// Placeholders:
/// %M Manga Name
/// %V Volume
/// %C Chapter
/// %T Title
/// %A Author (first in list)
/// %I Chapter Internal ID
/// %i Manga Internal ID
/// %Y Year (Manga)
private static readonly Regex NullableRex = new(@"\?([a-zA-Z])\(([^\)]*)\)|(.+?)");
private static readonly Regex ReplaceRexx = new(@"%([a-zA-Z])|(.+?)");
private string GetArchiveFilePath(string? parentMangaName = null)
{
return $"{parentMangaName ?? ParentManga?.Name ?? ""} - Vol.{VolumeNumber ?? 0} Ch.{ChapterNumber}{(Title is null ? "" : $" - {Title}")}.cbz";
string archiveNamingScheme = TrangaSettings.chapterNamingScheme;
StringBuilder stringBuilder = new();
foreach (Match nullable in NullableRex.Matches(archiveNamingScheme))
{
if (nullable.Groups[3].Success)
{
stringBuilder.Append(nullable.Groups[3].Value);
continue;
}
char placeholder = nullable.Groups[1].Value[0];
bool isNull = placeholder switch
{
'M' => ParentManga?.Name is null && parentMangaName is null,
'V' => VolumeNumber is null,
'C' => ChapterNumber is null,
'T' => Title is null,
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName is null,
'I' => ChapterId is null,
'i' => ParentMangaId is null,
'Y' => ParentManga?.Year is null,
_ => true
};
if(!isNull)
stringBuilder.Append(nullable.Groups[2].Value);
}
string checkedString = stringBuilder.ToString();
stringBuilder = new();
foreach (Match replace in ReplaceRexx.Matches(checkedString))
{
if (replace.Groups[2].Success)
{
stringBuilder.Append(replace.Groups[2].Value);
continue;
}
char placeholder = replace.Groups[1].Value[0];
string? value = placeholder switch
{
'M' => ParentManga?.Name ?? parentMangaName,
'V' => VolumeNumber?.ToString(),
'C' => ChapterNumber,
'T' => Title,
'A' => ParentManga?.Authors?.FirstOrDefault()?.AuthorName,
'I' => ChapterId,
'i' => ParentMangaId,
'Y' => ParentManga?.Year.ToString(),
_ => null
};
stringBuilder.Append(value);
}
stringBuilder.Append(".cbz");
return stringBuilder.ToString();
}
private static int CompareChapterNumbers(string ch1, string ch2)

View File

@ -3,7 +3,6 @@ using API.MangaDownloadClients;
using API.Schema;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static System.IO.UnixFileMode;
namespace API;
@ -16,6 +15,21 @@ public static class TrangaSettings
public static string userAgent { get; private set; } = DefaultUserAgent;
public static int compression{ get; private set; } = 40;
public static bool bwImages { get; private set; } = false;
/// <summary>
/// Placeholders:
/// %M Manga Name
/// %V Volume
/// %C Chapter
/// %T Title
/// %A Author (first in list)
/// %I Chapter Internal ID
/// %i Manga Internal ID
/// %Y Year (Manga)
///
/// ?_(...) replace _ with a value from above:
/// Everything inside the braces will only be added if the value of %_ is not null
/// </summary>
public static string chapterNamingScheme { get; private set; } = "%M - ?V(Vol.%V )Ch.%C?T( - %T)";
[JsonIgnore]
public static string settingsFilePath => Path.Join(workingDirectory, "settings.json");
[JsonIgnore]
@ -70,42 +84,6 @@ public static class TrangaSettings
ExportSettings();
}
public static void UpdateDownloadLocation(string newPath, bool moveFiles = true)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
Directory.CreateDirectory(newPath, GroupRead | GroupWrite | None | OtherRead | OtherWrite | UserRead | UserWrite);
else
Directory.CreateDirectory(newPath);
if (moveFiles)
MoveContentsOfDirectoryTo(TrangaSettings.downloadLocation, newPath);
TrangaSettings.downloadLocation = newPath;
ExportSettings();
}
private static void MoveContentsOfDirectoryTo(string oldDir, string newDir)
{
string[] directoryPaths = Directory.GetDirectories(oldDir);
string[] filePaths = Directory.GetFiles(oldDir);
foreach (string file in filePaths)
{
string newPath = Path.Join(newDir, Path.GetFileName(file));
File.Move(file, newPath, true);
}
foreach(string directory in directoryPaths)
{
string? dirName = Path.GetDirectoryName(directory);
if(dirName is null)
continue;
string newPath = Path.Join(newDir, dirName);
if(Directory.Exists(newPath))
MoveContentsOfDirectoryTo(directory, newPath);
else
Directory.Move(directory, newPath);
}
}
public static void UpdateUserAgent(string? customUserAgent)
{
userAgent = customUserAgent ?? DefaultUserAgent;
@ -118,6 +96,12 @@ public static class TrangaSettings
ExportSettings();
}
public static void UpdateChapterNamingScheme(string namingScheme)
{
chapterNamingScheme = namingScheme;
ExportSettings();
}
public static void ResetRequestLimits()
{
requestLimits = DefaultRequestLimits;
@ -154,7 +138,7 @@ public static class TrangaSettings
public static JObject AsJObject()
{
JObject jobj = new JObject();
JObject jobj = new ();
jobj.Add("downloadLocation", JToken.FromObject(downloadLocation));
jobj.Add("workingDirectory", JToken.FromObject(workingDirectory));
jobj.Add("userAgent", JToken.FromObject(userAgent));
@ -163,6 +147,7 @@ public static class TrangaSettings
jobj.Add("compression", JToken.FromObject(compression));
jobj.Add("bwImages", JToken.FromObject(bwImages));
jobj.Add("startNewJobTimeoutMs", JToken.FromObject(startNewJobTimeoutMs));
jobj.Add("chapterNamingScheme", JToken.FromObject(chapterNamingScheme));
return jobj;
}
@ -187,5 +172,7 @@ public static class TrangaSettings
bwImages = bwi.Value<bool>()!;
if (jobj.TryGetValue("startNewJobTimeoutMs", out JToken? snjt))
startNewJobTimeoutMs = snjt.Value<int>()!;
if (jobj.TryGetValue("chapterNamingScheme", out JToken? cns))
chapterNamingScheme = cns.Value<string>()!;
}
}