diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue
index 233c4db26..c59bbcf8b 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 d39a2e798..91b672da1 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 96f85757d..29f27f13c 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 25304e510..6f83c520c 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 12445c614..d2023add3 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 d758f4bf0..1a61ed22b 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 ae05967c2..8abde1d2e 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
})