Skip to content

Commit 149c0f3

Browse files
committed
Added storing the comic files for import server-side [#2483]
1 parent be617ea commit 149c0f3

File tree

11 files changed

+258
-43
lines changed

11 files changed

+258
-43
lines changed

comixed-rest/src/main/java/org/comixedproject/rest/comicfiles/ComicFileController.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,20 @@
1919
package org.comixedproject.rest.comicfiles;
2020

2121
import com.fasterxml.jackson.annotation.JsonView;
22+
import com.fasterxml.jackson.core.JsonProcessingException;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
2224
import io.micrometer.core.annotation.Timed;
25+
import jakarta.servlet.http.HttpSession;
2326
import java.io.IOException;
2427
import java.util.List;
28+
import java.util.Objects;
2529
import lombok.extern.log4j.Log4j2;
2630
import org.apache.commons.io.FilenameUtils;
2731
import org.apache.commons.io.IOUtils;
2832
import org.comixedproject.adaptors.AdaptorException;
33+
import org.comixedproject.model.comicfiles.ComicFileGroup;
2934
import org.comixedproject.model.metadata.FilenameMetadata;
30-
import org.comixedproject.model.net.comicfiles.FilenameMetadataRequest;
31-
import org.comixedproject.model.net.comicfiles.FilenameMetadataResponse;
32-
import org.comixedproject.model.net.comicfiles.GetAllComicsUnderRequest;
33-
import org.comixedproject.model.net.comicfiles.ImportComicFilesRequest;
34-
import org.comixedproject.model.net.comicfiles.LoadComicFilesResponse;
35+
import org.comixedproject.model.net.comicfiles.*;
3536
import org.comixedproject.service.comicfiles.ComicFileService;
3637
import org.comixedproject.service.metadata.FilenameScrapingRuleService;
3738
import org.comixedproject.views.View;
@@ -53,8 +54,36 @@
5354
@RestController
5455
@Log4j2
5556
public class ComicFileController {
57+
static final String COMIC_FILES = "comic.files";
58+
5659
@Autowired private ComicFileService comicFileService;
5760
@Autowired private FilenameScrapingRuleService filenameScrapingRuleService;
61+
@Autowired private ObjectMapper objectMapper;
62+
63+
/**
64+
* Loads the cached list of comic files.
65+
*
66+
* @param session the user session
67+
* @return the comic files
68+
*/
69+
@GetMapping(value = "/api/files/session", produces = MediaType.APPLICATION_JSON_VALUE)
70+
@Timed(value = "comixed.comic-file.load-comic-files-session")
71+
@JsonView(View.ComicFileList.class)
72+
public LoadComicFilesResponse loadComicFilesFromSession(final HttpSession session) {
73+
log.info("Loading comic files from user session");
74+
final Object encodedComicFiles = session.getAttribute(COMIC_FILES);
75+
if (Objects.isNull(encodedComicFiles)) {
76+
log.debug("No comic files found in session");
77+
return null;
78+
}
79+
try {
80+
return new LoadComicFilesResponse(
81+
this.objectMapper.readValue(encodedComicFiles.toString(), List.class));
82+
} catch (JsonProcessingException error) {
83+
log.error("Failed to parse comic files from session", error);
84+
return null;
85+
}
86+
}
5887

5988
/**
6089
* Retrieves all comic files under the specified directory.
@@ -69,10 +98,11 @@ public class ComicFileController {
6998
produces = MediaType.APPLICATION_JSON_VALUE,
7099
consumes = MediaType.APPLICATION_JSON_VALUE)
71100
@PreAuthorize("hasRole('ADMIN')")
72-
@Timed(value = "comixed.comic-file.load")
101+
@Timed(value = "comixed.comic-file.load-comic-files")
73102
@JsonView(View.ComicFileList.class)
74103
public LoadComicFilesResponse loadComicFiles(
75-
@RequestBody() final GetAllComicsUnderRequest request) throws IOException {
104+
final HttpSession session, @RequestBody() final GetAllComicsUnderRequest request)
105+
throws IOException {
76106
String directory = request.getDirectory();
77107
Integer maximum = request.getMaximum();
78108

@@ -81,7 +111,11 @@ public LoadComicFilesResponse loadComicFiles(
81111
directory,
82112
maximum > 0 ? maximum : "UNLIMITED");
83113

84-
return new LoadComicFilesResponse(this.comicFileService.getAllComicsUnder(directory, maximum));
114+
final List<ComicFileGroup> files = this.comicFileService.getAllComicsUnder(directory, maximum);
115+
116+
session.setAttribute(COMIC_FILES, this.objectMapper.writeValueAsString(files));
117+
118+
return new LoadComicFilesResponse(files);
85119
}
86120

87121
/**

comixed-rest/src/test/java/org/comixedproject/rest/comicfiles/ComicFileControllerTest.java

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,27 @@
1818

1919
package org.comixedproject.rest.comicfiles;
2020

21+
import static org.comixedproject.rest.comicfiles.ComicFileController.COMIC_FILES;
2122
import static org.junit.Assert.*;
2223

24+
import com.fasterxml.jackson.core.JsonProcessingException;
25+
import com.fasterxml.jackson.databind.ObjectMapper;
26+
import jakarta.servlet.http.HttpSession;
2327
import java.io.IOException;
2428
import java.util.List;
2529
import java.util.Random;
2630
import org.apache.commons.lang.math.RandomUtils;
2731
import org.comixedproject.adaptors.AdaptorException;
2832
import org.comixedproject.model.comicfiles.ComicFileGroup;
2933
import org.comixedproject.model.metadata.FilenameMetadata;
30-
import org.comixedproject.model.net.comicfiles.FilenameMetadataRequest;
31-
import org.comixedproject.model.net.comicfiles.FilenameMetadataResponse;
32-
import org.comixedproject.model.net.comicfiles.GetAllComicsUnderRequest;
33-
import org.comixedproject.model.net.comicfiles.ImportComicFilesRequest;
34-
import org.comixedproject.model.net.comicfiles.LoadComicFilesResponse;
34+
import org.comixedproject.model.net.comicfiles.*;
3535
import org.comixedproject.service.comicfiles.ComicFileService;
3636
import org.comixedproject.service.metadata.FilenameScrapingRuleService;
3737
import org.junit.jupiter.api.Test;
3838
import org.junit.jupiter.api.extension.ExtendWith;
39-
import org.mockito.*;
39+
import org.mockito.InjectMocks;
40+
import org.mockito.Mock;
41+
import org.mockito.Mockito;
4042
import org.mockito.junit.jupiter.MockitoExtension;
4143
import org.springframework.batch.core.JobParametersInvalidException;
4244
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
@@ -57,13 +59,58 @@ class ComicFileControllerTest {
5759
private static final String TEST_ISSUE_NUMBER = "983a";
5860
private static final Boolean TEST_SKIP_METADATA = RandomUtils.nextBoolean();
5961
private static final Boolean TEST_SKIP_BLOCKING_PAGES = RandomUtils.nextBoolean();
62+
private static final String TEST_ENCODED_COMIC_FILES = "The encoded comic file list";
6063

6164
@InjectMocks private ComicFileController controller;
6265
@Mock private ComicFileService comicFileService;
6366
@Mock private FilenameScrapingRuleService filenameScrapingRuleService;
67+
@Mock private ObjectMapper objectMapper;
6468
@Mock private List<ComicFileGroup> comicFileGroupList;
6569
@Mock private List<String> filenameList;
6670
@Mock private FilenameMetadata filenameMetadata;
71+
@Mock private HttpSession session;
72+
73+
@Test
74+
void loadComicFilesFromSession() throws JsonProcessingException {
75+
Mockito.when(session.getAttribute(COMIC_FILES)).thenReturn(TEST_ENCODED_COMIC_FILES);
76+
Mockito.when(objectMapper.readValue(Mockito.anyString(), Mockito.any(Class.class)))
77+
.thenReturn(comicFileGroupList);
78+
79+
final LoadComicFilesResponse result = controller.loadComicFilesFromSession(session);
80+
81+
assertNotNull(result);
82+
assertSame(comicFileGroupList, result.getGroups());
83+
84+
Mockito.verify(session, Mockito.times(1)).getAttribute(COMIC_FILES);
85+
Mockito.verify(objectMapper, Mockito.times(1)).readValue(TEST_ENCODED_COMIC_FILES, List.class);
86+
}
87+
88+
@Test
89+
void loadComicFilesFromSession_nothingStored() throws JsonProcessingException {
90+
Mockito.when(session.getAttribute(COMIC_FILES)).thenReturn(null);
91+
92+
final LoadComicFilesResponse result = controller.loadComicFilesFromSession(session);
93+
94+
assertNull(result);
95+
96+
Mockito.verify(session, Mockito.times(1)).getAttribute(COMIC_FILES);
97+
Mockito.verify(objectMapper, Mockito.never())
98+
.readValue(Mockito.anyString(), Mockito.any(Class.class));
99+
}
100+
101+
@Test
102+
void loadComicFilesFromSession_parsingError() throws JsonProcessingException {
103+
Mockito.when(session.getAttribute(COMIC_FILES)).thenReturn(TEST_ENCODED_COMIC_FILES);
104+
Mockito.when(objectMapper.readValue(Mockito.anyString(), Mockito.any(Class.class)))
105+
.thenThrow(JsonProcessingException.class);
106+
107+
final LoadComicFilesResponse result = controller.loadComicFilesFromSession(session);
108+
109+
assertNull(result);
110+
111+
Mockito.verify(session, Mockito.times(1)).getAttribute(COMIC_FILES);
112+
Mockito.verify(objectMapper, Mockito.times(1)).readValue(TEST_ENCODED_COMIC_FILES, List.class);
113+
}
67114

68115
@Test
69116
void getImportFileCoverServiceThrowsException() throws AdaptorException {
@@ -91,33 +138,43 @@ void getImportFileCover() throws AdaptorException {
91138
}
92139

93140
@Test
94-
void getAllComicsUnderNoLimit() throws IOException {
141+
void loadComicFiles_noLimit() throws IOException {
95142
Mockito.when(comicFileService.getAllComicsUnder(Mockito.anyString(), Mockito.anyInt()))
96143
.thenReturn(comicFileGroupList);
144+
Mockito.when(objectMapper.writeValueAsString(Mockito.any()))
145+
.thenReturn(TEST_ENCODED_COMIC_FILES);
97146

98147
final LoadComicFilesResponse response =
99-
controller.loadComicFiles(new GetAllComicsUnderRequest(TEST_DIRECTORY, TEST_NO_LIMIT));
148+
controller.loadComicFiles(
149+
session, new GetAllComicsUnderRequest(TEST_DIRECTORY, TEST_NO_LIMIT));
100150

101151
assertNotNull(response);
102152
assertSame(comicFileGroupList, response.getGroups());
103153

104154
Mockito.verify(comicFileService, Mockito.times(1))
105155
.getAllComicsUnder(TEST_DIRECTORY, TEST_NO_LIMIT);
156+
Mockito.verify(objectMapper, Mockito.times(1)).writeValueAsString(comicFileGroupList);
157+
Mockito.verify(session, Mockito.times(1)).setAttribute(COMIC_FILES, TEST_ENCODED_COMIC_FILES);
106158
}
107159

108160
@Test
109-
void getAllComicsUnder() throws IOException {
161+
void loadComicFiles_withLimit() throws IOException {
110162
Mockito.when(comicFileService.getAllComicsUnder(Mockito.anyString(), Mockito.anyInt()))
111163
.thenReturn(comicFileGroupList);
164+
Mockito.when(objectMapper.writeValueAsString(Mockito.any()))
165+
.thenReturn(TEST_ENCODED_COMIC_FILES);
112166

113167
final LoadComicFilesResponse response =
114-
controller.loadComicFiles(new GetAllComicsUnderRequest(TEST_DIRECTORY, TEST_LIMIT));
168+
controller.loadComicFiles(
169+
session, new GetAllComicsUnderRequest(TEST_DIRECTORY, TEST_LIMIT));
115170

116171
assertNotNull(response);
117172
assertSame(comicFileGroupList, response.getGroups());
118173

119174
Mockito.verify(comicFileService, Mockito.times(1))
120175
.getAllComicsUnder(TEST_DIRECTORY, TEST_LIMIT);
176+
Mockito.verify(objectMapper, Mockito.times(1)).writeValueAsString(comicFileGroupList);
177+
Mockito.verify(session, Mockito.times(1)).setAttribute(COMIC_FILES, TEST_ENCODED_COMIC_FILES);
121178
}
122179

123180
@Test

comixed-webui/src/app/comic-files/actions/comic-file-list.actions.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,31 @@ import { createAction, props } from '@ngrx/store';
2020
import { ComicFile } from '@app/comic-files/models/comic-file';
2121
import { ComicFileGroup } from '@app/comic-files/models/comic-file-group';
2222

23+
export const loadComicFilesFromSession = createAction('[Comic File List]');
24+
2325
export const loadComicFileLists = createAction(
24-
'[Comic File] Load comics in a file system',
26+
'[Comic File List] Load comics in a file system',
2527
props<{ directory: string; maximum: number }>()
2628
);
2729

2830
export const loadComicFileListSuccess = createAction(
29-
'[Comic File] Loaded comics in a file system',
31+
'[Comic File List] Loaded comics in a file system',
3032
props<{ groups: ComicFileGroup[] }>()
3133
);
3234

3335
export const loadComicFileListFailure = createAction(
34-
'[Comic File] Failed to load comic files in a file system'
36+
'[Comic File List] Failed to load comic files in a file system'
3537
);
3638

3739
export const resetComicFileList = createAction(
38-
'[Comic File] Clears the list of comic book files'
40+
'[Comic File List] Clears the list of comic book files'
3941
);
4042

4143
export const setComicFilesSelectedState = createAction(
42-
'[Comic File] Set the selected state on comic files',
44+
'[Comic File List] Set the selected state on comic files',
4345
props<{ files: ComicFile[]; selected: boolean }>()
4446
);
4547

4648
export const clearComicFileSelections = createAction(
47-
'[Comic File] Clear all selected comic files'
49+
'[Comic File List] Clear all selected comic files'
4850
);

comixed-webui/src/app/comic-files/comic-file.constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import { API_ROOT_URL } from '../core';
2020

21+
export const LOAD_COMIC_FILES_FROM_SESSION_URL = `${API_ROOT_URL}/files/session`;
2122
export const LOAD_COMIC_FILES_URL = `${API_ROOT_URL}/files/contents`;
2223
export const SEND_COMIC_FILES_URL = `${API_ROOT_URL}/files/import`;
2324
export const SCRAPE_FILENAME_URL = `${API_ROOT_URL}/files/metadata`;

comixed-webui/src/app/comic-files/effects/comic-file-list.effects.spec.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ import { LoggerModule } from '@angular-ru/cdk/logger';
3434
import { MatSnackBarModule } from '@angular/material/snack-bar';
3535
import { LoadComicFilesResponse } from '@app/library/models/net/load-comic-files-response';
3636
import {
37-
loadComicFileListSuccess,
37+
loadComicFileListFailure,
3838
loadComicFileLists,
39-
loadComicFileListFailure
39+
loadComicFileListSuccess,
40+
loadComicFilesFromSession
4041
} from '@app/comic-files/actions/comic-file-list.actions';
4142
import { saveUserPreference } from '@app/user/actions/user.actions';
4243
import {
@@ -77,6 +78,9 @@ describe('ComicFileListEffects', () => {
7778
{
7879
provide: ComicImportService,
7980
useValue: {
81+
loadComicFilesFromSession: jasmine.createSpy(
82+
'ComicFileService.loadComicFilesFromSession()'
83+
),
8084
loadComicFiles: jasmine.createSpy(
8185
'ComicFileService.loadComicFiles()'
8286
),
@@ -101,6 +105,49 @@ describe('ComicFileListEffects', () => {
101105
expect(effects).toBeTruthy();
102106
});
103107

108+
describe('loading the comic files from the user session', () => {
109+
it('fires an action on success', () => {
110+
const serviceResponse = { groups: GROUPS } as LoadComicFilesResponse;
111+
const action = loadComicFilesFromSession();
112+
const outcome = loadComicFileListSuccess({ groups: GROUPS });
113+
114+
actions$ = hot('-a', { a: action });
115+
comicImportService.loadComicFilesFromSession.and.returnValue(
116+
of(serviceResponse)
117+
);
118+
119+
const expected = hot('-b', { b: outcome });
120+
expect(effects.loadComicFilesFromSession$).toBeObservable(expected);
121+
});
122+
123+
it('fires an action on service failure', () => {
124+
const serviceResponse = new HttpErrorResponse({});
125+
const action = loadComicFilesFromSession();
126+
const outcome = loadComicFileListFailure();
127+
128+
actions$ = hot('-a', { a: action });
129+
comicImportService.loadComicFilesFromSession.and.returnValue(
130+
throwError(serviceResponse)
131+
);
132+
133+
const expected = hot('-b', { b: outcome });
134+
expect(effects.loadComicFilesFromSession$).toBeObservable(expected);
135+
expect(alertService.error).toHaveBeenCalledWith(jasmine.any(String));
136+
});
137+
138+
it('fires an action on general failure', () => {
139+
const action = loadComicFilesFromSession();
140+
const outcome = loadComicFileListFailure();
141+
142+
actions$ = hot('-a', { a: action });
143+
comicImportService.loadComicFilesFromSession.and.throwError('expected');
144+
145+
const expected = hot('-(b|)', { b: outcome });
146+
expect(effects.loadComicFilesFromSession$).toBeObservable(expected);
147+
expect(alertService.error).toHaveBeenCalledWith(jasmine.any(String));
148+
});
149+
});
150+
104151
describe('loading comic files', () => {
105152
const MAXIMUM_RESULT = 100;
106153

0 commit comments

Comments
 (0)