Add rescan feature

This commit is contained in:
Tiberiu Ichim 2026-02-06 14:25:20 +02:00
parent d358b82b17
commit e433cf9c05
4 changed files with 55 additions and 11 deletions

View file

@ -56,11 +56,27 @@ POST /api/items/:id/move
### Localization Strings Added
- `ButtonMove`, `ButtonMoveToLibrary`
- `ButtonMove`, `ButtonMoveToLibrary`, `ButtonReScan`
- `LabelMoveToLibrary`, `LabelMovingItem`
- `LabelSelectTargetLibrary`, `LabelSelectTargetFolder`
- `MessageNoCompatibleLibraries`
- `ToastItemMoved`, `ToastItemMoveFailed`
- `ToastItemMoved`, `ToastItemMoveFailed`, `ToastRescanUpdated`, `ToastRescanUpToDate`, `ToastRescanFailed`
---
## Post-Move Rescan Feature
In addition to automated handling during moves, a manual "Re-scan" feature has been enhanced and exposed to users with move permissions.
### Why it's needed
If a book was moved before the recent logic enhancements, it might still point to authors or series in its _old_ library. The "Re-scan" action fixes this.
### Logic Improvements
- During a rescan, the system now validates that all linked authors and series belong to the library the book is currently in.
- If a link to an author/series in a different library is found, it is removed.
- The system then re-evaluates the file metadata and links the book to the correct author/series in its _current_ library (creating them if they don't exist).
---

View file

@ -566,7 +566,7 @@ export default {
text: this.$strings.HeaderMatch
})
}
if (this.userIsAdminOrUp && !this.isFile) {
if ((this.userIsAdminOrUp || this.userCanDelete) && !this.isFile) {
items.push({
func: 'rescan',
text: this.$strings.ButtonReScan

View file

@ -424,6 +424,10 @@ export default {
}
if (this.userCanDelete) {
items.push({
text: this.$strings.ButtonReScan,
action: 'rescan'
})
items.push({
text: this.$strings.ButtonMoveToLibrary,
action: 'move'
@ -760,6 +764,28 @@ export default {
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
rescan() {
this.processing = true
this.$axios
.$post(`/api/items/${this.libraryItemId}/scan`)
.then((data) => {
var result = data.result
if (!result) {
this.$toast.error(this.$getString('ToastRescanFailed', [this.title]))
} else if (result === 'UPDATED') {
this.$toast.success(this.$strings.ToastRescanUpdated)
} else if (result === 'UPTODATE') {
this.$toast.success(this.$strings.ToastRescanUpToDate)
}
})
.catch((error) => {
console.error('Failed to rescan', error)
this.$toast.error(this.$getString('ToastRescanFailed', [this.title]))
})
.finally(() => {
this.processing = false
})
},
contextMenuAction({ action, data }) {
if (action === 'collections') {
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
@ -775,6 +801,8 @@ export default {
this.downloadLibraryItem()
} else if (action === 'delete') {
this.deleteLibraryItem()
} else if (action === 'rescan') {
this.rescan()
} else if (action === 'move') {
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
this.$store.commit('globals/setShowMoveToLibraryModal', true)

View file

@ -220,7 +220,7 @@ class BookScanner {
if (key === 'authors') {
// Check for authors added
for (const authorName of bookMetadata.authors) {
if (!media.authors.some((au) => au.name === authorName)) {
if (!media.authors.some((au) => au.name === authorName && au.libraryId === libraryItemData.libraryId)) {
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
if (existingAuthorId) {
await Database.bookAuthorModel.create({
@ -242,11 +242,11 @@ class BookScanner {
}
}
}
// Check for authors removed
// Check for authors removed (including those from wrong library)
for (const author of media.authors) {
if (!bookMetadata.authors.includes(author.name)) {
if (!bookMetadata.authors.includes(author.name) || author.libraryId !== libraryItemData.libraryId) {
await author.bookAuthor.destroy()
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed author "${author.name}"`)
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed author "${author.name}"${author.libraryId !== libraryItemData.libraryId ? ' (wrong library)' : ''}`)
authorsUpdated = true
bookAuthorsRemoved.push(author.id)
}
@ -254,7 +254,7 @@ class BookScanner {
} else if (key === 'series') {
// Check for series added
for (const seriesObj of bookMetadata.series) {
const existingBookSeries = media.series.find((se) => se.name === seriesObj.name)
const existingBookSeries = media.series.find((se) => se.name === seriesObj.name && se.libraryId === libraryItemData.libraryId)
if (!existingBookSeries) {
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
if (existingSeriesId) {
@ -283,11 +283,11 @@ class BookScanner {
await existingBookSeries.bookSeries.save()
}
}
// Check for series removed
// Check for series removed (including those from wrong library)
for (const series of media.series) {
if (!bookMetadata.series.some((se) => se.name === series.name)) {
if (!bookMetadata.series.some((se) => se.name === series.name) || series.libraryId !== libraryItemData.libraryId) {
await series.bookSeries.destroy()
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"${series.libraryId !== libraryItemData.libraryId ? ' (wrong library)' : ''}`)
seriesUpdated = true
bookSeriesRemoved.push(series.id)
}