@jbrowse/core 4.1.15 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { RemoteFile } from 'generic-filehandle2';
|
|
2
2
|
export declare function clearCache(): void;
|
|
3
3
|
export declare class RemoteFileWithRangeCache extends RemoteFile {
|
|
4
|
+
private cachedStat?;
|
|
5
|
+
stat(): Promise<{
|
|
6
|
+
size: number;
|
|
7
|
+
}>;
|
|
4
8
|
private fetchRange;
|
|
5
9
|
private getCachedRange;
|
|
6
10
|
fetch(url: string | RequestInfo, init?: RequestInit): Promise<Response>;
|
|
@@ -36,7 +36,7 @@ function limitConcurrency(fn) {
|
|
|
36
36
|
runNext();
|
|
37
37
|
}, (err) => {
|
|
38
38
|
activeCount--;
|
|
39
|
-
reject(err);
|
|
39
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
40
40
|
runNext();
|
|
41
41
|
});
|
|
42
42
|
}
|
|
@@ -53,46 +53,75 @@ function cacheKey(url, chunkIndex) {
|
|
|
53
53
|
return `${url}:${chunkIndex}`;
|
|
54
54
|
}
|
|
55
55
|
export class RemoteFileWithRangeCache extends RemoteFile {
|
|
56
|
+
cachedStat;
|
|
57
|
+
async stat() {
|
|
58
|
+
if (!this.cachedStat) {
|
|
59
|
+
await this.getCachedRange(this.url, 0, 1);
|
|
60
|
+
}
|
|
61
|
+
return this.cachedStat ?? { size: 0 };
|
|
62
|
+
}
|
|
56
63
|
async fetchRange(url, start, end, signal) {
|
|
57
64
|
const res = await super.fetch(url, {
|
|
58
65
|
signal: signal ?? undefined,
|
|
59
66
|
headers: { range: `bytes=${start}-${end}` },
|
|
60
67
|
});
|
|
68
|
+
if (res.status === 416) {
|
|
69
|
+
return new Uint8Array(0);
|
|
70
|
+
}
|
|
61
71
|
if (!res.ok) {
|
|
62
|
-
const errorMessage = `HTTP ${res.status} fetching ${url} bytes ${start}-${end}`;
|
|
63
72
|
const hint = ' (should be 206 for range requests)';
|
|
64
|
-
|
|
73
|
+
const msg = `HTTP ${res.status} fetching ${url} bytes ${start}-${end}${res.status === 200 ? hint : ''}`;
|
|
74
|
+
throw Object.assign(new Error(msg), { status: res.status });
|
|
75
|
+
}
|
|
76
|
+
if (!this.cachedStat) {
|
|
77
|
+
const contentRange = res.headers.get('content-range');
|
|
78
|
+
const match = contentRange ? /\/(\d+)$/.exec(contentRange) : null;
|
|
79
|
+
if (match) {
|
|
80
|
+
this.cachedStat = { size: parseInt(match[1], 10) };
|
|
81
|
+
}
|
|
65
82
|
}
|
|
66
83
|
return new Uint8Array(await res.arrayBuffer());
|
|
67
84
|
}
|
|
68
85
|
async getCachedRange(url, start, length, signal) {
|
|
69
86
|
const startChunk = Math.floor(start / CHUNK_SIZE);
|
|
70
87
|
const endChunk = Math.floor((start + length - 1) / CHUNK_SIZE);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return Promise.resolve(existing);
|
|
88
|
+
let effectiveEndChunk = endChunk;
|
|
89
|
+
for (let i = startChunk; i < endChunk; i++) {
|
|
90
|
+
const c = getCached(cacheKey(url, i));
|
|
91
|
+
if (c !== undefined && c.length < CHUNK_SIZE) {
|
|
92
|
+
effectiveEndChunk = i;
|
|
93
|
+
break;
|
|
78
94
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
}
|
|
96
|
+
const chunkCount = effectiveEndChunk - startChunk + 1;
|
|
97
|
+
const runs = [];
|
|
98
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
99
|
+
if (!getCached(cacheKey(url, startChunk + i))) {
|
|
100
|
+
const lastRun = runs[runs.length - 1];
|
|
101
|
+
if (lastRun?.end === i - 1) {
|
|
102
|
+
lastRun.end = i;
|
|
83
103
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
else {
|
|
105
|
+
runs.push({ start: i, end: i });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
await Promise.all(runs.map(run => limitConcurrency(async () => {
|
|
110
|
+
const runStartChunk = startChunk + run.start;
|
|
111
|
+
const runEndChunk = startChunk + run.end;
|
|
112
|
+
const rangeStart = runStartChunk * CHUNK_SIZE;
|
|
113
|
+
const rangeEnd = (runEndChunk + 1) * CHUNK_SIZE - 1;
|
|
114
|
+
const data = await this.fetchRange(url, rangeStart, rangeEnd, signal);
|
|
115
|
+
for (let i = run.start; i <= run.end; i++) {
|
|
116
|
+
const offset = (i - run.start) * CHUNK_SIZE;
|
|
117
|
+
putCached(cacheKey(url, startChunk + i), data.subarray(offset, offset + CHUNK_SIZE));
|
|
118
|
+
}
|
|
119
|
+
})));
|
|
91
120
|
const offsetInFirstChunk = start - startChunk * CHUNK_SIZE;
|
|
92
121
|
const result = new Uint8Array(length);
|
|
93
122
|
let written = 0;
|
|
94
|
-
for (
|
|
95
|
-
const chunk =
|
|
123
|
+
for (let i = 0; i < chunkCount; i++) {
|
|
124
|
+
const chunk = getCached(cacheKey(url, startChunk + i));
|
|
96
125
|
const sourceStart = i === 0 ? offsetInFirstChunk : 0;
|
|
97
126
|
const available = chunk.length - sourceStart;
|
|
98
127
|
const needed = length - written;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/core",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 core libraries used by plugins",
|
|
6
6
|
"keywords": [
|
|
@@ -484,9 +484,9 @@
|
|
|
484
484
|
"@jbrowse/mobx-state-tree": "^5.6.0",
|
|
485
485
|
"@jbrowse/quick-lru": "^7.3.5",
|
|
486
486
|
"@leeoniya/ufuzzy": "^1.0.19",
|
|
487
|
-
"@mui/icons-material": "^7.3.
|
|
488
|
-
"@mui/material": "^7.3.
|
|
489
|
-
"@mui/system": "^7.3.
|
|
487
|
+
"@mui/icons-material": "^7.3.10",
|
|
488
|
+
"@mui/material": "^7.3.10",
|
|
489
|
+
"@mui/system": "^7.3.10",
|
|
490
490
|
"@mui/types": "^7.4.12",
|
|
491
491
|
"@mui/x-data-grid": "^8.28.2",
|
|
492
492
|
"@types/file-saver-es": "^2.0.3",
|
|
@@ -495,7 +495,7 @@
|
|
|
495
495
|
"copy-to-clipboard": "^3.3.3",
|
|
496
496
|
"deepmerge": "^4.3.1",
|
|
497
497
|
"detect-node": "^2.1.0",
|
|
498
|
-
"dompurify": "^3.
|
|
498
|
+
"dompurify": "^3.4.0",
|
|
499
499
|
"escape-html": "^1.0.3",
|
|
500
500
|
"fast-deep-equal": "^3.1.3",
|
|
501
501
|
"file-saver-es": "^2.0.5",
|