Skip to content

Commit

Permalink
Added a new control to allow users to opt-out of metadata matching pe…
Browse files Browse the repository at this point in the history
…r library.
  • Loading branch information
majora2007 committed Feb 2, 2025
1 parent ff8c088 commit 0e2eae9
Show file tree
Hide file tree
Showing 16 changed files with 133 additions and 35 deletions.
65 changes: 43 additions & 22 deletions API.Tests/AbstractDbTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;
Expand All @@ -20,14 +21,15 @@

namespace API.Tests;

public abstract class AbstractDbTest
public abstract class AbstractDbTest : IDisposable
{
protected readonly DbConnection _connection;
protected readonly DataContext _context;
protected readonly IUnitOfWork _unitOfWork;


protected const string CacheDirectory = "C:/kavita/config/cache/";
protected const string CacheLongDirectory = "C:/kavita/config/cache-long/";
protected const string CoverImageDirectory = "C:/kavita/config/covers/";
protected const string BackupDirectory = "C:/kavita/config/backups/";
protected const string LogDirectory = "C:/kavita/config/logs/";
Expand All @@ -38,21 +40,22 @@ public abstract class AbstractDbTest

protected AbstractDbTest()
{
var contextOptions = new DbContextOptionsBuilder()
var contextOptions = new DbContextOptionsBuilder<DataContext>()
.UseSqlite(CreateInMemoryDatabase())
.Options;

_connection = RelationalOptionsExtension.Extract(contextOptions).Connection;

_context = new DataContext(contextOptions);

_context.Database.EnsureCreated(); // Ensure DB schema is created

Task.Run(SeedDb).GetAwaiter().GetResult();

var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperProfiles>());
var mapper = config.CreateMapper();

// Set up Hangfire to use in-memory storage for testing
GlobalConfiguration.Configuration.UseInMemoryStorage();


_unitOfWork = new UnitOfWork(_context, mapper, null);
}

Expand All @@ -66,29 +69,40 @@ private static DbConnection CreateInMemoryDatabase()

private async Task<bool> SeedDb()
{
await _context.Database.MigrateAsync();
var filesystem = CreateFileSystem();
try
{
await _context.Database.MigrateAsync();
var filesystem = CreateFileSystem();

await Seed.SeedSettings(_context, new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem));
await Seed.SeedSettings(_context, new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem));

var setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.CacheDirectory).SingleAsync();
setting.Value = CacheDirectory;
var setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.CacheDirectory).SingleAsync();
setting.Value = CacheDirectory;

setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BackupDirectory).SingleAsync();
setting.Value = BackupDirectory;
setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BackupDirectory).SingleAsync();
setting.Value = BackupDirectory;

setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BookmarkDirectory).SingleAsync();
setting.Value = BookmarkDirectory;
setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BookmarkDirectory).SingleAsync();
setting.Value = BookmarkDirectory;

setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.TotalLogs).SingleAsync();
setting.Value = "10";
setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.TotalLogs).SingleAsync();
setting.Value = "10";

_context.ServerSetting.Update(setting);
_context.ServerSetting.Update(setting);

_context.Library.Add(new LibraryBuilder("Manga")
.WithFolderPath(new FolderPathBuilder("C:/data/").Build())
.Build());
return await _context.SaveChangesAsync() > 0;
await Seed.SeedMetadataSettings(_context);

_context.Library.Add(new LibraryBuilder("Manga")
.WithFolderPath(new FolderPathBuilder(DataDirectory).Build())
.Build());

return await _context.SaveChangesAsync() > 0;
}
catch (Exception ex)
{
Console.WriteLine($"[SeedDb] Error: {ex.Message}");
return false;
}
}

protected abstract Task ResetDb();
Expand All @@ -99,6 +113,7 @@ protected static MockFileSystem CreateFileSystem()
fileSystem.Directory.SetCurrentDirectory("C:/kavita/");
fileSystem.AddDirectory("C:/kavita/config/");
fileSystem.AddDirectory(CacheDirectory);
fileSystem.AddDirectory(CacheLongDirectory);
fileSystem.AddDirectory(CoverImageDirectory);
fileSystem.AddDirectory(BackupDirectory);
fileSystem.AddDirectory(BookmarkDirectory);
Expand All @@ -109,4 +124,10 @@ protected static MockFileSystem CreateFileSystem()

return fileSystem;
}

public void Dispose()
{
_context.Dispose();
_connection.Dispose();
}
}
3 changes: 2 additions & 1 deletion API/Controllers/LibraryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public async Task<ActionResult> AddLibrary(UpdateLibraryDto dto)
.WithIncludeInDashboard(dto.IncludeInDashboard)
.WithManageCollections(dto.ManageCollections)
.WithManageReadingLists(dto.ManageReadingLists)
.WIthAllowScrobbling(dto.AllowScrobbling)
.WithAllowScrobbling(dto.AllowScrobbling)
.WithAllowMetadataMatching(dto.AllowMetadataMatching)
.Build();

library.LibraryFileTypes = dto.FileGroupTypes
Expand Down
6 changes: 6 additions & 0 deletions API/DTOs/LibraryDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ public class LibraryDto
/// A set of globs that will exclude matching content from being scanned
/// </summary>
public ICollection<string> ExcludePatterns { get; set; }
/// <summary>
/// Allow any series within this Library to download metadata.
/// </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;
}
2 changes: 2 additions & 0 deletions API/DTOs/UpdateLibraryDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class UpdateLibraryDto
public bool ManageReadingLists { get; init; }
[Required]
public bool AllowScrobbling { get; init; }
[Required]
public bool AllowMetadataMatching { get; init; }
/// <summary>
/// What types of files to allow the scanner to pickup
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions API/Data/DataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ protected override void OnModelCreating(ModelBuilder builder)
builder.Entity<Library>()
.Property(b => b.AllowScrobbling)
.HasDefaultValue(true);
builder.Entity<Library>()
.Property(b => b.AllowMetadataMatching)
.HasDefaultValue(true);

builder.Entity<Chapter>()
.Property(b => b.WebLinks)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ public partial class KavitaPlusUserAndMetadataSettings : Migration
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "AllowMetadataMatching",
table: "Library",
type: "INTEGER",
nullable: false,
defaultValue: true);

migrationBuilder.AddColumn<bool>(
name: "AniListScrobblingEnabled",
table: "AppUserPreferences",
Expand All @@ -30,7 +37,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Enabled = table.Column<bool>(type: "INTEGER", nullable: false),
Enabled = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true),
EnableSummary = table.Column<bool>(type: "INTEGER", nullable: false),
EnablePublicationStatus = table.Column<bool>(type: "INTEGER", nullable: false),
EnableRelationships = table.Column<bool>(type: "INTEGER", nullable: false),
Expand Down Expand Up @@ -89,6 +96,10 @@ protected override void Down(MigrationBuilder migrationBuilder)
migrationBuilder.DropTable(
name: "MetadataSettings");

migrationBuilder.DropColumn(
name: "AllowMetadataMatching",
table: "Library");

migrationBuilder.DropColumn(
name: "AniListScrobblingEnabled",
table: "AppUserPreferences");
Expand Down
9 changes: 8 additions & 1 deletion API/Data/Migrations/DataContextModelSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");

b.Property<bool>("AllowMetadataMatching")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);

b.Property<bool>("AllowScrobbling")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
Expand Down Expand Up @@ -1672,7 +1677,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("INTEGER");

b.Property<bool>("Enabled")
.HasColumnType("INTEGER");
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);

b.Property<bool>("FirstLastPeopleNaming")
.HasColumnType("INTEGER");
Expand Down
8 changes: 7 additions & 1 deletion API/Entities/Library.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,14 @@ public class Library : IEntityDate, IHasCoverImage
/// <summary>
/// Should this library allow Scrobble events to emit from it
/// </summary>
/// <remarks>Scrobbling requires a valid LicenseKey</remarks>
/// <remarks>Requires a valid LicenseKey</remarks>
public bool AllowScrobbling { get; set; } = true;
/// <summary>
/// Allow any series within this Library to download metadata.
/// </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 DateTime Created { get; set; }
Expand Down
8 changes: 7 additions & 1 deletion API/Helpers/Builders/LibraryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ public LibraryBuilder WithManageReadingLists(bool toInclude)
return this;
}

public LibraryBuilder WIthAllowScrobbling(bool allowScrobbling)
public LibraryBuilder WithAllowMetadataMatching(bool allow)
{
_library.AllowMetadataMatching = allow;
return this;
}

public LibraryBuilder WithAllowScrobbling(bool allowScrobbling)
{
_library.AllowScrobbling = allowScrobbling;
return this;
Expand Down
4 changes: 2 additions & 2 deletions API/Services/Plus/ExternalMetadataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ public async Task UpdateSeriesDontMatch(int seriesId, bool dontMatch)
private async Task<SeriesDetailPlusDto> FetchExternalMetadataForSeries(int seriesId, LibraryType libraryType, PlusSeriesRequestDto data)
{

var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.Library);
if (series == null) return _defaultReturn;

try
Expand Down Expand Up @@ -436,7 +436,7 @@ private async Task<SeriesDetailPlusDto> FetchExternalMetadataForSeries(int serie

// If there is metadata and the user has metadata download turned on
var madeMetadataModification = false;
if (result.Series != null)
if (result.Series != null && series.Library.AllowMetadataMatching)
{
madeMetadataModification = await WriteExternalMetadataToSeries(result.Series, seriesId);
if (madeMetadataModification)
Expand Down
1 change: 1 addition & 0 deletions UI/Web/src/app/_models/library/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface Library {
manageCollections: boolean;
manageReadingLists: boolean;
allowScrobbling: boolean;
allowMetadataMatching: boolean;
collapseSeriesRelationships: boolean;
libraryFileTypes: Array<FileTypeGroup>;
excludePatterns: Array<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,16 @@ <h4 class="modal-title" id="modal-basic-title">
</app-setting-switch>
</div>

<div class="row g-0 mt-4 mb-4">
<app-setting-switch [title]="t('allow-metadata-matching-label')" [subtitle]="t('allow-metadata-matching-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input type="checkbox" id="metadata-matching" role="switch" formControlName="allowMetadataMatching" 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('folder-watching-label')" [subtitle]="t('folder-watching-tooltip')">
<ng-template #switch>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import {
} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {
NgbAccordionBody,
NgbAccordionButton, NgbAccordionCollapse,
NgbAccordionDirective, NgbAccordionHeader, NgbAccordionItem,
NgbActiveModal,
NgbModal,
NgbModalModule,
Expand Down Expand Up @@ -71,7 +68,7 @@ enum StepID {
standalone: true,
imports: [CommonModule, NgbModalModule, NgbNavLink, NgbNavItem, NgbNavContent, ReactiveFormsModule, NgbTooltip,
SentenceCasePipe, NgbNav, NgbNavOutlet, CoverImageChooserComponent, TranslocoModule, DefaultDatePipe,
FileTypeGroupPipe, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton, NgbAccordionCollapse, NgbAccordionBody, EditListComponent, SettingItemComponent, SettingSwitchComponent, SettingButtonComponent],
FileTypeGroupPipe, EditListComponent, SettingItemComponent, SettingSwitchComponent, SettingButtonComponent],
templateUrl: './library-settings-modal.component.html',
styleUrls: ['./library-settings-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
Expand Down Expand Up @@ -116,6 +113,7 @@ export class LibrarySettingsModalComponent implements OnInit {
manageCollections: new FormControl<boolean>(true, { nonNullable: true, validators: [Validators.required] }),
manageReadingLists: new FormControl<boolean>(true, { 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] }),
});

Expand Down Expand Up @@ -153,7 +151,9 @@ export class LibrarySettingsModalComponent implements OnInit {

if (this.library && !(this.library.type === LibraryType.Manga || this.library.type === LibraryType.LightNovel) ) {
this.libraryForm.get('allowScrobbling')?.setValue(false);
this.libraryForm.get('allowMetadataMatching')?.setValue(false);
this.libraryForm.get('allowScrobbling')?.disable();
this.libraryForm.get('allowMetadataMatching')?.disable();
}

this.libraryForm.get('name')?.valueChanges.pipe(
Expand Down Expand Up @@ -216,8 +216,10 @@ export class LibrarySettingsModalComponent implements OnInit {
this.libraryForm.get('allowScrobbling')?.setValue(this.IsKavitaPlusEligible);
if (!this.IsKavitaPlusEligible) {
this.libraryForm.get('allowScrobbling')?.disable();
this.libraryForm.get('allowMetadataMatching')?.disable();
} else {
this.libraryForm.get('allowScrobbling')?.enable();
this.libraryForm.get('allowMetadataMatching')?.enable();
}
this.cdRef.markForCheck();
}),
Expand All @@ -237,6 +239,7 @@ export class LibrarySettingsModalComponent implements OnInit {
this.libraryForm.get('manageReadingLists')?.setValue(this.library.manageReadingLists);
this.libraryForm.get('collapseSeriesRelationships')?.setValue(this.library.collapseSeriesRelationships);
this.libraryForm.get('allowScrobbling')?.setValue(this.library.allowScrobbling);
this.libraryForm.get('allowMetadataMatching')?.setValue(this.library.allowMetadataMatching);
this.selectedFolders = this.library.folders;
this.madeChanges = false;
for(let fileTypeGroup of allFileTypeGroup) {
Expand Down
2 changes: 2 additions & 0 deletions UI/Web/src/assets/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,8 @@
"manage-reading-list-tooltip": "Should Kavita create Reading Lists from StoryArc/StoryArcNumber and AlternativeSeries/AlternativeCount tags found within ComicInfo.xml/opf files",
"allow-scrobbling-label": "Allow Scrobbling",
"allow-scrobbling-tooltip": "Should Kavita scrobble reading events, want to read status, ratings, and reviews to configured providers. This will only occur if the server has an active Kavita+ Subscription.",
"allow-metadata-matching-label": "Allow Metadata Matching",
"allow-metadata-matching-tooltip": "Should Kavita download metadata for Series within this Library. This will only occur if the server has an active Kavita+ Subscription.",
"folder-watching-label": "Folder Watching",
"folder-watching-tooltip": "Override Server folder watching for this library. If off, folder watching won't run on the folders this library contains. If libraries share folders, then folders may still be ran against. Will always wait 10 minutes before triggering scan.",
"include-in-dashboard-label": "Include in Dashboard",
Expand Down
Loading

0 comments on commit 0e2eae9

Please sign in to comment.