mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-07-16 04:14:58 +02:00
Init sqlite take 2
This commit is contained in:
parent
d86a3b3dc2
commit
cf7fd315b6
88 changed files with 7017 additions and 692 deletions
763
server/utils/migrations/dbMigration.js
Normal file
763
server/utils/migrations/dbMigration.js
Normal file
|
@ -0,0 +1,763 @@
|
|||
const Path = require('path')
|
||||
const uuidv4 = require("uuid").v4
|
||||
const Logger = require('../../Logger')
|
||||
const fs = require('../../libs/fsExtra')
|
||||
const oldDbFiles = require('./oldDbFiles')
|
||||
|
||||
const oldDbIdMap = {
|
||||
users: {},
|
||||
libraries: {},
|
||||
libraryFolders: {},
|
||||
libraryItems: {},
|
||||
authors: {},
|
||||
series: {},
|
||||
collections: {},
|
||||
podcastEpisodes: {},
|
||||
books: {}, // key is library item id
|
||||
podcasts: {}, // key is library item id
|
||||
devices: {} // key is a json stringify of the old DeviceInfo data OR deviceId if it exists
|
||||
}
|
||||
const newRecords = {
|
||||
user: [],
|
||||
library: [],
|
||||
libraryFolder: [],
|
||||
author: [],
|
||||
book: [],
|
||||
podcast: [],
|
||||
libraryItem: [],
|
||||
bookAuthor: [],
|
||||
series: [],
|
||||
bookSeries: [],
|
||||
podcastEpisode: [],
|
||||
mediaProgress: [],
|
||||
device: [],
|
||||
playbackSession: [],
|
||||
collection: [],
|
||||
collectionBook: [],
|
||||
playlist: [],
|
||||
playlistMediaItem: [],
|
||||
feed: [],
|
||||
feedEpisode: [],
|
||||
setting: []
|
||||
}
|
||||
|
||||
function getDeviceInfoString(deviceInfo, UserId) {
|
||||
if (!deviceInfo) return null
|
||||
if (deviceInfo.deviceId) return deviceInfo.deviceId
|
||||
|
||||
const keys = [
|
||||
UserId,
|
||||
deviceInfo.browserName || null,
|
||||
deviceInfo.browserVersion || null,
|
||||
deviceInfo.osName || null,
|
||||
deviceInfo.osVersion || null,
|
||||
deviceInfo.clientVersion || null,
|
||||
deviceInfo.manufacturer || null,
|
||||
deviceInfo.model || null,
|
||||
deviceInfo.sdkVersion || null,
|
||||
deviceInfo.ipAddress || null
|
||||
].map(k => k || '')
|
||||
return 'temp-' + Buffer.from(keys.join('-'), 'utf-8').toString('base64')
|
||||
}
|
||||
|
||||
function migrateBook(oldLibraryItem, LibraryItem) {
|
||||
const oldBook = oldLibraryItem.media
|
||||
|
||||
//
|
||||
// Migrate Book
|
||||
//
|
||||
const Book = {
|
||||
id: uuidv4(),
|
||||
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,
|
||||
abridged: !!oldBook.metadata.abridged,
|
||||
lastCoverSearchQuery: oldBook.lastCoverSearchQuery,
|
||||
lastCoverSearch: oldBook.lastCoverSearch,
|
||||
createdAt: LibraryItem.createdAt,
|
||||
updatedAt: LibraryItem.updatedAt,
|
||||
narrators: oldBook.metadata.narrators,
|
||||
ebookFile: oldBook.ebookFile,
|
||||
coverPath: oldBook.coverPath,
|
||||
audioFiles: oldBook.audioFiles,
|
||||
chapters: oldBook.chapters,
|
||||
tags: oldBook.tags,
|
||||
genres: oldBook.metadata.genres
|
||||
}
|
||||
newRecords.book.push(Book)
|
||||
oldDbIdMap.books[oldLibraryItem.id] = Book.id
|
||||
|
||||
//
|
||||
// Migrate BookAuthors
|
||||
//
|
||||
for (const oldBookAuthor of oldBook.metadata.authors) {
|
||||
if (oldDbIdMap.authors[oldBookAuthor.id]) {
|
||||
newRecords.bookAuthor.push({
|
||||
id: uuidv4(),
|
||||
authorId: oldDbIdMap.authors[oldBookAuthor.id],
|
||||
bookId: Book.id
|
||||
})
|
||||
} else {
|
||||
Logger.warn(`[dbMigration] migrateBook: Book author not found "${oldBookAuthor.name}"`)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Migrate BookSeries
|
||||
//
|
||||
for (const oldBookSeries of oldBook.metadata.series) {
|
||||
if (oldDbIdMap.series[oldBookSeries.id]) {
|
||||
const BookSeries = {
|
||||
id: uuidv4(),
|
||||
sequence: oldBookSeries.sequence,
|
||||
seriesId: oldDbIdMap.series[oldBookSeries.id],
|
||||
bookId: Book.id
|
||||
}
|
||||
newRecords.bookSeries.push(BookSeries)
|
||||
} else {
|
||||
Logger.warn(`[dbMigration] migrateBook: Series not found "${oldBookSeries.name}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migratePodcast(oldLibraryItem, LibraryItem) {
|
||||
const oldPodcast = oldLibraryItem.media
|
||||
const oldPodcastMetadata = oldPodcast.metadata
|
||||
|
||||
//
|
||||
// Migrate Podcast
|
||||
//
|
||||
const Podcast = {
|
||||
id: uuidv4(),
|
||||
title: oldPodcastMetadata.title,
|
||||
author: oldPodcastMetadata.author,
|
||||
releaseDate: oldPodcastMetadata.releaseDate,
|
||||
feedURL: oldPodcastMetadata.feedUrl,
|
||||
imageURL: oldPodcastMetadata.imageUrl,
|
||||
description: oldPodcastMetadata.description,
|
||||
itunesPageURL: oldPodcastMetadata.itunesPageUrl,
|
||||
itunesId: oldPodcastMetadata.itunesId,
|
||||
itunesArtistId: oldPodcastMetadata.itunesArtistId,
|
||||
language: oldPodcastMetadata.language,
|
||||
podcastType: oldPodcastMetadata.type,
|
||||
explicit: !!oldPodcastMetadata.explicit,
|
||||
autoDownloadEpisodes: !!oldPodcast.autoDownloadEpisodes,
|
||||
autoDownloadSchedule: oldPodcast.autoDownloadSchedule,
|
||||
lastEpisodeCheck: oldPodcast.lastEpisodeCheck,
|
||||
maxEpisodesToKeep: oldPodcast.maxEpisodesToKeep,
|
||||
maxNewEpisodesToDownload: oldPodcast.maxNewEpisodesToDownload,
|
||||
lastCoverSearchQuery: oldPodcast.lastCoverSearchQuery,
|
||||
lastCoverSearch: oldPodcast.lastCoverSearch,
|
||||
createdAt: LibraryItem.createdAt,
|
||||
updatedAt: LibraryItem.updatedAt,
|
||||
coverPath: oldPodcast.coverPath,
|
||||
tags: oldPodcast.tags,
|
||||
genres: oldPodcastMetadata.genres
|
||||
}
|
||||
newRecords.podcast.push(Podcast)
|
||||
oldDbIdMap.podcasts[oldLibraryItem.id] = Podcast.id
|
||||
|
||||
//
|
||||
// Migrate PodcastEpisodes
|
||||
//
|
||||
const oldEpisodes = oldPodcast.episodes || []
|
||||
for (const oldEpisode of oldEpisodes) {
|
||||
const PodcastEpisode = {
|
||||
id: uuidv4(),
|
||||
index: oldEpisode.index,
|
||||
season: oldEpisode.season,
|
||||
episode: oldEpisode.episode,
|
||||
episodeType: oldEpisode.episodeType,
|
||||
title: oldEpisode.title,
|
||||
subtitle: oldEpisode.subtitle,
|
||||
description: oldEpisode.description,
|
||||
pubDate: oldEpisode.pubDate,
|
||||
enclosureURL: oldEpisode.enclosure?.url || null,
|
||||
enclosureSize: oldEpisode.enclosure?.length || null,
|
||||
enclosureType: oldEpisode.enclosure?.type || null,
|
||||
publishedAt: oldEpisode.publishedAt,
|
||||
createdAt: oldEpisode.addedAt,
|
||||
updatedAt: oldEpisode.updatedAt,
|
||||
podcastId: Podcast.id,
|
||||
audioFile: oldEpisode.audioFile,
|
||||
chapters: oldEpisode.chapters
|
||||
}
|
||||
newRecords.podcastEpisode.push(PodcastEpisode)
|
||||
oldDbIdMap.podcastEpisodes[oldEpisode.id] = PodcastEpisode.id
|
||||
}
|
||||
}
|
||||
|
||||
function migrateLibraryItems(oldLibraryItems) {
|
||||
for (const oldLibraryItem of oldLibraryItems) {
|
||||
const libraryFolderId = oldDbIdMap.libraryFolders[oldLibraryItem.folderId]
|
||||
if (!libraryFolderId) {
|
||||
Logger.error(`[dbMigration] migrateLibraryItems: Old library folder id not found "${oldLibraryItem.folderId}"`)
|
||||
continue
|
||||
}
|
||||
const libraryId = oldDbIdMap.libraries[oldLibraryItem.libraryId]
|
||||
if (!libraryId) {
|
||||
Logger.error(`[dbMigration] migrateLibraryItems: Old library id not found "${oldLibraryItem.libraryId}"`)
|
||||
continue
|
||||
}
|
||||
if (!['book', 'podcast'].includes(oldLibraryItem.mediaType)) {
|
||||
Logger.error(`[dbMigration] migrateLibraryItems: Not migrating library item with mediaType=${oldLibraryItem.mediaType}`)
|
||||
continue
|
||||
}
|
||||
|
||||
//
|
||||
// Migrate LibraryItem
|
||||
//
|
||||
const LibraryItem = {
|
||||
id: uuidv4(),
|
||||
ino: oldLibraryItem.ino,
|
||||
path: oldLibraryItem.path,
|
||||
relPath: oldLibraryItem.relPath,
|
||||
mediaId: null, // set below
|
||||
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,
|
||||
libraryFolderId,
|
||||
libraryFiles: oldLibraryItem.libraryFiles.map(lf => {
|
||||
if (lf.isSupplementary === undefined) lf.isSupplementary = null
|
||||
return lf
|
||||
})
|
||||
}
|
||||
oldDbIdMap.libraryItems[oldLibraryItem.id] = LibraryItem.id
|
||||
newRecords.libraryItem.push(LibraryItem)
|
||||
|
||||
//
|
||||
// Migrate Book/Podcast
|
||||
//
|
||||
if (oldLibraryItem.mediaType === 'book') {
|
||||
migrateBook(oldLibraryItem, LibraryItem)
|
||||
LibraryItem.mediaId = oldDbIdMap.books[oldLibraryItem.id]
|
||||
} else if (oldLibraryItem.mediaType === 'podcast') {
|
||||
migratePodcast(oldLibraryItem, LibraryItem)
|
||||
LibraryItem.mediaId = oldDbIdMap.podcasts[oldLibraryItem.id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateLibraries(oldLibraries) {
|
||||
for (const oldLibrary of oldLibraries) {
|
||||
if (!['book', 'podcast'].includes(oldLibrary.mediaType)) {
|
||||
Logger.error(`[dbMigration] migrateLibraries: Not migrating library with mediaType=${oldLibrary.mediaType}`)
|
||||
continue
|
||||
}
|
||||
|
||||
//
|
||||
// Migrate Library
|
||||
//
|
||||
const Library = {
|
||||
id: uuidv4(),
|
||||
name: oldLibrary.name,
|
||||
displayOrder: oldLibrary.displayOrder,
|
||||
icon: oldLibrary.icon || null,
|
||||
mediaType: oldLibrary.mediaType || null,
|
||||
provider: oldLibrary.provider,
|
||||
settings: oldLibrary.settings || {},
|
||||
createdAt: oldLibrary.createdAt,
|
||||
updatedAt: oldLibrary.lastUpdate
|
||||
}
|
||||
oldDbIdMap.libraries[oldLibrary.id] = Library.id
|
||||
newRecords.library.push(Library)
|
||||
|
||||
//
|
||||
// Migrate LibraryFolders
|
||||
//
|
||||
for (const oldFolder of oldLibrary.folders) {
|
||||
const LibraryFolder = {
|
||||
id: uuidv4(),
|
||||
path: oldFolder.fullPath,
|
||||
createdAt: oldFolder.addedAt,
|
||||
updatedAt: oldLibrary.lastUpdate,
|
||||
libraryId: Library.id
|
||||
}
|
||||
oldDbIdMap.libraryFolders[oldFolder.id] = LibraryFolder.id
|
||||
newRecords.libraryFolder.push(LibraryFolder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateAuthors(oldAuthors) {
|
||||
for (const oldAuthor of oldAuthors) {
|
||||
const Author = {
|
||||
id: uuidv4(),
|
||||
name: oldAuthor.name,
|
||||
asin: oldAuthor.asin || null,
|
||||
description: oldAuthor.description,
|
||||
imagePath: oldAuthor.imagePath,
|
||||
createdAt: oldAuthor.addedAt || Date.now(),
|
||||
updatedAt: oldAuthor.updatedAt || Date.now()
|
||||
}
|
||||
oldDbIdMap.authors[oldAuthor.id] = Author.id
|
||||
newRecords.author.push(Author)
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSeries(oldSerieses) {
|
||||
for (const oldSeries of oldSerieses) {
|
||||
const Series = {
|
||||
id: uuidv4(),
|
||||
name: oldSeries.name,
|
||||
description: oldSeries.description || null,
|
||||
createdAt: oldSeries.addedAt || Date.now(),
|
||||
updatedAt: oldSeries.updatedAt || Date.now()
|
||||
}
|
||||
oldDbIdMap.series[oldSeries.id] = Series.id
|
||||
newRecords.series.push(Series)
|
||||
}
|
||||
}
|
||||
|
||||
function migrateUsers(oldUsers) {
|
||||
for (const oldUser of oldUsers) {
|
||||
//
|
||||
// Migrate User
|
||||
//
|
||||
const User = {
|
||||
id: uuidv4(),
|
||||
username: oldUser.username,
|
||||
pash: oldUser.pash || null,
|
||||
type: oldUser.type || null,
|
||||
token: oldUser.token || null,
|
||||
isActive: !!oldUser.isActive,
|
||||
lastSeen: oldUser.lastSeen || null,
|
||||
extraData: {
|
||||
seriesHideFromContinueListening: oldUser.seriesHideFromContinueListening || [],
|
||||
oldUserId: oldUser.id // Used to keep old tokens
|
||||
},
|
||||
createdAt: oldUser.createdAt || Date.now(),
|
||||
permissions: {
|
||||
...oldUser.permissions,
|
||||
librariesAccessible: oldUser.librariesAccessible || [],
|
||||
itemTagsSelected: oldUser.itemTagsSelected || []
|
||||
},
|
||||
bookmarks: oldUser.bookmarks
|
||||
}
|
||||
oldDbIdMap.users[oldUser.id] = User.id
|
||||
newRecords.user.push(User)
|
||||
|
||||
//
|
||||
// Migrate MediaProgress
|
||||
//
|
||||
for (const oldMediaProgress of oldUser.mediaProgress) {
|
||||
let mediaItemType = 'book'
|
||||
let mediaItemId = null
|
||||
if (oldMediaProgress.episodeId) {
|
||||
mediaItemType = 'podcastEpisode'
|
||||
mediaItemId = oldDbIdMap.podcastEpisodes[oldMediaProgress.episodeId]
|
||||
} else {
|
||||
mediaItemId = oldDbIdMap.books[oldMediaProgress.libraryItemId]
|
||||
}
|
||||
|
||||
if (!mediaItemId) {
|
||||
Logger.warn(`[dbMigration] migrateUsers: Unable to find media item for media progress "${oldMediaProgress.id}"`)
|
||||
continue
|
||||
}
|
||||
|
||||
const MediaProgress = {
|
||||
id: uuidv4(),
|
||||
mediaItemId,
|
||||
mediaItemType,
|
||||
duration: oldMediaProgress.duration,
|
||||
currentTime: oldMediaProgress.currentTime,
|
||||
ebookLocation: oldMediaProgress.ebookLocation || null,
|
||||
ebookProgress: oldMediaProgress.ebookProgress || null,
|
||||
isFinished: !!oldMediaProgress.isFinished,
|
||||
hideFromContinueListening: !!oldMediaProgress.hideFromContinueListening,
|
||||
finishedAt: oldMediaProgress.finishedAt,
|
||||
createdAt: oldMediaProgress.startedAt || oldMediaProgress.lastUpdate,
|
||||
updatedAt: oldMediaProgress.lastUpdate,
|
||||
userId: User.id,
|
||||
extraData: {
|
||||
libraryItemId: oldDbIdMap.libraryItems[oldMediaProgress.libraryItemId],
|
||||
progress: oldMediaProgress.progress
|
||||
}
|
||||
}
|
||||
newRecords.mediaProgress.push(MediaProgress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSessions(oldSessions) {
|
||||
for (const oldSession of oldSessions) {
|
||||
const userId = oldDbIdMap.users[oldSession.userId] || null // Can be null
|
||||
|
||||
//
|
||||
// Migrate Device
|
||||
//
|
||||
let deviceId = null
|
||||
if (oldSession.deviceInfo) {
|
||||
const oldDeviceInfo = oldSession.deviceInfo
|
||||
const deviceDeviceId = getDeviceInfoString(oldDeviceInfo, userId)
|
||||
deviceId = oldDbIdMap.devices[deviceDeviceId]
|
||||
if (!deviceId) {
|
||||
let clientName = 'Unknown'
|
||||
let clientVersion = null
|
||||
let deviceName = null
|
||||
let deviceVersion = oldDeviceInfo.browserVersion || null
|
||||
let extraData = {}
|
||||
if (oldDeviceInfo.sdkVersion) {
|
||||
clientName = 'Abs Android'
|
||||
clientVersion = oldDeviceInfo.clientVersion || null
|
||||
deviceName = `${oldDeviceInfo.manufacturer} ${oldDeviceInfo.model}`
|
||||
deviceVersion = oldDeviceInfo.sdkVersion
|
||||
} else if (oldDeviceInfo.model) {
|
||||
clientName = 'Abs iOS'
|
||||
clientVersion = oldDeviceInfo.clientVersion || null
|
||||
deviceName = `${oldDeviceInfo.manufacturer} ${oldDeviceInfo.model}`
|
||||
} else if (oldDeviceInfo.osName && oldDeviceInfo.browserName) {
|
||||
clientName = 'Abs Web'
|
||||
clientVersion = oldDeviceInfo.serverVersion || null
|
||||
deviceName = `${oldDeviceInfo.osName} ${oldDeviceInfo.osVersion || 'N/A'} ${oldDeviceInfo.browserName}`
|
||||
}
|
||||
|
||||
if (oldDeviceInfo.manufacturer) {
|
||||
extraData.manufacturer = oldDeviceInfo.manufacturer
|
||||
}
|
||||
if (oldDeviceInfo.model) {
|
||||
extraData.model = oldDeviceInfo.model
|
||||
}
|
||||
if (oldDeviceInfo.osName) {
|
||||
extraData.osName = oldDeviceInfo.osName
|
||||
}
|
||||
if (oldDeviceInfo.osVersion) {
|
||||
extraData.osVersion = oldDeviceInfo.osVersion
|
||||
}
|
||||
if (oldDeviceInfo.browserName) {
|
||||
extraData.browserName = oldDeviceInfo.browserName
|
||||
}
|
||||
|
||||
const id = uuidv4()
|
||||
const Device = {
|
||||
id,
|
||||
deviceId: deviceDeviceId,
|
||||
clientName,
|
||||
clientVersion,
|
||||
ipAddress: oldDeviceInfo.ipAddress,
|
||||
deviceName, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3
|
||||
deviceVersion,
|
||||
userId,
|
||||
extraData
|
||||
}
|
||||
newRecords.device.push(Device)
|
||||
oldDbIdMap.devices[deviceDeviceId] = Device.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Migrate PlaybackSession
|
||||
//
|
||||
let mediaItemId = null
|
||||
let mediaItemType = 'book'
|
||||
if (oldSession.mediaType === 'podcast') {
|
||||
mediaItemId = oldDbIdMap.podcastEpisodes[oldSession.episodeId] || null
|
||||
mediaItemType = 'podcastEpisode'
|
||||
} else {
|
||||
mediaItemId = oldDbIdMap.books[oldSession.libraryItemId] || null
|
||||
}
|
||||
|
||||
const PlaybackSession = {
|
||||
id: uuidv4(),
|
||||
mediaItemId, // Can be null
|
||||
mediaItemType,
|
||||
libraryId: oldDbIdMap.libraries[oldSession.libraryId] || null,
|
||||
displayTitle: oldSession.displayTitle,
|
||||
displayAuthor: oldSession.displayAuthor,
|
||||
duration: oldSession.duration,
|
||||
playMethod: oldSession.playMethod,
|
||||
mediaPlayer: oldSession.mediaPlayer,
|
||||
startTime: oldSession.startTime,
|
||||
currentTime: oldSession.currentTime,
|
||||
serverVersion: oldSession.deviceInfo?.serverVersion || null,
|
||||
createdAt: oldSession.startedAt,
|
||||
updatedAt: oldSession.updatedAt,
|
||||
userId, // Can be null
|
||||
deviceId,
|
||||
timeListening: oldSession.timeListening,
|
||||
coverPath: oldSession.coverPath,
|
||||
mediaMetadata: oldSession.mediaMetadata,
|
||||
date: oldSession.date,
|
||||
dayOfWeek: oldSession.dayOfWeek,
|
||||
extraData: {
|
||||
libraryItemId: oldDbIdMap.libraryItems[oldSession.libraryItemId]
|
||||
}
|
||||
}
|
||||
newRecords.playbackSession.push(PlaybackSession)
|
||||
}
|
||||
}
|
||||
|
||||
function migrateCollections(oldCollections) {
|
||||
for (const oldCollection of oldCollections) {
|
||||
const libraryId = oldDbIdMap.libraries[oldCollection.libraryId]
|
||||
if (!libraryId) {
|
||||
Logger.warn(`[dbMigration] migrateCollections: Library not found for collection "${oldCollection.name}" (id:${oldCollection.libraryId})`)
|
||||
continue
|
||||
}
|
||||
|
||||
const BookIds = oldCollection.books.map(lid => oldDbIdMap.books[lid]).filter(bid => bid)
|
||||
if (!BookIds.length) {
|
||||
Logger.warn(`[dbMigration] migrateCollections: Collection "${oldCollection.name}" has no books`)
|
||||
continue
|
||||
}
|
||||
|
||||
const Collection = {
|
||||
id: uuidv4(),
|
||||
name: oldCollection.name,
|
||||
description: oldCollection.description,
|
||||
createdAt: oldCollection.createdAt,
|
||||
updatedAt: oldCollection.lastUpdate,
|
||||
libraryId
|
||||
}
|
||||
oldDbIdMap.collections[oldCollection.id] = Collection.id
|
||||
newRecords.collection.push(Collection)
|
||||
|
||||
let order = 1
|
||||
BookIds.forEach((bookId) => {
|
||||
const CollectionBook = {
|
||||
id: uuidv4(),
|
||||
createdAt: Collection.createdAt,
|
||||
bookId,
|
||||
collectionId: Collection.id,
|
||||
order: order++
|
||||
}
|
||||
newRecords.collectionBook.push(CollectionBook)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function migratePlaylists(oldPlaylists) {
|
||||
for (const oldPlaylist of oldPlaylists) {
|
||||
const libraryId = oldDbIdMap.libraries[oldPlaylist.libraryId]
|
||||
if (!libraryId) {
|
||||
Logger.warn(`[dbMigration] migratePlaylists: Library not found for playlist "${oldPlaylist.name}" (id:${oldPlaylist.libraryId})`)
|
||||
continue
|
||||
}
|
||||
|
||||
const userId = oldDbIdMap.users[oldPlaylist.userId]
|
||||
if (!userId) {
|
||||
Logger.warn(`[dbMigration] migratePlaylists: User not found for playlist "${oldPlaylist.name}" (id:${oldPlaylist.userId})`)
|
||||
continue
|
||||
}
|
||||
|
||||
let mediaItemType = 'book'
|
||||
let MediaItemIds = []
|
||||
oldPlaylist.items.forEach((itemObj) => {
|
||||
if (itemObj.episodeId) {
|
||||
mediaItemType = 'podcastEpisode'
|
||||
if (oldDbIdMap.podcastEpisodes[itemObj.episodeId]) {
|
||||
MediaItemIds.push(oldDbIdMap.podcastEpisodes[itemObj.episodeId])
|
||||
}
|
||||
} else if (oldDbIdMap.books[itemObj.libraryItemId]) {
|
||||
MediaItemIds.push(oldDbIdMap.books[itemObj.libraryItemId])
|
||||
}
|
||||
})
|
||||
if (!MediaItemIds.length) {
|
||||
Logger.warn(`[dbMigration] migratePlaylists: Playlist "${oldPlaylist.name}" has no items`)
|
||||
continue
|
||||
}
|
||||
|
||||
const Playlist = {
|
||||
id: uuidv4(),
|
||||
name: oldPlaylist.name,
|
||||
description: oldPlaylist.description,
|
||||
createdAt: oldPlaylist.createdAt,
|
||||
updatedAt: oldPlaylist.lastUpdate,
|
||||
userId,
|
||||
libraryId
|
||||
}
|
||||
newRecords.playlist.push(Playlist)
|
||||
|
||||
let order = 1
|
||||
MediaItemIds.forEach((mediaItemId) => {
|
||||
const PlaylistMediaItem = {
|
||||
id: uuidv4(),
|
||||
mediaItemId,
|
||||
mediaItemType,
|
||||
createdAt: Playlist.createdAt,
|
||||
playlistId: Playlist.id,
|
||||
order: order++
|
||||
}
|
||||
newRecords.playlistMediaItem.push(PlaylistMediaItem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function migrateFeeds(oldFeeds) {
|
||||
for (const oldFeed of oldFeeds) {
|
||||
if (!oldFeed.episodes?.length) {
|
||||
continue
|
||||
}
|
||||
|
||||
let entityId = null
|
||||
|
||||
if (oldFeed.entityType === 'collection') {
|
||||
entityId = oldDbIdMap.collections[oldFeed.entityId]
|
||||
} else if (oldFeed.entityType === 'libraryItem') {
|
||||
entityId = oldDbIdMap.libraryItems[oldFeed.entityId]
|
||||
} else if (oldFeed.entityType === 'series') {
|
||||
entityId = oldDbIdMap.series[oldFeed.entityId]
|
||||
}
|
||||
|
||||
if (!entityId) {
|
||||
Logger.warn(`[dbMigration] migrateFeeds: Entity not found for feed "${oldFeed.entityType}" (id:${oldFeed.entityId})`)
|
||||
continue
|
||||
}
|
||||
|
||||
const userId = oldDbIdMap.users[oldFeed.userId]
|
||||
if (!userId) {
|
||||
Logger.warn(`[dbMigration] migrateFeeds: User not found for feed (id:${oldFeed.userId})`)
|
||||
continue
|
||||
}
|
||||
|
||||
const oldFeedMeta = oldFeed.meta
|
||||
|
||||
const Feed = {
|
||||
id: uuidv4(),
|
||||
slug: oldFeed.slug,
|
||||
entityType: oldFeed.entityType,
|
||||
entityId,
|
||||
entityUpdatedAt: oldFeed.entityUpdatedAt,
|
||||
serverAddress: oldFeed.serverAddress,
|
||||
feedURL: oldFeed.feedUrl,
|
||||
imageURL: oldFeedMeta.imageUrl,
|
||||
siteURL: oldFeedMeta.link,
|
||||
title: oldFeedMeta.title,
|
||||
description: oldFeedMeta.description,
|
||||
author: oldFeedMeta.author,
|
||||
podcastType: oldFeedMeta.type || null,
|
||||
language: oldFeedMeta.language || null,
|
||||
ownerName: oldFeedMeta.ownerName || null,
|
||||
ownerEmail: oldFeedMeta.ownerEmail || null,
|
||||
explicit: !!oldFeedMeta.explicit,
|
||||
preventIndexing: !!oldFeedMeta.preventIndexing,
|
||||
createdAt: oldFeed.createdAt,
|
||||
updatedAt: oldFeed.updatedAt,
|
||||
userId
|
||||
}
|
||||
newRecords.feed.push(Feed)
|
||||
|
||||
//
|
||||
// Migrate FeedEpisodes
|
||||
//
|
||||
for (const oldFeedEpisode of oldFeed.episodes) {
|
||||
const FeedEpisode = {
|
||||
id: uuidv4(),
|
||||
title: oldFeedEpisode.title,
|
||||
author: oldFeedEpisode.author,
|
||||
description: oldFeedEpisode.description,
|
||||
siteURL: oldFeedEpisode.link,
|
||||
enclosureURL: oldFeedEpisode.enclosure?.url || null,
|
||||
enclosureType: oldFeedEpisode.enclosure?.type || null,
|
||||
enclosureSize: oldFeedEpisode.enclosure?.size || null,
|
||||
pubDate: oldFeedEpisode.pubDate,
|
||||
season: oldFeedEpisode.season || null,
|
||||
episode: oldFeedEpisode.episode || null,
|
||||
episodeType: oldFeedEpisode.episodeType || null,
|
||||
duration: oldFeedEpisode.duration,
|
||||
filePath: oldFeedEpisode.fullPath,
|
||||
explicit: !!oldFeedEpisode.explicit,
|
||||
createdAt: oldFeed.createdAt,
|
||||
updatedAt: oldFeed.updatedAt,
|
||||
feedId: Feed.id
|
||||
}
|
||||
newRecords.feedEpisode.push(FeedEpisode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateSettings(oldSettings) {
|
||||
const serverSettings = oldSettings.find(s => s.id === 'server-settings')
|
||||
const notificationSettings = oldSettings.find(s => s.id === 'notification-settings')
|
||||
const emailSettings = oldSettings.find(s => s.id === 'email-settings')
|
||||
|
||||
if (serverSettings) {
|
||||
newRecords.setting.push({
|
||||
key: 'server-settings',
|
||||
value: serverSettings
|
||||
})
|
||||
}
|
||||
|
||||
if (notificationSettings) {
|
||||
newRecords.setting.push({
|
||||
key: 'notification-settings',
|
||||
value: notificationSettings
|
||||
})
|
||||
}
|
||||
|
||||
if (emailSettings) {
|
||||
newRecords.setting.push({
|
||||
key: 'email-settings',
|
||||
value: emailSettings
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.migrate = async (DatabaseModels) => {
|
||||
Logger.info(`[dbMigration] Starting migration`)
|
||||
|
||||
const data = await oldDbFiles.init()
|
||||
|
||||
const start = Date.now()
|
||||
migrateSettings(data.settings)
|
||||
migrateAuthors(data.authors)
|
||||
migrateSeries(data.series)
|
||||
migrateLibraries(data.libraries)
|
||||
migrateLibraryItems(data.libraryItems)
|
||||
migrateUsers(data.users)
|
||||
migrateSessions(data.sessions)
|
||||
migrateCollections(data.collections)
|
||||
migratePlaylists(data.playlists)
|
||||
migrateFeeds(data.feeds)
|
||||
|
||||
let totalRecords = 0
|
||||
for (const model in newRecords) {
|
||||
Logger.info(`[dbMigration] Inserting ${newRecords[model].length} ${model} rows`)
|
||||
if (newRecords[model].length) {
|
||||
await DatabaseModels[model].bulkCreate(newRecords[model])
|
||||
totalRecords += newRecords[model].length
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = Date.now() - start
|
||||
|
||||
// Purge author images and cover images from cache
|
||||
try {
|
||||
const CachePath = Path.join(global.MetadataPath, 'cache')
|
||||
await fs.emptyDir(Path.join(CachePath, 'covers'))
|
||||
await fs.emptyDir(Path.join(CachePath, 'images'))
|
||||
} catch (error) {
|
||||
Logger.error(`[dbMigration] Failed to purge author/cover image cache`, error)
|
||||
}
|
||||
|
||||
// Put all old db folders into a zipfile oldDb.zip
|
||||
await oldDbFiles.zipWrapOldDb()
|
||||
|
||||
Logger.info(`[dbMigration] Migration complete. ${totalRecords} rows. Elapsed ${(elapsed / 1000).toFixed(2)}s`)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} true if old database exists
|
||||
*/
|
||||
module.exports.checkShouldMigrate = async (force = false) => {
|
||||
if (await oldDbFiles.checkHasOldDb()) return true
|
||||
if (!force) return false
|
||||
return oldDbFiles.checkHasOldDbZip()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue