diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue
index 233c4db2..c59bbcf8 100644
--- a/client/components/tables/BackupsTable.vue
+++ b/client/components/tables/BackupsTable.vue
@@ -23,7 +23,7 @@
Important Notice!
Applying a backup will overwrite users, user progress, book details, settings, and covers stored in metadata with the backed up data.
-
Backups do not modify any files in your library folders, only data in the audiobookshelf created /config and /metadata directories.
+
Backups do not modify any files in your library folders, only data in the audiobookshelf created /config and /metadata directories. If you have enabled server settings to store cover art and metadata in your library folders then those are not backup up or overwritten.
All clients using your server will be automatically refreshed.
Are you sure you want to apply the backup created on {{ selectedBackup.datePretty }}?
@@ -77,14 +77,24 @@ export default {
methods: {
confirm() {
this.showConfirmApply = false
- this.$root.socket.once('apply_backup_complete', this.applyBackupComplete)
- this.$root.socket.emit('apply_backup', this.selectedBackup.id)
+
+ this.$axios
+ .$get(`/api/backups/${this.selectedBackup.id}/apply`)
+ .then(() => {
+ this.isBackingUp = false
+ location.replace('/config/backups?backup=1')
+ })
+ .catch((error) => {
+ this.isBackingUp = false
+ console.error('Failed', error)
+ this.$toast.error('Failed to apply backup')
+ })
},
deleteBackupClick(backup) {
if (confirm(`Are you sure you want to delete backup for ${backup.datePretty}?`)) {
this.processing = true
this.$axios
- .$delete(`/api/backup/${backup.id}`)
+ .$delete(`/api/backups/${backup.id}`)
.then((backups) => {
console.log('Backup deleted', backups)
this.$store.commit('setBackups', backups)
@@ -98,29 +108,24 @@ export default {
})
}
},
- applyBackupComplete(success) {
- if (success) {
- // this.$toast.success('Backup Applied, refresh the page')
- location.replace('/config/backups?backup=1')
- } else {
- this.$toast.error('Failed to apply backup')
- }
- },
applyBackup(backup) {
this.selectedBackup = backup
this.showConfirmApply = true
},
- backupComplete(backups) {
- this.isBackingUp = false
- if (backups) {
- this.$toast.success('Backup Successful')
- this.$store.commit('setBackups', backups)
- } else this.$toast.error('Backup Failed')
- },
clickCreateBackup() {
this.isBackingUp = true
- this.$root.socket.once('backup_complete', this.backupComplete)
- this.$root.socket.emit('create_backup')
+ this.$axios
+ .$post('/api/backups')
+ .then((backups) => {
+ this.isBackingUp = false
+ this.$toast.success('Backup Successful')
+ this.$store.commit('setBackups', backups)
+ })
+ .catch((error) => {
+ this.isBackingUp = false
+ console.error('Failed', error)
+ this.$toast.error('Backup Failed')
+ })
},
backupUploaded(file) {
var form = new FormData()
@@ -129,7 +134,7 @@ export default {
this.processing = true
this.$axios
- .$post('/api/backup/upload', form)
+ .$post('/api/backups/upload', form)
.then((result) => {
console.log('Upload backup result', result)
this.$store.commit('setBackups', result)
diff --git a/client/pages/config/stats.vue b/client/pages/config/stats.vue
index d39a2e79..91b672da 100644
--- a/client/pages/config/stats.vue
+++ b/client/pages/config/stats.vue
@@ -40,8 +40,8 @@
{{ index + 1 }}.
-
{{ item.mediaMetadata.title }}
-
{{ $dateDistanceFromNow(item.lastUpdate) }}
+
{{ item.mediaMetadata ? item.mediaMetadata.title : '' }}
+
{{ $dateDistanceFromNow(item.updatedAt) }}
diff --git a/server/BackupManager.js b/server/BackupManager.js
index 96f85757..29f27f13 100644
--- a/server/BackupManager.js
+++ b/server/BackupManager.js
@@ -13,11 +13,12 @@ const Logger = require('./Logger')
const Backup = require('./objects/Backup')
class BackupManager {
- constructor(db) {
+ constructor(db, emitter) {
this.BackupPath = Path.join(global.MetadataPath, 'backups')
this.MetadataBooksPath = Path.join(global.MetadataPath, 'books')
this.db = db
+ this.emitter = emitter
this.scheduleTask = null
@@ -104,59 +105,20 @@ class BackupManager {
return res.json(this.backups.map(b => b.toJSON()))
}
- async requestCreateBackup(socket) {
- // Only Root User allowed
- var client = socket.sheepClient
- if (!client || !client.user) {
- Logger.error(`[BackupManager] Invalid user attempting to create backup`)
- socket.emit('backup_complete', false)
- return
- } else if (!client.user.isRoot) {
- Logger.error(`[BackupManager] Non-Root user attempting to create backup`)
- socket.emit('backup_complete', false)
- return
- }
-
+ async requestCreateBackup(res) {
var backupSuccess = await this.runBackup()
- socket.emit('backup_complete', backupSuccess ? this.backups.map(b => b.toJSON()) : false)
+ if (backupSuccess) res.json(this.backups.map(b => b.toJSON()))
+ else res.sendStatus(500)
}
- async requestApplyBackup(socket, id) {
- // Only Root User allowed
- var client = socket.sheepClient
- if (!client || !client.user) {
- Logger.error(`[BackupManager] Invalid user attempting to create backup`)
- socket.emit('apply_backup_complete', false)
- return
- } else if (!client.user.isRoot) {
- Logger.error(`[BackupManager] Non-Root user attempting to create backup`)
- socket.emit('apply_backup_complete', false)
- return
- }
-
- var backup = this.backups.find(b => b.id === id)
- if (!backup) {
- socket.emit('apply_backup_complete', false)
- return
- }
+ async requestApplyBackup(backup) {
const zip = new StreamZip.async({ file: backup.fullPath })
await zip.extract('config/', global.ConfigPath)
if (backup.backupMetadataCovers) {
await zip.extract('metadata-books/', this.MetadataBooksPath)
}
await this.db.reinit()
- socket.emit('apply_backup_complete', true)
- socket.broadcast.emit('backup_applied')
- }
-
- async setLastBackup() {
- this.backups.sort((a, b) => b.createdAt - a.createdAt)
- var lastBackup = this.backups.shift()
-
- const zip = new StreamZip.async({ file: lastBackup.fullPath })
- await zip.extract('config/', global.ConfigPath)
- console.log('Set Last Backup')
- await this.db.reinit()
+ this.emitter('backup_applied')
}
async loadBackups() {
@@ -179,7 +141,6 @@ class BackupManager {
this.backups.push(backup)
}
-
Logger.debug(`[BackupManager] Backup found "${backup.id}"`)
zip.close()
}
@@ -304,12 +265,14 @@ class BackupManager {
// pipe archive data to the file
archive.pipe(output)
- archive.directory(this.db.AudiobooksPath, 'config/audiobooks')
- archive.directory(this.db.LibrariesPath, 'config/libraries')
- archive.directory(this.db.SettingsPath, 'config/settings')
+ archive.directory(this.db.LibraryItemsPath, 'config/libraryItems')
archive.directory(this.db.UsersPath, 'config/users')
archive.directory(this.db.SessionsPath, 'config/sessions')
+ archive.directory(this.db.LibrariesPath, 'config/libraries')
+ archive.directory(this.db.SettingsPath, 'config/settings')
archive.directory(this.db.CollectionsPath, 'config/collections')
+ archive.directory(this.db.AuthorsPath, 'config/authors')
+ archive.directory(this.db.SeriesPath, 'config/series')
if (metadataBooksPath) {
Logger.debug(`[BackupManager] Backing up Metadata Books "${metadataBooksPath}"`)
diff --git a/server/Server.js b/server/Server.js
index 25304e51..6f83c520 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -49,7 +49,7 @@ class Server {
this.db = new Db()
this.auth = new Auth(this.db)
- this.backupManager = new BackupManager(this.db)
+ this.backupManager = new BackupManager(this.db, this.emitter.bind(this))
this.logManager = new LogManager(this.db)
this.cacheManager = new CacheManager()
this.watcher = new Watcher()
@@ -158,9 +158,11 @@ class Server {
const distPath = Path.join(global.appRoot, '/client/dist')
app.use(express.static(distPath))
- // TODO: Are these necessary?
+
// Metadata folder static path
- // app.use('/metadata', this.authMiddleware.bind(this), express.static(global.MetadataPath))
+ app.use('/metadata', this.authMiddleware.bind(this), express.static(global.MetadataPath))
+
+ // TODO: Are these necessary?
// Downloads folder static path
// app.use('/downloads', this.authMiddleware.bind(this), express.static(this.downloadManager.downloadDirPath))
// Static folder
@@ -222,9 +224,6 @@ class Server {
socket.on('auth', (token) => this.authenticateSocket(socket, token))
- // TODO: Most of these web socket listeners will be moved to API routes instead
- // with the goal of the web socket connection being a nice-to-have not need-to-have
-
// Scanning
socket.on('cancel_scan', this.cancelScan.bind(this))
socket.on('save_metadata', (libraryItemId) => this.saveMetadata(socket, libraryItemId))
@@ -237,10 +236,6 @@ class Server {
socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level))
socket.on('fetch_daily_logs', () => this.logManager.socketRequestDailyLogs(socket))
- // Backups
- socket.on('create_backup', () => this.backupManager.requestCreateBackup(socket))
- socket.on('apply_backup', (id) => this.backupManager.requestApplyBackup(socket, id))
-
socket.on('disconnect', () => {
Logger.removeSocketListener(socket.id)
diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js
index 12445c61..d2023add 100644
--- a/server/controllers/BackupController.js
+++ b/server/controllers/BackupController.js
@@ -3,6 +3,14 @@ const Logger = require('../Logger')
class BackupController {
constructor() { }
+ async create(req, res) {
+ if (!req.user.isRoot) {
+ Logger.error(`[BackupController] Non-Root user attempting to craete backup`, req.user)
+ return res.sendStatus(403)
+ }
+ this.backupManager.requestCreateBackup(res)
+ }
+
async delete(req, res) {
if (!req.user.isRoot) {
Logger.error(`[BackupController] Non-Root user attempting to delete backup`, req.user)
@@ -27,5 +35,18 @@ class BackupController {
}
this.backupManager.uploadBackup(req, res)
}
+
+ async apply(req, res) {
+ if (!req.user.isRoot) {
+ Logger.error(`[BackupController] Non-Root user attempting to apply backup`, req.user)
+ return res.sendStatus(403)
+ }
+ var backup = this.backupManager.backups.find(b => b.id === req.params.id)
+ if (!backup) {
+ return res.sendStatus(404)
+ }
+ await this.backupManager.requestApplyBackup(backup)
+ res.sendStatus(200)
+ }
}
module.exports = new BackupController()
\ No newline at end of file
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index d758f4bf..1a61ed22 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -142,8 +142,10 @@ class ApiRouter {
//
// Backup Routes
//
- this.router.delete('/backup/:id', BackupController.delete.bind(this))
- this.router.post('/backup/upload', BackupController.upload.bind(this))
+ this.router.post('/backups', BackupController.create.bind(this))
+ this.router.delete('/backups/:id', BackupController.delete.bind(this))
+ this.router.get('/backups/:id/apply', BackupController.apply.bind(this))
+ this.router.post('/backups/upload', BackupController.upload.bind(this))
//
// File System Routes
diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js
index ae05967c..8abde1d2 100644
--- a/server/utils/dbMigration.js
+++ b/server/utils/dbMigration.js
@@ -373,7 +373,7 @@ function cleanSessionObj(db, userListeningSession) {
bookMetadata.title = userListeningSession.audiobookTitle || ''
newPlaybackSession.mediaMetadata = bookMetadata
- return db.sessionsDb.update((record) => record.id === newPlaybackSession.id, () => newPlaybackSession).then((results) => true).catch((error) => {
+ return db.sessionsDb.update((record) => record.id === userListeningSession.id, () => newPlaybackSession).then((results) => true).catch((error) => {
Logger.error(`[dbMigration] Update Session Failed: ${error}`)
return false
})