mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-01 05:29:41 +00:00
Fix library consolidation filter and implement podcast support
This commit is contained in:
parent
b3cdd880e1
commit
23034e6672
12 changed files with 153 additions and 14 deletions
|
|
@ -258,6 +258,11 @@ export default {
|
|||
sublist: false
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
text: 'Consolidated',
|
||||
value: 'consolidated',
|
||||
sublist: true
|
||||
})
|
||||
return items
|
||||
},
|
||||
podcastItems() {
|
||||
|
|
@ -303,7 +308,11 @@ export default {
|
|||
sublist: false
|
||||
})
|
||||
}
|
||||
|
||||
items.push({
|
||||
text: 'Consolidated',
|
||||
value: 'consolidated',
|
||||
sublist: true
|
||||
})
|
||||
return items
|
||||
},
|
||||
selectItems() {
|
||||
|
|
@ -350,6 +359,9 @@ export default {
|
|||
} else if (parts[0] === 'missing') {
|
||||
const item = this.missing.find((m) => m.id == decoded)
|
||||
if (item) filterValue = item.name
|
||||
} else if (parts[0] === 'consolidated') {
|
||||
const item = this.consolidated.find((c) => c.id == decoded)
|
||||
if (item) filterValue = item.name
|
||||
} else {
|
||||
filterValue = decoded
|
||||
}
|
||||
|
|
@ -504,6 +516,18 @@ export default {
|
|||
}
|
||||
]
|
||||
},
|
||||
consolidated() {
|
||||
return [
|
||||
{
|
||||
id: 'consolidated',
|
||||
name: 'Consolidated'
|
||||
},
|
||||
{
|
||||
id: 'not-consolidated',
|
||||
name: 'Not Consolidated'
|
||||
}
|
||||
]
|
||||
},
|
||||
sublistItems() {
|
||||
const sublistItems = (this[this.sublist] || []).map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ components:
|
|||
isInvalid:
|
||||
description: Whether the library item was scanned and no longer has media files.
|
||||
type: boolean
|
||||
isNotConsolidated:
|
||||
description: Whether the library item folder name does not match the "Author - Title" format.
|
||||
type: boolean
|
||||
mediaType:
|
||||
$ref: './mediaTypes/media.yaml#/components/schemas/mediaType'
|
||||
libraryItemMinified:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.32.1",
|
||||
"version": "2.32.7",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast server",
|
||||
"main": "index.js",
|
||||
|
|
|
|||
|
|
@ -401,6 +401,7 @@ class Database {
|
|||
|
||||
// Update server settings with version/build
|
||||
let updateServerSettings = false
|
||||
const oldVersion = this.serverSettings.version
|
||||
if (packageJson.version !== this.serverSettings.version) {
|
||||
Logger.info(`[Database] Server upgrade detected from ${this.serverSettings.version} to ${packageJson.version}`)
|
||||
this.serverSettings.version = packageJson.version
|
||||
|
|
@ -413,9 +414,48 @@ class Database {
|
|||
}
|
||||
if (updateServerSettings) {
|
||||
await this.updateServerSettings()
|
||||
|
||||
if (this.compareVersions(oldVersion, '2.32.7') < 0) {
|
||||
await this.populateIsNotConsolidated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async populateIsNotConsolidated() {
|
||||
Logger.info(`[Database] Populating isNotConsolidated flag for all items...`)
|
||||
const items = await this.models.libraryItem.findAll({
|
||||
include: [
|
||||
{
|
||||
model: this.models.book,
|
||||
include: {
|
||||
model: this.models.author,
|
||||
through: {
|
||||
attributes: ['createdAt']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
model: this.models.podcast
|
||||
}
|
||||
]
|
||||
})
|
||||
let count = 0
|
||||
let updatedCount = 0
|
||||
for (const item of items) {
|
||||
count++
|
||||
// LibraryItem.js hook afterFind sets item.media
|
||||
const isNotConsolidated = item.checkIsNotConsolidated()
|
||||
|
||||
if (item.isNotConsolidated !== isNotConsolidated) {
|
||||
Logger.debug(`[Database] Updating isNotConsolidated for ${item.relPath} to ${isNotConsolidated} (was ${item.isNotConsolidated})`)
|
||||
item.isNotConsolidated = isNotConsolidated
|
||||
await item.save()
|
||||
updatedCount++
|
||||
}
|
||||
}
|
||||
Logger.info(`[Database] Finished populating isNotConsolidated flag. Checked ${count} items, updated ${updatedCount}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create root user
|
||||
* @param {string} username
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ async function handleMoveLibraryItem(libraryItem, targetLibrary, targetFolder, n
|
|||
libraryItem.relPath = newRelPath
|
||||
libraryItem.isMissing = false
|
||||
libraryItem.isInvalid = false
|
||||
libraryItem.isNotConsolidated = libraryItem.checkIsNotConsolidated()
|
||||
libraryItem.changed('updatedAt', true)
|
||||
await libraryItem.save({ transaction })
|
||||
|
||||
|
|
|
|||
45
server/migrations/v2.32.2-add-is-not-consolidated-column.js
Normal file
45
server/migrations/v2.32.2-add-is-not-consolidated-column.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @typedef MigrationContext
|
||||
* @property {import('sequelize').QueryInterface} queryInterface - a suquelize QueryInterface object.
|
||||
* @property {import('../Logger')} logger - a Logger object.
|
||||
*
|
||||
* @typedef MigrationOptions
|
||||
* @property {MigrationContext} context - an object containing the migration context.
|
||||
*/
|
||||
|
||||
const migrationVersion = '2.32.2'
|
||||
const migrationName = `${migrationVersion}-add-is-not-consolidated-column`
|
||||
const loggerPrefix = `[${migrationVersion} migration]`
|
||||
|
||||
/**
|
||||
* Adds isNotConsolidated column to libraryItems table.
|
||||
*
|
||||
* @param {MigrationOptions} options
|
||||
*/
|
||||
async function up({ context: { queryInterface, logger } }) {
|
||||
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
|
||||
|
||||
const tableDescription = await queryInterface.describeTable('libraryItems')
|
||||
if (!tableDescription['isNotConsolidated']) {
|
||||
await queryInterface.addColumn('libraryItems', 'isNotConsolidated', {
|
||||
type: queryInterface.sequelize.Sequelize.BOOLEAN,
|
||||
defaultValue: false
|
||||
})
|
||||
logger.info(`${loggerPrefix} added column "isNotConsolidated" to table "libraryItems"`)
|
||||
} else {
|
||||
logger.info(`${loggerPrefix} column "isNotConsolidated" already exists in table "libraryItems"`)
|
||||
}
|
||||
|
||||
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
|
||||
}
|
||||
|
||||
async function down({ context: { queryInterface, logger } }) {
|
||||
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
|
||||
|
||||
await queryInterface.removeColumn('libraryItems', 'isNotConsolidated')
|
||||
logger.info(`${loggerPrefix} removed column "isNotConsolidated" from table "libraryItems"`)
|
||||
|
||||
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
|
||||
}
|
||||
|
||||
module.exports = { up, down }
|
||||
|
|
@ -688,7 +688,11 @@ class LibraryItem extends Model {
|
|||
title: DataTypes.STRING,
|
||||
titleIgnorePrefix: DataTypes.STRING,
|
||||
authorNamesFirstLast: DataTypes.STRING,
|
||||
authorNamesLastFirst: DataTypes.STRING
|
||||
authorNamesLastFirst: DataTypes.STRING,
|
||||
isNotConsolidated: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
|
|
@ -917,11 +921,17 @@ class LibraryItem extends Model {
|
|||
}
|
||||
|
||||
checkIsNotConsolidated() {
|
||||
if (this.isFile || this.mediaType !== 'book' || !this.media) return false
|
||||
const author = this.media.authors?.[0]?.name || 'Unknown Author'
|
||||
const title = this.media.title || 'Unknown Title'
|
||||
const folderName = sanitizeFilename(`${author} - ${title}`)
|
||||
return Path.basename(this.path) !== folderName
|
||||
if (this.mediaType !== 'book') return false
|
||||
if (this.isFile) return true
|
||||
const author = this.authorNamesFirstLast?.split(',')[0]?.trim() || 'Unknown Author'
|
||||
const title = this.title || 'Unknown Title'
|
||||
const folderName = this.checkIsNotConsolidated_FolderName(author, title)
|
||||
const currentFolderName = Path.basename(this.path.replace(/[\/\\]$/, ''))
|
||||
return currentFolderName !== folderName
|
||||
}
|
||||
|
||||
checkIsNotConsolidated_FolderName(author, title) {
|
||||
return sanitizeFilename(`${author} - ${title}`)
|
||||
}
|
||||
|
||||
toOldJSON() {
|
||||
|
|
@ -947,7 +957,7 @@ class LibraryItem extends Model {
|
|||
scanVersion: this.lastScanVersion,
|
||||
isMissing: !!this.isMissing,
|
||||
isInvalid: !!this.isInvalid,
|
||||
isNotConsolidated: this.checkIsNotConsolidated(),
|
||||
isNotConsolidated: !!this.isNotConsolidated,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toOldJSON(this.id),
|
||||
// LibraryFile JSON includes a fileType property that may not be saved in libraryFiles column in the database
|
||||
|
|
@ -976,7 +986,7 @@ class LibraryItem extends Model {
|
|||
updatedAt: this.updatedAt.valueOf(),
|
||||
isMissing: !!this.isMissing,
|
||||
isInvalid: !!this.isInvalid,
|
||||
isNotConsolidated: this.checkIsNotConsolidated(),
|
||||
isNotConsolidated: !!this.isNotConsolidated,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toOldJSONMinified(),
|
||||
numFiles: this.libraryFiles.length,
|
||||
|
|
@ -1003,7 +1013,7 @@ class LibraryItem extends Model {
|
|||
scanVersion: this.lastScanVersion,
|
||||
isMissing: !!this.isMissing,
|
||||
isInvalid: !!this.isInvalid,
|
||||
isNotConsolidated: this.checkIsNotConsolidated(),
|
||||
isNotConsolidated: !!this.isNotConsolidated,
|
||||
mediaType: this.mediaType,
|
||||
media: this.media.toOldJSONExpanded(this.id),
|
||||
// LibraryFile JSON includes a fileType property that may not be saved in libraryFiles column in the database
|
||||
|
|
|
|||
|
|
@ -382,8 +382,12 @@ class BookScanner {
|
|||
}
|
||||
|
||||
existingLibraryItem.media = media
|
||||
|
||||
let libraryItemUpdated = false
|
||||
const isNotConsolidated = existingLibraryItem.checkIsNotConsolidated()
|
||||
if (existingLibraryItem.isNotConsolidated !== isNotConsolidated) {
|
||||
existingLibraryItem.isNotConsolidated = isNotConsolidated
|
||||
libraryItemUpdated = true
|
||||
}
|
||||
|
||||
// Save Book changes to db
|
||||
if (hasMediaChanges) {
|
||||
|
|
@ -560,6 +564,7 @@ class BookScanner {
|
|||
}
|
||||
|
||||
libraryItemObj.book = bookObject
|
||||
libraryItemObj.isNotConsolidated = Database.libraryItemModel.prototype.checkIsNotConsolidated.call(libraryItemObj)
|
||||
const libraryItem = await Database.libraryItemModel.create(libraryItemObj, {
|
||||
include: {
|
||||
model: Database.bookModel,
|
||||
|
|
|
|||
|
|
@ -232,8 +232,12 @@ class PodcastScanner {
|
|||
}
|
||||
|
||||
existingLibraryItem.media = media
|
||||
|
||||
let libraryItemUpdated = false
|
||||
const isNotConsolidated = existingLibraryItem.checkIsNotConsolidated()
|
||||
if (existingLibraryItem.isNotConsolidated !== isNotConsolidated) {
|
||||
existingLibraryItem.isNotConsolidated = isNotConsolidated
|
||||
libraryItemUpdated = true
|
||||
}
|
||||
|
||||
// Save Podcast changes to db
|
||||
if (hasMediaChanges) {
|
||||
|
|
@ -337,6 +341,7 @@ class PodcastScanner {
|
|||
}
|
||||
|
||||
libraryItemObj.podcast = podcastObject
|
||||
libraryItemObj.isNotConsolidated = Database.libraryItemModel.prototype.checkIsNotConsolidated.call(libraryItemObj)
|
||||
const libraryItem = await Database.libraryItemModel.create(libraryItemObj, {
|
||||
include: {
|
||||
model: Database.podcastModel,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ module.exports = {
|
|||
let filterValue = null
|
||||
let filterGroup = null
|
||||
if (filterBy) {
|
||||
const searchGroups = ['genres', 'tags', 'series', 'authors', 'progress', 'narrators', 'publishers', 'publishedDecades', 'missing', 'languages', 'tracks', 'ebooks']
|
||||
const searchGroups = ['genres', 'tags', 'series', 'authors', 'progress', 'narrators', 'publishers', 'publishedDecades', 'missing', 'languages', 'tracks', 'ebooks', 'consolidated']
|
||||
const group = searchGroups.find((_group) => filterBy.startsWith(_group + '.'))
|
||||
filterGroup = group || filterBy
|
||||
filterValue = group ? this.decode(filterBy.replace(`${group}.`, '')) : null
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ module.exports = {
|
|||
mediaWhere = Sequelize.where(Sequelize.literal('CAST(publishedYear AS INTEGER)'), {
|
||||
[Sequelize.Op.between]: [startYear, endYear]
|
||||
})
|
||||
} else if (group === 'consolidated') {
|
||||
// This is handled in libraryItemWhere in getFilteredLibraryItems
|
||||
}
|
||||
|
||||
return { mediaWhere, replacements }
|
||||
|
|
@ -531,6 +533,8 @@ module.exports = {
|
|||
libraryItemWhere['createdAt'] = {
|
||||
[Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
|
||||
}
|
||||
} else if (filterGroup === 'consolidated') {
|
||||
libraryItemWhere['isNotConsolidated'] = filterValue === 'not-consolidated'
|
||||
}
|
||||
|
||||
// When sorting by progress but not filtering by progress, include media progresses
|
||||
|
|
|
|||
|
|
@ -172,6 +172,8 @@ module.exports = {
|
|||
libraryItemWhere['createdAt'] = {
|
||||
[Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
|
||||
}
|
||||
} else if (filterGroup === 'consolidated') {
|
||||
libraryItemWhere['isNotConsolidated'] = filterValue === 'not-consolidated'
|
||||
}
|
||||
|
||||
const podcastIncludes = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue