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:
parent
47a92a2e01
commit
ba77954d5c
60 changed files with 3605 additions and 723 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue