mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 02:17:01 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			226 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const { once } = require('events')
 | |
| const { createInterface } = require('readline')
 | |
| const Path = require('path')
 | |
| const Logger = require('../../Logger')
 | |
| const fs = require('../../libs/fsExtra')
 | |
| const archiver = require('../../libs/archiver')
 | |
| const StreamZip = require('../../libs/nodeStreamZip')
 | |
| 
 | |
| async function processDbFile(filepath) {
 | |
|   if (!fs.pathExistsSync(filepath)) {
 | |
|     Logger.error(`[oldDbFiles] Db file does not exist at "${filepath}"`)
 | |
|     return []
 | |
|   }
 | |
| 
 | |
|   const entities = []
 | |
| 
 | |
|   try {
 | |
|     const fileStream = fs.createReadStream(filepath)
 | |
| 
 | |
|     const rl = createInterface({
 | |
|       input: fileStream,
 | |
|       crlfDelay: Infinity,
 | |
|     })
 | |
| 
 | |
|     rl.on('line', (line) => {
 | |
|       if (line && line.trim()) {
 | |
|         try {
 | |
|           const entity = JSON.parse(line)
 | |
|           if (entity && Object.keys(entity).length) entities.push(entity)
 | |
|         } catch (jsonParseError) {
 | |
|           Logger.error(`[oldDbFiles] Failed to parse line "${line}" in db file "${filepath}"`, jsonParseError)
 | |
|         }
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     await once(rl, 'close')
 | |
| 
 | |
|     console.log(`[oldDbFiles] Db file "${filepath}" processed`)
 | |
| 
 | |
|     return entities
 | |
|   } catch (error) {
 | |
|     Logger.error(`[oldDbFiles] Failed to read db file "${filepath}"`, error)
 | |
|     return []
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function loadDbData(dbpath) {
 | |
|   try {
 | |
|     Logger.info(`[oldDbFiles] Loading db data at "${dbpath}"`)
 | |
|     const files = await fs.readdir(dbpath)
 | |
| 
 | |
|     const entities = []
 | |
|     for (const filename of files) {
 | |
|       if (Path.extname(filename).toLowerCase() !== '.json') {
 | |
|         Logger.warn(`[oldDbFiles] Ignoring filename "${filename}" in db folder "${dbpath}"`)
 | |
|         continue
 | |
|       }
 | |
| 
 | |
|       const filepath = Path.join(dbpath, filename)
 | |
|       Logger.info(`[oldDbFiles] Loading db data file "${filepath}"`)
 | |
|       const someEntities = await processDbFile(filepath)
 | |
|       Logger.info(`[oldDbFiles] Processed db data file with ${someEntities.length} entities`)
 | |
|       entities.push(...someEntities)
 | |
|     }
 | |
| 
 | |
|     Logger.info(`[oldDbFiles] Finished loading db data with ${entities.length} entities`)
 | |
|     return entities
 | |
|   } catch (error) {
 | |
|     Logger.error(`[oldDbFiles] Failed to load db data "${dbpath}"`, error)
 | |
|     return null
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports.loadOldData = async (dbName) => {
 | |
|   const dbPath = Path.join(global.ConfigPath, dbName, 'data')
 | |
|   const dbData = await loadDbData(dbPath) || []
 | |
|   Logger.info(`[oldDbFiles] ${dbData.length} ${dbName} loaded`)
 | |
|   return dbData
 | |
| }
 | |
| 
 | |
| module.exports.zipWrapOldDb = async () => {
 | |
|   const dbs = {
 | |
|     libraryItems: Path.join(global.ConfigPath, 'libraryItems'),
 | |
|     users: Path.join(global.ConfigPath, 'users'),
 | |
|     sessions: Path.join(global.ConfigPath, 'sessions'),
 | |
|     libraries: Path.join(global.ConfigPath, 'libraries'),
 | |
|     settings: Path.join(global.ConfigPath, 'settings'),
 | |
|     collections: Path.join(global.ConfigPath, 'collections'),
 | |
|     playlists: Path.join(global.ConfigPath, 'playlists'),
 | |
|     authors: Path.join(global.ConfigPath, 'authors'),
 | |
|     series: Path.join(global.ConfigPath, 'series'),
 | |
|     feeds: Path.join(global.ConfigPath, 'feeds')
 | |
|   }
 | |
| 
 | |
|   return new Promise((resolve) => {
 | |
|     const oldDbPath = Path.join(global.ConfigPath, 'oldDb.zip')
 | |
|     const output = fs.createWriteStream(oldDbPath)
 | |
|     const archive = archiver('zip', {
 | |
|       zlib: { level: 9 } // Sets the compression level.
 | |
|     })
 | |
| 
 | |
|     // listen for all archive data to be written
 | |
|     // 'close' event is fired only when a file descriptor is involved
 | |
|     output.on('close', async () => {
 | |
|       Logger.info(`[oldDbFiles] Old db files have been zipped in ${oldDbPath}. ${archive.pointer()} total bytes`)
 | |
| 
 | |
|       // Remove old db folders have successful zip
 | |
|       for (const db in dbs) {
 | |
|         await fs.remove(dbs[db])
 | |
|       }
 | |
| 
 | |
|       resolve(true)
 | |
|     })
 | |
| 
 | |
|     // This event is fired when the data source is drained no matter what was the data source.
 | |
|     // It is not part of this library but rather from the NodeJS Stream API.
 | |
|     // @see: https://nodejs.org/api/stream.html#stream_event_end
 | |
|     output.on('end', () => {
 | |
|       Logger.debug('[oldDbFiles] Data has been drained')
 | |
|     })
 | |
| 
 | |
|     // good practice to catch this error explicitly
 | |
|     archive.on('error', (err) => {
 | |
|       Logger.error(`[oldDbFiles] Failed to zip old db folders`, err)
 | |
|       resolve(false)
 | |
|     })
 | |
| 
 | |
|     // pipe archive data to the file
 | |
|     archive.pipe(output)
 | |
| 
 | |
|     for (const db in dbs) {
 | |
|       archive.directory(dbs[db], db)
 | |
|     }
 | |
| 
 | |
|     // finalize the archive (ie we are done appending files but streams have to finish yet)
 | |
|     // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
 | |
|     archive.finalize()
 | |
|   })
 | |
| }
 | |
| 
 | |
| module.exports.checkHasOldDb = async () => {
 | |
|   const dbs = {
 | |
|     libraryItems: Path.join(global.ConfigPath, 'libraryItems'),
 | |
|     users: Path.join(global.ConfigPath, 'users'),
 | |
|     sessions: Path.join(global.ConfigPath, 'sessions'),
 | |
|     libraries: Path.join(global.ConfigPath, 'libraries'),
 | |
|     settings: Path.join(global.ConfigPath, 'settings'),
 | |
|     collections: Path.join(global.ConfigPath, 'collections'),
 | |
|     playlists: Path.join(global.ConfigPath, 'playlists'),
 | |
|     authors: Path.join(global.ConfigPath, 'authors'),
 | |
|     series: Path.join(global.ConfigPath, 'series'),
 | |
|     feeds: Path.join(global.ConfigPath, 'feeds')
 | |
|   }
 | |
|   for (const db in dbs) {
 | |
|     if (await fs.pathExists(dbs[db])) {
 | |
|       return true
 | |
|     }
 | |
|   }
 | |
|   return false
 | |
| }
 | |
| 
 | |
| module.exports.checkHasOldDbZip = async () => {
 | |
|   const oldDbPath = Path.join(global.ConfigPath, 'oldDb.zip')
 | |
|   if (!await fs.pathExists(oldDbPath)) {
 | |
|     return false
 | |
|   }
 | |
| 
 | |
|   // Extract oldDb.zip
 | |
|   const zip = new StreamZip.async({ file: oldDbPath })
 | |
|   await zip.extract(null, global.ConfigPath)
 | |
|   await zip.close()
 | |
| 
 | |
|   return this.checkHasOldDb()
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Used for migration from 2.3.0 -> 2.3.1
 | |
|  * @returns {boolean} true if extracted
 | |
|  */
 | |
| module.exports.checkExtractItemsUsersAndLibraries = async () => {
 | |
|   const oldDbPath = Path.join(global.ConfigPath, 'oldDb.zip')
 | |
| 
 | |
|   const zip = new StreamZip.async({ file: oldDbPath })
 | |
|   const libraryItemsPath = Path.join(global.ConfigPath, 'libraryItems')
 | |
|   await zip.extract('libraryItems/', libraryItemsPath)
 | |
| 
 | |
|   if (!await fs.pathExists(libraryItemsPath)) {
 | |
|     Logger.error(`[oldDbFiles] Failed to extract old libraryItems from oldDb.zip`)
 | |
|     return false
 | |
|   }
 | |
| 
 | |
|   const usersPath = Path.join(global.ConfigPath, 'users')
 | |
|   await zip.extract('users/', usersPath)
 | |
| 
 | |
|   if (!await fs.pathExists(usersPath)) {
 | |
|     Logger.error(`[oldDbFiles] Failed to extract old users from oldDb.zip`)
 | |
|     await fs.remove(libraryItemsPath) // Remove old library items folder
 | |
|     return false
 | |
|   }
 | |
| 
 | |
|   const librariesPath = Path.join(global.ConfigPath, 'libraries')
 | |
|   await zip.extract('libraries/', librariesPath)
 | |
| 
 | |
|   if (!await fs.pathExists(librariesPath)) {
 | |
|     Logger.error(`[oldDbFiles] Failed to extract old libraries from oldDb.zip`)
 | |
|     await fs.remove(usersPath) // Remove old users folder
 | |
|     await fs.remove(libraryItemsPath) // Remove old library items folder
 | |
|     return false
 | |
|   }
 | |
| 
 | |
|   await zip.close()
 | |
| 
 | |
|   return true
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Used for migration from 2.3.0 -> 2.3.1
 | |
|  */
 | |
| module.exports.removeOldItemsUsersAndLibrariesFolders = async () => {
 | |
|   const libraryItemsPath = Path.join(global.ConfigPath, 'libraryItems')
 | |
|   const usersPath = Path.join(global.ConfigPath, 'users')
 | |
|   const librariesPath = Path.join(global.ConfigPath, 'libraries')
 | |
|   await fs.remove(libraryItemsPath)
 | |
|   await fs.remove(usersPath)
 | |
|   await fs.remove(librariesPath)
 | |
| } |