mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-06-23 15:10:31 -04:00
Fix:New authors not setting lastFirst column, updates for new Series model
This commit is contained in:
parent
7ff72a8920
commit
db86bfd63d
@ -447,21 +447,6 @@ class Database {
|
|||||||
return this.models.series.updateFromOld(oldSeries)
|
return this.models.series.updateFromOld(oldSeries)
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSeries(oldSeries) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.createFromOld(oldSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBulkSeries(oldSeriesObjs) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.createBulkFromOld(oldSeriesObjs)
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeSeries(seriesId) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.removeById(seriesId)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBulkBookAuthors(bookAuthors) {
|
async createBulkBookAuthors(bookAuthors) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
||||||
@ -678,7 +663,7 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
async getSeriesIdByName(libraryId, seriesName) {
|
async getSeriesIdByName(libraryId, seriesName) {
|
||||||
if (!this.libraryFilterData[libraryId]) {
|
if (!this.libraryFilterData[libraryId]) {
|
||||||
return (await this.seriesModel.getOldByNameAndLibrary(seriesName, libraryId))?.id || null
|
return (await this.seriesModel.getByNameAndLibrary(seriesName, libraryId))?.id || null
|
||||||
}
|
}
|
||||||
return this.libraryFilterData[libraryId].series.find((se) => se.name === seriesName)?.id || null
|
return this.libraryFilterData[libraryId].series.find((se) => se.name === seriesName)?.id || null
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,9 @@ class AuthorController {
|
|||||||
let hasUpdated = false
|
let hasUpdated = false
|
||||||
|
|
||||||
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
|
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
|
||||||
|
if (authorNameUpdate) {
|
||||||
|
payload.lastFirst = Database.authorModel.getLastFirst(payload.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if author name matches another author and merge the authors
|
// Check if author name matches another author and merge the authors
|
||||||
let existingAuthor = null
|
let existingAuthor = null
|
||||||
@ -169,6 +172,11 @@ class AuthorController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If lastFirst is not set, get it from the name
|
||||||
|
if (!authorNameUpdate && !req.author.lastFirst) {
|
||||||
|
payload.lastFirst = Database.authorModel.getLastFirst(req.author.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Regular author update
|
// Regular author update
|
||||||
req.author.set(payload)
|
req.author.set(payload)
|
||||||
if (req.author.changed()) {
|
if (req.author.changed()) {
|
||||||
|
@ -629,11 +629,10 @@ class LibraryController {
|
|||||||
|
|
||||||
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
const oldSeries = series.getOldSeries()
|
|
||||||
|
|
||||||
const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(oldSeries, req.user)
|
const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.user)
|
||||||
|
|
||||||
const seriesJson = oldSeries.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
if (include.includes('progress')) {
|
if (include.includes('progress')) {
|
||||||
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
|
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
|
||||||
seriesJson.progress = {
|
seriesJson.progress = {
|
||||||
|
@ -125,7 +125,7 @@ class RSSFeedController {
|
|||||||
async openRSSFeedForSeries(req, res) {
|
async openRSSFeedForSeries(req, res) {
|
||||||
const options = req.body || {}
|
const options = req.body || {}
|
||||||
|
|
||||||
const series = await Database.seriesModel.getOldById(req.params.seriesId)
|
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
|
|
||||||
// Check request body options exist
|
// Check request body options exist
|
||||||
@ -140,7 +140,7 @@ class RSSFeedController {
|
|||||||
return res.status(400).send('Slug already in use')
|
return res.status(400).send('Slug already in use')
|
||||||
}
|
}
|
||||||
|
|
||||||
const seriesJson = series.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
|
|
||||||
// Get books in series that have audio tracks
|
// Get books in series that have audio tracks
|
||||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||||
|
@ -9,6 +9,11 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
|
|||||||
* @property {import('../models/User')} user
|
* @property {import('../models/User')} user
|
||||||
*
|
*
|
||||||
* @typedef {Request & RequestUserObject} RequestWithUser
|
* @typedef {Request & RequestUserObject} RequestWithUser
|
||||||
|
*
|
||||||
|
* @typedef RequestEntityObject
|
||||||
|
* @property {import('../models/Series')} series
|
||||||
|
*
|
||||||
|
* @typedef {RequestWithUser & RequestEntityObject} SeriesControllerRequest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SeriesController {
|
class SeriesController {
|
||||||
@ -21,7 +26,7 @@ class SeriesController {
|
|||||||
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
||||||
* Series are not library specific so we need to know what the library id is
|
* Series are not library specific so we need to know what the library id is
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {SeriesControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async findOne(req, res) {
|
async findOne(req, res) {
|
||||||
@ -30,7 +35,7 @@ class SeriesController {
|
|||||||
.map((v) => v.trim())
|
.map((v) => v.trim())
|
||||||
.filter((v) => !!v)
|
.filter((v) => !!v)
|
||||||
|
|
||||||
const seriesJson = req.series.toJSON()
|
const seriesJson = req.series.toOldJSON()
|
||||||
|
|
||||||
// Add progress map with isFinished flag
|
// Add progress map with isFinished flag
|
||||||
if (include.includes('progress')) {
|
if (include.includes('progress')) {
|
||||||
@ -54,17 +59,19 @@ class SeriesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: Update to use new model
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {SeriesControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
const hasUpdated = req.series.update(req.body)
|
const oldSeries = req.series.getOldSeries()
|
||||||
|
const hasUpdated = oldSeries.update(req.body)
|
||||||
if (hasUpdated) {
|
if (hasUpdated) {
|
||||||
await Database.updateSeries(req.series)
|
await Database.updateSeries(oldSeries)
|
||||||
SocketAuthority.emitter('series_updated', req.series.toJSON())
|
SocketAuthority.emitter('series_updated', oldSeries.toJSON())
|
||||||
}
|
}
|
||||||
res.json(req.series.toJSON())
|
res.json(oldSeries.toJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,7 +81,7 @@ class SeriesController {
|
|||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async middleware(req, res, next) {
|
async middleware(req, res, next) {
|
||||||
const series = await Database.seriesModel.getOldById(req.params.id)
|
const series = await Database.seriesModel.findByPk(req.params.id)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,7 +25,7 @@ class RssFeedManager {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (feedObj.entityType === 'series') {
|
} else if (feedObj.entityType === 'series') {
|
||||||
const series = await Database.seriesModel.getOldById(feedObj.entityId)
|
const series = await Database.seriesModel.findByPk(feedObj.entityId)
|
||||||
if (!series) {
|
if (!series) {
|
||||||
Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found`)
|
Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found`)
|
||||||
return false
|
return false
|
||||||
@ -133,9 +133,9 @@ class RssFeedManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (feed.entityType === 'series') {
|
} else if (feed.entityType === 'series') {
|
||||||
const series = await Database.seriesModel.getOldById(feed.entityId)
|
const series = await Database.seriesModel.findByPk(feed.entityId)
|
||||||
if (series) {
|
if (series) {
|
||||||
const seriesJson = series.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
|
|
||||||
// Get books in series that have audio tracks
|
// Get books in series that have audio tracks
|
||||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||||
|
const parseNameString = require('../utils/parsers/parseNameString')
|
||||||
|
|
||||||
class Author extends Model {
|
class Author extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
@ -24,6 +25,16 @@ class Author extends Model {
|
|||||||
this.createdAt
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static getLastFirst(name) {
|
||||||
|
if (!name) return null
|
||||||
|
return parseNameString.nameToLastFirst(name)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if author exists
|
* Check if author exists
|
||||||
* @param {string} authorId
|
* @param {string} authorId
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||||
|
|
||||||
const oldSeries = require('../objects/entities/Series')
|
const oldSeries = require('../objects/entities/Series')
|
||||||
|
const { getTitlePrefixAtEnd } = require('../utils/index')
|
||||||
|
|
||||||
class Series extends Model {
|
class Series extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
@ -22,11 +23,6 @@ class Series extends Model {
|
|||||||
this.updatedAt
|
this.updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAllOldSeries() {
|
|
||||||
const series = await this.findAll()
|
|
||||||
return series.map((se) => se.getOldSeries())
|
|
||||||
}
|
|
||||||
|
|
||||||
getOldSeries() {
|
getOldSeries() {
|
||||||
return new oldSeries({
|
return new oldSeries({
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@ -47,16 +43,6 @@ class Series extends Model {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFromOld(oldSeries) {
|
|
||||||
const series = this.getFromOld(oldSeries)
|
|
||||||
return this.create(series)
|
|
||||||
}
|
|
||||||
|
|
||||||
static createBulkFromOld(oldSeriesObjs) {
|
|
||||||
const series = oldSeriesObjs.map(this.getFromOld)
|
|
||||||
return this.bulkCreate(series)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromOld(oldSeries) {
|
static getFromOld(oldSeries) {
|
||||||
return {
|
return {
|
||||||
id: oldSeries.id,
|
id: oldSeries.id,
|
||||||
@ -67,25 +53,6 @@ class Series extends Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeById(seriesId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: seriesId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get oldSeries by id
|
|
||||||
* @param {string} seriesId
|
|
||||||
* @returns {Promise<oldSeries>}
|
|
||||||
*/
|
|
||||||
static async getOldById(seriesId) {
|
|
||||||
const series = await this.findByPk(seriesId)
|
|
||||||
if (!series) return null
|
|
||||||
return series.getOldSeries()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if series exists
|
* Check if series exists
|
||||||
* @param {string} seriesId
|
* @param {string} seriesId
|
||||||
@ -96,15 +63,14 @@ class Series extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old series by name and libraryId. name case insensitive
|
* Get series by name and libraryId. name case insensitive
|
||||||
*
|
*
|
||||||
* @param {string} seriesName
|
* @param {string} seriesName
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @returns {Promise<oldSeries>}
|
* @returns {Promise<Series>}
|
||||||
*/
|
*/
|
||||||
static async getOldByNameAndLibrary(seriesName, libraryId) {
|
static async getByNameAndLibrary(seriesName, libraryId) {
|
||||||
const series = (
|
return this.findOne({
|
||||||
await this.findOne({
|
|
||||||
where: [
|
where: [
|
||||||
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
||||||
{
|
{
|
||||||
@ -112,8 +78,6 @@ class Series extends Model {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
)?.getOldSeries()
|
|
||||||
return series
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,6 +127,26 @@ class Series extends Model {
|
|||||||
})
|
})
|
||||||
Series.belongsTo(library)
|
Series.belongsTo(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toOldJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
nameIgnorePrefix: getTitlePrefixAtEnd(this.name),
|
||||||
|
description: this.description,
|
||||||
|
addedAt: this.createdAt.valueOf(),
|
||||||
|
updatedAt: this.updatedAt.valueOf(),
|
||||||
|
libraryId: this.libraryId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSONMinimal(sequence) {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Series
|
module.exports = Series
|
||||||
|
@ -33,7 +33,7 @@ const CustomMetadataProviderController = require('../controllers/CustomMetadataP
|
|||||||
const MiscController = require('../controllers/MiscController')
|
const MiscController = require('../controllers/MiscController')
|
||||||
const ShareController = require('../controllers/ShareController')
|
const ShareController = require('../controllers/ShareController')
|
||||||
|
|
||||||
const Series = require('../objects/entities/Series')
|
const { getTitleIgnorePrefix } = require('../utils/index')
|
||||||
|
|
||||||
class ApiRouter {
|
class ApiRouter {
|
||||||
constructor(Server) {
|
constructor(Server) {
|
||||||
@ -524,13 +524,15 @@ class ApiRouter {
|
|||||||
async removeEmptySeries(series) {
|
async removeEmptySeries(series) {
|
||||||
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
||||||
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
||||||
await Database.removeSeries(series.id)
|
|
||||||
// Remove series from library filter data
|
// Remove series from library filter data
|
||||||
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
||||||
SocketAuthority.emitter('series_removed', {
|
SocketAuthority.emitter('series_removed', {
|
||||||
id: series.id,
|
id: series.id,
|
||||||
libraryId: series.libraryId
|
libraryId: series.libraryId
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await series.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserListeningSessionsHelper(userId) {
|
async getUserListeningSessionsHelper(userId) {
|
||||||
@ -619,6 +621,7 @@ class ApiRouter {
|
|||||||
if (!author) {
|
if (!author) {
|
||||||
author = await Database.authorModel.create({
|
author = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId
|
libraryId
|
||||||
})
|
})
|
||||||
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
||||||
@ -663,11 +666,14 @@ class ApiRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mediaMetadata.series[i].id) {
|
if (!mediaMetadata.series[i].id) {
|
||||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesName, libraryId)
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesName, libraryId)
|
||||||
if (!seriesItem) {
|
if (!seriesItem) {
|
||||||
seriesItem = new Series()
|
seriesItem = await Database.seriesModel.create({
|
||||||
seriesItem.setData(mediaMetadata.series[i], libraryId)
|
name: seriesName,
|
||||||
Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`)
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesName),
|
||||||
|
libraryId
|
||||||
|
})
|
||||||
|
Logger.debug(`[ApiRouter] Creating new series "${seriesItem.name}"`)
|
||||||
newSeries.push(seriesItem)
|
newSeries.push(seriesItem)
|
||||||
// Update filter data
|
// Update filter data
|
||||||
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
||||||
@ -680,10 +686,9 @@ class ApiRouter {
|
|||||||
// Remove series without an id
|
// Remove series without an id
|
||||||
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
||||||
if (newSeries.length) {
|
if (newSeries.length) {
|
||||||
await Database.createBulkSeries(newSeries)
|
|
||||||
SocketAuthority.emitter(
|
SocketAuthority.emitter(
|
||||||
'multiple_series_added',
|
'multiple_series_added',
|
||||||
newSeries.map((se) => se.toJSON())
|
newSeries.map((se) => se.toOldJSON())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const sequelize = require('sequelize')
|
const sequelize = require('sequelize')
|
||||||
const { LogLevel } = require('../utils/constants')
|
const { LogLevel } = require('../utils/constants')
|
||||||
@ -13,14 +13,14 @@ const AudioFile = require('../objects/files/AudioFile')
|
|||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const LibraryFile = require('../objects/files/LibraryFile')
|
const LibraryFile = require('../objects/files/LibraryFile')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const fsExtra = require("../libs/fsExtra")
|
const fsExtra = require('../libs/fsExtra')
|
||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
|
|
||||||
const LibraryScan = require("./LibraryScan")
|
const LibraryScan = require('./LibraryScan')
|
||||||
const OpfFileScanner = require('./OpfFileScanner')
|
const OpfFileScanner = require('./OpfFileScanner')
|
||||||
const NfoFileScanner = require('./NfoFileScanner')
|
const NfoFileScanner = require('./NfoFileScanner')
|
||||||
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
||||||
const EBookFile = require("../objects/files/EBookFile")
|
const EBookFile = require('../objects/files/EBookFile')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for books pulled from files
|
* Metadata for books pulled from files
|
||||||
@ -46,7 +46,7 @@ const EBookFile = require("../objects/files/EBookFile")
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class BookScanner {
|
class BookScanner {
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('../models/LibraryItem')} existingLibraryItem
|
* @param {import('../models/LibraryItem')} existingLibraryItem
|
||||||
@ -81,19 +81,23 @@ class BookScanner {
|
|||||||
let hasMediaChanges = libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== media.audioFiles.length
|
let hasMediaChanges = libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== media.audioFiles.length
|
||||||
if (hasMediaChanges) {
|
if (hasMediaChanges) {
|
||||||
// Filter out audio files that were removed
|
// Filter out audio files that were removed
|
||||||
media.audioFiles = media.audioFiles.filter(af => !libraryItemData.checkAudioFileRemoved(af))
|
media.audioFiles = media.audioFiles.filter((af) => !libraryItemData.checkAudioFileRemoved(af))
|
||||||
|
|
||||||
// Update audio files that were modified
|
// Update audio files that were modified
|
||||||
if (libraryItemData.audioLibraryFilesModified.length) {
|
if (libraryItemData.audioLibraryFilesModified.length) {
|
||||||
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified.map(lf => lf.new))
|
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(
|
||||||
|
existingLibraryItem.mediaType,
|
||||||
|
libraryItemData,
|
||||||
|
libraryItemData.audioLibraryFilesModified.map((lf) => lf.new)
|
||||||
|
)
|
||||||
media.audioFiles = media.audioFiles.map((audioFileObj) => {
|
media.audioFiles = media.audioFiles.map((audioFileObj) => {
|
||||||
let matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.metadata.path === audioFileObj.metadata.path)
|
let matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.metadata.path === audioFileObj.metadata.path)
|
||||||
if (!matchedScannedAudioFile) {
|
if (!matchedScannedAudioFile) {
|
||||||
matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.ino === audioFileObj.ino)
|
matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.ino === audioFileObj.ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedScannedAudioFile) {
|
if (matchedScannedAudioFile) {
|
||||||
scannedAudioFiles = scannedAudioFiles.filter(saf => saf !== matchedScannedAudioFile)
|
scannedAudioFiles = scannedAudioFiles.filter((saf) => saf !== matchedScannedAudioFile)
|
||||||
const audioFile = new AudioFile(audioFileObj)
|
const audioFile = new AudioFile(audioFileObj)
|
||||||
audioFile.updateFromScan(matchedScannedAudioFile)
|
audioFile.updateFromScan(matchedScannedAudioFile)
|
||||||
return audioFile.toJSON()
|
return audioFile.toJSON()
|
||||||
@ -115,7 +119,7 @@ class BookScanner {
|
|||||||
// Add audio library files that are not already set on the book (safety check)
|
// Add audio library files that are not already set on the book (safety check)
|
||||||
let audioLibraryFilesToAdd = []
|
let audioLibraryFilesToAdd = []
|
||||||
for (const audioLibraryFile of libraryItemData.audioLibraryFiles) {
|
for (const audioLibraryFile of libraryItemData.audioLibraryFiles) {
|
||||||
if (!media.audioFiles.some(af => af.ino === audioLibraryFile.ino)) {
|
if (!media.audioFiles.some((af) => af.ino === audioLibraryFile.ino)) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Existing audio library file "${audioLibraryFile.metadata.relPath}" was not set on book "${media.title}" so setting it now`)
|
libraryScan.addLog(LogLevel.DEBUG, `Existing audio library file "${audioLibraryFile.metadata.relPath}" was not set on book "${media.title}" so setting it now`)
|
||||||
|
|
||||||
audioLibraryFilesToAdd.push(audioLibraryFile)
|
audioLibraryFilesToAdd.push(audioLibraryFile)
|
||||||
@ -139,14 +143,14 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if cover was removed
|
// Check if cover was removed
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some(lf => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some((lf) => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) {
|
||||||
media.coverPath = null
|
media.coverPath = null
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cover if it was modified
|
// Update cover if it was modified
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
||||||
let coverMatch = libraryItemData.imageLibraryFilesModified.find(iFile => iFile.old.metadata.path === media.coverPath)
|
let coverMatch = libraryItemData.imageLibraryFilesModified.find((iFile) => iFile.old.metadata.path === media.coverPath)
|
||||||
if (coverMatch) {
|
if (coverMatch) {
|
||||||
const coverPath = coverMatch.new.metadata.path
|
const coverPath = coverMatch.new.metadata.path
|
||||||
if (coverPath !== media.coverPath) {
|
if (coverPath !== media.coverPath) {
|
||||||
@ -161,7 +165,7 @@ class BookScanner {
|
|||||||
// Check if cover is not set and image files were found
|
// Check if cover is not set and image files were found
|
||||||
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
||||||
// Prefer using a cover image with the name "cover" otherwise use the first image
|
// Prefer using a cover image with the name "cover" otherwise use the first image
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
@ -174,7 +178,7 @@ class BookScanner {
|
|||||||
|
|
||||||
// Update ebook if it was modified
|
// Update ebook if it was modified
|
||||||
if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) {
|
if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) {
|
||||||
let ebookMatch = libraryItemData.ebookLibraryFilesModified.find(eFile => eFile.old.metadata.path === media.ebookFile.metadata.path)
|
let ebookMatch = libraryItemData.ebookLibraryFilesModified.find((eFile) => eFile.old.metadata.path === media.ebookFile.metadata.path)
|
||||||
if (ebookMatch) {
|
if (ebookMatch) {
|
||||||
const ebookFile = new EBookFile(ebookMatch.new)
|
const ebookFile = new EBookFile(ebookMatch.new)
|
||||||
ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase()
|
ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase()
|
||||||
@ -188,7 +192,7 @@ class BookScanner {
|
|||||||
// Check if ebook is not set and ebooks were found
|
// Check if ebook is not set and ebooks were found
|
||||||
if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) {
|
if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) {
|
||||||
// Prefer to use an epub ebook then fallback to the first ebook found
|
// Prefer to use an epub ebook then fallback to the first ebook found
|
||||||
let ebookLibraryFile = libraryItemData.ebookLibraryFiles.find(lf => lf.metadata.ext.slice(1).toLowerCase() === 'epub')
|
let ebookLibraryFile = libraryItemData.ebookLibraryFiles.find((lf) => lf.metadata.ext.slice(1).toLowerCase() === 'epub')
|
||||||
if (!ebookLibraryFile) ebookLibraryFile = libraryItemData.ebookLibraryFiles[0]
|
if (!ebookLibraryFile) ebookLibraryFile = libraryItemData.ebookLibraryFiles[0]
|
||||||
ebookLibraryFile = ebookLibraryFile.toJSON()
|
ebookLibraryFile = ebookLibraryFile.toJSON()
|
||||||
// Ebook file is the same as library file except for additional `ebookFormat`
|
// Ebook file is the same as library file except for additional `ebookFormat`
|
||||||
@ -213,7 +217,7 @@ class BookScanner {
|
|||||||
if (key === 'authors') {
|
if (key === 'authors') {
|
||||||
// Check for authors added
|
// Check for authors added
|
||||||
for (const authorName of bookMetadata.authors) {
|
for (const authorName of bookMetadata.authors) {
|
||||||
if (!media.authors.some(au => au.name === authorName)) {
|
if (!media.authors.some((au) => au.name === authorName)) {
|
||||||
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
||||||
if (existingAuthorId) {
|
if (existingAuthorId) {
|
||||||
await Database.bookAuthorModel.create({
|
await Database.bookAuthorModel.create({
|
||||||
@ -225,7 +229,7 @@ class BookScanner {
|
|||||||
} else {
|
} else {
|
||||||
const newAuthor = await Database.authorModel.create({
|
const newAuthor = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
lastFirst: parseNameString.nameToLastFirst(authorName),
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId: libraryItemData.libraryId
|
libraryId: libraryItemData.libraryId
|
||||||
})
|
})
|
||||||
await media.addAuthor(newAuthor)
|
await media.addAuthor(newAuthor)
|
||||||
@ -247,7 +251,7 @@ class BookScanner {
|
|||||||
} else if (key === 'series') {
|
} else if (key === 'series') {
|
||||||
// Check for series added
|
// Check for series added
|
||||||
for (const seriesObj of bookMetadata.series) {
|
for (const seriesObj of bookMetadata.series) {
|
||||||
const existingBookSeries = media.series.find(se => se.name === seriesObj.name)
|
const existingBookSeries = media.series.find((se) => se.name === seriesObj.name)
|
||||||
if (!existingBookSeries) {
|
if (!existingBookSeries) {
|
||||||
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
|
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
|
||||||
if (existingSeriesId) {
|
if (existingSeriesId) {
|
||||||
@ -278,7 +282,7 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
// Check for series removed
|
// Check for series removed
|
||||||
for (const series of media.series) {
|
for (const series of media.series) {
|
||||||
if (!bookMetadata.series.some(se => se.name === series.name)) {
|
if (!bookMetadata.series.some((se) => se.name === series.name)) {
|
||||||
await series.bookSeries.destroy()
|
await series.bookSeries.destroy()
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
|
||||||
seriesUpdated = true
|
seriesUpdated = true
|
||||||
@ -287,21 +291,21 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
} else if (key === 'genres') {
|
} else if (key === 'genres') {
|
||||||
const existingGenres = media.genres || []
|
const existingGenres = media.genres || []
|
||||||
if (bookMetadata.genres.some(g => !existingGenres.includes(g)) || existingGenres.some(g => !bookMetadata.genres.includes(g))) {
|
if (bookMetadata.genres.some((g) => !existingGenres.includes(g)) || existingGenres.some((g) => !bookMetadata.genres.includes(g))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book genres "${existingGenres.join(',')}" => "${bookMetadata.genres.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book genres "${existingGenres.join(',')}" => "${bookMetadata.genres.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.genres = bookMetadata.genres
|
media.genres = bookMetadata.genres
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
} else if (key === 'tags') {
|
} else if (key === 'tags') {
|
||||||
const existingTags = media.tags || []
|
const existingTags = media.tags || []
|
||||||
if (bookMetadata.tags.some(t => !existingTags.includes(t)) || existingTags.some(t => !bookMetadata.tags.includes(t))) {
|
if (bookMetadata.tags.some((t) => !existingTags.includes(t)) || existingTags.some((t) => !bookMetadata.tags.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book tags "${existingTags.join(',')}" => "${bookMetadata.tags.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book tags "${existingTags.join(',')}" => "${bookMetadata.tags.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.tags = bookMetadata.tags
|
media.tags = bookMetadata.tags
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
} else if (key === 'narrators') {
|
} else if (key === 'narrators') {
|
||||||
const existingNarrators = media.narrators || []
|
const existingNarrators = media.narrators || []
|
||||||
if (bookMetadata.narrators.some(t => !existingNarrators.includes(t)) || existingNarrators.some(t => !bookMetadata.narrators.includes(t))) {
|
if (bookMetadata.narrators.some((t) => !existingNarrators.includes(t)) || existingNarrators.some((t) => !bookMetadata.narrators.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book narrators "${existingNarrators.join(',')}" => "${bookMetadata.narrators.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book narrators "${existingNarrators.join(',')}" => "${bookMetadata.narrators.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.narrators = bookMetadata.narrators
|
media.narrators = bookMetadata.narrators
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
@ -333,17 +337,13 @@ class BookScanner {
|
|||||||
if (authorsUpdated) {
|
if (authorsUpdated) {
|
||||||
media.authors = await media.getAuthors({
|
media.authors = await media.getAuthors({
|
||||||
joinTableAttributes: ['createdAt'],
|
joinTableAttributes: ['createdAt'],
|
||||||
order: [
|
order: [sequelize.literal(`bookAuthor.createdAt ASC`)]
|
||||||
sequelize.literal(`bookAuthor.createdAt ASC`)
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (seriesUpdated) {
|
if (seriesUpdated) {
|
||||||
media.series = await media.getSeries({
|
media.series = await media.getSeries({
|
||||||
joinTableAttributes: ['sequence', 'createdAt'],
|
joinTableAttributes: ['sequence', 'createdAt'],
|
||||||
order: [
|
order: [sequelize.literal(`bookSeries.createdAt ASC`)]
|
||||||
sequelize.literal(`bookSeries.createdAt ASC`)
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +367,10 @@ class BookScanner {
|
|||||||
|
|
||||||
// If no cover then search for cover if enabled in server settings
|
// If no cover then search for cover if enabled in server settings
|
||||||
if (!media.coverPath && Database.serverSettings.scannerFindCovers) {
|
if (!media.coverPath && Database.serverSettings.scannerFindCovers) {
|
||||||
const authorName = media.authors.map(au => au.name).filter(au => au).join(', ')
|
const authorName = media.authors
|
||||||
|
.map((au) => au.name)
|
||||||
|
.filter((au) => au)
|
||||||
|
.join(', ')
|
||||||
const coverPath = await this.searchForCover(existingLibraryItem.id, libraryItemDir, media.title, authorName, libraryScan)
|
const coverPath = await this.searchForCover(existingLibraryItem.id, libraryItemDir, media.title, authorName, libraryScan)
|
||||||
if (coverPath) {
|
if (coverPath) {
|
||||||
media.coverPath = coverPath
|
media.coverPath = coverPath
|
||||||
@ -440,7 +443,7 @@ class BookScanner {
|
|||||||
scannedAudioFiles = AudioFileScanner.runSmartTrackOrder(libraryItemData.relPath, scannedAudioFiles)
|
scannedAudioFiles = AudioFileScanner.runSmartTrackOrder(libraryItemData.relPath, scannedAudioFiles)
|
||||||
|
|
||||||
// Find ebook file (prefer epub)
|
// Find ebook file (prefer epub)
|
||||||
let ebookLibraryFile = librarySettings.audiobooksOnly ? null : libraryItemData.ebookLibraryFiles.find(lf => lf.metadata.ext.slice(1).toLowerCase() === 'epub') || libraryItemData.ebookLibraryFiles[0]
|
let ebookLibraryFile = librarySettings.audiobooksOnly ? null : libraryItemData.ebookLibraryFiles.find((lf) => lf.metadata.ext.slice(1).toLowerCase() === 'epub') || libraryItemData.ebookLibraryFiles[0]
|
||||||
|
|
||||||
// Do not add library items that have no valid audio files and no ebook file
|
// Do not add library items that have no valid audio files and no ebook file
|
||||||
if (!ebookLibraryFile && !scannedAudioFiles.length) {
|
if (!ebookLibraryFile && !scannedAudioFiles.length) {
|
||||||
@ -460,7 +463,7 @@ class BookScanner {
|
|||||||
bookMetadata.abridged = !!bookMetadata.abridged // Ensure boolean
|
bookMetadata.abridged = !!bookMetadata.abridged // Ensure boolean
|
||||||
|
|
||||||
let duration = 0
|
let duration = 0
|
||||||
scannedAudioFiles.forEach((af) => duration += (!isNaN(af.duration) ? Number(af.duration) : 0))
|
scannedAudioFiles.forEach((af) => (duration += !isNaN(af.duration) ? Number(af.duration) : 0))
|
||||||
const bookObject = {
|
const bookObject = {
|
||||||
...bookMetadata,
|
...bookMetadata,
|
||||||
audioFiles: scannedAudioFiles,
|
audioFiles: scannedAudioFiles,
|
||||||
@ -482,7 +485,7 @@ class BookScanner {
|
|||||||
author: {
|
author: {
|
||||||
libraryId: libraryItemData.libraryId,
|
libraryId: libraryItemData.libraryId,
|
||||||
name: authorName,
|
name: authorName,
|
||||||
lastFirst: parseNameString.nameToLastFirst(authorName)
|
lastFirst: Database.authorModel.getLastFirst(authorName)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -664,7 +667,7 @@ class BookScanner {
|
|||||||
|
|
||||||
// Set cover from library file if one is found otherwise check audiofile
|
// Set cover from library file if one is found otherwise check audiofile
|
||||||
if (libraryItemData.imageLibraryFiles.length) {
|
if (libraryItemData.imageLibraryFiles.length) {
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
bookMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
bookMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,7 +676,6 @@ class BookScanner {
|
|||||||
return bookMetadata
|
return bookMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static BookMetadataSourceHandler = class {
|
static BookMetadataSourceHandler = class {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -805,12 +807,12 @@ class BookScanner {
|
|||||||
|
|
||||||
const jsonObject = {
|
const jsonObject = {
|
||||||
tags: libraryItem.media.tags || [],
|
tags: libraryItem.media.tags || [],
|
||||||
chapters: libraryItem.media.chapters?.map(c => ({ ...c })) || [],
|
chapters: libraryItem.media.chapters?.map((c) => ({ ...c })) || [],
|
||||||
title: libraryItem.media.title,
|
title: libraryItem.media.title,
|
||||||
subtitle: libraryItem.media.subtitle,
|
subtitle: libraryItem.media.subtitle,
|
||||||
authors: libraryItem.media.authors.map(a => a.name),
|
authors: libraryItem.media.authors.map((a) => a.name),
|
||||||
narrators: libraryItem.media.narrators,
|
narrators: libraryItem.media.narrators,
|
||||||
series: libraryItem.media.series.map(se => {
|
series: libraryItem.media.series.map((se) => {
|
||||||
const sequence = se.bookSeries?.sequence || ''
|
const sequence = se.bookSeries?.sequence || ''
|
||||||
if (!sequence) return se.name
|
if (!sequence) return se.name
|
||||||
return `${se.name} #${sequence}`
|
return `${se.name} #${sequence}`
|
||||||
@ -826,9 +828,11 @@ class BookScanner {
|
|||||||
explicit: !!libraryItem.media.explicit,
|
explicit: !!libraryItem.media.explicit,
|
||||||
abridged: !!libraryItem.media.abridged
|
abridged: !!libraryItem.media.abridged
|
||||||
}
|
}
|
||||||
return fsExtra.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2)).then(async () => {
|
return fsExtra
|
||||||
|
.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
|
||||||
|
.then(async () => {
|
||||||
// Add metadata.json to libraryFiles array if it is new
|
// Add metadata.json to libraryFiles array if it is new
|
||||||
let metadataLibraryFile = libraryItem.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
let metadataLibraryFile = libraryItem.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||||
if (storeMetadataWithItem) {
|
if (storeMetadataWithItem) {
|
||||||
if (!metadataLibraryFile) {
|
if (!metadataLibraryFile) {
|
||||||
const newLibraryFile = new LibraryFile()
|
const newLibraryFile = new LibraryFile()
|
||||||
@ -849,7 +853,7 @@ class BookScanner {
|
|||||||
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
||||||
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
||||||
let size = 0
|
let size = 0
|
||||||
libraryItem.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
libraryItem.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
||||||
libraryItem.size = size
|
libraryItem.size = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -857,7 +861,8 @@ class BookScanner {
|
|||||||
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
@ -871,25 +876,27 @@ class BookScanner {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async checkAuthorsRemovedFromBooks(libraryId, scanLogger) {
|
async checkAuthorsRemovedFromBooks(libraryId, scanLogger) {
|
||||||
const bookAuthorsToRemove = (await Database.authorModel.findAll({
|
const bookAuthorsToRemove = (
|
||||||
|
await Database.authorModel.findAll({
|
||||||
where: [
|
where: [
|
||||||
{
|
{
|
||||||
id: scanLogger.authorsRemovedFromBooks,
|
id: scanLogger.authorsRemovedFromBooks,
|
||||||
asin: {
|
asin: {
|
||||||
[sequelize.Op.or]: [null, ""]
|
[sequelize.Op.or]: [null, '']
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
[sequelize.Op.or]: [null, ""]
|
[sequelize.Op.or]: [null, '']
|
||||||
},
|
},
|
||||||
imagePath: {
|
imagePath: {
|
||||||
[sequelize.Op.or]: [null, ""]
|
[sequelize.Op.or]: [null, '']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
||||||
],
|
],
|
||||||
attributes: ['id'],
|
attributes: ['id'],
|
||||||
raw: true
|
raw: true
|
||||||
})).map(au => au.id)
|
})
|
||||||
|
).map((au) => au.id)
|
||||||
if (bookAuthorsToRemove.length) {
|
if (bookAuthorsToRemove.length) {
|
||||||
await Database.authorModel.destroy({
|
await Database.authorModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
@ -912,7 +919,8 @@ class BookScanner {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async checkSeriesRemovedFromBooks(libraryId, scanLogger) {
|
async checkSeriesRemovedFromBooks(libraryId, scanLogger) {
|
||||||
const bookSeriesToRemove = (await Database.seriesModel.findAll({
|
const bookSeriesToRemove = (
|
||||||
|
await Database.seriesModel.findAll({
|
||||||
where: [
|
where: [
|
||||||
{
|
{
|
||||||
id: scanLogger.seriesRemovedFromBooks
|
id: scanLogger.seriesRemovedFromBooks
|
||||||
@ -921,7 +929,8 @@ class BookScanner {
|
|||||||
],
|
],
|
||||||
attributes: ['id'],
|
attributes: ['id'],
|
||||||
raw: true
|
raw: true
|
||||||
})).map(se => se.id)
|
})
|
||||||
|
).map((se) => se.id)
|
||||||
if (bookSeriesToRemove.length) {
|
if (bookSeriesToRemove.length) {
|
||||||
await Database.seriesModel.destroy({
|
await Database.seriesModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
@ -956,7 +965,6 @@ class BookScanner {
|
|||||||
|
|
||||||
// If the first cover result fails, attempt to download the second
|
// If the first cover result fails, attempt to download the second
|
||||||
for (let i = 0; i < results.length && i < 2; i++) {
|
for (let i = 0; i < results.length && i < 2; i++) {
|
||||||
|
|
||||||
// Downloads and updates the book cover
|
// Downloads and updates the book cover
|
||||||
const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath)
|
const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath)
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcast
|
|||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
const PodcastFinder = require('../finders/PodcastFinder')
|
const PodcastFinder = require('../finders/PodcastFinder')
|
||||||
const LibraryScan = require('./LibraryScan')
|
const LibraryScan = require('./LibraryScan')
|
||||||
const Series = require('../objects/entities/Series')
|
|
||||||
const LibraryScanner = require('./LibraryScanner')
|
const LibraryScanner = require('./LibraryScanner')
|
||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const TaskManager = require('../managers/TaskManager')
|
const TaskManager = require('../managers/TaskManager')
|
||||||
@ -209,6 +208,7 @@ class Scanner {
|
|||||||
if (!author) {
|
if (!author) {
|
||||||
author = await Database.authorModel.create({
|
author = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId: libraryItem.libraryId
|
libraryId: libraryItem.libraryId
|
||||||
})
|
})
|
||||||
SocketAuthority.emitter('author_added', author.toOldJSON())
|
SocketAuthority.emitter('author_added', author.toOldJSON())
|
||||||
@ -225,14 +225,16 @@ class Scanner {
|
|||||||
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
||||||
const seriesPayload = []
|
const seriesPayload = []
|
||||||
for (const seriesMatchItem of matchData.series) {
|
for (const seriesMatchItem of matchData.series) {
|
||||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
||||||
if (!seriesItem) {
|
if (!seriesItem) {
|
||||||
seriesItem = new Series()
|
seriesItem = await Database.seriesModel.create({
|
||||||
seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId)
|
name: seriesMatchItem.series,
|
||||||
await Database.createSeries(seriesItem)
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesMatchItem.series),
|
||||||
|
libraryId
|
||||||
|
})
|
||||||
// Update filter data
|
// Update filter data
|
||||||
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
||||||
SocketAuthority.emitter('series_added', seriesItem.toJSON())
|
SocketAuthority.emitter('series_added', seriesItem.toOldJSON())
|
||||||
}
|
}
|
||||||
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
||||||
}
|
}
|
||||||
|
@ -276,7 +276,7 @@ module.exports = {
|
|||||||
|
|
||||||
const allOldSeries = []
|
const allOldSeries = []
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const oldSeries = s.getOldSeries().toJSON()
|
const oldSeries = s.toOldJSON()
|
||||||
|
|
||||||
if (s.feeds?.length) {
|
if (s.feeds?.length) {
|
||||||
oldSeries.rssFeed = Database.feedModel.getOldFeed(s.feeds[0]).toJSONMinified()
|
oldSeries.rssFeed = Database.feedModel.getOldFeed(s.feeds[0]).toJSONMinified()
|
||||||
|
@ -954,12 +954,12 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get library items for series
|
* Get library items for series
|
||||||
* @param {import('../../objects/entities/Series')} oldSeries
|
* @param {import('../../models/Series')} series
|
||||||
* @param {import('../../models/User')} [user]
|
* @param {import('../../models/User')} [user]
|
||||||
* @returns {Promise<import('../../objects/LibraryItem')[]>}
|
* @returns {Promise<import('../../objects/LibraryItem')[]>}
|
||||||
*/
|
*/
|
||||||
async getLibraryItemsForSeries(oldSeries, user) {
|
async getLibraryItemsForSeries(series, user) {
|
||||||
const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, user, 'series', oldSeries.id, null, null, false, [], null, null)
|
const { libraryItems } = await this.getFilteredLibraryItems(series.libraryId, user, 'series', series.id, null, null, false, [], null, null)
|
||||||
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1130,7 +1130,7 @@ module.exports = {
|
|||||||
return Database.libraryItemModel.getOldLibraryItem(libraryItem).toJSON()
|
return Database.libraryItemModel.getOldLibraryItem(libraryItem).toJSON()
|
||||||
})
|
})
|
||||||
seriesMatches.push({
|
seriesMatches.push({
|
||||||
series: series.getOldSeries().toJSON(),
|
series: series.toOldJSON(),
|
||||||
books
|
books
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ module.exports = {
|
|||||||
// Map series to old series
|
// Map series to old series
|
||||||
const allOldSeries = []
|
const allOldSeries = []
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const oldSeries = s.getOldSeries().toJSON()
|
const oldSeries = s.toOldJSON()
|
||||||
|
|
||||||
if (s.dataValues.totalDuration) {
|
if (s.dataValues.totalDuration) {
|
||||||
oldSeries.totalDuration = s.dataValues.totalDuration
|
oldSeries.totalDuration = s.dataValues.totalDuration
|
||||||
|
Loading…
x
Reference in New Issue
Block a user