diff --git a/API.Tests/ChapterSortComparerTest.cs b/API.Tests/ChapterSortComparerTest.cs new file mode 100644 index 000000000..7ab909ec5 --- /dev/null +++ b/API.Tests/ChapterSortComparerTest.cs @@ -0,0 +1,19 @@ +using System.Linq; +using API.Comparators; +using Xunit; + +namespace API.Tests +{ + public class ChapterSortComparerTest + { + [Theory] + [InlineData(new[] {1, 2, 0}, new[] {1, 2, 0})] + [InlineData(new[] {3, 1, 2}, new[] {1, 2, 3})] + [InlineData(new[] {1, 0, 0}, new[] {1, 0, 0})] + public void ChapterSortTest(int[] input, int[] expected) + { + Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparer()).ToArray()); + } + + } +} \ No newline at end of file diff --git a/API.Tests/Services/CacheServiceTests.cs b/API.Tests/Services/CacheServiceTests.cs index e5581c98b..345ff93fb 100644 --- a/API.Tests/Services/CacheServiceTests.cs +++ b/API.Tests/Services/CacheServiceTests.cs @@ -1,4 +1,6 @@ -using API.Interfaces; +using System.Collections.Generic; +using API.Entities; +using API.Interfaces; using API.Services; using Microsoft.Extensions.Logging; using NSubstitute; @@ -8,7 +10,7 @@ namespace API.Tests.Services { public class CacheServiceTests { - private readonly ICacheService _cacheService; + private readonly CacheService _cacheService; private readonly ILogger _logger = Substitute.For>(); private readonly IUnitOfWork _unitOfWork = Substitute.For(); private readonly IArchiveService _archiveService = Substitute.For(); @@ -56,6 +58,42 @@ namespace API.Tests.Services // Assert.Equal(expected, _cacheService.GetCachedPagePath(volume, pageNum)); Assert.True(true); } + + [Fact] + public void GetOrderedChaptersTest() + { + var files = new List() + { + new() + { + Chapter = 1 + }, + new() + { + Chapter = 2 + }, + new() + { + Chapter = 0 + }, + }; + var expected = new List() + { + new() + { + Chapter = 1 + }, + new() + { + Chapter = 2 + }, + new() + { + Chapter = 0 + }, + }; + Assert.NotStrictEqual(expected, _cacheService.GetOrderedChapters(files)); + } } diff --git a/API.Tests/Services/StringLogicalComparerTest.cs b/API.Tests/Services/StringLogicalComparerTest.cs index 25c5d3b2f..3ffa0f8a6 100644 --- a/API.Tests/Services/StringLogicalComparerTest.cs +++ b/API.Tests/Services/StringLogicalComparerTest.cs @@ -11,7 +11,6 @@ namespace API.Tests.Services new[] {"x1.jpg", "x10.jpg", "x3.jpg", "x4.jpg", "x11.jpg"}, new[] {"x1.jpg", "x3.jpg", "x4.jpg", "x10.jpg", "x11.jpg"} )] - public void TestLogicalComparer(string[] input, string[] expected) { NumericComparer nc = new NumericComparer(); diff --git a/API/Comparators/ChapterSortComparer.cs b/API/Comparators/ChapterSortComparer.cs new file mode 100644 index 000000000..725622bec --- /dev/null +++ b/API/Comparators/ChapterSortComparer.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace API.Comparators +{ + public class ChapterSortComparer : IComparer + { + public int Compare(int x, int y) + { + if (x == 0 && y == 0) return 0; + // if x is 0, it comes second + if (x == 0) return 1; + // if y is 0, it comes second + if (y == 0) return -1; + + return x.CompareTo(y); + } + } +} \ No newline at end of file diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index d2fed05f7..7aaba8c94 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -6,6 +6,7 @@ using API.DTOs; using API.Entities; using API.Extensions; using API.Interfaces; +using AutoMapper; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -17,14 +18,16 @@ namespace API.Controllers private readonly ICacheService _cacheService; private readonly ILogger _logger; private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; public ReaderController(IDirectoryService directoryService, ICacheService cacheService, - ILogger logger, IUnitOfWork unitOfWork) + ILogger logger, IUnitOfWork unitOfWork, IMapper mapper) { _directoryService = directoryService; _cacheService = cacheService; _logger = logger; _unitOfWork = unitOfWork; + _mapper = mapper; } [HttpGet("image")] @@ -33,9 +36,12 @@ namespace API.Controllers // Temp let's iterate the directory each call to get next image var volume = await _cacheService.Ensure(volumeId); - var path = _cacheService.GetCachedPagePath(volume, page); + var (path, mangaFile) = _cacheService.GetCachedPagePath(volume, page); + if (string.IsNullOrEmpty(path)) return BadRequest($"No such image for page {page}"); var file = await _directoryService.ReadImageAsync(path); file.Page = page; + file.Chapter = mangaFile.Chapter; + file.MangaFileName = mangaFile.FilePath; return Ok(file); } @@ -57,6 +63,8 @@ namespace API.Controllers { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); _logger.LogInformation($"Saving {user.UserName} progress for {bookmarkDto.VolumeId} to page {bookmarkDto.PageNum}"); + + // TODO: Don't let user bookmark past total pages. user.Progresses ??= new List(); var userProgress = user.Progresses.SingleOrDefault(x => x.VolumeId == bookmarkDto.VolumeId && x.AppUserId == user.Id); diff --git a/API/DTOs/ImageDto.cs b/API/DTOs/ImageDto.cs index 473f2c110..18ffe7178 100644 --- a/API/DTOs/ImageDto.cs +++ b/API/DTOs/ImageDto.cs @@ -9,5 +9,7 @@ public int Height { get; init; } public string Format { get; init; } public byte[] Content { get; init; } + public int Chapter { get; set; } + public string MangaFileName { get; set; } } } \ No newline at end of file diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs index 8fe8e6628..a4b225736 100644 --- a/API/Entities/Series.cs +++ b/API/Entities/Series.cs @@ -27,7 +27,7 @@ namespace API.Entities public DateTime LastModified { get; set; } public byte[] CoverImage { get; set; } /// - /// Sum of all Volume pages + /// Sum of all Volume page counts /// public int Pages { get; set; } diff --git a/API/Interfaces/ICacheService.cs b/API/Interfaces/ICacheService.cs index 4bef9bf06..9bf9eaa71 100644 --- a/API/Interfaces/ICacheService.cs +++ b/API/Interfaces/ICacheService.cs @@ -31,7 +31,7 @@ namespace API.Interfaces /// /// Page number to look for /// - string GetCachedPagePath(Volume volume, int page); + (string path, MangaFile file) GetCachedPagePath(Volume volume, int page); bool CacheDirectoryIsAccessible(); } diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index 694956202..33bf67978 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -169,7 +169,7 @@ namespace API.Parser /// /// Root folder /// or null if Series was empty - public static ParserInfo? Parse(string filePath, string rootPath) + public static ParserInfo Parse(string filePath, string rootPath) { var fileName = Path.GetFileName(filePath); var directoryName = (new FileInfo(filePath)).Directory?.Name; diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index 2308261ce..f9d9242f1 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -108,10 +108,12 @@ namespace API.Services public IEnumerable GetOrderedChapters(ICollection files) { - return files.OrderBy(f => f.Chapter).Where(f => f.Chapter > 0 || f.Volume.Number != 0); + // BUG: This causes a problem because total pages on a volume assumes "specials" to be there + //return files.OrderBy(f => f.Chapter).Where(f => f.Chapter > 0 || f.Volume.Number != 0); + return files.OrderBy(f => f.Chapter, new ChapterSortComparer()); } - public string GetCachedPagePath(Volume volume, int page) + public (string path, MangaFile file) GetCachedPagePath(Volume volume, int page) { // Calculate what chapter the page belongs to var pagesSoFar = 0; @@ -125,12 +127,12 @@ namespace API.Services var files = _directoryService.GetFiles(path); Array.Sort(files, _numericComparer); - return files.ElementAt(page - pagesSoFar); + return (files.ElementAt(page - pagesSoFar), mangaFile); } pagesSoFar += mangaFile.NumberOfPages; } - return ""; + return ("", null); } } } \ No newline at end of file diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 73676d1d9..a4e7c5ce8 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -61,7 +61,7 @@ namespace API.Services FullPath = Path.GetFullPath(imagePath), Width = image.Width, Height = image.Height, - Format = image.Format + Format = image.Format, }; }