mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Migration change metadata folder from /books to /items, podcast data model updates, add podcast routes
This commit is contained in:
		
							parent
							
								
									43bbfbfee3
								
							
						
					
					
						commit
						f8d0384155
					
				| @ -82,7 +82,7 @@ export default { | |||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       this.processing = true |       this.processing = true | ||||||
|       var podcastfeed = await this.$axios.$post(`/api/getPodcastFeed`, { rssFeed: podcast.feedUrl }).catch((error) => { |       var podcastfeed = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: podcast.feedUrl }).catch((error) => { | ||||||
|         console.error('Failed to get feed', error) |         console.error('Failed to get feed', error) | ||||||
|         this.$toast.error('Failed to get podcast feed') |         this.$toast.error('Failed to get podcast feed') | ||||||
|         return null |         return null | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ const Backup = require('./objects/Backup') | |||||||
| class BackupManager { | class BackupManager { | ||||||
|   constructor(db, emitter) { |   constructor(db, emitter) { | ||||||
|     this.BackupPath = Path.join(global.MetadataPath, 'backups') |     this.BackupPath = Path.join(global.MetadataPath, 'backups') | ||||||
|     this.MetadataBooksPath = Path.join(global.MetadataPath, 'books') |     this.ItemsMetadataPath = Path.join(global.MetadataPath, 'items') | ||||||
| 
 | 
 | ||||||
|     this.db = db |     this.db = db | ||||||
|     this.emitter = emitter |     this.emitter = emitter | ||||||
| @ -115,7 +115,7 @@ class BackupManager { | |||||||
|     const zip = new StreamZip.async({ file: backup.fullPath }) |     const zip = new StreamZip.async({ file: backup.fullPath }) | ||||||
|     await zip.extract('config/', global.ConfigPath) |     await zip.extract('config/', global.ConfigPath) | ||||||
|     if (backup.backupMetadataCovers) { |     if (backup.backupMetadataCovers) { | ||||||
|       await zip.extract('metadata-books/', this.MetadataBooksPath) |       await zip.extract('metadata-items/', this.ItemsMetadataPath) | ||||||
|     } |     } | ||||||
|     await this.db.reinit() |     await this.db.reinit() | ||||||
|     this.emitter('backup_applied') |     this.emitter('backup_applied') | ||||||
| @ -154,7 +154,7 @@ class BackupManager { | |||||||
|   async runBackup() { |   async runBackup() { | ||||||
|     // Check if Metadata Path is inside Config Path (otherwise there will be an infinite loop as the archiver tries to zip itself)
 |     // Check if Metadata Path is inside Config Path (otherwise there will be an infinite loop as the archiver tries to zip itself)
 | ||||||
|     Logger.info(`[BackupManager] Running Backup`) |     Logger.info(`[BackupManager] Running Backup`) | ||||||
|     var metadataBooksPath = this.serverSettings.backupMetadataCovers ? this.MetadataBooksPath : null |     var metadataItemsPath = this.serverSettings.backupMetadataCovers ? this.ItemsMetadataPath : null | ||||||
| 
 | 
 | ||||||
|     var newBackup = new Backup() |     var newBackup = new Backup() | ||||||
| 
 | 
 | ||||||
| @ -164,7 +164,7 @@ class BackupManager { | |||||||
|     } |     } | ||||||
|     newBackup.setData(newBackData) |     newBackup.setData(newBackData) | ||||||
| 
 | 
 | ||||||
|     var zipResult = await this.zipBackup(metadataBooksPath, newBackup).then(() => true).catch((error) => { |     var zipResult = await this.zipBackup(metadataItemsPath, newBackup).then(() => true).catch((error) => { | ||||||
|       Logger.error(`[BackupManager] Backup Failed ${error}`) |       Logger.error(`[BackupManager] Backup Failed ${error}`) | ||||||
|       return false |       return false | ||||||
|     }) |     }) | ||||||
| @ -204,7 +204,7 @@ class BackupManager { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   zipBackup(metadataBooksPath, backup) { |   zipBackup(metadataItemsPath, backup) { | ||||||
|     return new Promise((resolve, reject) => { |     return new Promise((resolve, reject) => { | ||||||
|       // create a file to stream archive data to
 |       // create a file to stream archive data to
 | ||||||
|       const output = fs.createWriteStream(backup.fullPath) |       const output = fs.createWriteStream(backup.fullPath) | ||||||
| @ -274,9 +274,9 @@ class BackupManager { | |||||||
|       archive.directory(this.db.AuthorsPath, 'config/authors') |       archive.directory(this.db.AuthorsPath, 'config/authors') | ||||||
|       archive.directory(this.db.SeriesPath, 'config/series') |       archive.directory(this.db.SeriesPath, 'config/series') | ||||||
| 
 | 
 | ||||||
|       if (metadataBooksPath) { |       if (metadataItemsPath) { | ||||||
|         Logger.debug(`[BackupManager] Backing up Metadata Books "${metadataBooksPath}"`) |         Logger.debug(`[BackupManager] Backing up Metadata Items "${metadataItemsPath}"`) | ||||||
|         archive.directory(metadataBooksPath, 'metadata-books') |         archive.directory(metadataItemsPath, 'metadata-items') | ||||||
|       } |       } | ||||||
|       archive.append(backup.detailsString, { name: 'details' }) |       archive.append(backup.detailsString, { name: 'details' }) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,14 +15,14 @@ class CoverController { | |||||||
|     this.db = db |     this.db = db | ||||||
|     this.cacheManager = cacheManager |     this.cacheManager = cacheManager | ||||||
| 
 | 
 | ||||||
|     this.BookMetadataPath = Path.posix.join(global.MetadataPath, 'books') |     this.ItemMetadataPath = Path.posix.join(global.MetadataPath, 'items') | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getCoverDirectory(libraryItem) { |   getCoverDirectory(libraryItem) { | ||||||
|     if (this.db.serverSettings.storeCoverWithBook) { |     if (this.db.serverSettings.storeCoverWithBook) { | ||||||
|       return libraryItem.path |       return libraryItem.path | ||||||
|     } else { |     } else { | ||||||
|       return Path.posix.join(this.BookMetadataPath, libraryItem.id) |       return Path.posix.join(this.ItemMetadataPath, libraryItem.id) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -237,7 +237,7 @@ class CoverController { | |||||||
| 
 | 
 | ||||||
|     var coverAlreadyExists = await fs.pathExists(coverFilePath) |     var coverAlreadyExists = await fs.pathExists(coverFilePath) | ||||||
|     if (coverAlreadyExists) { |     if (coverAlreadyExists) { | ||||||
|       Logger.warn(`[Audiobook] Extract embedded cover art but cover already exists for "${libraryItem.media.metadata.title}" - bail`) |       Logger.warn(`[CoverController] Extract embedded cover art but cover already exists for "${libraryItem.media.metadata.title}" - bail`) | ||||||
|       return false |       return false | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -111,28 +111,19 @@ class Server { | |||||||
|     await this.downloadManager.removeOrphanDownloads() |     await this.downloadManager.removeOrphanDownloads() | ||||||
| 
 | 
 | ||||||
|     if (version.localeCompare('1.7.3') < 0) { // Old version data model migration
 |     if (version.localeCompare('1.7.3') < 0) { // Old version data model migration
 | ||||||
|       await dbMigration.migrateUserData(this.db) // Db not yet loaded
 |       await dbMigration.migrate(this.db) | ||||||
|       await this.db.init() |  | ||||||
|       await dbMigration.migrateLibraryItems(this.db) |  | ||||||
|       // TODO: Eventually remove audiobooks db when stable
 |  | ||||||
|     } else { |     } else { | ||||||
|       await this.db.init() |       await this.db.init() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.auth.init() |     this.auth.init() | ||||||
| 
 | 
 | ||||||
|     // TODO: Implement method to remove old user auidobook data and book metadata folders
 |     await this.checkUserLibraryItemProgress() // Remove invalid user item progress
 | ||||||
|     // await this.checkUserAudiobookData()
 |     await this.purgeMetadata() // Remove metadata folders without library item
 | ||||||
|     // await this.purgeMetadata()
 |  | ||||||
| 
 | 
 | ||||||
|     await this.backupManager.init() |     await this.backupManager.init() | ||||||
|     await this.logManager.init() |     await this.logManager.init() | ||||||
| 
 | 
 | ||||||
|     // If server upgrade and last version was 1.7.0 or earlier - add abmetadata files
 |  | ||||||
|     // if (this.db.checkPreviousVersionIsBefore('1.7.1')) {
 |  | ||||||
|     // TODO: wait until stable
 |  | ||||||
|     // }
 |  | ||||||
| 
 |  | ||||||
|     if (this.db.serverSettings.scannerDisableWatcher) { |     if (this.db.serverSettings.scannerDisableWatcher) { | ||||||
|       Logger.info(`[Server] Watcher is disabled`) |       Logger.info(`[Server] Watcher is disabled`) | ||||||
|       this.watcher.disabled = true |       this.watcher.disabled = true | ||||||
| @ -275,18 +266,17 @@ class Server { | |||||||
|     socket.emit('save_metadata_complete', response) |     socket.emit('save_metadata_complete', response) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Remove unused /metadata/books/{id} folders
 |   // Remove unused /metadata/items/{id} folders
 | ||||||
|   async purgeMetadata() { |   async purgeMetadata() { | ||||||
|     var booksMetadata = Path.join(global.MetadataPath, 'books') |     var itemsMetadata = Path.join(global.MetadataPath, 'items') | ||||||
|     var booksMetadataExists = await fs.pathExists(booksMetadata) |     if (!(await fs.pathExists(itemsMetadata))) return | ||||||
|     if (!booksMetadataExists) return |     var foldersInItemsMetadata = await fs.readdir(itemsMetadata) | ||||||
|     var foldersInBooksMetadata = await fs.readdir(booksMetadata) |  | ||||||
| 
 | 
 | ||||||
|     var purged = 0 |     var purged = 0 | ||||||
|     await Promise.all(foldersInBooksMetadata.map(async foldername => { |     await Promise.all(foldersInItemsMetadata.map(async foldername => { | ||||||
|       var hasMatchingAudiobook = this.db.audiobooks.find(ab => ab.id === foldername) |       var hasMatchingItem = this.db.libraryItems.find(ab => ab.id === foldername) | ||||||
|       if (!hasMatchingAudiobook) { |       if (!hasMatchingItem) { | ||||||
|         var folderPath = Path.join(booksMetadata, foldername) |         var folderPath = Path.join(itemsMetadata, foldername) | ||||||
|         Logger.debug(`[Server] Purging unused metadata ${folderPath}`) |         Logger.debug(`[Server] Purging unused metadata ${folderPath}`) | ||||||
| 
 | 
 | ||||||
|         await fs.remove(folderPath).then(() => { |         await fs.remove(folderPath).then(() => { | ||||||
| @ -297,24 +287,21 @@ class Server { | |||||||
|       } |       } | ||||||
|     })) |     })) | ||||||
|     if (purged > 0) { |     if (purged > 0) { | ||||||
|       Logger.info(`[Server] Purged ${purged} unused audiobook metadata`) |       Logger.info(`[Server] Purged ${purged} unused library item metadata`) | ||||||
|     } |     } | ||||||
|     return purged |     return purged | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Check user audiobook data has matching audiobook
 |   // Remove user library item progress entries that dont have a library item
 | ||||||
|   async checkUserAudiobookData() { |   async checkUserLibraryItemProgress() { | ||||||
|     for (let i = 0; i < this.db.users.length; i++) { |     for (let i = 0; i < this.db.users.length; i++) { | ||||||
|       var _user = this.db.users[i] |       var _user = this.db.users[i] | ||||||
|       if (_user.audiobooks) { |       if (_user.libraryItemProgress) { | ||||||
|         // Find user audiobook data that has no matching audiobook
 |         var itemProgressIdsToRemove = _user.libraryItemProgress.map(lip => lip.id).filter(lipId => !this.db.libraryItems.find(_li => _li.id == lipId)) | ||||||
|         var audiobookIdsToRemove = Object.keys(_user.audiobooks).filter(aid => { |         if (itemProgressIdsToRemove.length) { | ||||||
|           return !this.db.audiobooks.find(ab => ab.id === aid) |           Logger.debug(`[Server] Found ${itemProgressIdsToRemove.length} library item progress data to remove from user ${_user.username}`) | ||||||
|         }) |           for (const lipId of itemProgressIdsToRemove) { | ||||||
|         if (audiobookIdsToRemove.length) { |             _user.removeLibraryItemProgress(lipId) | ||||||
|           Logger.debug(`[Server] Found ${audiobookIdsToRemove.length} audiobook data to remove from user ${_user.username}`) |  | ||||||
|           for (let y = 0; y < audiobookIdsToRemove.length; y++) { |  | ||||||
|             _user.removeLibraryItemProgress(audiobookIdsToRemove[y]) |  | ||||||
|           } |           } | ||||||
|           await this.db.updateEntity('user', _user) |           await this.db.updateEntity('user', _user) | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,9 +1,7 @@ | |||||||
| const Path = require('path') | const Path = require('path') | ||||||
| const fs = require('fs-extra') | const fs = require('fs-extra') | ||||||
| const axios = require('axios') |  | ||||||
| 
 |  | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const { parsePodcastRssFeedXml } = require('../utils/podcastUtils') | 
 | ||||||
| const { isObject } = require('../utils/index') | const { isObject } = require('../utils/index') | ||||||
| 
 | 
 | ||||||
| //
 | //
 | ||||||
| @ -139,28 +137,6 @@ class MiscController { | |||||||
|     res.sendStatus(200) |     res.sendStatus(200) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getPodcastFeed(req, res) { |  | ||||||
|     var url = req.body.rssFeed |  | ||||||
|     if (!url) { |  | ||||||
|       return res.status(400).send('Bad request') |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     axios.get(url).then(async (data) => { |  | ||||||
|       if (!data || !data.data) { |  | ||||||
|         Logger.error('Invalid podcast feed request response') |  | ||||||
|         return res.status(500).send('Bad response from feed request') |  | ||||||
|       } |  | ||||||
|       var podcast = await parsePodcastRssFeedXml(data.data) |  | ||||||
|       if (!podcast) { |  | ||||||
|         return res.status(500).send('Invalid podcast RSS feed') |  | ||||||
|       } |  | ||||||
|       res.json(podcast) |  | ||||||
|     }).catch((error) => { |  | ||||||
|       console.error('Failed', error) |  | ||||||
|       res.status(500).send(error) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async findBooks(req, res) { |   async findBooks(req, res) { | ||||||
|     var provider = req.query.provider || 'google' |     var provider = req.query.provider || 'google' | ||||||
|     var title = req.query.title || '' |     var title = req.query.title || '' | ||||||
|  | |||||||
							
								
								
									
										62
									
								
								server/controllers/PodcastController.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								server/controllers/PodcastController.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | const axios = require('axios') | ||||||
|  | const fs = require('fs-extra') | ||||||
|  | const Logger = require('../Logger') | ||||||
|  | const { parsePodcastRssFeedXml } = require('../utils/podcastUtils') | ||||||
|  | const LibraryItem = require('../objects/LibraryItem') | ||||||
|  | 
 | ||||||
|  | class PodcastController { | ||||||
|  | 
 | ||||||
|  |   async create(req, res) { | ||||||
|  |     if (!req.user.isRoot) { | ||||||
|  |       Logger.error(`[PodcastController] Non-root user attempted to create podcast`, req.user) | ||||||
|  |       return res.sendStatus(500) | ||||||
|  |     } | ||||||
|  |     const payload = req.body | ||||||
|  | 
 | ||||||
|  |     if (await fs.pathExists(payload.path)) { | ||||||
|  |       Logger.error(`[PodcastController] Attempt to create podcast when folder path already exists "${payload.path}"`) | ||||||
|  |       return res.status(400).send('Path already exists') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var success = await fs.ensureDir(payload.path).then(() => true).catch((error) => { | ||||||
|  |       Logger.error(`[PodcastController] Failed to ensure podcast dir "${payload.path}"`, error) | ||||||
|  |       return false | ||||||
|  |     }) | ||||||
|  |     if (!success) return res.status(400).send('Invalid podcast path') | ||||||
|  | 
 | ||||||
|  |     if (payload.mediaMetadata.imageUrl) { | ||||||
|  |       // TODO: Download image
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var libraryItem = new LibraryItem() | ||||||
|  |     libraryItem.setData('podcast', payload) | ||||||
|  | 
 | ||||||
|  |     await this.db.insertLibraryItem(libraryItem) | ||||||
|  |     this.emitter('item_added', libraryItem.toJSONExpanded()) | ||||||
|  | 
 | ||||||
|  |     res.json(libraryItem.toJSONExpanded()) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   getPodcastFeed(req, res) { | ||||||
|  |     var url = req.body.rssFeed | ||||||
|  |     if (!url) { | ||||||
|  |       return res.status(400).send('Bad request') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     axios.get(url).then(async (data) => { | ||||||
|  |       if (!data || !data.data) { | ||||||
|  |         Logger.error('Invalid podcast feed request response') | ||||||
|  |         return res.status(500).send('Bad response from feed request') | ||||||
|  |       } | ||||||
|  |       var podcast = await parsePodcastRssFeedXml(data.data) | ||||||
|  |       if (!podcast) { | ||||||
|  |         return res.status(500).send('Invalid podcast RSS feed') | ||||||
|  |       } | ||||||
|  |       res.json(podcast) | ||||||
|  |     }).catch((error) => { | ||||||
|  |       console.error('Failed', error) | ||||||
|  |       res.status(500).send(error) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | module.exports = new PodcastController() | ||||||
| @ -1,3 +1,4 @@ | |||||||
|  | const { getId } = require('../../utils/index') | ||||||
| const AudioFile = require('../files/AudioFile') | const AudioFile = require('../files/AudioFile') | ||||||
| const AudioTrack = require('../files/AudioTrack') | const AudioTrack = require('../files/AudioTrack') | ||||||
| 
 | 
 | ||||||
| @ -5,9 +6,8 @@ class PodcastEpisode { | |||||||
|   constructor(episode) { |   constructor(episode) { | ||||||
|     this.id = null |     this.id = null | ||||||
|     this.index = null |     this.index = null | ||||||
|     this.podcastId = null |  | ||||||
|     this.episodeNumber = null |  | ||||||
| 
 | 
 | ||||||
|  |     this.episodeNumber = null | ||||||
|     this.title = null |     this.title = null | ||||||
|     this.description = null |     this.description = null | ||||||
|     this.enclosure = null |     this.enclosure = null | ||||||
| @ -25,7 +25,6 @@ class PodcastEpisode { | |||||||
|   construct(episode) { |   construct(episode) { | ||||||
|     this.id = episode.id |     this.id = episode.id | ||||||
|     this.index = episode.index |     this.index = episode.index | ||||||
|     this.podcastId = episode.podcastId |  | ||||||
|     this.episodeNumber = episode.episodeNumber |     this.episodeNumber = episode.episodeNumber | ||||||
|     this.title = episode.title |     this.title = episode.title | ||||||
|     this.description = episode.description |     this.description = episode.description | ||||||
| @ -40,7 +39,6 @@ class PodcastEpisode { | |||||||
|     return { |     return { | ||||||
|       id: this.id, |       id: this.id, | ||||||
|       index: this.index, |       index: this.index, | ||||||
|       podcastId: this.podcastId, |  | ||||||
|       episodeNumber: this.episodeNumber, |       episodeNumber: this.episodeNumber, | ||||||
|       title: this.title, |       title: this.title, | ||||||
|       description: this.description, |       description: this.description, | ||||||
| @ -61,6 +59,18 @@ class PodcastEpisode { | |||||||
|   } |   } | ||||||
|   get size() { return this.audioFile.metadata.size } |   get size() { return this.audioFile.metadata.size } | ||||||
| 
 | 
 | ||||||
|  |   setData(data, index = 1) { | ||||||
|  |     this.id = getId('ep') | ||||||
|  |     this.index = index | ||||||
|  |     this.title = data.title | ||||||
|  |     this.pubDate = data.pubDate || '' | ||||||
|  |     this.description = data.description || '' | ||||||
|  |     this.enclosure = data.enclosure ? { ...data.enclosure } : null | ||||||
|  |     this.episodeNumber = data.episodeNumber || '' | ||||||
|  |     this.addedAt = Date.now() | ||||||
|  |     this.updatedAt = Date.now() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   // Only checks container format
 |   // Only checks container format
 | ||||||
|   checkCanDirectPlay(payload) { |   checkCanDirectPlay(payload) { | ||||||
|     var supportedMimeTypes = payload.supportedMimeTypes || [] |     var supportedMimeTypes = payload.supportedMimeTypes || [] | ||||||
|  | |||||||
| @ -4,8 +4,6 @@ const { areEquivalent, copyValue } = require('../../utils/index') | |||||||
| 
 | 
 | ||||||
| class Podcast { | class Podcast { | ||||||
|   constructor(podcast) { |   constructor(podcast) { | ||||||
|     this.id = null |  | ||||||
| 
 |  | ||||||
|     this.metadata = null |     this.metadata = null | ||||||
|     this.coverPath = null |     this.coverPath = null | ||||||
|     this.tags = [] |     this.tags = [] | ||||||
| @ -22,7 +20,6 @@ class Podcast { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   construct(podcast) { |   construct(podcast) { | ||||||
|     this.id = podcast.id |  | ||||||
|     this.metadata = new PodcastMetadata(podcast.metadata) |     this.metadata = new PodcastMetadata(podcast.metadata) | ||||||
|     this.coverPath = podcast.coverPath |     this.coverPath = podcast.coverPath | ||||||
|     this.tags = [...podcast.tags] |     this.tags = [...podcast.tags] | ||||||
| @ -32,7 +29,6 @@ class Podcast { | |||||||
| 
 | 
 | ||||||
|   toJSON() { |   toJSON() { | ||||||
|     return { |     return { | ||||||
|       id: this.id, |  | ||||||
|       metadata: this.metadata.toJSON(), |       metadata: this.metadata.toJSON(), | ||||||
|       coverPath: this.coverPath, |       coverPath: this.coverPath, | ||||||
|       tags: [...this.tags], |       tags: [...this.tags], | ||||||
| @ -43,7 +39,6 @@ class Podcast { | |||||||
| 
 | 
 | ||||||
|   toJSONMinified() { |   toJSONMinified() { | ||||||
|     return { |     return { | ||||||
|       id: this.id, |  | ||||||
|       metadata: this.metadata.toJSON(), |       metadata: this.metadata.toJSON(), | ||||||
|       coverPath: this.coverPath, |       coverPath: this.coverPath, | ||||||
|       tags: [...this.tags], |       tags: [...this.tags], | ||||||
| @ -54,7 +49,6 @@ class Podcast { | |||||||
| 
 | 
 | ||||||
|   toJSONExpanded() { |   toJSONExpanded() { | ||||||
|     return { |     return { | ||||||
|       id: this.id, |  | ||||||
|       metadata: this.metadata.toJSONExpanded(), |       metadata: this.metadata.toJSONExpanded(), | ||||||
|       coverPath: this.coverPath, |       coverPath: this.coverPath, | ||||||
|       tags: [...this.tags], |       tags: [...this.tags], | ||||||
| @ -124,9 +118,10 @@ class Podcast { | |||||||
|     return this.episodes[0] |     return this.episodes[0] | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setData(scanMediaMetadata) { |   setData(metadata, coverPath = null, autoDownload = false) { | ||||||
|     this.metadata = new PodcastMetadata() |     this.metadata = new PodcastMetadata(metadata) | ||||||
|     this.metadata.setData(scanMediaMetadata) |     this.coverPath = coverPath | ||||||
|  |     this.autoDownloadEpisodes = autoDownload | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) { |   async syncMetadataFiles(textMetadataFiles, opfMetadataOverrideDetails) { | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ const SeriesController = require('../controllers/SeriesController') | |||||||
| const AuthorController = require('../controllers/AuthorController') | const AuthorController = require('../controllers/AuthorController') | ||||||
| const MediaEntityController = require('../controllers/MediaEntityController') | const MediaEntityController = require('../controllers/MediaEntityController') | ||||||
| const SessionController = require('../controllers/SessionController') | const SessionController = require('../controllers/SessionController') | ||||||
|  | const PodcastController = require('../controllers/PodcastController') | ||||||
| const MiscController = require('../controllers/MiscController') | const MiscController = require('../controllers/MiscController') | ||||||
| 
 | 
 | ||||||
| const BookFinder = require('../finders/BookFinder') | const BookFinder = require('../finders/BookFinder') | ||||||
| @ -173,6 +174,12 @@ class ApiRouter { | |||||||
|     this.router.post('/session/:id/sync', SessionController.middleware.bind(this), SessionController.sync.bind(this)) |     this.router.post('/session/:id/sync', SessionController.middleware.bind(this), SessionController.sync.bind(this)) | ||||||
|     this.router.post('/session/:id/close', SessionController.middleware.bind(this), SessionController.close.bind(this)) |     this.router.post('/session/:id/close', SessionController.middleware.bind(this), SessionController.close.bind(this)) | ||||||
| 
 | 
 | ||||||
|  |     //
 | ||||||
|  |     // Podcast Routes
 | ||||||
|  |     //
 | ||||||
|  |     this.router.post('/podcasts', PodcastController.create.bind(this)) | ||||||
|  |     this.router.post('/podcasts/feed', PodcastController.getPodcastFeed.bind(this)) | ||||||
|  | 
 | ||||||
|     //
 |     //
 | ||||||
|     // Misc Routes
 |     // Misc Routes
 | ||||||
|     //
 |     //
 | ||||||
| @ -180,7 +187,6 @@ class ApiRouter { | |||||||
|     this.router.get('/download/:id', MiscController.download.bind(this)) |     this.router.get('/download/:id', MiscController.download.bind(this)) | ||||||
|     this.router.patch('/settings', MiscController.updateServerSettings.bind(this)) // Root only
 |     this.router.patch('/settings', MiscController.updateServerSettings.bind(this)) // Root only
 | ||||||
|     this.router.post('/purgecache', MiscController.purgeCache.bind(this)) // Root only
 |     this.router.post('/purgecache', MiscController.purgeCache.bind(this)) // Root only
 | ||||||
|     this.router.post('/getPodcastFeed', MiscController.getPodcastFeed.bind(this)) |  | ||||||
|     this.router.post('/authorize', MiscController.authorize.bind(this)) |     this.router.post('/authorize', MiscController.authorize.bind(this)) | ||||||
|     this.router.get('/search/covers', MiscController.findCovers.bind(this)) |     this.router.get('/search/covers', MiscController.findCovers.bind(this)) | ||||||
|     this.router.get('/search/books', MiscController.findBooks.bind(this)) |     this.router.get('/search/books', MiscController.findBooks.bind(this)) | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ const Series = require('../objects/entities/Series') | |||||||
| 
 | 
 | ||||||
| class Scanner { | class Scanner { | ||||||
|   constructor(db, coverController, emitter) { |   constructor(db, coverController, emitter) { | ||||||
|     this.BookMetadataPath = Path.posix.join(global.MetadataPath, 'books') |  | ||||||
|     this.ScanLogPath = Path.posix.join(global.MetadataPath, 'logs', 'scans') |     this.ScanLogPath = Path.posix.join(global.MetadataPath, 'logs', 'scans') | ||||||
| 
 | 
 | ||||||
|     this.db = db |     this.db = db | ||||||
|  | |||||||
| @ -151,6 +151,17 @@ function makeFilesFromOldAb(audiobook) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Metadata path was changed to /metadata/items make sure cover is using new path
 | ||||||
|  | function cleanOldCoverPath(coverPath) { | ||||||
|  |   if (!coverPath) return null | ||||||
|  |   var oldMetadataPath = Path.posix.join(global.MetadataPath, 'books') | ||||||
|  |   if (coverPath.startsWith(oldMetadataPath)) { | ||||||
|  |     const newMetadataPath = Path.posix.join(global.MetadataPath, 'items') | ||||||
|  |     return coverPath.replace(oldMetadataPath, newMetadataPath) | ||||||
|  |   } | ||||||
|  |   return coverPath | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function makeLibraryItemFromOldAb(audiobook) { | function makeLibraryItemFromOldAb(audiobook) { | ||||||
|   var libraryItem = new LibraryItem() |   var libraryItem = new LibraryItem() | ||||||
|   libraryItem.id = getId('li') |   libraryItem.id = getId('li') | ||||||
| @ -184,7 +195,7 @@ function makeLibraryItemFromOldAb(audiobook) { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   bookEntity.metadata = bookMetadata |   bookEntity.metadata = bookMetadata | ||||||
|   bookEntity.coverPath = audiobook.book.coverFullPath |   bookEntity.coverPath = cleanOldCoverPath(audiobook.book.coverFullPath) | ||||||
|   bookEntity.tags = [...audiobook.tags] |   bookEntity.tags = [...audiobook.tags] | ||||||
| 
 | 
 | ||||||
|   var payload = makeFilesFromOldAb(audiobook) |   var payload = makeFilesFromOldAb(audiobook) | ||||||
| @ -312,8 +323,6 @@ async function migrateLibraryItems(db) { | |||||||
|   seriesToAdd = [] |   seriesToAdd = [] | ||||||
|   Logger.info(`==== Library Item migration complete ====`) |   Logger.info(`==== Library Item migration complete ====`) | ||||||
| } | } | ||||||
| module.exports.migrateLibraryItems = migrateLibraryItems |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| function cleanUserObject(db, userObj) { | function cleanUserObject(db, userObj) { | ||||||
|   var cleanedUserPayload = { |   var cleanedUserPayload = { | ||||||
| @ -445,4 +454,24 @@ async function migrateUserData(db) { | |||||||
| 
 | 
 | ||||||
|   Logger.info(`==== User migration complete (${userCount} Users, ${sessionCount} Sessions) ====`) |   Logger.info(`==== User migration complete (${userCount} Users, ${sessionCount} Sessions) ====`) | ||||||
| } | } | ||||||
| module.exports.migrateUserData = migrateUserData | 
 | ||||||
|  | async function checkUpdateMetadataPath() { | ||||||
|  |   var bookMetadataPath = Path.posix.join(global.MetadataPath, 'books') // OLD
 | ||||||
|  |   if (!(await fs.pathExists(bookMetadataPath))) { | ||||||
|  |     Logger.debug(`[dbMigration] No need to update books metadata path`) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |   var itemsMetadataPath = Path.posix.join(global.MetadataPath, 'items') | ||||||
|  |   await fs.rename(bookMetadataPath, itemsMetadataPath) | ||||||
|  |   Logger.info(`>>> Renamed metadata dir from /metadata/books to /metadata/items`) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.migrate = async (db) => { | ||||||
|  |   await checkUpdateMetadataPath() | ||||||
|  |   // Before DB Load clean data
 | ||||||
|  |   await migrateUserData(db) | ||||||
|  |   await db.init() | ||||||
|  |   // After DB Load
 | ||||||
|  |   await migrateLibraryItems(db) | ||||||
|  |   // TODO: Eventually remove audiobooks db when stable
 | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user