feat: library deep scan is now a parameter of the scan API

Closes: #1137
This commit is contained in:
Gauthier Roebroeck 2023-06-29 12:13:32 +08:00
parent 308a068f42
commit 63e3e7a8ac
22 changed files with 60 additions and 57 deletions

View File

@ -108,13 +108,6 @@
</v-tooltip>
</template>
</v-checkbox>
<v-checkbox
v-model="form.scanDeep"
:label="$t('dialog.edit_library.field_scanner_deep_scan')"
hide-details
class="mx-4"
/>
</v-col>
</v-row>
<v-row>
@ -392,7 +385,6 @@ export default Vue.extend({
importLocalArtwork: true,
importBarcodeIsbn: false,
scanForceModifiedTime: false,
scanDeep: false,
repairExtensions: false,
convertToCbz: false,
emptyTrashAfterScan: false,
@ -525,7 +517,6 @@ export default Vue.extend({
this.form.importLocalArtwork = library ? library.importLocalArtwork : true
this.form.importBarcodeIsbn = library ? library.importBarcodeIsbn : false
this.form.scanForceModifiedTime = library ? library.scanForceModifiedTime : false
this.form.scanDeep = library ? library.scanDeep : false
this.form.repairExtensions = library ? library.repairExtensions : false
this.form.convertToCbz = library ? library.convertToCbz : false
this.form.emptyTrashAfterScan = library ? library.emptyTrashAfterScan : false
@ -553,7 +544,6 @@ export default Vue.extend({
importLocalArtwork: this.form.importLocalArtwork,
importBarcodeIsbn: this.form.importBarcodeIsbn,
scanForceModifiedTime: this.form.scanForceModifiedTime,
scanDeep: this.form.scanDeep,
repairExtensions: this.form.repairExtensions,
convertToCbz: this.form.convertToCbz,
emptyTrashAfterScan: this.form.emptyTrashAfterScan,

View File

@ -22,7 +22,7 @@
<v-list-item @click="markUnread" v-if="!isUnread">
<v-list-item-title>{{ $t('menu.mark_unread') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="promptDeleteBook" class="list-warning" v-if="isAdmin">
<v-list-item @click="promptDeleteBook" class="list-danger" v-if="isAdmin">
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
</v-list-item>
</v-list>

View File

@ -8,7 +8,7 @@
</template>
<v-list dense>
<v-list-item @click="promptDeleteCollection"
class="list-warning">
class="list-danger">
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
</v-list-item>
</v-list>

View File

@ -10,6 +10,9 @@
<v-list-item @click="scan">
<v-list-item-title>{{ $t('menu.scan_library_files') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="scan(true)" class="list-warning">
<v-list-item-title>{{ $t('menu.scan_library_files_deep') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="confirmAnalyzeModal = true">
<v-list-item-title>{{ $t('menu.analyze') }}</v-list-item-title>
</v-list-item>
@ -23,7 +26,7 @@
<v-list-item-title>{{ $t('menu.edit') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="promptDeleteLibrary"
class="list-warning">
class="list-danger">
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
</v-list-item>
</v-list>
@ -81,8 +84,8 @@ export default Vue.extend({
},
},
methods: {
scan() {
this.$komgaLibraries.scanLibrary(this.library)
scan(scanDeep: boolean = false) {
this.$komgaLibraries.scanLibrary(this.library, scanDeep)
},
analyze() {
this.$komgaLibraries.analyzeLibrary(this.library)

View File

@ -8,7 +8,7 @@
</template>
<v-list dense>
<v-list-item @click="promptDeleteReadList"
class="list-warning">
class="list-danger">
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
</v-list-item>
</v-list>

View File

@ -22,7 +22,7 @@
<v-list-item @click="markUnread" v-if="!isUnread">
<v-list-item-title>{{ $t('menu.mark_unread') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="promptDeleteSeries" class="list-warning" v-if="isAdmin">
<v-list-item @click="promptDeleteSeries" class="list-danger" v-if="isAdmin">
<v-list-item-title>{{ $t('menu.delete') }}</v-list-item-title>
</v-list-item>
</v-list>

View File

@ -425,7 +425,6 @@
"field_name": "Name",
"field_repair_extensions": "Automatically repair incorrect file extensions",
"field_root_folder": "Root folder",
"field_scanner_deep_scan": "Deep scan",
"field_scanner_empty_trash_after_scan": "Empty trash automatically after every scan",
"field_scanner_force_directory_modified_time": "Force directory modified time",
"field_series_cover": "Series cover",
@ -590,9 +589,9 @@
"count": "Count",
"delete_count": "Deletion count",
"delete_size": "Space saved",
"match_count": "Match count",
"size": "Size",
"total_size": "Total size",
"match_count": "Match count"
"total_size": "Total size"
},
"info": "Deleting duplicate pages will modify your files. Backup your files and use manual deletion before using automatic deletion.",
"known": "Known",
@ -752,6 +751,7 @@
"mark_unread": "Mark as unread",
"refresh_metadata": "Refresh metadata",
"scan_library_files": "Scan library files",
"scan_library_files_deep": "Scan library files (deep)",
"select_all": "Select all"
},
"metrics": {
@ -803,6 +803,7 @@
"button_cancel_all_tasks": "Cancel all tasks",
"button_empty_trash": "Empty trash for all libraries",
"button_scan_libraries": "Scan all libraries",
"button_scan_libraries_deep": "Scan all libraries (deep)",
"button_shutdown": "Shut down",
"notification_tasks_cancelled": "No tasks to cancel | One task cancelled | {count} tasks cancelled",
"section_title": "Server Management"

View File

@ -6,11 +6,11 @@ const API_LIBRARIES = '/api/v1/libraries'
export default class KomgaLibrariesService {
private http: AxiosInstance
constructor (http: AxiosInstance) {
constructor(http: AxiosInstance) {
this.http = http
}
async getLibraries (): Promise<LibraryDto[]> {
async getLibraries(): Promise<LibraryDto[]> {
try {
return (await this.http.get(API_LIBRARIES)).data
} catch (e) {
@ -22,7 +22,7 @@ export default class KomgaLibrariesService {
}
}
async getLibrary (libraryId: string): Promise<LibraryDto> {
async getLibrary(libraryId: string): Promise<LibraryDto> {
try {
return (await this.http.get(`${API_LIBRARIES}/${libraryId}`)).data
} catch (e) {
@ -34,7 +34,7 @@ export default class KomgaLibrariesService {
}
}
async postLibrary (library: LibraryCreationDto): Promise<LibraryDto> {
async postLibrary(library: LibraryCreationDto): Promise<LibraryDto> {
try {
return (await this.http.post(API_LIBRARIES, library)).data
} catch (e) {
@ -46,7 +46,7 @@ export default class KomgaLibrariesService {
}
}
async updateLibrary (libraryId: string, library: LibraryUpdateDto) {
async updateLibrary(libraryId: string, library: LibraryUpdateDto) {
try {
await this.http.put(`${API_LIBRARIES}/${libraryId}`, library)
} catch (e) {
@ -58,7 +58,7 @@ export default class KomgaLibrariesService {
}
}
async deleteLibrary (library: LibraryDto) {
async deleteLibrary(library: LibraryDto) {
try {
await this.http.delete(`${API_LIBRARIES}/${library.id}`)
} catch (e) {
@ -70,9 +70,11 @@ export default class KomgaLibrariesService {
}
}
async scanLibrary (library: LibraryDto) {
async scanLibrary(library: LibraryDto, scanDeep: boolean = false) {
try {
await this.http.post(`${API_LIBRARIES}/${library.id}/scan`)
await this.http.post(`${API_LIBRARIES}/${library.id}/scan`, null, {
params: {deep: scanDeep},
})
} catch (e) {
let msg = `An error occurred while trying to scan library '${library.name}'`
if (e.response.data.message) {
@ -82,7 +84,7 @@ export default class KomgaLibrariesService {
}
}
async analyzeLibrary (library: LibraryDto) {
async analyzeLibrary(library: LibraryDto) {
try {
await this.http.post(`${API_LIBRARIES}/${library.id}/analyze`)
} catch (e) {
@ -94,7 +96,7 @@ export default class KomgaLibrariesService {
}
}
async refreshMetadata (library: LibraryDto) {
async refreshMetadata(library: LibraryDto) {
try {
await this.http.post(`${API_LIBRARIES}/${library.id}/metadata/refresh`)
} catch (e) {
@ -106,7 +108,7 @@ export default class KomgaLibrariesService {
}
}
async emptyTrash (library: LibraryDto) {
async emptyTrash(library: LibraryDto) {
try {
await this.http.post(`${API_LIBRARIES}/${library.id}/empty-trash`)
} catch (e) {

View File

@ -1,5 +1,13 @@
.list-danger:hover {
background: #ff5252;
}
.list-danger:hover .v-list-item__title {
color: white;
}
.list-warning:hover {
background: #F44336;
background: #fb8c00;
}
.list-warning:hover .v-list-item__title {

View File

@ -14,7 +14,6 @@ export interface LibraryCreationDto {
importLocalArtwork: boolean,
importBarcodeIsbn: boolean,
scanForceModifiedTime: boolean,
scanDeep: boolean,
repairExtensions: boolean,
convertToCbz: boolean,
emptyTrashAfterScan: boolean,
@ -38,7 +37,6 @@ export interface LibraryUpdateDto {
importLocalArtwork: boolean,
importBarcodeIsbn: boolean,
scanForceModifiedTime: boolean,
scanDeep: boolean,
repairExtensions: boolean,
convertToCbz: boolean,
emptyTrashAfterScan: boolean,
@ -63,7 +61,6 @@ export interface LibraryDto {
importLocalArtwork: boolean,
importBarcodeIsbn: boolean,
scanForceModifiedTime: boolean,
scanDeep: boolean,
repairExtensions: boolean,
convertToCbz: boolean,
emptyTrashAfterScan: boolean,

View File

@ -7,6 +7,12 @@
<v-col cols="auto">
<v-btn @click="scanAllLibraries">{{ $t('server.server_management.button_scan_libraries') }}</v-btn>
</v-col>
<v-col cols="auto">
<v-btn @click="scanAllLibraries(true)"
color="warning"
>{{ $t('server.server_management.button_scan_libraries_deep') }}
</v-btn>
</v-col>
<v-col cols="auto">
<v-btn @click="confirmEmptyTrash = true">{{ $t('server.server_management.button_empty_trash') }}</v-btn>
</v-col>
@ -68,9 +74,9 @@ export default Vue.extend({
this.$komgaLibraries.emptyTrash(library)
})
},
scanAllLibraries() {
scanAllLibraries(scanDeep: boolean = false) {
this.libraries.forEach(library => {
this.$komgaLibraries.scanLibrary(library)
this.$komgaLibraries.scanLibrary(library, scanDeep)
})
},
async cancelAllTasks() {

View File

@ -0,0 +1,2 @@
ALTER TABLE LIBRARY
RENAME COLUMN SCAN_DEEP to _UNUSED;

View File

@ -16,9 +16,9 @@ sealed class Task(priority: Int = DEFAULT_PRIORITY, val groupId: String? = null)
abstract fun uniqueId(): String
val priority = priority.coerceIn(0, 9)
class ScanLibrary(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
override fun uniqueId() = "SCAN_LIBRARY_$libraryId"
override fun toString(): String = "ScanLibrary(libraryId='$libraryId', priority='$priority')"
class ScanLibrary(val libraryId: String, val scanDeep: Boolean, priority: Int = DEFAULT_PRIORITY) : Task(priority) {
override fun uniqueId() = "SCAN_LIBRARY_${libraryId}_DEEP_$scanDeep"
override fun toString(): String = "ScanLibrary(libraryId='$libraryId', scanDeep='$scanDeep', priority='$priority')"
}
class FindBooksToConvert(val libraryId: String, priority: Int = DEFAULT_PRIORITY) : Task(priority) {

View File

@ -42,8 +42,8 @@ class TaskEmitter(
libraryRepository.findAll().forEach { scanLibrary(it.id) }
}
fun scanLibrary(libraryId: String, priority: Int = DEFAULT_PRIORITY) {
submitTask(Task.ScanLibrary(libraryId, priority))
fun scanLibrary(libraryId: String, scanDeep: Boolean = false, priority: Int = DEFAULT_PRIORITY) {
submitTask(Task.ScanLibrary(libraryId, scanDeep, priority))
}
fun emptyTrash(libraryId: String, priority: Int = DEFAULT_PRIORITY) {

View File

@ -57,7 +57,7 @@ class TaskHandler(
when (task) {
is Task.ScanLibrary ->
libraryRepository.findByIdOrNull(task.libraryId)?.let { library ->
libraryContentLifecycle.scanRootFolder(library)
libraryContentLifecycle.scanRootFolder(library, task.scanDeep)
taskEmitter.analyzeUnknownAndOutdatedBooks(library)
taskEmitter.repairExtensions(library, LOW_PRIORITY)
taskEmitter.findBooksToConvert(library, LOWEST_PRIORITY)

View File

@ -21,7 +21,6 @@ data class Library(
val importLocalArtwork: Boolean = true,
val importBarcodeIsbn: Boolean = true,
val scanForceModifiedTime: Boolean = false,
val scanDeep: Boolean = false,
val repairExtensions: Boolean = false,
val convertToCbz: Boolean = false,
val emptyTrashAfterScan: Boolean = false,

View File

@ -62,7 +62,7 @@ class LibraryContentLifecycle(
private val eventPublisher: EventPublisher,
) {
fun scanRootFolder(library: Library) {
fun scanRootFolder(library: Library, scanDeep: Boolean = false) {
logger.info { "Updating library: $library" }
measureTime {
val scanResult = try {
@ -136,7 +136,7 @@ class LibraryContentLifecycle(
logger.info { "Series changed on disk, updating: $existingSeries" }
seriesRepository.update(existingSeries.copy(fileLastModified = newSeries.fileLastModified, deletedDate = null))
}
if (library.scanDeep || seriesChanged) {
if (scanDeep || seriesChanged) {
// update list of books with existing entities if they exist
val existingBooks = bookRepository.findAllBySeriesId(existingSeries.id)
logger.debug { "Existing books: $existingBooks" }

View File

@ -72,7 +72,6 @@ class LibraryDao(
.set(l.IMPORT_LOCAL_ARTWORK, library.importLocalArtwork)
.set(l.IMPORT_BARCODE_ISBN, library.importBarcodeIsbn)
.set(l.SCAN_FORCE_MODIFIED_TIME, library.scanForceModifiedTime)
.set(l.SCAN_DEEP, library.scanDeep)
.set(l.REPAIR_EXTENSIONS, library.repairExtensions)
.set(l.CONVERT_TO_CBZ, library.convertToCbz)
.set(l.EMPTY_TRASH_AFTER_SCAN, library.emptyTrashAfterScan)
@ -100,7 +99,6 @@ class LibraryDao(
.set(l.IMPORT_LOCAL_ARTWORK, library.importLocalArtwork)
.set(l.IMPORT_BARCODE_ISBN, library.importBarcodeIsbn)
.set(l.SCAN_FORCE_MODIFIED_TIME, library.scanForceModifiedTime)
.set(l.SCAN_DEEP, library.scanDeep)
.set(l.REPAIR_EXTENSIONS, library.repairExtensions)
.set(l.CONVERT_TO_CBZ, library.convertToCbz)
.set(l.EMPTY_TRASH_AFTER_SCAN, library.emptyTrashAfterScan)
@ -131,7 +129,6 @@ class LibraryDao(
importLocalArtwork = importLocalArtwork,
importBarcodeIsbn = importBarcodeIsbn,
scanForceModifiedTime = scanForceModifiedTime,
scanDeep = scanDeep,
repairExtensions = repairExtensions,
convertToCbz = convertToCbz,
emptyTrashAfterScan = emptyTrashAfterScan,

View File

@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
@ -90,7 +91,6 @@ class LibraryController(
importLocalArtwork = library.importLocalArtwork,
importBarcodeIsbn = library.importBarcodeIsbn,
scanForceModifiedTime = library.scanForceModifiedTime,
scanDeep = library.scanDeep,
repairExtensions = library.repairExtensions,
convertToCbz = library.convertToCbz,
emptyTrashAfterScan = library.emptyTrashAfterScan,
@ -136,7 +136,6 @@ class LibraryController(
importLocalArtwork = library.importLocalArtwork,
importBarcodeIsbn = library.importBarcodeIsbn,
scanForceModifiedTime = library.scanForceModifiedTime,
scanDeep = library.scanDeep,
repairExtensions = library.repairExtensions,
convertToCbz = library.convertToCbz,
emptyTrashAfterScan = library.emptyTrashAfterScan,
@ -173,9 +172,12 @@ class LibraryController(
@PostMapping("{libraryId}/scan")
@PreAuthorize("hasRole('$ROLE_ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
fun scan(@PathVariable libraryId: String) {
fun scan(
@PathVariable libraryId: String,
@RequestParam(required = false) deep: Boolean = false,
) {
libraryRepository.findByIdOrNull(libraryId)?.let { library ->
taskEmitter.scanLibrary(library.id, HIGHEST_PRIORITY)
taskEmitter.scanLibrary(library.id, deep, HIGHEST_PRIORITY)
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}

View File

@ -16,7 +16,6 @@ data class LibraryCreationDto(
val importLocalArtwork: Boolean = true,
val importBarcodeIsbn: Boolean = true,
val scanForceModifiedTime: Boolean = false,
val scanDeep: Boolean = false,
val repairExtensions: Boolean = false,
val convertToCbz: Boolean = false,
val emptyTrashAfterScan: Boolean = false,

View File

@ -18,7 +18,6 @@ data class LibraryDto(
val importLocalArtwork: Boolean,
val importBarcodeIsbn: Boolean,
val scanForceModifiedTime: Boolean,
val scanDeep: Boolean,
val repairExtensions: Boolean,
val convertToCbz: Boolean,
val emptyTrashAfterScan: Boolean,
@ -44,7 +43,6 @@ fun Library.toDto(includeRoot: Boolean) = LibraryDto(
importLocalArtwork = importLocalArtwork,
importBarcodeIsbn = importBarcodeIsbn,
scanForceModifiedTime = scanForceModifiedTime,
scanDeep = scanDeep,
repairExtensions = repairExtensions,
convertToCbz = convertToCbz,
emptyTrashAfterScan = emptyTrashAfterScan,

View File

@ -16,7 +16,6 @@ data class LibraryUpdateDto(
val importLocalArtwork: Boolean,
val importBarcodeIsbn: Boolean,
val scanForceModifiedTime: Boolean,
val scanDeep: Boolean,
val repairExtensions: Boolean,
val convertToCbz: Boolean,
val emptyTrashAfterScan: Boolean,