mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
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:
parent
c448a3e493
commit
67ba5e302f
@ -6,8 +6,10 @@ using API.Data;
|
|||||||
using API.DTOs.CollectionTags;
|
using API.DTOs.CollectionTags;
|
||||||
using API.Entities.Metadata;
|
using API.Entities.Metadata;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
using API.SignalR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace API.Controllers
|
||||||
{
|
{
|
||||||
@ -17,11 +19,13 @@ namespace API.Controllers
|
|||||||
public class CollectionController : BaseApiController
|
public class CollectionController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
private readonly IHubContext<MessageHub> _messageHub;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public CollectionController(IUnitOfWork unitOfWork)
|
public CollectionController(IUnitOfWork unitOfWork, IHubContext<MessageHub> messageHub)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
|
_messageHub = messageHub;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -152,6 +156,7 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
tag.CoverImageLocked = false;
|
tag.CoverImageLocked = false;
|
||||||
tag.CoverImage = string.Empty;
|
tag.CoverImage = string.Empty;
|
||||||
|
await _messageHub.Clients.All.SendAsync(SignalREvents.CoverUpdate, MessageFactory.CoverUpdateEvent(tag.Id, "collection"));
|
||||||
_unitOfWork.CollectionTagRepository.Update(tag);
|
_unitOfWork.CollectionTagRepository.Update(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ public class MetadataService : IMetadataService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="chapter"></param>
|
/// <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>
|
/// <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();
|
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);
|
_logger.LogDebug("[MetadataService] Generating cover image for {File}", firstFile.FilePath);
|
||||||
chapter.CoverImage = _readingItemService.GetCoverImage(firstFile.FilePath, ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId), firstFile.Format);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -86,7 +87,7 @@ public class MetadataService : IMetadataService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="volume"></param>
|
/// <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>
|
/// <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
|
// We need to check if Volume coverImage matches first chapters if forceUpdate is false
|
||||||
if (volume == null || !_cacheHelper.ShouldUpdateCoverImage(
|
if (volume == null || !_cacheHelper.ShouldUpdateCoverImage(
|
||||||
@ -98,6 +99,8 @@ public class MetadataService : IMetadataService
|
|||||||
if (firstChapter == null) return false;
|
if (firstChapter == null) return false;
|
||||||
|
|
||||||
volume.CoverImage = firstChapter.CoverImage;
|
volume.CoverImage = firstChapter.CoverImage;
|
||||||
|
await _messageHub.Clients.All.SendAsync(SignalREvents.CoverUpdate, MessageFactory.CoverUpdateEvent(volume.Id, "volume"));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +109,7 @@ public class MetadataService : IMetadataService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="series"></param>
|
/// <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>
|
/// <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;
|
if (series == null) return;
|
||||||
|
|
||||||
@ -133,6 +136,7 @@ public class MetadataService : IMetadataService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
series.CoverImage = firstCover?.CoverImage ?? coverImage;
|
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>
|
/// </summary>
|
||||||
/// <param name="series"></param>
|
/// <param name="series"></param>
|
||||||
/// <param name="forceUpdate"></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);
|
_logger.LogDebug("[MetadataService] Processing series {SeriesName}", series.OriginalName);
|
||||||
try
|
try
|
||||||
@ -154,7 +158,7 @@ public class MetadataService : IMetadataService
|
|||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var chapter in volume.Chapters)
|
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
|
// If cover was update, either the file has changed or first scan and we should force a metadata update
|
||||||
UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated);
|
UpdateChapterLastModified(chapter, forceUpdate || chapterUpdated);
|
||||||
if (index == 0 && chapterUpdated)
|
if (index == 0 && chapterUpdated)
|
||||||
@ -165,7 +169,7 @@ public class MetadataService : IMetadataService
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var volumeUpdated = UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate);
|
var volumeUpdated = await UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate);
|
||||||
if (volumeIndex == 0 && volumeUpdated)
|
if (volumeIndex == 0 && volumeUpdated)
|
||||||
{
|
{
|
||||||
firstVolumeUpdated = true;
|
firstVolumeUpdated = true;
|
||||||
@ -173,7 +177,7 @@ public class MetadataService : IMetadataService
|
|||||||
volumeIndex++;
|
volumeIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate);
|
await UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -222,7 +226,7 @@ public class MetadataService : IMetadataService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ProcessSeriesMetadataUpdate(series, forceUpdate);
|
await ProcessSeriesMetadataUpdate(series, forceUpdate);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -237,10 +241,11 @@ public class MetadataService : IMetadataService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
foreach (var series in nonLibrarySeries)
|
// foreach (var series in nonLibrarySeries)
|
||||||
{
|
// {
|
||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadata, MessageFactory.RefreshMetadataEvent(library.Id, series.Id));
|
// // TODO: This can be removed, we use CoverUpdate elsewhere
|
||||||
}
|
// await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadata, MessageFactory.RefreshMetadataEvent(library.Id, series.Id));
|
||||||
|
// }
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"[MetadataService] Processed {SeriesStart} - {SeriesEnd} out of {TotalSeries} series in {ElapsedScanTime} milliseconds for {LibraryName}",
|
"[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);
|
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();
|
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>
|
/// <summary>
|
||||||
/// Refreshes Metadata for a Series. Will always force updates.
|
/// Refreshes Metadata for a Series. Will always force updates.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -336,17 +285,17 @@ public class MetadataService : IMetadataService
|
|||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
|
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
|
||||||
MessageFactory.RefreshMetadataProgressEvent(libraryId, 0F));
|
MessageFactory.RefreshMetadataProgressEvent(libraryId, 0F));
|
||||||
|
|
||||||
ProcessSeriesMetadataUpdate(series, forceUpdate);
|
await ProcessSeriesMetadataUpdate(series, forceUpdate);
|
||||||
|
|
||||||
|
|
||||||
|
if (_unitOfWork.HasChanges())
|
||||||
|
{
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
|
await _messageHub.Clients.All.SendAsync(SignalREvents.RefreshMetadataProgress,
|
||||||
MessageFactory.RefreshMetadataProgressEvent(libraryId, 1F));
|
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();
|
await RemoveAbandonedMetadataKeys();
|
||||||
|
|
||||||
_logger.LogInformation("[MetadataService] Updated metadata for {SeriesName} in {ElapsedMilliseconds} milliseconds", series.Name, sw.ElapsedMilliseconds);
|
_logger.LogInformation("[MetadataService] Updated metadata for {SeriesName} in {ElapsedMilliseconds} milliseconds", series.Name, sw.ElapsedMilliseconds);
|
||||||
|
@ -379,6 +379,11 @@ public class ScannerService : IScannerService
|
|||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.SeriesRemoved, MessageFactory.SeriesRemovedEvent(missing.Id, missing.Name, library.Id));
|
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));
|
var progress = Math.Max(0, Math.Min(1, ((chunk + 1F) * chunkInfo.ChunkSize) / chunkInfo.TotalSize));
|
||||||
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,
|
||||||
MessageFactory.ScanLibraryProgressEvent(library.Id, progress));
|
MessageFactory.ScanLibraryProgressEvent(library.Id, progress));
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using API.DTOs.Update;
|
using API.DTOs.Update;
|
||||||
|
|
||||||
namespace API.SignalR
|
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)
|
public static SignalRMessage BackupDatabaseProgressEvent(float progress)
|
||||||
{
|
{
|
||||||
return new SignalRMessage()
|
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,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
{
|
{
|
||||||
public static class SignalREvents
|
public static class SignalREvents
|
||||||
{
|
{
|
||||||
public const string UpdateAvailable = "UpdateAvailable";
|
|
||||||
public const string ScanSeries = "ScanSeries";
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event during Refresh Metadata for cover image change
|
/// An update is available for the Kavita instance
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Event sent out during Refresh Metadata for progress tracking
|
/// Event sent out during Refresh Metadata for progress tracking
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,6 +50,9 @@
|
|||||||
/// Event sent out during downloading of files
|
/// Event sent out during downloading of files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DownloadProgress = "DownloadProgress";
|
public const string DownloadProgress = "DownloadProgress";
|
||||||
|
/// <summary>
|
||||||
|
/// A cover was updated
|
||||||
|
/// </summary>
|
||||||
|
public const string CoverUpdate = "CoverUpdate";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
UI/Web/src/app/_models/events/cover-update-event.ts
Normal file
7
UI/Web/src/app/_models/events/cover-update-event.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Represents a generic cover update event. Id is used based on entityType
|
||||||
|
*/
|
||||||
|
export interface CoverUpdateEvent {
|
||||||
|
id: number;
|
||||||
|
entityType: 'series' | 'chapter' | 'volume' | 'collection';
|
||||||
|
}
|
@ -79,7 +79,7 @@ export class ImageService implements OnDestroy {
|
|||||||
* @returns Url with a random parameter attached
|
* @returns Url with a random parameter attached
|
||||||
*/
|
*/
|
||||||
randomize(url: string) {
|
randomize(url: string) {
|
||||||
const r = Math.random() * 100 + 1;
|
const r = Math.round(Math.random() * 100 + 1);
|
||||||
if (url.indexOf('&random') >= 0) {
|
if (url.indexOf('&random') >= 0) {
|
||||||
return url + 1;
|
return url + 1;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ import { User } from '../_models/user';
|
|||||||
export enum EVENTS {
|
export enum EVENTS {
|
||||||
UpdateAvailable = 'UpdateAvailable',
|
UpdateAvailable = 'UpdateAvailable',
|
||||||
ScanSeries = 'ScanSeries',
|
ScanSeries = 'ScanSeries',
|
||||||
RefreshMetadata = 'RefreshMetadata',
|
|
||||||
RefreshMetadataProgress = 'RefreshMetadataProgress',
|
RefreshMetadataProgress = 'RefreshMetadataProgress',
|
||||||
SeriesAdded = 'SeriesAdded',
|
SeriesAdded = 'SeriesAdded',
|
||||||
SeriesRemoved = 'SeriesRemoved',
|
SeriesRemoved = 'SeriesRemoved',
|
||||||
@ -25,7 +24,11 @@ export enum EVENTS {
|
|||||||
ScanLibraryError = 'ScanLibraryError',
|
ScanLibraryError = 'ScanLibraryError',
|
||||||
BackupDatabaseProgress = 'BackupDatabaseProgress',
|
BackupDatabaseProgress = 'BackupDatabaseProgress',
|
||||||
CleanupProgress = 'CleanupProgress',
|
CleanupProgress = 'CleanupProgress',
|
||||||
DownloadProgress = 'DownloadProgress'
|
DownloadProgress = 'DownloadProgress',
|
||||||
|
/**
|
||||||
|
* A cover is updated
|
||||||
|
*/
|
||||||
|
CoverUpdate = 'CoverUpdate'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Message<T> {
|
export interface Message<T> {
|
||||||
@ -49,7 +52,6 @@ export class MessageHubService {
|
|||||||
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
|
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
|
||||||
public scanLibrary: EventEmitter<ProgressEvent> = new EventEmitter<ProgressEvent>(); // TODO: Refactor this name to be generic
|
public scanLibrary: EventEmitter<ProgressEvent> = new EventEmitter<ProgressEvent>(); // TODO: Refactor this name to be generic
|
||||||
public seriesAdded: EventEmitter<SeriesAddedEvent> = new EventEmitter<SeriesAddedEvent>();
|
public seriesAdded: EventEmitter<SeriesAddedEvent> = new EventEmitter<SeriesAddedEvent>();
|
||||||
public refreshMetadata: EventEmitter<RefreshMetadataEvent> = new EventEmitter<RefreshMetadataEvent>();
|
|
||||||
|
|
||||||
isAdmin: boolean = false;
|
isAdmin: boolean = false;
|
||||||
|
|
||||||
@ -152,12 +154,19 @@ export class MessageHubService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hubConnection.on(EVENTS.RefreshMetadata, resp => {
|
// this.hubConnection.on(EVENTS.RefreshMetadata, resp => {
|
||||||
|
// this.messagesSource.next({
|
||||||
|
// event: EVENTS.RefreshMetadata,
|
||||||
|
// payload: resp.body
|
||||||
|
// });
|
||||||
|
// this.refreshMetadata.emit(resp.body); // TODO: Remove this
|
||||||
|
// });
|
||||||
|
|
||||||
|
this.hubConnection.on(EVENTS.CoverUpdate, resp => {
|
||||||
this.messagesSource.next({
|
this.messagesSource.next({
|
||||||
event: EVENTS.RefreshMetadata,
|
event: EVENTS.CoverUpdate,
|
||||||
payload: resp.body
|
payload: resp.body
|
||||||
});
|
});
|
||||||
this.refreshMetadata.emit(resp.body);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hubConnection.on(EVENTS.UpdateAvailable, resp => {
|
this.hubConnection.on(EVENTS.UpdateAvailable, resp => {
|
||||||
|
@ -61,13 +61,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.data) {
|
if (this.data) {
|
||||||
this.imageUrl = this.imageService.randomize(this.imageService.getSeriesCoverImage(this.data.id));
|
this.imageUrl = this.imageService.getSeriesCoverImage(this.data.id);
|
||||||
|
|
||||||
this.hubService.refreshMetadata.pipe(takeWhile(event => event.libraryId === this.libraryId), takeUntil(this.onDestroy)).subscribe((event: RefreshMetadataEvent) => {
|
|
||||||
if (this.data.id === event.seriesId) {
|
|
||||||
this.imageUrl = this.imageService.randomize(this.imageService.getSeriesCoverImage(this.data.id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { take, takeUntil } from 'rxjs/operators';
|
import { take, takeUntil } from 'rxjs/operators';
|
||||||
|
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
|
||||||
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
import { SeriesAddedEvent } from '../_models/events/series-added-event';
|
||||||
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
import { SeriesRemovedEvent } from '../_models/events/series-removed-event';
|
||||||
import { Library } from '../_models/library';
|
import { Library } from '../_models/library';
|
||||||
|
@ -200,13 +200,14 @@ export class SeriesDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.toastr.info('This series no longer exists');
|
this.toastr.info('This series no longer exists');
|
||||||
this.router.navigateByUrl('/libraries');
|
this.router.navigateByUrl('/libraries');
|
||||||
}
|
}
|
||||||
} else if (event.event === EVENTS.RefreshMetadata) {
|
} else if (event.event === EVENTS.ScanSeries) {
|
||||||
const seriesRemovedEvent = event.payload as RefreshMetadataEvent;
|
const seriesCoverUpdatedEvent = event.payload as ScanSeriesEvent;
|
||||||
if (seriesRemovedEvent.seriesId === this.series.id) {
|
if (seriesCoverUpdatedEvent.seriesId === this.series.id) {
|
||||||
|
console.log('ScanSeries called')
|
||||||
this.seriesService.getMetadata(this.series.id).pipe(take(1)).subscribe(metadata => {
|
this.seriesService.getMetadata(this.series.id).pipe(take(1)).subscribe(metadata => {
|
||||||
this.seriesMetadata = metadata;
|
this.seriesMetadata = metadata;
|
||||||
this.createHTML();
|
this.createHTML();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { Component, ElementRef, Input, OnChanges, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { ImageService } from 'src/app/_services/image.service';
|
import { ImageService } from 'src/app/_services/image.service';
|
||||||
|
import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used for images with placeholder fallback.
|
* This is used for images with placeholder fallback.
|
||||||
@ -9,7 +12,7 @@ import { ImageService } from 'src/app/_services/image.service';
|
|||||||
templateUrl: './image.component.html',
|
templateUrl: './image.component.html',
|
||||||
styleUrls: ['./image.component.scss']
|
styleUrls: ['./image.component.scss']
|
||||||
})
|
})
|
||||||
export class ImageComponent implements OnChanges {
|
export class ImageComponent implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source url to load image
|
* Source url to load image
|
||||||
@ -38,7 +41,15 @@ export class ImageComponent implements OnChanges {
|
|||||||
|
|
||||||
@ViewChild('img', {static: true}) imgElem!: ElementRef<HTMLImageElement>;
|
@ViewChild('img', {static: true}) imgElem!: ElementRef<HTMLImageElement>;
|
||||||
|
|
||||||
constructor(public imageService: ImageService, private renderer: Renderer2) { }
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
|
constructor(public imageService: ImageService, private renderer: Renderer2, private hubService: MessageHubService) {
|
||||||
|
this.hubService.messages$.pipe(takeUntil(this.onDestroy)).subscribe(res => {
|
||||||
|
if (res.event === EVENTS.CoverUpdate) {
|
||||||
|
this.imageUrl = this.imageService.randomize(this.imageUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (this.width != '') {
|
if (this.width != '') {
|
||||||
@ -60,7 +71,11 @@ export class ImageComponent implements OnChanges {
|
|||||||
if (this.borderRadius != '') {
|
if (this.borderRadius != '') {
|
||||||
this.renderer.setStyle(this.imgElem.nativeElement, 'border-radius', this.borderRadius);
|
this.renderer.setStyle(this.imgElem.nativeElement, 'border-radius', this.borderRadius);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.onDestroy.next();
|
||||||
|
this.onDestroy.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user