Skip to content
Snippets Groups Projects
Select Git revision
  • 0c52be2ac5ca13153396172cdc5f339b3a5d294b
  • master default
  • nodeCollections
  • v0.0.1
4 results

uploadsManager.js

Blame
  • uploadsManager.js 6.38 KiB
    /*
     * This file is part of vospace-ui
     * Copyright (C) 2021 Istituto Nazionale di Astrofisica
     * SPDX-License-Identifier: GPL-3.0-or-later
     */
    import Vue from 'vue';
    import client from 'api-client';
    
    function resetArray(array) {
      array.splice(0, array.length);
    }
    
    export default {
      state: {
        files: [], // array of selected files
        progress: [], // array of upload progress percentages (for tracking upload progress)
        cancellations: [], // array of axios cancel tokens (for aborting uploads)
        deletionStatuses: [], // status of upload deletions (true if request is being aborted, false otherwise)
        errors: [], // errors happened during downloads
        creatingMetadata: false, // true when nodes metadata is being created (pre-upload phase)
        uploadInProgress: false, // true when data is being uploaded
      },
      mutations: {
        setFilesToUpload(state, files) {
          state.files = files;
        },
        resetUploadState(state) {
          resetArray(state.progress);
          resetArray(state.cancellations);
          resetArray(state.deletionStatuses);
          resetArray(state.errors);
        },
        setDeletion(state, data) {
          Vue.set(state.deletionStatuses, data.index, data.value);
        },
        setProgress(state, data) {
          Vue.set(state.progress, data.index, data.percent);
        },
        setCreatingMetadata(state, value) {
          state.creatingMetadata = value;
        },
        setUploadInProgress(state, value) {
          state.uploadInProgress = value;
        },
        setPreUploadResults(state, preUploadResults) {
          for (let result of preUploadResults) {
            if (Object.keys(result).includes('request')) {
              state.cancellations.push(result.source); // axios cancellation token
              state.deletionStatuses.push(null);
              state.errors.push(null);
            } else {
              state.cancellations.push(null);
              state.deletionStatuses.push(null);
              state.errors.push(result);
            }
          }
          state.uploadInProgress = true;
        },
        setError(state, data) {
          Vue.set(state.errors, data.index, data.error);
        }
      },
      actions: {
        upload({ state, rootState, commit, dispatch }, bvModal) {
    
          // Progress bars initialization
          for (let index = 0; index < state.files.length; index++) {
            let file = state.files[index];
            let reader = new FileReader();
    
            reader.addEventListener('progress', (event) => {
              if (event.loaded && event.total) {
                let percent = (event.loaded / event.total) * 100;
                commit('setProgress', { index, percent });
              }
            });
    
            reader.readAsDataURL(file);
          }
    
          commit('setCreatingMetadata', true);
    
          // Create nodes metadata and obtain upload URLs
          dispatch('prepareUpload')
            .then((preUploadResults) => {
              commit('setCreatingMetadata', false);
    
              let promises = [];
              for (let result of preUploadResults) {
                if (Object.keys(result).includes('request')) {
                  promises.push(result.request); // upload http promise
                } else {
                  // fictitious promise for failed pre-upload event
                  promises.push(new Promise(resolve => resolve()));
                }
              }
    
              commit('setPreUploadResults', preUploadResults);
    
              // wait until all downloads have been completed (both successfully or not)
              return Promise.allSettled(promises);
            })
            .then((responses) => {
    
              let deletionPromises = [];
              for (let index = 0; index < responses.length; index++) {
                let error = state.errors[index];
                let response = responses[index];
    
                if (response.status === 'fulfilled' && error === null) {
                  commit('setProgress', { index, percent: 100 });
                } else {
                  if (state.deletionStatuses[index] || error === null) {
                    // delete node metadata both when user aborted the upload and when it failed
                    deletionPromises.push(client.deleteNodes(['/' + rootState.path + '/' + state.files[index].name], true));
                    if (state.deletionStatuses[index] === null) {
                      // e.g. server became unreachable during the upload
                      commit('setError', { index, error: 'Unable to upload file' });
                    }
                  }
                }
              }
    
              // if there are some deletion promises, wait them
              return Promise.allSettled(deletionPromises);
            })
            .then(deletionResults => {
              // verify if metadata of aborted uploads has been correctly removed
              let resultIndex = 0;
              for (let i = 0; i < state.deletionStatuses.length; i++) {
                let deletionStatus = state.deletionStatuses[i];
                if (deletionStatus !== null) {
                  let deletionResult = deletionResults[resultIndex];
                  if (deletionResult.status === 'fulfilled') {
                    commit('setDeletion', { index: i, value: false });
                  } else {
                    commit('setError', { index: i, error: 'Failed to delete node metadata. Manual cleanup may be required.' });
                  }
                  resultIndex++;
                }
              }
            })
            .finally(() => {
              // Reload current node when all uploads completed
              dispatch('setPath', rootState.path);
              commit('setCreatingMetadata', false);
              commit('setUploadInProgress', false);
              let hasErrors = state.errors.filter(e => e !== null).length > 0;
              if (!hasErrors) {
                bvModal.hide('upload-files-modal');
              }
            });
        },
        prepareUpload({ state, rootState }) {
          let names = [];
          for (let file of state.files) {
            names.push(file.name);
          }
          return new Promise((resolve, reject) => {
            client.prepareForUpload(rootState.path, names)
              .then(preUploadResponses => {
                let uploads = [];
                for (let i = 0; i < state.files.length; i++) {
                  let uploadUrl = preUploadResponses[i].url;
                  if (uploadUrl !== null) {
                    uploads.push(client.uploadFile(uploadUrl, state.files[i]));
                  } else {
                    uploads.push(preUploadResponses[i].error);
                  }
                }
                resolve(uploads);
              })
              .catch(error => reject(error));
          });
        },
        cancelUpload({ state, commit }, index) {
          commit('setDeletion', { index, value: true });
          state.cancellations[index].cancel();
        }
      }
    }