mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-02-28 21:19:42 +00:00
Starting db migration file
This commit is contained in:
parent
b2e1e24ca5
commit
c738e35a8c
14 changed files with 477 additions and 39 deletions
188
server/utils/migrations/dbMigration.js
Normal file
188
server/utils/migrations/dbMigration.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
const package = require('../../../package.json')
|
||||
const Logger = require('../../Logger')
|
||||
const Database = require('../../Database')
|
||||
const oldDbFiles = require('./oldDbFiles')
|
||||
|
||||
const oldDbIdMap = {
|
||||
users: {},
|
||||
libraries: {},
|
||||
libraryFolders: {},
|
||||
libraryItems: {},
|
||||
books: {},
|
||||
tags: {}
|
||||
}
|
||||
|
||||
async function migrateBook(oldLibraryItem, LibraryItem) {
|
||||
const oldBook = oldLibraryItem.media
|
||||
|
||||
const Book = await Database.models.Book.create({
|
||||
title: oldBook.metadata.title,
|
||||
subtitle: oldBook.metadata.subtitle,
|
||||
publishedYear: oldBook.metadata.publishedYear,
|
||||
publishedDate: oldBook.metadata.publishedDate,
|
||||
publisher: oldBook.metadata.publisher,
|
||||
description: oldBook.metadata.description,
|
||||
isbn: oldBook.metadata.isbn,
|
||||
asin: oldBook.metadata.asin,
|
||||
language: oldBook.metadata.language,
|
||||
explicit: !!oldBook.metadata.explicit,
|
||||
lastCoverSearchQuery: oldBook.lastCoverSearchQuery,
|
||||
lastCoverSearch: oldBook.lastCoverSearch,
|
||||
LibraryItemId: LibraryItem.id,
|
||||
createdAt: LibraryItem.createdAt,
|
||||
updatedAt: LibraryItem.updatedAt
|
||||
})
|
||||
|
||||
oldDbIdMap.books[oldLibraryItem.id] = Book.id
|
||||
|
||||
// TODO: Handle cover image record
|
||||
// TODO: Handle EBook record
|
||||
|
||||
Logger.info(`[dbMigration] migrateBook: Book migrated "${Book.title}" (${Book.id})`)
|
||||
|
||||
const oldTags = oldBook.tags || []
|
||||
for (const oldTag of oldTags) {
|
||||
let tagId = null
|
||||
if (oldDbIdMap[oldTag]) {
|
||||
tagId = oldDbIdMap[oldTag]
|
||||
} else {
|
||||
const Tag = await Database.models.Tag.create({
|
||||
name: oldTag
|
||||
})
|
||||
tagId = Tag.id
|
||||
}
|
||||
|
||||
const BookTag = await Database.models.BookTag.create({
|
||||
BookId: Book.id,
|
||||
TagId: tagId
|
||||
})
|
||||
Logger.info(`[dbMigration] migrateBook: BookTag migrated "${oldTag}" (${BookTag.id})`)
|
||||
}
|
||||
|
||||
for (const oldChapter of oldBook.chapters) {
|
||||
const BookChapter = await Database.models.BookChapter.create({
|
||||
index: oldChapter.id,
|
||||
start: oldChapter.start,
|
||||
end: oldChapter.end,
|
||||
title: oldChapter.title,
|
||||
BookId: Book.id
|
||||
})
|
||||
Logger.info(`[dbMigration] migrateBook: BookChapter migrated "${BookChapter.title}" (${BookChapter.id})`)
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateLibraryItems(oldLibraryItems) {
|
||||
for (const oldLibraryItem of oldLibraryItems) {
|
||||
Logger.info(`[dbMigration] migrateLibraryItems: Migrating library item "${oldLibraryItem.media.metadata.title}" (${oldLibraryItem.id})`)
|
||||
|
||||
const LibraryId = oldDbIdMap.libraryFolders[oldLibraryItem.folderId]
|
||||
if (!LibraryId) {
|
||||
Logger.error(`[dbMigration] migrateLibraryItems: Old library folder id not found "${oldLibraryItem.folderId}"`)
|
||||
continue
|
||||
}
|
||||
|
||||
const LibraryItem = await Database.models.LibraryItem.create({
|
||||
ino: oldLibraryItem.ino,
|
||||
path: oldLibraryItem.path,
|
||||
relPath: oldLibraryItem.relPath,
|
||||
mediaType: oldLibraryItem.mediaType,
|
||||
isFile: !!oldLibraryItem.isFile,
|
||||
isMissing: !!oldLibraryItem.isMissing,
|
||||
isInvalid: !!oldLibraryItem.isInvalid,
|
||||
mtime: oldLibraryItem.mtimeMs,
|
||||
ctime: oldLibraryItem.ctimeMs,
|
||||
birthtime: oldLibraryItem.birthtimeMs,
|
||||
lastScan: oldLibraryItem.lastScan,
|
||||
lastScanVersion: oldLibraryItem.scanVersion,
|
||||
createdAt: oldLibraryItem.addedAt,
|
||||
updatedAt: oldLibraryItem.updatedAt,
|
||||
LibraryId
|
||||
})
|
||||
|
||||
Logger.info(`[dbMigration] migrateLibraryItems: LibraryItem "${LibraryItem.path}" migrated (${LibraryItem.id})`)
|
||||
|
||||
if (oldLibraryItem.mediaType === 'book') {
|
||||
await migrateBook(oldLibraryItem, LibraryItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateLibraries(oldLibraries) {
|
||||
for (const oldLibrary of oldLibraries) {
|
||||
Logger.info(`[dbMigration] migrateLibraries: Migrating library "${oldLibrary.name}" (${oldLibrary.id})`)
|
||||
|
||||
const Library = await Database.models.Library.create({
|
||||
name: oldLibrary.name,
|
||||
displayOrder: oldLibrary.displayOrder,
|
||||
icon: oldLibrary.icon || null,
|
||||
mediaType: oldLibrary.mediaType || null,
|
||||
provider: oldLibrary.provider,
|
||||
createdAt: oldLibrary.createdAt,
|
||||
updatedAt: oldLibrary.lastUpdate
|
||||
})
|
||||
|
||||
oldDbIdMap.libraries[oldLibrary.id] = Library.id
|
||||
|
||||
const oldLibrarySettings = oldLibrary.settings || {}
|
||||
for (const oldSettingsKey in oldLibrarySettings) {
|
||||
const LibrarySetting = await Database.models.LibrarySetting.create({
|
||||
key: oldSettingsKey,
|
||||
value: oldLibrarySettings[oldSettingsKey],
|
||||
LibraryId: Library.id
|
||||
})
|
||||
Logger.info(`[dbMigration] migrateLibraries: LibrarySetting "${LibrarySetting.key}" migrated (${LibrarySetting.id})`)
|
||||
}
|
||||
|
||||
Logger.info(`[dbMigration] migrateLibraries: Library "${Library.name}" migrated (${Library.id})`)
|
||||
|
||||
for (const oldFolder of oldLibrary.folders) {
|
||||
Logger.info(`[dbMigration] migrateLibraries: Migrating folder "${oldFolder.fullPath}" (${oldFolder.id})`)
|
||||
|
||||
const LibraryFolder = await Database.models.LibraryFolder.create({
|
||||
path: oldFolder.fullPath,
|
||||
LibraryId: Library.id,
|
||||
createdAt: oldFolder.addedAt
|
||||
})
|
||||
|
||||
oldDbIdMap.libraryFolders[oldFolder.id] = LibraryFolder.id
|
||||
|
||||
Logger.info(`[dbMigration] migrateLibraries: LibraryFolder "${LibraryFolder.path}" migrated (${LibraryFolder.id})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateUsers(oldUsers) {
|
||||
for (const oldUser of oldUsers) {
|
||||
Logger.info(`[dbMigration] migrateUsers: Migrating user "${oldUser.username}" (${oldUser.id})`)
|
||||
|
||||
const User = await Database.models.User.create({
|
||||
username: oldUser.username,
|
||||
pash: oldUser.pash || null,
|
||||
type: oldUser.type || null,
|
||||
token: oldUser.token || null,
|
||||
isActive: !!oldUser.isActive,
|
||||
lastSeen: oldUser.lastSeen || null,
|
||||
createdAt: oldUser.createdAt || Date.now()
|
||||
})
|
||||
|
||||
oldDbIdMap.users[oldUser.id] = User.id
|
||||
|
||||
Logger.info(`[dbMigration] migrateUsers: User "${User.username}" migrated (${User.id})`)
|
||||
|
||||
// for (const oldMediaProgress of oldUser.mediaProgress) {
|
||||
// const MediaProgress = await Database.models.MediaProgress.create({
|
||||
|
||||
// })
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.migrate = async () => {
|
||||
Logger.info(`[dbMigration3] Starting migration`)
|
||||
|
||||
const data = await oldDbFiles.init()
|
||||
|
||||
await migrateLibraries(data.libraries)
|
||||
await migrateLibraryItems(data.libraryItems.slice(0, 10))
|
||||
await migrateUsers(data.users)
|
||||
}
|
||||
410
server/utils/migrations/dbMigrationOld.js
Normal file
410
server/utils/migrations/dbMigrationOld.js
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
const Path = require('path')
|
||||
const fs = require('../../libs/fsExtra')
|
||||
const njodb = require('../../libs/njodb')
|
||||
|
||||
const { SupportedEbookTypes } = require('../globals')
|
||||
const { PlayMethod } = require('../constants')
|
||||
const { getId } = require('../index')
|
||||
const { filePathToPOSIX } = require('../fileUtils')
|
||||
const Logger = require('../../Logger')
|
||||
|
||||
const Library = require('../../objects/Library')
|
||||
const LibraryItem = require('../../objects/LibraryItem')
|
||||
const Book = require('../../objects/mediaTypes/Book')
|
||||
|
||||
const BookMetadata = require('../../objects/metadata/BookMetadata')
|
||||
const FileMetadata = require('../../objects/metadata/FileMetadata')
|
||||
|
||||
const AudioFile = require('../../objects/files/AudioFile')
|
||||
const EBookFile = require('../../objects/files/EBookFile')
|
||||
const LibraryFile = require('../../objects/files/LibraryFile')
|
||||
const AudioMetaTags = require('../../objects/metadata/AudioMetaTags')
|
||||
|
||||
const Author = require('../../objects/entities/Author')
|
||||
const Series = require('../../objects/entities/Series')
|
||||
|
||||
const MediaProgress = require('../../objects/user/MediaProgress')
|
||||
const PlaybackSession = require('../../objects/PlaybackSession')
|
||||
|
||||
const { isObject } = require('..')
|
||||
const User = require('../../objects/user/User')
|
||||
|
||||
var authorsToAdd = []
|
||||
var existingDbAuthors = []
|
||||
var seriesToAdd = []
|
||||
var existingDbSeries = []
|
||||
|
||||
// Load old audiobooks
|
||||
async function loadAudiobooks() {
|
||||
var audiobookPath = Path.join(global.ConfigPath, 'audiobooks')
|
||||
|
||||
Logger.debug(`[dbMigration] loadAudiobooks path ${audiobookPath}`)
|
||||
var pathExists = await fs.pathExists(audiobookPath)
|
||||
if (!pathExists) {
|
||||
Logger.debug(`[dbMigration] loadAudiobooks path does not exist ${audiobookPath}`)
|
||||
return []
|
||||
}
|
||||
|
||||
var audiobooksDb = new njodb.Database(audiobookPath)
|
||||
return audiobooksDb.select(() => true).then((results) => {
|
||||
Logger.debug(`[dbMigration] loadAudiobooks select results ${results.data.length}`)
|
||||
return results.data
|
||||
})
|
||||
}
|
||||
|
||||
function makeAuthorsFromOldAb(authorsList) {
|
||||
return authorsList.filter(a => !!a).map(authorName => {
|
||||
var existingAuthor = authorsToAdd.find(a => a.name.toLowerCase() === authorName.toLowerCase())
|
||||
if (existingAuthor) {
|
||||
return existingAuthor.toJSONMinimal()
|
||||
}
|
||||
var existingDbAuthor = existingDbAuthors.find(a => a.name.toLowerCase() === authorName.toLowerCase())
|
||||
if (existingDbAuthor) {
|
||||
return existingDbAuthor.toJSONMinimal()
|
||||
}
|
||||
|
||||
var newAuthor = new Author()
|
||||
newAuthor.setData({ name: authorName })
|
||||
authorsToAdd.push(newAuthor)
|
||||
// Logger.debug(`>>> Created new author named "${authorName}"`)
|
||||
return newAuthor.toJSONMinimal()
|
||||
})
|
||||
}
|
||||
|
||||
function makeSeriesFromOldAb({ series, volumeNumber }) {
|
||||
var existingSeries = seriesToAdd.find(s => s.name.toLowerCase() === series.toLowerCase())
|
||||
if (existingSeries) {
|
||||
return [existingSeries.toJSONMinimal(volumeNumber)]
|
||||
}
|
||||
var existingDbSeriesItem = existingDbSeries.find(s => s.name.toLowerCase() === series.toLowerCase())
|
||||
if (existingDbSeriesItem) {
|
||||
return [existingDbSeriesItem.toJSONMinimal(volumeNumber)]
|
||||
}
|
||||
var newSeries = new Series()
|
||||
newSeries.setData({ name: series })
|
||||
seriesToAdd.push(newSeries)
|
||||
Logger.info(`>>> Created new series named "${series}"`)
|
||||
return [newSeries.toJSONMinimal(volumeNumber)]
|
||||
}
|
||||
|
||||
function getRelativePath(srcPath, basePath) {
|
||||
srcPath = filePathToPOSIX(srcPath)
|
||||
basePath = filePathToPOSIX(basePath)
|
||||
return srcPath.replace(basePath, '')
|
||||
}
|
||||
|
||||
function makeFilesFromOldAb(audiobook) {
|
||||
var libraryFiles = []
|
||||
var ebookFiles = []
|
||||
|
||||
var _audioFiles = audiobook.audioFiles || []
|
||||
var audioFiles = _audioFiles.map((af) => {
|
||||
var fileMetadata = new FileMetadata(af)
|
||||
fileMetadata.path = af.fullPath
|
||||
fileMetadata.relPath = getRelativePath(af.fullPath, audiobook.fullPath)
|
||||
|
||||
var newLibraryFile = new LibraryFile()
|
||||
newLibraryFile.ino = af.ino
|
||||
newLibraryFile.metadata = fileMetadata.clone()
|
||||
newLibraryFile.addedAt = af.addedAt
|
||||
newLibraryFile.updatedAt = Date.now()
|
||||
libraryFiles.push(newLibraryFile)
|
||||
|
||||
var audioMetaTags = new AudioMetaTags(af.metadata || {}) // Old metaTags was named metadata
|
||||
delete af.metadata
|
||||
|
||||
var newAudioFile = new AudioFile(af)
|
||||
newAudioFile.metadata = fileMetadata
|
||||
newAudioFile.metaTags = audioMetaTags
|
||||
newAudioFile.updatedAt = Date.now()
|
||||
return newAudioFile
|
||||
})
|
||||
|
||||
var _otherFiles = audiobook.otherFiles || []
|
||||
_otherFiles.forEach((file) => {
|
||||
var fileMetadata = new FileMetadata(file)
|
||||
fileMetadata.path = file.fullPath
|
||||
fileMetadata.relPath = getRelativePath(file.fullPath, audiobook.fullPath)
|
||||
|
||||
var newLibraryFile = new LibraryFile()
|
||||
newLibraryFile.ino = file.ino
|
||||
newLibraryFile.metadata = fileMetadata.clone()
|
||||
newLibraryFile.addedAt = file.addedAt
|
||||
newLibraryFile.updatedAt = Date.now()
|
||||
libraryFiles.push(newLibraryFile)
|
||||
|
||||
var formatExt = (file.ext || '').slice(1)
|
||||
if (SupportedEbookTypes.includes(formatExt)) {
|
||||
var newEBookFile = new EBookFile()
|
||||
newEBookFile.ino = file.ino
|
||||
newEBookFile.metadata = fileMetadata
|
||||
newEBookFile.ebookFormat = formatExt
|
||||
newEBookFile.addedAt = file.addedAt
|
||||
newEBookFile.updatedAt = Date.now()
|
||||
ebookFiles.push(newEBookFile)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
libraryFiles,
|
||||
ebookFiles,
|
||||
audioFiles
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var libraryItem = new LibraryItem()
|
||||
libraryItem.id = audiobook.id
|
||||
libraryItem.ino = audiobook.ino
|
||||
libraryItem.libraryId = audiobook.libraryId
|
||||
libraryItem.folderId = audiobook.folderId
|
||||
libraryItem.path = audiobook.fullPath
|
||||
libraryItem.relPath = audiobook.path
|
||||
libraryItem.mtimeMs = audiobook.mtimeMs || 0
|
||||
libraryItem.ctimeMs = audiobook.ctimeMs || 0
|
||||
libraryItem.birthtimeMs = audiobook.birthtimeMs || 0
|
||||
libraryItem.addedAt = audiobook.addedAt
|
||||
libraryItem.updatedAt = audiobook.lastUpdate
|
||||
libraryItem.lastScan = audiobook.lastScan
|
||||
libraryItem.scanVersion = audiobook.scanVersion
|
||||
libraryItem.isMissing = audiobook.isMissing
|
||||
libraryItem.mediaType = 'book'
|
||||
|
||||
var bookEntity = new Book()
|
||||
var bookMetadata = new BookMetadata(audiobook.book)
|
||||
bookMetadata.publishedYear = audiobook.book.publishYear || null
|
||||
if (audiobook.book.narrator) {
|
||||
bookMetadata.narrators = (audiobook.book.narrator || '').split(', ')
|
||||
}
|
||||
// Returns array of json minimal authors
|
||||
bookMetadata.authors = makeAuthorsFromOldAb((audiobook.book.authorFL || '').split(', '))
|
||||
|
||||
// Returns array of json minimal series
|
||||
if (audiobook.book.series) {
|
||||
bookMetadata.series = makeSeriesFromOldAb(audiobook.book)
|
||||
}
|
||||
|
||||
bookEntity.libraryItemId = libraryItem.id
|
||||
bookEntity.metadata = bookMetadata
|
||||
bookEntity.coverPath = cleanOldCoverPath(audiobook.book.coverFullPath)
|
||||
bookEntity.tags = [...audiobook.tags]
|
||||
|
||||
var payload = makeFilesFromOldAb(audiobook)
|
||||
bookEntity.audioFiles = payload.audioFiles
|
||||
bookEntity.chapters = []
|
||||
if (audiobook.chapters && audiobook.chapters.length) {
|
||||
bookEntity.chapters = audiobook.chapters.map(c => ({ ...c }))
|
||||
}
|
||||
bookEntity.missingParts = audiobook.missingParts || []
|
||||
|
||||
if (payload.ebookFiles.length) {
|
||||
bookEntity.ebookFile = payload.ebookFiles[0]
|
||||
}
|
||||
|
||||
libraryItem.media = bookEntity
|
||||
libraryItem.libraryFiles = payload.libraryFiles
|
||||
return libraryItem
|
||||
}
|
||||
|
||||
async function migrateLibraryItems(db) {
|
||||
Logger.info(`==== Starting Library Item migration ====`)
|
||||
|
||||
var audiobooks = await loadAudiobooks()
|
||||
if (!audiobooks.length) {
|
||||
Logger.info(`>>> No audiobooks in db, no migration necessary`)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.info(`>>> Loaded old audiobook data with ${audiobooks.length} records`)
|
||||
|
||||
if (db.libraryItems.length) {
|
||||
Logger.info(`>>> Some library items already loaded ${db.libraryItems.length} items | ${db.series.length} series | ${db.authors.length} authors`)
|
||||
return
|
||||
}
|
||||
|
||||
if (db.authors && db.authors.length) {
|
||||
existingDbAuthors = db.authors
|
||||
}
|
||||
if (db.series && db.series.length) {
|
||||
existingDbSeries = db.series
|
||||
}
|
||||
|
||||
var libraryItems = audiobooks.map((ab) => makeLibraryItemFromOldAb(ab))
|
||||
|
||||
Logger.info(`>>> ${libraryItems.length} Library Items made`)
|
||||
await db.bulkInsertEntities('libraryItem', libraryItems)
|
||||
if (authorsToAdd.length) {
|
||||
Logger.info(`>>> ${authorsToAdd.length} Authors made`)
|
||||
await db.bulkInsertEntities('author', authorsToAdd)
|
||||
}
|
||||
if (seriesToAdd.length) {
|
||||
Logger.info(`>>> ${seriesToAdd.length} Series made`)
|
||||
await db.insertEntities('series', seriesToAdd)
|
||||
}
|
||||
existingDbSeries = []
|
||||
existingDbAuthors = []
|
||||
authorsToAdd = []
|
||||
seriesToAdd = []
|
||||
Logger.info(`==== Library Item migration complete ====`)
|
||||
}
|
||||
|
||||
function cleanUserObject(db, userObj) {
|
||||
var cleanedUserPayload = {
|
||||
...userObj,
|
||||
mediaProgress: [],
|
||||
bookmarks: []
|
||||
}
|
||||
|
||||
// UserAudiobookData is now MediaProgress and AudioBookmarks separated
|
||||
if (userObj.audiobooks) {
|
||||
for (const audiobookId in userObj.audiobooks) {
|
||||
if (isObject(userObj.audiobooks[audiobookId])) {
|
||||
// Bookmarks now live on User.js object instead of inside UserAudiobookData
|
||||
if (userObj.audiobooks[audiobookId].bookmarks) {
|
||||
const cleanedBookmarks = userObj.audiobooks[audiobookId].bookmarks.map((bm) => {
|
||||
bm.libraryItemId = audiobookId
|
||||
return bm
|
||||
})
|
||||
cleanedUserPayload.bookmarks = cleanedUserPayload.bookmarks.concat(cleanedBookmarks)
|
||||
}
|
||||
|
||||
var userAudiobookData = userObj.audiobooks[audiobookId]
|
||||
var liProgress = new MediaProgress() // New Progress Object
|
||||
liProgress.id = userAudiobookData.audiobookId
|
||||
liProgress.libraryItemId = userAudiobookData.audiobookId
|
||||
liProgress.duration = userAudiobookData.totalDuration
|
||||
liProgress.isFinished = !!userAudiobookData.isRead
|
||||
Object.keys(liProgress.toJSON()).forEach((key) => {
|
||||
if (userAudiobookData[key] !== undefined) {
|
||||
liProgress[key] = userAudiobookData[key]
|
||||
}
|
||||
})
|
||||
cleanedUserPayload.mediaProgress.push(liProgress.toJSON())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const user = new User(cleanedUserPayload)
|
||||
return db.usersDb.update((record) => record.id === user.id, () => user).then((results) => {
|
||||
Logger.debug(`[dbMigration] Updated User: ${results.updated} | Selected: ${results.selected}`)
|
||||
return true
|
||||
}).catch((error) => {
|
||||
Logger.error(`[dbMigration] Update User Failed: ${error}`)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function cleanSessionObj(db, userListeningSession) {
|
||||
var newPlaybackSession = new PlaybackSession(userListeningSession)
|
||||
newPlaybackSession.id = getId('play')
|
||||
newPlaybackSession.mediaType = 'book'
|
||||
newPlaybackSession.updatedAt = userListeningSession.lastUpdate
|
||||
newPlaybackSession.libraryItemId = userListeningSession.audiobookId
|
||||
newPlaybackSession.playMethod = PlayMethod.TRANSCODE
|
||||
|
||||
// We only have title to transfer over nicely
|
||||
var bookMetadata = new BookMetadata()
|
||||
bookMetadata.title = userListeningSession.audiobookTitle || ''
|
||||
newPlaybackSession.mediaMetadata = bookMetadata
|
||||
|
||||
return db.sessionsDb.update((record) => record.id === userListeningSession.id, () => newPlaybackSession).then((results) => true).catch((error) => {
|
||||
Logger.error(`[dbMigration] Update Session Failed: ${error}`)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
async function migrateUserData(db) {
|
||||
Logger.info(`==== Starting User migration ====`)
|
||||
|
||||
// Libraries with previous mediaType of "podcast" moved to "book"
|
||||
// because migrating those items to podcast objects will be a nightmare
|
||||
// users will need to create a new library for podcasts
|
||||
var availableIcons = ['database', 'audiobook', 'book', 'comic', 'podcast']
|
||||
const libraries = await db.librariesDb.select((result) => (result.mediaType != 'book' || !availableIcons.includes(result.icon)))
|
||||
.then((results) => results.data.map(lib => new Library(lib)))
|
||||
if (!libraries.length) {
|
||||
Logger.info('[dbMigration] No libraries found needing migration')
|
||||
} else {
|
||||
for (const library of libraries) {
|
||||
Logger.info(`>> Migrating library "${library.name}" with media type "${library.mediaType}"`)
|
||||
await db.librariesDb.update((record) => record.id === library.id, () => library).then(() => true).catch((error) => {
|
||||
Logger.error(`[dbMigration] Update library failed: ${error}`)
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const userObjects = await db.usersDb.select((result) => result.audiobooks != undefined).then((results) => results.data)
|
||||
if (!userObjects.length) {
|
||||
Logger.warn('[dbMigration] No users found needing migration')
|
||||
return
|
||||
}
|
||||
|
||||
var userCount = 0
|
||||
for (const userObj of userObjects) {
|
||||
Logger.info(`[dbMigration] Migrating User "${userObj.username}"`)
|
||||
var success = await cleanUserObject(db, userObj)
|
||||
if (!success) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
Logger.warn(`[dbMigration] Second attempt Migrating User "${userObj.username}"`)
|
||||
success = await cleanUserObject(db, userObj)
|
||||
if (!success) {
|
||||
throw new Error('Db migration failed migrating users')
|
||||
}
|
||||
}
|
||||
userCount++
|
||||
}
|
||||
|
||||
var sessionCount = 0
|
||||
const userListeningSessions = await db.sessionsDb.select((result) => result.audiobookId != undefined).then((results) => results.data)
|
||||
if (userListeningSessions.length) {
|
||||
|
||||
for (const session of userListeningSessions) {
|
||||
var success = await cleanSessionObj(db, session)
|
||||
if (!success) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
Logger.warn(`[dbMigration] Second attempt Migrating Session "${session.id}"`)
|
||||
success = await cleanSessionObj(db, session)
|
||||
if (!success) {
|
||||
Logger.error(`[dbMigration] Failed to migrate session "${session.id}"`)
|
||||
}
|
||||
}
|
||||
if (success) sessionCount++
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info(`==== User migration complete (${userCount} Users, ${sessionCount} Sessions) ====`)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
93
server/utils/migrations/oldDbFiles.js
Normal file
93
server/utils/migrations/oldDbFiles.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
const { once } = require('events')
|
||||
const { createInterface } = require('readline')
|
||||
const Path = require('path')
|
||||
const Logger = require('../../Logger')
|
||||
const fs = require('../../libs/fsExtra')
|
||||
|
||||
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.init = async () => {
|
||||
const dbs = {
|
||||
libraryItems: Path.join(global.ConfigPath, 'libraryItems', 'data'),
|
||||
users: Path.join(global.ConfigPath, 'users', 'data'),
|
||||
sessions: Path.join(global.ConfigPath, 'sessions', 'data'),
|
||||
libraries: Path.join(global.ConfigPath, 'libraries', 'data'),
|
||||
settings: Path.join(global.ConfigPath, 'settings', 'data'),
|
||||
collections: Path.join(global.ConfigPath, 'collections', 'data'),
|
||||
playlists: Path.join(global.ConfigPath, 'playlists', 'data'),
|
||||
authors: Path.join(global.ConfigPath, 'authors', 'data'),
|
||||
series: Path.join(global.ConfigPath, 'series', 'data'),
|
||||
feeds: Path.join(global.ConfigPath, 'feeds', 'data')
|
||||
}
|
||||
|
||||
const data = {}
|
||||
for (const key in dbs) {
|
||||
data[key] = await loadDbData(dbs[key])
|
||||
Logger.info(`[oldDbFiles] ${data[key].length} ${key} loaded`)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue