mirror of
https://github.com/adityachandelgit/BookLore.git
synced 2026-03-16 16:42:08 -05:00
Enhance metadata options to allow skipping fields during fetch and support extended field options (#1263)
This commit is contained in:
+59
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+75
-61
@@ -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() {
|
||||
|
||||
+184
-166
@@ -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<BookMetadata> createInterruptibleMetadataFuture(java.util.function.Supplier<BookMetadata> metadataSupplier) {
|
||||
private CompletableFuture<BookMetadata> createInterruptibleMetadataFuture(Supplier<BookMetadata> 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<MetadataProvider, BookMetadata> 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<BookReview> 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<MetadataProvider, BookMetadata> 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> T resolveField(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, Function<BookMetadata, T> extractor) {
|
||||
return resolveFieldWithProviders(metadataMap, fieldProvider, extractor, (value) -> value != null);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FieldValueExtractor {
|
||||
String extract(BookMetadata metadata);
|
||||
protected Integer resolveFieldAsInteger(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, Function<BookMetadata, Integer> fieldValueExtractor) {
|
||||
return resolveFieldWithProviders(metadataMap, fieldProvider, fieldValueExtractor, (value) -> value != null);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FieldValueExtractorList {
|
||||
Set<String> extract(BookMetadata metadata);
|
||||
}
|
||||
|
||||
|
||||
protected String resolveFieldAsString(Map<MetadataProvider, BookMetadata> 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<String> resolveFieldAsList(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) {
|
||||
Set<String> values = new HashSet<>();
|
||||
if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) {
|
||||
Set<String> 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> T resolveFieldWithProviders(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, Function<BookMetadata, T> extractor, Predicate<T> isValidValue) {
|
||||
if (fieldProvider == null) {
|
||||
return null;
|
||||
}
|
||||
if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) {
|
||||
Set<String> 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<String> 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<String> newValues = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1()));
|
||||
if (newValues != null && !newValues.isEmpty()) values = newValues;
|
||||
}
|
||||
return values;
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> getAllCategories(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) {
|
||||
Set<String> uniqueCategories = new HashSet<>();
|
||||
if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) {
|
||||
Set<String> 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<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3()));
|
||||
if (extracted != null) uniqueCategories.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2()));
|
||||
if (extracted != null) uniqueCategories.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1()));
|
||||
if (extracted != null) uniqueCategories.addAll(extracted);
|
||||
}
|
||||
return new HashSet<>(uniqueCategories);
|
||||
}
|
||||
|
||||
Set<String> getAllMoods(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) {
|
||||
Set<String> uniqueMoods = new HashSet<>();
|
||||
if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4()));
|
||||
if (extracted != null) uniqueMoods.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3()));
|
||||
if (extracted != null) uniqueMoods.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2()));
|
||||
if (extracted != null) uniqueMoods.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) {
|
||||
Set<String> 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<String> getAllTags(Map<MetadataProvider, BookMetadata> metadataMap, MetadataRefreshOptions.FieldProvider fieldProvider, FieldValueExtractorList fieldValueExtractor) {
|
||||
Set<String> uniqueTags = new HashSet<>();
|
||||
if (fieldProvider.getP4() != null && metadataMap.containsKey(fieldProvider.getP4())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP4()));
|
||||
if (extracted != null) uniqueTags.addAll(extracted);
|
||||
for (MetadataProvider provider : providers) {
|
||||
if (provider != null && metadataMap.containsKey(provider)) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(provider));
|
||||
if (extracted != null) {
|
||||
uniqueCategories.addAll(extracted);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fieldProvider.getP3() != null && metadataMap.containsKey(fieldProvider.getP3())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP3()));
|
||||
if (extracted != null) uniqueTags.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP2() != null && metadataMap.containsKey(fieldProvider.getP2())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP2()));
|
||||
if (extracted != null) uniqueTags.addAll(extracted);
|
||||
}
|
||||
if (fieldProvider.getP1() != null && metadataMap.containsKey(fieldProvider.getP1())) {
|
||||
Set<String> extracted = fieldValueExtractor.extract(metadataMap.get(fieldProvider.getP1()));
|
||||
if (extracted != null) uniqueTags.addAll(extracted);
|
||||
}
|
||||
return new HashSet<>(uniqueTags);
|
||||
}
|
||||
|
||||
return uniqueCategories;
|
||||
}
|
||||
|
||||
protected Set<Long> getBookEntities(MetadataRefreshRequest request) {
|
||||
MetadataRefreshRequest.RefreshType refreshType = request.getRefreshType();
|
||||
@@ -578,4 +596,4 @@ public class MetadataRefreshService {
|
||||
case BOOKS -> request.getBookIds();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
-59
@@ -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<MetadataProvider, BookMetadata> metadataMap = new HashMap<>();
|
||||
metadataMap.put(MetadataProvider.GoodReads, BookMetadata.builder()
|
||||
|
||||
+46
-48
@@ -177,58 +177,56 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row w-full gap-4 mt-2 pb-1">
|
||||
<div class="flex flex-col md:flex-row w-full gap-4 mt-2 pb-1 md:basis-[50%]">
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<label class="text-sm" for="moods">Moods</label>
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="w-full">
|
||||
<p-autoComplete
|
||||
class="w-full"
|
||||
formControlName="moods"
|
||||
[multiple]="true"
|
||||
[dropdown]="false"
|
||||
[suggestions]="filteredMoods"
|
||||
[forceSelection]="false"
|
||||
[showClear]="true"
|
||||
(completeMethod)="filterMoods($event)"
|
||||
(onKeyUp)="onAutoCompleteKeyUp('moods', $event)"
|
||||
(onSelect)="onAutoCompleteSelect('moods', $event)">
|
||||
</p-autoComplete>
|
||||
</div>
|
||||
@if (!book.metadata!['moodsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock-open" [outlined]="true" (onClick)="toggleLock('moods')" severity="success"></p-button>
|
||||
}
|
||||
@if (book.metadata!['moodsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock" [outlined]="true" (onClick)="toggleLock('moods')" severity="warn"></p-button>
|
||||
}
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<label class="text-sm" for="moods">Moods</label>
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="w-full">
|
||||
<p-autoComplete
|
||||
class="w-full"
|
||||
formControlName="moods"
|
||||
[multiple]="true"
|
||||
[dropdown]="false"
|
||||
[suggestions]="filteredMoods"
|
||||
[forceSelection]="false"
|
||||
[showClear]="true"
|
||||
(completeMethod)="filterMoods($event)"
|
||||
(onKeyUp)="onAutoCompleteKeyUp('moods', $event)"
|
||||
(onSelect)="onAutoCompleteSelect('moods', $event)">
|
||||
</p-autoComplete>
|
||||
</div>
|
||||
@if (!book.metadata!['moodsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock-open" [outlined]="true" (onClick)="toggleLock('moods')" severity="success"></p-button>
|
||||
}
|
||||
@if (book.metadata!['moodsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock" [outlined]="true" (onClick)="toggleLock('moods')" severity="warn"></p-button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row w-full gap-4 mt-2 pb-1 md:basis-[50%]">
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<label class="text-sm" for="tags">Tags</label>
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="w-full">
|
||||
<p-autoComplete
|
||||
class="w-full"
|
||||
formControlName="tags"
|
||||
[multiple]="true"
|
||||
[dropdown]="false"
|
||||
[suggestions]="filteredTags"
|
||||
[forceSelection]="false"
|
||||
[showClear]="true"
|
||||
(completeMethod)="filterTags($event)"
|
||||
(onKeyUp)="onAutoCompleteKeyUp('tags', $event)"
|
||||
(onSelect)="onAutoCompleteSelect('tags', $event)">
|
||||
</p-autoComplete>
|
||||
</div>
|
||||
@if (!book.metadata!['tagsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock-open" [outlined]="true" (onClick)="toggleLock('tags')" severity="success"></p-button>
|
||||
}
|
||||
@if (book.metadata!['tagsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock" [outlined]="true" (onClick)="toggleLock('tags')" severity="warn"></p-button>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row w-full gap-4 mt-2 pb-1">
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<label class="text-sm" for="tags">Tags</label>
|
||||
<div class="flex justify-between items-center gap-2">
|
||||
<div class="w-full">
|
||||
<p-autoComplete
|
||||
class="w-full"
|
||||
formControlName="tags"
|
||||
[multiple]="true"
|
||||
[dropdown]="false"
|
||||
[suggestions]="filteredTags"
|
||||
[forceSelection]="false"
|
||||
[showClear]="true"
|
||||
(completeMethod)="filterTags($event)"
|
||||
(onKeyUp)="onAutoCompleteKeyUp('tags', $event)"
|
||||
(onSelect)="onAutoCompleteSelect('tags', $event)">
|
||||
</p-autoComplete>
|
||||
</div>
|
||||
@if (!book.metadata!['tagsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock-open" [outlined]="true" (onClick)="toggleLock('tags')" severity="success"></p-button>
|
||||
}
|
||||
@if (book.metadata!['tagsLocked']) {
|
||||
<p-button size="small" icon="pi pi-lock" [outlined]="true" (onClick)="toggleLock('tags')" severity="warn"></p-button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+53
-34
@@ -2,95 +2,100 @@
|
||||
<table class="min-w-full table-auto border-collapse custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-gray-300">Book Field</th>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-gray-300">
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-zinc-300">Skip</th>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-zinc-300">Metadata Field</th>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-zinc-300">
|
||||
4th Priority
|
||||
<i class="pi pi-question-circle ml-1 text-xs"
|
||||
pTooltip="Last fallback option - only used if 1st, 2nd, and 3rd priorities fail or are empty"
|
||||
tooltipPosition="top"></i>
|
||||
</th>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-gray-300">
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-zinc-300">
|
||||
3rd Priority
|
||||
<i class="pi pi-question-circle ml-1 text-xs"
|
||||
pTooltip="Third choice - used if 1st and 2nd priorities don't have data"
|
||||
tooltipPosition="top"></i>
|
||||
</th>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-gray-300">
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-zinc-300">
|
||||
2nd Priority
|
||||
<i class="pi pi-question-circle ml-1 text-xs"
|
||||
pTooltip="Second choice - used if 1st priority doesn't have data"
|
||||
tooltipPosition="top"></i>
|
||||
</th>
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-gray-300">
|
||||
<th class="px-4 py-1.5 text-left font-semibold text-zinc-300">
|
||||
1st Priority
|
||||
<i class="pi pi-question-circle ml-1 text-xs"
|
||||
pTooltip="First choice - always tried first for this field"
|
||||
tooltipPosition="top"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="text-sm px-4 py-1.5 text-gray-300">
|
||||
All Other Fields
|
||||
<i class="pi pi-question-circle ml-1 text-xs"
|
||||
pTooltip="Quick way to set the same provider priority for all fields at once"
|
||||
tooltipPosition="right"></i>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="allP4.value"
|
||||
(onChange)="syncProvider($event, 'p4')"
|
||||
placeholder="Select All" appendTo="body"
|
||||
<td class="px-4 py-2"></td>
|
||||
<td class="px-4 py-2 text-sm text-zinc-400 italic">Set All:</td>
|
||||
<td class="px-4 py-2">
|
||||
<p-select [options]="providersWithClear" [(ngModel)]="bulkP4"
|
||||
(ngModelChange)="setBulkProvider('p4', $event)"
|
||||
placeholder="Set all P4" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="allP3.value"
|
||||
(onChange)="syncProvider($event, 'p3')"
|
||||
placeholder="Select All" appendTo="body"
|
||||
<td class="px-4 py-2">
|
||||
<p-select [options]="providersWithClear" [(ngModel)]="bulkP3"
|
||||
(ngModelChange)="setBulkProvider('p3', $event)"
|
||||
placeholder="Set all P3" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="allP2.value"
|
||||
(onChange)="syncProvider($event, 'p2')"
|
||||
placeholder="Select All" appendTo="body"
|
||||
<td class="px-4 py-2">
|
||||
<p-select [options]="providersWithClear" [(ngModel)]="bulkP2"
|
||||
(ngModelChange)="setBulkProvider('p2', $event)"
|
||||
placeholder="Set all P2" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="allP1.value"
|
||||
(onChange)="syncProvider($event, 'p1')"
|
||||
placeholder="Select All" appendTo="body"
|
||||
<td class="px-4 py-2">
|
||||
<p-select [options]="providersWithClear" [(ngModel)]="bulkP1"
|
||||
(ngModelChange)="setBulkProvider('p1', $event)"
|
||||
placeholder="Set all P1" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@for (field of fields; track field) {
|
||||
<tr [hidden]="field === 'cover' && !refreshCovers">
|
||||
<td class="text-sm px-4 py-1.5 text-gray-300">{{ formatLabel(field) }}</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (field of nonProviderSpecificFields; track field) {
|
||||
<tr [hidden]="field === 'cover' && !refreshCovers"
|
||||
[class.opacity-50]="skipFields[field]">
|
||||
<td class="px-4 py-1.5">
|
||||
<p-checkbox [(ngModel)]="skipFields[field]" [binary]="true"
|
||||
pTooltip="Skip this field during metadata fetch"
|
||||
tooltipPosition="top"></p-checkbox>
|
||||
</td>
|
||||
<td class="px-4 py-1.5 text-zinc-200">{{ formatLabel(field) }}</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="fieldOptions[field].p4"
|
||||
[disabled]="skipFields[field]"
|
||||
placeholder="Unset" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="fieldOptions[field].p3"
|
||||
[disabled]="skipFields[field]"
|
||||
placeholder="Unset" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="fieldOptions[field].p2"
|
||||
[disabled]="skipFields[field]"
|
||||
placeholder="Unset" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
</td>
|
||||
<td class="px-4 py-1.5">
|
||||
<p-select [options]="providers" [(ngModel)]="fieldOptions[field].p1"
|
||||
[disabled]="skipFields[field]"
|
||||
placeholder="Unset" appendTo="body"
|
||||
class="w-full" size="small">
|
||||
</p-select>
|
||||
@@ -100,6 +105,20 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-zinc-300">Provider-Specific Fields</h3>
|
||||
<p class="text-sm text-zinc-400">These fields are unique to specific providers and cannot have custom priority settings. Use the checkboxes to skip fetching these fields entirely.</p>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
@for (field of providerSpecificFields; track field) {
|
||||
<div class="flex items-center space-x-3 p-3 border border-zinc-600 rounded-lg">
|
||||
<p-checkbox [(ngModel)]="skipFields[field]" [binary]="true"
|
||||
pTooltip="Skip this field during metadata fetch"
|
||||
tooltipPosition="top"></p-checkbox>
|
||||
<span class="text-sm text-zinc-300">{{ formatLabel(field) }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center justify-between gap-4 w-full">
|
||||
|
||||
|
||||
+123
-37
@@ -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<keyof FieldOptions, boolean> = 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<keyof FieldOptions, boolean> {
|
||||
return this.fields.reduce((acc, field) => {
|
||||
acc[field] = false;
|
||||
return acc;
|
||||
}, {} as Record<keyof FieldOptions, boolean>);
|
||||
}
|
||||
|
||||
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<string, string> = {
|
||||
'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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<keyof FieldOptions, boolean>;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+15
-6
@@ -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}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user