querysub 0.374.0 → 0.375.0

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 (33) hide show
  1. package/package.json +2 -4
  2. package/src/deployManager/components/MachineDetailPage.tsx +2 -5
  3. package/src/deployManager/components/ServiceDetailPage.tsx +2 -5
  4. package/src/deployManager/machineApplyMainCode.ts +7 -0
  5. package/src/diagnostics/NodeViewer.tsx +4 -5
  6. package/src/diagnostics/logs/IndexedLogs/BufferIndexHelpers.ts +1 -1
  7. package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +7 -7
  8. package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +221 -220
  9. package/src/diagnostics/logs/IndexedLogs/LogViewerParams.ts +21 -0
  10. package/src/diagnostics/logs/IndexedLogs/bufferMatcher.ts +3 -3
  11. package/src/diagnostics/logs/diskLogger.ts +0 -38
  12. package/src/diagnostics/logs/errorNotifications2/errorNotifications2.ts +3 -0
  13. package/src/diagnostics/logs/injectFileLocationToConsole.ts +3 -0
  14. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +32 -22
  15. package/src/diagnostics/managementPages.tsx +0 -18
  16. package/test.ts +0 -5
  17. package/bin/error-email.js +0 -8
  18. package/bin/error-im.js +0 -8
  19. package/src/diagnostics/logs/FastArchiveAppendable.ts +0 -843
  20. package/src/diagnostics/logs/FastArchiveController.ts +0 -573
  21. package/src/diagnostics/logs/FastArchiveViewer.tsx +0 -1090
  22. package/src/diagnostics/logs/LogViewer2.tsx +0 -552
  23. package/src/diagnostics/logs/errorNotifications/ErrorDigestPage.tsx +0 -409
  24. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +0 -756
  25. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +0 -280
  26. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +0 -254
  27. package/src/diagnostics/logs/errorNotifications/errorDigestEmail.tsx +0 -233
  28. package/src/diagnostics/logs/errorNotifications/errorDigestEntry.tsx +0 -14
  29. package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +0 -292
  30. package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +0 -209
  31. package/src/diagnostics/logs/importLogsEntry.ts +0 -38
  32. package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePages.tsx +0 -150
  33. package/src/diagnostics/logs/logViewerExtractField.ts +0 -36
@@ -1,573 +0,0 @@
1
- module.hotreload = true;
2
- module.noserverhotreload = false;
3
- import { measureBlock, measureFnc, measureWrap } from "socket-function/src/profiling/measure";
4
- import { getMachineId, getOwnMachineId } from "../../-a-auth/certs";
5
- import { isDefined, parseFileNameKVP, parsePath, partialCopyObject, streamToIteratable, sum, toFileNameKVP } from "../../misc";
6
- import { registerShutdownHandler } from "../periodic";
7
- import { batchFunction, delay, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
8
- import { PromiseObj, isNode, keyByArray, nextId, sort, timeInDay, timeInHour, timeInMinute } from "socket-function/src/misc";
9
- import os from "os";
10
- import { getOwnThreadId } from "../../-f-node-discovery/NodeDiscovery";
11
- import fs from "fs";
12
- import { MaybePromise, canHaveChildren } from "socket-function/src/types";
13
- import { formatNumber, formatTime } from "socket-function/src/formatting/format";
14
- import { cache, lazy } from "socket-function/src/caching";
15
- import { getArchives, nestArchives } from "../../-a-archives/archives";
16
- import { Zip } from "../../zip";
17
- import { SocketFunction } from "socket-function/SocketFunction";
18
- import { assertIsManagementUser } from "../managementPages";
19
- import { getControllerNodeIdList } from "../../-g-core-values/NodeCapabilities";
20
- import { errorToUndefined, ignoreErrors, timeoutToUndefinedSilent } from "../../errors";
21
- import { getCallObj } from "socket-function/src/nodeProxy";
22
- import { getSyncedController } from "../../library-components/SyncedController";
23
- import { getBrowserUrlNode, getOwnNodeId } from "../../-f-node-discovery/NodeDiscovery";
24
- import { secureRandom } from "../../misc/random";
25
- import { getPathIndex, getPathStr2 } from "../../path";
26
- import { onNextPaint } from "../../functional/onNextPaint";
27
- import { getArchivesBackblazePrivateImmutable, getArchivesBackblazePublicImmutable } from "../../-a-archives/archivesBackBlaze";
28
- import { httpsRequest } from "socket-function/src/https";
29
- import { getDomain, isPublic } from "../../config";
30
- import { getIPDomain } from "../../-e-certs/EdgeCertController";
31
- import { getArchivesPrivateFileSystem } from "../../-a-archives/archivesPrivateFileSystem";
32
- import { createArchivesLimitedCache } from "../../-a-archives/archivesLimitedCache";
33
- import { sha256 } from "js-sha256";
34
- import { assertIsNetworkTrusted } from "../../-d-trust/NetworkTrust2";
35
- import { blue, magenta } from "socket-function/src/formatting/logColors";
36
- import { FastArchiveAppendable, getFileTimeStamp } from "./FastArchiveAppendable";
37
- import { IdentityController_getMachineId, IdentityController_getReconnectNodeId } from "../../-c-identity/IdentityController";
38
- import { fsExistsAsync } from "../../fs";
39
- import { Querysub } from "../../4-querysub/QuerysubController";
40
-
41
- export type FileMetadata = {
42
- nodeId?: string;
43
- machineId?: string;
44
- path: string;
45
- url: string;
46
- size: number;
47
- startTime: number;
48
- endTime: number;
49
- };
50
-
51
- type InternalFileMetadata = {
52
- path: string;
53
- size: number;
54
- };
55
-
56
- type SynchronizeInfo = {
57
- id: string;
58
- nodeId: string;
59
- config: {
60
- range: {
61
- startTime: number;
62
- endTime: number;
63
- };
64
- rootPath: string;
65
- };
66
- createTime: number;
67
- };
68
-
69
- // Excluding authorize tokens, then hashed
70
- export function getFileMetadataHash(file: FileMetadata): string {
71
- // NOTE: This size will be at least equal or older to the size when we read it. And so for the file to have new data, it means the size will have to be larger. And because this size is always equal older to when we read it and never newer, it means it won't include the newer writes. And so even for local files which are constantly changing, this will properly invalidate when we write to them again.
72
- return sha256(JSON.stringify({ path: file.path, size: file.size, machineId: file.machineId }));
73
- }
74
-
75
- export class FastArchiveAppendableControllerBase {
76
- private static activeSynchronizes = new Map<string, SynchronizeInfo>();
77
- public static progressCallbacks = new Map<string, (progress: { section: string; value: number; max: number; }) => void>();
78
-
79
- /** Get all pending local files for this rootPath that haven't been uploaded to Backblaze yet */
80
- public async getPendingFiles(rootPath: string, timeRange: { startTime: number; endTime: number; }): Promise<InternalFileMetadata[]> {
81
-
82
- let rootCacheFolder = new FastArchiveAppendable(rootPath).baseGetLocalPathRoot();
83
- if (!await fsExistsAsync(rootCacheFolder)) return [];
84
-
85
- //console.log(`Searching for pending files in ${rootCacheFolder} for time range ${new Date(timeRange.startTime).toISOString()} to ${new Date(timeRange.endTime).toISOString()}`);
86
-
87
- let result: InternalFileMetadata[] = [];
88
-
89
- try {
90
- let allFolders = await fs.promises.readdir(rootCacheFolder);
91
- for (let threadId of allFolders) {
92
- let threadDir = rootCacheFolder + threadId + "/";
93
- let files = await fs.promises.readdir(threadDir);
94
-
95
- for (let file of files) {
96
- if (file === "heartbeat") continue;
97
-
98
- let fileTimestamp = getFileTimeStamp(file);
99
- //console.log(`Found ${new Date(fileTimestamp).toISOString()} in ${threadDir + file}`);
100
- if (fileTimestamp.endTime < timeRange.startTime || fileTimestamp.startTime > timeRange.endTime) {
101
- continue;
102
- }
103
-
104
- let fullPath = threadDir + file;
105
- let stat = await fs.promises.stat(fullPath);
106
-
107
- result.push({
108
- path: fullPath,
109
- size: stat.size,
110
- });
111
- }
112
- }
113
- } catch (e: any) {
114
- // If directory doesn't exist, return empty array
115
- if (e.code === "ENOENT") {
116
- return [];
117
- }
118
- throw e;
119
- }
120
-
121
- return result;
122
- }
123
-
124
- /** Download a local file from another server (coordinator forwards request).
125
- * NOTE: This doesn't support streaming, which isn't great. However, eventually most logs will make their way to backblaze. So not supporting streaming here doesn't mean we don't support streaming in general. We also compress the file. So for the compressed file to be so big that you can't send it all at once would mean your uncompressed data would just be ridiculously large.
126
- */
127
- public async downloadLocalFile(syncId: string, targetNodeId: string, path: string, fromLocalArchives?: boolean): Promise<Buffer> {
128
- const caller = SocketFunction.getCaller();
129
- let syncInfo = FastArchiveAppendableControllerBase.activeSynchronizes.get(syncId);
130
- if (!syncInfo) {
131
- throw new Error(`Invalid sync ID: ${syncId}`);
132
- }
133
-
134
- if (fromLocalArchives) {
135
- let archives = new FastArchiveAppendable(syncInfo.config.rootPath).getArchives(false);
136
- let file = await archives.get(path);
137
- if (!file) {
138
- throw new Error(`File not found in local archives: ${path}`);
139
- }
140
- return file;
141
- }
142
- try {
143
- // Forward the request to the actual server that has the file
144
- let targetController = FastArchiveAppendableController.nodes[targetNodeId];
145
- return await targetController.downloadLocalFileInternal(path);
146
- } catch {
147
- let machineId = getMachineId(targetNodeId);
148
- let nodeIds = await getControllerNodeIdList(FastArchiveAppendableController);
149
- let byMachineId = keyByArray(nodeIds, x => getMachineId(x.nodeId));
150
- let nodeIdsOfMachine = byMachineId.get(machineId) || [];
151
-
152
- let firstAliveNode = new PromiseObj<string>();
153
- let allFinished = Promise.all(nodeIdsOfMachine.map(async ({ nodeId, entryPoint }) => {
154
- if (await timeoutToUndefinedSilent(5000, FastArchiveAppendableController.nodes[nodeId].isNodeAlive(nodeId))) {
155
- firstAliveNode.resolve(nodeId);
156
- }
157
- }));
158
- let aliveNodeId = await Promise.race([firstAliveNode.promise, allFinished]);
159
- if (Array.isArray(aliveNodeId)) {
160
- throw new Error(`No alive node found for machine ${machineId}`);
161
- }
162
- console.log(magenta(`Remapped request for ${targetNodeId} to ${aliveNodeId} for machine ${machineId}`));
163
- let targetController = FastArchiveAppendableController.nodes[aliveNodeId];
164
- return await targetController.downloadLocalFileInternal(path);
165
- }
166
- }
167
-
168
- /** Internal method to download local file (called by coordinator) */
169
- public async downloadLocalFileInternal(path: string): Promise<Buffer> {
170
- return await Zip.gzip(await fs.promises.readFile(path));
171
- }
172
- public async isNodeAlive(nodeId: string): Promise<boolean> {
173
- return true;
174
- }
175
-
176
- /** Create a new sync session and return the sync ID */
177
- public async createSyncSession(): Promise<string> {
178
- let caller = SocketFunction.getCaller();
179
- let syncId = nextId();
180
-
181
- let syncInfo: SynchronizeInfo = {
182
- id: syncId,
183
- nodeId: caller.nodeId,
184
- config: {
185
- range: { startTime: 0, endTime: 0 }, // Will be set by startSynchronize
186
- rootPath: "",
187
- },
188
- createTime: Date.now(),
189
- };
190
-
191
- FastArchiveAppendableControllerBase.activeSynchronizes.set(syncId, syncInfo);
192
-
193
- // Clean up on disconnect
194
- SocketFunction.onNextDisconnect(caller.nodeId, () => {
195
- FastArchiveAppendableControllerBase.activeSynchronizes.delete(syncId);
196
- });
197
-
198
- return syncId;
199
- }
200
-
201
- public async startSynchronize(config: {
202
- syncId: string;
203
- range: {
204
- startTime: number;
205
- endTime: number;
206
- };
207
- rootPath: string;
208
- noLocalFiles?: boolean;
209
- forceGetPublic?: boolean;
210
- }): Promise<{
211
- files: FileMetadata[];
212
- }> {
213
- let caller = SocketFunction.getCaller();
214
- // If the user uses a bad syncId and it gets leak, screw it.
215
- let syncId = config.syncId;
216
-
217
- let syncInfo: SynchronizeInfo = {
218
- id: syncId,
219
- nodeId: caller.nodeId,
220
- config: {
221
- range: config.range,
222
- rootPath: config.rootPath,
223
- },
224
- createTime: Date.now(),
225
- };
226
-
227
- FastArchiveAppendableControllerBase.activeSynchronizes.set(syncId, syncInfo);
228
-
229
- // Clean up on disconnect
230
- SocketFunction.onNextDisconnect(caller.nodeId, () => {
231
- FastArchiveAppendableControllerBase.activeSynchronizes.delete(syncId);
232
- });
233
-
234
- return await this.startSynchronizeBase({
235
- syncId,
236
- range: config.range,
237
- rootPath: config.rootPath,
238
- noLocalFiles: config.noLocalFiles,
239
- forceGetPublic: config.forceGetPublic,
240
- });
241
- }
242
-
243
- public async startSynchronizeInternal(config: {
244
- range: {
245
- startTime: number;
246
- endTime: number;
247
- };
248
- rootPath: string;
249
- noLocalFiles?: boolean;
250
- forceGetPublic?: boolean;
251
- }): Promise<{
252
- files: FileMetadata[];
253
- }> {
254
- let syncId = "no-progress" + nextId();
255
- FastArchiveAppendableControllerBase.activeSynchronizes.set(syncId, {
256
- id: syncId,
257
- nodeId: getOwnNodeId(),
258
- config: {
259
- range: config.range,
260
- rootPath: config.rootPath,
261
- },
262
- createTime: Date.now(),
263
- });
264
- return await this.startSynchronizeBase({
265
- syncId,
266
- range: config.range,
267
- rootPath: config.rootPath,
268
- noLocalFiles: config.noLocalFiles,
269
- forceGetPublic: config.forceGetPublic,
270
- });
271
- }
272
-
273
- private async startSynchronizeBase(config: {
274
- syncId?: string;
275
- range: {
276
- startTime: number;
277
- endTime: number;
278
- };
279
- rootPath: string;
280
- noLocalFiles?: boolean;
281
- forceGetPublic?: boolean;
282
- }): Promise<{
283
- files: FileMetadata[];
284
- }> {
285
- if (!SocketFunction.mountedNodeId) {
286
- throw new Error(`Cannot use FastArchiveAppendableController before SocketFunction is mounted`);
287
- }
288
- let syncId = config.syncId ?? "";
289
-
290
- let isPublicValue = isPublic() || config.forceGetPublic;
291
-
292
- const getLocalFileMetadata = async (config: {
293
- nodeId: string;
294
- path: string;
295
- size: number;
296
- fromLocalArchives?: boolean;
297
- }): Promise<FileMetadata> => {
298
- let { nodeId, path, size, fromLocalArchives } = config;
299
- // Create download URL that points to the coordinator (this server)
300
- // The coordinator will forward the request to the actual file server
301
- let coordinatorNodeId = getOwnNodeId();
302
- let coordinatorController = FastArchiveAppendableController.nodes[coordinatorNodeId];
303
- let downloadCall = coordinatorController.downloadLocalFile[getCallObj](
304
- syncId,
305
- nodeId, // Target server that has the file
306
- path,
307
- fromLocalArchives,
308
- );
309
- let url = SocketFunction.getHTTPCallLink(downloadCall);
310
- // Have to use the IP domain, as it's externally available. That, plus the port, should uniquely identify us.
311
- let ipDomain = await getIPDomain();
312
- let urlObj = new URL(url);
313
- urlObj.hostname = ipDomain;
314
- url = urlObj.toString();
315
-
316
- let timeStamp = getFileTimeStamp(path);
317
- let startTime = timeStamp.startTime;
318
- let endTime = timeStamp.endTime;
319
-
320
- return {
321
- nodeId: nodeId,
322
- machineId: getMachineId(nodeId),
323
- path: path,
324
- url: url,
325
- size: size,
326
- startTime: startTime,
327
- endTime: endTime,
328
- };
329
- };
330
-
331
- // Define inline functions for parallel execution
332
- const searchBackblazeFiles = async (): Promise<FileMetadata[]> => {
333
- let archives = new FastArchiveAppendable(config.rootPath).getArchives(config.forceGetPublic ?? false);
334
- let backblazeFiles: FileMetadata[] = [];
335
- if (!archives.getDownloadAuthorization && isPublicValue) {
336
- throw new Error(`archives.getDownloadAuthorization is `);
337
- }
338
-
339
- if (!archives.getURL && isPublicValue) throw new Error(`archives.getURL is missing?`);
340
- let authorization = await archives.getDownloadAuthorization?.({
341
- validDurationInSeconds: timeInDay * 6 / 1000,
342
- });
343
- let authToken = authorization?.authorizationToken;
344
-
345
- const folderProgress = this.updateProgress(syncId, "Backblaze folder search", 0);
346
- let folderMax = 0;
347
- let folderValue = 0;
348
-
349
- async function searchBackblazeFilesRecursive(prefix: string, level: "year" | "month" | "day" | "hour") {
350
- folderMax++;
351
- let folders = await archives.find(prefix, { shallow: true, type: "folders" });
352
- folderValue++;
353
- folderProgress(folderValue, folderMax);
354
- // It's actually really annoying because the time ranges aren't the same amount of time. It's probably fine though. This code only needs to exist in one place.
355
-
356
- await Promise.all(folders.map(async (folder) => {
357
- let folderName = folder.split("/").pop()!;
358
- let folderNum = parseInt(folderName, 10);
359
-
360
- if (level === "year") {
361
- let yearStart = Date.UTC(folderNum, 0, 1);
362
- let yearEnd = Date.UTC(folderNum + 1, 0, 1);
363
-
364
- if (yearEnd > config.range.startTime && yearStart <= config.range.endTime) {
365
- await searchBackblazeFilesRecursive(folder + "/", "month");
366
- }
367
- } else if (level === "month") {
368
- // folderName is 1-based month (01, 02, etc)
369
- let year = parseInt(folder.split("/")[0], 10);
370
- let monthStart = Date.UTC(year, folderNum - 1, 1);
371
- let monthEnd = Date.UTC(year, folderNum, 1);
372
-
373
- if (monthEnd > config.range.startTime && monthStart <= config.range.endTime) {
374
- await searchBackblazeFilesRecursive(folder + "/", "day");
375
- }
376
- } else if (level === "day") {
377
- let pathParts = folder.split("/");
378
- let year = parseInt(pathParts[0], 10);
379
- let month = parseInt(pathParts[1], 10) - 1; // Date constructor expects 0-based month
380
- let dayStart = Date.UTC(year, month, folderNum);
381
- let dayEnd = Date.UTC(year, month, folderNum + 1);
382
-
383
- if (dayEnd > config.range.startTime && dayStart <= config.range.endTime) {
384
- await searchBackblazeFilesRecursive(folder + "/", "hour");
385
- }
386
- } else if (level === "hour") {
387
- let pathParts = folder.split("/");
388
- let year = parseInt(pathParts[0], 10);
389
- let month = parseInt(pathParts[1], 10) - 1;
390
- let day = parseInt(pathParts[2], 10);
391
- let hourStart = Date.UTC(year, month, day, folderNum);
392
- let hourEnd = Date.UTC(year, month, day, folderNum + 1);
393
-
394
- let inRange = hourEnd > config.range.startTime && hourStart <= config.range.endTime;
395
- if (!inRange) return;
396
- // This hour folder is in range, get all files from it
397
- let filePaths = await archives.findInfo(folder + "/", { shallow: true, type: "files" });
398
- for (let info of filePaths) {
399
- if (!info.path.endsWith(".log")) continue;
400
- if (archives.getURL && authToken !== undefined) {
401
- let url = await archives.getURL(info.path);
402
- let urlObj = new URL(url);
403
- // IMPORTANT! This is CASE SENSITIVE! Ugh...
404
- urlObj.searchParams.set("Authorization", authToken);
405
- url = urlObj.toString();
406
-
407
- backblazeFiles.push({
408
- path: info.path,
409
- url: url,
410
- size: info.size,
411
- startTime: hourStart,
412
- endTime: hourEnd,
413
- });
414
- } else {
415
- backblazeFiles.push(await getLocalFileMetadata({
416
- nodeId: getOwnNodeId(),
417
- path: info.path,
418
- size: info.size,
419
- fromLocalArchives: true,
420
- }));
421
- }
422
- }
423
- }
424
- }));
425
- }
426
-
427
- await searchBackblazeFilesRecursive("", "year");
428
-
429
- return backblazeFiles;
430
- };
431
-
432
- // NOTE: Disk files are on our machine, and on every other server (as we want to search live logs as well).
433
- const getDiskFiles = async (): Promise<FileMetadata[]> => {
434
- const getControllerProgress = this.updateProgress(syncId, "Discovering remote machines", 0);
435
-
436
- let localFiles: FileMetadata[] = [];
437
-
438
- let nodeIds = await getControllerNodeIdList(FastArchiveAppendableController);
439
- // This filtering is somewhat of a hack, as machines can have mixed public status, and change it easliy. However in practice... they don't... We COULD have two sets of pending logs, for public and non-public (like we have backblaze and local archives for non-pending logs). But... at the moment... I don't think it's needed.
440
- let ownMachineId = getOwnMachineId();
441
- if (!isPublicValue) {
442
- nodeIds = nodeIds.filter(x => getMachineId(x.nodeId) === ownMachineId);
443
- } else if (isPublicValue && !isPublic()) {
444
- nodeIds = nodeIds.filter(x => getMachineId(x.nodeId) !== ownMachineId);
445
- }
446
- let byMachineId = keyByArray(nodeIds, x => getMachineId(x.nodeId));
447
- getControllerProgress(byMachineId.size, byMachineId.size);
448
-
449
- let remoteProgress = this.updateProgress(syncId, "Discovering remote files", byMachineId.size);
450
- let remoteValue = 0;
451
-
452
-
453
- await Promise.all(Array.from(byMachineId).map(async ([machineId, nodeObjs]) => {
454
- let firstAliveNode = new PromiseObj<string>();
455
- let allFinished = Promise.all(nodeObjs.map(async ({ nodeId, entryPoint }) => {
456
- if (await timeoutToUndefinedSilent(5000, FastArchiveAppendableController.nodes[nodeId].isNodeAlive(nodeId))) {
457
- firstAliveNode.resolve(nodeId);
458
- }
459
- }));
460
- let aliveNodeId = await Promise.race([firstAliveNode.promise, allFinished]);
461
- if (Array.isArray(aliveNodeId)) {
462
- console.log(blue(`No alive nodes found for machine ${machineId}`), nodeObjs);
463
- remoteValue++;
464
- remoteProgress(remoteValue);
465
- return;
466
- }
467
-
468
- let controller = FastArchiveAppendableController.nodes[aliveNodeId];
469
-
470
- let pendingFiles = await errorToUndefined(
471
- controller.getPendingFiles(config.rootPath, config.range)
472
- );
473
- console.log(blue(`Found ${pendingFiles?.length} pending files on node ${aliveNodeId}`));
474
-
475
- remoteValue++;
476
- remoteProgress(remoteValue);
477
-
478
- if (!pendingFiles) return;
479
- for (let file of pendingFiles) {
480
- localFiles.push(await getLocalFileMetadata({ nodeId: aliveNodeId, path: file.path, size: file.size }));
481
- }
482
- }));
483
-
484
- return localFiles;
485
- };
486
- // Execute both operations in parallel
487
- let filePromises: Promise<FileMetadata[]>[] = [];
488
- filePromises.push(searchBackblazeFiles());
489
- if (!config.noLocalFiles) filePromises.push(getDiskFiles());
490
-
491
- let allFilesList = await Promise.all(filePromises);
492
- let allFiles = allFilesList.flat();
493
- // Newest first, so recent errors are found quickly
494
- sort(allFiles, x => -x.startTime);
495
-
496
- return {
497
- files: allFiles,
498
- };
499
- }
500
-
501
- /** Update progress for a synchronization session - returns a function that takes only the progress value */
502
- public updateProgress(syncId: string, section: string, max: number) {
503
- let cancelled: Error | undefined;
504
- let baseBatch = batchFunction({ delay: 150, },
505
- async (config: { value: number, overrideMax?: number }[]) => {
506
- if (cancelled) return;
507
- let syncInfo = FastArchiveAppendableControllerBase.activeSynchronizes.get(syncId);
508
- if (!syncInfo) {
509
- cancelled = new Error(`Cancelled (sync for id ${syncId} is missing)`);
510
- return;
511
- }
512
-
513
- let value = config.at(-1)!.value;
514
- let usedMax = config.map(c => c.overrideMax).filter(isDefined).at(-1) ?? max;
515
- value = Math.min(value, usedMax);
516
-
517
- try {
518
- let result = await FastArchiveAppendableController.nodes[syncInfo.nodeId]
519
- .onSynchronizeProgress(syncId, { section, value, max: usedMax });
520
- if (result === "stop") {
521
- cancelled = new Error(`Sync for id ${syncId} was stopped by the client`);
522
- return;
523
- }
524
- } catch (e: any) {
525
- cancelled = e;
526
- }
527
- }
528
- );
529
- let onProgress = (value: number, overrideMax?: number) => {
530
- if (!syncId) return;
531
- if (syncId.startsWith("no-progress")) return;
532
- if (cancelled) throw cancelled;
533
- void baseBatch({ value, overrideMax });
534
- };
535
- onProgress(0);
536
- return onProgress;
537
- }
538
-
539
- /** Progress callback - called by server on client */
540
- public async onSynchronizeProgress(syncId: string, progress: {
541
- section: string;
542
- value: number;
543
- max: number;
544
- }): Promise<"stop" | undefined> {
545
- const callback = FastArchiveAppendableControllerBase.progressCallbacks.get(syncId);
546
- if (!callback) {
547
- return "stop";
548
- }
549
- callback(progress);
550
- }
551
- }
552
-
553
- export const FastArchiveAppendableController = SocketFunction.register(
554
- "FastArchiveAppendableController-b8c9e4d5-1f2a-4b6c-8d7e-9f0a1b2c3d4e",
555
- new FastArchiveAppendableControllerBase(),
556
- () => ({
557
- getPendingFiles: { hooks: [assertIsManagementUser] },
558
- // Secured via syncId (can't use assertIsManagementUser, because it is an HTTP call, so there is no negotiation step)
559
- downloadLocalFile: {},
560
- downloadLocalFileInternal: { hooks: [assertIsManagementUser] },
561
- startSynchronize: { hooks: [assertIsManagementUser] },
562
- updateProgress: { hooks: [assertIsManagementUser] },
563
- onSynchronizeProgress: { hooks: [assertIsManagementUser] },
564
- isNodeAlive: { hooks: [assertIsNetworkTrusted] },
565
- createSyncSession: { hooks: [assertIsManagementUser] },
566
- }),
567
- () => ({
568
-
569
- }),
570
- );
571
-
572
- export const fastArchiveAppendableController = getSyncedController(FastArchiveAppendableController);
573
-