Advanced file operations with progress tracking, queue management, and control
Apisto's Upload and Download Managers provide enterprise-grade file operations with queue management, progress tracking, pause/resume capabilities, and error recovery.
💡 Production Ready: These managers are designed for real-world applications with large files, unreliable networks, and complex user interactions.
Manage multiple file uploads with progress tracking, queue control, and sophisticated error handling.
// Basic upload manager usage
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
// Start upload with manager
const upload = await api.upload.upload('/files', file, {
description: 'Project document',
category: 'documents'
}, {
onProgress: (progress) => {
console.log(`Upload: ${progress.percentage.toFixed(1)}%`);
updateProgressBar(progress.percentage);
}
});
// Control the upload
// upload.pause(); // Pause upload
// upload.resume(); // Resume upload
// upload.cancel(); // Cancel upload
// upload.retry(); // Retry if failed
// Wait for completion
try {
const result = await upload.promise;
console.log('Upload completed:', result);
showSuccess('File uploaded successfully!');
} catch (error) {
if (error.code === 'UPLOAD_CANCELLED') {
console.log('Upload was cancelled by user');
} else {
console.error('Upload failed:', error);
showError('Upload failed: ' + error.message);
}
}
// Get upload status
const status = api.upload.getUploadStatus(upload.id);
console.log('Upload status:', status);
// Multiple files with individual progress
async function uploadMultipleFiles(files) {
const uploads = [];
for (const file of files) {
const upload = await api.upload.upload('/files', file, {
originalName: file.name,
uploadTime: new Date().toISOString()
}, {
onProgress: (progress) => {
// Update individual file progress
updateFileProgress(file.name, progress.percentage);
}
});
uploads.push({
file,
upload,
element: createUploadUI(file.name, upload)
});
}
// Monitor all uploads
const results = await Promise.allSettled(
uploads.map(u => u.upload.promise)
);
// Handle results
results.forEach((result, index) => {
const { file, element } = uploads[index];
if (result.status === 'fulfilled') {
markUploadComplete(element, file.name);
} else {
markUploadFailed(element, file.name, result.reason);
}
});
return results;
}
// Chunked upload for large files
const largeFileUpload = await api.upload.uploadChunked(
'/files/large',
largeFile,
{
chunkSize: 5 * 1024 * 1024, // 5MB chunks
maxChunkRetries: 3,
onProgress: (progress) => {
console.log(`Chunk ${progress.chunk}/${progress.totalChunks}: ${progress.percentage}%`);
}
}
);
// Complete upload manager with UI integration
class UploadManagerUI {
constructor(api) {
this.api = api;
this.uploads = new Map();
this.setupEventListeners();
}
setupEventListeners() {
// File input change
document.getElementById('fileInput').addEventListener('change', (e) => {
this.handleFiles(e.target.files);
});
// Drag and drop
const dropZone = document.getElementById('dropZone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('border-blue-400');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('border-blue-400');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('border-blue-400');
this.handleFiles(e.dataTransfer.files);
});
// Global controls
document.getElementById('pauseAll').addEventListener('click', () => {
this.pauseAllUploads();
});
document.getElementById('resumeAll').addEventListener('click', () => {
this.resumeAllUploads();
});
document.getElementById('cancelAll').addEventListener('click', () => {
this.cancelAllUploads();
});
}
async handleFiles(files) {
for (const file of files) {
await this.startUpload(file);
}
}
async startUpload(file) {
const uploadId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Create UI element
const uploadElement = this.createUploadElement(file.name, uploadId);
// Start upload
const upload = await this.api.upload.upload('/files', file, {
originalName: file.name,
size: file.size,
type: file.type
}, {
onProgress: (progress) => {
this.updateUploadProgress(uploadId, progress);
}
});
// Store upload info
this.uploads.set(uploadId, {
file,
upload,
element: uploadElement,
progress: 0,
status: 'uploading'
});
// Set up control handlers
this.setupUploadControls(uploadId, upload);
// Wait for completion
upload.promise
.then(result => {
this.markUploadComplete(uploadId, result);
})
.catch(error => {
this.markUploadFailed(uploadId, error);
});
return uploadId;
}
createUploadElement(filename, uploadId) {
const template = document.getElementById('upload-template');
const element = template.content.cloneNode(true);
const uploadElement = element.querySelector('.upload-item');
uploadElement.id = `upload-${uploadId}`;
const nameElement = uploadElement.querySelector('.file-name');
nameElement.textContent = filename;
const progressBar = uploadElement.querySelector('.progress-bar');
const progressText = uploadElement.querySelector('.progress-text');
const speedText = uploadElement.querySelector('.speed-text');
// Store references for updates
uploadElement._progressBar = progressBar;
uploadElement._progressText = progressText;
uploadElement._speedText = speedText;
document.getElementById('uploads-list').appendChild(uploadElement);
return uploadElement;
}
updateUploadProgress(uploadId, progress) {
const upload = this.uploads.get(uploadId);
if (!upload) return;
upload.progress = progress.percentage;
const { _progressBar, _progressText, _speedText } = upload.element;
_progressBar.style.width = `${progress.percentage}%`;
_progressText.textContent = `${progress.percentage.toFixed(1)}%`;
if (progress.speed) {
_speedText.textContent = this.formatSpeed(progress.speed);
}
// Update progress bar color based on status
if (progress.percentage === 100) {
_progressBar.classList.add('bg-green-500');
}
}
setupUploadControls(uploadId, upload) {
const uploadElement = document.getElementById(`upload-${uploadId}`);
const pauseBtn = uploadElement.querySelector('.pause-btn');
const resumeBtn = uploadElement.querySelector('.resume-btn');
const cancelBtn = uploadElement.querySelector('.cancel-btn');
const retryBtn = uploadElement.querySelector('.retry-btn');
pauseBtn.addEventListener('click', () => {
upload.pause();
this.updateUploadStatus(uploadId, 'paused');
});
resumeBtn.addEventListener('click', () => {
upload.resume();
this.updateUploadStatus(uploadId, 'uploading');
});
cancelBtn.addEventListener('click', () => {
upload.cancel();
this.removeUpload(uploadId);
});
retryBtn.addEventListener('click', () => {
upload.retry();
this.updateUploadStatus(uploadId, 'uploading');
});
}
markUploadComplete(uploadId, result) {
const upload = this.uploads.get(uploadId);
if (!upload) return;
upload.status = 'completed';
upload.element.classList.add('upload-completed');
const completeElement = upload.element.querySelector('.upload-complete');
completeElement.classList.remove('hidden');
// Remove after delay
setTimeout(() => {
this.removeUpload(uploadId);
}, 3000);
}
markUploadFailed(uploadId, error) {
const upload = this.uploads.get(uploadId);
if (!upload) return;
upload.status = 'error';
upload.element.classList.add('upload-failed');
const errorElement = upload.element.querySelector('.upload-error');
errorElement.textContent = error.message;
errorElement.classList.remove('hidden');
}
updateUploadStatus(uploadId, status) {
const upload = this.uploads.get(uploadId);
if (upload) {
upload.status = status;
upload.element.setAttribute('data-status', status);
}
}
removeUpload(uploadId) {
const upload = this.uploads.get(uploadId);
if (upload) {
upload.element.remove();
this.uploads.delete(uploadId);
}
}
pauseAllUploads() {
for (const [uploadId, upload] of this.uploads) {
if (upload.status === 'uploading') {
upload.upload.pause();
this.updateUploadStatus(uploadId, 'paused');
}
}
}
resumeAllUploads() {
for (const [uploadId, upload] of this.uploads) {
if (upload.status === 'paused') {
upload.upload.resume();
this.updateUploadStatus(uploadId, 'uploading');
}
}
}
cancelAllUploads() {
for (const [uploadId, upload] of this.uploads) {
upload.upload.cancel();
this.removeUpload(uploadId);
}
}
formatSpeed(bytesPerSecond) {
if (bytesPerSecond < 1024) {
return `${bytesPerSecond.toFixed(0)} B/s`;
} else if (bytesPerSecond < 1024 * 1024) {
return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
} else {
return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`;
}
}
getQueueStatus() {
const status = {
total: this.uploads.size,
uploading: 0,
paused: 0,
completed: 0,
error: 0
};
for (const upload of this.uploads.values()) {
status[upload.status]++;
}
return status;
}
}
// Usage
const uploadManager = new UploadManagerUI(api);
// Monitor queue status
setInterval(() => {
const status = uploadManager.getQueueStatus();
updateQueueStatusDisplay(status);
}, 1000);
Manage file downloads with progress tracking, queue control, and sophisticated error handling.
// Basic download manager usage
const download = await api.download.download('/files/document.pdf', {
onProgress: (progress) => {
console.log(`Download: ${progress.percentage.toFixed(1)}%`);
updateDownloadProgress(progress.percentage);
}
});
// Control the download
// download.pause(); // Pause download
// download.resume(); // Resume download
// download.cancel(); // Cancel download
// Wait for completion
try {
const blob = await download.promise;
// Create download link
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'document.pdf';
link.click();
// Clean up
URL.revokeObjectURL(url);
console.log('Download completed');
showSuccess('File downloaded successfully!');
} catch (error) {
if (error.code === 'DOWNLOAD_CANCELLED') {
console.log('Download was cancelled by user');
} else {
console.error('Download failed:', error);
showError('Download failed: ' + error.message);
}
}
// Get download status
const status = api.download.getDownloadStatus(download.id);
console.log('Download status:', status);
// Multiple downloads with queue management
class DownloadQueue {
constructor(api) {
this.api = api;
this.downloads = new Map();
this.maxConcurrent = 3;
this.activeDownloads = 0;
}
async downloadFiles(fileList) {
const results = [];
for (const fileInfo of fileList) {
// Wait if we've reached concurrent limit
while (this.activeDownloads >= this.maxConcurrent) {
await new Promise(resolve => setTimeout(resolve, 100));
}
this.activeDownloads++;
const result = await this.downloadFile(fileInfo);
this.activeDownloads--;
results.push(result);
}
return results;
}
async downloadFile(fileInfo) {
const downloadId = `download_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const download = await this.api.download.download(fileInfo.url, {
onProgress: (progress) => {
this.updateDownloadProgress(downloadId, progress);
}
});
this.downloads.set(downloadId, {
fileInfo,
download,
progress: 0,
status: 'downloading'
});
try {
const blob = await download.promise;
this.downloads.get(downloadId).status = 'completed';
return { success: true, blob, fileInfo };
} catch (error) {
this.downloads.get(downloadId).status = 'error';
return { success: false, error, fileInfo };
}
}
updateDownloadProgress(downloadId, progress) {
const download = this.downloads.get(downloadId);
if (download) {
download.progress = progress.percentage;
// Update UI
this.updateDownloadUI(downloadId, progress);
}
}
updateDownloadUI(downloadId, progress) {
// Implementation depends on your UI framework
console.log(`Download ${downloadId}: ${progress.percentage}%`);
}
getQueueStatus() {
const status = {
total: this.downloads.size,
downloading: 0,
paused: 0,
completed: 0,
error: 0
};
for (const download of this.downloads.values()) {
status[download.status]++;
}
status.active = this.activeDownloads;
status.queued = status.total - status.downloading - status.completed - status.error;
return status;
}
}
// Usage
const downloadQueue = new DownloadQueue(api);
// Download multiple files with concurrency control
const filesToDownload = [
{ url: '/files/doc1.pdf', name: 'Document 1' },
{ url: '/files/doc2.pdf', name: 'Document 2' },
{ url: '/files/doc3.pdf', name: 'Document 3' },
{ url: '/files/doc4.pdf', name: 'Document 4' }
];
const results = await downloadQueue.downloadFiles(filesToDownload);
// Complete file management system with both upload and download managers
class FileManagementSystem {
constructor(api) {
this.api = api;
this.uploadManager = new UploadManagerUI(api);
this.downloadManager = new DownloadManagerUI(api);
this.setupGlobalControls();
}
setupGlobalControls() {
// Global pause/resume/cancel
document.getElementById('globalPause').addEventListener('click', () => {
this.pauseAll();
});
document.getElementById('globalResume').addEventListener('click', () => {
this.resumeAll();
});
document.getElementById('globalCancel').addEventListener('click', () => {
this.cancelAll();
});
// Auto-cleanup completed transfers
setInterval(() => {
this.cleanupCompleted();
}, 30000); // Every 30 seconds
}
pauseAll() {
this.uploadManager.pauseAllUploads();
this.downloadManager.pauseAllDownloads();
}
resumeAll() {
this.uploadManager.resumeAllUploads();
this.downloadManager.resumeAllDownloads();
}
cancelAll() {
this.uploadManager.cancelAllUploads();
this.downloadManager.cancelAllDownloads();
}
cleanupCompleted() {
this.uploadManager.cleanupCompleted();
this.downloadManager.cleanupCompleted();
}
getSystemStatus() {
const uploadStatus = this.uploadManager.getQueueStatus();
const downloadStatus = this.downloadManager.getQueueStatus();
return {
uploads: uploadStatus,
downloads: downloadStatus,
total: uploadStatus.total + downloadStatus.total,
active: uploadStatus.uploading + downloadStatus.downloading
};
}
// Batch operations
async uploadMultiple(files, options = {}) {
return this.uploadManager.uploadMultiple(files, options);
}
async downloadMultiple(fileUrls, options = {}) {
return this.downloadManager.downloadMultiple(fileUrls, options);
}
// File operations with progress
async uploadWithProgress(file, onProgress) {
return this.uploadManager.uploadWithProgress(file, onProgress);
}
async downloadWithProgress(fileUrl, onProgress) {
return this.downloadManager.downloadWithProgress(fileUrl, onProgress);
}
}
// Download Manager UI implementation
class DownloadManagerUI {
constructor(api) {
this.api = api;
this.downloads = new Map();
this.setupEventListeners();
}
setupEventListeners() {
// Download buttons
document.addEventListener('click', (e) => {
if (e.target.classList.contains('download-btn')) {
const fileUrl = e.target.dataset.url;
const fileName = e.target.dataset.name;
this.startDownload(fileUrl, fileName);
}
});
}
async startDownload(url, filename) {
const downloadId = `download_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Create UI element
const downloadElement = this.createDownloadElement(filename, downloadId);
// Start download
const download = await this.api.download.download(url, {
onProgress: (progress) => {
this.updateDownloadProgress(downloadId, progress);
}
});
// Store download info
this.downloads.set(downloadId, {
url,
filename,
download,
element: downloadElement,
progress: 0,
status: 'downloading'
});
// Set up control handlers
this.setupDownloadControls(downloadId, download);
// Wait for completion
download.promise
.then(blob => {
this.handleDownloadComplete(downloadId, blob);
})
.catch(error => {
this.markDownloadFailed(downloadId, error);
});
return downloadId;
}
createDownloadElement(filename, downloadId) {
const template = document.getElementById('download-template');
const element = template.content.cloneNode(true);
const downloadElement = element.querySelector('.download-item');
downloadElement.id = `download-${downloadId}`;
const nameElement = downloadElement.querySelector('.file-name');
nameElement.textContent = filename;
const progressBar = downloadElement.querySelector('.progress-bar');
const progressText = downloadElement.querySelector('.progress-text');
const speedText = downloadElement.querySelector('.speed-text');
// Store references for updates
downloadElement._progressBar = progressBar;
downloadElement._progressText = progressText;
downloadElement._speedText = speedText;
document.getElementById('downloads-list').appendChild(downloadElement);
return downloadElement;
}
updateDownloadProgress(downloadId, progress) {
const download = this.downloads.get(downloadId);
if (!download) return;
download.progress = progress.percentage;
const { _progressBar, _progressText, _speedText } = download.element;
_progressBar.style.width = `${progress.percentage}%`;
_progressText.textContent = `${progress.percentage.toFixed(1)}%`;
if (progress.speed) {
_speedText.textContent = this.formatSpeed(progress.speed);
}
}
setupDownloadControls(downloadId, download) {
const downloadElement = document.getElementById(`download-${downloadId}`);
const pauseBtn = downloadElement.querySelector('.pause-btn');
const resumeBtn = downloadElement.querySelector('.resume-btn');
const cancelBtn = downloadElement.querySelector('.cancel-btn');
pauseBtn.addEventListener('click', () => {
download.pause();
this.updateDownloadStatus(downloadId, 'paused');
});
resumeBtn.addEventListener('click', () => {
download.resume();
this.updateDownloadStatus(downloadId, 'downloading');
});
cancelBtn.addEventListener('click', () => {
download.cancel();
this.removeDownload(downloadId);
});
}
handleDownloadComplete(downloadId, blob) {
const download = this.downloads.get(downloadId);
if (!download) return;
// Create download link
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = download.filename;
link.click();
// Clean up
URL.revokeObjectURL(url);
this.markDownloadComplete(downloadId);
}
markDownloadComplete(downloadId) {
const download = this.downloads.get(downloadId);
if (!download) return;
download.status = 'completed';
download.element.classList.add('download-completed');
const completeElement = download.element.querySelector('.download-complete');
completeElement.classList.remove('hidden');
// Remove after delay
setTimeout(() => {
this.removeDownload(downloadId);
}, 3000);
}
markDownloadFailed(downloadId, error) {
const download = this.downloads.get(downloadId);
if (!download) return;
download.status = 'error';
download.element.classList.add('download-failed');
const errorElement = download.element.querySelector('.download-error');
errorElement.textContent = error.message;
errorElement.classList.remove('hidden');
}
updateDownloadStatus(downloadId, status) {
const download = this.downloads.get(downloadId);
if (download) {
download.status = status;
download.element.setAttribute('data-status', status);
}
}
removeDownload(downloadId) {
const download = this.downloads.get(downloadId);
if (download) {
download.element.remove();
this.downloads.delete(downloadId);
}
}
pauseAllDownloads() {
for (const [downloadId, download] of this.downloads) {
if (download.status === 'downloading') {
download.download.pause();
this.updateDownloadStatus(downloadId, 'paused');
}
}
}
resumeAllDownloads() {
for (const [downloadId, download] of this.downloads) {
if (download.status === 'paused') {
download.download.resume();
this.updateDownloadStatus(downloadId, 'downloading');
}
}
}
cancelAllDownloads() {
for (const [downloadId, download] of this.downloads) {
download.download.cancel();
this.removeDownload(downloadId);
}
}
cleanupCompleted() {
for (const [downloadId, download] of this.downloads) {
if (download.status === 'completed' || download.status === 'error') {
this.removeDownload(downloadId);
}
}
}
formatSpeed(bytesPerSecond) {
if (bytesPerSecond < 1024) {
return `${bytesPerSecond.toFixed(0)} B/s`;
} else if (bytesPerSecond < 1024 * 1024) {
return `${(bytesPerSecond / 1024).toFixed(1)} KB/s`;
} else {
return `${(bytesPerSecond / 1024 / 1024).toFixed(1)} MB/s`;
}
}
getQueueStatus() {
const status = {
total: this.downloads.size,
downloading: 0,
paused: 0,
completed: 0,
error: 0
};
for (const download of this.downloads.values()) {
status[download.status]++;
}
return status;
}
}
// Initialize the complete system
const fileSystem = new FileManagementSystem(api);
// Monitor system status
setInterval(() => {
const status = fileSystem.getSystemStatus();
updateSystemStatusDisplay(status);
}, 1000);
🎉 Incredible! You've mastered Apisto's Upload and Download Managers. You can now build sophisticated file management systems with progress tracking, queue control, and enterprise-grade features.
Explore streaming, GraphQL, interceptors, batch requests, and other advanced Apisto capabilities.
Continue to Advanced Features →