From f584d2d8d26b378434802a84e0a9e22da9c17323 Mon Sep 17 00:00:00 2001 From: ta264 Date: Tue, 30 Mar 2021 21:24:40 +0100 Subject: [PATCH] New: Allow keeping calibre in sync with goodreads --- .../Development/DevelopmentSettings.js | 15 +++++ .../MetadataProvider/MetadataProvider.js | 62 ++++++++----------- .../src/Settings/Metadata/MetadataSettings.js | 4 +- .../Books/Services/RefreshEditionService.cs | 4 ++ .../Configuration/ConfigService.cs | 21 +++++++ .../Configuration/IConfigService.cs | 3 + .../Configuration/WriteBookTagsType.cs | 9 +++ .../BookImport/ImportApprovedBooks.cs | 8 +-- .../MediaFiles/EbookTagService.cs | 48 ++++++++++++++ .../MediaFiles/UpgradeMediaFileService.cs | 12 ++-- .../Config/DevelopmentConfigController.cs | 5 ++ .../Config/DevelopmentConfigResource.cs | 2 + .../MetadataProviderConfigController.cs | 4 -- .../Config/MetadataProviderConfigResource.cs | 12 ++-- 14 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 src/NzbDrone.Core/Configuration/WriteBookTagsType.cs diff --git a/frontend/src/Settings/Development/DevelopmentSettings.js b/frontend/src/Settings/Development/DevelopmentSettings.js index 8741b7a6f..bb93caacb 100644 --- a/frontend/src/Settings/Development/DevelopmentSettings.js +++ b/frontend/src/Settings/Development/DevelopmentSettings.js @@ -59,6 +59,21 @@ class DevelopmentSettings extends Component { id="developmentSettings" {...otherProps} > +
+ + Metadata Source + + + +
+
Log Rotation diff --git a/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js b/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js index f29b389e0..6dabc28d0 100644 --- a/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js +++ b/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js @@ -8,16 +8,14 @@ import FormLabel from 'Components/Form/FormLabel'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import { inputTypes } from 'Helpers/Props'; -const writeAudioTagOptions = [ - { key: 'sync', value: 'All files; keep in sync with MusicBrainz' }, +const writeBookTagOptions = [ + { key: 'sync', value: 'All files; keep in sync with Goodreads' }, { key: 'allFiles', value: 'All files; initial import only' }, - { key: 'newFiles', value: 'For new downloads only' }, - { key: 'no', value: 'Never' } + { key: 'newFiles', value: 'For new downloads only' } ]; function MetadataProvider(props) { const { - advancedSettings, isFetching, error, settings, @@ -41,51 +39,42 @@ function MetadataProvider(props) { { hasSettings && !isFetching && !error &&
- { - advancedSettings && -
- - Metadata Source - - - -
- } - -
+
- Tag Audio Files with Metadata + Send Metadata to Calibre + + + + Update Covers + + - Scrub Existing Tags + Embed Metadata in Book Files @@ -98,7 +87,6 @@ function MetadataProvider(props) { } MetadataProvider.propTypes = { - advancedSettings: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired, error: PropTypes.object, settings: PropTypes.object.isRequired, diff --git a/frontend/src/Settings/Metadata/MetadataSettings.js b/frontend/src/Settings/Metadata/MetadataSettings.js index dab4046a0..a58d3a280 100644 --- a/frontend/src/Settings/Metadata/MetadataSettings.js +++ b/frontend/src/Settings/Metadata/MetadataSettings.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; -import MetadatasConnector from './Metadata/MetadatasConnector'; +// import MetadatasConnector from './Metadata/MetadatasConnector'; import MetadataProviderConnector from './MetadataProvider/MetadataProviderConnector'; class MetadataSettings extends Component { @@ -59,7 +59,7 @@ class MetadataSettings extends Component { onChildMounted={this.onChildMounted} onChildStateChange={this.onChildStateChange} /> - + {/* */} ); diff --git a/src/NzbDrone.Core/Books/Services/RefreshEditionService.cs b/src/NzbDrone.Core/Books/Services/RefreshEditionService.cs index c214a6ed2..f63e3ed92 100644 --- a/src/NzbDrone.Core/Books/Services/RefreshEditionService.cs +++ b/src/NzbDrone.Core/Books/Services/RefreshEditionService.cs @@ -15,14 +15,17 @@ namespace NzbDrone.Core.Books { private readonly IEditionService _editionService; private readonly IAudioTagService _audioTagService; + private readonly IEBookTagService _eBookTagService; private readonly Logger _logger; public RefreshEditionService(IEditionService editionService, IAudioTagService audioTagService, + IEBookTagService eBookTagService, Logger logger) { _editionService = editionService; _audioTagService = audioTagService; + _eBookTagService = eBookTagService; _logger = logger; } @@ -52,6 +55,7 @@ namespace NzbDrone.Core.Books } _audioTagService.SyncTags(tagsToUpdate); + _eBookTagService.SyncTags(tagsToUpdate); return add.Any() || delete.Any() || updateList.Any() || merge.Any(); } diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 4e2e123e5..4e3095058 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -290,6 +290,27 @@ namespace NzbDrone.Core.Configuration set { SetValue("ScrubAudioTags", value); } } + public WriteBookTagsType WriteBookTags + { + get { return GetValueEnum("WriteBookTags", WriteBookTagsType.NewFiles); } + + set { SetValue("WriteBookTags", value); } + } + + public bool UpdateCovers + { + get { return GetValueBoolean("UpdateCovers", true); } + + set { SetValue("UpdateCovers", value); } + } + + public bool EmbedMetadata + { + get { return GetValueBoolean("EmbedMetadata", false); } + + set { SetValue("EmbedMetadata", value); } + } + public int FirstDayOfWeek { get { return GetValueInt("FirstDayOfWeek", (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index b55783f7c..2d9d62561 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -70,6 +70,9 @@ namespace NzbDrone.Core.Configuration string MetadataSource { get; set; } WriteAudioTagsType WriteAudioTags { get; set; } bool ScrubAudioTags { get; set; } + WriteBookTagsType WriteBookTags { get; set; } + bool UpdateCovers { get; set; } + bool EmbedMetadata { get; set; } //Forms Auth string RijndaelPassphrase { get; } diff --git a/src/NzbDrone.Core/Configuration/WriteBookTagsType.cs b/src/NzbDrone.Core/Configuration/WriteBookTagsType.cs new file mode 100644 index 000000000..f8353e751 --- /dev/null +++ b/src/NzbDrone.Core/Configuration/WriteBookTagsType.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.Configuration +{ + public enum WriteBookTagsType + { + NewFiles, + AllFiles, + Sync + } +} diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs b/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs index 78b4d07a0..2b782ca4e 100644 --- a/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs +++ b/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs @@ -14,7 +14,6 @@ using NzbDrone.Core.Extras; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.RootFolders; @@ -31,9 +30,9 @@ namespace NzbDrone.Core.MediaFiles.BookImport private readonly IUpgradeMediaFiles _bookFileUpgrader; private readonly IMediaFileService _mediaFileService; private readonly IAudioTagService _audioTagService; + private readonly IEBookTagService _eBookTagService; private readonly IAuthorService _authorService; private readonly IAddAuthorService _addAuthorService; - private readonly IRefreshAuthorService _refreshAuthorService; private readonly IBookService _bookService; private readonly IEditionService _editionService; private readonly IRootFolderService _rootFolderService; @@ -47,9 +46,9 @@ namespace NzbDrone.Core.MediaFiles.BookImport public ImportApprovedBooks(IUpgradeMediaFiles bookFileUpgrader, IMediaFileService mediaFileService, IAudioTagService audioTagService, + IEBookTagService eBookTagService, IAuthorService authorService, IAddAuthorService addAuthorService, - IRefreshAuthorService refreshAuthorService, IBookService bookService, IEditionService editionService, IRootFolderService rootFolderService, @@ -63,9 +62,9 @@ namespace NzbDrone.Core.MediaFiles.BookImport _bookFileUpgrader = bookFileUpgrader; _mediaFileService = mediaFileService; _audioTagService = audioTagService; + _eBookTagService = eBookTagService; _authorService = authorService; _addAuthorService = addAuthorService; - _refreshAuthorService = refreshAuthorService; _bookService = bookService; _editionService = editionService; _rootFolderService = rootFolderService; @@ -207,6 +206,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport } _audioTagService.WriteTags(bookFile, false); + _eBookTagService.WriteTags(bookFile, false); } filesToAdd.Add(bookFile); diff --git a/src/NzbDrone.Core/MediaFiles/EbookTagService.cs b/src/NzbDrone.Core/MediaFiles/EbookTagService.cs index fbf259def..cfc07d7b3 100644 --- a/src/NzbDrone.Core/MediaFiles/EbookTagService.cs +++ b/src/NzbDrone.Core/MediaFiles/EbookTagService.cs @@ -10,6 +10,7 @@ using NzbDrone.Common.Instrumentation.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Books; using NzbDrone.Core.Books.Calibre; +using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.Azw; using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.Messaging.Commands; @@ -25,6 +26,8 @@ namespace NzbDrone.Core.MediaFiles public interface IEBookTagService { ParsedTrackInfo ReadTags(IFileInfo file); + void WriteTags(BookFile trackfile, bool newDownload, bool force = false); + void SyncTags(List books); List GetRetagPreviewsByAuthor(int authorId); List GetRetagPreviewsByBook(int authorId); } @@ -36,18 +39,21 @@ namespace NzbDrone.Core.MediaFiles private readonly IAuthorService _authorService; private readonly IMediaFileService _mediaFileService; private readonly IRootFolderService _rootFolderService; + private readonly IConfigService _configService; private readonly ICalibreProxy _calibre; private readonly Logger _logger; public EBookTagService(IAuthorService authorService, IMediaFileService mediaFileService, IRootFolderService rootFolderService, + IConfigService configService, ICalibreProxy calibre, Logger logger) { _authorService = authorService; _mediaFileService = mediaFileService; _rootFolderService = rootFolderService; + _configService = configService; _calibre = calibre; _logger = logger; @@ -72,6 +78,48 @@ namespace NzbDrone.Core.MediaFiles } } + public void WriteTags(BookFile bookFile, bool newDownload, bool force = false) + { + if (!force) + { + if (_configService.WriteBookTags == WriteBookTagsType.NewFiles && !newDownload) + { + return; + } + } + + _logger.Debug($"Writing tags for {bookFile}"); + + var rootFolder = _rootFolderService.GetBestRootFolder(bookFile.Path); + _calibre.SetFields(bookFile, rootFolder.CalibreSettings, _configService.UpdateCovers, _configService.EmbedMetadata); + } + + public void SyncTags(List editions) + { + if (_configService.WriteBookTags != WriteBookTagsType.Sync) + { + return; + } + + // get the tracks to update + foreach (var edition in editions) + { + var bookFiles = edition.BookFiles.Value; + + _logger.Debug($"Syncing ebook tags for {edition}"); + + foreach (var file in bookFiles) + { + // populate tracks (which should also have release/book/author set) because + // not all of the updates will have been committed to the database yet + file.Edition = edition; + + var rootFolder = _rootFolderService.GetBestRootFolder(file.Path); + _calibre.SetFields(file, rootFolder.CalibreSettings, _configService.UpdateCovers, _configService.EmbedMetadata); + } + } + } + public List GetRetagPreviewsByAuthor(int authorId) { var files = _mediaFileService.GetFilesByAuthor(authorId); diff --git a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs index 0ff4baf8e..e8118d6ec 100644 --- a/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/UpgradeMediaFileService.cs @@ -5,6 +5,7 @@ using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Core.Books.Calibre; +using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles.BookImport; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RootFolders; @@ -18,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles public class UpgradeMediaFileService : IUpgradeMediaFiles { + private readonly IConfigService _configService; private readonly IRecycleBinProvider _recycleBinProvider; private readonly IMediaFileService _mediaFileService; private readonly IAudioTagService _audioTagService; @@ -28,7 +30,8 @@ namespace NzbDrone.Core.MediaFiles private readonly ICalibreProxy _calibre; private readonly Logger _logger; - public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider, + public UpgradeMediaFileService(IConfigService configService, + IRecycleBinProvider recycleBinProvider, IMediaFileService mediaFileService, IAudioTagService audioTagService, IMoveBookFiles bookFileMover, @@ -38,6 +41,7 @@ namespace NzbDrone.Core.MediaFiles ICalibreProxy calibre, Logger logger) { + _configService = configService; _recycleBinProvider = recycleBinProvider; _mediaFileService = mediaFileService; _audioTagService = audioTagService; @@ -136,15 +140,15 @@ namespace NzbDrone.Core.MediaFiles _calibre.AddFormat(file, settings); } - _calibre.SetFields(file, settings); + _rootFolderWatchingService.ReportFileSystemChangeBeginning(file.Path); + + _calibre.SetFields(file, settings, true, _configService.EmbedMetadata); var updated = _calibre.GetBook(file.CalibreId, settings); var path = updated.Formats.Values.OrderByDescending(x => x.LastModified).First().Path; file.Path = path; - _rootFolderWatchingService.ReportFileSystemChangeBeginning(file.Path); - if (settings.OutputFormat.IsNotNullOrWhiteSpace()) { _logger.Trace($"Getting book data for {file.CalibreId}"); diff --git a/src/Readarr.Api.V1/Config/DevelopmentConfigController.cs b/src/Readarr.Api.V1/Config/DevelopmentConfigController.cs index d7070d6b3..3a9eaf8d2 100644 --- a/src/Readarr.Api.V1/Config/DevelopmentConfigController.cs +++ b/src/Readarr.Api.V1/Config/DevelopmentConfigController.cs @@ -1,7 +1,10 @@ using System.Linq; using System.Reflection; +using FluentValidation; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Validation; using NzbDrone.Http.REST.Attributes; using Readarr.Http; using Readarr.Http.REST; @@ -19,6 +22,8 @@ namespace Prowlarr.Api.V1.Config { _configFileProvider = configFileProvider; _configService = configService; + + SharedValidator.RuleFor(c => c.MetadataSource).IsValidUrl().When(c => !c.MetadataSource.IsNullOrWhiteSpace()); } public override DevelopmentConfigResource GetResourceById(int id) diff --git a/src/Readarr.Api.V1/Config/DevelopmentConfigResource.cs b/src/Readarr.Api.V1/Config/DevelopmentConfigResource.cs index 440ed93dc..4a0bb0be0 100644 --- a/src/Readarr.Api.V1/Config/DevelopmentConfigResource.cs +++ b/src/Readarr.Api.V1/Config/DevelopmentConfigResource.cs @@ -5,6 +5,7 @@ namespace Prowlarr.Api.V1.Config { public class DevelopmentConfigResource : RestResource { + public string MetadataSource { get; set; } public string ConsoleLogLevel { get; set; } public bool LogSql { get; set; } public int LogRotate { get; set; } @@ -17,6 +18,7 @@ namespace Prowlarr.Api.V1.Config { return new DevelopmentConfigResource { + MetadataSource = configService.MetadataSource, ConsoleLogLevel = model.ConsoleLogLevel, LogSql = model.LogSql, LogRotate = model.LogRotate, diff --git a/src/Readarr.Api.V1/Config/MetadataProviderConfigController.cs b/src/Readarr.Api.V1/Config/MetadataProviderConfigController.cs index 089a4cc0e..3d3a57b0e 100644 --- a/src/Readarr.Api.V1/Config/MetadataProviderConfigController.cs +++ b/src/Readarr.Api.V1/Config/MetadataProviderConfigController.cs @@ -1,7 +1,4 @@ -using FluentValidation; -using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Validation; using Readarr.Http; namespace Readarr.Api.V1.Config @@ -12,7 +9,6 @@ namespace Readarr.Api.V1.Config public MetadataProviderConfigController(IConfigService configService) : base(configService) { - SharedValidator.RuleFor(c => c.MetadataSource).IsValidUrl().When(c => !c.MetadataSource.IsNullOrWhiteSpace()); } protected override MetadataProviderConfigResource ToResource(IConfigService model) diff --git a/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs b/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs index 8619440e7..03f112d9d 100644 --- a/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs +++ b/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs @@ -5,9 +5,9 @@ namespace Readarr.Api.V1.Config { public class MetadataProviderConfigResource : RestResource { - public string MetadataSource { get; set; } - public WriteAudioTagsType WriteAudioTags { get; set; } - public bool ScrubAudioTags { get; set; } + public WriteBookTagsType WriteBookTags { get; set; } + public bool UpdateCovers { get; set; } + public bool EmbedMetadata { get; set; } } public static class MetadataProviderConfigResourceMapper @@ -16,9 +16,9 @@ namespace Readarr.Api.V1.Config { return new MetadataProviderConfigResource { - MetadataSource = model.MetadataSource, - WriteAudioTags = model.WriteAudioTags, - ScrubAudioTags = model.ScrubAudioTags, + WriteBookTags = model.WriteBookTags, + UpdateCovers = model.UpdateCovers, + EmbedMetadata = model.EmbedMetadata }; } }