Fixes, Tweaks, and Series Filtering (#1217)
* From previous fix, added the other locking conditions on the update series metadata. * Fixed a bug where custom series, collection tag, and reading list covers weren't being removed on cleanup. * Ensure reading list detail has a margin to align to the standard * Refactored some event stuff to use dedicated consts. Introduced a new event when users read something, which can update progress bars on cards. * Added recomended and library tags to the library detail page. This will eventually offer more custom analytics * Cleanup some code onc arousel * Adjusted scale to height/width css to better fit * Small css tweaks to better center images in the manga reader in both axis. This takes care of double page rendering as well. * When a special has a Title set in the metadata, on series detail page, show that on the card rather than filename. * Fixed a bug where when paging in manga reader, the scroll to top wasn't working due to changing where scrolling is done * More css goodness for rendering images in manga reader * Fixed a bug where clearing a typeahead externally wouldn't clear the x button * Fixed a bug where filering then using keyboard would select wrong option * Added a new sorting field for Last Chapter Added (new field) to get a similar on deck feel. * Tweaked recently updated to hit the NFR of 500ms (300ms fresh start) and still give a much better experience. * Refactored On deck to now go to all series and also sort by last updated. Recently Added Series now loads all series with sort by created. * Some tweaks on css for cover image chooser * Fixed a bug in pagination control where multiple pagination events could trigger on load and thus multiple requests for data on parent controller. * Updated edit series modal to show when the last chapter was added and when user last read it. * Implemented a highlight on the fitler button when a filter is active. * Refactored metadata filter screens to perserve the filters in the url and thus when navigating back and forth, it will retain. users should click side nav to reset the state. * Hide middle section on companion bar on phones * Cleaned up some prefilters and console.logs * Don't open drawer by default when a filter is active
This commit is contained in:
parent
5e629913b7
commit
553f9b0d98
63 changed files with 864 additions and 537 deletions
|
@ -157,7 +157,7 @@ namespace API.Controllers
|
|||
tag.CoverImageLocked = false;
|
||||
tag.CoverImage = string.Empty;
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
MessageFactory.CoverUpdateEvent(tag.Id, "collection"), false);
|
||||
MessageFactory.CoverUpdateEvent(tag.Id, MessageFactoryEntityTypes.CollectionTag), false);
|
||||
_unitOfWork.CollectionTagRepository.Update(tag);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ using API.Entities;
|
|||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using API.SignalR;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -25,23 +27,21 @@ namespace API.Controllers
|
|||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<ReaderController> _logger;
|
||||
private readonly IReaderService _readerService;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly ICleanupService _cleanupService;
|
||||
private readonly IBookmarkService _bookmarkService;
|
||||
private readonly IEventHub _eventHub;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReaderController(ICacheService cacheService,
|
||||
IUnitOfWork unitOfWork, ILogger<ReaderController> logger,
|
||||
IReaderService readerService, IDirectoryService directoryService,
|
||||
ICleanupService cleanupService, IBookmarkService bookmarkService)
|
||||
IReaderService readerService, IBookmarkService bookmarkService,
|
||||
IEventHub eventHub)
|
||||
{
|
||||
_cacheService = cacheService;
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
_readerService = readerService;
|
||||
_directoryService = directoryService;
|
||||
_cleanupService = cleanupService;
|
||||
_bookmarkService = bookmarkService;
|
||||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -111,13 +111,14 @@ namespace API.Controllers
|
|||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
await _readerService.MarkSeriesAsRead(user, markReadDto.SeriesId);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
|
||||
|
||||
|
||||
return BadRequest("There was an issue saving progress");
|
||||
// var series = new List<SeriesDto>()
|
||||
// {await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(markReadDto.SeriesId, user.Id)};
|
||||
// await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
||||
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
||||
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markReadDto.SeriesId, series[0], series[0].Pages));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
|
@ -132,13 +133,19 @@ namespace API.Controllers
|
|||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
await _readerService.MarkSeriesAsUnread(user, markReadDto.SeriesId);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
|
||||
|
||||
|
||||
return BadRequest("There was an issue saving progress");
|
||||
// Should I do this for every chapter? Maybe in a background task?
|
||||
// foreach (var chapterId in await
|
||||
// _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new List<int>() {markReadDto.SeriesId}))
|
||||
// {
|
||||
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
||||
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, chapterId, MessageFactoryEntityTypes.Chapter, 0));
|
||||
// }
|
||||
//
|
||||
// await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
||||
// MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markReadDto.SeriesId, MessageFactoryEntityTypes.Series, 0));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -211,12 +211,8 @@ namespace API.Controllers
|
|||
{
|
||||
return BadRequest("A list of this name already exists");
|
||||
}
|
||||
user.ReadingLists.Add(new ReadingList()
|
||||
{
|
||||
Promoted = false,
|
||||
Title = dto.Title,
|
||||
Summary = string.Empty
|
||||
});
|
||||
|
||||
user.ReadingLists.Add(DbFactory.ReadingList(dto.Title, string.Empty, false));
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return BadRequest("There was a problem creating list");
|
||||
|
||||
|
@ -257,7 +253,7 @@ namespace API.Controllers
|
|||
readingList.CoverImageLocked = false;
|
||||
readingList.CoverImage = string.Empty;
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
MessageFactory.CoverUpdateEvent(readingList.Id, "readingList"), false);
|
||||
MessageFactory.CoverUpdateEvent(readingList.Id, MessageFactoryEntityTypes.ReadingList), false);
|
||||
_unitOfWork.ReadingListRepository.Update(readingList);
|
||||
}
|
||||
|
||||
|
@ -474,14 +470,7 @@ namespace API.Controllers
|
|||
foreach (var chapter in chaptersForSeries)
|
||||
{
|
||||
if (existingChapterExists.Contains(chapter.Id)) continue;
|
||||
|
||||
readingList.Items.Add(new ReadingListItem()
|
||||
{
|
||||
Order = index,
|
||||
ChapterId = chapter.Id,
|
||||
SeriesId = seriesId,
|
||||
VolumeId = chapter.VolumeId
|
||||
});
|
||||
readingList.Items.Add(DbFactory.ReadingListItem(index, seriesId, chapter.VolumeId, chapter.Id));
|
||||
index += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ namespace API.Controllers
|
|||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
MessageFactory.CoverUpdateEvent(tag.Id, "collection"), false);
|
||||
MessageFactory.CoverUpdateEvent(tag.Id, MessageFactoryEntityTypes.CollectionTag), false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ namespace API.Controllers
|
|||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
MessageFactory.CoverUpdateEvent(readingList.Id, "readingList"), false);
|
||||
MessageFactory.CoverUpdateEvent(readingList.Id, MessageFactoryEntityTypes.ReadingList), false);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,4 +5,5 @@ public enum SortField
|
|||
SortName = 1,
|
||||
CreatedDate = 2,
|
||||
LastModifiedDate = 3,
|
||||
LastChapterAdded = 4
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ namespace API.DTOs
|
|||
/// </summary>
|
||||
public DateTime LatestReadDate { get; set; }
|
||||
/// <summary>
|
||||
/// DateTime representing last time a chapter was added to the Series
|
||||
/// </summary>
|
||||
public DateTime LastChapterAdded { get; set; }
|
||||
/// <summary>
|
||||
/// Rating from logged in user. Calculated at API-time.
|
||||
/// </summary>
|
||||
public int UserRating { get; set; }
|
||||
|
|
|
@ -82,6 +82,29 @@ namespace API.Data
|
|||
};
|
||||
}
|
||||
|
||||
public static ReadingList ReadingList(string title, string summary, bool promoted)
|
||||
{
|
||||
return new ReadingList()
|
||||
{
|
||||
NormalizedTitle = API.Parser.Parser.Normalize(title?.Trim()).ToUpper(),
|
||||
Title = title?.Trim(),
|
||||
Summary = summary?.Trim(),
|
||||
Promoted = promoted,
|
||||
Items = new List<ReadingListItem>()
|
||||
};
|
||||
}
|
||||
|
||||
public static ReadingListItem ReadingListItem(int index, int seriesId, int volumeId, int chapterId)
|
||||
{
|
||||
return new ReadingListItem()
|
||||
{
|
||||
Order = index,
|
||||
ChapterId = chapterId,
|
||||
SeriesId = seriesId,
|
||||
VolumeId = volumeId
|
||||
};
|
||||
}
|
||||
|
||||
public static Genre Genre(string name, bool external)
|
||||
{
|
||||
return new Genre()
|
||||
|
|
|
@ -27,6 +27,7 @@ public interface IReadingListRepository
|
|||
void Update(ReadingList list);
|
||||
Task<int> Count();
|
||||
Task<string> GetCoverImageAsync(int readingListId);
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
}
|
||||
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
|
@ -59,6 +60,15 @@ public class ReadingListRepository : IReadingListRepository
|
|||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<string>> GetAllCoverImagesAsync()
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Select(t => t.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public void Remove(ReadingListItem item)
|
||||
{
|
||||
_context.ReadingListItem.Remove(item);
|
||||
|
|
|
@ -95,7 +95,7 @@ public interface ISeriesRepository
|
|||
Task<IList<AgeRatingDto>> GetAllAgeRatingsDtosForLibrariesAsync(List<int> libraryIds);
|
||||
Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync(List<int> libraryIds);
|
||||
Task<IList<PublicationStatusDto>> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds);
|
||||
Task<IEnumerable<GroupedSeriesDto>> GetRecentlyUpdatedSeries(int userId);
|
||||
Task<IEnumerable<GroupedSeriesDto>> GetRecentlyUpdatedSeries(int userId, int pageSize = 30);
|
||||
}
|
||||
|
||||
public class SeriesRepository : ISeriesRepository
|
||||
|
@ -231,7 +231,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
/// <summary>
|
||||
/// Gets all series
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="libraryId">Restricts to just one library</param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="userParams"></param>
|
||||
/// <param name="filter"></param>
|
||||
|
@ -617,6 +617,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
LastReadingProgress = _context.AppUserProgresses
|
||||
.Where(p => p.Id == progress.Id && p.AppUserId == userId)
|
||||
.Max(p => p.LastModified),
|
||||
LastModified = _context.AppUserProgresses.Where(p => p.Id == progress.Id && p.AppUserId == userId).Max(p => p.LastModified),
|
||||
s.LastChapterAdded
|
||||
});
|
||||
if (cutoffOnDate)
|
||||
|
@ -628,8 +629,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
var retSeries = query.Where(s => s.AppUserId == userId
|
||||
&& s.PagesRead > 0
|
||||
&& s.PagesRead < s.Series.Pages)
|
||||
.OrderByDescending(s => s.LastReadingProgress)
|
||||
.ThenByDescending(s => s.LastChapterAdded)
|
||||
.OrderByDescending(s => s.LastChapterAdded)
|
||||
.ThenByDescending(s => s.LastReadingProgress)
|
||||
.Select(s => s.Series)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsSplitQuery()
|
||||
|
@ -680,6 +681,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
SortField.SortName => query.OrderBy(s => s.SortName),
|
||||
SortField.CreatedDate => query.OrderBy(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderBy(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
@ -690,6 +692,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
SortField.SortName => query.OrderByDescending(s => s.SortName),
|
||||
SortField.CreatedDate => query.OrderByDescending(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
@ -900,16 +903,18 @@ public class SeriesRepository : ISeriesRepository
|
|||
/// <summary>
|
||||
/// Return recently updated series, regardless of read progress, and group the number of volume or chapters added.
|
||||
/// </summary>
|
||||
/// <remarks>This provides 2 levels of pagination. Fetching the individual chapters only looks at 3000. Then when performing grouping
|
||||
/// in memory, we stop after 30 series. </remarks>
|
||||
/// <param name="userId">Used to ensure user has access to libraries</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<GroupedSeriesDto>> GetRecentlyUpdatedSeries(int userId)
|
||||
public async Task<IEnumerable<GroupedSeriesDto>> GetRecentlyUpdatedSeries(int userId, int pageSize = 30)
|
||||
{
|
||||
var ret = await GetRecentlyAddedChaptersQuery(userId, 150);
|
||||
|
||||
var seriesMap = new Dictionary<string, GroupedSeriesDto>();
|
||||
var seriesMap = new Dictionary<string, GroupedSeriesDto>();
|
||||
var index = 0;
|
||||
foreach (var item in ret)
|
||||
foreach (var item in await GetRecentlyAddedChaptersQuery(userId))
|
||||
{
|
||||
if (seriesMap.Keys.Count == pageSize) break;
|
||||
|
||||
if (seriesMap.ContainsKey(item.SeriesName))
|
||||
{
|
||||
seriesMap[item.SeriesName].Count += 1;
|
||||
|
@ -932,43 +937,9 @@ public class SeriesRepository : ISeriesRepository
|
|||
}
|
||||
|
||||
return seriesMap.Values.AsEnumerable();
|
||||
|
||||
//return seriesMap.Values.ToList();
|
||||
|
||||
// var libraries = await _context.AppUser
|
||||
// .Where(u => u.Id == userId)
|
||||
// .SelectMany(u => u.Libraries.Select(l => new {LibraryId = l.Id, LibraryType = l.Type}))
|
||||
// .ToListAsync();
|
||||
// var libraryIds = libraries.Select(l => l.LibraryId).ToList();
|
||||
//
|
||||
// var cuttoffDate = DateTime.Now - TimeSpan.FromDays(12);
|
||||
//
|
||||
// var ret2 = _context.Series
|
||||
// .Where(s => s.LastChapterAdded >= cuttoffDate
|
||||
// && libraryIds.Contains(s.LibraryId))
|
||||
// .Select((s) => new GroupedSeriesDto
|
||||
// {
|
||||
// LibraryId = s.LibraryId,
|
||||
// LibraryType = s.Library.Type,
|
||||
// SeriesId = s.Id,
|
||||
// SeriesName = s.Name,
|
||||
// //Created = s.LastChapterAdded, // Hmm on first migration this wont work
|
||||
// Created = s.Volumes.SelectMany(v => v.Chapters).Max(c => c.Created), // Hmm on first migration this wont work
|
||||
// Count = s.Volumes.SelectMany(v => v.Chapters).Count(c => c.Created >= cuttoffDate),
|
||||
// //Id = index,
|
||||
// Format = s.Format
|
||||
// })
|
||||
// .Take(50)
|
||||
// .OrderByDescending(c => c.Created)
|
||||
// .AsSplitQuery()
|
||||
// .AsEnumerable();
|
||||
//
|
||||
// return ret2;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<RecentlyAddedSeries>> GetRecentlyAddedChaptersQuery(int userId, int maxRecords = 50)
|
||||
private async Task<IEnumerable<RecentlyAddedSeries>> GetRecentlyAddedChaptersQuery(int userId, int maxRecords = 3000)
|
||||
{
|
||||
var libraries = await _context.AppUser
|
||||
.Where(u => u.Id == userId)
|
||||
|
@ -1000,7 +971,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
VolumeNumber = c.Volume.Number,
|
||||
ChapterTitle = c.Title
|
||||
})
|
||||
.Take(maxRecords)
|
||||
//.Take(maxRecords)
|
||||
.AsSplitQuery()
|
||||
.Where(c => c.Created >= withinLastWeek && libraryIds.Contains(c.LibraryId))
|
||||
.AsEnumerable();
|
||||
|
|
|
@ -26,8 +26,9 @@ public class ImageService : IImageService
|
|||
private readonly ILogger<ImageService> _logger;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
public const string ChapterCoverImageRegex = @"v\d+_c\d+";
|
||||
public const string SeriesCoverImageRegex = @"series_\d+";
|
||||
public const string CollectionTagCoverImageRegex = @"tag_\d+";
|
||||
public const string SeriesCoverImageRegex = @"series\d+";
|
||||
public const string CollectionTagCoverImageRegex = @"tag\d+";
|
||||
public const string ReadingListCoverImageRegex = @"readinglist\d+";
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -72,7 +72,7 @@ public class MetadataService : IMetadataService
|
|||
_logger.LogDebug("[MetadataService] Generating cover image for {File}", firstFile.FilePath);
|
||||
chapter.CoverImage = _readingItemService.GetCoverImage(firstFile.FilePath, ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId), firstFile.Format);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
MessageFactory.CoverUpdateEvent(chapter.Id, "chapter"), false);
|
||||
MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ public class MetadataService : IMetadataService
|
|||
if (firstChapter == null) return false;
|
||||
|
||||
volume.CoverImage = firstChapter.CoverImage;
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(volume.Id, "volume"), false);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ public class MetadataService : IMetadataService
|
|||
}
|
||||
}
|
||||
series.CoverImage = firstCover?.CoverImage ?? coverImage;
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(series.Id, "series"), false);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series), false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -300,7 +300,7 @@ public class MetadataService : IMetadataService
|
|||
|
||||
if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(series.Id, "series"), false);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(series.Id, MessageFactoryEntityTypes.Series), false);
|
||||
}
|
||||
|
||||
_logger.LogInformation("[MetadataService] Updated metadata for {SeriesName} in {ElapsedMilliseconds} milliseconds", series.Name, sw.ElapsedMilliseconds);
|
||||
|
|
|
@ -9,6 +9,7 @@ using API.Data.Repositories;
|
|||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -33,13 +34,15 @@ public class ReaderService : IReaderService
|
|||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<ReaderService> _logger;
|
||||
private readonly IEventHub _eventHub;
|
||||
private readonly ChapterSortComparer _chapterSortComparer = new ChapterSortComparer();
|
||||
private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
|
||||
|
||||
public ReaderService(IUnitOfWork unitOfWork, ILogger<ReaderService> logger)
|
||||
public ReaderService(IUnitOfWork unitOfWork, ILogger<ReaderService> logger, IEventHub eventHub)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
public static string FormatBookmarkFolderPath(string baseDirectory, int userId, int seriesId, int chapterId)
|
||||
|
@ -211,9 +214,11 @@ public class ReaderService : IReaderService
|
|||
_unitOfWork.AppUserProgressRepository.Update(userProgress);
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return true;
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
|
||||
MessageFactory.UserProgressUpdateEvent(userId, user.UserName, progressDto.SeriesId, progressDto.VolumeId, progressDto.ChapterId, progressDto.PageNum));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,26 +151,22 @@ public class SeriesService : ISeriesService
|
|||
UpdatePeopleList(PersonRole.CoverArtist, updateSeriesMetadataDto.SeriesMetadata.CoverArtists, series, allPeople,
|
||||
HandleAddPerson, () => series.Metadata.CoverArtistLocked = true);
|
||||
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.AgeRatingLocked) series.Metadata.AgeRatingLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.PublicationStatusLocked) series.Metadata.PublicationStatusLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.LanguageLocked) series.Metadata.LanguageLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.GenresLocked) series.Metadata.GenresLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.TagsLocked) series.Metadata.TagsLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.CharacterLocked) series.Metadata.CharacterLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.ColoristLocked) series.Metadata.ColoristLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.EditorLocked) series.Metadata.EditorLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.InkerLocked) series.Metadata.InkerLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.LettererLocked) series.Metadata.LettererLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.PencillerLocked) series.Metadata.PencillerLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.PublisherLocked) series.Metadata.PublisherLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.TranslatorLocked) series.Metadata.TranslatorLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.CoverArtistLocked) series.Metadata.CoverArtistLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.WriterLocked) series.Metadata.WriterLocked = false;
|
||||
if (!updateSeriesMetadataDto.SeriesMetadata.SummaryLocked) series.Metadata.SummaryLocked = false;
|
||||
|
||||
series.Metadata.AgeRatingLocked = updateSeriesMetadataDto.SeriesMetadata.AgeRatingLocked;
|
||||
series.Metadata.PublicationStatusLocked = updateSeriesMetadataDto.SeriesMetadata.PublicationStatusLocked;
|
||||
series.Metadata.LanguageLocked = updateSeriesMetadataDto.SeriesMetadata.LanguageLocked;
|
||||
series.Metadata.GenresLocked = updateSeriesMetadataDto.SeriesMetadata.GenresLocked;
|
||||
series.Metadata.TagsLocked = updateSeriesMetadataDto.SeriesMetadata.TagsLocked;
|
||||
series.Metadata.CharacterLocked = updateSeriesMetadataDto.SeriesMetadata.CharacterLocked;
|
||||
series.Metadata.ColoristLocked = updateSeriesMetadataDto.SeriesMetadata.ColoristLocked;
|
||||
series.Metadata.EditorLocked = updateSeriesMetadataDto.SeriesMetadata.EditorLocked;
|
||||
series.Metadata.InkerLocked = updateSeriesMetadataDto.SeriesMetadata.InkerLocked;
|
||||
series.Metadata.LettererLocked = updateSeriesMetadataDto.SeriesMetadata.LettererLocked;
|
||||
series.Metadata.PencillerLocked = updateSeriesMetadataDto.SeriesMetadata.PencillerLocked;
|
||||
series.Metadata.PublisherLocked = updateSeriesMetadataDto.SeriesMetadata.PublisherLocked;
|
||||
|
||||
|
||||
series.Metadata.TranslatorLocked = updateSeriesMetadataDto.SeriesMetadata.TranslatorLocked;
|
||||
series.Metadata.CoverArtistLocked = updateSeriesMetadataDto.SeriesMetadata.CoverArtistLocked;
|
||||
series.Metadata.WriterLocked = updateSeriesMetadataDto.SeriesMetadata.WriterLocked;
|
||||
series.Metadata.SummaryLocked = updateSeriesMetadataDto.SeriesMetadata.SummaryLocked;
|
||||
|
||||
if (!_unitOfWork.HasChanges())
|
||||
{
|
||||
|
@ -491,10 +487,10 @@ public class SeriesService : ISeriesService
|
|||
foreach (var chapter in chapters)
|
||||
{
|
||||
chapter.Title = FormatChapterTitle(chapter, libraryType);
|
||||
if (chapter.IsSpecial)
|
||||
{
|
||||
specials.Add(chapter);
|
||||
}
|
||||
if (!chapter.IsSpecial) continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(chapter.TitleName)) chapter.Title = chapter.TitleName;
|
||||
specials.Add(chapter);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ namespace API.Services.Tasks
|
|||
await DeleteChapterCoverImages();
|
||||
await SendProgress(0.7F, "Cleaning deleted cover images");
|
||||
await DeleteTagCoverImages();
|
||||
await DeleteReadingListCoverImages();
|
||||
await SendProgress(0.8F, "Cleaning deleted cover images");
|
||||
await SendProgress(1F, "Cleanup finished");
|
||||
_logger.LogInformation("Cleanup finished");
|
||||
|
@ -116,6 +117,16 @@ namespace API.Services.Tasks
|
|||
_directoryService.DeleteFiles(files.Where(file => !images.Contains(_directoryService.FileSystem.Path.GetFileName(file))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all reading list images that are not in the database. They must follow <see cref="ImageService.ReadingListCoverImageRegex"/> filename pattern.
|
||||
/// </summary>
|
||||
public async Task DeleteReadingListCoverImages()
|
||||
{
|
||||
var images = await _unitOfWork.ReadingListRepository.GetAllCoverImagesAsync();
|
||||
var files = _directoryService.GetFiles(_directoryService.CoverImageDirectory, ImageService.ReadingListCoverImageRegex);
|
||||
_directoryService.DeleteFiles(files.Where(file => !images.Contains(_directoryService.FileSystem.Path.GetFileName(file))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all files and directories in the cache directory
|
||||
/// </summary>
|
||||
|
|
|
@ -7,6 +7,14 @@ using API.Entities;
|
|||
|
||||
namespace API.SignalR
|
||||
{
|
||||
public static class MessageFactoryEntityTypes
|
||||
{
|
||||
public const string Series = "series";
|
||||
public const string Volume = "volume";
|
||||
public const string Chapter = "chapter";
|
||||
public const string CollectionTag = "collection";
|
||||
public const string ReadingList = "readingList";
|
||||
}
|
||||
public static class MessageFactory
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -78,6 +86,11 @@ namespace API.SignalR
|
|||
/// When a library is created/deleted in the Server
|
||||
/// </summary>
|
||||
public const string LibraryModified = "LibraryModified";
|
||||
/// <summary>
|
||||
/// A user's progress was modified
|
||||
/// </summary>
|
||||
public const string UserProgressUpdate = "UserProgressUpdate";
|
||||
|
||||
|
||||
|
||||
public static SignalRMessage ScanSeriesEvent(int libraryId, int seriesId, string seriesName)
|
||||
|
@ -320,6 +333,25 @@ namespace API.SignalR
|
|||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage UserProgressUpdateEvent(int userId, string username, int seriesId, int volumeId, int chapterId, int pagesRead)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
{
|
||||
Name = UserProgressUpdate,
|
||||
Title = "Updating User Progress",
|
||||
Progress = ProgressType.None,
|
||||
Body = new
|
||||
{
|
||||
UserId = userId,
|
||||
Username = username,
|
||||
SeriesId = seriesId,
|
||||
VolumeId = volumeId,
|
||||
ChapterId = chapterId,
|
||||
PagesRead = pagesRead,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignalRMessage SiteThemeProgressEvent(string subtitle, string themeName, string eventType)
|
||||
{
|
||||
return new SignalRMessage()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue