Last Batch before Release (#2899)
This commit is contained in:
parent
8d77b398b2
commit
32bedb4e06
32 changed files with 3302 additions and 124 deletions
|
@ -961,7 +961,7 @@ public class OpdsController : BaseApiController
|
|||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(await GetUser(apiKey));
|
||||
if (!await _accountService.HasDownloadPermission(user))
|
||||
{
|
||||
return BadRequest("User does not have download permissions");
|
||||
return Forbid("User does not have download permissions");
|
||||
}
|
||||
|
||||
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
|
||||
|
|
3019
API/Data/Migrations/20240418163829_ChapterSortOrderLock.Designer.cs
generated
Normal file
3019
API/Data/Migrations/20240418163829_ChapterSortOrderLock.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
40
API/Data/Migrations/20240418163829_ChapterSortOrderLock.cs
Normal file
40
API/Data/Migrations/20240418163829_ChapterSortOrderLock.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChapterSortOrderLock : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PdfLayoutMode",
|
||||
table: "AppUserPreferences");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SortOrderLocked",
|
||||
table: "Chapter",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrderLocked",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PdfLayoutMode",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.3");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
@ -415,9 +415,6 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PdfLayoutMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PdfScrollMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -784,6 +781,9 @@ namespace API.Data.Migrations
|
|||
b.Property<float>("SortOrder")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<bool>("SortOrderLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("StoryArc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
@ -116,11 +116,6 @@ public class AppUserPreferences
|
|||
/// </summary>
|
||||
public PdfScrollMode PdfScrollMode { get; set; } = PdfScrollMode.Vertical;
|
||||
/// <summary>
|
||||
/// PDF Reader: Layout Mode of the reader
|
||||
/// </summary>
|
||||
/// Book mode is too buggy to include
|
||||
//public PdfLayoutMode PdfLayoutMode { get; set; } = PdfLayoutMode.Multiple;
|
||||
/// <summary>
|
||||
/// PDF Reader: Spread Mode of the reader
|
||||
/// </summary>
|
||||
public PdfSpreadMode PdfSpreadMode { get; set; } = PdfSpreadMode.None;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Interfaces;
|
||||
|
@ -33,6 +34,10 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
|||
/// </summary>
|
||||
public float SortOrder { get; set; }
|
||||
/// <summary>
|
||||
/// Can the sort order be updated on scan or is it locked from UI
|
||||
/// </summary>
|
||||
public bool SortOrderLocked { get; set; }
|
||||
/// <summary>
|
||||
/// The files that represent this Chapter
|
||||
/// </summary>
|
||||
public ICollection<MangaFile> Files { get; set; } = null!;
|
||||
|
@ -171,7 +176,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
|||
return Parser.RemoveExtensionIfSupported(Title);
|
||||
}
|
||||
|
||||
if (MinNumber.Is(0) && !float.TryParse(Range, out _))
|
||||
if (MinNumber.Is(0) && !float.TryParse(Range, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
return $"{Range}";
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public static class ChapterListExtensions
|
|||
fakeChapter.UpdateFrom(info);
|
||||
return specialTreatment
|
||||
? chapters.FirstOrDefault(c => c.Range == Parser.RemoveExtensionIfSupported(info.Filename) || c.Files.Select(f => Parser.NormalizePath(f.FilePath)).Contains(normalizedPath))
|
||||
: chapters.FirstOrDefault(c => c.Range == fakeChapter.GetNumberTitle());
|
||||
: chapters.FirstOrDefault(c => c.Range == fakeChapter.GetNumberTitle()); // BUG: TODO: On non-english locales, for floats, the range will be 20,5 but the NumberTitle will return 20.5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -86,7 +86,7 @@ public static class FilterFieldValueConverter
|
|||
.Select(x => (MangaFormat) Enum.Parse(typeof(MangaFormat), x))
|
||||
.ToList(),
|
||||
FilterField.ReadTime => int.Parse(value),
|
||||
FilterField.AverageRating => float.Parse(value),
|
||||
FilterField.AverageRating => value.AsFloat(),
|
||||
_ => throw new ArgumentException("Invalid field type")
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
@ -37,6 +38,9 @@ public class Program
|
|||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
||||
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console()
|
||||
|
|
|
@ -551,7 +551,7 @@ public class BookService : IBookService
|
|||
}
|
||||
|
||||
// If this is a single book and not a collection, set publication status to Completed
|
||||
if (string.IsNullOrEmpty(info.Volume) && Parser.ParseVolume(filePath).Equals(Parser.LooseLeafVolume))
|
||||
if (string.IsNullOrEmpty(info.Volume) && Parser.ParseVolume(filePath, LibraryType.Manga).Equals(Parser.LooseLeafVolume))
|
||||
{
|
||||
info.Count = 1;
|
||||
}
|
||||
|
@ -560,14 +560,14 @@ public class BookService : IBookService
|
|||
info.Writer = string.Join(",",
|
||||
epubBook.Schema.Package.Metadata.Creators.Select(c => Parser.CleanAuthor(c.Creator)));
|
||||
|
||||
var hasVolumeInSeries = !Parser.ParseVolume(info.Title)
|
||||
var hasVolumeInSeries = !Parser.ParseVolume(info.Title, LibraryType.Manga)
|
||||
.Equals(Parser.LooseLeafVolume);
|
||||
|
||||
if (string.IsNullOrEmpty(info.Volume) && hasVolumeInSeries && (!info.Series.Equals(info.Title) || string.IsNullOrEmpty(info.Series)))
|
||||
{
|
||||
// This is likely a light novel for which we can set series from parsed title
|
||||
info.Series = Parser.ParseSeries(info.Title);
|
||||
info.Volume = Parser.ParseVolume(info.Title);
|
||||
info.Series = Parser.ParseSeries(info.Title, LibraryType.Manga);
|
||||
info.Volume = Parser.ParseVolume(info.Title, LibraryType.Manga);
|
||||
}
|
||||
|
||||
return info;
|
||||
|
@ -608,7 +608,6 @@ public class BookService : IBookService
|
|||
item.Property == "display-seq" && item.Refines == metadataItem.Refines);
|
||||
if (count == null || count.Content == "0")
|
||||
{
|
||||
// TODO: Rewrite this to use a StringBuilder
|
||||
// Treat this as a Collection
|
||||
info.SeriesGroup += (string.IsNullOrEmpty(info.StoryArc) ? string.Empty : ",") +
|
||||
readingListElem.Title.Replace(',', '_');
|
||||
|
@ -740,8 +739,6 @@ public class BookService : IBookService
|
|||
|
||||
private static string EscapeTags(string content)
|
||||
{
|
||||
// content = StartingScriptTag().Replace(content, "<script$1></script>");
|
||||
// content = StartingTitleTag().Replace(content, "<title$1></title>");
|
||||
content = Regex.Replace(content, @"<script(.*)(/>)", "<script$1></script>", RegexOptions.None, Parser.RegexTimeout);
|
||||
content = Regex.Replace(content, @"<title(.*)(/>)", "<title$1></title>", RegexOptions.None, Parser.RegexTimeout);
|
||||
return content;
|
||||
|
@ -1043,8 +1040,6 @@ public class BookService : IBookService
|
|||
|
||||
// TODO: We may want to check if there is a toc.ncs file to better handle nested toc
|
||||
// We could do a fallback first with ol/lis
|
||||
//var sections = doc.DocumentNode.SelectNodes("//ol");
|
||||
//if (sections == null)
|
||||
|
||||
|
||||
|
||||
|
@ -1239,7 +1234,7 @@ public class BookService : IBookService
|
|||
{
|
||||
_logger.LogWarning(ex, "[BookService] There was a critical error and prevented thumbnail generation on {BookFile}. Defaulting to no cover image", fileFilePath);
|
||||
_mediaErrorService.ReportMediaIssue(fileFilePath, MediaErrorProducer.BookService,
|
||||
"There was a critical error and prevented thumbnail generation", ex); // TODO: Localize this
|
||||
"There was a critical error and prevented thumbnail generation", ex);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
|
|
@ -489,7 +489,7 @@ public class ReaderService : IReaderService
|
|||
currentChapter.SortOrder,
|
||||
dto => dto.SortOrder);
|
||||
if (chapterId > 0) return chapterId;
|
||||
currentVolume = volumes.FirstOrDefault(v => v.IsLooseLeaf());
|
||||
currentVolume = volumes.Find(v => v.IsLooseLeaf());
|
||||
}
|
||||
|
||||
if (currentVolume != null && currentVolume.IsLooseLeaf())
|
||||
|
@ -506,7 +506,7 @@ public class ReaderService : IReaderService
|
|||
// When we started as a special and there was no loose leafs, reset the currentVolume
|
||||
if (currentVolume == null)
|
||||
{
|
||||
currentVolume = volumes.FirstOrDefault(v => !v.IsLooseLeaf() && !v.IsSpecial());
|
||||
currentVolume = volumes.Find(v => !v.IsLooseLeaf() && !v.IsSpecial());
|
||||
if (currentVolume == null) return -1;
|
||||
return currentVolume.Chapters.OrderBy(x => x.SortOrder).Last()?.Id ?? -1;
|
||||
}
|
||||
|
@ -786,7 +786,7 @@ public class ReaderService : IReaderService
|
|||
}
|
||||
|
||||
var files = _directoryService.GetFilesWithExtension(outputDirectory,
|
||||
Tasks.Scanner.Parser.Parser.ImageFileExtensions);
|
||||
Parser.ImageFileExtensions);
|
||||
return CacheService.GetPageFromFiles(files, pageNum);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -421,6 +422,7 @@ public class ParseScannedFiles
|
|||
_logger.LogDebug("[ScannerService] Found {Count} files for {Folder}", files.Count, folder);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.FileScanProgressEvent($"{files.Count} files in {folder}", library.Name, ProgressEventType.Updated));
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("[ScannerService] {Folder} is empty, no longer in this location, or has no file types that match Library File Types", folder);
|
||||
|
@ -483,17 +485,17 @@ public class ParseScannedFiles
|
|||
}
|
||||
|
||||
chapters = infos
|
||||
.OrderByNatural(info => info.Chapters)
|
||||
.OrderByNatural(info => info.Chapters, StringComparer.InvariantCulture)
|
||||
.ToList();
|
||||
|
||||
counter = 0f;
|
||||
var prevIssue = string.Empty;
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
if (float.TryParse(chapter.Chapters, out var parsedChapter))
|
||||
if (float.TryParse(chapter.Chapters, CultureInfo.InvariantCulture, out var parsedChapter))
|
||||
{
|
||||
counter = parsedChapter;
|
||||
if (!string.IsNullOrEmpty(prevIssue) && float.TryParse(prevIssue, out var prevIssueFloat) && parsedChapter.Is(prevIssueFloat))
|
||||
if (!string.IsNullOrEmpty(prevIssue) && float.TryParse(prevIssue, CultureInfo.InvariantCulture, out var prevIssueFloat) && parsedChapter.Is(prevIssueFloat))
|
||||
{
|
||||
// Bump by 0.1
|
||||
counter += 0.1f;
|
||||
|
@ -565,7 +567,10 @@ public class ParseScannedFiles
|
|||
// Normalize this as many of the cases is a capitalization difference
|
||||
var nonLocalizedSeriesFound = infos
|
||||
.Where(i => !i.IsSpecial)
|
||||
.Select(i => i.Series).DistinctBy(Parser.Parser.Normalize).ToList();
|
||||
.Select(i => i.Series)
|
||||
.DistinctBy(Parser.Parser.Normalize)
|
||||
.ToList();
|
||||
|
||||
if (nonLocalizedSeriesFound.Count == 1)
|
||||
{
|
||||
nonLocalizedSeries = nonLocalizedSeriesFound[0];
|
||||
|
|
|
@ -35,17 +35,15 @@ public class BasicParser(IDirectoryService directoryService, IDefaultParser imag
|
|||
// This will be called if the epub is already parsed once then we call and merge the information, if the
|
||||
if (Parser.IsEpub(filePath))
|
||||
{
|
||||
ret.Chapters = Parser.ParseChapter(fileName);
|
||||
ret.Series = Parser.ParseSeries(fileName);
|
||||
ret.Volumes = Parser.ParseVolume(fileName);
|
||||
ret.Chapters = Parser.ParseChapter(fileName, type);
|
||||
ret.Series = Parser.ParseSeries(fileName, type);
|
||||
ret.Volumes = Parser.ParseVolume(fileName, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.Chapters = type == LibraryType.Comic
|
||||
? Parser.ParseComicChapter(fileName)
|
||||
: Parser.ParseChapter(fileName);
|
||||
ret.Series = type == LibraryType.Comic ? Parser.ParseComicSeries(fileName) : Parser.ParseSeries(fileName);
|
||||
ret.Volumes = type == LibraryType.Comic ? Parser.ParseComicVolume(fileName) : Parser.ParseVolume(fileName);
|
||||
ret.Chapters = Parser.ParseChapter(fileName, type);
|
||||
ret.Series = type == LibraryType.Comic ? Parser.ParseComicSeries(fileName) : Parser.ParseSeries(fileName, type);
|
||||
ret.Volumes = type == LibraryType.Comic ? Parser.ParseComicVolume(fileName) : Parser.ParseVolume(fileName, type);
|
||||
}
|
||||
|
||||
if (ret.Series == string.Empty || Parser.IsImage(filePath))
|
||||
|
@ -61,7 +59,7 @@ public class BasicParser(IDirectoryService directoryService, IDefaultParser imag
|
|||
ret.Edition = edition;
|
||||
}
|
||||
|
||||
var isSpecial = type == LibraryType.Comic ? Parser.IsComicSpecial(fileName) : Parser.IsMangaSpecial(fileName);
|
||||
var isSpecial = Parser.IsSpecial(fileName, type);
|
||||
// We must ensure that we can only parse a special out. As some files will have v20 c171-180+Omake and that
|
||||
// could cause a problem as Omake is a special term, but there is valid volume/chapter information.
|
||||
if (ret.Chapters == Parser.DefaultChapter && ret.Volumes == Parser.LooseLeafVolume && isSpecial)
|
||||
|
|
|
@ -13,25 +13,25 @@ public class BookParser(IDirectoryService directoryService, IBookService bookSer
|
|||
info.ComicInfo = comicInfo;
|
||||
|
||||
// This catches when original library type is Manga/Comic and when parsing with non
|
||||
if (Parser.ParseVolume(info.Series) != Parser.LooseLeafVolume) // Shouldn't this be info.Volume != DefaultVolume?
|
||||
if (Parser.ParseVolume(info.Series, type) != Parser.LooseLeafVolume) // Shouldn't this be info.Volume != DefaultVolume?
|
||||
{
|
||||
var hasVolumeInTitle = !Parser.ParseVolume(info.Title)
|
||||
var hasVolumeInTitle = !Parser.ParseVolume(info.Title, type)
|
||||
.Equals(Parser.LooseLeafVolume);
|
||||
var hasVolumeInSeries = !Parser.ParseVolume(info.Series)
|
||||
var hasVolumeInSeries = !Parser.ParseVolume(info.Series, type)
|
||||
.Equals(Parser.LooseLeafVolume);
|
||||
|
||||
if (string.IsNullOrEmpty(info.ComicInfo?.Volume) && hasVolumeInTitle && (hasVolumeInSeries || string.IsNullOrEmpty(info.Series)))
|
||||
{
|
||||
// NOTE: I'm not sure the comment is true. I've never seen this triggered
|
||||
// This is likely a light novel for which we can set series from parsed title
|
||||
info.Series = Parser.ParseSeries(info.Title);
|
||||
info.Volumes = Parser.ParseVolume(info.Title);
|
||||
info.Series = Parser.ParseSeries(info.Title, type);
|
||||
info.Volumes = Parser.ParseVolume(info.Title, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
var info2 = basicParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Book, comicInfo);
|
||||
info.Merge(info2);
|
||||
if (hasVolumeInSeries && info2 != null && Parser.ParseVolume(info2.Series)
|
||||
if (hasVolumeInSeries && info2 != null && Parser.ParseVolume(info2.Series, type)
|
||||
.Equals(Parser.LooseLeafVolume))
|
||||
{
|
||||
// Override the Series name so it groups appropriately
|
||||
|
|
|
@ -37,8 +37,8 @@ public class ComicVineParser(IDirectoryService directoryService) : DefaultParser
|
|||
FullFilePath = Parser.NormalizePath(filePath),
|
||||
Series = string.Empty,
|
||||
ComicInfo = comicInfo,
|
||||
Chapters = Parser.ParseComicChapter(fileName),
|
||||
Volumes = Parser.ParseComicVolume(fileName)
|
||||
Chapters = Parser.ParseChapter(fileName, type),
|
||||
Volumes = Parser.ParseVolume(fileName, type)
|
||||
};
|
||||
|
||||
// See if we can formulate the name from the ComicInfo
|
||||
|
@ -78,7 +78,7 @@ public class ComicVineParser(IDirectoryService directoryService) : DefaultParser
|
|||
}
|
||||
|
||||
// Check if this is a Special/Annual
|
||||
info.IsSpecial = Parser.IsComicSpecial(info.Filename) || Parser.IsComicSpecial(info.ComicInfo?.Format);
|
||||
info.IsSpecial = Parser.IsSpecial(info.Filename, type) || Parser.IsSpecial(info.ComicInfo?.Format, type);
|
||||
|
||||
// Patch in other information from ComicInfo
|
||||
UpdateFromComicInfo(info);
|
||||
|
|
|
@ -39,13 +39,13 @@ public abstract class DefaultParser(IDirectoryService directoryService) : IDefau
|
|||
public void ParseFromFallbackFolders(string filePath, string rootPath, LibraryType type, ref ParserInfo ret)
|
||||
{
|
||||
var fallbackFolders = directoryService.GetFoldersTillRoot(rootPath, filePath)
|
||||
.Where(f => !Parser.IsMangaSpecial(f))
|
||||
.Where(f => !Parser.IsSpecial(f, type))
|
||||
.ToList();
|
||||
|
||||
if (fallbackFolders.Count == 0)
|
||||
{
|
||||
var rootFolderName = directoryService.FileSystem.DirectoryInfo.New(rootPath).Name;
|
||||
var series = Parser.ParseSeries(rootFolderName);
|
||||
var series = Parser.ParseSeries(rootFolderName, type);
|
||||
|
||||
if (string.IsNullOrEmpty(series))
|
||||
{
|
||||
|
@ -64,16 +64,18 @@ public abstract class DefaultParser(IDirectoryService directoryService) : IDefau
|
|||
{
|
||||
var folder = fallbackFolders[i];
|
||||
|
||||
var parsedVolume = type is LibraryType.Manga ? Parser.ParseVolume(folder) : Parser.ParseComicVolume(folder);
|
||||
var parsedChapter = type is LibraryType.Manga ? Parser.ParseChapter(folder) : Parser.ParseComicChapter(folder);
|
||||
var parsedVolume = Parser.ParseVolume(folder, type);
|
||||
var parsedChapter = Parser.ParseChapter(folder, type);
|
||||
|
||||
if (!parsedVolume.Equals(Parser.LooseLeafVolume) || !parsedChapter.Equals(Parser.DefaultChapter))
|
||||
{
|
||||
if ((string.IsNullOrEmpty(ret.Volumes) || ret.Volumes.Equals(Parser.LooseLeafVolume)) && !string.IsNullOrEmpty(parsedVolume) && !parsedVolume.Equals(Parser.LooseLeafVolume))
|
||||
if ((string.IsNullOrEmpty(ret.Volumes) || ret.Volumes.Equals(Parser.LooseLeafVolume))
|
||||
&& !string.IsNullOrEmpty(parsedVolume) && !parsedVolume.Equals(Parser.LooseLeafVolume))
|
||||
{
|
||||
ret.Volumes = parsedVolume;
|
||||
}
|
||||
if ((string.IsNullOrEmpty(ret.Chapters) || ret.Chapters.Equals(Parser.DefaultChapter)) && !string.IsNullOrEmpty(parsedChapter) && !parsedChapter.Equals(Parser.DefaultChapter))
|
||||
if ((string.IsNullOrEmpty(ret.Chapters) || ret.Chapters.Equals(Parser.DefaultChapter))
|
||||
&& !string.IsNullOrEmpty(parsedChapter) && !parsedChapter.Equals(Parser.DefaultChapter))
|
||||
{
|
||||
ret.Chapters = parsedChapter;
|
||||
}
|
||||
|
@ -82,7 +84,7 @@ public abstract class DefaultParser(IDirectoryService directoryService) : IDefau
|
|||
// Generally users group in series folders. Let's try to parse series from the top folder
|
||||
if (!folder.Equals(ret.Series) && i == fallbackFolders.Count - 1)
|
||||
{
|
||||
var series = Parser.ParseSeries(folder);
|
||||
var series = Parser.ParseSeries(folder, type);
|
||||
|
||||
if (string.IsNullOrEmpty(series))
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -722,20 +722,37 @@ public static class Parser
|
|||
return int.Parse(match);
|
||||
}
|
||||
|
||||
public static bool IsMangaSpecial(string filePath)
|
||||
public static bool IsSpecial(string? filePath, LibraryType type)
|
||||
{
|
||||
filePath = ReplaceUnderscores(filePath);
|
||||
return MangaSpecialRegex.IsMatch(filePath);
|
||||
return type switch
|
||||
{
|
||||
LibraryType.Manga => IsMangaSpecial(filePath),
|
||||
LibraryType.Comic => IsComicSpecial(filePath),
|
||||
LibraryType.Book => IsMangaSpecial(filePath),
|
||||
LibraryType.Image => IsMangaSpecial(filePath),
|
||||
LibraryType.LightNovel => IsMangaSpecial(filePath),
|
||||
LibraryType.ComicVine => IsComicSpecial(filePath),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsComicSpecial(string? filePath)
|
||||
private static bool IsMangaSpecial(string? filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return false;
|
||||
filePath = ReplaceUnderscores(filePath);
|
||||
return MangaSpecialRegex.IsMatch(filePath);
|
||||
}
|
||||
|
||||
private static bool IsComicSpecial(string? filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return false;
|
||||
filePath = ReplaceUnderscores(filePath);
|
||||
return ComicSpecialRegex.IsMatch(filePath);
|
||||
}
|
||||
|
||||
public static string ParseSeries(string filename)
|
||||
|
||||
|
||||
public static string ParseMangaSeries(string filename)
|
||||
{
|
||||
foreach (var regex in MangaSeriesRegex)
|
||||
{
|
||||
|
@ -762,7 +779,7 @@ public static class Parser
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string ParseVolume(string filename)
|
||||
public static string ParseMangaVolume(string filename)
|
||||
{
|
||||
foreach (var regex in MangaVolumeRegex)
|
||||
{
|
||||
|
@ -798,6 +815,7 @@ public static class Parser
|
|||
return LooseLeafVolume;
|
||||
}
|
||||
|
||||
|
||||
private static string FormatValue(string value, bool hasPart)
|
||||
{
|
||||
if (!value.Contains('-'))
|
||||
|
@ -807,6 +825,7 @@ public static class Parser
|
|||
|
||||
var tokens = value.Split("-");
|
||||
var from = RemoveLeadingZeroes(tokens[0]);
|
||||
|
||||
if (tokens.Length != 2) return from;
|
||||
|
||||
// Occasionally users will use c01-c02 instead of c01-02, clean any leftover c
|
||||
|
@ -818,7 +837,49 @@ public static class Parser
|
|||
return $"{from}-{to}";
|
||||
}
|
||||
|
||||
public static string ParseChapter(string filename)
|
||||
public static string ParseSeries(string filename, LibraryType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
LibraryType.Manga => ParseMangaSeries(filename),
|
||||
LibraryType.Comic => ParseComicSeries(filename),
|
||||
LibraryType.Book => ParseMangaSeries(filename),
|
||||
LibraryType.Image => ParseMangaSeries(filename),
|
||||
LibraryType.LightNovel => ParseMangaSeries(filename),
|
||||
LibraryType.ComicVine => ParseComicSeries(filename),
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public static string ParseVolume(string filename, LibraryType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
LibraryType.Manga => ParseMangaVolume(filename),
|
||||
LibraryType.Comic => ParseComicVolume(filename),
|
||||
LibraryType.Book => ParseMangaVolume(filename),
|
||||
LibraryType.Image => ParseMangaVolume(filename),
|
||||
LibraryType.LightNovel => ParseMangaVolume(filename),
|
||||
LibraryType.ComicVine => ParseComicVolume(filename),
|
||||
_ => LooseLeafVolume
|
||||
};
|
||||
}
|
||||
|
||||
public static string ParseChapter(string filename, LibraryType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
LibraryType.Manga => ParseMangaChapter(filename),
|
||||
LibraryType.Comic => ParseComicChapter(filename),
|
||||
LibraryType.Book => ParseMangaChapter(filename),
|
||||
LibraryType.Image => ParseMangaChapter(filename),
|
||||
LibraryType.LightNovel => ParseMangaChapter(filename),
|
||||
LibraryType.ComicVine => ParseComicChapter(filename),
|
||||
_ => DefaultChapter
|
||||
};
|
||||
}
|
||||
|
||||
private static string ParseMangaChapter(string filename)
|
||||
{
|
||||
foreach (var regex in MangaChapterRegex)
|
||||
{
|
||||
|
@ -847,7 +908,7 @@ public static class Parser
|
|||
return $"{value}.5";
|
||||
}
|
||||
|
||||
public static string ParseComicChapter(string filename)
|
||||
private static string ParseComicChapter(string filename)
|
||||
{
|
||||
foreach (var regex in ComicChapterRegex)
|
||||
{
|
||||
|
@ -1003,7 +1064,7 @@ public static class Parser
|
|||
return tokens.Min(t => t.AsFloat());
|
||||
}
|
||||
|
||||
return float.Parse(range);
|
||||
return range.AsFloat();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -1030,7 +1091,7 @@ public static class Parser
|
|||
return tokens.Max(t => t.AsFloat());
|
||||
}
|
||||
|
||||
return float.Parse(range);
|
||||
return range.AsFloat();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
|
|
@ -17,9 +17,7 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc
|
|||
FullFilePath = Parser.NormalizePath(filePath),
|
||||
Series = string.Empty,
|
||||
ComicInfo = comicInfo,
|
||||
Chapters = type == LibraryType.Comic
|
||||
? Parser.ParseComicChapter(fileName)
|
||||
: Parser.ParseChapter(fileName)
|
||||
Chapters = Parser.ParseChapter(fileName, type)
|
||||
};
|
||||
|
||||
if (type == LibraryType.Book)
|
||||
|
@ -27,8 +25,8 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc
|
|||
ret.Chapters = Parser.DefaultChapter;
|
||||
}
|
||||
|
||||
ret.Series = type == LibraryType.Comic ? Parser.ParseComicSeries(fileName) : Parser.ParseSeries(fileName);
|
||||
ret.Volumes = type == LibraryType.Comic ? Parser.ParseComicVolume(fileName) : Parser.ParseVolume(fileName);
|
||||
ret.Series = Parser.ParseSeries(fileName, type);
|
||||
ret.Volumes = Parser.ParseVolume(fileName, type);
|
||||
|
||||
if (ret.Series == string.Empty)
|
||||
{
|
||||
|
@ -43,7 +41,7 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc
|
|||
ret.Edition = edition;
|
||||
}
|
||||
|
||||
var isSpecial = type == LibraryType.Comic ? Parser.IsComicSpecial(fileName) : Parser.IsMangaSpecial(fileName);
|
||||
var isSpecial = Parser.IsSpecial(fileName, type);
|
||||
// We must ensure that we can only parse a special out. As some files will have v20 c171-180+Omake and that
|
||||
// could cause a problem as Omake is a special term, but there is valid volume/chapter information.
|
||||
if (ret.Chapters == Parser.DefaultChapter && ret.Volumes == Parser.LooseLeafVolume && isSpecial)
|
||||
|
|
|
@ -705,7 +705,10 @@ public class ProcessSeries : IProcessSeries
|
|||
chapter.Number = Parser.Parser.MinNumberFromRange(info.Chapters).ToString(CultureInfo.InvariantCulture);
|
||||
chapter.MinNumber = Parser.Parser.MinNumberFromRange(info.Chapters);
|
||||
chapter.MaxNumber = Parser.Parser.MaxNumberFromRange(info.Chapters);
|
||||
chapter.SortOrder = info.IssueOrder;
|
||||
if (!chapter.SortOrderLocked)
|
||||
{
|
||||
chapter.SortOrder = info.IssueOrder;
|
||||
}
|
||||
chapter.Range = chapter.GetNumberTitle();
|
||||
}
|
||||
|
||||
|
@ -725,7 +728,8 @@ public class ProcessSeries : IProcessSeries
|
|||
// Ensure we remove any files that no longer exist AND order
|
||||
existingChapter.Files = existingChapter.Files
|
||||
.Where(f => parsedInfos.Any(p => Parser.Parser.NormalizePath(p.FullFilePath) == Parser.Parser.NormalizePath(f.FilePath)))
|
||||
.OrderByNatural(f => f.FilePath).ToList();
|
||||
.OrderByNatural(f => f.FilePath)
|
||||
.ToList();
|
||||
existingChapter.Pages = existingChapter.Files.Sum(f => f.Pages);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue