+
-
+
+
-
+
Invalid Cover
@@ -40,6 +55,7 @@ export default {
},
data() {
return {
+ loading: true,
imageFailed: false,
showCoverBg: false
}
@@ -115,6 +131,7 @@ export default {
},
hideCoverBg() {},
imageLoaded() {
+ this.loading = false
if (this.$refs.cover && this.cover !== this.placeholderUrl) {
var { naturalWidth, naturalHeight } = this.$refs.cover
var aspectRatio = naturalHeight / naturalWidth
@@ -130,10 +147,223 @@ export default {
}
},
imageError(err) {
+ this.loading = false
console.error('ImgError', err)
this.imageFailed = true
}
},
mounted() {}
}
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index 48d8ba1a..2c0aa241 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
- "version": "1.6.18",
+ "version": "1.6.19",
"description": "Audiobook manager and player",
"main": "index.js",
"scripts": {
diff --git a/package.json b/package.json
index 634c2c4e..2e2041e0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
- "version": "1.6.18",
+ "version": "1.6.19",
"description": "Self-hosted audiobook server for managing and playing audiobooks",
"main": "index.js",
"scripts": {
diff --git a/server/ApiController.js b/server/ApiController.js
index 8de1aefe..6b7fc4a1 100644
--- a/server/ApiController.js
+++ b/server/ApiController.js
@@ -4,7 +4,7 @@ const fs = require('fs-extra')
const date = require('date-and-time')
const Logger = require('./Logger')
-const { isObject } = require('./utils/index')
+const { isObject, getId } = require('./utils/index')
const audioFileScanner = require('./utils/audioFileScanner')
const BookFinder = require('./BookFinder')
@@ -702,7 +702,7 @@ class ApiController {
return res.status(500).send('Username already taken')
}
- account.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ account.id = getId('usr')
account.pash = await this.auth.hashPass(account.password)
delete account.password
account.token = await this.auth.generateAccessToken({ userId: account.id })
diff --git a/server/Db.js b/server/Db.js
index 4b5cc66b..e2c5f450 100644
--- a/server/Db.js
+++ b/server/Db.js
@@ -3,6 +3,7 @@ const njodb = require("njodb")
const fs = require('fs-extra')
const jwt = require('jsonwebtoken')
const Logger = require('./Logger')
+const { version } = require('../package.json')
const Audiobook = require('./objects/Audiobook')
const User = require('./objects/User')
const UserCollection = require('./objects/UserCollection')
@@ -36,6 +37,9 @@ class Db {
this.collections = []
this.serverSettings = null
+
+ // Stores previous version only if upgraded
+ this.previousVersion = null
}
getEntityDb(entityName) {
@@ -138,6 +142,11 @@ class Db {
var serverSettings = this.settings.find(s => s.id === 'server-settings')
if (serverSettings) {
this.serverSettings = new ServerSettings(serverSettings)
+
+ // Check if server was upgraded
+ if (!this.serverSettings.version || this.serverSettings.version !== version) {
+ this.previousVersion = this.serverSettings.version || '1.0.0'
+ }
}
}
})
@@ -146,6 +155,12 @@ class Db {
Logger.info(`[DB] ${this.collections.length} Collections Loaded`)
})
await Promise.all([p1, p2, p3, p4, p5])
+
+ // Update server version in server settings
+ if (this.previousVersion) {
+ this.serverSettings.version = version
+ await this.updateEntity('settings', this.serverSettings)
+ }
}
updateAudiobook(audiobook) {
diff --git a/server/DownloadManager.js b/server/DownloadManager.js
index 4ab790bb..bbfe1708 100644
--- a/server/DownloadManager.js
+++ b/server/DownloadManager.js
@@ -5,6 +5,7 @@ const archiver = require('archiver')
const workerThreads = require('worker_threads')
const Logger = require('./Logger')
const Download = require('./objects/Download')
+const { getId } = require('./utils/index')
const { writeConcatFile, writeMetadataFile } = require('./utils/ffmpegHelpers')
const { getFileSize } = require('./utils/fileUtils')
const TAG = 'DownloadManager'
@@ -61,7 +62,7 @@ class DownloadManager {
}
async prepareDownload(client, audiobook, options = {}) {
- var downloadId = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ var downloadId = getId('dl')
var dlpath = Path.join(this.downloadDirPath, downloadId)
Logger.info(`Start Download for ${audiobook.id} - DownloadId: ${downloadId} - ${dlpath}`)
diff --git a/server/Scanner.js b/server/Scanner.js
index d91ba19a..f83faffa 100644
--- a/server/Scanner.js
+++ b/server/Scanner.js
@@ -6,7 +6,7 @@ const Logger = require('./Logger')
const { version } = require('../package.json')
const audioFileScanner = require('./utils/audioFileScanner')
const { groupFilesIntoAudiobookPaths, getAudiobookFileData, scanRootDir } = require('./utils/scandir')
-const { comparePaths, getIno } = require('./utils/index')
+const { comparePaths, getIno, getId } = require('./utils/index')
const { secondsToTimestamp } = require('./utils/fileUtils')
const { ScanResult, CoverDestination } = require('./utils/constants')
@@ -752,5 +752,29 @@ class Scanner {
var result = await this.bookFinder.findCovers(query.provider, query.title, query.author || null, options)
res.json(result)
}
+
+ async fixDuplicateIds() {
+ var ids = {}
+ var audiobooksUpdated = 0
+ for (let i = 0; i < this.db.audiobooks.length; i++) {
+ var ab = this.db.audiobooks[i]
+ if (ids[ab.id]) {
+ var abCopy = new Audiobook(ab.toJSON())
+ abCopy.id = getId('ab')
+ if (abCopy.book.cover) {
+ abCopy.book.cover = abCopy.book.cover.replace(ab.id, abCopy.id)
+ }
+ Logger.warn('Found duplicate ID - updating from', ab.id, 'to', abCopy.id)
+ await this.db.removeEntity('audiobook', ab.id)
+ await this.db.insertEntity('audiobook', abCopy)
+ audiobooksUpdated++
+ } else {
+ ids[ab.id] = true
+ }
+ }
+ if (audiobooksUpdated) {
+ Logger.info(`[Scanner] Updated ${audiobooksUpdated} audiobook IDs`)
+ }
+ }
}
module.exports = Scanner
\ No newline at end of file
diff --git a/server/Server.js b/server/Server.js
index f7e31556..ad68efaa 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -118,6 +118,12 @@ class Server {
await this.backupManager.init()
await this.logManager.init()
+ // Only fix duplicate ids once on upgrade
+ if (this.db.previousVersion === '1.0.0') {
+ Logger.info(`[Server] Running scan for duplicate book IDs`)
+ await this.scanner.fixDuplicateIds()
+ }
+
this.watcher.initWatcher(this.libraries)
this.watcher.on('files', this.filesChanged.bind(this))
}
diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js
index fa1cc421..0cd099fd 100644
--- a/server/objects/Audiobook.js
+++ b/server/objects/Audiobook.js
@@ -1,7 +1,7 @@
const Path = require('path')
const fs = require('fs-extra')
const { bytesPretty, elapsedPretty, readTextFile } = require('../utils/fileUtils')
-const { comparePaths, getIno } = require('../utils/index')
+const { comparePaths, getIno, getId } = require('../utils/index')
const { parseOpfMetadataXML } = require('../utils/parseOpfMetadata')
const { extractCoverArt } = require('../utils/ffmpegHelpers')
const nfoGenerator = require('../utils/nfoGenerator')
@@ -317,7 +317,7 @@ class Audiobook {
}
setData(data) {
- this.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ this.id = getId('ab')
this.libraryId = data.libraryId || 'main'
this.folderId = data.folderId || 'audiobooks'
this.ino = data.ino || null
diff --git a/server/objects/Folder.js b/server/objects/Folder.js
index c4f02594..f06f9ed4 100644
--- a/server/objects/Folder.js
+++ b/server/objects/Folder.js
@@ -1,3 +1,5 @@
+const { getId } = require("../utils")
+
class Folder {
constructor(folder = null) {
this.id = null
@@ -27,7 +29,7 @@ class Folder {
}
setData(data) {
- this.id = data.id ? data.id : 'fol' + (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ this.id = data.id ? data.id : getId('fol')
this.fullPath = data.fullPath
this.libraryId = data.libraryId
this.addedAt = Date.now()
diff --git a/server/objects/Library.js b/server/objects/Library.js
index e14dfa5a..d89ba156 100644
--- a/server/objects/Library.js
+++ b/server/objects/Library.js
@@ -1,4 +1,5 @@
const Folder = require('./Folder')
+const { getId } = require('../utils/index')
class Library {
constructor(library = null) {
@@ -46,7 +47,7 @@ class Library {
}
setData(data) {
- this.id = data.id ? data.id : 'lib' + (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ this.id = data.id ? data.id : getId('lib')
this.name = data.name
if (data.folder) {
this.folders = [
diff --git a/server/objects/ServerSettings.js b/server/objects/ServerSettings.js
index 6b368dcd..2d1f5e60 100644
--- a/server/objects/ServerSettings.js
+++ b/server/objects/ServerSettings.js
@@ -32,6 +32,7 @@ class ServerSettings {
this.loggerScannerLogsToKeep = 2
this.logLevel = Logger.logLevel
+ this.version = null
if (settings) {
this.construct(settings)
@@ -56,6 +57,7 @@ class ServerSettings {
this.loggerScannerLogsToKeep = settings.loggerScannerLogsToKeep || 2
this.logLevel = settings.logLevel || Logger.logLevel
+ this.version = settings.version || null
if (this.logLevel !== Logger.logLevel) {
Logger.setLogLevel(this.logLevel)
@@ -78,7 +80,8 @@ class ServerSettings {
backupMetadataCovers: this.backupMetadataCovers,
loggerDailyLogsToKeep: this.loggerDailyLogsToKeep,
loggerScannerLogsToKeep: this.loggerScannerLogsToKeep,
- logLevel: this.logLevel
+ logLevel: this.logLevel,
+ version: this.version
}
}
diff --git a/server/objects/Stream.js b/server/objects/Stream.js
index b4a71583..aca60dcc 100644
--- a/server/objects/Stream.js
+++ b/server/objects/Stream.js
@@ -3,6 +3,7 @@ const EventEmitter = require('events')
const Path = require('path')
const fs = require('fs-extra')
const Logger = require('../Logger')
+const { getId } = require('../utils/index')
const { secondsToTimestamp } = require('../utils/fileUtils')
const { writeConcatFile } = require('../utils/ffmpegHelpers')
const hlsPlaylistGenerator = require('../utils/hlsPlaylistGenerator')
@@ -13,7 +14,7 @@ class Stream extends EventEmitter {
constructor(streamPath, client, audiobook, transcodeOptions = {}) {
super()
- this.id = (Date.now() + Math.trunc(Math.random() * 1000)).toString(36)
+ this.id = getId('str')
this.client = client
this.audiobook = audiobook
diff --git a/server/objects/UserCollection.js b/server/objects/UserCollection.js
index 9c87fd28..be761d85 100644
--- a/server/objects/UserCollection.js
+++ b/server/objects/UserCollection.js
@@ -1,4 +1,5 @@
const Logger = require('../Logger')
+const { getId } = require('../utils/index')
class UserCollection {
constructor(collection) {
@@ -62,7 +63,7 @@ class UserCollection {
if (!data.userId || !data.libraryId || !data.name) {
return false
}
- this.id = (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ this.id = getId('usr')
this.userId = data.userId
this.libraryId = data.libraryId
this.name = data.name
diff --git a/server/objects/UserListeningSession.js b/server/objects/UserListeningSession.js
index ecd66edd..35985e67 100644
--- a/server/objects/UserListeningSession.js
+++ b/server/objects/UserListeningSession.js
@@ -1,5 +1,6 @@
const Logger = require('../Logger')
const date = require('date-and-time')
+const { getId } = require('../utils/index')
class UserListeningSession {
constructor(session) {
@@ -58,7 +59,7 @@ class UserListeningSession {
}
setData(audiobook, user) {
- this.id = 'ls_' + (Math.trunc(Math.random() * 1000) + Date.now()).toString(36)
+ this.id = getId('ls')
this.userId = user.id
this.audiobookId = audiobook.id
this.audiobookTitle = audiobook.title || ''
diff --git a/server/utils/index.js b/server/utils/index.js
index d693e0f0..bdb10e69 100644
--- a/server/utils/index.js
+++ b/server/utils/index.js
@@ -58,3 +58,9 @@ const xmlToJSON = (xml) => {
})
}
module.exports.xmlToJSON = xmlToJSON
+
+module.exports.getId = (prepend = '') => {
+ var _id = Math.random().toString(36).substring(2, 8) + Math.random().toString(36).substring(2, 8) + Math.random().toString(36).substring(2, 8)
+ if (prepend) return prepend + '_' + _id
+ return _id
+}
\ No newline at end of file