Skip to content

Commit 5a34dde

Browse files
committed
Added discovering comic books in the library path [#2496]
1 parent 489b1b4 commit 5a34dde

File tree

15 files changed

+135
-65
lines changed

15 files changed

+135
-65
lines changed

COMICBOOK-STATES.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ This diagram describes the state changes for comics as events occur.
66

77
```mermaid
88
stateDiagram-v2
9-
[*] --> ADDED: Comic book objected created
10-
ADDED --> UNPROCESSED: Record inserted into the ComicBooks table
9+
[*] --> CREATED: Comic book objected created
10+
CREATED --> DISCOVERED: A comic book was discovered
11+
DISCOVERED --> UNPROCESSED: A discovered comic was imported
12+
CREATED --> UNPROCESSED: Record inserted into the ComicBooks table
1113
UNPROCESSED --> STABLE: Contents of comic file are loaded
1214
STABLE --> CHANGED: The metadata for the comic changed
1315
STABLE --> CHANGED: The page state changed

comixed-batch/src/main/java/org/comixedproject/batch/initiators/ProcessUnhashedComicsInitiator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class ProcessUnhashedComicsInitiator {
6363
public void execute() {
6464
synchronized (MUTEX) {
6565
log.trace("Checking for pages without hashes");
66-
if (this.comicBookService.hasComicsWithUnashedPages()
66+
if (this.comicBookService.hasComicsWithUnhashedPages()
6767
&& !this.batchProcessesService.hasActiveExecutions(PROCESS_UNHASHED_COMICS_JOB)) {
6868
try {
6969
log.trace("Starting batch job: load page hashes");

comixed-batch/src/test/java/org/comixedproject/batch/initiators/ProcessUnhashedComicsInitiatorTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void setUp()
6666
JobExecutionAlreadyRunningException,
6767
JobParametersInvalidException,
6868
JobRestartException {
69-
Mockito.when(comicBookService.hasComicsWithUnashedPages()).thenReturn(true);
69+
Mockito.when(comicBookService.hasComicsWithUnhashedPages()).thenReturn(true);
7070
Mockito.when(batchProcessesService.hasActiveExecutions(Mockito.anyString())).thenReturn(false);
7171
Mockito.when(jobLauncher.run(Mockito.any(Job.class), jobParametersArgumentCaptor.capture()))
7272
.thenReturn(jobExecution);
@@ -78,7 +78,7 @@ void execute_noMissingHashes()
7878
JobExecutionAlreadyRunningException,
7979
JobParametersInvalidException,
8080
JobRestartException {
81-
Mockito.when(comicBookService.hasComicsWithUnashedPages()).thenReturn(false);
81+
Mockito.when(comicBookService.hasComicsWithUnhashedPages()).thenReturn(false);
8282

8383
initiator.execute();
8484

comixed-model/src/main/java/org/comixedproject/model/comicbooks/ComicState.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
* @author Darryl L. Pierce
2525
*/
2626
public enum ComicState {
27+
// the comic book was discovered, but not imported
28+
DISCOVERED,
2729
// the comic book object was created
2830
CREATED,
2931
// the comic is unprocessed and needs to be loaded

comixed-services/src/main/java/org/comixedproject/service/comicbooks/ComicBookService.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import org.comixedproject.repositories.comicbooks.ComicBookRepository;
4545
import org.comixedproject.repositories.comicbooks.ComicDetailRepository;
4646
import org.comixedproject.repositories.comicbooks.ComicTagRepository;
47-
import org.comixedproject.repositories.comicpages.ComicPageRepository;
4847
import org.comixedproject.state.comicbooks.ComicEvent;
4948
import org.comixedproject.state.comicbooks.ComicStateHandler;
5049
import org.springframework.beans.factory.annotation.Autowired;
@@ -83,8 +82,6 @@ public class ComicBookService {
8382
@Autowired private ApplicationEventPublisher applicationEventPublisher;
8483
@Autowired private ComicTagRepository comicTagRepository;
8584

86-
@Autowired private ComicPageRepository comicPageRepository;
87-
8885
/**
8986
* Retrieves a single comic by id. It is expected that this comic exists.
9087
*
@@ -944,7 +941,7 @@ public void markComicAsFound(final String filename) {
944941
public void markComicAsMissing(final String filename) {
945942
final ComicBook comicBook = this.comicBookRepository.findByFilename(filename);
946943
if (Objects.nonNull(comicBook)) {
947-
log.debug("Marking comic book as missing: id={}", comicBook.getComicBookId());
944+
log.debug("Processing missing comic file: {} [{}]", filename, comicBook.getComicBookId());
948945
this.comicStateHandler.fireEvent(comicBook, ComicEvent.markAsMissing);
949946
}
950947
}
@@ -1015,7 +1012,7 @@ public long findComicsWithUnhashedPagesCount() {
10151012
* @return true if there are comic books with unhashed pages
10161013
*/
10171014
@Transactional
1018-
public boolean hasComicsWithUnashedPages() {
1015+
public boolean hasComicsWithUnhashedPages() {
10191016
return this.comicBookRepository.findComicsWithUnhashedPagesCount() > 0L;
10201017
}
10211018
}

comixed-services/src/main/java/org/comixedproject/service/comicfiles/ComicFileService.java

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -164,31 +164,41 @@ public void importComicFiles(final List<String> filenames) {
164164
for (int index = 0; index < filenames.size(); index++) {
165165
final String filename = filenames.get(index);
166166
if (!this.comicDetailService.filenameFound(filename)) {
167-
try {
168-
log.debug("Creating comicBook: filename={}", filename);
169-
final ComicBook comicBook = this.comicBookAdaptor.createComic(filename);
170-
log.trace("Scraping comicBook filename");
171-
final FilenameMetadata metadata =
172-
this.filenameScrapingRuleService.loadFilenameMetadata(
173-
comicBook.getComicDetail().getBaseFilename());
174-
if (metadata.isFound()) {
175-
log.trace("Scraping rule applied");
176-
comicBook.getComicDetail().setSeries(metadata.getSeries());
177-
comicBook.getComicDetail().setVolume(metadata.getVolume());
178-
comicBook.getComicDetail().setIssueNumber(metadata.getIssueNumber());
179-
comicBook.getComicDetail().setCoverDate(metadata.getCoverDate());
180-
}
181-
log.debug("Firing new comic book event: {}", filename);
182-
this.comicStateHandler.fireEvent(comicBook, ComicEvent.readyForProcessing);
183-
} catch (AdaptorException error) {
184-
log.error("Failed to create comic for file: " + filename, error);
185-
}
167+
doImportComicFile(filename, ComicEvent.readyForProcessing);
186168
}
187169
}
188170
log.debug("Initiating processing");
189171
this.applicationEventPublisher.publishEvent(LoadComicBooksEvent.instance);
190172
}
191173

174+
@Transactional
175+
public void discoverComicFile(final String filename) {
176+
log.debug("Adding discovered comic file: {}", filename);
177+
this.doImportComicFile(filename, ComicEvent.comicDiscovered);
178+
}
179+
180+
private void doImportComicFile(final String filename, final ComicEvent event) {
181+
try {
182+
log.debug("Creating comicBook: filename={}", filename);
183+
final ComicBook comicBook = this.comicBookAdaptor.createComic(filename);
184+
log.trace("Scraping comicBook filename");
185+
final FilenameMetadata metadata =
186+
this.filenameScrapingRuleService.loadFilenameMetadata(
187+
comicBook.getComicDetail().getBaseFilename());
188+
if (metadata.isFound()) {
189+
log.trace("Scraping rule applied");
190+
comicBook.getComicDetail().setSeries(metadata.getSeries());
191+
comicBook.getComicDetail().setVolume(metadata.getVolume());
192+
comicBook.getComicDetail().setIssueNumber(metadata.getIssueNumber());
193+
comicBook.getComicDetail().setCoverDate(metadata.getCoverDate());
194+
}
195+
log.debug("Firing new comic book event: {}:{}", event, filename);
196+
this.comicStateHandler.fireEvent(comicBook, event);
197+
} catch (AdaptorException error) {
198+
log.error("Failed to create comic for file: " + filename, error);
199+
}
200+
}
201+
192202
/**
193203
* Toggles the selected state for comic files.
194204
*

comixed-services/src/main/java/org/comixedproject/service/library/MissingFileScanner.java renamed to comixed-services/src/main/java/org/comixedproject/service/library/LibraryScannerService.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,24 @@
3434
import org.comixedproject.service.admin.ConfigurationService;
3535
import org.comixedproject.service.comicbooks.ComicBookService;
3636
import org.comixedproject.service.comicbooks.ComicDetailService;
37+
import org.comixedproject.service.comicfiles.ComicFileService;
3738
import org.springframework.beans.factory.InitializingBean;
3839
import org.springframework.beans.factory.annotation.Autowired;
3940
import org.springframework.stereotype.Component;
4041
import org.springframework.util.StringUtils;
4142

4243
/**
43-
* <code>MissingFileScanner</code> performs checks for missing comic files, updating the library.
44+
* <code>LibraryScannerService</code> monitors the library directory, processing changes.
4445
*
4546
* @author Darryl L. Pierce
4647
*/
4748
@Component
4849
@Log4j2
49-
public class MissingFileScanner implements InitializingBean, ConfigurationChangedListener {
50+
public class LibraryScannerService implements InitializingBean, ConfigurationChangedListener {
5051
@Autowired private ConfigurationService configurationService;
5152
@Autowired private ComicBookService comicBookService;
5253
@Autowired private ComicDetailService comicDetailService;
54+
@Autowired private ComicFileService comicFileService;
5355

5456
private static final Object SEMAPHORE = new Object();
5557

@@ -117,7 +119,7 @@ public void watchDirectory(final String directory) throws IOException, Interrupt
117119
new Thread() {
118120
@Override
119121
public void run() {
120-
MissingFileScanner.this.processEvents();
122+
LibraryScannerService.this.processEvents();
121123
}
122124
};
123125
processingThread.start();
@@ -167,19 +169,30 @@ void processWatchEvent(final WatchKey key, final WatchEvent<?> event) {
167169
final Path dir = (Path) key.watchable();
168170
final Path name = Path.of(((WatchEvent<Path>) event).context().toString());
169171
final String filename = dir.resolve(name).toString();
170-
if (event.kind() != OVERFLOW && this.comicDetailService.filenameFound(filename)) {
171-
if (event.kind() == ENTRY_CREATE || event.kind() == ENTRY_MODIFY) {
172-
log.trace("File found: {}", filename);
173-
this.comicBookService.markComicAsFound(filename);
174-
} else if (event.kind() == ENTRY_DELETE) {
175-
log.trace("File deleted: {}", filename);
176-
this.comicBookService.markComicAsMissing(filename);
177-
} else {
178-
log.trace("Not a library file: {}", filename);
179-
}
172+
if (event.kind() == ENTRY_CREATE || event.kind() == ENTRY_MODIFY) {
173+
this.doFileFound(filename);
174+
} else if (event.kind() == ENTRY_DELETE) {
175+
this.doFileDeleted(filename);
176+
} else {
177+
log.debug("Unhandled watch event: {}", event.kind());
180178
}
181179
}
182180

181+
private void doFileFound(final String filename) {
182+
if (this.comicDetailService.filenameFound(filename)) {
183+
log.debug("Missing file found: {}", filename);
184+
this.comicBookService.markComicAsFound(filename);
185+
} else {
186+
log.debug("Comic book discovered: {}", filename);
187+
this.comicFileService.discoverComicFile(filename);
188+
}
189+
}
190+
191+
private void doFileDeleted(final String filename) {
192+
log.debug("File deleted: {}", filename);
193+
this.comicBookService.markComicAsMissing(filename);
194+
}
195+
183196
private void scanLibrary() {
184197
synchronized (SEMAPHORE) {
185198
if (!this.active) {

comixed-services/src/test/java/org/comixedproject/service/comicbooks/ComicBookServiceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,7 +1450,7 @@ void findComicsWithUnhashedPagesCount() {
14501450
void hasComicsWithUnhashedPagesCount_withNoSuchComics() {
14511451
Mockito.when(comicBookRepository.findComicsWithUnhashedPagesCount()).thenReturn(0L);
14521452

1453-
final boolean result = service.hasComicsWithUnashedPages();
1453+
final boolean result = service.hasComicsWithUnhashedPages();
14541454

14551455
assertFalse(result);
14561456

@@ -1462,7 +1462,7 @@ void hasComicsWithUnhashedPagesCount_withComics() {
14621462
Mockito.when(comicBookRepository.findComicsWithUnhashedPagesCount())
14631463
.thenReturn(TEST_COMIC_COUNT);
14641464

1465-
final boolean result = service.hasComicsWithUnashedPages();
1465+
final boolean result = service.hasComicsWithUnhashedPages();
14661466

14671467
assertTrue(result);
14681468

comixed-services/src/test/java/org/comixedproject/service/comicfiles/ComicFileServiceTest.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ void importComicFiles_alreadyFound() {
210210
}
211211

212212
@Test
213-
void imporComicFiles() throws AdaptorException {
213+
void importComicFiles() throws AdaptorException {
214214
Mockito.when(comicDetailService.filenameFound(Mockito.anyString())).thenReturn(false);
215215

216216
service.importComicFiles(filenameList);
@@ -270,6 +270,19 @@ void importComicFiles_withFilenameMetadata() throws AdaptorException {
270270
.publishEvent(LoadComicBooksEvent.instance);
271271
}
272272

273+
@Test
274+
void discoverComicFile() throws AdaptorException {
275+
Mockito.when(comicDetailService.filenameFound(Mockito.anyString())).thenReturn(false);
276+
277+
service.discoverComicFile(TEST_ARCHIVE_FILENAME);
278+
279+
Mockito.verify(comicBookAdaptor, Mockito.times(1)).createComic(TEST_ARCHIVE_FILENAME);
280+
Mockito.verify(filenameScrapingRuleService, Mockito.times(1))
281+
.loadFilenameMetadata(TEST_ARCHIVE_FILENAME);
282+
Mockito.verify(comicStateHandler, Mockito.times(1))
283+
.fireEvent(comicBook, ComicEvent.comicDiscovered);
284+
}
285+
273286
@Test
274287
void toggleComicFileSelections_allFiles() {
275288
comicFileGroup.getFiles().add(new ComicFile(TEST_COMIC_ARCHIVE, TEST_FILE_SIZE));

comixed-services/src/test/java/org/comixedproject/service/library/MissingFileScannerTest.java renamed to comixed-services/src/test/java/org/comixedproject/service/library/LibraryScannerServiceTest.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.comixedproject.service.admin.ConfigurationService;
3131
import org.comixedproject.service.comicbooks.ComicBookService;
3232
import org.comixedproject.service.comicbooks.ComicDetailService;
33+
import org.comixedproject.service.comicfiles.ComicFileService;
3334
import org.junit.jupiter.api.AfterEach;
3435
import org.junit.jupiter.api.BeforeEach;
3536
import org.junit.jupiter.api.Test;
@@ -43,18 +44,19 @@
4344

4445
@ExtendWith(MockitoExtension.class)
4546
@MockitoSettings(strictness = Strictness.LENIENT)
46-
class MissingFileScannerTest {
47+
class LibraryScannerServiceTest {
4748
private static final String TEST_ROOT_DIRECTORY =
4849
new File("target/test-classes").getAbsolutePath();
4950
private static final String TEST_COMIC_FILENAME =
5051
new File("target/test-classes/example.cbz").getAbsolutePath();
5152
private static final String TEST_MISSING_COMIC_FILENAME = TEST_COMIC_FILENAME + "-not-found";
5253
private static final String TEST_RELATIVE_FILENAME = "example.cbz";
5354

54-
@InjectMocks private MissingFileScanner scanner;
55+
@InjectMocks private LibraryScannerService scanner;
5556
@Mock private ConfigurationService configurationService;
5657
@Mock private ComicBookService comicBookService;
5758
@Mock private ComicDetailService comicDetailService;
59+
@Mock private ComicFileService comicFileService;
5860
@Mock private WatchService watchService;
5961
@Mock private WatchKey key;
6062
@Mock private Path watchEventPath;
@@ -66,7 +68,7 @@ class MissingFileScannerTest {
6668
private Set<String> notMissingComicDetailSet = new HashSet<>();
6769

6870
@BeforeEach
69-
public void setUp() {
71+
void setUp() {
7072
Mockito.when(configurationService.getOptionValue(Mockito.anyString()))
7173
.thenReturn(TEST_ROOT_DIRECTORY);
7274
missingComicDetailSet.add(TEST_COMIC_FILENAME);
@@ -84,7 +86,7 @@ public void setUp() {
8486
}
8587

8688
@AfterEach
87-
public void tearDown() throws IOException {
89+
void tearDown() throws IOException {
8890
if (scanner.watchService != null) {
8991
scanner.stopWatching();
9092
}
@@ -151,14 +153,25 @@ void watchDirectory_fileNotDirectory() throws IOException, InterruptedException
151153
}
152154

153155
@Test
154-
void processWatchEvent_notInLibrary() {
156+
void processWatchEvent_entrycreate_notInLibrary() {
157+
Mockito.when(watchEvent.kind()).thenReturn(ENTRY_CREATE);
155158
Mockito.when(comicDetailService.filenameFound(Mockito.anyString())).thenReturn(false);
156159

157160
scanner.processWatchEvent(key, watchEvent);
158161

159162
Mockito.verify(comicDetailService, Mockito.times(1)).filenameFound(TEST_COMIC_FILENAME);
160-
Mockito.verify(comicBookService, Mockito.never()).markComicAsFound(Mockito.anyString());
161-
Mockito.verify(comicBookService, Mockito.never()).markComicAsMissing(Mockito.anyString());
163+
Mockito.verify(comicFileService, Mockito.times(1)).discoverComicFile(TEST_COMIC_FILENAME);
164+
}
165+
166+
@Test
167+
void processWatchEvent_entrycreate_inLibrary() {
168+
Mockito.when(watchEvent.kind()).thenReturn(ENTRY_CREATE);
169+
Mockito.when(comicDetailService.filenameFound(Mockito.anyString())).thenReturn(true);
170+
171+
scanner.processWatchEvent(key, watchEvent);
172+
173+
Mockito.verify(comicDetailService, Mockito.times(1)).filenameFound(TEST_COMIC_FILENAME);
174+
Mockito.verify(comicBookService, Mockito.times(1)).markComicAsFound(TEST_COMIC_FILENAME);
162175
}
163176

164177
@Test
@@ -167,7 +180,6 @@ void processWatchEvent_fileDeleted() {
167180

168181
scanner.processWatchEvent(key, watchEvent);
169182

170-
Mockito.verify(comicDetailService, Mockito.times(1)).filenameFound(TEST_COMIC_FILENAME);
171183
Mockito.verify(comicBookService, Mockito.times(1)).markComicAsMissing(TEST_COMIC_FILENAME);
172184
}
173185

0 commit comments

Comments
 (0)