Merge pull request #1207 from booklore-app/develop

Merge develop into master for the release
This commit is contained in:
Aditya Chandel
2025-09-25 13:59:25 -06:00
committed by GitHub
20 changed files with 183 additions and 125 deletions

View File

@@ -4,7 +4,11 @@ FROM node:22-alpine AS angular-build
WORKDIR /angular-app
COPY ./booklore-ui/package.json ./booklore-ui/package-lock.json ./
RUN npm install --force
RUN npm config set registry http://registry.npmjs.org/ \
&& npm config set fetch-retries 5 \
&& npm config set fetch-retry-mintimeout 20000 \
&& npm config set fetch-retry-maxtimeout 120000 \
&& npm install --force
COPY ./booklore-ui /angular-app/
RUN npm run build --configuration=production

View File

@@ -3,9 +3,11 @@ package com.adityachandel.booklore.mapper;
import com.adityachandel.booklore.model.dto.Shelf;
import com.adityachandel.booklore.model.entity.ShelfEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface ShelfMapper {
@Mapping(source = "user.id", target = "userId")
Shelf toShelf(ShelfEntity shelfEntity);
}

View File

@@ -1,5 +1,6 @@
package com.adityachandel.booklore.mapper.v2;
import com.adityachandel.booklore.mapper.ShelfMapper;
import com.adityachandel.booklore.model.dto.Book;
import com.adityachandel.booklore.model.dto.BookMetadata;
import com.adityachandel.booklore.model.dto.LibraryPath;
@@ -11,7 +12,7 @@ import org.mapstruct.Named;
import java.util.Set;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring")
@Mapper(componentModel = "spring", uses = ShelfMapper.class)
public interface BookMapperV2 {
@Mapping(source = "library.id", target = "libraryId")

View File

@@ -12,7 +12,5 @@ import lombok.NoArgsConstructor;
public class MetadataPersistenceSettings {
private boolean saveToOriginalFile;
private boolean convertCbrCb7ToCbz;
private boolean backupMetadata;
private boolean backupCover;
private boolean moveFilesToLibraryPattern;
}

View File

@@ -142,6 +142,7 @@ public class BookService {
UserBookProgressEntity userProgress = userBookProgressRepository.findByUserIdAndBookId(user.getId(), bookId).orElse(new UserBookProgressEntity());
Book book = bookMapper.toBook(bookEntity);
book.setShelves(filterShelvesByUserId(book.getShelves(), user.getId()));
book.setLastReadTime(userProgress.getLastReadTime());
if (bookEntity.getBookType() == BookFileType.PDF) {
@@ -482,6 +483,7 @@ public class BookService {
return bookEntities.stream().map(bookEntity -> {
Book book = bookMapper.toBook(bookEntity);
book.setShelves(filterShelvesByUserId(book.getShelves(), user.getId()));
book.setFilePath(FileUtils.getBookFullPath(bookEntity));
enrichBookWithProgress(book, progressMap.get(bookEntity.getId()));
return book;
@@ -637,4 +639,11 @@ public class BookService {
}
}
private Set<Shelf> filterShelvesByUserId(Set<Shelf> shelves, Long userId) {
if (shelves == null) return Collections.emptySet();
return shelves.stream()
.filter(shelf -> userId.equals(shelf.getUserId()))
.collect(Collectors.toSet());
}
}

View File

@@ -184,8 +184,6 @@ public class SettingPersistenceHelper {
return MetadataPersistenceSettings.builder()
.saveToOriginalFile(false)
.convertCbrCb7ToCbz(false)
.backupMetadata(false)
.backupCover(false)
.moveFilesToLibraryPattern(false)
.build();
}

View File

@@ -1,15 +1,14 @@
package com.adityachandel.booklore.service.kobo;
import com.adityachandel.booklore.util.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Collectors;
@@ -17,13 +16,22 @@ import java.util.stream.Collectors;
@Service
public class KepubConversionService {
private static final String KEPUBIFY_BINARY_MACOS_ARM64 = "/bin/kepubify-darwin-arm64";
private static final String KEPUBIFY_BINARY_LINUX_X64 = "/bin/kepubify-linux-64bit";
@Autowired
private FileService fileService;
private static final String KEPUBIFY_GITHUB_BASE_URL = "https://github.com/booklore-app/booklore-tools/raw/main/kepubify/";
private static final String BIN_DARWIN_ARM64 = "kepubify-darwin-arm64";
private static final String BIN_DARWIN_X64 = "kepubify-darwin-64bit";
private static final String BIN_LINUX_X64 = "kepubify-linux-64bit";
private static final String BIN_LINUX_X86 = "kepubify-linux-32bit";
private static final String BIN_LINUX_ARM = "kepubify-linux-arm";
private static final String BIN_LINUX_ARM64 = "kepubify-linux-arm64";
public File convertEpubToKepub(File epubFile, File tempDir) throws IOException, InterruptedException {
validateInputs(epubFile);
Path kepubifyBinary = setupKepubifyBinary(tempDir);
Path kepubifyBinary = setupKepubifyBinary();
File outputFile = executeKepubifyConversion(epubFile, tempDir, kepubifyBinary);
log.info("Successfully converted {} to {} (size: {} bytes)", epubFile.getName(), outputFile.getName(), outputFile.length());
@@ -36,33 +44,58 @@ public class KepubConversionService {
}
}
private Path setupKepubifyBinary(File tempDir) throws IOException {
Path tempKepubify = tempDir.toPath().resolve("kepubify");
String resourcePath = getKepubifyResourcePath();
try (InputStream in = getClass().getResourceAsStream(resourcePath)) {
if (in == null) {
throw new IOException("Resource not found: " + resourcePath);
}
Files.copy(in, tempKepubify, StandardCopyOption.REPLACE_EXISTING);
private Path setupKepubifyBinary() throws IOException {
String binaryName = getKepubifyBinaryName();
String toolsDirPath = fileService.getToolsKepubifyPath();
Path toolsDir = Paths.get(toolsDirPath);
if (!Files.exists(toolsDir)) {
Files.createDirectories(toolsDir);
}
tempKepubify.toFile().setExecutable(true);
return tempKepubify;
Path binaryPath = toolsDir.resolve(binaryName);
if (!Files.exists(binaryPath)) {
String downloadUrl = KEPUBIFY_GITHUB_BASE_URL + binaryName;
log.info("Downloading kepubify binary '{}' from {}", binaryName, downloadUrl);
try (InputStream in = java.net.URI.create(downloadUrl).toURL().openStream()) {
Files.copy(in, binaryPath, StandardCopyOption.REPLACE_EXISTING);
}
if (!binaryPath.toFile().setExecutable(true)) {
log.warn("Failed to set executable permission for '{}'", binaryPath.toAbsolutePath());
}
log.info("Downloaded kepubify binary to {}", binaryPath.toAbsolutePath());
} else {
if (!binaryPath.toFile().setExecutable(true)) {
log.warn("Failed to set executable permission for '{}'", binaryPath.toAbsolutePath());
}
log.debug("Using existing kepubify binary at {}", binaryPath.toAbsolutePath());
}
return binaryPath;
}
private String getKepubifyResourcePath() {
private String getKepubifyBinaryName() {
String osName = System.getProperty("os.name").toLowerCase();
String osArch = System.getProperty("os.arch").toLowerCase();
log.debug("Detected OS: {} ({})", osName, osArch);
if (osName.contains("mac") || osName.contains("darwin")) {
return KEPUBIFY_BINARY_MACOS_ARM64;
if (osArch.contains("arm") || osArch.contains("aarch64")) {
return BIN_DARWIN_ARM64;
} else {
return BIN_DARWIN_X64;
}
} else if (osName.contains("linux")) {
return KEPUBIFY_BINARY_LINUX_X64;
} else {
throw new IllegalStateException("Unsupported operating system: " + osName);
if (osArch.contains("arm64") || osArch.contains("aarch64")) {
return BIN_LINUX_ARM64;
} else if (osArch.contains("arm")) {
return BIN_LINUX_ARM;
} else if (osArch.contains("64")) {
return BIN_LINUX_X64;
} else if (osArch.contains("86")) {
return BIN_LINUX_X86;
}
}
throw new IllegalStateException("Unsupported operating system or architecture: " + osName + " / " + osArch);
}
private File executeKepubifyConversion(File epubFile, File tempDir, Path kepubifyBinary) throws IOException, InterruptedException {

View File

@@ -91,6 +91,9 @@ public class KoboResourcesComponent {
"image_host": "//cdn.kobo.com/book-images/",
"image_url_quality_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/{Quality}/{IsGreyscale}/image.jpg",
"image_url_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/false/image.jpg",
"instapaper_enabled": "True",
"instapaper_env_url": "https://www.instapaper.com/api/kobo",
"instapaper_link_account_start": "https://authorize.kobo.com/{region}/{language}/linkinstapaper",
"kobo_audiobooks_credit_redemption": "True",
"kobo_audiobooks_enabled": "True",
"kobo_audiobooks_orange_deal_enabled": "True",

View File

@@ -77,22 +77,8 @@ public class BookMetadataUpdater {
MetadataPersistenceSettings settings = appSettingService.getAppSettings().getMetadataPersistenceSettings();
boolean writeToFile = settings.isSaveToOriginalFile();
boolean convertCbrCb7ToCbz = settings.isConvertCbrCb7ToCbz();
boolean backupEnabled = settings.isBackupMetadata();
boolean backupCover = settings.isBackupCover();
BookFileType bookType = bookEntity.getBookType();
if (writeToFile && backupEnabled && (bookType != BookFileType.CBX || convertCbrCb7ToCbz)) {
try {
MetadataBackupRestore service = metadataBackupRestoreFactory.getService(bookType);
if (service != null) {
boolean coverBackup = bookType == BookFileType.EPUB && backupCover;
service.backupEmbeddedMetadataIfNotExists(bookEntity, coverBackup);
}
} catch (Exception e) {
log.warn("Metadata backup failed for book ID {}: {}", bookId, e.getMessage());
}
}
updateBasicFields(newMetadata, metadata, clearFlags);
updateAuthorsIfNeeded(newMetadata, metadata, clearFlags);
updateCategoriesIfNeeded(newMetadata, metadata, clearFlags, mergeCategories);

View File

@@ -111,6 +111,10 @@ public class FileService {
return Paths.get(appProperties.getPathConfig(), "bookdrop_temp", bookdropFileId + ".jpg").toString();
}
public String getToolsKepubifyPath() {
return Paths.get(appProperties.getPathConfig(), "tools", "kepubify").toString();
}
// ========================================
// VALIDATION
// ========================================

View File

@@ -36,6 +36,8 @@ public class PathPatternResolver {
? metadata.getTitle()
: "Untitled");
String subtitle = sanitize(metadata != null ? metadata.getSubtitle() : "");
String authors = sanitize(
metadata != null
? String.join(", ", metadata.getAuthors())
@@ -70,6 +72,7 @@ public class PathPatternResolver {
Map<String, String> values = new LinkedHashMap<>();
values.put("authors", authors);
values.put("title", title);
values.put("subtitle", subtitle);
values.put("year", year);
values.put("series", series);
values.put("seriesIndex", seriesIndex);
@@ -169,6 +172,8 @@ public class PathPatternResolver {
private interface MetadataProvider {
String getTitle();
String getSubtitle();
List<String> getAuthors();
Integer getYear();
@@ -211,6 +216,11 @@ public class PathPatternResolver {
return metadata.getTitle();
}
@Override
public String getSubtitle() {
return metadata.getSubtitle();
}
@Override
public List<String> getAuthors() {
return metadata.getAuthors() != null ? metadata.getAuthors().stream().toList() : Collections.emptyList();
@@ -264,6 +274,11 @@ public class PathPatternResolver {
return metadata.getTitle();
}
@Override
public String getSubtitle() {
return metadata.getSubtitle();
}
@Override
public List<String> getAuthors() {
return metadata.getAuthors() != null

View File

@@ -17,7 +17,7 @@ import static org.mockito.Mockito.when;
class PathPatternResolverTest {
private BookEntity createBook(String title, List<String> authors, LocalDate date,
private BookEntity createBook(String title, String subtitle, List<String> authors, LocalDate date,
String series, Float seriesNum, String lang,
String publisher, String isbn13, String isbn10,
String fileName) {
@@ -28,6 +28,7 @@ class PathPatternResolverTest {
when(book.getFileName()).thenReturn(fileName);
when(metadata.getTitle()).thenReturn(title);
when(metadata.getSubtitle()).thenReturn(subtitle);
if (authors == null) {
when(metadata.getAuthors()).thenReturn(null);
@@ -51,6 +52,14 @@ class PathPatternResolverTest {
return book;
}
// Helper method for backward compatibility
private BookEntity createBook(String title, List<String> authors, LocalDate date,
String series, Float seriesNum, String lang,
String publisher, String isbn13, String isbn10,
String fileName) {
return createBook(title, null, authors, date, series, seriesNum, lang, publisher, isbn13, isbn10, fileName);
}
@Test void emptyPattern_returnsOnlyExtension() {
var book = createBook("Title", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.pdf");
assertThat(PathPatternResolver.resolvePattern(book, "")).isEqualTo("file.pdf");
@@ -210,4 +219,58 @@ class PathPatternResolverTest {
String pattern = "{title}.{extension}";
assertThat(PathPatternResolver.resolvePattern(book, pattern)).isEqualTo("X.mobi");
}
@Test void subtitleInPattern_replacedCorrectly() {
var book = createBook("Main Title", "The Subtitle", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
assertThat(PathPatternResolver.resolvePattern(book, "{title} - {subtitle}")).isEqualTo("Main Title - The Subtitle.epub");
}
@Test void subtitleEmpty_replacedWithEmpty() {
var book = createBook("Title", "", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
assertThat(PathPatternResolver.resolvePattern(book, "{title} - {subtitle}")).isEqualTo("Title - .epub");
}
@Test void subtitleNull_replacedWithEmpty() {
var book = createBook("Title", null, List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
assertThat(PathPatternResolver.resolvePattern(book, "{title} - {subtitle}")).isEqualTo("Title - .epub");
}
@Test void subtitleInOptionalBlock_withValue_blockIncluded() {
var book = createBook("Title", "Subtitle", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
assertThat(PathPatternResolver.resolvePattern(book, "{title}< - {subtitle}>")).isEqualTo("Title - Subtitle.epub");
}
@Test void subtitleInOptionalBlock_withoutValue_blockRemoved() {
var book = createBook("Title", null, List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
assertThat(PathPatternResolver.resolvePattern(book, "{title}< - {subtitle}>")).isEqualTo("Title.epub");
}
@Test void subtitleWithIllegalChars_sanitized() {
var book = createBook("Title", "Sub:title<>|*?", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
String result = PathPatternResolver.resolvePattern(book, "{title} - {subtitle}");
assertThat(result).doesNotContain(":", "<", ">", "|", "*", "?")
.contains("Title").contains("Subtitle");
}
@Test void subtitleWithWhitespace_trimmedAndSanitized() {
var book = createBook("Title", " Sub title ", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
assertThat(PathPatternResolver.resolvePattern(book, "{title} - {subtitle}")).isEqualTo("Title - Sub title.epub");
}
@Test void complexPatternWithSubtitle_allPlaceholdersPresent() {
var book = createBook("Main Title", "The Great Subtitle", List.of("Author One"), LocalDate.of(2010, 5, 5),
"Series", 1f, "English", "Publisher", "ISBN13", "ISBN10", "complex.epub");
String pattern = "<{series}/>{title}< - {subtitle}> - {authors} - {year}";
assertThat(PathPatternResolver.resolvePattern(book, pattern))
.isEqualTo("Series/Main Title - The Great Subtitle - Author One - 2010.epub");
}
@Test void optionalBlockWithTitleAndSubtitle_partialValues() {
var book1 = createBook("Title", "Subtitle", List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
var book2 = createBook("Title", null, List.of("Author"), LocalDate.now(), null, null, null, null, null, null, "file.epub");
String pattern = "<{title} - {subtitle}>";
assertThat(PathPatternResolver.resolvePattern(book1, pattern)).isEqualTo("Title - Subtitle.epub");
assertThat(PathPatternResolver.resolvePattern(book2, pattern)).isEqualTo("file.epub");
}
}

View File

@@ -84,8 +84,6 @@ export interface MetadataPersistenceSettings {
moveFilesToLibraryPattern: boolean;
saveToOriginalFile: boolean;
convertCbrCb7ToCbz: boolean;
backupMetadata: boolean;
backupCover: boolean;
}
export interface ReviewProviderConfig {

View File

@@ -451,17 +451,7 @@
pTooltip="Automatically fetch metadata using default sources"
tooltipPosition="top">
</p-button>
@if (book.bookType === 'CBX') {
<p-button label="Restore ComicInfo" icon="pi pi-info-circle" [outlined]="true" severity="info" (onClick)="restoreCbxMetadata()" pTooltip="Retrieve metadata from ComicInfo.xml file" tooltipPosition="top"></p-button>
<p-divider layout="vertical"/>
}
@if (book.bookType === 'PDF' || book.bookType === 'EPUB') {
<p-button label="Restore" icon="pi pi-refresh" [outlined]="true" severity="danger" (onClick)="restoreMetadata()" pTooltip="Revert all changes to original metadata" tooltipPosition="top"></p-button>
<p-divider layout="vertical"/>
}
<p-divider layout="vertical"/>
<p-button label="Unlock All" icon="pi pi-lock-open" [outlined]="true" severity="success" (onClick)="unlockAll()" pTooltip="Unlock all metadata fields for editing" tooltipPosition="top"></p-button>
<p-button label="Lock All" icon="pi pi-lock" [outlined]="true" severity="warn" (onClick)="lockAll()" pTooltip="Lock all metadata fields to prevent changes" tooltipPosition="top"></p-button>
<p-divider layout="vertical"/>
@@ -491,28 +481,6 @@
tooltipPosition="top">
</p-button>
@if (book.bookType === 'CBX') {
<p-button
icon="pi pi-info-circle"
[outlined]="true"
severity="warn"
(onClick)="restoreCbxMetadata()"
pTooltip="Restore ComicInfo"
tooltipPosition="top">
</p-button>
}
@if (book.bookType === 'PDF' || book.bookType === 'EPUB') {
<p-button
icon="pi pi-refresh"
[outlined]="true"
severity="danger"
(onClick)="restoreMetadata()"
pTooltip="Restore"
tooltipPosition="top">
</p-button>
}
<p-button
icon="pi pi-lock-open"
[outlined]="true"

View File

@@ -130,6 +130,7 @@
<h4 class="subsection-title">Available Placeholders</h4>
<ul>
<li><code class="placeholder-code">{{ '{title}' }}</code> Book title</li>
<li><code class="placeholder-code">{{ '{subtitle}' }}</code> Book subtitle</li>
<li><code class="placeholder-code">{{ '{authors}' }}</code> Author(s)</li>
<li><code class="placeholder-code">{{ '{year}' }}</code> Full year (e.g. 2025)</li>
<li><code class="placeholder-code">{{ '{series}' }}</code> Series name</li>
@@ -180,6 +181,7 @@
<h4 class="subsection-title">Examples with Full Metadata</h4>
<div class="metadata-sample">
<span>title: <code>Harry Potter and the Sorcerer's Stone</code></span>
<span>subtitle: <code>The Boy Who Lived</code></span>
<span>authors: <code>J.K. Rowling</code></span>
<span>series: <code>Harry Potter</code></span>
<span>seriesIndex: <code>01</code></span>
@@ -217,6 +219,11 @@
<p class="example-pattern"><strong>Reuse original filename in path:</strong> <code>{{ '{authors}/{series}/{currentFilename}' }}</code></p>
<p class="example-output"><strong>Output:</strong> <code>J.K. Rowling/Harry Potter/harry1_original.epub</code></p>
</div>
<div class="example-item">
<p class="example-pattern"><strong>Title + Subtitle:</strong> <code>{{ '{title}: {subtitle}' }}</code></p>
<p class="example-output"><strong>Output:</strong> <code>Harry Potter and the Sorcerer's Stone: The Boy Who Lived.epub</code></p>
</div>
</div>
</div>
@@ -226,6 +233,7 @@
<h4 class="subsection-title">Examples with Missing Optional Fields</h4>
<div class="metadata-sample">
<span>title: <code>Project Hail Mary</code></span>
<span>subtitle: <code>(not provided)</code></span>
<span>authors: <code>Andy Weir</code></span>
<span>year: <code>2021</code></span>
<span>series: <code>(not provided)</code></span>
@@ -258,6 +266,11 @@
<p class="example-pattern"><strong>Use original filename with year suffix:</strong> <code>{{ '{authors}/{year}__{currentFilename}' }}</code></p>
<p class="example-output"><strong>Output:</strong> <code>Andy Weir/2021__project_hail_mary_final.epub</code></p>
</div>
<div class="example-item">
<p class="example-pattern"><strong>Title + Subtitle fallback:</strong> <code>{{ '{title}<: {subtitle}>' }}</code></p>
<p class="example-output"><strong>Output:</strong> <code>Project Hail Mary.epub</code></p>
</div>
</div>
</div>
</div>

View File

@@ -20,14 +20,15 @@ import {Divider} from 'primeng/divider';
})
export class FileNamingPatternComponent implements OnInit {
readonly exampleMetadata: Record<string, string> = {
title: 'The Fellowship of the Ring',
authors: 'J.R.R. Tolkien',
year: '1954',
series: 'The Lord of the Rings',
title: "Harry Potter and the Sorcerer's Stone",
subtitle: 'The Boy Who Lived',
authors: 'J.K. Rowling',
year: '1997',
series: 'Harry Potter',
seriesIndex: '01',
language: 'en',
publisher: 'Allen & Unwin',
isbn: '9780618574940',
publisher: 'Bloomsbury',
isbn: '9780747532699',
};
defaultPattern = '';

View File

@@ -47,40 +47,6 @@
</div>
</div>
<div class="setting-item setting-item-indented">
<div class="setting-info">
<div class="setting-label-row">
<label class="setting-label">Backup Metadata</label>
<p-toggleswitch
[ngModel]="metadataPersistence.backupMetadata"
(onChange)="onPersistenceToggle('backupMetadata')"
[disabled]="!metadataPersistence.saveToOriginalFile">
</p-toggleswitch>
</div>
<p class="setting-description">
<i class="pi pi-info-circle"></i>
Save a JSON copy of the current metadata before writing new data to the file.
</p>
</div>
</div>
<div class="setting-item setting-item-indented">
<div class="setting-info">
<div class="setting-label-row">
<label class="setting-label">Backup Cover</label>
<p-toggleswitch
[ngModel]="metadataPersistence.backupCover"
(onChange)="onPersistenceToggle('backupCover')"
[disabled]="!metadataPersistence.saveToOriginalFile">
</p-toggleswitch>
</div>
<p class="setting-description">
<i class="pi pi-info-circle"></i>
Save a copy of the existing embedded cover image before it is replaced.
</p>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<div class="setting-label-row">

View File

@@ -21,9 +21,7 @@ export class MetadataPersistenceSettingsComponent implements OnInit {
metadataPersistence: MetadataPersistenceSettings = {
saveToOriginalFile: false,
convertCbrCb7ToCbz: false,
moveFilesToLibraryPattern: false,
backupMetadata: true,
backupCover: true
moveFilesToLibraryPattern: false
};
private readonly appSettingsService = inject(AppSettingsService);
@@ -65,8 +63,6 @@ export class MetadataPersistenceSettingsComponent implements OnInit {
if (!this.metadataPersistence.saveToOriginalFile) {
this.metadataPersistence.convertCbrCb7ToCbz = false;
this.metadataPersistence.backupMetadata = false;
this.metadataPersistence.backupCover = false;
}
} else {
this.metadataPersistence[key] = !this.metadataPersistence[key];