Refactored the way cover images are updated from SignalR to use an explicit event that is sent at a granular level for a given type of entity. (#1046)

Fixed a bad event listener for RefreshMetadata (now removed) to update metadata on Series Detail. Now uses ScanService, which indicates a series has completed a scan.
This commit is contained in:
Joseph Milazzo 2022-02-07 17:44:06 -08:00 committed by GitHub
parent c448a3e493
commit 67ba5e302f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 108 additions and 117 deletions

View file

@ -6,8 +6,10 @@ using API.Data;
using API.DTOs.CollectionTags;
using API.Entities.Metadata;
using API.Extensions;
using API.SignalR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
namespace API.Controllers
{
@ -17,11 +19,13 @@ namespace API.Controllers
public class CollectionController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IHubContext<MessageHub> _messageHub;
/// <inheritdoc />
public CollectionController(IUnitOfWork unitOfWork)
public CollectionController(IUnitOfWork unitOfWork, IHubContext<MessageHub> messageHub)
{
_unitOfWork = unitOfWork;
_messageHub = messageHub;
}
/// <summary>
@ -152,6 +156,7 @@ namespace API.Controllers
{
tag.CoverImageLocked = false;
tag.CoverImage = string.Empty;
await _messageHub.Clients.All.SendAsync(SignalREvents.CoverUpdate, MessageFactory.CoverUpdateEvent(tag.Id, "collection"));
_unitOfWork.CollectionTagRepository.Update(tag);
}

View file

@ -58,7 +58,7 @@ public class MetadataService : IMetadataService
/// </summary>
/// <param name="chapter"></param>
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
private bool UpdateChapterCoverImage(Chapter chapter, bool forceUpdate)
private async Task<bool> UpdateChapterCoverImage(Chapter chapter, bool forceUpdate)
{
var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
@ -69,6 +69,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 _messageHub.Clients.All.SendAsync(SignalREvents.CoverUpdate, MessageFactory.CoverUpdateEvent(chapter.Id, "chapter"));
return true;
}
@ -86,7 +87,7 @@ public class MetadataService : IMetadataService
/// </summary>
/// <param name="volume"></param>
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
private bool UpdateVolumeCoverImage(Volume volume, bool forceUpdate)
private async Task<bool> UpdateVolumeCoverImage(Volume volume, bool forceUpdate)
{
// We need to check if Volume coverImage matches first chapters if forceUpdate is false
if (volume == null || !_cacheHelper.ShouldUpdateCoverImage(
@ -98,6 +99,8 @@ public class MetadataService : IMetadataService
if (firstChapter == null) return false;
volume.CoverImage = firstChapter.CoverImage;
await _messageHub.Clients.All.SendAsync(SignalREvents.CoverUpdate, MessageFactory.CoverUpdateEvent(volume.Id, "volume"));
return true;
}
@ -106,7 +109,7 @@ public class MetadataService : IMetadataService
/// </summary>
/// <param name="series"></param>
/// <param name="forceUpdate">Force updating cover image even if underlying file has not been modified or chapter already has a cover image</param>
private void UpdateSeriesCoverImage(Series series, bool forceUpdate)
private async Task UpdateSeriesCoverImage(Series series, bool forceUpdate)
{
if (series == null) return;
@ -133,6 +136,7 @@ public class MetadataService : IMetadataService
}
}
series.CoverImage = firstCover?.CoverImage ?? coverImage;
await _messageHub.Clients.All.SendAsync(SignalREvents.CoverUpdate, MessageFactory.CoverUpdateEvent(series.Id, "series"));
}
@ -141,7 +145,7 @@ public class MetadataService : IMetadataService
/// </summary>
/// <param name="series"></param>
/// <param name="forceUpdate"></param>
private void ProcessSeriesMetadataUpdate(Series series, bool forceUpdate)
private async Task ProcessSeriesMetadataUpdate(Series series, bool forceUpdate)
{
_logger.LogDebug("[MetadataService] Processing series {SeriesName}", series.OriginalName);
try
@ -154,7 +158,7 @@ public class MetadataService : IMetadataService
var index = 0;
foreach (var chapter in volume.Chapters)
{
var chapterUpdated = UpdateChapterCoverImage(chapter, forceUpdate);
var chapterUpdated = await UpdateChapterCoverImage(chapter, forceUpdate);
// If cover was update, either the file has changed or first scan and we should force a metadata update
UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated);
if (index == 0 && chapterUpdated)
@ -165,7 +169,7 @@ public class MetadataService : IMetadataService
index++;
}
var volumeUpdated = UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate);
var volumeUpdated = await UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate);
if (volumeIndex == 0 && volumeUpdated)
{
firstVolumeUpdated = true;
@ -173,7 +177,7 @@ public class MetadataService : IMetadataService
volumeIndex++;
}
UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate);
await UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate);
}
catch (Exception ex)
{
@ -222,7 +226,7 @@ public class MetadataService : IMetadataService
{
try
{
ProcessSeriesMetadataUpdate(series, forceUpdate);
await ProcessSeriesMetadataUpdate(series, forceUpdate);
}
catch (Exception ex)
{
@ -237,10 +241,11 @@ public class MetadataService : IMetadataService
}
await _unitOfWork.CommitAsync();
foreach (var series in nonLibrarySeries)
{
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadata, MessageFactory.RefreshMetadataEvent(library.Id, series.Id));
}
// foreach (var series in nonLibrarySeries)
// {
// // TODO: This can be removed, we use CoverUpdate elsewhere
// await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadata, MessageFactory.RefreshMetadataEvent(library.Id, series.Id));
// }
_logger.LogInformation(
"[MetadataService] Processed {SeriesStart} - {SeriesEnd} out of {TotalSeries} series in {ElapsedScanTime} milliseconds for {LibraryName}",
chunk * chunkInfo.ChunkSize, (chunk * chunkInfo.ChunkSize) + nonLibrarySeries.Count, chunkInfo.TotalSize, stopwatch.ElapsedMilliseconds, library.Name);
@ -262,62 +267,6 @@ public class MetadataService : IMetadataService
await _unitOfWork.GenreRepository.RemoveAllGenreNoLongerAssociated();
}
// TODO: Write out a single piece of code that can iterate over a collection/chunk and perform custom actions
private async Task PerformScan(Library library, bool forceUpdate, Action<int, Chunk> action)
{
var chunkInfo = await _unitOfWork.SeriesRepository.GetChunkInfo(library.Id);
var stopwatch = Stopwatch.StartNew();
_logger.LogInformation("[MetadataService] Refreshing Library {LibraryName}. Total Items: {TotalSize}. Total Chunks: {TotalChunks} with {ChunkSize} size", library.Name, chunkInfo.TotalSize, chunkInfo.TotalChunks, chunkInfo.ChunkSize);
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
MessageFactory.RefreshMetadataProgressEvent(library.Id, 0F));
for (var chunk = 1; chunk <= chunkInfo.TotalChunks; chunk++)
{
if (chunkInfo.TotalChunks == 0) continue;
stopwatch.Restart();
action(chunk, chunkInfo);
// _logger.LogInformation("[MetadataService] Processing chunk {ChunkNumber} / {TotalChunks} with size {ChunkSize}. Series ({SeriesStart} - {SeriesEnd}",
// chunk, chunkInfo.TotalChunks, chunkInfo.ChunkSize, chunk * chunkInfo.ChunkSize, (chunk + 1) * chunkInfo.ChunkSize);
// var nonLibrarySeries = await _unitOfWork.SeriesRepository.GetFullSeriesForLibraryIdAsync(library.Id,
// new UserParams()
// {
// PageNumber = chunk,
// PageSize = chunkInfo.ChunkSize
// });
// _logger.LogDebug("[MetadataService] Fetched {SeriesCount} series for refresh", nonLibrarySeries.Count);
//
// var chapterIds = await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync(nonLibrarySeries.Select(s => s.Id).ToArray());
// var allPeople = await _unitOfWork.PersonRepository.GetAllPeople();
// var allGenres = await _unitOfWork.GenreRepository.GetAllGenres();
//
//
// var seriesIndex = 0;
// foreach (var series in nonLibrarySeries)
// {
// try
// {
// ProcessSeriesMetadataUpdate(series, chapterIds, allPeople, allGenres, forceUpdate);
// }
// catch (Exception ex)
// {
// _logger.LogError(ex, "[MetadataService] There was an exception during metadata refresh for {SeriesName}", series.Name);
// }
// var index = chunk * seriesIndex;
// var progress = Math.Max(0F, Math.Min(1F, index * 1F / chunkInfo.TotalSize));
//
// await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
// MessageFactory.RefreshMetadataProgressEvent(library.Id, progress));
// seriesIndex++;
// }
await _unitOfWork.CommitAsync();
}
}
/// <summary>
/// Refreshes Metadata for a Series. Will always force updates.
/// </summary>
@ -336,17 +285,17 @@ public class MetadataService : IMetadataService
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
MessageFactory.RefreshMetadataProgressEvent(libraryId, 0F));
ProcessSeriesMetadataUpdate(series, forceUpdate);
await ProcessSeriesMetadataUpdate(series, forceUpdate);
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
}
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
MessageFactory.RefreshMetadataProgressEvent(libraryId, 1F));
if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
{
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadata, MessageFactory.RefreshMetadataEvent(series.LibraryId, series.Id));
}
await RemoveAbandonedMetadataKeys();
_logger.LogInformation("[MetadataService] Updated metadata for {SeriesName} in {ElapsedMilliseconds} milliseconds", series.Name, sw.ElapsedMilliseconds);

View file

@ -379,6 +379,11 @@ public class ScannerService : IScannerService
await _messageHub.Clients.All.SendAsync(SignalREvents.SeriesRemoved, MessageFactory.SeriesRemovedEvent(missing.Id, missing.Name, library.Id));
}
foreach (var series in librarySeries)
{
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanSeries, MessageFactory.ScanSeriesEvent(series.Id, series.Name));
}
var progress = Math.Max(0, Math.Min(1, ((chunk + 1F) * chunkInfo.ChunkSize) / chunkInfo.TotalSize));
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
MessageFactory.ScanLibraryProgressEvent(library.Id, progress));

View file

@ -1,4 +1,5 @@
using System;
using System.Threading;
using API.DTOs.Update;
namespace API.SignalR
@ -75,20 +76,6 @@ namespace API.SignalR
}
public static SignalRMessage RefreshMetadataEvent(int libraryId, int seriesId)
{
return new SignalRMessage()
{
Name = SignalREvents.RefreshMetadata,
Body = new
{
SeriesId = seriesId,
LibraryId = libraryId
}
};
}
public static SignalRMessage BackupDatabaseProgressEvent(float progress)
{
return new SignalRMessage()
@ -161,5 +148,18 @@ namespace API.SignalR
}
};
}
public static SignalRMessage CoverUpdateEvent(int id, string entityType)
{
return new SignalRMessage()
{
Name = SignalREvents.CoverUpdate,
Body = new
{
Id = id,
EntityType = entityType,
}
};
}
}
}

View file

@ -2,12 +2,14 @@
{
public static class SignalREvents
{
public const string UpdateAvailable = "UpdateAvailable";
public const string ScanSeries = "ScanSeries";
/// <summary>
/// Event during Refresh Metadata for cover image change
/// An update is available for the Kavita instance
/// </summary>
public const string RefreshMetadata = "RefreshMetadata";
public const string UpdateAvailable = "UpdateAvailable";
/// <summary>
/// Used to tell when a scan series completes
/// </summary>
public const string ScanSeries = "ScanSeries";
/// <summary>
/// Event sent out during Refresh Metadata for progress tracking
/// </summary>
@ -48,6 +50,9 @@
/// Event sent out during downloading of files
/// </summary>
public const string DownloadProgress = "DownloadProgress";
/// <summary>
/// A cover was updated
/// </summary>
public const string CoverUpdate = "CoverUpdate";
}
}