From 850ed4895577e5ed073556e4dd93f08f1a028bf7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 7 Nov 2024 17:26:51 -0600 Subject: [PATCH] Fix:Podcast episodes duplicated when a scan runs while the episode is downloading #2785 --- server/Server.js | 3 +++ server/Watcher.js | 22 ++++++++++++++++++++-- server/managers/PodcastManager.js | 3 +++ server/objects/PodcastEpisodeDownload.js | 9 +++++---- server/scanner/LibraryItemScanner.js | 9 +++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/server/Server.js b/server/Server.js index be129464..e40e7c57 100644 --- a/server/Server.js +++ b/server/Server.js @@ -149,6 +149,9 @@ class Server { Watcher.disabled = true } else { Watcher.initWatcher(libraries) + Watcher.on('scanFilesChanged', (pendingFileUpdates, pendingTask) => { + LibraryScanner.scanFilesChanged(pendingFileUpdates, pendingTask) + }) } } diff --git a/server/Watcher.js b/server/Watcher.js index 8c2d652e..85c13e2a 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -2,7 +2,6 @@ const Path = require('path') const EventEmitter = require('events') const Watcher = require('./libs/watcher/watcher') const Logger = require('./Logger') -const LibraryScanner = require('./scanner/LibraryScanner') const Task = require('./objects/Task') const TaskManager = require('./managers/TaskManager') @@ -31,6 +30,8 @@ class FolderWatcher extends EventEmitter { this.filesBeingAdded = new Set() + /** @type {Set} */ + this.ignoreFilePathsDownloading = new Set() /** @type {string[]} */ this.ignoreDirs = [] /** @type {string[]} */ @@ -333,7 +334,7 @@ class FolderWatcher extends EventEmitter { } if (this.pendingFileUpdates.length) { - LibraryScanner.scanFilesChanged(this.pendingFileUpdates, this.pendingTask) + this.emit('scanFilesChanged', this.pendingFileUpdates, this.pendingTask) } else { const taskFinishedString = { text: 'No files to scan', @@ -348,12 +349,29 @@ class FolderWatcher extends EventEmitter { }, this.pendingDelay) } + /** + * + * @param {string} path + * @returns {boolean} + */ checkShouldIgnorePath(path) { return !!this.ignoreDirs.find((dirpath) => { return isSameOrSubPath(dirpath, path) }) } + /** + * When scanning a library item folder these files should be ignored + * Either a podcast episode downloading or a file that is pending by the watcher + * + * @param {string} path + * @returns {boolean} + */ + checkShouldIgnoreFilePath(path) { + if (this.pendingFilePaths.includes(path)) return true + return this.ignoreFilePathsDownloading.has(path) + } + /** * Convert to POSIX and remove trailing slash * @param {string} path diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js index f9eb72e4..96ffcb6a 100644 --- a/server/managers/PodcastManager.js +++ b/server/managers/PodcastManager.js @@ -97,6 +97,7 @@ class PodcastManager { // Ignores all added files to this dir Watcher.addIgnoreDir(this.currentDownload.libraryItem.path) + Watcher.ignoreFilePathsDownloading.add(this.currentDownload.targetPath) // Make sure podcast library item folder exists if (!(await fs.pathExists(this.currentDownload.libraryItem.path))) { @@ -151,6 +152,8 @@ class PodcastManager { SocketAuthority.emitter('episode_download_queue_updated', this.getDownloadQueueDetails()) Watcher.removeIgnoreDir(this.currentDownload.libraryItem.path) + + Watcher.ignoreFilePathsDownloading.delete(this.currentDownload.targetPath) this.currentDownload = null if (this.downloadQueue.length) { this.startPodcastEpisodeDownload(this.downloadQueue.shift()) diff --git a/server/objects/PodcastEpisodeDownload.js b/server/objects/PodcastEpisodeDownload.js index 2dfdc52e..86a85801 100644 --- a/server/objects/PodcastEpisodeDownload.js +++ b/server/objects/PodcastEpisodeDownload.js @@ -1,6 +1,6 @@ const Path = require('path') -const uuidv4 = require("uuid").v4 -const { sanitizeFilename } = require('../utils/fileUtils') +const uuidv4 = require('uuid').v4 +const { sanitizeFilename, filePathToPOSIX } = require('../utils/fileUtils') const globals = require('../utils/globals') class PodcastEpisodeDownload { @@ -60,7 +60,7 @@ class PodcastEpisodeDownload { return sanitizeFilename(filename) } get targetPath() { - return Path.join(this.libraryItem.path, this.targetFilename) + return filePathToPOSIX(Path.join(this.libraryItem.path, this.targetFilename)) } get targetRelPath() { return this.targetFilename @@ -74,7 +74,8 @@ class PodcastEpisodeDownload { this.podcastEpisode = podcastEpisode const url = podcastEpisode.enclosure.url - if (decodeURIComponent(url) !== url) { // Already encoded + if (decodeURIComponent(url) !== url) { + // Already encoded this.url = url } else { this.url = encodeURI(url) diff --git a/server/scanner/LibraryItemScanner.js b/server/scanner/LibraryItemScanner.js index 38608e47..5edfc2e2 100644 --- a/server/scanner/LibraryItemScanner.js +++ b/server/scanner/LibraryItemScanner.js @@ -4,7 +4,9 @@ const { LogLevel, ScanResult } = require('../utils/constants') const fileUtils = require('../utils/fileUtils') const scanUtils = require('../utils/scandir') const libraryFilters = require('../utils/queries/libraryFilters') +const Logger = require('../Logger') const Database = require('../Database') +const Watcher = require('../Watcher') const LibraryScan = require('./LibraryScan') const LibraryItemScanData = require('./LibraryItemScanData') const BookScanner = require('./BookScanner') @@ -128,6 +130,13 @@ class LibraryItemScanner { const libraryFiles = [] for (let i = 0; i < fileItems.length; i++) { const fileItem = fileItems[i] + + if (Watcher.checkShouldIgnoreFilePath(fileItem.fullpath)) { + // Skip file if it's pending + Logger.info(`[LibraryItemScanner] Skipping watcher pending file "${fileItem.fullpath}" during scan of library item path "${libraryItemPath}"`) + continue + } + const newLibraryFile = new LibraryFile() // fileItem.path is the relative path await newLibraryFile.setDataFromPath(fileItem.fullpath, fileItem.path)