Scan Chunking (#604)

* Some performance refactoring around getting Library and avoid a byte[] copy for getting cover images for epubs.

* Initial commit. Rewrote the main series scan loop to use chunks of data at a time. Not fully shaken out.

* Hooked in the ability for the UI to react to series being added or removed from the DB.

* Cleaned up the messaging in the scan loop to be more clear.

* Metadata scan and scan work as expected and populate data to the UI. There is a slow down in speed for overall operation.

Scan series and refresh series metadata does not work fully.

* Fixed a bug where MangaFiles were not having LastModified Updated correctly, meaning they were opening archives every scan.

* Modified the code to be more realistic to the underlying file

* Updated ScanService to properly handle deleted files and not result in a higher-level scan.

* Shuffled around volume related repo apis to the volume repo rather than being in series.

* Rewrote scan series to be much cleaner and more concise on the flow. Fixed an issue in UpdateVolumes such that the debug code to log out removed volumes could throw an exception and actually break updating volumes.

* Refactored the code to set MangaFile last modified timestamp into the MangaFile entity.

* Added Series Name to ScanSeries event

* Added additional checks in ScanSeries to ensure we never go outside the library folder.

Added extra debug messages for when a metadata refresh doesn't actually make changes and for when we regen cover images.

* More logging statements saying where they originate from. Fixed a critical bug which caused only 1 chunk to ever be processed.

* Fixed a concurrency issue with natural sorter which could cause issues in ArchiveService.cs.

* Log cleanups

* Fixed an issue with logging out total time of a scan.

* Only show added toastrs for admins. When kicking off a refresh metadata for series, make sure we regenerate all cover images.

* Code smells on benchmark despite it being ignored
This commit is contained in:
Joseph Milazzo 2021-09-30 06:08:05 -07:00 committed by GitHub
parent 2b50fd6380
commit 56cf7be799
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1503 additions and 403 deletions

View file

@ -0,0 +1,4 @@
export interface RefreshMetadataEvent {
libraryId: number;
seriesId: number;
}

View file

@ -1,3 +1,4 @@
export interface ScanSeriesEvent {
seriesId: number;
seriesName: string;
}

View file

@ -0,0 +1,5 @@
export interface SeriesAddedEvent {
libraryId: number;
seriesId: number;
seriesName: string;
}

View file

@ -2,17 +2,23 @@ import { EventEmitter, Injectable } from '@angular/core';
import { HubConnection, HubConnectionBuilder } from '@microsoft/signalr';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { User } from '@sentry/angular';
import { BehaviorSubject, ReplaySubject } from 'rxjs';
import { ToastrService } from 'ngx-toastr';
import { ReplaySubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { UpdateNotificationModalComponent } from '../shared/update-notification/update-notification-modal.component';
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
import { ScanLibraryEvent } from '../_models/events/scan-library-event';
import { ScanSeriesEvent } from '../_models/events/scan-series-event';
import { SeriesAddedEvent } from '../_models/events/series-added-event';
import { AccountService } from './account.service';
export enum EVENTS {
UpdateAvailable = 'UpdateAvailable',
ScanSeries = 'ScanSeries',
ScanLibrary = 'ScanLibrary',
RefreshMetadata = 'RefreshMetadata',
SeriesAdded = 'SeriesAdded'
}
export interface Message<T> {
@ -33,8 +39,18 @@ export class MessageHubService {
public scanSeries: EventEmitter<ScanSeriesEvent> = new EventEmitter<ScanSeriesEvent>();
public scanLibrary: EventEmitter<ScanLibraryEvent> = new EventEmitter<ScanLibraryEvent>();
public seriesAdded: EventEmitter<SeriesAddedEvent> = new EventEmitter<SeriesAddedEvent>();
public refreshMetadata: EventEmitter<RefreshMetadataEvent> = new EventEmitter<RefreshMetadataEvent>();
constructor(private modalService: NgbModal) { }
isAdmin: boolean = false;
constructor(private modalService: NgbModal, private toastr: ToastrService, private accountService: AccountService) {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.isAdmin = this.accountService.hasAdminRole(user);
}
});
}
createHubConnection(user: User) {
this.hubConnection = new HubConnectionBuilder()
@ -71,6 +87,25 @@ export class MessageHubService {
// }
});
this.hubConnection.on(EVENTS.SeriesAdded, resp => {
this.messagesSource.next({
event: EVENTS.SeriesAdded,
payload: resp.body
});
this.seriesAdded.emit(resp.body);
if (this.isAdmin) {
this.toastr.info('Series ' + (resp.body as SeriesAddedEvent).seriesName + ' added');
}
});
this.hubConnection.on(EVENTS.RefreshMetadata, resp => {
this.messagesSource.next({
event: EVENTS.RefreshMetadata,
payload: resp.body
});
this.refreshMetadata.emit(resp.body);
});
this.hubConnection.on(EVENTS.UpdateAvailable, resp => {
this.messagesSource.next({
event: EVENTS.UpdateAvailable,

View file

@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angu
import { Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { take, takeWhile } from 'rxjs/operators';
import { Series } from 'src/app/_models/series';
import { AccountService } from 'src/app/_services/account.service';
import { ImageService } from 'src/app/_services/image.service';
@ -11,6 +11,8 @@ import { SeriesService } from 'src/app/_services/series.service';
import { ConfirmService } from 'src/app/shared/confirm.service';
import { ActionService } from 'src/app/_services/action.service';
import { EditSeriesModalComponent } from '../_modals/edit-series-modal/edit-series-modal.component';
import { RefreshMetadataEvent } from 'src/app/_models/events/refresh-metadata-event';
import { MessageHubService } from 'src/app/_services/message-hub.service';
@Component({
selector: 'app-series-card',
@ -46,7 +48,7 @@ export class SeriesCardComponent implements OnInit, OnChanges {
private seriesService: SeriesService, private toastr: ToastrService,
private modalService: NgbModal, private confirmService: ConfirmService,
public imageService: ImageService, private actionFactoryService: ActionFactoryService,
private actionService: ActionService) {
private actionService: ActionService, private hubService: MessageHubService) {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.isAdmin = this.accountService.hasAdminRole(user);
@ -58,6 +60,14 @@ export class SeriesCardComponent implements OnInit, OnChanges {
ngOnInit(): void {
if (this.data) {
this.imageUrl = this.imageService.randomize(this.imageService.getSeriesCoverImage(this.data.id));
this.hubService.refreshMetadata.pipe(takeWhile(event => event.libraryId === this.libraryId)).subscribe((event: RefreshMetadataEvent) => {
if (this.data.id === event.seriesId) {
this.imageUrl = this.imageService.randomize(this.imageService.getSeriesCoverImage(this.data.id));
console.log('Refresh event came through, updating cover image');
}
});
}
}

View file

@ -1,10 +1,12 @@
import { Component, HostListener, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { take } from 'rxjs/operators';
import { take, takeWhile } from 'rxjs/operators';
import { BulkSelectionService } from '../cards/bulk-selection.service';
import { UpdateFilterEvent } from '../cards/card-detail-layout/card-detail-layout.component';
import { KEY_CODES } from '../shared/_services/utility.service';
import { RefreshMetadataEvent } from '../_models/events/refresh-metadata-event';
import { SeriesAddedEvent } from '../_models/events/series-added-event';
import { Library } from '../_models/library';
import { Pagination } from '../_models/pagination';
import { Series } from '../_models/series';
@ -12,6 +14,7 @@ import { FilterItem, mangaFormatFilters, SeriesFilter } from '../_models/series-
import { Action, ActionFactoryService, ActionItem } from '../_services/action-factory.service';
import { ActionService } from '../_services/action.service';
import { LibraryService } from '../_services/library.service';
import { MessageHubService } from '../_services/message-hub.service';
import { SeriesService } from '../_services/series.service';
@Component({
@ -60,7 +63,7 @@ export class LibraryDetailComponent implements OnInit {
constructor(private route: ActivatedRoute, private router: Router, private seriesService: SeriesService,
private libraryService: LibraryService, private titleService: Title, private actionFactoryService: ActionFactoryService,
private actionService: ActionService, public bulkSelectionService: BulkSelectionService) {
private actionService: ActionService, public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService) {
const routeId = this.route.snapshot.paramMap.get('id');
if (routeId === null) {
this.router.navigateByUrl('/libraries');
@ -78,7 +81,10 @@ export class LibraryDetailComponent implements OnInit {
}
ngOnInit(): void {
this.hubService.seriesAdded.pipe(takeWhile(event => event.libraryId === this.libraryId)).subscribe((event: SeriesAddedEvent) => {
this.loadPage();
});
}
@HostListener('document:keydown.shift', ['$event'])

View file

@ -1,5 +1,5 @@
<div class="badge">
<!-- TODO: Put a person image container here -->
<!-- Put a person image container here -->
<div class="img">
</div>
@ -9,4 +9,4 @@
<ng-content select="[role]"></ng-content>
</div>
</div>
</div>
</div>