diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/MetadataRefreshOptions.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/MetadataRefreshOptions.java index f7293e0f1..a58084e16 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/MetadataRefreshOptions.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/MetadataRefreshOptions.java @@ -6,27 +6,28 @@ import lombok.Getter; import lombok.Setter; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; +import lombok.Builder; @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@Builder public class MetadataRefreshOptions { private Long libraryId; - @NotNull(message = "Default Provider cannot be null") - private MetadataProvider allP1; - private MetadataProvider allP2; - private MetadataProvider allP3; - private MetadataProvider allP4; private boolean refreshCovers; private boolean mergeCategories; private Boolean reviewBeforeApply; + @NotNull(message = "Field options cannot be null") private FieldOptions fieldOptions; + @NotNull(message = "Skip fields cannot be null") + private SkipFields skipFields; @Getter @Setter @NoArgsConstructor @AllArgsConstructor + @Builder public static class FieldOptions { private FieldProvider title; private FieldProvider subtitle; @@ -41,19 +42,68 @@ public class MetadataRefreshOptions { private FieldProvider isbn10; private FieldProvider language; private FieldProvider categories; + private FieldProvider cover; + private FieldProvider pageCount; + private FieldProvider asin; + private FieldProvider goodreadsId; + private FieldProvider comicvineId; + private FieldProvider hardcoverId; + private FieldProvider googleId; + private FieldProvider amazonRating; + private FieldProvider amazonReviewCount; + private FieldProvider goodreadsRating; + private FieldProvider goodreadsReviewCount; + private FieldProvider hardcoverRating; + private FieldProvider hardcoverReviewCount; private FieldProvider moods; private FieldProvider tags; - private FieldProvider cover; } @Getter @Setter @NoArgsConstructor @AllArgsConstructor + @Builder public static class FieldProvider { - private MetadataProvider p4; - private MetadataProvider p3; - private MetadataProvider p2; private MetadataProvider p1; + private MetadataProvider p2; + private MetadataProvider p3; + private MetadataProvider p4; + } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class SkipFields { + private boolean title; + private boolean subtitle; + private boolean description; + private boolean authors; + private boolean publisher; + private boolean publishedDate; + private boolean seriesName; + private boolean seriesNumber; + private boolean seriesTotal; + private boolean isbn13; + private boolean isbn10; + private boolean language; + private boolean categories; + private boolean cover; + private boolean pageCount; + private boolean asin; + private boolean goodreadsId; + private boolean comicvineId; + private boolean hardcoverId; + private boolean googleId; + private boolean amazonRating; + private boolean amazonReviewCount; + private boolean goodreadsRating; + private boolean goodreadsReviewCount; + private boolean hardcoverRating; + private boolean hardcoverReviewCount; + private boolean moods; + private boolean tags; } } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java index 2f7fc5441..d6c56d31a 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java @@ -110,69 +110,83 @@ public class SettingPersistenceHelper { } MetadataRefreshOptions getDefaultMetadataRefreshOptions() { - MetadataRefreshOptions.FieldProvider titleProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider subtitleProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider descriptionProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider authorsProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider publisherProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider publishedDateProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider seriesNameProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider seriesNumberProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider seriesTotalProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider isbn13Providers = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider isbn10Providers = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider languageProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider categoriesProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider moodsProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider tagsProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider coverProviders = - new MetadataRefreshOptions.FieldProvider(null, MetadataProvider.Google, MetadataProvider.Amazon, MetadataProvider.GoodReads); + MetadataRefreshOptions.FieldProvider amazonProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Amazon) + .build(); - MetadataRefreshOptions.FieldOptions fieldOptions = new MetadataRefreshOptions.FieldOptions( - titleProviders, - subtitleProviders, - descriptionProviders, - authorsProviders, - publisherProviders, - publishedDateProviders, - seriesNameProviders, - seriesNumberProviders, - seriesTotalProviders, - isbn13Providers, - isbn10Providers, - languageProviders, - categoriesProviders, - moodsProviders, - tagsProviders, - coverProviders - ); + MetadataRefreshOptions.FieldProvider nullProvider = MetadataRefreshOptions.FieldProvider.builder() + .build(); - return new MetadataRefreshOptions( - null, - MetadataProvider.GoodReads, - MetadataProvider.Amazon, - MetadataProvider.Google, - null, - false, - true, - false, - fieldOptions - ); + MetadataRefreshOptions.FieldOptions fieldOptions = MetadataRefreshOptions.FieldOptions.builder() + .title(amazonProvider) + .subtitle(amazonProvider) + .description(amazonProvider) + .authors(amazonProvider) + .publisher(amazonProvider) + .publishedDate(amazonProvider) + .seriesName(amazonProvider) + .seriesNumber(amazonProvider) + .seriesTotal(amazonProvider) + .isbn13(amazonProvider) + .isbn10(amazonProvider) + .language(amazonProvider) + .categories(amazonProvider) + .cover(amazonProvider) + .pageCount(amazonProvider) + .asin(nullProvider) + .goodreadsId(nullProvider) + .comicvineId(nullProvider) + .hardcoverId(nullProvider) + .googleId(nullProvider) + .amazonRating(nullProvider) + .amazonReviewCount(nullProvider) + .goodreadsRating(nullProvider) + .goodreadsReviewCount(nullProvider) + .hardcoverRating(nullProvider) + .hardcoverReviewCount(nullProvider) + .moods(nullProvider) + .tags(nullProvider) + .build(); + + MetadataRefreshOptions.SkipFields skipFields = MetadataRefreshOptions.SkipFields.builder() + .title(false) + .subtitle(false) + .description(false) + .authors(false) + .publisher(false) + .publishedDate(false) + .seriesName(false) + .seriesNumber(false) + .seriesTotal(false) + .isbn13(false) + .isbn10(false) + .language(false) + .categories(false) + .cover(false) + .pageCount(false) + .asin(false) + .goodreadsId(false) + .comicvineId(false) + .hardcoverId(false) + .googleId(false) + .amazonRating(false) + .amazonReviewCount(false) + .goodreadsRating(false) + .goodreadsReviewCount(false) + .hardcoverRating(false) + .hardcoverReviewCount(false) + .moods(false) + .tags(false) + .build(); + + return MetadataRefreshOptions.builder() + .libraryId(null) + .refreshCovers(false) + .mergeCategories(true) + .reviewBeforeApply(false) + .fieldOptions(fieldOptions) + .skipFields(skipFields) + .build(); } public MetadataMatchWeights getDefaultMetadataMatchWeights() { diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/MetadataRefreshService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/MetadataRefreshService.java index addfd019e..808c66f12 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/MetadataRefreshService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/MetadataRefreshService.java @@ -32,6 +32,9 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import static com.adityachandel.booklore.model.enums.MetadataProvider.*; @@ -213,7 +216,7 @@ public class MetadataRefreshService { )); } - private CompletableFuture createInterruptibleMetadataFuture(java.util.function.Supplier metadataSupplier) { + private CompletableFuture createInterruptibleMetadataFuture(Supplier metadataSupplier) { return CompletableFuture.supplyAsync(() -> { if (Thread.currentThread().isInterrupted()) { log.info("Skipping metadata fetch due to interruption"); @@ -323,12 +326,33 @@ public class MetadataRefreshService { if (fieldOptions != null) { addProviderToSet(fieldOptions.getTitle(), uniqueProviders); + addProviderToSet(fieldOptions.getSubtitle(), uniqueProviders); addProviderToSet(fieldOptions.getDescription(), uniqueProviders); addProviderToSet(fieldOptions.getAuthors(), uniqueProviders); + addProviderToSet(fieldOptions.getPublisher(), uniqueProviders); + addProviderToSet(fieldOptions.getPublishedDate(), uniqueProviders); + addProviderToSet(fieldOptions.getSeriesName(), uniqueProviders); + addProviderToSet(fieldOptions.getSeriesNumber(), uniqueProviders); + addProviderToSet(fieldOptions.getSeriesTotal(), uniqueProviders); + addProviderToSet(fieldOptions.getIsbn13(), uniqueProviders); + addProviderToSet(fieldOptions.getIsbn10(), uniqueProviders); + addProviderToSet(fieldOptions.getLanguage(), uniqueProviders); addProviderToSet(fieldOptions.getCategories(), uniqueProviders); + addProviderToSet(fieldOptions.getCover(), uniqueProviders); + addProviderToSet(fieldOptions.getPageCount(), uniqueProviders); + addProviderToSet(fieldOptions.getAsin(), uniqueProviders); + addProviderToSet(fieldOptions.getGoodreadsId(), uniqueProviders); + addProviderToSet(fieldOptions.getComicvineId(), uniqueProviders); + addProviderToSet(fieldOptions.getHardcoverId(), uniqueProviders); + addProviderToSet(fieldOptions.getGoogleId(), uniqueProviders); + addProviderToSet(fieldOptions.getAmazonRating(), uniqueProviders); + addProviderToSet(fieldOptions.getAmazonReviewCount(), uniqueProviders); + addProviderToSet(fieldOptions.getGoodreadsRating(), uniqueProviders); + addProviderToSet(fieldOptions.getGoodreadsReviewCount(), uniqueProviders); + addProviderToSet(fieldOptions.getHardcoverRating(), uniqueProviders); + addProviderToSet(fieldOptions.getHardcoverReviewCount(), uniqueProviders); addProviderToSet(fieldOptions.getMoods(), uniqueProviders); addProviderToSet(fieldOptions.getTags(), uniqueProviders); - addProviderToSet(fieldOptions.getCover(), uniqueProviders); } return uniqueProviders; @@ -343,7 +367,6 @@ public class MetadataRefreshService { } } - public BookMetadata fetchTopMetadataFromAProvider(MetadataProvider provider, Book book) { return getParser(provider).fetchTopMetadata(book, buildFetchMetadataRequestFromBook(book)); } @@ -370,10 +393,122 @@ public class MetadataRefreshService { public BookMetadata buildFetchMetadata(Long bookId, MetadataRefreshOptions refreshOptions, Map metadataMap) { BookMetadata metadata = BookMetadata.builder().bookId(bookId).build(); MetadataRefreshOptions.FieldOptions fieldOptions = refreshOptions.getFieldOptions(); + MetadataRefreshOptions.SkipFields skipFields = refreshOptions.getSkipFields(); - metadata.setTitle(resolveFieldAsString(metadataMap, fieldOptions.getTitle(), BookMetadata::getTitle)); - metadata.setDescription(resolveFieldAsString(metadataMap, fieldOptions.getDescription(), BookMetadata::getDescription)); - metadata.setAuthors(resolveFieldAsList(metadataMap, fieldOptions.getAuthors(), BookMetadata::getAuthors)); + if (!skipFields.isTitle()) { + metadata.setTitle(resolveFieldAsString(metadataMap, fieldOptions.getTitle(), BookMetadata::getTitle)); + } + if (!skipFields.isSubtitle()) { + metadata.setSubtitle(resolveFieldAsString(metadataMap, fieldOptions.getSubtitle(), BookMetadata::getSubtitle)); + } + if (!skipFields.isDescription()) { + metadata.setDescription(resolveFieldAsString(metadataMap, fieldOptions.getDescription(), BookMetadata::getDescription)); + } + if (!skipFields.isAuthors()) { + metadata.setAuthors(resolveFieldAsList(metadataMap, fieldOptions.getAuthors(), BookMetadata::getAuthors)); + } + if (!skipFields.isPublisher()) { + metadata.setPublisher(resolveFieldAsString(metadataMap, fieldOptions.getPublisher(), BookMetadata::getPublisher)); + } + if (!skipFields.isPublishedDate()) { + metadata.setPublishedDate(resolveField(metadataMap, fieldOptions.getPublishedDate(), BookMetadata::getPublishedDate)); + } + if (!skipFields.isSeriesName()) { + metadata.setSeriesName(resolveFieldAsString(metadataMap, fieldOptions.getSeriesName(), BookMetadata::getSeriesName)); + } + if (!skipFields.isSeriesNumber()) { + metadata.setSeriesNumber(resolveField(metadataMap, fieldOptions.getSeriesNumber(), BookMetadata::getSeriesNumber)); + } + if (!skipFields.isSeriesTotal()) { + metadata.setSeriesTotal(resolveFieldAsInteger(metadataMap, fieldOptions.getSeriesTotal(), BookMetadata::getSeriesTotal)); + } + if (!skipFields.isIsbn13()) { + metadata.setIsbn13(resolveFieldAsString(metadataMap, fieldOptions.getIsbn13(), BookMetadata::getIsbn13)); + } + if (!skipFields.isIsbn10()) { + metadata.setIsbn10(resolveFieldAsString(metadataMap, fieldOptions.getIsbn10(), BookMetadata::getIsbn10)); + } + if (!skipFields.isLanguage()) { + metadata.setLanguage(resolveFieldAsString(metadataMap, fieldOptions.getLanguage(), BookMetadata::getLanguage)); + } + if (!skipFields.isPageCount()) { + metadata.setPageCount(resolveFieldAsInteger(metadataMap, fieldOptions.getPageCount(), BookMetadata::getPageCount)); + } + if (!skipFields.isCover()) { + metadata.setThumbnailUrl(resolveFieldAsString(metadataMap, fieldOptions.getCover(), BookMetadata::getThumbnailUrl)); + } + if (!skipFields.isAmazonRating()) { + if (metadataMap.containsKey(Amazon)) { + metadata.setAmazonRating(metadataMap.get(Amazon).getAmazonRating()); + } + } + if (!skipFields.isAmazonReviewCount()) { + if (metadataMap.containsKey(Amazon)) { + metadata.setAmazonReviewCount(metadataMap.get(Amazon).getAmazonReviewCount()); + } + } + if (!skipFields.isGoodreadsRating()) { + if (metadataMap.containsKey(GoodReads)) { + metadata.setGoodreadsRating(metadataMap.get(GoodReads).getGoodreadsRating()); + } + } + if (!skipFields.isGoodreadsReviewCount()) { + if (metadataMap.containsKey(GoodReads)) { + metadata.setGoodreadsReviewCount(metadataMap.get(GoodReads).getGoodreadsReviewCount()); + } + } + if (!skipFields.isHardcoverRating()) { + if (metadataMap.containsKey(Hardcover)) { + metadata.setHardcoverRating(metadataMap.get(Hardcover).getHardcoverRating()); + } + } + if (!skipFields.isHardcoverReviewCount()) { + if (metadataMap.containsKey(Hardcover)) { + metadata.setHardcoverReviewCount(metadataMap.get(Hardcover).getHardcoverReviewCount()); + } + } + if (!skipFields.isAsin()) { + if (metadataMap.containsKey(Amazon)) { + metadata.setAsin(metadataMap.get(Amazon).getAsin()); + } + } + if (!skipFields.isGoodreadsId()) { + if (metadataMap.containsKey(GoodReads)) { + metadata.setGoodreadsId(metadataMap.get(GoodReads).getGoodreadsId()); + } + } + if (!skipFields.isHardcoverId()) { + if (metadataMap.containsKey(Hardcover)) { + metadata.setHardcoverId(metadataMap.get(Hardcover).getHardcoverId()); + } + } + if (!skipFields.isGoogleId()) { + if (metadataMap.containsKey(Google)) { + metadata.setGoogleId(metadataMap.get(Google).getGoogleId()); + } + } + if (!skipFields.isComicvineId()) { + if (metadataMap.containsKey(Comicvine)) { + metadata.setComicvineId(metadataMap.get(Comicvine).getComicvineId()); + } + } + if (!skipFields.isMoods()) { + if (metadataMap.containsKey(Hardcover)) { + metadata.setMoods(metadataMap.get(Hardcover).getMoods()); + } + } + if (!skipFields.isTags()) { + if (metadataMap.containsKey(Hardcover)) { + metadata.setTags(metadataMap.get(Hardcover).getTags()); + } + } + if (!skipFields.isCategories()) { + if (refreshOptions.isMergeCategories()) { + metadata.setCategories(getAllCategories(metadataMap, fieldOptions.getCategories(), BookMetadata::getCategories)); + } else { + metadata.setCategories(resolveFieldAsList(metadataMap, fieldOptions.getCategories(), BookMetadata::getCategories)); + } + } List allReviews = metadataMap.values().stream() .filter(Objects::nonNull) @@ -383,187 +518,70 @@ public class MetadataRefreshService { metadata.setBookReviews(allReviews); } - if (metadataMap.containsKey(GoodReads)) { - metadata.setGoodreadsId(metadataMap.get(GoodReads).getGoodreadsId()); - } - if (metadataMap.containsKey(Hardcover)) { - metadata.setHardcoverId(metadataMap.get(Hardcover).getHardcoverId()); - } - if (metadataMap.containsKey(Google)) { - metadata.setGoogleId(metadataMap.get(Google).getGoogleId()); - } - if (metadataMap.containsKey(Comicvine)) { - metadata.setComicvineId(metadataMap.get(Comicvine).getComicvineId()); - } - - if (refreshOptions.isMergeCategories()) { - metadata.setCategories(getAllCategories(metadataMap, fieldOptions.getCategories(), BookMetadata::getCategories)); - metadata.setMoods(getAllMoods(metadataMap, fieldOptions.getMoods(), BookMetadata::getMoods)); - metadata.setTags(getAllTags(metadataMap, fieldOptions.getTags(), BookMetadata::getTags)); - } else { - metadata.setCategories(resolveFieldAsList(metadataMap, fieldOptions.getCategories(), BookMetadata::getCategories)); - metadata.setMoods(resolveFieldAsList(metadataMap, fieldOptions.getMoods(), BookMetadata::getMoods)); - metadata.setTags(resolveFieldAsList(metadataMap, fieldOptions.getTags(), BookMetadata::getTags)); - } - metadata.setThumbnailUrl(resolveFieldAsString(metadataMap, fieldOptions.getCover(), BookMetadata::getThumbnailUrl)); - - if (refreshOptions.getAllP4() != null) { - setOtherUnspecifiedMetadata(metadataMap, metadata, refreshOptions.getAllP4()); - } - if (refreshOptions.getAllP3() != null) { - setOtherUnspecifiedMetadata(metadataMap, metadata, refreshOptions.getAllP3()); - } - if (refreshOptions.getAllP2() != null) { - setOtherUnspecifiedMetadata(metadataMap, metadata, refreshOptions.getAllP2()); - } - if (refreshOptions.getAllP1() != null) { - setOtherUnspecifiedMetadata(metadataMap, metadata, refreshOptions.getAllP1()); - } - return metadata; } - protected void setOtherUnspecifiedMetadata(Map metadataMap, BookMetadata metadataCombined, MetadataProvider provider) { - if (metadataMap.containsKey(provider)) { - BookMetadata metadata = metadataMap.get(provider); - metadataCombined.setSubtitle(metadata.getSubtitle() != null ? metadata.getSubtitle() : metadataCombined.getSubtitle()); - metadataCombined.setPublisher(metadata.getPublisher() != null ? metadata.getPublisher() : metadataCombined.getPublisher()); - metadataCombined.setPublishedDate(metadata.getPublishedDate() != null ? metadata.getPublishedDate() : metadataCombined.getPublishedDate()); - metadataCombined.setIsbn10(metadata.getIsbn10() != null ? metadata.getIsbn10() : metadataCombined.getIsbn10()); - metadataCombined.setIsbn13(metadata.getIsbn13() != null ? metadata.getIsbn13() : metadataCombined.getIsbn13()); - metadataCombined.setAsin(metadata.getAsin() != null ? metadata.getAsin() : metadataCombined.getAsin()); - metadataCombined.setPageCount(metadata.getPageCount() != null ? metadata.getPageCount() : metadataCombined.getPageCount()); - metadataCombined.setLanguage(metadata.getLanguage() != null ? metadata.getLanguage() : metadataCombined.getLanguage()); - metadataCombined.setGoodreadsRating(metadata.getGoodreadsRating() != null ? metadata.getGoodreadsRating() : metadataCombined.getGoodreadsRating()); - metadataCombined.setGoodreadsReviewCount(metadata.getGoodreadsReviewCount() != null ? metadata.getGoodreadsReviewCount() : metadataCombined.getGoodreadsReviewCount()); - metadataCombined.setAmazonRating(metadata.getAmazonRating() != null ? metadata.getAmazonRating() : metadataCombined.getAmazonRating()); - metadataCombined.setAmazonReviewCount(metadata.getAmazonReviewCount() != null ? metadata.getAmazonReviewCount() : metadataCombined.getAmazonReviewCount()); - metadataCombined.setHardcoverRating(metadata.getHardcoverRating() != null ? metadata.getHardcoverRating() : metadataCombined.getHardcoverRating()); - metadataCombined.setHardcoverReviewCount(metadata.getHardcoverReviewCount() != null ? metadata.getHardcoverReviewCount() : metadataCombined.getHardcoverReviewCount()); - metadataCombined.setPersonalRating(metadata.getPersonalRating() != null ? metadata.getPersonalRating() : metadataCombined.getPersonalRating()); - metadataCombined.setSeriesName(metadata.getSeriesName() != null ? metadata.getSeriesName() : metadataCombined.getSeriesName()); - metadataCombined.setSeriesNumber(metadata.getSeriesNumber() != null ? metadata.getSeriesNumber() : metadataCombined.getSeriesNumber()); - metadataCombined.setSeriesTotal(metadata.getSeriesTotal() != null ? metadata.getSeriesTotal() : metadataCombined.getSeriesTotal()); - } + protected T resolveField(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, Function extractor) { + return resolveFieldWithProviders(metadataMap, fieldProvider, extractor, (value) -> value != null); } - @FunctionalInterface - public interface FieldValueExtractor { - String extract(BookMetadata metadata); + protected Integer resolveFieldAsInteger(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, Function fieldValueExtractor) { + return resolveFieldWithProviders(metadataMap, fieldProvider, fieldValueExtractor, (value) -> value != null); } - @FunctionalInterface - public interface FieldValueExtractorList { - Set extract(BookMetadata metadata); - } - - protected String resolveFieldAsString(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractor fieldValueExtractor) { - String value = null; - if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) { - String newValue = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4())); - if (newValue != null) value = newValue; - } - if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) { - String newValue = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3())); - if (newValue != null) value = newValue; - } - if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) { - String newValue = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2())); - if (newValue != null) value = newValue; - } - if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) { - String newValue = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1())); - if (newValue != null) value = newValue; - } - return value; + return resolveFieldWithProviders(metadataMap, fieldProvider, fieldValueExtractor::extract, (value) -> value != null); } - protected Set resolveFieldAsList(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) { - Set values = new HashSet<>(); - if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) { - Set newValues = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4())); - if (newValues != null && !newValues.isEmpty()) values = newValues; + return resolveFieldWithProviders(metadataMap, fieldProvider, fieldValueExtractor::extract, (value) -> value != null && !value.isEmpty()); + } + + private T resolveFieldWithProviders(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, Function extractor, Predicate isValidValue) { + if (fieldProvider == null) { + return null; } - if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) { - Set newValues = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3())); - if (newValues != null && !newValues.isEmpty()) values = newValues; + MetadataProvider[] providers = { + fieldProvider.getP4(), + fieldProvider.getP3(), + fieldProvider.getP2(), + fieldProvider.getP1() + }; + for (MetadataProvider provider : providers) { + if (provider != null && metadataMap.containsKey(provider)) { + T value = extractor.apply(metadataMap.get(provider)); + if (isValidValue.test(value)) { + return value; + } + } } - if (values.isEmpty() && fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) { - Set newValues = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2())); - if (newValues != null && !newValues.isEmpty()) values = newValues; - } - if (values.isEmpty() && fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) { - Set newValues = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1())); - if (newValues != null && !newValues.isEmpty()) values = newValues; - } - return values; + return null; } Set getAllCategories(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) { Set uniqueCategories = new HashSet<>(); - if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4())); - if (extracted != null) uniqueCategories.addAll(extracted); + if (fieldProvider == null) { + return uniqueCategories; } - if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3())); - if (extracted != null) uniqueCategories.addAll(extracted); - } - if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2())); - if (extracted != null) uniqueCategories.addAll(extracted); - } - if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1())); - if (extracted != null) uniqueCategories.addAll(extracted); - } - return new HashSet<>(uniqueCategories); - } - Set getAllMoods(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) { - Set uniqueMoods = new HashSet<>(); - if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4())); - if (extracted != null) uniqueMoods.addAll(extracted); - } - if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3())); - if (extracted != null) uniqueMoods.addAll(extracted); - } - if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2())); - if (extracted != null) uniqueMoods.addAll(extracted); - } - if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1())); - if (extracted != null) uniqueMoods.addAll(extracted); - } - return new HashSet<>(uniqueMoods); - } + MetadataProvider[] providers = { + fieldProvider.getP4(), + fieldProvider.getP3(), + fieldProvider.getP2(), + fieldProvider.getP1() + }; - Set getAllTags(Map metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) { - Set uniqueTags = new HashSet<>(); - if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4())); - if (extracted != null) uniqueTags.addAll(extracted); + for (MetadataProvider provider : providers) { + if (provider != null && metadataMap.containsKey(provider)) { + Set extracted = fieldValueExtractor.extract(metadataMap.get(provider)); + if (extracted != null) { + uniqueCategories.addAll(extracted); + } + } } - if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3())); - if (extracted != null) uniqueTags.addAll(extracted); - } - if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2())); - if (extracted != null) uniqueTags.addAll(extracted); - } - if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) { - Set extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1())); - if (extracted != null) uniqueTags.addAll(extracted); - } - return new HashSet<>(uniqueTags); - } + return uniqueCategories; + } protected Set getBookEntities(MetadataRefreshRequest request) { MetadataRefreshRequest.RefreshType refreshType = request.getRefreshType(); @@ -578,4 +596,4 @@ public class MetadataRefreshService { case BOOKS -> request.getBookIds(); }; } -} +} \ No newline at end of file diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/MetadataRefreshServiceTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/MetadataRefreshServiceTest.java index 1fce67766..fba06ea77 100644 --- a/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/MetadataRefreshServiceTest.java +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/MetadataRefreshServiceTest.java @@ -83,41 +83,69 @@ class MetadataRefreshServiceTest { } private void setupDefaultOptions() { - MetadataRefreshOptions.FieldProvider titleProvider = new MetadataRefreshOptions.FieldProvider( - null, null, MetadataProvider.Google, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider descriptionProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider authorsProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider categoriesProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider moodProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider tagProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider coverProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.GoodReads); + MetadataRefreshOptions.FieldProvider titleProvider = MetadataRefreshOptions.FieldProvider.builder() + .p3(MetadataProvider.Google) + .p1(MetadataProvider.GoodReads) + .build(); + MetadataRefreshOptions.FieldProvider descriptionProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider authorsProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.GoodReads) + .build(); + MetadataRefreshOptions.FieldProvider categoriesProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider moodProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider tagProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider coverProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.GoodReads) + .build(); - MetadataRefreshOptions.FieldOptions fieldOptions = new MetadataRefreshOptions.FieldOptions( - titleProvider, null, descriptionProvider, authorsProvider, null, null, - null, null, null, null, null, null, categoriesProvider, moodProvider, tagProvider, coverProvider); + MetadataRefreshOptions.FieldOptions fieldOptions = MetadataRefreshOptions.FieldOptions.builder() + .title(titleProvider) + .description(descriptionProvider) + .authors(authorsProvider) + .categories(categoriesProvider) + .moods(moodProvider) + .tags(tagProvider) + .cover(coverProvider) + .build(); - defaultOptions = new MetadataRefreshOptions( - null, MetadataProvider.GoodReads, MetadataProvider.Google, null, null, - true, false, false, fieldOptions); + MetadataRefreshOptions.SkipFields skipFields = MetadataRefreshOptions.SkipFields.builder().build(); + + defaultOptions = MetadataRefreshOptions.builder() + .refreshCovers(true) + .mergeCategories(false) + .reviewBeforeApply(false) + .fieldOptions(fieldOptions) + .skipFields(skipFields) + .build(); } private void setupLibraryOptions() { - MetadataRefreshOptions.FieldProvider titleProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); + MetadataRefreshOptions.FieldProvider titleProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); - MetadataRefreshOptions.FieldOptions fieldOptions = new MetadataRefreshOptions.FieldOptions( - titleProvider, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null); + MetadataRefreshOptions.FieldOptions fieldOptions = MetadataRefreshOptions.FieldOptions.builder() + .title(titleProvider) + .build(); - libraryOptions = new MetadataRefreshOptions( - 1L, MetadataProvider.Google, null, null, null, - false, true, true, fieldOptions); + MetadataRefreshOptions.SkipFields skipFields = MetadataRefreshOptions.SkipFields.builder().build(); + + libraryOptions = MetadataRefreshOptions.builder() + .libraryId(1L) + .refreshCovers(false) + .mergeCategories(true) + .reviewBeforeApply(true) + .fieldOptions(fieldOptions) + .skipFields(skipFields) + .build(); } private void setupAppSettings() { @@ -225,15 +253,21 @@ class MetadataRefreshServiceTest { @Test void testRefreshMetadata_WithRequestOptions_ShouldUseRequestOptions() { // Given - MetadataRefreshOptions.FieldProvider titleProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Hardcover); - MetadataRefreshOptions.FieldOptions fieldOptions = new MetadataRefreshOptions.FieldOptions( - titleProvider, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null); + MetadataRefreshOptions.FieldProvider titleProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Hardcover) + .build(); + MetadataRefreshOptions.FieldOptions fieldOptions = MetadataRefreshOptions.FieldOptions.builder() + .title(titleProvider) + .build(); + MetadataRefreshOptions.SkipFields skipFields = MetadataRefreshOptions.SkipFields.builder().build(); - MetadataRefreshOptions requestOptions = new MetadataRefreshOptions( - null, MetadataProvider.Hardcover, null, null, null, - true, false, false, fieldOptions); + MetadataRefreshOptions requestOptions = MetadataRefreshOptions.builder() + .refreshCovers(true) + .mergeCategories(false) + .reviewBeforeApply(false) + .fieldOptions(fieldOptions) + .skipFields(skipFields) + .build(); MetadataRefreshRequest request = MetadataRefreshRequest.builder() .refreshType(MetadataRefreshRequest.RefreshType.BOOKS) @@ -288,9 +322,15 @@ class MetadataRefreshServiceTest { @Test void testRefreshMetadata_WithReviewMode_ShouldCreateTaskAndProposals() throws JsonProcessingException { - MetadataRefreshOptions reviewOptions = new MetadataRefreshOptions( - null, MetadataProvider.GoodReads, MetadataProvider.Google, null, null, - true, false, true, defaultOptions.getFieldOptions()); + MetadataRefreshOptions.SkipFields skipFields = MetadataRefreshOptions.SkipFields.builder().build(); + + MetadataRefreshOptions reviewOptions = MetadataRefreshOptions.builder() + .refreshCovers(true) + .mergeCategories(false) + .reviewBeforeApply(true) + .fieldOptions(defaultOptions.getFieldOptions()) + .skipFields(skipFields) + .build(); MetadataRefreshRequest request = MetadataRefreshRequest.builder() .refreshType(MetadataRefreshRequest.RefreshType.BOOKS) @@ -449,28 +489,48 @@ class MetadataRefreshServiceTest { @Test void testBuildFetchMetadata_WithMergeCategories_ShouldMergeAllCategories() { - MetadataRefreshOptions.FieldProvider titleProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider descriptionProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider authorsProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider moodProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider tagProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); - MetadataRefreshOptions.FieldProvider categoriesProvider = new MetadataRefreshOptions.FieldProvider( - null, null, MetadataProvider.Google, MetadataProvider.GoodReads); - MetadataRefreshOptions.FieldProvider coverProvider = new MetadataRefreshOptions.FieldProvider( - null, null, null, MetadataProvider.Google); + MetadataRefreshOptions.FieldProvider titleProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider descriptionProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider authorsProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider moodProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider tagProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); + MetadataRefreshOptions.FieldProvider categoriesProvider = MetadataRefreshOptions.FieldProvider.builder() + .p3(MetadataProvider.Google) + .p1(MetadataProvider.GoodReads) + .build(); + MetadataRefreshOptions.FieldProvider coverProvider = MetadataRefreshOptions.FieldProvider.builder() + .p1(MetadataProvider.Google) + .build(); - MetadataRefreshOptions.FieldOptions fieldOptions = new MetadataRefreshOptions.FieldOptions( - titleProvider, null, descriptionProvider, authorsProvider, null, null, - null, null, null, null, null, null, categoriesProvider, moodProvider, tagProvider, coverProvider); + MetadataRefreshOptions.FieldOptions fieldOptions = MetadataRefreshOptions.FieldOptions.builder() + .title(titleProvider) + .description(descriptionProvider) + .authors(authorsProvider) + .categories(categoriesProvider) + .moods(moodProvider) + .tags(tagProvider) + .cover(coverProvider) + .build(); - MetadataRefreshOptions mergeOptions = new MetadataRefreshOptions( - null, MetadataProvider.GoodReads, MetadataProvider.Google, null, null, - true, true, false, fieldOptions); + MetadataRefreshOptions.SkipFields skipFields = MetadataRefreshOptions.SkipFields.builder().build(); + + MetadataRefreshOptions mergeOptions = MetadataRefreshOptions.builder() + .refreshCovers(true) + .mergeCategories(true) + .reviewBeforeApply(false) + .fieldOptions(fieldOptions) + .skipFields(skipFields) + .build(); Map metadataMap = new HashMap<>(); metadataMap.put(MetadataProvider.GoodReads, BookMetadata.builder() diff --git a/booklore-ui/src/app/metadata/book-metadata-center-component/metadata-editor/metadata-editor.component.html b/booklore-ui/src/app/metadata/book-metadata-center-component/metadata-editor/metadata-editor.component.html index 3021232ee..fd9c6044a 100644 --- a/booklore-ui/src/app/metadata/book-metadata-center-component/metadata-editor/metadata-editor.component.html +++ b/booklore-ui/src/app/metadata/book-metadata-center-component/metadata-editor/metadata-editor.component.html @@ -177,58 +177,56 @@
-
-
- -
-
- - -
- @if (!book.metadata!['moodsLocked']) { - - } - @if (book.metadata!['moodsLocked']) { - - } +
+ +
+
+ +
+ @if (!book.metadata!['moodsLocked']) { + + } + @if (book.metadata!['moodsLocked']) { + + }
-
-
- -
-
- - -
- @if (!book.metadata!['tagsLocked']) { - - } - @if (book.metadata!['tagsLocked']) { - - } +
+
+
+ +
+
+ +
+ @if (!book.metadata!['tagsLocked']) { + + } + @if (book.metadata!['tagsLocked']) { + + }
diff --git a/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html b/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html index 9b69814b9..773dc9865 100644 --- a/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html +++ b/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.html @@ -2,95 +2,100 @@ - - + + - - - - - - - - + + - - - - - @for (field of fields; track field) { - - + + + @for (field of nonProviderSpecificFields; track field) { + + +
Book Field + SkipMetadata Field 4th Priority + 3rd Priority + 2nd Priority + 1st Priority
- All Other Fields - - - Set All: + - + - + - +
{{ formatLabel(field) }}
+ + {{ formatLabel(field) }} @@ -100,6 +105,20 @@
+
+

Provider-Specific Fields

+

These fields are unique to specific providers and cannot have custom priority settings. Use the checkboxes to skip fetching these fields entirely.

+
+ @for (field of providerSpecificFields; track field) { +
+ + {{ formatLabel(field) }} +
+ } +
+
diff --git a/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.ts b/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.ts index e0a439aaf..c4a6e6864 100644 --- a/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.ts +++ b/booklore-ui/src/app/metadata/metadata-options-dialog/metadata-advanced-fetch-options/metadata-advanced-fetch-options.component.ts @@ -1,17 +1,11 @@ -import { - Component, EventEmitter, inject, Input, OnChanges, Output, SimpleChanges -} from '@angular/core'; -import {Select, SelectChangeEvent} from 'primeng/select'; +import {Component, EventEmitter, inject, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; +import {Select} from 'primeng/select'; import {FormsModule} from '@angular/forms'; import {Checkbox} from 'primeng/checkbox'; import {Button} from 'primeng/button'; import {MessageService} from 'primeng/api'; -import { - FieldOptions, - FieldProvider, - MetadataRefreshOptions -} from '../../model/request/metadata-refresh-options.model'; +import {FieldOptions, MetadataRefreshOptions} from '../../model/request/metadata-refresh-options.model'; import {Tooltip} from 'primeng/tooltip'; @Component({ @@ -30,25 +24,49 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { fields: (keyof FieldOptions)[] = [ 'title', 'subtitle', 'description', 'authors', 'publisher', 'publishedDate', 'seriesName', 'seriesNumber', 'seriesTotal', 'isbn13', 'isbn10', - 'language', 'categories', 'cover' + 'language', 'categories', 'cover', 'pageCount', + 'asin', 'goodreadsId', 'comicvineId', 'hardcoverId', 'googleId', + 'amazonRating', 'amazonReviewCount', 'goodreadsRating', 'goodreadsReviewCount', + 'hardcoverRating', 'hardcoverReviewCount', 'moods', 'tags' ]; + + providerSpecificFields: (keyof FieldOptions)[] = [ + 'asin', 'goodreadsId', 'comicvineId', 'hardcoverId', 'googleId', + 'amazonRating', 'amazonReviewCount', 'goodreadsRating', 'goodreadsReviewCount', + 'hardcoverRating', 'hardcoverReviewCount', 'moods', 'tags' + ]; + + nonProviderSpecificFields: (keyof FieldOptions)[] = [ + 'title', 'subtitle', 'description', 'authors', 'publisher', 'publishedDate', + 'seriesName', 'seriesNumber', 'seriesTotal', 'isbn13', 'isbn10', + 'language', 'categories', 'cover', 'pageCount', + ]; + providers: string[] = ['Amazon', 'Google', 'GoodReads', 'Hardcover', 'Comicvine', 'Douban']; + providersWithClear: string[] = ['Clear All', 'Amazon', 'Google', 'GoodReads', 'Hardcover', 'Comicvine', 'Douban']; refreshCovers: boolean = false; mergeCategories: boolean = false; reviewBeforeApply: boolean = false; - allP1 = {placeholder: 'Set All', value: null as string | null}; - allP2 = {placeholder: 'Set All', value: null as string | null}; - allP3 = {placeholder: 'Set All', value: null as string | null}; - allP4 = {placeholder: 'Set All', value: null as string | null}; - fieldOptions: FieldOptions = this.initializeFieldOptions(); + skipFields: Record = this.initializeSkipFields(); + + bulkP1: string | null = null; + bulkP2: string | null = null; + bulkP3: string | null = null; + bulkP4: string | null = null; private messageService = inject(MessageService); private justSubmitted = false; + private providerSpecificFieldsList = [ + 'asin', 'goodreadsId', 'comicvineId', 'hardcoverId', 'googleId', + 'amazonRating', 'amazonReviewCount', 'goodreadsRating', 'goodreadsReviewCount', + 'hardcoverRating', 'hardcoverReviewCount', 'moods', 'tags' + ]; + private initializeFieldOptions(): FieldOptions { return this.fields.reduce((acc, field) => { acc[field] = {p1: null, p2: null, p3: null, p4: null}; @@ -56,6 +74,13 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { }, {} as FieldOptions); } + private initializeSkipFields(): Record { + return this.fields.reduce((acc, field) => { + acc[field] = false; + return acc; + }, {} as Record); + } + ngOnChanges(changes: SimpleChanges): void { if (changes['currentMetadataOptions'] && this.currentMetadataOptions && !this.justSubmitted) { this.refreshCovers = this.currentMetadataOptions.refreshCovers || false; @@ -72,10 +97,11 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { } this.fieldOptions = backendFieldOptions; - this.allP1 = {placeholder: 'Set All', value: this.currentMetadataOptions.allP1 || null}; - this.allP2 = {placeholder: 'Set All', value: this.currentMetadataOptions.allP2 || null}; - this.allP3 = {placeholder: 'Set All', value: this.currentMetadataOptions.allP3 || null}; - this.allP4 = {placeholder: 'Set All', value: this.currentMetadataOptions.allP4 || null}; + if (this.currentMetadataOptions.skipFields) { + this.skipFields = {...this.skipFields, ...this.currentMetadataOptions.skipFields}; + } else { + this.skipFields = this.initializeSkipFields(); + } } } @@ -92,14 +118,10 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { return cloned; } - syncProvider(event: SelectChangeEvent, providerType: keyof FieldProvider) { - for (const field of Object.keys(this.fieldOptions)) { - this.fieldOptions[field as keyof FieldOptions][providerType] = event.value; - } - } - submit() { - const allFieldsHaveProvider = Object.values(this.fieldOptions).every(opt => + const allFieldsHaveProvider = Object.entries(this.fieldOptions).every(([field, opt]) => + this.skipFields[field as keyof FieldOptions] || + this.isProviderSpecificField(field as keyof FieldOptions) || opt.p1 !== null || opt.p2 !== null || opt.p3 !== null || opt.p4 !== null ); @@ -108,14 +130,11 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { const metadataRefreshOptions: MetadataRefreshOptions = { libraryId: null, - allP1: this.allP1.value, - allP2: this.allP2.value, - allP3: this.allP3.value, - allP4: this.allP4.value, refreshCovers: this.refreshCovers, mergeCategories: this.mergeCategories, reviewBeforeApply: this.reviewBeforeApply, - fieldOptions: this.fieldOptions + fieldOptions: this.fieldOptions, + skipFields: this.skipFields }; this.metadataOptionsSubmitted.emit(metadataRefreshOptions); @@ -127,18 +146,41 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { this.messageService.add({ severity: 'error', summary: 'Error', - detail: 'At least one provider (P1–P4) must be selected for each book field.', + detail: 'At least one provider (P1–P4) must be selected for each non-skipped book field.', life: 5000 }); } } + setBulkProvider(priority: 'p1' | 'p2' | 'p3' | 'p4', provider: string | null): void { + if (!provider) return; + + const value = provider === 'Clear All' ? null : provider; + + for (const field of this.nonProviderSpecificFields) { + if (!this.skipFields[field]) { + this.fieldOptions[field][priority] = value; + } + } + + switch (priority) { + case 'p1': + this.bulkP1 = null; + break; + case 'p2': + this.bulkP2 = null; + break; + case 'p3': + this.bulkP3 = null; + break; + case 'p4': + this.bulkP4 = null; + break; + } + } + reset() { this.justSubmitted = false; - this.allP1.value = null; - this.allP2.value = null; - this.allP3.value = null; - this.allP4.value = null; for (const field of Object.keys(this.fieldOptions)) { this.fieldOptions[field as keyof FieldOptions] = { p1: null, @@ -147,9 +189,53 @@ export class MetadataAdvancedFetchOptionsComponent implements OnChanges { p4: null }; } + this.skipFields = this.initializeSkipFields(); + + // Reset bulk selectors + this.bulkP1 = null; + this.bulkP2 = null; + this.bulkP3 = null; + this.bulkP4 = null; } formatLabel(field: string): string { - return field.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()).trim(); + const fieldLabels: Record = { + 'title': 'Title', + 'subtitle': 'Subtitle', + 'description': 'Description', + 'authors': 'Authors', + 'publisher': 'Publisher', + 'publishedDate': 'Published Date', + 'seriesName': 'Series Name', + 'seriesNumber': 'Series Number', + 'seriesTotal': 'Series Total', + 'isbn13': 'ISBN-13', + 'isbn10': 'ISBN-10', + 'language': 'Language', + 'categories': 'Genres', + 'cover': 'Cover Image', + 'pageCount': 'Page Count', + 'rating': 'Rating', + 'reviewCount': 'Review Count', + 'asin': 'Amazon ASIN', + 'goodreadsId': 'Goodreads ID', + 'comicvineId': 'Comicvine ID', + 'hardcoverId': 'Hardcover ID', + 'googleId': 'Google Books ID', + 'amazonRating': 'Amazon Rating', + 'amazonReviewCount': 'Amazon Review Count', + 'goodreadsRating': 'Goodreads Rating', + 'goodreadsReviewCount': 'Goodreads Review Count', + 'hardcoverRating': 'Hardcover Rating', + 'hardcoverReviewCount': 'Hardcover Review Count', + 'moods': 'Moods (Hardcover)', + 'tags': 'Tags (Hardcover)' + }; + + return fieldLabels[field] || field.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()).trim(); + } + + isProviderSpecificField(field: keyof FieldOptions): boolean { + return this.providerSpecificFieldsList.includes(field as string); } } diff --git a/booklore-ui/src/app/metadata/model/request/metadata-refresh-options.model.ts b/booklore-ui/src/app/metadata/model/request/metadata-refresh-options.model.ts index 550556602..737993f72 100644 --- a/booklore-ui/src/app/metadata/model/request/metadata-refresh-options.model.ts +++ b/booklore-ui/src/app/metadata/model/request/metadata-refresh-options.model.ts @@ -1,13 +1,10 @@ export interface MetadataRefreshOptions { libraryId: number | null; - allP4: string | null; - allP3: string | null; - allP2: string | null; - allP1: string | null; refreshCovers: boolean; mergeCategories: boolean; reviewBeforeApply: boolean; fieldOptions?: FieldOptions; + skipFields?: Record; } export interface FieldProvider { @@ -32,4 +29,18 @@ export interface FieldOptions { isbn13: FieldProvider; isbn10: FieldProvider; language: FieldProvider; + pageCount: FieldProvider; + asin: FieldProvider; + goodreadsId: FieldProvider; + comicvineId: FieldProvider; + hardcoverId: FieldProvider; + googleId: FieldProvider; + amazonRating: FieldProvider; + amazonReviewCount: FieldProvider; + goodreadsRating: FieldProvider; + goodreadsReviewCount: FieldProvider; + hardcoverRating: FieldProvider; + hardcoverReviewCount: FieldProvider; + moods: FieldProvider; + tags: FieldProvider; } diff --git a/booklore-ui/src/app/settings/library-metadata-settings-component/library-metadata-settings.component.ts b/booklore-ui/src/app/settings/library-metadata-settings-component/library-metadata-settings.component.ts index 9a86741c5..3a0d82484 100644 --- a/booklore-ui/src/app/settings/library-metadata-settings-component/library-metadata-settings.component.ts +++ b/booklore-ui/src/app/settings/library-metadata-settings-component/library-metadata-settings.component.ts @@ -184,10 +184,6 @@ export class LibraryMetadataSettingsComponent implements OnInit { private getDefaultMetadataOptions(): MetadataRefreshOptions { return { libraryId: null, - allP1: null, - allP2: null, - allP3: null, - allP4: null, refreshCovers: false, mergeCategories: false, reviewBeforeApply: false, @@ -205,9 +201,22 @@ export class LibraryMetadataSettingsComponent implements OnInit { isbn10: {p1: null, p2: null, p3: null, p4: null}, language: {p1: null, p2: null, p3: null, p4: null}, categories: {p1: null, p2: null, p3: null, p4: null}, - cover: {p1: null, p2: null, p3: null, p4: null} + cover: {p1: null, p2: null, p3: null, p4: null}, + pageCount: {p1: null, p2: null, p3: null, p4: null}, + asin: {p1: null, p2: null, p3: null, p4: null}, + goodreadsId: {p1: null, p2: null, p3: null, p4: null}, + comicvineId: {p1: null, p2: null, p3: null, p4: null}, + hardcoverId: {p1: null, p2: null, p3: null, p4: null}, + googleId: {p1: null, p2: null, p3: null, p4: null}, + amazonRating: {p1: null, p2: null, p3: null, p4: null}, + amazonReviewCount: {p1: null, p2: null, p3: null, p4: null}, + goodreadsRating: {p1: null, p2: null, p3: null, p4: null}, + goodreadsReviewCount: {p1: null, p2: null, p3: null, p4: null}, + hardcoverRating: {p1: null, p2: null, p3: null, p4: null}, + hardcoverReviewCount: {p1: null, p2: null, p3: null, p4: null}, + moods: {p1: null, p2: null, p3: null, p4: null}, + tags: {p1: null, p2: null, p3: null, p4: null} } }; } } -