Skip to content

Commit e982223

Browse files
committed
refactor: replace state file callbacks with result-based flow
Replace StateFileContext callbacks with StateFileSetupResult type. State file restoration now collects results and completes at end of import instead of tracking pending counts with callbacks.
1 parent ce4e480 commit e982223

3 files changed

Lines changed: 67 additions & 122 deletions

File tree

src/io/import/common.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ export interface IntermediateResult {
3838
dataSources: DataSource[];
3939
}
4040

41+
export interface StateFileSetupResult {
42+
type: 'stateFileSetup';
43+
dataSources: DataSource[];
44+
manifest: Manifest;
45+
stateFiles: FileEntry[];
46+
}
47+
4148
export interface ErrorResult {
4249
type: 'error';
4350
error: Error;
@@ -48,6 +55,7 @@ export type ImportResult =
4855
| LoadableResult
4956
| ConfigResult
5057
| IntermediateResult
58+
| StateFileSetupResult
5159
| OkayResult
5260
| ErrorResult;
5361

@@ -101,31 +109,14 @@ export const asOkayResult = (dataSource: DataSource): OkayResult => ({
101109
export type ArchiveContents = Record<string, File>;
102110
export type ArchiveCache = Map<File, Awaitable<ArchiveContents>>;
103111

104-
export interface StateFileContext {
105-
manifest: Manifest;
106-
stateFiles: FileEntry[];
107-
stateIDToStoreID: Map<string, string>;
108-
pendingLeafCount: number;
109-
onLeafImported: (stateID: string, storeID: string) => void;
110-
onAllLeavesImported: () => Promise<void>;
111-
}
112-
113112
export interface ImportContext {
114-
// Caches URL responses
115113
fetchFileCache?: FetchCache<File>;
116-
// Caches archives. ArchiveFile -> { [archivePath]: File }
117114
archiveCache?: ArchiveCache;
118-
// Records dicom files
119115
dicomDataSources?: DataSource[];
120116
onCleanup?: (fn: () => void) => void;
121-
/**
122-
* A reference to importDataSources for nested imports.
123-
*/
124117
importDataSources?: (
125118
dataSources: DataSource[]
126119
) => Promise<ImportDataSourcesResult[]>;
127-
// State file restoration context for 3-phase restoration
128-
stateFileContext?: StateFileContext;
129120
}
130121

131122
export type ImportHandler = ChainHandler<

src/io/import/importDataSources.ts

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ import {
1010
ErrorResult,
1111
ImportDataSourcesResult,
1212
asIntermediateResult,
13+
StateFileSetupResult,
1314
} from '@/src/io/import/common';
14-
import {
15-
DataSource,
16-
ChunkSource,
17-
StateFileLeaf,
18-
} from '@/src/io/import/dataSource';
15+
import { DataSource, ChunkSource } from '@/src/io/import/dataSource';
1916
import handleDicomFile from '@/src/io/import/processors/handleDicomFile';
2017
import extractArchive from '@/src/io/import/processors/extractArchive';
2118
import extractArchiveTarget from '@/src/io/import/processors/extractArchiveTarget';
2219
import handleAmazonS3 from '@/src/io/import/processors/handleAmazonS3';
2320
import handleGoogleCloudStorage from '@/src/io/import/processors/handleGoogleCloudStorage';
2421
import importSingleFile from '@/src/io/import/processors/importSingleFile';
2522
import handleRemoteManifest from '@/src/io/import/processors/remoteManifest';
26-
import { restoreStateFile } from '@/src/io/import/processors/restoreStateFile';
23+
import {
24+
restoreStateFile,
25+
completeStateFileRestore,
26+
} from '@/src/io/import/processors/restoreStateFile';
2727
import updateFileMimeType from '@/src/io/import/processors/updateFileMimeType';
2828
import handleConfig from '@/src/io/import/processors/handleConfig';
2929
import {
@@ -74,39 +74,18 @@ const applyConfigsPostState = (
7474
}
7575
});
7676

77-
function findStateFileLeaf(dataSource: DataSource): StateFileLeaf | undefined {
77+
function findStateFileLeaf(dataSource: DataSource) {
7878
let current: DataSource | undefined = dataSource;
7979
while (current) {
8080
if (current.stateFileLeaf) return current.stateFileLeaf;
8181
current = current.parent;
8282
}
83-
// For collections (DICOM volumes), check the first source's parent chain
8483
if (dataSource.type === 'collection' && dataSource.sources.length > 0) {
8584
return findStateFileLeaf(dataSource.sources[0]);
8685
}
8786
return undefined;
8887
}
8988

90-
async function handleStateFileResult(
91-
result: LoadableResult,
92-
importContext: ImportContext
93-
) {
94-
const stateLeaf = findStateFileLeaf(result.dataSource);
95-
if (stateLeaf && importContext.stateFileContext) {
96-
const ctx = importContext.stateFileContext;
97-
ctx.stateIDToStoreID.set(stateLeaf.stateID, result.dataID);
98-
99-
// Phase 2: Immediately bind view to this data so user sees streaming
100-
ctx.onLeafImported(stateLeaf.stateID, result.dataID);
101-
102-
ctx.pendingLeafCount--;
103-
if (ctx.pendingLeafCount === 0) {
104-
// Phase 3: Restore tools/segments after all data loaded
105-
await ctx.onAllLeavesImported();
106-
}
107-
}
108-
}
109-
11089
async function importDicomChunkSources(sources: ChunkSource[]) {
11190
if (sources.length === 0) return [];
11291

@@ -180,6 +159,7 @@ export async function importDataSources(
180159

181160
const chunkSources: DataSource[] = [];
182161
const configResults: ConfigResult[] = [];
162+
const stateFileSetups: StateFileSetupResult[] = [];
183163
const results: ImportDataSourcesResult[] = [];
184164

185165
let queue = dataSources.map((src) => ({
@@ -194,14 +174,16 @@ export async function importDataSources(
194174
queue = queue.filter((_, i) => i !== index);
195175

196176
switch (result.type) {
177+
case 'stateFileSetup':
178+
stateFileSetups.push(result);
179+
// fallthrough to handle dataSources
197180
case 'intermediate': {
198181
const [chunks, otherSources] = partition(
199182
(ds) => ds.type === 'chunk',
200183
result.dataSources
201184
);
202185
chunkSources.push(...chunks);
203186

204-
// try loading intermediate results
205187
queue.push(
206188
...otherSources.map((src) => ({
207189
promise: evaluateChain(src, handlers, importContext),
@@ -220,11 +202,8 @@ export async function importDataSources(
220202
break;
221203
case 'ok':
222204
case 'error':
223-
results.push(result);
224-
break;
225205
case 'data':
226206
results.push(result);
227-
await handleStateFileResult(result, importContext);
228207
break;
229208
default:
230209
throw new Error(`Invalid result: ${result}`);
@@ -243,10 +222,6 @@ export async function importDataSources(
243222
try {
244223
const dicomResults = await importDicomChunkSources(dicomChunkSources);
245224
results.push(...dicomResults);
246-
// Handle state file results for DICOM volumes
247-
for (const dicomResult of dicomResults) {
248-
await handleStateFileResult(dicomResult, importContext);
249-
}
250225
} catch (err) {
251226
const errorSource =
252227
dicomChunkSources.length === 1
@@ -255,11 +230,27 @@ export async function importDataSources(
255230
results.push(asErrorResult(ensureError(err), errorSource));
256231
}
257232

258-
// save data sources
259-
useDatasetStore().addDataSources(
260-
results.filter((result): result is LoadableResult => result.type === 'data')
233+
const loadableResults = results.filter(
234+
(r): r is LoadableResult => r.type === 'data'
261235
);
262236

237+
useDatasetStore().addDataSources(loadableResults);
238+
239+
for (const setup of stateFileSetups) {
240+
const stateIDToStoreID: Record<string, string> = {};
241+
for (const loadable of loadableResults) {
242+
const leaf = findStateFileLeaf(loadable.dataSource);
243+
if (leaf) {
244+
stateIDToStoreID[leaf.stateID] = loadable.dataID;
245+
}
246+
}
247+
await completeStateFileRestore(
248+
setup.manifest,
249+
setup.stateFiles,
250+
stateIDToStoreID
251+
);
252+
}
253+
263254
return results;
264255
}
265256

src/io/import/processors/restoreStateFile.ts

Lines changed: 29 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {
55
} from '@/src/io/state-file/schema';
66
import {
77
asErrorResult,
8-
asIntermediateResult,
98
ImportHandler,
10-
StateFileContext,
9+
StateFileSetupResult,
1110
} from '@/src/io/import/common';
1211
import { MANIFEST, isStateFile } from '@/src/io/state-file';
1312
import { partition, getURLBasename } from '@/src/utils';
@@ -113,27 +112,38 @@ function prepareLeafDataSources(manifest: Manifest, datasetFiles: FileEntry[]) {
113112
});
114113
}
115114

116-
async function completeStateFileRestore(ctx: StateFileContext) {
117-
const { manifest, stateFiles, stateIDToStoreID } = ctx;
118-
const stateIDToStoreIDRecord = Object.fromEntries(stateIDToStoreID);
115+
export async function completeStateFileRestore(
116+
manifest: Manifest,
117+
stateFiles: FileEntry[],
118+
stateIDToStoreID: Record<string, string>
119+
) {
120+
const viewStore = useViewStore();
119121

120-
useViewConfigStore().deserializeAll(manifest, stateIDToStoreIDRecord);
122+
Object.entries(stateIDToStoreID).forEach(([stateID, storeID]) => {
123+
viewStore.bindViewsToData(stateID, storeID, manifest);
124+
});
125+
126+
if (!manifest.viewByID) {
127+
const firstStoreID = Object.values(stateIDToStoreID)[0];
128+
if (firstStoreID) {
129+
viewStore.setDataForAllViews(firstStoreID);
130+
}
131+
}
132+
133+
useViewConfigStore().deserializeAll(manifest, stateIDToStoreID);
121134

122135
const segmentGroupIDMap = await useSegmentGroupStore().deserialize(
123136
manifest,
124137
stateFiles,
125-
stateIDToStoreIDRecord
138+
stateIDToStoreID
126139
);
127-
useLayersStore().deserialize(manifest, stateIDToStoreIDRecord);
128140

129-
useToolStore().deserialize(
130-
manifest,
131-
segmentGroupIDMap,
132-
stateIDToStoreIDRecord
133-
);
141+
useLayersStore().deserialize(manifest, stateIDToStoreID);
142+
143+
useToolStore().deserialize(manifest, segmentGroupIDMap, stateIDToStoreID);
134144
}
135145

136-
export const restoreStateFile: ImportHandler = async (dataSource, context) => {
146+
export const restoreStateFile: ImportHandler = async (dataSource) => {
137147
if (dataSource.type === 'file' && (await isStateFile(dataSource.file))) {
138148
const stateFileContents = await extractFilesFromZip(dataSource.file);
139149

@@ -158,61 +168,14 @@ export const restoreStateFile: ImportHandler = async (dataSource, context) => {
158168
);
159169
}
160170

161-
// Phase 1: Set up view layout immediately (without data bindings)
162-
const viewStore = useViewStore();
163-
viewStore.deserializeLayout(manifest);
164-
165-
// Prepare leaf data sources with state file tags
166-
const leafDataSources = prepareLeafDataSources(manifest, restOfStateFile);
167-
168-
if (leafDataSources.length === 0) {
169-
// No datasets to import, complete restoration immediately
170-
await completeStateFileRestore({
171-
manifest,
172-
stateFiles: restOfStateFile,
173-
stateIDToStoreID: new Map(),
174-
pendingLeafCount: 0,
175-
onLeafImported: () => {},
176-
onAllLeavesImported: async () => {},
177-
});
178-
179-
// When viewByID is not in manifest, there's no data to assign
180-
return asIntermediateResult([]);
181-
}
171+
useViewStore().deserializeLayout(manifest);
182172

183-
// Set up state file context for phase 2 and 3 callbacks
184-
const stateFileContext: StateFileContext = {
173+
return {
174+
type: 'stateFileSetup',
175+
dataSources: prepareLeafDataSources(manifest, restOfStateFile),
185176
manifest,
186177
stateFiles: restOfStateFile,
187-
stateIDToStoreID: new Map(),
188-
pendingLeafCount: leafDataSources.length,
189-
onLeafImported: (stateID: string, storeID: string) => {
190-
// Phase 2: Bind view to data as each leaf completes
191-
viewStore.bindViewsToData(stateID, storeID, manifest);
192-
},
193-
onAllLeavesImported: async () => {
194-
// Phase 3: Restore segment groups, tools, layers after all data loaded
195-
await completeStateFileRestore(stateFileContext);
196-
197-
// When viewByID is not in manifest, assign first dataset to all views
198-
if (!manifest.viewByID) {
199-
const firstStoreID = stateFileContext.stateIDToStoreID
200-
.values()
201-
.next().value;
202-
if (firstStoreID) {
203-
viewStore.setDataForAllViews(firstStoreID);
204-
}
205-
}
206-
},
207-
};
208-
209-
// Store context for use by main pipeline
210-
if (context) {
211-
context.stateFileContext = stateFileContext;
212-
}
213-
214-
// Return leaf data sources to be processed by main pipeline
215-
return asIntermediateResult(leafDataSources);
178+
} as StateFileSetupResult;
216179
}
217180
return Skip;
218181
};

0 commit comments

Comments
 (0)