mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-14 18:38:11 +02:00
Add chunked upload for large files
This commit is contained in:
1 parent
da4e02472f
commit
24a6d83ea0
5 files changed
+212
-19
No files matched your search
@@ -175,6 +175,63 @@ export async function UploadFiles(RootName, RelativePath, Files) {
|
||||
return ParseJsonOrThrow(Response);
|
||||
}
|
||||
|
||||
const UploadChunkSizeBytes = 20 * 1024 * 1024;
|
||||
export const ChunkedUploadThresholdBytes = 40 * 1024 * 1024;
|
||||
|
||||
export function IsLargeUpload(File) {
|
||||
return File.size > ChunkedUploadThresholdBytes;
|
||||
}
|
||||
|
||||
function GenerateUploadId() {
|
||||
if (window.crypto && window.crypto.randomUUID) {
|
||||
return window.crypto.randomUUID();
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
async function UploadFileChunk(RootName, RelativePath, UploadId, ChunkIndex, TotalChunks, FileName, ChunkBlob) {
|
||||
const Form = new FormData();
|
||||
Form.append("Chunk", ChunkBlob, "chunk");
|
||||
const Params = new URLSearchParams({
|
||||
Root: RootName,
|
||||
Path: RelativePath,
|
||||
UploadId,
|
||||
ChunkIndex: String(ChunkIndex),
|
||||
TotalChunks: String(TotalChunks),
|
||||
FileName,
|
||||
});
|
||||
const Response = await fetch(`${ApiBase}/upload/chunk?${Params}`, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: Form,
|
||||
});
|
||||
return ParseJsonOrThrow(Response);
|
||||
}
|
||||
|
||||
function CompleteChunkedUpload(RootName, RelativePath, UploadId, TotalChunks, FileName) {
|
||||
const Params = new URLSearchParams({
|
||||
Root: RootName,
|
||||
Path: RelativePath,
|
||||
UploadId,
|
||||
TotalChunks: String(TotalChunks),
|
||||
FileName,
|
||||
});
|
||||
return FetchJson(`/upload/complete?${Params}`, { method: "POST" });
|
||||
}
|
||||
|
||||
export async function UploadFileChunked(RootName, RelativePath, File) {
|
||||
const UploadId = GenerateUploadId();
|
||||
const TotalChunks = Math.max(1, Math.ceil(File.size / UploadChunkSizeBytes));
|
||||
|
||||
for (let ChunkIndex = 0; ChunkIndex < TotalChunks; ChunkIndex++) {
|
||||
const Start = ChunkIndex * UploadChunkSizeBytes;
|
||||
const ChunkBlob = File.slice(Start, Start + UploadChunkSizeBytes);
|
||||
await UploadFileChunk(RootName, RelativePath, UploadId, ChunkIndex, TotalChunks, File.name, ChunkBlob);
|
||||
}
|
||||
|
||||
return CompleteChunkedUpload(RootName, RelativePath, UploadId, TotalChunks, File.name);
|
||||
}
|
||||
|
||||
export function SaveFile(RootName, RelativePath, Content) {
|
||||
const Params = new URLSearchParams({ Root: RootName, Path: RelativePath });
|
||||
return FetchJson(`/file?${Params}`, { method: "PUT", body: Content });
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
FetchLock,
|
||||
FetchTree,
|
||||
GetDownloadUrl,
|
||||
IsLargeUpload,
|
||||
MoveEntry,
|
||||
RenameEntry,
|
||||
SetLock as SetLockApi,
|
||||
UploadFileChunked,
|
||||
UploadFiles,
|
||||
} from "../Api.js";
|
||||
import { GetFileIcon, GetFolderIcon } from "../IconStyle.js";
|
||||
@@ -161,16 +163,36 @@ export default function DirectoryPane({
|
||||
async function HandleUpload(Files) {
|
||||
if (!Files || Files.length === 0) return;
|
||||
|
||||
try {
|
||||
const Result = await UploadFiles(RootName, DirectoryPath, Files);
|
||||
if (Result.Errors && Result.Errors.length > 0) {
|
||||
SetErrorMessage(Result.Errors.join("; "));
|
||||
const FileArray = Array.from(Files);
|
||||
const SmallFiles = FileArray.filter((File) => !IsLargeUpload(File));
|
||||
const LargeFiles = FileArray.filter((File) => IsLargeUpload(File));
|
||||
const Errors = [];
|
||||
|
||||
if (SmallFiles.length > 0) {
|
||||
try {
|
||||
const Result = await UploadFiles(RootName, DirectoryPath, SmallFiles);
|
||||
if (Result.Errors && Result.Errors.length > 0) {
|
||||
Errors.push(...Result.Errors);
|
||||
}
|
||||
} catch (Error) {
|
||||
Errors.push(Error.message);
|
||||
}
|
||||
await Reload();
|
||||
OnEntryCreated();
|
||||
} catch (Error) {
|
||||
SetErrorMessage(Error.message);
|
||||
}
|
||||
|
||||
for (const File of LargeFiles) {
|
||||
try {
|
||||
await UploadFileChunked(RootName, DirectoryPath, File);
|
||||
} catch (Error) {
|
||||
Errors.push(`${File.name}: ${Error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (Errors.length > 0) {
|
||||
SetErrorMessage(Errors.join("; "));
|
||||
}
|
||||
|
||||
await Reload();
|
||||
OnEntryCreated();
|
||||
}
|
||||
|
||||
function HandleUploadButtonClick() {
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
FetchTree,
|
||||
GetAvatarUrl,
|
||||
GetDownloadUrl,
|
||||
IsLargeUpload,
|
||||
MoveEntry,
|
||||
RenameEntry,
|
||||
SetLock as SetLockApi,
|
||||
UploadFileChunked,
|
||||
UploadFiles,
|
||||
} from "../Api.js";
|
||||
import { GetFileIcon, GetFolderIcon } from "../IconStyle.js";
|
||||
@@ -362,15 +364,35 @@ export default function Sidebar({
|
||||
async function HandleUpload(Files) {
|
||||
if (!Files || Files.length === 0) return;
|
||||
|
||||
try {
|
||||
const Result = await UploadFiles(RootName, CurrentDirectoryPath, Files);
|
||||
if (Result.Errors && Result.Errors.length > 0) {
|
||||
SetErrorMessage(Result.Errors.join("; "));
|
||||
const FileArray = Array.from(Files);
|
||||
const SmallFiles = FileArray.filter((File) => !IsLargeUpload(File));
|
||||
const LargeFiles = FileArray.filter((File) => IsLargeUpload(File));
|
||||
const Errors = [];
|
||||
|
||||
if (SmallFiles.length > 0) {
|
||||
try {
|
||||
const Result = await UploadFiles(RootName, CurrentDirectoryPath, SmallFiles);
|
||||
if (Result.Errors && Result.Errors.length > 0) {
|
||||
Errors.push(...Result.Errors);
|
||||
}
|
||||
} catch (Error) {
|
||||
Errors.push(Error.message);
|
||||
}
|
||||
OnEntryCreated();
|
||||
} catch (Error) {
|
||||
SetErrorMessage(Error.message);
|
||||
}
|
||||
|
||||
for (const File of LargeFiles) {
|
||||
try {
|
||||
await UploadFileChunked(RootName, CurrentDirectoryPath, File);
|
||||
} catch (Error) {
|
||||
Errors.push(`${File.name}: ${Error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (Errors.length > 0) {
|
||||
SetErrorMessage(Errors.join("; "));
|
||||
}
|
||||
|
||||
OnEntryCreated();
|
||||
}
|
||||
|
||||
function HandleUploadButtonClick() {
|
||||
|
||||
Reference in new issue
Block a user