Skip to content

Commit c288a48

Browse files
committed
Added a feature flag to turn batch locking comics on/off [#2444]
1 parent d7d1768 commit c288a48

File tree

11 files changed

+101
-19
lines changed

11 files changed

+101
-19
lines changed

comixed-batch/src/main/java/org/comixedproject/batch/ComicCheckOutManager.java

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818

1919
package org.comixedproject.batch;
2020

21+
import static org.comixedproject.service.admin.ConfigurationService.CFG_EXCLUSIVE_COMIC_LOCK;
22+
2123
import java.util.Set;
2224
import java.util.concurrent.ConcurrentSkipListSet;
2325
import lombok.extern.log4j.Log4j2;
26+
import org.comixedproject.service.admin.ConfigurationService;
27+
import org.springframework.beans.factory.annotation.Autowired;
2428
import org.springframework.stereotype.Component;
2529

2630
/**
@@ -34,24 +38,28 @@
3438
public class ComicCheckOutManager {
3539
private static final Object MUTEX = new Object();
3640

41+
@Autowired private ConfigurationService configurationService;
42+
3743
Set<Long> catalog = new ConcurrentSkipListSet<>();
3844

3945
public void checkOut(final Long comicBookId) {
40-
synchronized (MUTEX) {
41-
boolean done = false;
42-
while (!done) {
43-
if (this.catalog.contains(comicBookId)) {
44-
log.info("Waiting for comic to be checked in: id={}", comicBookId);
45-
try {
46-
MUTEX.wait(1000L);
47-
} catch (InterruptedException error) {
48-
throw new RuntimeException("Interrupted waiting for comic checkin", error);
46+
if (this.configurationService.isFeatureEnabled(CFG_EXCLUSIVE_COMIC_LOCK)) {
47+
synchronized (MUTEX) {
48+
boolean done = false;
49+
while (!done) {
50+
if (this.catalog.contains(comicBookId)) {
51+
log.info("Waiting for comic to be checked in: id={}", comicBookId);
52+
try {
53+
MUTEX.wait(1000L);
54+
} catch (InterruptedException error) {
55+
throw new RuntimeException("Interrupted waiting for comic checkin", error);
56+
}
57+
} else {
58+
log.info("Checking out comic book: id={}", comicBookId);
59+
this.catalog.add(comicBookId);
60+
done = true;
61+
MUTEX.notifyAll();
4962
}
50-
} else {
51-
log.info("Checking out comic book: id={}", comicBookId);
52-
this.catalog.add(comicBookId);
53-
done = true;
54-
MUTEX.notifyAll();
5563
}
5664
}
5765
}

comixed-batch/src/test/java/org/comixedproject/batch/ComicCheckOutManagerTest.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,51 @@
2323

2424
import java.util.concurrent.TimeUnit;
2525
import net.jodah.concurrentunit.Waiter;
26+
import org.comixedproject.service.admin.ConfigurationService;
2627
import org.junit.jupiter.api.Test;
2728
import org.junit.jupiter.api.extension.ExtendWith;
2829
import org.mockito.InjectMocks;
30+
import org.mockito.Mock;
31+
import org.mockito.Mockito;
2932
import org.mockito.junit.jupiter.MockitoExtension;
3033

3134
@ExtendWith(MockitoExtension.class)
3235
class ComicCheckOutManagerTest {
3336
private static final long TEST_COMIC_BOOK_ID = 717L;
3437

3538
@InjectMocks private ComicCheckOutManager manager;
39+
@Mock private ConfigurationService configurationService;
40+
41+
@Test
42+
void checkOut_featureDisabled() {
43+
Mockito.when(
44+
configurationService.isFeatureEnabled(ConfigurationService.CFG_EXCLUSIVE_COMIC_LOCK))
45+
.thenReturn(false);
46+
47+
manager.checkOut(TEST_COMIC_BOOK_ID);
48+
49+
assertFalse(manager.catalog.contains(TEST_COMIC_BOOK_ID));
50+
}
3651

3752
@Test
3853
void checkOut() {
54+
Mockito.when(
55+
configurationService.isFeatureEnabled(ConfigurationService.CFG_EXCLUSIVE_COMIC_LOCK))
56+
.thenReturn(true);
57+
3958
manager.checkOut(TEST_COMIC_BOOK_ID);
4059

4160
assertTrue(manager.catalog.contains(TEST_COMIC_BOOK_ID));
4261
}
4362

4463
@Test
45-
void checkOut_alreadyClaimed() throws InterruptedException {
64+
void checkOut_alreadyClaimed() {
4665
final Waiter waiter = new Waiter();
4766

67+
Mockito.when(
68+
configurationService.isFeatureEnabled(ConfigurationService.CFG_EXCLUSIVE_COMIC_LOCK))
69+
.thenReturn(true);
70+
4871
manager.checkOut(TEST_COMIC_BOOK_ID);
4972

5073
new Thread(

comixed-model/src/main/java/org/comixedproject/model/admin/ConfigurationOption.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818

1919
package org.comixedproject.model.admin;
2020

21-
import com.fasterxml.jackson.annotation.JsonFormat;
22-
import com.fasterxml.jackson.annotation.JsonProperty;
23-
import com.fasterxml.jackson.annotation.JsonView;
21+
import com.fasterxml.jackson.annotation.*;
2422
import jakarta.persistence.*;
2523
import java.util.Date;
2624
import java.util.Objects;
@@ -36,6 +34,9 @@
3634
@Table(name = "configuration_options")
3735
@NoArgsConstructor
3836
@RequiredArgsConstructor
37+
@JsonIdentityInfo(
38+
generator = ObjectIdGenerators.PropertyGenerator.class,
39+
property = "configurationOptionId")
3940
public class ConfigurationOption {
4041
@Id
4142
@GeneratedValue(strategy = GenerationType.IDENTITY)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
5+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
6+
<changeSet id="005_2444_add_batch_comic_lock_feature_flag.xml" author="mcpierce">
7+
<preConditions onFail="MARK_RAN">
8+
<sqlCheck expectedResult="0">SELECT COUNT(*)
9+
FROM configuration_options
10+
WHERE option_name = 'batch.comic-lock';</sqlCheck>
11+
</preConditions>
12+
13+
<sql>INSERT INTO configuration_options(option_name, option_value, last_modified_on)
14+
VALUES ('batch.comic-lock', '0', NOW());</sql>
15+
</changeSet>
16+
</databaseChangeLog>

comixed-model/src/main/resources/db/migrations/3.0/changelog-3.0.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
<include file="/db/migrations/3.0/002_2178_add_filename_to_displayable_comics_view.xml"/>
2121
<include file="/db/migrations/3.0/003_2448_add_last_modified_date_to_comic_details.xml"/>
2222
<include file="/db/migrations/3.0/004_2427_add_last_updated_date_to_comic_metadata_sources.xml"/>
23+
<include file="/db/migrations/3.0/005_2444_add_batch_comic_lock_feature_flag.xml"/>
2324

2425
</databaseChangeLog>

comixed-services/src/main/java/org/comixedproject/service/admin/ConfigurationService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public class ConfigurationService {
5959
"library.metadata.create-external-files";
6060
public static final String CFG_MANAGE_BLOCKED_PAGES = "library.blocked-pages-enabled";
6161
public static final String CFG_STRIP_HTML_FROM_METADATA = "library.strip-html-from-metadata";
62+
public static final String CFG_EXCLUSIVE_COMIC_LOCK = "batch.comic-lock";
6263

6364
@Autowired private ConfigurationRepository configurationRepository;
6465

comixed-webui/src/app/admin/admin.constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const METADATA_SCRAPING_ERROR_THRESHOLD =
5555
export const BLOCKED_PAGES_ENABLED = 'library.blocked-pages-enabled';
5656
export const LIBRARY_STRIP_HTML_FROM_METADATA =
5757
'library.strip-html-from-metadata';
58+
export const BATCH_COMIC_LOCK = 'batch.comic-lock';
5859

5960
export const LOAD_CONFIGURATION_OPTIONS_URL = `${API_ROOT_URL}/admin/config`;
6061
export const SAVE_CONFIGURATION_OPTIONS_URL = `${API_ROOT_URL}/admin/config`;

comixed-webui/src/app/admin/components/library-configuration/library-configuration.component.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,13 @@ <h3>{{ "configuration.label.file-options" | translate }}</h3>
125125
>
126126
{{ "configuration.label.strip-html-from-metadata" | translate }}
127127
</mat-checkbox>
128+
<br />
129+
<mat-checkbox
130+
id="batch-comic-lock-checkbox"
131+
formControlName="batchComicLock"
132+
>
133+
{{ "configuration.label.batch-comic-lock" | translate }}
134+
</mat-checkbox>
128135
</div>
129136
</div>
130137

comixed-webui/src/app/admin/components/library-configuration/library-configuration.component.spec.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { LoggerModule } from '@angular-ru/cdk/logger';
2828
import { MockStore, provideMockStore } from '@ngrx/store/testing';
2929
import { MatDialogModule } from '@angular/material/dialog';
3030
import {
31+
BATCH_COMIC_LOCK,
3132
BLOCKED_PAGES_ENABLED,
3233
CREATE_EXTERNAL_METADATA_FILES,
3334
LIBRARY_COMIC_RENAMING_RULE,
@@ -51,7 +52,7 @@ import { MatIconModule } from '@angular/material/icon';
5152
import { purgeLibrary } from '@app/library/actions/purge-library.actions';
5253
import { MatTooltipModule } from '@angular/material/tooltip';
5354

54-
describe('LibraryConfigurationComponent', () => {
55+
fdescribe('LibraryConfigurationComponent', () => {
5556
const DELETE_PURGED_COMIC_FILES = Math.random() > 0.5;
5657
const DELETE_EMPTY_DIRECTORIES = Math.random() > 0.5;
5758
const DONT_MOVE_UNSCRAPED_COMICS = Math.random() > 0.5;
@@ -63,6 +64,7 @@ describe('LibraryConfigurationComponent', () => {
6364
const PAGE_RENAMING_RULE = 'The page renaming rule';
6465
const BLOCKED_PAGES_ENABLED_FEATURE_STATE = Math.random() > 0.5;
6566
const LIBRARY_STRIP_HTML_FROM_METADATA_STATE = Math.random() > 0.5;
67+
const BATCH_COMIC_LOCK_STATE = Math.random() > 0.5;
6668
const OPTIONS = [
6769
{
6870
name: LIBRARY_COMIC_RENAMING_RULE,
@@ -107,6 +109,10 @@ describe('LibraryConfigurationComponent', () => {
107109
{
108110
name: LIBRARY_STRIP_HTML_FROM_METADATA,
109111
value: `${LIBRARY_STRIP_HTML_FROM_METADATA_STATE}`
112+
},
113+
{
114+
name: BATCH_COMIC_LOCK,
115+
value: `${BATCH_COMIC_LOCK_STATE}`
110116
}
111117
];
112118
const initialState = {};
@@ -252,6 +258,10 @@ describe('LibraryConfigurationComponent', () => {
252258
{
253259
name: LIBRARY_STRIP_HTML_FROM_METADATA,
254260
value: `${LIBRARY_STRIP_HTML_FROM_METADATA_STATE}`
261+
},
262+
{
263+
name: BATCH_COMIC_LOCK,
264+
value: `${BATCH_COMIC_LOCK_STATE}`
255265
}
256266
]
257267
})

comixed-webui/src/app/admin/components/library-configuration/library-configuration.component.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { LoggerService } from '@angular-ru/cdk/logger';
3030
import { ConfigurationOption } from '@app/admin/models/configuration-option';
3131
import { getConfigurationOption } from '@app/admin';
3232
import {
33+
BATCH_COMIC_LOCK,
3334
BLOCKED_PAGES_ENABLED,
3435
CREATE_EXTERNAL_METADATA_FILES,
3536
LIBRARY_COMIC_RENAMING_RULE,
@@ -127,6 +128,7 @@ export class LibraryConfigurationComponent {
127128
createExternalMetadataFile: ['', []],
128129
skipInternalMetadataFile: ['', []],
129130
blockedPagesEnabled: ['', []],
131+
batchComicLock: ['', []],
130132
rootDirectory: ['', [Validators.required]],
131133
comicRenamingRule: ['', []],
132134
noRecreateComics: ['', []],
@@ -149,6 +151,9 @@ export class LibraryConfigurationComponent {
149151
this.libraryConfigurationForm.controls.stripHtmlFromMetadata.setValue(
150152
getConfigurationOption(options, LIBRARY_STRIP_HTML_FROM_METADATA, '')
151153
);
154+
this.libraryConfigurationForm.controls.batchComicLock.setValue(
155+
getConfigurationOption(options, BATCH_COMIC_LOCK, '')
156+
);
152157
this.libraryConfigurationForm.controls.deletePurgedComicFilesDirectories.setValue(
153158
getConfigurationOption(
154159
options,
@@ -195,6 +200,10 @@ export class LibraryConfigurationComponent {
195200
getConfigurationOption(options, BLOCKED_PAGES_ENABLED, `${false}`) ===
196201
`${true}`
197202
);
203+
this.libraryConfigurationForm.controls.batchComicLock.setValue(
204+
getConfigurationOption(options, BATCH_COMIC_LOCK, `${false}`) ===
205+
`${true}`
206+
);
198207
this.libraryConfigurationForm.controls.stripHtmlFromMetadata.setValue(
199208
getConfigurationOption(
200209
options,
@@ -282,6 +291,10 @@ export class LibraryConfigurationComponent {
282291
{
283292
name: LIBRARY_STRIP_HTML_FROM_METADATA,
284293
value: `${this.libraryConfigurationForm.controls.stripHtmlFromMetadata.value}`
294+
},
295+
{
296+
name: BATCH_COMIC_LOCK,
297+
value: `${this.libraryConfigurationForm.controls.batchComicLock.value}`
285298
}
286299
];
287300
}

0 commit comments

Comments
 (0)