mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-03 23:56:53 +00:00
141 lines
3.6 KiB
TypeScript
141 lines
3.6 KiB
TypeScript
import { limitFunction } from "p-limit";
|
|
import { SvelteMap } from "svelte/reactivity";
|
|
import { CHUNK_SIZE } from "$lib/constants";
|
|
import {
|
|
decryptFileMetadata,
|
|
getFileInfo,
|
|
type FileInfo,
|
|
type MaybeFileInfo,
|
|
} from "$lib/modules/filesystem";
|
|
import { uploadBlob } from "$lib/modules/upload";
|
|
import { requestFileDownload } from "$lib/services/file";
|
|
import { HybridPromise, Scheduler } from "$lib/utils";
|
|
import { trpc } from "$trpc/client";
|
|
import type { RouterOutputs } from "$trpc/router.server";
|
|
|
|
export type MigrationStatus =
|
|
| "queued"
|
|
| "downloading"
|
|
| "upload-pending"
|
|
| "uploading"
|
|
| "uploaded"
|
|
| "error";
|
|
|
|
export interface MigrationState {
|
|
status: MigrationStatus;
|
|
progress?: number;
|
|
rate?: number;
|
|
}
|
|
|
|
const scheduler = new Scheduler();
|
|
const states = new SvelteMap<number, MigrationState>();
|
|
|
|
export const requestLegacyFiles = async (
|
|
filesRaw: RouterOutputs["file"]["listLegacy"],
|
|
masterKey: CryptoKey,
|
|
) => {
|
|
const files = await HybridPromise.all(
|
|
filesRaw.map((file) =>
|
|
HybridPromise.resolve(
|
|
getFileInfo(file.id, masterKey, {
|
|
async fetchFromServer(id, cachedInfo, masterKey) {
|
|
const metadata = await decryptFileMetadata(file, masterKey);
|
|
return {
|
|
categories: [],
|
|
...cachedInfo,
|
|
id: id as number,
|
|
exists: true,
|
|
isLegacy: file.isLegacy,
|
|
parentId: file.parent,
|
|
contentType: file.contentType,
|
|
isFavorite: file.isFavorite,
|
|
...metadata,
|
|
};
|
|
},
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
return files;
|
|
};
|
|
|
|
const createState = (status: MigrationStatus): MigrationState => {
|
|
const state = $state({ status });
|
|
return state;
|
|
};
|
|
|
|
export const getMigrationState = (fileId: number) => {
|
|
return states.get(fileId);
|
|
};
|
|
|
|
export const clearMigrationStates = () => {
|
|
for (const [id, state] of states) {
|
|
if (state.status === "uploaded" || state.status === "error") {
|
|
states.delete(id);
|
|
}
|
|
}
|
|
};
|
|
|
|
const requestFileUpload = limitFunction(
|
|
async (
|
|
state: MigrationState,
|
|
fileId: number,
|
|
fileBuffer: ArrayBuffer,
|
|
dataKey: CryptoKey,
|
|
dataKeyVersion: Date,
|
|
) => {
|
|
state.status = "uploading";
|
|
|
|
const { uploadId } = await trpc().upload.startMigrationUpload.mutate({
|
|
file: fileId,
|
|
chunks: Math.ceil(fileBuffer.byteLength / CHUNK_SIZE),
|
|
dekVersion: dataKeyVersion,
|
|
});
|
|
|
|
await uploadBlob(uploadId, new Blob([fileBuffer]), dataKey, {
|
|
onProgress(s) {
|
|
state.progress = s.progress;
|
|
state.rate = s.rate;
|
|
},
|
|
});
|
|
|
|
await trpc().upload.completeMigrationUpload.mutate({ uploadId });
|
|
state.status = "uploaded";
|
|
},
|
|
{ concurrency: 1 },
|
|
);
|
|
|
|
export const requestFileMigration = async (fileInfo: FileInfo) => {
|
|
let state = states.get(fileInfo.id);
|
|
if (state) {
|
|
if (state.status !== "error") return;
|
|
state.status = "queued";
|
|
state.progress = undefined;
|
|
state.rate = undefined;
|
|
} else {
|
|
state = createState("queued");
|
|
states.set(fileInfo.id, state);
|
|
}
|
|
|
|
try {
|
|
const dataKey = fileInfo.dataKey;
|
|
if (!dataKey) {
|
|
throw new Error("Data key not available");
|
|
}
|
|
|
|
let fileBuffer: ArrayBuffer | undefined;
|
|
|
|
await scheduler.schedule(
|
|
async () => {
|
|
state.status = "downloading";
|
|
fileBuffer = await requestFileDownload(fileInfo.id, dataKey.key, true);
|
|
return fileBuffer.byteLength;
|
|
},
|
|
() => requestFileUpload(state, fileInfo.id, fileBuffer!, dataKey.key, dataKey.version),
|
|
);
|
|
} catch (e) {
|
|
state.status = "error";
|
|
throw e;
|
|
}
|
|
};
|