More Scan Loop Fixes (#1473)

* Added a ToList() to avoid a bug where a person could be removed from a list while iterating over the list.

* When deleting a series, want to read page will now automatically remove that series from the view.

* Fixed a series lookup which was ignoring format

* Ignore XML comment warnings

* Removed a note since it was already working that way

* Fixed unit test
This commit is contained in:
Joseph Milazzo 2022-08-24 19:02:16 -05:00 committed by GitHub
parent e37d7cfbba
commit f92ef19b61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 52 additions and 19 deletions

View File

@ -125,9 +125,12 @@ public class SeriesRepositoryTests
} }
}; };
var s = DbFactory.Series("The Idaten Deities Know Only Peace", "Heion Sedai no Idaten-tachi");
s.Format = MangaFormat.Archive;
library.Series = new List<Series>() library.Series = new List<Series>()
{ {
DbFactory.Series("The Idaten Deities Know Only Peace", "Heion Sedai no Idaten-tachi"), s,
}; };
_unitOfWork.LibraryRepository.Add(library); _unitOfWork.LibraryRepository.Add(library);
@ -135,13 +138,14 @@ public class SeriesRepositoryTests
} }
[InlineData("Heion Sedai no Idaten-tachi", "", "The Idaten Deities Know Only Peace")] // Matching on localized name in DB [InlineData("Heion Sedai no Idaten-tachi", "", MangaFormat.Archive, "The Idaten Deities Know Only Peace")] // Matching on localized name in DB
[InlineData("Heion Sedai no Idaten-tachi", "", MangaFormat.Pdf, null)]
public async Task GetFullSeriesByAnyName_Should(string seriesName, string localizedName, string? expected) public async Task GetFullSeriesByAnyName_Should(string seriesName, string localizedName, string? expected)
{ {
var firstSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); var firstSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1);
var series = var series =
await _unitOfWork.SeriesRepository.GetFullSeriesByAnyName(seriesName, localizedName, await _unitOfWork.SeriesRepository.GetFullSeriesByAnyName(seriesName, localizedName,
1); 1, MangaFormat.Unknown);
if (expected == null) if (expected == null)
{ {
Assert.Null(series); Assert.Null(series);

View File

@ -19,6 +19,12 @@
<NoWarn>1701;1702;1591</NoWarn> <NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<!-- Ignore XML comments -->
<PropertyGroup>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>

View File

@ -121,7 +121,7 @@ public interface ISeriesRepository
Task<int> GetSeriesIdByFolder(string folder); Task<int> GetSeriesIdByFolder(string folder);
Task<Series> GetSeriesByFolderPath(string folder); Task<Series> GetSeriesByFolderPath(string folder);
Task<Series> GetFullSeriesByName(string series, int libraryId); Task<Series> GetFullSeriesByName(string series, int libraryId);
Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId); Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format);
Task RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId); Task RemoveSeriesNotInList(IList<ParsedSeries> seenSeries, int libraryId);
Task<IDictionary<string, IList<SeriesModified>>> GetFolderPathMap(int libraryId); Task<IDictionary<string, IList<SeriesModified>>> GetFolderPathMap(int libraryId);
} }
@ -1218,12 +1218,13 @@ public class SeriesRepository : ISeriesRepository
/// <param name="localizedName"></param> /// <param name="localizedName"></param>
/// <param name="libraryId"></param> /// <param name="libraryId"></param>
/// <returns></returns> /// <returns></returns>
public Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId) public Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format)
{ {
var normalizedSeries = Parser.Parser.Normalize(seriesName); var normalizedSeries = Parser.Parser.Normalize(seriesName);
var normalizedLocalized = Parser.Parser.Normalize(localizedName); var normalizedLocalized = Parser.Parser.Normalize(localizedName);
var query = _context.Series var query = _context.Series
.Where(s => s.LibraryId == libraryId) .Where(s => s.LibraryId == libraryId)
.Where(s => s.Format == format && format != MangaFormat.Unknown)
.Where(s => s.NormalizedName.Equals(normalizedSeries) .Where(s => s.NormalizedName.Equals(normalizedSeries)
|| (s.NormalizedLocalizedName.Equals(normalizedSeries) && s.NormalizedLocalizedName != string.Empty)); || (s.NormalizedLocalizedName.Equals(normalizedSeries) && s.NormalizedLocalizedName != string.Empty));
if (!string.IsNullOrEmpty(normalizedLocalized)) if (!string.IsNullOrEmpty(normalizedLocalized))

View File

@ -82,7 +82,8 @@ public static class PersonHelper
{ {
foreach (var person in existingPeople) foreach (var person in existingPeople)
{ {
var existingPerson = removeAllExcept.FirstOrDefault(p => p.Role == person.Role && person.NormalizedName.Equals(p.NormalizedName)); var existingPerson = removeAllExcept
.FirstOrDefault(p => p.Role == person.Role && person.NormalizedName.Equals(p.NormalizedName));
if (existingPerson == null) if (existingPerson == null)
{ {
action?.Invoke(person); action?.Invoke(person);

View File

@ -93,7 +93,7 @@ public class ProcessSeries : IProcessSeries
{ {
series = series =
await _unitOfWork.SeriesRepository.GetFullSeriesByAnyName(firstInfo.Series, firstInfo.LocalizedSeries, await _unitOfWork.SeriesRepository.GetFullSeriesByAnyName(firstInfo.Series, firstInfo.LocalizedSeries,
library.Id); library.Id, firstInfo.Format);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -375,7 +375,7 @@ public class ProcessSeries : IProcessSeries
// NOTE: The issue here is that people is just from chapter, but series metadata might already have some people on it // NOTE: The issue here is that people is just from chapter, but series metadata might already have some people on it
// I might be able to filter out people that are in locked fields? // I might be able to filter out people that are in locked fields?
var people = chapters.SelectMany(c => c.People).ToList(); var people = chapters.SelectMany(c => c.People).ToList();
PersonHelper.KeepOnlySamePeopleBetweenLists(series.Metadata.People, PersonHelper.KeepOnlySamePeopleBetweenLists(series.Metadata.People.ToList(),
people, person => people, person =>
{ {
switch (person.Role) switch (person.Role)

View File

@ -436,7 +436,7 @@ public class ScannerService : IScannerService
var shouldUseLibraryScan = !(await _unitOfWork.LibraryRepository.DoAnySeriesFoldersMatch(libraryFolderPaths)); var shouldUseLibraryScan = !(await _unitOfWork.LibraryRepository.DoAnySeriesFoldersMatch(libraryFolderPaths));
if (!shouldUseLibraryScan) if (!shouldUseLibraryScan)
{ {
_logger.LogInformation("Library {LibraryName} consists of one ore more Series folders, using series scan", library.Name); _logger.LogError("Library {LibraryName} consists of one or more Series folders, using series scan", library.Name);
} }
@ -459,8 +459,6 @@ public class ScannerService : IScannerService
Format = parsedFiles.First().Format Format = parsedFiles.First().Format
}; };
// NOTE: Could we check if there are multiple found series (different series) and process each one?
if (skippedScan) if (skippedScan)
{ {
seenSeries.AddRange(parsedFiles.Select(pf => new ParsedSeries() seenSeries.AddRange(parsedFiles.Select(pf => new ParsedSeries()
@ -485,7 +483,6 @@ public class ScannerService : IScannerService
await Task.WhenAll(processTasks); await Task.WhenAll(processTasks);
//await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, string.Empty));
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent(string.Empty, library.Name, ProgressEventType.Ended)); await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent(string.Empty, library.Name, ProgressEventType.Ended));
_logger.LogInformation("[ScannerService] Finished file scan in {ScanAndUpdateTime}. Updating database", scanElapsedTime); _logger.LogInformation("[ScannerService] Finished file scan in {ScanAndUpdateTime}. Updating database", scanElapsedTime);

View File

@ -20,9 +20,10 @@
[filterOpen]="filterOpen" [filterOpen]="filterOpen"
[jumpBarKeys]="jumpbarKeys" [jumpBarKeys]="jumpbarKeys"
[trackByIdentity]="trackByIdentity" [trackByIdentity]="trackByIdentity"
[refresh]="refresh"
(applyFilter)="updateFilter($event)"> (applyFilter)="updateFilter($event)">
<ng-template #cardItem let-item let-position="idx"> <ng-template #cardItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="loadPage()" <app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="removeSeries(item.id)"
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true" (selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
></app-series-card> ></app-series-card>
</ng-template> </ng-template>

View File

@ -7,6 +7,7 @@ import { BulkSelectionService } from 'src/app/cards/bulk-selection.service';
import { FilterSettings } from 'src/app/metadata-filter/filter-settings'; import { FilterSettings } from 'src/app/metadata-filter/filter-settings';
import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service'; import { FilterUtilitiesService } from 'src/app/shared/_services/filter-utilities.service';
import { UtilityService, KEY_CODES } from 'src/app/shared/_services/utility.service'; import { UtilityService, KEY_CODES } from 'src/app/shared/_services/utility.service';
import { SeriesRemovedEvent } from 'src/app/_models/events/series-removed-event';
import { JumpKey } from 'src/app/_models/jumpbar/jump-key'; import { JumpKey } from 'src/app/_models/jumpbar/jump-key';
import { Pagination } from 'src/app/_models/pagination'; import { Pagination } from 'src/app/_models/pagination';
import { Series } from 'src/app/_models/series'; import { Series } from 'src/app/_models/series';
@ -35,6 +36,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
seriesPagination!: Pagination; seriesPagination!: Pagination;
filter: SeriesFilter | undefined = undefined; filter: SeriesFilter | undefined = undefined;
filterSettings: FilterSettings = new FilterSettings(); filterSettings: FilterSettings = new FilterSettings();
refresh: EventEmitter<void> = new EventEmitter();
filterActiveCheck!: SeriesFilter; filterActiveCheck!: SeriesFilter;
filterActive: boolean = false; filterActive: boolean = false;
@ -43,7 +45,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
private onDestory: Subject<void> = new Subject<void>(); private onDestroy: Subject<void> = new Subject<void>();
trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`; trackByIdentity = (index: number, item: Series) => `${item.name}_${item.localizedName}_${item.pagesRead}`;
bulkActionCallback = (action: Action, data: any) => { bulkActionCallback = (action: Action, data: any) => {
@ -77,7 +79,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
private seriesService: SeriesService, private titleService: Title, private seriesService: SeriesService, private titleService: Title,
public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService, public bulkSelectionService: BulkSelectionService, private actionService: ActionService, private messageHub: MessageHubService,
private filterUtilityService: FilterUtilitiesService, private utilityService: UtilityService, @Inject(DOCUMENT) private document: Document, private filterUtilityService: FilterUtilitiesService, private utilityService: UtilityService, @Inject(DOCUMENT) private document: Document,
private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService) { private readonly cdRef: ChangeDetectorRef, private scrollService: ScrollService, private hubService: MessageHubService) {
this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.router.routeReuseStrategy.shouldReuseRoute = () => false;
this.titleService.setTitle('Want To Read'); this.titleService.setTitle('Want To Read');
@ -85,12 +87,26 @@ export class WantToReadComponent implements OnInit, OnDestroy {
[this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot); [this.filterSettings.presets, this.filterSettings.openByDefault] = this.filterUtilityService.filterPresetsFromUrl(this.route.snapshot);
this.filterActiveCheck = this.seriesService.createSeriesFilter(); this.filterActiveCheck = this.seriesService.createSeriesFilter();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.hubService.messages$.pipe(takeUntil(this.onDestroy)).subscribe((event) => {
if (event.event === EVENTS.SeriesRemoved) {
const seriesRemoved = event.payload as SeriesRemovedEvent;
if (!this.utilityService.deepEqual(this.filter, this.filterActiveCheck)) {
this.loadPage();
return;
}
this.series = this.series.filter(s => s.id != seriesRemoved.seriesId);
this.seriesPagination.totalItems--;
this.cdRef.markForCheck();
this.refresh.emit();
}
});
} }
ngOnInit(): void { ngOnInit(): void {
this.messageHub.messages$.pipe(takeUntil(this.onDestroy), debounceTime(2000)).subscribe(event => {
this.messageHub.messages$.pipe(takeUntil(this.onDestory), debounceTime(2000)).subscribe(event => {
if (event.event === EVENTS.SeriesRemoved) { if (event.event === EVENTS.SeriesRemoved) {
this.loadPage(); this.loadPage();
} }
@ -102,8 +118,8 @@ export class WantToReadComponent implements OnInit, OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
this.onDestory.next(); this.onDestroy.next();
this.onDestory.complete(); this.onDestroy.complete();
} }
@HostListener('document:keydown.shift', ['$event']) @HostListener('document:keydown.shift', ['$event'])
@ -120,6 +136,13 @@ export class WantToReadComponent implements OnInit, OnDestroy {
} }
} }
removeSeries(seriesId: number) {
this.series = this.series.filter(s => s.id != seriesId);
this.seriesPagination.totalItems--;
this.cdRef.markForCheck();
this.refresh.emit();
}
loadPage() { loadPage() {
this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck); this.filterActive = !this.utilityService.deepEqual(this.filter, this.filterActiveCheck);
this.isLoading = true; this.isLoading = true;