This commit is contained in:
Jason Axley 2026-05-06 13:51:20 +02:00 committed by GitHub
commit f3a220c173
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 2441 additions and 304 deletions

View file

@ -47,6 +47,10 @@ function getFileStat(path) {
}
}
/**
* @param {string} path
* @returns {Promise<object | null>}
*/
async function getFileTimestampsWithIno(path) {
try {
var stat = await fs.stat(path, { bigint: true })
@ -55,11 +59,12 @@ async function getFileTimestampsWithIno(path) {
mtimeMs: Number(stat.mtimeMs),
ctimeMs: Number(stat.ctimeMs),
birthtimeMs: Number(stat.birthtimeMs),
ino: String(stat.ino)
ino: String(stat.ino),
deviceId: String(stat.dev)
}
} catch (err) {
Logger.error(`[fileUtils] Failed to getFileTimestampsWithIno for path "${path}"`, err)
return false
return null
}
}
module.exports.getFileTimestampsWithIno = getFileTimestampsWithIno
@ -92,7 +97,7 @@ module.exports.getFileMTimeMs = async (path) => {
/**
*
* @param {string} filepath
* @returns {boolean}
* @returns {Promise<boolean>} isFile
*/
async function checkPathIsFile(filepath) {
try {
@ -104,6 +109,10 @@ async function checkPathIsFile(filepath) {
}
module.exports.checkPathIsFile = checkPathIsFile
/**
* @param {string} path
* @returns {string | null} inode
*/
function getIno(path) {
return fs
.stat(path, { bigint: true })
@ -115,10 +124,25 @@ function getIno(path) {
}
module.exports.getIno = getIno
/**
* @param {string} path
* @returns {Promise<string | null>} deviceId
*/
async function getDeviceId(path) {
try {
var data = await fs.stat(path)
return String(data.dev)
} catch (error) {
Logger.error(`[Utils] Failed to get device Id for path "${path}": ${error}`)
return null
}
}
module.exports.getDeviceId = getDeviceId
/**
* Read contents of file
* @param {string} path
* @returns {string}
* @returns {Promise<string>} file contents
*/
async function readTextFile(path) {
try {
@ -135,7 +159,7 @@ module.exports.readTextFile = readTextFile
* Check if file or directory should be ignored. Returns a string of the reason to ignore, or null if not ignored
*
* @param {string} path
* @returns {string}
* @returns {string | null} reason to ignore
*/
module.exports.shouldIgnoreFile = (path) => {
// Check if directory or file name starts with "."
@ -178,8 +202,8 @@ module.exports.shouldIgnoreFile = (path) => {
/**
* Get array of files inside dir
* @param {string} path
* @param {string} [relPathToReplace]
* @returns {FilePathItem[]}
* @param {string | null} [relPathToReplace]
* @returns {Promise<FilePathItem[]>}
*/
module.exports.recurseFiles = async (path, relPathToReplace = null) => {
path = filePathToPOSIX(path)
@ -219,6 +243,8 @@ module.exports.recurseFiles = async (path, relPathToReplace = null) => {
item.fullname = filePathToPOSIX(item.fullname)
item.path = filePathToPOSIX(item.path)
// BUGBUG: This is broken with symlinked directory /tmp -> /private/tmp. when library is in /tmp/testLibrary, it tries to replace /tmp/testLibrary with '' but in a canonical path (non-symlinked)
// TODO: find the commit that added relPathToReplace and figure out what it's trying to do and make it do that properly
const relpath = item.fullname.replace(relPathToReplace, '')
let reldirname = Path.dirname(relpath)
if (reldirname === '.') reldirname = ''
@ -292,7 +318,7 @@ module.exports.getFilePathItemFromFileUpdate = (fileUpdate) => {
*
* @param {string} url
* @param {string} filepath path to download the file to
* @param {Function} [contentTypeFilter] validate content type before writing
* @param {Function | null} [contentTypeFilter] validate content type before writing
* @returns {Promise}
*/
module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {

View file

@ -5,17 +5,17 @@ const fileUtils = require('../fileUtils')
const LibraryFile = require('../../objects/files/LibraryFile')
/**
*
* @param {import('../../models/LibraryItem')} libraryItem
*
* @param {import('../../models/LibraryItem')} libraryItem
* @returns {Promise<boolean>} false if failed
*/
async function writeMetadataFileForItem(libraryItem) {
const storeMetadataWithItem = global.ServerSettings.storeMetadataWithItem && !libraryItem.isFile
const metadataPath = storeMetadataWithItem ? libraryItem.path : Path.join(global.MetadataPath, 'items', libraryItem.id)
const metadataFilepath = fileUtils.filePathToPOSIX(Path.join(metadataPath, 'metadata.json'))
if ((await fsExtra.pathExists(metadataFilepath))) {
if (await fsExtra.pathExists(metadataFilepath)) {
// Metadata file already exists do nothing
return null
return false
}
Logger.info(`[absMetadataMigration] metadata file not found at "${metadataFilepath}" - creating`)
@ -27,20 +27,24 @@ async function writeMetadataFileForItem(libraryItem) {
const metadataJson = libraryItem.media.getAbsMetadataJson()
// Save to file
const success = await fsExtra.writeFile(metadataFilepath, JSON.stringify(metadataJson, null, 2)).then(() => true).catch((error) => {
Logger.error(`[absMetadataMigration] failed to save metadata file at "${metadataFilepath}"`, error.message || error)
return false
})
const success = await fsExtra
.writeFile(metadataFilepath, JSON.stringify(metadataJson, null, 2))
.then(() => true)
.catch((error) => {
Logger.error(`[absMetadataMigration] failed to save metadata file at "${metadataFilepath}"`, error.message || error)
return false
})
if (!success) return false
if (!storeMetadataWithItem) return true // No need to do anything else
// Safety check to make sure library file with the same path isnt already there
libraryItem.libraryFiles = libraryItem.libraryFiles.filter(lf => lf.metadata.path !== metadataFilepath)
libraryItem.libraryFiles = libraryItem.libraryFiles.filter((lf) => lf.metadata.path !== metadataFilepath)
// Put new library file in library item
const newLibraryFile = new LibraryFile()
await newLibraryFile.setDataFromPath(metadataFilepath, 'metadata.json')
// TODO: BUGBUG - this shouldn't be JSON and it may not be the right type LibraryFileObject
libraryItem.libraryFiles.push(newLibraryFile.toJSON())
// Update library item timestamps and total size
@ -49,20 +53,23 @@ async function writeMetadataFileForItem(libraryItem) {
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
let size = 0
libraryItem.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
libraryItem.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
libraryItem.size = size
}
libraryItem.changed('libraryFiles', true)
return libraryItem.save().then(() => true).catch((error) => {
Logger.error(`[absMetadataMigration] failed to save libraryItem "${libraryItem.id}"`, error.message || error)
return false
})
return libraryItem
.save()
.then(() => true)
.catch((error) => {
Logger.error(`[absMetadataMigration] failed to save libraryItem "${libraryItem.id}"`, error.message || error)
return false
})
}
/**
*
* @param {import('../../Database')} Database
*
* @param {import('../../Database')} Database
* @param {number} [offset=0]
* @param {number} [totalCreated=0]
*/
@ -83,11 +90,11 @@ async function runMigration(Database, offset = 0, totalCreated = 0) {
}
/**
*
* @param {import('../../Database')} Database
*
* @param {import('../../Database')} Database
*/
module.exports.migrate = async (Database) => {
Logger.info(`[absMetadataMigration] Starting metadata.json migration`)
const totalCreated = await runMigration(Database)
Logger.info(`[absMetadataMigration] Finished metadata.json migration (${totalCreated} files created)`)
}
}