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 - Scrub Existing Tags + Update Covers + + + + 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 @@ public class RefreshEditionService : IRefreshEditionService { 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 @@ public bool RefreshEditionInfo(List add, List update, List Import(List> decisions, bool } _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.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 @@ public class EBookTagService : IEBookTagService, 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 @@ public ParsedTrackInfo ReadTags(IFileInfo file) } } + 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 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 @@ public interface IUpgradeMediaFiles public class UpgradeMediaFileService : IUpgradeMediaFiles { + private readonly IConfigService _configService; private readonly IRecycleBinProvider _recycleBinProvider; private readonly IMediaFileService _mediaFileService; private readonly IAudioTagService _audioTagService; @@ -28,7 +30,8 @@ public class UpgradeMediaFileService : IUpgradeMediaFiles 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 @@ public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider, ICalibreProxy calibre, Logger logger) { + _configService = configService; _recycleBinProvider = recycleBinProvider; _mediaFileService = mediaFileService; _audioTagService = audioTagService; @@ -136,15 +140,15 @@ public BookFile CalibreAddAndConvert(BookFile file, CalibreSettings settings) _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 @@ public DevelopmentConfigController(IConfigFileProvider configFileProvider, { _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 @@ public static DevelopmentConfigResource ToResource(this IConfigFileProvider mode { 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 @@ public class MetadataProviderConfigController : ConfigController 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 @@ public static MetadataProviderConfigResource ToResource(IConfigService model) { return new MetadataProviderConfigResource { - MetadataSource = model.MetadataSource, - WriteAudioTags = model.WriteAudioTags, - ScrubAudioTags = model.ScrubAudioTags, + WriteBookTags = model.WriteBookTags, + UpdateCovers = model.UpdateCovers, + EmbedMetadata = model.EmbedMetadata }; } }