From addf7c0c1724a6484b7261ee36c7100b858606fd Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Fri, 29 Aug 2025 10:30:27 -0600 Subject: [PATCH] Allow manual bookdrop folder refresh for non-local file systems --- .../controller/BookdropFileController.java | 9 ++++ .../bookdrop/BookdropMonitoringService.java | 5 +++ .../bookdrop-file-review.component.html | 37 ++++++++++++--- .../bookdrop-file-review.component.ts | 45 +++++++++++++++---- .../bookdrop-files-widget.component.ts | 2 +- ...okdrop-finalize-result-dialog-component.ts | 2 +- ...le-task.service.ts => bookdrop.service.ts} | 6 ++- .../layout-topbar/app.topbar.component.html | 9 +++- .../layout-topbar/app.topbar.component.ts | 4 ++ 9 files changed, 98 insertions(+), 21 deletions(-) rename booklore-ui/src/app/bookdrop/{bookdrop-file-task.service.ts => bookdrop.service.ts} (93%) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java index 6563da499..5d60bc7d3 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java @@ -6,6 +6,8 @@ import com.adityachandel.booklore.model.dto.request.BookdropFinalizeRequest; import com.adityachandel.booklore.model.dto.request.BookdropSelectionRequest; import com.adityachandel.booklore.model.dto.response.BookdropFinalizeResult; import com.adityachandel.booklore.service.bookdrop.BookDropService; +import com.adityachandel.booklore.service.bookdrop.BookdropMonitoringService; +import com.adityachandel.booklore.service.monitoring.MonitoringService; import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -18,6 +20,7 @@ import org.springframework.web.bind.annotation.*; public class BookdropFileController { private final BookDropService bookDropService; + private final BookdropMonitoringService monitoringService; @GetMapping("/notification") public BookdropFileNotification getSummary() { @@ -40,4 +43,10 @@ public class BookdropFileController { BookdropFinalizeResult result = bookDropService.finalizeImport(request); return ResponseEntity.ok(result); } + + @PostMapping("/rescan") + public ResponseEntity rescanBookdrop() { + monitoringService.rescanBookdropFolder(); + return ResponseEntity.ok().build(); + } } \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMonitoringService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMonitoringService.java index 6c07c4d18..52fd2c6cf 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMonitoringService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMonitoringService.java @@ -179,6 +179,11 @@ public class BookdropMonitoringService { } } + public void rescanBookdropFolder() { + log.info("Manual rescan of Bookdrop folder triggered."); + scanExistingBookdropFiles(); + } + private void scanExistingBookdropFiles() { try (Stream files = Files.walk(bookdrop)) { files.filter(Files::isRegularFile) diff --git a/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.html b/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.html index 2fa7569d6..d786d6954 100644 --- a/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.html +++ b/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.html @@ -1,12 +1,35 @@
-
-

Review Bookdrop Files

-

- These files were uploaded to the - Bookdrop Folder. - Review their fetched metadata, assign a library and subpath, and finalize where they belong in your collection. -

+
+
+

+ Review Bookdrop Files + + + +

+

+ These files were uploaded to the + Bookdrop Folder. + Review their fetched metadata, assign a library and subpath, and finalize where they belong in your collection. +

+
+ +
+ + +
@if (loading) { diff --git a/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.ts b/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.ts index e8f044452..8b31c769f 100644 --- a/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.ts +++ b/booklore-ui/src/app/bookdrop/bookdrop-file-review-component/bookdrop-file-review.component.ts @@ -1,8 +1,8 @@ import {Component, DestroyRef, inject, OnInit, QueryList, ViewChildren} from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; -import {filter, take} from 'rxjs/operators'; +import {filter, startWith, take, tap} from 'rxjs/operators'; -import {BookdropFile, BookdropFileTaskService, BookdropFinalizePayload, BookdropFinalizeResult} from '../bookdrop-file-task.service'; +import {BookdropFile, BookdropService, BookdropFinalizePayload, BookdropFinalizeResult} from '../bookdrop.service'; import {LibraryService} from '../../book/service/library.service'; import {Library} from '../../book/model/library.model'; @@ -15,7 +15,7 @@ import {Divider} from 'primeng/divider'; import {ConfirmationService, MessageService} from 'primeng/api'; import {BookdropFileMetadataPickerComponent} from '../bookdrop-file-metadata-picker-component/bookdrop-file-metadata-picker.component'; -import {Observable} from 'rxjs'; +import {Observable, Subscription} from 'rxjs'; import {AppSettings} from '../../core/model/app-settings.model'; import {AppSettingsService} from '../../core/service/app-settings.service'; @@ -26,6 +26,7 @@ import {UrlHelperService} from '../../utilities/service/url-helper.service'; import {Checkbox} from 'primeng/checkbox'; import {NgClass, NgStyle} from '@angular/common'; import {Paginator} from 'primeng/paginator'; +import {ActivatedRoute, NavigationEnd, Router} from '@angular/router'; export interface BookdropFileUI { file: BookdropFile; @@ -59,7 +60,7 @@ export interface BookdropFileUI { ], }) export class BookdropFileReviewComponent implements OnInit { - private readonly bookdropFileService = inject(BookdropFileTaskService); + private readonly bookdropService = inject(BookdropService); private readonly libraryService = inject(LibraryService); private readonly confirmationService = inject(ConfirmationService); private readonly destroyRef = inject(DestroyRef); @@ -67,10 +68,12 @@ export class BookdropFileReviewComponent implements OnInit { private readonly appSettingsService = inject(AppSettingsService); private readonly messageService = inject(MessageService); private readonly urlHelper = inject(UrlHelperService); + private readonly activatedRoute = inject(ActivatedRoute); @ViewChildren('metadataPicker') metadataPickers!: QueryList; appSettings$: Observable = this.appSettingsService.appSettings$; + private routerSub!: Subscription; uploadPattern = ''; _defaultLibraryId: string | null = null; @@ -91,8 +94,12 @@ export class BookdropFileReviewComponent implements OnInit { excludedFiles = new Set(); ngOnInit(): void { - this.loading = true; - this.loadPage(0); + this.activatedRoute.queryParams + .pipe(startWith({}), tap(() => { + this.loading = true; + this.loadPage(0); + })) + .subscribe(); this.libraryService.libraryState$ .pipe(filter(state => !!state?.loaded), take(1)) @@ -162,7 +169,7 @@ export class BookdropFileReviewComponent implements OnInit { } loadPage(page: number): void { - this.bookdropFileService.getPendingFiles(page, this.pageSize) + this.bookdropService.getPendingFiles(page, this.pageSize) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: response => { @@ -354,7 +361,7 @@ export class BookdropFileReviewComponent implements OnInit { .map(file => file.file.id); } - this.bookdropFileService.discardFiles(payload).subscribe({ + this.bookdropService.discardFiles(payload).subscribe({ next: () => { this.messageService.add({ severity: 'success', @@ -443,7 +450,7 @@ export class BookdropFileReviewComponent implements OnInit { files, }; - this.bookdropFileService.finalizeImport(payload).subscribe({ + this.bookdropService.finalizeImport(payload).subscribe({ next: (result: BookdropFinalizeResult) => { this.saving = false; @@ -528,4 +535,24 @@ export class BookdropFileReviewComponent implements OnInit { savedFields: {} }; } + + rescanBookdrop() { + this.bookdropService.rescan().subscribe({ + next: () => { + this.messageService.add({ + severity: 'success', + summary: 'Rescan Triggered', + detail: 'Bookdrop rescan has been started successfully.', + }); + }, + error: (err) => { + this.messageService.add({ + severity: 'error', + summary: 'Rescan Failed', + detail: 'Unable to trigger bookdrop rescan. Please try again.', + }); + console.error(err); + } + }); + } } diff --git a/booklore-ui/src/app/bookdrop/bookdrop-files-widget-component/bookdrop-files-widget.component.ts b/booklore-ui/src/app/bookdrop/bookdrop-files-widget-component/bookdrop-files-widget.component.ts index 9bd2cc73e..bf6cb186d 100644 --- a/booklore-ui/src/app/bookdrop/bookdrop-files-widget-component/bookdrop-files-widget.component.ts +++ b/booklore-ui/src/app/bookdrop/bookdrop-files-widget-component/bookdrop-files-widget.component.ts @@ -36,7 +36,7 @@ export class BookdropFilesWidgetComponent implements OnInit, OnDestroy { } openReviewDialog(): void { - this.router.navigate(['/bookdrop']); + this.router.navigate(['/bookdrop'], {queryParams: {reload: Date.now()}}); } ngOnDestroy(): void { diff --git a/booklore-ui/src/app/bookdrop/bookdrop-finalize-result-dialog-component/bookdrop-finalize-result-dialog-component.ts b/booklore-ui/src/app/bookdrop/bookdrop-finalize-result-dialog-component/bookdrop-finalize-result-dialog-component.ts index 4155f9b19..a6b7cc2f5 100644 --- a/booklore-ui/src/app/bookdrop/bookdrop-finalize-result-dialog-component/bookdrop-finalize-result-dialog-component.ts +++ b/booklore-ui/src/app/bookdrop/bookdrop-finalize-result-dialog-component/bookdrop-finalize-result-dialog-component.ts @@ -1,6 +1,6 @@ import {Component, OnDestroy} from '@angular/core'; import {DatePipe, NgClass} from '@angular/common'; -import {BookdropFinalizeResult} from '../bookdrop-file-task.service'; +import {BookdropFinalizeResult} from '../bookdrop.service'; import {DynamicDialogConfig, DynamicDialogRef} from "primeng/dynamicdialog"; @Component({ diff --git a/booklore-ui/src/app/bookdrop/bookdrop-file-task.service.ts b/booklore-ui/src/app/bookdrop/bookdrop.service.ts similarity index 93% rename from booklore-ui/src/app/bookdrop/bookdrop-file-task.service.ts rename to booklore-ui/src/app/bookdrop/bookdrop.service.ts index 5cb82ebb2..2e3ecd919 100644 --- a/booklore-ui/src/app/bookdrop/bookdrop-file-task.service.ts +++ b/booklore-ui/src/app/bookdrop/bookdrop.service.ts @@ -57,7 +57,7 @@ export interface Page { } @Injectable({providedIn: 'root'}) -export class BookdropFileTaskService { +export class BookdropService { private readonly url = `${API_CONFIG.BASE_URL}/api/v1/bookdrop`; private http = inject(HttpClient); @@ -72,4 +72,8 @@ export class BookdropFileTaskService { discardFiles(payload: { selectAll: boolean; excludedIds?: number[]; selectedIds?: number[] }): Observable { return this.http.post(`${this.url}/files/discard`, payload); } + + rescan(): Observable { + return this.http.post(`${this.url}/rescan`, {}); + } } diff --git a/booklore-ui/src/app/layout/component/layout-topbar/app.topbar.component.html b/booklore-ui/src/app/layout/component/layout-topbar/app.topbar.component.html index c28a195a7..31bd72ed2 100644 --- a/booklore-ui/src/app/layout/component/layout-topbar/app.topbar.component.html +++ b/booklore-ui/src/app/layout/component/layout-topbar/app.topbar.component.html @@ -30,6 +30,13 @@