diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 0cd243fd6..2ca5de8dd 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -8,6 +8,7 @@ const Database = require('../Database') const CacheManager = require('../managers/CacheManager') const CoverManager = require('../managers/CoverManager') const AuthorFinder = require('../finders/AuthorFinder') +const Scanner = require('../scanner/Scanner') const { reqSupportsWebp } = require('../utils/index') @@ -191,54 +192,21 @@ class AuthorController { res.sendStatus(200) } + // POST api/authors/:id/match async match(req, res) { - let authorData = null - const region = req.body.region || 'us' - if (req.body.asin) { - authorData = await AuthorFinder.findAuthorByASIN(req.body.asin, region) - } else { - authorData = await AuthorFinder.findAuthorByName(req.body.q, region) - } - if (!authorData) { + var author = req.author + var options = req.body || {} + var matchResult = await Scanner.quickMatchAuthor(author, options) + if (!matchResult) { return res.status(404).send('Author not found') } - Logger.debug(`[AuthorController] match author with "${req.body.q || req.body.asin}"`, authorData) - - let hasUpdates = false - if (authorData.asin && req.author.asin !== authorData.asin) { - req.author.asin = authorData.asin - hasUpdates = true + if (matchResult.updated) { + matchResult.author.updatedAt = Date.now() + await Database.updateAuthor(matchResult.author) + const numBooks = await Database.libraryItemModel.getForAuthor(matchResult.author).length + SocketAuthority.emitter('author_updated', matchResult.author.toJSONExpanded(numBooks)) } - - // Only updates image if there was no image before or the author ASIN was updated - if (authorData.image && (!req.author.imagePath || hasUpdates)) { - await CacheManager.purgeImageCache(req.author.id) - - const imageData = await AuthorFinder.saveAuthorImage(req.author.id, authorData.image) - if (imageData) { - req.author.imagePath = imageData.path - hasUpdates = true - } - } - - if (authorData.description && req.author.description !== authorData.description) { - req.author.description = authorData.description - hasUpdates = true - } - - if (hasUpdates) { - req.author.updatedAt = Date.now() - - await Database.updateAuthor(req.author) - - const numBooks = await Database.libraryItemModel.getForAuthor(req.author).length - SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) - } - - res.json({ - updated: hasUpdates, - author: req.author - }) + res.json(matchResult) } // GET api/authors/:id/image diff --git a/server/models/Author.js b/server/models/Author.js index c6537ec12..5c6fa20c3 100644 --- a/server/models/Author.js +++ b/server/models/Author.js @@ -1,4 +1,5 @@ const { DataTypes, Model, where, fn, col } = require('sequelize') +const { nameToLastFirst } = require('../utils/parsers/parseNameString') const oldAuthor = require('../objects/entities/Author') @@ -166,6 +167,12 @@ class Author extends Model { onDelete: 'CASCADE' }) Author.belongsTo(library) + + Author.addHook('beforeCreate', async (author) => { + if (!author || author.lastFirst) return + author.lastFirst = nameToLastFirst(author.name) + }) + } } module.exports = Author diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index e579bcc95..4becf447f 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -15,10 +15,12 @@ const LibraryFile = require('../objects/files/LibraryFile') const SocketAuthority = require('../SocketAuthority') const fsExtra = require("../libs/fsExtra") const BookFinder = require('../finders/BookFinder') +const Author = require('../objects/entities/Author') const LibraryScan = require("./LibraryScan") const OpfFileScanner = require('./OpfFileScanner') const AbsMetadataFileScanner = require('./AbsMetadataFileScanner') +const Scanner = require('../scanner/Scanner') /** * Metadata for books pulled from files @@ -192,11 +194,15 @@ class BookScanner { libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" added author "${authorName}"`) authorsUpdated = true } else { - const newAuthor = await Database.authorModel.create({ - name: authorName, - lastFirst: parseNameString.nameToLastFirst(authorName), - libraryId: libraryItemData.libraryId - }) + let author = new Author() + author.setData({ name: authorName }, libraryItemData.libraryId) + let authorOptions = {} + authorOptions.region = libraryScan.library.provider.includes('.') ? libraryScan.library.provider.split('.').pop() : 'us' + let authorData = await Scanner.quickMatchAuthor(author, authorOptions) + if (authorData) { + author = authorData.author + } + const newAuthor = await Database.authorModel.create(author) await media.addAuthor(newAuthor) Database.addAuthorToFilterData(libraryItemData.libraryId, newAuthor.name, newAuthor.id) libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" added new author "${authorName}"`) @@ -416,12 +422,16 @@ class BookScanner { }) } else { // New author + let author = new Author() + author.setData({ name: authorName }, libraryItemData.libraryId) + let authorOptions = {} + authorOptions.region = libraryScan.library.provider.includes('.') ? libraryScan.library.provider.split('.').pop() : 'us' + let authorData = await Scanner.quickMatchAuthor(author, authorOptions) + if (authorData) { + author = authorData.author + } bookObject.bookAuthors.push({ - author: { - libraryId: libraryItemData.libraryId, - name: authorName, - lastFirst: parseNameString.nameToLastFirst(authorName) - } + author: author.toJSON() }) } } diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js index ebce36073..7313cd3b5 100644 --- a/server/scanner/Scanner.js +++ b/server/scanner/Scanner.js @@ -12,12 +12,14 @@ const Author = require('../objects/entities/Author') const Series = require('../objects/entities/Series') const LibraryScanner = require('./LibraryScanner') const CoverManager = require('../managers/CoverManager') +const CacheManager = require('../managers/CacheManager') +const AuthorFinder = require('../finders/AuthorFinder') class Scanner { constructor() { } async quickMatchLibraryItem(libraryItem, options = {}) { - var provider = options.provider || 'google' + options.provider = options.provider || 'google' var searchTitle = options.title || libraryItem.media.metadata.title var searchAuthor = options.author || libraryItem.media.metadata.authorName var overrideDefaults = options.overrideDefaults || false @@ -36,10 +38,10 @@ class Scanner { var searchISBN = options.isbn || libraryItem.media.metadata.isbn var searchASIN = options.asin || libraryItem.media.metadata.asin - var results = await BookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 }) + var results = await BookFinder.search(options.provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 }) if (!results.length) { return { - warning: `No ${provider} match found` + warning: `No ${options.provider} match found` } } var matchData = results[0] @@ -60,7 +62,7 @@ class Scanner { var results = await PodcastFinder.search(searchTitle) if (!results.length) { return { - warning: `No ${provider} match found` + warning: `No ${options.provider} match found` } } var matchData = results[0] @@ -189,6 +191,13 @@ class Scanner { if (!author) { author = new Author() author.setData({ name: authorName }, libraryItem.libraryId) + let authorOptions = {} + authorOptions.region = options.provider.includes('.') ? options.provider.split('.').pop() : 'us' + let authorData = await this.quickMatchAuthor(author, authorOptions) + if (authorData) { + author = authorData.author + } + await Database.createAuthor(author) SocketAuthority.emitter('author_added', author.toJSON()) // Update filter data @@ -362,5 +371,50 @@ class Scanner { LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter(ls => ls.id !== library.id) SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) } + + + /** + * Search providers for author + * @param {Author} author + * @param {{region:string, asin:string, q:string}} options + */ + async quickMatchAuthor(author, options = {}) { + let authorData = null + const region = options.region || 'us' + if (options.asin) { + authorData = await AuthorFinder.findAuthorByASIN(options.asin, region) + } else { + authorData = await AuthorFinder.findAuthorByName(options.q || author.name, region) + } + if (!authorData) { + return null + } + Logger.debug(`[Scanner] match author from with "${options.q || options.asin}"`, authorData) + + let hasUpdated = false; + ['asin', 'name', 'description'].forEach(key => { + if (authorData[key] && author[key] !== authorData[key]) { + author[key] = authorData[key]; + hasUpdated = true; + } + }); + + // Only updates image if there was no image before or the author ASIN was updated + if (authorData.image && (!author.imagePath || (authorData.asin && author.asin !== authorData.asin))) { + await CacheManager.purgeImageCache(author.id) + + const imageData = await AuthorFinder.saveAuthorImage(author.id, authorData.image) + if (imageData) { + author.imagePath = imageData.path + hasUpdated = true + } + } + + return { + updated: hasUpdated, + author: author + } + } + } module.exports = new Scanner()