mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-16 06:11:38 +00:00
Added functionality to rename book folders.
This commit is contained in:
parent
f9e6655359
commit
8ccaec3d9f
12 changed files with 652 additions and 148 deletions
266
server/managers/LibraryItemFolderManager.js
Normal file
266
server/managers/LibraryItemFolderManager.js
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
|
||||
|
||||
const fs = require('../libs/fsExtra')
|
||||
|
||||
const Logger = require('../Logger')
|
||||
const LibrarySettings = require('../objects/settings/LibrarySettings')
|
||||
const fileUtils = require('../utils/fileUtils')
|
||||
|
||||
|
||||
const { getLibraryItemFileData } = require('../utils/scandir')
|
||||
|
||||
const LoggerTag = `[LibraryItemFolderManager]`
|
||||
|
||||
|
||||
class LibraryItemFolderManager {
|
||||
constructor(db, watcher) {
|
||||
this.db = db
|
||||
this.watcher = watcher
|
||||
this.directorySeperatorChar = '/'
|
||||
}
|
||||
parseRenameFormat(libraryItem, format, includeTemplateSample) {
|
||||
const library = this.db.libraries.find(lib => lib.id === libraryItem.libraryId)
|
||||
const folder = library.folders.find(f => f.id === libraryItem.folderId)
|
||||
const basePath = fileUtils.filePathToPOSIX(folder.fullPath + this.directorySeperatorChar)
|
||||
if (format == "") {
|
||||
format = global.ServerSettings.defaultRenameString
|
||||
}
|
||||
const newRelativeDirectoryRaw = fileUtils.filePathToPOSIX(this.parseTemplateString(format, libraryItem))
|
||||
const directorySplit = newRelativeDirectoryRaw.split(this.directorySeperatorChar)
|
||||
var newRelativeDirectory = ""
|
||||
for (const index in directorySplit) {
|
||||
if (directorySplit[index]) { //make sure they dont put a bunch of //// in a row
|
||||
const newProposedName = fileUtils.sanitizeFilename(directorySplit[index])
|
||||
if (newProposedName) { //make sure there are at least some valid characters
|
||||
if (newRelativeDirectory) newRelativeDirectory += this.directorySeperatorChar //add a slash if it is not the first element
|
||||
newRelativeDirectory += newProposedName
|
||||
}
|
||||
}
|
||||
}
|
||||
var santizedLogString = ""
|
||||
if (newRelativeDirectory != newRelativeDirectoryRaw) {
|
||||
santizedLogString = ` and then sanitized to ${newRelativeDirectory}`
|
||||
}
|
||||
Logger.info(`${LoggerTag} ${format} processed to ${newRelativeDirectoryRaw}${santizedLogString}`)
|
||||
|
||||
const newDirectory = basePath + newRelativeDirectory
|
||||
const oldRelativeDirectory = libraryItem.relPath
|
||||
const oldDirectory = libraryItem.path
|
||||
|
||||
|
||||
var templateSample = null
|
||||
if (includeTemplateSample) {
|
||||
templateSample = {
|
||||
bookAuthor: fileUtils.sanitizeFilename(this.parseTemplateString("\$bookAuthor", libraryItem)),
|
||||
bookTitle: fileUtils.sanitizeFilename(this.parseTemplateString("\$bookTitle", libraryItem)),
|
||||
explicit: fileUtils.sanitizeFilename(this.parseTemplateString("\$explicit", libraryItem)),
|
||||
subtitle: fileUtils.sanitizeFilename(this.parseTemplateString("\$subtitle", libraryItem)),
|
||||
narrator: fileUtils.sanitizeFilename(this.parseTemplateString("\$narrator", libraryItem)),
|
||||
genres: fileUtils.sanitizeFilename(this.parseTemplateString("\$genres", libraryItem)),
|
||||
publishedYear: fileUtils.sanitizeFilename(this.parseTemplateString("\$publishedYear", libraryItem)),
|
||||
publishedDate: fileUtils.sanitizeFilename(this.parseTemplateString("\$publishedDate", libraryItem)),
|
||||
publisher: fileUtils.sanitizeFilename(this.parseTemplateString("\$publisher", libraryItem)),
|
||||
isbn: fileUtils.sanitizeFilename(this.parseTemplateString("\$isbn", libraryItem)),
|
||||
asin: fileUtils.sanitizeFilename(this.parseTemplateString("\$asin", libraryItem)),
|
||||
language: fileUtils.sanitizeFilename(this.parseTemplateString("\$language", libraryItem)),
|
||||
seriesSummary: fileUtils.sanitizeFilename(this.parseTemplateString("\$seriesSummary", libraryItem)),
|
||||
seriesName: fileUtils.sanitizeFilename(this.parseTemplateString("\$seriesName", libraryItem)),
|
||||
seriesNamelbr0rbr: fileUtils.sanitizeFilename(this.parseTemplateString("\$seriesName[0]", libraryItem)),
|
||||
seriesSequence: fileUtils.sanitizeFilename(this.parseTemplateString("\$seriesSequence", libraryItem)),
|
||||
seriesSequencelbr0rbr: fileUtils.sanitizeFilename(this.parseTemplateString("\$seriesSequence[0]", libraryItem))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
basePath: basePath,
|
||||
newRelativeDirectory: newRelativeDirectory,
|
||||
newDirectory: newDirectory,
|
||||
oldRelativeDirectory: oldRelativeDirectory,
|
||||
oldDirectory: oldDirectory,
|
||||
formatUsed: format,
|
||||
directorySeperatorChar: this.directorySeperatorChar,
|
||||
templateSample: templateSample
|
||||
}
|
||||
}
|
||||
async startRenameBookFolder(libraryItem, format) {
|
||||
Logger.info(`${LoggerTag} Start rename for ${libraryItem.id}`)
|
||||
const newPaths = this.parseRenameFormat(libraryItem, format)
|
||||
const basePath = newPaths.basePath
|
||||
const newRelativeDirectory = newPaths.newRelativeDirectory
|
||||
const newDirectory = newPaths.newDirectory
|
||||
const oldRelativeDirectory = newPaths.oldRelativeDirectory
|
||||
const oldDirectory = newPaths.oldDirectory
|
||||
|
||||
if (newDirectory === oldDirectory) {
|
||||
Logger.info(`${LoggerTag} New folder (${newDirectory}) equal to current folder(${oldDirectory}), aborting rename`)
|
||||
return {
|
||||
msg: "Folder not Renamed",
|
||||
success: false,
|
||||
details: `New folder (${newDirectory}) equal to current folder(${oldDirectory}), aborting rename`
|
||||
}
|
||||
}
|
||||
|
||||
//TODO - Check if target directory is a sub-directory of another book, fail if so.
|
||||
|
||||
Logger.info(`${LoggerTag} Renaming ${oldDirectory} to ${newDirectory}`)
|
||||
this.watcher.addIgnoreDir(oldDirectory)
|
||||
this.watcher.addIgnoreDir(newDirectory)
|
||||
if (!await fs.pathExists(newDirectory)) {
|
||||
|
||||
const li = this.db.libraryItems.find(li => newDirectory.startsWith(li.path))
|
||||
if (li) {
|
||||
return {
|
||||
msg: "Folder is sub path of another book",
|
||||
success: false,
|
||||
details: `The path you are trying to move to ${newDirectory} is a sub-directory of another book - ${li.media.metadata.title}(${li.id}) stored in ${li.path}`,
|
||||
}
|
||||
}
|
||||
|
||||
var moveStatus = await fs.move(oldDirectory, newDirectory, { overwrite: false }).catch((err) => {
|
||||
return err
|
||||
})
|
||||
if (moveStatus != null) {
|
||||
Logger.error(`${LoggerTag} Failed folder rename ${moveStatus}`)
|
||||
return {
|
||||
msg: "Failed to move folder",
|
||||
success: false,
|
||||
details: `${moveStatus}`
|
||||
}
|
||||
} else {
|
||||
//move Success, check subfolders if empty and then remove.
|
||||
var currentCheckingRelDirectory = oldRelativeDirectory
|
||||
if (currentCheckingRelDirectory.endsWith(this.directorySeperatorChar)) currentCheckingRelDirectory = currentCheckingRelDirectory.substring(0, currentCheckingRelDirectory.length - 1)
|
||||
const dirParts = currentCheckingRelDirectory.split(this.directorySeperatorChar)
|
||||
for (var i = dirParts.length - 1; i >= 0; i--) {
|
||||
currentCheckingRelDirectory = currentCheckingRelDirectory.substring(0, currentCheckingRelDirectory.length - dirParts[i].length - 1)
|
||||
var currentCheckingDirectory = basePath + currentCheckingRelDirectory
|
||||
if (currentCheckingRelDirectory) {
|
||||
var fileItems = await fileUtils.recurseFiles(currentCheckingDirectory) //this will find files nested in subdirectories as well.
|
||||
if (fileItems && fileItems.length == 0) {
|
||||
Logger.debug(`${LoggerTag} no files in sub folders, safe to delete '${currentCheckingRelDirectory}'`)
|
||||
fileUtils.removeFile(currentCheckingDirectory)
|
||||
} else {
|
||||
break; //if we do find a file, then no need to check parent dirs they will all have files in subfolders.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Logger.error(`${LoggerTag} target folder ${newDirectory} already exists. Cancelling move`)
|
||||
return { msg: "failed to move folder", success: false, details: `target folder ${newDirectory} already exists. Cancelling move` }
|
||||
}
|
||||
Logger.debug(`${LoggerTag} dynamically updating all path variables in Library Item from ${oldRelativeDirectory} to ${newRelativeDirectory}`)
|
||||
this.recursiveUpdatePath(libraryItem, libraryItem.toJSON(), oldRelativeDirectory, newRelativeDirectory, basePath)
|
||||
await this.db.updateLibraryItem(libraryItem)
|
||||
const libItem = this.db.getLibraryItem(libraryItem.id)
|
||||
|
||||
|
||||
//you have to put the removal of the ignore on a delay for long enough that the Watcher process
|
||||
//will have checkd on the directory, recognized it should be ignored, before removing the ignore process
|
||||
setTimeout(() => {
|
||||
this.watcher.removeIgnoreDir(oldDirectory)
|
||||
this.watcher.removeIgnoreDir(newDirectory)
|
||||
}, this.watcher.pendingDelay)
|
||||
|
||||
|
||||
Logger.info(`${LoggerTag} rename successful.`)
|
||||
return {
|
||||
msg: "Rename Success",
|
||||
success: true,
|
||||
details: newPaths
|
||||
//, testObj: testObj
|
||||
}
|
||||
|
||||
}
|
||||
recursiveUpdatePath(item, json, oldRelPath, newRelPath, basePath) {
|
||||
for (const key in json) {
|
||||
if (key.toLowerCase().includes("path")) {
|
||||
if (item[key] == basePath + oldRelPath) {
|
||||
item[key] = basePath + newRelPath
|
||||
}
|
||||
else if (item[key] == oldRelPath) {
|
||||
item[key] = newRelPath
|
||||
}
|
||||
else if (item[key].startsWith(oldRelPath + this.directorySeperatorChar)) {
|
||||
item[key] = item[key].replace(oldRelPath + this.directorySeperatorChar, newRelPath + this.directorySeperatorChar)
|
||||
}
|
||||
else if (item[key].startsWith(basePath + oldRelPath + this.directorySeperatorChar)) {
|
||||
item[key] = item[key].replace(basePath + oldRelPath + this.directorySeperatorChar, basePath + newRelPath + this.directorySeperatorChar)
|
||||
}
|
||||
}
|
||||
if (typeof json[key] === "object") {
|
||||
this.recursiveUpdatePath(item[key], json[key], oldRelPath, newRelPath, basePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sanitizeText(str) {
|
||||
if (str) {
|
||||
return fileUtils.sanitizeFilename(str)
|
||||
}
|
||||
else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
parseTemplateString(userTemplate, libraryItem) {
|
||||
var template = userTemplate ? userTemplate : global.ServerSettings.defaultRenameString
|
||||
|
||||
var bookTitle = this.sanitizeText(libraryItem.media.metadata.title)
|
||||
var bookAuthor = this.sanitizeText(libraryItem.media.metadata.authorName)
|
||||
var subtitle = this.sanitizeText(libraryItem.media.metadata.subtitle)
|
||||
//series
|
||||
var narrator = this.sanitizeText(libraryItem.media.metadata.narratorName)
|
||||
var genres = this.sanitizeText(libraryItem.media.metadata.genresDisplay)
|
||||
var publishedYear = this.sanitizeText(libraryItem.media.metadata.publishedYear)
|
||||
var publishedDate = this.sanitizeText(libraryItem.media.metadata.publishedDate)
|
||||
var publisher = this.sanitizeText(libraryItem.media.metadata.publisher)
|
||||
var isbn = this.sanitizeText(libraryItem.media.metadata.isbn)
|
||||
var asin = this.sanitizeText(libraryItem.media.metadata.asin)
|
||||
var language = this.sanitizeText(libraryItem.media.metadata.language)
|
||||
var explicit = this.sanitizeText(libraryItem.media.metadata.explicitDisplay)
|
||||
var seriesSummary = this.sanitizeText(libraryItem.media.metadata.seriesName)
|
||||
var output = template
|
||||
while (output.includes("\$bookTitle")) output = output.replace("\$bookTitle", bookTitle)
|
||||
while (output.includes("\$bookAuthor")) output = output.replace("\$bookAuthor", bookAuthor)
|
||||
while (output.includes("\$subtitle")) output = output.replace("\$subtitle", subtitle)
|
||||
while (output.includes("\$narrator")) output = output.replace("\$narrator", narrator)
|
||||
while (output.includes("\$genres")) output = output.replace("\$genres", genres)
|
||||
while (output.includes("\$publishedYear")) output = output.replace("\$publishedYear", publishedYear)
|
||||
while (output.includes("\$publishedDate")) output = output.replace("\$publishedDate", publishedDate)
|
||||
while (output.includes("\$publisher")) output = output.replace("\$publisher", publisher)
|
||||
while (output.includes("\$isbn")) output = output.replace("\$isbn", isbn)
|
||||
while (output.includes("\$asin")) output = output.replace("\$asin", asin)
|
||||
while (output.includes("\$language")) output = output.replace("\$language", language)
|
||||
while (output.includes("\$explicit")) output = output.replace("\$explicit", explicit)
|
||||
while (output.includes("\$seriesSummary")) output = output.replace("\$seriesSummary", seriesSummary)
|
||||
|
||||
var seriesNameRegex = /\$seriesName\[\d+\]/
|
||||
while (output.match(seriesNameRegex) != null) {
|
||||
var subString = output.match(seriesNameRegex)[0]
|
||||
if (subString != null) {
|
||||
var digitStr = subString.replace("\$seriesName[", "").replace("]", "")
|
||||
var digit = parseInt(digitStr)
|
||||
var replacementText = this.sanitizeText(libraryItem.media.metadata.getSeriesNameDisplay(digit))
|
||||
output = output.replace(subString, replacementText)
|
||||
}
|
||||
}
|
||||
while (output.includes("\$seriesName")) output = output.replace("\$seriesName", this.sanitizeText(libraryItem.media.metadata.getSeriesNameDisplay(0)))
|
||||
|
||||
var seriesSeqRegex = /\$seriesSequence\[\d+\]/
|
||||
while (output.match(seriesSeqRegex) != null) {
|
||||
var subString = output.match(seriesSeqRegex)[0]
|
||||
if (subString != null) {
|
||||
var digitStr = subString.replace("\$seriesSequence[", "").replace("]", "")
|
||||
var digit = parseInt(digitStr)
|
||||
var replacementText = this.sanitizeText(libraryItem.media.metadata.getSeriesSequenceDisplay(digit))
|
||||
output = output.replace(subString, replacementText)
|
||||
}
|
||||
}
|
||||
while (output.includes("\$seriesSequence")) output = output.replace("\$seriesSequence", this.sanitizeText(libraryItem.media.metadata.getSeriesSequenceDisplay(0)))
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = LibraryItemFolderManager
|
||||
Loading…
Add table
Add a link
Reference in a new issue