mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Metadata Optimizations (#910)
* Added a tooltip to inform user that format and collection filter selections do not only show for the selected library. * Refactored a lot of code around when we update chapter cover images. Applied an optimization for when we re-calculate volume/series covers, such that it only occurs when the first chapter's image updates. * Updated code to ensure only lastmodified gets refreshed in metadata since it always follows a scan * Optimized how metadata is populated on the series. Instead of re-reading the comicInfos, instead I read the data from the underlying chapter entities. This reduces N additional reads AND enables the ability in the future to show/edit chapter level metadata. * Spelling mistake * Fixed a concurency issue by not selecting Genres from DB. Added a test for long paths. * Fixed a bug in filter where collection tag wasn't populating on load * Cleaned up the logic for changelog to better compare against the installed verison. For nightly users, show the last stable as installed. * Removed some demo code * SplitQuery to allow loading tags much faster for series metadata load.
This commit is contained in:
parent
c215d5b7a8
commit
0be0e294aa
@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Abstractions;
|
||||||
using System.IO.Abstractions.TestingHelpers;
|
using System.IO.Abstractions.TestingHelpers;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace API.Tests.Helpers;
|
namespace API.Tests.Helpers;
|
||||||
@ -73,6 +76,19 @@ public class CacheHelperTests
|
|||||||
false, false));
|
false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ShouldUpdateCoverImage_ShouldNotUpdateOnSecondRunWithCoverImageSetNotLocked_2()
|
||||||
|
{
|
||||||
|
// Represents first run
|
||||||
|
var file = new MangaFile()
|
||||||
|
{
|
||||||
|
FilePath = TestCoverArchive,
|
||||||
|
LastModified = DateTime.Now
|
||||||
|
};
|
||||||
|
Assert.False(_cacheHelper.ShouldUpdateCoverImage(_testCoverPath, file, DateTime.Now,
|
||||||
|
false, false));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ShouldUpdateCoverImage_ShouldNotUpdateOnSecondRunWithCoverImageSetLocked()
|
public void ShouldUpdateCoverImage_ShouldNotUpdateOnSecondRunWithCoverImageSetLocked()
|
||||||
{
|
{
|
||||||
|
@ -79,7 +79,7 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("src: local(\"/fonts/OpenSans-Regular-webfont.woff2\")", new [] {"src: local(\"", "/fonts/OpenSans-Regular-webfont.woff2", "\")"})]
|
[InlineData("src: local(\"/fonts/OpenSans-Regular-webfont.woff2\")", new [] {"src: local(\"", "/fonts/OpenSans-Regular-webfont.woff2", "\")"})]
|
||||||
public void FontCssCorrectlySeparates(string input, string[] expected)
|
public void FontCssCorrectlySeparates(string input, string[] expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, FontSrcUrlRegex.Match(input).Groups.Values.Select(g => g.Value).Where((s, i) => i > 0).ToArray());
|
Assert.Equal(expected, FontSrcUrlRegex.Match(input).Groups.Values.Select(g => g.Value).Where((_, i) => i > 0).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,39 @@ namespace API.Tests.Services
|
|||||||
Assert.Equal(28, files.Count);
|
Assert.Equal(28, files.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TraverseTreeParallelForEach_LongDirectory_ShouldBe1()
|
||||||
|
{
|
||||||
|
var fileSystem = new MockFileSystem();
|
||||||
|
// Create a super long path
|
||||||
|
var testDirectory = "/manga/";
|
||||||
|
for (var i = 0; i < 200; i++)
|
||||||
|
{
|
||||||
|
testDirectory = fileSystem.FileSystem.Path.Join(testDirectory, "supercalifragilisticexpialidocious");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fileSystem.AddFile(fileSystem.FileSystem.Path.Join(testDirectory, "file_29.jpg"), new MockFileData(""));
|
||||||
|
|
||||||
|
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fileSystem);
|
||||||
|
var files = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileCount = ds.TraverseTreeParallelForEach("/manga/", s => files.Add(s),
|
||||||
|
API.Parser.Parser.ImageFileExtensions, _logger);
|
||||||
|
Assert.Equal(1, fileCount);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Assert.False(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Assert.Equal(1, files.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TraverseTreeParallelForEach_DontCountExcludedDirectories_ShouldBe28()
|
public void TraverseTreeParallelForEach_DontCountExcludedDirectories_ShouldBe28()
|
||||||
{
|
{
|
||||||
|
@ -23,7 +23,7 @@ namespace API.Comparators
|
|||||||
_isAscending = inAscendingOrder;
|
_isAscending = inAscendingOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
int IComparer<string>.Compare(string x, string y)
|
int IComparer<string>.Compare(string? x, string? y)
|
||||||
{
|
{
|
||||||
if (x == y) return 0;
|
if (x == y) return 0;
|
||||||
|
|
||||||
|
@ -62,6 +62,14 @@ namespace API.DTOs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Metadata field</remarks>
|
/// <remarks>Metadata field</remarks>
|
||||||
public string TitleName { get; set; }
|
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; }
|
||||||
public ICollection<PersonDto> Writers { get; set; } = new List<PersonDto>();
|
public ICollection<PersonDto> Writers { get; set; } = new List<PersonDto>();
|
||||||
public ICollection<PersonDto> Penciller { 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> Inker { get; set; } = new List<PersonDto>();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using API.Data.Metadata;
|
using API.Data.Metadata;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
@ -118,7 +119,7 @@ namespace API.Data
|
|||||||
FilePath = filePath,
|
FilePath = filePath,
|
||||||
Format = format,
|
Format = format,
|
||||||
Pages = pages,
|
Pages = pages,
|
||||||
LastModified = DateTime.Now //File.GetLastWriteTime(filePath)
|
LastModified = File.GetLastWriteTime(filePath) // NOTE: Changed this from DateTime.Now
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,5 +85,6 @@ namespace API.Data.Metadata
|
|||||||
.SingleOrDefault(t => t.ToDescription().ToUpperInvariant().Equals(value.ToUpperInvariant()), Entities.Enums.AgeRating.Unknown);
|
.SingleOrDefault(t => t.ToDescription().ToUpperInvariant().Equals(value.ToUpperInvariant()), Entities.Enums.AgeRating.Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1339
API/Data/Migrations/20220107232822_ChapterMetadataOptimization.Designer.cs
generated
Normal file
1339
API/Data/Migrations/20220107232822_ChapterMetadataOptimization.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,108 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class ChapterMetadataOptimization : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Chapter_Genre_GenreId",
|
||||||
|
table: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Chapter_GenreId",
|
||||||
|
table: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "GenreId",
|
||||||
|
table: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "FullscreenMode",
|
||||||
|
table: "AppUserPreferences");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Language",
|
||||||
|
table: "Chapter",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Summary",
|
||||||
|
table: "Chapter",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ChapterGenre",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ChaptersId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
GenresId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ChapterGenre", x => new { x.ChaptersId, x.GenresId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ChapterGenre_Chapter_ChaptersId",
|
||||||
|
column: x => x.ChaptersId,
|
||||||
|
principalTable: "Chapter",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ChapterGenre_Genre_GenresId",
|
||||||
|
column: x => x.GenresId,
|
||||||
|
principalTable: "Genre",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ChapterGenre_GenresId",
|
||||||
|
table: "ChapterGenre",
|
||||||
|
column: "GenresId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ChapterGenre");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Language",
|
||||||
|
table: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Summary",
|
||||||
|
table: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "GenreId",
|
||||||
|
table: "Chapter",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "FullscreenMode",
|
||||||
|
table: "AppUserPreferences",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Chapter_GenreId",
|
||||||
|
table: "Chapter",
|
||||||
|
column: "GenreId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Chapter_Genre_GenreId",
|
||||||
|
table: "Chapter",
|
||||||
|
column: "GenreId",
|
||||||
|
principalTable: "Genre",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -186,9 +186,6 @@ namespace API.Data.Migrations
|
|||||||
b.Property<bool>("BookReaderTapToPaginate")
|
b.Property<bool>("BookReaderTapToPaginate")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("FullscreenMode")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int>("PageSplitOption")
|
b.Property<int>("PageSplitOption")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -311,12 +308,12 @@ namespace API.Data.Migrations
|
|||||||
b.Property<DateTime>("Created")
|
b.Property<DateTime>("Created")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("GenreId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("IsSpecial")
|
b.Property<bool>("IsSpecial")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<DateTime>("LastModified")
|
b.Property<DateTime>("LastModified")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
@ -332,6 +329,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<DateTime>("ReleaseDate")
|
b.Property<DateTime>("ReleaseDate")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
@ -343,8 +343,6 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("GenreId");
|
|
||||||
|
|
||||||
b.HasIndex("VolumeId");
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
b.ToTable("Chapter");
|
b.ToTable("Chapter");
|
||||||
@ -749,6 +747,21 @@ namespace API.Data.Migrations
|
|||||||
b.ToTable("AppUserLibrary");
|
b.ToTable("AppUserLibrary");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ChapterGenre", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ChaptersId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("GenresId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("ChaptersId", "GenresId");
|
||||||
|
|
||||||
|
b.HasIndex("GenresId");
|
||||||
|
|
||||||
|
b.ToTable("ChapterGenre");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ChapterPerson", b =>
|
modelBuilder.Entity("ChapterPerson", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("ChapterMetadatasId")
|
b.Property<int>("ChapterMetadatasId")
|
||||||
@ -1000,10 +1013,6 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Genre", null)
|
|
||||||
.WithMany("Chapters")
|
|
||||||
.HasForeignKey("GenreId");
|
|
||||||
|
|
||||||
b.HasOne("API.Entities.Volume", "Volume")
|
b.HasOne("API.Entities.Volume", "Volume")
|
||||||
.WithMany("Chapters")
|
.WithMany("Chapters")
|
||||||
.HasForeignKey("VolumeId")
|
.HasForeignKey("VolumeId")
|
||||||
@ -1129,6 +1138,21 @@ namespace API.Data.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ChapterGenre", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.Chapter", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ChaptersId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.Genre", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("GenresId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ChapterPerson", b =>
|
modelBuilder.Entity("ChapterPerson", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Chapter", null)
|
b.HasOne("API.Entities.Chapter", null)
|
||||||
@ -1280,11 +1304,6 @@ namespace API.Data.Migrations
|
|||||||
b.Navigation("Files");
|
b.Navigation("Files");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Genre", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Chapters");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Library", b =>
|
modelBuilder.Entity("API.Entities.Library", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Folders");
|
b.Navigation("Folders");
|
||||||
|
@ -156,6 +156,11 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
|
|
||||||
.Include(s => s.Volumes)
|
.Include(s => s.Volumes)
|
||||||
.ThenInclude(v => v.Chapters)
|
.ThenInclude(v => v.Chapters)
|
||||||
|
.ThenInclude(c => c.Genres)
|
||||||
|
|
||||||
|
.Include(s => s.Volumes)
|
||||||
|
.ThenInclude(v => v.Chapters)
|
||||||
|
.ThenInclude(c => c.Tags)
|
||||||
|
|
||||||
.Include(s => s.Volumes)
|
.Include(s => s.Volumes)
|
||||||
.ThenInclude(v => v.Chapters)
|
.ThenInclude(v => v.Chapters)
|
||||||
@ -186,7 +191,12 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
|
|
||||||
.Include(s => s.Volumes)
|
.Include(s => s.Volumes)
|
||||||
.ThenInclude(v => v.Chapters)
|
.ThenInclude(v => v.Chapters)
|
||||||
.ThenInclude(cm => cm.Tags)
|
.ThenInclude(c => c.Tags)
|
||||||
|
|
||||||
|
.Include(s => s.Volumes)
|
||||||
|
.ThenInclude(v => v.Chapters)
|
||||||
|
.ThenInclude(c => c.Genres)
|
||||||
|
|
||||||
|
|
||||||
.Include(s => s.Metadata)
|
.Include(s => s.Metadata)
|
||||||
.ThenInclude(m => m.Tags)
|
.ThenInclude(m => m.Tags)
|
||||||
@ -590,6 +600,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.Include(m => m.People)
|
.Include(m => m.People)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ProjectTo<SeriesMetadataDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<SeriesMetadataDto>(_mapper.ConfigurationProvider)
|
||||||
|
.AsSplitQuery()
|
||||||
.SingleOrDefaultAsync();
|
.SingleOrDefaultAsync();
|
||||||
|
|
||||||
if (metadataDto != null)
|
if (metadataDto != null)
|
||||||
|
@ -12,10 +12,11 @@ namespace API.Entities
|
|||||||
public int VolumeId { get; set; }
|
public int VolumeId { get; set; }
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
public int ChapterId { get; set; }
|
public int ChapterId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Filename in the Bookmark Directory
|
/// Filename in the Bookmark Directory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
|
@ -56,12 +56,24 @@ namespace API.Entities
|
|||||||
/// Date which chapter was released
|
/// Date which chapter was released
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime ReleaseDate { get; set; }
|
public DateTime ReleaseDate { 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>
|
/// <summary>
|
||||||
/// All people attached at a Chapter level. Usually Comics will have different people per issue.
|
/// All people attached at a Chapter level. Usually Comics will have different people per issue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<Person> People { get; set; } = new List<Person>();
|
public ICollection<Person> People { get; set; } = new List<Person>();
|
||||||
|
/// <summary>
|
||||||
|
/// Genres for the Chapter
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<Genre> Genres { get; set; } = new List<Genre>();
|
||||||
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ namespace API.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last time underlying file was modified
|
/// Last time underlying file was modified
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>This gets updated anytime the file is scanned</remarks>
|
||||||
public DateTime LastModified { get; set; }
|
public DateTime LastModified { get; set; }
|
||||||
|
|
||||||
|
|
||||||
@ -32,11 +33,10 @@ namespace API.Entities
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the Last Modified time of the underlying file
|
/// Updates the Last Modified time of the underlying file to the LastWriteTime
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateLastModified()
|
public void UpdateLastModified()
|
||||||
{
|
{
|
||||||
// Should this be DateTime.Now ?
|
|
||||||
LastModified = File.GetLastWriteTime(FilePath);
|
LastModified = File.GetLastWriteTime(FilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ public class CacheHelper : ICacheHelper
|
|||||||
if (isCoverLocked && fileExists) return false;
|
if (isCoverLocked && fileExists) return false;
|
||||||
if (forceUpdate) return true;
|
if (forceUpdate) return true;
|
||||||
if (firstFile == null) return true;
|
if (firstFile == null) return true;
|
||||||
return (_fileService.HasFileBeenModifiedSince(coverPath, chapterCreated)) || !fileExists;
|
return (_fileService.HasFileBeenModifiedSince(firstFile.FilePath, firstFile.LastModified)) || !fileExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -28,7 +28,7 @@ public interface IMetadataService
|
|||||||
/// <param name="forceUpdate"></param>
|
/// <param name="forceUpdate"></param>
|
||||||
Task RefreshMetadata(int libraryId, bool forceUpdate = false);
|
Task RefreshMetadata(int libraryId, bool forceUpdate = false);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a forced refresh of metatdata just for a series and it's nested entities
|
/// Performs a forced refresh of metadata just for a series and it's nested entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryId"></param>
|
/// <param name="libraryId"></param>
|
||||||
/// <param name="seriesId"></param>
|
/// <param name="seriesId"></param>
|
||||||
@ -76,18 +76,17 @@ public class MetadataService : IMetadataService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateChapterMetadata(Chapter chapter, ICollection<Person> allPeople, ICollection<Tag> allTags, bool forceUpdate)
|
private void UpdateChapterMetadata(Chapter chapter, ICollection<Person> allPeople, ICollection<Tag> allTags, ICollection<Genre> allGenres, bool forceUpdate)
|
||||||
{
|
{
|
||||||
var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||||
if (firstFile == null || _cacheHelper.HasFileNotChangedSinceCreationOrLastScan(chapter, forceUpdate, firstFile)) return;
|
if (firstFile == null || _cacheHelper.HasFileNotChangedSinceCreationOrLastScan(chapter, forceUpdate, firstFile)) return;
|
||||||
|
|
||||||
UpdateChapterFromComicInfo(chapter, allPeople, allTags, firstFile);
|
UpdateChapterFromComicInfo(chapter, allPeople, allTags, allGenres, firstFile);
|
||||||
firstFile.UpdateLastModified();
|
firstFile.UpdateLastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateChapterFromComicInfo(Chapter chapter, ICollection<Person> allPeople, ICollection<Tag> allTags, MangaFile firstFile)
|
private void UpdateChapterFromComicInfo(Chapter chapter, ICollection<Person> allPeople, ICollection<Tag> allTags, ICollection<Genre> allGenres, MangaFile firstFile)
|
||||||
{
|
{
|
||||||
// TODO: Think about letting the higher level loop have access for series to avoid duplicate IO operations
|
|
||||||
var comicInfo = _readingItemService.GetComicInfo(firstFile.FilePath, firstFile.Format);
|
var comicInfo = _readingItemService.GetComicInfo(firstFile.FilePath, firstFile.Format);
|
||||||
if (comicInfo == null) return;
|
if (comicInfo == null) return;
|
||||||
|
|
||||||
@ -196,6 +195,14 @@ public class MetadataService : IMetadataService
|
|||||||
PersonHelper.UpdatePeople(allPeople, people, PersonRole.Publisher,
|
PersonHelper.UpdatePeople(allPeople, people, PersonRole.Publisher,
|
||||||
person => PersonHelper.AddPersonIfNotExists(chapter.People, person));
|
person => PersonHelper.AddPersonIfNotExists(chapter.People, person));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(comicInfo.Genre))
|
||||||
|
{
|
||||||
|
var genres = comicInfo.Genre.Split(",");
|
||||||
|
GenreHelper.KeepOnlySameGenreBetweenLists(chapter.Genres, genres.Select(g => DbFactory.Genre(g, false)).ToList());
|
||||||
|
GenreHelper.UpdateGenre(allGenres, genres, false,
|
||||||
|
genre => chapter.Genres.Add(genre));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -253,17 +260,44 @@ public class MetadataService : IMetadataService
|
|||||||
series.CoverImage = firstCover?.CoverImage ?? coverImage;
|
series.CoverImage = firstCover?.CoverImage ?? coverImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSeriesMetadata(Series series, ICollection<Person> allPeople, ICollection<Genre> allGenres, ICollection<Tag> allTags, bool forceUpdate)
|
private static void UpdateSeriesMetadata(Series series, ICollection<Person> allPeople, ICollection<Genre> allGenres, ICollection<Tag> allTags, bool forceUpdate)
|
||||||
{
|
{
|
||||||
var isBook = series.Library.Type == LibraryType.Book;
|
var isBook = series.Library.Type == LibraryType.Book;
|
||||||
var firstVolume = series.Volumes.OrderBy(c => c.Number, new ChapterSortComparer()).FirstWithChapters(isBook);
|
var firstVolume = series.Volumes.OrderBy(c => c.Number, new ChapterSortComparer()).FirstWithChapters(isBook);
|
||||||
var firstChapter = firstVolume?.Chapters.GetFirstChapterWithFiles();
|
var firstChapter = firstVolume?.Chapters.GetFirstChapterWithFiles();
|
||||||
|
|
||||||
var firstFile = firstChapter?.Files.FirstOrDefault();
|
var firstFile = firstChapter?.Files.FirstOrDefault();
|
||||||
if (firstFile == null || _cacheHelper.HasFileNotChangedSinceCreationOrLastScan(firstChapter, forceUpdate, firstFile)) return;
|
if (firstFile == null) return;
|
||||||
if (Parser.Parser.IsPdf(firstFile.FilePath)) return;
|
if (Parser.Parser.IsPdf(firstFile.FilePath)) return;
|
||||||
|
|
||||||
foreach (var chapter in series.Volumes.SelectMany(volume => volume.Chapters))
|
var chapters = series.Volumes.SelectMany(volume => volume.Chapters).ToList();
|
||||||
|
|
||||||
|
// Update Metadata based on Chapter metadata
|
||||||
|
series.Metadata.ReleaseYear = chapters.Min(c => c.ReleaseDate.Year);
|
||||||
|
|
||||||
|
if (series.Metadata.ReleaseYear < 1000)
|
||||||
|
{
|
||||||
|
// Not a valid year, default to 0
|
||||||
|
series.Metadata.ReleaseYear = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the AgeRating as highest in all the comicInfos
|
||||||
|
series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
|
||||||
|
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(firstChapter.Summary))
|
||||||
|
{
|
||||||
|
series.Metadata.Summary = firstChapter.Summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(firstChapter.Language))
|
||||||
|
{
|
||||||
|
series.Metadata.Language = firstChapter.Language;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Handle People
|
||||||
|
foreach (var chapter in chapters)
|
||||||
{
|
{
|
||||||
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Writer).Select(p => p.Name), PersonRole.Writer,
|
PersonHelper.UpdatePeople(allPeople, chapter.People.Where(p => p.Role == PersonRole.Writer).Select(p => p.Name), PersonRole.Writer,
|
||||||
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
|
person => PersonHelper.AddPersonIfNotExists(series.Metadata.People, person));
|
||||||
@ -297,49 +331,14 @@ public class MetadataService : IMetadataService
|
|||||||
|
|
||||||
TagHelper.UpdateTag(allTags, chapter.Tags.Select(t => t.Title), false, (tag, added) =>
|
TagHelper.UpdateTag(allTags, chapter.Tags.Select(t => t.Title), false, (tag, added) =>
|
||||||
TagHelper.AddTagIfNotExists(series.Metadata.Tags, tag));
|
TagHelper.AddTagIfNotExists(series.Metadata.Tags, tag));
|
||||||
|
|
||||||
|
GenreHelper.UpdateGenre(allGenres, chapter.Genres.Select(t => t.Title), false, genre =>
|
||||||
|
GenreHelper.AddGenreIfNotExists(series.Metadata.Genres, genre));
|
||||||
}
|
}
|
||||||
|
|
||||||
var comicInfos = series.Volumes
|
var people = chapters.SelectMany(c => c.People).ToList();
|
||||||
.SelectMany(volume => volume.Chapters)
|
|
||||||
.OrderBy(c => double.Parse(c.Number), new ChapterSortComparer())
|
|
||||||
.SelectMany(c => c.Files)
|
|
||||||
.Select(file => _readingItemService.GetComicInfo(file.FilePath, file.Format))
|
|
||||||
.Where(ci => ci != null)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var comicInfo = comicInfos.FirstOrDefault();
|
|
||||||
if (!string.IsNullOrEmpty(comicInfo?.Summary))
|
|
||||||
{
|
|
||||||
series.Metadata.Summary = comicInfo.Summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(comicInfo?.LanguageISO))
|
|
||||||
{
|
|
||||||
series.Metadata.Language = comicInfo.LanguageISO;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the AgeRating as highest in all the comicInfos
|
|
||||||
series.Metadata.AgeRating = comicInfos.Max(i => ComicInfo.ConvertAgeRatingToEnum(comicInfo?.AgeRating));
|
|
||||||
series.Metadata.ReleaseYear = series.Volumes
|
|
||||||
.SelectMany(volume => volume.Chapters).Min(c => c.ReleaseDate.Year);
|
|
||||||
|
|
||||||
if (series.Metadata.ReleaseYear < 1000)
|
|
||||||
{
|
|
||||||
// Not a valid year, default to 0
|
|
||||||
series.Metadata.ReleaseYear = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var genres = comicInfos.SelectMany(i => i?.Genre.Split(",")).Distinct().ToList();
|
|
||||||
var tags = comicInfos.SelectMany(i => i?.Tags.Split(",")).Distinct().ToList();
|
|
||||||
var people = series.Volumes.SelectMany(volume => volume.Chapters).SelectMany(c => c.People).ToList();
|
|
||||||
|
|
||||||
|
|
||||||
PersonHelper.KeepOnlySamePeopleBetweenLists(series.Metadata.People,
|
PersonHelper.KeepOnlySamePeopleBetweenLists(series.Metadata.People,
|
||||||
people, person => series.Metadata.People.Remove(person));
|
people, person => series.Metadata.People.Remove(person));
|
||||||
|
|
||||||
GenreHelper.UpdateGenre(allGenres, genres, false, genre => GenreHelper.AddGenreIfNotExists(series.Metadata.Genres, genre));
|
|
||||||
GenreHelper.KeepOnlySameGenreBetweenLists(series.Metadata.Genres, genres.Select(g => DbFactory.Genre(g, false)).ToList(),
|
|
||||||
genre => series.Metadata.Genres.Remove(genre));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -352,20 +351,34 @@ public class MetadataService : IMetadataService
|
|||||||
_logger.LogDebug("[MetadataService] Processing series {SeriesName}", series.OriginalName);
|
_logger.LogDebug("[MetadataService] Processing series {SeriesName}", series.OriginalName);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var volumeUpdated = false;
|
var volumeIndex = 0;
|
||||||
|
var firstVolumeUpdated = false;
|
||||||
foreach (var volume in series.Volumes)
|
foreach (var volume in series.Volumes)
|
||||||
{
|
{
|
||||||
var chapterUpdated = false;
|
var firstChapterUpdated = false; // This only needs to be FirstChapter updated
|
||||||
|
var index = 0;
|
||||||
foreach (var chapter in volume.Chapters)
|
foreach (var chapter in volume.Chapters)
|
||||||
{
|
{
|
||||||
chapterUpdated = UpdateChapterCoverImage(chapter, forceUpdate);
|
var chapterUpdated = UpdateChapterCoverImage(chapter, forceUpdate);
|
||||||
UpdateChapterMetadata(chapter, allPeople, allTags, forceUpdate || chapterUpdated);
|
// If cover was update, either the file has changed or first scan and we should force a metadata update
|
||||||
|
UpdateChapterMetadata(chapter, allPeople, allTags, allGenres, forceUpdate || chapterUpdated);
|
||||||
|
if (index == 0 && chapterUpdated)
|
||||||
|
{
|
||||||
|
firstChapterUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeUpdated = UpdateVolumeCoverImage(volume, chapterUpdated || forceUpdate);
|
var volumeUpdated = UpdateVolumeCoverImage(volume, firstChapterUpdated || forceUpdate);
|
||||||
|
if (volumeIndex == 0 && volumeUpdated)
|
||||||
|
{
|
||||||
|
firstVolumeUpdated = true;
|
||||||
|
}
|
||||||
|
volumeIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateSeriesCoverImage(series, volumeUpdated || forceUpdate);
|
UpdateSeriesCoverImage(series, firstVolumeUpdated || forceUpdate);
|
||||||
UpdateSeriesMetadata(series, allPeople, allGenres, allTags, forceUpdate);
|
UpdateSeriesMetadata(series, allPeople, allGenres, allTags, forceUpdate);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -641,6 +641,7 @@ public class ScannerService : IScannerService
|
|||||||
existingFile.Format = info.Format;
|
existingFile.Format = info.Format;
|
||||||
if (!_fileService.HasFileBeenModifiedSince(existingFile.FilePath, existingFile.LastModified) && existingFile.Pages != 0) return;
|
if (!_fileService.HasFileBeenModifiedSince(existingFile.FilePath, existingFile.LastModified) && existingFile.Pages != 0) return;
|
||||||
existingFile.Pages = _readingItemService.GetNumberOfPages(info.FullFilePath, info.Format);
|
existingFile.Pages = _readingItemService.GetNumberOfPages(info.FullFilePath, info.Format);
|
||||||
|
//existingFile.UpdateLastModified(); // We skip updating DB here so that metadata refresh can do it
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
<div class="card w-100 mb-2" style="width: 18rem;">
|
<div class="card w-100 mb-2" style="width: 18rem;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">{{update.updateTitle}}
|
<h4 class="card-title">{{update.updateTitle}}
|
||||||
<span class="badge badge-secondary" *ngIf="update.updateVersion === update.currentVersion">Installed</span>
|
<span class="badge badge-secondary" *ngIf="update.updateVersion === installedVersion">Installed</span>
|
||||||
<span class="badge badge-secondary" *ngIf="update.updateVersion > update.currentVersion">Available</span>
|
<span class="badge badge-secondary" *ngIf="update.updateVersion > installedVersion">Available</span>
|
||||||
</h4>
|
</h4>
|
||||||
<h6 class="card-subtitle mb-2 text-muted">Published: {{update.publishDate | date: 'short'}}</h6>
|
<h6 class="card-subtitle mb-2 text-muted">Published: {{update.publishDate | date: 'short'}}</h6>
|
||||||
|
|
||||||
<pre class="card-text update-body" [innerHtml]="update.updateBody | safeHtml"></pre>
|
<pre class="card-text update-body" [innerHtml]="update.updateBody | safeHtml"></pre>
|
||||||
<a *ngIf="!update.isDocker" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-right" target="_blank">Download</a>
|
<a *ngIf="!update.isDocker" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-right" target="_blank">Download</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,13 +11,26 @@ export class ChangelogComponent implements OnInit {
|
|||||||
|
|
||||||
updates: Array<UpdateVersionEvent> = [];
|
updates: Array<UpdateVersionEvent> = [];
|
||||||
isLoading: boolean = true;
|
isLoading: boolean = true;
|
||||||
|
installedVersion: string = '';
|
||||||
|
|
||||||
constructor(private serverService: ServerService) { }
|
constructor(private serverService: ServerService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.serverService.getChangelog().subscribe(updates => {
|
|
||||||
this.updates = updates;
|
this.serverService.getServerInfo().subscribe(info => {
|
||||||
this.isLoading = false;
|
this.installedVersion = info.kavitaVersion;
|
||||||
|
this.serverService.getChangelog().subscribe(updates => {
|
||||||
|
this.updates = updates;
|
||||||
|
this.isLoading = false;
|
||||||
|
|
||||||
|
if (this.updates.filter(u => u.updateVersion === this.installedVersion).length === 0) {
|
||||||
|
// User is on a nightly version. Tell them the last stable is installed
|
||||||
|
this.installedVersion = this.updates[0].updateVersion;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #filterSection>
|
<ng-template #filterSection>
|
||||||
|
<ng-template #globalFilterTooltip>This is library agnostic</ng-template>
|
||||||
<div class="filter-section mx-auto pb-3">
|
<div class="filter-section mx-auto pb-3">
|
||||||
<div class="row justify-content-center no-gutters">
|
<div class="row justify-content-center no-gutters">
|
||||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.formatDisabled">
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.formatDisabled">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="format">Format</label>
|
<label for="format">Format</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||||
|
<span class="sr-only" id="filter-global-format-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||||
<app-typeahead (selectedData)="updateFormatFilters($event)" [settings]="formatSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateFormatFilters($event)" [settings]="formatSettings" [reset]="resetTypeaheads">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
@ -70,7 +72,8 @@
|
|||||||
|
|
||||||
<div class="col-md-2 mr-3" *ngIf="!filterSettings.collectionDisabled">
|
<div class="col-md-2 mr-3" *ngIf="!filterSettings.collectionDisabled">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="collections">Collections</label>
|
<label for="collections">Collections</label> <i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="globalFilterTooltip" role="button" tabindex="0"></i>
|
||||||
|
<span class="sr-only" id="filter-global-collections-help"><ng-container [ngTemplateOutlet]="globalFilterTooltip"></ng-container></span>
|
||||||
<app-typeahead (selectedData)="updateCollectionFilters($event)" [settings]="collectionSettings" [reset]="resetTypeaheads">
|
<app-typeahead (selectedData)="updateCollectionFilters($event)" [settings]="collectionSettings" [reset]="resetTypeaheads">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
{{item.title}}
|
{{item.title}}
|
||||||
|
@ -84,7 +84,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
|||||||
libraries: Array<FilterItem<Library>> = [];
|
libraries: Array<FilterItem<Library>> = [];
|
||||||
genres: Array<FilterItem<Genre>> = [];
|
genres: Array<FilterItem<Genre>> = [];
|
||||||
persons: Array<FilterItem<Person>> = [];
|
persons: Array<FilterItem<Person>> = [];
|
||||||
collectionTags: Array<FilterItem<CollectionTag>> = [];
|
//collectionTags: Array<FilterItem<CollectionTag>> = [];
|
||||||
|
|
||||||
readProgressGroup!: FormGroup;
|
readProgressGroup!: FormGroup;
|
||||||
sortGroup!: FormGroup;
|
sortGroup!: FormGroup;
|
||||||
@ -329,9 +329,11 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy {
|
|||||||
return options.filter(m => m.title.toLowerCase() === f);
|
return options.filter(m => m.title.toLowerCase() === f);
|
||||||
}
|
}
|
||||||
if (this.filterSettings.presetCollectionId > 0) {
|
if (this.filterSettings.presetCollectionId > 0) {
|
||||||
this.collectionSettings.savedData = this.collectionTags.filter(item => item.value.id === this.filterSettings.presetCollectionId);
|
this.collectionSettings.fetchFn('').subscribe(tags => {
|
||||||
this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.value.id);
|
this.collectionSettings.savedData = tags.filter(item => item.value.id === this.filterSettings.presetCollectionId);
|
||||||
this.resetTypeaheads.next(true);
|
this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.value.id);
|
||||||
|
this.resetTypeaheads.next(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user