diff --git a/public/index.html b/public/index.html index 3468742..513c1d1 100644 --- a/public/index.html +++ b/public/index.html @@ -1090,71 +1090,48 @@ async function startUpload() { } // ============================================================ -// PARALLEL CHUNK UPLOAD (Option 4 — HTTP parallelism, Aspera-class speed) -// Slices each file into 32 MB chunks, uploads 6 concurrently via POST, -// server proxies to S3 multipart. Same approach as MASV — no UDP needed. +// SIMPLE UPLOAD — uses /api/upload with @aws-sdk/lib-storage on server +// Compatible with RustFS / MinIO / generic S3 endpoints. +// XMLHttpRequest used for upload progress tracking. // ============================================================ -const CHUNK_SIZE = 32 * 1024 * 1024; // 32 MB per part -const CHUNK_WORKERS = 6; // concurrent chunk POSTs per file -async function uploadFileChunked(item, idx) { - const file = item.file; - const totalParts = Math.ceil(file.size / CHUNK_SIZE); - const mime = file.type || 'application/octet-stream'; +async function uploadFileDirect(item, idx) { + return new Promise((resolve, reject) => { + const fd = new FormData(); + fd.append('prefix', selectedPrefix); + fd.append('files', item.file, item.name); - // 1. Initiate S3 multipart upload - const init = await api('POST','/api/upload/initiate',{ - filename: item.name, prefix: selectedPrefix, - contentType: mime, totalParts, + const xhr = new XMLHttpRequest(); + const pb = document.getElementById(`progbar-${idx}`); + + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable) { + const pct = Math.round(e.loaded / e.total * 100); + if (pb) pb.style.width = pct + '%'; + setFileStatus(idx, 'uploading', pct + '%'); + } + }); + + xhr.addEventListener('load', () => { + try { + const data = JSON.parse(xhr.responseText); + if (xhr.status >= 200 && xhr.status < 300 && data.success) { + resolve(data); + } else { + reject(new Error(data.error || `Server returned ${xhr.status}`)); + } + } catch (e) { + reject(new Error(`Bad response: ${xhr.status}`)); + } + }); + + xhr.addEventListener('error', () => reject(new Error('Network error'))); + xhr.addEventListener('abort', () => reject(new Error('Upload aborted'))); + + xhr.open('POST', '/api/upload'); + xhr.setRequestHeader('x-auth-token', authToken); + xhr.send(fd); }); - if (!init.success) throw new Error(init.error || 'Failed to initiate upload'); - const { uploadId } = init; - - const pb = document.getElementById(`progbar-${idx}`); - let done = 0; - - // 2. Upload all parts with bounded concurrency - const queue = Array.from({length: totalParts}, (_,i) => i+1); // 1-indexed - - async function worker() { - while (queue.length) { - const partNumber = queue.shift(); - if (partNumber === undefined) break; - const start = (partNumber - 1) * CHUNK_SIZE; - const blob = file.slice(start, Math.min(start + CHUNK_SIZE, file.size)); - const fd = new FormData(); - fd.append('uploadId', uploadId); - fd.append('partNumber', String(partNumber)); - fd.append('chunk', blob, item.name); - const resp = await fetch('/api/upload/chunk', { - method: 'POST', - headers: { 'x-auth-token': authToken }, - body: fd, - }); - const data = await resp.json(); - if (!data.success) throw new Error(`Part ${partNumber} failed: ${data.error}`); - done++; - const pct = Math.round(done / totalParts * 100); - if (pb) pb.style.width = pct + '%'; - setFileStatus(idx, 'uploading', pct + '%'); - } - } - - try { - await Promise.all(Array.from({length: Math.min(CHUNK_WORKERS, totalParts)}, worker)); - } catch (err) { - // Abort orphaned multipart upload on any chunk failure - fetch('/api/upload/abort', { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'x-auth-token': authToken }, - body: JSON.stringify({ uploadId }), - }).catch(() => {}); - throw err; - } - - // 3. Complete the multipart upload - const complete = await api('POST','/api/upload/complete',{ uploadId }); - if (!complete.success) throw new Error(complete.error || 'Failed to complete upload'); } async function uploadHTTP(files) { @@ -1170,7 +1147,7 @@ async function uploadHTTP(files) { setFileStatus(idx,'uploading','Uploading…'); document.getElementById(`prog-${idx}`).style.display='block'; try { - await uploadFileChunked(item, idx); + await uploadFileDirect(item, idx); document.getElementById(`progbar-${idx}`).style.width='100%'; setFileStatus(idx,'done','✓ Done'); item.status='done'; showToast(`Uploaded: ${item.name}`,'success');