External Storage Backends

This section shows how a standard app can provide external storage backends.

To do so, requires several steps. These are:

To save time, however, you can learn from an existing example, by reading through the source code of the FTP external storage app.

Configure the filesystem type

First, the /appinfo/info.xml must be adjusted to specify the type as filesystem.
For example:

<?xml version="1.0"?>
<info>
  <id>mystorageapp</id>
  <name>My storage app</name>
  ...
  <types>
    <filesystem/>
  </types>
  ...
</info>

Implement the storage class(es)

Next, you need to create a storage class. Usually, you should implement the interface \\OCP\\Files\\Storage\\IStorage. But, the easiest way is to directly extend \\OCP\\Files\\Storage\\StorageAdapter, as it already provides an implementation for many of the commonly required methods.

Here’s an example of how you would create one that implements all the filesystem operations required by ownCloud, using a fictitious library called FakeStorageLib.

For this example we mapped the available storage methods to the ones from the library. Note that, in many cases, the underlying library might not support some operations and might need extra code to work this around.

When extending StorageAdapter, it is good practice to implement the following methods, for performance reasons:

  • file_exists

  • filetype

  • fopen

  • getId

  • mkdir

  • opendir

  • rmdir

  • stat

  • touch

  • unlink

If you don’t, your storage backend will still work. But, it will likely not perform as well as it could. In the case of the rename method, this is because it uses a combination of a stream copy plus a delete for renaming a file.

Stat/metadata cache

To create a mature implementation, we need to consider stat and metadata caching. Within a single PHP request, ownCloud might call the same storage methods repeatedly, due to different checks which it needs to carry out. As a result, there is the potential to incur significant overhead, when working with the underlying filesystem.

To avoid — or at the very least reduce this — a stat/metadata cache should be implemented, if the underlying library does not support stat/metadata caching. To do this, the metadata of any folder entries which are read should be cached in a local array and returned by the storage class’ methods.

Writing a Flysystem adapter

Instead of writing everything by hand, it is also possible to write an ownCloud adapter based on a Flysystem adapter, as external storage. You can see how it was done in the FTP storage adapter.

Create the backend adapter

After implementing the storage class, a backend adapter needs to be created. To do that, create a class that extends from \\OCP\\Files\\External\\Backend:

Definition parameters

Flags:

Flag Description

DefinitionParameter::FLAG_NONE

No flags (default)

DefinitionParameter::FLAG_OPTIONAL

For optional parameters

Types:

Type Description

DefinitionParameter::VALUE_TEXT

Text field (default)

DefinitionParameter::VALUE_PASSWORD

Masked text field, for passwords and

keys

DefinitionParameter::VALUE_BOOLEAN

Boolean / checkbox

DefinitionParameter::VALUE_HIDDEN

Hidden field, useful with custom

scripts

Authentication schemes

Several authentication schemes can be specified.

Scheme Description

AuthMechanism::SCHEME_NULL

No authentication supported

AuthMechanism::SCHEME_BUILTIN

Authentication is provided through

definition parameters

AuthMechanism::SCHEME_PASSWORD

Support for password-based auth,

provides two fields user and

password to the parameter list

AuthMechanism::SCHEME_OAUTH1

OAuth1, provides fields app_key,

app_secret, token, token_secret

and configured

AuthMechanism::SCHEME_OAUTH2

OAuth2, provides fields client_id,

client_secret, token and configured

AuthMechanism::SCHEME_PUBLICKEY

Public key, provides fields user,

public_key, private_key

Custom user interface

When dealing with complex field values or workflows like OAuth, an application might need to provide custom JavaScript code to implement such workflow. To add a custom script, use the following in the backend constructor:

$this->addCustomJs('script');

This will automatically load the script /js/script.js from the app folder. The script itself will need to inject events into the external storage GUI as there is currently no proper public API to do so.

Register the backend adapter

With the backend adapter created, it next needs to be registered. This can be done in the Application class by implementing the IBackendProvider interface, as in the example below:

<?php

namespace OCA\MyStorageApp\AppInfo;

use OCP\AppFramework\App;
use OCP\AppFramework\IAppContainer;
use OCP\IContainer;
use OCP\Files\External\Config\IBackendProvider;

/**
 * @package OCA\MyStorageApp\AppInfo
 */
class Application extends App implements IBackendProvider {
	public function __construct(array $urlParams = array()) {
		parent::__construct('mystorageapp', $urlParams);
		$container = $this->getContainer();

		// retrieve the backend service
		$backendService = $container->getServer()->getStoragesBackendService();

		// register this class as backend provider
		$backendService->registerBackendProvider($this);
	}

	/**
	 * Return a list of backends to register
	 */
	public function getBackends() {
		$container = $this->getContainer();
		$backends = [
			$container->query('OCA\MyStorageApp\Backend\MyStorageBackend'),
		];
		return $backends;
	}
}

Then in appinfo/app.php instantiate the Application class:

<?php

  $app = new \OCA\MyStorageApp\AppInfo\Application();

Test the storage backend

Once the steps above are done, you should be able to mount the storage in the external storage section.