Metadata Editing from the UI! (#1135)

* Added the skeleton code for layout, hooked up Age Rating, Publication Status, and Tags

* Tweaked message of Scan service to Finished scan of to better indicate the total scan time

* Hooked in foundation for person typeaheads

* Fixed people not populating typeaheads on load

* For manga/comics, when parsing, set the SeriesSort from ComicInfo if it exists.

* Implemented the ability to override and create new genre tags. Code is ready to flush out the rest.

* Ability to update metadata from the UI is hooked up. Next is locking.

* Updated typeahead to allow for non-multiple usage. Implemented ability to update Language tag in Series Metadata.

* Fixed a bug in GetContinuePoint for a case where we have Volumes, Loose Leaf chapters and no read progress.

* Added ETag headers on Images to allow for better caching (bookmarks and images in manga reader)

* Built out UI code to show locked indication to user

* Implemented Series locking and refactored a lot of styles in typeahead to make the lock setting work, plus misc cleanup.

* Added locked properties to dtos. Updated typeahead loading indicator to not interfere with close button if present

* Hooked up locking flags in UI

* Integrated regular field locking/unlocking

* Removed some old code

* Prevent input group from wrapping

* Implemented some basic layout for metadata on volume/chapter card modal. Refactored out all metadata from Chapter object in terms of UI and put into a separate call to ensure speedy delivery and simplicity of code.

* Refactored code to hide covers section if not an admin

* Implemented ability to modify a chapter/volume cover from the detail modal

* Removed a few variables and change cover image modal

* Added bookmark to single chapter view

* Put a temp fix in for a ngb v12 z-index bug (reported). Bumped ngb to 12.0 stable and fixed some small rendering bugs

* loading buttons ftw

* Lots of cleanup, looks like the story is finished

* Changed action name from Info to Details

* Style tweaks

* Fixed an issue where Summary would assume it's locked due to a subscription firing on setting the model

* Fixed some misc bugs

* Code smells

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
Joseph Milazzo 2022-03-04 15:04:15 -06:00 committed by GitHub
parent 47a92a2e01
commit ba77954d5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 3605 additions and 723 deletions

View file

@ -328,7 +328,7 @@ public class ScannerService : IScannerService
if (await _unitOfWork.CommitAsync())
{
_logger.LogInformation(
"[ScannerService] Processed {TotalFiles} files and {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {LibraryName}",
"[ScannerService] Finished scan of {TotalFiles} files and {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {LibraryName}",
totalFiles, series.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, library.Name);
}
else
@ -351,8 +351,7 @@ public class ScannerService : IScannerService
var parsedSeries = await scanner.ScanLibrariesForSeries(library.Type, dirs, library.Name);
var totalFiles = parsedSeries.Keys.Sum(key => parsedSeries[key].Count);
var scanElapsedTime = scanWatch.ElapsedMilliseconds;
_logger.LogInformation("Scanned {TotalFiles} files in {ElapsedScanTime} milliseconds", totalFiles,
scanElapsedTime);
return new Tuple<int, long, Dictionary<ParsedSeries, List<ParserInfo>>>(totalFiles, scanElapsedTime, parsedSeries);
}
@ -426,24 +425,15 @@ public class ScannerService : IScannerService
// Now, we only have to deal with series that exist on disk. Let's recalculate the volumes for each series
var librarySeries = cleanedSeries.ToList();
//var index = 0;
foreach (var series in librarySeries)
{
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Started, series.Name));
await UpdateSeries(series, parsedSeries, allPeople, allTags, allGenres, library);
// await _eventHub.SendMessageAsync(SignalREvents.NotificationProgress,
// MessageFactory.ScanLibraryProgressEvent(library.Id, (1F * index) / librarySeries.Count));
// index += 1;
}
try
{
await _unitOfWork.CommitAsync();
// Update the people, genres, and tags after committing as we might have inserted new ones.
allPeople = await _unitOfWork.PersonRepository.GetAllPeople();
allGenres = await _unitOfWork.GenreRepository.GetAllGenresAsync();
allTags = await _unitOfWork.TagRepository.GetAllTagsAsync();
}
catch (Exception ex)
{
@ -471,10 +461,6 @@ public class ScannerService : IScannerService
// This is something more like, the series has finished updating in the backend. It may or may not have been modified.
await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, MessageFactory.ScanSeriesEvent(series.Id, series.Name));
}
//var progress = Math.Max(0, Math.Min(1, ((chunk + 1F) * chunkInfo.ChunkSize) / chunkInfo.TotalSize));
// await _eventHub.SendMessageAsync(SignalREvents.NotificationProgress,
// MessageFactory.ScanLibraryProgressEvent(library.Id, progress));
}
@ -484,6 +470,7 @@ public class ScannerService : IScannerService
var allSeries = (await _unitOfWork.SeriesRepository.GetSeriesForLibraryIdAsync(library.Id)).ToList();
_logger.LogDebug("[ScannerService] Fetched {AllSeriesCount} series for comparing new series with. There should be {DeltaToParsedSeries} new series",
allSeries.Count, parsedSeries.Count - allSeries.Count);
// TODO: Once a parsedSeries is processed, remove the key to free up some memory
foreach (var (key, infos) in parsedSeries)
{
// Key is normalized already
@ -518,7 +505,6 @@ public class ScannerService : IScannerService
}
var i = 0;
foreach(var series in newSeries)
{
_logger.LogDebug("[ScannerService] Processing series {SeriesName}", series.OriginalName);
@ -539,11 +525,6 @@ public class ScannerService : IScannerService
_logger.LogCritical(ex, "[ScannerService] There was a critical exception adding new series entry for {SeriesName} with a duplicate index key: {IndexKey} ",
series.Name, $"{series.Name}_{series.NormalizedName}_{series.LocalizedName}_{series.LibraryId}_{series.Format}");
}
//var progress = Math.Max(0F, Math.Min(1F, i * 1F / newSeries.Count));
// await _eventHub.SendMessageAsync(SignalREvents.NotificationProgress,
// MessageFactory.ScanLibraryProgressEvent(library.Id, progress));
i++;
}
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended));
@ -559,8 +540,6 @@ public class ScannerService : IScannerService
try
{
_logger.LogInformation("[ScannerService] Processing series {SeriesName}", series.OriginalName);
//await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.DbUpdateProgressEvent(series, ProgressEventType.Started));
//await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.DbUpdateProgressEvent(series, ProgressEventType.Updated));
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name));
// Get all associated ParsedInfos to the series. This includes infos that use a different filename that matches Series LocalizedName
@ -575,8 +554,8 @@ public class ScannerService : IScannerService
series.Format = parsedInfos[0].Format;
}
series.OriginalName ??= parsedInfos[0].Series;
series.SortName ??= parsedInfos[0].SeriesSort;
//await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.DbUpdateProgressEvent(series, ProgressEventType.Updated));
if (!series.SortNameLocked) series.SortName ??= parsedInfos[0].SeriesSort;
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name));
UpdateSeriesMetadata(series, allPeople, allGenres, allTags, library.Type);
@ -585,7 +564,7 @@ public class ScannerService : IScannerService
{
_logger.LogError(ex, "[ScannerService] There was an exception updating volumes for {SeriesName}", series.Name);
}
//await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.DbUpdateProgressEvent(series, ProgressEventType.Ended));
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name));
}
@ -624,65 +603,116 @@ public class ScannerService : IScannerService
}
// Set the AgeRating as highest in all the comicInfos
series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
if (!series.Metadata.AgeRatingLocked) series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
series.Metadata.Count = chapters.Max(chapter => chapter.TotalCount);
series.Metadata.PublicationStatus = PublicationStatus.OnGoing;
if (chapters.Max(chapter => chapter.Count) >= series.Metadata.Count && series.Metadata.Count > 0)
if (!series.Metadata.PublicationStatusLocked)
{
series.Metadata.PublicationStatus = PublicationStatus.Completed;
series.Metadata.PublicationStatus = PublicationStatus.OnGoing;
if (chapters.Max(chapter => chapter.Count) >= series.Metadata.Count && series.Metadata.Count > 0)
{
series.Metadata.PublicationStatus = PublicationStatus.Completed;
}
}
if (!string.IsNullOrEmpty(firstChapter.Summary))
if (!string.IsNullOrEmpty(firstChapter.Summary) && !series.Metadata.SummaryLocked)
{
series.Metadata.Summary = firstChapter.Summary;
}
if (!string.IsNullOrEmpty(firstChapter.Language))
if (!string.IsNullOrEmpty(firstChapter.Language) && !series.Metadata.LanguageLocked)
{
series.Metadata.Language = firstChapter.Language;
}
void HandleAddPerson(Person person)
{
PersonHelper.AddPersonIfNotExists(series.Metadata.People, person);
allPeople.Add(person);
}
// Handle People
foreach (var chapter in chapters)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Writer).Select(p => p.Name), PersonRole.Writer,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.WriterLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Writer).Select(p => p.Name), PersonRole.Writer,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.CoverArtist).Select(p => p.Name), PersonRole.CoverArtist,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.CoverArtistLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.CoverArtist).Select(p => p.Name), PersonRole.CoverArtist,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Publisher).Select(p => p.Name), PersonRole.Publisher,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.PublisherLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Publisher).Select(p => p.Name), PersonRole.Publisher,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Character).Select(p => p.Name), PersonRole.Character,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.CharacterLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Character).Select(p => p.Name), PersonRole.Character,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Colorist).Select(p => p.Name), PersonRole.Colorist,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.ColoristLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Colorist).Select(p => p.Name), PersonRole.Colorist,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Editor).Select(p => p.Name), PersonRole.Editor,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.EditorLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Editor).Select(p => p.Name), PersonRole.Editor,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Inker).Select(p => p.Name), PersonRole.Inker,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.InkerLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Inker).Select(p => p.Name), PersonRole.Inker,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Letterer).Select(p => p.Name), PersonRole.Letterer,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.LettererLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Letterer).Select(p => p.Name), PersonRole.Letterer,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Penciller).Select(p => p.Name), PersonRole.Penciller,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.PencillerLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Penciller).Select(p => p.Name), PersonRole.Penciller,
HandleAddPerson);
}
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Translator).Select(p => p.Name), PersonRole.Translator,
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
if (!series.Metadata.TranslatorLocked)
{
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Translator).Select(p => p.Name), PersonRole.Translator,
HandleAddPerson);
}
TagHelper.UpdateTag(allTags, chapter.Tags.Select(t => t.Title), false, (tag, _) =>
TagHelper.AddTagIfNotExists(series.Metadata.Tags, tag));
if (!series.Metadata.TagsLocked)
{
TagHelper.UpdateTag(allTags, chapter.Tags.Select(t => t.Title), false, (tag, _) =>
{
TagHelper.AddTagIfNotExists(series.Metadata.Tags, tag);
allTags.Add(tag);
});
}
GenreHelper.UpdateGenre(allGenres, chapter.Genres.Select(t => t.Title), false, genre =>
GenreHelper.AddGenreIfNotExists(series.Metadata.Genres, genre));
if (!series.Metadata.GenresLocked)
{
GenreHelper.UpdateGenre(allGenres, chapter.Genres.Select(t => t.Title), false, genre =>
{
GenreHelper.AddGenreIfNotExists(series.Metadata.Genres, genre);
allGenres.Add(genre);
});
}
}
var people = chapters.SelectMany(c => c.People).ToList();
@ -708,7 +738,6 @@ public class ScannerService : IScannerService
_unitOfWork.VolumeRepository.Add(volume);
}
// TODO: Here we can put a signalR update
_logger.LogDebug("[ScannerService] Parsing {SeriesName} - Volume {VolumeNumber}", series.Name, volume.Name);
var infos = parsedInfos.Where(p => p.Volumes == volumeNumber).ToArray();
UpdateChapters(volume, infos);