From 92bb3527dea5af48dee90df5b10fc5372195d4cc Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 19 Mar 2025 17:39:23 -0500 Subject: [PATCH] Add logs when sanitizing filename and update podcast episode download to set targetFilename on init #4121 --- server/managers/PodcastManager.js | 2 +- server/objects/PodcastEpisodeDownload.js | 26 +++++++++++++++++++----- server/utils/fileUtils.js | 10 +++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js index 5ed0fb89..086238d6 100644 --- a/server/managers/PodcastManager.js +++ b/server/managers/PodcastManager.js @@ -108,7 +108,7 @@ class PodcastManager { // e.g. "/tagesschau 20 Uhr.mp3" becomes "/tagesschau 20 Uhr (ep_asdfasdf).mp3" // this handles podcasts where every title is the same (ref https://github.com/advplyr/audiobookshelf/issues/1802) if (await fs.pathExists(this.currentDownload.targetPath)) { - this.currentDownload.appendRandomId = true + this.currentDownload.setAppendRandomId(true) } // Ignores all added files to this dir diff --git a/server/objects/PodcastEpisodeDownload.js b/server/objects/PodcastEpisodeDownload.js index 7ff89395..3c1d82ac 100644 --- a/server/objects/PodcastEpisodeDownload.js +++ b/server/objects/PodcastEpisodeDownload.js @@ -20,6 +20,8 @@ class PodcastEpisodeDownload { this.appendRandomId = false + this.targetFilename = null + this.startedAt = null this.createdAt = null this.finishedAt = null @@ -74,11 +76,6 @@ class PodcastEpisodeDownload { get episodeTitle() { return this.rssPodcastEpisode.title } - get targetFilename() { - const appendage = this.appendRandomId ? ` (${this.id})` : '' - const filename = `${this.rssPodcastEpisode.title}${appendage}.${this.fileExtension}` - return sanitizeFilename(filename) - } get targetPath() { return filePathToPOSIX(Path.join(this.libraryItem.path, this.targetFilename)) } @@ -93,6 +90,23 @@ class PodcastEpisodeDownload { return new Date(this.rssPodcastEpisode.publishedAt).getFullYear() } + /** + * @param {string} title + */ + getSanitizedFilename(title) { + const appendage = this.appendRandomId ? ` (${this.id})` : '' + const filename = `${title.trim()}${appendage}.${this.fileExtension}` + return sanitizeFilename(filename) + } + + /** + * @param {boolean} appendRandomId + */ + setAppendRandomId(appendRandomId) { + this.appendRandomId = appendRandomId + this.targetFilename = this.getSanitizedFilename(this.rssPodcastEpisode.title || '') + } + /** * * @param {import('../utils/podcastUtils').RssPodcastEpisode} rssPodcastEpisode - from rss feed @@ -112,6 +126,8 @@ class PodcastEpisodeDownload { this.url = encodeURI(url) } + this.targetFilename = this.getSanitizedFilename(this.rssPodcastEpisode.title || '') + this.libraryItem = libraryItem this.isAutoDownload = isAutoDownload this.createdAt = Date.now() diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index 4b6915b7..2c935557 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -362,6 +362,9 @@ module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => { return false } + // Normalize the string first to ensure consistent byte calculations + filename = filename.normalize('NFC') + // Most file systems use number of bytes for max filename // to support most filesystems we will use max of 255 bytes in utf-16 // Ref: https://doc.owncloud.com/server/next/admin_manual/troubleshooting/path_filename_length.html @@ -390,8 +393,11 @@ module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => { const ext = Path.extname(sanitized) // separate out file extension const basename = Path.basename(sanitized, ext) const extByteLength = Buffer.byteLength(ext, 'utf16le') + const basenameByteLength = Buffer.byteLength(basename, 'utf16le') if (basenameByteLength + extByteLength > MAX_FILENAME_BYTES) { + Logger.debug(`[fileUtils] Filename "${filename}" is too long (${basenameByteLength + extByteLength} bytes), trimming basename to ${MAX_FILENAME_BYTES - extByteLength} bytes.`) + const MaxBytesForBasename = MAX_FILENAME_BYTES - extByteLength let totalBytes = 0 let trimmedBasename = '' @@ -407,6 +413,10 @@ module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => { sanitized = trimmedBasename + ext } + if (filename !== sanitized) { + Logger.debug(`[fileUtils] Sanitized filename "${filename}" to "${sanitized}" (${Buffer.byteLength(sanitized, 'utf16le')} bytes)`) + } + return sanitized }