diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index db1cdeb23b..65ceba2145 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -255,8 +255,9 @@ abstract class StoragesController extends Controller { ) ); } catch (InsufficientDataForMeaningfulAnswerException $e) { + $status = $e->getCode() ? $e->getCode() : StorageNotAvailableException::STATUS_INDETERMINATE; $storage->setStatus( - StorageNotAvailableException::STATUS_INDETERMINATE, + $status, $this->l10n->t('Insufficient data: %s', [$e->getMessage()]) ); } catch (StorageNotAvailableException $e) { diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js index 27635d2f1d..33d2ea104b 100644 --- a/apps/files_external/js/statusmanager.js +++ b/apps/files_external/js/statusmanager.js @@ -22,15 +22,15 @@ if (!OCA.External.StatusManager) { OCA.External.StatusManager = { - mountStatus : null, - mountPointList : null, + mountStatus: null, + mountPointList: null, /** * Function * @param {callback} afterCallback */ - getMountStatus : function(afterCallback) { + getMountStatus: function (afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { return; @@ -46,9 +46,9 @@ OCA.External.StatusManager = { * @param {string} mount_point */ - getMountPointListElement : function(mount_point) { + getMountPointListElement: function (mount_point) { var element; - $.each(this.mountPointList, function(key, value){ + $.each(this.mountPointList, function (key, value) { if (value.mount_point === mount_point) { element = value; return false; @@ -63,7 +63,7 @@ OCA.External.StatusManager = { * @param {string} mount_point */ - getMountStatusForMount : function(mountData, afterCallback) { + getMountStatusForMount: function (mountData, afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountStatusRunning) { return $.Deferred().resolve(); @@ -72,41 +72,46 @@ OCA.External.StatusManager = { var defObj; if (self.mountStatus[mountData.mount_point]) { defObj = $.Deferred(); - afterCallback(mountData, self.mountStatus[mountData.mount_point]); + afterCallback(mountData, self.mountStatus[mountData.mount_point]); defObj.resolve(); // not really useful, but it'll keep the same behaviour } else { defObj = $.ajax({ - type : 'GET', + type: 'GET', url: OC.webroot + '/index.php/apps/files_external/' + ((mountData.type === 'personal') ? 'userstorages' : 'userglobalstorages') + '/' + mountData.id, - success : function(response) { + success: function (response) { if (response && response.status === 0) { self.mountStatus[mountData.mount_point] = response; } else { - if (response && response.statusMessage) { - // failure response with error message - self.mountStatus[mountData.mount_point] = { type: mountData.type, - status: 1, - error: response.statusMessage}; - } else { - self.mountStatus[mountData.mount_point] = { type: mountData.type, - status: 1, - error: t('files_external', 'Empty response from the server')}; - } + 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) { + error: function (jqxhr, state, error) { var message; - if(mountData.location === 3){ + 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}); + 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}; + self.mountStatus[mountData.mount_point] = { + type: mountData.type, + status: 1, + location: mountData.location, + error: message + }; afterCallback(mountData, self.mountStatus[mountData.mount_point]); } }); @@ -119,7 +124,7 @@ OCA.External.StatusManager = { * @param {function} afterCallback function to be executed */ - getMountPointList : function(afterCallback) { + getMountPointList: function (afterCallback) { var self = this; if (typeof afterCallback !== 'function' || self.isGetMountPointListRunning) { return; @@ -130,11 +135,11 @@ OCA.External.StatusManager = { } else { self.isGetMountPointListRunning = true; $.ajax({ - type : 'GET', - url : OC.linkToOCS('apps/files_external/api/v1') + 'mounts?format=json', - success : function(response) { + type: 'GET', + url: OC.linkToOCS('apps/files_external/api/v1') + 'mounts?format=json', + success: function (response) { self.mountPointList = []; - _.each(response.ocs.data, function(mount){ + _.each(response.ocs.data, function (mount) { var element = {}; element.mount_point = mount.name; element.type = mount.scope; @@ -147,11 +152,11 @@ OCA.External.StatusManager = { }); afterCallback(self.mountPointList); }, - error : function(jqxhr, state, error) { + 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})); + OC.Notification.showTemporary(t('files_external', 'Couldn\'t get the list of external mount points: {type}', {type: error})); }, - complete : function() { + complete: function () { self.isGetMountPointListRunning = false; } }); @@ -163,21 +168,25 @@ OCA.External.StatusManager = { * @param {string} name MountPoint Name */ - manageMountPointError : function(name) { - var self = this; - this.getMountStatus($.proxy(function(allMountStatus) { - if (typeof allMountStatus[name] !== 'undefined' || allMountStatus[name].status === 1) { + 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") { - 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) { - window.location.href = OC.generateUrl('/settings/admin#files_external'); - } - }); + 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) { - window.location.href = OC.generateUrl('/settings/personal#external-storage'); + 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'))); } }); } @@ -191,13 +200,13 @@ OCA.External.StatusManager = { * @param {object} mountStatus */ - processMountStatusIndividual : function(mountData, mountStatus) { + processMountStatusIndividual: function (mountData, mountStatus) { var mountPoint = mountData.mount_point; - if (mountStatus.status === 1) { + if (mountStatus.status > 0) { var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint)); - route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error'; + 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); @@ -218,9 +227,9 @@ OCA.External.StatusManager = { * @param {object} mountStatus */ - processMountList : function(mountList) { + processMountList: function (mountList) { var elementList = null; - $.each(mountList, function(name, value){ + $.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) { @@ -247,9 +256,9 @@ OCA.External.StatusManager = { * Function to process the whole mount point list in relation with their status (Async queue) */ - launchFullConnectivityCheckOneByOne : function() { + launchFullConnectivityCheckOneByOne: function () { var self = this; - this.getMountPointList(function(list){ + this.getMountPointList(function (list) { // check if we have a list first if (list === undefined && !self.emptyWarningShown) { self.emptyWarningShown = true; @@ -264,17 +273,19 @@ OCA.External.StatusManager = { } var ajaxQueue = []; - $.each(list, function(key, value){ - var queueElement = {funcName: $.proxy(self.getMountStatusForMount, self), - funcArgs: [value, - $.proxy(self.processMountStatusIndividual, self)]}; + $.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(){ + var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function () { if (!self.notificationHasShown) { var showNotification = false; - $.each(self.mountStatus, function(key, value){ + $.each(self.mountStatus, function (key, value) { if (value.status === 1) { self.notificationHasShown = true; showNotification = true; @@ -297,20 +308,22 @@ OCA.External.StatusManager = { * @param {boolean} recheck delete cached info and force api call to check mount point status */ - launchPartialConnectivityCheck : function(mountListData, recheck) { + launchPartialConnectivityCheck: function (mountListData, recheck) { if (mountListData.length === 0) { return; } var self = this; var ajaxQueue = []; - $.each(mountListData, function(key, value){ + $.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)]}; + var queueElement = { + funcName: $.proxy(self.getMountStatusForMount, self), + funcArgs: [value, + $.proxy(self.processMountStatusIndividual, self)] + }; ajaxQueue.push(queueElement); }); new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue(); @@ -323,21 +336,19 @@ OCA.External.StatusManager = { * @param {boolean} recheck delete cached info and force api call to check mount point status */ - recheckConnectivityForMount : function(mountListNames, recheck) { + recheckConnectivityForMount: function (mountListNames, recheck) { if (mountListNames.length === 0) { return; } var self = this; var mountListData = []; - var recheckPersonalGlobal = false; - var recheckAdminGlobal = false; if (!self.mountStatus) { self.mountStatus = {}; } - $.each(mountListNames, function(key, value){ + $.each(mountListNames, function (key, value) { var mountData = self.getMountPointListElement(value); if (mountData) { mountListData.push(mountData); @@ -346,7 +357,7 @@ OCA.External.StatusManager = { // for all mounts in the list, delete the cached status values if (recheck) { - $.each(mountListData, function(key, value){ + $.each(mountListData, function (key, value) { if (value.mount_point in self.mountStatus) { delete self.mountStatus[value.mount_point]; } @@ -355,12 +366,96 @@ OCA.External.StatusManager = { 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) { + 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); @@ -371,14 +466,14 @@ OCA.External.StatusManager.Utils = { /** * @param folder string with the folder or jQuery element pointing to the tr element */ - storeDefaultFolderIconAndBgcolor: function(folder) { + 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(){ + trFolder.each(function () { var thisElement = $(this); if (thisElement.data('oldbgcolor') === undefined) { thisElement.data('oldbgcolor', thisElement.css('background-color')); @@ -386,7 +481,7 @@ OCA.External.StatusManager.Utils = { }); var icon = trFolder.find('td:first-child div.thumbnail'); - icon.each(function(){ + icon.each(function () { var thisElement = $(this); if (thisElement.data('oldImage') === undefined) { thisElement.data('oldImage', thisElement.css('background-image')); @@ -397,7 +492,7 @@ OCA.External.StatusManager.Utils = { /** * @param folder string with the folder or jQuery element pointing to the tr element */ - restoreFolder: function(folder) { + restoreFolder: function (folder) { var trFolder; if (folder instanceof $) { trFolder = folder; @@ -407,7 +502,7 @@ OCA.External.StatusManager.Utils = { } trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow'); tdChilds = trFolder.find("td:first-child div.thumbnail"); - tdChilds.each(function(){ + tdChilds.each(function () { var thisElement = $(this); thisElement.css('background-image', thisElement.data('oldImage')); }); @@ -417,12 +512,12 @@ OCA.External.StatusManager.Utils = { * @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) { + changeFolderIcon: function (filename) { var file; var route; if (filename instanceof $) { //trElementList - $.each(filename, function(index){ + $.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'); @@ -440,7 +535,7 @@ OCA.External.StatusManager.Utils = { * @param backend string with the name of the external storage backend * of the tr matching the folder name */ - getIconRoute: function(tr) { + getIconRoute: function (tr) { var icon = OC.imagePath('core', 'filetypes/folder-external'); var backend = null; @@ -460,7 +555,7 @@ OCA.External.StatusManager.Utils = { return icon; }, - toggleLink: function(filename, active, action) { + toggleLink: function (filename, active, action) { var link; if (filename instanceof $) { link = filename; @@ -473,7 +568,7 @@ OCA.External.StatusManager.Utils = { } else { link.find('.fileactions, .nametext .action').remove(); // from files/js/fileactions (display) link.off('click.connectivity'); - link.on('click.connectivity', function(e){ + link.on('click.connectivity', function (e) { if (action && $.isFunction(action)) { action(filename); } @@ -483,7 +578,7 @@ OCA.External.StatusManager.Utils = { } }, - isCorrectViewAndRootFolder: function() { + 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() === '/'; @@ -492,15 +587,15 @@ OCA.External.StatusManager.Utils = { }, /* escape a selector expression for jQuery */ - jqSelEscape: function(expression) { - if(expression){ + 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*/) { + checkNested: function (cobj /*, level1, level2, ... levelN*/) { var args = Array.prototype.slice.call(arguments), obj = args.shift(); diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index 2bf39bcaa4..51c2debd72 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -130,6 +130,16 @@ class ConfigAdapter implements IMountProvider { $impl = new FailedStorage(['exception' => $e]); } + try { + $availability = $impl->getAvailability(); + if (!$availability['available']) { + $impl = new FailedStorage(['exception' => null]); + } + } catch (\Exception $e) { + // propagate exception into filesystem + $impl = new FailedStorage(['exception' => $e]); + } + $mount = new MountPoint( $impl, '/' . $user->getUID() . '/files' . $storage->getMountPoint(), diff --git a/apps/files_external/lib/failedcache.php b/apps/files_external/lib/failedcache.php index f9866f4305..0f59495e59 100644 --- a/apps/files_external/lib/failedcache.php +++ b/apps/files_external/lib/failedcache.php @@ -22,6 +22,7 @@ namespace OCA\Files_External\Lib; use OC\Files\Cache\CacheEntry; +use OCP\Constants; use OCP\Files\Cache\ICache; /** @@ -40,7 +41,7 @@ class FailedCache implements ICache { 'size' => 0, 'mimetype' => 'httpd/unix-directory', 'mimepart' => 'httpd', - 'permissions' => 0, + 'permissions' => Constants::PERMISSION_READ, 'mtime' => time() ]); } else { diff --git a/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php b/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php index 871301b9b5..22d83ef56f 100644 --- a/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php +++ b/apps/files_external/lib/insufficientdataformeaningfulanswerexception.php @@ -27,4 +27,15 @@ use \OCP\Files\StorageNotAvailableException; * Authentication mechanism or backend has insufficient data */ class InsufficientDataForMeaningfulAnswerException extends StorageNotAvailableException { + /** + * StorageNotAvailableException constructor. + * + * @param string $message + * @param int $code + * @param \Exception $previous + * @since 6.0.0 + */ + public function __construct($message = '', $code = self::STATUS_INDETERMINATE, \Exception $previous = null) { + parent::__construct($message, $code, $previous); + } } diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php index 9da21dc88e..50bd56f28a 100644 --- a/apps/files_external/lib/smb.php +++ b/apps/files_external/lib/smb.php @@ -314,4 +314,17 @@ class SMB extends Common { || Server::NativeAvailable() ) ? true : ['smbclient']; } + + /** + * Test a storage for availability + * + * @return bool + */ + public function test() { + try { + return parent::test(); + } catch (Exception $e) { + return false; + } + } } diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php index 7f71689384..6f44b25a2e 100644 --- a/apps/files_external/lib/storageconfig.php +++ b/apps/files_external/lib/storageconfig.php @@ -24,6 +24,7 @@ namespace OCA\Files_external\Lib; +use OCA\Files_External\Lib\Auth\IUserProvided; use \OCA\Files_External\Lib\Backend\Backend; use \OCA\Files_External\Lib\Auth\AuthMechanism; @@ -406,6 +407,7 @@ class StorageConfig implements \JsonSerializable { if (!is_null($this->statusMessage)) { $result['statusMessage'] = $this->statusMessage; } + $result['userProvided'] = $this->authMechanism instanceof IUserProvided; $result['type'] = ($this->getType() === self::MOUNT_TYPE_PERSONAl) ? 'personal': 'system'; return $result; }