mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 18:37:00 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			272 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const { DataTypes, Model } = require('sequelize')
 | |
| const Logger = require('../Logger')
 | |
| const oldLibrary = require('../objects/Library')
 | |
| 
 | |
| /**
 | |
|  * @typedef LibrarySettingsObject
 | |
|  * @property {number} coverAspectRatio BookCoverAspectRatio
 | |
|  * @property {boolean} disableWatcher
 | |
|  * @property {boolean} skipMatchingMediaWithAsin
 | |
|  * @property {boolean} skipMatchingMediaWithIsbn
 | |
|  * @property {string} autoScanCronExpression
 | |
|  * @property {boolean} audiobooksOnly
 | |
|  * @property {boolean} hideSingleBookSeries Do not show series that only have 1 book 
 | |
|  * @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read
 | |
|  * @property {string[]} metadataPrecedence
 | |
|  */
 | |
| 
 | |
| class Library extends Model {
 | |
|   constructor(values, options) {
 | |
|     super(values, options)
 | |
| 
 | |
|     /** @type {UUIDV4} */
 | |
|     this.id
 | |
|     /** @type {string} */
 | |
|     this.name
 | |
|     /** @type {number} */
 | |
|     this.displayOrder
 | |
|     /** @type {string} */
 | |
|     this.icon
 | |
|     /** @type {string} */
 | |
|     this.mediaType
 | |
|     /** @type {string} */
 | |
|     this.provider
 | |
|     /** @type {Date} */
 | |
|     this.lastScan
 | |
|     /** @type {string} */
 | |
|     this.lastScanVersion
 | |
|     /** @type {LibrarySettingsObject} */
 | |
|     this.settings
 | |
|     /** @type {Object} */
 | |
|     this.extraData
 | |
|     /** @type {Date} */
 | |
|     this.createdAt
 | |
|     /** @type {Date} */
 | |
|     this.updatedAt
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get all old libraries
 | |
|    * @returns {Promise<oldLibrary[]>}
 | |
|    */
 | |
|   static async getAllOldLibraries() {
 | |
|     const libraries = await this.findAll({
 | |
|       include: this.sequelize.models.libraryFolder,
 | |
|       order: [['displayOrder', 'ASC']]
 | |
|     })
 | |
|     return libraries.map(lib => this.getOldLibrary(lib))
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Convert expanded Library to oldLibrary
 | |
|    * @param {Library} libraryExpanded 
 | |
|    * @returns {Promise<oldLibrary>}
 | |
|    */
 | |
|   static getOldLibrary(libraryExpanded) {
 | |
|     const folders = libraryExpanded.libraryFolders.map(folder => {
 | |
|       return {
 | |
|         id: folder.id,
 | |
|         fullPath: folder.path,
 | |
|         libraryId: folder.libraryId,
 | |
|         addedAt: folder.createdAt.valueOf()
 | |
|       }
 | |
|     })
 | |
|     return new oldLibrary({
 | |
|       id: libraryExpanded.id,
 | |
|       oldLibraryId: libraryExpanded.extraData?.oldLibraryId || null,
 | |
|       name: libraryExpanded.name,
 | |
|       folders,
 | |
|       displayOrder: libraryExpanded.displayOrder,
 | |
|       icon: libraryExpanded.icon,
 | |
|       mediaType: libraryExpanded.mediaType,
 | |
|       provider: libraryExpanded.provider,
 | |
|       settings: libraryExpanded.settings,
 | |
|       lastScan: libraryExpanded.lastScan?.valueOf() || null,
 | |
|       lastScanVersion: libraryExpanded.lastScanVersion || null,
 | |
|       lastScanMetadataPrecedence: libraryExpanded.extraData?.lastScanMetadataPrecedence || null,
 | |
|       createdAt: libraryExpanded.createdAt.valueOf(),
 | |
|       lastUpdate: libraryExpanded.updatedAt.valueOf()
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @param {object} oldLibrary 
 | |
|    * @returns {Library|null}
 | |
|    */
 | |
|   static async createFromOld(oldLibrary) {
 | |
|     const library = this.getFromOld(oldLibrary)
 | |
| 
 | |
|     library.libraryFolders = oldLibrary.folders.map(folder => {
 | |
|       return {
 | |
|         id: folder.id,
 | |
|         path: folder.fullPath
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     return this.create(library, {
 | |
|       include: this.sequelize.models.libraryFolder
 | |
|     }).catch((error) => {
 | |
|       Logger.error(`[Library] Failed to create library ${library.id}`, error)
 | |
|       return null
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Update library and library folders
 | |
|    * @param {object} oldLibrary 
 | |
|    * @returns 
 | |
|    */
 | |
|   static async updateFromOld(oldLibrary) {
 | |
|     const existingLibrary = await this.findByPk(oldLibrary.id, {
 | |
|       include: this.sequelize.models.libraryFolder
 | |
|     })
 | |
|     if (!existingLibrary) {
 | |
|       Logger.error(`[Library] Failed to update library ${oldLibrary.id} - not found`)
 | |
|       return null
 | |
|     }
 | |
| 
 | |
|     const library = this.getFromOld(oldLibrary)
 | |
| 
 | |
|     const libraryFolders = oldLibrary.folders.map(folder => {
 | |
|       return {
 | |
|         id: folder.id,
 | |
|         path: folder.fullPath,
 | |
|         libraryId: library.id
 | |
|       }
 | |
|     })
 | |
|     for (const libraryFolder of libraryFolders) {
 | |
|       const existingLibraryFolder = existingLibrary.libraryFolders.find(lf => lf.id === libraryFolder.id)
 | |
|       if (!existingLibraryFolder) {
 | |
|         await this.sequelize.models.libraryFolder.create(libraryFolder)
 | |
|       } else if (existingLibraryFolder.path !== libraryFolder.path) {
 | |
|         await existingLibraryFolder.update({ path: libraryFolder.path })
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const libraryFoldersRemoved = existingLibrary.libraryFolders.filter(lf => !libraryFolders.some(_lf => _lf.id === lf.id))
 | |
|     for (const existingLibraryFolder of libraryFoldersRemoved) {
 | |
|       await existingLibraryFolder.destroy()
 | |
|     }
 | |
| 
 | |
|     return existingLibrary.update(library)
 | |
|   }
 | |
| 
 | |
|   static getFromOld(oldLibrary) {
 | |
|     const extraData = {}
 | |
|     if (oldLibrary.oldLibraryId) {
 | |
|       extraData.oldLibraryId = oldLibrary.oldLibraryId
 | |
|     }
 | |
|     if (oldLibrary.lastScanMetadataPrecedence) {
 | |
|       extraData.lastScanMetadataPrecedence = oldLibrary.lastScanMetadataPrecedence
 | |
|     }
 | |
|     return {
 | |
|       id: oldLibrary.id,
 | |
|       name: oldLibrary.name,
 | |
|       displayOrder: oldLibrary.displayOrder,
 | |
|       icon: oldLibrary.icon || null,
 | |
|       mediaType: oldLibrary.mediaType || null,
 | |
|       provider: oldLibrary.provider,
 | |
|       settings: oldLibrary.settings?.toJSON() || {},
 | |
|       lastScan: oldLibrary.lastScan || null,
 | |
|       lastScanVersion: oldLibrary.lastScanVersion || null,
 | |
|       createdAt: oldLibrary.createdAt,
 | |
|       updatedAt: oldLibrary.lastUpdate,
 | |
|       extraData
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Destroy library by id
 | |
|    * @param {string} libraryId 
 | |
|    * @returns 
 | |
|    */
 | |
|   static removeById(libraryId) {
 | |
|     return this.destroy({
 | |
|       where: {
 | |
|         id: libraryId
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get all library ids
 | |
|    * @returns {Promise<string[]>} array of library ids
 | |
|    */
 | |
|   static async getAllLibraryIds() {
 | |
|     const libraries = await this.findAll({
 | |
|       attributes: ['id', 'displayOrder'],
 | |
|       order: [['displayOrder', 'ASC']]
 | |
|     })
 | |
|     return libraries.map(l => l.id)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Find Library by primary key & return oldLibrary
 | |
|    * @param {string} libraryId 
 | |
|    * @returns {Promise<oldLibrary|null>} Returns null if not found
 | |
|    */
 | |
|   static async getOldById(libraryId) {
 | |
|     if (!libraryId) return null
 | |
|     const library = await this.findByPk(libraryId, {
 | |
|       include: this.sequelize.models.libraryFolder
 | |
|     })
 | |
|     if (!library) return null
 | |
|     return this.getOldLibrary(library)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get the largest value in the displayOrder column
 | |
|    * Used for setting a new libraries display order
 | |
|    * @returns {Promise<number>}
 | |
|    */
 | |
|   static getMaxDisplayOrder() {
 | |
|     return this.max('displayOrder') || 0
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Updates displayOrder to be sequential
 | |
|    * Used after removing a library
 | |
|    */
 | |
|   static async resetDisplayOrder() {
 | |
|     const libraries = await this.findAll({
 | |
|       order: [['displayOrder', 'ASC']]
 | |
|     })
 | |
|     for (let i = 0; i < libraries.length; i++) {
 | |
|       const library = libraries[i]
 | |
|       if (library.displayOrder !== i + 1) {
 | |
|         Logger.debug(`[Library] Updating display order of library from ${library.displayOrder} to ${i + 1}`)
 | |
|         await library.update({ displayOrder: i + 1 }).catch((error) => {
 | |
|           Logger.error(`[Library] Failed to update library display order to ${i + 1}`, error)
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Initialize model
 | |
|    * @param {import('../Database').sequelize} sequelize 
 | |
|    */
 | |
|   static init(sequelize) {
 | |
|     super.init({
 | |
|       id: {
 | |
|         type: DataTypes.UUID,
 | |
|         defaultValue: DataTypes.UUIDV4,
 | |
|         primaryKey: true
 | |
|       },
 | |
|       name: DataTypes.STRING,
 | |
|       displayOrder: DataTypes.INTEGER,
 | |
|       icon: DataTypes.STRING,
 | |
|       mediaType: DataTypes.STRING,
 | |
|       provider: DataTypes.STRING,
 | |
|       lastScan: DataTypes.DATE,
 | |
|       lastScanVersion: DataTypes.STRING,
 | |
|       settings: DataTypes.JSON,
 | |
|       extraData: DataTypes.JSON
 | |
|     }, {
 | |
|       sequelize,
 | |
|       modelName: 'library'
 | |
|     })
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = Library |