2022-07-06 02:53:01 +02:00
const fs = require ( '../libs/fsExtra' )
2021-11-23 02:58:20 +01:00
const Path = require ( 'path' )
// Utils
const Logger = require ( '../Logger' )
2022-03-13 00:45:32 +01:00
const { groupFilesIntoLibraryItemPaths , getLibraryItemFileData , scanFolder } = require ( '../utils/scandir' )
2022-03-18 15:16:10 +01:00
const { comparePaths } = require ( '../utils/index' )
2022-05-12 01:18:54 +02:00
const { getIno } = require ( '../utils/fileUtils' )
2022-02-27 19:47:56 +01:00
const { ScanResult , LogLevel } = require ( '../utils/constants' )
2021-11-23 02:58:20 +01:00
2022-05-31 02:26:53 +02:00
const MediaFileScanner = require ( './MediaFileScanner' )
2022-03-06 23:32:04 +01:00
const BookFinder = require ( '../finders/BookFinder' )
2022-03-13 00:45:32 +01:00
const LibraryItem = require ( '../objects/LibraryItem' )
2021-11-23 02:58:20 +01:00
const LibraryScan = require ( './LibraryScan' )
const ScanOptions = require ( './ScanOptions' )
2022-03-13 00:45:32 +01:00
const Author = require ( '../objects/entities/Author' )
const Series = require ( '../objects/entities/Series' )
2021-11-23 02:58:20 +01:00
class Scanner {
2022-03-20 22:41:06 +01:00
constructor ( db , coverManager , emitter ) {
2022-02-27 20:47:52 +01:00
this . ScanLogPath = Path . posix . join ( global . MetadataPath , 'logs' , 'scans' )
2021-11-23 02:58:20 +01:00
this . db = db
2022-03-20 22:41:06 +01:00
this . coverManager = coverManager
2021-11-23 02:58:20 +01:00
this . emitter = emitter
this . cancelLibraryScan = { }
this . librariesScanning = [ ]
this . bookFinder = new BookFinder ( )
}
2021-11-26 01:39:02 +01:00
isLibraryScanning ( libraryId ) {
return this . librariesScanning . find ( ls => ls . id === libraryId )
}
2021-11-26 03:25:44 +01:00
setCancelLibraryScan ( libraryId ) {
var libraryScanning = this . librariesScanning . find ( ls => ls . id === libraryId )
if ( ! libraryScanning ) return
this . cancelLibraryScan [ libraryId ] = true
}
2022-03-13 00:45:32 +01:00
async scanLibraryItemById ( libraryItemId ) {
var libraryItem = this . db . libraryItems . find ( li => li . id === libraryItemId )
if ( ! libraryItem ) {
Logger . error ( ` [Scanner] Scan libraryItem by id not found ${ libraryItemId } ` )
2021-11-26 01:39:02 +01:00
return ScanResult . NOTHING
}
2022-03-13 00:45:32 +01:00
const library = this . db . libraries . find ( lib => lib . id === libraryItem . libraryId )
2021-11-26 01:39:02 +01:00
if ( ! library ) {
2022-03-13 00:45:32 +01:00
Logger . error ( ` [Scanner] Scan libraryItem by id library not found " ${ libraryItem . libraryId } " ` )
2021-11-26 01:39:02 +01:00
return ScanResult . NOTHING
}
2022-03-13 00:45:32 +01:00
const folder = library . folders . find ( f => f . id === libraryItem . folderId )
2021-11-26 01:39:02 +01:00
if ( ! folder ) {
2022-03-13 00:45:32 +01:00
Logger . error ( ` [Scanner] Scan libraryItem by id folder not found " ${ libraryItem . folderId } " in library " ${ library . name } " ` )
2021-11-26 01:39:02 +01:00
return ScanResult . NOTHING
}
2022-03-13 00:45:32 +01:00
Logger . info ( ` [Scanner] Scanning Library Item " ${ libraryItem . media . metadata . title } " ` )
return this . scanLibraryItem ( library . mediaType , folder , libraryItem )
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
async scanLibraryItem ( libraryMediaType , folder , libraryItem ) {
2022-05-24 01:15:15 +02:00
// TODO: Support for single media item
var libraryItemData = await getLibraryItemFileData ( libraryMediaType , folder , libraryItem . path , false , this . db . serverSettings )
2022-03-13 00:45:32 +01:00
if ( ! libraryItemData ) {
2021-11-26 01:39:02 +01:00
return ScanResult . NOTHING
}
var hasUpdated = false
2022-03-13 00:45:32 +01:00
var checkRes = libraryItem . checkScanData ( libraryItemData )
2021-11-26 01:39:02 +01:00
if ( checkRes . updated ) hasUpdated = true
// Sync other files first so that local images are used as cover art
2022-03-13 00:45:32 +01:00
if ( await libraryItem . syncFiles ( this . db . serverSettings . scannerPreferOpfMetadata ) ) {
2021-11-26 01:39:02 +01:00
hasUpdated = true
}
// Scan all audio files
2022-03-13 00:45:32 +01:00
if ( libraryItem . hasAudioFiles ) {
var libraryAudioFiles = libraryItem . libraryFiles . filter ( lf => lf . fileType === 'audio' )
2022-06-12 08:56:36 +02:00
if ( await MediaFileScanner . scanMediaFiles ( libraryAudioFiles , libraryItemData , libraryItem , this . db . serverSettings . scannerPreferAudioMetadata , this . db . serverSettings . scannerPreferOverdriveMediaMarker ) ) {
2021-11-26 01:39:02 +01:00
hasUpdated = true
}
// Extract embedded cover art if cover is not already in directory
2022-03-13 00:45:32 +01:00
if ( libraryItem . media . hasEmbeddedCoverArt && ! libraryItem . media . coverPath ) {
2022-03-20 22:41:06 +01:00
var coverPath = await this . coverManager . saveEmbeddedCoverArt ( libraryItem )
2022-03-13 00:45:32 +01:00
if ( coverPath ) {
Logger . debug ( ` [Scanner] Saved embedded cover art " ${ coverPath } " ` )
2021-11-26 01:39:02 +01:00
hasUpdated = true
}
}
}
2022-03-14 01:34:31 +01:00
2022-03-18 20:08:57 +01:00
await this . createNewAuthorsAndSeries ( libraryItem )
2022-05-29 14:25:30 +02:00
// Library Item is invalid - (a book has no audio files or ebook files)
if ( ! libraryItem . hasMediaEntities && libraryItem . mediaType !== 'podcast' ) {
2022-03-13 00:45:32 +01:00
libraryItem . setInvalid ( )
2021-11-26 01:39:02 +01:00
hasUpdated = true
2022-03-13 00:45:32 +01:00
} else if ( libraryItem . isInvalid ) {
libraryItem . isInvalid = false
2021-11-26 01:39:02 +01:00
hasUpdated = true
}
if ( hasUpdated ) {
2022-03-13 00:45:32 +01:00
this . emitter ( 'item_updated' , libraryItem . toJSONExpanded ( ) )
await this . db . updateLibraryItem ( libraryItem )
2021-11-26 01:39:02 +01:00
return ScanResult . UPDATED
}
return ScanResult . UPTODATE
}
2022-03-18 17:51:55 +01:00
async scan ( library , options = { } ) {
if ( this . isLibraryScanning ( library . id ) ) {
Logger . error ( ` [Scanner] Already scanning ${ library . id } ` )
2021-11-23 02:58:20 +01:00
return
}
2022-03-18 17:51:55 +01:00
if ( ! library . folders . length ) {
2021-11-23 02:58:20 +01:00
Logger . warn ( ` [Scanner] Library has no folders to scan " ${ library . name } " ` )
return
}
var scanOptions = new ScanOptions ( )
scanOptions . setData ( options , this . db . serverSettings )
var libraryScan = new LibraryScan ( )
libraryScan . setData ( library , scanOptions )
2021-11-26 01:39:02 +01:00
libraryScan . verbose = false
this . librariesScanning . push ( libraryScan . getScanEmitData )
2021-11-25 03:15:50 +01:00
this . emitter ( 'scan_start' , libraryScan . getScanEmitData )
2021-11-23 02:58:20 +01:00
Logger . info ( ` [Scanner] Starting library scan ${ libraryScan . id } for ${ libraryScan . libraryName } ` )
2021-11-26 01:39:02 +01:00
var canceled = await this . scanLibrary ( libraryScan )
if ( canceled ) {
Logger . info ( ` [Scanner] Library scan canceled for " ${ libraryScan . libraryName } " ` )
delete this . cancelLibraryScan [ libraryScan . libraryId ]
}
2021-11-23 02:58:20 +01:00
2021-11-25 03:15:50 +01:00
libraryScan . setComplete ( )
2021-11-23 02:58:20 +01:00
2021-11-26 01:39:02 +01:00
Logger . info ( ` [Scanner] Library scan ${ libraryScan . id } completed in ${ libraryScan . elapsedTimestamp } | ${ libraryScan . resultStats } ` )
2021-11-25 03:15:50 +01:00
this . librariesScanning = this . librariesScanning . filter ( ls => ls . id !== library . id )
2021-11-26 01:39:02 +01:00
if ( canceled && ! libraryScan . totalResults ) {
var emitData = libraryScan . getScanEmitData
emitData . results = null
this . emitter ( 'scan_complete' , emitData )
return
}
2021-11-25 03:15:50 +01:00
this . emitter ( 'scan_complete' , libraryScan . getScanEmitData )
2021-11-26 01:39:02 +01:00
if ( libraryScan . totalResults ) {
libraryScan . saveLog ( this . ScanLogPath )
}
2021-11-23 02:58:20 +01:00
}
async scanLibrary ( libraryScan ) {
2022-03-13 00:45:32 +01:00
var libraryItemDataFound = [ ]
2021-11-26 01:39:02 +01:00
// Scan each library
2021-11-23 02:58:20 +01:00
for ( let i = 0 ; i < libraryScan . folders . length ; i ++ ) {
var folder = libraryScan . folders [ i ]
2022-03-13 00:45:32 +01:00
var itemDataFoundInFolder = await scanFolder ( libraryScan . libraryMediaType , folder , this . db . serverSettings )
libraryScan . addLog ( LogLevel . INFO , ` ${ itemDataFoundInFolder . length } item data found in folder " ${ folder . fullPath } " ` )
libraryItemDataFound = libraryItemDataFound . concat ( itemDataFoundInFolder )
2021-11-23 02:58:20 +01:00
}
2021-11-26 01:39:02 +01:00
if ( this . cancelLibraryScan [ libraryScan . libraryId ] ) return true
2022-03-26 20:29:49 +01:00
// Remove items with no inode
2022-03-13 00:45:32 +01:00
libraryItemDataFound = libraryItemDataFound . filter ( lid => lid . ino )
var libraryItemsInLibrary = this . db . libraryItems . filter ( li => li . libraryId === libraryScan . libraryId )
2021-11-23 02:58:20 +01:00
2022-03-18 15:16:10 +01:00
const MaxSizePerChunk = 2.5 e9
2022-03-13 00:45:32 +01:00
const itemDataToRescanChunks = [ ]
const newItemDataToScanChunks = [ ]
var itemsToUpdate = [ ]
var itemDataToRescan = [ ]
2022-03-18 15:16:10 +01:00
var itemDataToRescanSize = 0
2022-03-13 00:45:32 +01:00
var newItemDataToScan = [ ]
2022-03-18 15:16:10 +01:00
var newItemDataToScanSize = 0
2022-03-13 00:45:32 +01:00
var itemsToFindCovers = [ ]
// Check for existing & removed library items
for ( let i = 0 ; i < libraryItemsInLibrary . length ; i ++ ) {
var libraryItem = libraryItemsInLibrary [ i ]
// Find library item folder with matching inode or matching path
var dataFound = libraryItemDataFound . find ( lid => lid . ino === libraryItem . ino || comparePaths ( lid . relPath , libraryItem . relPath ) )
2021-11-23 02:58:20 +01:00
if ( ! dataFound ) {
2022-03-13 00:45:32 +01:00
libraryScan . addLog ( LogLevel . WARN , ` Library Item " ${ libraryItem . media . metadata . title } " is missing ` )
2021-11-26 01:39:02 +01:00
libraryScan . resultsMissing ++
2022-03-13 00:45:32 +01:00
libraryItem . setMissing ( )
itemsToUpdate . push ( libraryItem )
2021-11-23 02:58:20 +01:00
} else {
2022-03-13 00:45:32 +01:00
var checkRes = libraryItem . checkScanData ( dataFound )
if ( checkRes . newLibraryFiles . length || libraryScan . scanOptions . forceRescan ) { // Item has new files
checkRes . libraryItem = libraryItem
checkRes . scanData = dataFound
2022-03-18 15:16:10 +01:00
// If this item will go over max size then push current chunk
if ( libraryItem . audioFileTotalSize + itemDataToRescanSize > MaxSizePerChunk && itemDataToRescan . length > 0 ) {
itemDataToRescanChunks . push ( itemDataToRescan )
itemDataToRescanSize = 0
itemDataToRescan = [ ]
}
2022-03-13 00:45:32 +01:00
itemDataToRescan . push ( checkRes )
2022-03-18 15:16:10 +01:00
itemDataToRescanSize += libraryItem . audioFileTotalSize
if ( itemDataToRescanSize >= MaxSizePerChunk ) {
2022-03-13 00:45:32 +01:00
itemDataToRescanChunks . push ( itemDataToRescan )
2022-03-18 15:16:10 +01:00
itemDataToRescanSize = 0
2022-03-13 00:45:32 +01:00
itemDataToRescan = [ ]
2021-11-26 01:39:02 +01:00
}
2022-03-18 15:16:10 +01:00
} else if ( libraryScan . findCovers && libraryItem . media . shouldSearchForCover ) { // Search cover
2021-11-26 01:39:02 +01:00
libraryScan . resultsUpdated ++
2022-03-13 00:45:32 +01:00
itemsToFindCovers . push ( libraryItem )
itemsToUpdate . push ( libraryItem )
2021-11-26 01:39:02 +01:00
} else if ( checkRes . updated ) { // Updated but no scan required
2021-11-25 03:15:50 +01:00
libraryScan . resultsUpdated ++
2022-03-13 00:45:32 +01:00
itemsToUpdate . push ( libraryItem )
2021-11-23 02:58:20 +01:00
}
2022-03-13 00:45:32 +01:00
libraryItemDataFound = libraryItemDataFound . filter ( lid => lid . ino !== dataFound . ino )
2021-11-23 02:58:20 +01:00
}
}
2022-03-13 00:45:32 +01:00
if ( itemDataToRescan . length ) itemDataToRescanChunks . push ( itemDataToRescan )
// Potential NEW Library Items
for ( let i = 0 ; i < libraryItemDataFound . length ; i ++ ) {
var dataFound = libraryItemDataFound [ i ]
2021-11-23 02:58:20 +01:00
2022-03-13 00:45:32 +01:00
var hasMediaFile = dataFound . libraryFiles . some ( lf => lf . isMediaFile )
if ( ! hasMediaFile ) {
2022-04-28 02:42:34 +02:00
libraryScan . addLog ( LogLevel . WARN , ` Item found " ${ libraryItemDataFound . path } " has no media files ` )
2021-11-23 02:58:20 +01:00
} else {
2022-05-31 02:26:53 +02:00
var mediaFileSize = 0
dataFound . libraryFiles . filter ( lf => lf . fileType === 'audio' || lf . fileType === 'video' ) . forEach ( lf => mediaFileSize += lf . metadata . size )
2022-03-18 15:16:10 +01:00
// If this item will go over max size then push current chunk
2022-05-31 02:26:53 +02:00
if ( mediaFileSize + newItemDataToScanSize > MaxSizePerChunk && newItemDataToScan . length > 0 ) {
2022-03-18 15:16:10 +01:00
newItemDataToScanChunks . push ( newItemDataToScan )
newItemDataToScanSize = 0
newItemDataToScan = [ ]
}
2022-03-13 00:45:32 +01:00
newItemDataToScan . push ( dataFound )
2022-05-31 02:26:53 +02:00
newItemDataToScanSize += mediaFileSize
2022-03-18 15:16:10 +01:00
if ( newItemDataToScanSize >= MaxSizePerChunk ) {
2022-03-13 00:45:32 +01:00
newItemDataToScanChunks . push ( newItemDataToScan )
2022-03-18 15:16:10 +01:00
newItemDataToScanSize = 0
2022-03-13 00:45:32 +01:00
newItemDataToScan = [ ]
2021-11-26 01:39:02 +01:00
}
2021-11-23 02:58:20 +01:00
}
}
2022-03-13 00:45:32 +01:00
if ( newItemDataToScan . length ) newItemDataToScanChunks . push ( newItemDataToScan )
2021-11-23 02:58:20 +01:00
2022-03-13 00:45:32 +01:00
// Library Items not requiring a scan but require a search for cover
for ( let i = 0 ; i < itemsToFindCovers . length ; i ++ ) {
var libraryItem = itemsToFindCovers [ i ]
var updatedCover = await this . searchForCover ( libraryItem , libraryScan )
libraryItem . media . updateLastCoverSearch ( updatedCover )
2021-11-23 02:58:20 +01:00
}
2022-03-18 15:16:10 +01:00
if ( itemsToUpdate . length ) {
await this . updateLibraryItemChunk ( itemsToUpdate )
2021-11-26 01:39:02 +01:00
if ( this . cancelLibraryScan [ libraryScan . libraryId ] ) return true
}
2022-03-13 00:45:32 +01:00
for ( let i = 0 ; i < itemDataToRescanChunks . length ; i ++ ) {
await this . rescanLibraryItemDataChunk ( itemDataToRescanChunks [ i ] , libraryScan )
2021-11-26 01:39:02 +01:00
if ( this . cancelLibraryScan [ libraryScan . libraryId ] ) return true
2022-03-13 00:45:32 +01:00
// console.log('Rescan chunk done', i, 'of', itemDataToRescanChunks.length)
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
for ( let i = 0 ; i < newItemDataToScanChunks . length ; i ++ ) {
await this . scanNewLibraryItemDataChunk ( newItemDataToScanChunks [ i ] , libraryScan )
// console.log('New scan chunk done', i, 'of', newItemDataToScanChunks.length)
2021-11-26 01:39:02 +01:00
if ( this . cancelLibraryScan [ libraryScan . libraryId ] ) return true
2021-11-23 02:58:20 +01:00
}
}
2022-03-13 00:45:32 +01:00
async updateLibraryItemChunk ( itemsToUpdate ) {
await this . db . updateLibraryItems ( itemsToUpdate )
this . emitter ( 'items_updated' , itemsToUpdate . map ( li => li . toJSONExpanded ( ) ) )
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
async rescanLibraryItemDataChunk ( itemDataToRescan , libraryScan ) {
var itemsUpdated = await Promise . all ( itemDataToRescan . map ( ( lid ) => {
return this . rescanLibraryItem ( lid , libraryScan )
2021-11-26 01:39:02 +01:00
} ) )
2022-03-18 20:08:57 +01:00
2022-05-09 14:23:29 +02:00
itemsUpdated = itemsUpdated . filter ( li => li ) // Filter out nulls
2022-03-18 20:08:57 +01:00
for ( const libraryItem of itemsUpdated ) {
// Temp authors & series are inserted - create them if found
await this . createNewAuthorsAndSeries ( libraryItem )
}
2022-03-13 00:45:32 +01:00
if ( itemsUpdated . length ) {
libraryScan . resultsUpdated += itemsUpdated . length
await this . db . updateLibraryItems ( itemsUpdated )
this . emitter ( 'items_updated' , itemsUpdated . map ( li => li . toJSONExpanded ( ) ) )
2021-12-05 18:29:42 +01:00
}
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
async scanNewLibraryItemDataChunk ( newLibraryItemsData , libraryScan ) {
var newLibraryItems = await Promise . all ( newLibraryItemsData . map ( ( lid ) => {
2022-06-12 10:54:58 +02:00
return this . scanNewLibraryItem ( lid , libraryScan . libraryMediaType , libraryScan . preferAudioMetadata , libraryScan . preferOpfMetadata , libraryScan . findCovers , libraryScan . preferOverdriveMediaMarker , libraryScan )
2021-11-26 01:39:02 +01:00
} ) )
2022-03-13 00:45:32 +01:00
newLibraryItems = newLibraryItems . filter ( li => li ) // Filter out nulls
2022-03-18 20:08:57 +01:00
for ( const libraryItem of newLibraryItems ) {
// Temp authors & series are inserted - create them if found
await this . createNewAuthorsAndSeries ( libraryItem )
}
2022-03-13 00:45:32 +01:00
libraryScan . resultsAdded += newLibraryItems . length
await this . db . insertLibraryItems ( newLibraryItems )
this . emitter ( 'items_added' , newLibraryItems . map ( li => li . toJSONExpanded ( ) ) )
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
async rescanLibraryItem ( libraryItemCheckData , libraryScan ) {
const { newLibraryFiles , filesRemoved , existingLibraryFiles , libraryItem , scanData , updated } = libraryItemCheckData
libraryScan . addLog ( LogLevel . DEBUG , ` Library " ${ libraryScan . libraryName } " Re-scanning " ${ libraryItem . path } " ` )
2021-12-05 18:29:42 +01:00
var hasUpdated = updated
2021-11-26 01:39:02 +01:00
// Sync other files first to use local images as cover before extracting audio file cover
2022-03-13 00:45:32 +01:00
if ( await libraryItem . syncFiles ( libraryScan . preferOpfMetadata ) ) {
hasUpdated = true
2021-11-26 01:39:02 +01:00
}
2021-11-25 03:15:50 +01:00
2021-12-05 18:29:42 +01:00
// forceRescan all existing audio files - will probe and update ID3 tag metadata
2022-03-13 00:45:32 +01:00
var existingAudioFiles = existingLibraryFiles . filter ( lf => lf . fileType === 'audio' )
if ( libraryScan . scanOptions . forceRescan && existingAudioFiles . length ) {
2022-06-12 10:54:58 +02:00
if ( await MediaFileScanner . scanMediaFiles ( existingAudioFiles , scanData , libraryItem , libraryScan . preferAudioMetadata , libraryScan . preferOverdriveMediaMarker , libraryScan ) ) {
2021-12-05 18:29:42 +01:00
hasUpdated = true
}
}
// Scan new audio files
2022-03-13 00:45:32 +01:00
var newAudioFiles = newLibraryFiles . filter ( lf => lf . fileType === 'audio' )
var removedAudioFiles = filesRemoved . filter ( lf => lf . fileType === 'audio' )
if ( newAudioFiles . length || removedAudioFiles . length ) {
2022-06-12 10:54:58 +02:00
if ( await MediaFileScanner . scanMediaFiles ( newAudioFiles , scanData , libraryItem , libraryScan . preferAudioMetadata , libraryScan . preferOverdriveMediaMarker , libraryScan ) ) {
2021-12-05 18:29:42 +01:00
hasUpdated = true
}
}
// If an audio file has embedded cover art and no cover is set yet, extract & use it
2022-03-13 00:45:32 +01:00
if ( newAudioFiles . length || libraryScan . scanOptions . forceRescan ) {
if ( libraryItem . media . hasEmbeddedCoverArt && ! libraryItem . media . coverPath ) {
2022-03-20 22:41:06 +01:00
var savedCoverPath = await this . coverManager . saveEmbeddedCoverArt ( libraryItem )
2022-03-13 00:45:32 +01:00
if ( savedCoverPath ) {
2021-12-05 18:29:42 +01:00
hasUpdated = true
2022-03-13 00:45:32 +01:00
libraryScan . addLog ( LogLevel . DEBUG , ` Saved embedded cover art " ${ savedCoverPath } " ` )
2021-11-25 03:15:50 +01:00
}
}
2021-11-23 02:58:20 +01:00
}
2021-11-26 01:39:02 +01:00
2022-05-29 14:25:30 +02:00
// Library Item is invalid - (a book has no audio files or ebook files)
if ( ! libraryItem . hasMediaEntities && libraryItem . mediaType !== 'podcast' ) {
2022-03-13 00:45:32 +01:00
libraryItem . setInvalid ( )
2021-12-05 18:29:42 +01:00
hasUpdated = true
2022-03-13 00:45:32 +01:00
} else if ( libraryItem . isInvalid ) {
libraryItem . isInvalid = false
2021-12-05 18:29:42 +01:00
hasUpdated = true
2021-11-26 01:39:02 +01:00
}
2021-12-05 18:29:42 +01:00
// Scan for cover if enabled and has no cover (and author or title has changed OR has been 7 days since last lookup)
2022-03-13 00:45:32 +01:00
if ( libraryScan . findCovers && ! libraryItem . media . coverPath && libraryItem . media . shouldSearchForCover ) {
var updatedCover = await this . searchForCover ( libraryItem , libraryScan )
libraryItem . media . updateLastCoverSearch ( updatedCover )
2021-12-05 18:29:42 +01:00
hasUpdated = true
2021-11-23 02:58:20 +01:00
}
2021-11-26 01:39:02 +01:00
2022-03-13 00:45:32 +01:00
return hasUpdated ? libraryItem : null
2021-11-25 03:15:50 +01:00
}
2021-11-23 02:58:20 +01:00
2022-06-12 08:56:36 +02:00
async scanNewLibraryItem ( libraryItemData , libraryMediaType , preferAudioMetadata , preferOpfMetadata , findCovers , preferOverdriveMediaMarker , libraryScan = null ) {
2022-03-13 00:45:32 +01:00
if ( libraryScan ) libraryScan . addLog ( LogLevel . DEBUG , ` Scanning new library item " ${ libraryItemData . path } " ` )
else Logger . debug ( ` [Scanner] Scanning new item " ${ libraryItemData . path } " ` )
2021-11-26 01:39:02 +01:00
2022-03-13 00:45:32 +01:00
var libraryItem = new LibraryItem ( )
libraryItem . setData ( libraryMediaType , libraryItemData )
2021-11-25 03:15:50 +01:00
2022-05-31 02:26:53 +02:00
var mediaFiles = libraryItemData . libraryFiles . filter ( lf => lf . fileType === 'audio' || lf . fileType === 'video' )
if ( mediaFiles . length ) {
2022-06-12 08:56:36 +02:00
await MediaFileScanner . scanMediaFiles ( mediaFiles , libraryItemData , libraryItem , preferAudioMetadata , preferOverdriveMediaMarker , libraryScan )
2021-11-26 01:39:02 +01:00
}
2022-04-20 04:10:24 +02:00
await libraryItem . syncFiles ( preferOpfMetadata )
2022-03-17 01:15:25 +01:00
if ( ! libraryItem . hasMediaEntities ) {
2022-03-13 00:45:32 +01:00
Logger . warn ( ` [Scanner] Library item has no media files " ${ libraryItemData . path } " ` )
2021-11-26 01:39:02 +01:00
return null
2021-11-23 02:58:20 +01:00
}
2021-11-25 03:15:50 +01:00
// Extract embedded cover art if cover is not already in directory
2022-03-13 00:45:32 +01:00
if ( libraryItem . media . hasEmbeddedCoverArt && ! libraryItem . media . coverPath ) {
2022-03-20 22:41:06 +01:00
var coverPath = await this . coverManager . saveEmbeddedCoverArt ( libraryItem )
2022-03-13 00:45:32 +01:00
if ( coverPath ) {
if ( libraryScan ) libraryScan . addLog ( LogLevel . DEBUG , ` Saved embedded cover art " ${ coverPath } " ` )
else Logger . debug ( ` [Scanner] Saved embedded cover art " ${ coverPath } " ` )
2021-11-25 03:15:50 +01:00
}
2021-11-23 02:58:20 +01:00
}
2021-11-25 03:15:50 +01:00
2021-11-26 01:39:02 +01:00
// Scan for cover if enabled and has no cover
2022-05-31 02:26:53 +02:00
if ( libraryMediaType === 'book' ) {
2022-03-13 00:45:32 +01:00
if ( libraryItem && findCovers && ! libraryItem . media . coverPath && libraryItem . media . shouldSearchForCover ) {
var updatedCover = await this . searchForCover ( libraryItem , libraryScan )
libraryItem . media . updateLastCoverSearch ( updatedCover )
}
2022-03-13 19:47:36 +01:00
}
return libraryItem
}
2022-04-12 23:05:16 +02:00
// Any series or author object on library item with an id starting with "new"
// will create a new author/series OR find a matching author/series
2022-03-13 19:47:36 +01:00
async createNewAuthorsAndSeries ( libraryItem ) {
2022-03-18 20:08:57 +01:00
if ( libraryItem . mediaType !== 'book' ) return
2022-03-13 19:47:36 +01:00
// Create or match all new authors and series
if ( libraryItem . media . metadata . authors . some ( au => au . id . startsWith ( 'new' ) ) ) {
var newAuthors = [ ]
libraryItem . media . metadata . authors = libraryItem . media . metadata . authors . map ( ( tempMinAuthor ) => {
var _author = this . db . authors . find ( au => au . checkNameEquals ( tempMinAuthor . name ) )
if ( ! _author ) _author = newAuthors . find ( au => au . checkNameEquals ( tempMinAuthor . name ) ) // Check new unsaved authors
2022-04-12 23:05:16 +02:00
if ( ! _author ) { // Must create new author
2022-03-13 19:47:36 +01:00
_author = new Author ( )
_author . setData ( tempMinAuthor )
newAuthors . push ( _author )
2022-03-13 00:45:32 +01:00
}
2022-04-12 23:05:16 +02:00
2022-03-13 19:47:36 +01:00
return {
id : _author . id ,
name : _author . name
}
} )
if ( newAuthors . length ) {
await this . db . insertEntities ( 'author' , newAuthors )
this . emitter ( 'authors_added' , newAuthors . map ( au => au . toJSON ( ) ) )
2022-03-13 00:45:32 +01:00
}
2022-03-13 19:47:36 +01:00
}
if ( libraryItem . media . metadata . series . some ( se => se . id . startsWith ( 'new' ) ) ) {
var newSeries = [ ]
libraryItem . media . metadata . series = libraryItem . media . metadata . series . map ( ( tempMinSeries ) => {
var _series = this . db . series . find ( se => se . checkNameEquals ( tempMinSeries . name ) )
if ( ! _series ) _series = newSeries . find ( se => se . checkNameEquals ( tempMinSeries . name ) ) // Check new unsaved series
2022-04-12 23:05:16 +02:00
if ( ! _series ) { // Must create new series
2022-03-13 19:47:36 +01:00
_series = new Series ( )
_series . setData ( tempMinSeries )
newSeries . push ( _series )
2022-03-13 00:45:32 +01:00
}
2022-03-13 19:47:36 +01:00
return {
id : _series . id ,
name : _series . name ,
sequence : tempMinSeries . sequence
}
} )
if ( newSeries . length ) {
await this . db . insertEntities ( 'series' , newSeries )
this . emitter ( 'series_added' , newSeries . map ( se => se . toJSON ( ) ) )
2022-03-13 00:45:32 +01:00
}
2021-11-26 01:39:02 +01:00
}
2021-11-23 02:58:20 +01:00
}
2021-11-26 01:39:02 +01:00
getFileUpdatesGrouped ( fileUpdates ) {
var folderGroups = { }
fileUpdates . forEach ( ( file ) => {
if ( folderGroups [ file . folderId ] ) {
folderGroups [ file . folderId ] . fileUpdates . push ( file )
} else {
folderGroups [ file . folderId ] = {
libraryId : file . libraryId ,
folderId : file . folderId ,
fileUpdates : [ file ]
}
}
} )
return folderGroups
}
async scanFilesChanged ( fileUpdates ) {
if ( ! fileUpdates . length ) return
// files grouped by folder
var folderGroups = this . getFileUpdatesGrouped ( fileUpdates )
for ( const folderId in folderGroups ) {
var libraryId = folderGroups [ folderId ] . libraryId
var library = this . db . libraries . find ( lib => lib . id === libraryId )
if ( ! library ) {
Logger . error ( ` [Scanner] Library not found in files changed ${ libraryId } ` )
continue ;
}
var folder = library . getFolderById ( folderId )
if ( ! folder ) {
Logger . error ( ` [Scanner] Folder is not in library in files changed " ${ folderId } ", Library " ${ library . name } " ` )
continue ;
}
var relFilePaths = folderGroups [ folderId ] . fileUpdates . map ( fileUpdate => fileUpdate . relPath )
2022-05-24 01:15:15 +02:00
var fileUpdateGroup = groupFilesIntoLibraryItemPaths ( library . mediaType , relFilePaths )
if ( ! Object . keys ( fileUpdateGroup ) . length ) {
Logger . info ( ` [Scanner] No important changes to scan for in folder " ${ folderId } " ` )
continue ;
}
2022-03-13 00:45:32 +01:00
var folderScanResults = await this . scanFolderUpdates ( library , folder , fileUpdateGroup )
2021-11-26 01:39:02 +01:00
Logger . debug ( ` [Scanner] Folder scan results ` , folderScanResults )
}
}
2022-03-13 00:45:32 +01:00
async scanFolderUpdates ( library , folder , fileUpdateGroup ) {
2021-11-26 01:39:02 +01:00
Logger . debug ( ` [Scanner] Scanning file update groups in folder " ${ folder . id } " of library " ${ library . name } " ` )
2022-03-22 01:24:38 +01:00
Logger . debug ( ` [Scanner] scanFolderUpdates fileUpdateGroup ` , fileUpdateGroup )
2021-11-26 01:39:02 +01:00
2022-03-13 00:45:32 +01:00
// First pass - Remove files in parent dirs of items and remap the fileupdate group
// Test Case: Moving audio files from library item folder to author folder should trigger a re-scan of the item
var updateGroup = { ... fileUpdateGroup }
for ( const itemDir in updateGroup ) {
2022-05-24 01:15:15 +02:00
if ( itemDir == fileUpdateGroup [ itemDir ] ) continue ; // Media in root path
2022-03-13 00:45:32 +01:00
var itemDirNestedFiles = fileUpdateGroup [ itemDir ] . filter ( b => b . includes ( '/' ) )
if ( ! itemDirNestedFiles . length ) continue ;
2022-01-10 18:12:47 +01:00
2022-03-13 00:45:32 +01:00
var firstNest = itemDirNestedFiles [ 0 ] . split ( '/' ) . shift ( )
var altDir = ` ${ itemDir } / ${ firstNest } `
2022-01-10 18:12:47 +01:00
2022-03-13 00:45:32 +01:00
var fullPath = Path . posix . join ( folder . fullPath . replace ( /\\/g , '/' ) , itemDir )
2022-04-23 07:29:38 +02:00
var childLibraryItem = this . db . libraryItems . find ( li => li . path !== fullPath && li . path . startsWith ( fullPath ) )
2022-03-13 00:45:32 +01:00
if ( ! childLibraryItem ) {
2022-01-10 18:12:47 +01:00
continue ;
}
var altFullPath = Path . posix . join ( folder . fullPath . replace ( /\\/g , '/' ) , altDir )
2022-03-13 00:45:32 +01:00
var altChildLibraryItem = this . db . libraryItems . find ( li => li . path !== altFullPath && li . path . startsWith ( altFullPath ) )
if ( altChildLibraryItem ) {
2022-01-10 18:12:47 +01:00
continue ;
}
2022-03-13 00:45:32 +01:00
delete fileUpdateGroup [ itemDir ]
fileUpdateGroup [ altDir ] = itemDirNestedFiles . map ( ( f ) => f . split ( '/' ) . slice ( 1 ) . join ( '/' ) )
Logger . warn ( ` [Scanner] Some files were modified in a parent directory of a library item " ${ childLibraryItem . title } " - ignoring ` )
2022-01-10 18:12:47 +01:00
}
2022-03-13 00:45:32 +01:00
// Second pass: Check for new/updated/removed items
var itemGroupingResults = { }
for ( const itemDir in fileUpdateGroup ) {
var fullPath = Path . posix . join ( folder . fullPath . replace ( /\\/g , '/' ) , itemDir )
2022-05-12 01:18:54 +02:00
const dirIno = await getIno ( fullPath )
2021-11-26 01:39:02 +01:00
2022-03-13 00:45:32 +01:00
// Check if book dir group is already an item
var existingLibraryItem = this . db . libraryItems . find ( li => fullPath . startsWith ( li . path ) )
2022-05-12 01:18:54 +02:00
if ( ! existingLibraryItem ) {
existingLibraryItem = this . db . libraryItems . find ( li => li . ino === dirIno )
if ( existingLibraryItem ) {
Logger . debug ( ` [Scanner] scanFolderUpdates: Library item found by inode value " ${ existingLibraryItem . relPath } => ${ itemDir } " ` )
// Update library item paths for scan and all library item paths will get updated in LibraryItem.checkScanData
existingLibraryItem . path = fullPath
existingLibraryItem . relPath = itemDir
}
}
2022-03-13 00:45:32 +01:00
if ( existingLibraryItem ) {
// Is the item exactly - check if was deleted
if ( existingLibraryItem . path === fullPath ) {
2021-11-26 01:39:02 +01:00
var exists = await fs . pathExists ( fullPath )
if ( ! exists ) {
2022-03-13 00:45:32 +01:00
Logger . info ( ` [Scanner] Scanning file update group and library item was deleted " ${ existingLibraryItem . media . metadata . title } " - marking as missing ` )
existingLibraryItem . setMissing ( )
await this . db . updateLibraryItem ( existingLibraryItem )
this . emitter ( 'item_updated' , existingLibraryItem . toJSONExpanded ( ) )
2021-11-26 01:39:02 +01:00
2022-03-13 00:45:32 +01:00
itemGroupingResults [ itemDir ] = ScanResult . REMOVED
2021-11-26 01:39:02 +01:00
continue ;
}
}
2022-03-13 00:45:32 +01:00
// Scan library item for updates
Logger . debug ( ` [Scanner] Folder update for relative path " ${ itemDir } " is in library item " ${ existingLibraryItem . media . metadata . title } " - scan for updates ` )
itemGroupingResults [ itemDir ] = await this . scanLibraryItem ( library . mediaType , folder , existingLibraryItem )
2021-11-26 01:39:02 +01:00
continue ;
}
2022-03-13 00:45:32 +01:00
// Check if a library item is a subdirectory of this dir
var childItem = this . db . libraryItems . find ( li => li . path . startsWith ( fullPath ) )
if ( childItem ) {
Logger . warn ( ` [Scanner] Files were modified in a parent directory of a library item " ${ childItem . media . metadata . title } " - ignoring ` )
itemGroupingResults [ itemDir ] = ScanResult . NOTHING
2021-11-26 01:39:02 +01:00
continue ;
}
2022-03-13 00:45:32 +01:00
Logger . debug ( ` [Scanner] Folder update group must be a new item " ${ itemDir } " in library " ${ library . name } " ` )
2022-05-24 01:15:15 +02:00
var isSingleMediaItem = itemDir === fileUpdateGroup [ itemDir ]
var newLibraryItem = await this . scanPotentialNewLibraryItem ( library . mediaType , folder , fullPath , isSingleMediaItem )
2022-03-13 00:45:32 +01:00
if ( newLibraryItem ) {
2022-03-18 20:08:57 +01:00
await this . createNewAuthorsAndSeries ( newLibraryItem )
2022-03-13 00:45:32 +01:00
await this . db . insertLibraryItem ( newLibraryItem )
this . emitter ( 'item_added' , newLibraryItem . toJSONExpanded ( ) )
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
itemGroupingResults [ itemDir ] = newLibraryItem ? ScanResult . ADDED : ScanResult . NOTHING
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
return itemGroupingResults
2021-11-26 01:39:02 +01:00
}
2022-05-24 01:15:15 +02:00
async scanPotentialNewLibraryItem ( libraryMediaType , folder , fullPath , isSingleMediaItem = false ) {
var libraryItemData = await getLibraryItemFileData ( libraryMediaType , folder , fullPath , isSingleMediaItem , this . db . serverSettings )
2022-03-13 00:45:32 +01:00
if ( ! libraryItemData ) return null
2021-11-26 01:39:02 +01:00
var serverSettings = this . db . serverSettings
2022-06-12 08:56:36 +02:00
return this . scanNewLibraryItem ( libraryItemData , libraryMediaType , serverSettings . scannerPreferAudioMetadata , serverSettings . scannerPreferOpfMetadata , serverSettings . scannerFindCovers , serverSettings . scannerPreferOverdriveMediaMarker )
2021-11-26 01:39:02 +01:00
}
2022-03-13 00:45:32 +01:00
async searchForCover ( libraryItem , libraryScan = null ) {
2021-11-26 01:39:02 +01:00
var options = {
titleDistance : 2 ,
authorDistance : 2
}
2022-01-09 00:03:33 +01:00
var scannerCoverProvider = this . db . serverSettings . scannerCoverProvider
2022-03-13 00:45:32 +01:00
var results = await this . bookFinder . findCovers ( scannerCoverProvider , libraryItem . media . metadata . title , libraryItem . media . metadata . authorName , options )
2021-11-26 01:39:02 +01:00
if ( results . length ) {
2022-03-13 00:45:32 +01:00
if ( libraryScan ) libraryScan . addLog ( LogLevel . DEBUG , ` Found best cover for " ${ libraryItem . media . metadata . title } " ` )
else Logger . debug ( ` [Scanner] Found best cover for " ${ libraryItem . media . metadata . title } " ` )
2021-11-26 01:39:02 +01:00
// If the first cover result fails, attempt to download the second
for ( let i = 0 ; i < results . length && i < 2 ; i ++ ) {
// Downloads and updates the book cover
2022-03-20 22:41:06 +01:00
var result = await this . coverManager . downloadCoverFromUrl ( libraryItem , results [ i ] )
2021-11-26 01:39:02 +01:00
if ( result . error ) {
Logger . error ( ` [Scanner] Failed to download cover from url " ${ results [ i ] } " | Attempt ${ i + 1 } ` , result . error )
} else {
return true
}
}
}
return false
2021-12-25 01:06:17 +01:00
}
2022-04-21 01:05:09 +02:00
async quickMatchLibraryItem ( libraryItem , options = { } ) {
2022-02-16 01:33:33 +01:00
var provider = options . provider || 'google'
2022-03-14 01:34:31 +01:00
var searchTitle = options . title || libraryItem . media . metadata . title
var searchAuthor = options . author || libraryItem . media . metadata . authorName
2022-05-23 04:56:51 +02:00
var searchISBN = options . isbn || libraryItem . media . metadata . isbn
var searchASIN = options . asin || libraryItem . media . metadata . asin
2022-02-16 01:33:33 +01:00
2022-05-23 04:56:51 +02:00
var results = await this . bookFinder . search ( provider , searchTitle , searchAuthor , searchISBN , searchASIN )
2022-02-16 01:33:33 +01:00
if ( ! results . length ) {
return {
warning : ` No ${ provider } match found `
}
}
var matchData = results [ 0 ]
2022-05-23 04:56:51 +02:00
// Set to override existing metadata if scannerPreferMatchedMetadata setting is true
2022-05-29 14:25:30 +02:00
if ( this . db . serverSettings . scannerPreferMatchedMetadata ) {
2022-05-23 04:56:51 +02:00
options . overrideCover = true
options . overrideDetails = true
}
2022-02-16 01:33:33 +01:00
// Update cover if not set OR overrideCover flag
var hasUpdated = false
2022-03-14 01:34:31 +01:00
if ( matchData . cover && ( ! libraryItem . media . coverPath || options . overrideCover ) ) {
Logger . debug ( ` [Scanner] Updating cover " ${ matchData . cover } " ` )
2022-03-20 22:41:06 +01:00
var coverResult = await this . coverManager . downloadCoverFromUrl ( libraryItem , matchData . cover )
2022-02-16 01:33:33 +01:00
if ( ! coverResult || coverResult . error || ! coverResult . cover ) {
2022-03-14 01:34:31 +01:00
Logger . warn ( ` [Scanner] Match cover " ${ matchData . cover } " failed to use: ${ coverResult ? coverResult . error : 'Unknown Error' } ` )
2022-02-16 01:33:33 +01:00
} else {
hasUpdated = true
}
}
2022-03-14 01:34:31 +01:00
// Update media metadata if not set OR overrideDetails flag
2022-05-23 04:56:51 +02:00
const detailKeysToUpdate = [ 'title' , 'subtitle' , 'description' , 'narrator' , 'publisher' , 'publishedYear' , 'genres' , 'tags' , 'language' , 'explicit' , 'asin' , 'isbn' ]
2022-02-16 01:33:33 +01:00
const updatePayload = { }
2022-05-23 04:56:51 +02:00
updatePayload . metadata = { }
2022-02-16 01:33:33 +01:00
for ( const key in matchData ) {
2022-03-14 01:34:31 +01:00
if ( matchData [ key ] && detailKeysToUpdate . includes ( key ) ) {
if ( key === 'narrator' ) {
if ( ( ! libraryItem . media . metadata . narratorName || options . overrideDetails ) ) {
2022-07-06 00:26:14 +02:00
updatePayload . metadata . narrators = matchData [ key ] . split ( ',' ) . map ( v => v . trim ( ) ) . filter ( v => ! ! v )
2022-05-23 04:56:51 +02:00
}
} else if ( key === 'genres' ) {
if ( ( ! libraryItem . media . metadata . genres || options . overrideDetails ) ) {
2022-07-06 00:26:14 +02:00
updatePayload . metadata [ key ] = matchData [ key ] . split ( ',' ) . map ( v => v . trim ( ) ) . filter ( v => ! ! v )
2022-05-23 04:56:51 +02:00
}
} else if ( key === 'tags' ) {
if ( ( ! libraryItem . media . tags || options . overrideDetails ) ) {
2022-07-06 00:26:14 +02:00
updatePayload [ key ] = matchData [ key ] . split ( ',' ) . map ( v => v . trim ( ) ) . filter ( v => ! ! v )
2022-03-14 01:34:31 +01:00
}
} else if ( ( ! libraryItem . media . metadata [ key ] || options . overrideDetails ) ) {
2022-05-23 04:56:51 +02:00
updatePayload . metadata [ key ] = matchData [ key ]
2022-03-14 01:34:31 +01:00
}
}
}
// Add or set author if not set
2022-06-04 17:52:37 +02:00
if ( matchData . author && ( ! libraryItem . media . metadata . authorName || options . overrideDetails ) ) {
2022-07-06 00:26:14 +02:00
if ( ! Array . isArray ( matchData . author ) ) {
matchData . author = matchData . author . split ( ',' ) . map ( au => au . trim ( ) ) . filter ( au => ! ! au )
}
2022-05-23 04:56:51 +02:00
const authorPayload = [ ]
for ( let index = 0 ; index < matchData . author . length ; index ++ ) {
const authorName = matchData . author [ index ]
var author = this . db . authors . find ( au => au . checkNameEquals ( authorName ) )
if ( ! author ) {
author = new Author ( )
author . setData ( { name : authorName } )
await this . db . insertEntity ( 'author' , author )
this . emitter ( 'author_added' , author )
}
authorPayload . push ( author . toJSONMinimal ( ) )
2022-02-16 01:33:33 +01:00
}
2022-05-23 04:56:51 +02:00
updatePayload . metadata . authors = authorPayload
2022-03-14 01:34:31 +01:00
}
// Add or set series if not set
2022-06-04 17:52:37 +02:00
if ( matchData . series && ( ! libraryItem . media . metadata . seriesName || options . overrideDetails ) ) {
2022-05-29 14:25:30 +02:00
if ( ! Array . isArray ( matchData . series ) ) matchData . series = [ { series : matchData . series , volumeNumber : matchData . volumeNumber } ]
2022-05-23 04:56:51 +02:00
const seriesPayload = [ ]
for ( let index = 0 ; index < matchData . series . length ; index ++ ) {
const seriesMatchItem = matchData . series [ index ]
var seriesItem = this . db . series . find ( au => au . checkNameEquals ( seriesMatchItem . series ) )
if ( ! seriesItem ) {
seriesItem = new Series ( )
seriesItem . setData ( { name : seriesMatchItem . series } )
await this . db . insertEntity ( 'series' , seriesItem )
this . emitter ( 'series_added' , seriesItem )
}
seriesPayload . push ( seriesItem . toJSONMinimal ( seriesMatchItem . volumeNumber ) )
2022-03-14 01:34:31 +01:00
}
2022-05-23 04:56:51 +02:00
updatePayload . metadata . series = seriesPayload
2022-02-16 01:33:33 +01:00
}
if ( Object . keys ( updatePayload ) . length ) {
2022-03-14 01:34:31 +01:00
Logger . debug ( '[Scanner] Updating details' , updatePayload )
2022-05-23 04:56:51 +02:00
if ( libraryItem . media . update ( updatePayload ) ) {
2022-02-16 01:33:33 +01:00
hasUpdated = true
}
}
if ( hasUpdated ) {
2022-03-14 01:34:31 +01:00
await this . db . updateLibraryItem ( libraryItem )
this . emitter ( 'item_updated' , libraryItem . toJSONExpanded ( ) )
2022-02-16 01:33:33 +01:00
}
return {
updated : hasUpdated ,
2022-03-14 01:34:31 +01:00
libraryItem : libraryItem . toJSONExpanded ( )
2022-02-16 01:33:33 +01:00
}
}
2022-04-21 01:05:09 +02:00
async matchLibraryItems ( library ) {
if ( library . mediaType === 'podcast' ) {
Logger . error ( ` [Scanner] matchLibraryItems: Match all not supported for podcasts yet ` )
return
}
if ( this . isLibraryScanning ( library . id ) ) {
Logger . error ( ` [Scanner] matchLibraryItems: Already scanning ${ library . id } ` )
return
}
var itemsInLibrary = this . db . getLibraryItemsInLibrary ( library . id )
if ( ! itemsInLibrary . length ) {
Logger . error ( ` [Scanner] matchLibraryItems: Library has no items ${ library . id } ` )
return
}
const provider = library . provider
var libraryScan = new LibraryScan ( )
libraryScan . setData ( library , null , 'match' )
this . librariesScanning . push ( libraryScan . getScanEmitData )
this . emitter ( 'scan_start' , libraryScan . getScanEmitData )
Logger . info ( ` [Scanner] matchLibraryItems: Starting library match scan ${ libraryScan . id } for ${ libraryScan . libraryName } ` )
for ( let i = 0 ; i < itemsInLibrary . length ; i ++ ) {
var libraryItem = itemsInLibrary [ i ]
2022-04-27 02:36:29 +02:00
if ( libraryItem . media . metadata . asin && library . settings . skipMatchingMediaWithAsin ) {
2022-05-09 14:23:29 +02:00
Logger . debug ( ` [Scanner] matchLibraryItems: Skipping " ${ libraryItem . media . metadata . title
} " because it already has an ASIN ( $ { i + 1 } of $ { itemsInLibrary . length } ) ` )
2022-04-27 02:36:29 +02:00
continue ;
}
if ( libraryItem . media . metadata . isbn && library . settings . skipMatchingMediaWithIsbn ) {
2022-05-09 14:23:29 +02:00
Logger . debug ( ` [Scanner] matchLibraryItems: Skipping " ${ libraryItem . media . metadata . title
} " because it already has an ISBN ( $ { i + 1 } of $ { itemsInLibrary . length } ) ` )
2022-04-27 02:36:29 +02:00
continue ;
}
2022-04-21 01:05:09 +02:00
Logger . debug ( ` [Scanner] matchLibraryItems: Quick matching " ${ libraryItem . media . metadata . title } " ( ${ i + 1 } of ${ itemsInLibrary . length } ) ` )
var result = await this . quickMatchLibraryItem ( libraryItem , { provider } )
if ( result . warning ) {
Logger . warn ( ` [Scanner] matchLibraryItems: Match warning ${ result . warning } for library item " ${ libraryItem . media . metadata . title } " ` )
} else if ( result . updated ) {
libraryScan . resultsUpdated ++
}
if ( this . cancelLibraryScan [ libraryScan . libraryId ] ) {
Logger . info ( ` [Scanner] matchLibraryItems: Library match scan canceled for " ${ libraryScan . libraryName } " ` )
delete this . cancelLibraryScan [ libraryScan . libraryId ]
var scanData = libraryScan . getScanEmitData
scanData . results = false
this . emitter ( 'scan_complete' , scanData )
this . librariesScanning = this . librariesScanning . filter ( ls => ls . id !== library . id )
return
}
}
this . librariesScanning = this . librariesScanning . filter ( ls => ls . id !== library . id )
this . emitter ( 'scan_complete' , libraryScan . getScanEmitData )
2022-02-16 01:33:33 +01:00
}
2021-11-23 02:58:20 +01:00
}
module . exports = Scanner