/** * Copyright (C) 2024 Puter Technologies Inc. * * This file is part of Puter. * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ /** * Launches a download process for an item, tracking its progress and handling success or error states. * The function returns a promise that resolves with the downloaded item or rejects in case of an error. * It uses XMLHttpRequest to manage the download and tracks progress both for the individual item and the entire batch it belongs to. * * @param {Object} options - Configuration options for the download process. * @param {string} options.url - The URL from which the item will be downloaded. * @param {string} options.operation_id - Unique identifier for the download operation, used for progress tracking. * @param {string} options.item_upload_id - Identifier for the specific item being downloaded, used for individual progress tracking. * @param {string} [options.name] - Optional name for the item being downloaded. * @param {string} [options.dest_path] - Destination path for the downloaded item. * @param {string} [options.shortcut_to] - Optional shortcut path for the item. * @param {boolean} [options.dedupe_name=false] - Flag to enable or disable deduplication of item names. * @param {boolean} [options.overwrite=false] - Flag to enable or disable overwriting of existing items. * @param {function} [options.success] - Optional callback function that is executed on successful download. * @param {function} [options.error] - Optional callback function that is executed in case of an error. * @param {number} [options.return_timeout=500] - Optional timeout in milliseconds before resolving the download. * @returns {Promise} A promise that resolves with the downloaded item or rejects with an error. */ const download = function(options){ return new Promise((resolve, reject) => { // The item that is being downloaded and will be returned to the caller at the end of the process let item; // Intervals that check for progress and cancel every few milliseconds let progress_check_interval, cancel_check_interval; // Progress tracker for the entire batch to which this item belongs let batch_download_progress = window.progress_tracker[options.operation_id]; // Tracker for this specific item's download progress let item_download_progress = batch_download_progress[options.item_upload_id]; let xhr = new XMLHttpRequest(); xhr.open("post", (api_origin + '/download'), true); xhr.setRequestHeader("Authorization", "Bearer " + auth_token); xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xhr.addEventListener('load', function(e){ // error if(this.status !== 200){ if(options.error && typeof options.error === 'function') options.error(JSON.parse(this.responseText)) return reject(JSON.parse(this.responseText)) } // success else{ item = JSON.parse(this.responseText); } }); // error xhr.addEventListener('error', function(e){ if(options.error && typeof options.error === 'function') options.error(e) return reject(e) }) xhr.send(JSON.stringify({ url: options.url, operation_id: options.operation_id, socket_id: window.socket ? window.socket.id : null, item_upload_id: options.item_upload_id, // original_client_socket_id: window.socket.id, name: options.name, path: options.dest_path, shortcut_to: options.shortcut_to, dedupe_name: options.dedupe_name ?? false, overwrite: options.overwrite ?? false, })); //---------------------------------------------- // Regularly check if this operation has been cancelled by the user //---------------------------------------------- cancel_check_interval = setInterval(() => { if(operation_cancelled[options.operation_id]){ xhr.abort(); clearInterval(cancel_check_interval); clearInterval(progress_check_interval); } }, 100); //---------------------------------------------- // Regularly check the progress of the cloud-write operation //---------------------------------------------- progress_check_interval = setInterval(function() { // Individual item progress let item_progress = 1; if(item_download_progress.total) item_progress = (item_download_progress.cloud_uploaded + item_download_progress.downloaded) / item_download_progress.total; // Entire batch progress let batch_progress = ((batch_download_progress[0].cloud_uploaded + batch_download_progress[0].downloaded)/batch_download_progress[0].total * 100).toFixed(0); batch_progress = batch_progress > 100 ? 100 : batch_progress; // Update the progress bar $(`[data-download-operation-id="${options.operation_id}"]`).find('.download-progress-bar').css( 'width', batch_progress+'%'); // If download is finished resolve promise if((item_progress >= 1 || item_progress === 0) && item){ // For a better UX, resolve 0.5 second after operation is finished. setTimeout(function() { clearInterval(progress_check_interval); clearInterval(cancel_check_interval); if(options.success && typeof options.success === 'function'){ options.success(item) } resolve(item); }, options.return_timeout ?? 500); // Stop and clear the the cloud progress check interval clearInterval(progress_check_interval) } }, 200); return xhr; }) } export default download;