@fluid-tools/fetch-tool 1.4.0-115997 → 2.0.0-dev-rc.1.0.0.224419

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.
Files changed (37) hide show
  1. package/.eslintrc.js +6 -8
  2. package/CHANGELOG.md +117 -0
  3. package/README.md +38 -7
  4. package/bin/fluid-fetch +0 -0
  5. package/dist/fluidAnalyzeMessages.d.ts.map +1 -1
  6. package/dist/fluidAnalyzeMessages.js +106 -116
  7. package/dist/fluidAnalyzeMessages.js.map +1 -1
  8. package/dist/fluidFetch.js +5 -3
  9. package/dist/fluidFetch.js.map +1 -1
  10. package/dist/fluidFetchArgs.d.ts +0 -3
  11. package/dist/fluidFetchArgs.d.ts.map +1 -1
  12. package/dist/fluidFetchArgs.js +10 -14
  13. package/dist/fluidFetchArgs.js.map +1 -1
  14. package/dist/fluidFetchInit.d.ts +0 -1
  15. package/dist/fluidFetchInit.d.ts.map +1 -1
  16. package/dist/fluidFetchInit.js +41 -34
  17. package/dist/fluidFetchInit.js.map +1 -1
  18. package/dist/fluidFetchMessages.d.ts.map +1 -1
  19. package/dist/fluidFetchMessages.js +168 -200
  20. package/dist/fluidFetchMessages.js.map +1 -1
  21. package/dist/fluidFetchSharePoint.d.ts +0 -1
  22. package/dist/fluidFetchSharePoint.d.ts.map +1 -1
  23. package/dist/fluidFetchSharePoint.js +20 -6
  24. package/dist/fluidFetchSharePoint.js.map +1 -1
  25. package/dist/fluidFetchSnapshot.d.ts.map +1 -1
  26. package/dist/fluidFetchSnapshot.js +18 -20
  27. package/dist/fluidFetchSnapshot.js.map +1 -1
  28. package/package.json +47 -42
  29. package/prettier.config.cjs +8 -0
  30. package/src/fluidAnalyzeMessages.ts +701 -630
  31. package/src/fluidFetch.ts +93 -88
  32. package/src/fluidFetchArgs.ts +167 -168
  33. package/src/fluidFetchInit.ts +133 -104
  34. package/src/fluidFetchMessages.ts +253 -232
  35. package/src/fluidFetchSharePoint.ts +130 -112
  36. package/src/fluidFetchSnapshot.ts +313 -295
  37. package/tsconfig.json +8 -15
@@ -5,351 +5,369 @@
5
5
 
6
6
  import fs from "fs";
7
7
  import util from "util";
8
- import { bufferToString, stringToBuffer, TelemetryNullLogger } from "@fluidframework/common-utils";
9
- import {
10
- IDocumentService,
11
- IDocumentStorageService,
12
- } from "@fluidframework/driver-definitions";
13
- import {
14
- ISnapshotTree,
15
- IVersion,
16
- } from "@fluidframework/protocol-definitions";
17
- import { BlobAggregationStorage } from "@fluidframework/driver-utils";
8
+
9
+ import { bufferToString, stringToBuffer } from "@fluid-internal/client-utils";
10
+ import { IDocumentService, IDocumentStorageService } from "@fluidframework/driver-definitions";
11
+ import { ISnapshotTree, IVersion } from "@fluidframework/protocol-definitions";
12
+
18
13
  import { formatNumber } from "./fluidAnalyzeMessages";
19
14
  import {
20
- dumpSnapshotStats,
21
- dumpSnapshotTrees,
22
- dumpSnapshotVersions,
23
- paramActualFormatting,
24
- paramNumSnapshotVersions,
25
- paramSnapshotVersionIndex,
26
- paramUnpackAggregatedBlobs,
15
+ dumpSnapshotStats,
16
+ dumpSnapshotTrees,
17
+ dumpSnapshotVersions,
18
+ paramActualFormatting,
19
+ paramNumSnapshotVersions,
20
+ paramSnapshotVersionIndex,
27
21
  } from "./fluidFetchArgs";
28
22
  import { latestVersionsId } from "./fluidFetchInit";
29
23
 
30
24
  interface ISnapshotInfo {
31
- blobCountNew: number;
32
- blobCount: number;
33
- size: number;
34
- sizeNew: number;
25
+ blobCountNew: number;
26
+ blobCount: number;
27
+ size: number;
28
+ sizeNew: number;
35
29
  }
36
30
 
37
31
  type IFetchedData = IFetchedBlob | IFetchedTree;
38
32
 
39
33
  interface IFetchedBlob {
40
- treePath: string;
41
- filename: string;
42
- blobId: string;
43
- blob: Promise<ArrayBufferLike | undefined>;
44
- reused: boolean;
34
+ treePath: string;
35
+ filename: string;
36
+ blobId: string;
37
+ blob: Promise<ArrayBufferLike | undefined>;
38
+ reused: boolean;
45
39
  }
46
40
 
47
41
  interface IFetchedTree {
48
- treePath: string;
49
- blobId: string;
50
- filename: string;
51
- blob: ArrayBufferLike;
42
+ treePath: string;
43
+ blobId: string;
44
+ filename: string;
45
+ blob: ArrayBufferLike;
52
46
 
53
- reused: false;
47
+ reused: false;
54
48
 
55
- patched: boolean;
49
+ patched: boolean;
56
50
  }
57
51
 
58
52
  function isFetchedTree(fetchedData: IFetchedData): fetchedData is IFetchedTree {
59
- return "patched" in fetchedData;
53
+ return "patched" in fetchedData;
60
54
  }
61
55
 
62
56
  const blobCache = new Map<string, Promise<ArrayBufferLike>>();
63
57
  let blobCachePrevious = new Map<string, Promise<ArrayBufferLike>>();
64
58
  let blobCacheCurrent = new Map<string, Promise<ArrayBufferLike>>();
65
59
 
66
- function fetchBlobs(prefix: string,
67
- tree: ISnapshotTree,
68
- storage: IDocumentStorageService,
69
- blobIdMap: Map<string, number>,
60
+ function fetchBlobs(
61
+ prefix: string,
62
+ tree: ISnapshotTree,
63
+ storage: IDocumentStorageService,
64
+ blobIdMap: Map<string, number>,
70
65
  ) {
71
- const result: IFetchedBlob[] = [];
72
- for (const item of Object.keys(tree.blobs)) {
73
- const treePath = `${prefix}${item}`;
74
- const blobId = tree.blobs[item];
75
- if (blobId !== null) {
76
- let reused = true;
77
- let blob = blobCachePrevious.get(blobId);
78
- if (!blob) {
79
- reused = false;
80
- blob = blobCache.get(blobId);
81
- if (blob === undefined) {
82
- blob = storage.readBlob(blobId);
83
- blobCache.set(blobId, blob);
84
- }
85
- }
86
- blobCacheCurrent.set(blobId, blob);
87
-
88
- // Use the blobIdMap to assign a number for each unique blob
89
- // and use it as a prefix for files to avoid case-insensitive fs
90
- let index = blobIdMap.get(blobId);
91
- if (index === undefined) {
92
- index = blobIdMap.size;
93
- blobIdMap.set(blobId, index);
94
- }
95
- const filename = `${index}-${blobId}`;
96
- result.push({ treePath, blobId, blob, reused, filename });
97
-
98
- // patch the tree so that we can write it out to reference the file
99
- tree.blobs[item] = filename;
100
- }
101
- }
102
- return result;
66
+ const result: IFetchedBlob[] = [];
67
+ for (const item of Object.keys(tree.blobs)) {
68
+ const treePath = `${prefix}${item}`;
69
+ const blobId = tree.blobs[item];
70
+ if (blobId !== null) {
71
+ let reused = true;
72
+ let blob = blobCachePrevious.get(blobId);
73
+ if (!blob) {
74
+ reused = false;
75
+ blob = blobCache.get(blobId);
76
+ if (blob === undefined) {
77
+ blob = storage.readBlob(blobId);
78
+ blobCache.set(blobId, blob);
79
+ }
80
+ }
81
+ blobCacheCurrent.set(blobId, blob);
82
+
83
+ // Use the blobIdMap to assign a number for each unique blob
84
+ // and use it as a prefix for files to avoid case-insensitive fs
85
+ let index = blobIdMap.get(blobId);
86
+ if (index === undefined) {
87
+ index = blobIdMap.size;
88
+ blobIdMap.set(blobId, index);
89
+ }
90
+ const filename = `${index}-${blobId}`;
91
+ result.push({ treePath, blobId, blob, reused, filename });
92
+
93
+ // patch the tree so that we can write it out to reference the file
94
+ tree.blobs[item] = filename;
95
+ }
96
+ }
97
+ return result;
103
98
  }
104
99
 
105
100
  function createTreeBlob(tree: ISnapshotTree, prefix: string, patched: boolean): IFetchedTree {
106
- const id = tree.id ?? "original";
107
- const blob = stringToBuffer(JSON.stringify(tree), "utf8");
108
- const filename = patched ? "tree" : `tree-${id}`;
109
- const treePath = `${prefix}${filename}`;
110
- return { treePath, blobId: "original tree $id", filename, blob, patched, reused: false };
101
+ const id = tree.id ?? "original";
102
+ const blob = stringToBuffer(JSON.stringify(tree), "utf8");
103
+ const filename = patched ? "tree" : `tree-${id}`;
104
+ const treePath = `${prefix}${filename}`;
105
+ return { treePath, blobId: "original tree $id", filename, blob, patched, reused: false };
111
106
  }
112
107
 
113
108
  async function fetchBlobsFromSnapshotTree(
114
- storage: IDocumentStorageService,
115
- tree: ISnapshotTree,
116
- prefix: string = "/",
117
- parentBlobIdMap?: Map<string, number>): Promise<IFetchedData[]> {
118
- const isTopLevel = !parentBlobIdMap;
119
- if (isTopLevel && dumpSnapshotTrees) {
120
- console.log(tree);
121
- }
122
-
123
- if (prefix === "/") {
124
- blobCachePrevious = blobCacheCurrent;
125
- blobCacheCurrent = new Map<string, Promise<ArrayBufferLike>>();
126
- }
127
-
128
- // Create the tree info before fetching blobs (which will modify it)
129
- let topLevelBlob: IFetchedTree | undefined;
130
- if (isTopLevel) {
131
- topLevelBlob = createTreeBlob(tree, prefix, false);
132
- }
133
-
134
- const blobIdMap = parentBlobIdMap ?? new Map<string, number>();
135
- let result: IFetchedData[] = fetchBlobs(prefix, tree, storage, blobIdMap);
136
-
137
- for (const subtreeId of Object.keys(tree.trees)) {
138
- const subtree = tree.trees[subtreeId];
139
- const dataStoreBlobs = await fetchBlobsFromSnapshotTree(
140
- storage,
141
- subtree,
142
- `${prefix}${subtreeId}/`, blobIdMap);
143
- result = result.concat(dataStoreBlobs);
144
- }
145
-
146
- if (topLevelBlob) {
147
- result.push(topLevelBlob);
148
- result.push(createTreeBlob(tree, prefix, true));
149
- }
150
- return result;
109
+ storage: IDocumentStorageService,
110
+ tree: ISnapshotTree,
111
+ prefix: string = "/",
112
+ parentBlobIdMap?: Map<string, number>,
113
+ ): Promise<IFetchedData[]> {
114
+ const isTopLevel = !parentBlobIdMap;
115
+ if (isTopLevel && dumpSnapshotTrees) {
116
+ console.log(tree);
117
+ }
118
+
119
+ if (prefix === "/") {
120
+ blobCachePrevious = blobCacheCurrent;
121
+ blobCacheCurrent = new Map<string, Promise<ArrayBufferLike>>();
122
+ }
123
+
124
+ // Create the tree info before fetching blobs (which will modify it)
125
+ let topLevelBlob: IFetchedTree | undefined;
126
+ if (isTopLevel) {
127
+ topLevelBlob = createTreeBlob(tree, prefix, false);
128
+ }
129
+
130
+ const blobIdMap = parentBlobIdMap ?? new Map<string, number>();
131
+ let result: IFetchedData[] = fetchBlobs(prefix, tree, storage, blobIdMap);
132
+
133
+ for (const subtreeId of Object.keys(tree.trees)) {
134
+ const subtree = tree.trees[subtreeId];
135
+ const dataStoreBlobs = await fetchBlobsFromSnapshotTree(
136
+ storage,
137
+ subtree,
138
+ `${prefix}${subtreeId}/`,
139
+ blobIdMap,
140
+ );
141
+ result = result.concat(dataStoreBlobs);
142
+ }
143
+
144
+ if (topLevelBlob) {
145
+ result.push(topLevelBlob);
146
+ result.push(createTreeBlob(tree, prefix, true));
147
+ }
148
+ return result;
151
149
  }
152
150
 
153
151
  function getDumpFetchedData(fetchedData: IFetchedData[]) {
154
- const sorted = fetchedData.sort((a, b) => a.treePath.localeCompare(b.treePath));
155
- return sorted.filter((item) => !isFetchedTree(item) || !item.patched);
152
+ const sorted = fetchedData.sort((a, b) => a.treePath.localeCompare(b.treePath));
153
+ return sorted.filter((item) => !isFetchedTree(item) || !item.patched);
156
154
  }
157
155
 
158
156
  async function dumpSnapshotTreeVerbose(name: string, fetchedData: IFetchedData[]) {
159
- let size = 0;
160
- const sorted = getDumpFetchedData(fetchedData);
161
-
162
- let nameLength = 10;
163
- for (const item of sorted) {
164
- nameLength = Math.max(nameLength, item.treePath.length);
165
- }
166
-
167
- console.log("");
168
- console.log(`${"Blob Path".padEnd(nameLength)} | Reused | Bytes`);
169
- console.log("-".repeat(nameLength + 26));
170
- for (const item of sorted) {
171
- const buffer = await item.blob;
172
- if (buffer === undefined) {
173
- continue;
174
- }
175
- const blob = bufferToString(buffer, "utf8");
176
- // eslint-disable-next-line max-len
177
- console.log(`${item.treePath.padEnd(nameLength)} | ${item.reused ? "X" : " "} | ${formatNumber(blob.length).padStart(10)}`);
178
- size += blob.length;
179
- }
180
-
181
- console.log("-".repeat(nameLength + 26));
182
- console.log(`${"Total snapshot size".padEnd(nameLength)} | | ${formatNumber(size).padStart(10)}`);
157
+ let size = 0;
158
+ const sorted = getDumpFetchedData(fetchedData);
159
+
160
+ let nameLength = 10;
161
+ for (const item of sorted) {
162
+ nameLength = Math.max(nameLength, item.treePath.length);
163
+ }
164
+
165
+ console.log("");
166
+ console.log(`${"Blob Path".padEnd(nameLength)} | Reused | Bytes`);
167
+ console.log("-".repeat(nameLength + 26));
168
+ for (const item of sorted) {
169
+ const buffer = await item.blob;
170
+ if (buffer === undefined) {
171
+ continue;
172
+ }
173
+ const blob = bufferToString(buffer, "utf8");
174
+ console.log(
175
+ `${item.treePath.padEnd(nameLength)} | ${item.reused ? "X" : " "} | ${formatNumber(
176
+ blob.length,
177
+ ).padStart(10)}`,
178
+ );
179
+ size += blob.length;
180
+ }
181
+
182
+ console.log("-".repeat(nameLength + 26));
183
+ console.log(
184
+ `${"Total snapshot size".padEnd(nameLength)} | | ${formatNumber(size).padStart(10)}`,
185
+ );
183
186
  }
184
187
 
185
188
  async function dumpSnapshotTree(name: string, fetchedData: IFetchedData[]): Promise<ISnapshotInfo> {
186
- let size = 0;
187
- let sizeNew = 0;
188
- let blobCountNew = 0;
189
- const sorted = getDumpFetchedData(fetchedData);
190
-
191
- for (const item of sorted) {
192
- const buffer = await item.blob;
193
- if (buffer === undefined) {
194
- continue;
195
- }
196
- const blob = bufferToString(buffer, "utf8");
197
- if (!item.reused) {
198
- sizeNew += blob.length;
199
- blobCountNew++;
200
- }
201
- size += blob.length;
202
- }
203
-
204
- return { blobCountNew, blobCount: sorted.length, size, sizeNew };
189
+ let size = 0;
190
+ let sizeNew = 0;
191
+ let blobCountNew = 0;
192
+ const sorted = getDumpFetchedData(fetchedData);
193
+
194
+ for (const item of sorted) {
195
+ const buffer = await item.blob;
196
+ if (buffer === undefined) {
197
+ continue;
198
+ }
199
+ const blob = bufferToString(buffer, "utf8");
200
+ if (!item.reused) {
201
+ sizeNew += blob.length;
202
+ blobCountNew++;
203
+ }
204
+ size += blob.length;
205
+ }
206
+
207
+ return { blobCountNew, blobCount: sorted.length, size, sizeNew };
205
208
  }
206
209
 
207
210
  async function saveSnapshot(name: string, fetchedData: IFetchedData[], saveDir: string) {
208
- const outDir = `${saveDir}/${name}/`;
209
- const mkdir = util.promisify(fs.mkdir);
210
-
211
- await mkdir(`${outDir}/decoded`, { recursive: true });
212
- await Promise.all(fetchedData.map(async (item) => {
213
- const buffer = await item.blob;
214
- if (buffer === undefined) {
215
- console.error(`ERROR: Unable to get data for blob ${item.blobId}`);
216
- return;
217
- }
218
-
219
- if (!isFetchedTree(item)) {
220
- // Just write the data as is.
221
- fs.writeFileSync(`${outDir}/${item.filename}`, Buffer.from(buffer));
222
-
223
- // we assume that the buffer is utf8 here, which currently is true for
224
- // all of our snapshot blobs. It doesn't necessary be true in the future
225
- let decoded = bufferToString(buffer, "utf8");
226
- try {
227
- if (!paramActualFormatting) {
228
- decoded = JSON.stringify(JSON.parse(decoded), undefined, 2);
229
- }
230
- } catch (e) {
231
- }
232
- fs.writeFileSync(
233
- `${outDir}/decoded/${item.filename}.json`, decoded);
234
- } else {
235
- // Write out same data for tree decoded or not, except for formatting
236
- const treeString = bufferToString(buffer, "utf8");
237
- fs.writeFileSync(`${outDir}/${item.filename}.json`, treeString);
238
- fs.writeFileSync(`${outDir}/decoded/${item.filename}.json`,
239
- paramActualFormatting ? treeString : JSON.stringify(JSON.parse(treeString), undefined, 2));
240
- }
241
- }));
211
+ const outDir = `${saveDir}/${name}/`;
212
+ const mkdir = util.promisify(fs.mkdir);
213
+
214
+ await mkdir(`${outDir}/decoded`, { recursive: true });
215
+ await Promise.all(
216
+ fetchedData.map(async (item) => {
217
+ const buffer = await item.blob;
218
+ if (buffer === undefined) {
219
+ console.error(`ERROR: Unable to get data for blob ${item.blobId}`);
220
+ return;
221
+ }
222
+
223
+ if (!isFetchedTree(item)) {
224
+ // Just write the data as is.
225
+ fs.writeFileSync(`${outDir}/${item.filename}`, Buffer.from(buffer));
226
+
227
+ // we assume that the buffer is utf8 here, which currently is true for
228
+ // all of our snapshot blobs. It doesn't necessary be true in the future
229
+ let decoded = bufferToString(buffer, "utf8");
230
+ try {
231
+ if (!paramActualFormatting) {
232
+ decoded = JSON.stringify(JSON.parse(decoded), undefined, 2);
233
+ }
234
+ } catch (e) {}
235
+ fs.writeFileSync(`${outDir}/decoded/${item.filename}.json`, decoded);
236
+ } else {
237
+ // Write out same data for tree decoded or not, except for formatting
238
+ const treeString = bufferToString(buffer, "utf8");
239
+ fs.writeFileSync(`${outDir}/${item.filename}.json`, treeString);
240
+ fs.writeFileSync(
241
+ `${outDir}/decoded/${item.filename}.json`,
242
+ paramActualFormatting
243
+ ? treeString
244
+ : JSON.stringify(JSON.parse(treeString), undefined, 2),
245
+ );
246
+ }
247
+ }),
248
+ );
242
249
  }
243
250
 
244
251
  async function fetchBlobsFromVersion(storage: IDocumentStorageService, version: IVersion) {
245
- const tree = await reportErrors(`getSnapshotTree ${version.id}`, storage.getSnapshotTree(version));
246
- if (!tree) {
247
- return Promise.reject(new Error("Failed to load snapshot tree"));
248
- }
249
- return fetchBlobsFromSnapshotTree(storage, tree);
252
+ const tree = await reportErrors(
253
+ `getSnapshotTree ${version.id}`,
254
+ storage.getSnapshotTree(version),
255
+ );
256
+ if (!tree) {
257
+ throw new Error("Failed to load snapshot tree");
258
+ }
259
+ return fetchBlobsFromSnapshotTree(storage, tree);
250
260
  }
251
261
 
252
262
  async function reportErrors<T>(message: string, res: Promise<T>) {
253
- try {
254
- return await res;
255
- } catch (error) {
256
- console.error(`Error calling ${message}`);
257
- throw error;
258
- }
263
+ try {
264
+ return await res;
265
+ } catch (error) {
266
+ console.error(`Error calling ${message}`);
267
+ throw error;
268
+ }
259
269
  }
260
270
 
261
- export async function fluidFetchSnapshot(
262
- documentService?: IDocumentService,
263
- saveDir?: string,
264
- ) {
265
- if (!dumpSnapshotStats && !dumpSnapshotTrees && !dumpSnapshotVersions && saveDir === undefined) {
266
- return;
267
- }
268
-
269
- // --local mode - do not connect to storage.
270
- // For now, bail out early.
271
- // In future, separate download from analyzes parts and allow offline analyzes
272
- if (!documentService) {
273
- return;
274
- }
275
-
276
- console.log("\n");
277
-
278
- let storage = await documentService.connectToStorage();
279
- if (paramUnpackAggregatedBlobs) {
280
- storage = BlobAggregationStorage.wrap(storage, new TelemetryNullLogger(), false /* allowPacking */);
281
- }
282
-
283
- let version: IVersion | undefined;
284
- const versions = await reportErrors(
285
- `getVersions ${latestVersionsId}`,
286
- storage.getVersions(latestVersionsId, paramNumSnapshotVersions));
287
- if (dumpSnapshotVersions) {
288
- console.log("Snapshot versions");
289
- console.log(versions);
290
- }
291
-
292
- let blobsToDump: IFetchedData[] | undefined;
293
- if (paramSnapshotVersionIndex !== undefined) {
294
- version = versions[paramSnapshotVersionIndex];
295
- if (version === undefined) {
296
- console.log(`There are only ${versions.length} snapshots, --snapshotVersionIndex is too large`);
297
- return;
298
- }
299
- if (saveDir !== undefined) {
300
- blobsToDump = await fetchBlobsFromVersion(storage, version);
301
- const name = version.id;
302
- console.log(`Saving snapshot ${name}`);
303
- await saveSnapshot(name, blobsToDump, saveDir);
304
- }
305
- } else {
306
- version = versions[0];
307
- if (saveDir !== undefined && versions.length > 0) {
308
- console.log(" Name | Date | Size | New Size | Blobs | New Blobs");
309
- console.log("-".repeat(86));
310
-
311
- // Go in reverse order, to correctly calculate blob reuse - from oldest to newest snapshots
312
- for (let i = versions.length - 1; i >= 0; i--) {
313
- const v = versions[i];
314
- const blobs = await fetchBlobsFromVersion(storage, v);
315
- blobsToDump = blobs;
316
- const name = `${i}-${v.id}`;
317
- const res = await dumpSnapshotTree(name, blobs);
318
-
319
- let date = "";
320
- if (v.date !== undefined) {
321
- try {
322
- date = new Date(v.date).toLocaleString();
323
- } catch (e) {
324
- date = v.date.replace("T", " ");
325
- const index = date.lastIndexOf(".");
326
- if (index > 0) {
327
- date = `${date.substr(0, index)} Z`;
328
- }
329
- }
330
- }
331
- date = date.padStart(23);
332
- const size = formatNumber(res.size).padStart(10);
333
- const sizeNew = formatNumber(res.sizeNew).padStart(10);
334
- const blobCount = formatNumber(res.blobCount).padStart(6);
335
- const blobCountNew = formatNumber(res.blobCountNew).padStart(9);
336
-
337
- console.log(`${name.padEnd(15)} | ${date} | ${size} | ${sizeNew} | ${blobCount} | ${blobCountNew}`);
338
-
339
- await saveSnapshot(name, blobs, saveDir);
340
- }
341
- }
342
- }
343
-
344
- if (dumpSnapshotStats || dumpSnapshotTrees) {
345
- if (version === undefined) {
346
- console.log("No snapshot tree");
347
- } else {
348
- if (blobsToDump === undefined) {
349
- blobsToDump = await fetchBlobsFromVersion(storage, version);
350
- }
351
- console.log(`\n\nSnapshot version ${version.id}`);
352
- await dumpSnapshotTreeVerbose(version.id, blobsToDump);
353
- }
354
- }
271
+ export async function fluidFetchSnapshot(documentService?: IDocumentService, saveDir?: string) {
272
+ if (
273
+ !dumpSnapshotStats &&
274
+ !dumpSnapshotTrees &&
275
+ !dumpSnapshotVersions &&
276
+ saveDir === undefined
277
+ ) {
278
+ return;
279
+ }
280
+
281
+ // --local mode - do not connect to storage.
282
+ // For now, bail out early.
283
+ // In future, separate download from analyzes parts and allow offline analyzes
284
+ if (!documentService) {
285
+ return;
286
+ }
287
+
288
+ console.log("\n");
289
+
290
+ const storage = await documentService.connectToStorage();
291
+
292
+ let version: IVersion | undefined;
293
+ const versions = await reportErrors(
294
+ `getVersions ${latestVersionsId}`,
295
+ storage.getVersions(latestVersionsId, paramNumSnapshotVersions),
296
+ );
297
+ if (dumpSnapshotVersions) {
298
+ console.log("Snapshot versions");
299
+ console.log(versions);
300
+ }
301
+
302
+ let blobsToDump: IFetchedData[] | undefined;
303
+ if (paramSnapshotVersionIndex !== undefined) {
304
+ version = versions[paramSnapshotVersionIndex];
305
+ if (version === undefined) {
306
+ console.log(
307
+ `There are only ${versions.length} snapshots, --snapshotVersionIndex is too large`,
308
+ );
309
+ return;
310
+ }
311
+ if (saveDir !== undefined) {
312
+ blobsToDump = await fetchBlobsFromVersion(storage, version);
313
+ const name = version.id;
314
+ console.log(`Saving snapshot ${name}`);
315
+ await saveSnapshot(name, blobsToDump, saveDir);
316
+ }
317
+ } else {
318
+ version = versions[0];
319
+ if (saveDir !== undefined && versions.length > 0) {
320
+ console.log(
321
+ " Name | Date | Size | New Size | Blobs | New Blobs",
322
+ );
323
+ console.log("-".repeat(86));
324
+
325
+ // Go in reverse order, to correctly calculate blob reuse - from oldest to newest snapshots
326
+ for (let i = versions.length - 1; i >= 0; i--) {
327
+ const v = versions[i];
328
+ const blobs = await fetchBlobsFromVersion(storage, v);
329
+ blobsToDump = blobs;
330
+ const name = `${i}-${v.id}`;
331
+ const res = await dumpSnapshotTree(name, blobs);
332
+
333
+ let date = "";
334
+ if (v.date !== undefined) {
335
+ try {
336
+ date = new Date(v.date).toLocaleString();
337
+ } catch (e) {
338
+ date = v.date.replace("T", " ");
339
+ const index = date.lastIndexOf(".");
340
+ if (index > 0) {
341
+ date = `${date.substr(0, index)} Z`;
342
+ }
343
+ }
344
+ }
345
+ date = date.padStart(23);
346
+ const size = formatNumber(res.size).padStart(10);
347
+ const sizeNew = formatNumber(res.sizeNew).padStart(10);
348
+ const blobCount = formatNumber(res.blobCount).padStart(6);
349
+ const blobCountNew = formatNumber(res.blobCountNew).padStart(9);
350
+
351
+ console.log(
352
+ `${name.padEnd(
353
+ 15,
354
+ )} | ${date} | ${size} | ${sizeNew} | ${blobCount} | ${blobCountNew}`,
355
+ );
356
+
357
+ await saveSnapshot(name, blobs, saveDir);
358
+ }
359
+ }
360
+ }
361
+
362
+ if (dumpSnapshotStats || dumpSnapshotTrees) {
363
+ if (version === undefined) {
364
+ console.log("No snapshot tree");
365
+ } else {
366
+ if (blobsToDump === undefined) {
367
+ blobsToDump = await fetchBlobsFromVersion(storage, version);
368
+ }
369
+ console.log(`\n\nSnapshot version ${version.id}`);
370
+ await dumpSnapshotTreeVerbose(version.id, blobsToDump);
371
+ }
372
+ }
355
373
  }