]> git.mxchange.org Git - friendica.git/commitdiff
Restructure Storage to new paradigm
authorPhilipp <admin@philipp.info>
Sat, 23 Oct 2021 10:11:38 +0000 (12:11 +0200)
committerPhilipp <admin@philipp.info>
Wed, 27 Oct 2021 18:00:19 +0000 (20:00 +0200)
47 files changed:
doc/AddonStorageBackend.md
doc/Addons.md
src/Console/Storage.php
src/Core/Storage/Capability/ICanConfigureStorage.php [new file with mode: 0644]
src/Core/Storage/Capability/ICanReadFromStorage.php [new file with mode: 0644]
src/Core/Storage/Capability/ICanWriteToStorage.php [new file with mode: 0644]
src/Core/Storage/Exception/InvalidClassStorageException.php [new file with mode: 0644]
src/Core/Storage/Exception/ReferenceStorageException.php [new file with mode: 0644]
src/Core/Storage/Exception/StorageException.php [new file with mode: 0644]
src/Core/Storage/Repository/StorageManager.php [new file with mode: 0644]
src/Core/Storage/Type/Database.php [new file with mode: 0644]
src/Core/Storage/Type/ExternalResource.php [new file with mode: 0644]
src/Core/Storage/Type/Filesystem.php [new file with mode: 0644]
src/Core/Storage/Type/FilesystemConfig.php [new file with mode: 0644]
src/Core/Storage/Type/SystemResource.php [new file with mode: 0644]
src/Core/StorageManager.php [deleted file]
src/DI.php
src/Model/Attach.php
src/Model/Photo.php
src/Model/Storage/Database.php [deleted file]
src/Model/Storage/ExternalResource.php [deleted file]
src/Model/Storage/Filesystem.php [deleted file]
src/Model/Storage/FilesystemConfig.php [deleted file]
src/Model/Storage/IStorage.php [deleted file]
src/Model/Storage/IStorageConfiguration.php [deleted file]
src/Model/Storage/IWritableStorage.php [deleted file]
src/Model/Storage/InvalidClassStorageException.php [deleted file]
src/Model/Storage/ReferenceStorageException.php [deleted file]
src/Model/Storage/StorageException.php [deleted file]
src/Model/Storage/SystemResource.php [deleted file]
src/Module/Admin/Storage.php
src/Module/Photo.php
static/dependencies.config.php
tests/Util/SampleStorageBackend.php
tests/src/Core/Storage/DatabaseStorageTest.php [new file with mode: 0644]
tests/src/Core/Storage/FilesystemStorageConfigTest.php [new file with mode: 0644]
tests/src/Core/Storage/FilesystemStorageTest.php [new file with mode: 0644]
tests/src/Core/Storage/Repository/StorageManagerTest.php [new file with mode: 0644]
tests/src/Core/Storage/StorageConfigTest.php [new file with mode: 0644]
tests/src/Core/Storage/StorageTest.php [new file with mode: 0644]
tests/src/Core/StorageManagerTest.php [deleted file]
tests/src/Model/Storage/DatabaseStorageTest.php [deleted file]
tests/src/Model/Storage/FilesystemStorageConfigTest.php [deleted file]
tests/src/Model/Storage/FilesystemStorageTest.php [deleted file]
tests/src/Model/Storage/StorageConfigTest.php [deleted file]
tests/src/Model/Storage/StorageTest.php [deleted file]
update.php

index 0b067237659260b4ff56f44eb9428c25ff4ec298..eaa58cd4d871606bde194b79c6dbb436f468d484 100644 (file)
@@ -4,7 +4,7 @@ Friendica Storage Backend Addon development
 * [Home](help)
 
 Storage backends can be added via addons.
-A storage backend is implemented as a class, and the plugin register the class to make it avaiable to the system.
+A storage backend is implemented as a class, and the plugin register the class to make it available to the system.
 
 ## The Storage Backend Class
 
@@ -12,14 +12,14 @@ The class must live in `Friendica\Addon\youraddonname` namespace, where `youradd
 
 There are two different interfaces you need to implement.
 
-### `IWritableStorage`
+### `ICanWriteToStorage`
 
-The class must implement `Friendica\Model\Storage\IWritableStorage` interface. All method in the interface must be implemented:
+The class must implement `Friendica\Core\Storage\Capability\ICanWriteToStorage` interface. All method in the interface must be implemented:
 
 ```php
-namespace Friendica\Model\Storage\IWritableStorage;
+namespace Friendica\Core\Storage\Capability\ICanWriteToStorage;
 
-interface IWritableStorage
+interface ICanWriteToStorage
 {
        public function get(string $reference);
        public function put(string $data, string $reference = '');
@@ -33,17 +33,17 @@ interface IWritableStorage
 - `put(string $data, string $reference)` saves data in `$data` to position `$reference`, or a new position if `$reference` is empty.
 - `delete(string $reference)` delete data pointed by `$reference`
 
-### `IStorageConfiguration`
+### `ICanConfigureStorage`
 
 Each storage backend can have options the admin can set in admin page.
-To make the options possible, you need to implement the `Friendica\Model\Storage\IStorageConfiguration` interface.
+To make the options possible, you need to implement the `Friendica\Core\Storage\Capability\ICanConfigureStorage` interface.
 
 All methods in the interface must be implemented:
 
 ```php
-namespace Friendica\Model\Storage\IStorageConfiguration;
+namespace Friendica\Core\Storage\Capability\ICanConfigureStorage;
 
-interface IStorageConfiguration
+interface ICanConfigureStorage
 {
        public function getOptions();
        public function saveOptions(array $data);
@@ -108,7 +108,7 @@ When the plugin is uninstalled, registered backends must be unregistered using
 `DI::facStorage()->unregister(string $class)`.
 
 You have to register a new hook in your addon, listening on `storage_instance(App $a, array $data)`.
-In case `$data['name']` is your storage class name, you have to instance a new instance of your `Friendica\Model\Storage\IStorage` class.
+In case `$data['name']` is your storage class name, you have to instance a new instance of your `Friendica\Core\Storage\Capability\ICanReadFromStorage` class.
 Set the instance of your class as `$data['storage']` to pass it back to the backend.
 
 This is necessary because it isn't always clear, if you need further construction arguments.
@@ -124,7 +124,7 @@ Add a new test class which's naming convention is `StorageClassTest`, which exte
 Override the two necessary instances:
 
 ```php
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 
 abstract class StorageTest 
 {
@@ -132,7 +132,7 @@ abstract class StorageTest
        abstract protected function getInstance();
 
        // Assertion for the option array you return for your new StorageClass
-       abstract protected function assertOption(IWritableStorage $storage);
+       abstract protected function assertOption(ICanWriteToStorage $storage);
 } 
 ```
 
@@ -156,16 +156,16 @@ If there's a predecessor to this exception (e.g. you caught an exception and are
 Example:
 
 ```php
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 
-class ExampleStorage implements IWritableStorage 
+class ExampleStorage implements ICanWriteToStorage 
 {
        public function get(string $reference) : string
        {
                try {
                        throw new Exception('a real bad exception');
                } catch (Exception $exception) {
-                       throw new \Friendica\Model\Storage\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
+                       throw new \Friendica\Core\Storage\Exception\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception);
                }
        }
 } 
@@ -186,12 +186,12 @@ The file will be `addon/samplestorage/SampleStorageBackend.php`:
 <?php
 namespace Friendica\Addon\samplestorage;
 
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 
 use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Core\L10n;
 
-class SampleStorageBackend implements IWritableStorage
+class SampleStorageBackend implements ICanWriteToStorage
 {
        const NAME = 'Sample Storage';
 
@@ -247,12 +247,12 @@ class SampleStorageBackend implements IWritableStorage
 <?php
 namespace Friendica\Addon\samplestorage;
 
-use Friendica\Model\Storage\IStorageConfiguration;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
 
 use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Core\L10n;
 
-class SampleStorageBackendConfig implements IStorageConfiguration
+class SampleStorageBackendConfig implements ICanConfigureStorage
 {
        /** @var \Friendica\Core\Config\Capability\IManageConfigValues */
        private $config;
@@ -357,8 +357,8 @@ function samplestorage_storage_config(App $a, array &$data)
 **Theoretically - until tests for Addons are enabled too - create a test class with the name `addon/tests/SampleStorageTest.php`:
 
 ```php
-use Friendica\Model\Storage\IWritableStorage;
-use Friendica\Test\src\Model\Storage\StorageTest;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Test\src\Core\Storage\StorageTest;
 
 class SampleStorageTest extends StorageTest 
 {
@@ -371,7 +371,7 @@ class SampleStorageTest extends StorageTest
        }
 
        // Assertion for the option array you return for your new StorageClass
-       protected function assertOption(IWritableStorage $storage)
+       protected function assertOption(ICanWriteToStorage $storage)
        {
                $this->assertEquals([
                        'filename' => [
index debdc89dd47390871234c6d5906ce6a49d43d53d..6b3cd169bfe796852af6fbcb58fa0e6ec84e98a7 100644 (file)
@@ -544,7 +544,7 @@ Called when a custom storage is used (e.g. webdav_storage)
 
 Hook data:
 - **name** (input): the name of the used storage backend
-- **data['storage']** (output): the storage instance to use (**must** implement `\Friendica\Model\Storage\IWritableStorage`) 
+- **data['storage']** (output): the storage instance to use (**must** implement `\Friendica\Core\Storage\IWritableStorage`) 
 
 ### storage_config
 
@@ -552,7 +552,7 @@ Called when the admin of the node wants to configure a custom storage (e.g. webd
 
 Hook data:
 - **name** (input): the name of the used storage backend
-- **data['storage_config']** (output): the storage configuration instance to use (**must** implement `\Friendica\Model\Storage\IStorageConfiguration`)
+- **data['storage_config']** (output): the storage configuration instance to use (**must** implement `\Friendica\Core\Storage\Capability\IConfigureStorage`)
 
 ## Complete list of hook callbacks
 
index 3377f33ddf5d28f1f27d8a663668e468bb7b11ad..fbe55f34c3c8a39c3aee9203e2fabb4ff2250c2b 100644 (file)
@@ -22,9 +22,9 @@
 namespace Friendica\Console;
 
 use Asika\SimpleConsole\CommandArgsException;
-use Friendica\Core\StorageManager;
-use Friendica\Model\Storage\ReferenceStorageException;
-use Friendica\Model\Storage\StorageException;
+use Friendica\Core\Storage\Repository\StorageManager;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
 
 /**
  * tool to manage storage backend and stored data from CLI
@@ -33,7 +33,7 @@ class Storage extends \Asika\SimpleConsole\Console
 {
        protected $helpOptions = ['h', 'help', '?'];
 
-       /** @var StorageManager */
+       /** @var \Friendica\Core\Storage\Repository\StorageManager */
        private $storageManager;
 
        /**
diff --git a/src/Core/Storage/Capability/ICanConfigureStorage.php b/src/Core/Storage/Capability/ICanConfigureStorage.php
new file mode 100644 (file)
index 0000000..09d0718
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Capability;
+
+/**
+ * The interface to use for configurable storage backends
+ */
+interface ICanConfigureStorage
+{
+       /**
+        * Get info about storage options
+        *
+        * @return array
+        *
+        * This method return an array with information about storage options
+        * from which the form presented to the user is build.
+        *
+        * The returned array is:
+        *
+        *    [
+        *      'option1name' => [ ..info.. ],
+        *      'option2name' => [ ..info.. ],
+        *      ...
+        *    ]
+        *
+        * An empty array can be returned if backend doesn't have any options
+        *
+        * The info array for each option MUST be as follows:
+        *
+        *    [
+        *      'type',      // define the field used in form, and the type of data.
+        *                   // one of 'checkbox', 'combobox', 'custom', 'datetime',
+        *                   // 'input', 'intcheckbox', 'password', 'radio', 'richtext'
+        *                   // 'select', 'select_raw', 'textarea'
+        *
+        *      'label',     // Translatable label of the field
+        *      'value',     // Current value
+        *      'help text', // Translatable description for the field
+        *      extra data   // Optional. Depends on 'type':
+        *                   // select: array [ value => label ] of choices
+        *                   // intcheckbox: value of input element
+        *                   // select_raw: prebuild html string of < option > tags
+        *    ]
+        *
+        * See https://github.com/friendica/friendica/wiki/Quick-Template-Guide
+        */
+       public function getOptions(): array;
+
+       /**
+        * Validate and save options
+        *
+        * @param array $data Array [optionname => value] to be saved
+        *
+        * @return array  Validation errors: [optionname => error message]
+        *
+        * Return array must be empty if no error.
+        */
+       public function saveOptions(array $data): array;
+}
diff --git a/src/Core/Storage/Capability/ICanReadFromStorage.php b/src/Core/Storage/Capability/ICanReadFromStorage.php
new file mode 100644 (file)
index 0000000..b6c6375
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Capability;
+
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+
+/**
+ * Interface for basic storage backends
+ */
+interface ICanReadFromStorage
+{
+       /**
+        * Get data from backend
+        *
+        * @param string $reference Data reference
+        *
+        * @return string
+        *
+        * @throws StorageException in case there's an unexpected error
+        * @throws ReferenceStorageException in case the reference doesn't exist
+        */
+       public function get(string $reference): string;
+
+       /**
+        * The name of the backend
+        *
+        * @return string
+        */
+       public function __toString(): string;
+
+       /**
+        * The name of the backend
+        *
+        * @return string
+        */
+       public static function getName(): string;
+}
diff --git a/src/Core/Storage/Capability/ICanWriteToStorage.php b/src/Core/Storage/Capability/ICanWriteToStorage.php
new file mode 100644 (file)
index 0000000..fd9d0e1
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Capability;
+
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+
+/**
+ * Interface for writable storage backends
+ *
+ * Used for storages with CRUD functionality, mainly used for user data (e.g. photos, attachements).
+ * There's only one active writable storage possible. This type of storage is selectable by the current administrator.
+ */
+interface ICanWriteToStorage extends ICanReadFromStorage
+{
+       /**
+        * Put data in backend as $ref. If $ref is not defined a new reference is created.
+        *
+        * @param string $data      Data to save
+        * @param string $reference Data reference. Optional.
+        *
+        * @return string Saved data reference
+        *
+        * @throws StorageException in case there's an unexpected error
+        */
+       public function put(string $data, string $reference = ""): string;
+
+       /**
+        * Remove data from backend
+        *
+        * @param string $reference Data reference
+        *
+        * @throws StorageException in case there's an unexpected error
+        * @throws ReferenceStorageException in case the reference doesn't exist
+        */
+       public function delete(string $reference);
+}
diff --git a/src/Core/Storage/Exception/InvalidClassStorageException.php b/src/Core/Storage/Exception/InvalidClassStorageException.php
new file mode 100644 (file)
index 0000000..9c1c7bb
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Exception;
+
+/**
+ * Storage Exception in case of invalid storage class
+ */
+class InvalidClassStorageException extends StorageException
+{
+}
diff --git a/src/Core/Storage/Exception/ReferenceStorageException.php b/src/Core/Storage/Exception/ReferenceStorageException.php
new file mode 100644 (file)
index 0000000..7ef5960
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Exception;
+
+/**
+ * Storage Exception in case of invalid references
+ */
+class ReferenceStorageException extends StorageException
+{
+}
diff --git a/src/Core/Storage/Exception/StorageException.php b/src/Core/Storage/Exception/StorageException.php
new file mode 100644 (file)
index 0000000..7c7b3d1
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Exception;
+
+use Exception;
+
+/**
+ * Storage Exception for unexpected failures
+ */
+class StorageException extends Exception
+{
+}
diff --git a/src/Core/Storage/Repository/StorageManager.php b/src/Core/Storage/Repository/StorageManager.php
new file mode 100644 (file)
index 0000000..5df6e5a
--- /dev/null
@@ -0,0 +1,407 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Repository;
+
+use Exception;
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Database\Database;
+use Friendica\Core\Storage\Type;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Manage storage backends
+ *
+ * Core code uses this class to get and set current storage backend class.
+ * Addons use this class to register and unregister additional backends.
+ */
+class StorageManager
+{
+       // Default tables to look for data
+       const TABLES = ['photo', 'attach'];
+
+       // Default storage backends
+       /** @var string[]  */
+       const DEFAULT_BACKENDS = [
+               Type\Filesystem::NAME,
+               Type\Database::NAME,
+       ];
+
+       /** @var string[] List of valid backend classes */
+       private $validBackends;
+
+       /**
+        * @var ICanReadFromStorage[] A local cache for storage instances
+        */
+       private $backendInstances = [];
+
+       /** @var Database */
+       private $dba;
+       /** @var IManageConfigValues */
+       private $config;
+       /** @var LoggerInterface */
+       private $logger;
+       /** @var L10n */
+       private $l10n;
+
+       /** @var ICanWriteToStorage */
+       private $currentBackend;
+
+       /**
+        * @param Database            $dba
+        * @param IManageConfigValues $config
+        * @param LoggerInterface     $logger
+        * @param L10n                $l10n
+        *
+        * @throws InvalidClassStorageException in case the active backend class is invalid
+        * @throws StorageException in case of unexpected errors during the active backend class loading
+        */
+       public function __construct(Database $dba, IManageConfigValues $config, LoggerInterface $logger, L10n $l10n)
+       {
+               $this->dba           = $dba;
+               $this->config        = $config;
+               $this->logger        = $logger;
+               $this->l10n          = $l10n;
+               $this->validBackends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
+
+               $currentName = $this->config->get('storage', 'name');
+
+               // you can only use user backends as a "default" backend, so the second parameter is true
+               $this->currentBackend = $this->getWritableStorageByName($currentName);
+       }
+
+       /**
+        * Return current storage backend class
+        *
+        * @return ICanWriteToStorage
+        */
+       public function getBackend(): ICanWriteToStorage
+       {
+               return $this->currentBackend;
+       }
+
+       /**
+        * Returns a writable storage backend class by registered name
+        *
+        * @param string $name Backend name
+        *
+        * @return ICanWriteToStorage
+        *
+        * @throws InvalidClassStorageException in case there's no backend class for the name
+        * @throws StorageException in case of an unexpected failure during the hook call
+        */
+       public function getWritableStorageByName(string $name): ICanWriteToStorage
+       {
+               $storage = $this->getByName($name, $this->validBackends);
+               if (!$storage instanceof ICanWriteToStorage) {
+                       throw new InvalidClassStorageException(sprintf('Backend %s is not writable', $name));
+               }
+
+               return $storage;
+       }
+
+       /**
+        * Return storage backend configuration by registered name
+        *
+        * @param string     $name Backend name
+        *
+        * @return ICanConfigureStorage|false
+        *
+        * @throws InvalidClassStorageException in case there's no backend class for the name
+        * @throws StorageException in case of an unexpected failure during the hook call
+        */
+       public function getConfigurationByName(string $name)
+       {
+               switch ($name) {
+                       // Try the filesystem backend
+                       case Type\Filesystem::getName():
+                               return new Type\FilesystemConfig($this->config, $this->l10n);
+                       // try the database backend
+                       case Type\Database::getName():
+                               return false;
+                       default:
+                               $data = [
+                                       'name'           => $name,
+                                       'storage_config' => null,
+                               ];
+                               try {
+                                       Hook::callAll('storage_config', $data);
+                                       if (!($data['storage_config'] ?? null) instanceof ICanConfigureStorage) {
+                                               throw new InvalidClassStorageException(sprintf('Configuration for backend %s was not found', $name));
+                                       }
+
+                                       return $data['storage_config'];
+                               } catch (InternalServerErrorException $exception) {
+                                       throw new StorageException(sprintf('Failed calling hook::storage_config for backend %s', $name), $exception);
+                               }
+               }
+       }
+
+       /**
+        * Return storage backend class by registered name
+        *
+        * @param string     $name Backend name
+        * @param string[]|null $validBackends possible, manual override of the valid backends
+        *
+        * @return ICanReadFromStorage
+        *
+        * @throws InvalidClassStorageException in case there's no backend class for the name
+        * @throws StorageException in case of an unexpected failure during the hook call
+        */
+       public function getByName(string $name, array $validBackends = null): ICanReadFromStorage
+       {
+               // If there's no cached instance create a new instance
+               if (!isset($this->backendInstances[$name])) {
+                       // If the current name isn't a valid backend (or the SystemResource instance) create it
+                       if (!$this->isValidBackend($name, $validBackends)) {
+                               throw new InvalidClassStorageException(sprintf('Backend %s is not valid', $name));
+                       }
+
+                       switch ($name) {
+                               // Try the filesystem backend
+                               case Type\Filesystem::getName():
+                                       $storageConfig                 = new Type\FilesystemConfig($this->config, $this->l10n);
+                                       $this->backendInstances[$name] = new Type\Filesystem($storageConfig->getStoragePath());
+                                       break;
+                               // try the database backend
+                               case Type\Database::getName():
+                                       $this->backendInstances[$name] = new Type\Database($this->dba);
+                                       break;
+                               // at least, try if there's an addon for the backend
+                               case \Friendica\Core\Storage\Type\SystemResource::getName():
+                                       $this->backendInstances[$name] = new \Friendica\Core\Storage\Type\SystemResource();
+                                       break;
+                               case \Friendica\Core\Storage\Type\ExternalResource::getName():
+                                       $this->backendInstances[$name] = new \Friendica\Core\Storage\Type\ExternalResource();
+                                       break;
+                               default:
+                                       $data = [
+                                               'name'    => $name,
+                                               'storage' => null,
+                                       ];
+                                       try {
+                                               Hook::callAll('storage_instance', $data);
+                                               if (!($data['storage'] ?? null) instanceof ICanReadFromStorage) {
+                                                       throw new InvalidClassStorageException(sprintf('Backend %s was not found', $name));
+                                               }
+
+                                               $this->backendInstances[$data['name'] ?? $name] = $data['storage'];
+                                       } catch (InternalServerErrorException $exception) {
+                                               throw new StorageException(sprintf('Failed calling hook::storage_instance for backend %s', $name), $exception);
+                                       }
+                                       break;
+                       }
+               }
+
+               return $this->backendInstances[$name];
+       }
+
+       /**
+        * Checks, if the storage is a valid backend
+        *
+        * @param string|null   $name          The name or class of the backend
+        * @param string[]|null $validBackends Possible, valid backends to check
+        *
+        * @return boolean True, if the backend is a valid backend
+        */
+       public function isValidBackend(string $name = null, array $validBackends = null): bool
+       {
+               $validBackends = $validBackends ?? array_merge($this->validBackends,
+                               [
+                                       Type\SystemResource::getName(),
+                                       Type\ExternalResource::getName(),
+                               ]);
+               return in_array($name, $validBackends);
+       }
+
+       /**
+        * Set current storage backend class
+        *
+        * @param ICanWriteToStorage $storage The storage class
+        *
+        * @return boolean True, if the set was successful
+        */
+       public function setBackend(ICanWriteToStorage $storage): bool
+       {
+               if ($this->config->set('storage', 'name', $storage::getName())) {
+                       $this->currentBackend = $storage;
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Get registered backends
+        *
+        * @return string[]
+        */
+       public function listBackends(): array
+       {
+               return $this->validBackends;
+       }
+
+       /**
+        * Register a storage backend class
+        *
+        * You have to register the hook "storage_instance" as well to make this class work!
+        *
+        * @param string $class Backend class name
+        *
+        * @return boolean True, if the registration was successful
+        */
+       public function register(string $class): bool
+       {
+               if (is_subclass_of($class, ICanReadFromStorage::class)) {
+                       /** @var ICanReadFromStorage $class */
+                       if ($this->isValidBackend($class::getName(), $this->validBackends)) {
+                               return true;
+                       }
+
+                       $backends   = $this->validBackends;
+                       $backends[] = $class::getName();
+
+                       if ($this->config->set('storage', 'backends', $backends)) {
+                               $this->validBackends = $backends;
+                               return true;
+                       } else {
+                               return false;
+                       }
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Unregister a storage backend class
+        *
+        * @param string $class Backend class name
+        *
+        * @return boolean True, if unregistering was successful
+        *
+        * @throws StorageException
+        */
+       public function unregister(string $class): bool
+       {
+               if (is_subclass_of($class, ICanReadFromStorage::class)) {
+                       /** @var ICanReadFromStorage $class */
+                       if ($this->currentBackend::getName() == $class::getName()) {
+                               throw new StorageException(sprintf('Cannot unregister %s, because it\'s currently active.', $class::getName()));
+                       }
+
+                       $key = array_search($class::getName(), $this->validBackends);
+
+                       if ($key !== false) {
+                               $backends = $this->validBackends;
+                               unset($backends[$key]);
+                               $backends = array_values($backends);
+                               if ($this->config->set('storage', 'backends', $backends)) {
+                                       $this->validBackends = $backends;
+                                       return true;
+                               } else {
+                                       return false;
+                               }
+                       } else {
+                               return true;
+                       }
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Move up to 5000 resources to storage $dest
+        *
+        * Copy existing data to destination storage and delete from source.
+        * This method cannot move to legacy in-table `data` field.
+        *
+        * @param ICanWriteToStorage $destination Destination storage class name
+        * @param array              $tables      Tables to look in for resources. Optional, defaults to ['photo', 'attach']
+        * @param int                $limit       Limit of the process batch size, defaults to 5000
+        *
+        * @return int Number of moved resources
+        * @throws StorageException
+        * @throws Exception
+        */
+       public function move(ICanWriteToStorage $destination, array $tables = self::TABLES, int $limit = 5000): int
+       {
+               if (!$this->isValidBackend($destination, $this->validBackends)) {
+                       throw new StorageException(sprintf("Can't move to storage backend '%s'", $destination::getName()));
+               }
+
+               $moved = 0;
+               foreach ($tables as $table) {
+                       // Get the rows where backend class is not the destination backend class
+                       $resources = $this->dba->select(
+                               $table,
+                               ['id', 'data', 'backend-class', 'backend-ref'],
+                               ['`backend-class` IS NULL or `backend-class` != ?', $destination::getName()],
+                               ['limit' => $limit]
+                       );
+
+                       while ($resource = $this->dba->fetch($resources)) {
+                               $id        = $resource['id'];
+                               $sourceRef = $resource['backend-ref'];
+                               $source    = null;
+
+                               try {
+                                       $source = $this->getWritableStorageByName($resource['backend-class'] ?? '');
+                                       $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+                                       $data = $source->get($sourceRef);
+                               } catch (InvalidClassStorageException $exception) {
+                                       $this->logger->info('Get data from DB resource field.', ['oldReference' => $sourceRef]);
+                                       $data = $resource['data'];
+                               } catch (ReferenceStorageException $exception) {
+                                       $this->logger->info('Invalid source reference.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+                                       continue;
+                               }
+
+                               $this->logger->info('Save data to new backend.', ['newBackend' => $destination::getName()]);
+                               $destinationRef = $destination->put($data);
+                               $this->logger->info('Saved data.', ['newReference' => $destinationRef]);
+
+                               if ($destinationRef !== '') {
+                                       $this->logger->info('update row');
+                                       if ($this->dba->update($table, ['backend-class' => $destination::getName(), 'backend-ref' => $destinationRef, 'data' => ''], ['id' => $id])) {
+                                               if (!empty($source)) {
+                                                       $this->logger->info('Deleted data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
+                                                       $source->delete($sourceRef);
+                                               }
+                                               $moved++;
+                                       }
+                               }
+                       }
+
+                       $this->dba->close($resources);
+               }
+
+               return $moved;
+       }
+}
diff --git a/src/Core/Storage/Type/Database.php b/src/Core/Storage/Type/Database.php
new file mode 100644 (file)
index 0000000..a22f8ae
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Exception;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Database\Database as DBA;
+
+/**
+ * Database based storage system
+ *
+ * This class manage data stored in database table.
+ */
+class Database implements ICanWriteToStorage
+{
+       const NAME = 'Database';
+
+       /** @var DBA */
+       private $dba;
+
+       /**
+        * @param DBA             $dba
+        */
+       public function __construct(DBA $dba)
+       {
+               $this->dba = $dba;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function get(string $reference): string
+       {
+               try {
+                       $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]);
+                       if (!$this->dba->isResult($result)) {
+                               throw new ReferenceStorageException(sprintf('Database storage cannot find data for reference %s', $reference));
+                       }
+
+                       return $result['data'];
+               } catch (Exception $exception) {
+                       if ($exception instanceof ReferenceStorageException) {
+                               throw $exception;
+                       } else {
+                               throw new StorageException(sprintf('Database storage failed to get %s', $reference), $exception->getCode(), $exception);
+                       }
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function put(string $data, string $reference = ''): string
+       {
+               if ($reference !== '') {
+                       try {
+                               $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]);
+                       } catch (Exception $exception) {
+                               throw new StorageException(sprintf('Database storage failed to update %s', $reference), $exception->getCode(), $exception);
+                       }
+                       if ($result === false) {
+                               throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
+                       }
+
+                       return $reference;
+               } else {
+                       try {
+                               $result = $this->dba->insert('storage', ['data' => $data]);
+                       } catch (Exception $exception) {
+                               throw new StorageException(sprintf('Database storage failed to insert %s', $reference), $exception->getCode(), $exception);
+                       }
+                       if ($result === false) {
+                               throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
+                       }
+
+                       return $this->dba->lastInsertId();
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function delete(string $reference)
+       {
+               try {
+                       if (!$this->dba->delete('storage', ['id' => $reference]) || $this->dba->affectedRows() === 0) {
+                               throw new ReferenceStorageException(sprintf('Database storage failed to delete %s', $reference));
+                       }
+               } catch (Exception $exception) {
+                       if ($exception instanceof ReferenceStorageException) {
+                               throw $exception;
+                       } else {
+                               throw new StorageException(sprintf('Database storage failed to delete %s', $reference), $exception->getCode(), $exception);
+                       }
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public static function getName(): string
+       {
+               return self::NAME;
+       }
+
+       public function __toString(): string
+       {
+               return self::getName();
+       }
+}
diff --git a/src/Core/Storage/Type/ExternalResource.php b/src/Core/Storage/Type/ExternalResource.php
new file mode 100644 (file)
index 0000000..33e6592
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Exception;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Util\HTTPSignature;
+
+/**
+ * External resource storage class
+ *
+ * This class is used to load external resources, like images.
+ * Is not intended to be selectable by admins as default storage class.
+ */
+class ExternalResource implements ICanReadFromStorage
+{
+       const NAME = 'ExternalResource';
+
+       /**
+        * @inheritDoc
+        */
+       public function get(string $reference): string
+       {
+               $data = json_decode($reference);
+               if (empty($data->url)) {
+                       throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot retrieve URL', $reference));
+               }
+
+               $parts = parse_url($data->url);
+               if (empty($parts['scheme']) || empty($parts['host'])) {
+                       throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot extract scheme and host', $reference));
+               }
+
+               try {
+                       $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => []]);
+               } catch (Exception $exception) {
+                       throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $exception->getCode(), $exception);
+               }
+               if ($fetchResult->isSuccess()) {
+                       return $fetchResult->getBody();
+               } else {
+                       throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $fetchResult->getReturnCode(), new Exception($fetchResult->getBody()));
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function __toString(): string
+       {
+               return self::NAME;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public static function getName(): string
+       {
+               return self::NAME;
+       }
+}
diff --git a/src/Core/Storage/Type/Filesystem.php b/src/Core/Storage/Type/Filesystem.php
new file mode 100644 (file)
index 0000000..21a5319
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Exception;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Util\Strings;
+
+/**
+ * Filesystem based storage backend
+ *
+ * This class manage data on filesystem.
+ * Base folder for storage is set in storage.filesystem_path.
+ * Best would be for storage folder to be outside webserver folder, we are using a
+ * folder relative to code tree root as default to ease things for users in shared hostings.
+ * Each new resource gets a value as reference and is saved in a
+ * folder tree stucture created from that value.
+ */
+class Filesystem implements ICanWriteToStorage
+{
+       const NAME = 'Filesystem';
+
+       /** @var string */
+       private $basePath;
+
+       /**
+        * Filesystem constructor.
+        *
+        * @param string $filesystemPath
+        *
+        * @throws StorageException in case the path doesn't exist or isn't writeable
+        */
+       public function __construct(string $filesystemPath = FilesystemConfig::DEFAULT_BASE_FOLDER)
+       {
+               $path           = $filesystemPath;
+               $this->basePath = rtrim($path, '/');
+
+               if (!is_dir($this->basePath) || !is_writable($this->basePath)) {
+                       throw new StorageException(sprintf('Path "%s" does not exist or is not writeable.', $this->basePath));
+               }
+       }
+
+       /**
+        * Split data ref and return file path
+        *
+        * @param string $reference Data reference
+        *
+        * @return string
+        */
+       private function pathForRef(string $reference): string
+       {
+               $fold1 = substr($reference, 0, 2);
+               $fold2 = substr($reference, 2, 2);
+               $file  = substr($reference, 4);
+
+               return implode('/', [$this->basePath, $fold1, $fold2, $file]);
+       }
+
+
+       /**
+        * Create directory tree to store file, with .htaccess and index.html files
+        *
+        * @param string $file Path and filename
+        *
+        * @throws StorageException
+        */
+       private function createFoldersForFile(string $file)
+       {
+               $path = dirname($file);
+
+               if (!is_dir($path)) {
+                       if (!mkdir($path, 0770, true)) {
+                               throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
+                       }
+               }
+
+               while ($path !== $this->basePath) {
+                       if (!is_file($path . '/index.html')) {
+                               file_put_contents($path . '/index.html', '');
+                       }
+                       chmod($path . '/index.html', 0660);
+                       chmod($path, 0770);
+                       $path = dirname($path);
+               }
+               if (!is_file($path . '/index.html')) {
+                       file_put_contents($path . '/index.html', '');
+                       chmod($path . '/index.html', 0660);
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function get(string $reference): string
+       {
+               $file = $this->pathForRef($reference);
+               if (!is_file($file)) {
+                       throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
+               }
+
+               $result = file_get_contents($file);
+
+               if ($result === false) {
+                       throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
+               }
+
+               return $result;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function put(string $data, string $reference = ''): string
+       {
+               if ($reference === '') {
+                       try {
+                               $reference = Strings::getRandomHex();
+                       } catch (Exception $exception) {
+                               throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
+                       }
+               }
+               $file = $this->pathForRef($reference);
+
+               $this->createFoldersForFile($file);
+
+               $result = file_put_contents($file, $data);
+
+               // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
+               if ($result === false) {
+                       throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
+               }
+
+               chmod($file, 0660);
+               return $reference;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function delete(string $reference)
+       {
+               $file = $this->pathForRef($reference);
+               if (!is_file($file)) {
+                       throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
+               }
+
+               if (!unlink($file)) {
+                       throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
+               }
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public static function getName(): string
+       {
+               return self::NAME;
+       }
+
+       public function __toString(): string
+       {
+               return self::getName();
+       }
+}
diff --git a/src/Core/Storage/Type/FilesystemConfig.php b/src/Core/Storage/Type/FilesystemConfig.php
new file mode 100644 (file)
index 0000000..b30b2af
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\L10n;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+
+/**
+ * Filesystem based storage backend configuration
+ */
+class FilesystemConfig implements ICanConfigureStorage
+{
+       // Default base folder
+       const DEFAULT_BASE_FOLDER = 'storage';
+
+       /** @var IManageConfigValues */
+       private $config;
+
+       /** @var string */
+       private $storagePath;
+
+       /** @var L10n */
+       private $l10n;
+
+       /**
+        * Returns the current storage path
+        *
+        * @return string
+        */
+       public function getStoragePath(): string
+       {
+               return $this->storagePath;
+       }
+
+       /**
+        * Filesystem constructor.
+        *
+        * @param IManageConfigValues $config
+        * @param L10n                $l10n
+        */
+       public function __construct(IManageConfigValues $config, L10n $l10n)
+       {
+               $this->config = $config;
+               $this->l10n   = $l10n;
+
+               $path              = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
+               $this->storagePath = rtrim($path, '/');
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function getOptions(): array
+       {
+               return [
+                       'storagepath' => [
+                               'input',
+                               $this->l10n->t('Storage base path'),
+                               $this->storagePath,
+                               $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
+                       ]
+               ];
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function saveOptions(array $data): array
+       {
+               $storagePath = $data['storagepath'] ?? '';
+               if ($storagePath === '' || !is_dir($storagePath)) {
+                       return [
+                               'storagepath' => $this->l10n->t('Enter a valid existing folder')
+                       ];
+               };
+               $this->config->set('storage', 'filesystem_path', $storagePath);
+               $this->storagePath = $storagePath;
+               return [];
+       }
+}
diff --git a/src/Core/Storage/Type/SystemResource.php b/src/Core/Storage/Type/SystemResource.php
new file mode 100644 (file)
index 0000000..7b73b7c
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Core\Storage\Type;
+
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+
+/**
+ * System resource storage class
+ *
+ * This class is used to load system resources, like images.
+ * Is not intended to be selectable by admins as default storage class.
+ */
+class SystemResource implements ICanReadFromStorage
+{
+       const NAME = 'SystemResource';
+
+       // Valid folders to look for resources
+       const VALID_FOLDERS = ["images"];
+
+       /**
+        * @inheritDoc
+        */
+       public function get(string $reference): string
+       {
+               $folder = dirname($reference);
+               if (!in_array($folder, self::VALID_FOLDERS)) {
+                       throw new ReferenceStorageException(sprintf('System Resource is invalid for reference %s, no valid folder found', $reference));
+               }
+               if (!file_exists($reference)) {
+                       throw new StorageException(sprintf('System Resource is invalid for reference %s, the file doesn\'t exist', $reference));
+               }
+               $content = file_get_contents($reference);
+
+               if ($content === false) {
+                       throw new StorageException(sprintf('Cannot get content for reference %s', $reference));
+               }
+
+               return $content;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function __toString(): string
+       {
+               return self::NAME;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public static function getName(): string
+       {
+               return self::NAME;
+       }
+}
diff --git a/src/Core/StorageManager.php b/src/Core/StorageManager.php
deleted file mode 100644 (file)
index e27b59e..0000000
+++ /dev/null
@@ -1,401 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Core;
-
-use Exception;
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Database\Database;
-use Friendica\Model\Storage;
-use Friendica\Network\HTTPException\InternalServerErrorException;
-use Psr\Log\LoggerInterface;
-
-/**
- * Manage storage backends
- *
- * Core code uses this class to get and set current storage backend class.
- * Addons use this class to register and unregister additional backends.
- */
-class StorageManager
-{
-       // Default tables to look for data
-       const TABLES = ['photo', 'attach'];
-
-       // Default storage backends
-       /** @var string[]  */
-       const DEFAULT_BACKENDS = [
-               Storage\Filesystem::NAME,
-               Storage\Database::NAME,
-       ];
-
-       /** @var string[] List of valid backend classes */
-       private $validBackends;
-
-       /**
-        * @var Storage\IStorage[] A local cache for storage instances
-        */
-       private $backendInstances = [];
-
-       /** @var Database */
-       private $dba;
-       /** @var IManageConfigValues */
-       private $config;
-       /** @var LoggerInterface */
-       private $logger;
-       /** @var L10n */
-       private $l10n;
-
-       /** @var Storage\IWritableStorage */
-       private $currentBackend;
-
-       /**
-        * @param Database            $dba
-        * @param IManageConfigValues $config
-        * @param LoggerInterface     $logger
-        * @param L10n                $l10n
-        *
-        * @throws Storage\InvalidClassStorageException in case the active backend class is invalid
-        * @throws Storage\StorageException in case of unexpected errors during the active backend class loading
-        */
-       public function __construct(Database $dba, IManageConfigValues $config, LoggerInterface $logger, L10n $l10n)
-       {
-               $this->dba           = $dba;
-               $this->config        = $config;
-               $this->logger        = $logger;
-               $this->l10n          = $l10n;
-               $this->validBackends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
-
-               $currentName = $this->config->get('storage', 'name');
-
-               // you can only use user backends as a "default" backend, so the second parameter is true
-               $this->currentBackend = $this->getWritableStorageByName($currentName);
-       }
-
-       /**
-        * Return current storage backend class
-        *
-        * @return Storage\IWritableStorage
-        */
-       public function getBackend()
-       {
-               return $this->currentBackend;
-       }
-
-       /**
-        * Returns a writable storage backend class by registered name
-        *
-        * @param string $name Backend name
-        *
-        * @return Storage\IWritableStorage
-        *
-        * @throws Storage\InvalidClassStorageException in case there's no backend class for the name
-        * @throws Storage\StorageException in case of an unexpected failure during the hook call
-        */
-       public function getWritableStorageByName(string $name): Storage\IWritableStorage
-       {
-               $storage = $this->getByName($name, $this->validBackends);
-               if (!$storage instanceof Storage\IWritableStorage) {
-                       throw new Storage\InvalidClassStorageException(sprintf('Backend %s is not writable', $name));
-               }
-
-               return $storage;
-       }
-
-       /**
-        * Return storage backend configuration by registered name
-        *
-        * @param string     $name Backend name
-        *
-        * @return Storage\IStorageConfiguration|false
-        *
-        * @throws Storage\InvalidClassStorageException in case there's no backend class for the name
-        * @throws Storage\StorageException in case of an unexpected failure during the hook call
-        */
-       public function getConfigurationByName(string $name)
-       {
-               switch ($name) {
-                       // Try the filesystem backend
-                       case Storage\Filesystem::getName():
-                               return new Storage\FilesystemConfig($this->config, $this->l10n);
-                       // try the database backend
-                       case Storage\Database::getName():
-                               return false;
-                       default:
-                               $data = [
-                                       'name'           => $name,
-                                       'storage_config' => null,
-                               ];
-                               try {
-                                       Hook::callAll('storage_config', $data);
-                                       if (!($data['storage_config'] ?? null) instanceof Storage\IStorageConfiguration) {
-                                               throw new Storage\InvalidClassStorageException(sprintf('Configuration for backend %s was not found', $name));
-                                       }
-
-                                       return $data['storage_config'];
-                               } catch (InternalServerErrorException $exception) {
-                                       throw new Storage\StorageException(sprintf('Failed calling hook::storage_config for backend %s', $name), $exception);
-                               }
-               }
-       }
-
-       /**
-        * Return storage backend class by registered name
-        *
-        * @param string     $name Backend name
-        * @param string[]|null $validBackends possible, manual override of the valid backends
-        *
-        * @return Storage\IStorage
-        *
-        * @throws Storage\InvalidClassStorageException in case there's no backend class for the name
-        * @throws Storage\StorageException in case of an unexpected failure during the hook call
-        */
-       public function getByName(string $name, array $validBackends = null): Storage\IStorage
-       {
-               // If there's no cached instance create a new instance
-               if (!isset($this->backendInstances[$name])) {
-                       // If the current name isn't a valid backend (or the SystemResource instance) create it
-                       if (!$this->isValidBackend($name, $validBackends)) {
-                               throw new Storage\InvalidClassStorageException(sprintf('Backend %s is not valid', $name));
-                       }
-
-                       switch ($name) {
-                               // Try the filesystem backend
-                               case Storage\Filesystem::getName():
-                                       $storageConfig                 = new Storage\FilesystemConfig($this->config, $this->l10n);
-                                       $this->backendInstances[$name] = new Storage\Filesystem($storageConfig->getStoragePath());
-                                       break;
-                               // try the database backend
-                               case Storage\Database::getName():
-                                       $this->backendInstances[$name] = new Storage\Database($this->dba);
-                                       break;
-                               // at least, try if there's an addon for the backend
-                               case Storage\SystemResource::getName():
-                                       $this->backendInstances[$name] = new Storage\SystemResource();
-                                       break;
-                               case Storage\ExternalResource::getName():
-                                       $this->backendInstances[$name] = new Storage\ExternalResource();
-                                       break;
-                               default:
-                                       $data = [
-                                               'name'    => $name,
-                                               'storage' => null,
-                                       ];
-                                       try {
-                                               Hook::callAll('storage_instance', $data);
-                                               if (!($data['storage'] ?? null) instanceof Storage\IStorage) {
-                                                       throw new Storage\InvalidClassStorageException(sprintf('Backend %s was not found', $name));
-                                               }
-
-                                               $this->backendInstances[$data['name'] ?? $name] = $data['storage'];
-                                       } catch (InternalServerErrorException $exception) {
-                                               throw new Storage\StorageException(sprintf('Failed calling hook::storage_instance for backend %s', $name), $exception);
-                                       }
-                                       break;
-                       }
-               }
-
-               return $this->backendInstances[$name];
-       }
-
-       /**
-        * Checks, if the storage is a valid backend
-        *
-        * @param string|null   $name          The name or class of the backend
-        * @param string[]|null $validBackends Possible, valid backends to check
-        *
-        * @return boolean True, if the backend is a valid backend
-        */
-       public function isValidBackend(string $name = null, array $validBackends = null): bool
-       {
-               $validBackends = $validBackends ?? array_merge($this->validBackends,
-                               [
-                                       Storage\SystemResource::getName(),
-                                       Storage\ExternalResource::getName(),
-                               ]);
-               return in_array($name, $validBackends);
-       }
-
-       /**
-        * Set current storage backend class
-        *
-        * @param Storage\IWritableStorage $storage The storage class
-        *
-        * @return boolean True, if the set was successful
-        */
-       public function setBackend(Storage\IWritableStorage $storage): bool
-       {
-               if ($this->config->set('storage', 'name', $storage::getName())) {
-                       $this->currentBackend = $storage;
-                       return true;
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Get registered backends
-        *
-        * @return string[]
-        */
-       public function listBackends(): array
-       {
-               return $this->validBackends;
-       }
-
-       /**
-        * Register a storage backend class
-        *
-        * You have to register the hook "storage_instance" as well to make this class work!
-        *
-        * @param string $class Backend class name
-        *
-        * @return boolean True, if the registration was successful
-        */
-       public function register(string $class): bool
-       {
-               if (is_subclass_of($class, Storage\IStorage::class)) {
-                       /** @var Storage\IStorage $class */
-
-                       if ($this->isValidBackend($class::getName(), $this->validBackends)) {
-                               return true;
-                       }
-
-                       $backends   = $this->validBackends;
-                       $backends[] = $class::getName();
-
-                       if ($this->config->set('storage', 'backends', $backends)) {
-                               $this->validBackends = $backends;
-                               return true;
-                       } else {
-                               return false;
-                       }
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Unregister a storage backend class
-        *
-        * @param string $class Backend class name
-        *
-        * @return boolean True, if unregistering was successful
-        *
-        * @throws Storage\StorageException
-        */
-       public function unregister(string $class): bool
-       {
-               if (is_subclass_of($class, Storage\IStorage::class)) {
-                       /** @var Storage\IStorage $class */
-
-                       if ($this->currentBackend::getName() == $class::getName()) {
-                               throw new Storage\StorageException(sprintf('Cannot unregister %s, because it\'s currently active.', $class::getName()));
-                       }
-
-                       $key = array_search($class::getName(), $this->validBackends);
-
-                       if ($key !== false) {
-                               $backends = $this->validBackends;
-                               unset($backends[$key]);
-                               $backends = array_values($backends);
-                               if ($this->config->set('storage', 'backends', $backends)) {
-                                       $this->validBackends = $backends;
-                                       return true;
-                               } else {
-                                       return false;
-                               }
-                       } else {
-                               return true;
-                       }
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * Move up to 5000 resources to storage $dest
-        *
-        * Copy existing data to destination storage and delete from source.
-        * This method cannot move to legacy in-table `data` field.
-        *
-        * @param Storage\IWritableStorage $destination Destination storage class name
-        * @param array                    $tables      Tables to look in for resources. Optional, defaults to ['photo', 'attach']
-        * @param int                      $limit       Limit of the process batch size, defaults to 5000
-        *
-        * @return int Number of moved resources
-        * @throws Storage\StorageException
-        * @throws Exception
-        */
-       public function move(Storage\IWritableStorage $destination, array $tables = self::TABLES, int $limit = 5000): int
-       {
-               if (!$this->isValidBackend($destination, $this->validBackends)) {
-                       throw new Storage\StorageException(sprintf("Can't move to storage backend '%s'", $destination::getName()));
-               }
-
-               $moved = 0;
-               foreach ($tables as $table) {
-                       // Get the rows where backend class is not the destination backend class
-                       $resources = $this->dba->select(
-                               $table,
-                               ['id', 'data', 'backend-class', 'backend-ref'],
-                               ['`backend-class` IS NULL or `backend-class` != ?', $destination::getName()],
-                               ['limit' => $limit]
-                       );
-
-                       while ($resource = $this->dba->fetch($resources)) {
-                               $id        = $resource['id'];
-                               $sourceRef = $resource['backend-ref'];
-                               $source    = null;
-
-                               try {
-                                       $source = $this->getWritableStorageByName($resource['backend-class'] ?? '');
-                                       $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
-                                       $data = $source->get($sourceRef);
-                               } catch (Storage\InvalidClassStorageException $exception) {
-                                       $this->logger->info('Get data from DB resource field.', ['oldReference' => $sourceRef]);
-                                       $data = $resource['data'];
-                               } catch (Storage\ReferenceStorageException $exception) {
-                                       $this->logger->info('Invalid source reference.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
-                                       continue;
-                               }
-
-                               $this->logger->info('Save data to new backend.', ['newBackend' => $destination::getName()]);
-                               $destinationRef = $destination->put($data);
-                               $this->logger->info('Saved data.', ['newReference' => $destinationRef]);
-
-                               if ($destinationRef !== '') {
-                                       $this->logger->info('update row');
-                                       if ($this->dba->update($table, ['backend-class' => $destination::getName(), 'backend-ref' => $destinationRef, 'data' => ''], ['id' => $id])) {
-                                               if (!empty($source)) {
-                                                       $this->logger->info('Delete data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]);
-                                                       $source->delete($sourceRef);
-                                               }
-                                               $moved++;
-                                       }
-                               }
-                       }
-
-                       $this->dba->close($resources);
-               }
-
-               return $moved;
-       }
-}
index d45801a9f69780bf59c54db0cac38b2f594b2af4..5ba7e88db1f00379891b1e97d33d7352a81cbd01 100644 (file)
@@ -211,11 +211,11 @@ abstract class DI
        }
 
        /**
-        * @return Core\StorageManager
+        * @return \Friendica\Core\Storage\Repository\StorageManager
         */
        public static function storageManager()
        {
-               return self::$dice->create(Core\StorageManager::class);
+               return self::$dice->create(Core\Storage\Repository\StorageManager::class);
        }
 
        //
@@ -395,11 +395,11 @@ abstract class DI
        }
 
        /**
-        * @return Model\Storage\IWritableStorage
+        * @return Core\Storage\Capability\ICanWriteToStorage
         */
        public static function storage()
        {
-               return self::$dice->create(Model\Storage\IWritableStorage::class);
+               return self::$dice->create(Core\Storage\Capability\ICanWriteToStorage::class);
        }
 
        /**
index e11fd01bc3321d08edf02e83ca7e2f3f1fcb5cc5..dcc79067631758e237bafe6b04304645bc0438e6 100644 (file)
@@ -25,8 +25,8 @@ use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\Database\DBStructure;
 use Friendica\DI;
-use Friendica\Model\Storage\InvalidClassStorageException;
-use Friendica\Model\Storage\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
 use Friendica\Object\Image;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Mimetype;
index ebd278753b9a17acc2a3e09204fa1010bdc508c5..13beb05b3caabdc831adc394dee7aa6cd502054a 100644 (file)
@@ -27,11 +27,11 @@ use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\Database\DBStructure;
 use Friendica\DI;
-use Friendica\Model\Storage\ExternalResource;
-use Friendica\Model\Storage\InvalidClassStorageException;
-use Friendica\Model\Storage\ReferenceStorageException;
-use Friendica\Model\Storage\StorageException;
-use Friendica\Model\Storage\SystemResource;
+use Friendica\Core\Storage\Type\ExternalResource;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Type\SystemResource;
 use Friendica\Object\Image;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
diff --git a/src/Model/Storage/Database.php b/src/Model/Storage/Database.php
deleted file mode 100644 (file)
index 7b90d87..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-use Friendica\Database\Database as DBA;
-
-/**
- * Database based storage system
- *
- * This class manage data stored in database table.
- */
-class Database implements IWritableStorage
-{
-       const NAME = 'Database';
-
-       /** @var DBA */
-       private $dba;
-
-       /**
-        * @param DBA             $dba
-        */
-       public function __construct(DBA $dba)
-       {
-               $this->dba = $dba;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function get(string $reference): string
-       {
-               try {
-                       $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]);
-                       if (!$this->dba->isResult($result)) {
-                               throw new ReferenceStorageException(sprintf('Database storage cannot find data for reference %s', $reference));
-                       }
-
-                       return $result['data'];
-               } catch (Exception $exception) {
-                       if ($exception instanceof ReferenceStorageException) {
-                               throw $exception;
-                       } else {
-                               throw new StorageException(sprintf('Database storage failed to get %s', $reference), $exception->getCode(), $exception);
-                       }
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function put(string $data, string $reference = ''): string
-       {
-               if ($reference !== '') {
-                       try {
-                               $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]);
-                       } catch (Exception $exception) {
-                               throw new StorageException(sprintf('Database storage failed to update %s', $reference), $exception->getCode(), $exception);
-                       }
-                       if ($result === false) {
-                               throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
-                       }
-
-                       return $reference;
-               } else {
-                       try {
-                               $result = $this->dba->insert('storage', ['data' => $data]);
-                       } catch (Exception $exception) {
-                               throw new StorageException(sprintf('Database storage failed to insert %s', $reference), $exception->getCode(), $exception);
-                       }
-                       if ($result === false) {
-                               throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
-                       }
-
-                       return $this->dba->lastInsertId();
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function delete(string $reference)
-       {
-               try {
-                       if (!$this->dba->delete('storage', ['id' => $reference]) || $this->dba->affectedRows() === 0) {
-                               throw new ReferenceStorageException(sprintf('Database storage failed to delete %s', $reference));
-                       }
-               } catch (Exception $exception) {
-                       if ($exception instanceof ReferenceStorageException) {
-                               throw $exception;
-                       } else {
-                               throw new StorageException(sprintf('Database storage failed to delete %s', $reference), $exception->getCode(), $exception);
-                       }
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public static function getName(): string
-       {
-               return self::NAME;
-       }
-
-       public function __toString()
-       {
-               return self::getName();
-       }
-}
diff --git a/src/Model/Storage/ExternalResource.php b/src/Model/Storage/ExternalResource.php
deleted file mode 100644 (file)
index 413050c..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-use Friendica\Util\HTTPSignature;
-
-/**
- * External resource storage class
- *
- * This class is used to load external resources, like images.
- * Is not intended to be selectable by admins as default storage class.
- */
-class ExternalResource implements IStorage
-{
-       const NAME = 'ExternalResource';
-
-       /**
-        * @inheritDoc
-        */
-       public function get(string $reference): string
-       {
-               $data = json_decode($reference);
-               if (empty($data->url)) {
-                       throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot retrieve URL', $reference));
-               }
-
-               $parts = parse_url($data->url);
-               if (empty($parts['scheme']) || empty($parts['host'])) {
-                       throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot extract scheme and host', $reference));
-               }
-
-               try {
-                       $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => []]);
-               } catch (Exception $exception) {
-                       throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $exception->getCode(), $exception);
-               }
-               if ($fetchResult->isSuccess()) {
-                       return $fetchResult->getBody();
-               } else {
-                       throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $fetchResult->getReturnCode(), new Exception($fetchResult->getBody()));
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function __toString()
-       {
-               return self::NAME;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public static function getName(): string
-       {
-               return self::NAME;
-       }
-}
diff --git a/src/Model/Storage/Filesystem.php b/src/Model/Storage/Filesystem.php
deleted file mode 100644 (file)
index 8b5078f..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-use Friendica\Util\Strings;
-
-/**
- * Filesystem based storage backend
- *
- * This class manage data on filesystem.
- * Base folder for storage is set in storage.filesystem_path.
- * Best would be for storage folder to be outside webserver folder, we are using a
- * folder relative to code tree root as default to ease things for users in shared hostings.
- * Each new resource gets a value as reference and is saved in a
- * folder tree stucture created from that value.
- */
-class Filesystem implements IWritableStorage
-{
-       const NAME = 'Filesystem';
-
-       /** @var string */
-       private $basePath;
-
-       /**
-        * Filesystem constructor.
-        *
-        * @param string $filesystemPath
-        *
-        * @throws StorageException in case the path doesn't exist or isn't writeable
-        */
-       public function __construct(string $filesystemPath = FilesystemConfig::DEFAULT_BASE_FOLDER)
-       {
-               $path           = $filesystemPath;
-               $this->basePath = rtrim($path, '/');
-
-               if (!is_dir($this->basePath) || !is_writable($this->basePath)) {
-                       throw new StorageException(sprintf('Path "%s" does not exist or is not writeable.', $this->basePath));
-               }
-       }
-
-       /**
-        * Split data ref and return file path
-        *
-        * @param string $reference Data reference
-        *
-        * @return string
-        */
-       private function pathForRef(string $reference): string
-       {
-               $fold1 = substr($reference, 0, 2);
-               $fold2 = substr($reference, 2, 2);
-               $file  = substr($reference, 4);
-
-               return implode('/', [$this->basePath, $fold1, $fold2, $file]);
-       }
-
-
-       /**
-        * Create directory tree to store file, with .htaccess and index.html files
-        *
-        * @param string $file Path and filename
-        *
-        * @throws StorageException
-        */
-       private function createFoldersForFile(string $file)
-       {
-               $path = dirname($file);
-
-               if (!is_dir($path)) {
-                       if (!mkdir($path, 0770, true)) {
-                               throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path));
-                       }
-               }
-
-               while ($path !== $this->basePath) {
-                       if (!is_file($path . '/index.html')) {
-                               file_put_contents($path . '/index.html', '');
-                       }
-                       chmod($path . '/index.html', 0660);
-                       chmod($path, 0770);
-                       $path = dirname($path);
-               }
-               if (!is_file($path . '/index.html')) {
-                       file_put_contents($path . '/index.html', '');
-                       chmod($path . '/index.html', 0660);
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function get(string $reference): string
-       {
-               $file = $this->pathForRef($reference);
-               if (!is_file($file)) {
-                       throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference));
-               }
-
-               $result = file_get_contents($file);
-
-               if ($result === false) {
-                       throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file));
-               }
-
-               return $result;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function put(string $data, string $reference = ''): string
-       {
-               if ($reference === '') {
-                       try {
-                               $reference = Strings::getRandomHex();
-                       } catch (Exception $exception) {
-                               throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception);
-                       }
-               }
-               $file = $this->pathForRef($reference);
-
-               $this->createFoldersForFile($file);
-
-               $result = file_put_contents($file, $data);
-
-               // just in case the result is REALLY false, not zero or empty or anything else, throw the exception
-               if ($result === false) {
-                       throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
-               }
-
-               chmod($file, 0660);
-               return $reference;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function delete(string $reference)
-       {
-               $file = $this->pathForRef($reference);
-               if (!is_file($file)) {
-                       throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference));
-               }
-
-               if (!unlink($file)) {
-                       throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference));
-               }
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public static function getName(): string
-       {
-               return self::NAME;
-       }
-
-       public function __toString()
-       {
-               return self::getName();
-       }
-}
diff --git a/src/Model/Storage/FilesystemConfig.php b/src/Model/Storage/FilesystemConfig.php
deleted file mode 100644 (file)
index 1aa1c38..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\L10n;
-
-/**
- * Filesystem based storage backend configuration
- */
-class FilesystemConfig implements IStorageConfiguration
-{
-       // Default base folder
-       const DEFAULT_BASE_FOLDER = 'storage';
-
-       /** @var IManageConfigValues */
-       private $config;
-
-       /** @var string */
-       private $storagePath;
-
-       /** @var L10n */
-       private $l10n;
-
-       /**
-        * Returns the current storage path
-        *
-        * @return string
-        */
-       public function getStoragePath(): string
-       {
-               return $this->storagePath;
-       }
-
-       /**
-        * Filesystem constructor.
-        *
-        * @param IManageConfigValues $config
-        * @param L10n                $l10n
-        */
-       public function __construct(IManageConfigValues $config, L10n $l10n)
-       {
-               $this->config = $config;
-               $this->l10n   = $l10n;
-
-               $path              = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
-               $this->storagePath = rtrim($path, '/');
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function getOptions(): array
-       {
-               return [
-                       'storagepath' => [
-                               'input',
-                               $this->l10n->t('Storage base path'),
-                               $this->storagePath,
-                               $this->l10n->t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
-                       ]
-               ];
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function saveOptions(array $data): array
-       {
-               $storagePath = $data['storagepath'] ?? '';
-               if ($storagePath === '' || !is_dir($storagePath)) {
-                       return [
-                               'storagepath' => $this->l10n->t('Enter a valid existing folder')
-                       ];
-               };
-               $this->config->set('storage', 'filesystem_path', $storagePath);
-               $this->storagePath = $storagePath;
-               return [];
-       }
-}
diff --git a/src/Model/Storage/IStorage.php b/src/Model/Storage/IStorage.php
deleted file mode 100644 (file)
index 8841487..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Interface for basic storage backends
- */
-interface IStorage
-{
-       /**
-        * Get data from backend
-        *
-        * @param string $reference Data reference
-        *
-        * @return string
-        *
-        * @throws StorageException in case there's an unexpected error
-        * @throws ReferenceStorageException in case the reference doesn't exist
-        */
-       public function get(string $reference): string;
-
-       /**
-        * The name of the backend
-        *
-        * @return string
-        */
-       public function __toString();
-
-       /**
-        * The name of the backend
-        *
-        * @return string
-        */
-       public static function getName(): string;
-}
diff --git a/src/Model/Storage/IStorageConfiguration.php b/src/Model/Storage/IStorageConfiguration.php
deleted file mode 100644 (file)
index c589f5e..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * The interface to use for configurable storage backends
- */
-interface IStorageConfiguration
-{
-       /**
-        * Get info about storage options
-        *
-        * @return array
-        *
-        * This method return an array with information about storage options
-        * from which the form presented to the user is build.
-        *
-        * The returned array is:
-        *
-        *    [
-        *      'option1name' => [ ..info.. ],
-        *      'option2name' => [ ..info.. ],
-        *      ...
-        *    ]
-        *
-        * An empty array can be returned if backend doesn't have any options
-        *
-        * The info array for each option MUST be as follows:
-        *
-        *    [
-        *      'type',      // define the field used in form, and the type of data.
-        *                   // one of 'checkbox', 'combobox', 'custom', 'datetime',
-        *                   // 'input', 'intcheckbox', 'password', 'radio', 'richtext'
-        *                   // 'select', 'select_raw', 'textarea'
-        *
-        *      'label',     // Translatable label of the field
-        *      'value',     // Current value
-        *      'help text', // Translatable description for the field
-        *      extra data   // Optional. Depends on 'type':
-        *                   // select: array [ value => label ] of choices
-        *                   // intcheckbox: value of input element
-        *                   // select_raw: prebuild html string of < option > tags
-        *    ]
-        *
-        * See https://github.com/friendica/friendica/wiki/Quick-Template-Guide
-        */
-       public function getOptions(): array;
-
-       /**
-        * Validate and save options
-        *
-        * @param array $data Array [optionname => value] to be saved
-        *
-        * @return array  Validation errors: [optionname => error message]
-        *
-        * Return array must be empty if no error.
-        */
-       public function saveOptions(array $data): array;
-}
diff --git a/src/Model/Storage/IWritableStorage.php b/src/Model/Storage/IWritableStorage.php
deleted file mode 100644 (file)
index 118f4b2..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Interface for writable storage backends
- *
- * Used for storages with CRUD functionality, mainly used for user data (e.g. photos, attachements).
- * There's only one active writable storage possible. This type of storage is selectable by the current administrator.
- */
-interface IWritableStorage extends IStorage
-{
-       /**
-        * Put data in backend as $ref. If $ref is not defined a new reference is created.
-        *
-        * @param string $data      Data to save
-        * @param string $reference Data reference. Optional.
-        *
-        * @return string Saved data reference
-        *
-        * @throws StorageException in case there's an unexpected error
-        */
-       public function put(string $data, string $reference = ""): string;
-
-       /**
-        * Remove data from backend
-        *
-        * @param string $reference Data reference
-        *
-        * @throws StorageException in case there's an unexpected error
-        * @throws ReferenceStorageException in case the reference doesn't exist
-        */
-       public function delete(string $reference);
-}
diff --git a/src/Model/Storage/InvalidClassStorageException.php b/src/Model/Storage/InvalidClassStorageException.php
deleted file mode 100644 (file)
index 9c39b3a..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Storage Exception in case of invalid storage class
- */
-class InvalidClassStorageException extends StorageException
-{
-}
diff --git a/src/Model/Storage/ReferenceStorageException.php b/src/Model/Storage/ReferenceStorageException.php
deleted file mode 100644 (file)
index fcfd3ab..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * Storage Exception in case of invalid references
- */
-class ReferenceStorageException extends StorageException
-{
-}
diff --git a/src/Model/Storage/StorageException.php b/src/Model/Storage/StorageException.php
deleted file mode 100644 (file)
index 34a09d5..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-use Exception;
-
-/**
- * Storage Exception for unexpected failures
- */
-class StorageException extends Exception
-{
-}
diff --git a/src/Model/Storage/SystemResource.php b/src/Model/Storage/SystemResource.php
deleted file mode 100644 (file)
index 39bc0a0..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Model\Storage;
-
-/**
- * System resource storage class
- *
- * This class is used to load system resources, like images.
- * Is not intended to be selectable by admins as default storage class.
- */
-class SystemResource implements IStorage
-{
-       const NAME = 'SystemResource';
-
-       // Valid folders to look for resources
-       const VALID_FOLDERS = ["images"];
-
-       /**
-        * @inheritDoc
-        */
-       public function get(string $reference): string
-       {
-               $folder = dirname($reference);
-               if (!in_array($folder, self::VALID_FOLDERS)) {
-                       throw new ReferenceStorageException(sprintf('System Resource is invalid for reference %s, no valid folder found', $reference));
-               }
-               if (!file_exists($reference)) {
-                       throw new StorageException(sprintf('System Resource is invalid for reference %s, the file doesn\'t exist', $reference));
-               }
-               $content = file_get_contents($reference);
-
-               if ($content === false) {
-                       throw new StorageException(sprintf('Cannot get content for reference %s', $reference));
-               }
-
-               return $content;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public function __toString()
-       {
-               return self::NAME;
-       }
-
-       /**
-        * @inheritDoc
-        */
-       public static function getName(): string
-       {
-               return self::NAME;
-       }
-}
index 6b22d905cf2474cbb6f48f17a7f32aa238099d59..8eb706fa03addb6df3950fa6ab755c30a7c2089d 100644 (file)
@@ -23,9 +23,9 @@ namespace Friendica\Module\Admin;
 
 use Friendica\Core\Renderer;
 use Friendica\DI;
-use Friendica\Model\Storage\InvalidClassStorageException;
-use Friendica\Model\Storage\IStorageConfiguration;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 use Friendica\Module\BaseAdmin;
 use Friendica\Util\Strings;
 
@@ -40,7 +40,7 @@ class Storage extends BaseAdmin
                $storagebackend = Strings::escapeTags(trim($parameters['name'] ?? ''));
 
                try {
-                       /** @var IStorageConfiguration|false $newStorageConfig */
+                       /** @var \Friendica\Core\Storage\Capability\ICanConfigureStorage|false $newStorageConfig */
                        $newStorageConfig = DI::storageManager()->getConfigurationByName($storagebackend);
                } catch (InvalidClassStorageException $storageException) {
                        notice(DI::l10n()->t('Storage backend, %s is invalid.', $storagebackend));
@@ -78,7 +78,7 @@ class Storage extends BaseAdmin
 
                if (!empty($_POST['submit_save_set'])) {
                        try {
-                               /** @var IWritableStorage $newstorage */
+                               /** @var \Friendica\Core\Storage\Capability\ICanWriteToStorage $newstorage */
                                $newstorage = DI::storageManager()->getWritableStorageByName($storagebackend);
 
                                if (!DI::storageManager()->setBackend($newstorage)) {
@@ -129,7 +129,7 @@ class Storage extends BaseAdmin
                                'name'   => $name,
                                'prefix' => $storage_form_prefix,
                                'form'   => $storage_form,
-                               'active' => $current_storage_backend instanceof IWritableStorage && $name === $current_storage_backend::getName(),
+                               'active' => $current_storage_backend instanceof ICanWriteToStorage && $name === $current_storage_backend::getName(),
                        ];
                }
 
@@ -147,7 +147,7 @@ class Storage extends BaseAdmin
                        '$noconfig'              => DI::l10n()->t('This backend doesn\'t have custom settings'),
                        '$baseurl'               => DI::baseUrl()->get(true),
                        '$form_security_token'   => self::getFormSecurityToken("admin_storage"),
-                       '$storagebackend'        => $current_storage_backend instanceof IWritableStorage ? $current_storage_backend::getName() : DI::l10n()->t('Database (legacy)'),
+                       '$storagebackend'        => $current_storage_backend instanceof ICanWriteToStorage ? $current_storage_backend::getName() : DI::l10n()->t('Database (legacy)'),
                        '$availablestorageforms' => $available_storage_forms,
                ]);
        }
index 0b03f4a7995965bbfaa5bbc36988357973b6c24c..db21803e6df454e1ff4873344703bc71647d711c 100644 (file)
@@ -29,8 +29,8 @@ use Friendica\Model\Contact;
 use Friendica\Model\Photo as MPhoto;
 use Friendica\Model\Post;
 use Friendica\Model\Profile;
-use Friendica\Model\Storage\ExternalResource;
-use Friendica\Model\Storage\SystemResource;
+use Friendica\Core\Storage\Type\ExternalResource;
+use Friendica\Core\Storage\Type\SystemResource;
 use Friendica\Model\User;
 use Friendica\Network\HTTPException;
 use Friendica\Object\Image;
index f4aacb340ce3e46c53c54328cda17c03171397fe..bbf8c5599c1d65e6769b34b62ff859db150eea2d 100644 (file)
@@ -42,10 +42,10 @@ use Friendica\Core\L10n;
 use Friendica\Core\Lock;
 use Friendica\Core\Process;
 use Friendica\Core\Session\Capability\IHandleSessions;
-use Friendica\Core\StorageManager;
+use Friendica\Core\Storage\Repository\StorageManager;
 use Friendica\Database\Database;
 use Friendica\Factory;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 use Friendica\Model\User\Cookie;
 use Friendica\Model\Log\ParsedLogIterator;
 use Friendica\Network;
@@ -218,7 +218,7 @@ return [
                        $_SERVER, $_COOKIE
                ],
        ],
-       IWritableStorage::class => [
+       ICanWriteToStorage::class => [
                'instanceOf' => StorageManager::class,
                'call' => [
                        ['getBackend', [], Dice::CHAIN_CALL],
index 1185a2564695be78b82432d956ae1ab03576bc74..03fca0171f64685c02dc18707b842fb31cfe3d09 100644 (file)
 namespace Friendica\Test\Util;
 
 use Friendica\Core\Hook;
-use Friendica\Model\Storage\IWritableStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 
 use Friendica\Core\L10n;
 
 /**
  * A backend storage example class
  */
-class SampleStorageBackend implements IWritableStorage
+class SampleStorageBackend implements ICanWriteToStorage
 {
        const NAME = 'Sample Storage';
 
@@ -102,7 +102,7 @@ class SampleStorageBackend implements IWritableStorage
                return $this->options;
        }
 
-       public function __toString()
+       public function __toString(): string
        {
                return self::NAME;
        }
diff --git a/tests/src/Core/Storage/DatabaseStorageTest.php b/tests/src/Core/Storage/DatabaseStorageTest.php
new file mode 100644 (file)
index 0000000..b51f9fc
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Config\Factory\Config;
+use Friendica\Core\Storage\Type\Database;
+use Friendica\Test\DatabaseTestTrait;
+use Friendica\Test\Util\Database\StaticDatabase;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Util\Profiler;
+use Psr\Log\NullLogger;
+
+class DatabaseStorageTest extends StorageTest
+{
+       use DatabaseTestTrait;
+       use VFSTrait;
+
+       protected function setUp(): void
+       {
+               $this->setUpVfsDir();
+
+               $this->setUpDb();
+
+               parent::setUp();
+       }
+
+       protected function getInstance()
+       {
+               $logger   = new NullLogger();
+               $profiler = \Mockery::mock(Profiler::class);
+               $profiler->shouldReceive('startRecording');
+               $profiler->shouldReceive('stopRecording');
+               $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
+
+               // load real config to avoid mocking every config-entry which is related to the Database class
+               $configFactory = new Config();
+               $loader        = (new Config())->createConfigFileLoader($this->root->url(), []);
+               $configCache   = $configFactory->createCache($loader);
+
+               $dba = new StaticDatabase($configCache, $profiler, $logger);
+
+               return new Database($dba);
+       }
+
+       protected function tearDown(): void
+       {
+               $this->tearDownDb();
+
+               parent::tearDown();
+       }
+}
diff --git a/tests/src/Core/Storage/FilesystemStorageConfigTest.php b/tests/src/Core/Storage/FilesystemStorageConfigTest.php
new file mode 100644 (file)
index 0000000..a48faed
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\L10n;
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Core\Storage\Type\FilesystemConfig;
+use Friendica\Test\Util\VFSTrait;
+use Mockery\MockInterface;
+use org\bovigo\vfs\vfsStream;
+
+class FilesystemStorageConfigTest extends StorageConfigTest
+{
+       use VFSTrait;
+
+       protected function setUp(): void
+       {
+               $this->setUpVfsDir();
+
+               vfsStream::create(['storage' => []], $this->root);
+
+               parent::setUp();
+       }
+
+       protected function getInstance()
+       {
+               /** @var MockInterface|L10n $l10n */
+               $l10n   = \Mockery::mock(L10n::class)->makePartial();
+               $config = \Mockery::mock(IManageConfigValues::class);
+               $config->shouldReceive('get')
+                                        ->with('storage', 'filesystem_path', FilesystemConfig::DEFAULT_BASE_FOLDER)
+                                        ->andReturn($this->root->getChild('storage')->url());
+
+               return new FilesystemConfig($config, $l10n);
+       }
+
+       protected function assertOption(ICanConfigureStorage $storage)
+       {
+               self::assertEquals([
+                       'storagepath' => [
+                               'input', 'Storage base path',
+                               $this->root->getChild('storage')->url(),
+                               'Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree'
+                       ]
+               ], $storage->getOptions());
+       }
+}
diff --git a/tests/src/Core/Storage/FilesystemStorageTest.php b/tests/src/Core/Storage/FilesystemStorageTest.php
new file mode 100644 (file)
index 0000000..c97b937
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Storage\Exception\StorageException;
+use Friendica\Core\Storage\Type\Filesystem;
+use Friendica\Core\Storage\Type\FilesystemConfig;
+use Friendica\Test\Util\VFSTrait;
+use org\bovigo\vfs\vfsStream;
+
+class FilesystemStorageTest extends StorageTest
+{
+       use VFSTrait;
+
+       protected function setUp(): void
+       {
+               $this->setUpVfsDir();
+
+               vfsStream::create(['storage' => []], $this->root);
+
+               parent::setUp();
+       }
+
+       protected function getInstance()
+       {
+               return new Filesystem($this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
+       }
+
+       /**
+        * Test the exception in case of missing directory permissions during put new files
+        */
+       public function testMissingDirPermissionsDuringPut()
+       {
+               $this->expectException(StorageException::class);
+               $this->expectExceptionMessageMatches("/Filesystem storage failed to create \".*\". Check you write permissions./");
+               $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0777);
+
+               $instance = $this->getInstance();
+
+               $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
+               $instance->put('test');
+       }
+
+       /**
+        * Test the exception in case the directory isn't writeable
+        */
+       public function testMissingDirPermissions()
+       {
+               $this->expectException(StorageException::class);
+               $this->expectExceptionMessageMatches("/Path \".*\" does not exist or is not writeable./");
+               $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
+
+               $this->getInstance();
+       }
+
+       /**
+        * Test the exception in case of missing file permissions
+        *
+        */
+       public function testMissingFilePermissions()
+       {
+               static::markTestIncomplete("Cannot catch file_put_content() error due vfsStream failure");
+
+               $this->expectException(StorageException::class);
+               $this->expectExceptionMessageMatches("/Filesystem storage failed to save data to \".*\". Check your write permissions/");
+
+               vfsStream::create(['storage' => ['f0' => ['c0' => ['k0i0' => '']]]], $this->root);
+
+               $this->root->getChild('storage/f0/c0/k0i0')->chmod(000);
+
+               $instance = $this->getInstance();
+               $instance->put('test', 'f0c0k0i0');
+       }
+
+       /**
+        * Test the backend storage of the Filesystem Storage class
+        */
+       public function testDirectoryTree()
+       {
+               $instance = $this->getInstance();
+
+               $instance->put('test', 'f0c0d0i0');
+
+               $dir  = $this->root->getChild('storage/f0/c0')->url();
+               $file = $this->root->getChild('storage/f0/c0/d0i0')->url();
+
+               self::assertDirectoryExists($dir);
+               self::assertFileExists($file);
+
+               self::assertDirectoryIsWritable($dir);
+               self::assertFileIsWritable($file);
+
+               self::assertEquals('test', file_get_contents($file));
+       }
+}
diff --git a/tests/src/Core/Storage/Repository/StorageManagerTest.php b/tests/src/Core/Storage/Repository/StorageManagerTest.php
new file mode 100644 (file)
index 0000000..c7b6569
--- /dev/null
@@ -0,0 +1,358 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage\Repository;
+
+use Dice\Dice;
+use Friendica\Core\Config\Capability\IManageConfigValues;
+use Friendica\Core\Config\Type\PreloadConfig;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n;
+use Friendica\Core\Session\Capability\IHandleSessions;
+use Friendica\Core\Session\Type\Memory;
+use Friendica\Core\Storage\Exception\InvalidClassStorageException;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Core\Storage\Repository\StorageManager;
+use Friendica\Core\Storage\Type\Filesystem;
+use Friendica\Core\Storage\Type\SystemResource;
+use Friendica\Database\Database;
+use Friendica\DI;
+use Friendica\Core\Config\Factory\Config;
+use Friendica\Core\Config\Repository;
+use Friendica\Core\Storage\Type;
+use Friendica\Network\HTTPClient;
+use Friendica\Test\DatabaseTest;
+use Friendica\Test\Util\Database\StaticDatabase;
+use Friendica\Test\Util\VFSTrait;
+use Friendica\Util\Profiler;
+use org\bovigo\vfs\vfsStream;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Friendica\Test\Util\SampleStorageBackend;
+
+class StorageManagerTest extends DatabaseTest
+{
+       use VFSTrait;
+       /** @var Database */
+       private $dba;
+       /** @var IManageConfigValues */
+       private $config;
+       /** @var LoggerInterface */
+       private $logger;
+       /** @var L10n */
+       private $l10n;
+       /** @var HTTPClient */
+       private $httpRequest;
+
+       protected function setUp(): void
+       {
+               parent::setUp();
+
+               $this->setUpVfsDir();
+
+               vfsStream::newDirectory(Type\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root);
+
+               $this->logger = new NullLogger();
+
+               $profiler = \Mockery::mock(Profiler::class);
+               $profiler->shouldReceive('startRecording');
+               $profiler->shouldReceive('stopRecording');
+               $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
+
+               // load real config to avoid mocking every config-entry which is related to the Database class
+               $configFactory = new Config();
+               $loader        = $configFactory->createConfigFileLoader($this->root->url(), []);
+               $configCache   = $configFactory->createCache($loader);
+
+               $this->dba = new StaticDatabase($configCache, $profiler, $this->logger);
+
+               $configModel  = new Repository\Config($this->dba);
+               $this->config = new PreloadConfig($configCache, $configModel);
+               $this->config->set('storage', 'name', 'Database');
+               $this->config->set('storage', 'filesystem_path', $this->root->getChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
+
+               $this->l10n = \Mockery::mock(L10n::class);
+
+               $this->httpRequest = \Mockery::mock(HTTPClient::class);
+       }
+
+       protected function tearDown(): void
+       {
+               $this->root->removeChild(Type\FilesystemConfig::DEFAULT_BASE_FOLDER);
+
+               parent::tearDown();
+       }
+
+       /**
+        * Test plain instancing first
+        */
+       public function testInstance()
+       {
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest);
+
+               self::assertInstanceOf(StorageManager::class, $storageManager);
+       }
+
+       public function dataStorages()
+       {
+               return [
+                       'empty' => [
+                               'name'       => '',
+                               'valid'      => false,
+                               'interface'  => ICanReadFromStorage::class,
+                               'assert'     => null,
+                               'assertName' => '',
+                       ],
+                       'database' => [
+                               'name'       => Type\Database::NAME,
+                               'valid'      => true,
+                               'interface'  => ICanWriteToStorage::class,
+                               'assert'     => Type\Database::class,
+                               'assertName' => Type\Database::NAME,
+                       ],
+                       'filesystem' => [
+                               'name'       => Filesystem::NAME,
+                               'valid'      => true,
+                               'interface'  => ICanWriteToStorage::class,
+                               'assert'     => Filesystem::class,
+                               'assertName' => Filesystem::NAME,
+                       ],
+                       'systemresource' => [
+                               'name'       => SystemResource::NAME,
+                               'valid'      => true,
+                               'interface'  => ICanReadFromStorage::class,
+                               'assert'     => SystemResource::class,
+                               'assertName' => SystemResource::NAME,
+                       ],
+                       'invalid' => [
+                               'name'        => 'invalid',
+                               'valid'       => false,
+                               'interface'   => null,
+                               'assert'      => null,
+                               'assertName'  => '',
+                               'userBackend' => false,
+                       ],
+               ];
+       }
+
+       /**
+        * Test the getByName() method
+        *
+        * @dataProvider dataStorages
+        */
+       public function testGetByName($name, $valid, $interface, $assert, $assertName)
+       {
+               if (!$valid) {
+                       $this->expectException(InvalidClassStorageException::class);
+               }
+
+               if ($interface === ICanWriteToStorage::class) {
+                       $this->config->set('storage', 'name', $name);
+               }
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               if ($interface === ICanWriteToStorage::class) {
+                       $storage = $storageManager->getWritableStorageByName($name);
+               } else {
+                       $storage = $storageManager->getByName($name);
+               }
+
+               self::assertInstanceOf($interface, $storage);
+               self::assertInstanceOf($assert, $storage);
+               self::assertEquals($assertName, $storage);
+       }
+
+       /**
+        * Test the isValidBackend() method
+        *
+        * @dataProvider dataStorages
+        */
+       public function testIsValidBackend($name, $valid, $interface, $assert, $assertName)
+       {
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               // true in every of the backends
+               self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name));
+
+               // if it's a ICanWriteToStorage, the valid backend should return true, otherwise false
+               self::assertEquals($interface === ICanWriteToStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS));
+       }
+
+       /**
+        * Test the method listBackends() with default setting
+        */
+       public function testListBackends()
+       {
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
+       }
+
+       /**
+        * Test the method getBackend()
+        *
+        * @dataProvider dataStorages
+        */
+       public function testGetBackend($name, $valid, $interface, $assert, $assertName)
+       {
+               if ($interface !== ICanWriteToStorage::class) {
+                       static::markTestSkipped('only works for ICanWriteToStorage');
+               }
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               $selBackend = $storageManager->getWritableStorageByName($name);
+               $storageManager->setBackend($selBackend);
+
+               self::assertInstanceOf($assert, $storageManager->getBackend());
+       }
+
+       /**
+        * Test the method getBackend() with a pre-configured backend
+        *
+        * @dataProvider dataStorages
+        */
+       public function testPresetBackend($name, $valid, $interface, $assert, $assertName)
+       {
+               $this->config->set('storage', 'name', $name);
+               if ($interface !== ICanWriteToStorage::class) {
+                       $this->expectException(InvalidClassStorageException::class);
+               }
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               self::assertInstanceOf($assert, $storageManager->getBackend());
+       }
+
+       /**
+        * Tests the register and unregister methods for a new backend storage class
+        *
+        * Uses a sample storage for testing
+        *
+        * @see SampleStorageBackend
+        */
+       public function testRegisterUnregisterBackends()
+       {
+               /// @todo Remove dice once "Hook" is dynamic and mockable
+               $dice = (new Dice())
+                       ->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php')
+                       ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
+                       ->addRule(IHandleSessions::class, ['instanceOf' => Session\Type\Memory::class, 'shared' => true, 'call' => null]);
+               DI::init($dice);
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               self::assertTrue($storageManager->register(SampleStorageBackend::class));
+
+               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+                       SampleStorageBackend::getName(),
+               ]), $storageManager->listBackends());
+               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+                       SampleStorageBackend::getName()
+               ]), $this->config->get('storage', 'backends'));
+
+               self::assertTrue($storageManager->unregister(SampleStorageBackend::class));
+               self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends'));
+               self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
+       }
+
+       /**
+        * tests that an active backend cannot get unregistered
+        */
+       public function testUnregisterActiveBackend()
+       {
+               /// @todo Remove dice once "Hook" is dynamic and mockable
+               $dice = (new Dice())
+                       ->addRules(include __DIR__ . '/../../../../../static/dependencies.config.php')
+                       ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
+                       ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]);
+               DI::init($dice);
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+
+               self::assertTrue($storageManager->register(SampleStorageBackend::class));
+
+               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+                       SampleStorageBackend::getName(),
+               ]), $storageManager->listBackends());
+               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
+                       SampleStorageBackend::getName()
+               ]), $this->config->get('storage', 'backends'));
+
+               // inline call to register own class as hook (testing purpose only)
+               SampleStorageBackend::registerHook();
+               Hook::loadHooks();
+
+               self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME)));
+               self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
+
+               self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend());
+
+               self::expectException(\Friendica\Core\Storage\Exception\StorageException::class);
+               self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.');
+
+               $storageManager->unregister(SampleStorageBackend::class);
+       }
+
+       /**
+        * Test moving data to a new storage (currently testing db & filesystem)
+        *
+        * @dataProvider dataStorages
+        */
+       public function testMoveStorage($name, $valid, $interface, $assert, $assertName)
+       {
+               if ($interface !== ICanWriteToStorage::class) {
+                       self::markTestSkipped("No user backend");
+               }
+
+               $this->loadFixture(__DIR__ . '/../../../../datasets/storage/database.fixture.php', $this->dba);
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+               $storage        = $storageManager->getWritableStorageByName($name);
+               $storageManager->move($storage);
+
+               $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']);
+
+               while ($photo = $this->dba->fetch($photos)) {
+                       self::assertEmpty($photo['data']);
+
+                       $storage = $storageManager->getByName($photo['backend-class']);
+                       $data    = $storage->get($photo['backend-ref']);
+
+                       self::assertNotEmpty($data);
+               }
+       }
+
+       /**
+        * Test moving data to a WRONG storage
+        */
+       public function testWrongWritableStorage()
+       {
+               $this->expectException(InvalidClassStorageException::class);
+               $this->expectExceptionMessage('Backend SystemResource is not valid');
+
+               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
+               $storage        = $storageManager->getWritableStorageByName(SystemResource::getName());
+               $storageManager->move($storage);
+       }
+}
diff --git a/tests/src/Core/Storage/StorageConfigTest.php b/tests/src/Core/Storage/StorageConfigTest.php
new file mode 100644 (file)
index 0000000..a01be34
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Storage\Capability\ICanConfigureStorage;
+use Friendica\Test\MockedTest;
+
+abstract class StorageConfigTest extends MockedTest
+{
+       /** @return \Friendica\Core\Storage\Capability\ICanConfigureStorage */
+       abstract protected function getInstance();
+
+       abstract protected function assertOption(\Friendica\Core\Storage\Capability\ICanConfigureStorage $storage);
+
+       /**
+        * Test if the "getOption" is asserted
+        */
+       public function testGetOptions()
+       {
+               $instance = $this->getInstance();
+
+               $this->assertOption($instance);
+       }
+}
diff --git a/tests/src/Core/Storage/StorageTest.php b/tests/src/Core/Storage/StorageTest.php
new file mode 100644 (file)
index 0000000..71377a9
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Test\src\Core\Storage;
+
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
+use Friendica\Core\Storage\Capability\ICanWriteToStorage;
+use Friendica\Core\Storage\Exception\ReferenceStorageException;
+use Friendica\Test\MockedTest;
+
+abstract class StorageTest extends MockedTest
+{
+       /** @return ICanWriteToStorage */
+       abstract protected function getInstance();
+
+       /**
+        * Test if the instance is "really" implementing the interface
+        */
+       public function testInstance()
+       {
+               $instance = $this->getInstance();
+               self::assertInstanceOf(ICanReadFromStorage::class, $instance);
+       }
+
+       /**
+        * Test basic put, get and delete operations
+        */
+       public function testPutGetDelete()
+       {
+               $instance = $this->getInstance();
+
+               $ref = $instance->put('data12345');
+               self::assertNotEmpty($ref);
+
+               self::assertEquals('data12345', $instance->get($ref));
+
+               $instance->delete($ref);
+       }
+
+       /**
+        * Test a delete with an invalid reference
+        */
+       public function testInvalidDelete()
+       {
+               self::expectException(ReferenceStorageException::class);
+
+               $instance = $this->getInstance();
+
+               $instance->delete(-1234456);
+       }
+
+       /**
+        * Test a get with an invalid reference
+        */
+       public function testInvalidGet()
+       {
+               self::expectException(ReferenceStorageException::class);
+
+               $instance = $this->getInstance();
+
+               $instance->get(-123456);
+       }
+
+       /**
+        * Test an update with a given reference
+        */
+       public function testUpdateReference()
+       {
+               $instance = $this->getInstance();
+
+               $ref = $instance->put('data12345');
+               self::assertNotEmpty($ref);
+
+               self::assertEquals('data12345', $instance->get($ref));
+
+               self::assertEquals($ref, $instance->put('data5432', $ref));
+               self::assertEquals('data5432', $instance->get($ref));
+       }
+
+       /**
+        * Test that an invalid update results in an insert
+        */
+       public function testInvalidUpdate()
+       {
+               $instance = $this->getInstance();
+
+               self::assertEquals(-123, $instance->put('data12345', -123));
+       }
+}
diff --git a/tests/src/Core/StorageManagerTest.php b/tests/src/Core/StorageManagerTest.php
deleted file mode 100644 (file)
index f9d05e6..0000000
+++ /dev/null
@@ -1,353 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Core;
-
-use Dice\Dice;
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Config\Type\PreloadConfig;
-use Friendica\Core\Hook;
-use Friendica\Core\L10n;
-use Friendica\Core\Session\Capability\IHandleSessions;
-use Friendica\Core\Session\Type\Memory;
-use Friendica\Core\StorageManager;
-use Friendica\Database\Database;
-use Friendica\DI;
-use Friendica\Core\Config\Factory\Config;
-use Friendica\Core\Config\Repository;
-use Friendica\Model\Storage;
-use Friendica\Network\HTTPClient;
-use Friendica\Test\DatabaseTest;
-use Friendica\Test\Util\Database\StaticDatabase;
-use Friendica\Test\Util\VFSTrait;
-use Friendica\Util\Profiler;
-use org\bovigo\vfs\vfsStream;
-use Psr\Log\LoggerInterface;
-use Psr\Log\NullLogger;
-use Friendica\Test\Util\SampleStorageBackend;
-
-class StorageManagerTest extends DatabaseTest
-{
-       use VFSTrait;
-       /** @var Database */
-       private $dba;
-       /** @var IManageConfigValues */
-       private $config;
-       /** @var LoggerInterface */
-       private $logger;
-       /** @var L10n */
-       private $l10n;
-       /** @var HTTPClient */
-       private $httpRequest;
-
-       protected function setUp(): void
-       {
-               parent::setUp();
-
-               $this->setUpVfsDir();
-
-               vfsStream::newDirectory(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root);
-
-               $this->logger = new NullLogger();
-
-               $profiler = \Mockery::mock(Profiler::class);
-               $profiler->shouldReceive('startRecording');
-               $profiler->shouldReceive('stopRecording');
-               $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
-
-               // load real config to avoid mocking every config-entry which is related to the Database class
-               $configFactory = new Config();
-               $loader        = $configFactory->createConfigFileLoader($this->root->url(), []);
-               $configCache   = $configFactory->createCache($loader);
-
-               $this->dba = new StaticDatabase($configCache, $profiler, $this->logger);
-
-               $configModel  = new Repository\Config($this->dba);
-               $this->config = new PreloadConfig($configCache, $configModel);
-               $this->config->set('storage', 'name', 'Database');
-               $this->config->set('storage', 'filesystem_path', $this->root->getChild(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
-
-               $this->l10n = \Mockery::mock(L10n::class);
-
-               $this->httpRequest = \Mockery::mock(HTTPClient::class);
-       }
-
-       protected function tearDown(): void
-       {
-               $this->root->removeChild(Storage\FilesystemConfig::DEFAULT_BASE_FOLDER);
-
-               parent::tearDown();
-       }
-
-       /**
-        * Test plain instancing first
-        */
-       public function testInstance()
-       {
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest);
-
-               self::assertInstanceOf(StorageManager::class, $storageManager);
-       }
-
-       public function dataStorages()
-       {
-               return [
-                       'empty' => [
-                               'name'       => '',
-                               'valid'      => false,
-                               'interface'  => Storage\IStorage::class,
-                               'assert'     => null,
-                               'assertName' => '',
-                       ],
-                       'database' => [
-                               'name'       => Storage\Database::NAME,
-                               'valid'      => true,
-                               'interface'  => Storage\IWritableStorage::class,
-                               'assert'     => Storage\Database::class,
-                               'assertName' => Storage\Database::NAME,
-                       ],
-                       'filesystem' => [
-                               'name'       => Storage\Filesystem::NAME,
-                               'valid'      => true,
-                               'interface'  => Storage\IWritableStorage::class,
-                               'assert'     => Storage\Filesystem::class,
-                               'assertName' => Storage\Filesystem::NAME,
-                       ],
-                       'systemresource' => [
-                               'name'       => Storage\SystemResource::NAME,
-                               'valid'      => true,
-                               'interface'  => Storage\IStorage::class,
-                               'assert'     => Storage\SystemResource::class,
-                               'assertName' => Storage\SystemResource::NAME,
-                       ],
-                       'invalid' => [
-                               'name'        => 'invalid',
-                               'valid'       => false,
-                               'interface'   => null,
-                               'assert'      => null,
-                               'assertName'  => '',
-                               'userBackend' => false,
-                       ],
-               ];
-       }
-
-       /**
-        * Test the getByName() method
-        *
-        * @dataProvider dataStorages
-        */
-       public function testGetByName($name, $valid, $interface, $assert, $assertName)
-       {
-               if (!$valid) {
-                       $this->expectException(Storage\InvalidClassStorageException::class);
-               }
-
-               if ($interface === Storage\IWritableStorage::class) {
-                       $this->config->set('storage', 'name', $name);
-               }
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               if ($interface === Storage\IWritableStorage::class) {
-                       $storage = $storageManager->getWritableStorageByName($name);
-               } else {
-                       $storage = $storageManager->getByName($name);
-               }
-
-               self::assertInstanceOf($interface, $storage);
-               self::assertInstanceOf($assert, $storage);
-               self::assertEquals($assertName, $storage);
-       }
-
-       /**
-        * Test the isValidBackend() method
-        *
-        * @dataProvider dataStorages
-        */
-       public function testIsValidBackend($name, $valid, $interface, $assert, $assertName)
-       {
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               // true in every of the backends
-               self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name));
-
-               // if it's a IWritableStorage, the valid backend should return true, otherwise false
-               self::assertEquals($interface === Storage\IWritableStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS));
-       }
-
-       /**
-        * Test the method listBackends() with default setting
-        */
-       public function testListBackends()
-       {
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
-       }
-
-       /**
-        * Test the method getBackend()
-        *
-        * @dataProvider dataStorages
-        */
-       public function testGetBackend($name, $valid, $interface, $assert, $assertName)
-       {
-               if ($interface !== Storage\IWritableStorage::class) {
-                       static::markTestSkipped('only works for IWritableStorage');
-               }
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               $selBackend = $storageManager->getWritableStorageByName($name);
-               $storageManager->setBackend($selBackend);
-
-               self::assertInstanceOf($assert, $storageManager->getBackend());
-       }
-
-       /**
-        * Test the method getBackend() with a pre-configured backend
-        *
-        * @dataProvider dataStorages
-        */
-       public function testPresetBackend($name, $valid, $interface, $assert, $assertName)
-       {
-               $this->config->set('storage', 'name', $name);
-               if ($interface !== Storage\IWritableStorage::class) {
-                       $this->expectException(Storage\InvalidClassStorageException::class);
-               }
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               self::assertInstanceOf($assert, $storageManager->getBackend());
-       }
-
-       /**
-        * Tests the register and unregister methods for a new backend storage class
-        *
-        * Uses a sample storage for testing
-        *
-        * @see SampleStorageBackend
-        */
-       public function testRegisterUnregisterBackends()
-       {
-               /// @todo Remove dice once "Hook" is dynamic and mockable
-               $dice = (new Dice())
-                       ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
-                       ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
-                       ->addRule(IHandleSessions::class, ['instanceOf' => Session\Type\Memory::class, 'shared' => true, 'call' => null]);
-               DI::init($dice);
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               self::assertTrue($storageManager->register(SampleStorageBackend::class));
-
-               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
-                       SampleStorageBackend::getName(),
-               ]), $storageManager->listBackends());
-               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
-                       SampleStorageBackend::getName()
-               ]), $this->config->get('storage', 'backends'));
-
-               self::assertTrue($storageManager->unregister(SampleStorageBackend::class));
-               self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends'));
-               self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends());
-       }
-
-       /**
-        * tests that an active backend cannot get unregistered
-        */
-       public function testUnregisterActiveBackend()
-       {
-               /// @todo Remove dice once "Hook" is dynamic and mockable
-               $dice = (new Dice())
-                       ->addRules(include __DIR__ . '/../../../static/dependencies.config.php')
-                       ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true])
-                       ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]);
-               DI::init($dice);
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-
-               self::assertTrue($storageManager->register(SampleStorageBackend::class));
-
-               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
-                       SampleStorageBackend::getName(),
-               ]), $storageManager->listBackends());
-               self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [
-                       SampleStorageBackend::getName()
-               ]), $this->config->get('storage', 'backends'));
-
-               // inline call to register own class as hook (testing purpose only)
-               SampleStorageBackend::registerHook();
-               Hook::loadHooks();
-
-               self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME)));
-               self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name'));
-
-               self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend());
-
-               self::expectException(Storage\StorageException::class);
-               self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.');
-
-               $storageManager->unregister(SampleStorageBackend::class);
-       }
-
-       /**
-        * Test moving data to a new storage (currently testing db & filesystem)
-        *
-        * @dataProvider dataStorages
-        */
-       public function testMoveStorage($name, $valid, $interface, $assert, $assertName)
-       {
-               if ($interface !== Storage\IWritableStorage::class) {
-                       self::markTestSkipped("No user backend");
-               }
-
-               $this->loadFixture(__DIR__ . '/../../datasets/storage/database.fixture.php', $this->dba);
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-               $storage        = $storageManager->getWritableStorageByName($name);
-               $storageManager->move($storage);
-
-               $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']);
-
-               while ($photo = $this->dba->fetch($photos)) {
-                       self::assertEmpty($photo['data']);
-
-                       $storage = $storageManager->getByName($photo['backend-class']);
-                       $data    = $storage->get($photo['backend-ref']);
-
-                       self::assertNotEmpty($data);
-               }
-       }
-
-       /**
-        * Test moving data to a WRONG storage
-        */
-       public function testWrongWritableStorage()
-       {
-               $this->expectException(Storage\InvalidClassStorageException::class);
-               $this->expectExceptionMessage('Backend SystemResource is not valid');
-
-               $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n);
-               $storage        = $storageManager->getWritableStorageByName(Storage\SystemResource::getName());
-               $storageManager->move($storage);
-       }
-}
diff --git a/tests/src/Model/Storage/DatabaseStorageTest.php b/tests/src/Model/Storage/DatabaseStorageTest.php
deleted file mode 100644 (file)
index 796b893..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Core\Config\Factory\Config;
-use Friendica\Model\Storage\Database;
-use Friendica\Test\DatabaseTestTrait;
-use Friendica\Test\Util\Database\StaticDatabase;
-use Friendica\Test\Util\VFSTrait;
-use Friendica\Util\Profiler;
-use Psr\Log\NullLogger;
-
-class DatabaseStorageTest extends StorageTest
-{
-       use DatabaseTestTrait;
-       use VFSTrait;
-
-       protected function setUp(): void
-       {
-               $this->setUpVfsDir();
-
-               $this->setUpDb();
-
-               parent::setUp();
-       }
-
-       protected function getInstance()
-       {
-               $logger   = new NullLogger();
-               $profiler = \Mockery::mock(Profiler::class);
-               $profiler->shouldReceive('startRecording');
-               $profiler->shouldReceive('stopRecording');
-               $profiler->shouldReceive('saveTimestamp')->withAnyArgs()->andReturn(true);
-
-               // load real config to avoid mocking every config-entry which is related to the Database class
-               $configFactory = new Config();
-               $loader        = (new Config())->createConfigFileLoader($this->root->url(), []);
-               $configCache   = $configFactory->createCache($loader);
-
-               $dba = new StaticDatabase($configCache, $profiler, $logger);
-
-               return new Database($dba);
-       }
-
-       protected function tearDown(): void
-       {
-               $this->tearDownDb();
-
-               parent::tearDown();
-       }
-}
diff --git a/tests/src/Model/Storage/FilesystemStorageConfigTest.php b/tests/src/Model/Storage/FilesystemStorageConfigTest.php
deleted file mode 100644 (file)
index a5989b1..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\L10n;
-use Friendica\Model\Storage\FilesystemConfig;
-use Friendica\Model\Storage\IStorageConfiguration;
-use Friendica\Test\Util\VFSTrait;
-use Mockery\MockInterface;
-use org\bovigo\vfs\vfsStream;
-
-class FilesystemStorageConfigTest extends StorageConfigTest
-{
-       use VFSTrait;
-
-       protected function setUp(): void
-       {
-               $this->setUpVfsDir();
-
-               vfsStream::create(['storage' => []], $this->root);
-
-               parent::setUp();
-       }
-
-       protected function getInstance()
-       {
-               /** @var MockInterface|L10n $l10n */
-               $l10n   = \Mockery::mock(L10n::class)->makePartial();
-               $config = \Mockery::mock(IManageConfigValues::class);
-               $config->shouldReceive('get')
-                                        ->with('storage', 'filesystem_path', FilesystemConfig::DEFAULT_BASE_FOLDER)
-                                        ->andReturn($this->root->getChild('storage')->url());
-
-               return new FilesystemConfig($config, $l10n);
-       }
-
-       protected function assertOption(IStorageConfiguration $storage)
-       {
-               self::assertEquals([
-                       'storagepath' => [
-                               'input', 'Storage base path',
-                               $this->root->getChild('storage')->url(),
-                               'Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree'
-                       ]
-               ], $storage->getOptions());
-       }
-}
diff --git a/tests/src/Model/Storage/FilesystemStorageTest.php b/tests/src/Model/Storage/FilesystemStorageTest.php
deleted file mode 100644 (file)
index 837c166..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Model\Storage\Filesystem;
-use Friendica\Model\Storage\FilesystemConfig;
-use Friendica\Model\Storage\StorageException;
-use Friendica\Test\Util\VFSTrait;
-use org\bovigo\vfs\vfsStream;
-
-class FilesystemStorageTest extends StorageTest
-{
-       use VFSTrait;
-
-       protected function setUp(): void
-       {
-               $this->setUpVfsDir();
-
-               vfsStream::create(['storage' => []], $this->root);
-
-               parent::setUp();
-       }
-
-       protected function getInstance()
-       {
-               return new Filesystem($this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->url());
-       }
-
-       /**
-        * Test the exception in case of missing directory permissions during put new files
-        */
-       public function testMissingDirPermissionsDuringPut()
-       {
-               $this->expectException(StorageException::class);
-               $this->expectExceptionMessageMatches("/Filesystem storage failed to create \".*\". Check you write permissions./");
-               $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0777);
-
-               $instance = $this->getInstance();
-
-               $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
-               $instance->put('test');
-       }
-
-       /**
-        * Test the exception in case the directory isn't writeable
-        */
-       public function testMissingDirPermissions()
-       {
-               $this->expectException(StorageException::class);
-               $this->expectExceptionMessageMatches("/Path \".*\" does not exist or is not writeable./");
-               $this->root->getChild(FilesystemConfig::DEFAULT_BASE_FOLDER)->chmod(0000);
-
-               $this->getInstance();
-       }
-
-       /**
-        * Test the exception in case of missing file permissions
-        *
-        */
-       public function testMissingFilePermissions()
-       {
-               static::markTestIncomplete("Cannot catch file_put_content() error due vfsStream failure");
-
-               $this->expectException(StorageException::class);
-               $this->expectExceptionMessageMatches("/Filesystem storage failed to save data to \".*\". Check your write permissions/");
-
-               vfsStream::create(['storage' => ['f0' => ['c0' => ['k0i0' => '']]]], $this->root);
-
-               $this->root->getChild('storage/f0/c0/k0i0')->chmod(000);
-
-               $instance = $this->getInstance();
-               $instance->put('test', 'f0c0k0i0');
-       }
-
-       /**
-        * Test the backend storage of the Filesystem Storage class
-        */
-       public function testDirectoryTree()
-       {
-               $instance = $this->getInstance();
-
-               $instance->put('test', 'f0c0d0i0');
-
-               $dir  = $this->root->getChild('storage/f0/c0')->url();
-               $file = $this->root->getChild('storage/f0/c0/d0i0')->url();
-
-               self::assertDirectoryExists($dir);
-               self::assertFileExists($file);
-
-               self::assertDirectoryIsWritable($dir);
-               self::assertFileIsWritable($file);
-
-               self::assertEquals('test', file_get_contents($file));
-       }
-}
diff --git a/tests/src/Model/Storage/StorageConfigTest.php b/tests/src/Model/Storage/StorageConfigTest.php
deleted file mode 100644 (file)
index 80cbbae..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Model\Storage\IStorageConfiguration;
-use Friendica\Test\MockedTest;
-
-abstract class StorageConfigTest extends MockedTest
-{
-       /** @return IStorageConfiguration */
-       abstract protected function getInstance();
-
-       abstract protected function assertOption(IStorageConfiguration $storage);
-
-       /**
-        * Test if the "getOption" is asserted
-        */
-       public function testGetOptions()
-       {
-               $instance = $this->getInstance();
-
-               $this->assertOption($instance);
-       }
-}
diff --git a/tests/src/Model/Storage/StorageTest.php b/tests/src/Model/Storage/StorageTest.php
deleted file mode 100644 (file)
index 7433747..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * @copyright Copyright (C) 2010-2021, the Friendica project
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program 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 program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-
-namespace Friendica\Test\src\Model\Storage;
-
-use Friendica\Model\Storage\IWritableStorage;
-use Friendica\Model\Storage\IStorage;
-use Friendica\Model\Storage\ReferenceStorageException;
-use Friendica\Test\MockedTest;
-
-abstract class StorageTest extends MockedTest
-{
-       /** @return IWritableStorage */
-       abstract protected function getInstance();
-
-       /**
-        * Test if the instance is "really" implementing the interface
-        */
-       public function testInstance()
-       {
-               $instance = $this->getInstance();
-               self::assertInstanceOf(IStorage::class, $instance);
-       }
-
-       /**
-        * Test basic put, get and delete operations
-        */
-       public function testPutGetDelete()
-       {
-               $instance = $this->getInstance();
-
-               $ref = $instance->put('data12345');
-               self::assertNotEmpty($ref);
-
-               self::assertEquals('data12345', $instance->get($ref));
-
-               $instance->delete($ref);
-       }
-
-       /**
-        * Test a delete with an invalid reference
-        */
-       public function testInvalidDelete()
-       {
-               self::expectException(ReferenceStorageException::class);
-
-               $instance = $this->getInstance();
-
-               $instance->delete(-1234456);
-       }
-
-       /**
-        * Test a get with an invalid reference
-        */
-       public function testInvalidGet()
-       {
-               self::expectException(ReferenceStorageException::class);
-
-               $instance = $this->getInstance();
-
-               $instance->get(-123456);
-       }
-
-       /**
-        * Test an update with a given reference
-        */
-       public function testUpdateReference()
-       {
-               $instance = $this->getInstance();
-
-               $ref = $instance->put('data12345');
-               self::assertNotEmpty($ref);
-
-               self::assertEquals('data12345', $instance->get($ref));
-
-               self::assertEquals($ref, $instance->put('data5432', $ref));
-               self::assertEquals('data5432', $instance->get($ref));
-       }
-
-       /**
-        * Test that an invalid update results in an insert
-        */
-       public function testInvalidUpdate()
-       {
-               $instance = $this->getInstance();
-
-               self::assertEquals(-123, $instance->put('data12345', -123));
-       }
-}
index a2999ed5a566bcec3c98aac516930f71feb8eac5..368b70b228bb26a76dcbbea9c0b1841e09873147 100644 (file)
@@ -41,6 +41,7 @@
  */
 
 use Friendica\Core\Logger;
+use Friendica\Core\Storage\Capability\ICanReadFromStorage;
 use Friendica\Core\Update;
 use Friendica\Core\Worker;
 use Friendica\Database\Database;
@@ -54,7 +55,6 @@ use Friendica\Model\Notification;
 use Friendica\Model\Photo;
 use Friendica\Model\Post;
 use Friendica\Model\Profile;
-use Friendica\Model\Storage;
 use Friendica\Security\PermissionSet\Repository\PermissionSet;
 use Friendica\Worker\Delivery;
 
@@ -183,7 +183,7 @@ function update_1330()
 
        // set the name of the storage instead of the classpath as config
        if (!empty($currStorage)) {
-               /** @var Storage\IStorage $currStorage */
+               /** @var ICanReadFromStorage $currStorage */
                if (!DI::config()->set('storage', 'name', $currStorage::getName())) {
                        return Update::FAILED;
                }
@@ -989,7 +989,7 @@ function update_1434()
 
        // in case of an empty config, set "Database" as default storage backend
        if (empty($name)) {
-               DI::config()->set('storage', 'name', Storage\Database::getName());
+               DI::config()->set('storage', 'name', \Friendica\Core\Storage\Type\Database::getName());
        }
 
        // In case of a Using deprecated storage class value, set the right name for it