Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / plugins / composer / src / src / stores / CatalogPackageManagerStore.js
diff --git a/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js b/skyquake/plugins/composer/src/src/stores/CatalogPackageManagerStore.js
new file mode 100644 (file)
index 0000000..733e938
--- /dev/null
@@ -0,0 +1,243 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+'use strict';
+
+import _ from 'lodash'
+import alt from '../alt'
+import guid from '../libraries/guid'
+import numeral from 'numeral'
+import moment from 'moment'
+import utils from '../libraries/utils'
+import CatalogPackageManagerSource from '../sources/CatalogPackageManagerSource'
+import CatalogPackageManagerActions from '../actions/CatalogPackageManagerActions'
+import CatalogDataSource from '../sources/CatalogDataSource'
+
+import imgDownload from '../../../node_modules/open-iconic/svg/cloud-download.svg'
+import imgOnboard from '../../../node_modules/open-iconic/svg/cloud-upload.svg'
+import imgUpdate from '../../../node_modules/open-iconic/svg/data-transfer-upload.svg'
+
+const defaults = {
+       downloadPackage: {
+               id: '',
+               name: '',
+               icon: imgDownload,
+               catalogItems: [],
+               transactionId: '',
+               progress: 0,
+               message: 'Requesting catalog package export...',
+               pending: false,
+               success: false,
+               error: false,
+               url: '',
+               urlValidUntil: ''
+       },
+       checkStatusDelayInSeconds: 2,
+       downloadUrlTimeToLiveInMinutes: 5
+};
+
+const exception = function ignoreException() {};
+
+const packagePropertyNames = Object.keys(defaults.downloadPackage);
+
+function getCatalogPackageManagerServerOrigin() {
+       return utils.getSearchParams(window.location).upload_server + ':4567';
+}
+
+function delayStatusCheck(statusCheckFunction, catalogPackage) {
+       if (!catalogPackage.checkStatusTimeoutId) {
+               const delayCallback = function () {
+                       delete catalogPackage.checkStatusTimeoutId;
+                       statusCheckFunction(catalogPackage).catch(exception);
+               };
+               catalogPackage.checkStatusTimeoutId = _.delay(delayCallback, defaults.checkStatusDelayInSeconds * 1000);
+       }
+}
+
+class CatalogPackageManagerStore {
+
+       constructor() {
+
+               this.packages = [];
+
+               this.registerAsync(CatalogDataSource);
+               this.registerAsync(CatalogPackageManagerSource);
+               this.bindAction(CatalogPackageManagerActions.REMOVE_CATALOG_PACKAGE, this.removeCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE, this.downloadCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onDownloadCatalogPackageStatusUpdated);
+               this.bindAction(CatalogPackageManagerActions.DOWNLOAD_CATALOG_PACKAGE_ERROR, this.onDownloadCatalogPackageError);
+               this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE, this.uploadCatalogPackage);
+               this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_STATUS_UPDATED, this.onUploadCatalogPackageStatusUpdated);
+               this.bindAction(CatalogPackageManagerActions.UPLOAD_CATALOG_PACKAGE_ERROR, this.onUploadCatalogPackageError);
+
+       }
+
+       addPackage(catalogPackage) {
+               const packages = [catalogPackage].concat(this.packages);
+               this.setState({packages: packages});
+       }
+
+       updatePackage(catalogPackage) {
+               const packages = this.packages.map(d => {
+                       if (d.id === catalogPackage.id) {
+                               return Object.assign({}, d, catalogPackage);
+                       }
+                       return d;
+               });
+               this.setState({packages: packages});
+       }
+
+       removeCatalogPackage(catalogPackage) {
+               const packages = this.packages.filter(d => d.id !== catalogPackage.id);
+               this.setState({packages: packages});
+       }
+
+       uploadCatalogPackage(file) {
+               file.id = file.id || guid();
+               const catalogPackage = _.pick(file, packagePropertyNames);
+               catalogPackage.icon = file.riftAction === 'onboard' ? imgOnboard : imgUpdate;
+               catalogPackage.type = 'upload';
+               this.addPackage(catalogPackage);
+               // note DropZone.js handles the async upload so we don't have to invoke any async action creators
+       }
+
+       onUploadCatalogPackageStatusUpdated(response) {
+               const upload = updateStatusInfo(response);
+               this.updatePackage(upload);
+               // if pending with no transaction id - do nothing
+               // bc DropZone.js will notify upload progress
+               if (upload.pending && upload.transactionId) {
+                       delayStatusCheck(this.getInstance().requestCatalogPackageUploadStatus, upload);
+               } else if (upload.success) {
+                       this.getInstance().loadCatalogs();
+               }
+       }
+
+       onUploadCatalogPackageError(response) {
+               console.warn('onUploadCatalogPackageError', response);
+               const catalogPackage = updateStatusInfo(response);
+               this.updatePackage(catalogPackage);
+       }
+
+       downloadCatalogPackage(data) {
+               let catalogItems = data['selectedItems'] || [];
+               let format = data['selectedFormat'] || 'mano';
+               let grammar = data['selectedGrammar'] || 'osm';
+               if (catalogItems.length) {
+                       const catalogPackage = Object.assign({}, defaults.downloadPackage, {id: guid()});
+                       catalogPackage.name = catalogItems[0].name;
+                       catalogPackage.type = 'download';
+                       if (catalogItems.length > 1) {
+                               catalogPackage.name += ' (' + catalogItems.length + ' items)';
+                       }
+                       catalogPackage.ids = catalogItems.map(d => d.id).sort().toString();
+                       catalogPackage.catalogItems = catalogItems;
+                       this.addPackage(catalogPackage);
+                       this.getInstance().requestCatalogPackageDownload(catalogPackage, format, grammar).catch(exception);
+               }
+       }
+
+       onDownloadCatalogPackageStatusUpdated(response) {
+               const download = updateStatusInfo(response);
+               this.updatePackage(download);
+               if (download.pending) {
+                       delayStatusCheck(this.getInstance().requestCatalogPackageDownloadStatus, download);
+               }
+       }
+
+       onDownloadCatalogPackageError(response) {
+               console.warn('onDownloadCatalogPackageError', response);
+               const catalogPackage = updateStatusInfo(response);
+               this.updatePackage(catalogPackage);
+       }
+
+}
+
+function calculateUploadProgressMessage(size = 0, progress = 0, bytesSent = 0) {
+       const amount = parseFloat(progress) || 0;
+       const loaded = amount === 100 ? size : size * amount / 100;
+       let progressText;
+       if (amount === 100) {
+               progressText = numeral(loaded).format('0.0b') + ' loaded ';
+       } else if (typeof amount === 'number' && amount != 0) {
+               progressText = numeral(bytesSent).format('0.0b') + ' out of ' + numeral(size).format('0.0b');
+       } else {
+               progressText = progress;
+       }
+       return progressText;
+}
+
+function updateStatusInfo(response) {
+       // returns the catalogPackage object with the status fields updated based on the server response
+       const statusInfo = {
+               pending: false,
+               success: false,
+               error: false
+       };
+       const responseData = response.data;
+       const catalogPackage = response.state;
+       switch(responseData.status) {
+       case 'upload-progress':
+               statusInfo.pending = true;
+               statusInfo.progress = parseFloat(responseData.progress) || 0;
+               statusInfo.message = calculateUploadProgressMessage(catalogPackage.size, responseData.progress, responseData.bytesSent);
+               break;
+       case 'upload-success':
+               statusInfo.pending = true;
+               statusInfo.progress = 100;
+               statusInfo.message = 'Upload completed.';
+               statusInfo.transactionId = responseData.transaction_id;
+               break;
+       case 'upload-error':
+               statusInfo.error = true;
+               statusInfo.message = responseData.message;
+               break;
+       case 'download-requested':
+               statusInfo.pending = true;
+               statusInfo.progress = 25;
+               statusInfo.transactionId = responseData.transaction_id;
+               break;
+       case 'pending':
+               statusInfo.pending = true;
+               statusInfo.progress = 50;
+               statusInfo.message = responseData.events[responseData.events.length - 1].text;
+               break;
+       case 'success':
+               statusInfo.success = true;
+               statusInfo.progress = 100;
+               statusInfo.message = responseData.events[responseData.events.length - 1].text;
+               if (catalogPackage.type === 'download') {
+                       statusInfo.urlValidUntil = moment().add(defaults.downloadUrlTimeToLiveInMinutes, 'minutes').toISOString();
+                       if (responseData.filename) {
+                               statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + responseData.filename;
+                       } else {
+                               statusInfo.url = getCatalogPackageManagerServerOrigin() + '/api/export/' + catalogPackage.transactionId + '.tar.gz';
+                       }
+               }
+               break;
+       case 'failure':
+               statusInfo.error = true;
+               statusInfo.message = responseData.errors[0].value;
+               break;
+       default:
+               throw new ReferenceError('a status of "request", "success", "failure", "pending", "upload-completed", "upload-error", "download-requested", "upload-progress", "upload-action" is required');
+       }
+       return Object.assign({}, catalogPackage, statusInfo);
+}
+
+export default alt.createStore(CatalogPackageManagerStore, 'CatalogPackageManagerStore');