diff options
37 files changed, 1495 insertions, 142 deletions
diff --git a/apps/dav/lib/connector/sabre/objecttree.php b/apps/dav/lib/connector/sabre/objecttree.php index ba4c855f58a..55b310a4405 100644 --- a/apps/dav/lib/connector/sabre/objecttree.php +++ b/apps/dav/lib/connector/sabre/objecttree.php @@ -135,9 +135,8 @@ class ObjectTree extends \Sabre\DAV\Tree { /** * @var \OC\Files\Storage\Storage $storage */ - $scanner = $storage->getScanner($internalPath); // get data directly - $data = $scanner->getData($internalPath); + $data = $storage->getMetaData($internalPath); $info = new FileInfo($absPath, $storage, $internalPath, $data, $mount); } else { $info = null; diff --git a/apps/files_sharing/lib/cache.php b/apps/files_sharing/lib/cache.php index 714cc144c0b..10d1e787922 100644 --- a/apps/files_sharing/lib/cache.php +++ b/apps/files_sharing/lib/cache.php @@ -33,6 +33,7 @@ namespace OC\Files\Cache; use OC\User\NoUserException; +use OCP\Files\Cache\ICacheEntry; use OCP\Share_Backend_Collection; /** @@ -98,8 +99,8 @@ class Shared_Cache extends Cache { /** * get the stored metadata of a file or folder * - * @param string $file - * @return array|false + * @param string|int $file + * @return ICacheEntry|false */ public function get($file) { $mimetypeLoader = \OC::$server->getMimeTypeLoader(); @@ -161,7 +162,7 @@ class Shared_Cache extends Cache { * get the metadata of all files stored in $folder * * @param string $folderId - * @return array|false + * @return ICacheEntry[]|false */ public function getFolderContentsById($folderId) { $cache = $this->getSourceCache(''); @@ -281,7 +282,7 @@ class Shared_Cache extends Cache { * search for files matching $pattern * * @param string $pattern - * @return array of file data + * @return ICacheEntry[] of file data */ public function search($pattern) { @@ -320,7 +321,7 @@ class Shared_Cache extends Cache { * search for files by mimetype * * @param string $mimetype - * @return array + * @return ICacheEntry[] */ public function searchByMime($mimetype) { $mimepart = null; @@ -373,7 +374,7 @@ class Shared_Cache extends Cache { * * @param string|int $tag tag to search for * @param string $userId owner of the tags - * @return array file data + * @return ICacheEntry[] file data */ public function searchByTag($tag, $userId) { // TODO: inject this diff --git a/apps/files_sharing/lib/scanner.php b/apps/files_sharing/lib/scanner.php index 1152c49755a..bd6a28a4934 100644 --- a/apps/files_sharing/lib/scanner.php +++ b/apps/files_sharing/lib/scanner.php @@ -35,7 +35,7 @@ class SharedScanner extends Scanner { * * @return array an array of metadata of the file */ - public function getData($path){ + protected function getData($path){ $data = parent::getData($path); $sourcePath = $this->storage->getSourcePath($path); list($sourceStorage, $internalPath) = \OC\Files\Filesystem::resolvePath($sourcePath); diff --git a/apps/files_sharing/lib/watcher.php b/apps/files_sharing/lib/watcher.php index 9a8968f2265..5b4736c21e1 100644 --- a/apps/files_sharing/lib/watcher.php +++ b/apps/files_sharing/lib/watcher.php @@ -24,6 +24,7 @@ */ namespace OC\Files\Cache; +use OCP\Files\Cache\ICacheEntry; /** * check the storage backends for updates and change the cache accordingly @@ -38,7 +39,7 @@ class Shared_Watcher extends Watcher { * Update the cache for changes to $path * * @param string $path - * @param array $cachedData + * @param ICacheEntry $cachedData */ public function update($path, $cachedData) { parent::update($path, $cachedData); diff --git a/apps/files_sharing/tests/backend.php b/apps/files_sharing/tests/backend.php index 0e151d9e76a..acb59855394 100644 --- a/apps/files_sharing/tests/backend.php +++ b/apps/files_sharing/tests/backend.php @@ -58,8 +58,10 @@ class Test_Files_Sharing_Backend extends TestCase { } protected function tearDown() { - $this->view->unlink($this->filename); - $this->view->deleteAll($this->folder); + if ($this->view) { + $this->view->unlink($this->filename); + $this->view->deleteAll($this->folder); + } parent::tearDown(); } diff --git a/apps/files_sharing/tests/external/cache.php b/apps/files_sharing/tests/external/cache.php index 52e01677fa3..3e078bf3722 100644 --- a/apps/files_sharing/tests/external/cache.php +++ b/apps/files_sharing/tests/external/cache.php @@ -75,7 +75,9 @@ class Cache extends TestCase { } protected function tearDown() { - $this->cache->clear(); + if ($this->cache) { + $this->cache->clear(); + } parent::tearDown(); } diff --git a/apps/files_sharing/tests/sharedmount.php b/apps/files_sharing/tests/sharedmount.php index 347ec0d2a7b..e01deeb60f4 100644 --- a/apps/files_sharing/tests/sharedmount.php +++ b/apps/files_sharing/tests/sharedmount.php @@ -48,8 +48,10 @@ class Test_Files_Sharing_Mount extends OCA\Files_sharing\Tests\TestCase { } protected function tearDown() { - $this->view->unlink($this->folder); - $this->view->unlink($this->filename); + if ($this->view) { + $this->view->unlink($this->folder); + $this->view->unlink($this->filename); + } parent::tearDown(); } diff --git a/apps/files_sharing/tests/updater.php b/apps/files_sharing/tests/updater.php index 02c5f487e0a..dd1f83c99a8 100644 --- a/apps/files_sharing/tests/updater.php +++ b/apps/files_sharing/tests/updater.php @@ -52,8 +52,10 @@ class Test_Files_Sharing_Updater extends OCA\Files_Sharing\Tests\TestCase { } protected function tearDown() { - $this->view->unlink($this->filename); - $this->view->deleteAll($this->folder); + if ($this->view) { + $this->view->unlink($this->filename); + $this->view->deleteAll($this->folder); + } parent::tearDown(); } diff --git a/apps/files_sharing/tests/watcher.php b/apps/files_sharing/tests/watcher.php index 021f10bacca..247fb59f351 100644 --- a/apps/files_sharing/tests/watcher.php +++ b/apps/files_sharing/tests/watcher.php @@ -88,13 +88,15 @@ class Test_Files_Sharing_Watcher extends OCA\Files_sharing\Tests\TestCase { self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - $fileinfo = $this->view->getFileInfo('container/shareddir'); - \OCP\Share::unshare('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, - self::TEST_FILES_SHARING_API_USER2); + if ($this->view) { + $fileinfo = $this->view->getFileInfo('container/shareddir'); + \OCP\Share::unshare('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, + self::TEST_FILES_SHARING_API_USER2); - $this->view->deleteAll('container'); + $this->view->deleteAll('container'); - $this->ownerCache->clear(); + $this->ownerCache->clear(); + } parent::tearDown(); } diff --git a/apps/files_versions/tests/versions.php b/apps/files_versions/tests/versions.php index 74d6006cfd9..ac922b74b9f 100644 --- a/apps/files_versions/tests/versions.php +++ b/apps/files_versions/tests/versions.php @@ -86,10 +86,12 @@ class Test_Files_Versioning extends \Test\TestCase { } protected function tearDown() { - $this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files/'); - $this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files/'); - $this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files_versions/'); - $this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files_versions/'); + if ($this->rootView) { + $this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files/'); + $this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files/'); + $this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files_versions/'); + $this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files_versions/'); + } \OC_Hook::clear(); diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php index 7a976e18888..527c8b76e52 100644 --- a/lib/private/files/cache/cache.php +++ b/lib/private/files/cache/cache.php @@ -33,6 +33,8 @@ namespace OC\Files\Cache; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; use \OCP\Files\IMimeTypeLoader; use OCP\IDBConnection; @@ -46,12 +48,7 @@ use OCP\IDBConnection; * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater */ -class Cache { - const NOT_FOUND = 0; - const PARTIAL = 1; //only partial data available, file not cached in the database - const SHALLOW = 2; //folder in cache, but not all child files are completely scanned - const COMPLETE = 3; - +class Cache implements ICache { /** * @var array partial data for the cache */ @@ -105,28 +102,8 @@ class Cache { /** * get the stored metadata of a file or folder * - * the returned cache entry contains at least the following values: - * [ - * 'fileid' => int, the numeric id of a file (see getId) - * 'storage' => int, the numeric id of the storage the file is stored on - * 'path' => string, the path of the file within the storage ('foo/bar.txt') - * 'name' => string, the basename of a file ('bar.txt) - * 'mimetype' => string, the full mimetype of the file ('text/plain') - * 'mimepart' => string, the first half of the mimetype ('text') - * 'size' => int, the size of the file or folder in bytes - * 'mtime' => int, the last modified date of the file as unix timestamp as shown in the ui - * 'storage_mtime' => int, the last modified date of the file as unix timestamp as stored on the storage - * Note that when a file is updated we also update the mtime of all parent folders to make it visible to the user which folder has had updates most recently - * This can differ from the mtime on the underlying storage which usually only changes when a direct child is added, removed or renamed - * 'etag' => string, the etag for the file - * An etag is used for change detection of files and folders, an etag of a file changes whenever the content of the file changes - * Etag for folders change whenever a file in the folder has changed - * 'permissions' int, the permissions for the file stored as bitwise combination of \OCP\PERMISSION_READ, \OCP\PERMISSION_CREATE - * \OCP\PERMISSION_UPDATE, \OCP\PERMISSION_DELETE and \OCP\PERMISSION_SHARE - * ] - * * @param string | int $file either the path of a file or folder or the file id for a file or folder - * @return array|false the cache entry as array of false if the file is not found in the cache + * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache */ public function get($file) { if (is_string($file) or $file == '') { @@ -156,6 +133,7 @@ class Cache { if (isset($this->partial[$file])) { $data = $this->partial[$file]; } + return $data; } else { //fix types $data['fileid'] = (int)$data['fileid']; @@ -171,16 +149,15 @@ class Cache { $data['storage_mtime'] = $data['mtime']; } $data['permissions'] = (int)$data['permissions']; + return new CacheEntry($data); } - - return $data; } /** * get the metadata of all files stored in $folder * * @param string $folder - * @return array + * @return ICacheEntry[] */ public function getFolderContents($folder) { $fileId = $this->getId($folder); @@ -191,7 +168,7 @@ class Cache { * get the metadata of all files stored in $folder * * @param int $fileId the file id of the folder - * @return array + * @return ICacheEntry[] */ public function getFolderContentsById($fileId) { if ($fileId > -1) { @@ -211,7 +188,9 @@ class Cache { $file['storage_mtime'] = (int)$file['storage_mtime']; $file['size'] = 0 + $file['size']; } - return $files; + return array_map(function (array $data) { + return new CacheEntry($data); + }, $files); } else { return array(); } @@ -392,7 +371,7 @@ class Cache { return -1; } else { $parent = $this->getParentPath($file); - return (int) $this->getId($parent); + return (int)$this->getId($parent); } } @@ -481,12 +460,12 @@ class Cache { /** * Move a file or folder in the cache * - * @param \OC\Files\Cache\Cache $sourceCache + * @param \OCP\Files\Cache\ICache $sourceCache * @param string $sourcePath * @param string $targetPath * @throws \OC\DatabaseException */ - public function moveFromCache(Cache $sourceCache, $sourcePath, $targetPath) { + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { // normalize source and target $sourcePath = $this->normalize($sourcePath); $targetPath = $this->normalize($targetPath); @@ -571,7 +550,7 @@ class Cache { * search for files matching $pattern * * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%') - * @return array an array of cache entries where the name matches the search pattern + * @return ICacheEntry[] an array of cache entries where the name matches the search pattern */ public function search($pattern) { // normalize pattern @@ -594,7 +573,9 @@ class Cache { $row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']); $files[] = $row; } - return $files; + return array_map(function(array $data) { + return new CacheEntry($data); + }, $files); } /** @@ -602,7 +583,7 @@ class Cache { * * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') * where it will search for all mimetypes in the group ('image/*') - * @return array an array of cache entries where the mimetype matches the search + * @return ICacheEntry[] an array of cache entries where the mimetype matches the search */ public function searchByMime($mimetype) { if (strpos($mimetype, '/')) { @@ -620,7 +601,9 @@ class Cache { $row['mimepart'] = $this->mimetypeLoader->getMimetypeById($row['mimepart']); $files[] = $row; } - return $files; + return array_map(function (array $data) { + return new CacheEntry($data); + }, $files); } /** @@ -630,7 +613,7 @@ class Cache { * * @param string|int $tag name or tag id * @param string $userId owner of the tags - * @return array file data + * @return ICacheEntry[] file data */ public function searchByTag($tag, $userId) { $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' . @@ -665,7 +648,9 @@ class Cache { while ($row = $result->fetch()) { $files[] = $row; } - return $files; + return array_map(function (array $data) { + return new CacheEntry($data); + }, $files); } /** diff --git a/lib/private/files/cache/cacheentry.php b/lib/private/files/cache/cacheentry.php new file mode 100644 index 00000000000..3db3b3f8aa0 --- /dev/null +++ b/lib/private/files/cache/cacheentry.php @@ -0,0 +1,114 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Files\Cache; + +use OCP\Files\Cache\ICacheEntry; + +/** + * meta data for a file or folder + */ +class CacheEntry implements ICacheEntry, \ArrayAccess { + /** + * @var array + */ + private $data; + + public function __construct(array $data) { + $this->data = $data; + } + + public function offsetSet($offset, $value) { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) { + unset($this->data[$offset]); + } + + public function offsetGet($offset) { + if (isset($this->data[$offset])) { + return $this->data[$offset]; + } else { + return null; + } + } + + public function getId() { + return (int)$this->data['fileid']; + } + + public function getStorageId() { + return $this->data['storage']; + } + + + public function getPath() { + return $this->data['path']; + } + + + public function getName() { + return $this->data['name']; + } + + + public function getMimeType() { + return $this->data['mimetype']; + } + + + public function getMimePart() { + return $this->data['mimepart']; + } + + public function getSize() { + return $this->data['size']; + } + + public function getMTime() { + return $this->data['mtime']; + } + + public function getStorageMTime() { + return $this->data['storage_mtime']; + } + + public function getEtag() { + return $this->data['etag']; + } + + public function getPermissions() { + return $this->data['permissions']; + } + + public function isEncrypted() { + return isset($this->data['encrypted']) && $this->data['encrypted']; + } + + public function getData() { + return $this->data; + } +} diff --git a/lib/private/files/cache/homecache.php b/lib/private/files/cache/homecache.php index 693896fccfb..ae92504ddd6 100644 --- a/lib/private/files/cache/homecache.php +++ b/lib/private/files/cache/homecache.php @@ -26,6 +26,8 @@ namespace OC\Files\Cache; +use OCP\Files\Cache\ICacheEntry; + class HomeCache extends Cache { /** * get the size of a folder and set it in the cache @@ -67,7 +69,7 @@ class HomeCache extends Cache { /** * @param string $path - * @return array + * @return ICacheEntry */ public function get($path) { $data = parent::get($path); diff --git a/lib/private/files/cache/propagator.php b/lib/private/files/cache/propagator.php index 56abcdadee2..1e85a2ecc8b 100644 --- a/lib/private/files/cache/propagator.php +++ b/lib/private/files/cache/propagator.php @@ -21,10 +21,12 @@ namespace OC\Files\Cache; +use OCP\Files\Cache\IPropagator; + /** * Propagate etags and mtimes within the storage */ -class Propagator { +class Propagator implements IPropagator { /** * @var \OC\Files\Storage\Storage */ @@ -41,7 +43,7 @@ class Propagator { /** * @param string $internalPath * @param int $time - * @return array[] all propagated entries + * @return array[] all propagated cache entries */ public function propagateChange($internalPath, $time) { $cache = $this->storage->getCache($internalPath); diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php index 79f749394f1..743b50f54a9 100644 --- a/lib/private/files/cache/scanner.php +++ b/lib/private/files/cache/scanner.php @@ -37,6 +37,8 @@ namespace OC\Files\Cache; use OC\Files\Filesystem; use OC\Hooks\BasicEmitter; use OCP\Config; +use OCP\Files\Cache\IScanner; +use OCP\Files\Storage\ILockingStorage; use OCP\Lock\ILockingProvider; /** @@ -50,7 +52,7 @@ use OCP\Lock\ILockingProvider; * * @package OC\Files\Cache */ -class Scanner extends BasicEmitter { +class Scanner extends BasicEmitter implements IScanner { /** * @var \OC\Files\Storage\Storage $storage */ @@ -81,12 +83,6 @@ class Scanner extends BasicEmitter { */ protected $lockingProvider; - const SCAN_RECURSIVE = true; - const SCAN_SHALLOW = false; - - const REUSE_ETAG = 1; - const REUSE_SIZE = 2; - public function __construct(\OC\Files\Storage\Storage $storage) { $this->storage = $storage; $this->storageId = $this->storage->getId(); @@ -112,7 +108,7 @@ class Scanner extends BasicEmitter { * @param string $path * @return array an array of metadata of the file */ - public function getData($path) { + protected function getData($path) { $data = $this->storage->getMetaData($path); if (is_null($data)) { \OCP\Util::writeLog('OC\Files\Cache\Scanner', "!!! Path '$path' is not accessible or present !!!", \OCP\Util::DEBUG); @@ -137,7 +133,9 @@ class Scanner extends BasicEmitter { and !Filesystem::isFileBlacklisted($file) ) { if ($lock) { - $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } } $this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId)); \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); @@ -160,6 +158,7 @@ class Scanner extends BasicEmitter { $data['parent'] = $parentId; } if (is_null($cacheData)) { + /** @var CacheEntry $cacheData */ $cacheData = $this->cache->get($file); } if ($cacheData and $reuseExisting and isset($cacheData['fileid'])) { @@ -182,7 +181,7 @@ class Scanner extends BasicEmitter { } } // Only update metadata that has changed - $newData = array_diff_assoc($data, $cacheData); + $newData = array_diff_assoc($data, $cacheData->getData()); } else { $newData = $data; $fileId = -1; @@ -196,7 +195,9 @@ class Scanner extends BasicEmitter { $this->removeFromCache($file); } if ($lock) { - $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } } return $data; } @@ -263,7 +264,9 @@ class Scanner extends BasicEmitter { $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG; } if ($lock) { - $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } } $data = $this->scanFile($path, $reuse, -1, null, $lock); if ($data and $data['mimetype'] === 'httpd/unix-directory') { @@ -271,7 +274,9 @@ class Scanner extends BasicEmitter { $data['size'] = $size; } if ($lock) { - $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider); + } } return $data; } diff --git a/lib/private/files/cache/updater.php b/lib/private/files/cache/updater.php index c5b1e7de32e..58d8e53cfd1 100644 --- a/lib/private/files/cache/updater.php +++ b/lib/private/files/cache/updater.php @@ -24,12 +24,14 @@ */ namespace OC\Files\Cache; +use OCP\Files\Cache\IUpdater; +use OCP\Files\Storage\IStorage; /** * Update the cache and propagate changes * */ -class Updater { +class Updater implements IUpdater { /** * @var bool */ @@ -145,18 +147,18 @@ class Updater { /** * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders * - * @param \OC\Files\Storage\Storage $sourceStorage + * @param IStorage $sourceStorage * @param string $source * @param string $target */ - public function renameFromStorage(\OC\Files\Storage\Storage $sourceStorage, $source, $target) { + public function renameFromStorage(IStorage $sourceStorage, $source, $target) { if (!$this->enabled or Scanner::isPartialFile($source) or Scanner::isPartialFile($target)) { return; } $time = time(); - $sourceCache = $sourceStorage->getCache($source); + $sourceCache = $sourceStorage->getCache(); $sourceUpdater = $sourceStorage->getUpdater(); $sourcePropagator = $sourceStorage->getPropagator(); @@ -181,7 +183,9 @@ class Updater { $sourceCache->correctFolderSize($source); $this->cache->correctFolderSize($target); - $sourceUpdater->correctParentStorageMtime($source); + if ($sourceUpdater instanceof Updater) { + $sourceUpdater->correctParentStorageMtime($source); + } $this->correctParentStorageMtime($target); $this->updateStorageMTimeOnly($target); $sourcePropagator->propagateChange($source, $time); @@ -205,7 +209,7 @@ class Updater { * * @param string $internalPath */ - public function correctParentStorageMtime($internalPath) { + private function correctParentStorageMtime($internalPath) { $parentId = $this->cache->getParentId($internalPath); $parent = dirname($internalPath); if ($parentId != -1) { diff --git a/lib/private/files/cache/watcher.php b/lib/private/files/cache/watcher.php index bb80dcbd80c..a00e875a2d4 100644 --- a/lib/private/files/cache/watcher.php +++ b/lib/private/files/cache/watcher.php @@ -22,14 +22,13 @@ */ namespace OC\Files\Cache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Cache\IWatcher; /** * check the storage backends for updates and change the cache accordingly */ -class Watcher { - const CHECK_NEVER = 0; // never check the underlying filesystem for updates - const CHECK_ONCE = 1; // check the underlying filesystem for updates once every request for each file - const CHECK_ALWAYS = 2; // always check the underlying filesystem for updates +class Watcher implements IWatcher { protected $watchPolicy = self::CHECK_ONCE; @@ -77,7 +76,7 @@ class Watcher { * check $path for updates and update if needed * * @param string $path - * @param array $cachedEntry + * @param ICacheEntry|null $cachedEntry * @return boolean true if path was updated */ public function checkUpdate($path, $cachedEntry = null) { @@ -96,7 +95,7 @@ class Watcher { * Update the cache for changes to $path * * @param string $path - * @param array $cachedData + * @param ICacheEntry $cachedData */ public function update($path, $cachedData) { if ($this->storage->is_dir($path)) { @@ -114,7 +113,7 @@ class Watcher { * Check if the cache for $path needs to be updated * * @param string $path - * @param array $cachedData + * @param ICacheEntry $cachedData * @return bool */ public function needsUpdate($path, $cachedData) { diff --git a/lib/private/files/cache/wrapper/cachewrapper.php b/lib/private/files/cache/wrapper/cachewrapper.php index f401b7482eb..39a6b63205b 100644 --- a/lib/private/files/cache/wrapper/cachewrapper.php +++ b/lib/private/files/cache/wrapper/cachewrapper.php @@ -25,6 +25,7 @@ namespace OC\Files\Cache\Wrapper; use OC\Files\Cache\Cache; +use OCP\Files\Cache\ICacheEntry; class CacheWrapper extends Cache { /** @@ -42,8 +43,8 @@ class CacheWrapper extends Cache { /** * Make it easy for wrappers to modify every returned cache entry * - * @param array $entry - * @return array + * @param ICacheEntry $entry + * @return ICacheEntry */ protected function formatCacheEntry($entry) { return $entry; @@ -53,7 +54,7 @@ class CacheWrapper extends Cache { * get the stored metadata of a file or folder * * @param string /int $file - * @return array|false + * @return ICacheEntry|false */ public function get($file) { $result = $this->cache->get($file); @@ -67,7 +68,7 @@ class CacheWrapper extends Cache { * get the metadata of all files stored in $folder * * @param string $folder - * @return array + * @return ICacheEntry[] */ public function getFolderContents($folder) { // cant do a simple $this->cache->.... call here since getFolderContentsById needs to be called on this @@ -178,7 +179,7 @@ class CacheWrapper extends Cache { * search for files matching $pattern * * @param string $pattern - * @return array an array of file data + * @return ICacheEntry[] an array of file data */ public function search($pattern) { $results = $this->cache->search($pattern); @@ -189,7 +190,7 @@ class CacheWrapper extends Cache { * search for files by mimetype * * @param string $mimetype - * @return array + * @return ICacheEntry[] */ public function searchByMime($mimetype) { $results = $this->cache->searchByMime($mimetype); @@ -201,7 +202,7 @@ class CacheWrapper extends Cache { * * @param string|int $tag name or tag id * @param string $userId owner of the tags - * @return array file data + * @return ICacheEntry[] file data */ public function searchByTag($tag, $userId) { $results = $this->cache->searchByTag($tag, $userId); diff --git a/lib/private/files/fileinfo.php b/lib/private/files/fileinfo.php index 3e5e894eed4..1e6fe474f7b 100644 --- a/lib/private/files/fileinfo.php +++ b/lib/private/files/fileinfo.php @@ -29,6 +29,7 @@ namespace OC\Files; +use OCP\Files\Cache\ICacheEntry; use OCP\IUser; class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { @@ -71,7 +72,7 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { * @param string|boolean $path * @param Storage\Storage $storage * @param string $internalPath - * @param array $data + * @param array|ICacheEntry $data * @param \OCP\Files\Mount\IMountPoint $mount * @param \OCP\IUser|null $owner */ diff --git a/lib/private/files/objectstore/objectstorestorage.php b/lib/private/files/objectstore/objectstorestorage.php index 9cea07b7f69..35c2c19c75b 100644 --- a/lib/private/files/objectstore/objectstorestorage.php +++ b/lib/private/files/objectstore/objectstorestorage.php @@ -25,6 +25,7 @@ namespace OC\Files\ObjectStore; use Icewind\Streams\IteratorDirectory; +use OC\Files\Cache\CacheEntry; use OCP\Files\ObjectStore\IObjectStore; class ObjectStoreStorage extends \OC\Files\Storage\Common { @@ -192,7 +193,12 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { public function stat($path) { $path = $this->normalizePath($path); - return $this->getCache()->get($path); + $cacheEntry = $this->getCache()->get($path); + if ($cacheEntry instanceof CacheEntry) { + return $cacheEntry->getData(); + } else { + return false; + } } /** diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php index c27f9d759f1..95bb3f74ba7 100644 --- a/lib/private/files/storage/common.php +++ b/lib/private/files/storage/common.php @@ -46,6 +46,7 @@ use OCP\Files\FileNameTooLongException; use OCP\Files\InvalidCharacterInPathException; use OCP\Files\InvalidPathException; use OCP\Files\ReservedWordException; +use OCP\Files\Storage\ILockingStorage; use OCP\Lock\ILockingProvider; /** @@ -59,7 +60,7 @@ use OCP\Lock\ILockingProvider; * Some \OC\Files\Storage\Common methods call functions which are first defined * in classes which extend it, e.g. $this->stat() . */ -abstract class Common implements Storage { +abstract class Common implements Storage, ILockingStorage { use LocalTempFileTrait; diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php index fda28079d0f..69438ef0c7c 100644 --- a/lib/private/files/storage/wrapper/encryption.php +++ b/lib/private/files/storage/wrapper/encryption.php @@ -28,6 +28,7 @@ namespace OC\Files\Storage\Wrapper; use OC\Encryption\Exceptions\ModuleDoesNotExistsException; use OC\Encryption\Update; use OC\Encryption\Util; +use OC\Files\Cache\CacheEntry; use OC\Files\Filesystem; use OC\Files\Mount\Manager; use OC\Files\Storage\LocalTempFileTrait; @@ -123,13 +124,14 @@ class Encryption extends Wrapper { public function filesize($path) { $fullPath = $this->getFullPath($path); + /** @var CacheEntry $info */ $info = $this->getCache()->get($path); if (isset($this->unencryptedSize[$fullPath])) { $size = $this->unencryptedSize[$fullPath]; // update file cache $info['encrypted'] = true; $info['size'] = $size; - $this->getCache()->put($path, $info); + $this->getCache()->put($path, $info->getData()); return $size; } diff --git a/lib/private/files/storage/wrapper/quota.php b/lib/private/files/storage/wrapper/quota.php index 55c826092c1..844505679df 100644 --- a/lib/private/files/storage/wrapper/quota.php +++ b/lib/private/files/storage/wrapper/quota.php @@ -25,6 +25,8 @@ namespace OC\Files\Storage\Wrapper; +use OCP\Files\Cache\ICacheEntry; + class Quota extends Wrapper { /** @@ -64,7 +66,7 @@ class Quota extends Wrapper { $cache = $storage->getCache(); } $data = $cache->get($path); - if (is_array($data) and isset($data['size'])) { + if ($data instanceof ICacheEntry and isset($data['size'])) { return $data['size']; } else { return \OCP\Files\FileInfo::SPACE_NOT_COMPUTED; diff --git a/lib/private/files/storage/wrapper/wrapper.php b/lib/private/files/storage/wrapper/wrapper.php index 472c60b5faa..c632aa399e1 100644 --- a/lib/private/files/storage/wrapper/wrapper.php +++ b/lib/private/files/storage/wrapper/wrapper.php @@ -26,9 +26,10 @@ namespace OC\Files\Storage\Wrapper; use OCP\Files\InvalidPathException; +use OCP\Files\Storage\ILockingStorage; use OCP\Lock\ILockingProvider; -class Wrapper implements \OC\Files\Storage\Storage { +class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage { /** * @var \OC\Files\Storage\Storage $storage */ @@ -583,7 +584,9 @@ class Wrapper implements \OC\Files\Storage\Storage { * @throws \OCP\Lock\LockedException */ public function acquireLock($path, $type, ILockingProvider $provider) { - $this->storage->acquireLock($path, $type, $provider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->acquireLock($path, $type, $provider); + } } /** @@ -592,7 +595,9 @@ class Wrapper implements \OC\Files\Storage\Storage { * @param \OCP\Lock\ILockingProvider $provider */ public function releaseLock($path, $type, ILockingProvider $provider) { - $this->storage->releaseLock($path, $type, $provider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->releaseLock($path, $type, $provider); + } } /** @@ -601,6 +606,8 @@ class Wrapper implements \OC\Files\Storage\Storage { * @param \OCP\Lock\ILockingProvider $provider */ public function changeLock($path, $type, ILockingProvider $provider) { - $this->storage->changeLock($path, $type, $provider); + if ($this->storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $this->storage->changeLock($path, $type, $provider); + } } } diff --git a/lib/private/files/view.php b/lib/private/files/view.php index 7b0f1d37255..55b8da165e1 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -43,15 +43,16 @@ namespace OC\Files; use Icewind\Streams\CallbackWrapper; -use OC\Files\Cache\Updater; use OC\Files\Mount\MoveableMount; use OC\Files\Storage\Storage; use OC\User\User; +use OCP\Files\Cache\ICacheEntry; use OCP\Files\FileNameTooLongException; use OCP\Files\InvalidCharacterInPathException; use OCP\Files\InvalidPathException; use OCP\Files\NotFoundException; use OCP\Files\ReservedWordException; +use OCP\Files\Storage\ILockingStorage; use OCP\IUser; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; @@ -1274,7 +1275,7 @@ class View { if ($storage) { $data = $this->getCacheEntry($storage, $internalPath, $relativePath); - if (!is_array($data)) { + if (!$data instanceof ICacheEntry) { return false; } @@ -1334,7 +1335,7 @@ class View { $data = $this->getCacheEntry($storage, $internalPath, $directory); - if (!is_array($data) || !isset($data['fileid'])) { + if (!$data instanceof ICacheEntry || !isset($data['fileid'])) { return []; } @@ -1345,7 +1346,7 @@ class View { /** * @var \OC\Files\FileInfo[] $files */ - $files = array_map(function (array $content) use ($path, $storage, $mount, $sharingDisabled) { + $files = array_map(function (ICacheEntry $content) use ($path, $storage, $mount, $sharingDisabled) { if ($sharingDisabled) { $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; } @@ -1834,11 +1835,14 @@ class View { $mount = $this->getMountForLock($absolutePath, $lockMountPoint); if ($mount) { try { - $mount->getStorage()->acquireLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->acquireLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } } catch (\OCP\Lock\LockedException $e) { // rethrow with the a human-readable path throw new \OCP\Lock\LockedException( @@ -1872,11 +1876,14 @@ class View { $mount = $this->getMountForLock($absolutePath, $lockMountPoint); if ($mount) { try { - $mount->getStorage()->changeLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->changeLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } } catch (\OCP\Lock\LockedException $e) { // rethrow with the a human-readable path throw new \OCP\Lock\LockedException( @@ -1907,11 +1914,14 @@ class View { $mount = $this->getMountForLock($absolutePath, $lockMountPoint); if ($mount) { - $mount->getStorage()->releaseLock( - $mount->getInternalPath($absolutePath), - $type, - $this->lockingProvider - ); + $storage = $mount->getStorage(); + if ($storage->instanceOfStorage('\OCP\Files\Storage\ILockingStorage')) { + $storage->releaseLock( + $mount->getInternalPath($absolutePath), + $type, + $this->lockingProvider + ); + } } return true; diff --git a/lib/public/files/cache/icache.php b/lib/public/files/cache/icache.php new file mode 100644 index 00000000000..07396db4588 --- /dev/null +++ b/lib/public/files/cache/icache.php @@ -0,0 +1,249 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Cache; + +/** + * Metadata cache for a storage + * + * The cache stores the metadata for all files and folders in a storage and is kept up to date trough the following mechanisms: + * + * - Scanner: scans the storage and updates the cache where needed + * - Watcher: checks for changes made to the filesystem outside of the ownCloud instance and rescans files and folder when a change is detected + * - Updater: listens to changes made to the filesystem inside of the ownCloud instance and updates the cache where needed + * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater + * + * @since 9.0.0 + */ +interface ICache { + const NOT_FOUND = 0; + const PARTIAL = 1; //only partial data available, file not cached in the database + const SHALLOW = 2; //folder in cache, but not all child files are completely scanned + const COMPLETE = 3; + + /** + * Get the numeric storage id for this cache's storage + * + * @return int + * @since 9.0.0 + */ + public function getNumericStorageId(); + + /** + * get the stored metadata of a file or folder + * + * @param string | int $file either the path of a file or folder or the file id for a file or folder + * @return ICacheEntry[]|false the cache entry or false if the file is not found in the cache + * @since 9.0.0 + */ + public function get($file); + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return ICacheEntry[] + * @since 9.0.0 + */ + public function getFolderContents($folder); + + /** + * get the metadata of all files stored in $folder + * + * @param int $fileId the file id of the folder + * @return ICacheEntry[] + * @since 9.0.0 + */ + public function getFolderContentsById($fileId); + + /** + * store meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + * @throws \RuntimeException + * @since 9.0.0 + */ + public function put($file, array $data); + + /** + * update the metadata of an existing file or folder in the cache + * + * @param int $id the fileid of the existing file or folder + * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged + * @since 9.0.0 + */ + public function update($id, array $data); + + /** + * get the file id for a file + * + * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file + * + * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing + * + * @param string $file + * @return int + * @since 9.0.0 + */ + public function getId($file); + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + * @since 9.0.0 + */ + public function getParentId($file); + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + * @since 9.0.0 + */ + public function inCache($file); + + /** + * remove a file or folder from the cache + * + * when removing a folder from the cache all files and folders inside the folder will be removed as well + * + * @param string $file + * @since 9.0.0 + */ + public function remove($file); + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + * @since 9.0.0 + */ + public function move($source, $target); + + /** + * Move a file or folder in the cache + * + * @param \OCP\Files\Cache\ICache $sourceCache + * @param string $sourcePath + * @param string $targetPath + * @throws \OC\DatabaseException + * @since 9.0.0 + */ + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath); + + /** + * remove all entries for files that are stored on the storage from the cache + * + * @since 9.0.0 + */ + public function clear(); + + /** + * Get the scan status of a file + * + * - ICache::NOT_FOUND: File is not in the cache + * - ICache::PARTIAL: File is not stored in the cache but some incomplete data is known + * - ICache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned + * - ICache::COMPLETE: The file or folder, with all it's children) are fully scanned + * + * @param string $file + * + * @return int ICache::NOT_FOUND, ICache::PARTIAL, ICache::SHALLOW or ICache::COMPLETE + * @since 9.0.0 + */ + public function getStatus($file); + + /** + * search for files matching $pattern + * + * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%') + * @return ICacheEntry[] an array of cache entries where the name matches the search pattern + * @since 9.0.0 + */ + public function search($pattern); + + /** + * search for files by mimetype + * + * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') + * where it will search for all mimetypes in the group ('image/*') + * @return ICacheEntry[] an array of cache entries where the mimetype matches the search + * @since 9.0.0 + */ + public function searchByMime($mimetype); + + /** + * Search for files by tag of a given users. + * + * Note that every user can tag files differently. + * + * @param string|int $tag name or tag id + * @param string $userId owner of the tags + * @return ICacheEntry[] file data + * @since 9.0.0 + */ + public function searchByTag($tag, $userId); + + /** + * get all file ids on the files on the storage + * + * @return int[] + * @since 9.0.0 + */ + public function getAll(); + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiple incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + * @since 9.0.0 + */ + public function getIncomplete(); + + /** + * get the path of a file on this storage by it's file id + * + * @param int $id the file id of the file or folder to search + * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache + * @since 9.0.0 + */ + public function getPathById($id); + + /** + * normalize the given path for usage in the cache + * + * @param string $path + * @return string + * @since 9.0.0 + */ + public function normalize($path); +} diff --git a/lib/public/files/cache/icacheentry.php b/lib/public/files/cache/icacheentry.php new file mode 100644 index 00000000000..8d14bd2c555 --- /dev/null +++ b/lib/public/files/cache/icacheentry.php @@ -0,0 +1,132 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Cache; + +/** + * meta data for a file or folder + * + * @since 9.0.0 + */ +interface ICacheEntry { + /** + * Get the numeric id of a file + * + * @return int + * @since 9.0.0 + */ + public function getId(); + + /** + * Get the numeric id for the storage + * + * @return int + * @since 9.0.0 + */ + public function getStorageId(); + + /** + * Get the path of the file relative to the storage root + * + * @return string + * @since 9.0.0 + */ + public function getPath(); + + /** + * Get the file name + * + * @return string + * @since 9.0.0 + */ + public function getName(); + + /** + * Get the full mimetype + * + * @return string + * @since 9.0.0 + */ + public function getMimeType(); + + /** + * Get the first part of the mimetype + * + * @return string + * @since 9.0.0 + */ + public function getMimePart(); + + /** + * Get the file size in bytes + * + * @return int + * @since 9.0.0 + */ + public function getSize(); + + /** + * Get the last modified date as unix timestamp + * + * @return int + * @since 9.0.0 + */ + public function getMTime(); + + /** + * Get the last modified date on the storage as unix timestamp + * + * Note that when a file is updated we also update the mtime of all parent folders to make it visible to the user which folder has had updates most recently + * This can differ from the mtime on the underlying storage which usually only changes when a direct child is added, removed or renamed + * + * @return int + * @since 9.0.0 + */ + public function getStorageMTime(); + + /** + * Get the etag for the file + * + * An etag is used for change detection of files and folders, an etag of a file changes whenever the content of the file changes + * Etag for folders change whenever a file in the folder has changed + * + * @return string + * @since 9.0.0 + */ + public function getEtag(); + + /** + * Get the permissions for the file stored as bitwise combination of \OCP\PERMISSION_READ, \OCP\PERMISSION_CREATE + * \OCP\PERMISSION_UPDATE, \OCP\PERMISSION_DELETE and \OCP\PERMISSION_SHARE + * + * @return int + * @since 9.0.0 + */ + public function getPermissions(); + + /** + * Check if the file is encrypted + * + * @return bool + * @since 9.0.0 + */ + public function isEncrypted(); +} diff --git a/lib/public/files/cache/ipropagator.php b/lib/public/files/cache/ipropagator.php new file mode 100644 index 00000000000..7f7dbada532 --- /dev/null +++ b/lib/public/files/cache/ipropagator.php @@ -0,0 +1,37 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Cache; + +/** + * Propagate etags and mtimes within the storage + * + * @since 9.0.0 + */ +interface IPropagator { + /** + * @param string $internalPath + * @param int $time + * @return array[] all propagated cache entries + * @since 9.0.0 + */ + public function propagateChange($internalPath, $time); +} diff --git a/lib/public/files/cache/iscanner.php b/lib/public/files/cache/iscanner.php new file mode 100644 index 00000000000..47e33a98bae --- /dev/null +++ b/lib/public/files/cache/iscanner.php @@ -0,0 +1,81 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Cache; + +/** + * Scan files from the storage and save to the cache + * + * @since 9.0.0 + */ +interface IScanner { + const SCAN_RECURSIVE = true; + const SCAN_SHALLOW = false; + + const REUSE_ETAG = 1; + const REUSE_SIZE = 2; + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @param int $reuseExisting + * @param int $parentId + * @param array | null $cacheData existing data in the cache for the file to be scanned + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return array an array of metadata of the scanned file + * @throws \OC\ServerNotAvailableException + * @throws \OCP\Lock\LockedException + * @since 9.0.0 + */ + public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true); + + /** + * scan a folder and all its children + * + * @param string $path + * @param bool $recursive + * @param int $reuse + * @param bool $lock set to false to disable getting an additional read lock during scanning + * @return array an array of the meta data of the scanned file or folder + * @since 9.0.0 + */ + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true); + + /** + * check if the file should be ignored when scanning + * NOTE: files with a '.part' extension are ignored as well! + * prevents unfinished put requests to be scanned + * + * @param string $file + * @return boolean + * @since 9.0.0 + */ + public static function isPartialFile($file); + + /** + * walk over any folders that are not fully scanned yet and scan them + * + * @since 9.0.0 + */ + public function backgroundScan(); +} + diff --git a/lib/public/files/cache/iupdater.php b/lib/public/files/cache/iupdater.php new file mode 100644 index 00000000000..241cd8636a1 --- /dev/null +++ b/lib/public/files/cache/iupdater.php @@ -0,0 +1,75 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Cache; + +use OCP\Files\Storage\IStorage; + +/** + * Update the cache and propagate changes + * + * @since 9.0.0 + */ +interface IUpdater { + /** + * Get the propagator for etags and mtime for the view the updater works on + * + * @return IPropagator + * @since 9.0.0 + */ + public function getPropagator(); + + /** + * Propagate etag and mtime changes for the parent folders of $path up to the root of the filesystem + * + * @param string $path the path of the file to propagate the changes for + * @param int|null $time the timestamp to set as mtime for the parent folders, if left out the current time is used + * @since 9.0.0 + */ + public function propagate($path, $time = null); + + /** + * Update the cache for $path and update the size, etag and mtime of the parent folders + * + * @param string $path + * @param int $time + * @since 9.0.0 + */ + public function update($path, $time = null); + + /** + * Remove $path from the cache and update the size, etag and mtime of the parent folders + * + * @param string $path + * @since 9.0.0 + */ + public function remove($path); + + /** + * Rename a file or folder in the cache and update the size, etag and mtime of the parent folders + * + * @param IStorage $sourceStorage + * @param string $source + * @param string $target + * @since 9.0.0 + */ + public function renameFromStorage(IStorage $sourceStorage, $source, $target); +} diff --git a/lib/public/files/cache/iwatcher.php b/lib/public/files/cache/iwatcher.php new file mode 100644 index 00000000000..a61975036f8 --- /dev/null +++ b/lib/public/files/cache/iwatcher.php @@ -0,0 +1,82 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com>> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Cache; + +/** + * check the storage backends for updates and change the cache accordingly + * + * @since 9.0.0 + */ +interface IWatcher { + const CHECK_NEVER = 0; // never check the underlying filesystem for updates + const CHECK_ONCE = 1; // check the underlying filesystem for updates once every request for each file + const CHECK_ALWAYS = 2; // always check the underlying filesystem for updates + + /** + * @param int $policy either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS + * @since 9.0.0 + */ + public function setPolicy($policy); + + /** + * @return int either IWatcher::CHECK_NEVER, IWatcher::CHECK_ONCE, IWatcher::CHECK_ALWAYS + * @since 9.0.0 + */ + public function getPolicy(); + + /** + * check $path for updates and update if needed + * + * @param string $path + * @param ICacheEntry|null $cachedEntry + * @return boolean true if path was updated + * @since 9.0.0 + */ + public function checkUpdate($path, $cachedEntry = null); + + /** + * Update the cache for changes to $path + * + * @param string $path + * @param ICacheEntry $cachedData + * @since 9.0.0 + */ + public function update($path, $cachedData); + + /** + * Check if the cache for $path needs to be updated + * + * @param string $path + * @param ICacheEntry $cachedData + * @return bool + * @since 9.0.0 + */ + public function needsUpdate($path, $cachedData); + + /** + * remove deleted files in $path from the cache + * + * @param string $path + * @since 9.0.0 + */ + public function cleanFolder($path); +} diff --git a/lib/public/files/storage.php b/lib/public/files/storage.php index f6f5081abaa..1c125221449 100644 --- a/lib/public/files/storage.php +++ b/lib/public/files/storage.php @@ -33,16 +33,19 @@ // use OCP namespace for all classes that are considered public. // This means that they should be used by apps instead of the internal ownCloud classes namespace OCP\Files; -use OCP\Files\InvalidPathException; + +use OCP\Files\Storage\IStorage; use OCP\Lock\ILockingProvider; /** * Provide a common interface to all different storage options * * All paths passed to the storage are relative to the storage and should NOT have a leading slash. + * * @since 6.0.0 + * @deprecated 9.0.0 use \OCP\Files\Storage\IStorage instead */ -interface Storage { +interface Storage extends IStorage { /** * $parameters is a free form array with the configuration options needed to construct the storage * @@ -462,10 +465,4 @@ interface Storage { * @param bool $isAvailable */ public function setAvailability($isAvailable); - - /** - * @param $path path for which to retrieve the owner - * @since 9.0.0 - */ - public function getOwner($path); } diff --git a/lib/public/files/storage/ilockingstorage.php b/lib/public/files/storage/ilockingstorage.php new file mode 100644 index 00000000000..32cc32ffb05 --- /dev/null +++ b/lib/public/files/storage/ilockingstorage.php @@ -0,0 +1,60 @@ +<?php +/** + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCP\Files\Storage; + +use OCP\Lock\ILockingProvider; + +/** + * Storage backends that require explicit locking + * + * Storage backends implementing this interface do not need to implement their own locking implementation but should use the provided lockingprovider instead + * The implementation of the locking methods only need to map internal storage paths to "lock keys" + * + * @since 9.0.0 + */ +interface ILockingStorage { + /** + * @param string $path The path of the file to acquire the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + * @since 9.0.0 + */ + public function acquireLock($path, $type, ILockingProvider $provider); + + /** + * @param string $path The path of the file to acquire the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @since 9.0.0 + */ + public function releaseLock($path, $type, ILockingProvider $provider); + + /** + * @param string $path The path of the file to change the lock for + * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE + * @param \OCP\Lock\ILockingProvider $provider + * @throws \OCP\Lock\LockedException + * @since 9.0.0 + */ + public function changeLock($path, $type, ILockingProvider $provider); +} diff --git a/lib/public/files/storage/istorage.php b/lib/public/files/storage/istorage.php new file mode 100644 index 00000000000..4bc5e3536dc --- /dev/null +++ b/lib/public/files/storage/istorage.php @@ -0,0 +1,482 @@ +<?php +/** + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Michael Roth <michael.roth@rz.uni-augsburg.de> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Robin McCorkell <rmccorkell@karoshi.org.uk> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 <http://www.gnu.org/licenses/> + * + */ + +/** + * Public interface of ownCloud for apps to use. + * Files/Storage interface + */ + +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal ownCloud classes +namespace OCP\Files\Storage; + +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\IPropagator; +use OCP\Files\Cache\IScanner; +use OCP\Files\Cache\IUpdater; +use OCP\Files\Cache\IWatcher; +use OCP\Files\InvalidPathException; + +/** + * Provide a common interface to all different storage options + * + * All paths passed to the storage are relative to the storage and should NOT have a leading slash. + * + * @since 9.0.0 + */ +interface IStorage { + /** + * $parameters is a free form array with the configuration options needed to construct the storage + * + * @param array $parameters + * @since 9.0.0 + */ + public function __construct($parameters); + + /** + * Get the identifier for the storage, + * the returned id should be the same for every storage object that is created with the same parameters + * and two storage objects with the same id should refer to two storages that display the same files. + * + * @return string + * @since 9.0.0 + */ + public function getId(); + + /** + * see http://php.net/manual/en/function.mkdir.php + * implementations need to implement a recursive mkdir + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function mkdir($path); + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function rmdir($path); + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource|false + * @since 9.0.0 + */ + public function opendir($path); + + /** + * see http://php.net/manual/en/function.is-dir.php + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function is_dir($path); + + /** + * see http://php.net/manual/en/function.is-file.php + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function is_file($path); + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array|false + * @since 9.0.0 + */ + public function stat($path); + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return string|false + * @since 9.0.0 + */ + public function filetype($path); + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int|false + * @since 9.0.0 + */ + public function filesize($path); + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function isCreatable($path); + + /** + * check if a file can be read + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function isReadable($path); + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function isUpdatable($path); + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function isDeletable($path); + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function isSharable($path); + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + * @since 9.0.0 + */ + public function getPermissions($path); + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function file_exists($path); + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int|false + * @since 9.0.0 + */ + public function filemtime($path); + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string|false + * @since 9.0.0 + */ + public function file_get_contents($path); + + /** + * see http://php.net/manual/en/function.file_put_contents.php + * + * @param string $path + * @param string $data + * @return bool + * @since 9.0.0 + */ + public function file_put_contents($path, $data); + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + * @since 9.0.0 + */ + public function unlink($path); + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + * @since 9.0.0 + */ + public function rename($path1, $path2); + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + * @since 9.0.0 + */ + public function copy($path1, $path2); + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource|false + * @since 9.0.0 + */ + public function fopen($path, $mode); + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string|false + * @since 9.0.0 + */ + public function getMimeType($path); + + /** + * see http://php.net/manual/en/function.hash-file.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string|false + * @since 9.0.0 + */ + public function hash($type, $path, $raw = false); + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int|false + * @since 9.0.0 + */ + public function free_space($path); + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array|false + * @since 9.0.0 + */ + public function search($query); + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + * @since 9.0.0 + */ + public function touch($path, $mtime = null); + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string|false + * @since 9.0.0 + */ + public function getLocalFile($path); + + /** + * get the path to a local version of the folder. + * The local version of the folder can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string|false + * @since 9.0.0 + */ + public function getLocalFolder($path); + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * @since 9.0.0 + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time); + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string|false + * @since 9.0.0 + */ + public function getETag($path); + + /** + * Returns whether the storage is local, which means that files + * are stored on the local filesystem instead of remotely. + * Calling getLocalFile() for local storages should always + * return the local files, whereas for non-local storages + * it might return a temporary file. + * + * @return bool true if the files are stored locally, false otherwise + * @since 9.0.0 + */ + public function isLocal(); + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + * @since 9.0.0 + */ + public function instanceOfStorage($class); + + /** + * A custom storage implementation can return an url for direct download of a give file. + * + * For now the returned array can hold the parameter url - in future more attributes might follow. + * + * @param string $path + * @return array|false + * @since 9.0.0 + */ + public function getDirectDownload($path); + + /** + * @param string $path the path of the target folder + * @param string $fileName the name of the file itself + * @return void + * @throws InvalidPathException + * @since 9.0.0 + */ + public function verifyPath($path, $fileName); + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + * @since 9.0.0 + */ + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath); + + /** + * @param \OCP\Files\Storage $sourceStorage + * @param string $sourceInternalPath + * @param string $targetInternalPath + * @return bool + * @since 9.0.0 + */ + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath); + + /** + * Test a storage for availability + * + * @since 9.0.0 + * @return bool + */ + public function test(); + + /** + * @since 9.0.0 + * @return array [ available, last_checked ] + */ + public function getAvailability(); + + /** + * @since 9.0.0 + * @param bool $isAvailable + */ + public function setAvailability($isAvailable); + + /** + * @param string $path path for which to retrieve the owner + * @since 9.0.0 + */ + public function getOwner($path); + + /** + * @return ICache + * @since 9.0.0 + */ + public function getCache(); + + /** + * @return IPropagator + * @since 9.0.0 + */ + public function getPropagator(); + + /** + * @return IScanner + * @since 9.0.0 + */ + public function getScanner(); + + /** + * @return IUpdater + * @since 9.0.0 + */ + public function getUpdater(); + + /** + * @return IWatcher + * @since 9.0.0 + */ + public function getWatcher(); +} diff --git a/tests/lib/files/cache/cache.php b/tests/lib/files/cache/cache.php index d674ac27fa1..de4ae9cd8e9 100644 --- a/tests/lib/files/cache/cache.php +++ b/tests/lib/files/cache/cache.php @@ -556,7 +556,7 @@ class Cache extends \Test\TestCase { $this->assertEquals($folderWith00F6, $unNormalizedFolderName['name']); // put normalized folder - $this->assertTrue(is_array($this->cache->get('folder/' . $folderWith00F6))); + $this->assertInstanceOf('\OCP\Files\Cache\ICacheEntry', $this->cache->get('folder/' . $folderWith00F6)); $this->assertGreaterThan(0, $this->cache->put('folder/' . $folderWith00F6, $data)); // at this point we should have only one folder named "Schön" diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 8186fe29493..b1eb3f589e8 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -7,6 +7,7 @@ */ namespace Test\Files\Cache; +use OC\Files\Cache\CacheEntry; /** * Class Scanner @@ -226,6 +227,7 @@ class Scanner extends \Test\TestCase { // manipulate etag to simulate an empty etag $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG); + /** @var CacheEntry $data0 */ $data0 = $this->cache->get('folder/bar.txt'); $this->assertInternalType('string', $data0['etag']); $data1 = $this->cache->get('folder'); @@ -233,7 +235,7 @@ class Scanner extends \Test\TestCase { $data2 = $this->cache->get(''); $this->assertInternalType('string', $data2['etag']); $data0['etag'] = ''; - $this->cache->put('folder/bar.txt', $data0); + $this->cache->put('folder/bar.txt', $data0->getData()); // rescan $this->scanner->scan('folder/bar.txt', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG); diff --git a/tests/lib/files/storage/wrapper/quota.php b/tests/lib/files/storage/wrapper/quota.php index 95bc2ff7a1a..d087a3eef33 100644 --- a/tests/lib/files/storage/wrapper/quota.php +++ b/tests/lib/files/storage/wrapper/quota.php @@ -9,6 +9,8 @@ namespace Test\Files\Storage\Wrapper; //ensure the constants are loaded +use OC\Files\Cache\CacheEntry; + \OC::$loader->load('\OC\Files\Filesystem'); /** @@ -194,7 +196,7 @@ class Quota extends \Test\Files\Storage\Storage { $cache->expects($this->once()) ->method('get') ->with('files') - ->will($this->returnValue(array('size' => 50))); + ->will($this->returnValue(new CacheEntry(['size' => 50]))); $instance = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => 1024, 'root' => 'files')); |