You can now send emails if you don't use Authentication (#2718)

This commit is contained in:
Joe Milazzo 2024-02-14 14:36:55 -06:00 committed by GitHub
parent 2d81527bd1
commit 7b7609652c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 414 additions and 350 deletions

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using API.Services.Tasks.Scanner.Parser;
namespace API.Comparators;
@ -17,11 +18,11 @@ public class ChapterSortComparer : IComparer<double>
/// <returns></returns>
public int Compare(double x, double y)
{
if (x == 0.0 && y == 0.0) return 0;
if (x == Parser.DefaultChapterNumber && y == Parser.DefaultChapterNumber) return 0;
// if x is 0, it comes second
if (x == 0.0) return 1;
if (x == Parser.DefaultChapterNumber) return 1;
// if y is 0, it comes second
if (y == 0.0) return -1;
if (y == Parser.DefaultChapterNumber) return -1;
return x.CompareTo(y);
}
@ -40,11 +41,11 @@ public class ChapterSortComparerZeroFirst : IComparer<double>
{
public int Compare(double x, double y)
{
if (x == 0.0 && y == 0.0) return 0;
if (x == Parser.DefaultChapterNumber && y == Parser.DefaultChapterNumber) return 0;
// if x is 0, it comes first
if (x == 0.0) return -1;
if (x == Parser.DefaultChapterNumber) return -1;
// if y is 0, it comes first
if (y == 0.0) return 1;
if (y == Parser.DefaultChapterNumber) return 1;
return x.CompareTo(y);
}
@ -56,11 +57,11 @@ public class SortComparerZeroLast : IComparer<double>
{
public int Compare(double x, double y)
{
if (x == 0.0 && y == 0.0) return 0;
if (x == Parser.DefaultChapterNumber && y == Parser.DefaultChapterNumber) return 0;
// if x is 0, it comes last
if (x == 0.0) return 1;
if (x == Parser.DefaultChapterNumber) return 1;
// if y is 0, it comes last
if (y == 0.0) return -1;
if (y == Parser.DefaultChapterNumber) return -1;
return x.CompareTo(y);
}

View file

@ -19,6 +19,7 @@ using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.Services;
using API.Services.Tasks.Scanner.Parser;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -1104,12 +1105,12 @@ public class OpdsController : BaseApiController
{
var volumeLabel = await _localizationService.Translate(userId, "volume-num", string.Empty);
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType, volumeLabel);
if (volume.Name != "0")
if (volume.Name != Services.Tasks.Scanner.Parser.Parser.DefaultChapter)
{
title += $" - {volume.Name}";
}
}
else if (volume.MinNumber != 0)
else if (!volume.IsLooseLeaf())
{
title = $"{series.Name} - Volume {volume.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
}

View file

@ -263,10 +263,10 @@ public class ReaderController : BaseApiController
info.Title += " - " + info.ChapterTitle;
}
if (info.IsSpecial && dto.VolumeNumber.Equals(Services.Tasks.Scanner.Parser.Parser.DefaultVolume))
if (info.IsSpecial && dto.VolumeNumber.Equals(Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume))
{
info.Subtitle = info.FileName;
} else if (!info.IsSpecial && info.VolumeNumber.Equals(Services.Tasks.Scanner.Parser.Parser.DefaultVolume))
} else if (!info.IsSpecial && info.VolumeNumber.Equals(Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume))
{
info.Subtitle = ReaderService.FormatChapterName(info.LibraryType, true, true) + info.ChapterNumber;
}

View file

@ -96,7 +96,7 @@ public class ServerSettingDto
public bool IsEmailSetup()
{
return !string.IsNullOrEmpty(SmtpConfig.Host)
&& !string.IsNullOrEmpty(SmtpConfig.UserName)
&& !string.IsNullOrEmpty(SmtpConfig.SenderAddress)
&& !string.IsNullOrEmpty(HostName);
}
@ -107,6 +107,6 @@ public class ServerSettingDto
public bool IsEmailSetupForSendToDevice()
{
return !string.IsNullOrEmpty(SmtpConfig.Host)
&& !string.IsNullOrEmpty(SmtpConfig.UserName);
&& !string.IsNullOrEmpty(SmtpConfig.SenderAddress);
}
}

View file

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using API.Entities;
using API.Entities.Interfaces;
using API.Services.Tasks.Scanner.Parser;
namespace API.DTOs;
@ -42,4 +43,13 @@ public class VolumeDto : IHasReadTimeEstimate
public int MaxHoursToRead { get; set; }
/// <inheritdoc cref="IHasReadTimeEstimate.AvgHoursToRead"/>
public int AvgHoursToRead { get; set; }
/// <summary>
/// Is this a loose leaf volume
/// </summary>
/// <returns></returns>
public bool IsLooseLeaf()
{
return Math.Abs(this.MinNumber - Parser.LooseLeafVolumeNumber) < 0.001f;
}
}

View file

@ -215,7 +215,7 @@ public class VolumeRepository : IVolumeRepository
private static void SortSpecialChapters(IEnumerable<VolumeDto> volumes)
{
foreach (var v in volumes.Where(vDto => vDto.MinNumber == 0))
foreach (var v in volumes.WhereLooseLeaf())
{
v.Chapters = v.Chapters.OrderByNatural(x => x.Range).ToList();
}

View file

@ -129,7 +129,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
IsSpecial = info.IsSpecialInfo();
if (IsSpecial)
{
Number = "0";
Number = Parser.DefaultChapter;
}
Title = (IsSpecial && info.Format == MangaFormat.Epub)
? info.Title

View file

@ -34,16 +34,16 @@ public static class SeriesExtensions
}
// just volumes
if (volumes.TrueForAll(v => $"{v.MinNumber}" != Parser.DefaultVolume))
if (volumes.TrueForAll(v => $"{v.MinNumber}" != Parser.LooseLeafVolume))
{
return firstVolume.CoverImage;
}
// If we have loose leaf chapters
// if loose leaf chapters AND volumes, just return first volume
if (volumes.Count >= 1 && $"{volumes[0].MinNumber}" != Parser.DefaultVolume)
if (volumes.Count >= 1 && $"{volumes[0].MinNumber}" != Parser.LooseLeafVolume)
{
var looseLeafChapters = volumes.Where(v => $"{v.MinNumber}" == Parser.DefaultVolume)
var looseLeafChapters = volumes.Where(v => $"{v.MinNumber}" == Parser.LooseLeafVolume)
.SelectMany(c => c.Chapters.Where(c => !c.IsSpecial))
.OrderBy(c => c.Number.AsDouble(), ChapterSortComparerZeroFirst.Default)
.ToList();
@ -55,7 +55,7 @@ public static class SeriesExtensions
}
var firstLooseLeafChapter = volumes
.Where(v => $"{v.MinNumber}" == Parser.DefaultVolume)
.Where(v => $"{v.MinNumber}" == Parser.LooseLeafVolume)
.SelectMany(v => v.Chapters)
.OrderBy(c => c.Number.AsDouble(), ChapterSortComparerZeroFirst.Default)
.FirstOrDefault(c => !c.IsSpecial);

View file

@ -1,6 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using API.DTOs;
using API.Entities;
using API.Entities.Enums;
using API.Services.Tasks.Scanner.Parser;
@ -26,13 +29,52 @@ public static class VolumeListExtensions
return volumes.MinBy(x => x.MinNumber);
}
if (volumes.Any(x => x.MinNumber != 0f)) // TODO: Refactor this so we can avoid a magic number
if (volumes.HasAnyNonLooseLeafVolumes())
{
return volumes.OrderBy(x => x.MinNumber).FirstOrDefault(x => x.MinNumber != 0);
return volumes.FirstNonLooseLeafOrDefault();
}
// We only have 1 volume of chapters, we need to be cautious if there are specials, as we don't want to order them first
return volumes.MinBy(x => x.MinNumber);
}
/// <summary>
/// If the collection of volumes has any non-loose leaf volumes
/// </summary>
/// <param name="volumes"></param>
/// <returns></returns>
public static bool HasAnyNonLooseLeafVolumes(this IEnumerable<Volume> volumes)
{
return volumes.Any(x => Math.Abs(x.MinNumber - Parser.DefaultChapterNumber) > 0.001f);
}
/// <summary>
/// Returns first non-loose leaf volume
/// </summary>
/// <param name="volumes"></param>
/// <returns></returns>
public static Volume? FirstNonLooseLeafOrDefault(this IEnumerable<Volume> volumes)
{
return volumes.OrderBy(x => x.MinNumber).FirstOrDefault(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) >= 0.001f);
}
/// <summary>
/// Returns the first (and only) loose leaf volume or null if none
/// </summary>
/// <param name="volumes"></param>
/// <returns></returns>
public static Volume? GetLooseLeafVolumeOrDefault(this IEnumerable<Volume> volumes)
{
return volumes.FirstOrDefault(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) < 0.001f);
}
public static IEnumerable<VolumeDto> WhereNotLooseLeaf(this IEnumerable<VolumeDto> volumes)
{
return volumes.Where(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) >= 0.001f);
}
public static IEnumerable<VolumeDto> WhereLooseLeaf(this IEnumerable<VolumeDto> volumes)
{
return volumes.Where(v => Math.Abs(v.MinNumber - Parser.DefaultChapterNumber) < 0.001f);
}
}

View file

@ -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.DefaultVolume))
if (string.IsNullOrEmpty(info.Volume) && Parser.ParseVolume(filePath).Equals(Parser.LooseLeafVolume))
{
info.Count = 1;
}
@ -561,7 +561,7 @@ public class BookService : IBookService
epubBook.Schema.Package.Metadata.Creators.Select(c => Parser.CleanAuthor(c.Creator)));
var hasVolumeInSeries = !Parser.ParseVolume(info.Title)
.Equals(Parser.DefaultVolume);
.Equals(Parser.LooseLeafVolume);
if (string.IsNullOrEmpty(info.Volume) && hasVolumeInSeries && (!info.Series.Equals(info.Title) || string.IsNullOrEmpty(info.Series)))
{
@ -873,7 +873,7 @@ public class BookService : IBookService
FullFilePath = filePath,
IsSpecial = false,
Series = epubBook.Title.Trim(),
Volumes = Parser.DefaultVolume,
Volumes = Parser.LooseLeafVolume,
};
}
catch (Exception ex)

View file

@ -274,7 +274,7 @@ public class ScrobblingService : IScrobblingService
SeriesId = series.Id,
LibraryId = series.LibraryId,
ScrobbleEventType = ScrobbleEventType.ScoreUpdated,
AniListId = ExtractId<int?>(series.Metadata.WebLinks, AniListWeblinkWebsite),
AniListId = ExtractId<int?>(series.Metadata.WebLinks, AniListWeblinkWebsite), // TODO: We can get this also from ExternalSeriesMetadata
MalId = ExtractId<long?>(series.Metadata.WebLinks, MalWeblinkWebsite),
AppUserId = userId,
Format = LibraryTypeHelper.GetFormat(series.Library.Type),

View file

@ -146,9 +146,8 @@ public class ReaderService : IReaderService
MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName!, seriesId, chapter.VolumeId, chapter.Id, chapter.Pages));
// Send out volume events for each distinct volume
if (!seenVolume.ContainsKey(chapter.VolumeId))
if (seenVolume.TryAdd(chapter.VolumeId, true))
{
seenVolume[chapter.VolumeId] = true;
await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName!, seriesId,
chapter.VolumeId, 0, chapters.Where(c => c.VolumeId == chapter.VolumeId).Sum(c => c.Pages)));
@ -365,7 +364,7 @@ public class ReaderService : IReaderService
var currentVolume = volumes.Single(v => v.Id == volumeId);
var currentChapter = currentVolume.Chapters.Single(c => c.Id == currentChapterId);
if (currentVolume.MinNumber == 0)
if (currentVolume.IsLooseLeaf())
{
// Handle specials by sorting on their Filename aka Range
var chapterId = GetNextChapterId(currentVolume.Chapters.OrderByNatural(x => x.Range), currentChapter.Range, dto => dto.Range);
@ -416,12 +415,12 @@ public class ReaderService : IReaderService
if (chapterId > 0) return chapterId;
} else if (firstChapter.Number.AsDouble() >= currentChapter.Number.AsDouble()) return firstChapter.Id;
// If we are the last chapter and next volume is there, we should try to use it (unless it's volume 0)
else if (firstChapter.Number.AsDouble() == 0) return firstChapter.Id;
else if (firstChapter.Number.AsDouble() == Parser.DefaultChapterNumber) return firstChapter.Id;
// If on last volume AND there are no specials left, then let's return -1
var anySpecials = volumes.Where(v => $"{v.MinNumber}" == Parser.DefaultVolume)
var anySpecials = volumes.Where(v => $"{v.MinNumber}" == Parser.LooseLeafVolume)
.SelectMany(v => v.Chapters.Where(c => c.IsSpecial)).Any();
if (currentVolume.MinNumber != 0 && !anySpecials)
if (!currentVolume.IsLooseLeaf() && !anySpecials)
{
return -1;
}
@ -433,10 +432,10 @@ public class ReaderService : IReaderService
// This has an added problem that it will loop up to the beginning always
// Should I change this to Max number? volumes.LastOrDefault()?.Number -> volumes.Max(v => v.Number)
if (currentVolume.MinNumber != 0 && currentVolume.MinNumber == volumes.LastOrDefault()?.MinNumber && volumes.Count > 1)
if (!currentVolume.IsLooseLeaf() && currentVolume.MinNumber == volumes.LastOrDefault()?.MinNumber && volumes.Count > 1)
{
var chapterVolume = volumes.FirstOrDefault();
if (chapterVolume?.MinNumber != 0) return -1;
if (chapterVolume == null || !chapterVolume.IsLooseLeaf()) return -1;
// This is my attempt at fixing a bug where we loop around to the beginning, but I just can't seem to figure it out
// var orderedVolumes = volumes.OrderBy(v => v.Number, SortComparerZeroLast.Default).ToList();
@ -478,7 +477,7 @@ public class ReaderService : IReaderService
var currentVolume = volumes.Single(v => v.Id == volumeId);
var currentChapter = currentVolume.Chapters.Single(c => c.Id == currentChapterId);
if (currentVolume.MinNumber == 0)
if (currentVolume.IsLooseLeaf())
{
var chapterId = GetNextChapterId(currentVolume.Chapters.OrderByNatural(x => x.Range).Reverse(), currentChapter.Range,
dto => dto.Range);
@ -498,7 +497,7 @@ public class ReaderService : IReaderService
}
if (next)
{
if (currentVolume.MinNumber - 1 == 0) break; // If we have walked all the way to chapter volume, then we should break so logic outside can work
if (currentVolume.MinNumber - 1 == Parser.LooseLeafVolumeNumber) break; // If we have walked all the way to chapter volume, then we should break so logic outside can work
var lastChapter = volume.Chapters.MaxBy(x => x.Number.AsDouble(), _chapterSortComparerForInChapterSorting);
if (lastChapter == null) return -1;
return lastChapter.Id;
@ -506,7 +505,7 @@ public class ReaderService : IReaderService
}
var lastVolume = volumes.MaxBy(v => v.MinNumber);
if (currentVolume.MinNumber == 0 && currentVolume.MinNumber != lastVolume?.MinNumber && lastVolume?.Chapters.Count > 1)
if (currentVolume.IsLooseLeaf() && currentVolume.MinNumber != lastVolume?.MinNumber && lastVolume?.Chapters.Count > 1)
{
var lastChapter = lastVolume.Chapters.MaxBy(x => x.Number.AsDouble(), _chapterSortComparerForInChapterSorting);
if (lastChapter == null) return -1;
@ -553,7 +552,7 @@ public class ReaderService : IReaderService
// Loop through all chapters that are not in volume 0
var volumeChapters = volumes
.Where(v => v.MinNumber != 0)
.WhereNotLooseLeaf()
.SelectMany(v => v.Chapters)
.ToList();

View file

@ -69,12 +69,12 @@ public class ReadingItemService : IReadingItemService
// This catches when original library type is Manga/Comic and when parsing with non
if (Parser.IsEpub(path) && Parser.ParseVolume(info.Series) != Parser.DefaultVolume) // Shouldn't this be info.Volume != DefaultVolume?
if (Parser.IsEpub(path) && Parser.ParseVolume(info.Series) != Parser.LooseLeafVolume) // Shouldn't this be info.Volume != DefaultVolume?
{
var hasVolumeInTitle = !Parser.ParseVolume(info.Title)
.Equals(Parser.DefaultVolume);
.Equals(Parser.LooseLeafVolume);
var hasVolumeInSeries = !Parser.ParseVolume(info.Series)
.Equals(Parser.DefaultVolume);
.Equals(Parser.LooseLeafVolume);
if (string.IsNullOrEmpty(info.ComicInfo?.Volume) && hasVolumeInTitle && (hasVolumeInSeries || string.IsNullOrEmpty(info.Series)))
{
@ -117,7 +117,7 @@ public class ReadingItemService : IReadingItemService
{
info.IsSpecial = true;
info.Chapters = Parser.DefaultChapter;
info.Volumes = Parser.DefaultVolume;
info.Volumes = Parser.LooseLeafVolume;
}
if (!string.IsNullOrEmpty(info.ComicInfo.SeriesSort))

View file

@ -71,7 +71,7 @@ public class ReadingListService : IReadingListService
public static string FormatTitle(ReadingListItemDto item)
{
var title = string.Empty;
if (item.ChapterNumber == Parser.DefaultChapter && item.VolumeNumber != Parser.DefaultVolume) {
if (item.ChapterNumber == Parser.DefaultChapter && item.VolumeNumber != Parser.LooseLeafVolume) {
title = $"Volume {item.VolumeNumber}";
}
@ -631,9 +631,9 @@ public class ReadingListService : IReadingListService
}
// Prioritize lookup by Volume then Chapter, but allow fallback to just Chapter
var bookVolume = string.IsNullOrEmpty(book.Volume)
? Parser.DefaultVolume
? Parser.LooseLeafVolume
: book.Volume;
var matchingVolume = bookSeries.Volumes.Find(v => bookVolume == v.Name) ?? bookSeries.Volumes.Find(v => v.MinNumber == 0);
var matchingVolume = bookSeries.Volumes.Find(v => bookVolume == v.Name) ?? bookSeries.Volumes.GetLooseLeafVolumeOrDefault();
if (matchingVolume == null)
{
importSummary.Results.Add(new CblBookResult(book)

View file

@ -81,10 +81,9 @@ public class SeriesService : ISeriesService
public static Chapter? GetFirstChapterForMetadata(Series series)
{
var sortedVolumes = series.Volumes
.Where(v => float.TryParse(v.Name, CultureInfo.InvariantCulture, out var parsedValue) && parsedValue != 0.0f)
.Where(v => float.TryParse(v.Name, CultureInfo.InvariantCulture, out var parsedValue) && parsedValue != Parser.LooseLeafVolumeNumber)
.OrderBy(v => float.TryParse(v.Name, CultureInfo.InvariantCulture, out var parsedValue) ? parsedValue : float.MaxValue);
var minVolumeNumber = sortedVolumes
.MinBy(v => v.Name.AsFloat());
var minVolumeNumber = sortedVolumes.MinBy(v => v.MinNumber);
var allChapters = series.Volumes
@ -94,7 +93,7 @@ public class SeriesService : ISeriesService
.FirstOrDefault();
if (minVolumeNumber != null && minChapter != null && float.TryParse(minChapter.Number, CultureInfo.InvariantCulture, out var chapNum) &&
(chapNum >= minVolumeNumber.MinNumber || chapNum == 0))
(chapNum >= minVolumeNumber.MinNumber || chapNum == Parser.DefaultChapterNumber))
{
return minVolumeNumber.Chapters.MinBy(c => c.Number.AsFloat(), ChapterSortComparer.Default);
}
@ -516,7 +515,7 @@ public class SeriesService : ISeriesService
var specials = new List<ChapterDto>();
var chapters = volumes.SelectMany(v => v.Chapters.Select(c =>
{
if (v.MinNumber == 0) return c;
if (v.IsLooseLeaf()) return c;
c.VolumeTitle = v.Name;
return c;
}).OrderBy(c => c.Number.AsFloat(), ChapterSortComparer.Default)).ToList();
@ -542,7 +541,7 @@ public class SeriesService : ISeriesService
}
var storylineChapters = volumes
.Where(v => v.MinNumber == 0)
.WhereLooseLeaf()
.SelectMany(v => v.Chapters.Where(c => !c.IsSpecial))
.OrderBy(c => c.Number.AsFloat(), ChapterSortComparer.Default)
.ToList();
@ -575,16 +574,17 @@ public class SeriesService : ISeriesService
public static void RenameVolumeName(ChapterDto firstChapter, VolumeDto volume, LibraryType libraryType, string volumeLabel = "Volume")
{
// TODO: Move this into DB
if (libraryType is LibraryType.Book or LibraryType.LightNovel)
{
if (string.IsNullOrEmpty(firstChapter.TitleName))
{
if (firstChapter.Range.Equals(Parser.DefaultVolume)) return;
if (firstChapter.Range.Equals(Parser.LooseLeafVolume)) return;
var title = Path.GetFileNameWithoutExtension(firstChapter.Range);
if (string.IsNullOrEmpty(title)) return;
volume.Name += $" - {title}";
}
else if (volume.Name != "0")
else if (volume.Name != Parser.LooseLeafVolume)
{
// If the titleName has Volume inside it, let's just send that back?
volume.Name += $" - {firstChapter.TitleName}";

View file

@ -9,6 +9,7 @@ using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Extensions.QueryExtensions;
using API.Services.Tasks.Scanner.Parser;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
@ -269,7 +270,6 @@ public class StatisticService : IStatisticService
var distinctPeople = _context.Person
.AsSplitQuery()
.AsEnumerable()
.GroupBy(sm => sm.NormalizedName)
.Select(sm => sm.Key)
@ -287,7 +287,7 @@ public class StatisticService : IStatisticService
TotalPeople = distinctPeople,
TotalSize = await _context.MangaFile.SumAsync(m => m.Bytes),
TotalTags = await _context.Tag.CountAsync(),
VolumeCount = await _context.Volume.Where(v => v.MinNumber != 0).CountAsync(),
VolumeCount = await _context.Volume.Where(v => Math.Abs(v.MinNumber - Parser.LooseLeafVolumeNumber) > 0.001f).CountAsync(),
MostActiveUsers = mostActiveUsers,
MostActiveLibraries = mostActiveLibrary,
MostPopularSeries = mostPopularSeries,

View file

@ -69,14 +69,14 @@ public class TachiyomiService : ITachiyomiService
// Else return the max chapter to Tachiyomi so it can consider everything read
var volumes = (await _unitOfWork.VolumeRepository.GetVolumes(seriesId)).ToImmutableList();
var looseLeafChapterVolume = volumes.Find(v => v.MinNumber == 0);
var looseLeafChapterVolume = volumes.GetLooseLeafVolumeOrDefault();
if (looseLeafChapterVolume == null)
{
var volumeChapter = _mapper.Map<ChapterDto>(volumes
[^1].Chapters
.OrderBy(c => c.Number.AsFloat(), ChapterSortComparerZeroFirst.Default)
.Last());
if (volumeChapter.Number == Parser.DefaultVolume)
if (volumeChapter.Number == Parser.LooseLeafVolume)
{
var volume = volumes.First(v => v.Id == volumeChapter.VolumeId);
return new ChapterDto()
@ -104,7 +104,7 @@ public class TachiyomiService : ITachiyomiService
var volumeWithProgress = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(prevChapter.VolumeId, userId);
// We only encode for single-file volumes
if (volumeWithProgress!.MinNumber != 0 && volumeWithProgress.Chapters.Count == 1)
if (!volumeWithProgress!.IsLooseLeaf() && volumeWithProgress.Chapters.Count == 1)
{
// The progress is on a volume, encode it as a fake chapterDTO
return new ChapterDto()

View file

@ -86,7 +86,7 @@ public class DefaultParser : IDefaultParser
var isSpecial = type == LibraryType.Comic ? Parser.IsComicSpecial(fileName) : Parser.IsMangaSpecial(fileName);
// 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.DefaultVolume && isSpecial)
if (ret.Chapters == Parser.DefaultChapter && ret.Volumes == Parser.LooseLeafVolume && isSpecial)
{
ret.IsSpecial = true;
ParseFromFallbackFolders(filePath, rootPath, type, ref ret); // NOTE: This can cause some complications, we should try to be a bit less aggressive to fallback to folder
@ -97,7 +97,7 @@ public class DefaultParser : IDefaultParser
{
ret.IsSpecial = true;
ret.Chapters = Parser.DefaultChapter;
ret.Volumes = Parser.DefaultVolume;
ret.Volumes = Parser.LooseLeafVolume;
ParseFromFallbackFolders(filePath, rootPath, type, ref ret);
}
@ -118,7 +118,7 @@ public class DefaultParser : IDefaultParser
private ParserInfo ParseImage(string filePath, string rootPath, ParserInfo ret)
{
ret.Volumes = Parser.DefaultVolume;
ret.Volumes = Parser.LooseLeafVolume;
ret.Chapters = Parser.DefaultChapter;
var directoryName = _directoryService.FileSystem.DirectoryInfo.New(rootPath).Name;
ret.Series = directoryName;
@ -134,7 +134,7 @@ public class DefaultParser : IDefaultParser
{
var parsedVolume = Parser.ParseVolume(ret.Filename);
var parsedChapter = Parser.ParseChapter(ret.Filename);
if (IsEmptyOrDefault(ret.Volumes, string.Empty) && !parsedVolume.Equals(Parser.DefaultVolume))
if (IsEmptyOrDefault(ret.Volumes, string.Empty) && !parsedVolume.Equals(Parser.LooseLeafVolume))
{
ret.Volumes = parsedVolume;
}
@ -157,7 +157,7 @@ public class DefaultParser : IDefaultParser
private static bool IsEmptyOrDefault(string volumes, string chapters)
{
return (string.IsNullOrEmpty(chapters) || chapters == Parser.DefaultChapter) &&
(string.IsNullOrEmpty(volumes) || volumes == Parser.DefaultVolume);
(string.IsNullOrEmpty(volumes) || volumes == Parser.LooseLeafVolume);
}
/// <summary>
@ -198,9 +198,9 @@ public class DefaultParser : IDefaultParser
var parsedVolume = type is LibraryType.Manga ? Parser.ParseVolume(folder) : Parser.ParseComicVolume(folder);
var parsedChapter = type is LibraryType.Manga ? Parser.ParseChapter(folder) : Parser.ParseComicChapter(folder);
if (!parsedVolume.Equals(Parser.DefaultVolume) || !parsedChapter.Equals(Parser.DefaultChapter))
if (!parsedVolume.Equals(Parser.LooseLeafVolume) || !parsedChapter.Equals(Parser.DefaultChapter))
{
if ((string.IsNullOrEmpty(ret.Volumes) || ret.Volumes.Equals(Parser.DefaultVolume)) && !string.IsNullOrEmpty(parsedVolume) && !parsedVolume.Equals(Parser.DefaultVolume))
if ((string.IsNullOrEmpty(ret.Volumes) || ret.Volumes.Equals(Parser.LooseLeafVolume)) && !string.IsNullOrEmpty(parsedVolume) && !parsedVolume.Equals(Parser.LooseLeafVolume))
{
ret.Volumes = parsedVolume;
}

View file

@ -11,8 +11,11 @@ namespace API.Services.Tasks.Scanner.Parser;
public static class Parser
{
public const string DefaultChapter = "0";
public const string DefaultVolume = "0";
// NOTE: If you change this, don't forget to change in the UI (see Series Detail)
public const string DefaultChapter = "0"; // -2147483648
public const string LooseLeafVolume = "0";
public const int DefaultChapterNumber = 0;
public const int LooseLeafVolumeNumber = 0;
public static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(500);
public const string ImageFileExtensions = @"^(\.png|\.jpeg|\.jpg|\.webp|\.gif|\.avif)"; // Don't forget to update CoverChooser
@ -729,7 +732,7 @@ public static class Parser
}
}
return DefaultVolume;
return LooseLeafVolume;
}
public static string ParseComicVolume(string filename)
@ -747,7 +750,7 @@ public static class Parser
}
}
return DefaultVolume;
return LooseLeafVolume;
}
private static string FormatValue(string value, bool hasPart)

View file

@ -73,7 +73,7 @@ public class ParserInfo
/// <returns></returns>
public bool IsSpecialInfo()
{
return (IsSpecial || (Volumes == Parser.DefaultVolume && Chapters == Parser.DefaultChapter));
return (IsSpecial || (Volumes == Parser.LooseLeafVolume && Chapters == Parser.DefaultChapter));
}
/// <summary>
@ -91,7 +91,7 @@ public class ParserInfo
{
if (info2 == null) return;
Chapters = string.IsNullOrEmpty(Chapters) || Chapters == Parser.DefaultChapter ? info2.Chapters: Chapters;
Volumes = string.IsNullOrEmpty(Volumes) || Volumes == Parser.DefaultVolume ? info2.Volumes : Volumes;
Volumes = string.IsNullOrEmpty(Volumes) || Volumes == Parser.LooseLeafVolume ? info2.Volumes : Volumes;
Edition = string.IsNullOrEmpty(Edition) ? info2.Edition : Edition;
Title = string.IsNullOrEmpty(Title) ? info2.Title : Title;
Series = string.IsNullOrEmpty(Series) ? info2.Series : Series;

View file

@ -325,7 +325,7 @@ public class ProcessSeries : IProcessSeries
{
// If a series has a TotalCount of 1 (or no total count) and there is only a Special, mark it as Complete
series.Metadata.MaxCount = series.Metadata.TotalCount;
} else if ((maxChapter == 0 || maxChapter > series.Metadata.TotalCount) && maxVolume <= series.Metadata.TotalCount)
} else if ((maxChapter == Parser.Parser.DefaultChapterNumber || maxChapter > series.Metadata.TotalCount) && maxVolume <= series.Metadata.TotalCount)
{
series.Metadata.MaxCount = maxVolume;
} else if (maxVolume == series.Metadata.TotalCount)