mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
업로드된 청크 목록을 비트맵을 활용해 효율적으로 저장하도록 개선
This commit is contained in:
@@ -80,12 +80,12 @@ export const uploadBlob = async (
|
|||||||
const limit = pLimit(options?.concurrency ?? 4);
|
const limit = pLimit(options?.concurrency ?? 4);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from({ length: totalChunks }, (_, chunkIndex) =>
|
Array.from({ length: totalChunks }, (_, i) =>
|
||||||
limit(() =>
|
limit(() =>
|
||||||
uploadChunk(
|
uploadChunk(
|
||||||
uploadId,
|
uploadId,
|
||||||
chunkIndex,
|
i + 1, // 1-based chunk index
|
||||||
blob.slice(chunkIndex * CHUNK_SIZE, (chunkIndex + 1) * CHUNK_SIZE),
|
blob.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
|
||||||
dataKey,
|
dataKey,
|
||||||
onChunkProgress,
|
onChunkProgress,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -21,8 +21,14 @@ export const up = async (db: Kysely<any>) => {
|
|||||||
.addColumn("type", "text", (col) => col.notNull())
|
.addColumn("type", "text", (col) => col.notNull())
|
||||||
.addColumn("user_id", "integer", (col) => col.references("user.id").notNull())
|
.addColumn("user_id", "integer", (col) => col.references("user.id").notNull())
|
||||||
.addColumn("path", "text", (col) => col.notNull())
|
.addColumn("path", "text", (col) => col.notNull())
|
||||||
|
.addColumn("bitmap", "bytea", (col) => col.notNull())
|
||||||
.addColumn("total_chunks", "integer", (col) => col.notNull())
|
.addColumn("total_chunks", "integer", (col) => col.notNull())
|
||||||
.addColumn("uploaded_chunks", sql`integer[]`, (col) => col.notNull().defaultTo(sql`'{}'`))
|
.addColumn("uploaded_chunks", "integer", (col) =>
|
||||||
|
col
|
||||||
|
.generatedAlwaysAs(sql`bit_count(bitmap)`)
|
||||||
|
.stored()
|
||||||
|
.notNull(),
|
||||||
|
)
|
||||||
.addColumn("expires_at", "timestamp(3)", (col) => col.notNull())
|
.addColumn("expires_at", "timestamp(3)", (col) => col.notNull())
|
||||||
.addColumn("parent_id", "integer", (col) => col.references("directory.id"))
|
.addColumn("parent_id", "integer", (col) => col.references("directory.id"))
|
||||||
.addColumn("master_encryption_key_version", "integer")
|
.addColumn("master_encryption_key_version", "integer")
|
||||||
@@ -46,6 +52,11 @@ export const up = async (db: Kysely<any>) => {
|
|||||||
"hmac_secret_key",
|
"hmac_secret_key",
|
||||||
["user_id", "version"],
|
["user_id", "version"],
|
||||||
)
|
)
|
||||||
|
.addCheckConstraint("upload_session_ck01", sql`uploaded_chunks <= total_chunks`)
|
||||||
|
.addCheckConstraint(
|
||||||
|
"upload_session_ck02",
|
||||||
|
sql`length(bitmap) = ceil(total_chunks / 8.0)::integer`,
|
||||||
|
)
|
||||||
.execute();
|
.execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ interface UploadSessionTable {
|
|||||||
type: "file" | "thumbnail" | "migration";
|
type: "file" | "thumbnail" | "migration";
|
||||||
user_id: number;
|
user_id: number;
|
||||||
path: string;
|
path: string;
|
||||||
|
bitmap: Buffer;
|
||||||
total_chunks: number;
|
total_chunks: number;
|
||||||
uploaded_chunks: Generated<number[]>;
|
uploaded_chunks: Generated<number>;
|
||||||
expires_at: Date;
|
expires_at: Date;
|
||||||
|
|
||||||
// For file uploads
|
// For file uploads
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ interface BaseUploadSession {
|
|||||||
id: string;
|
id: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
path: string;
|
path: string;
|
||||||
|
bitmap: Buffer;
|
||||||
totalChunks: number;
|
totalChunks: number;
|
||||||
uploadedChunks: number[];
|
uploadedChunks: number;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ interface MigrationUploadSession extends BaseUploadSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createFileUploadSession = async (
|
export const createFileUploadSession = async (
|
||||||
params: Omit<FileUploadSession, "type" | "uploadedChunks">,
|
params: Omit<FileUploadSession, "type" | "bitmap" | "uploadedChunks">,
|
||||||
) => {
|
) => {
|
||||||
await db.transaction().execute(async (trx) => {
|
await db.transaction().execute(async (trx) => {
|
||||||
const mek = await trx
|
const mek = await trx
|
||||||
@@ -73,6 +74,7 @@ export const createFileUploadSession = async (
|
|||||||
type: "file",
|
type: "file",
|
||||||
user_id: params.userId,
|
user_id: params.userId,
|
||||||
path: params.path,
|
path: params.path,
|
||||||
|
bitmap: Buffer.alloc(Math.ceil(params.totalChunks / 8)),
|
||||||
total_chunks: params.totalChunks,
|
total_chunks: params.totalChunks,
|
||||||
expires_at: params.expiresAt,
|
expires_at: params.expiresAt,
|
||||||
parent_id: params.parentId !== "root" ? params.parentId : null,
|
parent_id: params.parentId !== "root" ? params.parentId : null,
|
||||||
@@ -90,7 +92,7 @@ export const createFileUploadSession = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createThumbnailUploadSession = async (
|
export const createThumbnailUploadSession = async (
|
||||||
params: Omit<ThumbnailUploadSession, "type" | "uploadedChunks">,
|
params: Omit<ThumbnailUploadSession, "type" | "bitmap" | "uploadedChunks">,
|
||||||
) => {
|
) => {
|
||||||
await db.transaction().execute(async (trx) => {
|
await db.transaction().execute(async (trx) => {
|
||||||
const file = await trx
|
const file = await trx
|
||||||
@@ -114,6 +116,7 @@ export const createThumbnailUploadSession = async (
|
|||||||
type: "thumbnail",
|
type: "thumbnail",
|
||||||
user_id: params.userId,
|
user_id: params.userId,
|
||||||
path: params.path,
|
path: params.path,
|
||||||
|
bitmap: Buffer.alloc(Math.ceil(params.totalChunks / 8)),
|
||||||
total_chunks: params.totalChunks,
|
total_chunks: params.totalChunks,
|
||||||
expires_at: params.expiresAt,
|
expires_at: params.expiresAt,
|
||||||
file_id: params.fileId,
|
file_id: params.fileId,
|
||||||
@@ -124,7 +127,7 @@ export const createThumbnailUploadSession = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createMigrationUploadSession = async (
|
export const createMigrationUploadSession = async (
|
||||||
params: Omit<MigrationUploadSession, "type" | "uploadedChunks">,
|
params: Omit<MigrationUploadSession, "type" | "bitmap" | "uploadedChunks">,
|
||||||
) => {
|
) => {
|
||||||
await db.transaction().execute(async (trx) => {
|
await db.transaction().execute(async (trx) => {
|
||||||
const file = await trx
|
const file = await trx
|
||||||
@@ -148,6 +151,7 @@ export const createMigrationUploadSession = async (
|
|||||||
type: "migration",
|
type: "migration",
|
||||||
user_id: params.userId,
|
user_id: params.userId,
|
||||||
path: params.path,
|
path: params.path,
|
||||||
|
bitmap: Buffer.alloc(Math.ceil(params.totalChunks / 8)),
|
||||||
total_chunks: params.totalChunks,
|
total_chunks: params.totalChunks,
|
||||||
expires_at: params.expiresAt,
|
expires_at: params.expiresAt,
|
||||||
file_id: params.fileId,
|
file_id: params.fileId,
|
||||||
@@ -173,6 +177,7 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
|
|||||||
id: session.id,
|
id: session.id,
|
||||||
userId: session.user_id,
|
userId: session.user_id,
|
||||||
path: session.path,
|
path: session.path,
|
||||||
|
bitmap: session.bitmap,
|
||||||
totalChunks: session.total_chunks,
|
totalChunks: session.total_chunks,
|
||||||
uploadedChunks: session.uploaded_chunks,
|
uploadedChunks: session.uploaded_chunks,
|
||||||
expiresAt: session.expires_at,
|
expiresAt: session.expires_at,
|
||||||
@@ -192,6 +197,7 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
|
|||||||
id: session.id,
|
id: session.id,
|
||||||
userId: session.user_id,
|
userId: session.user_id,
|
||||||
path: session.path,
|
path: session.path,
|
||||||
|
bitmap: session.bitmap,
|
||||||
totalChunks: session.total_chunks,
|
totalChunks: session.total_chunks,
|
||||||
uploadedChunks: session.uploaded_chunks,
|
uploadedChunks: session.uploaded_chunks,
|
||||||
expiresAt: session.expires_at,
|
expiresAt: session.expires_at,
|
||||||
@@ -204,6 +210,7 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
|
|||||||
id: session.id,
|
id: session.id,
|
||||||
userId: session.user_id,
|
userId: session.user_id,
|
||||||
path: session.path,
|
path: session.path,
|
||||||
|
bitmap: session.bitmap,
|
||||||
totalChunks: session.total_chunks,
|
totalChunks: session.total_chunks,
|
||||||
uploadedChunks: session.uploaded_chunks,
|
uploadedChunks: session.uploaded_chunks,
|
||||||
expiresAt: session.expires_at,
|
expiresAt: session.expires_at,
|
||||||
@@ -215,7 +222,9 @@ export const getUploadSession = async (sessionId: string, userId: number) => {
|
|||||||
export const markChunkAsUploaded = async (sessionId: string, chunkIndex: number) => {
|
export const markChunkAsUploaded = async (sessionId: string, chunkIndex: number) => {
|
||||||
await db
|
await db
|
||||||
.updateTable("upload_session")
|
.updateTable("upload_session")
|
||||||
.set({ uploaded_chunks: sql`array_append(uploaded_chunks, ${chunkIndex})` })
|
.set({
|
||||||
|
bitmap: sql`set_bit(${sql.ref("bitmap")}, ${chunkIndex - 1}, 1)`,
|
||||||
|
})
|
||||||
.where("id", "=", sessionId)
|
.where("id", "=", sessionId)
|
||||||
.execute();
|
.execute();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ import { safeRecursiveRm, safeUnlink } from "$lib/server/modules/filesystem";
|
|||||||
|
|
||||||
const chunkLocks = new Set<string>();
|
const chunkLocks = new Set<string>();
|
||||||
|
|
||||||
|
const isChunkUploaded = (bitmap: Buffer, chunkIndex: number) => {
|
||||||
|
chunkIndex -= 1;
|
||||||
|
const byte = bitmap[Math.floor(chunkIndex / 8)];
|
||||||
|
return !!byte && (byte & (1 << (chunkIndex % 8))) !== 0; // Postgres sucks
|
||||||
|
};
|
||||||
|
|
||||||
export const uploadChunk = async (
|
export const uploadChunk = async (
|
||||||
userId: number,
|
userId: number,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
@@ -28,13 +34,13 @@ export const uploadChunk = async (
|
|||||||
const session = await UploadRepo.getUploadSession(sessionId, userId);
|
const session = await UploadRepo.getUploadSession(sessionId, userId);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
error(404, "Invalid upload id");
|
error(404, "Invalid upload id");
|
||||||
} else if (chunkIndex >= session.totalChunks) {
|
} else if (chunkIndex > session.totalChunks) {
|
||||||
error(400, "Invalid chunk index");
|
error(400, "Invalid chunk index");
|
||||||
} else if (session.uploadedChunks.includes(chunkIndex)) {
|
} else if (isChunkUploaded(session.bitmap, chunkIndex)) {
|
||||||
error(409, "Chunk already uploaded");
|
error(409, "Chunk already uploaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLastChunk = chunkIndex === session.totalChunks - 1;
|
const isLastChunk = chunkIndex === session.totalChunks;
|
||||||
filePath = `${session.path}/${chunkIndex}`;
|
filePath = `${session.path}/${chunkIndex}`;
|
||||||
|
|
||||||
const hashStream = createHash("sha256");
|
const hashStream = createHash("sha256");
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getAllFileInfos } from "$lib/indexedDB/filesystem";
|
import { getAllFileInfos } from "$lib/indexedDB/filesystem";
|
||||||
import { encodeToBase64, digestMessage } from "$lib/modules/crypto";
|
|
||||||
import {
|
import {
|
||||||
getFileCache,
|
getFileCache,
|
||||||
storeFileCache,
|
storeFileCache,
|
||||||
@@ -7,6 +6,7 @@ import {
|
|||||||
downloadFile,
|
downloadFile,
|
||||||
deleteFileThumbnailCache,
|
deleteFileThumbnailCache,
|
||||||
} from "$lib/modules/file";
|
} from "$lib/modules/file";
|
||||||
|
import { uploadBlob } from "$lib/modules/upload";
|
||||||
import { trpc } from "$trpc/client";
|
import { trpc } from "$trpc/client";
|
||||||
|
|
||||||
export const requestFileDownload = async (
|
export const requestFileDownload = async (
|
||||||
@@ -24,41 +24,24 @@ export const requestFileDownload = async (
|
|||||||
|
|
||||||
export const requestFileThumbnailUpload = async (
|
export const requestFileThumbnailUpload = async (
|
||||||
fileId: number,
|
fileId: number,
|
||||||
|
thumbnail: Blob,
|
||||||
|
dataKey: CryptoKey,
|
||||||
dataKeyVersion: Date,
|
dataKeyVersion: Date,
|
||||||
thumbnailEncrypted: { ciphertext: ArrayBuffer; iv: ArrayBuffer },
|
|
||||||
) => {
|
) => {
|
||||||
|
try {
|
||||||
const { uploadId } = await trpc().upload.startFileThumbnailUpload.mutate({
|
const { uploadId } = await trpc().upload.startFileThumbnailUpload.mutate({
|
||||||
file: fileId,
|
file: fileId,
|
||||||
dekVersion: dataKeyVersion,
|
dekVersion: dataKeyVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prepend IV to ciphertext (consistent with file download format)
|
await uploadBlob(uploadId, thumbnail, dataKey);
|
||||||
const ivAndCiphertext = new Uint8Array(
|
|
||||||
thumbnailEncrypted.iv.byteLength + thumbnailEncrypted.ciphertext.byteLength,
|
|
||||||
);
|
|
||||||
ivAndCiphertext.set(new Uint8Array(thumbnailEncrypted.iv), 0);
|
|
||||||
ivAndCiphertext.set(
|
|
||||||
new Uint8Array(thumbnailEncrypted.ciphertext),
|
|
||||||
thumbnailEncrypted.iv.byteLength,
|
|
||||||
);
|
|
||||||
|
|
||||||
const chunkHash = encodeToBase64(await digestMessage(ivAndCiphertext));
|
|
||||||
|
|
||||||
const response = await fetch(`/api/upload/${uploadId}/chunks/0`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
"Content-Digest": `sha-256=:${chunkHash}:`,
|
|
||||||
},
|
|
||||||
body: ivAndCiphertext,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Thumbnail upload failed: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await trpc().upload.completeFileThumbnailUpload.mutate({ uploadId });
|
await trpc().upload.completeFileThumbnailUpload.mutate({ uploadId });
|
||||||
return response;
|
return true;
|
||||||
|
} catch {
|
||||||
|
// TODO: Error Handling
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requestDeletedFilesCleanup = async () => {
|
export const requestDeletedFilesCleanup = async () => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { encryptData } from "$lib/modules/crypto";
|
|
||||||
import { storeFileThumbnailCache } from "$lib/modules/file";
|
import { storeFileThumbnailCache } from "$lib/modules/file";
|
||||||
import { prepareFileDecryption, getDecryptedFileUrl } from "$lib/serviceWorker";
|
import { prepareFileDecryption, getDecryptedFileUrl } from "$lib/serviceWorker";
|
||||||
import { requestFileThumbnailUpload } from "$lib/services/file";
|
import { requestFileThumbnailUpload } from "$lib/services/file";
|
||||||
@@ -33,12 +32,10 @@ export const requestThumbnailUpload = async (
|
|||||||
dataKey: CryptoKey,
|
dataKey: CryptoKey,
|
||||||
dataKeyVersion: Date,
|
dataKeyVersion: Date,
|
||||||
) => {
|
) => {
|
||||||
const thumbnailBuffer = await thumbnail.arrayBuffer();
|
const res = await requestFileThumbnailUpload(fileId, thumbnail, dataKey, dataKeyVersion);
|
||||||
const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey);
|
if (!res) return false;
|
||||||
const res = await requestFileThumbnailUpload(fileId, dataKeyVersion, thumbnailEncrypted);
|
|
||||||
if (!res.ok) return false;
|
|
||||||
|
|
||||||
storeFileThumbnailCache(fileId, thumbnailBuffer); // Intended
|
void thumbnail.arrayBuffer().then((buffer) => storeFileThumbnailCache(fileId, buffer));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { limitFunction } from "p-limit";
|
import { limitFunction } from "p-limit";
|
||||||
import { SvelteMap } from "svelte/reactivity";
|
import { SvelteMap } from "svelte/reactivity";
|
||||||
import { encryptData } from "$lib/modules/crypto";
|
|
||||||
import { storeFileThumbnailCache } from "$lib/modules/file";
|
import { storeFileThumbnailCache } from "$lib/modules/file";
|
||||||
import type { FileInfo } from "$lib/modules/filesystem";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { Scheduler } from "$lib/modules/scheduler";
|
import { Scheduler } from "$lib/modules/scheduler";
|
||||||
import { generateThumbnail as doGenerateThumbnail } from "$lib/modules/thumbnail";
|
import { generateThumbnail } from "$lib/modules/thumbnail";
|
||||||
import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file";
|
import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file";
|
||||||
|
|
||||||
export type GenerationStatus =
|
export type GenerationStatus =
|
||||||
@@ -31,33 +30,25 @@ export const clearThumbnailGenerationStatuses = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateThumbnail = limitFunction(
|
|
||||||
async (fileId: number, fileBuffer: ArrayBuffer, fileType: string, dataKey: CryptoKey) => {
|
|
||||||
statuses.set(fileId, "generating");
|
|
||||||
|
|
||||||
const thumbnail = await doGenerateThumbnail(new Blob([fileBuffer], { type: fileType }));
|
|
||||||
if (!thumbnail) return null;
|
|
||||||
|
|
||||||
const thumbnailBuffer = await thumbnail.arrayBuffer();
|
|
||||||
const thumbnailEncrypted = await encryptData(thumbnailBuffer, dataKey);
|
|
||||||
statuses.set(fileId, "upload-pending");
|
|
||||||
return { plaintext: thumbnailBuffer, ...thumbnailEncrypted };
|
|
||||||
},
|
|
||||||
{ concurrency: 4 },
|
|
||||||
);
|
|
||||||
|
|
||||||
const requestThumbnailUpload = limitFunction(
|
const requestThumbnailUpload = limitFunction(
|
||||||
async (
|
async (fileInfo: FileInfo, fileBuffer: ArrayBuffer) => {
|
||||||
fileId: number,
|
statuses.set(fileInfo.id, "generating");
|
||||||
dataKeyVersion: Date,
|
|
||||||
thumbnail: { plaintext: ArrayBuffer; ciphertext: ArrayBuffer; iv: ArrayBuffer },
|
|
||||||
) => {
|
|
||||||
statuses.set(fileId, "uploading");
|
|
||||||
|
|
||||||
const res = await requestFileThumbnailUpload(fileId, dataKeyVersion, thumbnail);
|
const thumbnail = await generateThumbnail(
|
||||||
if (!res.ok) return false;
|
new Blob([fileBuffer], { type: fileInfo.contentType }),
|
||||||
statuses.set(fileId, "uploaded");
|
);
|
||||||
storeFileThumbnailCache(fileId, thumbnail.plaintext); // Intended
|
if (!thumbnail) return false;
|
||||||
|
|
||||||
|
const res = await requestFileThumbnailUpload(
|
||||||
|
fileInfo.id,
|
||||||
|
thumbnail,
|
||||||
|
fileInfo.dataKey?.key!,
|
||||||
|
fileInfo.dataKey?.version!,
|
||||||
|
);
|
||||||
|
if (!res) return false;
|
||||||
|
|
||||||
|
statuses.set(fileInfo.id, "uploaded");
|
||||||
|
void thumbnail.arrayBuffer().then((buffer) => storeFileThumbnailCache(fileInfo.id, buffer));
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
{ concurrency: 4 },
|
{ concurrency: 4 },
|
||||||
@@ -81,16 +72,7 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => {
|
|||||||
return file.byteLength;
|
return file.byteLength;
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const thumbnail = await generateThumbnail(
|
if (!(await requestThumbnailUpload(fileInfo, file!))) {
|
||||||
fileInfo.id,
|
|
||||||
file!,
|
|
||||||
fileInfo.contentType,
|
|
||||||
fileInfo.dataKey?.key!,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
!thumbnail ||
|
|
||||||
!(await requestThumbnailUpload(fileInfo.id, fileInfo.dataKey?.version!, thumbnail))
|
|
||||||
) {
|
|
||||||
statuses.set(fileInfo.id, "error");
|
statuses.set(fileInfo.id, "error");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const POST: RequestHandler = async ({ locals, params, request }) => {
|
|||||||
const zodRes = z
|
const zodRes = z
|
||||||
.object({
|
.object({
|
||||||
id: z.uuidv4(),
|
id: z.uuidv4(),
|
||||||
index: z.coerce.number().int().nonnegative(),
|
index: z.coerce.number().int().positive(),
|
||||||
})
|
})
|
||||||
.safeParse(params);
|
.safeParse(params);
|
||||||
if (!zodRes.success) error(400, "Invalid path parameters");
|
if (!zodRes.success) error(400, "Invalid path parameters");
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ const uploadRouter = router({
|
|||||||
(!session.hskVersion && input.contentHmac)
|
(!session.hskVersion && input.contentHmac)
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid content HMAC" });
|
throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid content HMAC" });
|
||||||
} else if (session.uploadedChunks.length < session.totalChunks) {
|
} else if (session.uploadedChunks < session.totalChunks) {
|
||||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
|
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ const uploadRouter = router({
|
|||||||
const hashStream = createHash("sha256");
|
const hashStream = createHash("sha256");
|
||||||
const writeStream = createWriteStream(filePath, { flags: "wx", mode: 0o600 });
|
const writeStream = createWriteStream(filePath, { flags: "wx", mode: 0o600 });
|
||||||
|
|
||||||
for (let i = 0; i < session.totalChunks; i++) {
|
for (let i = 1; i <= session.totalChunks; i++) {
|
||||||
for await (const chunk of createReadStream(`${session.path}/${i}`)) {
|
for await (const chunk of createReadStream(`${session.path}/${i}`)) {
|
||||||
hashStream.update(chunk);
|
hashStream.update(chunk);
|
||||||
writeStream.write(chunk);
|
writeStream.write(chunk);
|
||||||
@@ -215,13 +215,13 @@ const uploadRouter = router({
|
|||||||
const session = await UploadRepo.getUploadSession(uploadId, ctx.session.userId);
|
const session = await UploadRepo.getUploadSession(uploadId, ctx.session.userId);
|
||||||
if (!session || session.type !== "thumbnail") {
|
if (!session || session.type !== "thumbnail") {
|
||||||
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid upload id" });
|
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid upload id" });
|
||||||
} else if (session.uploadedChunks.length < session.totalChunks) {
|
} else if (session.uploadedChunks < session.totalChunks) {
|
||||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
|
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailPath = `${env.thumbnailsPath}/${ctx.session.userId}/${uploadId}`;
|
thumbnailPath = `${env.thumbnailsPath}/${ctx.session.userId}/${uploadId}`;
|
||||||
await mkdir(dirname(thumbnailPath), { recursive: true });
|
await mkdir(dirname(thumbnailPath), { recursive: true });
|
||||||
await rename(`${session.path}/0`, thumbnailPath);
|
await rename(`${session.path}/1`, thumbnailPath);
|
||||||
|
|
||||||
const oldThumbnailPath = await db.transaction().execute(async (trx) => {
|
const oldThumbnailPath = await db.transaction().execute(async (trx) => {
|
||||||
const oldPath = await MediaRepo.updateFileThumbnail(
|
const oldPath = await MediaRepo.updateFileThumbnail(
|
||||||
@@ -305,7 +305,7 @@ const uploadRouter = router({
|
|||||||
const session = await UploadRepo.getUploadSession(uploadId, ctx.session.userId);
|
const session = await UploadRepo.getUploadSession(uploadId, ctx.session.userId);
|
||||||
if (!session || session.type !== "migration") {
|
if (!session || session.type !== "migration") {
|
||||||
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid upload id" });
|
throw new TRPCError({ code: "NOT_FOUND", message: "Invalid upload id" });
|
||||||
} else if (session.uploadedChunks.length < session.totalChunks) {
|
} else if (session.uploadedChunks < session.totalChunks) {
|
||||||
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
|
throw new TRPCError({ code: "BAD_REQUEST", message: "Upload not completed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ const uploadRouter = router({
|
|||||||
const hashStream = createHash("sha256");
|
const hashStream = createHash("sha256");
|
||||||
const writeStream = createWriteStream(filePath, { flags: "wx", mode: 0o600 });
|
const writeStream = createWriteStream(filePath, { flags: "wx", mode: 0o600 });
|
||||||
|
|
||||||
for (let i = 0; i < session.totalChunks; i++) {
|
for (let i = 1; i <= session.totalChunks; i++) {
|
||||||
for await (const chunk of createReadStream(`${session.path}/${i}`)) {
|
for await (const chunk of createReadStream(`${session.path}/${i}`)) {
|
||||||
hashStream.update(chunk);
|
hashStream.update(chunk);
|
||||||
writeStream.write(chunk);
|
writeStream.write(chunk);
|
||||||
|
|||||||
Reference in New Issue
Block a user