sliftutils 1.7.9 → 1.7.11
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.
- package/bin/storageserver.js +4 -0
- package/{teststorage → examplestorage}/browser.tsx +18 -22
- package/{teststorage/server.ts → examplestorage/exampleserver.ts} +4 -6
- package/index.d.ts +770 -84
- package/misc/dist/environment.ts.cache +3 -3
- package/misc/dist/getSecret.ts.cache +139 -0
- package/misc/dist/strings.ts.cache +13 -0
- package/misc/dist/zip.ts.cache +9 -0
- package/misc/getSecret.ts +38 -16
- package/misc/https/cloudflareHelpers.d.ts +19 -0
- package/misc/https/cloudflareHelpers.ts +69 -0
- package/misc/https/dist/certs.ts.cache +115 -179
- package/misc/https/dist/cloudflareHelpers.ts.cache +68 -0
- package/misc/https/dist/dns.ts.cache +112 -100
- package/misc/https/dist/hostServer.ts.cache +130 -0
- package/misc/https/dist/httpsCerts.ts.cache +24 -5
- package/misc/https/dist/persistentLocalStorage.ts.cache +3 -2
- package/misc/https/dns.d.ts +41 -8
- package/misc/https/dns.ts +104 -106
- package/misc/https/hostServer.d.ts +0 -3
- package/misc/https/hostServer.ts +12 -8
- package/package.json +3 -2
- package/render-utils/dist/observer.tsx.cache +3 -2
- package/spec.txt +0 -55
- package/storage/ArchivesDisk.d.ts +67 -0
- package/storage/ArchivesDisk.ts +369 -0
- package/storage/BulkDatabase2/dist/BulkDatabaseBase.ts.cache +1242 -0
- package/storage/BulkDatabase2/dist/BulkDatabaseFormat.ts.cache +513 -0
- package/storage/BulkDatabase2/dist/BulkDatabaseMerge.ts.cache +352 -0
- package/storage/BulkDatabase2/dist/BulkDatabaseReader.ts.cache +306 -0
- package/storage/BulkDatabase2/dist/LoadedIndex.ts.cache +435 -0
- package/storage/BulkDatabase2/dist/WriteOverlay.ts.cache +58 -0
- package/storage/BulkDatabase2/dist/blockCache.ts.cache +179 -0
- package/storage/BulkDatabase2/dist/mergeLock.ts.cache +149 -0
- package/storage/BulkDatabase2/dist/mergeMarkers.ts.cache +101 -0
- package/storage/BulkDatabase2/dist/streamLog.ts.cache +227 -0
- package/storage/BulkDatabase2/dist/syncClient.ts.cache +104 -0
- package/storage/IArchives.d.ts +127 -1
- package/storage/IArchives.ts +201 -1
- package/storage/backblaze.d.ts +29 -2
- package/storage/backblaze.ts +214 -78
- package/storage/dist/ArchivesDisk.ts.cache +373 -0
- package/storage/dist/FileFolderAPI.tsx.cache +508 -65
- package/storage/dist/IArchives.ts.cache +44 -0
- package/storage/dist/IndexedDBFileFolderAPI.ts.cache +8 -2
- package/storage/dist/JSONStorage.ts.cache +6 -2
- package/storage/dist/PendingManager.tsx.cache +1 -1
- package/storage/dist/TransactionStorage.ts.cache +231 -92
- package/storage/dist/backblaze.ts.cache +797 -0
- package/storage/dist/fileSystemPointer.ts.cache +36 -3
- package/storage/dist/remoteFileStorage.ts.cache +495 -0
- package/storage/remoteStorage/ArchivesRemote.d.ts +34 -18
- package/storage/remoteStorage/ArchivesRemote.ts +85 -65
- package/storage/remoteStorage/ArchivesUrl.d.ts +48 -0
- package/storage/remoteStorage/ArchivesUrl.ts +86 -0
- package/storage/remoteStorage/accessPage.tsx +111 -28
- package/storage/remoteStorage/blobStore.d.ts +128 -25
- package/storage/remoteStorage/blobStore.ts +757 -290
- package/storage/remoteStorage/cliArgs.d.ts +1 -0
- package/storage/remoteStorage/cliArgs.ts +9 -0
- package/storage/remoteStorage/createArchives.d.ts +85 -0
- package/storage/remoteStorage/createArchives.ts +612 -0
- package/storage/remoteStorage/dist/ArchivesRemote.ts.cache +197 -0
- package/storage/remoteStorage/dist/ArchivesUrl.ts.cache +85 -0
- package/storage/remoteStorage/dist/accessPage.tsx.cache +222 -0
- package/storage/remoteStorage/dist/blobStore.ts.cache +837 -0
- package/storage/remoteStorage/dist/cliArgs.ts.cache +16 -0
- package/storage/remoteStorage/dist/createArchives.ts.cache +610 -0
- package/storage/remoteStorage/dist/remoteConfig.ts.cache +206 -0
- package/storage/remoteStorage/dist/sourceWrapper.ts.cache +223 -0
- package/storage/remoteStorage/dist/storageController.ts.cache +500 -0
- package/storage/remoteStorage/dist/storageServer.ts.cache +106 -0
- package/storage/remoteStorage/dist/storageServerCli.ts.cache +39 -0
- package/storage/remoteStorage/dist/storageServerState.ts.cache +518 -0
- package/storage/remoteStorage/grantAccess.js +4 -0
- package/storage/remoteStorage/grantAccessCli.d.ts +1 -0
- package/storage/remoteStorage/grantAccessCli.ts +27 -0
- package/storage/remoteStorage/remoteConfig.d.ts +29 -0
- package/storage/remoteStorage/remoteConfig.ts +194 -0
- package/storage/remoteStorage/sourceWrapper.d.ts +41 -0
- package/storage/remoteStorage/sourceWrapper.ts +223 -0
- package/storage/remoteStorage/spec.md +31 -0
- package/storage/remoteStorage/storageController.d.ts +30 -27
- package/storage/remoteStorage/storageController.ts +297 -159
- package/storage/remoteStorage/storageServer.d.ts +6 -0
- package/storage/remoteStorage/storageServer.ts +88 -94
- package/storage/remoteStorage/storageServerCli.d.ts +1 -0
- package/storage/remoteStorage/storageServerCli.ts +38 -0
- package/storage/remoteStorage/storageServerState.d.ts +43 -0
- package/storage/remoteStorage/storageServerState.ts +540 -0
- package/testsite/server.ts +0 -2
- package/yarn.lock +11 -6
package/storage/backblaze.ts
CHANGED
|
@@ -9,7 +9,7 @@ import debugbreak from "debugbreak";
|
|
|
9
9
|
import dns from "dns";
|
|
10
10
|
import { getSecret } from "../misc/getSecret";
|
|
11
11
|
import { httpsRequest } from "socket-function/src/https";
|
|
12
|
-
import { IArchives } from "./IArchives";
|
|
12
|
+
import { IArchives, ArchivesConfig, assertValidLastModified, IMMUTABLE_CACHE_TIME } from "./IArchives";
|
|
13
13
|
|
|
14
14
|
type BackblazeCreds = {
|
|
15
15
|
applicationKeyId: string;
|
|
@@ -55,7 +55,6 @@ const getAPI = lazy(async () => {
|
|
|
55
55
|
}
|
|
56
56
|
try {
|
|
57
57
|
let url = auth.apiUrl + "/b2api/v2/" + name;
|
|
58
|
-
let time = Date.now();
|
|
59
58
|
let result = await httpsRequest(url, Buffer.from(JSON.stringify(arg)), type, undefined, {
|
|
60
59
|
headers: {
|
|
61
60
|
Authorization: auth.authorizationToken,
|
|
@@ -221,7 +220,7 @@ const getAPI = lazy(async () => {
|
|
|
221
220
|
};
|
|
222
221
|
action: string;
|
|
223
222
|
uploadTimestamp: number;
|
|
224
|
-
}>("b2_get_file_info", "POST");
|
|
223
|
+
}>("b2_get_file_info", "POST", "noAccountId");
|
|
225
224
|
|
|
226
225
|
const listFileNames = createB2Function<{
|
|
227
226
|
bucketId: string;
|
|
@@ -367,6 +366,7 @@ const getAPI = lazy(async () => {
|
|
|
367
366
|
getDownloadAuthorization,
|
|
368
367
|
getDownloadURL,
|
|
369
368
|
apiUrl: auth.apiUrl,
|
|
369
|
+
allowed: auth.allowed,
|
|
370
370
|
};
|
|
371
371
|
});
|
|
372
372
|
|
|
@@ -379,7 +379,11 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
379
379
|
public?: boolean;
|
|
380
380
|
immutable?: boolean;
|
|
381
381
|
cacheTime?: number;
|
|
382
|
-
|
|
382
|
+
allowedOrigins?: string[];
|
|
383
|
+
}) {
|
|
384
|
+
// Get the api, to setup cors
|
|
385
|
+
void this.getBucketAPI();
|
|
386
|
+
}
|
|
383
387
|
|
|
384
388
|
private bucketName = this.config.bucketName.replaceAll(/[^\w\d]/g, "-");
|
|
385
389
|
private bucketId = "";
|
|
@@ -402,13 +406,13 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
402
406
|
|
|
403
407
|
let cacheTime = this.config.cacheTime ?? 0;
|
|
404
408
|
if (this.config.immutable) {
|
|
405
|
-
cacheTime =
|
|
409
|
+
cacheTime = IMMUTABLE_CACHE_TIME;
|
|
406
410
|
}
|
|
407
411
|
|
|
408
412
|
// ALWAYS set access control, as we can make urls for private buckets with getDownloadAuthorization
|
|
409
413
|
let desiredCorsRules = [{
|
|
410
414
|
corsRuleName: "allowAll",
|
|
411
|
-
allowedOrigins: ["https"],
|
|
415
|
+
allowedOrigins: this.config.allowedOrigins ?? ["https"],
|
|
412
416
|
allowedOperations: ["b2_download_file_by_id", "b2_download_file_by_name"],
|
|
413
417
|
allowedHeaders: ["range"],
|
|
414
418
|
exposeHeaders: ["x-bz-content-sha1"],
|
|
@@ -421,24 +425,34 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
421
425
|
|
|
422
426
|
|
|
423
427
|
let exists = false;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
"
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
428
|
+
let retries = 0;
|
|
429
|
+
while (true) {
|
|
430
|
+
try {
|
|
431
|
+
await api.createBucket({
|
|
432
|
+
bucketName: this.bucketName,
|
|
433
|
+
bucketType: this.config.public ? "allPublic" : "allPrivate",
|
|
434
|
+
lifecycleRules: [{
|
|
435
|
+
"daysFromUploadingToHiding": null,
|
|
436
|
+
// Keep files for 7 days, which should be enough time to recover accidental hiding.
|
|
437
|
+
"daysFromHidingToDeleting": 7,
|
|
438
|
+
"fileNamePrefix": ""
|
|
439
|
+
}],
|
|
440
|
+
corsRules: desiredCorsRules,
|
|
441
|
+
bucketInfo
|
|
442
|
+
});
|
|
443
|
+
} catch (e: any) {
|
|
444
|
+
if (!e.stack.includes(`"duplicate_bucket_name"`)) {
|
|
445
|
+
if (retries < 3) {
|
|
446
|
+
console.error(`Backblaze create bucket failed, retrying in 5s: ${e.stack}`);
|
|
447
|
+
await delay(5000);
|
|
448
|
+
retries++;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
throw e;
|
|
452
|
+
}
|
|
453
|
+
exists = true;
|
|
440
454
|
}
|
|
441
|
-
|
|
455
|
+
break;
|
|
442
456
|
}
|
|
443
457
|
|
|
444
458
|
let bucketList = await api.listBuckets({
|
|
@@ -484,47 +498,57 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
484
498
|
return api;
|
|
485
499
|
});
|
|
486
500
|
|
|
501
|
+
private currentReset: Promise<void> | undefined;
|
|
502
|
+
|
|
487
503
|
// Keep track of when we last reset because of a 503
|
|
488
504
|
private last503Reset = 0;
|
|
489
505
|
// IMPORTANT! We must always CATCH AROUND the apiRetryLogic, NEVER inside of fnc. Otherwise we won't
|
|
490
506
|
// be able to recreate the auth token.
|
|
507
|
+
// `context` is a short label (verb + file path) included in every retry/error log so a stuck
|
|
508
|
+
// silent-retry loop is identifiable from the logs.
|
|
491
509
|
private async apiRetryLogic<T>(
|
|
510
|
+
context: string,
|
|
492
511
|
fnc: (api: B2Api) => Promise<T>,
|
|
493
512
|
retries = 3
|
|
494
513
|
): Promise<T> {
|
|
495
|
-
let api
|
|
514
|
+
let api: B2Api | undefined;
|
|
496
515
|
try {
|
|
516
|
+
api = await this.getBucketAPI();
|
|
497
517
|
return await fnc(api);
|
|
498
518
|
} catch (err: any) {
|
|
499
519
|
if (retries <= 0) throw err;
|
|
500
520
|
|
|
501
|
-
// If it's a 503 and it's been a minute since we last reset, then Wait and reset.
|
|
521
|
+
// If it's a 503 and it's been a minute since we last reset, then Wait and reset.
|
|
502
522
|
if (
|
|
503
|
-
(err.stack.includes(`503`)
|
|
523
|
+
(err.stack.includes(`"status": 503`)
|
|
504
524
|
|| err.stack.includes(`"service_unavailable"`)
|
|
505
525
|
|| err.stack.includes(`"internal_error"`)
|
|
506
526
|
|| err.stack.includes(`ENOBUFS`)
|
|
507
527
|
) && Date.now() - this.last503Reset > 60 * 1000) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
528
|
+
// Backblaze is so flaky that we're just going to warn here.
|
|
529
|
+
console.warn(`[${context}] Backblaze error, waiting and resetting: ${err.message}`);
|
|
530
|
+
this.log(`[${context}] Backblaze error, waiting and resetting: ${err.message}`);
|
|
531
|
+
this.currentReset = this.currentReset || (async () => {
|
|
532
|
+
await delay(10 * 1000);
|
|
533
|
+
// We check again in case, and in the very likely case that this is being run in parallel, we only want to reset once.
|
|
534
|
+
if (Date.now() - this.last503Reset > 60 * 1000) {
|
|
535
|
+
this.log(`[${context}] Resetting getAPI and getBucketAPI: ${err.message}`);
|
|
536
|
+
this.last503Reset = Date.now();
|
|
537
|
+
getAPI.reset();
|
|
538
|
+
this.getBucketAPI.reset();
|
|
539
|
+
}
|
|
540
|
+
})().finally(() => this.currentReset = undefined);
|
|
541
|
+
await this.currentReset;
|
|
542
|
+
return this.apiRetryLogic(context, fnc, retries - 1);
|
|
519
543
|
}
|
|
520
544
|
|
|
521
545
|
// If the error is that the authorization token is invalid, reset getBucketAPI and getAPI
|
|
522
546
|
// If the error is that the bucket isn't found, reset getBucketAPI
|
|
523
547
|
if (err.stack.includes(`"expired_auth_token"`)) {
|
|
524
|
-
this.log(
|
|
548
|
+
this.log(`[${context}] Authorization token expired`);
|
|
525
549
|
getAPI.reset();
|
|
526
550
|
this.getBucketAPI.reset();
|
|
527
|
-
return this.apiRetryLogic(fnc, retries - 1);
|
|
551
|
+
return this.apiRetryLogic(context, fnc, retries - 1);
|
|
528
552
|
}
|
|
529
553
|
|
|
530
554
|
if (
|
|
@@ -539,29 +563,30 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
539
563
|
|| err.stack.includes(`ECONNREFUSED`)
|
|
540
564
|
|| err.stack.includes(`ENOBUFS`)
|
|
541
565
|
) {
|
|
542
|
-
console.
|
|
543
|
-
this.log(err.message
|
|
566
|
+
console.warn(`[${context}] Retrying in 5s: ${err.message}`);
|
|
567
|
+
this.log(`[${context}] ${err.message} retrying in 5s`);
|
|
544
568
|
await delay(5000);
|
|
545
|
-
return this.apiRetryLogic(fnc, retries - 1);
|
|
569
|
+
return this.apiRetryLogic(context, fnc, retries - 1);
|
|
546
570
|
}
|
|
547
571
|
|
|
548
572
|
if (err.stack.includes(`getaddrinfo ENOTFOUND`)) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
573
|
+
if (api) {
|
|
574
|
+
let urlObj = new URL(api.apiUrl);
|
|
575
|
+
let hostname = urlObj.hostname;
|
|
576
|
+
let lookupAddresses = await new Promise(resolve => {
|
|
577
|
+
dns.lookup(hostname, (err, addresses) => {
|
|
578
|
+
resolve(addresses);
|
|
579
|
+
});
|
|
554
580
|
});
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
581
|
+
let resolveAddresses = await new Promise(resolve => {
|
|
582
|
+
dns.resolve4(hostname, (err, addresses) => {
|
|
583
|
+
resolve(addresses);
|
|
584
|
+
});
|
|
559
585
|
});
|
|
560
|
-
|
|
561
|
-
|
|
586
|
+
console.error(`[${context}] getaddrinfo ENOTFOUND ${hostname}`, { lookupAddresses, resolveAddresses, apiUrl: api.apiUrl, fullError: err.stack });
|
|
587
|
+
}
|
|
562
588
|
}
|
|
563
589
|
|
|
564
|
-
// TODO: Handle if the bucket is deleted?
|
|
565
590
|
throw err;
|
|
566
591
|
}
|
|
567
592
|
}
|
|
@@ -576,11 +601,28 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
576
601
|
setTimeout(downloadPoll, 5000);
|
|
577
602
|
};
|
|
578
603
|
setTimeout(downloadPoll, 5000);
|
|
579
|
-
let result = await this.apiRetryLogic(async (api) => {
|
|
604
|
+
let result = await this.apiRetryLogic(`get ${fileName}`, async (api) => {
|
|
605
|
+
let range = config?.range;
|
|
606
|
+
if (range) {
|
|
607
|
+
let fileInfo = await this.getInfo(fileName);
|
|
608
|
+
if (!fileInfo) throw new Error(`File ${fileName} not found`);
|
|
609
|
+
let rangeStart = range.start;
|
|
610
|
+
let rangeEnd = Math.min(range.end, fileInfo.size);
|
|
611
|
+
// NOTE: I think if we request nothing, it confuses Backblaze and ends up giving us the entire file.
|
|
612
|
+
if (rangeEnd <= rangeStart) return Buffer.alloc(0);
|
|
613
|
+
let result = await api.downloadFileByName({
|
|
614
|
+
bucketName: this.bucketName,
|
|
615
|
+
fileName,
|
|
616
|
+
range: { start: rangeStart, end: rangeEnd },
|
|
617
|
+
});
|
|
618
|
+
if (result.length !== rangeEnd - rangeStart) {
|
|
619
|
+
console.error(`Backblaze range download returned the wrong number of bytes. Tried to get ${rangeStart}-${rangeEnd}, but received ${rangeStart}-${rangeStart + result.length}. For file: ${fileName}`);
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
580
623
|
return await api.downloadFileByName({
|
|
581
624
|
bucketName: this.bucketName,
|
|
582
625
|
fileName,
|
|
583
|
-
range: config?.range
|
|
584
626
|
});
|
|
585
627
|
});
|
|
586
628
|
let timeStr = formatTime(Date.now() - time);
|
|
@@ -594,10 +636,42 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
594
636
|
downloading = false;
|
|
595
637
|
}
|
|
596
638
|
}
|
|
597
|
-
public async
|
|
639
|
+
public async get2(fileName: string, config?: { range?: { start: number; end: number; } }): Promise<{ data: Buffer; writeTime: number; size: number } | undefined> {
|
|
640
|
+
// B2 downloads don't return the upload time, so this takes a second API call
|
|
641
|
+
let [data, info] = await Promise.all([this.get(fileName, config), this.getInfo(fileName)]);
|
|
642
|
+
if (!data || !info) return undefined;
|
|
643
|
+
return { data, writeTime: info.writeTime, size: info.size };
|
|
644
|
+
}
|
|
645
|
+
public async getConfig(): Promise<ArchivesConfig> {
|
|
646
|
+
return {};
|
|
647
|
+
}
|
|
648
|
+
public async hasWriteAccess(): Promise<boolean> {
|
|
649
|
+
try {
|
|
650
|
+
let api = await getAPI();
|
|
651
|
+
// B2 v2 documents allowed as a single object; our type says a list - handle both
|
|
652
|
+
let allowedRaw = api.allowed as unknown;
|
|
653
|
+
let allowedList = (Array.isArray(allowedRaw) && allowedRaw || [allowedRaw]) as typeof api.allowed;
|
|
654
|
+
return allowedList.some(allowed =>
|
|
655
|
+
allowed.capabilities.includes("writeFiles")
|
|
656
|
+
&& (!allowed.bucketName || allowed.bucketName === this.bucketName)
|
|
657
|
+
);
|
|
658
|
+
} catch (e) {
|
|
659
|
+
// No / invalid credentials
|
|
660
|
+
this.log(`backblaze hasWriteAccess check failed: ${(e as Error).stack ?? e}`);
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
public async set(fileName: string, data: Buffer, config?: { lastModified?: number }): Promise<void> {
|
|
665
|
+
if (config?.lastModified) {
|
|
666
|
+
assertValidLastModified(config.lastModified);
|
|
667
|
+
let existing = await this.getInfo(fileName);
|
|
668
|
+
// An older write never overwrites a newer one (see IArchives.set). B2 stamps its own
|
|
669
|
+
// upload time, so the exact lastModified is not preserved on the stored file.
|
|
670
|
+
if (existing && config.lastModified < existing.writeTime) return;
|
|
671
|
+
}
|
|
598
672
|
this.log(`backblaze upload (${formatNumber(data.length)}B) ${fileName}`);
|
|
599
673
|
let f = fileName;
|
|
600
|
-
await this.apiRetryLogic(async (api) => {
|
|
674
|
+
await this.apiRetryLogic(`uploadFile ${fileName}`, async (api) => {
|
|
601
675
|
await api.uploadFile({ bucketId: this.bucketId, fileName, data: data, });
|
|
602
676
|
});
|
|
603
677
|
let existsChecks = 30;
|
|
@@ -616,7 +690,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
616
690
|
public async del(fileName: string): Promise<void> {
|
|
617
691
|
this.log(`backblaze delete ${fileName}`);
|
|
618
692
|
try {
|
|
619
|
-
await this.apiRetryLogic(async (api) => {
|
|
693
|
+
await this.apiRetryLogic(`hideFile ${fileName}`, async (api) => {
|
|
620
694
|
await api.hideFile({ bucketId: this.bucketId, fileName: fileName });
|
|
621
695
|
});
|
|
622
696
|
} catch (e: any) {
|
|
@@ -685,7 +759,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
685
759
|
dataQueue.unshift(data, secondData);
|
|
686
760
|
|
|
687
761
|
|
|
688
|
-
let uploadInfo = await this.apiRetryLogic(async (api) => {
|
|
762
|
+
let uploadInfo = await this.apiRetryLogic(`startLargeFile ${fileName}`, async (api) => {
|
|
689
763
|
return await api.startLargeFile({
|
|
690
764
|
bucketId: this.bucketId,
|
|
691
765
|
fileName: fileName,
|
|
@@ -694,7 +768,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
694
768
|
});
|
|
695
769
|
});
|
|
696
770
|
onError.push(async () => {
|
|
697
|
-
await this.apiRetryLogic(async (api) => {
|
|
771
|
+
await this.apiRetryLogic(`cancelLargeFile ${fileName}`, async (api) => {
|
|
698
772
|
await api.cancelLargeFile({ fileId: uploadInfo.fileId });
|
|
699
773
|
});
|
|
700
774
|
});
|
|
@@ -731,7 +805,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
731
805
|
sha1.update(data);
|
|
732
806
|
let sha1Hex = sha1.digest("hex");
|
|
733
807
|
partSha1Array.push(sha1Hex);
|
|
734
|
-
await this.apiRetryLogic(async (api) => {
|
|
808
|
+
await this.apiRetryLogic(`uploadPart#${partNumber} ${fileName}`, async (api) => {
|
|
735
809
|
if (!data) throw new Error("Impossible, data is undefined");
|
|
736
810
|
|
|
737
811
|
let timeStr = formatTime(Date.now() - time);
|
|
@@ -757,7 +831,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
757
831
|
}
|
|
758
832
|
this.log(`Finished uploading large file uploaded ${green(formatNumber(totalBytes))}B`);
|
|
759
833
|
|
|
760
|
-
await this.apiRetryLogic(async (api) => {
|
|
834
|
+
await this.apiRetryLogic(`finishLargeFile ${fileName}`, async (api) => {
|
|
761
835
|
await api.finishLargeFile({
|
|
762
836
|
fileId: uploadInfo.fileId,
|
|
763
837
|
partSha1Array: partSha1Array,
|
|
@@ -777,18 +851,27 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
777
851
|
}
|
|
778
852
|
|
|
779
853
|
public async getInfo(fileName: string): Promise<{ writeTime: number; size: number; } | undefined> {
|
|
780
|
-
return await this.apiRetryLogic(async (api) => {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
854
|
+
return await this.apiRetryLogic(`getInfo ${fileName}`, async (api) => {
|
|
855
|
+
try {
|
|
856
|
+
// NOTE: Apparently, there's no other way to do this, as the file name does not equal the file ID, and get file info requires the file ID.
|
|
857
|
+
let info = await api.listFileNames({ bucketId: this.bucketId, prefix: fileName, maxFileCount: 10 });
|
|
858
|
+
let file = info.files.find(x => x.fileName === fileName && x.action === "upload");
|
|
859
|
+
if (!file) {
|
|
860
|
+
this.log(`Backblaze file not exists ${fileName}`);
|
|
861
|
+
return undefined;
|
|
862
|
+
}
|
|
863
|
+
this.log(`Backblaze file exists ${fileName}`);
|
|
864
|
+
return {
|
|
865
|
+
writeTime: file.uploadTimestamp,
|
|
866
|
+
size: file.contentLength,
|
|
867
|
+
};
|
|
868
|
+
} catch (e: any) {
|
|
869
|
+
if (e.stack.includes(`file_not_found`)) {
|
|
870
|
+
this.log(`Backblaze file not exists ${fileName}`);
|
|
871
|
+
return undefined;
|
|
872
|
+
}
|
|
873
|
+
throw e;
|
|
786
874
|
}
|
|
787
|
-
this.log(`Backblaze file exists ${fileName}`);
|
|
788
|
-
return {
|
|
789
|
-
writeTime: file.uploadTimestamp,
|
|
790
|
-
size: file.contentLength,
|
|
791
|
-
};
|
|
792
875
|
});
|
|
793
876
|
}
|
|
794
877
|
|
|
@@ -798,7 +881,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
798
881
|
return result.map(x => x.path);
|
|
799
882
|
}
|
|
800
883
|
public async findInfo(prefix: string, config?: { shallow?: boolean; type: "files" | "folders" }): Promise<{ path: string; createTime: number; size: number; }[]> {
|
|
801
|
-
return await this.apiRetryLogic(async (api) => {
|
|
884
|
+
return await this.apiRetryLogic(`findInfo ${prefix}`, async (api) => {
|
|
802
885
|
if (!config?.shallow && config?.type === "folders") {
|
|
803
886
|
let allFiles = await this.findInfo(prefix);
|
|
804
887
|
let allFolders = new Map<string, { path: string; createTime: number; size: number }>();
|
|
@@ -845,8 +928,59 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
845
928
|
}
|
|
846
929
|
}
|
|
847
930
|
|
|
931
|
+
public async move(config: {
|
|
932
|
+
path: string;
|
|
933
|
+
target: IArchives;
|
|
934
|
+
targetPath: string;
|
|
935
|
+
copyInstead?: boolean;
|
|
936
|
+
}) {
|
|
937
|
+
let { path, target, targetPath } = config;
|
|
938
|
+
// A self move should NOOP (and definitely not copy, and then delete itself!)
|
|
939
|
+
if (target === this && path === targetPath) {
|
|
940
|
+
this.log(`Backblaze move path to itself. Skipping move, as there is no work to do. ${path}`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (target instanceof ArchivesBackblaze) {
|
|
944
|
+
let targetBucketId = target.bucketId;
|
|
945
|
+
if (targetBucketId === this.bucketId && path === targetPath) return;
|
|
946
|
+
await this.apiRetryLogic(`move ${path} -> ${targetPath}`, async (api) => {
|
|
947
|
+
// Ugh... listing the file name sucks, but... I guess it's still better than
|
|
948
|
+
// downloading and re-uploading the entire file.
|
|
949
|
+
let info = await api.listFileNames({ bucketId: this.bucketId, prefix: path, maxFileCount: 10 });
|
|
950
|
+
let file = info.files.find(x => x.fileName === path);
|
|
951
|
+
if (!file) throw new Error(`File not found to move: ${path}`);
|
|
952
|
+
await api.copyFile({
|
|
953
|
+
sourceFileId: file.fileId,
|
|
954
|
+
fileName: targetPath,
|
|
955
|
+
destinationBucketId: targetBucketId,
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
} else {
|
|
959
|
+
let data = await this.get(path);
|
|
960
|
+
if (!data) throw new Error(`File not found to move: ${path}`);
|
|
961
|
+
await target.set(targetPath, data);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (!config.copyInstead) {
|
|
965
|
+
let exists = await this.getInfo(targetPath);
|
|
966
|
+
if (!exists) {
|
|
967
|
+
console.error(`File not found after move. Leaving BOTH files. ${targetPath} was not found. Being moved from ${path}`);
|
|
968
|
+
} else {
|
|
969
|
+
await this.del(path);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
public async copy(config: {
|
|
975
|
+
path: string;
|
|
976
|
+
target: IArchives;
|
|
977
|
+
targetPath: string;
|
|
978
|
+
}): Promise<void> {
|
|
979
|
+
return this.move({ ...config, copyInstead: true });
|
|
980
|
+
}
|
|
981
|
+
|
|
848
982
|
public async getURL(path: string) {
|
|
849
|
-
return await this.apiRetryLogic(async (api) => {
|
|
983
|
+
return await this.apiRetryLogic(`getURL ${path}`, async (api) => {
|
|
850
984
|
if (path.startsWith("/")) {
|
|
851
985
|
path = path.slice(1);
|
|
852
986
|
}
|
|
@@ -868,7 +1002,7 @@ export class ArchivesBackblaze implements IArchives {
|
|
|
868
1002
|
fileNamePrefix: string;
|
|
869
1003
|
authorizationToken: string;
|
|
870
1004
|
}> {
|
|
871
|
-
return await this.apiRetryLogic(async (api) => {
|
|
1005
|
+
return await this.apiRetryLogic(`getDownloadAuthorization ${config.fileNamePrefix ?? ""}`, async (api) => {
|
|
872
1006
|
return await api.getDownloadAuthorization({
|
|
873
1007
|
bucketId: this.bucketId,
|
|
874
1008
|
fileNamePrefix: config.fileNamePrefix ?? "",
|
|
@@ -900,7 +1034,8 @@ export const getArchivesBackblazePublicImmutable = cache((domain: string) => {
|
|
|
900
1034
|
return new ArchivesBackblaze({
|
|
901
1035
|
bucketName: domain + "-public-immutable",
|
|
902
1036
|
public: true,
|
|
903
|
-
immutable: true
|
|
1037
|
+
immutable: true,
|
|
1038
|
+
allowedOrigins: [`https://${domain}`, `https://127-0-0-1.${domain}:7007`],
|
|
904
1039
|
});
|
|
905
1040
|
});
|
|
906
1041
|
|
|
@@ -912,5 +1047,6 @@ export const getArchivesBackblazePublic = cache((domain: string) => {
|
|
|
912
1047
|
bucketName: domain + "-public",
|
|
913
1048
|
public: true,
|
|
914
1049
|
cacheTime: timeInMinute,
|
|
1050
|
+
allowedOrigins: [`https://${domain}`, `https://127-0-0-1.${domain}:7007`],
|
|
915
1051
|
});
|
|
916
1052
|
});
|