Implement chunked ZIP upload for all upload interfaces

- Add chunking JavaScript to fixture_detail.html for individual match uploads
- Add chunking JavaScript to fixture_detail.html for fixture-level bulk uploads
- Add chunking JavaScript to match_detail.html for match ZIP uploads
- All ZIP uploads now use 1MB chunks instead of uploading entire files at once
- Fixes issue where large ZIP files would fail due to memory/timeouts
parent a959b49b
......@@ -528,78 +528,114 @@
</div>
<script>
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
let activeUploads = new Map();
function startUpload(matchId) {
const fileInput = document.getElementById(`zip_file_${matchId}`);
const file = fileInput.files[0];
if (!file) {
return;
}
function uploadFileInChunks(file, matchId) {
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
const uploadId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
let currentChunk = 0;
// Show progress bar and hide upload form
const uploadForm = document.getElementById(`upload_form_${matchId}`);
const progressContainer = document.getElementById(`progress_${matchId}`);
const statusDiv = document.getElementById(`status_${matchId}`);
uploadForm.style.display = 'none';
progressContainer.style.display = 'block';
statusDiv.style.display = 'block';
statusDiv.className = 'upload-status uploading';
statusDiv.textContent = 'Uploading...';
// Create FormData
const formData = new FormData();
formData.append('zip_file', file);
formData.append('match_id', matchId);
// Create XMLHttpRequest for progress tracking
const xhr = new XMLHttpRequest();
activeUploads.set(matchId, xhr);
// Progress event handler
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = Math.round((e.loaded / e.total) * 100);
updateProgress(matchId, percentComplete);
statusDiv.textContent = 'Uploading... 0%';
function uploadChunk() {
if (currentChunk >= totalChunks) {
// All chunks uploaded, finalize
finalizeUpload(uploadId, file.name, matchId);
return;
}
});
// Load event handler (upload complete)
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
// Success
const start = currentChunk * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', currentChunk);
formData.append('totalChunks', totalChunks);
formData.append('uploadId', uploadId);
formData.append('fileName', file.name);
formData.append('matchId', matchId);
fetch('/upload/chunk', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentChunk++;
const progress = (currentChunk / totalChunks) * 100;
updateProgress(matchId, Math.round(progress));
statusDiv.textContent = `Uploading... ${Math.round(progress)}%`;
uploadChunk();
} else {
throw new Error(data.error || 'Upload failed');
}
})
.catch(error => {
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Upload failed: ' + error.message;
});
}
uploadChunk();
}
function finalizeUpload(uploadId, fileName, matchId) {
const statusDiv = document.getElementById(`status_${matchId}`);
fetch('/upload/finalize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
uploadId: uploadId,
fileName: fileName,
matchId: matchId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateProgress(matchId, 100);
statusDiv.className = 'upload-status success';
statusDiv.textContent = 'Upload successful!';
// Reload page after a short delay to show updated status
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
// Error
handleUploadError(matchId, 'Upload failed');
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Finalization failed: ' + (data.error || 'Unknown error');
}
activeUploads.delete(matchId);
})
.catch(error => {
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Finalization failed: ' + error.message;
});
}
// Error event handler
xhr.addEventListener('error', function() {
handleUploadError(matchId, 'Network error');
activeUploads.delete(matchId);
});
function startUpload(matchId) {
const fileInput = document.getElementById(`zip_file_${matchId}`);
const file = fileInput.files[0];
// Abort event handler
xhr.addEventListener('abort', function() {
handleUploadError(matchId, 'Upload cancelled');
activeUploads.delete(matchId);
});
if (!file) {
return;
}
// Start upload
xhr.open('POST', '{{ url_for("upload.upload_zip") }}', true);
xhr.send(formData);
uploadFileInChunks(file, matchId);
}
function updateProgress(matchId, percent) {
......@@ -651,80 +687,117 @@
// Fixture-level upload functionality
let fixtureUploadActive = false;
function startFixtureUpload() {
const fileInput = document.getElementById('fixture_zip_file');
const file = fileInput.files[0];
if (!file) {
return;
}
function uploadFixtureFileInChunks(file) {
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
const uploadId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
if (fixtureUploadActive) {
return; // Prevent multiple uploads
}
fixtureUploadActive = true;
let currentChunk = 0;
// Show progress bar and hide upload form
const uploadForm = document.getElementById('fixture_upload_form');
const progressContainer = document.getElementById('fixture_progress_container');
const statusDiv = document.getElementById('fixture_upload_status');
uploadForm.style.display = 'none';
progressContainer.style.display = 'block';
statusDiv.style.display = 'block';
statusDiv.className = 'upload-status uploading';
statusDiv.textContent = 'Uploading to all matches...';
statusDiv.textContent = 'Uploading to all matches... 0%';
function uploadChunk() {
if (currentChunk >= totalChunks) {
// All chunks uploaded, finalize
finalizeFixtureUpload(uploadId, file.name);
return;
}
// Create FormData
const formData = new FormData();
formData.append('zip_file', file);
const start = currentChunk * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', currentChunk);
formData.append('totalChunks', totalChunks);
formData.append('uploadId', uploadId);
formData.append('fileName', file.name);
fetch('/upload/chunk', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentChunk++;
const progress = (currentChunk / totalChunks) * 100;
updateFixtureProgress(Math.round(progress));
statusDiv.textContent = `Uploading to all matches... ${Math.round(progress)}%`;
uploadChunk();
} else {
throw new Error(data.error || 'Upload failed');
}
})
.catch(error => {
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Upload failed: ' + error.message;
fixtureUploadActive = false;
});
}
// Create XMLHttpRequest for progress tracking
const xhr = new XMLHttpRequest();
uploadChunk();
}
// Progress event handler
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = Math.round((e.loaded / e.total) * 100);
updateFixtureProgress(percentComplete);
}
});
function finalizeFixtureUpload(uploadId, fileName) {
const statusDiv = document.getElementById('fixture_upload_status');
// Load event handler (upload complete)
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
// Success
fetch('/upload/finalize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
uploadId: uploadId,
fileName: fileName
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateFixtureProgress(100);
statusDiv.className = 'upload-status success';
statusDiv.textContent = 'Upload successful! All matches are now active.';
// Reload page after a short delay to show updated status
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
// Error
handleFixtureUploadError('Upload failed');
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Finalization failed: ' + (data.error || 'Unknown error');
}
fixtureUploadActive = false;
});
// Error event handler
xhr.addEventListener('error', function() {
handleFixtureUploadError('Network error');
})
.catch(error => {
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Finalization failed: ' + error.message;
fixtureUploadActive = false;
});
}
// Abort event handler
xhr.addEventListener('abort', function() {
handleFixtureUploadError('Upload cancelled');
fixtureUploadActive = false;
});
function startFixtureUpload() {
const fileInput = document.getElementById('fixture_zip_file');
const file = fileInput.files[0];
// Start upload
xhr.open('POST', uploadForm.action, true);
xhr.send(formData);
if (!file) {
return;
}
if (fixtureUploadActive) {
return; // Prevent multiple uploads
}
fixtureUploadActive = true;
uploadFixtureFileInChunks(file);
}
function updateFixtureProgress(percent) {
......
......@@ -761,76 +761,113 @@
}
// ZIP Upload Functions
function startUpload(matchId) {
const fileInput = document.getElementById(`zip_file_${matchId}`);
const file = fileInput.files[0];
if (!file) {
return;
}
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
function uploadFileInChunks(file, matchId) {
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
const uploadId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
let currentChunk = 0;
// Show progress bar and hide upload form
const uploadForm = document.getElementById(`upload_form_${matchId}`);
const progressContainer = document.getElementById(`progress_${matchId}`);
const statusDiv = document.getElementById(`status_${matchId}`);
uploadForm.style.display = 'none';
progressContainer.style.display = 'block';
statusDiv.style.display = 'block';
statusDiv.className = 'upload-status uploading';
statusDiv.textContent = 'Uploading...';
statusDiv.textContent = 'Uploading... 0%';
// Create FormData
const formData = new FormData();
formData.append('zip_file', file);
formData.append('match_id', matchId);
function uploadChunk() {
if (currentChunk >= totalChunks) {
// All chunks uploaded, finalize
finalizeUpload(uploadId, file.name, matchId);
return;
}
// Create XMLHttpRequest for progress tracking
const xhr = new XMLHttpRequest();
activeUploads.set(matchId, xhr);
const start = currentChunk * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
// Progress event handler
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
const percentComplete = Math.round((e.loaded / e.total) * 100);
updateProgress(matchId, percentComplete);
}
});
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', currentChunk);
formData.append('totalChunks', totalChunks);
formData.append('uploadId', uploadId);
formData.append('fileName', file.name);
formData.append('matchId', matchId);
fetch('/upload/chunk', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
currentChunk++;
const progress = (currentChunk / totalChunks) * 100;
updateProgress(matchId, Math.round(progress));
statusDiv.textContent = `Uploading... ${Math.round(progress)}%`;
uploadChunk();
} else {
throw new Error(data.error || 'Upload failed');
}
})
.catch(error => {
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Upload failed: ' + error.message;
});
}
// Load event handler (upload complete)
xhr.addEventListener('load', function() {
if (xhr.status === 200) {
// Success
uploadChunk();
}
function finalizeUpload(uploadId, fileName, matchId) {
const statusDiv = document.getElementById(`status_${matchId}`);
fetch('/upload/finalize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
uploadId: uploadId,
fileName: fileName,
matchId: matchId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateProgress(matchId, 100);
statusDiv.className = 'upload-status success';
statusDiv.textContent = 'Upload successful!';
// Reload page after a short delay to show updated status
setTimeout(() => {
window.location.reload();
}, 1500);
} else {
// Error
handleUploadError(matchId, 'Upload failed');
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Finalization failed: ' + (data.error || 'Unknown error');
}
activeUploads.delete(matchId);
})
.catch(error => {
statusDiv.className = 'upload-status error';
statusDiv.textContent = 'Finalization failed: ' + error.message;
});
}
// Error event handler
xhr.addEventListener('error', function() {
handleUploadError(matchId, 'Network error');
activeUploads.delete(matchId);
});
function startUpload(matchId) {
const fileInput = document.getElementById(`zip_file_${matchId}`);
const file = fileInput.files[0];
// Abort event handler
xhr.addEventListener('abort', function() {
handleUploadError(matchId, 'Upload cancelled');
activeUploads.delete(matchId);
});
if (!file) {
return;
}
// Start upload
xhr.open('POST', '{{ url_for("upload.upload_zip") }}', true);
xhr.send(formData);
uploadFileInChunks(file, matchId);
}
function updateProgress(matchId, percent) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment