mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-25 05:09:40 +00:00
Added deviceId in addition to inode to uniquely identify files
This commit is contained in:
parent
d8f07eb956
commit
3a4aacb7bf
17 changed files with 1445 additions and 227 deletions
|
|
@ -19,6 +19,8 @@ class LibraryItemScanData {
|
|||
this.mediaType = data.mediaType
|
||||
/** @type {string} */
|
||||
this.ino = data.ino
|
||||
/** @type {string} */
|
||||
this.deviceId = data.dev
|
||||
/** @type {number} */
|
||||
this.mtimeMs = data.mtimeMs
|
||||
/** @type {number} */
|
||||
|
|
@ -54,9 +56,10 @@ class LibraryItemScanData {
|
|||
*/
|
||||
get libraryItemObject() {
|
||||
let size = 0
|
||||
this.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
||||
this.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
||||
return {
|
||||
ino: this.ino,
|
||||
deviceId: this.deviceId,
|
||||
path: this.path,
|
||||
relPath: this.relPath,
|
||||
mediaType: this.mediaType,
|
||||
|
|
@ -80,107 +83,107 @@ class LibraryItemScanData {
|
|||
|
||||
/** @type {boolean} */
|
||||
get hasAudioFileChanges() {
|
||||
return (this.audioLibraryFilesRemoved.length + this.audioLibraryFilesAdded.length + this.audioLibraryFilesModified.length) > 0
|
||||
return this.audioLibraryFilesRemoved.length + this.audioLibraryFilesAdded.length + this.audioLibraryFilesModified.length > 0
|
||||
}
|
||||
|
||||
/** @type {LibraryFileModifiedObject[]} */
|
||||
get audioLibraryFilesModified() {
|
||||
return this.libraryFilesModified.filter(lf => globals.SupportedAudioTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesModified.filter((lf) => globals.SupportedAudioTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get audioLibraryFilesRemoved() {
|
||||
return this.libraryFilesRemoved.filter(lf => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesRemoved.filter((lf) => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get audioLibraryFilesAdded() {
|
||||
return this.libraryFilesAdded.filter(lf => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesAdded.filter((lf) => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get audioLibraryFiles() {
|
||||
return this.libraryFiles.filter(lf => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFiles.filter((lf) => globals.SupportedAudioTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryFileModifiedObject[]} */
|
||||
get imageLibraryFilesModified() {
|
||||
return this.libraryFilesModified.filter(lf => globals.SupportedImageTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesModified.filter((lf) => globals.SupportedImageTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get imageLibraryFilesRemoved() {
|
||||
return this.libraryFilesRemoved.filter(lf => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesRemoved.filter((lf) => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get imageLibraryFilesAdded() {
|
||||
return this.libraryFilesAdded.filter(lf => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesAdded.filter((lf) => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get imageLibraryFiles() {
|
||||
return this.libraryFiles.filter(lf => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFiles.filter((lf) => globals.SupportedImageTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryFileModifiedObject[]} */
|
||||
get ebookLibraryFilesModified() {
|
||||
return this.libraryFilesModified.filter(lf => globals.SupportedEbookTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesModified.filter((lf) => globals.SupportedEbookTypes.includes(lf.old.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get ebookLibraryFilesRemoved() {
|
||||
return this.libraryFilesRemoved.filter(lf => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesRemoved.filter((lf) => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get ebookLibraryFilesAdded() {
|
||||
return this.libraryFilesAdded.filter(lf => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFilesAdded.filter((lf) => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject[]} */
|
||||
get ebookLibraryFiles() {
|
||||
return this.libraryFiles.filter(lf => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
return this.libraryFiles.filter((lf) => globals.SupportedEbookTypes.includes(lf.metadata.ext?.slice(1).toLowerCase() || ''))
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject} */
|
||||
get descTxtLibraryFile() {
|
||||
return this.libraryFiles.find(lf => lf.metadata.filename === 'desc.txt')
|
||||
return this.libraryFiles.find((lf) => lf.metadata.filename === 'desc.txt')
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject} */
|
||||
get readerTxtLibraryFile() {
|
||||
return this.libraryFiles.find(lf => lf.metadata.filename === 'reader.txt')
|
||||
return this.libraryFiles.find((lf) => lf.metadata.filename === 'reader.txt')
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject} */
|
||||
get metadataAbsLibraryFile() {
|
||||
return this.libraryFiles.find(lf => lf.metadata.filename === 'metadata.abs')
|
||||
return this.libraryFiles.find((lf) => lf.metadata.filename === 'metadata.abs')
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject} */
|
||||
get metadataJsonLibraryFile() {
|
||||
return this.libraryFiles.find(lf => lf.metadata.filename === 'metadata.json')
|
||||
return this.libraryFiles.find((lf) => lf.metadata.filename === 'metadata.json')
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject} */
|
||||
get metadataOpfLibraryFile() {
|
||||
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.opf')
|
||||
return this.libraryFiles.find((lf) => lf.metadata.ext.toLowerCase() === '.opf')
|
||||
}
|
||||
|
||||
/** @type {LibraryItem.LibraryFileObject} */
|
||||
get metadataNfoLibraryFile() {
|
||||
return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.nfo')
|
||||
return this.libraryFiles.find((lf) => lf.metadata.ext.toLowerCase() === '.nfo')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {LibraryItem} existingLibraryItem
|
||||
*
|
||||
* @param {LibraryItem} existingLibraryItem
|
||||
* @param {import('./LibraryScan')} libraryScan
|
||||
* @returns {boolean} true if changes found
|
||||
*/
|
||||
async checkLibraryItemData(existingLibraryItem, libraryScan) {
|
||||
const keysToCompare = ['libraryFolderId', 'ino', 'path', 'relPath', 'isFile']
|
||||
const keysToCompare = ['libraryFolderId', 'ino', 'deviceId', 'path', 'relPath', 'isFile']
|
||||
this.hasChanges = false
|
||||
this.hasPathChange = false
|
||||
for (const key of keysToCompare) {
|
||||
|
|
@ -219,28 +222,29 @@ class LibraryItemScanData {
|
|||
|
||||
this.libraryFilesRemoved = []
|
||||
this.libraryFilesModified = []
|
||||
let libraryFilesAdded = this.libraryFiles.map(lf => lf)
|
||||
let libraryFilesAdded = this.libraryFiles.map((lf) => lf)
|
||||
|
||||
for (const existingLibraryFile of existingLibraryItem.libraryFiles) {
|
||||
// Find matching library file using path first and fallback to using inode value
|
||||
let matchingLibraryFile = this.libraryFiles.find(lf => lf.metadata.path === existingLibraryFile.metadata.path)
|
||||
let matchingLibraryFile = this.libraryFiles.find((lf) => lf.metadata.path === existingLibraryFile.metadata.path)
|
||||
if (!matchingLibraryFile) {
|
||||
matchingLibraryFile = this.libraryFiles.find(lf => lf.ino === existingLibraryFile.ino)
|
||||
matchingLibraryFile = this.libraryFiles.find((lf) => lf.ino === existingLibraryFile.ino)
|
||||
if (matchingLibraryFile) {
|
||||
libraryScan.addLog(LogLevel.INFO, `Library file with path "${existingLibraryFile.metadata.path}" not found, but found file with matching inode value "${existingLibraryFile.ino}" at path "${matchingLibraryFile.metadata.path}"`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingLibraryFile) { // Library file removed
|
||||
if (!matchingLibraryFile) {
|
||||
// Library file removed
|
||||
libraryScan.addLog(LogLevel.INFO, `Library file "${existingLibraryFile.metadata.path}" was removed from library item "${existingLibraryItem.relPath}"`)
|
||||
this.libraryFilesRemoved.push(existingLibraryFile)
|
||||
existingLibraryItem.libraryFiles = existingLibraryItem.libraryFiles.filter(lf => lf !== existingLibraryFile)
|
||||
existingLibraryItem.libraryFiles = existingLibraryItem.libraryFiles.filter((lf) => lf !== existingLibraryFile)
|
||||
this.hasChanges = true
|
||||
} else {
|
||||
libraryFilesAdded = libraryFilesAdded.filter(lf => lf !== matchingLibraryFile)
|
||||
libraryFilesAdded = libraryFilesAdded.filter((lf) => lf !== matchingLibraryFile)
|
||||
let existingLibraryFileBefore = structuredClone(existingLibraryFile)
|
||||
if (this.compareUpdateLibraryFile(existingLibraryItem.path, existingLibraryFile, matchingLibraryFile, libraryScan)) {
|
||||
this.libraryFilesModified.push({old: existingLibraryFileBefore, new: existingLibraryFile})
|
||||
this.libraryFilesModified.push({ old: existingLibraryFileBefore, new: existingLibraryFile })
|
||||
this.hasChanges = true
|
||||
}
|
||||
}
|
||||
|
|
@ -263,7 +267,7 @@ class LibraryItemScanData {
|
|||
|
||||
if (this.hasChanges) {
|
||||
existingLibraryItem.size = 0
|
||||
existingLibraryItem.libraryFiles.forEach((lf) => existingLibraryItem.size += lf.metadata.size)
|
||||
existingLibraryItem.libraryFiles.forEach((lf) => (existingLibraryItem.size += lf.metadata.size))
|
||||
|
||||
existingLibraryItem.lastScan = Date.now()
|
||||
existingLibraryItem.lastScanVersion = packageJson.version
|
||||
|
|
@ -283,16 +287,17 @@ class LibraryItemScanData {
|
|||
/**
|
||||
* Update existing library file with scanned in library file data
|
||||
* @param {string} libraryItemPath
|
||||
* @param {LibraryItem.LibraryFileObject} existingLibraryFile
|
||||
* @param {import('../objects/files/LibraryFile')} scannedLibraryFile
|
||||
* @param {LibraryItem.LibraryFileObject} existingLibraryFile
|
||||
* @param {import('../objects/files/LibraryFile')} scannedLibraryFile
|
||||
* @param {import('./LibraryScan')} libraryScan
|
||||
* @returns {boolean} false if no changes
|
||||
*/
|
||||
compareUpdateLibraryFile(libraryItemPath, existingLibraryFile, scannedLibraryFile, libraryScan) {
|
||||
let hasChanges = false
|
||||
|
||||
if (existingLibraryFile.ino !== scannedLibraryFile.ino) {
|
||||
if (existingLibraryFile.ino !== scannedLibraryFile.ino && existingLibraryFile.deviceId !== scannedLibraryFile.deviceId) {
|
||||
existingLibraryFile.ino = scannedLibraryFile.ino
|
||||
existingLibraryFile.deviceId = scannedLibraryFile.deviceId
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
|
|
@ -317,38 +322,38 @@ class LibraryItemScanData {
|
|||
|
||||
/**
|
||||
* Check if existing audio file on Book was removed
|
||||
* @param {import('../models/Book').AudioFileObject} existingAudioFile
|
||||
* @param {import('../models/Book').AudioFileObject} existingAudioFile
|
||||
* @returns {boolean} true if audio file was removed
|
||||
*/
|
||||
checkAudioFileRemoved(existingAudioFile) {
|
||||
if (!this.audioLibraryFilesRemoved.length) return false
|
||||
// First check exact path
|
||||
if (this.audioLibraryFilesRemoved.some(af => af.metadata.path === existingAudioFile.metadata.path)) {
|
||||
if (this.audioLibraryFilesRemoved.some((af) => af.metadata.path === existingAudioFile.metadata.path)) {
|
||||
return true
|
||||
}
|
||||
// Fallback to check inode value
|
||||
return this.audioLibraryFilesRemoved.some(af => af.ino === existingAudioFile.ino)
|
||||
return this.audioLibraryFilesRemoved.some((af) => af.ino === existingAudioFile.ino)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if existing ebook file on Book was removed
|
||||
* @param {import('../models/Book').EBookFileObject} ebookFile
|
||||
* @param {import('../models/Book').EBookFileObject} ebookFile
|
||||
* @returns {boolean} true if ebook file was removed
|
||||
*/
|
||||
checkEbookFileRemoved(ebookFile) {
|
||||
if (!this.ebookLibraryFiles.length) return true
|
||||
|
||||
if (this.ebookLibraryFiles.some(lf => lf.metadata.path === ebookFile.metadata.path)) {
|
||||
if (this.ebookLibraryFiles.some((lf) => lf.metadata.path === ebookFile.metadata.path)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !this.ebookLibraryFiles.some(lf => lf.ino === ebookFile.ino)
|
||||
return !this.ebookLibraryFiles.some((lf) => lf.ino === ebookFile.ino)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data parsed from filenames
|
||||
*
|
||||
* @param {Object} bookMetadata
|
||||
*
|
||||
* @param {Object} bookMetadata
|
||||
*/
|
||||
setBookMetadataFromFilenames(bookMetadata) {
|
||||
const keysToMap = ['title', 'subtitle', 'publishedYear', 'asin']
|
||||
|
|
@ -374,4 +379,4 @@ class LibraryItemScanData {
|
|||
}
|
||||
}
|
||||
}
|
||||
module.exports = LibraryItemScanData
|
||||
module.exports = LibraryItemScanData
|
||||
|
|
|
|||
|
|
@ -139,26 +139,15 @@ class LibraryItemScanner {
|
|||
const newLibraryFile = new LibraryFile()
|
||||
// fileItem.path is the relative path
|
||||
await newLibraryFile.setDataFromPath(fileItem.fullpath, fileItem.path)
|
||||
// TODO: BUGBUG - this is pushing the object, not a JSON string of the object like elsewhere
|
||||
libraryFiles.push(newLibraryFile)
|
||||
}
|
||||
|
||||
const libraryItemStats = await fileUtils.getFileTimestampsWithIno(libraryItemData.path)
|
||||
return new LibraryItemScanData({
|
||||
libraryFolderId: folder.id,
|
||||
libraryId: library.id,
|
||||
mediaType: library.mediaType,
|
||||
ino: libraryItemStats.ino,
|
||||
mtimeMs: libraryItemStats.mtimeMs || 0,
|
||||
ctimeMs: libraryItemStats.ctimeMs || 0,
|
||||
birthtimeMs: libraryItemStats.birthtimeMs || 0,
|
||||
path: libraryItemData.path,
|
||||
relPath: libraryItemData.relPath,
|
||||
isFile: isSingleMediaItem,
|
||||
mediaMetadata: libraryItemData.mediaMetadata || null,
|
||||
libraryFiles
|
||||
})
|
||||
return await buildLibraryItemScanData(libraryItemData, folder, library, isSingleMediaItem, libraryFiles)
|
||||
}
|
||||
|
||||
async setDataFromPath(path) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../models/LibraryItem')} existingLibraryItem
|
||||
|
|
@ -219,3 +208,22 @@ class LibraryItemScanner {
|
|||
}
|
||||
}
|
||||
module.exports = new LibraryItemScanner()
|
||||
|
||||
async function buildLibraryItemScanData(libraryItemData, folder, library, isSingleMediaItem, libraryFiles) {
|
||||
const libraryItemStats = await fileUtils.getFileTimestampsWithIno(libraryItemData.path)
|
||||
return new LibraryItemScanData({
|
||||
libraryFolderId: folder.id,
|
||||
libraryId: library.id,
|
||||
mediaType: library.mediaType,
|
||||
ino: libraryItemStats.ino,
|
||||
deviceId: libraryItemStats.dev,
|
||||
mtimeMs: libraryItemStats.mtimeMs || 0,
|
||||
ctimeMs: libraryItemStats.ctimeMs || 0,
|
||||
birthtimeMs: libraryItemStats.birthtimeMs || 0,
|
||||
path: libraryItemData.path,
|
||||
relPath: libraryItemData.relPath,
|
||||
isFile: isSingleMediaItem,
|
||||
mediaMetadata: libraryItemData.mediaMetadata || null,
|
||||
libraryFiles
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ class LibraryScanner {
|
|||
* Get scan data for library folder
|
||||
* @param {import('../models/Library')} library
|
||||
* @param {import('../models/LibraryFolder')} folder
|
||||
* @returns {LibraryItemScanData[]}
|
||||
* @returns {Promise<LibraryItemScanData[]>}
|
||||
*/
|
||||
async scanFolder(library, folder) {
|
||||
const folderPath = fileUtils.filePathToPOSIX(folder.path)
|
||||
|
|
@ -350,6 +350,7 @@ class LibraryScanner {
|
|||
libraryId: folder.libraryId,
|
||||
mediaType: library.mediaType,
|
||||
ino: libraryItemFolderStats.ino,
|
||||
deviceId: libraryItemFolderStats.dev,
|
||||
mtimeMs: libraryItemFolderStats.mtimeMs || 0,
|
||||
ctimeMs: libraryItemFolderStats.ctimeMs || 0,
|
||||
birthtimeMs: libraryItemFolderStats.birthtimeMs || 0,
|
||||
|
|
@ -642,12 +643,25 @@ class LibraryScanner {
|
|||
}
|
||||
module.exports = new LibraryScanner()
|
||||
|
||||
/**
|
||||
* @param {import("../models/LibraryItem") | LibraryItemScanData} libraryItem1
|
||||
* @param {import("../models/LibraryItem") | LibraryItemScanData} libraryItem2
|
||||
*/
|
||||
function ItemToFileInoMatch(libraryItem1, libraryItem2) {
|
||||
return libraryItem1.isFile && libraryItem2.libraryFiles.some((lf) => lf.ino === libraryItem1.ino)
|
||||
return (
|
||||
libraryItem1.isFile &&
|
||||
libraryItem2.libraryFiles.some((lf) => {
|
||||
return lf.ino === libraryItem1.ino && lf.deviceId === libraryItem1.deviceId
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LibraryItemScanData} libraryItem1
|
||||
* @param {import("../models/LibraryItem")} libraryItem2
|
||||
*/
|
||||
function ItemToItemInoMatch(libraryItem1, libraryItem2) {
|
||||
return libraryItem1.ino === libraryItem2.ino
|
||||
return libraryItem1.ino === libraryItem2.ino && libraryItem1.deviceId === libraryItem2.deviceId
|
||||
}
|
||||
|
||||
function hasAudioFiles(fileUpdateGroup, itemDir) {
|
||||
|
|
@ -658,54 +672,85 @@ function isSingleMediaFile(fileUpdateGroup, itemDir) {
|
|||
return itemDir === fileUpdateGroup[itemDir]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UUIDV4} libraryId
|
||||
* @param {string} fullPath
|
||||
* @returns {Promise<import('../models/LibraryItem').LibraryItemExpanded | null>} library item that matches
|
||||
*/
|
||||
async function findLibraryItemByItemToItemInoMatch(libraryId, fullPath) {
|
||||
const ino = await fileUtils.getIno(fullPath)
|
||||
const deviceId = await fileUtils.getDeviceId(fullPath)
|
||||
if (!ino) return null
|
||||
const existingLibraryItem = await Database.libraryItemModel.findOneExpanded({
|
||||
libraryId: libraryId,
|
||||
ino: ino
|
||||
ino: ino,
|
||||
deviceId: deviceId
|
||||
})
|
||||
if (existingLibraryItem) Logger.debug(`[LibraryScanner] Found library item with matching inode "${ino}" at path "${existingLibraryItem.path}"`)
|
||||
return existingLibraryItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UUIDV4} libraryId
|
||||
* @param {string} fullPath
|
||||
* @param {boolean} isSingleMedia
|
||||
* @returns {Promise<import('../models/LibraryItem').LibraryItemExpanded | null>} library item that matches
|
||||
*/
|
||||
async function findLibraryItemByItemToFileInoMatch(libraryId, fullPath, isSingleMedia) {
|
||||
if (!isSingleMedia) return null
|
||||
// check if it was moved from another folder by comparing the ino to the library files
|
||||
const ino = await fileUtils.getIno(fullPath)
|
||||
const deviceId = await fileUtils.getDeviceId(fullPath)
|
||||
if (!ino) return null
|
||||
const existingLibraryItem = await Database.libraryItemModel.findOneExpanded(
|
||||
[
|
||||
{
|
||||
libraryId: libraryId
|
||||
},
|
||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM json_each(libraryFiles) WHERE json_valid(json_each.value) AND json_each.value->>"$.ino" = :inode)'), {
|
||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM json_each(libraryFiles) WHERE json_valid(json_each.value) AND json_each.value->>"$.ino" = :inode AND json_each.value->>"$.deviceId" = :deviceId)'), {
|
||||
[sequelize.Op.gt]: 0
|
||||
})
|
||||
],
|
||||
{
|
||||
inode: ino
|
||||
inode: ino,
|
||||
deviceId: deviceId
|
||||
}
|
||||
)
|
||||
if (existingLibraryItem) Logger.debug(`[LibraryScanner] Found library item with a library file matching inode "${ino}" at path "${existingLibraryItem.path}"`)
|
||||
return existingLibraryItem
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UUIDV4} libraryId
|
||||
* @param {string} fullPath
|
||||
* @param {boolean} isSingleMedia
|
||||
* @param {string[]} itemFiles
|
||||
* @returns {Promise<import('../models/LibraryItem').LibraryItemExpanded | null>} library item that matches
|
||||
*/
|
||||
async function findLibraryItemByFileToItemInoMatch(libraryId, fullPath, isSingleMedia, itemFiles) {
|
||||
if (isSingleMedia) return null
|
||||
// check if it was moved from the root folder by comparing the ino to the ino of the scanned files
|
||||
// check if it was moved from the root folder by comparing the ino and deviceId to the ino and deviceId of the scanned files
|
||||
let itemFileInos = []
|
||||
for (const itemFile of itemFiles) {
|
||||
const ino = await fileUtils.getIno(Path.posix.join(fullPath, itemFile))
|
||||
if (ino) itemFileInos.push(ino)
|
||||
const deviceId = await fileUtils.getDeviceId(Path.posix.join(fullPath, itemFile))
|
||||
if (ino && deviceId) itemFileInos.push({ ino: ino, deviceId: deviceId })
|
||||
}
|
||||
if (!itemFileInos.length) return null
|
||||
const existingLibraryItem = await Database.libraryItemModel.findOneExpanded({
|
||||
libraryId: libraryId,
|
||||
ino: {
|
||||
[sequelize.Op.in]: itemFileInos
|
||||
/** @type {import('../models/LibraryItem').LibraryItemExpanded | null} */
|
||||
let existingLibraryItem = null
|
||||
for (let item in itemFileInos) {
|
||||
existingLibraryItem = await Database.libraryItemModel.findOneExpanded({
|
||||
libraryId: libraryId,
|
||||
ino: {
|
||||
[sequelize.Op.in]: itemFileInos
|
||||
}
|
||||
})
|
||||
if (existingLibraryItem) {
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (existingLibraryItem) Logger.debug(`[LibraryScanner] Found library item with inode matching one of "${itemFileInos.join(',')}" at path "${existingLibraryItem.path}"`)
|
||||
return existingLibraryItem
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue