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

@ -337,29 +337,67 @@ public class ReaderService : IReaderService
return -1;
}
/// <summary>
/// Finds the chapter to continue reading from. If a chapter has progress and not complete, return that. If not, progress in the
/// ordering (Volumes -> Loose Chapters -> Special) to find next chapter. If all are read, return first in order for series.
/// </summary>
/// <param name="seriesId"></param>
/// <param name="userId"></param>
/// <returns></returns>
public async Task<ChapterDto> GetContinuePoint(int seriesId, int userId)
{
// Loop through all chapters that are not in volume 0
var progress = (await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(seriesId, userId)).ToList();
var volumes = (await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, userId)).ToList();
var nonSpecialChapters = volumes
if (progress.Count == 0)
{
// I think i need a way to sort volumes last
return volumes.OrderBy(v => double.Parse(v.Number + ""), _chapterSortComparer).First().Chapters
.OrderBy(c => float.Parse(c.Number)).First();
}
// Loop through all chapters that are not in volume 0
var volumeChapters = volumes
.Where(v => v.Number != 0)
.SelectMany(v => v.Chapters)
.OrderBy(c => float.Parse(c.Number))
.ToList();
var currentlyReadingChapter = nonSpecialChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages);
// If there are any volumes that have progress, return those. If not, move on.
var currentlyReadingChapter = volumeChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages && chapter.PagesRead > 0);
if (currentlyReadingChapter != null) return currentlyReadingChapter;
// Check if there are any specials
// Check loose leaf chapters (and specials). First check if there are any
var volume = volumes.SingleOrDefault(v => v.Number == 0);
if (volume == null) return nonSpecialChapters.First();
return FindNextReadingChapter(volume == null ? volumeChapters : volume.Chapters.OrderBy(c => float.Parse(c.Number)).ToList());
}
var chapters = volume.Chapters.OrderBy(c => float.Parse(c.Number)).ToList();
private static ChapterDto FindNextReadingChapter(IList<ChapterDto> volumeChapters)
{
var chaptersWithProgress = volumeChapters.Where(c => c.PagesRead > 0).ToList();
if (chaptersWithProgress.Count > 0)
{
var last = chaptersWithProgress.FindLastIndex(c => c.PagesRead > 0);
if (last + 1 < chaptersWithProgress.Count)
{
return chaptersWithProgress.ElementAt(last + 1);
}
return chapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages) ?? chapters.First();
var lastChapter = chaptersWithProgress.ElementAt(last);
if (lastChapter.PagesRead < lastChapter.Pages)
{
return chaptersWithProgress.ElementAt(last);
}
// chaptersWithProgress are all read, then we need to get the next chapter that doesn't have progress
var lastIndexWithProgress = volumeChapters.IndexOf(lastChapter);
if (lastIndexWithProgress + 1 < volumeChapters.Count)
{
return volumeChapters.ElementAt(lastIndexWithProgress + 1);
}
}
return volumeChapters.First();
}

View file

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -6,10 +7,14 @@ using System.Threading.Tasks;
using API.Comparators;
using API.Data;
using API.DTOs;
using API.DTOs.CollectionTags;
using API.DTOs.Metadata;
using API.Entities;
using API.Entities.Enums;
using API.Helpers;
using API.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.VisualBasic;
namespace API.Services;
@ -44,52 +49,100 @@ public class SeriesService : ISeriesService
{
var seriesId = updateSeriesMetadataDto.SeriesMetadata.SeriesId;
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
var allTags = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).ToList();
var allCollectionTags = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).ToList();
var allGenres = (await _unitOfWork.GenreRepository.GetAllGenresAsync()).ToList();
var allPeople = (await _unitOfWork.PersonRepository.GetAllPeople()).ToList();
var allTags = (await _unitOfWork.TagRepository.GetAllTagsAsync()).ToList();
if (series.Metadata == null)
{
series.Metadata = DbFactory.SeriesMetadata(updateSeriesMetadataDto.Tags
series.Metadata = DbFactory.SeriesMetadata(updateSeriesMetadataDto.CollectionTags
.Select(dto => DbFactory.CollectionTag(dto.Id, dto.Title, dto.Summary, dto.Promoted)).ToList());
}
else
{
if (series.Metadata.AgeRating != updateSeriesMetadataDto.SeriesMetadata.AgeRating)
{
series.Metadata.AgeRating = updateSeriesMetadataDto.SeriesMetadata.AgeRating;
series.Metadata.AgeRatingLocked = true;
}
if (series.Metadata.PublicationStatus != updateSeriesMetadataDto.SeriesMetadata.PublicationStatus)
{
series.Metadata.PublicationStatus = updateSeriesMetadataDto.SeriesMetadata.PublicationStatus;
series.Metadata.PublicationStatusLocked = true;
}
if (series.Metadata.Summary != updateSeriesMetadataDto.SeriesMetadata.Summary.Trim())
{
series.Metadata.Summary = updateSeriesMetadataDto.SeriesMetadata?.Summary.Trim();
series.Metadata.SummaryLocked = true;
}
series.Metadata.CollectionTags ??= new List<CollectionTag>();
// TODO: Move this merging logic into a reusable code as it can be used for any Tag
var newTags = new List<CollectionTag>();
// I want a union of these 2 lists. Return only elements that are in both lists, but the list types are different
var existingTags = series.Metadata.CollectionTags.ToList();
foreach (var existing in existingTags)
{
if (updateSeriesMetadataDto.Tags.SingleOrDefault(t => t.Id == existing.Id) == null)
{
// Remove tag
series.Metadata.CollectionTags.Remove(existing);
}
}
// At this point, all tags that aren't in dto have been removed.
foreach (var tag in updateSeriesMetadataDto.Tags)
{
var existingTag = allTags.SingleOrDefault(t => t.Title == tag.Title);
if (existingTag != null)
{
if (series.Metadata.CollectionTags.All(t => t.Title != tag.Title))
{
newTags.Add(existingTag);
}
}
else
{
// Add new tag
newTags.Add(DbFactory.CollectionTag(tag.Id, tag.Title, tag.Summary, tag.Promoted));
}
}
foreach (var tag in newTags)
UpdateRelatedList(updateSeriesMetadataDto.CollectionTags, series, allCollectionTags, (tag) =>
{
series.Metadata.CollectionTags.Add(tag);
});
series.Metadata.Genres ??= new List<Genre>();
UpdateGenreList(updateSeriesMetadataDto.SeriesMetadata.Genres, series, allGenres, (genre) =>
{
series.Metadata.Genres.Add(genre);
}, () => series.Metadata.GenresLocked = true);
series.Metadata.Tags ??= new List<Tag>();
UpdateTagList(updateSeriesMetadataDto.SeriesMetadata.Tags, series, allTags, (tag) =>
{
series.Metadata.Tags.Add(tag);
}, () => series.Metadata.TagsLocked = true);
void HandleAddPerson(Person person)
{
PersonHelper.AddPersonIfNotExists(series.Metadata.People, person);
allPeople.Add(person);
}
series.Metadata.People ??= new List<Person>();
UpdatePeopleList(PersonRole.Writer, updateSeriesMetadataDto.SeriesMetadata.Writers, series, allPeople,
HandleAddPerson, () => series.Metadata.WriterLocked = true);
UpdatePeopleList(PersonRole.Character, updateSeriesMetadataDto.SeriesMetadata.Characters, series, allPeople,
HandleAddPerson, () => series.Metadata.CharacterLocked = true);
UpdatePeopleList(PersonRole.Colorist, updateSeriesMetadataDto.SeriesMetadata.Colorists, series, allPeople,
HandleAddPerson, () => series.Metadata.ColoristLocked = true);
UpdatePeopleList(PersonRole.Editor, updateSeriesMetadataDto.SeriesMetadata.Editors, series, allPeople,
HandleAddPerson, () => series.Metadata.EditorLocked = true);
UpdatePeopleList(PersonRole.Inker, updateSeriesMetadataDto.SeriesMetadata.Inkers, series, allPeople,
HandleAddPerson, () => series.Metadata.InkerLocked = true);
UpdatePeopleList(PersonRole.Letterer, updateSeriesMetadataDto.SeriesMetadata.Letterers, series, allPeople,
HandleAddPerson, () => series.Metadata.LettererLocked = true);
UpdatePeopleList(PersonRole.Penciller, updateSeriesMetadataDto.SeriesMetadata.Pencillers, series, allPeople,
HandleAddPerson, () => series.Metadata.PencillerLocked = true);
UpdatePeopleList(PersonRole.Publisher, updateSeriesMetadataDto.SeriesMetadata.Publishers, series, allPeople,
HandleAddPerson, () => series.Metadata.PublisherLocked = true);
UpdatePeopleList(PersonRole.Translator, updateSeriesMetadataDto.SeriesMetadata.Translators, series, allPeople,
HandleAddPerson, () => series.Metadata.TranslatorLocked = true);
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;
}
if (!_unitOfWork.HasChanges())
@ -99,13 +152,16 @@ public class SeriesService : ISeriesService
if (await _unitOfWork.CommitAsync())
{
foreach (var tag in updateSeriesMetadataDto.Tags)
foreach (var tag in updateSeriesMetadataDto.CollectionTags)
{
await _eventHub.SendMessageAsync(MessageFactory.SeriesAddedToCollection,
MessageFactory.SeriesAddedToCollectionEvent(tag.Id,
updateSeriesMetadataDto.SeriesMetadata.SeriesId), false);
}
await _eventHub.SendMessageAsync(MessageFactory.ScanSeries,
MessageFactory.ScanSeriesEvent(series.Id, series.Name), false);
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
return true;
@ -120,6 +176,165 @@ public class SeriesService : ISeriesService
return false;
}
// TODO: Move this to a helper so we can easily test
private static void UpdateRelatedList(ICollection<CollectionTagDto> tags, Series series, IReadOnlyCollection<CollectionTag> allTags,
Action<CollectionTag> handleAdd)
{
// I want a union of these 2 lists. Return only elements that are in both lists, but the list types are different
var existingTags = series.Metadata.CollectionTags.ToList();
foreach (var existing in existingTags)
{
if (tags.SingleOrDefault(t => t.Id == existing.Id) == null)
{
// Remove tag
series.Metadata.CollectionTags.Remove(existing);
}
}
// At this point, all tags that aren't in dto have been removed.
foreach (var tag in tags)
{
var existingTag = allTags.SingleOrDefault(t => t.Title == tag.Title);
if (existingTag != null)
{
if (series.Metadata.CollectionTags.All(t => t.Title != tag.Title))
{
handleAdd(existingTag);
}
}
else
{
// Add new tag
handleAdd(DbFactory.CollectionTag(tag.Id, tag.Title, tag.Summary, tag.Promoted));
}
}
}
private static void UpdateGenreList(ICollection<GenreTagDto> tags, Series series, IReadOnlyCollection<Genre> allTags, Action<Genre> handleAdd, Action onModified)
{
var isModified = false;
// I want a union of these 2 lists. Return only elements that are in both lists, but the list types are different
var existingTags = series.Metadata.Genres.ToList();
foreach (var existing in existingTags)
{
if (tags.SingleOrDefault(t => t.Id == existing.Id) == null)
{
// Remove tag
series.Metadata.Genres.Remove(existing);
isModified = true;
}
}
// At this point, all tags that aren't in dto have been removed.
foreach (var tagTitle in tags.Select(t => t.Title))
{
var existingTag = allTags.SingleOrDefault(t => t.Title == tagTitle);
if (existingTag != null)
{
if (series.Metadata.Genres.All(t => t.Title != tagTitle))
{
handleAdd(existingTag);
isModified = true;
}
}
else
{
// Add new tag
handleAdd(DbFactory.Genre(tagTitle, false));
isModified = true;
}
}
if (isModified)
{
onModified();
}
}
private static void UpdateTagList(ICollection<TagDto> tags, Series series, IReadOnlyCollection<Tag> allTags, Action<Tag> handleAdd, Action onModified)
{
var isModified = false;
// I want a union of these 2 lists. Return only elements that are in both lists, but the list types are different
var existingTags = series.Metadata.Tags.ToList();
foreach (var existing in existingTags)
{
if (tags.SingleOrDefault(t => t.Id == existing.Id) == null)
{
// Remove tag
series.Metadata.Tags.Remove(existing);
isModified = true;
}
}
// At this point, all tags that aren't in dto have been removed.
foreach (var tagTitle in tags.Select(t => t.Title))
{
var existingTag = allTags.SingleOrDefault(t => t.Title == tagTitle);
if (existingTag != null)
{
if (series.Metadata.Tags.All(t => t.Title != tagTitle))
{
handleAdd(existingTag);
isModified = true;
}
}
else
{
// Add new tag
handleAdd(DbFactory.Tag(tagTitle, false));
isModified = true;
}
}
if (isModified)
{
onModified();
}
}
private static void UpdatePeopleList(PersonRole role, ICollection<PersonDto> tags, Series series, IReadOnlyCollection<Person> allTags,
Action<Person> handleAdd, Action onModified)
{
var isModified = false;
// I want a union of these 2 lists. Return only elements that are in both lists, but the list types are different
var existingTags = series.Metadata.People.Where(p => p.Role == role).ToList();
foreach (var existing in existingTags)
{
if (tags.SingleOrDefault(t => t.Id == existing.Id) == null) // This needs to check against role
{
// Remove tag
series.Metadata.People.Remove(existing);
isModified = true;
}
}
// At this point, all tags that aren't in dto have been removed.
foreach (var tag in tags)
{
var existingTag = allTags.SingleOrDefault(t => t.Name == tag.Name && t.Role == tag.Role);
if (existingTag != null)
{
if (series.Metadata.People.All(t => t.Name != tag.Name && t.Role == tag.Role))
{
handleAdd(existingTag);
isModified = true;
}
}
else
{
// Add new tag
handleAdd(DbFactory.Person(tag.Name, role));
isModified = true;
}
}
if (isModified)
{
onModified();
}
}
/// <summary>
///
/// </summary>

View file

@ -115,6 +115,12 @@ namespace API.Services.Tasks.Scanner
{
info.Chapters = info.ComicInfo.Number;
}
// Patch is SeriesSort from ComicInfo
if (info.ComicInfo != null && !string.IsNullOrEmpty(info.ComicInfo.TitleSort))
{
info.SeriesSort = info.ComicInfo.TitleSort;
}
}
TrackSeries(info);

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);