mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
172 lines
5.6 KiB
JavaScript
172 lines
5.6 KiB
JavaScript
|
const fs = require('fs-extra')
|
||
|
const Path = require('path')
|
||
|
|
||
|
// Utils
|
||
|
const Logger = require('../Logger')
|
||
|
const { version } = require('../../package.json')
|
||
|
const audioFileScanner = require('../utils/audioFileScanner')
|
||
|
const { groupFilesIntoAudiobookPaths, getAudiobookFileData, scanRootDir } = require('../utils/scandir')
|
||
|
const { comparePaths, getIno, getId } = require('../utils/index')
|
||
|
const { secondsToTimestamp } = require('../utils/fileUtils')
|
||
|
const { ScanResult, CoverDestination } = require('../utils/constants')
|
||
|
|
||
|
const AudioFileScanner = require('./AudioFileScanner')
|
||
|
const BookFinder = require('../BookFinder')
|
||
|
const Audiobook = require('../objects/Audiobook')
|
||
|
const LibraryScan = require('./LibraryScan')
|
||
|
const ScanOptions = require('./ScanOptions')
|
||
|
|
||
|
class Scanner {
|
||
|
constructor(AUDIOBOOK_PATH, METADATA_PATH, db, coverController, emitter) {
|
||
|
this.AudiobookPath = AUDIOBOOK_PATH
|
||
|
this.MetadataPath = METADATA_PATH
|
||
|
this.BookMetadataPath = Path.posix.join(this.MetadataPath.replace(/\\/g, '/'), 'books')
|
||
|
|
||
|
this.db = db
|
||
|
this.coverController = coverController
|
||
|
this.emitter = emitter
|
||
|
|
||
|
this.cancelScan = false
|
||
|
this.cancelLibraryScan = {}
|
||
|
this.librariesScanning = []
|
||
|
|
||
|
this.bookFinder = new BookFinder()
|
||
|
}
|
||
|
|
||
|
async scan(libraryId, options = {}) {
|
||
|
if (this.librariesScanning.includes(libraryId)) {
|
||
|
Logger.error(`[Scanner] Already scanning ${libraryId}`)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var library = this.db.libraries.find(lib => lib.id === libraryId)
|
||
|
if (!library) {
|
||
|
Logger.error(`[Scanner] Library not found for scan ${libraryId}`)
|
||
|
return
|
||
|
} else if (!library.folders.length) {
|
||
|
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)
|
||
|
|
||
|
Logger.info(`[Scanner] Starting library scan ${libraryScan.id} for ${libraryScan.libraryName}`)
|
||
|
|
||
|
var results = await this.scanLibrary(libraryScan)
|
||
|
|
||
|
Logger.info(`[Scanner] Library scan ${libraryScan.id} complete`)
|
||
|
|
||
|
return results
|
||
|
}
|
||
|
|
||
|
async scanLibrary(libraryScan) {
|
||
|
var audiobookDataFound = []
|
||
|
for (let i = 0; i < libraryScan.folders.length; i++) {
|
||
|
var folder = libraryScan.folders[i]
|
||
|
var abDataFoundInFolder = await scanRootDir(folder, this.db.serverSettings)
|
||
|
Logger.debug(`[Scanner] ${abDataFoundInFolder.length} ab data found in folder "${folder.fullPath}"`)
|
||
|
audiobookDataFound = audiobookDataFound.concat(abDataFoundInFolder)
|
||
|
}
|
||
|
|
||
|
// Remove audiobooks with no inode
|
||
|
audiobookDataFound = audiobookDataFound.filter(abd => abd.ino)
|
||
|
|
||
|
var audiobooksInLibrary = this.db.audiobooks.filter(ab => ab.libraryId === libraryScan.libraryId)
|
||
|
|
||
|
const audiobooksToUpdate = []
|
||
|
const audiobooksToRescan = []
|
||
|
const newAudiobookData = []
|
||
|
|
||
|
// Check for existing & removed audiobooks
|
||
|
for (let i = 0; i < audiobooksInLibrary.length; i++) {
|
||
|
var audiobook = audiobooksInLibrary[i]
|
||
|
var dataFound = audiobookDataFound.find(abd => abd.ino === audiobook.ino || comparePaths(abd.path, audiobook.path))
|
||
|
if (!dataFound) {
|
||
|
Logger.info(`[Scanner] Audiobook "${audiobook.title}" is missing`)
|
||
|
audiobook.isMissing = true
|
||
|
audiobook.lastUpdate = Date.now()
|
||
|
scanResults.missing++
|
||
|
audiobooksToUpdate.push(audiobook)
|
||
|
} else {
|
||
|
var checkRes = audiobook.checkShouldRescan(dataFound)
|
||
|
if (checkRes.newAudioFileData.length || checkRes.newOtherFileData.length) {
|
||
|
// existing audiobook has new files
|
||
|
checkRes.audiobook = audiobook
|
||
|
audiobooksToRescan.push(checkRes)
|
||
|
} else if (checkRes.updated) {
|
||
|
audiobooksToUpdate.push(audiobook)
|
||
|
}
|
||
|
|
||
|
// Remove this abf
|
||
|
audiobookDataFound = audiobookDataFound.filter(abf => abf.ino !== dataFound.ino)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Potential NEW Audiobooks
|
||
|
for (let i = 0; i < audiobookDataFound.length; i++) {
|
||
|
var dataFound = audiobookDataFound[i]
|
||
|
var hasEbook = dataFound.otherFiles.find(otherFile => otherFile.filetype === 'ebook')
|
||
|
if (!hasEbook && !dataFound.audioFiles.length) {
|
||
|
Logger.info(`[Scanner] Directory found "${audiobookDataFound.path}" has no ebook or audio files`)
|
||
|
} else {
|
||
|
newAudiobookData.push(dataFound)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var rescans = []
|
||
|
for (let i = 0; i < audiobooksToRescan.length; i++) {
|
||
|
var rescan = this.rescanAudiobook(audiobooksToRescan[i])
|
||
|
rescans.push(rescan)
|
||
|
}
|
||
|
var newscans = []
|
||
|
for (let i = 0; i < newAudiobookData.length; i++) {
|
||
|
var newscan = this.scanNewAudiobook(newAudiobookData[i])
|
||
|
newscans.push(newscan)
|
||
|
}
|
||
|
|
||
|
var rescanResults = await Promise.all(rescans)
|
||
|
|
||
|
var newscanResults = await Promise.all(newscans)
|
||
|
|
||
|
// TODO: Return report
|
||
|
return {
|
||
|
updates: 0,
|
||
|
additions: 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return scan result payload
|
||
|
async rescanAudiobook(audiobookCheckData) {
|
||
|
const { newAudioFileData, newOtherFileData, audiobook } = audiobookCheckData
|
||
|
if (newAudioFileData.length) {
|
||
|
var newAudioFiles = await this.scanAudioFiles(newAudioFileData)
|
||
|
// TODO: Update audiobook tracks
|
||
|
}
|
||
|
if (newOtherFileData.length) {
|
||
|
// TODO: Check other files
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
updated: true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async scanNewAudiobook(audiobookData) {
|
||
|
// TODO: Return new audiobook
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
async scanAudioFiles(audioFileData) {
|
||
|
var proms = []
|
||
|
for (let i = 0; i < audioFileData.length; i++) {
|
||
|
var prom = AudioFileScanner.scan(audioFileData[i])
|
||
|
proms.push(prom)
|
||
|
}
|
||
|
return Promise.all(proms)
|
||
|
}
|
||
|
}
|
||
|
module.exports = Scanner
|