mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Refactored the setContinuePoint code from the UI to the backend. Now, through an API, we can get the chapter to restart from. (#973)
This commit is contained in:
parent
3eb494dff4
commit
218f642870
@ -35,7 +35,7 @@ namespace API.Tests.Helpers
|
||||
};
|
||||
}
|
||||
|
||||
public static Chapter CreateChapter(string range, bool isSpecial, List<MangaFile> files = null)
|
||||
public static Chapter CreateChapter(string range, bool isSpecial, List<MangaFile> files = null, int pageCount = 0)
|
||||
{
|
||||
return new Chapter()
|
||||
{
|
||||
@ -43,7 +43,7 @@ namespace API.Tests.Helpers
|
||||
Range = range,
|
||||
Number = API.Parser.Parser.MinimumNumberFromRange(range) + string.Empty,
|
||||
Files = files ?? new List<MangaFile>(),
|
||||
Pages = 0,
|
||||
Pages = pageCount,
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -788,27 +788,227 @@ public class ReaderServiceTests
|
||||
|
||||
#endregion
|
||||
|
||||
// #region GetNumberOfPages
|
||||
//
|
||||
// [Fact]
|
||||
// public void GetNumberOfPages_EPUB()
|
||||
// {
|
||||
// const string testDirectory = "/manga/";
|
||||
// var fileSystem = new MockFileSystem();
|
||||
//
|
||||
// var actualFile = Path.Join(Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService/EPUB"), "The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub")
|
||||
// fileSystem.File.WriteAllBytes("${testDirectory}test.epub", File.ReadAllBytes(actualFile));
|
||||
//
|
||||
// fileSystem.AddDirectory(CacheDirectory);
|
||||
//
|
||||
// var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
// var cs = new CacheService(_logger, _unitOfWork, ds, new MockReadingItemServiceForCacheService(ds));
|
||||
// var readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>(), ds, cs);
|
||||
//
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// #endregion
|
||||
#region GetContinuePoint
|
||||
|
||||
[Fact]
|
||||
public async Task GetContinuePoint_ShouldReturnFirstNonSpecial()
|
||||
{
|
||||
_context.Series.Add(new Series()
|
||||
{
|
||||
Name = "Test",
|
||||
Library = new Library() {
|
||||
Name = "Test LIb",
|
||||
Type = LibraryType.Manga,
|
||||
},
|
||||
Volumes = new List<Volume>()
|
||||
{
|
||||
EntityFactory.CreateVolume("1", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("1", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("2", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("2", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("21", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("22", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("3", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("31", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("32", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds, new MockReadingItemServiceForCacheService(ds));
|
||||
var readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>(), ds, cs);
|
||||
|
||||
// Save progress on first volume chapters and 1st of second volume
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 1,
|
||||
SeriesId = 1,
|
||||
VolumeId = 1
|
||||
}, 1);
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 2,
|
||||
SeriesId = 1,
|
||||
VolumeId = 1
|
||||
}, 1);
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 3,
|
||||
SeriesId = 1,
|
||||
VolumeId = 2
|
||||
}, 1);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var nextChapter = await readerService.GetContinuePoint(1, 1);
|
||||
|
||||
Assert.Equal("22", nextChapter.Range);
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetContinuePoint_ShouldReturnFirstSpecial()
|
||||
{
|
||||
_context.Series.Add(new Series()
|
||||
{
|
||||
Name = "Test",
|
||||
Library = new Library() {
|
||||
Name = "Test LIb",
|
||||
Type = LibraryType.Manga,
|
||||
},
|
||||
Volumes = new List<Volume>()
|
||||
{
|
||||
EntityFactory.CreateVolume("1", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("1", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("2", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("2", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("21", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("0", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("31", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("32", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds, new MockReadingItemServiceForCacheService(ds));
|
||||
var readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>(), ds, cs);
|
||||
|
||||
// Save progress on first volume chapters and 1st of second volume
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 1,
|
||||
SeriesId = 1,
|
||||
VolumeId = 1
|
||||
}, 1);
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 2,
|
||||
SeriesId = 1,
|
||||
VolumeId = 1
|
||||
}, 1);
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 3,
|
||||
SeriesId = 1,
|
||||
VolumeId = 2
|
||||
}, 1);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var nextChapter = await readerService.GetContinuePoint(1, 1);
|
||||
|
||||
Assert.Equal("31", nextChapter.Range);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetContinuePoint_ShouldReturnFirstChapter_WhenAllRead()
|
||||
{
|
||||
_context.Series.Add(new Series()
|
||||
{
|
||||
Name = "Test",
|
||||
Library = new Library() {
|
||||
Name = "Test LIb",
|
||||
Type = LibraryType.Manga,
|
||||
},
|
||||
Volumes = new List<Volume>()
|
||||
{
|
||||
EntityFactory.CreateVolume("1", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("1", false, new List<MangaFile>(), 1),
|
||||
EntityFactory.CreateChapter("2", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
EntityFactory.CreateVolume("2", new List<Chapter>()
|
||||
{
|
||||
EntityFactory.CreateChapter("21", false, new List<MangaFile>(), 1),
|
||||
}),
|
||||
}
|
||||
});
|
||||
|
||||
_context.AppUser.Add(new AppUser()
|
||||
{
|
||||
UserName = "majora2007"
|
||||
});
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||
var cs = new CacheService(_logger, _unitOfWork, ds, new MockReadingItemServiceForCacheService(ds));
|
||||
var readerService = new ReaderService(_unitOfWork, Substitute.For<ILogger<ReaderService>>(), ds, cs);
|
||||
|
||||
// Save progress on first volume chapters and 1st of second volume
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 1,
|
||||
SeriesId = 1,
|
||||
VolumeId = 1
|
||||
}, 1);
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 2,
|
||||
SeriesId = 1,
|
||||
VolumeId = 1
|
||||
}, 1);
|
||||
await readerService.SaveReadingProgress(new ProgressDto()
|
||||
{
|
||||
PageNum = 1,
|
||||
ChapterId = 3,
|
||||
SeriesId = 1,
|
||||
VolumeId = 2
|
||||
}, 1);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var nextChapter = await readerService.GetContinuePoint(1, 1);
|
||||
|
||||
Assert.Equal("1", nextChapter.Range);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -356,6 +356,19 @@ namespace API.Controllers
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Continue point is the chapter which you should start reading again from. If there is no progress on a series, then the first chapter will be returned (non-special unless only specials).
|
||||
/// Otherwise, loop through the chapters and volumes in order to find the next chapter which has progress.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("continue-point")]
|
||||
public async Task<ActionResult<ChapterDto>> GetContinuePoint(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
|
||||
return Ok(await _readerService.GetContinuePoint(seriesId, userId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of bookmarked pages for a given Chapter
|
||||
/// </summary>
|
||||
|
@ -22,6 +22,7 @@ public interface IReaderService
|
||||
Task<int> CapPageToChapter(int chapterId, int page);
|
||||
Task<int> GetNextChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId);
|
||||
Task<int> GetPrevChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId);
|
||||
Task<ChapterDto> GetContinuePoint(int seriesId, int userId);
|
||||
}
|
||||
|
||||
public class ReaderService : IReaderService
|
||||
@ -305,6 +306,39 @@ public class ReaderService : IReaderService
|
||||
return -1;
|
||||
}
|
||||
|
||||
public async Task<ChapterDto> GetContinuePoint(int seriesId, int userId)
|
||||
{
|
||||
// Loop through all chapters that are not in volume 0
|
||||
var volumes = (await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, userId)).ToList();
|
||||
|
||||
var nonSpecialChapters = volumes
|
||||
.Where(v => v.Number != 0)
|
||||
.SelectMany(v => v.Chapters)
|
||||
.OrderBy(c => float.Parse(c.Number))
|
||||
.ToList();
|
||||
|
||||
var currentlyReadingChapter = nonSpecialChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages);
|
||||
|
||||
|
||||
// Check if there are any specials
|
||||
if (currentlyReadingChapter == null)
|
||||
{
|
||||
var volume = volumes.SingleOrDefault(v => v.Number == 0);
|
||||
if (volume == null) return nonSpecialChapters.First();
|
||||
|
||||
foreach (var chapter in volume.Chapters.OrderBy(c => float.Parse(c.Number)))
|
||||
{
|
||||
if (chapter.PagesRead < chapter.Pages)
|
||||
{
|
||||
currentlyReadingChapter = chapter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentlyReadingChapter ?? nonSpecialChapters.First();
|
||||
}
|
||||
|
||||
|
||||
private static int GetNextChapterId(IEnumerable<ChapterDto> chapters, string currentChapterNumber)
|
||||
{
|
||||
|
@ -103,33 +103,8 @@ export class ReaderService {
|
||||
return this.httpClient.get<number>(this.baseUrl + 'reader/prev-chapter?seriesId=' + seriesId + '&volumeId=' + volumeId + '¤tChapterId=' + currentChapterId);
|
||||
}
|
||||
|
||||
getCurrentChapter(volumes: Array<Volume>): Chapter {
|
||||
let currentlyReadingChapter: Chapter | undefined = undefined;
|
||||
const chapters = volumes.filter(v => v.number !== 0).map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters);
|
||||
|
||||
for (const c of chapters) {
|
||||
if (c.pagesRead < c.pages) {
|
||||
currentlyReadingChapter = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentlyReadingChapter === undefined) {
|
||||
// Check if there are specials we can load:
|
||||
const specials = volumes.filter(v => v.number === 0).map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters);
|
||||
for (const c of specials) {
|
||||
if (c.pagesRead < c.pages) {
|
||||
currentlyReadingChapter = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentlyReadingChapter === undefined) {
|
||||
// Default to first chapter
|
||||
currentlyReadingChapter = chapters[0];
|
||||
}
|
||||
}
|
||||
|
||||
return currentlyReadingChapter;
|
||||
getCurrentChapter(seriesId: number) {
|
||||
return this.httpClient.get<Chapter>(this.baseUrl + 'reader/continue-point?seriesId=' + seriesId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,9 +135,9 @@ export class SeriesService {
|
||||
}));
|
||||
}
|
||||
|
||||
getContinueReading(libraryId: number = 0) {
|
||||
return this.httpClient.get<InProgressChapter[]>(this.baseUrl + 'series/continue-reading?libraryId=' + libraryId);
|
||||
}
|
||||
// getContinueReading(libraryId: number = 0) {
|
||||
// return this.httpClient.get<InProgressChapter[]>(this.baseUrl + 'series/continue-reading?libraryId=' + libraryId);
|
||||
// }
|
||||
|
||||
refreshMetadata(series: Series) {
|
||||
return this.httpClient.post(this.baseUrl + 'series/refresh-metadata', {libraryId: series.libraryId, seriesId: series.id});
|
||||
|
@ -354,14 +354,14 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
.filter(action => this.actionFactoryService.filterBookmarksForFormat(action, this.series));
|
||||
this.volumeActions = this.actionFactoryService.getVolumeActions(this.handleVolumeActionCallback.bind(this));
|
||||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this));
|
||||
|
||||
|
||||
|
||||
|
||||
this.seriesService.getVolumes(this.series.id).subscribe(volumes => {
|
||||
this.chapters = volumes.filter(v => v.number === 0).map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters);
|
||||
this.volumes = volumes.sort(this.utilityService.sortVolumes);
|
||||
|
||||
|
||||
this.setContinuePoint();
|
||||
|
||||
const vol0 = this.volumes.filter(v => v.number === 0);
|
||||
this.hasSpecials = vol0.map(v => v.chapters || []).flat().sort(this.utilityService.sortChapters).filter(c => c.isSpecial || isNaN(parseInt(c.range, 10))).length > 0 ;
|
||||
if (this.hasSpecials) {
|
||||
@ -398,7 +398,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
setContinuePoint() {
|
||||
this.hasReadingProgress = this.volumes.filter(v => v.pagesRead > 0).length > 0 || this.chapters.filter(c => c.pagesRead > 0).length > 0;
|
||||
this.currentlyReadingChapter = this.readerService.getCurrentChapter(this.volumes);
|
||||
this.readerService.getCurrentChapter(this.series.id).subscribe(chapter => this.currentlyReadingChapter = chapter);
|
||||
}
|
||||
|
||||
markAsRead(vol: Volume) {
|
||||
@ -488,7 +488,7 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
||||
// If user has progress on the volume, load them where they left off
|
||||
if (volume.pagesRead < volume.pages && volume.pagesRead > 0) {
|
||||
// Find the continue point chapter and load it
|
||||
this.openChapter(this.readerService.getCurrentChapter([volume]));
|
||||
this.readerService.getCurrentChapter(this.series.id).subscribe(chapter => this.openChapter(chapter));
|
||||
return;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user