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
|
@ -106,6 +106,8 @@ namespace API.Controllers
|
|||
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
|
||||
var file = new FileInfo(Path.Join(bookmarkDirectory, bookmark.FileName));
|
||||
var format = Path.GetExtension(file.FullName).Replace(".", "");
|
||||
|
||||
Response.AddCacheHeader(file.FullName);
|
||||
return PhysicalFile(file.FullName, "image/" + format, Path.GetFileName(file.FullName));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,18 +123,30 @@ public class MetadataController : BaseApiController
|
|||
public async Task<ActionResult<IList<LanguageDto>>> GetAllLanguages(string? libraryIds)
|
||||
{
|
||||
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
||||
if (ids != null && ids.Count > 0)
|
||||
if (ids is {Count: > 0})
|
||||
{
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetAllLanguagesForLibrariesAsync(ids));
|
||||
}
|
||||
|
||||
var englishTag = CultureInfo.GetCultureInfo("en");
|
||||
return Ok(new List<LanguageDto>()
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Title = CultureInfo.GetCultureInfo("en").DisplayName,
|
||||
IsoCode = "en"
|
||||
Title = englishTag.DisplayName,
|
||||
IsoCode = englishTag.IetfLanguageTag
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("all-languages")]
|
||||
public IEnumerable<LanguageDto> GetAllValidLanguages()
|
||||
{
|
||||
return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(c =>
|
||||
new LanguageDto()
|
||||
{
|
||||
Title = c.DisplayName,
|
||||
IsoCode = c.IetfLanguageTag
|
||||
}).Where(l => !string.IsNullOrEmpty(l.IsoCode));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ namespace API.Controllers
|
|||
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}");
|
||||
var format = Path.GetExtension(path).Replace(".", "");
|
||||
|
||||
Response.AddCacheHeader(path);
|
||||
return PhysicalFile(path, "image/" + format, Path.GetFileName(path));
|
||||
}
|
||||
catch (Exception)
|
||||
|
|
|
@ -121,6 +121,12 @@ namespace API.Controllers
|
|||
return Ok(await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId));
|
||||
}
|
||||
|
||||
[HttpGet("chapter-metadata")]
|
||||
public async Task<ActionResult<ChapterDto>> GetChapterMetadata(int chapterId)
|
||||
{
|
||||
return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId));
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("update-rating")]
|
||||
public async Task<ActionResult> UpdateSeriesRating(UpdateSeriesRatingDto updateSeriesRatingDto)
|
||||
|
@ -143,10 +149,27 @@ namespace API.Controllers
|
|||
{
|
||||
return BadRequest("A series already exists in this library with this name. Series Names must be unique to a library.");
|
||||
}
|
||||
series.Name = updateSeries.Name.Trim();
|
||||
series.LocalizedName = updateSeries.LocalizedName.Trim();
|
||||
series.SortName = updateSeries.SortName?.Trim();
|
||||
series.Metadata.Summary = updateSeries.Summary?.Trim();
|
||||
|
||||
if (!series.Name.Equals(updateSeries.Name.Trim()))
|
||||
{
|
||||
series.Name = updateSeries.Name.Trim();
|
||||
series.NameLocked = true;
|
||||
}
|
||||
if (!series.SortName.Equals(updateSeries.SortName.Trim()))
|
||||
{
|
||||
series.SortName = updateSeries.SortName.Trim();
|
||||
series.SortNameLocked = true;
|
||||
}
|
||||
if (!series.LocalizedName.Equals(updateSeries.LocalizedName.Trim()))
|
||||
{
|
||||
series.LocalizedName = updateSeries.LocalizedName.Trim();
|
||||
series.LocalizedNameLocked = true;
|
||||
}
|
||||
|
||||
|
||||
if (!series.NameLocked) series.NameLocked = false;
|
||||
if (!series.SortNameLocked) series.SortNameLocked = false;
|
||||
if (!series.LocalizedNameLocked) series.LocalizedNameLocked = false;
|
||||
|
||||
var needsRefreshMetadata = false;
|
||||
// This is when you hit Reset
|
||||
|
|
|
@ -61,31 +61,5 @@ namespace API.DTOs
|
|||
/// </summary>
|
||||
/// <remarks>Metadata field</remarks>
|
||||
public string TitleName { get; set; }
|
||||
/// <summary>
|
||||
/// Summary for the Chapter/Issue
|
||||
/// </summary>
|
||||
public string Summary { get; set; }
|
||||
/// <summary>
|
||||
/// Language for the Chapter/Issue
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
/// <summary>
|
||||
/// Number in the TotalCount of issues
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
/// <summary>
|
||||
/// Total number of issues for the series
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
public ICollection<PersonDto> Writers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Penciller { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Inker { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Colorist { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Letterer { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> CoverArtist { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Editor { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Publisher { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Translators { get; set; } = new List<PersonDto>();
|
||||
public ICollection<TagDto> Tags { get; set; } = new List<TagDto>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.DTOs.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Exclusively metadata about a given chapter
|
||||
/// </summary>
|
||||
public class ChapterMetadataDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int ChapterId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public ICollection<PersonDto> Writers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Penciller { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Inker { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Colorist { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Letterer { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> CoverArtist { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Editor { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Publisher { get; set; } = new List<PersonDto>();
|
||||
public int ChapterId { get; set; }
|
||||
public ICollection<PersonDto> CoverArtists { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Publishers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Characters { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Pencillers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Inkers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Colorists { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Letterers { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Editors { get; set; } = new List<PersonDto>();
|
||||
public ICollection<PersonDto> Translators { get; set; } = new List<PersonDto>();
|
||||
|
||||
public ICollection<GenreTagDto> Genres { get; set; } = new List<GenreTagDto>();
|
||||
|
||||
/// <summary>
|
||||
/// Collection of all Tags from underlying chapters for a Series
|
||||
/// </summary>
|
||||
public ICollection<TagDto> Tags { get; set; } = new List<TagDto>();
|
||||
public AgeRating AgeRating { get; set; }
|
||||
public string ReleaseDate { get; set; }
|
||||
public PublicationStatus PublicationStatus { get; set; }
|
||||
/// <summary>
|
||||
/// Summary for the Chapter/Issue
|
||||
/// </summary>
|
||||
public string Summary { get; set; }
|
||||
/// <summary>
|
||||
/// Language for the Chapter/Issue
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
/// <summary>
|
||||
/// Number in the TotalCount of issues
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
/// <summary>
|
||||
/// Total number of issues for the series
|
||||
/// </summary>
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,10 @@ namespace API.DTOs
|
|||
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public bool NameLocked { get; set; }
|
||||
public bool SortNameLocked { get; set; }
|
||||
public bool LocalizedNameLocked { get; set; }
|
||||
|
||||
public int LibraryId { get; set; }
|
||||
public string LibraryName { get; set; }
|
||||
}
|
||||
|
|
|
@ -56,6 +56,30 @@ namespace API.DTOs
|
|||
/// </summary>
|
||||
public PublicationStatus PublicationStatus { get; set; }
|
||||
|
||||
public bool LanguageLocked { get; set; }
|
||||
public bool SummaryLocked { get; set; }
|
||||
/// <summary>
|
||||
/// Locked by user so metadata updates from scan loop will not override AgeRating
|
||||
/// </summary>
|
||||
public bool AgeRatingLocked { get; set; }
|
||||
/// <summary>
|
||||
/// Locked by user so metadata updates from scan loop will not override PublicationStatus
|
||||
/// </summary>
|
||||
public bool PublicationStatusLocked { get; set; }
|
||||
public bool GenresLocked { get; set; }
|
||||
public bool TagsLocked { get; set; }
|
||||
public bool WriterLocked { get; set; }
|
||||
public bool CharacterLocked { get; set; }
|
||||
public bool ColoristLocked { get; set; }
|
||||
public bool EditorLocked { get; set; }
|
||||
public bool InkerLocked { get; set; }
|
||||
public bool LettererLocked { get; set; }
|
||||
public bool PencillerLocked { get; set; }
|
||||
public bool PublisherLocked { get; set; }
|
||||
public bool TranslatorLocked { get; set; }
|
||||
public bool CoverArtistLocked { get; set; }
|
||||
|
||||
|
||||
public int SeriesId { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
public string Name { get; init; }
|
||||
public string LocalizedName { get; init; }
|
||||
public string SortName { get; init; }
|
||||
public string Summary { get; init; }
|
||||
public byte[] CoverImage { get; init; }
|
||||
public int UserRating { get; set; }
|
||||
public string UserReview { get; set; }
|
||||
public bool CoverImageLocked { get; set; }
|
||||
|
||||
public bool UnlockName { get; set; }
|
||||
public bool UnlockSortName { get; set; }
|
||||
public bool UnlockLocalizedName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ namespace API.DTOs
|
|||
public class UpdateSeriesMetadataDto
|
||||
{
|
||||
public SeriesMetadataDto SeriesMetadata { get; set; }
|
||||
public ICollection<CollectionTagDto> Tags { get; set; }
|
||||
public ICollection<CollectionTagDto> CollectionTags { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace API.Data.Metadata
|
|||
public int PageCount { get; set; }
|
||||
// ReSharper disable once InconsistentNaming
|
||||
/// <summary>
|
||||
/// ISO 639-1 Code to represent the language of the content
|
||||
/// IETF BCP 47 Code to represent the language of the content
|
||||
/// </summary>
|
||||
public string LanguageISO { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
|
|
1448
API/Data/Migrations/20220303205301_SeriesLockedFields.Designer.cs
generated
Normal file
1448
API/Data/Migrations/20220303205301_SeriesLockedFields.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
224
API/Data/Migrations/20220303205301_SeriesLockedFields.cs
Normal file
224
API/Data/Migrations/20220303205301_SeriesLockedFields.cs
Normal file
|
@ -0,0 +1,224 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class SeriesLockedFields : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "AgeRatingLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "CharacterLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "ColoristLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "CoverArtistLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "EditorLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "GenresLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "InkerLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "LanguageLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "LettererLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "PencillerLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "PublicationStatusLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "PublisherLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SummaryLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "TagsLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "TranslatorLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "WriterLocked",
|
||||
table: "SeriesMetadata",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "LocalizedNameLocked",
|
||||
table: "Series",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "NameLocked",
|
||||
table: "Series",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SortNameLocked",
|
||||
table: "Series",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AgeRatingLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CharacterLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ColoristLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CoverArtistLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EditorLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "GenresLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "InkerLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LanguageLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LettererLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PencillerLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublicationStatusLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PublisherLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SummaryLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TagsLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TranslatorLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WriterLocked",
|
||||
table: "SeriesMetadata");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LocalizedNameLocked",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "NameLocked",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortNameLocked",
|
||||
table: "Series");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.2");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
@ -502,15 +502,51 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("AgeRating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AgeRatingLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("CharacterLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ColoristLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Count")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("CoverArtistLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EditorLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("GenresLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("InkerLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Language")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LanguageLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LettererLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("PencillerLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PublicationStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("PublicationStatusLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("PublisherLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReleaseYear")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -524,6 +560,18 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("Summary")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("SummaryLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TagsLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("TranslatorLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WriterLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeriesId")
|
||||
|
@ -647,9 +695,15 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LocalizedNameLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("NameLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
@ -662,6 +716,9 @@ namespace API.Data.Migrations
|
|||
b.Property<string>("SortName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("SortNameLocked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
@ -13,6 +14,7 @@ public interface IAppUserProgressRepository
|
|||
Task<bool> UserHasProgress(LibraryType libraryType, int userId);
|
||||
Task<AppUserProgress> GetUserProgressAsync(int chapterId, int userId);
|
||||
Task<bool> HasAnyProgressOnSeriesAsync(int seriesId, int userId);
|
||||
Task<IEnumerable<AppUserProgress>> GetUserProgressForSeriesAsync(int seriesId, int userId);
|
||||
}
|
||||
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
|
@ -83,6 +85,19 @@ public class AppUserProgressRepository : IAppUserProgressRepository
|
|||
.AnyAsync(aup => aup.PagesRead > 0 && aup.AppUserId == userId && aup.SeriesId == seriesId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will return any user progress. This filters out progress rows that have no pages read.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<AppUserProgress>> GetUserProgressForSeriesAsync(int seriesId, int userId)
|
||||
{
|
||||
return await _context.AppUserProgresses
|
||||
.Where(p => p.SeriesId == seriesId && p.AppUserId == userId && p.PagesRead > 0)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserProgress> GetUserProgressAsync(int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserProgresses
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using AutoMapper;
|
||||
|
@ -18,6 +19,7 @@ public interface IChapterRepository
|
|||
Task<int> GetChapterTotalPagesAsync(int chapterId);
|
||||
Task<Chapter> GetChapterAsync(int chapterId);
|
||||
Task<ChapterDto> GetChapterDtoAsync(int chapterId);
|
||||
Task<ChapterMetadataDto> GetChapterMetadataDtoAsync(int chapterId);
|
||||
Task<IList<MangaFile>> GetFilesForChapterAsync(int chapterId);
|
||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||
Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds);
|
||||
|
@ -46,6 +48,7 @@ public class ChapterRepository : IChapterRepository
|
|||
return await _context.Chapter
|
||||
.Where(c => chapterIds.Contains(c.Id))
|
||||
.Include(c => c.Volume)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
@ -113,6 +116,19 @@ public class ChapterRepository : IChapterRepository
|
|||
.Include(c => c.Files)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
|
||||
return chapter;
|
||||
}
|
||||
|
||||
public async Task<ChapterMetadataDto> GetChapterMetadataDtoAsync(int chapterId)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.ProjectTo<ChapterMetadataDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
|
||||
return chapter;
|
||||
|
@ -140,6 +156,7 @@ public class ChapterRepository : IChapterRepository
|
|||
{
|
||||
return await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.Entities.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Has a 1-to-1 relationship with a Chapter. Represents metadata about a chapter.
|
||||
/// </summary>
|
||||
public class ChapterMetadata
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Chapter title
|
||||
/// </summary>
|
||||
/// <remarks>This should not be confused with Chapter.Title which is used for special filenames.</remarks>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Year { get; set; } // Only time I can think this will be more than 1 year is for a volume which will be a spread
|
||||
public string StoryArc { get; set; } // This might be a list
|
||||
|
||||
/// <summary>
|
||||
/// All people attached at a Chapter level. Usually Comics will have different people per issue.
|
||||
/// </summary>
|
||||
public ICollection<Person> People { get; set; } = new List<Person>();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Relationships
|
||||
public Chapter Chapter { get; set; }
|
||||
public int ChapterId { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -40,6 +40,31 @@ namespace API.Entities.Metadata
|
|||
public int Count { get; set; } = 0;
|
||||
public PublicationStatus PublicationStatus { get; set; }
|
||||
|
||||
// Locks
|
||||
public bool LanguageLocked { get; set; }
|
||||
public bool SummaryLocked { get; set; }
|
||||
/// <summary>
|
||||
/// Locked by user so metadata updates from scan loop will not override AgeRating
|
||||
/// </summary>
|
||||
public bool AgeRatingLocked { get; set; }
|
||||
/// <summary>
|
||||
/// Locked by user so metadata updates from scan loop will not override PublicationStatus
|
||||
/// </summary>
|
||||
public bool PublicationStatusLocked { get; set; }
|
||||
public bool GenresLocked { get; set; }
|
||||
public bool TagsLocked { get; set; }
|
||||
public bool WriterLocked { get; set; }
|
||||
public bool CharacterLocked { get; set; }
|
||||
public bool ColoristLocked { get; set; }
|
||||
public bool EditorLocked { get; set; }
|
||||
public bool InkerLocked { get; set; }
|
||||
public bool LettererLocked { get; set; }
|
||||
public bool PencillerLocked { get; set; }
|
||||
public bool PublisherLocked { get; set; }
|
||||
public bool TranslatorLocked { get; set; }
|
||||
public bool CoverArtistLocked { get; set; }
|
||||
|
||||
|
||||
// Relationship
|
||||
public Series Series { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
|
@ -48,6 +73,7 @@ namespace API.Entities.Metadata
|
|||
[ConcurrencyCheck]
|
||||
public uint RowVersion { get; private set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnSavingChanges()
|
||||
{
|
||||
|
|
|
@ -58,6 +58,10 @@ namespace API.Entities
|
|||
/// </summary>
|
||||
public MangaFormat Format { get; set; } = MangaFormat.Unknown;
|
||||
|
||||
public bool NameLocked { get; set; }
|
||||
public bool SortNameLocked { get; set; }
|
||||
public bool LocalizedNameLocked { get; set; }
|
||||
|
||||
public SeriesMetadata Metadata { get; set; }
|
||||
public ICollection<AppUserRating> Ratings { get; set; } = new List<AppUserRating>();
|
||||
public ICollection<AppUserProgress> Progress { get; set; } = new List<AppUserProgress>();
|
||||
|
|
|
@ -21,45 +21,16 @@ namespace API.Helpers
|
|||
public AutoMapperProfiles()
|
||||
{
|
||||
CreateMap<LibraryDto, Library>();
|
||||
|
||||
CreateMap<Volume, VolumeDto>();
|
||||
|
||||
CreateMap<MangaFile, MangaFileDto>();
|
||||
|
||||
CreateMap<Chapter, ChapterDto>()
|
||||
.ForMember(dest => dest.Writers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Writer)))
|
||||
.ForMember(dest => dest.CoverArtist,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.CoverArtist)))
|
||||
.ForMember(dest => dest.Colorist,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Colorist)))
|
||||
.ForMember(dest => dest.Inker,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Inker)))
|
||||
.ForMember(dest => dest.Letterer,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Letterer)))
|
||||
.ForMember(dest => dest.Penciller,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Penciller)))
|
||||
.ForMember(dest => dest.Publisher,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Publisher)))
|
||||
.ForMember(dest => dest.Editor,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Editor)))
|
||||
.ForMember(dest => dest.Translators,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Translator)));
|
||||
|
||||
CreateMap<Chapter, ChapterDto>();
|
||||
CreateMap<Series, SeriesDto>();
|
||||
CreateMap<CollectionTag, CollectionTagDto>();
|
||||
CreateMap<Person, PersonDto>();
|
||||
CreateMap<Genre, GenreTagDto>();
|
||||
CreateMap<Tag, TagDto>();
|
||||
CreateMap<AgeRating, AgeRatingDto>();
|
||||
CreateMap<PublicationStatus, PublicationStatusDto>();
|
||||
|
||||
CreateMap<SeriesMetadata, SeriesMetadataDto>()
|
||||
.ForMember(dest => dest.Writers,
|
||||
|
@ -93,29 +64,35 @@ namespace API.Helpers
|
|||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Editor)));
|
||||
|
||||
CreateMap<ChapterMetadata, ChapterMetadataDto>()
|
||||
CreateMap<Chapter, ChapterMetadataDto>()
|
||||
.ForMember(dest => dest.Writers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Writer)))
|
||||
.ForMember(dest => dest.CoverArtist,
|
||||
.ForMember(dest => dest.CoverArtists,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.CoverArtist)))
|
||||
.ForMember(dest => dest.Colorist,
|
||||
.ForMember(dest => dest.Colorists,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Colorist)))
|
||||
.ForMember(dest => dest.Inker,
|
||||
.ForMember(dest => dest.Inkers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Inker)))
|
||||
.ForMember(dest => dest.Letterer,
|
||||
.ForMember(dest => dest.Letterers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Letterer)))
|
||||
.ForMember(dest => dest.Penciller,
|
||||
.ForMember(dest => dest.Pencillers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Penciller)))
|
||||
.ForMember(dest => dest.Publisher,
|
||||
.ForMember(dest => dest.Publishers,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Publisher)))
|
||||
.ForMember(dest => dest.Editor,
|
||||
.ForMember(dest => dest.Translators,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Translator)))
|
||||
.ForMember(dest => dest.Characters,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Character)))
|
||||
.ForMember(dest => dest.Editors,
|
||||
opt =>
|
||||
opt.MapFrom(src => src.People.Where(p => p.Role == PersonRole.Editor)));
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace API.SignalR
|
|||
/// </summary>
|
||||
public const string UpdateAvailable = "UpdateAvailable";
|
||||
/// <summary>
|
||||
/// Used to tell when a scan series completes
|
||||
/// Used to tell when a scan series completes. This also informs UI to update series metadata
|
||||
/// </summary>
|
||||
public const string ScanSeries = "ScanSeries";
|
||||
/// <summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue