Skip to content

Commit a51fb30

Browse files
Merge pull request #31 from gradienthealth/feat/avoid-cod-dicomweb-server-cache-if-opfs-is-enabled
Avoid Cod dicomweb Server cache if OPFS is enabled
2 parents 024ec98 + 339a953 commit a51fb30

10 files changed

Lines changed: 96 additions & 31 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "cod-dicomweb-server",
33
"title": "COD Dicomweb server",
4-
"version": "1.3.12",
4+
"version": "1.3.13",
55
"private": false,
66
"description": "A wadors server proxy that get data from a Cloud Optimized Dicom format.",
77
"main": "dist/umd/main.js",

src/classes/CodDicomWebServer.ts

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { CustomErrorEvent } from './customClasses';
1919
import { download, getDirectoryHandle } from '../fileAccessSystemUtils';
2020

2121
class CodDicomWebServer {
22-
private filePromises: Record<string, Promise<void>> = {};
22+
private filePromises: Record<string, { promise: Promise<void>; requestCount: number }> = {};
23+
private files: Record<string, Uint8Array> = {};
2324
private options: CodDicomWebServerOptions = {
2425
maxCacheSize: 4 * 1024 * 1024 * 1024, // 4GB
2526
domain: constants.url.DOMAIN,
@@ -231,7 +232,11 @@ class CodDicomWebServer {
231232
const { url, position, fileArraybuffer } = evt.data;
232233

233234
if (url === fileUrl && fileArraybuffer) {
234-
this.fileManager.set(url, { data: fileArraybuffer, position });
235+
if (this.options.enableLocalCache) {
236+
this.files[fileUrl] = fileArraybuffer;
237+
} else {
238+
this.fileManager.set(url, { data: fileArraybuffer, position });
239+
}
235240

236241
dataRetrievalManager.removeEventListener(FILE_STREAMING_WORKER_NAME, 'message', handleFirstChunk);
237242
}
@@ -253,7 +258,6 @@ class CodDicomWebServer {
253258
})
254259
.then(() => {
255260
dataRetrievalManager.removeEventListener(FILE_STREAMING_WORKER_NAME, 'message', handleFirstChunk);
256-
delete this.filePromises[fileUrl];
257261
});
258262
} else if (fetchType === FetchTypeEnum.BYTES_OPTIMIZED && offsets) {
259263
const { startByte, endByte } = offsets;
@@ -268,7 +272,11 @@ class CodDicomWebServer {
268272
const { url, fileArraybuffer, offsets } = evt.data;
269273

270274
if (url === bytesRemovedUrl && offsets.startByte === startByte && offsets.endByte === endByte) {
271-
this.fileManager.set(fileUrl, { data: fileArraybuffer, position: fileArraybuffer.length });
275+
if (this.options.enableLocalCache) {
276+
this.files[fileUrl] = fileArraybuffer;
277+
} else {
278+
this.fileManager.set(fileUrl, { data: fileArraybuffer, position: fileArraybuffer.length });
279+
}
272280

273281
dataRetrievalManager.removeEventListener(FILE_PARTIAL_WORKER_NAME, 'message', handleSlice);
274282
resolveFile();
@@ -289,20 +297,21 @@ class CodDicomWebServer {
289297
})
290298
.then(() => {
291299
dataRetrievalManager.removeEventListener(FILE_PARTIAL_WORKER_NAME, 'message', handleSlice);
292-
delete this.filePromises[fileUrl];
293300
});
294301
} else {
295302
rejectFile(new CustomError('CodDicomWebServer.ts: Offsets is needed in bytes optimized fetching'));
296303
}
297304
});
298305

299-
this.filePromises[fileUrl] = tarPromise;
306+
this.filePromises[fileUrl] = { promise: tarPromise, requestCount: 1 };
300307
} else {
301-
tarPromise = this.filePromises[fileUrl];
308+
tarPromise = this.filePromises[fileUrl].promise;
309+
this.filePromises[fileUrl].requestCount++;
302310
}
303311

304312
return new Promise<ArrayBufferLike | undefined>((resolveRequest, rejectRequest) => {
305-
let requestResolved = false;
313+
let requestResolved = false,
314+
fileFetchingCompleted = false;
306315

307316
const handleChunkAppend = (evt: CustomMessageEvent | CustomErrorEvent): void => {
308317
if (evt instanceof CustomErrorEvent) {
@@ -314,7 +323,11 @@ class CodDicomWebServer {
314323

315324
if (isAppending) {
316325
if (chunk) {
317-
this.fileManager.append(url, chunk, position);
326+
if (this.options.enableLocalCache) {
327+
this.files[url].set(chunk, position - chunk.length);
328+
} else {
329+
this.fileManager.append(url, chunk, position);
330+
}
318331
} else {
319332
this.fileManager.setPosition(url, position);
320333
}
@@ -327,38 +340,64 @@ class CodDicomWebServer {
327340
}
328341
}
329342

330-
if (!requestResolved && url === fileUrl && offsets && position > offsets.endByte) {
343+
if (!requestResolved && url === fileUrl && position > offsets.endByte) {
331344
try {
332-
const file = this.fileManager.get(url, offsets);
333-
requestResolved = true;
345+
const file = this.options.enableLocalCache
346+
? this.files[url].slice(offsets.startByte, offsets.endByte)
347+
: this.fileManager.get(url, offsets);
348+
334349
resolveRequest(file?.buffer);
335350
} catch (error) {
336351
rejectRequest(error);
352+
} finally {
353+
completeRequest(url);
337354
}
338355
}
339356
};
340357

358+
const completeRequest = (url: string) => {
359+
requestResolved = true;
360+
this.filePromises[url].requestCount--;
361+
dataRetrievalManager.removeEventListener(FILE_STREAMING_WORKER_NAME, 'message', handleChunkAppend);
362+
363+
if (fileFetchingCompleted && this.filePromises[url] && !this.filePromises[url]?.requestCount) {
364+
delete this.filePromises[url];
365+
delete this.files[url];
366+
}
367+
};
368+
341369
if (offsets && !isBytesOptimized) {
342370
dataRetrievalManager.addEventListener(FILE_STREAMING_WORKER_NAME, 'message', handleChunkAppend);
343371
}
344372

345373
tarPromise
346374
.then(() => {
375+
fileFetchingCompleted = true;
376+
347377
if (!requestResolved) {
348-
if (this.fileManager.getPosition(fileUrl)) {
349-
const file = this.fileManager.get(fileUrl, isBytesOptimized ? undefined : offsets);
350-
requestResolved = true;
378+
if (this.fileManager.getPosition(fileUrl) || this.files[fileUrl]) {
379+
let file: Uint8Array;
380+
if (this.options.enableLocalCache) {
381+
file =
382+
isBytesOptimized || !offsets
383+
? this.files[fileUrl]
384+
: this.files[fileUrl].slice(offsets.startByte, offsets.endByte);
385+
} else {
386+
file = this.fileManager.get(fileUrl, isBytesOptimized ? undefined : offsets);
387+
}
388+
351389
resolveRequest(file?.buffer);
352390
} else {
353391
rejectRequest(new CustomError(`File - ${fileUrl} not found`));
354392
}
355393
}
356394
})
357395
.catch((error) => {
396+
fileFetchingCompleted = true;
358397
rejectRequest(error);
359398
})
360-
.then(() => {
361-
dataRetrievalManager.removeEventListener(FILE_STREAMING_WORKER_NAME, 'message', handleChunkAppend);
399+
.finally(() => {
400+
completeRequest(fileUrl);
362401
});
363402
});
364403
}

src/dataRetrieval/dataRetrievalManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class DataRetrievalManager {
4343
this.dataRetriever.register(name, arg);
4444
}
4545

46-
public async executeTask(loaderName: string, taskName: string, options: Record<string, unknown> | unknown): Promise<void> {
46+
public async executeTask(loaderName: string, taskName: string, options: Record<string, unknown> | unknown): Promise<any> {
4747
return await this.dataRetriever.executeTask(loaderName, taskName, options);
4848
}
4949

@@ -69,6 +69,7 @@ class DataRetrievalManager {
6969
}
7070

7171
const dataRetrievalManager = new DataRetrievalManager();
72+
Object.freeze(dataRetrievalManager);
7273

7374
export function getDataRetrievalManager(): DataRetrievalManager {
7475
return dataRetrievalManager;

src/dataRetrieval/requestManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class RequestManager {
3434
}
3535
};
3636

37-
public async executeTask(loaderName: string, taskName: string, options: Record<string, unknown> | unknown): Promise<void> {
37+
public async executeTask(loaderName: string, taskName: string, options: Record<string, unknown> | unknown): Promise<any> {
3838
const loaderObject = this.loaderRegistry[loaderName]?.loaderObject;
3939
if (!loaderObject) {
4040
throw new CustomError(`Loader ${loaderName} not registered`);

src/dataRetrieval/scripts/filePartial.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const filePartial = {
1010
directoryHandle?: FileSystemDirectoryHandle;
1111
},
1212
callBack: (data: { url: string; fileArraybuffer: Uint8Array; offsets: { startByte: number; endByte: number } }) => void
13-
): Promise<void | Error> {
13+
): Promise<Uint8Array | Error> {
1414
const { url, offsets, headers, directoryHandle } = args;
1515
if (offsets?.startByte && offsets?.endByte) {
1616
headers['Range'] = `bytes=${offsets.startByte}-${offsets.endByte - 1}`;
@@ -21,19 +21,23 @@ const filePartial = {
2121
if (directoryHandle) {
2222
const file = (await readFile(directoryHandle, storageName, { offsets, isJson: false })) as ArrayBuffer;
2323
if (file) {
24-
callBack({ url, fileArraybuffer: new Uint8Array(file), offsets });
25-
return;
24+
const fileBuffer = new Uint8Array(file);
25+
callBack({ url, fileArraybuffer: fileBuffer, offsets });
26+
return fileBuffer;
2627
}
2728
}
2829

29-
await fetch(url, { headers })
30+
return await fetch(url, { headers })
3031
.then((response) => response.arrayBuffer())
3132
.then((data) => {
32-
callBack({ url, fileArraybuffer: new Uint8Array(data), offsets });
33+
const fileBuffer = new Uint8Array(data);
34+
callBack({ url, fileArraybuffer: fileBuffer, offsets });
3335

3436
if (directoryHandle) {
3537
writeFile(directoryHandle, storageName, data);
3638
}
39+
40+
return fileBuffer;
3741
})
3842
.catch((error) => {
3943
throw new CustomError('filePartial.ts: Error when fetching file: ' + error?.message);

src/dataRetrieval/scripts/fileStreaming.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ const fileStreaming = {
2929
const file = (await readFile(directoryHandle, fileName, { isJson: false })) as ArrayBuffer;
3030
if (file) {
3131
const totalLength = file.byteLength;
32-
callBack({ url, position: totalLength, fileArraybuffer: new Uint8Array(file), totalLength });
33-
return;
32+
const fileBuffer = new Uint8Array(file);
33+
callBack({ url, position: totalLength, fileArraybuffer: fileBuffer, totalLength });
34+
return fileBuffer;
3435
}
3536
}
3637

@@ -96,6 +97,8 @@ const fileStreaming = {
9697
writeFile(directoryHandle, fileName, fileArraybuffer.slice().buffer);
9798
}
9899
}
100+
101+
return fileArraybuffer;
99102
} catch (error) {
100103
const streamingError = new CustomError(
101104
'fileStreaming.ts: ' + (error as CustomError).message || 'An error occured when streaming'

src/dataRetrieval/workerManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class WebWorkerManager {
2121
}
2222
}
2323

24-
public async executeTask(workerName: string, taskName: string, options: Record<string, unknown> | unknown): Promise<void> {
24+
public async executeTask(workerName: string, taskName: string, options: Record<string, unknown> | unknown): Promise<any> {
2525
const worker = this.workerRegistry[workerName]?.instance;
2626
if (!worker) {
2727
throw new CustomError(`Worker ${workerName} not registered`);

src/tests/classes/CodDicomWebServer.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('CodDicomWebServer', () => {
7676
describe('getOptions', () => {
7777
it('should return the default options if not set', () => {
7878
const options = server.getOptions();
79-
expect(options).toEqual({ maxCacheSize: Infinity, domain: url.DOMAIN, enableLocalCache: false });
79+
expect(options).toEqual({ maxCacheSize: 4 * 1024 * 1024 * 1024, domain: url.DOMAIN, enableLocalCache: false });
8080
});
8181
});
8282

src/tests/dataRetrieval/scripts/filePartial.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('filePartial', () => {
7676
const headers = { 'Content-Type': 'application/json' };
7777
const callback = jest.fn();
7878
const expected = await (await URL_RESPONSES[url]).arrayBuffer();
79-
await expect(filePartial.partial({ url, offsets, headers }, callback)).resolves.toBeUndefined();
79+
await expect(filePartial.partial({ url, offsets, headers }, callback)).resolves.toEqual(expected);
8080
expect(callback).toHaveBeenCalledWith({ url, fileArraybuffer: new Uint8Array(expected), offsets });
8181
expect(callback).toHaveBeenCalledTimes(1);
8282
});
@@ -87,7 +87,7 @@ describe('filePartial', () => {
8787
const headers = { 'Content-Type': 'application/json' };
8888
const callback = jest.fn();
8989
const expected = (await (await URL_RESPONSES[url]).arrayBuffer()).slice(3, 7);
90-
await expect(filePartial.partial({ url, offsets, headers }, callback)).resolves.toBeUndefined();
90+
await expect(filePartial.partial({ url, offsets, headers }, callback)).resolves.toEqual(expected);
9191
expect(callback).toHaveBeenCalledWith({ url, fileArraybuffer: new Uint8Array(expected), offsets });
9292
expect(callback).toHaveBeenCalledTimes(1);
9393
});

src/tests/fileManager.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,22 @@ describe('FileManager', () => {
148148
expect(fileManager.get(url2)).toBeNull();
149149
expect(fileManager.getTotalSize()).toEqual(0);
150150
});
151+
152+
it('should decache the necessary bytes', () => {
153+
const url1 = 'test-url-1';
154+
const file1 = { data: new Uint8Array([1, 2, 3]), position: 3 };
155+
fileManager.set(url1, file1);
156+
const url2 = 'test-url-2';
157+
const file2 = { data: new Uint8Array([4, 5, 6]), position: 3 };
158+
fileManager.set(url2, file2);
159+
const url3 = 'test-url-3';
160+
const file3 = { data: new Uint8Array([7, 8]), position: 2 };
161+
fileManager.set(url3, file3);
162+
fileManager.decacheNecessaryBytes(url3, file3.data.byteLength);
163+
164+
expect(fileManager.get(url1)).toBeNull();
165+
expect(fileManager.get(url2)).toBe(file2.data);
166+
expect(fileManager.get(url3)).toBe(file3.data);
167+
expect(fileManager.getTotalSize()).toEqual(5);
168+
});
151169
});

0 commit comments

Comments
 (0)