summaryrefslogtreecommitdiffstats
path: root/apps/files_external/service
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/service
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/service')
-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
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);
+ }
+ }
+}