Bulk Operations (#596)
* Implemented the ability to perform multi-selections on cards. Basic selection code is done, CSS needed and exposing actions. * Implemented a bulk selection bar. Added logic to the card on when to force show checkboxes. * Fixed some bad parsing groups and cases for Comic Chapters. * Hooked up some bulk actions on series detail page. Not hooked up to backend yet. * Fixes #593. URI Enocde library names as sometimes they can have & in them. * Implemented the ability to mark volume/chapters as read/unread. * Hooked up mark as unread with specials as well. * Add to reading list hooked up for Series Detail * Implemented ability to add multiple series to a reading list. * Implemented bulk selection for series cards * Added comments to the new code in ReaderService.cs * Implemented proper styling on bulk operation bar and integrated for collections. * Fixed an issue with shift clicking * Cleaned up css of bulk operations bar * Code cleanup
This commit is contained in:
parent
52c4285168
commit
f5229fd0e6
38 changed files with 1129 additions and 172 deletions
|
@ -101,27 +101,7 @@ namespace API.Controllers
|
|||
user.Progresses ??= new List<AppUserProgress>();
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
foreach (var chapter in volume.Chapters)
|
||||
{
|
||||
var userProgress = GetUserProgressForChapter(user, chapter);
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
user.Progresses.Add(new AppUserProgress
|
||||
{
|
||||
PagesRead = chapter.Pages,
|
||||
VolumeId = volume.Id,
|
||||
SeriesId = markReadDto.SeriesId,
|
||||
ChapterId = chapter.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
userProgress.PagesRead = chapter.Pages;
|
||||
userProgress.SeriesId = markReadDto.SeriesId;
|
||||
userProgress.VolumeId = volume.Id;
|
||||
}
|
||||
}
|
||||
_readerService.MarkChaptersAsRead(user, markReadDto.SeriesId, volume.Chapters);
|
||||
}
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
@ -135,30 +115,6 @@ namespace API.Controllers
|
|||
return BadRequest("There was an issue saving progress");
|
||||
}
|
||||
|
||||
private static AppUserProgress GetUserProgressForChapter(AppUser user, Chapter chapter)
|
||||
{
|
||||
AppUserProgress userProgress = null;
|
||||
try
|
||||
{
|
||||
userProgress =
|
||||
user.Progresses.SingleOrDefault(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// There is a very rare chance that user progress will duplicate current row. If that happens delete one with less pages
|
||||
var progresses = user.Progresses.Where(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id).ToList();
|
||||
if (progresses.Count > 1)
|
||||
{
|
||||
user.Progresses = new List<AppUserProgress>()
|
||||
{
|
||||
user.Progresses.First()
|
||||
};
|
||||
userProgress = user.Progresses.First();
|
||||
}
|
||||
}
|
||||
|
||||
return userProgress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a Series as Unread (progress)
|
||||
|
@ -175,7 +131,7 @@ namespace API.Controllers
|
|||
{
|
||||
foreach (var chapter in volume.Chapters)
|
||||
{
|
||||
var userProgress = GetUserProgressForChapter(user, chapter);
|
||||
var userProgress = ReaderService.GetUserProgressForChapter(user, chapter);
|
||||
|
||||
if (userProgress == null) continue;
|
||||
userProgress.PagesRead = 0;
|
||||
|
@ -206,28 +162,7 @@ namespace API.Controllers
|
|||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
|
||||
var chapters = await _unitOfWork.ChapterRepository.GetChaptersAsync(markVolumeReadDto.VolumeId);
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
var userProgress = user.Progresses.FirstOrDefault(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id);
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
user.Progresses.Add(new AppUserProgress
|
||||
{
|
||||
PagesRead = 0,
|
||||
VolumeId = markVolumeReadDto.VolumeId,
|
||||
SeriesId = markVolumeReadDto.SeriesId,
|
||||
ChapterId = chapter.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
userProgress.PagesRead = 0;
|
||||
userProgress.SeriesId = markVolumeReadDto.SeriesId;
|
||||
userProgress.VolumeId = markVolumeReadDto.VolumeId;
|
||||
}
|
||||
}
|
||||
_readerService.MarkChaptersAsUnread(user, markVolumeReadDto.SeriesId, chapters);
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
|
@ -250,27 +185,119 @@ namespace API.Controllers
|
|||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
|
||||
var chapters = await _unitOfWork.ChapterRepository.GetChaptersAsync(markVolumeReadDto.VolumeId);
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
var userProgress = user.Progresses.FirstOrDefault(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id);
|
||||
_readerService.MarkChaptersAsRead(user, markVolumeReadDto.SeriesId, chapters);
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
user.Progresses.Add(new AppUserProgress
|
||||
{
|
||||
PagesRead = chapter.Pages,
|
||||
VolumeId = markVolumeReadDto.VolumeId,
|
||||
SeriesId = markVolumeReadDto.SeriesId,
|
||||
ChapterId = chapter.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
userProgress.PagesRead = chapter.Pages;
|
||||
userProgress.SeriesId = markVolumeReadDto.SeriesId;
|
||||
userProgress.VolumeId = markVolumeReadDto.VolumeId;
|
||||
}
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Marks all chapters within a list of volumes as Read. All volumes must belong to the same Series.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("mark-multiple-read")]
|
||||
public async Task<ActionResult> MarkMultipleAsRead(MarkVolumesReadDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
|
||||
var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
|
||||
foreach (var chapterId in dto.ChapterIds)
|
||||
{
|
||||
chapterIds.Add(chapterId);
|
||||
}
|
||||
var chapters = await _unitOfWork.ChapterRepository.GetChaptersByIdsAsync(chapterIds);
|
||||
_readerService.MarkChaptersAsRead(user, dto.SeriesId, chapters);
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all chapters within a list of volumes as Unread. All volumes must belong to the same Series.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("mark-multiple-unread")]
|
||||
public async Task<ActionResult> MarkMultipleAsUnread(MarkVolumesReadDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
|
||||
var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
|
||||
foreach (var chapterId in dto.ChapterIds)
|
||||
{
|
||||
chapterIds.Add(chapterId);
|
||||
}
|
||||
var chapters = await _unitOfWork.ChapterRepository.GetChaptersByIdsAsync(chapterIds);
|
||||
_readerService.MarkChaptersAsUnread(user, dto.SeriesId, chapters);
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all chapters within a list of series as Read.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("mark-multiple-series-read")]
|
||||
public async Task<ActionResult> MarkMultipleSeriesAsRead(MarkMultipleSeriesAsReadDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
|
||||
var volumes = await _unitOfWork.SeriesRepository.GetVolumesForSeriesAsync(dto.SeriesIds.ToArray(), true);
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
_readerService.MarkChaptersAsRead(user, volume.SeriesId, volume.Chapters);
|
||||
}
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all chapters within a list of series as Unread.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("mark-multiple-series-unread")]
|
||||
public async Task<ActionResult> MarkMultipleSeriesAsUnread(MarkMultipleSeriesAsReadDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
|
||||
var volumes = await _unitOfWork.SeriesRepository.GetVolumesForSeriesAsync(dto.SeriesIds.ToArray(), true);
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
_readerService.MarkChaptersAsUnread(user, volume.SeriesId, volume.Chapters);
|
||||
}
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
|
|
@ -212,7 +212,7 @@ namespace API.Controllers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update a
|
||||
/// Update the properites (title, summary) of a reading list
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
|
@ -242,6 +242,11 @@ namespace API.Controllers
|
|||
return BadRequest("Could not update reading list");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all chapters from a Series to a reading list
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update-by-series")]
|
||||
public async Task<ActionResult> UpdateListBySeries(UpdateReadingListBySeriesDto dto)
|
||||
{
|
||||
|
@ -273,6 +278,86 @@ namespace API.Controllers
|
|||
return Ok("Nothing to do");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds all chapters from a list of volumes and chapters to a reading list
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update-by-multiple")]
|
||||
public async Task<ActionResult> UpdateListByMultiple(UpdateReadingListByMultipleDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
|
||||
var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
|
||||
foreach (var chapterId in dto.ChapterIds)
|
||||
{
|
||||
chapterIds.Add(chapterId);
|
||||
}
|
||||
|
||||
// If there are adds, tell tracking this has been modified
|
||||
if (await AddChaptersToReadingList(dto.SeriesId, chapterIds, readingList))
|
||||
{
|
||||
_unitOfWork.ReadingListRepository.Update(readingList);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
return Ok("Updated");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
|
||||
return Ok("Nothing to do");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all chapters from a list of series to a reading list
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update-by-multiple-series")]
|
||||
public async Task<ActionResult> UpdateListByMultipleSeries(UpdateReadingListByMultipleSeriesDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
|
||||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest("Reading List does not exist");
|
||||
|
||||
var ids = await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync(dto.SeriesIds.ToArray());
|
||||
|
||||
foreach (var seriesId in ids.Keys)
|
||||
{
|
||||
// If there are adds, tell tracking this has been modified
|
||||
if (await AddChaptersToReadingList(seriesId, ids[seriesId], readingList))
|
||||
{
|
||||
_unitOfWork.ReadingListRepository.Update(readingList);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
return Ok("Updated");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
|
||||
return Ok("Nothing to do");
|
||||
}
|
||||
|
||||
[HttpPost("update-by-volume")]
|
||||
public async Task<ActionResult> UpdateListByVolume(UpdateReadingListByVolumeDto dto)
|
||||
{
|
||||
|
|
9
API/DTOs/Reader/MarkMultipleSeriesAsReadDto.cs
Normal file
9
API/DTOs/Reader/MarkMultipleSeriesAsReadDto.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs.Reader
|
||||
{
|
||||
public class MarkMultipleSeriesAsReadDto
|
||||
{
|
||||
public IReadOnlyList<int> SeriesIds { get; init; }
|
||||
}
|
||||
}
|
20
API/DTOs/Reader/MarkVolumesReadDto.cs
Normal file
20
API/DTOs/Reader/MarkVolumesReadDto.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs.Reader
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used for bulk updating a set of volume and or chapters in one go
|
||||
/// </summary>
|
||||
public class MarkVolumesReadDto
|
||||
{
|
||||
public int SeriesId { get; set; }
|
||||
/// <summary>
|
||||
/// A list of Volumes to mark read
|
||||
/// </summary>
|
||||
public IReadOnlyList<int> VolumeIds { get; set; }
|
||||
/// <summary>
|
||||
/// A list of additional Chapters to mark as read
|
||||
/// </summary>
|
||||
public IReadOnlyList<int> ChapterIds { get; set; }
|
||||
}
|
||||
}
|
12
API/DTOs/ReadingLists/UpdateReadingListByMultipleDto.cs
Normal file
12
API/DTOs/ReadingLists/UpdateReadingListByMultipleDto.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs.ReadingLists
|
||||
{
|
||||
public class UpdateReadingListByMultipleDto
|
||||
{
|
||||
public int SeriesId { get; init; }
|
||||
public int ReadingListId { get; init; }
|
||||
public IReadOnlyList<int> VolumeIds { get; init; }
|
||||
public IReadOnlyList<int> ChapterIds { get; init; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs.ReadingLists
|
||||
{
|
||||
public class UpdateReadingListByMultipleSeriesDto
|
||||
{
|
||||
public int ReadingListId { get; init; }
|
||||
public IReadOnlyList<int> SeriesIds { get; init; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -188,11 +189,16 @@ namespace API.Data.Repositories
|
|||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(int[] seriesIds)
|
||||
public async Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false)
|
||||
{
|
||||
return await _context.Volume
|
||||
.Where(v => seriesIds.Contains(v.SeriesId))
|
||||
.ToListAsync();
|
||||
var query = _context.Volume
|
||||
.Where(v => seriesIds.Contains(v.SeriesId));
|
||||
|
||||
if (includeChapters)
|
||||
{
|
||||
query = query.Include(v => v.Chapters);
|
||||
}
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteSeriesAsync(int seriesId)
|
||||
|
@ -237,6 +243,35 @@ namespace API.Data.Repositories
|
|||
return chapterIds.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns a list of tuples<chapterId, seriesId> back for each series id passed
|
||||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IDictionary<int, IList<int>>> GetChapterIdWithSeriesIdForSeriesAsync(int[] seriesIds)
|
||||
{
|
||||
var volumes = await _context.Volume
|
||||
.Where(v => seriesIds.Contains(v.SeriesId))
|
||||
.Include(v => v.Chapters)
|
||||
.ToListAsync();
|
||||
|
||||
var seriesChapters = new Dictionary<int, IList<int>>();
|
||||
foreach (var v in volumes)
|
||||
{
|
||||
foreach (var c in v.Chapters)
|
||||
{
|
||||
if (!seriesChapters.ContainsKey(v.SeriesId))
|
||||
{
|
||||
var list = new List<int>();
|
||||
seriesChapters.Add(v.SeriesId, list);
|
||||
}
|
||||
seriesChapters[v.SeriesId].Add(c.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return seriesChapters;
|
||||
}
|
||||
|
||||
public async Task AddSeriesModifiers(int userId, List<SeriesDto> series)
|
||||
{
|
||||
var userProgress = await _context.AppUserProgresses
|
||||
|
|
|
@ -44,5 +44,13 @@ namespace API.Data.Repositories
|
|||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => volumeIds.Contains(c.VolumeId))
|
||||
.Select(c => c.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
|
@ -44,11 +45,12 @@ namespace API.Interfaces.Repositories
|
|||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
Task<VolumeDto> GetVolumeDtoAsync(int volumeId);
|
||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(int[] seriesIds);
|
||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
||||
Task<bool> DeleteSeriesAsync(int seriesId);
|
||||
Task<Volume> GetVolumeByIdAsync(int volumeId);
|
||||
Task<Series> GetSeriesByIdAsync(int seriesId);
|
||||
Task<int[]> GetChapterIdsForSeriesAsync(int[] seriesIds);
|
||||
Task<IDictionary<int, IList<int>>> GetChapterIdWithSeriesIdForSeriesAsync(int[] seriesIds);
|
||||
/// <summary>
|
||||
/// Used to add Progress/Rating information to series list.
|
||||
/// </summary>
|
||||
|
|
|
@ -10,5 +10,6 @@ namespace API.Interfaces.Repositories
|
|||
void Update(Volume volume);
|
||||
Task<IList<MangaFile>> GetFilesForVolume(int volumeId);
|
||||
Task<string> GetVolumeCoverImageAsync(int volumeId);
|
||||
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
|
||||
namespace API.Interfaces.Services
|
||||
{
|
||||
public interface IReaderService
|
||||
{
|
||||
void MarkChaptersAsRead(AppUser user, int seriesId, IEnumerable<Chapter> chapters);
|
||||
void MarkChaptersAsUnread(AppUser user, int seriesId, IEnumerable<Chapter> chapters);
|
||||
Task<bool> SaveReadingProgress(ProgressDto progressDto, int userId);
|
||||
Task<int> CapPageToChapter(int chapterId, int page);
|
||||
Task<int> GetNextChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId);
|
||||
|
|
|
@ -25,6 +25,99 @@ namespace API.Interfaces.Services
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all Chapters as Read by creating or updating UserProgress rows. Does not commit.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="chapters"></param>
|
||||
public void MarkChaptersAsRead(AppUser user, int seriesId, IEnumerable<Chapter> chapters)
|
||||
{
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
var userProgress = GetUserProgressForChapter(user, chapter);
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
user.Progresses.Add(new AppUserProgress
|
||||
{
|
||||
PagesRead = chapter.Pages,
|
||||
VolumeId = chapter.VolumeId,
|
||||
SeriesId = seriesId,
|
||||
ChapterId = chapter.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
userProgress.PagesRead = chapter.Pages;
|
||||
userProgress.SeriesId = seriesId;
|
||||
userProgress.VolumeId = chapter.VolumeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all Chapters as Unread by creating or updating UserProgress rows. Does not commit.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="chapters"></param>
|
||||
public void MarkChaptersAsUnread(AppUser user, int seriesId, IEnumerable<Chapter> chapters)
|
||||
{
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
var userProgress = GetUserProgressForChapter(user, chapter);
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
user.Progresses.Add(new AppUserProgress
|
||||
{
|
||||
PagesRead = 0,
|
||||
VolumeId = chapter.VolumeId,
|
||||
SeriesId = seriesId,
|
||||
ChapterId = chapter.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
userProgress.PagesRead = 0;
|
||||
userProgress.SeriesId = seriesId;
|
||||
userProgress.VolumeId = chapter.VolumeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the User Progress for a given Chapter. This will handle any duplicates that might have occured in past versions and will delete them. Does not commit.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="chapter"></param>
|
||||
/// <returns></returns>
|
||||
public static AppUserProgress GetUserProgressForChapter(AppUser user, Chapter chapter)
|
||||
{
|
||||
AppUserProgress userProgress = null;
|
||||
try
|
||||
{
|
||||
userProgress =
|
||||
user.Progresses.SingleOrDefault(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// There is a very rare chance that user progress will duplicate current row. If that happens delete one with less pages
|
||||
var progresses = user.Progresses.Where(x => x.ChapterId == chapter.Id && x.AppUserId == user.Id).ToList();
|
||||
if (progresses.Count > 1)
|
||||
{
|
||||
user.Progresses = new List<AppUserProgress>()
|
||||
{
|
||||
user.Progresses.First()
|
||||
};
|
||||
userProgress = user.Progresses.First();
|
||||
}
|
||||
}
|
||||
|
||||
return userProgress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves progress to DB
|
||||
/// </summary>
|
||||
|
@ -82,6 +175,12 @@ namespace API.Interfaces.Services
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the page is within 0 and total pages for a chapter. Makes one DB call.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <param name="page"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> CapPageToChapter(int chapterId, int page)
|
||||
{
|
||||
var totalPages = await _unitOfWork.ChapterRepository.GetChapterTotalPagesAsync(chapterId);
|
||||
|
|
|
@ -383,22 +383,22 @@ namespace API.Parser
|
|||
RegexTimeout),
|
||||
// Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus)
|
||||
new Regex(
|
||||
@"^(?<Series>.*)(?: |_)v(?<Volume>\d+)(?: |_)(c? ?)(?<Chapter>(\d+(\.\d)?)-?(\d+(\.\d)?)?)(c? ?)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Batman & Catwoman - Trail of the Gun 01, Batman & Grendel (1996) 01 - Devil's Bones, Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus)
|
||||
new Regex(
|
||||
@"^(?<Series>.*)(?: (?<Volume>\d+))",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Batman & Robin the Teen Wonder #0
|
||||
new Regex(
|
||||
@"^(?<Series>.*)(?: |_)#(?<Volume>\d+)",
|
||||
@"^(?<Series>.+?)(?: |_)v(?<Volume>\d+)(?: |_)(c? ?)(?<Chapter>(\d+(\.\d)?)-?(\d+(\.\d)?)?)(c? ?)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Invincible 070.5 - Invincible Returns 1 (2010) (digital) (Minutemen-InnerDemons).cbr
|
||||
new Regex(
|
||||
@"^(?<Series>.*)(?: |_)(c? ?)(?<Chapter>(\d+(\.\d)?)-?(\d+(\.\d)?)?)(c? ?)-",
|
||||
@"^(?<Series>.+?)(?: |_)(c? ?)(?<Chapter>(\d+(\.\d)?)-?(\d+(\.\d)?)?)(c? ?)-",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Batman & Catwoman - Trail of the Gun 01, Batman & Grendel (1996) 01 - Devil's Bones, Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus)
|
||||
new Regex(
|
||||
@"^(?<Series>.+?)(?: (?<Chapter>\d+))",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Batman & Robin the Teen Wonder #0
|
||||
new Regex(
|
||||
@"^(?<Series>.+?)(?:\s|_)#(?<Chapter>\d+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Saga 001 (2012) (Digital) (Empire-Zone)
|
||||
|
@ -408,12 +408,12 @@ namespace API.Parser
|
|||
RegexTimeout),
|
||||
// Amazing Man Comics chapter 25
|
||||
new Regex(
|
||||
@"^(?!Vol)(?<Series>.*)( |_)c(hapter)( |_)(?<Chapter>\d*)",
|
||||
@"^(?!Vol)(?<Series>.+?)( |_)c(hapter)( |_)(?<Chapter>\d*)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
// Amazing Man Comics issue #25
|
||||
new Regex(
|
||||
@"^(?!Vol)(?<Series>.*)( |_)i(ssue)( |_) #(?<Chapter>\d*)",
|
||||
@"^(?!Vol)(?<Series>.+?)( |_)i(ssue)( |_) #(?<Chapter>\d*)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled,
|
||||
RegexTimeout),
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue