Skip to content

Commit

Permalink
Added a rule to use when renaming pages in a comic file [comixed#516]
Browse files Browse the repository at this point in the history
 * Added a migration to insert a default page renaming rule.
 * Added a input for configuring the page renaming rule.
 * Added an adaptor for generating page names based on renaming rules.

Also removed the explicit page renaming flag when converting comics. Instead,
this flag is implicitly set if a page renaming rule is defined.
  • Loading branch information
mcpierce committed Oct 30, 2021
1 parent a79a40b commit 482ec20
Show file tree
Hide file tree
Showing 24 changed files with 408 additions and 145 deletions.
Expand Up @@ -24,7 +24,7 @@
import java.util.Optional;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.codehaus.plexus.util.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.comixedproject.adaptors.AdaptorException;
import org.comixedproject.adaptors.archive.ArchiveAdaptor;
import org.comixedproject.adaptors.archive.ArchiveAdaptorException;
Expand All @@ -51,9 +51,9 @@
@Component
@Log4j2
public class ComicBookAdaptor {
public static final String PAGE_FILENAME_PATTERN = "page-%03d.%s";
@Autowired private FileTypeAdaptor fileTypeAdaptor;
@Autowired private ComicFileAdaptor comicFileAdaptor;
@Autowired private ComicPageAdaptor comicPageAdaptor;
@Autowired private ComicMetadataContentAdaptor comicMetadataContentAdaptor;
@Autowired private FileAdaptor fileAdaptor;

Expand Down Expand Up @@ -111,14 +111,14 @@ public void load(final Comic comic) throws AdaptorException {
* @param comic the comic
* @param archiveType the target format
* @param removeDeletedPages remove deleted pages flag
* @param renamePages rename pages flag
* @param pageRenamingRule the page renaming rule
* @throws AdaptorException if an error occurs
*/
public void save(
final Comic comic,
final ArchiveType archiveType,
final boolean removeDeletedPages,
final boolean renamePages)
final String pageRenamingRule)
throws AdaptorException {
try {
final String temporaryFilename =
Expand Down Expand Up @@ -150,9 +150,9 @@ public void save(
log.trace("Reading comic page content: {}", page.getFilename());
final byte[] content = sourceArchive.readEntry(readHandle, page.getFilename());
@NonNull String pageFilename = page.getFilename();
if (renamePages) {
if (StringUtils.isNotEmpty(pageRenamingRule)) {
pageFilename =
String.format(PAGE_FILENAME_PATTERN, index, FileUtils.getExtension(pageFilename));
this.comicPageAdaptor.createFilenameFromRule(page, pageRenamingRule, index);
}
log.trace("Writing comic page content: {}", pageFilename);
destinationArchive.writeEntry(writeHandle, pageFilename, content);
Expand Down
@@ -0,0 +1,63 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2021, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.adaptors.comicbooks;

import lombok.extern.log4j.Log4j2;
import org.codehaus.plexus.util.FileUtils;
import org.comixedproject.model.comicpages.Page;
import org.springframework.stereotype.Component;

/**
* <code>ComicPageAdaptor</code> provides a set of utility methods for naming comic pages.
*
* @author Darryl L. Pierce
*/
@Component
@Log4j2
public class ComicPageAdaptor {
private static final String FORBIDDEN_RULE_CHARACTERS = "[\"':\\\\*?|<>]";

/**
* Generates a new filename for the given page.
*
* @param page the page
* @param renamingRule the renaming rule
* @param pageIndex the page index in the comic
* @return the page name
*/
public String createFilenameFromRule(
final Page page, final String renamingRule, final int pageIndex) {
log.debug("Scrubbing renaming rule: {}", renamingRule);
final String rule = this.scrub(renamingRule, FORBIDDEN_RULE_CHARACTERS);

log.debug("Generating relative filename based on renaming rule: {}", rule);
final String index = String.valueOf(pageIndex + 1);

String result = rule.replace("$INDEX", index);

log.debug("Relative page name: {}", result);

return String.format("%s.%s", result, FileUtils.getExtension(page.getFilename()));
}

private String scrub(final String text, final String forbidden) {
log.trace("Pre-sanitized text: {}", text);
return text.replaceAll(forbidden, "_");
}
}
Expand Up @@ -19,7 +19,6 @@
package org.comixedproject.adaptors.comicbooks;

import static junit.framework.TestCase.*;
import static org.comixedproject.adaptors.comicbooks.ComicBookAdaptor.PAGE_FILENAME_PATTERN;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -57,6 +56,8 @@ public class ComicBookAdaptorTest {
private static final String TEST_FINAL_FILENAME = "The final filename";
private static final byte[] TEST_COMICINFO_XML_CONTENT = "ComicInfo.xml content".getBytes();
private static final int TEST_PAGE_INDEX = 0;
private static final String TEST_PAGE_RENAMING_RULE = "page renaming rule";
private static final String TEST_NEW_PAGE_FILENAME = "new page filename";

@InjectMocks private ComicBookAdaptor adaptor;
@Mock private FileTypeAdaptor fileTypeAdaptor;
Expand All @@ -70,6 +71,7 @@ public class ComicBookAdaptorTest {
@Mock private ContentAdaptor contentAdaptor;
@Mock private ComicArchiveEntry archiveEntry;
@Mock private ComicFileAdaptor comicFileAdaptor;
@Mock private ComicPageAdaptor comicPageAdaptor;
@Mock private ComicMetadataContentAdaptor comicMetadataContentAdaptor;
@Mock private FileAdaptor fileAdaptor;

Expand Down Expand Up @@ -268,7 +270,7 @@ public void testSaveExceptionOnGetReadArchiveType() throws AdaptorException {
.thenThrow(AdaptorException.class);

try {
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, false);
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, "");
} finally {
Mockito.verify(fileTypeAdaptor, Mockito.times(1)).getArchiveAdaptorFor(TEST_COMIC_FILENAME);
}
Expand All @@ -281,7 +283,7 @@ public void testSaveExceptionOnOpenArchiveForRead()
.thenThrow(ArchiveAdaptorException.class);

try {
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, false);
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, "");
} finally {
Mockito.verify(readableArchiveAdaptor, Mockito.times(1))
.openArchiveForRead(TEST_COMIC_FILENAME);
Expand All @@ -295,7 +297,7 @@ public void testSaveExceptionOnWriteComicInfo()
.thenThrow(ContentAdaptorException.class);

try {
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, false);
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, "");
} finally {
Mockito.verify(comicMetadataContentAdaptor, Mockito.times(1)).createContent(comic);
}
Expand All @@ -308,7 +310,7 @@ public void testSaveReadPageThrowsException() throws AdaptorException, ArchiveAd
.readEntry(Mockito.any(ArchiveReadHandle.class), Mockito.anyString());

try {
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, false);
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, "");
} finally {
Mockito.verify(readableArchiveAdaptor, Mockito.times(1))
.readEntry(readHandle, TEST_ENTRY_FILENAME);
Expand All @@ -322,7 +324,7 @@ public void testSaveWritePageThrowsException() throws AdaptorException, ArchiveA
.writeEntry(writeHandle, TEST_ENTRY_FILENAME, TEST_ARCHIVE_ENTRY_CONTENT);

try {
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, false);
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, "");
} finally {
Mockito.verify(writeableArchiveAdaptor, Mockito.times(1))
.writeEntry(writeHandle, TEST_ENTRY_FILENAME, TEST_ARCHIVE_ENTRY_CONTENT);
Expand All @@ -332,8 +334,12 @@ public void testSaveWritePageThrowsException() throws AdaptorException, ArchiveA
@Test
public void testSaveRenamePages()
throws AdaptorException, ArchiveAdaptorException, ContentAdaptorException {
Mockito.when(
comicPageAdaptor.createFilenameFromRule(
Mockito.any(Page.class), Mockito.anyString(), Mockito.anyInt()))
.thenReturn(TEST_NEW_PAGE_FILENAME);

adaptor.save(comic, TEST_ARCHIVE_TYPE, false, true);
adaptor.save(comic, TEST_ARCHIVE_TYPE, false, TEST_PAGE_RENAMING_RULE);

Mockito.verify(fileTypeAdaptor, Mockito.times(1)).getArchiveAdaptorFor(TEST_COMIC_FILENAME);
Mockito.verify(readableArchiveAdaptor, Mockito.times(1))
Expand All @@ -345,18 +351,17 @@ public void testSaveRenamePages()
.readEntry(readHandle, TEST_ENTRY_FILENAME);
Mockito.verify(writeableArchiveAdaptor, Mockito.times(1))
.writeEntry(writeHandle, "ComicInfo.xml", TEST_COMICINFO_XML_CONTENT);
Mockito.verify(comicPageAdaptor, Mockito.times(1))
.createFilenameFromRule(page, TEST_PAGE_RENAMING_RULE, 0);
Mockito.verify(writeableArchiveAdaptor, Mockito.times(1))
.writeEntry(
writeHandle,
String.format(PAGE_FILENAME_PATTERN, 0, TEST_PAGE_EXTENSION),
TEST_ARCHIVE_ENTRY_CONTENT);
.writeEntry(writeHandle, TEST_NEW_PAGE_FILENAME, TEST_ARCHIVE_ENTRY_CONTENT);
}

@Test
public void testSaveRemoveDeletedPages()
throws AdaptorException, ArchiveAdaptorException, ContentAdaptorException, IOException {

adaptor.save(comic, TEST_ARCHIVE_TYPE, true, false);
adaptor.save(comic, TEST_ARCHIVE_TYPE, true, "");

Mockito.verify(fileTypeAdaptor, Mockito.times(1)).getArchiveAdaptorFor(TEST_COMIC_FILENAME);
Mockito.verify(readableArchiveAdaptor, Mockito.times(1))
Expand Down
@@ -0,0 +1,55 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2021, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.adaptors.comicbooks;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;

import org.comixedproject.model.comicpages.Page;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class ComicPageAdaptorTest {
private static final String TEST_RENAMING_RULE = "page-$INDEX";
private static final int TEST_PAGE_INDEX = 3;
private static final String TEST_FILENAME = "oldname3.png";
private static final String TEST_EXPECTED_PAGE_NAME = "page-4.png";

@InjectMocks private ComicPageAdaptor adaptor;
@Mock private Page page;

@Before
public void setUp() {
Mockito.when(page.getFilename()).thenReturn(TEST_FILENAME);
}

@Test
public void testCreateFilenameFromRule() {
final String result = adaptor.createFilenameFromRule(page, TEST_RENAMING_RULE, TEST_PAGE_INDEX);

assertNotNull(result);
assertEquals(TEST_EXPECTED_PAGE_NAME, result);
}
}
Expand Up @@ -44,7 +44,6 @@ public class RecreateComicFilesConfiguration {
public static final String JOB_RECREATE_COMICS_STARTED = "job.recreate-comic.started";
public static final String JOB_TARGET_ARCHIVE = "job.recreate-comic.target-archive";
public static final String JOB_DELETE_MARKED_PAGES = "job.recreate-comic.delete-blocked-pages";
public static final String JOB_RENAME_PAGES = "job.recreate-comic.rename-pages";

@Value("${batch.chunk-size}")
private int batchChunkSize = 10;
Expand Down
Expand Up @@ -18,12 +18,15 @@

package org.comixedproject.batch.comicbooks.processors;

import static org.comixedproject.batch.comicbooks.RecreateComicFilesConfiguration.*;
import static org.comixedproject.batch.comicbooks.RecreateComicFilesConfiguration.JOB_DELETE_MARKED_PAGES;
import static org.comixedproject.batch.comicbooks.RecreateComicFilesConfiguration.JOB_TARGET_ARCHIVE;
import static org.comixedproject.service.admin.ConfigurationService.CFG_LIBRARY_PAGE_RENAMING_RULE;

import lombok.extern.log4j.Log4j2;
import org.comixedproject.adaptors.comicbooks.ComicBookAdaptor;
import org.comixedproject.model.archives.ArchiveType;
import org.comixedproject.model.comicbooks.Comic;
import org.comixedproject.service.admin.ConfigurationService;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
Expand All @@ -42,6 +45,7 @@
public class RecreateComicFileProcessor
implements ItemProcessor<Comic, Comic>, StepExecutionListener {
@Autowired private ComicBookAdaptor comicBookAdaptor;
@Autowired private ConfigurationService configurationService;

private JobParameters jobParameters;

Expand All @@ -52,10 +56,12 @@ public Comic process(final Comic comic) throws Exception {
ArchiveType.forValue(this.jobParameters.getString(JOB_TARGET_ARCHIVE));
final boolean removeDeletedPages =
Boolean.parseBoolean(this.jobParameters.getString(JOB_DELETE_MARKED_PAGES));
final boolean renamePages =
Boolean.parseBoolean(this.jobParameters.getString(JOB_RENAME_PAGES));
log.trace("Recreating comic file{}", renamePages ? ", renaming pages" : "");
this.comicBookAdaptor.save(comic, archiveType, removeDeletedPages, renamePages);
log.trace("Recreating comic files");
this.comicBookAdaptor.save(
comic,
archiveType,
removeDeletedPages,
this.configurationService.getOptionValue(CFG_LIBRARY_PAGE_RENAMING_RULE, ""));
return comic;
}

Expand Down
Expand Up @@ -40,7 +40,7 @@ public class UpdateMetadataProcessor implements ItemProcessor<Comic, Comic> {
public Comic process(final Comic comic) {
try {
log.debug("Updating comic metadata: id={}", comic.getId());
this.comicBookAdaptor.save(comic, comic.getArchiveType(), false, false);
this.comicBookAdaptor.save(comic, comic.getArchiveType(), false, "");
} catch (AdaptorException error) {
log.error("Failed to update metadata for comic", error);
}
Expand Down

0 comments on commit 482ec20

Please sign in to comment.