diff --git a/server/Logger.js b/server/Logger.js index bea91553..6279f139 100644 --- a/server/Logger.js +++ b/server/Logger.js @@ -88,7 +88,7 @@ class Logger { trace(...args) { if (this.logLevel > LogLevel.TRACE) return - console.trace(`[${this.timestamp}[ TRACE:`, ...args) + console.trace(`[${this.timestamp}] TRACE:`, ...args) this.handleLog(LogLevel.TRACE, args) } diff --git a/server/Server.js b/server/Server.js index 1de00f98..3b9223da 100644 --- a/server/Server.js +++ b/server/Server.js @@ -10,6 +10,7 @@ const { version } = require('../package.json') // Utils const dbMigration = require('./utils/dbMigration') const filePerms = require('./utils/filePerms') +const fileUtils = require('./utils/fileUtils') const Logger = require('./Logger') const Auth = require('./Auth') @@ -44,16 +45,10 @@ class Server { global.isWin = process.platform === 'win32' global.Uid = isNaN(UID) ? 0 : Number(UID) global.Gid = isNaN(GID) ? 0 : Number(GID) - global.ConfigPath = Path.normalize(CONFIG_PATH) - global.MetadataPath = Path.normalize(METADATA_PATH) + global.ConfigPath = fileUtils.filePathToPOSIX(Path.normalize(CONFIG_PATH)) + global.MetadataPath = fileUtils.filePathToPOSIX(Path.normalize(METADATA_PATH)) global.RouterBasePath = ROUTER_BASE_PATH - // Fix backslash if not on Windows - if (process.platform !== 'win32') { - global.ConfigPath = global.ConfigPath.replace(/\\/g, '/') - global.MetadataPath = global.MetadataPath.replace(/\\/g, '/') - } - if (!fs.pathExistsSync(global.ConfigPath)) { fs.mkdirSync(global.ConfigPath) filePerms.setDefaultDirSync(global.ConfigPath, false) diff --git a/server/Watcher.js b/server/Watcher.js index 341c9c10..7d62296b 100644 --- a/server/Watcher.js +++ b/server/Watcher.js @@ -2,6 +2,8 @@ const EventEmitter = require('events') const Watcher = require('./libs/watcher/watcher') const Logger = require('./Logger') +const { filePathToPOSIX } = require('./utils/fileUtils') + class FolderWatcher extends EventEmitter { constructor() { super() @@ -143,23 +145,23 @@ class FolderWatcher extends EventEmitter { } addFileUpdate(libraryId, path, type) { - path = path.replace(/\\/g, '/') + path = filePathToPOSIX(path) if (this.pendingFilePaths.includes(path)) return // Get file library - var libwatcher = this.libraryWatchers.find(lw => lw.id === libraryId) + const libwatcher = this.libraryWatchers.find(lw => lw.id === libraryId) if (!libwatcher) { Logger.error(`[Watcher] Invalid library id from watcher ${libraryId}`) return } // Get file folder - var folder = libwatcher.folders.find(fold => path.startsWith(fold.fullPath.replace(/\\/g, '/'))) + const folder = libwatcher.folders.find(fold => path.startsWith(filePathToPOSIX(fold.fullPath))) if (!folder) { Logger.error(`[Watcher] New file folder not found in library "${libwatcher.name}" with path "${path}"`) return } - var folderFullPath = folder.fullPath.replace(/\\/g, '/') + const folderFullPath = filePathToPOSIX(folder.fullPath) var relPath = path.replace(folderFullPath, '') @@ -189,12 +191,12 @@ class FolderWatcher extends EventEmitter { checkShouldIgnorePath(path) { return !!this.ignoreDirs.find(dirpath => { - return path.replace(/\\/g, '/').startsWith(dirpath) + return filePathToPOSIX(path).startsWith(dirpath) }) } cleanDirPath(path) { - var path = path.replace(/\\/g, '/') + path = filePathToPOSIX(path) if (path.endsWith('/')) path = path.slice(0, -1) return path } diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index ecbf66fd..0eafd4e6 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -4,7 +4,7 @@ const SocketAuthority = require('../SocketAuthority') const fs = require('../libs/fsExtra') const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils') -const { getFileTimestampsWithIno } = require('../utils/fileUtils') +const { getFileTimestampsWithIno, filePathToPOSIX } = require('../utils/fileUtils') const filePerms = require('../utils/filePerms') const LibraryItem = require('../objects/LibraryItem') @@ -30,7 +30,7 @@ class PodcastController { return res.status(404).send('Folder not found') } - var podcastPath = payload.path.replace(/\\/g, '/') + const podcastPath = filePathToPOSIX(payload.path) if (await fs.pathExists(podcastPath)) { Logger.error(`[PodcastController] Podcast folder already exists "${podcastPath}"`) return res.status(400).send('Podcast already exists') diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js index d4d17211..8ba03858 100644 --- a/server/managers/AudioMetadataManager.js +++ b/server/managers/AudioMetadataManager.js @@ -8,6 +8,7 @@ const fs = require('../libs/fsExtra') const filePerms = require('../utils/filePerms') const { secondsToTimestamp } = require('../utils/index') +const { filePathToPOSIX } = require('../utils/fileUtils') const { writeMetadataFile } = require('../utils/ffmpegHelpers') const toneHelpers = require('../utils/toneHelpers') @@ -127,7 +128,7 @@ class AudioMetadataMangaer { await writeMetadataFile(libraryItem, metadataFilePath) if (libraryItem.media.coverPath != null) { - var coverPath = libraryItem.media.coverPath.replace(/\\/g, '/') + var coverPath = filePathToPOSIX(libraryItem.media.coverPath) } const proms = audioFiles.map(af => { diff --git a/server/managers/CoverManager.js b/server/managers/CoverManager.js index 3f11fd66..cb8b8718 100644 --- a/server/managers/CoverManager.js +++ b/server/managers/CoverManager.js @@ -1,13 +1,12 @@ const fs = require('../libs/fsExtra') const Path = require('path') -const axios = require('axios') const Logger = require('../Logger') const readChunk = require('../libs/readChunk') const imageType = require('../libs/imageType') const filePerms = require('../utils/filePerms') const globals = require('../utils/globals') -const { downloadFile } = require('../utils/fileUtils') +const { downloadFile, filePathToPOSIX } = require('../utils/fileUtils') const { extractCoverArt } = require('../utils/ffmpegHelpers') class CoverManager { @@ -173,7 +172,7 @@ class CoverManager { error: 'Invalid cover path' } } - coverPath = coverPath.replace(/\\/g, '/') + coverPath = filePathToPOSIX(coverPath) // Cover path already set on media if (libraryItem.media.coverPath == coverPath) { Logger.debug(`[CoverManager] validate cover path already set "${coverPath}"`) diff --git a/server/objects/Library.js b/server/objects/Library.js index 48106c68..719d4aa0 100644 --- a/server/objects/Library.js +++ b/server/objects/Library.js @@ -1,6 +1,7 @@ const Folder = require('./Folder') const LibrarySettings = require('./settings/LibrarySettings') const { getId } = require('../utils/index') +const { filePathToPOSIX } = require('../utils/fileUtils') class Library { constructor(library = null) { @@ -156,8 +157,8 @@ class Library { } checkFullPathInLibrary(fullPath) { - fullPath = fullPath.replace(/\\/g, '/') - return this.folders.find(folder => fullPath.startsWith(folder.fullPath.replace(/\\/g, '/'))) + fullPath = filePathToPOSIX(fullPath) + return this.folders.find(folder => fullPath.startsWith(filePathToPOSIX(folder.fullPath))) } getFolderById(id) { diff --git a/server/objects/files/AudioTrack.js b/server/objects/files/AudioTrack.js index dc7b75b6..c4d357c5 100644 --- a/server/objects/files/AudioTrack.js +++ b/server/objects/files/AudioTrack.js @@ -1,5 +1,5 @@ const Path = require('path') -const { encodeUriPath } = require('../../utils/index') +const { encodeUriPath } = require('../../utils/fileUtils') class AudioTrack { constructor() { diff --git a/server/objects/files/VideoTrack.js b/server/objects/files/VideoTrack.js index 7c4128f5..ef4b2a89 100644 --- a/server/objects/files/VideoTrack.js +++ b/server/objects/files/VideoTrack.js @@ -1,5 +1,5 @@ const Path = require('path') -const { encodeUriPath } = require('../../utils/index') +const { encodeUriPath } = require('../../utils/fileUtils') class VideoTrack { constructor() { diff --git a/server/objects/mediaTypes/Book.js b/server/objects/mediaTypes/Book.js index 0fac4270..2e536a26 100644 --- a/server/objects/mediaTypes/Book.js +++ b/server/objects/mediaTypes/Book.js @@ -5,7 +5,7 @@ const { areEquivalent, copyValue, cleanStringForSearch } = require('../../utils/ const { parseOpfMetadataXML } = require('../../utils/parsers/parseOpfMetadata') const { parseOverdriveMediaMarkersAsChapters } = require('../../utils/parsers/parseOverdriveMediaMarkers') const abmetadataGenerator = require('../../utils/abmetadataGenerator') -const { readTextFile } = require('../../utils/fileUtils') +const { readTextFile, filePathToPOSIX } = require('../../utils/fileUtils') const AudioFile = require('../files/AudioFile') const AudioTrack = require('../files/AudioTrack') const EBookFile = require('../files/EBookFile') @@ -182,7 +182,7 @@ class Book { } updateCover(coverPath) { - coverPath = coverPath.replace(/\\/g, '/') + coverPath = filePathToPOSIX(coverPath) if (this.coverPath === coverPath) return false this.coverPath = coverPath return true diff --git a/server/objects/mediaTypes/Music.js b/server/objects/mediaTypes/Music.js index 9d47f6c0..c92b6692 100644 --- a/server/objects/mediaTypes/Music.js +++ b/server/objects/mediaTypes/Music.js @@ -3,6 +3,7 @@ const AudioFile = require('../files/AudioFile') const AudioTrack = require('../files/AudioTrack') const MusicMetadata = require('../metadata/MusicMetadata') const { areEquivalent, copyValue } = require('../../utils/index') +const { filePathToPOSIX } = require('../../utils/fileUtils') class Music { constructor(music) { @@ -106,7 +107,7 @@ class Music { } updateCover(coverPath) { - coverPath = coverPath.replace(/\\/g, '/') + coverPath = filePathToPOSIX(coverPath) if (this.coverPath === coverPath) return false this.coverPath = coverPath return true diff --git a/server/objects/mediaTypes/Podcast.js b/server/objects/mediaTypes/Podcast.js index 5f05fed9..7d10d2d0 100644 --- a/server/objects/mediaTypes/Podcast.js +++ b/server/objects/mediaTypes/Podcast.js @@ -3,7 +3,7 @@ const PodcastEpisode = require('../entities/PodcastEpisode') const PodcastMetadata = require('../metadata/PodcastMetadata') const { areEquivalent, copyValue, cleanStringForSearch } = require('../../utils/index') const abmetadataGenerator = require('../../utils/abmetadataGenerator') -const { readTextFile } = require('../../utils/fileUtils') +const { readTextFile, filePathToPOSIX } = require('../../utils/fileUtils') const { createNewSortInstance } = require('../../libs/fastSort') const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare @@ -159,7 +159,7 @@ class Podcast { } updateCover(coverPath) { - coverPath = coverPath.replace(/\\/g, '/') + coverPath = filePathToPOSIX(coverPath) if (this.coverPath === coverPath) return false this.coverPath = coverPath return true diff --git a/server/objects/mediaTypes/Video.js b/server/objects/mediaTypes/Video.js index 268bf5f3..dae834c1 100644 --- a/server/objects/mediaTypes/Video.js +++ b/server/objects/mediaTypes/Video.js @@ -3,6 +3,7 @@ const VideoFile = require('../files/VideoFile') const VideoTrack = require('../files/VideoTrack') const VideoMetadata = require('../metadata/VideoMetadata') const { areEquivalent, copyValue } = require('../../utils/index') +const { filePathToPOSIX } = require('../../utils/fileUtils') class Video { constructor(video) { @@ -101,7 +102,7 @@ class Video { } updateCover(coverPath) { - coverPath = coverPath.replace(/\\/g, '/') + coverPath = filePathToPOSIX(coverPath) if (this.coverPath === coverPath) return false this.coverPath = coverPath return true diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 653f93d8..ec007118 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -6,7 +6,7 @@ const SocketAuthority = require('../SocketAuthority') // Utils const { groupFilesIntoLibraryItemPaths, getLibraryItemFileData, scanFolder } = require('../utils/scandir') const { comparePaths } = require('../utils/index') -const { getIno } = require('../utils/fileUtils') +const { getIno, filePathToPOSIX } = require('../utils/fileUtils') const { ScanResult, LogLevel } = require('../utils/constants') const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcastUtils') @@ -554,12 +554,12 @@ class Scanner { var firstNest = itemDirNestedFiles[0].split('/').shift() var altDir = `${itemDir}/${firstNest}` - var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), itemDir) + var fullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), itemDir) var childLibraryItem = this.db.libraryItems.find(li => li.path !== fullPath && li.path.startsWith(fullPath)) if (!childLibraryItem) { continue; } - var altFullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), altDir) + var altFullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), altDir) var altChildLibraryItem = this.db.libraryItems.find(li => li.path !== altFullPath && li.path.startsWith(altFullPath)) if (altChildLibraryItem) { continue; @@ -571,13 +571,13 @@ class Scanner { } // Second pass: Check for new/updated/removed items - var itemGroupingResults = {} + const itemGroupingResults = {} for (const itemDir in fileUpdateGroup) { - var fullPath = Path.posix.join(folder.fullPath.replace(/\\/g, '/'), itemDir) + const fullPath = Path.posix.join(filePathToPOSIX(folder.fullPath), itemDir) const dirIno = await getIno(fullPath) // Check if book dir group is already an item - var existingLibraryItem = this.db.libraryItems.find(li => fullPath.startsWith(li.path)) + let existingLibraryItem = this.db.libraryItems.find(li => fullPath.startsWith(li.path)) if (!existingLibraryItem) { existingLibraryItem = this.db.libraryItems.find(li => li.ino === dirIno) if (existingLibraryItem) { @@ -590,7 +590,7 @@ class Scanner { if (existingLibraryItem) { // Is the item exactly - check if was deleted if (existingLibraryItem.path === fullPath) { - var exists = await fs.pathExists(fullPath) + const exists = await fs.pathExists(fullPath) if (!exists) { Logger.info(`[Scanner] Scanning file update group and library item was deleted "${existingLibraryItem.media.metadata.title}" - marking as missing`) existingLibraryItem.setMissing() diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js index 778fd48d..4df9b0be 100644 --- a/server/utils/dbMigration.js +++ b/server/utils/dbMigration.js @@ -5,6 +5,7 @@ const njodb = require('../libs/njodb') const { SupportedEbookTypes } = require('./globals') const { PlayMethod } = require('./constants') const { getId } = require('./index') +const { filePathToPOSIX } = require('./fileUtils') const Logger = require('../Logger') const Library = require('../objects/Library') @@ -87,8 +88,8 @@ function makeSeriesFromOldAb({ series, volumeNumber }) { } function getRelativePath(srcPath, basePath) { - srcPath = srcPath.replace(/\\/g, '/') - basePath = basePath.replace(/\\/g, '/') + srcPath = filePathToPOSIX(srcPath) + basePath = filePathToPOSIX(basePath) return srcPath.replace(basePath, '') } diff --git a/server/utils/ffmpegHelpers.js b/server/utils/ffmpegHelpers.js index aaab46b7..0775292d 100644 --- a/server/utils/ffmpegHelpers.js +++ b/server/utils/ffmpegHelpers.js @@ -3,10 +3,11 @@ const fs = require('../libs/fsExtra') const Path = require('path') const package = require('../../package.json') const Logger = require('../Logger') +const { filePathToPOSIX } = require('./fileUtils') function escapeSingleQuotes(path) { // return path.replace(/'/g, '\'\\\'\'') - return path.replace(/\\/g, '/').replace(/ /g, '\\ ').replace(/'/g, '\\\'') + return filePathToPOSIX(path).replace(/ /g, '\\ ').replace(/'/g, '\\\'') } // Returns first track start time diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index ad00d661..996823ea 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -80,11 +80,11 @@ function bytesPretty(bytes, decimals = 0) { module.exports.bytesPretty = bytesPretty async function recurseFiles(path, relPathToReplace = null) { - path = path.replace(/\\/g, '/') + path = this.filePathToPOSIX(path) if (!path.endsWith('/')) path = path + '/' if (relPathToReplace) { - relPathToReplace = relPathToReplace.replace(/\\/g, '/') + relPathToReplace = this.filePathToPOSIX(relPathToReplace) if (!relPathToReplace.endsWith('/')) relPathToReplace += '/' } else { relPathToReplace = path @@ -244,4 +244,19 @@ module.exports.removeFile = (path) => { Logger.error(`[fileUtils] Failed remove file "${path}"`, error) return false }) +} + +/** +* Make sure folder separator is POSIX for Windows file paths. e.g. "C:\Users\Abs" becomes "C:/Users/Abs" +* +* @param {String} path - Ugly file path +* @return {String} Pretty posix file path +*/ +module.exports.filePathToPOSIX = (path) => { + if (!global.isWin || !path) return path + return path.replace(/\\/g, '/') +} + +module.exports.encodeUriPath = (path) => { + return this.filePathToPOSIX(path).replace(/%/g, '%25').replace(/#/g, '%23') } \ No newline at end of file diff --git a/server/utils/index.js b/server/utils/index.js index 21c959dc..503788ce 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -1,5 +1,4 @@ const Path = require('path') -const fs = require('fs') const Logger = require('../Logger') const { parseString } = require("xml2js") const areEquivalent = require('./areEquivalent') @@ -125,10 +124,6 @@ module.exports.copyValue = (val) => { } } -module.exports.encodeUriPath = (path) => { - return path.replace(/\\/g, '/').replace(/%/g, '%25').replace(/#/g, '%23') -} - module.exports.toNumber = (val, fallback = 0) => { if (isNaN(val) || val === null) return fallback return Number(val) diff --git a/server/utils/scandir.js b/server/utils/scandir.js index bea54cce..9df5254b 100644 --- a/server/utils/scandir.js +++ b/server/utils/scandir.js @@ -1,7 +1,7 @@ const Path = require('path') const fs = require('../libs/fsExtra') const Logger = require('../Logger') -const { recurseFiles, getFileTimestampsWithIno } = require('./fileUtils') +const { recurseFiles, getFileTimestampsWithIno, filePathToPOSIX } = require('./fileUtils') const globals = require('./globals') const LibraryFile = require('../objects/files/LibraryFile') @@ -176,7 +176,7 @@ function cleanFileObjects(libraryItemPath, files) { // Scan folder async function scanFolder(libraryMediaType, folder, serverSettings = {}) { - const folderPath = folder.fullPath.replace(/\\/g, '/') + const folderPath = filePathToPOSIX(folder.fullPath) const pathExists = await fs.pathExists(folderPath) if (!pathExists) { @@ -243,7 +243,7 @@ module.exports.scanFolder = scanFolder // Input relative filepath, output all details that can be parsed function getBookDataFromDir(folderPath, relPath, parseSubtitle = false) { - relPath = relPath.replace(/\\/g, '/') + relPath = filePathToPOSIX(relPath) var splitDir = relPath.split('/') var folder = splitDir.pop() // Audio files will always be in the directory named for the title @@ -333,7 +333,7 @@ function getSubtitle(folder) { } function getPodcastDataFromDir(folderPath, relPath) { - relPath = relPath.replace(/\\/g, '/') + relPath = filePathToPOSIX(relPath) const splitDir = relPath.split('/') // Audio files will always be in the directory named for the title @@ -360,8 +360,8 @@ function getDataFromMediaDir(libraryMediaType, folderPath, relPath, serverSettin // Called from Scanner.js async function getLibraryItemFileData(libraryMediaType, folder, libraryItemPath, isSingleMediaItem, serverSettings = {}) { - libraryItemPath = libraryItemPath.replace(/\\/g, '/') - const folderFullPath = folder.fullPath.replace(/\\/g, '/') + libraryItemPath = filePathToPOSIX(libraryItemPath) + const folderFullPath = filePathToPOSIX(folder.fullPath) const libraryItemDir = libraryItemPath.replace(folderFullPath, '').slice(1) let libraryItemData = {}