mirror of
https://github.com/adityachandelgit/BookLore.git
synced 2026-01-06 04:39:48 -06:00
Allow manual bookdrop folder refresh for non-local file systems
This commit is contained in:
committed by
Aditya Chandel
parent
a8102a3a75
commit
addf7c0c17
@@ -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<Void> rescanBookdrop() {
|
||||
monitoringService.rescanBookdropFolder();
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,11 @@ public class BookdropMonitoringService {
|
||||
}
|
||||
}
|
||||
|
||||
public void rescanBookdropFolder() {
|
||||
log.info("Manual rescan of Bookdrop folder triggered.");
|
||||
scanExistingBookdropFiles();
|
||||
}
|
||||
|
||||
private void scanExistingBookdropFiles() {
|
||||
try (Stream<Path> files = Files.walk(bookdrop)) {
|
||||
files.filter(Files::isRegularFile)
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
<div class="flex flex-col h-[calc(100dvh-6.1rem)] rounded-xl bg-[var(--card-background)] space-y-4">
|
||||
|
||||
<div class="px-6 pt-6 pb-4">
|
||||
<h2 class="text-xl font-semibold pb-1">Review Bookdrop Files</h2>
|
||||
<p class="text-sm text-gray-400">
|
||||
These files were uploaded to the
|
||||
<strong class="text-[var(--primary-color)]">Bookdrop Folder</strong>.
|
||||
Review their fetched metadata, assign a library and subpath, and finalize where they belong in your collection.
|
||||
</p>
|
||||
<div class="px-6 pt-6 pb-4 flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold pb-1 flex items-center gap-2">
|
||||
Review Bookdrop Files
|
||||
<a href="https://booklore-app.github.io/booklore-docs/docs/bookdrop" target="_blank" rel="noopener noreferrer">
|
||||
<i
|
||||
class="pi pi-external-link text-sky-600 cursor-pointer"
|
||||
style="font-size: 0.9rem"
|
||||
pTooltip="View documentation"
|
||||
tooltipPosition="top"></i>
|
||||
</a>
|
||||
</h2>
|
||||
<p class="text-sm text-gray-400">
|
||||
These files were uploaded to the
|
||||
<strong class="text-[var(--primary-color)]">Bookdrop Folder</strong>.
|
||||
Review their fetched metadata, assign a library and subpath, and finalize where they belong in your collection.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 md:mt-0">
|
||||
<p-button
|
||||
label="Rescan Bookdrop"
|
||||
icon="pi pi-refresh"
|
||||
severity="primary"
|
||||
outlined
|
||||
(click)="rescanBookdrop()"
|
||||
pTooltip="Manually trigger a rescan of the Bookdrop folder"
|
||||
tooltipPosition="top">
|
||||
</p-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (loading) {
|
||||
|
||||
@@ -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<BookdropFileMetadataPickerComponent>;
|
||||
|
||||
appSettings$: Observable<AppSettings | null> = this.appSettingsService.appSettings$;
|
||||
private routerSub!: Subscription;
|
||||
|
||||
uploadPattern = '';
|
||||
_defaultLibraryId: string | null = null;
|
||||
@@ -91,8 +94,12 @@ export class BookdropFileReviewComponent implements OnInit {
|
||||
excludedFiles = new Set<number>();
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface Page<T> {
|
||||
}
|
||||
|
||||
@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<void> {
|
||||
return this.http.post<void>(`${this.url}/files/discard`, payload);
|
||||
}
|
||||
|
||||
rescan(): Observable<void> {
|
||||
return this.http.post<void>(`${this.url}/rescan`, {});
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,13 @@
|
||||
<ul class="topbar-items hidden md:flex items-center gap-3 ml-auto pl-4">
|
||||
<div class="flex gap-6">
|
||||
@if (userService.userState$ | async; as userState) {
|
||||
<li>
|
||||
@if (userState.user?.permissions?.canManipulateLibrary || userState.user?.permissions?.admin) {
|
||||
<a class="topbar-item" (click)="navigateToBookdrop()" pTooltip="Bookdrop" tooltipPosition="bottom">
|
||||
<i class="pi pi-inbox text-surface-100"></i>
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
<li>
|
||||
@if (userState.user?.permissions?.canManipulateLibrary || userState.user?.permissions?.admin) {
|
||||
<a class="topbar-item" (click)="openLibraryCreatorDialog()" pTooltip="Create New Library" tooltipPosition="bottom">
|
||||
@@ -37,8 +44,6 @@
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
@if (userService.userState$ | async; as userState) {
|
||||
<li>
|
||||
@if (userState.user?.permissions?.canManipulateLibrary || userState.user?.permissions?.admin) {
|
||||
<a class="topbar-item" (click)="openFileUploadDialog()" pTooltip="Upload Book" tooltipPosition="bottom">
|
||||
|
||||
@@ -136,6 +136,10 @@ export class AppTopBarComponent implements OnDestroy {
|
||||
this.router.navigate(['/settings']);
|
||||
}
|
||||
|
||||
navigateToBookdrop() {
|
||||
this.router.navigate(['/bookdrop'], {queryParams: {reload: Date.now()}});
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authService.logout();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user