@@ -170,19 +170,16 @@ export default {
}
},
drop(evt) {
- console.log('Dropped event', evt)
this.isDragOver = false
this.preventDefaults(evt)
const files = [...evt.dataTransfer.files]
this.filesChanged(files)
},
dragover(evt) {
- console.log('Dragged over', evt)
this.isDragOver = true
this.preventDefaults(evt)
},
dragleave(evt) {
- console.log('Dragged leave', evt)
this.isDragOver = false
this.preventDefaults(evt)
},
@@ -195,7 +192,6 @@ export default {
e.stopPropagation()
},
filesChanged(files) {
- console.log('FilesChanged', files)
this.showUploader = false
for (let i = 0; i < files.length; i++) {
diff --git a/client/store/user.js b/client/store/user.js
index bf5db1e6..fce2943b 100644
--- a/client/store/user.js
+++ b/client/store/user.js
@@ -33,6 +33,9 @@ export const getters = {
},
getUserCanDownload: (state) => {
return state.user && state.user.permissions ? !!state.user.permissions.download : false
+ },
+ getUserCanUpload: (state) => {
+ return state.user && state.user.permissions ? !!state.user.permissions.upload : false
}
}
diff --git a/package.json b/package.json
index e1310697..baec0e15 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
- "version": "1.1.12",
+ "version": "1.1.13",
"description": "Self-hosted audiobook server for managing and playing audiobooks.",
"main": "index.js",
"scripts": {
diff --git a/readme.md b/readme.md
index 2e4d6926..f8daf3ee 100644
--- a/readme.md
+++ b/readme.md
@@ -9,21 +9,56 @@ Android app is in beta, try it out on the [Google Play Store](https://play.googl
-#### Folder Structures Supported:
+### Directory Structure
-```bash
-/Title/...
-/Author/Title/...
-/Author/Series/Title/...
+Author, Series, Volume Number, Title and Publish Year can all be parsed from your folder structure.
-Title can start with the publish year like so:
-/1989 - Book Title/...
+**Note**: Files in the root directory `/audiobooks` will be ignored, all audiobooks should be in a directory
-(Optional Setting) Subtitle can be seperated to its own field:
-/Book Title - With a Subtitle/...
-/1989 - Book Title - With a Subtitle/...
-will store "With a Subtitle" as the subtitle
-```
+**1 Folder:** `/Title/...`\
+**2 Folders:** `/Author/Title/...`\
+**3 Folders:** `/Author/Series/Title/...`
+
+\
+**Parsing publish year**
+
+`/1984 - Hackers/...`\
+Will save the publish year as `1984` and the title as `Hackers`
+
+\
+**Parsing volume number** (only if there is a series folder)
+
+`/Book 3 - Hackers/...`\
+Will save the volume number as `3` and the title as `Hackers`
+
+`Book` `Volume` `Vol` `Vol.` are all supported case insensitive
+
+These combinations will also work:\
+`/Hackers - Vol. 3/...`\
+`/1984 - Volume 3 - Hackers/...`\
+`/1984 - Hackers Book 3/...`
+
+\
+**Parsing book subtitles** (optional in settings)
+
+Title Folder: `/Hackers - Heroes of the Computer Revolution/...`
+
+Will save the title as `Hackers` and the subtitle as `Heroes of the Computer Revolution`
+
+\
+**Full example**
+
+`/Steven Levy/The Hacker Series/1984 - Hackers - Heroes of the Computer Revolution - Vol. 1/...`
+
+Becomes:\
+
+| Author | Steven Levy |
+|---------------|-----------------------------------|
+| Series | The Hacker Series |
+| Publish Year | 1984 |
+| Title | Hackers |
+| Subtitle | Heroes of the Computer Revolution |
+| Volume Number | 1 |
#### Features coming soon:
diff --git a/server/Server.js b/server/Server.js
index 335f1d07..b4701ce7 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -123,6 +123,51 @@ class Server {
this.auth.authMiddleware(req, res, next)
}
+ async handleUpload(req, res) {
+ if (!req.user.canUpload) {
+ Logger.warn('User attempted to upload without permission', req.user)
+ return res.sendStatus(403)
+ }
+ var files = Object.values(req.files)
+ var title = req.body.title
+ var author = req.body.author
+ var series = req.body.series
+
+ if (!files.length || !title || !author) {
+ return res.json({
+ error: 'Invalid post data received'
+ })
+ }
+
+ var outputDirectory = ''
+ if (series && series.length && series !== 'null') {
+ outputDirectory = Path.join(this.AudiobookPath, author, series, title)
+ } else {
+ outputDirectory = Path.join(this.AudiobookPath, author, title)
+ }
+
+ var exists = await fs.pathExists(outputDirectory)
+ if (exists) {
+ Logger.error(`[Server] Upload directory "${outputDirectory}" already exists`)
+ return res.json({
+ error: `Directory "${outputDirectory}" already exists`
+ })
+ }
+
+ await fs.ensureDir(outputDirectory)
+ Logger.info(`Uploading ${files.length} files to`, outputDirectory)
+
+ for (let i = 0; i < files.length; i++) {
+ var file = files[i]
+
+ var path = Path.join(outputDirectory, file.name)
+ await file.mv(path).catch((error) => {
+ Logger.error('Failed to move file', path, error)
+ })
+ }
+ res.sendStatus(200)
+ }
+
async start() {
Logger.info('=== Starting Server ===')
@@ -157,38 +202,7 @@ class Server {
// app.use('/hls', this.hlsController.router)
app.use('/feeds', this.rssFeeds.router)
- app.post('/upload', this.authMiddleware.bind(this), async (req, res) => {
- var files = Object.values(req.files)
- var title = req.body.title
- var author = req.body.author
- var series = req.body.series
-
- if (!files.length || !title || !author) {
- return res.json({
- error: 'Invalid post data received'
- })
- }
-
- var outputDirectory = ''
- if (series && series.length && series !== 'null') {
- outputDirectory = Path.join(this.AudiobookPath, author, series, title)
- } else {
- outputDirectory = Path.join(this.AudiobookPath, author, title)
- }
-
- await fs.ensureDir(outputDirectory)
- Logger.info(`Uploading ${files.length} files to`, outputDirectory)
-
- for (let i = 0; i < files.length; i++) {
- var file = files[i]
-
- var path = Path.join(outputDirectory, file.name)
- await file.mv(path).catch((error) => {
- Logger.error('Failed to move file', path, error)
- })
- }
- res.sendStatus(200)
- })
+ app.post('/upload', this.authMiddleware.bind(this), this.handleUpload.bind(this))
app.post('/login', (req, res) => this.auth.login(req, res))
app.post('/logout', this.logout.bind(this))
diff --git a/server/objects/User.js b/server/objects/User.js
index d8badfab..9d6243f6 100644
--- a/server/objects/User.js
+++ b/server/objects/User.js
@@ -32,6 +32,9 @@ class User {
get canDownload() {
return !!this.permissions.download && this.isActive
}
+ get canUpload() {
+ return !!this.permissions.upload && this.isActive
+ }
getDefaultUserSettings() {
return {
@@ -47,7 +50,8 @@ class User {
return {
download: true,
update: true,
- delete: this.id === 'root'
+ delete: this.type === 'root',
+ upload: this.type === 'root' || this.type === 'admin'
}
}
@@ -112,6 +116,8 @@ class User {
this.createdAt = user.createdAt || Date.now()
this.settings = user.settings || this.getDefaultUserSettings()
this.permissions = user.permissions || this.getDefaultUserPermissions()
+ // Upload permission added v1.1.13, make sure root user has upload permissions
+ if (this.type === 'root' && !this.permissions.upload) this.permissions.upload = true
}
update(payload) {
diff --git a/server/utils/scandir.js b/server/utils/scandir.js
index e7dea53d..5f6e0c6a 100644
--- a/server/utils/scandir.js
+++ b/server/utils/scandir.js
@@ -157,15 +157,6 @@ function getAudiobookDataFromDir(abRootPath, dir, parseSubtitle = false) {
if (splitDir.length > 0) author = splitDir.pop()
// There could be many more directories, but only the top 3 are used for naming /author/series/title/
- var publishYear = null
- // If Title is of format 1999 - Title, then use 1999 as publish year
- var publishYearMatch = title.match(/^([0-9]{4}) - (.+)/)
- if (publishYearMatch && publishYearMatch.length > 2) {
- if (!isNaN(publishYearMatch[1])) {
- publishYear = publishYearMatch[1]
- title = publishYearMatch[2]
- }
- }
// If in a series directory check for volume number match
/* ACCEPTS:
@@ -196,6 +187,18 @@ function getAudiobookDataFromDir(abRootPath, dir, parseSubtitle = false) {
}
}
+
+ var publishYear = null
+ // If Title is of format 1999 - Title, then use 1999 as publish year
+ var publishYearMatch = title.match(/^([0-9]{4}) - (.+)/)
+ if (publishYearMatch && publishYearMatch.length > 2) {
+ if (!isNaN(publishYearMatch[1])) {
+ publishYear = publishYearMatch[1]
+ title = publishYearMatch[2]
+ }
+ }
+
+
// Subtitle can be parsed from the title if user enabled
// Subtitle is everything after " - "
var subtitle = null