querysub 0.2.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 (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1,418 @@
1
+ import { isNode } from "socket-function/src/misc";
2
+ import { getSubFolder } from "../fs";
3
+ import fs from "fs";
4
+ import { blue, red, yellow } from "socket-function/src/formatting/logColors";
5
+ import { measureFnc } from "socket-function/src/profiling/measure";
6
+ import { delay } from "socket-function/src/batching";
7
+ import debugbreak from "debugbreak";
8
+ import { Archives, getArchives } from "./archives";
9
+ import { cache, lazy } from "socket-function/src/caching";
10
+ import { isDefined } from "../misc";
11
+
12
+ export let storageDisabled = false;
13
+ /** Useful for tests */
14
+ export function disableStorage() {
15
+ storageDisabled = true;
16
+ }
17
+
18
+ let maxFileNameLength = 260;
19
+ let maxFilePartLength = 255;
20
+
21
+ class ArchivesDisk {
22
+ public LAG = 0;
23
+ // Must end with "/"
24
+ public LOCAL_ARCHIVE_FOLDER = "";
25
+
26
+ private logging = false;
27
+ public enableLogging() {
28
+ this.logging = true;
29
+ }
30
+ private log(text: string) {
31
+ if (!this.logging) return;
32
+ console.log(text);
33
+ }
34
+
35
+ public getDebugName() {
36
+ return "disk/" + this.LOCAL_ARCHIVE_FOLDER;
37
+ }
38
+
39
+ private initFinished = false;
40
+ // TODO: Now that init isn't async, call it directly in validate, instead of the callers to validate
41
+ private init = lazy(() => {
42
+ if (isNode() && process.platform === "win32") {
43
+ try {
44
+ let longPart = "LONG_FILE_PATH_TEST_" + Array(210 - "LONG_FILE_PATH_TEST_".length).fill("_").join("");
45
+ let testPath = this.LOCAL_ARCHIVE_FOLDER + longPart + "/" + longPart + "/" + longPart;
46
+ fs.mkdirSync(testPath, { recursive: true });
47
+ maxFileNameLength = 600;
48
+ } catch (e) {
49
+ console.log(e);
50
+ console.error(red(`Failed to set long file name. Google "windows enable long paths", and enable long paths to allow long file names.`));
51
+ // TODO: We don't NEED to exit, but for now we will, because we can easily
52
+ // setup our machine properly.
53
+ console.log(`Exitting now`);
54
+ process.exit();
55
+ }
56
+ } else {
57
+ // 4096 for linux, but... 600 handles a lot more cases (backblaze, etc)
58
+ maxFileNameLength = 600;
59
+ }
60
+ this.initFinished = true;
61
+ });
62
+
63
+ private async ensureDirsExist(path: string) {
64
+ let fileNameParts = path.split("/");
65
+ // Don't create the drive (and also only add up to the last path, via slicing (0, i)
66
+ for (let i = 1; i < fileNameParts.length; i++) {
67
+ let dir = this.LOCAL_ARCHIVE_FOLDER + fileNameParts.slice(0, i).join("/");
68
+ if (!fs.existsSync(dir)) {
69
+ fs.mkdirSync(dir);
70
+ }
71
+ }
72
+ }
73
+
74
+ @measureFnc
75
+ public async set(fileName: string, data: Buffer): Promise<void> {
76
+ await this.init();
77
+
78
+ this.log(blue(`Setting file ${fileName} = ${data.length} bytes`));
79
+ if (storageDisabled) return;
80
+ fileName = escapeFileName(fileName);
81
+ await this.simulateLag();
82
+
83
+ await this.ensureDirsExist(fileName);
84
+
85
+ await fs.promises.writeFile(this.LOCAL_ARCHIVE_FOLDER + fileName, data);
86
+ }
87
+ @measureFnc
88
+ public async del(fileName: string): Promise<void> {
89
+ await this.init();
90
+ this.log(blue(`Deleting file ${fileName}`));
91
+ if (storageDisabled) return;
92
+ fileName = escapeFileName(fileName);
93
+ await this.simulateLag();
94
+ if (!fs.existsSync(this.LOCAL_ARCHIVE_FOLDER + fileName)) return;
95
+ try {
96
+ await fs.promises.unlink(this.LOCAL_ARCHIVE_FOLDER + fileName);
97
+ } catch { }
98
+ let dir = fileName.replaceAll("\\", "/").split("/").slice(0, -1).join("/");
99
+ await this.gcDir(dir);
100
+ }
101
+
102
+ public async setLargeFile(config: { path: string; getNextData(): Promise<Buffer | undefined>; }): Promise<void> {
103
+ await this.init();
104
+ let { path } = config;
105
+ path = escapeFileName(path);
106
+ await this.ensureDirsExist(path);
107
+ let handle: fs.promises.FileHandle | undefined;
108
+ try {
109
+ handle = await fs.promises.open(this.LOCAL_ARCHIVE_FOLDER + path, "w");
110
+ let pos = 0;
111
+ while (true) {
112
+ let data = await config.getNextData();
113
+ if (!data?.length) break;
114
+ await handle.write(data, 0, data.length, pos);
115
+ pos += data.length;
116
+ }
117
+ } finally {
118
+ if (handle) {
119
+ try {
120
+ await handle.close();
121
+ } catch { }
122
+ }
123
+ }
124
+ }
125
+
126
+ @measureFnc
127
+ public async gcDir(dir: string) {
128
+ await this.init();
129
+ if (!dir.startsWith(this.LOCAL_ARCHIVE_FOLDER)) {
130
+ dir = this.LOCAL_ARCHIVE_FOLDER + dir;
131
+ }
132
+ if (!dir.endsWith("/")) dir += "/";
133
+ let files: string[] = [];
134
+ try {
135
+ files = await fs.promises.readdir(dir);
136
+ } catch { }
137
+ for (let file of files) {
138
+ let isDir = false;
139
+ let path = dir + file + "/";
140
+ try {
141
+ let stat = await fs.promises.stat(path);
142
+ if (stat.isDirectory()) {
143
+ isDir = true;
144
+ }
145
+ } catch { }
146
+ if (isDir) {
147
+ await this.gcDir(path);
148
+ }
149
+ }
150
+ try {
151
+ files = await fs.promises.readdir(dir);
152
+ } catch { }
153
+ if (files.length === 0) {
154
+ try {
155
+ await fs.promises.rmdir(dir);
156
+ } catch { }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * NOTE: Generally we load data into memory and keep it there, so a streaming endpoint (instead of a buffer),
162
+ * wouldn't be that useful (and would be a lot slower, as streaming data is a lot slower than reading
163
+ * it all at once).
164
+ * @fsErrorRetryCount Retries count for file system error (not user errors). Ex, too many open files, etc.
165
+ */
166
+ @measureFnc
167
+ public async get(fileNameInput: string, config?: { range?: { start: number; end: number; }; retryCount?: number }): Promise<Buffer | undefined> {
168
+ await this.init();
169
+ this.log(blue(`Start read file ${fileNameInput}`));
170
+ if (storageDisabled) return undefined;
171
+ let fileName = escapeFileName(fileNameInput);
172
+ await this.simulateLag();
173
+ let handle: fs.promises.FileHandle | undefined;
174
+ let retryCount = config?.retryCount ?? 3;
175
+ config = config ?? {};
176
+ try {
177
+ handle = await fs.promises.open(this.LOCAL_ARCHIVE_FOLDER + fileName, "r");
178
+ let stats = await handle.stat();
179
+ let start = config.range?.start ?? 0;
180
+ let end = config.range?.end ?? stats.size;
181
+ let buffer = Buffer.alloc(end - start);
182
+ let read = await handle.read(buffer, 0, end - start, start);
183
+
184
+ let stats2 = await handle.stat();
185
+ if (stats.mtimeMs !== stats2.mtimeMs) {
186
+ if (retryCount > 0) {
187
+ // Wait for the file to stop changing
188
+ await delay(10);
189
+ return this.get(fileNameInput, { ...config, retryCount: retryCount - 1 });
190
+ }
191
+ throw new Error(`File modified changed while reading file ${fileName}`);
192
+ }
193
+
194
+ return buffer.slice(0, read.bytesRead);
195
+ } catch (e: any) {
196
+ if (e.code === "EMFILE") {
197
+ if (retryCount > 0) {
198
+ // Wait for some files to close.
199
+ await delay(10);
200
+ return this.get(fileNameInput, { ...config, retryCount: retryCount - 1 });
201
+ } else {
202
+ console.log(`Fixable error reading file, but we hit the retry limit. Returning undefined, even though the file likely exists. ${fileName}`, e);
203
+ }
204
+ }
205
+ return undefined;
206
+ } finally {
207
+ if (handle) {
208
+ try {
209
+ await handle.close();
210
+ } catch { }
211
+ }
212
+ }
213
+ }
214
+
215
+ @measureFnc
216
+ public async find(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<string[]> {
217
+ await this.init();
218
+ prefix = escapeFileName(prefix);
219
+ this.log(blue(`findFileNames ${prefix}`));
220
+ if (storageDisabled) return [];
221
+ await this.simulateLag();
222
+ let fileNames: string[] = [];
223
+ let folderNames: string[] = [];
224
+ async function readDir(dir: string) {
225
+ if (!fs.existsSync(dir)) {
226
+ return;
227
+ }
228
+ try {
229
+ let curFileNames = await fs.promises.readdir(dir, {
230
+ withFileTypes: true
231
+ });
232
+ await Promise.all(curFileNames.map(async fileObj => {
233
+ let fileName = fileObj.name;
234
+ // try/catch so nested directory errors don't break everything
235
+ // (I guess if the root directory is deleted we will still through though...)
236
+ try {
237
+ if (fileObj.isDirectory()) {
238
+ folderNames.push(dir + fileName);
239
+ await readDir(dir + fileName + "/");
240
+ } else {
241
+ fileNames.push(dir + fileName);
242
+ }
243
+ } catch { }
244
+ }));
245
+ } catch { }
246
+ }
247
+
248
+ let rootDir = "";
249
+ let pathParts = prefix.split("/");
250
+ if (pathParts.length > 1) {
251
+ rootDir = pathParts.slice(0, -1).join("/") + "/";
252
+ }
253
+ await readDir(this.LOCAL_ARCHIVE_FOLDER + rootDir);
254
+
255
+ let results = config?.type === "folders" ? folderNames : fileNames;
256
+
257
+ results = results.map(name => name.slice(this.LOCAL_ARCHIVE_FOLDER.length));
258
+ results = results.filter(name => name.startsWith(prefix));
259
+
260
+ if (config?.shallow) {
261
+ let targetDepth = prefix.split("/").length;
262
+ results = results.filter(name => name.split("/").length === targetDepth);
263
+ }
264
+
265
+ results = results.map(name => unescapeFileName(name));
266
+
267
+ return results;
268
+ }
269
+ @measureFnc
270
+ public async findInfo(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<{ path: string; createTime: number; size: number; }[]> {
271
+ // TODO: Isn't there any api to read file names and infos at the same time?
272
+ let files = await this.find(prefix, config);
273
+ return (await Promise.all(files.map(async file => {
274
+ try {
275
+ let stats = await fs.promises.stat(this.LOCAL_ARCHIVE_FOLDER + escapeFileName(file));
276
+ return { path: file, createTime: stats.ctimeMs, size: stats.size };
277
+ } catch {
278
+ return undefined;
279
+ }
280
+ }))).filter(isDefined);
281
+ }
282
+
283
+
284
+ @measureFnc
285
+ public async getInfo(pathInput: string): Promise<{
286
+ writeTime: number;
287
+ size: number;
288
+ } | undefined> {
289
+ let path = escapeFileName(pathInput);
290
+ try {
291
+ let stats = await fs.promises.stat(this.LOCAL_ARCHIVE_FOLDER + path);
292
+ return {
293
+ writeTime: stats.mtimeMs,
294
+ size: stats.size,
295
+ };
296
+ } catch {
297
+ return undefined;
298
+ }
299
+ }
300
+
301
+ private simulateLag() {
302
+ if (this.LAG === 0) return;
303
+ return delay(this.LAG);
304
+ }
305
+
306
+ public async assertPathValid(path: string) {
307
+ await this.init();
308
+ escapeFileName(this.LOCAL_ARCHIVE_FOLDER + path);
309
+ }
310
+
311
+ @measureFnc
312
+ public async move(config: {
313
+ path: string;
314
+ target: Archives;
315
+ targetPath: string;
316
+ }) {
317
+ let { path, target, targetPath } = config;
318
+ while (true) {
319
+ let targetUnwrapped = target.getBaseArchives?.();
320
+ if (!targetUnwrapped) break;
321
+ target = targetUnwrapped.archives;
322
+ targetPath = targetUnwrapped.parentPath + targetPath;
323
+ }
324
+
325
+ if (target instanceof ArchivesDisk) {
326
+ path = escapeFileName(path);
327
+ targetPath = escapeFileName(targetPath);
328
+ await this.ensureDirsExist(path);
329
+ await target.ensureDirsExist(targetPath);
330
+ await fs.promises.rename(
331
+ this.LOCAL_ARCHIVE_FOLDER + path,
332
+ target.LOCAL_ARCHIVE_FOLDER + targetPath
333
+ );
334
+ } else {
335
+ let data = await this.get(path);
336
+ if (!data) throw new Error(`File not found to move: ${path}`);
337
+ await target.set(targetPath, data);
338
+ await this.del(path);
339
+ }
340
+ }
341
+
342
+ @measureFnc
343
+ public async copy(config: {
344
+ path: string;
345
+ target: Archives;
346
+ targetPath: string;
347
+ }) {
348
+ let { path, target, targetPath } = config;
349
+ while (true) {
350
+ let targetUnwrapped = target.getBaseArchives?.();
351
+ if (!targetUnwrapped) break;
352
+ target = targetUnwrapped.archives;
353
+ targetPath = targetUnwrapped.parentPath + targetPath;
354
+ }
355
+
356
+ if (target instanceof ArchivesDisk) {
357
+ path = escapeFileName(path);
358
+ targetPath = escapeFileName(targetPath);
359
+ await this.ensureDirsExist(path);
360
+ await target.ensureDirsExist(targetPath);
361
+ await fs.promises.copyFile(
362
+ this.LOCAL_ARCHIVE_FOLDER + path,
363
+ target.LOCAL_ARCHIVE_FOLDER + targetPath
364
+ );
365
+ } else {
366
+ let data = await this.get(path);
367
+ if (!data) throw new Error(`File not found to move: ${path}`);
368
+ await target.set(targetPath, data);
369
+ }
370
+ }
371
+ }
372
+
373
+ export const diskEscapeFileName = escapeFileName;
374
+
375
+ function escapeFileName(fileName: string): string {
376
+ fileName = fileName.replaceAll("_", "__");
377
+ if (fileName[1] === ":" && fileName[2] === "/") {
378
+ fileName = fileName.slice(0, 2) + fileName.slice(2).replaceAll(":", "_=");
379
+ } else {
380
+ fileName = fileName.replaceAll(":", "_=");
381
+ }
382
+ fileName = fileName.replaceAll(`"`, `_'`);
383
+ // TODO: Better escaping for file system paths
384
+ // TODO: Support longer filenames in some way? (backblaze can support longer names, but...
385
+ // we're always going to want to support just using the local filesystem, as that is always
386
+ // preferred during development).
387
+ if (fileName.length > maxFileNameLength) {
388
+ throw new Error(`File name too long, must be at most ${maxFileNameLength} characters long, was ${fileName.length}, file name ${JSON.stringify(fileName)}`);
389
+ }
390
+ fileName = fileName.replaceAll("\\", "/");
391
+ let parts = fileName.split("/");
392
+ for (let part of parts) {
393
+ if (part.length > maxFilePartLength) {
394
+ throw new Error(`File name part too long, must be at most ${maxFilePartLength} characters long, was ${part.length}, file name ${JSON.stringify(fileName)}`);
395
+ }
396
+ }
397
+ if (fileName.startsWith("/")) {
398
+ fileName = fileName.slice("/".length);
399
+ }
400
+ return fileName;
401
+ }
402
+ function unescapeFileName(fileName: string): string {
403
+ fileName = fileName.replaceAll(`_'`, `"`);
404
+ fileName = fileName.replaceAll("_=", ":");
405
+ fileName = fileName.replaceAll("__", "_");
406
+ return fileName;
407
+ }
408
+
409
+ export const getArchivesLocal = cache((domain: string): Archives => {
410
+
411
+ const archivesLocal = new ArchivesDisk();
412
+
413
+ if (isNode()) {
414
+ archivesLocal.LOCAL_ARCHIVE_FOLDER = getSubFolder(domain).replaceAll("\\", "/");
415
+ }
416
+
417
+ return archivesLocal;
418
+ });
@@ -0,0 +1,24 @@
1
+ import { formatNumber } from "socket-function/src/formatting/format";
2
+ import { getDomain } from "../config";
3
+ import { getArchivesBackblaze } from "./archivesBackBlaze";
4
+ import { getArchivesLocal } from "./archivesDisk";
5
+
6
+ async function main() {
7
+ const domain = getDomain();
8
+ let local = getArchivesLocal(domain);
9
+ let remote = getArchivesBackblaze(domain);
10
+
11
+ let files = await local.find("");
12
+ let allRemoteFiles = new Set(await remote.find(""));
13
+ for (let file of files) {
14
+ if (allRemoteFiles.has(file)) continue;
15
+ let buffer = await local.get(file);
16
+ if (!buffer) continue;
17
+ console.log(`Copying ${file} size ${formatNumber(buffer.length)}B`);
18
+ await remote.set(file, buffer);
19
+ }
20
+ }
21
+ main().catch(console.error).finally(() => process.exit(0));
22
+
23
+ // archives = getArchivesLocal(domain);
24
+ // archives = getArchivesBackblaze(domain);