Added a new library setting to disable metadata completely.
This commit is contained in:
parent
3a01e9af3a
commit
52f6e235d0
33 changed files with 4026 additions and 72 deletions
|
|
@ -36,7 +36,7 @@ public class ComicVineParserTests
|
|||
public void Parse_SeriesWithComicInfo()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", "C:/Comics/Birds of Prey (2002)/",
|
||||
RootDirectory, LibraryType.ComicVine, new ComicInfo()
|
||||
RootDirectory, LibraryType.ComicVine, true, new ComicInfo()
|
||||
{
|
||||
Series = "Birds of Prey",
|
||||
Volume = "2002"
|
||||
|
|
@ -54,7 +54,7 @@ public class ComicVineParserTests
|
|||
public void Parse_SeriesWithDirectoryNameAsSeriesYear()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", "C:/Comics/Birds of Prey (2002)/",
|
||||
RootDirectory, LibraryType.ComicVine, null);
|
||||
RootDirectory, LibraryType.ComicVine, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey (2002)", actual.Series);
|
||||
|
|
@ -69,7 +69,7 @@ public class ComicVineParserTests
|
|||
public void Parse_SeriesWithADirectoryNameAsSeriesYear()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/DC Comics/Birds of Prey (1999)/Birds of Prey 001 (1999).cbz", "C:/Comics/DC Comics/",
|
||||
RootDirectory, LibraryType.ComicVine, null);
|
||||
RootDirectory, LibraryType.ComicVine, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey (1999)", actual.Series);
|
||||
|
|
@ -84,7 +84,7 @@ public class ComicVineParserTests
|
|||
public void Parse_FallbackToDirectoryNameOnly()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/DC Comics/Blood Syndicate/Blood Syndicate 001 (1999).cbz", "C:/Comics/DC Comics/",
|
||||
RootDirectory, LibraryType.ComicVine, null);
|
||||
RootDirectory, LibraryType.ComicVine, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Blood Syndicate", actual.Series);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class DefaultParserTests
|
|||
[InlineData("C:/", "C:/Something Random/Mujaki no Rakuen SP01.cbz", "Something Random")]
|
||||
public void ParseFromFallbackFolders_FallbackShouldParseSeries(string rootDir, string inputPath, string expectedSeries)
|
||||
{
|
||||
var actual = _defaultParser.Parse(inputPath, rootDir, rootDir, LibraryType.Manga, null);
|
||||
var actual = _defaultParser.Parse(inputPath, rootDir, rootDir, LibraryType.Manga, true, null);
|
||||
if (actual == null)
|
||||
{
|
||||
Assert.NotNull(actual);
|
||||
|
|
@ -74,7 +74,7 @@ public class DefaultParserTests
|
|||
fs.AddFile(inputFile, new MockFileData(""));
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fs);
|
||||
var parser = new BasicParser(ds, new ImageParser(ds));
|
||||
var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, null);
|
||||
var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, true, null);
|
||||
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
|
||||
Assert.Equal(expectedParseInfo, actual.Series);
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ public class DefaultParserTests
|
|||
fs.AddFile(inputFile, new MockFileData(""));
|
||||
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fs);
|
||||
var parser = new BasicParser(ds, new ImageParser(ds));
|
||||
var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, null);
|
||||
var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, true, null);
|
||||
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
|
||||
Assert.Equal(expectedParseInfo, actual.Series);
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ public class DefaultParserTests
|
|||
foreach (var file in expected.Keys)
|
||||
{
|
||||
var expectedInfo = expected[file];
|
||||
var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Manga, null);
|
||||
var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Manga, true, null);
|
||||
if (expectedInfo == null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
|
|
@ -289,7 +289,7 @@ public class DefaultParserTests
|
|||
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
var actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Monster #8", "E:/Manga", LibraryType.Manga, null);
|
||||
var actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Monster #8", "E:/Manga", LibraryType.Manga, true, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
|
|
@ -315,7 +315,7 @@ public class DefaultParserTests
|
|||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga",LibraryType.Manga, null);
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga",LibraryType.Manga, true, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
|
|
@ -341,7 +341,7 @@ public class DefaultParserTests
|
|||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga", LibraryType.Manga, null);
|
||||
actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga", LibraryType.Manga, true, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
|
|
@ -383,7 +383,7 @@ public class DefaultParserTests
|
|||
FullFilePath = filepath
|
||||
};
|
||||
|
||||
var actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, null);
|
||||
var actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
|
|
@ -412,7 +412,7 @@ public class DefaultParserTests
|
|||
FullFilePath = filepath
|
||||
};
|
||||
|
||||
actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, null);
|
||||
actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, true, null);
|
||||
Assert.NotNull(actual);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expected.Format, actual.Format);
|
||||
|
|
@ -475,7 +475,7 @@ public class DefaultParserTests
|
|||
foreach (var file in expected.Keys)
|
||||
{
|
||||
var expectedInfo = expected[file];
|
||||
var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Comic, null);
|
||||
var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Comic, true, null);
|
||||
if (expectedInfo == null)
|
||||
{
|
||||
Assert.Null(actual);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class ImageParserTests
|
|||
public void Parse_SeriesWithDirectoryName()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey/Chapter 01/01.jpg", "C:/Comics/Birds of Prey/",
|
||||
RootDirectory, LibraryType.Image, null);
|
||||
RootDirectory, LibraryType.Image, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey", actual.Series);
|
||||
|
|
@ -48,7 +48,7 @@ public class ImageParserTests
|
|||
public void Parse_SeriesWithNoNestedChapter()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey/Chapter 01 page 01.jpg", "C:/Comics/",
|
||||
RootDirectory, LibraryType.Image, null);
|
||||
RootDirectory, LibraryType.Image, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey", actual.Series);
|
||||
|
|
@ -62,7 +62,7 @@ public class ImageParserTests
|
|||
public void Parse_SeriesWithLooseImages()
|
||||
{
|
||||
var actual = _parser.Parse("C:/Comics/Birds of Prey/page 01.jpg", "C:/Comics/",
|
||||
RootDirectory, LibraryType.Image, null);
|
||||
RootDirectory, LibraryType.Image, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("Birds of Prey", actual.Series);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public class PdfParserTests
|
|||
{
|
||||
var actual = _parser.Parse("C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/A Dictionary of Japanese Food - Ingredients and Culture.pdf",
|
||||
"C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/",
|
||||
RootDirectory, LibraryType.Book, null);
|
||||
RootDirectory, LibraryType.Book, true, null);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
Assert.Equal("A Dictionary of Japanese Food - Ingredients and Culture", actual.Series);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class ImageParsingTests
|
|||
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
|
||||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
var actual2 = _parser.Parse(filepath, @"E:\Manga\Monster #8", "E:/Manga", LibraryType.Image, null);
|
||||
var actual2 = _parser.Parse(filepath, @"E:\Manga\Monster #8", "E:/Manga", LibraryType.Image, true, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
|
|
@ -60,7 +60,7 @@ public class ImageParsingTests
|
|||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, null);
|
||||
actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, true, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
|
|
@ -86,7 +86,7 @@ public class ImageParsingTests
|
|||
FullFilePath = filepath, IsSpecial = false
|
||||
};
|
||||
|
||||
actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, null);
|
||||
actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, true, null);
|
||||
Assert.NotNull(actual2);
|
||||
_testOutputHelper.WriteLine($"Validating {filepath}");
|
||||
Assert.Equal(expectedInfo2.Format, actual2.Format);
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public class BookServiceTests
|
|||
var comicInfo = _bookService.GetComicInfo(filePath);
|
||||
Assert.NotNull(comicInfo);
|
||||
|
||||
var parserInfo = pdfParser.Parse(filePath, testDirectory, ds.GetParentDirectoryName(testDirectory), LibraryType.Book, comicInfo);
|
||||
var parserInfo = pdfParser.Parse(filePath, testDirectory, ds.GetParentDirectoryName(testDirectory), LibraryType.Book, true, comicInfo);
|
||||
Assert.NotNull(parserInfo);
|
||||
Assert.Equal(parserInfo.Title, comicInfo.Title);
|
||||
Assert.Equal(parserInfo.Series, comicInfo.Title);
|
||||
|
|
|
|||
|
|
@ -50,12 +50,12 @@ internal class MockReadingItemServiceForCacheService : IReadingItemService
|
|||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest
|
|||
|
||||
_externalMetadataService = new ExternalMetadataService(UnitOfWork, Substitute.For<ILogger<ExternalMetadataService>>(),
|
||||
Mapper, Substitute.For<ILicenseService>(), Substitute.For<IScrobblingService>(), Substitute.For<IEventHub>(),
|
||||
Substitute.For<ICoverDbService>());
|
||||
Substitute.For<ICoverDbService>(), Substitute.For<IKavitaPlusApiService>());
|
||||
}
|
||||
|
||||
#region Gloabl
|
||||
|
|
|
|||
|
|
@ -58,35 +58,35 @@ public class MockReadingItemService : IReadingItemService
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata)
|
||||
{
|
||||
if (_comicVineParser.IsApplicable(path, type))
|
||||
{
|
||||
return _comicVineParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _comicVineParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_imageParser.IsApplicable(path, type))
|
||||
{
|
||||
return _imageParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _imageParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_bookParser.IsApplicable(path, type))
|
||||
{
|
||||
return _bookParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _bookParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_pdfParser.IsApplicable(path, type))
|
||||
{
|
||||
return _pdfParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _pdfParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_basicParser.IsApplicable(path, type))
|
||||
{
|
||||
return _basicParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _basicParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata)
|
||||
{
|
||||
return Parse(path, rootPath, libraryRoot, type);
|
||||
return Parse(path, rootPath, libraryRoot, type, enableMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ public class ScannerServiceTests : AbstractDbTest
|
|||
var infos = new Dictionary<string, ComicInfo>();
|
||||
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
|
||||
|
||||
library.LibraryExcludePatterns = [new LibraryExcludePattern() {Pattern = "**/Extra/*"}];
|
||||
library.LibraryExcludePatterns = [new LibraryExcludePattern() { Pattern = "**/Extra/*" }];
|
||||
UnitOfWork.LibraryRepository.Update(library);
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
|
|
@ -507,7 +507,7 @@ public class ScannerServiceTests : AbstractDbTest
|
|||
var infos = new Dictionary<string, ComicInfo>();
|
||||
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
|
||||
|
||||
library.LibraryExcludePatterns = [new LibraryExcludePattern() {Pattern = "**\\Extra\\*"}];
|
||||
library.LibraryExcludePatterns = [new LibraryExcludePattern() { Pattern = "**\\Extra\\*" }];
|
||||
UnitOfWork.LibraryRepository.Update(library);
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
|
|
@ -938,4 +938,37 @@ public class ScannerServiceTests : AbstractDbTest
|
|||
Assert.True(sortedChapters[1].SortOrder.Is(4f));
|
||||
Assert.True(sortedChapters[2].SortOrder.Is(5f));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ScanLibrary_MetadataEnabled_NoOverrides()
|
||||
{
|
||||
const string testcase = "Series with Localized 2 - Manga.json";
|
||||
|
||||
// Get the first file and generate a ComicInfo
|
||||
var infos = new Dictionary<string, ComicInfo>();
|
||||
infos.Add("Immoral Guild v01.cbz", new ComicInfo()
|
||||
{
|
||||
Series = "Immoral Guild",
|
||||
LocalizedSeries = "Futoku no Guild" // Filename has a capital N and localizedSeries has lowercase
|
||||
});
|
||||
|
||||
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
|
||||
|
||||
// Disable metadata
|
||||
library.EnableMetadata = false;
|
||||
UnitOfWork.LibraryRepository.Update(library);
|
||||
await UnitOfWork.CommitAsync();
|
||||
|
||||
var scanner = _scannerHelper.CreateServices();
|
||||
await scanner.ScanLibrary(library.Id);
|
||||
var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
|
||||
|
||||
// Validate that there are 2 series
|
||||
Assert.NotNull(postLib);
|
||||
Assert.Equal(2, postLib.Series.Count);
|
||||
|
||||
Assert.Contains(postLib.Series, x => x.Name == "Immoral Guild");
|
||||
Assert.Contains(postLib.Series, x => x.Name == "Futoku No Guild");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -623,6 +623,7 @@ public class LibraryController : BaseApiController
|
|||
library.ManageReadingLists = dto.ManageReadingLists;
|
||||
library.AllowScrobbling = dto.AllowScrobbling;
|
||||
library.AllowMetadataMatching = dto.AllowMetadataMatching;
|
||||
library.EnableMetadata = dto.EnableMetadata;
|
||||
library.LibraryFileTypes = dto.FileGroupTypes
|
||||
.Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id})
|
||||
.Distinct()
|
||||
|
|
|
|||
|
|
@ -65,5 +65,5 @@ public sealed record LibraryDto
|
|||
/// </summary>
|
||||
/// <remarks>This does not exclude the library from being linked to wrt Series Relationships</remarks>
|
||||
/// <remarks>Requires a valid LicenseKey</remarks>
|
||||
public bool AllowMetadataMatching { get; set; } = true;
|
||||
public bool EnableMetadata { get; set; } = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public sealed record UpdateLibraryDto
|
|||
public bool AllowScrobbling { get; init; }
|
||||
[Required]
|
||||
public bool AllowMetadataMatching { get; init; }
|
||||
[Required]
|
||||
public bool EnableMetadata { get; init; }
|
||||
/// <summary>
|
||||
/// What types of files to allow the scanner to pickup
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
|||
builder.Entity<Library>()
|
||||
.Property(b => b.AllowMetadataMatching)
|
||||
.HasDefaultValue(true);
|
||||
builder.Entity<Library>()
|
||||
.Property(b => b.EnableMetadata)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
builder.Entity<Chapter>()
|
||||
.Property(b => b.WebLinks)
|
||||
|
|
|
|||
3709
API/Data/Migrations/20250620215058_EnableMetadataLibrary.Designer.cs
generated
Normal file
3709
API/Data/Migrations/20250620215058_EnableMetadataLibrary.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
29
API/Data/Migrations/20250620215058_EnableMetadataLibrary.cs
Normal file
29
API/Data/Migrations/20250620215058_EnableMetadataLibrary.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class EnableMetadataLibrary : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "EnableMetadata",
|
||||
table: "Library",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EnableMetadata",
|
||||
table: "Library");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ namespace API.Data.Migrations
|
|||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.4");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppRole", b =>
|
||||
{
|
||||
|
|
@ -1296,6 +1296,11 @@ namespace API.Data.Migrations
|
|||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EnableMetadata")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("FolderWatching")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ public class Library : IEntityDate, IHasCoverImage
|
|||
/// <remarks>This does not exclude the library from being linked to wrt Series Relationships</remarks>
|
||||
/// <remarks>Requires a valid LicenseKey</remarks>
|
||||
public bool AllowMetadataMatching { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Should Kavita read metadata files from the library
|
||||
/// </summary>
|
||||
public bool EnableMetadata { get; set; } = true;
|
||||
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
|
|
|
|||
|
|
@ -110,6 +110,12 @@ public class LibraryBuilder : IEntityBuilder<Library>
|
|||
return this;
|
||||
}
|
||||
|
||||
public LibraryBuilder WithEnableMetadata(bool enable)
|
||||
{
|
||||
_library.EnableMetadata = enable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LibraryBuilder WithAllowScrobbling(bool allowScrobbling)
|
||||
{
|
||||
_library.AllowScrobbling = allowScrobbling;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public interface IReadingItemService
|
|||
int GetNumberOfPages(string filePath, MangaFormat format);
|
||||
string GetCoverImage(string filePath, string fileName, MangaFormat format, EncodeFormat encodeFormat, CoverImageSize size = CoverImageSize.Default);
|
||||
void Extract(string fileFilePath, string targetDirectory, MangaFormat format, int imageCount = 1);
|
||||
ParserInfo? ParseFile(string path, string rootPath, string libraryRoot, LibraryType type);
|
||||
ParserInfo? ParseFile(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata);
|
||||
}
|
||||
|
||||
public class ReadingItemService : IReadingItemService
|
||||
|
|
@ -71,11 +71,12 @@ public class ReadingItemService : IReadingItemService
|
|||
/// <param name="path">Path of a file</param>
|
||||
/// <param name="rootPath"></param>
|
||||
/// <param name="type">Library type to determine parsing to perform</param>
|
||||
public ParserInfo? ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
/// <param name="enableMetadata">Enable Metadata parsing overriding filename parsing</param>
|
||||
public ParserInfo? ParseFile(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = Parse(path, rootPath, libraryRoot, type);
|
||||
var info = Parse(path, rootPath, libraryRoot, type, enableMetadata);
|
||||
if (info == null)
|
||||
{
|
||||
_logger.LogError("Unable to parse any meaningful information out of file {FilePath}", path);
|
||||
|
|
@ -174,28 +175,29 @@ public class ReadingItemService : IReadingItemService
|
|||
/// <param name="path"></param>
|
||||
/// <param name="rootPath"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="enableMetadata"></param>
|
||||
/// <returns></returns>
|
||||
private ParserInfo? Parse(string path, string rootPath, string libraryRoot, LibraryType type)
|
||||
private ParserInfo? Parse(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata)
|
||||
{
|
||||
if (_comicVineParser.IsApplicable(path, type))
|
||||
{
|
||||
return _comicVineParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _comicVineParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_imageParser.IsApplicable(path, type))
|
||||
{
|
||||
return _imageParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _imageParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_bookParser.IsApplicable(path, type))
|
||||
{
|
||||
return _bookParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _bookParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_pdfParser.IsApplicable(path, type))
|
||||
{
|
||||
return _pdfParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _pdfParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
if (_basicParser.IsApplicable(path, type))
|
||||
{
|
||||
return _basicParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
|
||||
return _basicParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -804,7 +804,7 @@ public class ParseScannedFiles
|
|||
{
|
||||
// Process files sequentially
|
||||
result.ParserInfos = files
|
||||
.Select(file => _readingItemService.ParseFile(file, normalizedFolder, result.LibraryRoot, library.Type))
|
||||
.Select(file => _readingItemService.ParseFile(file, normalizedFolder, result.LibraryRoot, library.Type, library.EnableMetadata))
|
||||
.Where(info => info != null)
|
||||
.ToList()!;
|
||||
}
|
||||
|
|
@ -812,7 +812,7 @@ public class ParseScannedFiles
|
|||
{
|
||||
// Process files in parallel
|
||||
var tasks = files.Select(file => Task.Run(() =>
|
||||
_readingItemService.ParseFile(file, normalizedFolder, result.LibraryRoot, library.Type)));
|
||||
_readingItemService.ParseFile(file, normalizedFolder, result.LibraryRoot, library.Type, library.EnableMetadata)));
|
||||
|
||||
var infos = await Task.WhenAll(tasks);
|
||||
result.ParserInfos = infos.Where(info => info != null).ToList()!;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace API.Services.Tasks.Scanner.Parser;
|
|||
/// </summary>
|
||||
public class BasicParser(IDirectoryService directoryService, IDefaultParser imageParser) : DefaultParser(directoryService)
|
||||
{
|
||||
public override ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo? comicInfo = null)
|
||||
public override ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo? comicInfo = null)
|
||||
{
|
||||
var fileName = directoryService.FileSystem.Path.GetFileNameWithoutExtension(filePath);
|
||||
// TODO: Potential Bug: This will return null, but on Image libraries, if all images, we would want to include this.
|
||||
|
|
@ -20,7 +20,7 @@ public class BasicParser(IDirectoryService directoryService, IDefaultParser imag
|
|||
|
||||
if (Parser.IsImage(filePath))
|
||||
{
|
||||
return imageParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Image, comicInfo);
|
||||
return imageParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Image, enableMetadata, comicInfo);
|
||||
}
|
||||
|
||||
var ret = new ParserInfo()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace API.Services.Tasks.Scanner.Parser;
|
|||
|
||||
public class BookParser(IDirectoryService directoryService, IBookService bookService, BasicParser basicParser) : DefaultParser(directoryService)
|
||||
{
|
||||
public override ParserInfo Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo comicInfo = null)
|
||||
public override ParserInfo Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo comicInfo = null)
|
||||
{
|
||||
var info = bookService.ParseInfo(filePath);
|
||||
if (info == null) return null;
|
||||
|
|
@ -35,7 +35,7 @@ public class BookParser(IDirectoryService directoryService, IBookService bookSer
|
|||
}
|
||||
else
|
||||
{
|
||||
var info2 = basicParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Book, comicInfo);
|
||||
var info2 = basicParser.Parse(filePath, rootPath, libraryRoot, LibraryType.Book, enableMetadata, comicInfo);
|
||||
info.Merge(info2);
|
||||
if (hasVolumeInSeries && info2 != null && Parser.ParseVolume(info2.Series, type)
|
||||
.Equals(Parser.LooseLeafVolume))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class ComicVineParser(IDirectoryService directoryService) : DefaultParser
|
|||
/// <param name="rootPath"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public override ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo? comicInfo = null)
|
||||
public override ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo? comicInfo = null)
|
||||
{
|
||||
if (type != LibraryType.ComicVine) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace API.Services.Tasks.Scanner.Parser;
|
|||
|
||||
public interface IDefaultParser
|
||||
{
|
||||
ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo? comicInfo = null);
|
||||
ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo? comicInfo = null);
|
||||
void ParseFromFallbackFolders(string filePath, string rootPath, LibraryType type, ref ParserInfo ret);
|
||||
bool IsApplicable(string filePath, LibraryType type);
|
||||
}
|
||||
|
|
@ -26,8 +26,9 @@ public abstract class DefaultParser(IDirectoryService directoryService) : IDefau
|
|||
/// <param name="filePath"></param>
|
||||
/// <param name="rootPath">Root folder</param>
|
||||
/// <param name="type">Allows different Regex to be used for parsing.</param>
|
||||
/// <param name="enableMetadata">Allows overriding data from metadata (ComicInfo/pdf/epub)</param>
|
||||
/// <returns><see cref="ParserInfo"/> or null if Series was empty</returns>
|
||||
public abstract ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo? comicInfo = null);
|
||||
public abstract ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo? comicInfo = null);
|
||||
|
||||
/// <summary>
|
||||
/// Fills out <see cref="ParserInfo"/> by trying to parse volume, chapters, and series from folders
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace API.Services.Tasks.Scanner.Parser;
|
|||
|
||||
public class ImageParser(IDirectoryService directoryService) : DefaultParser(directoryService)
|
||||
{
|
||||
public override ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo? comicInfo = null)
|
||||
public override ParserInfo? Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo? comicInfo = null)
|
||||
{
|
||||
if (!IsApplicable(filePath, type)) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace API.Services.Tasks.Scanner.Parser;
|
|||
|
||||
public class PdfParser(IDirectoryService directoryService) : DefaultParser(directoryService)
|
||||
{
|
||||
public override ParserInfo Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, ComicInfo comicInfo = null)
|
||||
public override ParserInfo Parse(string filePath, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true, ComicInfo comicInfo = null)
|
||||
{
|
||||
var fileName = directoryService.FileSystem.Path.GetFileNameWithoutExtension(filePath);
|
||||
var ret = new ParserInfo
|
||||
|
|
@ -68,6 +68,8 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc
|
|||
ParseFromFallbackFolders(filePath, tempRootPath, type, ref ret);
|
||||
}
|
||||
|
||||
if (enableMetadata)
|
||||
{
|
||||
// Patch in other information from ComicInfo
|
||||
UpdateFromComicInfo(ret);
|
||||
|
||||
|
|
@ -75,6 +77,8 @@ public class PdfParser(IDirectoryService directoryService) : DefaultParser(direc
|
|||
{
|
||||
ret.Title = comicInfo.Title.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ret.Chapters == Parser.DefaultChapter && ret.Volumes == Parser.LooseLeafVolume && type == LibraryType.Book)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -521,6 +521,11 @@ public class ScannerService : IScannerService
|
|||
// Validations are done, now we can start actual scan
|
||||
_logger.LogInformation("[ScannerService] Beginning file scan on {LibraryName}", library.Name);
|
||||
|
||||
if (!library.EnableMetadata)
|
||||
{
|
||||
_logger.LogInformation("[ScannerService] Warning! {LibraryName} has metadata turned off", library.Name);
|
||||
}
|
||||
|
||||
// This doesn't work for something like M:/Manga/ and a series has library folder as root
|
||||
var shouldUseLibraryScan = !(await _unitOfWork.LibraryRepository.DoAnySeriesFoldersMatch(libraryFolderPaths));
|
||||
if (!shouldUseLibraryScan)
|
||||
|
|
|
|||
120
UI/Web/src/app/_helpers/form-debug.ts
Normal file
120
UI/Web/src/app/_helpers/form-debug.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import {AbstractControl, FormArray, FormControl, FormGroup} from '@angular/forms';
|
||||
|
||||
interface ValidationIssue {
|
||||
path: string;
|
||||
controlType: string;
|
||||
value: any;
|
||||
errors: { [key: string]: any } | null;
|
||||
status: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export function analyzeFormGroupValidation(formGroup: FormGroup, basePath: string = ''): ValidationIssue[] {
|
||||
const issues: ValidationIssue[] = [];
|
||||
|
||||
function analyzeControl(control: AbstractControl, path: string): void {
|
||||
// Determine control type for better debugging
|
||||
let controlType = 'AbstractControl';
|
||||
if (control instanceof FormGroup) {
|
||||
controlType = 'FormGroup';
|
||||
} else if (control instanceof FormArray) {
|
||||
controlType = 'FormArray';
|
||||
} else if (control instanceof FormControl) {
|
||||
controlType = 'FormControl';
|
||||
}
|
||||
|
||||
// Add issue if control has validation errors or is invalid
|
||||
if (control.invalid || control.errors || control.disabled) {
|
||||
issues.push({
|
||||
path: path || 'root',
|
||||
controlType,
|
||||
value: control.value,
|
||||
errors: control.errors,
|
||||
status: control.status,
|
||||
disabled: control.disabled
|
||||
});
|
||||
}
|
||||
|
||||
// Recursively check nested controls
|
||||
if (control instanceof FormGroup) {
|
||||
Object.keys(control.controls).forEach(key => {
|
||||
const childPath = path ? `${path}.${key}` : key;
|
||||
analyzeControl(control.controls[key], childPath);
|
||||
});
|
||||
} else if (control instanceof FormArray) {
|
||||
control.controls.forEach((childControl, index) => {
|
||||
const childPath = path ? `${path}[${index}]` : `[${index}]`;
|
||||
analyzeControl(childControl, childPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
analyzeControl(formGroup, basePath);
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function printFormGroupValidation(formGroup: FormGroup, basePath: string = ''): void {
|
||||
const issues = analyzeFormGroupValidation(formGroup, basePath);
|
||||
|
||||
console.group(`🔍 FormGroup Validation Analysis (${basePath || 'root'})`);
|
||||
console.log(`Overall Status: ${formGroup.status}`);
|
||||
console.log(`Overall Valid: ${formGroup.valid}`);
|
||||
console.log(`Total Issues Found: ${issues.length}`);
|
||||
|
||||
if (issues.length === 0) {
|
||||
console.log('✅ No validation issues found!');
|
||||
} else {
|
||||
console.log('\n📋 Detailed Issues:');
|
||||
issues.forEach((issue, index) => {
|
||||
console.group(`${index + 1}. ${issue.path} (${issue.controlType})`);
|
||||
console.log(`Status: ${issue.status}`);
|
||||
console.log(`Value:`, issue.value);
|
||||
console.log(`Disabled: ${issue.disabled}`);
|
||||
|
||||
if (issue.errors) {
|
||||
console.log('Validation Errors:');
|
||||
Object.entries(issue.errors).forEach(([errorKey, errorValue]) => {
|
||||
console.log(` • ${errorKey}:`, errorValue);
|
||||
});
|
||||
} else {
|
||||
console.log('No specific validation errors (but control is invalid)');
|
||||
}
|
||||
console.groupEnd();
|
||||
});
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
// Alternative function that returns a formatted string instead of console logging
|
||||
export function getFormGroupValidationReport(formGroup: FormGroup, basePath: string = ''): string {
|
||||
const issues = analyzeFormGroupValidation(formGroup, basePath);
|
||||
|
||||
let report = `FormGroup Validation Report (${basePath || 'root'})\n`;
|
||||
report += `Overall Status: ${formGroup.status}\n`;
|
||||
report += `Overall Valid: ${formGroup.valid}\n`;
|
||||
report += `Total Issues Found: ${issues.length}\n\n`;
|
||||
|
||||
if (issues.length === 0) {
|
||||
report += '✅ No validation issues found!';
|
||||
} else {
|
||||
report += 'Detailed Issues:\n';
|
||||
issues.forEach((issue, index) => {
|
||||
report += `\n${index + 1}. ${issue.path} (${issue.controlType})\n`;
|
||||
report += ` Status: ${issue.status}\n`;
|
||||
report += ` Value: ${JSON.stringify(issue.value)}\n`;
|
||||
report += ` Disabled: ${issue.disabled}\n`;
|
||||
|
||||
if (issue.errors) {
|
||||
report += ' Validation Errors:\n';
|
||||
Object.entries(issue.errors).forEach(([errorKey, errorValue]) => {
|
||||
report += ` • ${errorKey}: ${JSON.stringify(errorValue)}\n`;
|
||||
});
|
||||
} else {
|
||||
report += ' No specific validation errors (but control is invalid)\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ export interface Library {
|
|||
manageReadingLists: boolean;
|
||||
allowScrobbling: boolean;
|
||||
allowMetadataMatching: boolean;
|
||||
enableMetadata: boolean;
|
||||
collapseSeriesRelationships: boolean;
|
||||
libraryFileTypes: Array<FileTypeGroup>;
|
||||
excludePatterns: Array<string>;
|
||||
|
|
|
|||
|
|
@ -127,6 +127,16 @@
|
|||
</app-setting-item>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('enable-metadata-label')" [subtitle]="t('enable-metadata-tooltip')">
|
||||
<ng-template #switch>
|
||||
<div class="form-check form-switch float-end">
|
||||
<input type="checkbox" id="enable-metadata" role="switch" formControlName="enableMetadata" class="form-check-input">
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-setting-switch>
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-4 mb-4">
|
||||
<app-setting-switch [title]="t('manage-collection-label')" [subtitle]="t('manage-collection-tooltip')">
|
||||
<ng-template #switch>
|
||||
|
|
|
|||
|
|
@ -105,15 +105,16 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
libraryForm: FormGroup = new FormGroup({
|
||||
name: new FormControl<string>('', { nonNullable: true, validators: [Validators.required] }),
|
||||
type: new FormControl<LibraryType>(LibraryType.Manga, { nonNullable: true, validators: [Validators.required] }),
|
||||
folderWatching: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
|
||||
includeInDashboard: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
|
||||
includeInRecommended: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
|
||||
includeInSearch: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
|
||||
manageCollections: new FormControl<boolean>(false, { nonNullable: true, validators: [Validators.required] }),
|
||||
manageReadingLists: new FormControl<boolean>(false, { nonNullable: true, validators: [Validators.required] }),
|
||||
allowScrobbling: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
|
||||
allowMetadataMatching: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
|
||||
collapseSeriesRelationships: new FormControl<boolean>(false, { nonNullable: true, validators: [Validators.required] }),
|
||||
folderWatching: new FormControl<boolean>(true, { nonNullable: true, validators: [] }),
|
||||
includeInDashboard: new FormControl<boolean>(true, { nonNullable: true, validators: [] }),
|
||||
includeInRecommended: new FormControl<boolean>(true, { nonNullable: true, validators: [] }),
|
||||
includeInSearch: new FormControl<boolean>(true, { nonNullable: true, validators: [] }),
|
||||
manageCollections: new FormControl<boolean>(false, { nonNullable: true, validators: [] }),
|
||||
manageReadingLists: new FormControl<boolean>(false, { nonNullable: true, validators: [] }),
|
||||
allowScrobbling: new FormControl<boolean>(true, { nonNullable: true, validators: [] }),
|
||||
allowMetadataMatching: new FormControl<boolean>(true, { nonNullable: true, validators: [] }),
|
||||
collapseSeriesRelationships: new FormControl<boolean>(false, { nonNullable: true, validators: [] }),
|
||||
enableMetadata: new FormControl<boolean>(true, { nonNullable: true, validators: [] }), // required validator doesn't check value, just if true
|
||||
});
|
||||
|
||||
selectedFolders: string[] = [];
|
||||
|
|
@ -155,7 +156,7 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
this.libraryForm.get('allowScrobbling')?.disable();
|
||||
|
||||
if (this.IsMetadataDownloadEligible) {
|
||||
this.libraryForm.get('allowMetadataMatching')?.setValue(this.library.allowMetadataMatching);
|
||||
this.libraryForm.get('allowMetadataMatching')?.setValue(this.library.allowMetadataMatching ?? true);
|
||||
this.libraryForm.get('allowMetadataMatching')?.enable();
|
||||
} else {
|
||||
this.libraryForm.get('allowMetadataMatching')?.setValue(false);
|
||||
|
|
@ -184,6 +185,20 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
|
||||
this.setValues();
|
||||
|
||||
// Turn on/off manage collections/rl
|
||||
this.libraryForm.get('enableMetadata')?.valueChanges.pipe(
|
||||
tap(enabled => {
|
||||
const manageCollectionsFc = this.libraryForm.get('manageCollections');
|
||||
const manageReadingListsFc = this.libraryForm.get('manageReadingLists');
|
||||
|
||||
manageCollectionsFc?.setValue(enabled);
|
||||
manageReadingListsFc?.setValue(enabled);
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe();
|
||||
|
||||
// This needs to only apply after first render
|
||||
this.libraryForm.get('type')?.valueChanges.pipe(
|
||||
tap((type: LibraryType) => {
|
||||
|
|
@ -257,6 +272,8 @@ export class LibrarySettingsModalComponent implements OnInit {
|
|||
this.libraryForm.get('collapseSeriesRelationships')?.setValue(this.library.collapseSeriesRelationships);
|
||||
this.libraryForm.get('allowScrobbling')?.setValue(this.IsKavitaPlusEligible ? this.library.allowScrobbling : false);
|
||||
this.libraryForm.get('allowMetadataMatching')?.setValue(this.IsMetadataDownloadEligible ? this.library.allowMetadataMatching : false);
|
||||
this.libraryForm.get('excludePatterns')?.setValue(this.excludePatterns ? this.library.excludePatterns : false);
|
||||
this.libraryForm.get('enableMetadata')?.setValue(this.library.enableMetadata, true);
|
||||
this.selectedFolders = this.library.folders;
|
||||
|
||||
this.madeChanges = false;
|
||||
|
|
|
|||
|
|
@ -1129,6 +1129,8 @@
|
|||
"include-in-dashboard-tooltip": "Should series from the library be included on the Dashboard. This affects all streams, like On Deck, Recently Updated, Recently Added, or any custom additions.",
|
||||
"include-in-search-label": "Include in Search",
|
||||
"include-in-search-tooltip": "Should series and any derived information (genres, people, files) from the library be included in search results.",
|
||||
"enable-metadata-label": "Enable Metadata (ComicInfo/Epub/PDF)",
|
||||
"enable-metadata-tooltip": "Allow Kavita to read metadata files which override filename parsing.",
|
||||
"force-scan": "Force Scan",
|
||||
"force-scan-tooltip": "This will force a scan on the library, treating like a fresh scan",
|
||||
"reset": "{{common.reset}}",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue