/** * ownCloud * * @author Juan Pablo VillafaƱez Ramos * @author Jesus Macias Portela * @copyright (C) 2014 ownCloud, Inc. * * This file is licensed under the Affero General Public License version 3 * or later. * * See the COPYING-README file. * */ if (!OCA.External) { OCA.External = {}; } if (!OCA.External.StatusManager) { OCA.External.StatusManager = {}; } OCA.External.StatusManager = { mountStatus: null, mountPointList: null, /** * Function * @param {callback} afterCallback */ getMountStatus: function (afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { return; } if (self.mountStatus) { afterCallback(self.mountStatus); } }, /** * Function Check mount point status from cache * @param {string} mount_point */ getMountPointListElement: function (mount_point) { var element; $.each(this.mountPointList, function (key, value) { if (value.mount_point === mount_point) { element = value; return false; } }); return element; }, /** * Function Check mount point status from cache * @param {string} mount_point * @param {string} mount_point */ getMountStatusForMount: function (mountData, afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { return $.Deferred().resolve(); } var defObj; if (self.mountStatus[mountData.mount_point]) { defObj = $.Deferred(); afterCallback(mountData, self.mountStatus[mountData.mount_point]); defObj.resolve(); // not really useful, but it'll keep the same behaviour } else { defObj = $.ajax({ type: 'GET', url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'userglobalstorages') + '/' + mountData.id, data: {'testOnly' : false}, success: function (response) { if (response && response.status === 0) { self.mountStatus[mountData.mount_point] = response; } else { var statusCode = response.status ? response.status : 1; var statusMessage = response.statusMessage ? response.statusMessage : t('files_external', 'Empty response from the server') // failure response with error message self.mountStatus[mountData.mount_point] = { type: mountData.type, status: statusCode, id: mountData.id, error: statusMessage, userProvided: response.userProvided }; } afterCallback(mountData, self.mountStatus[mountData.mount_point]); }, error: function (jqxhr, state, error) { var message; if (mountData.location === 3) { // In this case the error is because mount point use Login credentials and don't exist in the session message = t('files_external', 'Couldn\'t access. Please logout and login to activate this mount point'); } else { message = t('files_external', 'Couldn\'t get the information from the ownCloud server: {code} {type}', { code: jqxhr.status, type: error }); } self.mountStatus[mountData.mount_point] = { type: mountData.type, status: 1, location: mountData.location, error: message }; afterCallback(mountData, self.mountStatus[mountData.mount_point]); } }); } return defObj; }, /** * Function to get external mount point list from the files_external API * @param {function} afterCallback function to be executed */ getMountPointList: function (afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountPointListRunning) { return; } if (self.mountPointList) { afterCallback(self.mountPointList); } else { self.isGetMountPointListRunning = true; $.ajax({ type: 'GET', url: OC.linkToOCS('apps/files_external/api/v1') + 'mounts?format=json', success: function (response) { self.mountPointList = []; _.each(response.ocs.data, function (mount) { var element = {}; element.mount_point = mount.name; element.type = mount.scope; element.location = ""; element.id = mount.id; element.backendText = mount.backend; element.backend = mount.class; self.mountPointList.push(element); }); afterCallback(self.mountPointList); }, error: function (jqxhr, state, error) { self.mountPointList = []; OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of external mount points: {type}', {type: error})); }, complete: function () { self.isGetMountPointListRunning = false; } }); } }, /** * Function to manage action when a mountpoint status = 1 (Errored). Show a dialog to be redirected to settings page. * @param {string} name MountPoint Name */ manageMountPointError: function (name) { this.getMountStatus($.proxy(function (allMountStatus) { if (allMountStatus.hasOwnProperty(name) && allMountStatus[name].status > 0 && allMountStatus[name].status < 7) { var mountData = allMountStatus[name]; if (mountData.type === "system") { if (mountData.userProvided) { // personal mount whit credentials problems this.showCredentialsDialog(name, mountData); } else { OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in admin settings page?', t('files_external', 'External mount error'), function (e) { if (e === true) { OC.redirect(OC.generateUrl('/settings/admin#files_external')); } }); } } else { OC.dialogs.confirm(t('files_external', 'There was an error with message: ') + mountData.error + '. Do you want to review mount point config in personal settings page?', t('files_external', 'External mount error'), function (e) { if (e === true) { OC.redirect(OC.generateUrl('/settings/personal#' + t('files_external', 'external-storage'))); } }); } } }, this)); }, /** * Function to process a mount point in relation with their status, Called from Async Queue. * @param {object} mountData * @param {object} mountStatus */ processMountStatusIndividual: function (mountData, mountStatus) { var mountPoint = mountData.mount_point; if (mountStatus.status > 0) { var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint)); var route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error'; if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { OCA.External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.External.StatusManager.manageMountPointError, OCA.External.StatusManager), route); } return false; } else { if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { OCA.External.StatusManager.Utils.restoreFolder(mountPoint); OCA.External.StatusManager.Utils.toggleLink(mountPoint, true, true); } return true; } }, /** * Function to process a mount point in relation with their status * @param {object} mountData * @param {object} mountStatus */ processMountList: function (mountList) { var elementList = null; $.each(mountList, function (name, value) { var trElement = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point)); trElement.attr('data-external-backend', value.backend); if (elementList) { elementList = elementList.add(trElement); } else { elementList = trElement; } }); if (elementList instanceof $) { if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { // Put their custom icon OCA.External.StatusManager.Utils.changeFolderIcon(elementList); // Save default view OCA.External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList); // Disable row until check status elementList.addClass('externalDisabledRow'); OCA.External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false); } } }, /** * Function to process the whole mount point list in relation with their status (Async queue) */ launchFullConnectivityCheckOneByOne: function () { var self = this; this.getMountPointList(function (list) { // check if we have a list first if (list === undefined && !self.emptyWarningShown) { self.emptyWarningShown = true; OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of Windows network drive mount points: empty response from the server')); return; } if (list && list.length > 0) { self.processMountList(list); if (!self.mountStatus) { self.mountStatus = {}; } var ajaxQueue = []; $.each(list, function (key, value) { var queueElement = { funcName: $.proxy(self.getMountStatusForMount, self), funcArgs: [value, $.proxy(self.processMountStatusIndividual, self)] }; ajaxQueue.push(queueElement); }); var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function () { if (!self.notificationHasShown) { var showNotification = false; $.each(self.mountStatus, function (key, value) { if (value.status === 1) { self.notificationHasShown = true; showNotification = true; } }); if (showNotification) { OC.Notification.showTemporary(t('files_external', 'Some of the configured external mount points are not connected. Please click on the red row(s) for more information')); } } }); rolQueue.runQueue(); } }); }, /** * Function to process a mount point list in relation with their status (Async queue) * @param {object} mountListData * @param {boolean} recheck delete cached info and force api call to check mount point status */ launchPartialConnectivityCheck: function (mountListData, recheck) { if (mountListData.length === 0) { return; } var self = this; var ajaxQueue = []; $.each(mountListData, function (key, value) { if (recheck && value.mount_point in self.mountStatus) { delete self.mountStatus[value.mount_point]; } var queueElement = { funcName: $.proxy(self.getMountStatusForMount, self), funcArgs: [value, $.proxy(self.processMountStatusIndividual, self)] }; ajaxQueue.push(queueElement); }); new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue(); }, /** * Function to relaunch some mount point status check * @param {string} mountListNames * @param {boolean} recheck delete cached info and force api call to check mount point status */ recheckConnectivityForMount: function (mountListNames, recheck) { if (mountListNames.length === 0) { return; } var self = this; var mountListData = []; if (!self.mountStatus) { self.mountStatus = {}; } $.each(mountListNames, function (key, value) { var mountData = self.getMountPointListElement(value); if (mountData) { mountListData.push(mountData); } }); // for all mounts in the list, delete the cached status values if (recheck) { $.each(mountListData, function (key, value) { if (value.mount_point in self.mountStatus) { delete self.mountStatus[value.mount_point]; } }); } self.processMountList(mountListData); self.launchPartialConnectivityCheck(mountListData, recheck); }, credentialsDialogTemplate: '
' + '
{{credentials_text}}
' + '
' + '' + '' + '
' + '
', /** * Function to display custom dialog to enter credentials * @param mountPoint * @param mountData */ showCredentialsDialog: function (mountPoint, mountData) { var template = Handlebars.compile(OCA.External.StatusManager.credentialsDialogTemplate); var dialog = $(template({ credentials_text: t('files_external', 'Please enter the credentials for the {mount} mount', { 'mount': mountPoint }), placeholder_username: t('files_external', 'Username'), placeholder_password: t('files_external', 'Password') })); $('body').append(dialog); var apply = function () { var username = dialog.find('[name=username]').val(); var password = dialog.find('[name=password]').val(); var endpoint = OC.generateUrl('apps/files_external/userglobalstorages/{id}', { id: mountData.id }); $('.oc-dialog-close').hide(); $.ajax({ type: 'PUT', url: endpoint, data: { backendOptions: { user: username, password: password } }, success: function (data) { OC.Notification.showTemporary(t('files_external', 'Credentials saved')); dialog.ocdialog('close'); /* Trigger status check again */ OCA.External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true); }, error: function () { $('.oc-dialog-close').show(); OC.Notification.showTemporary(t('files_external', 'Credentials saving failed')); } }); return false; }; var ocdialogParams = { modal: true, title: t('files_external', 'Credentials required'), buttons: [{ text: t('files_external', 'Save'), click: apply, closeOnEscape: true }], closeOnExcape: true }; dialog.ocdialog(ocdialogParams) .bind('ocdialogclose', function () { dialog.ocdialog('destroy').remove(); }); dialog.find('form').on('submit', apply); dialog.find('form input:first').focus(); dialog.find('form input').keyup(function (e) { if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) { $(e.target).closest('form').submit(); return false; } else { return true; } }); } }; OCA.External.StatusManager.Utils = { showIconError: function (folder, clickAction, errorImageUrl) { var imageUrl = "url(" + errorImageUrl + ")"; var trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); this.changeFolderIcon(folder, imageUrl); this.toggleLink(folder, false, clickAction); trFolder.addClass('externalErroredRow'); }, /** * @param folder string with the folder or jQuery element pointing to the tr element */ storeDefaultFolderIconAndBgcolor: function (folder) { var trFolder; if (folder instanceof $) { trFolder = folder; } else { trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); } trFolder.each(function () { var thisElement = $(this); if (thisElement.data('oldbgcolor') === undefined) { thisElement.data('oldbgcolor', thisElement.css('background-color')); } }); var icon = trFolder.find('td:first-child div.thumbnail'); icon.each(function () { var thisElement = $(this); if (thisElement.data('oldImage') === undefined) { thisElement.data('oldImage', thisElement.css('background-image')); } }); }, /** * @param folder string with the folder or jQuery element pointing to the tr element */ restoreFolder: function (folder) { var trFolder; if (folder instanceof $) { trFolder = folder; } else { // can't use here FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); } trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow'); tdChilds = trFolder.find("td:first-child div.thumbnail"); tdChilds.each(function () { var thisElement = $(this); thisElement.css('background-image', thisElement.data('oldImage')); }); }, /** * @param folder string with the folder or jQuery element pointing to the first td element * of the tr matching the folder name */ changeFolderIcon: function (filename) { var file; var route; if (filename instanceof $) { //trElementList $.each(filename, function (index) { route = OCA.External.StatusManager.Utils.getIconRoute($(this)); $(this).attr("data-icon", route); $(this).find('td:first-child div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); }); } else { file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td:first-child div.thumbnail"); parentTr = file.parents('tr:first'); route = OCA.External.StatusManager.Utils.getIconRoute(parentTr); parentTr.attr("data-icon", route); file.css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); } }, /** * @param backend string with the name of the external storage backend * of the tr matching the folder name */ getIconRoute: function (tr) { var icon = OC.imagePath('core', 'filetypes/folder-external'); var backend = null; if (tr instanceof $) { backend = tr.attr('data-external-backend'); } switch (backend) { case 'windows_network_drive': icon = OC.imagePath('windows_network_drive', 'folder-windows'); break; case 'sharepoint': icon = OC.imagePath('sharepoint', 'folder-sharepoint'); break; } return icon; }, toggleLink: function (filename, active, action) { var link; if (filename instanceof $) { link = filename; } else { link = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td:first-child a.name"); } if (active) { link.off('click.connectivity'); OCA.Files.App.fileList.fileActions.display(link.parent(), true, OCA.Files.App.fileList); } else { link.find('.fileactions, .nametext .action').remove(); // from files/js/fileactions (display) link.off('click.connectivity'); link.on('click.connectivity', function (e) { if (action && $.isFunction(action)) { action(filename); } e.preventDefault(); return false; }); } }, isCorrectViewAndRootFolder: function () { // correct views = files & extstoragemounts if (OCA.Files.App.getActiveView() === 'files' || OCA.Files.App.getActiveView() === 'extstoragemounts') { return OCA.Files.App.getCurrentAppContainer().find('#dir').val() === '/'; } return false; }, /* escape a selector expression for jQuery */ jqSelEscape: function (expression) { if (expression) { return expression.replace(/[!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~]/g, '\\$&'); } return null; }, /* Copied from http://stackoverflow.com/questions/2631001/javascript-test-for-existence-of-nested-object-key */ checkNested: function (cobj /*, level1, level2, ... levelN*/) { var args = Array.prototype.slice.call(arguments), obj = args.shift(); for (var i = 0; i < args.length; i++) { if (!obj || !obj.hasOwnProperty(args[i])) { return false; } obj = obj[args[i]]; } return true; } };