From 428a515c6a7240fbe3c02a9c8a112f0260f23db9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 27 Feb 2022 18:07:36 -0600 Subject: [PATCH] Add:mtime,ctime,birthtime to audiobook folder and files --- server/objects/AudioFile.js | 12 +++++++++ server/objects/Audiobook.js | 44 +++++++++++++++++++++++++++------ server/objects/AudiobookFile.js | 17 +++++++++++++ server/scanner/Scanner.js | 1 + server/utils/fileUtils.js | 26 +++++++++++++++++++ server/utils/index.js | 7 ------ server/utils/scandir.js | 39 +++++++++++++++++------------ 7 files changed, 115 insertions(+), 31 deletions(-) diff --git a/server/objects/AudioFile.js b/server/objects/AudioFile.js index 196381a2..65a228be 100644 --- a/server/objects/AudioFile.js +++ b/server/objects/AudioFile.js @@ -11,6 +11,9 @@ class AudioFile { this.ext = null this.path = null this.fullPath = null + this.mtimeMs = null + this.ctimeMs = null + this.birthtimeMs = null this.addedAt = null this.trackNumFromMeta = null @@ -51,6 +54,9 @@ class AudioFile { ext: this.ext, path: this.path, fullPath: this.fullPath, + mtimeMs: this.mtimeMs, + ctimeMs: this.ctimeMs, + birthtimeMs: this.birthtimeMs, addedAt: this.addedAt, trackNumFromMeta: this.trackNumFromMeta, discNumFromMeta: this.discNumFromMeta, @@ -82,6 +88,9 @@ class AudioFile { this.ext = data.ext this.path = data.path this.fullPath = data.fullPath + this.mtimeMs = data.mtimeMs || 0 + this.ctimeMs = data.ctimeMs || 0 + this.birthtimeMs = data.birthtimeMs || 0 this.addedAt = data.addedAt this.manuallyVerified = !!data.manuallyVerified this.invalid = !!data.invalid @@ -124,6 +133,9 @@ class AudioFile { this.ext = fileData.ext this.path = fileData.path this.fullPath = fileData.fullPath + this.mtimeMs = fileData.mtimeMs || 0 + this.ctimeMs = fileData.ctimeMs || 0 + this.birthtimeMs = fileData.birthtimeMs || 0 this.addedAt = Date.now() this.trackNumFromMeta = fileData.trackNumFromMeta diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js index c030b668..e24aedd2 100644 --- a/server/objects/Audiobook.js +++ b/server/objects/Audiobook.js @@ -1,7 +1,7 @@ const Path = require('path') const fs = require('fs-extra') -const { bytesPretty, readTextFile } = require('../utils/fileUtils') -const { comparePaths, getIno, getId, elapsedPretty } = require('../utils/index') +const { bytesPretty, readTextFile, getIno } = require('../utils/fileUtils') +const { comparePaths, getId, elapsedPretty } = require('../utils/index') const { parseOpfMetadataXML } = require('../utils/parseOpfMetadata') const { extractCoverArt } = require('../utils/ffmpegHelpers') const nfoGenerator = require('../utils/nfoGenerator') @@ -22,6 +22,9 @@ class Audiobook { this.path = null this.fullPath = null + this.mtimeMs = null + this.ctimeMs = null + this.birthtimeMs = null this.addedAt = null this.lastUpdate = null this.lastScan = null @@ -57,6 +60,9 @@ class Audiobook { this.folderId = audiobook.folderId || 'audiobooks' this.path = audiobook.path this.fullPath = audiobook.fullPath + this.mtimeMs = audiobook.mtimeMs || 0 + this.ctimeMs = audiobook.ctimeMs || 0 + this.birthtimeMs = audiobook.birthtimeMs || 0 this.addedAt = audiobook.addedAt this.lastUpdate = audiobook.lastUpdate || this.addedAt this.lastScan = audiobook.lastScan || null @@ -179,6 +185,9 @@ class Audiobook { folderId: this.folderId, path: this.path, fullPath: this.fullPath, + mtimeMs: this.mtimeMs, + ctimeMs: this.ctimeMs, + birthtimeMs: this.birthtimeMs, addedAt: this.addedAt, lastUpdate: this.lastUpdate, lastScan: this.lastScan, @@ -205,6 +214,9 @@ class Audiobook { tags: this.tags, path: this.path, fullPath: this.fullPath, + mtimeMs: this.mtimeMs, + ctimeMs: this.ctimeMs, + birthtimeMs: this.birthtimeMs, addedAt: this.addedAt, lastUpdate: this.lastUpdate, duration: this.duration, @@ -228,6 +240,9 @@ class Audiobook { folderId: this.folderId, path: this.path, fullPath: this.fullPath, + mtimeMs: this.mtimeMs, + ctimeMs: this.ctimeMs, + birthtimeMs: this.birthtimeMs, addedAt: this.addedAt, lastUpdate: this.lastUpdate, duration: this.duration, @@ -335,6 +350,9 @@ class Audiobook { this.path = data.path this.fullPath = data.fullPath + this.mtimeMs = data.mtimeMs || 0 + this.ctimeMs = data.ctimeMs || 0 + this.birthtimeMs = data.birthtimeMs || 0 this.addedAt = Date.now() this.lastUpdate = this.addedAt @@ -903,12 +921,6 @@ class Audiobook { } } - if (existingFile.filename !== fileFound.filename) { - existingFile.filename = fileFound.filename - existingFile.ext = fileFound.ext - hasUpdated = true - } - if (existingFile.path !== fileFound.path) { existingFile.path = fileFound.path existingFile.fullPath = fileFound.fullPath @@ -918,6 +930,14 @@ class Audiobook { hasUpdated = true } + var keysToCheck = ['filename', 'ext', 'mtimeMs', 'ctimeMs', 'birthtimeMs', 'size'] + keysToCheck.forEach((key) => { + if (existingFile[key] !== fileFound[key]) { + existingFile[key] = fileFound[key] + hasUpdated = true + } + }) + if (!isAudioFile && existingFile.filetype !== fileFound.filetype) { existingFile.filetype = fileFound.filetype hasUpdated = true @@ -957,6 +977,14 @@ class Audiobook { hasUpdated = true } + var keysToCheck = ['mtimeMs', 'ctimeMs', 'birthtimeMs'] + keysToCheck.forEach((key) => { + if (dataFound[key] != this[key]) { + this[key] = dataFound[key] || 0 + hasUpdated = true + } + }) + var newAudioFileData = [] var newOtherFileData = [] var existingAudioFileData = [] diff --git a/server/objects/AudiobookFile.js b/server/objects/AudiobookFile.js index db86912d..b57be8f4 100644 --- a/server/objects/AudiobookFile.js +++ b/server/objects/AudiobookFile.js @@ -6,6 +6,11 @@ class AudiobookFile { this.ext = null this.path = null this.fullPath = null + this.size = null + this.mtimeMs = null + this.ctimeMs = null + this.birthtimeMs = null + this.addedAt = null if (data) { @@ -25,6 +30,10 @@ class AudiobookFile { ext: this.ext, path: this.path, fullPath: this.fullPath, + size: this.size, + mtimeMs: this.mtimeMs, + ctimeMs: this.ctimeMs, + birthtimeMs: this.birthtimeMs, addedAt: this.addedAt } } @@ -36,6 +45,10 @@ class AudiobookFile { this.ext = data.ext this.path = data.path this.fullPath = data.fullPath + this.size = data.size || 0 + this.mtimeMs = data.mtimeMs || 0 + this.ctimeMs = data.ctimeMs || 0 + this.birthtimeMs = data.birthtimeMs || 0 this.addedAt = data.addedAt } @@ -46,6 +59,10 @@ class AudiobookFile { this.ext = data.ext this.path = data.path this.fullPath = data.fullPath + this.size = data.size || 0 + this.mtimeMs = data.mtimeMs || 0 + this.ctimeMs = data.ctimeMs || 0 + this.birthtimeMs = data.birthtimeMs || 0 this.addedAt = Date.now() } } diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index 635fda0c..2bc31ff8 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -205,6 +205,7 @@ class Scanner { // Check for existing & removed audiobooks for (let i = 0; i < audiobooksInLibrary.length; i++) { var audiobook = audiobooksInLibrary[i] + // Find audiobook folder with matching inode or matching path var dataFound = audiobookDataFound.find(abd => abd.ino === audiobook.ino || comparePaths(abd.path, audiobook.path)) if (!dataFound) { libraryScan.addLog(LogLevel.WARN, `Audiobook "${audiobook.title}" is missing`) diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index 44288e81..b9c08bd9 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -20,6 +20,23 @@ async function getFileStat(path) { } module.exports.getFileStat = getFileStat +async function getFileTimestampsWithIno(path) { + try { + var stat = await fs.stat(path, { bigint: true }) + return { + size: Number(stat.size), + mtimeMs: Number(stat.mtimeMs), + ctimeMs: Number(stat.ctimeMs), + birthtimeMs: Number(stat.birthtimeMs), + ino: String(stat.ino) + } + } catch (err) { + console.error('Failed to getFileTimestampsWithIno', err) + return false + } +} +module.exports.getFileTimestampsWithIno = getFileTimestampsWithIno + async function getFileSize(path) { var stat = await getFileStat(path) if (!stat) return 0 @@ -27,6 +44,15 @@ async function getFileSize(path) { } module.exports.getFileSize = getFileSize + +function getIno(path) { + return fs.stat(path, { bigint: true }).then((data => String(data.ino))).catch((err) => { + Logger.error('[Utils] Failed to get ino for path', path, err) + return null + }) +} +module.exports.getIno = getIno + async function readTextFile(path) { try { var data = await fs.readFile(path) diff --git a/server/utils/index.js b/server/utils/index.js index 43fbefa1..377a4164 100644 --- a/server/utils/index.js +++ b/server/utils/index.js @@ -38,13 +38,6 @@ module.exports.comparePaths = (path1, path2) => { return path1 === path2 || Path.normalize(path1) === Path.normalize(path2) } -module.exports.getIno = (path) => { - return fs.promises.stat(path, { bigint: true }).then((data => String(data.ino))).catch((err) => { - Logger.error('[Utils] Failed to get ino for path', path, err) - return null - }) -} - module.exports.isNullOrNaN = (num) => { return num === null || isNaN(num) } diff --git a/server/utils/scandir.js b/server/utils/scandir.js index ffbe832f..c8011c52 100644 --- a/server/utils/scandir.js +++ b/server/utils/scandir.js @@ -1,8 +1,7 @@ const Path = require('path') const fs = require('fs-extra') const Logger = require('../Logger') -const { getIno } = require('./index') -const { recurseFiles } = require('./fileUtils') +const { recurseFiles, getFileTimestampsWithIno } = require('./fileUtils') const globals = require('./globals') function isBookFile(path) { @@ -114,16 +113,20 @@ function groupFileItemsIntoBooks(fileItems) { } function cleanFileObjects(basepath, abrelpath, files) { - return files.map((file) => { + return Promise.all(files.map(async (file) => { + var fullPath = Path.posix.join(basepath, file) + var fileTsData = await getFileTimestampsWithIno(fullPath) + var ext = Path.extname(file) return { filetype: getFileType(ext), filename: Path.basename(file), path: Path.posix.join(abrelpath, file), // /AUDIOBOOK/PATH/filename.mp3 - fullPath: Path.posix.join(basepath, file), // /audiobooks/AUDIOBOOK/PATH/filename.mp3 - ext: ext + fullPath, // /audiobooks/AUDIOBOOK/PATH/filename.mp3 + ext: ext, + ...fileTsData } - }) + })) } function getFileType(ext) { @@ -162,15 +165,15 @@ async function scanRootDir(folder, serverSettings = {}) { for (const audiobookPath in audiobookGrouping) { var audiobookData = getAudiobookDataFromDir(folderPath, audiobookPath, parseSubtitle) - var fileObjs = cleanFileObjects(audiobookData.fullPath, audiobookPath, audiobookGrouping[audiobookPath]) - for (let i = 0; i < fileObjs.length; i++) { - fileObjs[i].ino = await getIno(fileObjs[i].fullPath) - } - var audiobookIno = await getIno(audiobookData.fullPath) + var fileObjs = await cleanFileObjects(audiobookData.fullPath, audiobookPath, audiobookGrouping[audiobookPath]) + var audiobookFolderStats = await getFileTimestampsWithIno(audiobookData.fullPath) audiobooks.push({ folderId: folder.id, libraryId: folder.libraryId, - ino: audiobookIno, + ino: audiobookFolderStats.ino, + mtimeMs: audiobookFolderStats.mtimeMs || 0, + ctimeMs: audiobookFolderStats.ctimeMs || 0, + birthtimeMs: audiobookFolderStats.birthtimeMs || 0, ...audiobookData, audioFiles: fileObjs.filter(f => f.filetype === 'audio'), otherFiles: fileObjs.filter(f => f.filetype !== 'audio') @@ -282,8 +285,12 @@ async function getAudiobookFileData(folder, audiobookPath, serverSettings = {}) var audiobookDir = audiobookPath.replace(folderFullPath, '').slice(1) var audiobookData = getAudiobookDataFromDir(folderFullPath, audiobookDir, parseSubtitle) + var audiobookFolderStats = await getFileTimestampsWithIno(audiobookData.fullPath) var audiobook = { - ino: await getIno(audiobookData.fullPath), + ino: audiobookFolderStats.ino, + mtimeMs: audiobookFolderStats.mtimeMs || 0, + ctimeMs: audiobookFolderStats.ctimeMs || 0, + birthtimeMs: audiobookFolderStats.birthtimeMs || 0, folderId: folder.id, libraryId: folder.libraryId, ...audiobookData, @@ -294,14 +301,14 @@ async function getAudiobookFileData(folder, audiobookPath, serverSettings = {}) for (let i = 0; i < fileItems.length; i++) { var fileItem = fileItems[i] - var ino = await getIno(fileItem.fullpath) + var fileStatData = await getFileTimestampsWithIno(fileItem.fullpath) var fileObj = { - ino, filetype: getFileType(fileItem.extension), filename: fileItem.name, path: fileItem.path, fullPath: fileItem.fullpath, - ext: fileItem.extension + ext: fileItem.extension, + ...fileStatData } if (fileObj.filetype === 'audio') { audiobook.audioFiles.push(fileObj)