Upload & Download Managers

Advanced file operations with progress tracking, queue management, and control

Advanced 25 min read

Advanced File Management

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.

Upload Manager

Manage multiple file uploads with progress tracking, queue control, and sophisticated error handling.

📤 Basic Upload Manager


// 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);
            

âš¡ Advanced Upload Features


// 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 Implementation


// 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);
          

Download Manager

Manage file downloads with progress tracking, queue control, and sophisticated error handling.

📥 Basic Download Manager


// 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);
            

âš¡ Advanced Download Features


// 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


// 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.

Ready for advanced features?

Explore streaming, GraphQL, interceptors, batch requests, and other advanced Apisto capabilities.

Continue to Advanced Features →