mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
error handling
This commit is contained in:
parent
b4fded0d40
commit
5657899749
2 changed files with 167 additions and 33 deletions
|
|
@ -79,8 +79,16 @@ export default {
|
||||||
newPath = item.media?.metadata?.title || 'Unknown Podcast'
|
newPath = item.media?.metadata?.title || 'Unknown Podcast'
|
||||||
} else {
|
} else {
|
||||||
const parts = []
|
const parts = []
|
||||||
if (item.media?.metadata?.authorName) parts.push(item.media.metadata.authorName)
|
// Use only the first author
|
||||||
if (item.media?.metadata?.seriesName) parts.push(item.media.metadata.seriesName)
|
const authors = item.media?.metadata?.authors
|
||||||
|
if (authors && authors.length > 0) {
|
||||||
|
parts.push(authors[0].name)
|
||||||
|
}
|
||||||
|
if (item.media?.metadata?.seriesName) {
|
||||||
|
// Remove sequence number from series name (e.g., "Series Name #1" -> "Series Name")
|
||||||
|
const seriesNameWithoutSequence = item.media.metadata.seriesName.replace(/\s*#\d+$/, '')
|
||||||
|
parts.push(seriesNameWithoutSequence)
|
||||||
|
}
|
||||||
if (item.media?.metadata?.title) parts.push(item.media.metadata.title)
|
if (item.media?.metadata?.title) parts.push(item.media.metadata.title)
|
||||||
newPath = parts.join(' / ') || 'Unknown Item'
|
newPath = parts.join(' / ') || 'Unknown Item'
|
||||||
}
|
}
|
||||||
|
|
@ -140,10 +148,15 @@ export default {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
|
|
||||||
if (result.successCount > 0 && result.errorCount === 0) {
|
if (result.successCount > 0 && result.warningCount === 0 && result.errorCount === 0) {
|
||||||
this.$toast.success(this.$getString('MessageBatchReorganizeSuccess', [result.successCount]))
|
this.$toast.success(this.$getString('MessageBatchReorganizeSuccess', [result.successCount]))
|
||||||
} else if (result.successCount > 0 && result.errorCount > 0) {
|
} else if ((result.successCount > 0 || result.warningCount > 0) && result.errorCount === 0) {
|
||||||
this.$toast.warning(this.$getString('MessageBatchReorganizePartial', [result.successCount, result.errorCount]))
|
const message = result.warningCount > 0
|
||||||
|
? this.$getString('MessageBatchReorganizePartial', [result.successCount, result.warningCount])
|
||||||
|
: this.$getString('MessageBatchReorganizeSuccess', [result.successCount])
|
||||||
|
this.$toast.warning(message)
|
||||||
|
} else if (result.errorCount > 0) {
|
||||||
|
this.$toast.error(this.$getString('MessageBatchReorganizePartial', [result.successCount + result.warningCount, result.errorCount]))
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Failed to reorganize files')
|
this.$toast.error('Failed to reorganize files')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1207,6 +1207,55 @@ class LibraryItemController {
|
||||||
|
|
||||||
Logger.info(`[LibraryItemController] Reorganizing item ${itemId}`)
|
Logger.info(`[LibraryItemController] Reorganizing item ${itemId}`)
|
||||||
|
|
||||||
|
// Get library folder early for error directory path
|
||||||
|
const library = await Database.libraryModel.findByIdWithFolders(libraryId)
|
||||||
|
if (!library || !library.libraryFolders || !library.libraryFolders.length) {
|
||||||
|
return { success: false, error: 'Library not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const libraryFolder = library.libraryFolders.find((f) => currentPath.startsWith(f.path))
|
||||||
|
if (!libraryFolder) {
|
||||||
|
return { success: false, error: 'Library folder not found' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh metadata before reorganizing
|
||||||
|
let hasMatchWarning = false
|
||||||
|
Logger.info(`[LibraryItemController] Refreshing metadata for item ${itemId}`)
|
||||||
|
try {
|
||||||
|
const matchResult = await Scanner.quickMatchLibraryItem(this, libraryItem, {
|
||||||
|
overrideDetails: true
|
||||||
|
})
|
||||||
|
if (matchResult.updated) {
|
||||||
|
Logger.info(`[LibraryItemController] Metadata updated for item ${itemId}`)
|
||||||
|
} else if (matchResult.warning) {
|
||||||
|
hasMatchWarning = true
|
||||||
|
Logger.warn(`[LibraryItemController] Metadata match warning for item ${itemId}: ${matchResult.warning}`)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Logger.warn(`[LibraryItemController] Failed to refresh metadata for item ${itemId}: ${err.message}`)
|
||||||
|
// Continue anyway - use existing metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// If match failed, move to error directory
|
||||||
|
if (hasMatchWarning) {
|
||||||
|
try {
|
||||||
|
const errorDirPath = Path.join(libraryFolder.path, '__Reorganize_Errors')
|
||||||
|
await fs.ensureDir(errorDirPath)
|
||||||
|
|
||||||
|
const errorPath = Path.join(errorDirPath, Path.basename(currentPath))
|
||||||
|
Logger.warn(`[LibraryItemController] Moving unmatched item to error directory: ${currentPath} -> ${errorPath}`)
|
||||||
|
await fs.move(currentPath, errorPath, { overwrite: false })
|
||||||
|
|
||||||
|
// Update database
|
||||||
|
await Database.libraryItemModel.update({ path: errorPath }, { where: { id: itemId } })
|
||||||
|
Logger.info(`[LibraryItemController] Moved unmatched item ${itemId} to error directory`)
|
||||||
|
|
||||||
|
return { success: true, warning: 'Item moved to error directory - no metadata match found' }
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Failed to move unmatched item to error directory: ${err.message}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reload media with authors and series for books
|
// Reload media with authors and series for books
|
||||||
let media = libraryItem.media
|
let media = libraryItem.media
|
||||||
if (isBook) {
|
if (isBook) {
|
||||||
|
|
@ -1236,27 +1285,19 @@ class LibraryItemController {
|
||||||
return { success: false, error: 'Media not found' }
|
return { success: false, error: 'Media not found' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get library folder
|
|
||||||
const library = await Database.libraryModel.findByIdWithFolders(libraryId)
|
|
||||||
if (!library || !library.libraryFolders || !library.libraryFolders.length) {
|
|
||||||
return { success: false, error: 'Library not found' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const libraryFolder = library.libraryFolders.find((f) => currentPath.startsWith(f.path))
|
|
||||||
if (!libraryFolder) {
|
|
||||||
return { success: false, error: 'Library folder not found' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build new path
|
// Build new path
|
||||||
const parts = []
|
const parts = []
|
||||||
if (isPodcast) {
|
if (isPodcast) {
|
||||||
parts.push(media.title)
|
parts.push(media.title)
|
||||||
} else if (isBook) {
|
} else if (isBook) {
|
||||||
if (media.authorName) {
|
// Use only the first author
|
||||||
parts.push(media.authorName)
|
if (media.authors && media.authors.length > 0) {
|
||||||
|
parts.push(media.authors[0].name)
|
||||||
}
|
}
|
||||||
if (media.seriesName) {
|
if (media.seriesName) {
|
||||||
parts.push(media.seriesName)
|
// Remove sequence number from series name (e.g., "Series Name #1" -> "Series Name")
|
||||||
|
const seriesNameWithoutSequence = media.seriesName.replace(/\s*#\d+$/, '')
|
||||||
|
parts.push(seriesNameWithoutSequence)
|
||||||
}
|
}
|
||||||
parts.push(media.title)
|
parts.push(media.title)
|
||||||
}
|
}
|
||||||
|
|
@ -1274,30 +1315,100 @@ class LibraryItemController {
|
||||||
return { success: false, error: 'Current directory does not exist' }
|
return { success: false, error: 'Current directory does not exist' }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if target already exists with different item
|
// Check if target path has conflicts
|
||||||
const targetExists = await fs.pathExists(newPath)
|
const targetExists = await fs.pathExists(newPath)
|
||||||
if (targetExists) {
|
if (targetExists) {
|
||||||
|
// Check if this item already owns the target directory
|
||||||
const existingItem = await Database.libraryItemModel.findOne({ where: { path: newPath } })
|
const existingItem = await Database.libraryItemModel.findOne({ where: { path: newPath } })
|
||||||
if (existingItem && existingItem.id !== itemId) {
|
if (existingItem) {
|
||||||
return { success: false, error: 'Target directory already contains a different item' }
|
if (existingItem.id === itemId) {
|
||||||
|
// Item already reorganized to this location - skip
|
||||||
|
Logger.info(`[LibraryItemController] Item ${itemId} already at target path, skipping`)
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
// Different item owns this directory - move it to error directory
|
||||||
|
try {
|
||||||
|
const errorDirPath = Path.join(libraryFolder.path, '__Reorganize_Errors')
|
||||||
|
await fs.ensureDir(errorDirPath)
|
||||||
|
|
||||||
|
const existingItemPath = existingItem.path
|
||||||
|
const errorPath = Path.join(errorDirPath, Path.basename(existingItemPath))
|
||||||
|
|
||||||
|
Logger.warn(`[LibraryItemController] Moving conflicting item to error directory: ${existingItemPath} -> ${errorPath}`)
|
||||||
|
await fs.move(existingItemPath, errorPath, { overwrite: false })
|
||||||
|
|
||||||
|
// Update the conflicting item's path in database
|
||||||
|
await Database.libraryItemModel.update({ path: errorPath }, { where: { id: existingItem.id } })
|
||||||
|
Logger.info(`[LibraryItemController] Moved conflicting item ${existingItem.id} to error directory`)
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Failed to move conflicting item: ${err.message}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target exists but isn't a registered item - check if it's empty
|
||||||
|
try {
|
||||||
|
const targetContents = await fs.readdir(newPath)
|
||||||
|
if (targetContents.length > 0) {
|
||||||
|
// Move conflicting directory to error directory
|
||||||
|
try {
|
||||||
|
const errorDirPath = Path.join(libraryFolder.path, '__Reorganize_Errors')
|
||||||
|
await fs.ensureDir(errorDirPath)
|
||||||
|
const errorPath = Path.join(errorDirPath, Path.basename(newPath) + '_conflict')
|
||||||
|
Logger.warn(`[LibraryItemController] Moving conflicting directory to error: ${newPath} -> ${errorPath}`)
|
||||||
|
await fs.move(newPath, errorPath, { overwrite: false })
|
||||||
|
Logger.info(`[LibraryItemController] Moved conflicting directory to error path`)
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Cannot handle conflicting directory: ${err.message}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Cannot access target directory: ${err.message}` }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create target directory
|
// Create target directory
|
||||||
await fs.ensureDir(newPath)
|
try {
|
||||||
|
await fs.ensureDir(newPath)
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Cannot create target directory: ${err.message}` }
|
||||||
|
}
|
||||||
|
|
||||||
// Read and move files
|
// Read and move files
|
||||||
const files = await fs.readdir(currentPath)
|
try {
|
||||||
Logger.info(`[LibraryItemController] Moving ${files.length} files for item ${itemId}`)
|
const files = await fs.readdir(currentPath)
|
||||||
|
Logger.info(`[LibraryItemController] Moving ${files.length} files for item ${itemId}`)
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const src = Path.join(currentPath, file)
|
const src = Path.join(currentPath, file)
|
||||||
const dst = Path.join(newPath, file)
|
const dst = Path.join(newPath, file)
|
||||||
await fs.move(src, dst, { overwrite: true })
|
try {
|
||||||
|
await fs.move(src, dst, { overwrite: true })
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Failed to move file ${file}: ${err.message}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return { success: false, error: `Failed to read source directory: ${err.message}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
await Database.libraryItemModel.update({ path: newPath }, { where: { id: itemId } })
|
try {
|
||||||
|
await Database.libraryItemModel.update({ path: newPath }, { where: { id: itemId } })
|
||||||
|
} catch (err) {
|
||||||
|
// Try to rollback files
|
||||||
|
try {
|
||||||
|
const movedFiles = await fs.readdir(newPath)
|
||||||
|
for (const file of movedFiles) {
|
||||||
|
const src = Path.join(newPath, file)
|
||||||
|
const dst = Path.join(currentPath, file)
|
||||||
|
await fs.move(src, dst, { overwrite: true })
|
||||||
|
}
|
||||||
|
} catch (rollbackErr) {
|
||||||
|
Logger.error(`[LibraryItemController] Failed to rollback files: ${rollbackErr.message}`)
|
||||||
|
}
|
||||||
|
return { success: false, error: `Database update failed: ${err.message}` }
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up old directory
|
// Clean up old directory
|
||||||
try {
|
try {
|
||||||
|
|
@ -1358,15 +1469,23 @@ class LibraryItemController {
|
||||||
// Process in background
|
// Process in background
|
||||||
Logger.info(`[LibraryItemController] Starting batch reorganize for ${itemsToReorganize.length} items`)
|
Logger.info(`[LibraryItemController] Starting batch reorganize for ${itemsToReorganize.length} items`)
|
||||||
let successCount = 0
|
let successCount = 0
|
||||||
|
let warningCount = 0
|
||||||
let errorCount = 0
|
let errorCount = 0
|
||||||
const errors = []
|
const errors = []
|
||||||
|
const warnings = []
|
||||||
|
|
||||||
for (const libraryItem of itemsToReorganize) {
|
for (const libraryItem of itemsToReorganize) {
|
||||||
// Get the controller instance to call helper methods
|
// Get the controller instance to call helper methods
|
||||||
const result = await controller.reorganizeFilesForItem(libraryItem)
|
const result = await controller.reorganizeFilesForItem(libraryItem)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
successCount++
|
if (result.warning) {
|
||||||
Logger.info(`[LibraryItemController] Successfully reorganized: ${libraryItem.media.title}`)
|
warningCount++
|
||||||
|
warnings.push({ id: libraryItem.id, title: libraryItem.media.title, warning: result.warning })
|
||||||
|
Logger.warn(`[LibraryItemController] Reorganized with warning: ${libraryItem.media.title} - ${result.warning}`)
|
||||||
|
} else {
|
||||||
|
successCount++
|
||||||
|
Logger.info(`[LibraryItemController] Successfully reorganized: ${libraryItem.media.title}`)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errorCount++
|
errorCount++
|
||||||
errors.push({ id: libraryItem.id, title: libraryItem.media.title, error: result.error })
|
errors.push({ id: libraryItem.id, title: libraryItem.media.title, error: result.error })
|
||||||
|
|
@ -1374,12 +1493,14 @@ class LibraryItemController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info(`[LibraryItemController] Batch reorganize complete: ${successCount} succeeded, ${errorCount} failed`)
|
Logger.info(`[LibraryItemController] Batch reorganize complete: ${successCount} succeeded, ${warningCount} moved to error dir, ${errorCount} failed`)
|
||||||
|
|
||||||
// Notify user of completion
|
// Notify user of completion
|
||||||
SocketAuthority.clientEmitter(req.user.id, 'batch_reorganize_complete', {
|
SocketAuthority.clientEmitter(req.user.id, 'batch_reorganize_complete', {
|
||||||
successCount,
|
successCount,
|
||||||
|
warningCount,
|
||||||
errorCount,
|
errorCount,
|
||||||
|
warnings,
|
||||||
errors,
|
errors,
|
||||||
total: itemsToReorganize.length
|
total: itemsToReorganize.length
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue