diff options
author | Vincent Petry <pvince81@owncloud.com> | 2014-10-31 11:41:07 +0100 |
---|---|---|
committer | Vincent Petry <pvince81@owncloud.com> | 2015-03-12 18:51:02 +0100 |
commit | ce94a998dd5a5801beef7874dd13752095e35de0 (patch) | |
tree | 8d91631f709549c40555dcb74e9976519f895ae2 /apps/files_external/service | |
parent | 23cc3cc5f2f42166c37fbe03fa62d3dd1dbfe5ed (diff) | |
download | nextcloud-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/service')
-rw-r--r-- | apps/files_external/service/globalstoragesservice.php | 192 | ||||
-rw-r--r-- | apps/files_external/service/storagesservice.php | 303 | ||||
-rw-r--r-- | apps/files_external/service/userstoragesservice.php | 142 |
3 files changed, 637 insertions, 0 deletions
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); + } + } +} |