Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort Order Fix on Volume Detail Page #3216

Merged
merged 4 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion API.Tests/Services/ScannerServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,18 @@ public async Task ScanLibrary_ComicVine_PublisherFolder()

Assert.NotNull(postLib);
Assert.Equal(4, postLib.Series.Count);
}

[Fact]
public async Task ScanLibrary_ShouldCombineNestedFolder()
{
var testcase = "Series and Series-Series Combined - Manga.json";
var postLib = await GenerateScannerData(testcase);

Assert.True(true);
Assert.NotNull(postLib);
Assert.Single(postLib.Series);
Assert.Single(postLib.Series);
Assert.Equal(2, postLib.Series.First().Volumes.Count);
}

private async Task<Library> GenerateScannerData(string testcase)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"Dress Up Darling/Dress Up Darling Ch 01.cbz",
"Dress Up Darling/Dress Up Darling/Dress Up Darling Vol 01.cbz"
]
30 changes: 16 additions & 14 deletions API/Data/Repositories/VolumeRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,18 @@ public async Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> serie

if (includeChapters)
{
query = query.Include(v => v.Chapters).AsSplitQuery();
query = query
.Includes(VolumeIncludes.Chapters)
.AsSplitQuery();
}
return await query.ToListAsync();
var volumes = await query.ToListAsync();

foreach (var volume in volumes)
{
volume.Chapters = volume.Chapters.OrderBy(c => c.SortOrder).ToList();
}

return volumes;
}

/// <summary>
Expand All @@ -142,12 +151,11 @@ public async Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> serie
{
var volume = await _context.Volume
.Where(vol => vol.Id == volumeId)
.Include(vol => vol.Chapters)
.ThenInclude(c => c.Files)
.Includes(VolumeIncludes.Chapters | VolumeIncludes.Files)
.AsSplitQuery()
.OrderBy(v => v.MinNumber)
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
.FirstOrDefaultAsync(vol => vol.Id == volumeId);

if (volume == null) return null;

Expand All @@ -166,8 +174,7 @@ public async Task<IEnumerable<Volume>> GetVolumes(int seriesId)
{
return await _context.Volume
.Where(vol => vol.SeriesId == seriesId)
.Include(vol => vol.Chapters)
.ThenInclude(c => c.Files)
.Includes(VolumeIncludes.Chapters | VolumeIncludes.Files)
.AsSplitQuery()
.OrderBy(vol => vol.MinNumber)
.ToListAsync();
Expand Down Expand Up @@ -205,24 +212,19 @@ public async Task<IList<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId,

await AddVolumeModifiers(userId, volumes);

foreach (var volume in volumes)
{
volume.Chapters = volume.Chapters.OrderBy(c => c.SortOrder).ToList();
}

return volumes;
}

public async Task<Volume?> GetVolumeByIdAsync(int volumeId)
{
return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId);
return await _context.Volume.FirstOrDefaultAsync(x => x.Id == volumeId);
}

public async Task<IList<Volume>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();
return await _context.Volume
.Include(v => v.Chapters)
.Includes(VolumeIncludes.Chapters)
.Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
.AsSplitQuery()
.ToListAsync();
Expand Down
8 changes: 4 additions & 4 deletions API/Extensions/QueryExtensions/IncludesExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,25 @@ public static IQueryable<Volume> Includes(this IQueryable<Volume> queryable,
if (includes.HasFlag(VolumeIncludes.Files))
{
queryable = queryable
.Include(vol => vol.Chapters.OrderBy(c => c.SortOrder))
.Include(vol => vol.Chapters)
.ThenInclude(c => c.Files);
} else if (includes.HasFlag(VolumeIncludes.Chapters))
{
queryable = queryable
.Include(vol => vol.Chapters.OrderBy(c => c.SortOrder));
.Include(vol => vol.Chapters);
}

if (includes.HasFlag(VolumeIncludes.People))
{
queryable = queryable
.Include(vol => vol.Chapters.OrderBy(c => c.SortOrder))
.Include(vol => vol.Chapters)
.ThenInclude(c => c.People);
}

if (includes.HasFlag(VolumeIncludes.Tags))
{
queryable = queryable
.Include(vol => vol.Chapters.OrderBy(c => c.SortOrder))
.Include(vol => vol.Chapters)
.ThenInclude(c => c.Tags);
}

Expand Down
5 changes: 4 additions & 1 deletion API/Helpers/AutoMapperProfiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ public AutoMapperProfiles()
.ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.Series));
CreateMap<LibraryDto, Library>();
CreateMap<Volume, VolumeDto>()
.ForMember(dest => dest.Number, opt => opt.MapFrom(src => (int) src.MinNumber));
.ForMember(dest => dest.Number,
opt => opt.MapFrom(src => (int) src.MinNumber))
.ForMember(dest => dest.Chapters,
opt => opt.MapFrom(src => src.Chapters.OrderBy(c => c.SortOrder)));
CreateMap<MangaFile, MangaFileDto>();
CreateMap<Series, SeriesDto>();
CreateMap<CollectionTag, CollectionTagDto>();
Expand Down
10 changes: 9 additions & 1 deletion API/Services/BookmarkService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using API.DTOs.Reader;
using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using Hangfire;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -90,6 +91,13 @@ public async Task ConvertBookmarkToEncoding(int bookmarkId)
var bookmark = await _unitOfWork.UserRepository.GetBookmarkAsync(bookmarkId);
if (bookmark == null) return;

// Validate the bookmark isn't already in target format
if (bookmark.FileName.EndsWith(encodeFormat.GetExtension()))
{
// Nothing to ddo
return;
}

bookmark.FileName = await _mediaConversionService.SaveAsEncodingFormat(bookmarkDirectory, bookmark.FileName,
BookmarkStem(bookmark.AppUserId, bookmark.SeriesId, bookmark.ChapterId), encodeFormat);
_unitOfWork.UserRepository.Update(bookmark);
Expand Down Expand Up @@ -137,7 +145,7 @@ public async Task<bool> BookmarkPage(AppUser userWithBookmarks, BookmarkDto book
_unitOfWork.UserRepository.Add(bookmark);
await _unitOfWork.CommitAsync();

if (settings.EncodeMediaAs == EncodeFormat.WEBP)
if (settings.EncodeMediaAs != EncodeFormat.PNG)
{
// Enqueue a task to convert the bookmark to webP
BackgroundJob.Enqueue(() => ConvertBookmarkToEncoding(bookmark.Id));
Expand Down
4 changes: 0 additions & 4 deletions API/Services/SeriesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,6 @@ public async Task<SeriesDetailDto> GetSeriesDetail(int seriesId, int userId)
continue;
}

volume.Chapters = volume.Chapters
.OrderBy(d => d.MinNumber, ChapterSortComparerDefaultLast.Default)
.ToList();

if (RenameVolumeName(volume, libraryType, volumeLabel) || (bookTreatment && !volume.IsSpecial()))
{
processedVolumes.Add(volume);
Expand Down
1 change: 1 addition & 0 deletions API/Services/TaskScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ public async Task ScheduleStatsTasks()

public void AnalyzeFilesForLibrary(int libraryId, bool forceUpdate = false)
{
_logger.LogInformation("Enqueuing library file analysis for: {LibraryId}", libraryId);
BackgroundJob.Enqueue(() => _wordCountAnalyzerService.ScanLibrary(libraryId, forceUpdate));
}

Expand Down
142 changes: 73 additions & 69 deletions API/Services/Tasks/Scanner/ParseScannedFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,15 @@ private async Task ParseAndTrackSeries(Library library, IDictionary<string, ILis
{
if (scannedSeries[series].Count <= 0) continue;

UpdateSortOrder(scannedSeries, series);
try
{
UpdateSortOrder(scannedSeries, series);
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue setting IssueOrder");
}


processedScannedSeries.Add(new ScannedSeriesResult()
{
Expand Down Expand Up @@ -500,88 +508,90 @@ await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
}


private void UpdateSortOrder(ConcurrentDictionary<ParsedSeries, List<ParserInfo>> scannedSeries, ParsedSeries series)
public static void UpdateSortOrder(ConcurrentDictionary<ParsedSeries, List<ParserInfo>> scannedSeries, ParsedSeries series)
{
try
// Set the Sort order per Volume
var volumes = scannedSeries[series].GroupBy(info => info.Volumes);
foreach (var volume in volumes)
{
// Set the Sort order per Volume
var volumes = scannedSeries[series].GroupBy(info => info.Volumes);
foreach (var volume in volumes)
var infos = scannedSeries[series].Where(info => info.Volumes == volume.Key).ToList();
IList<ParserInfo> chapters;
var specialTreatment = infos.TrueForAll(info => info.IsSpecial);
var hasAnySpMarker = infos.Exists(info => info.SpecialIndex > 0);
var counter = 0f;

// Handle specials with SpecialIndex
if (specialTreatment && hasAnySpMarker)
{
var infos = scannedSeries[series].Where(info => info.Volumes == volume.Key).ToList();
IList<ParserInfo> chapters;
var specialTreatment = infos.TrueForAll(info => info.IsSpecial);
var hasAnySpMarker = infos.Exists(info => info.SpecialIndex > 0);
var counter = 0f;
chapters = infos
.OrderBy(info => info.SpecialIndex)
.ToList();

if (specialTreatment && hasAnySpMarker)
foreach (var chapter in chapters)
{
chapters = infos
.OrderBy(info => info.SpecialIndex)
.ToList();

foreach (var chapter in chapters)
{
chapter.IssueOrder = counter;
counter++;
}
continue;
chapter.IssueOrder = counter;
counter++;
}
continue;
}

// Handle specials without SpecialIndex (natural order)
if (specialTreatment)
{
chapters = infos
.OrderByNatural(info => Parser.Parser.RemoveExtensionIfSupported(info.Filename)!)
.ToList();

// If everything is a special but we don't have any SpecialIndex, then order naturally and use 0, 1, 2
if (specialTreatment)
foreach (var chapter in chapters)
{
chapters = infos
.OrderByNatural(info => Parser.Parser.RemoveExtensionIfSupported(info.Filename)!)
.ToList();

foreach (var chapter in chapters)
{
chapter.IssueOrder = counter;
counter++;
}
continue;
chapter.IssueOrder = counter;
counter++;
}
continue;
}

chapters = infos
.OrderByNatural(info => info.Chapters, StringComparer.InvariantCulture)
.ToList();
// Ensure chapters are sorted numerically when possible, otherwise push unparseable to the end
chapters = infos
.OrderBy(info => float.TryParse(info.Chapters, NumberStyles.Any, CultureInfo.InvariantCulture, out var val) ? val : float.MaxValue)
.ToList();

counter = 0f;
var prevIssue = string.Empty;
foreach (var chapter in chapters)
counter = 0f;
var prevIssue = string.Empty;
foreach (var chapter in chapters)
{
if (float.TryParse(chapter.Chapters, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedChapter))
{
if (float.TryParse(chapter.Chapters, CultureInfo.InvariantCulture, out var parsedChapter))
// Parsed successfully, use the numeric value
counter = parsedChapter;
chapter.IssueOrder = counter;

// Increment for next chapter (unless the next has a similar value, then add 0.1)
if (!string.IsNullOrEmpty(prevIssue) && float.TryParse(prevIssue, CultureInfo.InvariantCulture, out var prevIssueFloat) && parsedChapter.Is(prevIssueFloat))
{
counter = parsedChapter;
if (!string.IsNullOrEmpty(prevIssue) && float.TryParse(prevIssue, CultureInfo.InvariantCulture, out var prevIssueFloat) && parsedChapter.Is(prevIssueFloat))
{
// Bump by 0.1
counter += 0.1f;
}
chapter.IssueOrder = counter;
prevIssue = $"{parsedChapter}";
counter += 0.1f; // bump if same value as the previous issue
}
else
prevIssue = $"{parsedChapter}";
}
else
{
// Unparsed chapters: use the current counter and bump for the next
if (!string.IsNullOrEmpty(prevIssue) && prevIssue == counter.ToString(CultureInfo.InvariantCulture))
{
// I need to bump by 0.1f as if the prevIssue matches counter
if (!string.IsNullOrEmpty(prevIssue) && prevIssue == counter + "")
{
// Bump by 0.1
counter += 0.1f;
}
chapter.IssueOrder = counter;
counter++;
prevIssue = chapter.Chapters;
counter += 0.1f; // bump if same value as the previous issue
}
chapter.IssueOrder = counter;
counter++;
prevIssue = chapter.Chapters;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue setting IssueOrder");
}
}


private bool HasAllSeriesFolderNotChangedSinceLastScan(IList<SeriesModified> seriesFolders,
string normalizedFolder)
{
return seriesFolders.All(f => HasSeriesFolderNotChangedSinceLastScan(f, normalizedFolder));
}

/// <summary>
Expand All @@ -603,12 +613,6 @@ private bool HasSeriesFolderNotChangedSinceLastScan(IDictionary<string, IList<Se
return false;
}

private bool HasAllSeriesFolderNotChangedSinceLastScan(IList<SeriesModified> seriesFolders,
string normalizedFolder)
{
return seriesFolders.All(f => HasSeriesFolderNotChangedSinceLastScan(f, normalizedFolder));
}

private bool HasSeriesFolderNotChangedSinceLastScan(SeriesModified seriesModified, string normalizedFolder)
{
return seriesModified.LastScanned.Truncate(TimeSpan.TicksPerSecond) >=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
&& (this.readerService.imageUrlToChapterId(img.src) == chapterId || this.readerService.imageUrlToChapterId(img.src) === -1)
);

console.log('Requesting page ', pageNum, ' found page: ', img, ' and app is requesting new image? ', forceNew);
//console.log('Requesting page ', pageNum, ' found page: ', img, ' and app is requesting new image? ', forceNew);
if (!img || forceNew) {
img = new Image();
img.src = this.getPageUrl(pageNum, chapterId);
Expand Down
Loading
Loading