diff --git a/lib/private/Files/Storage/CacheableFlysystem.php b/lib/private/Files/Storage/CacheableFlysystem.php new file mode 100644 index 000000000000..1fc3e93b1afd --- /dev/null +++ b/lib/private/Files/Storage/CacheableFlysystem.php @@ -0,0 +1,171 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OC\Files\Storage; + +use League\Flysystem\FileNotFoundException; +use Icewind\Streams\IteratorDirectory; + +/** + * Generic Cacheable adapter between flysystem adapters and owncloud's storage system + * + * To use: subclass and call $this->buildFlysystem with the flysystem adapter of choice + */ +abstract class CacheableFlysystem extends \OCP\Files\Storage\FlysystemStorageAdapter { + /** + * Stores the results in cache for the current request to prevent multiple requests to the API + * + * @var array + */ + protected $cacheContents = []; + + /** + * Get the location which will be used as a key in cache + * If Storage is not case sensitive then convert the key to lowercase + * + * @param string $path Path to file/folder + * @return string + */ + public function getCacheLocation($path) { + $location = $this->buildPath($path); + if ($location === '') { + $location = '/'; + } + return $location; + } + + public function buildPath($path) { + $location = parent::buildPath($path); + if ($this->isCaseInsensitiveStorage) { + $location = strtolower($location); + } + return $location; + } + + /** + * Check if Cache Contains the data for given path, if not then get the data + * from flysystem and store it in cache for this request lifetime + * + * @param string $path Path to file/folder + * @param boolean $overRideCache Whether to fetch the contents from Cache or directly from the API + * @return array|boolean + */ + public function getFlysystemMetadata($path, $overRideCache = false) { + $location = $this->getCacheLocation($path); + if (!isset($this->cacheContents[$location]) || $overRideCache) { + try { + $this->cacheContents[$location] = $this->flysystem->getMetadata($location); + } catch (FileNotFoundException $e) { + // do not store this info in cache as it might interfere with Upload process + return false; + } + } + return $this->cacheContents[$location]; + } + + /** + * Store the list of files/folders in the cache so that subsequent requests in the + * same request life cycle does not call the flysystem API + * + * @param array $contents Return value of $this->flysystem->listContents + */ + public function updateCache($contents) { + foreach ($contents as $object) { + $path = $object['path']; + $this->cacheContents[$path] = $object; + } + } + + /** + * {@inheritdoc} + */ + public function opendir($path) { + try { + $location = $this->buildPath($path); + $content = $this->flysystem->listContents($location); + $this->updateCache($content); + } catch (FileNotFoundException $e) { + return false; + } + $names = array_map(function ($object) { + return $object['basename']; + }, $content); + return IteratorDirectory::wrap($names); + } + + /** + * {@inheritdoc} + */ + public function fopen($path, $mode) { + switch ($mode) { + case 'r': + case 'rb': + return parent::fopen($path, $mode); + + default: + unset($this->cacheContents[$this->getCacheLocation($path)]); + return parent::fopen($path, $mode); + } + return false; + } + + /** + * {@inheritdoc} + */ + public function filesize($path) { + if ($this->is_dir($path)) { + return 0; + } else { + $info = $this->getFlysystemMetadata($path); + return $info['size']; + } + } + + /** + * {@inheritdoc} + */ + public function filemtime($path) { + $info = $this->getFlysystemMetadata($path); + return $info['timestamp']; + } + + /** + * {@inheritdoc} + */ + public function stat($path) { + $info = $this->getFlysystemMetadata($path); + return [ + 'mtime' => $info['timestamp'], + 'size' => $info['size'] + ]; + } + + /** + * {@inheritdoc} + */ + public function filetype($path) { + if ($path === '' or $path === '/' or $path === '.') { + return 'dir'; + } + $info = $this->getFlysystemMetadata($path); + return $info['type']; + } +} diff --git a/lib/public/Files/Storage/CacheableFlysystemAdapter.php b/lib/public/Files/Storage/CacheableFlysystemAdapter.php new file mode 100644 index 000000000000..ebe9ad352b5c --- /dev/null +++ b/lib/public/Files/Storage/CacheableFlysystemAdapter.php @@ -0,0 +1,36 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ + +namespace OCP\Files\Storage; + +/** + * Public Cacheable storage adapter to be extended to connect to + * flysystem adapters + * + * @since 10.0.4 + */ +abstract class CacheableFlysystemAdapter extends \OC\Files\Storage\CacheableFlysystem { + /** + * Check that if storage is case insensitive or not + * @var boolean + */ + protected $isCaseInsensitiveStorage = false; +}