aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_external
diff options
context:
space:
mode:
authorVincent Petry <pvince81@owncloud.com>2014-10-31 11:41:07 +0100
committerVincent Petry <pvince81@owncloud.com>2015-03-12 18:51:02 +0100
commitce94a998dd5a5801beef7874dd13752095e35de0 (patch)
tree8d91631f709549c40555dcb74e9976519f895ae2 /apps/files_external
parent23cc3cc5f2f42166c37fbe03fa62d3dd1dbfe5ed (diff)
downloadnextcloud-server-ce94a998dd5a5801beef7874dd13752095e35de0.tar.gz
nextcloud-server-ce94a998dd5a5801beef7874dd13752095e35de0.zip
Use storage id + appframework for ext storage CRUD
- Added StorageConfig class to replace ugly arrays - Implemented StorageService and StorageController for Global and User storages - Async status checking for storages (from Xenopathic) - Auto-generate id for external storage configs (not the same as storage_id) - Refactor JS classes for external storage settings, this mostly moves/encapsulated existing global event handlers into the MountConfigListView class. - Added some JS unit tests for the external storage UI
Diffstat (limited to 'apps/files_external')
-rw-r--r--apps/files_external/ajax/addMountPoint.php26
-rw-r--r--apps/files_external/ajax/removeMountPoint.php23
-rw-r--r--apps/files_external/appinfo/app.php2
-rw-r--r--apps/files_external/appinfo/application.php7
-rw-r--r--apps/files_external/appinfo/routes.php38
-rw-r--r--apps/files_external/controller/globalstoragescontroller.php145
-rw-r--r--apps/files_external/controller/storagescontroller.php157
-rw-r--r--apps/files_external/controller/userstoragescontroller.php172
-rw-r--r--apps/files_external/js/dropbox.js4
-rw-r--r--apps/files_external/js/google.js4
-rw-r--r--apps/files_external/js/settings.js1047
-rw-r--r--apps/files_external/js/sftp_key.js2
-rw-r--r--apps/files_external/lib/config.php51
-rw-r--r--apps/files_external/lib/notfoundexception.php15
-rw-r--r--apps/files_external/lib/storageconfig.php243
-rw-r--r--apps/files_external/service/globalstoragesservice.php192
-rw-r--r--apps/files_external/service/storagesservice.php303
-rw-r--r--apps/files_external/service/userstoragesservice.php142
-rw-r--r--apps/files_external/templates/settings.php8
-rw-r--r--apps/files_external/tests/controller/globalstoragescontrollertest.php41
-rw-r--r--apps/files_external/tests/controller/storagescontrollertest.php217
-rw-r--r--apps/files_external/tests/controller/userstoragescontrollertest.php112
-rw-r--r--apps/files_external/tests/js/settingsSpec.js164
-rw-r--r--apps/files_external/tests/service/globalstoragesservicetest.php705
-rw-r--r--apps/files_external/tests/service/storagesservicetest.php180
-rw-r--r--apps/files_external/tests/service/userstoragesservicetest.php200
-rw-r--r--apps/files_external/tests/storageconfigtest.php50
27 files changed, 3796 insertions, 454 deletions
diff --git a/apps/files_external/ajax/addMountPoint.php b/apps/files_external/ajax/addMountPoint.php
deleted file mode 100644
index 4e27ef98756..00000000000
--- a/apps/files_external/ajax/addMountPoint.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-OCP\JSON::checkAppEnabled('files_external');
-OCP\JSON::callCheck();
-
-if ($_POST['isPersonal'] == 'true') {
- OCP\JSON::checkLoggedIn();
- $isPersonal = true;
-} else {
- OCP\JSON::checkAdminUser();
- $isPersonal = false;
-}
-
-$mountPoint = (string)$_POST['mountPoint'];
-$oldMountPoint = (string)$_POST['oldMountPoint'];
-$class = (string)$_POST['class'];
-$options = (array)$_POST['classOptions'];
-$type = (string)$_POST['mountType'];
-$applicable = (string)$_POST['applicable'];
-
-if ($oldMountPoint and $oldMountPoint !== $mountPoint) {
- OC_Mount_Config::removeMountPoint($oldMountPoint, $type, $applicable, $isPersonal);
-}
-
-$status = OC_Mount_Config::addMountPoint($mountPoint, $class, $options, $type, $applicable, $isPersonal);
-OCP\JSON::success(array('data' => array('message' => $status)));
diff --git a/apps/files_external/ajax/removeMountPoint.php b/apps/files_external/ajax/removeMountPoint.php
deleted file mode 100644
index 0870911544b..00000000000
--- a/apps/files_external/ajax/removeMountPoint.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-OCP\JSON::checkAppEnabled('files_external');
-OCP\JSON::callCheck();
-
-if (!isset($_POST['isPersonal']))
- return;
-if (!isset($_POST['mountPoint']))
- return;
-if (!isset($_POST['mountType']))
- return;
-if (!isset($_POST['applicable']))
- return;
-
-if ($_POST['isPersonal'] == 'true') {
- OCP\JSON::checkLoggedIn();
- $isPersonal = true;
-} else {
- OCP\JSON::checkAdminUser();
- $isPersonal = false;
-}
-
-OC_Mount_Config::removeMountPoint((string)$_POST['mountPoint'], (string)$_POST['mountType'], (string)$_POST['applicable'], $isPersonal);
diff --git a/apps/files_external/appinfo/app.php b/apps/files_external/appinfo/app.php
index e74ce3594c1..7ea1e96bf2f 100644
--- a/apps/files_external/appinfo/app.php
+++ b/apps/files_external/appinfo/app.php
@@ -6,6 +6,8 @@
* later.
* See the COPYING-README file.
*/
+$app = new \OCA\Files_external\Appinfo\Application();
+
$l = \OC::$server->getL10N('files_external');
OC::$CLASSPATH['OC\Files\Storage\StreamWrapper'] = 'files_external/lib/streamwrapper.php';
diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php
index b1605bb98a8..3e6b80ccb48 100644
--- a/apps/files_external/appinfo/application.php
+++ b/apps/files_external/appinfo/application.php
@@ -12,12 +12,13 @@ use \OCA\Files_External\Controller\AjaxController;
use \OCP\AppFramework\App;
use \OCP\IContainer;
- /**
- * @package OCA\Files_External\Appinfo
- */
+/**
+ * @package OCA\Files_External\Appinfo
+ */
class Application extends App {
public function __construct(array $urlParams=array()) {
parent::__construct('files_external', $urlParams);
+
$container = $this->getContainer();
/**
diff --git a/apps/files_external/appinfo/routes.php b/apps/files_external/appinfo/routes.php
index 5c7c4eca909..506c9d34e26 100644
--- a/apps/files_external/appinfo/routes.php
+++ b/apps/files_external/appinfo/routes.php
@@ -22,28 +22,30 @@
namespace OCA\Files_External\Appinfo;
+/**
+ * @var $this OC\Route\Router
+ **/
+
$application = new Application();
$application->registerRoutes(
- $this,
- array(
- 'routes' => array(
- array(
- 'name' => 'Ajax#getSshKeys',
- 'url' => '/ajax/sftp_key.php',
- 'verb' => 'POST',
- 'requirements' => array()
- )
- )
- )
+ $this,
+ array(
+ 'resources' => array(
+ 'global_storages' => array('url' => '/globalstorages'),
+ 'user_storages' => array('url' => '/userstorages'),
+ ),
+ 'routes' => array(
+ array(
+ 'name' => 'Ajax#getSshKeys',
+ 'url' => '/ajax/sftp_key.php',
+ 'verb' => 'POST',
+ 'requirements' => array()
+ )
+ )
+ )
);
-/** @var $this OC\Route\Router */
-
-$this->create('files_external_add_mountpoint', 'ajax/addMountPoint.php')
- ->actionInclude('files_external/ajax/addMountPoint.php');
-$this->create('files_external_remove_mountpoint', 'ajax/removeMountPoint.php')
- ->actionInclude('files_external/ajax/removeMountPoint.php');
-
+// TODO: move these to app framework
$this->create('files_external_add_root_certificate', 'ajax/addRootCertificate.php')
->actionInclude('files_external/ajax/addRootCertificate.php');
$this->create('files_external_remove_root_certificate', 'ajax/removeRootCertificate.php')
diff --git a/apps/files_external/controller/globalstoragescontroller.php b/apps/files_external/controller/globalstoragescontroller.php
new file mode 100644
index 00000000000..3aa64f0d85d
--- /dev/null
+++ b/apps/files_external/controller/globalstoragescontroller.php
@@ -0,0 +1,145 @@
+<?php
+/**
+ * ownCloud - files_external
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Vincent Petry <pvince81@owncloud.com>
+ * @copyright Vincent Petry 2015
+ */
+
+namespace OCA\Files_External\Controller;
+
+
+use \OCP\IConfig;
+use \OCP\IUserSession;
+use \OCP\IRequest;
+use \OCP\AppFramework\Http\DataResponse;
+use \OCP\AppFramework\Controller;
+use \OCP\AppFramework\Http;
+use \OCA\Files_external\Service\GlobalStoragesService;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_external\Lib\StorageConfig;
+
+class GlobalStoragesController extends StoragesController {
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IConfig $config
+ * @param GlobalStoragesService $globalStoragesService
+ */
+ public function __construct(
+ $AppName,
+ IRequest $request,
+ \OCP\IL10N $l10n,
+ GlobalStoragesService $globalStoragesService
+ ){
+ parent::__construct(
+ $AppName,
+ $request,
+ $l10n,
+ $globalStoragesService
+ );
+ }
+
+ /**
+ * Create an external storage entry.
+ *
+ * @param string $mountPoint storage mount point
+ * @param string $backendClass backend class name
+ * @param array $backendOptions backend-specific options
+ * @param array $applicableUsers users for which to mount the storage
+ * @param array $applicableGroups groups for which to mount the storage
+ * @param int $priority priority
+ *
+ * @return DataResponse
+ */
+ public function create(
+ $mountPoint,
+ $backendClass,
+ $backendOptions,
+ $applicableUsers,
+ $applicableGroups,
+ $priority
+ ) {
+ $newStorage = new StorageConfig();
+ $newStorage->setMountPoint($mountPoint);
+ $newStorage->setBackendClass($backendClass);
+ $newStorage->setBackendOptions($backendOptions);
+ $newStorage->setApplicableUsers($applicableUsers);
+ $newStorage->setApplicableGroups($applicableGroups);
+ $newStorage->setPriority($priority);
+
+ $response = $this->validate($newStorage);
+ if (!empty($response)) {
+ return $response;
+ }
+
+ $newStorage = $this->service->addStorage($newStorage);
+
+ $this->updateStorageStatus($newStorage);
+
+ return new DataResponse(
+ $newStorage,
+ Http::STATUS_CREATED
+ );
+ }
+
+ /**
+ * Update an external storage entry.
+ *
+ * @param int $id storage id
+ * @param string $mountPoint storage mount point
+ * @param string $backendClass backend class name
+ * @param array $backendOptions backend-specific options
+ * @param array $applicableUsers users for which to mount the storage
+ * @param array $applicableGroups groups for which to mount the storage
+ * @param int $priority priority
+ *
+ * @return DataResponse
+ */
+ public function update(
+ $id,
+ $mountPoint,
+ $backendClass,
+ $backendOptions,
+ $applicableUsers,
+ $applicableGroups,
+ $priority
+ ) {
+ $storage = new StorageConfig($id);
+ $storage->setMountPoint($mountPoint);
+ $storage->setBackendClass($backendClass);
+ $storage->setBackendOptions($backendOptions);
+ $storage->setApplicableUsers($applicableUsers);
+ $storage->setApplicableGroups($applicableGroups);
+ $storage->setPriority($priority);
+
+ $response = $this->validate($storage);
+ if (!empty($response)) {
+ return $response;
+ }
+
+ try {
+ $storage = $this->service->updateStorage($storage);
+ } catch (NotFoundException $e) {
+ return new DataResponse(
+ [
+ 'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $this->updateStorageStatus($storage);
+
+ return new DataResponse(
+ $storage,
+ Http::STATUS_OK
+ );
+
+ }
+
+}
+
diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php
new file mode 100644
index 00000000000..f047ba34b50
--- /dev/null
+++ b/apps/files_external/controller/storagescontroller.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * ownCloud - files_external
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Vincent Petry <pvince81@owncloud.com>
+ * @copyright Vincent Petry 2014
+ */
+
+namespace OCA\Files_External\Controller;
+
+
+use \OCP\IConfig;
+use \OCP\IUserSession;
+use \OCP\IRequest;
+use \OCP\AppFramework\Http\DataResponse;
+use \OCP\AppFramework\Controller;
+use \OCP\AppFramework\Http;
+use \OCA\Files_external\Service\StoragesService;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_external\Lib\StorageConfig;
+
+abstract class StoragesController extends Controller {
+
+ /**
+ * @var \OCP\IL10N
+ */
+ protected $l10n;
+
+ /**
+ * @var StoragesService
+ */
+ protected $service;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IConfig $config
+ * @param StoragesService $storagesService
+ */
+ public function __construct(
+ $AppName,
+ IRequest $request,
+ \OCP\IL10N $l10n,
+ StoragesService $storagesService
+ ){
+ parent::__construct($AppName, $request);
+ $this->l10n = $l10n;
+ $this->service = $storagesService;
+ }
+
+ /**
+ * Validate storage config
+ *
+ * @param StorageConfig $storage storage config
+ *
+ * @return DataResponse|null returns response in case of validation error
+ */
+ protected function validate(StorageConfig $storage) {
+ $mountPoint = $storage->getMountPoint();
+ if ($mountPoint === '' || $mountPoint === '/') {
+ return new DataResponse(
+ array(
+ 'message' => (string)$this->l10n->t('Invalid mount point')
+ ),
+ Http::STATUS_UNPROCESSABLE_ENTITY
+ );
+ }
+
+ // TODO: validate that other attrs are set
+
+ $backends = \OC_Mount_Config::getBackends();
+ if (!isset($backends[$storage->getBackendClass()])) {
+ // invalid backend
+ return new DataResponse(
+ array(
+ 'message' => (string)$this->l10n->t('Invalid storage backend "%s"', array($storage->getBackendClass()))
+ ),
+ Http::STATUS_UNPROCESSABLE_ENTITY
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * Check whether the given storage is available / valid.
+ *
+ * Note that this operation can be time consuming depending
+ * on whether the remote storage is available or not.
+ *
+ * @param StorageConfig $storage
+ */
+ protected function updateStorageStatus(StorageConfig &$storage) {
+ // update status (can be time-consuming)
+ $storage->setStatus(
+ \OC_Mount_Config::getBackendStatus(
+ $storage->getBackendClass(),
+ $storage->getBackendOptions(),
+ false
+ )
+ );
+ }
+
+ /**
+ * Get an external storage entry.
+ *
+ * @param int $id storage id
+ *
+ * @return DataResponse
+ */
+ public function show($id) {
+ try {
+ $storage = $this->service->getStorage($id);
+
+ $this->updateStorageStatus($storage);
+ } catch (NotFoundException $e) {
+ return new DataResponse(
+ [
+ 'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ return new DataResponse(
+ $storage,
+ Http::STATUS_OK
+ );
+ }
+
+ /**
+ * Deletes the storage with the given id.
+ *
+ * @param int $id storage id
+ *
+ * @return DataResponse
+ */
+ public function destroy($id) {
+ try {
+ $this->service->removeStorage($id);
+ } catch (NotFoundException $e) {
+ return new DataResponse(
+ [
+ 'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ return new DataResponse([], Http::STATUS_NO_CONTENT);
+ }
+
+}
+
diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php
new file mode 100644
index 00000000000..b77cbca59fd
--- /dev/null
+++ b/apps/files_external/controller/userstoragescontroller.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * ownCloud - files_external
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Vincent Petry <pvince81@owncloud.com>
+ * @copyright Vincent Petry 2015
+ */
+
+namespace OCA\Files_External\Controller;
+
+
+use \OCP\IConfig;
+use \OCP\IUserSession;
+use \OCP\IRequest;
+use \OCP\AppFramework\Http\DataResponse;
+use \OCP\AppFramework\Controller;
+use \OCP\AppFramework\Http;
+use \OCA\Files_external\Service\UserStoragesService;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_external\Lib\StorageConfig;
+
+class UserStoragesController extends StoragesController {
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param IConfig $config
+ * @param UserStoragesService $userStoragesService
+ */
+ public function __construct(
+ $AppName,
+ IRequest $request,
+ \OCP\IL10N $l10n,
+ UserStoragesService $userStoragesService
+ ){
+ parent::__construct(
+ $AppName,
+ $request,
+ $l10n,
+ $userStoragesService
+ );
+ }
+
+ /**
+ * Validate storage config
+ *
+ * @param StorageConfig $storage storage config
+ *
+ * @return DataResponse|null returns response in case of validation error
+ */
+ protected function validate(StorageConfig $storage) {
+ $result = parent::validate($storage);
+
+ if ($result != null) {
+ return $result;
+ }
+
+ // Verify that the mount point applies for the current user
+ // Prevent non-admin users from mounting local storage and other disabled backends
+ $allowedBackends = \OC_Mount_Config::getPersonalBackends();
+ if (!isset($allowedBackends[$storage->getBackendClass()])) {
+ return new DataResponse(
+ array(
+ 'message' => (string)$this->l10n->t('Invalid storage backend "%s"', array($storage->getBackendClass()))
+ ),
+ Http::STATUS_UNPROCESSABLE_ENTITY
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @{inheritdoc}
+ */
+ public function show($id) {
+ return parent::show($id);
+ }
+
+ /**
+ * Create an external storage entry.
+ *
+ * @param string $mountPoint storage mount point
+ * @param string $backendClass backend class name
+ * @param array $backendOptions backend-specific options
+ *
+ * @return DataResponse
+ *
+ * @NoAdminRequired
+ */
+ public function create(
+ $mountPoint,
+ $backendClass,
+ $backendOptions
+ ) {
+ $newStorage = new StorageConfig();
+ $newStorage->setMountPoint($mountPoint);
+ $newStorage->setBackendClass($backendClass);
+ $newStorage->setBackendOptions($backendOptions);
+
+ $response = $this->validate($newStorage);
+ if (!empty($response)) {
+ return $response;
+ }
+
+ $newStorage = $this->service->addStorage($newStorage);
+ $this->updateStorageStatus($newStorage);
+
+ return new DataResponse(
+ $newStorage,
+ Http::STATUS_CREATED
+ );
+ }
+
+ /**
+ * Update an external storage entry.
+ *
+ * @param int $id storage id
+ * @param string $mountPoint storage mount point
+ * @param string $backendClass backend class name
+ * @param array $backendOptions backend-specific options
+ *
+ * @return DataResponse
+ */
+ public function update(
+ $id,
+ $mountPoint,
+ $backendClass,
+ $backendOptions
+ ) {
+ $storage = new StorageConfig($id);
+ $storage->setMountPoint($mountPoint);
+ $storage->setBackendClass($backendClass);
+ $storage->setBackendOptions($backendOptions);
+
+ $response = $this->validate($storage);
+ if (!empty($response)) {
+ return $response;
+ }
+
+ try {
+ $storage = $this->service->updateStorage($storage);
+ } catch (NotFoundException $e) {
+ return new DataResponse(
+ [
+ 'message' => (string)$this->l10n->t('Storage with id "%i" not found', array($id))
+ ],
+ Http::STATUS_NOT_FOUND
+ );
+ }
+
+ $this->updateStorageStatus($storage);
+
+ return new DataResponse(
+ $storage,
+ Http::STATUS_OK
+ );
+
+ }
+
+ /**
+ * {@inheritdoc}
+ * @NoAdminRequired
+ */
+ public function destroy($id) {
+ return parent::destroy($id);
+ }
+}
+
diff --git a/apps/files_external/js/dropbox.js b/apps/files_external/js/dropbox.js
index 2880e910f2c..53b5d5d666f 100644
--- a/apps/files_external/js/dropbox.js
+++ b/apps/files_external/js/dropbox.js
@@ -23,7 +23,7 @@ $(document).ready(function() {
$(token).val(result.access_token);
$(token_secret).val(result.access_token_secret);
$(configured).val('true');
- OC.MountConfig.saveStorage(tr, function(status) {
+ OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
if (status) {
$(tr).find('.configuration input').attr('disabled', 'disabled');
$(tr).find('.configuration').append('<span id="access" style="padding-left:0.5em;">'+t('files_external', 'Access granted')+'</span>');
@@ -93,7 +93,7 @@ $(document).ready(function() {
$(configured).val('false');
$(token).val(result.data.request_token);
$(token_secret).val(result.data.request_token_secret);
- OC.MountConfig.saveStorage(tr, function() {
+ OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() {
window.location = result.data.url;
});
} else {
diff --git a/apps/files_external/js/google.js b/apps/files_external/js/google.js
index b9a5e66b800..648538f8028 100644
--- a/apps/files_external/js/google.js
+++ b/apps/files_external/js/google.js
@@ -32,7 +32,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(token).val(result.data.token);
$(configured).val('true');
- OC.MountConfig.saveStorage(tr, function(status) {
+ OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
if (status) {
$(tr).find('.configuration input').attr('disabled', 'disabled');
$(tr).find('.configuration').append($('<span/>')
@@ -115,7 +115,7 @@ $(document).ready(function() {
if (result && result.status == 'success') {
$(configured).val('false');
$(token).val('false');
- OC.MountConfig.saveStorage(tr, function(status) {
+ OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) {
window.location = result.data.url;
});
} else {
diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js
index ee3d0b736da..b3567b7ebf5 100644
--- a/apps/files_external/js/settings.js
+++ b/apps/files_external/js/settings.js
@@ -1,20 +1,24 @@
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
(function(){
-function updateStatus(statusEl, result){
- statusEl.removeClass('success error loading-small');
- if (result && result.status === 'success' && result.data.message) {
- statusEl.addClass('success');
- return true;
- } else {
- statusEl.addClass('error');
- return false;
- }
-}
-
+/**
+ * Returns the selection of applicable users in the given configuration row
+ *
+ * @param $row configuration row
+ * @return array array of user names
+ */
function getSelection($row) {
var values = $row.find('.applicableUsers').select2('val');
if (!values || values.length === 0) {
- values = ['all'];
+ values = [];
}
return values;
}
@@ -31,298 +35,493 @@ function highlightInput($input) {
}
}
-OC.MountConfig={
- saveStorage:function($tr, callback) {
- var mountPoint = $tr.find('.mountPoint input').val();
- var oldMountPoint = $tr.find('.mountPoint input').data('mountpoint');
- if (mountPoint === '') {
- return false;
- }
- var statusSpan = $tr.find('.status span');
- var backendClass = $tr.find('.backend').data('class');
- var configuration = $tr.find('.configuration input');
- var addMountPoint = true;
- if (configuration.length < 1) {
- return false;
- }
- var classOptions = {};
- $.each(configuration, function(index, input) {
- if ($(input).val() === '' && !$(input).hasClass('optional')) {
- addMountPoint = false;
- return false;
- }
- if ($(input).is(':checkbox')) {
- if ($(input).is(':checked')) {
- classOptions[$(input).data('parameter')] = true;
+/**
+ * Initialize select2 plugin on the given elements
+ *
+ * @param {Array<Object>} array of jQuery elements
+ * @param {int} userListLimit page size for result list
+ */
+function addSelect2 ($elements, userListLimit) {
+ if (!$elements.length) {
+ return;
+ }
+ $elements.select2({
+ placeholder: t('files_external', 'All users. Type to select user or group.'),
+ allowClear: true,
+ multiple: true,
+ //minimumInputLength: 1,
+ ajax: {
+ url: OC.generateUrl('apps/files_external/applicable'),
+ dataType: 'json',
+ quietMillis: 100,
+ data: function (term, page) { // page is the one-based page number tracked by Select2
+ return {
+ pattern: term, //search term
+ limit: userListLimit, // page size
+ offset: userListLimit*(page-1) // page number starts with 0
+ };
+ },
+ results: function (data) {
+ if (data.status === 'success') {
+
+ var results = [];
+ var userCount = 0; // users is an object
+
+ // add groups
+ $.each(data.groups, function(i, group) {
+ results.push({name:group+'(group)', displayname:group, type:'group' });
+ });
+ // add users
+ $.each(data.users, function(id, user) {
+ userCount++;
+ results.push({name:id, displayname:user, type:'user' });
+ });
+
+
+ var more = (userCount >= userListLimit) || (data.groups.length >= userListLimit);
+ return {results: results, more: more};
} else {
- classOptions[$(input).data('parameter')] = false;
+ //FIXME add error handling
}
- } else {
- classOptions[$(input).data('parameter')] = $(input).val();
}
- });
- if ($('#externalStorage').data('admin') === true) {
- var multiselect = getSelection($tr);
- }
- if (addMountPoint) {
- var status = false;
- if ($('#externalStorage').data('admin') === true) {
- var isPersonal = false;
- var oldGroups = $tr.find('.applicable').data('applicable-groups');
- var oldUsers = $tr.find('.applicable').data('applicable-users');
- var groups = [];
- var users = [];
- $.each(multiselect, function(index, value) {
- var pos = value.indexOf('(group)');
- if (pos != -1) {
- var mountType = 'group';
- var applicable = value.substr(0, pos);
- if ($.inArray(applicable, oldGroups) != -1) {
- oldGroups.splice($.inArray(applicable, oldGroups), 1);
- }
- groups.push(applicable);
- } else {
- var mountType = 'user';
- var applicable = value;
- if ($.inArray(applicable, oldUsers) != -1) {
- oldUsers.splice($.inArray(applicable, oldUsers), 1);
- }
- users.push(applicable);
- }
- statusSpan.addClass('loading-small').removeClass('error success');
- $.ajax({type: 'POST',
- url: OC.filePath('files_external', 'ajax', 'addMountPoint.php'),
- data: {
- mountPoint: mountPoint,
- 'class': backendClass,
- classOptions: classOptions,
- mountType: mountType,
- applicable: applicable,
- isPersonal: isPersonal,
- oldMountPoint: oldMountPoint
- },
- success: function(result) {
- $tr.find('.mountPoint input').data('mountpoint', mountPoint);
- status = updateStatus(statusSpan, result);
- if (callback) {
- callback(status);
- }
- },
- error: function(result){
- status = updateStatus(statusSpan, result);
- if (callback) {
- callback(status);
- }
- }
- });
- });
- $tr.find('.applicable').data('applicable-groups', groups);
- $tr.find('.applicable').data('applicable-users', users);
- var mountType = 'group';
- $.each(oldGroups, function(index, applicable) {
- $.ajax({type: 'POST',
- url: OC.filePath('files_external', 'ajax', 'removeMountPoint.php'),
- data: {
- mountPoint: mountPoint,
- 'class': backendClass,
- classOptions: classOptions,
- mountType: mountType,
- applicable: applicable,
- isPersonal: isPersonal
- }
- });
- });
- var mountType = 'user';
- $.each(oldUsers, function(index, applicable) {
- $.ajax({type: 'POST',
- url: OC.filePath('files_external', 'ajax', 'removeMountPoint.php'),
- data: {
- mountPoint: mountPoint,
- 'class': backendClass,
- classOptions: classOptions,
- mountType: mountType,
- applicable: applicable,
- isPersonal: isPersonal
+ },
+ initSelection: function(element, callback) {
+ var users = {};
+ users['users'] = [];
+ var toSplit = element.val().split(",");
+ for (var i = 0; i < toSplit.length; i++) {
+ users['users'].push(toSplit[i]);
+ }
+
+ $.ajax(OC.generateUrl('displaynames'), {
+ type: 'POST',
+ contentType: 'application/json',
+ data: JSON.stringify(users),
+ dataType: 'json'
+ }).done(function(data) {
+ var results = [];
+ if (data.status === 'success') {
+ $.each(data.users, function(user, displayname) {
+ if (displayname !== false) {
+ results.push({name:user, displayname:displayname, type:'user'});
}
});
- });
+ callback(results);
+ } else {
+ //FIXME add error handling
+ }
+ });
+ },
+ id: function(element) {
+ return element.name;
+ },
+ formatResult: function (element) {
+ var $result = $('<span><div class="avatardiv"/><span>'+escapeHTML(element.displayname)+'</span></span>');
+ var $div = $result.find('.avatardiv')
+ .attr('data-type', element.type)
+ .attr('data-name', element.name)
+ .attr('data-displayname', element.displayname);
+ if (element.type === 'group') {
+ var url = OC.imagePath('core','places/contacts-dark'); // TODO better group icon
+ $div.html('<img width="32" height="32" src="'+url+'">');
+ }
+ return $result.get(0).outerHTML;
+ },
+ formatSelection: function (element) {
+ if (element.type === 'group') {
+ return '<span title="'+escapeHTML(element.name)+'" class="group">'+escapeHTML(element.displayname+' '+t('files_external', '(group)'))+'</span>';
} else {
- var isPersonal = true;
- var mountType = 'user';
- var applicable = OC.currentUser;
- statusSpan.addClass('loading-small').removeClass('error success');
- $.ajax({type: 'POST',
- url: OC.filePath('files_external', 'ajax', 'addMountPoint.php'),
- data: {
- mountPoint: mountPoint,
- 'class': backendClass,
- classOptions: classOptions,
- mountType: mountType,
- applicable: applicable,
- isPersonal: isPersonal,
- oldMountPoint: oldMountPoint
- },
- success: function(result) {
- $tr.find('.mountPoint input').data('mountpoint', mountPoint);
- status = updateStatus(statusSpan, result);
- if (callback) {
- callback(status);
- }
- },
- error: function(result){
- status = updateStatus(statusSpan, result);
- if (callback) {
- callback(status);
- }
- }
- });
+ return '<span title="'+escapeHTML(element.name)+'" class="user">'+escapeHTML(element.displayname)+'</span>';
}
- return status;
- }
- }
+ },
+ escapeMarkup: function (m) { return m; } // we escape the markup in formatResult and formatSelection
+ }).on('select2-loaded', function() {
+ $.each($('.avatardiv'), function(i, div) {
+ var $div = $(div);
+ if ($div.data('type') === 'user') {
+ $div.avatar($div.data('name'),32);
+ }
+ });
+ });
+}
+
+/**
+ * @class OCA.External.Settings.StorageConfig
+ *
+ * @classdesc External storage config
+ */
+var StorageConfig = function(id) {
+ this.id = id;
+ this.backendOptions = {};
+};
+// Keep this in sync with \OC_Mount_Config::STATUS_*
+StorageConfig.Status = {
+ IN_PROGRESS: -1,
+ SUCCESS: 0,
+ ERROR: 1
};
+/**
+ * @memberof OCA.External.Settings
+ */
+StorageConfig.prototype = {
+ _url: null,
-$(document).ready(function() {
- var $externalStorage = $('#externalStorage');
-
- //initialize hidden input field with list of users and groups
- $externalStorage.find('tr:not(#addMountPoint)').each(function(i,tr) {
- var $tr = $(tr);
- var $applicable = $tr.find('.applicable');
- if ($applicable.length > 0) {
- var groups = $applicable.data('applicable-groups');
- var groupsId = [];
- $.each(groups, function () {
- groupsId.push(this + '(group)');
- });
- var users = $applicable.data('applicable-users');
- if (users.indexOf('all') > -1) {
- $tr.find('.applicableUsers').val('');
- } else {
- $tr.find('.applicableUsers').val(groupsId.concat(users).join(','));
+ /**
+ * Storage id
+ *
+ * @type int
+ */
+ id: null,
+
+ /**
+ * Mount point
+ *
+ * @type string
+ */
+ mountPoint: '',
+
+ /**
+ * Backend class name
+ *
+ * @type string
+ */
+ backendClass: null,
+
+ /**
+ * Backend-specific configuration
+ *
+ * @type Object.<string,object>
+ */
+ backendOptions: null,
+
+ /**
+ * Creates or saves the storage.
+ *
+ * @param {Function} [options.success] success callback, receives result as argument
+ * @param {Function} [options.error] error callback
+ */
+ save: function(options) {
+ var self = this;
+ var url = OC.generateUrl(this._url);
+ var method = 'POST';
+ if (_.isNumber(this.id)) {
+ method = 'PUT';
+ url = OC.generateUrl(this._url + '/{id}', {id: this.id});
+ }
+
+ $.ajax({
+ type: method,
+ url: url,
+ data: this.getData(),
+ success: function(result) {
+ self.id = result.id;
+ if (_.isFunction(options.success)) {
+ options.success(result);
+ }
+ },
+ error: options.error
+ });
+ },
+
+ /**
+ * Returns the data from this object
+ *
+ * @return {Array} JSON array of the data
+ */
+ getData: function() {
+ var data = {
+ mountPoint: this.mountPoint,
+ backendClass: this.backendClass,
+ backendOptions: this.backendOptions
+ };
+ if (this.id) {
+ data.id = this.id;
+ }
+ return data;
+ },
+
+ /**
+ * Recheck the storage
+ *
+ * @param {Function} [options.success] success callback, receives result as argument
+ * @param {Function} [options.error] error callback
+ */
+ recheck: function(options) {
+ if (!_.isNumber(this.id)) {
+ if (_.isFunction(options.error)) {
+ options.error();
}
+ return;
}
- });
+ $.ajax({
+ type: 'GET',
+ url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
+ success: options.success,
+ error: options.error
+ });
+ },
- var userListLimit = 30;
- function addSelect2 ($elements) {
- if ($elements.length > 0) {
- $elements.select2({
- placeholder: t('files_external', 'All users. Type to select user or group.'),
- allowClear: true,
- multiple: true,
- //minimumInputLength: 1,
- ajax: {
- url: OC.generateUrl('apps/files_external/applicable'),
- dataType: 'json',
- quietMillis: 100,
- data: function (term, page) { // page is the one-based page number tracked by Select2
- return {
- pattern: term, //search term
- limit: userListLimit, // page size
- offset: userListLimit*(page-1) // page number starts with 0
- };
- },
- results: function (data, page) {
- if (data.status === 'success') {
-
- var results = [];
- var userCount = 0; // users is an object
-
- // add groups
- $.each(data.groups, function(i, group) {
- results.push({name:group+'(group)', displayname:group, type:'group' });
- });
- // add users
- $.each(data.users, function(id, user) {
- userCount++;
- results.push({name:id, displayname:user, type:'user' });
- });
-
-
- var more = (userCount >= userListLimit) || (data.groups.length >= userListLimit);
- return {results: results, more: more};
- } else {
- //FIXME add error handling
- }
- }
- },
- initSelection: function(element, callback) {
- var users = {};
- users['users'] = [];
- var toSplit = element.val().split(",");
- for (var i = 0; i < toSplit.length; i++) {
- users['users'].push(toSplit[i]);
- }
+ /**
+ * Deletes the storage
+ *
+ * @param {Function} [options.success] success callback
+ * @param {Function} [options.error] error callback
+ */
+ destroy: function(options) {
+ if (!_.isNumber(this.id)) {
+ // the storage hasn't even been created => success
+ if (_.isFunction(options.success)) {
+ options.success();
+ }
+ return;
+ }
+ var self = this;
+ $.ajax({
+ type: 'DELETE',
+ url: OC.generateUrl(this._url + '/{id}', {id: this.id}),
+ success: options.success,
+ error: options.error
+ });
+ },
- $.ajax(OC.generateUrl('displaynames'), {
- type: 'POST',
- contentType: 'application/json',
- data: JSON.stringify(users),
- dataType: 'json'
- }).done(function(data) {
- var results = [];
- if (data.status === 'success') {
- $.each(data.users, function(user, displayname) {
- if (displayname !== false) {
- results.push({name:user, displayname:displayname, type:'user'});
- }
- });
- callback(results);
- } else {
- //FIXME add error handling
- }
- });
- },
- id: function(element) {
- return element.name;
- },
- formatResult: function (element) {
- var $result = $('<span><div class="avatardiv"/><span>'+escapeHTML(element.displayname)+'</span></span>');
- var $div = $result.find('.avatardiv')
- .attr('data-type', element.type)
- .attr('data-name', element.name)
- .attr('data-displayname', element.displayname);
- if (element.type === 'group') {
- var url = OC.imagePath('core','places/contacts-dark'); // TODO better group icon
- $div.html('<img width="32" height="32" src="'+url+'">');
- }
- return $result.get(0).outerHTML;
- },
- formatSelection: function (element) {
- if (element.type === 'group') {
- return '<span title="'+escapeHTML(element.name)+'" class="group">'+escapeHTML(element.displayname+' '+t('files_external', '(group)'))+'</span>';
- } else {
- return '<span title="'+escapeHTML(element.name)+'" class="user">'+escapeHTML(element.displayname)+'</span>';
- }
- },
- escapeMarkup: function (m) { return m; } // we escape the markup in formatResult and formatSelection
- }).on('select2-loaded', function() {
- $.each($('.avatardiv'), function(i, div) {
- var $div = $(div);
- if ($div.data('type') === 'user') {
- $div.avatar($div.data('name'),32);
- }
- })
- });
+ /**
+ * Validate this model
+ *
+ * @return {boolean} false if errors exist, true otherwise
+ */
+ validate: function() {
+ if (this.mountPoint === '') {
+ return false;
+ }
+ if (this.errors) {
+ return false;
}
+ return true;
}
- addSelect2($('tr:not(#addMountPoint) .applicableUsers'));
-
- $externalStorage.on('change', '#selectBackend', function() {
- var $tr = $(this).closest('tr');
- $externalStorage.find('tbody').append($tr.clone());
- $externalStorage.find('tbody tr').last().find('.mountPoint input').val('');
- var selected = $(this).find('option:selected').text();
- var backendClass = $(this).val();
+};
+
+/**
+ * @class OCA.External.Settings.GlobalStorageConfig
+ * @augments OCA.External.Settings.StorageConfig
+ *
+ * @classdesc Global external storage config
+ */
+var GlobalStorageConfig = function(id) {
+ this.id = id;
+ this.applicableUsers = [];
+ this.applicableGroups = [];
+};
+/**
+ * @memberOf OCA.External.Settings
+ */
+GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
+ /** @lends OCA.External.Settings.GlobalStorageConfig.prototype */ {
+ _url: 'apps/files_external/globalstorages',
+
+ /**
+ * Applicable users
+ *
+ * @type Array.<string>
+ */
+ applicableUsers: null,
+
+ /**
+ * Applicable groups
+ *
+ * @type Array.<string>
+ */
+ applicableGroups: null,
+
+ /**
+ * Returns the data from this object
+ *
+ * @return {Array} JSON array of the data
+ */
+ getData: function() {
+ var data = StorageConfig.prototype.getData.apply(this, arguments);
+ return _.extend(data, {
+ applicableUsers: this.applicableUsers,
+ applicableGroups: this.applicableGroups,
+ });
+ }
+});
+
+/**
+ * @class OCA.External.Settings.UserStorageConfig
+ * @augments OCA.External.Settings.StorageConfig
+ *
+ * @classdesc User external storage config
+ */
+var UserStorageConfig = function(id) {
+ this.id = id;
+};
+UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype,
+ /** @lends OCA.External.Settings.UserStorageConfig.prototype */ {
+ _url: 'apps/files_external/userstorages'
+});
+
+/**
+ * @class OCA.External.Settings.MountConfigListView
+ *
+ * @classdesc Mount configuration list view
+ *
+ * @param {Object} $el DOM object containing the list
+ * @param {Object} [options]
+ * @param {int} [options.userListLimit] page size in applicable users dropdown
+ */
+var MountConfigListView = function($el, options) {
+ this.initialize($el, options);
+};
+/**
+ * @memberOf OCA.External.Settings
+ */
+MountConfigListView.prototype = {
+
+ /**
+ * jQuery element containing the config list
+ *
+ * @type Object
+ */
+ $el: null,
+
+ /**
+ * Storage config class
+ *
+ * @type Class
+ */
+ _storageConfigClass: null,
+
+ /**
+ * Flag whether the list is about user storage configs (true)
+ * or global storage configs (false)
+ *
+ * @type bool
+ */
+ _isPersonal: false,
+
+ /**
+ * Page size in applicable users dropdown
+ *
+ * @type int
+ */
+ _userListLimit: 30,
+
+ /**
+ * List of supported backends
+ *
+ * @type Object.<string,Object>
+ */
+ _allBackends: null,
+
+ /**
+ * @param {Object} $el DOM object containing the list
+ * @param {Object} [options]
+ * @param {int} [options.userListLimit] page size in applicable users dropdown
+ */
+ initialize: function($el, options) {
+ this.$el = $el;
+ this._isPersonal = ($el.data('admin') !== true);
+ if (this._isPersonal) {
+ this._storageConfigClass = OCA.External.Settings.UserStorageConfig;
+ } else {
+ this._storageConfigClass = OCA.External.Settings.GlobalStorageConfig;
+ }
+
+ if (options && !_.isUndefined(options.userListLimit)) {
+ this._userListLimit = options.userListLimit;
+ }
+
+ // read the backend config that was carefully crammed
+ // into the data-configurations attribute of the select
+ this._allBackends = this.$el.find('.selectBackend').data('configurations');
+
+ //initialize hidden input field with list of users and groups
+ this.$el.find('tr:not(#addMountPoint)').each(function(i,tr) {
+ var $tr = $(tr);
+ var $applicable = $tr.find('.applicable');
+ if ($applicable.length > 0) {
+ var groups = $applicable.data('applicable-groups');
+ var groupsId = [];
+ $.each(groups, function () {
+ groupsId.push(this + '(group)');
+ });
+ var users = $applicable.data('applicable-users');
+ if (users.indexOf('all') > -1 || users === '') {
+ $tr.find('.applicableUsers').val('');
+ } else {
+ $tr.find('.applicableUsers').val(groupsId.concat(users).join(','));
+ }
+ }
+ });
+
+ addSelect2(this.$el.find('tr:not(#addMountPoint) .applicableUsers'), this._userListLimit);
+
+ this._initEvents();
+ },
+
+ /**
+ * Initialize DOM event handlers
+ */
+ _initEvents: function() {
+ var self = this;
+
+ this.$el.on('paste', 'td input', function() {
+ var $me = $(this);
+ var $tr = $me.closest('tr');
+ setTimeout(function() {
+ highlightInput($me);
+ self.saveStorageConfig($tr);
+ }, 20);
+ });
+
+ var timer;
+
+ this.$el.on('keyup', 'td input', function() {
+ clearTimeout(timer);
+ var $tr = $(this).closest('tr');
+ highlightInput($(this));
+ if ($(this).val) {
+ timer = setTimeout(function() {
+ self.saveStorageConfig($tr);
+ }, 2000);
+ }
+ });
+
+ this.$el.on('change', 'td input:checkbox', function() {
+ self.saveStorageConfig($(this).closest('tr'));
+ });
+
+ this.$el.on('change', '.applicable', function() {
+ self.saveStorageConfig($(this).closest('tr'));
+ });
+
+ this.$el.on('click', '.status>span', function() {
+ self.recheckStorageConfig($(this).closest('tr'));
+ });
+
+ this.$el.on('click', 'td.remove>img', function() {
+ self.deleteStorageConfig($(this).closest('tr'));
+ });
+
+ this.$el.on('change', '.selectBackend', _.bind(this._onSelectBackend, this));
+ },
+
+ _onSelectBackend: function(event) {
+ var $target = $(event.target);
+ var $el = this.$el;
+ var $tr = $target.closest('tr');
+ $el.find('tbody').append($tr.clone());
+ $el.find('tbody tr').last().find('.mountPoint input').val('');
+ var selected = $target.find('option:selected').text();
+ var backendClass = $target.val();
$tr.find('.backend').text(selected);
if ($tr.find('.mountPoint input').val() === '') {
- $tr.find('.mountPoint input').val(suggestMountPoint(selected));
+ $tr.find('.mountPoint input').val(this._suggestMountPoint(selected));
}
$tr.addClass(backendClass);
$tr.find('.status').append('<span></span>');
$tr.find('.backend').data('class', backendClass);
- var configurations = $(this).data('configurations');
+ var configurations = this._allBackends;
var $td = $tr.find('td.configuration');
$.each(configurations, function(backend, parameters) {
if (backend === backendClass) {
@@ -347,7 +546,7 @@ $(document).ready(function() {
highlightInput(newElement);
$td.append(newElement);
});
- if (parameters['custom'] && $externalStorage.find('tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length === 1) {
+ if (parameters['custom'] && $el.find('tbody tr.'+backendClass.replace(/\\/g, '\\\\')).length === 1) {
OC.addScript('files_external', parameters['custom']);
}
$td.children().not('[type=hidden]').first().focus();
@@ -357,11 +556,190 @@ $(document).ready(function() {
$tr.find('td').last().attr('class', 'remove');
$tr.find('td').last().removeAttr('style');
$tr.removeAttr('id');
- $(this).remove();
- addSelect2($tr.find('.applicableUsers'));
- });
+ $target.remove();
+ addSelect2($tr.find('.applicableUsers'), this._userListLimit);
+ },
+
+ /**
+ * Gets the storage model from the given row
+ *
+ * @param $tr row element
+ * @return {OCA.External.StorageConfig} storage model instance
+ */
+ getStorageConfig: function($tr) {
+ var storageId = parseInt($tr.attr('data-id'), 10);
+ if (!storageId) {
+ // new entry
+ storageId = null;
+ }
+ var storage = new this._storageConfigClass(storageId);
+ storage.mountPoint = $tr.find('.mountPoint input').val();
+ storage.backendClass = $tr.find('.backend').data('class');
+
+ var classOptions = {};
+ var configuration = $tr.find('.configuration input');
+ var missingOptions = [];
+ $.each(configuration, function(index, input) {
+ var $input = $(input);
+ var parameter = $input.data('parameter');
+ if ($input.attr('type') === 'button') {
+ return;
+ }
+ if ($input.val() === '' && !$input.hasClass('optional')) {
+ missingOptions.push(parameter);
+ return;
+ }
+ if ($(input).is(':checkbox')) {
+ if ($(input).is(':checked')) {
+ classOptions[parameter] = true;
+ } else {
+ classOptions[parameter] = false;
+ }
+ } else {
+ classOptions[parameter] = $(input).val();
+ }
+ });
+
+ storage.backendOptions = classOptions;
+ if (missingOptions.length) {
+ storage.errors = {
+ backendOptions: missingOptions
+ };
+ }
+
+ // gather selected users and groups
+ if (!this._isPersonal) {
+ var groups = [];
+ var users = [];
+ var multiselect = getSelection($tr);
+ $.each(multiselect, function(index, value) {
+ var pos = value.indexOf('(group)');
+ if (pos !== -1) {
+ groups.push(value.substr(0, pos));
+ } else {
+ users.push(value);
+ }
+ });
+ // FIXME: this should be done in the multiselect change event instead
+ $tr.find('.applicable')
+ .data('applicable-groups', groups)
+ .data('applicable-users', users);
+
+ storage.applicableUsers = users;
+ storage.applicableGroups = groups;
+ }
+
+ return storage;
+ },
- function suggestMountPoint(defaultMountPoint) {
+ /**
+ * Deletes the storage from the given tr
+ *
+ * @param $tr storage row
+ * @param Function callback callback to call after save
+ */
+ deleteStorageConfig: function($tr) {
+ var self = this;
+ var configId = $tr.data('id');
+ if (!_.isNumber(configId)) {
+ // deleting unsaved storage
+ $tr.remove();
+ return;
+ }
+ var storage = new this._storageConfigClass(configId);
+ this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
+
+ storage.destroy({
+ success: function() {
+ $tr.remove();
+ },
+ error: function() {
+ self.updateStatus($tr, StorageConfig.Status.ERROR);
+ }
+ });
+ },
+
+ /**
+ * Saves the storage from the given tr
+ *
+ * @param $tr storage row
+ * @param Function callback callback to call after save
+ */
+ saveStorageConfig:function($tr, callback) {
+ var self = this;
+ var storage = this.getStorageConfig($tr);
+ if (!storage.validate()) {
+ return false;
+ }
+
+ this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
+ storage.save({
+ success: function(result) {
+ self.updateStatus($tr, result.status);
+ $tr.attr('data-id', result.id);
+
+ if (_.isFunction(callback)) {
+ callback(storage);
+ }
+ },
+ error: function() {
+ self.updateStatus($tr, StorageConfig.Status.ERROR);
+ }
+ });
+ },
+
+ /**
+ * Recheck storage availability
+ *
+ * @param {jQuery} $tr storage row
+ * @return {boolean} success
+ */
+ recheckStorageConfig: function($tr) {
+ var self = this;
+ var storage = this.getStorageConfig($tr);
+ if (!storage.validate()) {
+ return false;
+ }
+
+ this.updateStatus($tr, StorageConfig.Status.IN_PROGRESS);
+ storage.recheck({
+ success: function(result) {
+ self.updateStatus($tr, result.status);
+ },
+ error: function() {
+ self.updateStatus($tr, StorageConfig.Status.ERROR);
+ }
+ });
+ },
+
+ /**
+ * Update status display
+ *
+ * @param {jQuery} $tr
+ * @param {int} status
+ */
+ updateStatus: function($tr, status) {
+ var $statusSpan = $tr.find('.status span');
+ $statusSpan.removeClass('success error loading-small');
+ switch (status) {
+ case StorageConfig.Status.IN_PROGRESS:
+ $statusSpan.addClass('loading-small');
+ break;
+ case StorageConfig.Status.SUCCESS:
+ $statusSpan.addClass('success');
+ break;
+ default:
+ $statusSpan.addClass('error');
+ }
+ },
+
+ /**
+ * Suggest mount point name that doesn't conflict with the existing names in the list
+ *
+ * @param {string} defaultMountPoint default name
+ */
+ _suggestMountPoint: function(defaultMountPoint) {
+ var $el = this.$el;
var pos = defaultMountPoint.indexOf('/');
if (pos !== -1) {
defaultMountPoint = defaultMountPoint.substring(0, pos);
@@ -372,7 +750,7 @@ $(document).ready(function() {
var match = true;
while (match && i < 20) {
match = false;
- $externalStorage.find('tbody td.mountPoint input').each(function(index, mountPoint) {
+ $el.find('tbody td.mountPoint input').each(function(index, mountPoint) {
if ($(mountPoint).val() === defaultMountPoint+append) {
match = true;
return false;
@@ -385,42 +763,12 @@ $(document).ready(function() {
break;
}
}
- return defaultMountPoint+append;
+ return defaultMountPoint + append;
}
+};
- $externalStorage.on('paste', 'td input', function() {
- var $me = $(this);
- var $tr = $me.closest('tr');
- setTimeout(function() {
- highlightInput($me);
- OC.MountConfig.saveStorage($tr);
- }, 20);
- });
-
- var timer;
-
- $externalStorage.on('keyup', 'td input', function() {
- clearTimeout(timer);
- var $tr = $(this).closest('tr');
- highlightInput($(this));
- if ($(this).val) {
- timer = setTimeout(function() {
- OC.MountConfig.saveStorage($tr);
- }, 2000);
- }
- });
-
- $externalStorage.on('change', 'td input:checkbox', function() {
- OC.MountConfig.saveStorage($(this).closest('tr'));
- });
-
- $externalStorage.on('change', '.applicable', function() {
- OC.MountConfig.saveStorage($(this).closest('tr'));
- });
-
- $externalStorage.on('click', '.status>span', function() {
- OC.MountConfig.saveStorage($(this).closest('tr'));
- });
+$(document).ready(function() {
+ var mountConfigListView = new MountConfigListView($('#externalStorage'));
$('#sslCertificate').on('click', 'td.remove>img', function() {
var $tr = $(this).closest('tr');
@@ -429,33 +777,7 @@ $(document).ready(function() {
return true;
});
- $externalStorage.on('click', 'td.remove>img', function() {
- var $tr = $(this).closest('tr');
- var mountPoint = $tr.find('.mountPoint input').val();
-
- if ($externalStorage.data('admin') === true) {
- var isPersonal = false;
- var multiselect = getSelection($tr);
- $.each(multiselect, function(index, value) {
- var pos = value.indexOf('(group)');
- if (pos != -1) {
- var mountType = 'group';
- var applicable = value.substr(0, pos);
- } else {
- var mountType = 'user';
- var applicable = value;
- }
- $.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
- });
- } else {
- var mountType = 'user';
- var applicable = OC.currentUser;
- var isPersonal = true;
- $.post(OC.filePath('files_external', 'ajax', 'removeMountPoint.php'), { mountPoint: mountPoint, mountType: mountType, applicable: applicable, isPersonal: isPersonal });
- }
- $tr.remove();
- });
-
+ // TODO: move this into its own View class
var $allowUserMounting = $('#allowUserMounting');
$allowUserMounting.bind('change', function() {
OC.msg.startSaving('#userMountingMsg');
@@ -484,6 +806,31 @@ $(document).ready(function() {
}
});
+
+ // global instance
+ OCA.External.Settings.mountConfig = mountConfigListView;
+
+ /**
+ * Legacy
+ *
+ * @namespace
+ * @deprecated use OCA.External.Settings.mountConfig instead
+ */
+ OC.MountConfig = {
+ saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView)
+ };
});
+// export
+
+OCA.External = OCA.External || {};
+/**
+ * @namespace
+ */
+OCA.External.Settings = OCA.External.Settings || {};
+
+OCA.External.Settings.GlobalStorageConfig = GlobalStorageConfig;
+OCA.External.Settings.UserStorageConfig = UserStorageConfig;
+OCA.External.Settings.MountConfigListView = MountConfigListView;
+
})();
diff --git a/apps/files_external/js/sftp_key.js b/apps/files_external/js/sftp_key.js
index 2b39628247c..55b11b1fac9 100644
--- a/apps/files_external/js/sftp_key.js
+++ b/apps/files_external/js/sftp_key.js
@@ -42,7 +42,7 @@ $(document).ready(function() {
if (result && result.status === 'success') {
$(config).find('[data-parameter="public_key"]').val(result.data.public_key);
$(config).find('[data-parameter="private_key"]').val(result.data.private_key);
- OC.MountConfig.saveStorage(tr, function() {
+ OCA.External.mountConfig.saveStorageConfig(tr, function() {
// Nothing to do
});
} else {
diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php
index ddfab439879..deeedb98551 100644
--- a/apps/files_external/lib/config.php
+++ b/apps/files_external/lib/config.php
@@ -32,6 +32,10 @@ class OC_Mount_Config {
const MOUNT_TYPE_USER = 'user';
const MOUNT_TYPE_PERSONAL = 'personal';
+ // getBackendStatus return types
+ const STATUS_SUCCESS = 0;
+ const STATUS_ERROR = 1;
+
// whether to skip backend test (for unit tests, as this static class is not mockable)
public static $skipTest = false;
@@ -143,15 +147,9 @@ class OC_Mount_Config {
$mountPoints = array();
$datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data");
- $mount_file = \OC_Config::getValue("mount_file", $datadir . "/mount.json");
$backends = self::getBackends();
- //move config file to it's new position
- if (is_file(\OC::$SERVERROOT . '/config/mount.json')) {
- rename(\OC::$SERVERROOT . '/config/mount.json', $mount_file);
- }
-
// Load system mount points
$mountConfig = self::readData();
@@ -349,6 +347,8 @@ class OC_Mount_Config {
$mountPoint = substr($mountPoint, 13);
$config = array(
+ 'id' => (int) $mount['id'],
+ 'storage_id' => (int) $mount['storage_id'],
'class' => $mount['class'],
'mountpoint' => $mountPoint,
'backend' => $backends[$mount['class']]['backend'],
@@ -383,6 +383,8 @@ class OC_Mount_Config {
// Remove '/$user/files/' from mount point
$mountPoint = substr($mountPoint, 13);
$config = array(
+ 'id' => (int) $mount['id'],
+ 'storage_id' => (int) $mount['storage_id'],
'class' => $mount['class'],
'mountpoint' => $mountPoint,
'backend' => $backends[$mount['class']]['backend'],
@@ -425,6 +427,8 @@ class OC_Mount_Config {
}
$mount['options'] = self::decryptPasswords($mount['options']);
$personal[] = array(
+ 'id' => (int) $mount['id'],
+ 'storage_id' => (int) $mount['storage_id'],
'class' => $mount['class'],
// Remove '/uid/files/' from mount point
'mountpoint' => substr($mountPoint, strlen($uid) + 8),
@@ -442,11 +446,11 @@ class OC_Mount_Config {
*
* @param string $class backend class name
* @param array $options backend configuration options
- * @return bool true if the connection succeeded, false otherwise
+ * @return int see self::STATUS_*
*/
- private static function getBackendStatus($class, $options, $isPersonal) {
+ public static function getBackendStatus($class, $options, $isPersonal) {
if (self::$skipTest) {
- return true;
+ return self::STATUS_SUCCESS;
}
foreach ($options as &$option) {
$option = self::setUserVars(OCP\User::getUser(), $option);
@@ -454,13 +458,14 @@ class OC_Mount_Config {
if (class_exists($class)) {
try {
$storage = new $class($options);
- return $storage->test($isPersonal);
+ if ($storage->test($isPersonal)) {
+ return self::STATUS_SUCCESS;
+ }
} catch (Exception $exception) {
\OCP\Util::logException('files_external', $exception);
- return false;
}
}
- return false;
+ return self::STATUS_ERROR;
}
/**
@@ -474,6 +479,8 @@ class OC_Mount_Config {
* @param bool $isPersonal Personal or system mount point i.e. is this being called from the personal or admin page
* @param int|null $priority Mount point priority, null for default
* @return boolean
+ *
+ * @deprecated use StoragesService#addStorage() instead
*/
public static function addMountPoint($mountPoint,
$class,
@@ -537,7 +544,7 @@ class OC_Mount_Config {
self::writeData($isPersonal ? OCP\User::getUser() : null, $mountPoints);
$result = self::getBackendStatus($class, $classOptions, $isPersonal);
- if ($result && $isNew) {
+ if ($result === self::STATUS_SUCCESS && $isNew) {
\OC_Hook::emit(
\OC\Files\Filesystem::CLASSNAME,
\OC\Files\Filesystem::signal_create_mount,
@@ -558,6 +565,8 @@ class OC_Mount_Config {
* @param string $applicable User or group to remove mount from
* @param bool $isPersonal Personal or system mount point
* @return bool
+ *
+ * @deprecated use StoragesService#removeStorage() instead
*/
public static function removeMountPoint($mountPoint, $mountType, $applicable, $isPersonal = false) {
// Verify that the mount point applies for the current user
@@ -622,13 +631,10 @@ class OC_Mount_Config {
* @param string|null $user If not null, personal for $user, otherwise system
* @return array
*/
- private static function readData($user = null) {
- $parser = new \OC\ArrayParser();
+ public static function readData($user = null) {
if (isset($user)) {
- $phpFile = OC_User::getHome($user) . '/mount.php';
$jsonFile = OC_User::getHome($user) . '/mount.json';
} else {
- $phpFile = OC::$SERVERROOT . '/config/mount.php';
$datadir = \OC_Config::getValue('datadirectory', \OC::$SERVERROOT . '/data/');
$jsonFile = \OC_Config::getValue('mount_file', $datadir . '/mount.json');
}
@@ -637,11 +643,6 @@ class OC_Mount_Config {
if (is_array($mountPoints)) {
return $mountPoints;
}
- } elseif (is_file($phpFile)) {
- $mountPoints = $parser->parsePHP(file_get_contents($phpFile));
- if (is_array($mountPoints)) {
- return $mountPoints;
- }
}
return array();
}
@@ -652,7 +653,7 @@ class OC_Mount_Config {
* @param string|null $user If not null, personal for $user, otherwise system
* @param array $data Mount points
*/
- private static function writeData($user, $data) {
+ public static function writeData($user, $data) {
if (isset($user)) {
$file = OC_User::getHome($user) . '/mount.json';
} else {
@@ -769,7 +770,7 @@ class OC_Mount_Config {
* @param array $options mount options
* @return array updated options
*/
- private static function encryptPasswords($options) {
+ public static function encryptPasswords($options) {
if (isset($options['password'])) {
$options['password_encrypted'] = self::encryptPassword($options['password']);
// do not unset the password, we want to keep the keys order
@@ -785,7 +786,7 @@ class OC_Mount_Config {
* @param array $options mount options
* @return array updated options
*/
- private static function decryptPasswords($options) {
+ public static function decryptPasswords($options) {
// note: legacy options might still have the unencrypted password in the "password" field
if (isset($options['password_encrypted'])) {
$options['password'] = self::decryptPassword($options['password_encrypted']);
diff --git a/apps/files_external/lib/notfoundexception.php b/apps/files_external/lib/notfoundexception.php
new file mode 100644
index 00000000000..d1d15309d5b
--- /dev/null
+++ b/apps/files_external/lib/notfoundexception.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_external;
+
+/**
+ * Storage is not found
+ */
+class NotFoundException extends \Exception {
+}
diff --git a/apps/files_external/lib/storageconfig.php b/apps/files_external/lib/storageconfig.php
new file mode 100644
index 00000000000..f23b5cd86a9
--- /dev/null
+++ b/apps/files_external/lib/storageconfig.php
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_external\Lib;
+
+/**
+ * External storage configuration
+ */
+class StorageConfig implements \JsonSerializable {
+
+ /**
+ * @var int
+ */
+ private $id;
+
+ /**
+ * @var string
+ */
+ private $backendClass;
+
+ /**
+ * @var array
+ */
+ private $backendOptions = [];
+
+ /**
+ * @var string
+ */
+ private $mountPoint;
+
+ /**
+ * @var int
+ */
+ private $status;
+
+ /**
+ * @var int
+ */
+ private $priority;
+
+ /**
+ * @var array
+ */
+ private $applicableUsers = [];
+
+ /**
+ * @var array
+ */
+ private $applicableGroups = [];
+
+ /**
+ * @param int|null $id config id or null for a new config
+ */
+ public function __construct($id = null) {
+ $this->id = $id;
+ }
+
+ /**
+ * Returns the configuration id
+ *
+ * @return int
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+ /**
+ * Sets the configuration id
+ *
+ * @param int configuration id
+ */
+ public function setId($id) {
+ $this->id = $id;
+ }
+
+ /**
+ * Returns mount point path relative to the user's
+ * "files" folder.
+ *
+ * @return string path
+ */
+ public function getMountPoint() {
+ return $this->mountPoint;
+ }
+
+ /**
+ * Sets mount point path relative to the user's
+ * "files" folder.
+ * The path will be normalized.
+ *
+ * @param string path
+ */
+ public function setMountPoint($mountPoint) {
+ $this->mountPoint = \OC\Files\Filesystem::normalizePath($mountPoint);
+ }
+
+ /**
+ * Returns the external storage backend class name
+ *
+ * @return string external storage backend class name
+ */
+ public function getBackendClass() {
+ return $this->backendClass;
+ }
+
+ /**
+ * Sets the external storage backend class name
+ *
+ * @param string external storage backend class name
+ */
+ public function setBackendClass($backendClass) {
+ $this->backendClass = $backendClass;
+ }
+
+ /**
+ * Returns the external storage backend-specific options
+ *
+ * @return array backend options
+ */
+ public function getBackendOptions() {
+ return $this->backendOptions;
+ }
+
+ /**
+ * Sets the external storage backend-specific options
+ *
+ * @param array backend options
+ */
+ public function setBackendOptions($backendOptions) {
+ $this->backendOptions = $backendOptions;
+ }
+
+ /**
+ * Returns the mount priority
+ *
+ * @return int priority
+ */
+ public function getPriority() {
+ return $this->priority;
+ }
+
+ /**
+ * Sets the mount priotity
+ *
+ * @param int priority
+ */
+ public function setPriority($priority) {
+ $this->priority = $priority;
+ }
+
+ /**
+ * Returns the users for which to mount this storage
+ *
+ * @return array applicable users
+ */
+ public function getApplicableUsers() {
+ return $this->applicableUsers;
+ }
+
+ /**
+ * Sets the users for which to mount this storage
+ *
+ * @param array applicable users
+ */
+ public function setApplicableUsers($applicableUsers) {
+ if (is_null($applicableUsers)) {
+ $applicableUsers = [];
+ }
+ $this->applicableUsers = $applicableUsers;
+ }
+
+ /**
+ * Returns the groups for which to mount this storage
+ *
+ * @return array applicable groups
+ */
+ public function getApplicableGroups() {
+ return $this->applicableGroups;
+ }
+
+ /**
+ * Sets the groups for which to mount this storage
+ *
+ * @param array applicable groups
+ */
+ public function setApplicableGroups($applicableGroups) {
+ if (is_null($applicableGroups)) {
+ $applicableGroups = [];
+ }
+ $this->applicableGroups = $applicableGroups;
+ }
+
+ /**
+ * Sets the storage status, whether the config worked last time
+ *
+ * @return int $status status
+ */
+ public function getStatus() {
+ return $this->status;
+ }
+
+ /**
+ * Sets the storage status, whether the config worked last time
+ *
+ * @param int $status status
+ */
+ public function setStatus($status) {
+ $this->status = $status;
+ }
+
+ /**
+ * Serialize config to JSON
+ *
+ * @return array
+ */
+ public function jsonSerialize() {
+ $result = [];
+ if (!is_null($this->id)) {
+ $result['id'] = $this->id;
+ }
+ $result['mountPoint'] = $this->mountPoint;
+ $result['backendClass'] = $this->backendClass;
+ $result['backendOptions'] = $this->backendOptions;
+ if (!is_null($this->priority)) {
+ $result['priority'] = $this->priority;
+ }
+ if (!empty($this->applicableUsers)) {
+ $result['applicableUsers'] = $this->applicableUsers;
+ }
+ if (!empty($this->applicableGroups)) {
+ $result['applicableGroups'] = $this->applicableGroups;
+ }
+ if (!is_null($this->status)) {
+ $result['status'] = $this->status;
+ }
+ return $result;
+ }
+}
diff --git a/apps/files_external/service/globalstoragesservice.php b/apps/files_external/service/globalstoragesservice.php
new file mode 100644
index 00000000000..257c9bd4679
--- /dev/null
+++ b/apps/files_external/service/globalstoragesservice.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_external\Service;
+
+use \OCP\IUserSession;
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+
+/**
+ * Service class to manage global external storages
+ */
+class GlobalStoragesService extends StoragesService {
+
+ /**
+ * Write the storages to the configuration.
+ *
+ * @param string $user user or null for global config
+ * @param array $storages map of storage id to storage config
+ */
+ public function writeConfig($storages) {
+ // let the horror begin
+ $mountPoints = [];
+ foreach ($storages as $storageConfig) {
+ $mountPoint = $storageConfig->getMountPoint();
+ $oldBackendOptions = $storageConfig->getBackendOptions();
+ $storageConfig->setBackendOptions(
+ \OC_Mount_Config::encryptPasswords(
+ $oldBackendOptions
+ )
+ );
+
+ // system mount
+ $rootMountPoint = '/$user/files/' . ltrim($mountPoint, '/');
+
+ $applicableUsers = $storageConfig->getApplicableUsers();
+ $applicableGroups = $storageConfig->getApplicableGroups();
+ foreach ($applicableUsers as $applicable) {
+ $this->addMountPoint(
+ $mountPoints,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $applicable,
+ $rootMountPoint,
+ $storageConfig
+ );
+ }
+
+ foreach ($applicableGroups as $applicable) {
+ $this->addMountPoint(
+ $mountPoints,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $applicable,
+ $rootMountPoint,
+ $storageConfig
+ );
+ }
+
+ // if neither "applicableGroups" or "applicableUsers" were set, use "all" user
+ if (empty($applicableUsers) && empty($applicableGroups)) {
+ $this->addMountPoint(
+ $mountPoints,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'all',
+ $rootMountPoint,
+ $storageConfig
+ );
+ }
+
+ // restore old backend options where the password was not encrypted,
+ // because we don't want to change the state of the original object
+ $storageConfig->setBackendOptions($oldBackendOptions);
+ }
+
+ \OC_Mount_Config::writeData(null, $mountPoints);
+ }
+
+ /**
+ * Triggers $signal for all applicable users of the given
+ * storage
+ *
+ * @param StorageConfig $storage storage data
+ * @param string $signal signal to trigger
+ */
+ protected function triggerHooks(StorageConfig $storage, $signal) {
+ $applicableUsers = $storage->getApplicableUsers();
+ $applicableGroups = $storage->getApplicableGroups();
+ if (empty($applicableUsers) && empty($applicableGroups)) {
+ // raise for user "all"
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ ['all']
+ );
+ return;
+ }
+
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $applicableUsers
+ );
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $applicableGroups
+ );
+ }
+
+ /**
+ * Triggers signal_create_mount or signal_delete_mount to
+ * accomodate for additions/deletions in applicableUsers
+ * and applicableGroups fields.
+ *
+ * @param StorageConfig $oldStorage old storage data
+ * @param StorageConfig $newStorage new storage data
+ */
+ protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage) {
+ // if mount point changed, it's like a deletion + creation
+ if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) {
+ $this->triggerHooks($oldStorage, Filesystem::signal_delete_mount);
+ $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
+ return;
+ }
+
+ $userAdditions = array_diff($newStorage->getApplicableUsers(), $oldStorage->getApplicableUsers());
+ $userDeletions = array_diff($oldStorage->getApplicableUsers(), $newStorage->getApplicableUsers());
+ $groupAdditions = array_diff($newStorage->getApplicableGroups(), $oldStorage->getApplicableGroups());
+ $groupDeletions = array_diff($oldStorage->getApplicableGroups(), $newStorage->getApplicableGroups());
+
+ // if no applicable were set, raise a signal for "all"
+ if (empty($oldStorage->getApplicableUsers()) && empty($oldStorage->getApplicableGroups())) {
+ $this->triggerApplicableHooks(
+ Filesystem::signal_delete_mount,
+ $oldStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ ['all']
+ );
+ }
+
+ // trigger delete for removed users
+ $this->triggerApplicableHooks(
+ Filesystem::signal_delete_mount,
+ $oldStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $userDeletions
+ );
+
+ // trigger delete for removed groups
+ $this->triggerApplicableHooks(
+ Filesystem::signal_delete_mount,
+ $oldStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $groupDeletions
+ );
+
+ // and now add the new users
+ $this->triggerApplicableHooks(
+ Filesystem::signal_create_mount,
+ $newStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $userAdditions
+ );
+
+ // and now add the new groups
+ $this->triggerApplicableHooks(
+ Filesystem::signal_create_mount,
+ $newStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ $groupAdditions
+ );
+
+ // if no applicable, raise a signal for "all"
+ if (empty($newStorage->getApplicableUsers()) && empty($newStorage->getApplicableGroups())) {
+ $this->triggerApplicableHooks(
+ Filesystem::signal_create_mount,
+ $newStorage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ ['all']
+ );
+ }
+ }
+}
diff --git a/apps/files_external/service/storagesservice.php b/apps/files_external/service/storagesservice.php
new file mode 100644
index 00000000000..52188b23a39
--- /dev/null
+++ b/apps/files_external/service/storagesservice.php
@@ -0,0 +1,303 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_external\Service;
+
+use \OCP\IUserSession;
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+
+/**
+ * Service class to manage external storages
+ */
+abstract class StoragesService {
+
+ /**
+ * Read legacy config data
+ *
+ * @return array list of mount configs
+ */
+ protected function readLegacyConfig() {
+ // read global config
+ return \OC_Mount_Config::readData();
+ }
+
+ /**
+ * Read the external storages config
+ *
+ * @return array map of storage id to storage config
+ */
+ protected function readConfig() {
+ $mountPoints = $this->readLegacyConfig();
+
+ /**
+ * Here is the how the horribly messy mount point array looks like
+ * from the mount.json file:
+ *
+ * $storageOptions = $mountPoints[$mountType][$applicable][$mountPath]
+ *
+ * - $mountType is either "user" or "group"
+ * - $applicable is the name of a user or group (or the current user for personal mounts)
+ * - $mountPath is the mount point path (where the storage must be mounted)
+ * - $storageOptions is a map of storage options:
+ * - "priority": storage priority
+ * - "backend": backend class name
+ * - "options": backend-specific options
+ */
+
+ // group by storage id
+ $storages = [];
+ foreach ($mountPoints as $mountType => $applicables) {
+ foreach ($applicables as $applicable => $mountPaths) {
+ foreach ($mountPaths as $rootMountPath => $storageOptions) {
+ // the root mount point is in the format "/$user/files/the/mount/point"
+ // we remove the "/$user/files" prefix
+ $parts = explode('/', trim($rootMountPath, '/'), 3);
+ if (count($parts) < 3) {
+ // something went wrong, skip
+ \OCP\Util::writeLog(
+ 'files_external',
+ 'Could not parse mount point "' . $rootMountPath . '"',
+ \OCP\Util::ERROR
+ );
+ continue;
+ }
+
+ $relativeMountPath = $parts[2];
+
+ $configId = (int)$storageOptions['id'];
+ if (isset($storages[$configId])) {
+ $currentStorage = $storages[$configId];
+ } else {
+ $currentStorage = new StorageConfig($configId);
+ $currentStorage->setMountPoint($relativeMountPath);
+ }
+
+ $currentStorage->setBackendClass($storageOptions['class']);
+ $currentStorage->setBackendOptions($storageOptions['options']);
+ if (isset($storageOptions['priority'])) {
+ $currentStorage->setPriority($storageOptions['priority']);
+ }
+
+ if ($mountType === \OC_Mount_Config::MOUNT_TYPE_USER) {
+ $applicableUsers = $currentStorage->getApplicableUsers();
+ if ($applicable !== 'all') {
+ $applicableUsers[] = $applicable;
+ $currentStorage->setApplicableUsers($applicableUsers);
+ }
+ } else if ($mountType === \OC_Mount_Config::MOUNT_TYPE_GROUP) {
+ $applicableGroups = $currentStorage->getApplicableGroups();
+ $applicableGroups[] = $applicable;
+ $currentStorage->setApplicableGroups($applicableGroups);
+ }
+ $storages[$configId] = $currentStorage;
+ }
+ }
+ }
+
+ // decrypt passwords
+ foreach ($storages as &$storage) {
+ $storage->setBackendOptions(
+ \OC_Mount_Config::decryptPasswords(
+ $storage->getBackendOptions()
+ )
+ );
+ }
+
+ return $storages;
+ }
+
+ /**
+ * Add mount point into the messy mount point structure
+ *
+ * @param array $mountPoints messy array of mount points
+ * @param string $mountType mount type
+ * @param string $applicable single applicable user or group
+ * @param string $rootMountPoint root mount point to use
+ * @param array $storageConfig storage config to set to the mount point
+ */
+ protected function addMountPoint(&$mountPoints, $mountType, $applicable, $rootMountPoint, $storageConfig) {
+ if (!isset($mountPoints[$mountType])) {
+ $mountPoints[$mountType] = [];
+ }
+
+ if (!isset($mountPoints[$mountType][$applicable])) {
+ $mountPoints[$mountType][$applicable] = [];
+ }
+
+ $options = [
+ 'id' => $storageConfig->getId(),
+ 'class' => $storageConfig->getBackendClass(),
+ 'options' => $storageConfig->getBackendOptions(),
+ ];
+
+ if (!is_null($storageConfig->getPriority())) {
+ $options['priority'] = $storageConfig->getPriority();
+ }
+
+ $mountPoints[$mountType][$applicable][$rootMountPoint] = $options;
+ }
+
+ /**
+ * Write the storages to the configuration.
+ *
+ * @param array $storages map of storage id to storage config
+ */
+ abstract protected function writeConfig($storages);
+
+ /**
+ * Get a storage with status
+ *
+ * @param int $id
+ *
+ * @return StorageConfig
+ */
+ public function getStorage($id) {
+ $allStorages = $this->readConfig();
+
+ if (!isset($allStorages[$id])) {
+ throw new NotFoundException('Storage with id "' . $id . '" not found');
+ }
+
+ return $allStorages[$id];
+ }
+
+ /**
+ * Add new storage to the configuration
+ *
+ * @param array $newStorage storage attributes
+ *
+ * @return StorageConfig storage config, with added id
+ */
+ public function addStorage(StorageConfig $newStorage) {
+ $allStorages = $this->readConfig();
+
+ $configId = $this->generateNextId($allStorages);
+ $newStorage->setId($configId);
+
+ // add new storage
+ $allStorages[$configId] = $newStorage;
+
+ $this->writeConfig($allStorages);
+
+ $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
+
+ $newStorage->setStatus(\OC_Mount_Config::STATUS_SUCCESS);
+ return $newStorage;
+ }
+
+ /**
+ * Triggers the given hook signal for all the applicables given
+ *
+ * @param string $signal signal
+ * @param string $mountPoint hook mount pount param
+ * @param string $mountType hook mount type param
+ * @param array $applicableArray array of applicable users/groups for which to trigger the hook
+ */
+ protected function triggerApplicableHooks($signal, $mountPoint, $mountType, $applicableArray) {
+ foreach ($applicableArray as $applicable) {
+ \OC_Hook::emit(
+ Filesystem::CLASSNAME,
+ $signal,
+ [
+ Filesystem::signal_param_path => $mountPoint,
+ Filesystem::signal_param_mount_type => $mountType,
+ Filesystem::signal_param_users => $applicable,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Triggers $signal for all applicable users of the given
+ * storage
+ *
+ * @param StorageConfig $storage storage data
+ * @param string $signal signal to trigger
+ */
+ abstract protected function triggerHooks(StorageConfig $storage, $signal);
+
+ /**
+ * Triggers signal_create_mount or signal_delete_mount to
+ * accomodate for additions/deletions in applicableUsers
+ * and applicableGroups fields.
+ *
+ * @param StorageConfig $oldStorage old storage data
+ * @param StorageConfig $newStorage new storage data
+ */
+ abstract protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage);
+
+ /**
+ * Update storage to the configuration
+ *
+ * @param StorageConfig $updatedStorage storage attributes
+ *
+ * @return StorageConfig storage config
+ * @throws NotFoundException
+ */
+ public function updateStorage(StorageConfig $updatedStorage) {
+ $allStorages = $this->readConfig();
+
+ $id = $updatedStorage->getId();
+ if (!isset($allStorages[$id])) {
+ throw new NotFoundException('Storage with id "' . $id . '" not found');
+ }
+
+ $oldStorage = $allStorages[$id];
+ $allStorages[$id] = $updatedStorage;
+
+ $this->writeConfig($allStorages);
+
+ $this->triggerChangeHooks($oldStorage, $updatedStorage);
+
+ return $this->getStorage($id);
+ }
+
+ /**
+ * Delete the storage with the given id.
+ *
+ * @param int $id storage id
+ *
+ * @throws NotFoundException
+ */
+ public function removeStorage($id) {
+ $allStorages = $this->readConfig();
+
+ if (!isset($allStorages[$id])) {
+ throw new NotFoundException('Storage with id "' . $id . '" not found');
+ }
+
+ $deletedStorage = $allStorages[$id];
+ unset($allStorages[$id]);
+
+ $this->writeConfig($allStorages);
+
+ $this->triggerHooks($deletedStorage, Filesystem::signal_delete_mount);
+ }
+
+ /**
+ * Generates a configuration id to use for a new configuration entry.
+ *
+ * @param array $allStorages array of all storage configs
+ *
+ * @return int id
+ */
+ protected function generateNextId($allStorages) {
+ if (empty($allStorages)) {
+ return 1;
+ }
+ // note: this will mess up with with concurrency,
+ // but so did the mount.json. This horribly hack
+ // will disappear once we move to DB tables to
+ // store the config
+ return max(array_keys($allStorages)) + 1;
+ }
+
+}
diff --git a/apps/files_external/service/userstoragesservice.php b/apps/files_external/service/userstoragesservice.php
new file mode 100644
index 00000000000..fcf579c5d43
--- /dev/null
+++ b/apps/files_external/service/userstoragesservice.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\Files_external\Service;
+
+use \OCP\IUserSession;
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+
+/**
+ * Service class to manage user external storages
+ * (aka personal storages)
+ */
+class UserStoragesService extends StoragesService {
+ /**
+ * @var IUserSession
+ */
+ private $userSession;
+
+ public function __construct(
+ IUserSession $userSession
+ ) {
+ $this->userSession = $userSession;
+ }
+
+ /**
+ * Read legacy config data
+ *
+ * @return array list of storage configs
+ */
+ protected function readLegacyConfig() {
+ // read user config
+ $user = $this->userSession->getUser()->getUID();
+ return \OC_Mount_Config::readData($user);
+ }
+
+ /**
+ * Read the external storages config
+ *
+ * @return array map of storage id to storage config
+ */
+ protected function readConfig() {
+ $user = $this->userSession->getUser()->getUID();
+ // TODO: in the future don't rely on the global config reading code
+ $storages = parent::readConfig();
+
+ $filteredStorages = [];
+ foreach ($storages as $configId => $storage) {
+ // filter out all bogus storages that aren't for the current user
+ if (!in_array($user, $storage->getApplicableUsers())) {
+ continue;
+ }
+
+ // clear applicable users, should not be used
+ $storage->setApplicableUsers([]);
+
+ // strip out unneeded applicableUser fields
+ $filteredStorages[$configId] = $storage;
+ }
+
+ return $filteredStorages;
+ }
+
+ /**
+ * Write the storages to the user's configuration.
+ *
+ * @param array $storages map of storage id to storage config
+ */
+ public function writeConfig($storages) {
+ $user = $this->userSession->getUser()->getUID();
+
+ // let the horror begin
+ $mountPoints = [];
+ foreach ($storages as $storageConfig) {
+ $mountPoint = $storageConfig->getMountPoint();
+ $oldBackendOptions = $storageConfig->getBackendOptions();
+ $storageConfig->setBackendOptions(
+ \OC_Mount_Config::encryptPasswords(
+ $oldBackendOptions
+ )
+ );
+
+ $rootMountPoint = '/' . $user . '/files/' . ltrim($mountPoint, '/');
+
+ $this->addMountPoint(
+ $mountPoints,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $user,
+ $rootMountPoint,
+ $storageConfig
+ );
+
+ // restore old backend options where the password was not encrypted,
+ // because we don't want to change the state of the original object
+ $storageConfig->setBackendOptions($oldBackendOptions);
+ }
+
+ \OC_Mount_Config::writeData($user, $mountPoints);
+ }
+
+ /**
+ * Triggers $signal for all applicable users of the given
+ * storage
+ *
+ * @param StorageConfig $storage storage data
+ * @param string $signal signal to trigger
+ */
+ protected function triggerHooks(StorageConfig $storage, $signal) {
+ $user = $this->userSession->getUser()->getUID();
+
+ // trigger hook for the current user
+ $this->triggerApplicableHooks(
+ $signal,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ [$user]
+ );
+ }
+
+ /**
+ * Triggers signal_create_mount or signal_delete_mount to
+ * accomodate for additions/deletions in applicableUsers
+ * and applicableGroups fields.
+ *
+ * @param StorageConfig $oldStorage old storage data
+ * @param StorageConfig $newStorage new storage data
+ */
+ protected function triggerChangeHooks(StorageConfig $oldStorage, StorageConfig $newStorage) {
+ // if mount point changed, it's like a deletion + creation
+ if ($oldStorage->getMountPoint() !== $newStorage->getMountPoint()) {
+ $this->triggerHooks($oldStorage, Filesystem::signal_delete_mount);
+ $this->triggerHooks($newStorage, Filesystem::signal_create_mount);
+ }
+ }
+}
diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php
index 79950f30385..5f7d7cff752 100644
--- a/apps/files_external/templates/settings.php
+++ b/apps/files_external/templates/settings.php
@@ -13,12 +13,12 @@
</tr>
</thead>
<tbody>
- <?php $_['mounts'] = array_merge($_['mounts'], array('' => array())); ?>
+ <?php $_['mounts'] = array_merge($_['mounts'], array('' => array('id' => ''))); ?>
<?php foreach ($_['mounts'] as $mount): ?>
- <tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?>>
+ <tr <?php print_unescaped(isset($mount['mountpoint']) ? 'class="'.OC_Util::sanitizeHTML($mount['class']).'"' : 'id="addMountPoint"'); ?> data-id="<?php p($mount['id']) ?>">
<td class="status">
<?php if (isset($mount['status'])): ?>
- <span class="<?php p(($mount['status']) ? 'success' : 'error'); ?>"></span>
+ <span class="<?php p(($mount['status'] === \OC_Mount_Config::STATUS_SUCCESS) ? 'success' : 'error'); ?>"></span>
<?php endif; ?>
</td>
<td class="mountPoint"><input type="text" name="mountPoint"
@@ -28,7 +28,7 @@
</td>
<?php if (!isset($mount['mountpoint'])): ?>
<td class="backend">
- <select id="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
+ <select id="selectBackend" class="selectBackend" data-configurations='<?php p(json_encode($_['backends'])); ?>'>
<option value="" disabled selected
style="display:none;"><?php p($l->t('Add storage')); ?></option>
<?php foreach ($_['backends'] as $class => $backend): ?>
diff --git a/apps/files_external/tests/controller/globalstoragescontrollertest.php b/apps/files_external/tests/controller/globalstoragescontrollertest.php
new file mode 100644
index 00000000000..7ba4d16a7e9
--- /dev/null
+++ b/apps/files_external/tests/controller/globalstoragescontrollertest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_external\Tests\Controller;
+
+use \OCA\Files_external\Controller\GlobalStoragesController;
+use \OCA\Files_external\Service\GlobalStoragesService;
+use \OCP\AppFramework\Http;
+use \OCA\Files_external\NotFoundException;
+
+class GlobalStoragesControllerTest extends StoragesControllerTest {
+ public function setUp() {
+ parent::setUp();
+ $this->service = $this->getMock('\OCA\Files_external\Service\GlobalStoragesService');
+
+ $this->controller = new GlobalStoragesController(
+ 'files_external',
+ $this->getMock('\OCP\IRequest'),
+ $this->getMock('\OCP\IL10N'),
+ $this->service
+ );
+ }
+}
diff --git a/apps/files_external/tests/controller/storagescontrollertest.php b/apps/files_external/tests/controller/storagescontrollertest.php
new file mode 100644
index 00000000000..fefe2928d76
--- /dev/null
+++ b/apps/files_external/tests/controller/storagescontrollertest.php
@@ -0,0 +1,217 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_external\Tests\Controller;
+
+use \OCP\AppFramework\Http;
+
+use \OCA\Files_external\Controller\GlobalStoragesController;
+use \OCA\Files_external\Service\GlobalStoragesService;
+use \OCA\Files_external\Lib\StorageConfig;
+use \OCA\Files_external\NotFoundException;
+
+abstract class StoragesControllerTest extends \Test\TestCase {
+
+ /**
+ * @var GlobalStoragesController
+ */
+ protected $controller;
+
+ /**
+ * @var GlobalStoragesService
+ */
+ protected $service;
+
+ public function setUp() {
+ \OC_Mount_Config::$skipTest = true;
+ }
+
+ public function tearDown() {
+ \OC_Mount_Config::$skipTest = false;
+ }
+
+ public function testAddStorage() {
+ $storageConfig = new StorageConfig(1);
+ $storageConfig->setMountPoint('mount');
+
+ $this->service->expects($this->once())
+ ->method('addStorage')
+ ->will($this->returnValue($storageConfig));
+
+ $response = $this->controller->create(
+ 'mount',
+ '\OC\Files\Storage\SMB',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $data = $response->getData();
+ $this->assertEquals($storageConfig, $data);
+ $this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
+ }
+
+ public function testUpdateStorage() {
+ $storageConfig = new StorageConfig(1);
+ $storageConfig->setMountPoint('mount');
+
+ $this->service->expects($this->once())
+ ->method('updateStorage')
+ ->will($this->returnValue($storageConfig));
+
+ $response = $this->controller->update(
+ 1,
+ 'mount',
+ '\OC\Files\Storage\SMB',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $data = $response->getData();
+ $this->assertEquals($storageConfig, $data);
+ $this->assertEquals(Http::STATUS_OK, $response->getStatus());
+ }
+
+ function mountPointNamesProvider() {
+ return array(
+ array(''),
+ array('/'),
+ array('//'),
+ );
+ }
+
+ /**
+ * @dataProvider mountPointNamesProvider
+ */
+ public function testAddOrUpdateStorageInvalidMountPoint($mountPoint) {
+ $this->service->expects($this->never())
+ ->method('addStorage');
+ $this->service->expects($this->never())
+ ->method('updateStorage');
+
+ $response = $this->controller->create(
+ $mountPoint,
+ '\OC\Files\Storage\SMB',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
+
+ $response = $this->controller->update(
+ 1,
+ $mountPoint,
+ '\OC\Files\Storage\SMB',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
+ }
+
+ public function testAddOrUpdateStorageInvalidBackend() {
+ $this->service->expects($this->never())
+ ->method('addStorage');
+ $this->service->expects($this->never())
+ ->method('updateStorage');
+
+ $response = $this->controller->create(
+ 'mount',
+ '\OC\Files\Storage\InvalidStorage',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
+
+ $response = $this->controller->update(
+ 1,
+ 'mount',
+ '\OC\Files\Storage\InvalidStorage',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
+ }
+
+ public function testUpdateStorageNonExisting() {
+ $this->service->expects($this->once())
+ ->method('updateStorage')
+ ->will($this->throwException(new NotFoundException()));
+
+ $response = $this->controller->update(
+ 255,
+ 'mount',
+ '\OC\Files\Storage\SMB',
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
+ }
+
+ public function testDeleteStorage() {
+ $this->service->expects($this->once())
+ ->method('removeStorage');
+
+ $response = $this->controller->destroy(1);
+ $this->assertEquals(Http::STATUS_NO_CONTENT, $response->getStatus());
+ }
+
+ public function testDeleteStorageNonExisting() {
+ $this->service->expects($this->once())
+ ->method('removeStorage')
+ ->will($this->throwException(new NotFoundException()));
+
+ $response = $this->controller->destroy(255);
+ $this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
+ }
+
+ public function testGetStorage() {
+ $storageConfig = new StorageConfig(1);
+ $storageConfig->setMountPoint('test');
+ $storageConfig->setBackendClass('\OC\Files\Storage\SMB');
+ $storageConfig->setBackendOptions(['user' => 'test', 'password', 'password123']);
+
+ $this->service->expects($this->once())
+ ->method('getStorage')
+ ->with(1)
+ ->will($this->returnValue($storageConfig));
+ $response = $this->controller->show(1);
+
+ $this->assertEquals(Http::STATUS_OK, $response->getStatus());
+ $this->assertEquals($storageConfig, $response->getData());
+ }
+}
diff --git a/apps/files_external/tests/controller/userstoragescontrollertest.php b/apps/files_external/tests/controller/userstoragescontrollertest.php
new file mode 100644
index 00000000000..9d6fbb15e23
--- /dev/null
+++ b/apps/files_external/tests/controller/userstoragescontrollertest.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_external\Tests\Controller;
+
+use \OCA\Files_external\Controller\UserStoragesController;
+use \OCA\Files_external\Service\UserStoragesService;
+use \OCP\AppFramework\Http;
+use \OCA\Files_external\NotFoundException;
+
+class UserStoragesControllerTest extends StoragesControllerTest {
+
+ /**
+ * @var array
+ */
+ private $oldAllowedBackends;
+
+ public function setUp() {
+ parent::setUp();
+ $this->service = $this->getMockBuilder('\OCA\Files_external\Service\UserStoragesService')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->controller = new UserStoragesController(
+ 'files_external',
+ $this->getMock('\OCP\IRequest'),
+ $this->getMock('\OCP\IL10N'),
+ $this->service
+ );
+
+ $config = \OC::$server->getConfig();
+
+ $this->oldAllowedBackends = $config->getAppValue(
+ 'files_external',
+ 'user_mounting_backends',
+ ''
+ );
+ $config->setAppValue(
+ 'files_external',
+ 'user_mounting_backends',
+ '\OC\Files\Storage\SMB'
+ );
+ }
+
+ public function tearDown() {
+ $config = \OC::$server->getConfig();
+ $config->setAppValue(
+ 'files_external',
+ 'user_mounting_backends',
+ $this->oldAllowedBackends
+ );
+ parent::tearDown();
+ }
+
+ function disallowedBackendClassProvider() {
+ return array(
+ array('\OC\Files\Storage\Local'),
+ array('\OC\Files\Storage\FTP'),
+ );
+ }
+ /**
+ * @dataProvider disallowedBackendClassProvider
+ */
+ public function testAddOrUpdateStorageDisallowedBackend($backendClass) {
+ $this->service->expects($this->never())
+ ->method('addStorage');
+ $this->service->expects($this->never())
+ ->method('updateStorage');
+
+ $response = $this->controller->create(
+ 'mount',
+ $backendClass,
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
+
+ $response = $this->controller->update(
+ 1,
+ 'mount',
+ $backendClass,
+ array(),
+ [],
+ [],
+ null
+ );
+
+ $this->assertEquals(Http::STATUS_UNPROCESSABLE_ENTITY, $response->getStatus());
+ }
+
+}
diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js
new file mode 100644
index 00000000000..350840e542c
--- /dev/null
+++ b/apps/files_external/tests/js/settingsSpec.js
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('OCA.External.Settings tests', function() {
+ var clock;
+ var select2Stub;
+ var select2ApplicableUsers;
+
+ beforeEach(function() {
+ clock = sinon.useFakeTimers();
+ select2Stub = sinon.stub($.fn, 'select2', function(args) {
+ if (args === 'val') {
+ return select2ApplicableUsers;
+ }
+ return {
+ on: function() {}
+ };
+ });
+
+ // view still requires an existing DOM table
+ $('#testArea').append(
+ '<table id="externalStorage" data-admin="true">' +
+ '<thead></thead>' +
+ '<tbody>' +
+ '<tr id="addMountPoint" data-id="">' +
+ '<td class="status"></td>' +
+ '<td class="mountPoint"><input type="text" name="mountPoint"/></td>' +
+ '<td class="backend">' +
+ '<select class="selectBackend">' +
+ '<option disable selected>Add storage</option>' +
+ '<option value="\\OC\\TestBackend">Test Backend</option>' +
+ '<option value="\\OC\\AnotherTestBackend">Another Test Backend</option>' +
+ '</select>' +
+ '</td>' +
+ '<td class="configuration"></td>' +
+ '<td class="applicable">' +
+ '<input type="hidden" class="applicableUsers">' +
+ '</td>' +
+ '<td><img alt="Delete" title="Delete" class="svg action"/></td>' +
+ '</tr>' +
+ '</tbody>' +
+ '</table>'
+ );
+ // these are usually appended into the data attribute
+ // within the DOM by the server template
+ $('#externalStorage .selectBackend:first').data('configurations', {
+ '\\OC\\TestBackend': {
+ 'backend': 'Test Backend Name',
+ 'configuration': {
+ 'field1': 'Display Name 1',
+ 'field2': '&Display Name 2'
+ }
+ },
+ '\\OC\\AnotherTestBackend': {
+ 'backend': 'Another Test Backend Name',
+ 'configuration': {
+ 'field1': 'Display Name 1',
+ 'field2': '&Display Name 2'
+ }
+ }
+ }
+ );
+ });
+ afterEach(function() {
+ select2Stub.restore();
+ clock.restore();
+ });
+
+ describe('storage configuration', function() {
+ var view;
+
+ function selectBackend(backendName) {
+ view.$el.find('.selectBackend:first').val('\\OC\\TestBackend').trigger('change');
+ }
+
+ beforeEach(function() {
+ var $el = $('#externalStorage');
+ view = new OCA.External.Settings.MountConfigListView($el);
+ });
+ afterEach(function() {
+ view = null;
+ });
+ describe('selecting backend', function() {
+ it('populates the row and creates a new empty one', function() {
+ var $firstRow = view.$el.find('tr:first');
+ selectBackend('\\OC\\TestBackend');
+ expect($firstRow.find('.backend').text()).toEqual('Test Backend');
+ expect($firstRow.find('.selectBackend').length).toEqual(0);
+
+ // TODO: check "remove" button visibility
+
+ // the suggested mount point name
+ expect($firstRow.find('[name=mountPoint]').val()).toEqual('TestBackend');
+
+ // TODO: check that the options have been created
+
+ // TODO: check select2 call on the ".applicableUsers" element
+
+ var $emptyRow = $firstRow.next('tr');
+ expect($emptyRow.length).toEqual(1);
+ expect($emptyRow.find('.selectBackend').length).toEqual(1);
+ expect($emptyRow.find('.applicable select').length).toEqual(0);
+
+ // TODO: check "remove" button visibility
+ });
+ // TODO: test with personal mounts (no applicable fields)
+ // TODO: test suggested mount point logic
+ });
+ describe('saving storages', function() {
+ it('saves storage after editing config', function() {
+ var $tr = view.$el.find('tr:first');
+ selectBackend('\\OC\\TestBackend');
+
+ var $field1 = $tr.find('input[data-parameter=field1]');
+ expect($field1.length).toEqual(1);
+ $field1.val('test');
+ $field1.trigger(new $.Event('keyup', {keyCode: 97}));
+
+ clock.tick(4000);
+
+ expect(fakeServer.requests.length).toEqual(1);
+ var request = fakeServer.requests[0];
+ expect(request.url).toEqual(OC.webroot + '/index.php/apps/files_external/globalstorages');
+ expect(OC.parseQueryString(request.requestBody)).toEqual({
+ backendClass: '\\OC\\TestBackend',
+ 'backendOptions[field1]': 'test',
+ 'backendOptions[field2]': '',
+ mountPoint: 'TestBackend'
+ });
+
+ // TODO: respond and check data-id
+ });
+ // TODO: tests with "applicableUsers" and "applicableGroups"
+ // TODO: test with non-optional config parameters
+ // TODO: test with missing mount point value
+ // TODO: test with personal mounts (no applicable fields)
+ // TODO: test save triggers: paste, keyup, checkbox
+ // TODO: test "custom" field with addScript
+ // TODO: status indicator
+ });
+ describe('update storage', function() {
+ // TODO
+ });
+ describe('delete storage', function() {
+ // TODO
+ });
+ describe('recheck storages', function() {
+ // TODO
+ });
+ });
+ describe('applicable user list', function() {
+ // TODO: test select2 retrieval logic
+ });
+ describe('allow user mounts section', function() {
+ // TODO: test allowUserMounting section
+ });
+});
diff --git a/apps/files_external/tests/service/globalstoragesservicetest.php b/apps/files_external/tests/service/globalstoragesservicetest.php
new file mode 100644
index 00000000000..6286865bf43
--- /dev/null
+++ b/apps/files_external/tests/service/globalstoragesservicetest.php
@@ -0,0 +1,705 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_external\Tests\Service;
+
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Service\GlobalStoragesService;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_external\Lib\StorageConfig;
+
+class GlobalStoragesServiceTest extends StoragesServiceTest {
+ public function setUp() {
+ parent::setUp();
+ $this->service = new GlobalStoragesService();
+ }
+
+ public function tearDown() {
+ @unlink($this->dataDir . '/mount.json');
+ parent::tearDown();
+ }
+
+ protected function makeTestStorageData() {
+ return $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ 'applicableUsers' => [],
+ 'applicableGroups' => [],
+ 'priority' => 15,
+ ]);
+ }
+
+ function storageDataProvider() {
+ return [
+ // all users
+ [
+ $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ 'applicableUsers' => [],
+ 'applicableGroups' => [],
+ 'priority' => 15,
+ ]),
+ ],
+ // some users
+ [
+ $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ 'applicableUsers' => ['user1', 'user2'],
+ 'applicableGroups' => [],
+ 'priority' => 15,
+ ]),
+ ],
+ // some groups
+ [
+ $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ 'applicableUsers' => [],
+ 'applicableGroups' => ['group1', 'group2'],
+ 'priority' => 15,
+ ]),
+ ],
+ // both users and groups
+ [
+ $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ 'applicableUsers' => ['user1', 'user2'],
+ 'applicableGroups' => ['group1', 'group2'],
+ 'priority' => 15,
+ ]),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider storageDataProvider
+ */
+ public function testAddStorage($storage) {
+ $newStorage = $this->service->addStorage($storage);
+
+ $this->assertEquals(1, $newStorage->getId());
+
+
+ $newStorage = $this->service->getStorage(1);
+
+ $this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint());
+ $this->assertEquals($storage->getBackendClass(), $newStorage->getBackendClass());
+ $this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions());
+ $this->assertEquals($storage->getApplicableUsers(), $newStorage->getApplicableUsers());
+ $this->assertEquals($storage->getApplicableGroups(), $newStorage->getApplicableGroups());
+ $this->assertEquals($storage->getPriority(), $newStorage->getPriority());
+ $this->assertEquals(1, $newStorage->getId());
+ $this->assertEquals(0, $newStorage->getStatus());
+
+ // next one gets id 2
+ $nextStorage = $this->service->addStorage($storage);
+ $this->assertEquals(2, $nextStorage->getId());
+ }
+
+ /**
+ * @dataProvider storageDataProvider
+ */
+ public function testUpdateStorage($updatedStorage) {
+ $storage = $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ 'applicableUsers' => [],
+ 'applicableGroups' => [],
+ 'priority' => 15,
+ ]);
+
+ $newStorage = $this->service->addStorage($storage);
+ $this->assertEquals(1, $newStorage->getId());
+
+ $updatedStorage->setId(1);
+
+ $this->service->updateStorage($updatedStorage);
+ $newStorage = $this->service->getStorage(1);
+
+ $this->assertEquals($updatedStorage->getMountPoint(), $newStorage->getMountPoint());
+ $this->assertEquals($updatedStorage->getBackendOptions()['password'], $newStorage->getBackendOptions()['password']);
+ $this->assertEquals($updatedStorage->getApplicableUsers(), $newStorage->getApplicableUsers());
+ $this->assertEquals($updatedStorage->getApplicableGroups(), $newStorage->getApplicableGroups());
+ $this->assertEquals($updatedStorage->getPriority(), $newStorage->getPriority());
+ $this->assertEquals(1, $newStorage->getId());
+ $this->assertEquals(0, $newStorage->getStatus());
+ }
+
+ function hooksAddStorageDataProvider() {
+ return [
+ // applicable all
+ [
+ [],
+ [],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'all'
+ ],
+ ],
+ ],
+ // single user
+ [
+ ['user1'],
+ [],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ ],
+ ],
+ // single group
+ [
+ [],
+ ['group1'],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1',
+ ],
+ ],
+ ],
+ // multiple users
+ [
+ ['user1', 'user2'],
+ [],
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ ],
+ ],
+ // multiple groups
+ [
+ [],
+ ['group1', 'group2'],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1'
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2'
+ ],
+ ],
+ ],
+ // mixed groups and users
+ [
+ ['user1', 'user2'],
+ ['group1', 'group2'],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1'
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2'
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider hooksAddStorageDataProvider
+ */
+ public function testHooksAddStorage($applicableUsers, $applicableGroups, $expectedCalls) {
+ $storage = $this->makeTestStorageData();
+ $storage->setApplicableUsers($applicableUsers);
+ $storage->setApplicableGroups($applicableGroups);
+ $this->service->addStorage($storage);
+
+ $this->assertCount(count($expectedCalls), self::$hookCalls);
+
+ foreach ($expectedCalls as $index => $call) {
+ $this->assertHookCall(
+ self::$hookCalls[$index],
+ $call[0],
+ $storage->getMountPoint(),
+ $call[1],
+ $call[2]
+ );
+ }
+ }
+
+ function hooksUpdateStorageDataProvider() {
+ return [
+ [
+ // nothing to multiple users and groups
+ [],
+ [],
+ ['user1', 'user2'],
+ ['group1', 'group2'],
+ // expected hook calls
+ [
+ // delete the "all entry"
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'all',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1'
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2'
+ ],
+ ],
+ ],
+ [
+ // adding a user and a group
+ ['user1'],
+ ['group1'],
+ ['user1', 'user2'],
+ ['group1', 'group2'],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2'
+ ],
+ ],
+ ],
+ [
+ // removing a user and a group
+ ['user1', 'user2'],
+ ['group1', 'group2'],
+ ['user1'],
+ ['group1'],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2'
+ ],
+ ],
+ ],
+ [
+ // removing all
+ ['user1'],
+ ['group1'],
+ [],
+ [],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1'
+ ],
+ // create the "all" entry
+ [
+ Filesystem::signal_create_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'all'
+ ],
+ ],
+ ],
+ [
+ // no changes
+ ['user1'],
+ ['group1'],
+ ['user1'],
+ ['group1'],
+ // no hook calls
+ []
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider hooksUpdateStorageDataProvider
+ */
+ public function testHooksUpdateStorage(
+ $sourceApplicableUsers,
+ $sourceApplicableGroups,
+ $updatedApplicableUsers,
+ $updatedApplicableGroups,
+ $expectedCalls) {
+
+ $storage = $this->makeTestStorageData();
+ $storage->setApplicableUsers($sourceApplicableUsers);
+ $storage->setApplicableGroups($sourceApplicableGroups);
+ $storage = $this->service->addStorage($storage);
+
+ $storage->setapplicableUsers($updatedApplicableUsers);
+ $storage->setapplicableGroups($updatedApplicableGroups);
+
+ // reset calls
+ self::$hookCalls = [];
+
+ $this->service->updateStorage($storage);
+
+ $this->assertCount(count($expectedCalls), self::$hookCalls);
+
+ foreach ($expectedCalls as $index => $call) {
+ $this->assertHookCall(
+ self::$hookCalls[$index],
+ $call[0],
+ '/mountpoint',
+ $call[1],
+ $call[2]
+ );
+ }
+ }
+
+ /**
+ */
+ public function testHooksRenameMountPoint() {
+ $storage = $this->makeTestStorageData();
+ $storage->setApplicableUsers(['user1', 'user2']);
+ $storage->setApplicableGroups(['group1', 'group2']);
+ $storage = $this->service->addStorage($storage);
+
+ $storage->setMountPoint('renamedMountpoint');
+
+ // reset calls
+ self::$hookCalls = [];
+
+ $this->service->updateStorage($storage);
+
+ $expectedCalls = [
+ // deletes old mount
+ [
+ Filesystem::signal_delete_mount,
+ '/mountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ '/mountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ '/mountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ '/mountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2',
+ ],
+ // creates new one
+ [
+ Filesystem::signal_create_mount,
+ '/renamedMountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ '/renamedMountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ '/renamedMountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1',
+ ],
+ [
+ Filesystem::signal_create_mount,
+ '/renamedMountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2',
+ ],
+ ];
+
+ $this->assertCount(count($expectedCalls), self::$hookCalls);
+
+ foreach ($expectedCalls as $index => $call) {
+ $this->assertHookCall(
+ self::$hookCalls[$index],
+ $call[0],
+ $call[1],
+ $call[2],
+ $call[3]
+ );
+ }
+ }
+
+ function hooksDeleteStorageDataProvider() {
+ return [
+ [
+ ['user1', 'user2'],
+ ['group1', 'group2'],
+ // expected hook calls
+ [
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user1',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'user2',
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group1'
+ ],
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_GROUP,
+ 'group2'
+ ],
+ ],
+ ],
+ [
+ // deleting "all" entry
+ [],
+ [],
+ [
+ [
+ Filesystem::signal_delete_mount,
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ 'all',
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider hooksDeleteStorageDataProvider
+ */
+ public function testHooksDeleteStorage(
+ $sourceApplicableUsers,
+ $sourceApplicableGroups,
+ $expectedCalls) {
+
+ $storage = $this->makeTestStorageData();
+ $storage->setApplicableUsers($sourceApplicableUsers);
+ $storage->setApplicableGroups($sourceApplicableGroups);
+ $storage = $this->service->addStorage($storage);
+
+ // reset calls
+ self::$hookCalls = [];
+
+ $this->service->removeStorage($storage->getId());
+
+ $this->assertCount(count($expectedCalls), self::$hookCalls);
+
+ foreach ($expectedCalls as $index => $call) {
+ $this->assertHookCall(
+ self::$hookCalls[$index],
+ $call[0],
+ '/mountpoint',
+ $call[1],
+ $call[2]
+ );
+ }
+ }
+
+ /**
+ * Make sure it uses the correct format when reading/writing
+ * the legacy config
+ */
+ public function testLegacyConfigConversionApplicableAll() {
+ $configFile = $this->dataDir . '/mount.json';
+
+ $storage = $this->makeTestStorageData();
+ $storage = $this->service->addStorage($storage);
+
+ $json = json_decode(file_get_contents($configFile), true);
+
+ $this->assertCount(1, $json);
+
+ $this->assertEquals([\OC_Mount_Config::MOUNT_TYPE_USER], array_keys($json));
+ $this->assertEquals(['all'], array_keys($json[\OC_Mount_config::MOUNT_TYPE_USER]));
+
+ $mountPointData = $json[\OC_Mount_config::MOUNT_TYPE_USER]['all'];
+ $this->assertEquals(['/$user/files/mountpoint'], array_keys($mountPointData));
+
+ $mountPointOptions = current($mountPointData);
+ $this->assertEquals(1, $mountPointOptions['id']);
+ $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
+ $this->assertEquals(15, $mountPointOptions['priority']);
+
+ $backendOptions = $mountPointOptions['options'];
+ $this->assertEquals('value1', $backendOptions['option1']);
+ $this->assertEquals('value2', $backendOptions['option2']);
+ $this->assertEquals('', $backendOptions['password']);
+ $this->assertNotEmpty($backendOptions['password_encrypted']);
+ }
+
+ /**
+ * Make sure it uses the correct format when reading/writing
+ * the legacy config
+ */
+ public function testLegacyConfigConversionApplicableUserAndGroup() {
+ $configFile = $this->dataDir . '/mount.json';
+
+ $storage = $this->makeTestStorageData();
+ $storage->setApplicableUsers(['user1', 'user2']);
+ $storage->setApplicableGroups(['group1', 'group2']);
+
+ $storage = $this->service->addStorage($storage);
+
+ $json = json_decode(file_get_contents($configFile), true);
+
+ $this->assertCount(2, $json);
+
+ $this->assertTrue(isset($json[\OC_Mount_Config::MOUNT_TYPE_USER]));
+ $this->assertTrue(isset($json[\OC_Mount_Config::MOUNT_TYPE_GROUP]));
+ $this->assertEquals(['user1', 'user2'], array_keys($json[\OC_Mount_config::MOUNT_TYPE_USER]));
+ $this->assertEquals(['group1', 'group2'], array_keys($json[\OC_Mount_config::MOUNT_TYPE_GROUP]));
+
+ // check that all options are the same for both users and both groups
+ foreach ($json[\OC_Mount_Config::MOUNT_TYPE_USER] as $mountPointData) {
+ $this->assertEquals(['/$user/files/mountpoint'], array_keys($mountPointData));
+
+ $mountPointOptions = current($mountPointData);
+
+ $this->assertEquals(1, $mountPointOptions['id']);
+ $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
+ $this->assertEquals(15, $mountPointOptions['priority']);
+
+ $backendOptions = $mountPointOptions['options'];
+ $this->assertEquals('value1', $backendOptions['option1']);
+ $this->assertEquals('value2', $backendOptions['option2']);
+ $this->assertEquals('', $backendOptions['password']);
+ $this->assertNotEmpty($backendOptions['password_encrypted']);
+ }
+
+ foreach ($json[\OC_Mount_Config::MOUNT_TYPE_GROUP] as $mountPointData) {
+ $this->assertEquals(['/$user/files/mountpoint'], array_keys($mountPointData));
+
+ $mountPointOptions = current($mountPointData);
+
+ $this->assertEquals(1, $mountPointOptions['id']);
+ $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
+ $this->assertEquals(15, $mountPointOptions['priority']);
+
+ $backendOptions = $mountPointOptions['options'];
+ $this->assertEquals('value1', $backendOptions['option1']);
+ $this->assertEquals('value2', $backendOptions['option2']);
+ $this->assertEquals('', $backendOptions['password']);
+ $this->assertNotEmpty($backendOptions['password_encrypted']);
+ }
+ }
+
+}
diff --git a/apps/files_external/tests/service/storagesservicetest.php b/apps/files_external/tests/service/storagesservicetest.php
new file mode 100644
index 00000000000..1e338b3948d
--- /dev/null
+++ b/apps/files_external/tests/service/storagesservicetest.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_external\Tests\Service;
+
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_external\Lib\StorageConfig;
+
+abstract class StoragesServiceTest extends \Test\TestCase {
+
+ /**
+ * @var StoragesService
+ */
+ protected $service;
+
+ /**
+ * Data directory
+ *
+ * @var string
+ */
+ protected $dataDir;
+
+ /**
+ * Hook calls
+ *
+ * @var array
+ */
+ protected static $hookCalls;
+
+ public function setUp() {
+ self::$hookCalls = array();
+ $config = \OC::$server->getConfig();
+ $this->dataDir = $config->getSystemValue(
+ 'datadirectory',
+ \OC::$SERVERROOT . '/data/'
+ );
+ \OC_Mount_Config::$skipTest = true;
+
+ \OCP\Util::connectHook(
+ Filesystem::CLASSNAME,
+ Filesystem::signal_create_mount,
+ get_class($this), 'createHookCallback');
+ \OCP\Util::connectHook(
+ Filesystem::CLASSNAME,
+ Filesystem::signal_delete_mount,
+ get_class($this), 'deleteHookCallback');
+
+ }
+
+ public function tearDown() {
+ \OC_Mount_Config::$skipTest = false;
+ self::$hookCalls = array();
+ }
+
+ /**
+ * Creates a StorageConfig instance based on array data
+ *
+ * @param array data
+ *
+ * @return StorageConfig storage config instance
+ */
+ protected function makeStorageConfig($data) {
+ $storage = new StorageConfig();
+ if (isset($data['id'])) {
+ $storage->setId($data['id']);
+ }
+ $storage->setMountPoint($data['mountPoint']);
+ $storage->setBackendClass($data['backendClass']);
+ $storage->setBackendOptions($data['backendOptions']);
+ if (isset($data['applicableUsers'])) {
+ $storage->setApplicableUsers($data['applicableUsers']);
+ }
+ if (isset($data['applicableGroups'])) {
+ $storage->setApplicableGroups($data['applicableGroups']);
+ }
+ if (isset($data['priority'])) {
+ $storage->setPriority($data['priority']);
+ }
+ return $storage;
+ }
+
+
+ /**
+ * @expectedException \OCA\Files_external\NotFoundException
+ */
+ public function testNonExistingStorage() {
+ $storage = new StorageConfig(255);
+ $storage->setMountPoint('mountpoint');
+ $storage->setBackendClass('\OC\Files\Storage\SMB');
+ $this->service->updateStorage($storage);
+ }
+
+ public function testDeleteStorage() {
+ $storage = new StorageConfig(255);
+ $storage->setMountPoint('mountpoint');
+ $storage->setBackendClass('\OC\Files\Storage\SMB');
+ $storage->setBackendOptions(['password' => 'testPassword']);
+
+ $newStorage = $this->service->addStorage($storage);
+ $this->assertEquals(1, $newStorage->getId());
+
+ $newStorage = $this->service->removeStorage(1);
+
+ $caught = false;
+ try {
+ $this->service->getStorage(1);
+ } catch (NotFoundException $e) {
+ $caught = true;
+ }
+
+ $this->assertTrue($caught);
+ }
+
+ /**
+ * @expectedException \OCA\Files_external\NotFoundException
+ */
+ public function testDeleteUnexistingStorage() {
+ $this->service->removeStorage(255);
+ }
+
+ public static function createHookCallback($params) {
+ self::$hookCalls[] = array(
+ 'signal' => Filesystem::signal_create_mount,
+ 'params' => $params
+ );
+ }
+
+ public static function deleteHookCallback($params) {
+ self::$hookCalls[] = array(
+ 'signal' => Filesystem::signal_delete_mount,
+ 'params' => $params
+ );
+ }
+
+ /**
+ * Asserts hook call
+ *
+ * @param array $callData hook call data to check
+ * @param string $signal signal name
+ * @param string $mountPath mount path
+ * @param string $mountType mount type
+ * @param string $applicable applicable users
+ */
+ protected function assertHookCall($callData, $signal, $mountPath, $mountType, $applicable) {
+ $this->assertEquals($signal, $callData['signal']);
+ $params = $callData['params'];
+ $this->assertEquals(
+ $mountPath,
+ $params[Filesystem::signal_param_path]
+ );
+ $this->assertEquals(
+ $mountType,
+ $params[Filesystem::signal_param_mount_type]
+ );
+ $this->assertEquals(
+ $applicable,
+ $params[Filesystem::signal_param_users]
+ );
+ }
+}
diff --git a/apps/files_external/tests/service/userstoragesservicetest.php b/apps/files_external/tests/service/userstoragesservicetest.php
new file mode 100644
index 00000000000..64d59dc7d03
--- /dev/null
+++ b/apps/files_external/tests/service/userstoragesservicetest.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Files_external\Tests\Service;
+
+use \OC\Files\Filesystem;
+
+use \OCA\Files_external\Service\UserStoragesService;
+use \OCA\Files_external\NotFoundException;
+use \OCA\Files_external\Lib\StorageConfig;
+
+class UserStoragesServiceTest extends StoragesServiceTest {
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->userId = $this->getUniqueID('user_');
+
+ $this->user = new \OC\User\User($this->userId, null);
+ $userSession = $this->getMock('\OCP\IUserSession');
+ $userSession
+ ->expects($this->any())
+ ->method('getUser')
+ ->will($this->returnValue($this->user));
+
+ $this->service = new UserStoragesService($userSession);
+
+ // create home folder
+ mkdir($this->dataDir . '/' . $this->userId . '/');
+ }
+
+ public function tearDown() {
+ @unlink($this->dataDir . '/' . $this->userId . '/mount.json');
+ parent::tearDown();
+ }
+
+ private function makeTestStorageData() {
+ return $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ ]);
+ }
+
+ public function testAddStorage() {
+ $storage = $this->makeTestStorageData();
+
+ $newStorage = $this->service->addStorage($storage);
+
+ $this->assertEquals(1, $newStorage->getId());
+
+ $newStorage = $this->service->getStorage(1);
+
+ $this->assertEquals($storage->getMountPoint(), $newStorage->getMountPoint());
+ $this->assertEquals($storage->getBackendClass(), $newStorage->getBackendClass());
+ $this->assertEquals($storage->getBackendOptions(), $newStorage->getBackendOptions());
+ $this->assertEquals(1, $newStorage->getId());
+ $this->assertEquals(0, $newStorage->getStatus());
+
+ // hook called once for user
+ $this->assertHookCall(
+ current(self::$hookCalls),
+ Filesystem::signal_create_mount,
+ $storage->getMountPoint(),
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $this->userId
+ );
+
+ // next one gets id 2
+ $nextStorage = $this->service->addStorage($storage);
+ $this->assertEquals(2, $nextStorage->getId());
+ }
+
+ public function testUpdateStorage() {
+ $storage = $this->makeStorageConfig([
+ 'mountPoint' => 'mountpoint',
+ 'backendClass' => '\OC\Files\Storage\SMB',
+ 'backendOptions' => [
+ 'option1' => 'value1',
+ 'option2' => 'value2',
+ 'password' => 'testPassword',
+ ],
+ ]);
+
+ $newStorage = $this->service->addStorage($storage);
+ $this->assertEquals(1, $newStorage->getId());
+
+ $backendOptions = $newStorage->getBackendOptions();
+ $backendOptions['password'] = 'anotherPassword';
+ $newStorage->setBackendOptions($backendOptions);
+
+ self::$hookCalls = [];
+
+ $newStorage = $this->service->updateStorage($newStorage);
+
+ $this->assertEquals('anotherPassword', $newStorage->getBackendOptions()['password']);
+ // these attributes are unused for user storages
+ $this->assertEmpty($newStorage->getApplicableUsers());
+ $this->assertEmpty($newStorage->getApplicableGroups());
+ $this->assertEquals(1, $newStorage->getId());
+ $this->assertEquals(0, $newStorage->getStatus());
+
+ // no hook calls
+ $this->assertEmpty(self::$hookCalls);
+ }
+
+ public function testDeleteStorage() {
+ parent::testDeleteStorage();
+
+ // hook called once for user (first one was during test creation)
+ $this->assertHookCall(
+ self::$hookCalls[1],
+ Filesystem::signal_delete_mount,
+ '/mountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $this->userId
+ );
+ }
+
+ public function testHooksRenameMountPoint() {
+ $storage = $this->makeTestStorageData();
+ $storage = $this->service->addStorage($storage);
+
+ $storage->setMountPoint('renamedMountpoint');
+
+ // reset calls
+ self::$hookCalls = [];
+
+ $this->service->updateStorage($storage);
+
+ // hook called twice
+ $this->assertHookCall(
+ self::$hookCalls[0],
+ Filesystem::signal_delete_mount,
+ '/mountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $this->userId
+ );
+ $this->assertHookCall(
+ self::$hookCalls[1],
+ Filesystem::signal_create_mount,
+ '/renamedMountpoint',
+ \OC_Mount_Config::MOUNT_TYPE_USER,
+ $this->userId
+ );
+ }
+
+ /**
+ * Make sure it uses the correct format when reading/writing
+ * the legacy config
+ */
+ public function testLegacyConfigConversion() {
+ $configFile = $this->dataDir . '/' . $this->userId . '/mount.json';
+
+ $storage = $this->makeTestStorageData();
+ $storage = $this->service->addStorage($storage);
+
+ $json = json_decode(file_get_contents($configFile), true);
+
+ $this->assertCount(1, $json);
+
+ $this->assertEquals([\OC_Mount_Config::MOUNT_TYPE_USER], array_keys($json));
+ $this->assertEquals([$this->userId], array_keys($json[\OC_Mount_config::MOUNT_TYPE_USER]));
+
+ $mountPointData = $json[\OC_Mount_config::MOUNT_TYPE_USER][$this->userId];
+ $this->assertEquals(['/' . $this->userId . '/files/mountpoint'], array_keys($mountPointData));
+
+ $mountPointOptions = current($mountPointData);
+ $this->assertEquals(1, $mountPointOptions['id']);
+ $this->assertEquals('\OC\Files\Storage\SMB', $mountPointOptions['class']);
+
+ $backendOptions = $mountPointOptions['options'];
+ $this->assertEquals('value1', $backendOptions['option1']);
+ $this->assertEquals('value2', $backendOptions['option2']);
+ $this->assertEquals('', $backendOptions['password']);
+ $this->assertNotEmpty($backendOptions['password_encrypted']);
+ }
+}
diff --git a/apps/files_external/tests/storageconfigtest.php b/apps/files_external/tests/storageconfigtest.php
new file mode 100644
index 00000000000..473dc20b387
--- /dev/null
+++ b/apps/files_external/tests/storageconfigtest.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Vincent Petry
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files_external\Tests;
+
+use \OCA\Files_external\Lib\StorageConfig;
+
+class StorageConfigTest extends \Test\TestCase {
+
+ public function testJsonSerialization() {
+ $storageConfig = new StorageConfig(1);
+ $storageConfig->setMountPoint('test');
+ $storageConfig->setBackendClass('\OC\Files\Storage\SMB');
+ $storageConfig->setBackendOptions(['user' => 'test', 'password' => 'password123']);
+ $storageConfig->setPriority(128);
+ $storageConfig->setApplicableUsers(['user1', 'user2']);
+ $storageConfig->setApplicableGroups(['group1', 'group2']);
+
+ $json = $storageConfig->jsonSerialize();
+
+ $this->assertEquals(1, $json['id']);
+ $this->assertEquals('/test', $json['mountPoint']);
+ $this->assertEquals('\OC\Files\Storage\SMB', $json['backendClass']);
+ $this->assertEquals('test', $json['backendOptions']['user']);
+ $this->assertEquals('password123', $json['backendOptions']['password']);
+ $this->assertEquals(128, $json['priority']);
+ $this->assertEquals(['user1', 'user2'], $json['applicableUsers']);
+ $this->assertEquals(['group1', 'group2'], $json['applicableGroups']);
+ }
+
+}