summaryrefslogtreecommitdiffstats
path: root/tests/lib/Files/ViewTest.php
diff options
context:
space:
mode:
authorJoas Schilling <nickvergessen@gmx.de>2016-05-20 15:38:20 +0200
committerThomas Müller <DeepDiver1975@users.noreply.github.com>2016-05-20 15:38:20 +0200
commit94ad54ec9b96d41a614fbbad4a97b34c41a6901f (patch)
treef3eb7cdda2704aaf0cd59d58efe66bcbd34cb67d /tests/lib/Files/ViewTest.php
parent2ef751b1ec28f7b5c7113af60ec8c9fa0ae1cf87 (diff)
downloadnextcloud-server-94ad54ec9b96d41a614fbbad4a97b34c41a6901f.tar.gz
nextcloud-server-94ad54ec9b96d41a614fbbad4a97b34c41a6901f.zip
Move tests/ to PSR-4 (#24731)
* Move a-b to PSR-4 * Move c-d to PSR-4 * Move e+g to PSR-4 * Move h-l to PSR-4 * Move m-r to PSR-4 * Move s-u to PSR-4 * Move files/ to PSR-4 * Move remaining tests to PSR-4 * Remove Test\ from old autoloader
Diffstat (limited to 'tests/lib/Files/ViewTest.php')
-rw-r--r--tests/lib/Files/ViewTest.php2447
1 files changed, 2447 insertions, 0 deletions
diff --git a/tests/lib/Files/ViewTest.php b/tests/lib/Files/ViewTest.php
new file mode 100644
index 00000000000..2c27bb64a70
--- /dev/null
+++ b/tests/lib/Files/ViewTest.php
@@ -0,0 +1,2447 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file. */
+
+namespace Test\Files;
+
+use OC\Files\Cache\Watcher;
+use OC\Files\Storage\Common;
+use OC\Files\Mount\MountPoint;
+use OC\Files\Storage\Temporary;
+use OCP\Files\FileInfo;
+use OCP\Lock\ILockingProvider;
+
+class TemporaryNoTouch extends \OC\Files\Storage\Temporary {
+ public function touch($path, $mtime = null) {
+ return false;
+ }
+}
+
+class TemporaryNoCross extends \OC\Files\Storage\Temporary {
+ public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ }
+
+ public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
+ return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
+ }
+}
+
+class TemporaryNoLocal extends \OC\Files\Storage\Temporary {
+ public function instanceOfStorage($className) {
+ if ($className === '\OC\Files\Storage\Local') {
+ return false;
+ } else {
+ return parent::instanceOfStorage($className);
+ }
+ }
+}
+
+/**
+ * Class ViewTest
+ *
+ * @group DB
+ *
+ * @package Test\Files
+ */
+class ViewTest extends \Test\TestCase {
+ /**
+ * @var \OC\Files\Storage\Storage[] $storages
+ */
+ private $storages = array();
+
+ /**
+ * @var string
+ */
+ private $user;
+
+ /**
+ * @var \OCP\IUser
+ */
+ private $userObject;
+
+ /**
+ * @var \OCP\IGroup
+ */
+ private $groupObject;
+
+ /** @var \OC\Files\Storage\Storage */
+ private $tempStorage;
+
+ protected function setUp() {
+ parent::setUp();
+ \OC_Hook::clear();
+
+ \OC_User::clearBackends();
+ \OC_User::useBackend(new \Test\Util\User\Dummy());
+
+ //login
+ $userManager = \OC::$server->getUserManager();
+ $groupManager = \OC::$server->getGroupManager();
+ $this->user = 'test';
+ $this->userObject = $userManager->createUser('test', 'test');
+
+ $this->groupObject = $groupManager->createGroup('group1');
+ $this->groupObject->addUser($this->userObject);
+
+ $this->loginAsUser($this->user);
+ // clear mounts but somehow keep the root storage
+ // that was initialized above...
+ \OC\Files\Filesystem::clearMounts();
+
+ $this->tempStorage = null;
+ }
+
+ protected function tearDown() {
+ \OC_User::setUserId($this->user);
+ foreach ($this->storages as $storage) {
+ $cache = $storage->getCache();
+ $ids = $cache->getAll();
+ $cache->clear();
+ }
+
+ if ($this->tempStorage && !\OC_Util::runningOnWindows()) {
+ system('rm -rf ' . escapeshellarg($this->tempStorage->getDataDir()));
+ }
+
+ $this->logout();
+
+ $this->userObject->delete();
+ $this->groupObject->delete();
+
+ $mountProviderCollection = \OC::$server->getMountProviderCollection();
+ \Test\TestCase::invokePrivate($mountProviderCollection, 'providers', [[]]);
+
+ parent::tearDown();
+ }
+
+ /**
+ * @medium
+ */
+ public function testCacheAPI() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $storage3 = $this->getTestStorage();
+ $root = $this->getUniqueID('/');
+ \OC\Files\Filesystem::mount($storage1, array(), $root . '/');
+ \OC\Files\Filesystem::mount($storage2, array(), $root . '/substorage');
+ \OC\Files\Filesystem::mount($storage3, array(), $root . '/folder/anotherstorage');
+ $textSize = strlen("dummy file data\n");
+ $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png');
+ $storageSize = $textSize * 2 + $imageSize;
+
+ $storageInfo = $storage3->getCache()->get('');
+ $this->assertEquals($storageSize, $storageInfo['size']);
+
+ $rootView = new \OC\Files\View($root);
+
+ $cachedData = $rootView->getFileInfo('/foo.txt');
+ $this->assertEquals($textSize, $cachedData['size']);
+ $this->assertEquals('text/plain', $cachedData['mimetype']);
+ $this->assertNotEquals(-1, $cachedData['permissions']);
+
+ $cachedData = $rootView->getFileInfo('/');
+ $this->assertEquals($storageSize * 3, $cachedData['size']);
+ $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
+
+ // get cached data excluding mount points
+ $cachedData = $rootView->getFileInfo('/', false);
+ $this->assertEquals($storageSize, $cachedData['size']);
+ $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
+
+ $cachedData = $rootView->getFileInfo('/folder');
+ $this->assertEquals($storageSize + $textSize, $cachedData['size']);
+ $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
+
+ $folderData = $rootView->getDirectoryContent('/');
+ /**
+ * expected entries:
+ * folder
+ * foo.png
+ * foo.txt
+ * substorage
+ */
+ $this->assertEquals(4, count($folderData));
+ $this->assertEquals('folder', $folderData[0]['name']);
+ $this->assertEquals('foo.png', $folderData[1]['name']);
+ $this->assertEquals('foo.txt', $folderData[2]['name']);
+ $this->assertEquals('substorage', $folderData[3]['name']);
+
+ $this->assertEquals($storageSize + $textSize, $folderData[0]['size']);
+ $this->assertEquals($imageSize, $folderData[1]['size']);
+ $this->assertEquals($textSize, $folderData[2]['size']);
+ $this->assertEquals($storageSize, $folderData[3]['size']);
+
+ $folderData = $rootView->getDirectoryContent('/substorage');
+ /**
+ * expected entries:
+ * folder
+ * foo.png
+ * foo.txt
+ */
+ $this->assertEquals(3, count($folderData));
+ $this->assertEquals('folder', $folderData[0]['name']);
+ $this->assertEquals('foo.png', $folderData[1]['name']);
+ $this->assertEquals('foo.txt', $folderData[2]['name']);
+
+ $folderView = new \OC\Files\View($root . '/folder');
+ $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/'));
+
+ $cachedData = $rootView->getFileInfo('/foo.txt');
+ $this->assertFalse($cachedData['encrypted']);
+ $id = $rootView->putFileInfo('/foo.txt', array('encrypted' => true));
+ $cachedData = $rootView->getFileInfo('/foo.txt');
+ $this->assertTrue($cachedData['encrypted']);
+ $this->assertEquals($cachedData['fileid'], $id);
+
+ $this->assertFalse($rootView->getFileInfo('/non/existing'));
+ $this->assertEquals(array(), $rootView->getDirectoryContent('/non/existing'));
+ }
+
+ /**
+ * @medium
+ */
+ public function testGetPath() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $storage3 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+ \OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage');
+
+ $rootView = new \OC\Files\View('');
+
+ $cachedData = $rootView->getFileInfo('/foo.txt');
+ /** @var int $id1 */
+ $id1 = $cachedData['fileid'];
+ $this->assertEquals('/foo.txt', $rootView->getPath($id1));
+
+ $cachedData = $rootView->getFileInfo('/substorage/foo.txt');
+ /** @var int $id2 */
+ $id2 = $cachedData['fileid'];
+ $this->assertEquals('/substorage/foo.txt', $rootView->getPath($id2));
+
+ $folderView = new \OC\Files\View('/substorage');
+ $this->assertEquals('/foo.txt', $folderView->getPath($id2));
+ }
+
+ /**
+ * @expectedException \OCP\Files\NotFoundException
+ */
+ function testGetPathNotExisting() {
+ $storage1 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, [], '/');
+
+ $rootView = new \OC\Files\View('');
+ $cachedData = $rootView->getFileInfo('/foo.txt');
+ /** @var int $id1 */
+ $id1 = $cachedData['fileid'];
+ $folderView = new \OC\Files\View('/substorage');
+ $this->assertNull($folderView->getPath($id1));
+ }
+
+ /**
+ * @medium
+ */
+ public function testMountPointOverwrite() {
+ $storage1 = $this->getTestStorage(false);
+ $storage2 = $this->getTestStorage();
+ $storage1->mkdir('substorage');
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+
+ $rootView = new \OC\Files\View('');
+ $folderContent = $rootView->getDirectoryContent('/');
+ $this->assertEquals(4, count($folderContent));
+ }
+
+ public function sharingDisabledPermissionProvider() {
+ return [
+ ['no', '', true],
+ ['yes', 'group1', false],
+ ];
+ }
+
+ /**
+ * @dataProvider sharingDisabledPermissionProvider
+ */
+ public function testRemoveSharePermissionWhenSharingDisabledForUser($excludeGroups, $excludeGroupsList, $expectedShareable) {
+ $appConfig = \OC::$server->getAppConfig();
+ $oldExcludeGroupsFlag = $appConfig->getValue('core', 'shareapi_exclude_groups', 'no');
+ $oldExcludeGroupsList = $appConfig->getValue('core', 'shareapi_exclude_groups_list', '');
+ $appConfig->setValue('core', 'shareapi_exclude_groups', $excludeGroups);
+ $appConfig->setValue('core', 'shareapi_exclude_groups_list', $excludeGroupsList);
+
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/mount');
+
+ $view = new \OC\Files\View('/');
+
+ $folderContent = $view->getDirectoryContent('');
+ $this->assertEquals($expectedShareable, $folderContent[0]->isShareable());
+
+ $folderContent = $view->getDirectoryContent('mount');
+ $this->assertEquals($expectedShareable, $folderContent[0]->isShareable());
+
+ $appConfig->setValue('core', 'shareapi_exclude_groups', $oldExcludeGroupsFlag);
+ $appConfig->setValue('core', 'shareapi_exclude_groups_list', $oldExcludeGroupsList);
+ }
+
+ public function testCacheIncompleteFolder() {
+ $storage1 = $this->getTestStorage(false);
+ \OC\Files\Filesystem::clearMounts();
+ \OC\Files\Filesystem::mount($storage1, array(), '/incomplete');
+ $rootView = new \OC\Files\View('/incomplete');
+
+ $entries = $rootView->getDirectoryContent('/');
+ $this->assertEquals(3, count($entries));
+
+ // /folder will already be in the cache but not scanned
+ $entries = $rootView->getDirectoryContent('/folder');
+ $this->assertEquals(1, count($entries));
+ }
+
+ public function testAutoScan() {
+ $storage1 = $this->getTestStorage(false);
+ $storage2 = $this->getTestStorage(false);
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+ $textSize = strlen("dummy file data\n");
+
+ $rootView = new \OC\Files\View('');
+
+ $cachedData = $rootView->getFileInfo('/');
+ $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
+ $this->assertEquals(-1, $cachedData['size']);
+
+ $folderData = $rootView->getDirectoryContent('/substorage/folder');
+ $this->assertEquals('text/plain', $folderData[0]['mimetype']);
+ $this->assertEquals($textSize, $folderData[0]['size']);
+ }
+
+ /**
+ * @medium
+ */
+ public function testSearch() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $storage3 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+ \OC\Files\Filesystem::mount($storage3, array(), '/folder/anotherstorage');
+
+ $rootView = new \OC\Files\View('');
+
+ $results = $rootView->search('foo');
+ $this->assertEquals(6, count($results));
+ $paths = array();
+ foreach ($results as $result) {
+ $this->assertEquals($result['path'], \OC\Files\Filesystem::normalizePath($result['path']));
+ $paths[] = $result['path'];
+ }
+ $this->assertContains('/foo.txt', $paths);
+ $this->assertContains('/foo.png', $paths);
+ $this->assertContains('/substorage/foo.txt', $paths);
+ $this->assertContains('/substorage/foo.png', $paths);
+ $this->assertContains('/folder/anotherstorage/foo.txt', $paths);
+ $this->assertContains('/folder/anotherstorage/foo.png', $paths);
+
+ $folderView = new \OC\Files\View('/folder');
+ $results = $folderView->search('bar');
+ $this->assertEquals(2, count($results));
+ $paths = array();
+ foreach ($results as $result) {
+ $paths[] = $result['path'];
+ }
+ $this->assertContains('/anotherstorage/folder/bar.txt', $paths);
+ $this->assertContains('/bar.txt', $paths);
+
+ $results = $folderView->search('foo');
+ $this->assertEquals(2, count($results));
+ $paths = array();
+ foreach ($results as $result) {
+ $paths[] = $result['path'];
+ }
+ $this->assertContains('/anotherstorage/foo.txt', $paths);
+ $this->assertContains('/anotherstorage/foo.png', $paths);
+
+ $this->assertEquals(6, count($rootView->searchByMime('text')));
+ $this->assertEquals(3, count($folderView->searchByMime('text')));
+ }
+
+ /**
+ * @medium
+ */
+ public function testWatcher() {
+ $storage1 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ $storage1->getWatcher()->setPolicy(Watcher::CHECK_ALWAYS);
+
+ $rootView = new \OC\Files\View('');
+
+ $cachedData = $rootView->getFileInfo('foo.txt');
+ $this->assertEquals(16, $cachedData['size']);
+
+ $rootView->putFileInfo('foo.txt', array('storage_mtime' => 10));
+ $storage1->file_put_contents('foo.txt', 'foo');
+ clearstatcache();
+
+ $cachedData = $rootView->getFileInfo('foo.txt');
+ $this->assertEquals(3, $cachedData['size']);
+ }
+
+ /**
+ * @medium
+ */
+ public function testCopyBetweenStorageNoCross() {
+ $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+ $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+ $this->copyBetweenStorages($storage1, $storage2);
+ }
+
+ /**
+ * @medium
+ */
+ public function testCopyBetweenStorageCross() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $this->copyBetweenStorages($storage1, $storage2);
+ }
+
+ /**
+ * @medium
+ */
+ public function testCopyBetweenStorageCrossNonLocal() {
+ $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+ $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+ $this->copyBetweenStorages($storage1, $storage2);
+ }
+
+ function copyBetweenStorages($storage1, $storage2) {
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+
+ $rootView = new \OC\Files\View('');
+ $rootView->mkdir('substorage/emptyfolder');
+ $rootView->copy('substorage', 'anotherfolder');
+ $this->assertTrue($rootView->is_dir('/anotherfolder'));
+ $this->assertTrue($rootView->is_dir('/substorage'));
+ $this->assertTrue($rootView->is_dir('/anotherfolder/emptyfolder'));
+ $this->assertTrue($rootView->is_dir('/substorage/emptyfolder'));
+ $this->assertTrue($rootView->file_exists('/anotherfolder/foo.txt'));
+ $this->assertTrue($rootView->file_exists('/anotherfolder/foo.png'));
+ $this->assertTrue($rootView->file_exists('/anotherfolder/folder/bar.txt'));
+ $this->assertTrue($rootView->file_exists('/substorage/foo.txt'));
+ $this->assertTrue($rootView->file_exists('/substorage/foo.png'));
+ $this->assertTrue($rootView->file_exists('/substorage/folder/bar.txt'));
+ }
+
+ /**
+ * @medium
+ */
+ public function testMoveBetweenStorageNoCross() {
+ $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+ $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoCross');
+ $this->moveBetweenStorages($storage1, $storage2);
+ }
+
+ /**
+ * @medium
+ */
+ public function testMoveBetweenStorageCross() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $this->moveBetweenStorages($storage1, $storage2);
+ }
+
+ /**
+ * @medium
+ */
+ public function testMoveBetweenStorageCrossNonLocal() {
+ $storage1 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+ $storage2 = $this->getTestStorage(true, '\Test\Files\TemporaryNoLocal');
+ $this->moveBetweenStorages($storage1, $storage2);
+ }
+
+ function moveBetweenStorages($storage1, $storage2) {
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+
+ $rootView = new \OC\Files\View('');
+ $rootView->rename('foo.txt', 'substorage/folder/foo.txt');
+ $this->assertFalse($rootView->file_exists('foo.txt'));
+ $this->assertTrue($rootView->file_exists('substorage/folder/foo.txt'));
+ $rootView->rename('substorage/folder', 'anotherfolder');
+ $this->assertFalse($rootView->is_dir('substorage/folder'));
+ $this->assertTrue($rootView->file_exists('anotherfolder/foo.txt'));
+ $this->assertTrue($rootView->file_exists('anotherfolder/bar.txt'));
+ }
+
+ /**
+ * @medium
+ */
+ public function testUnlink() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+
+ $rootView = new \OC\Files\View('');
+ $rootView->file_put_contents('/foo.txt', 'asd');
+ $rootView->file_put_contents('/substorage/bar.txt', 'asd');
+
+ $this->assertTrue($rootView->file_exists('foo.txt'));
+ $this->assertTrue($rootView->file_exists('substorage/bar.txt'));
+
+ $this->assertTrue($rootView->unlink('foo.txt'));
+ $this->assertTrue($rootView->unlink('substorage/bar.txt'));
+
+ $this->assertFalse($rootView->file_exists('foo.txt'));
+ $this->assertFalse($rootView->file_exists('substorage/bar.txt'));
+ }
+
+ /**
+ * @medium
+ */
+ public function testUnlinkRootMustFail() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/substorage');
+
+ $rootView = new \OC\Files\View('');
+ $rootView->file_put_contents('/foo.txt', 'asd');
+ $rootView->file_put_contents('/substorage/bar.txt', 'asd');
+
+ $this->assertFalse($rootView->unlink(''));
+ $this->assertFalse($rootView->unlink('/'));
+ $this->assertFalse($rootView->unlink('substorage'));
+ $this->assertFalse($rootView->unlink('/substorage'));
+ }
+
+ /**
+ * @medium
+ */
+ public function testTouch() {
+ $storage = $this->getTestStorage(true, '\Test\Files\TemporaryNoTouch');
+
+ \OC\Files\Filesystem::mount($storage, array(), '/');
+
+ $rootView = new \OC\Files\View('');
+ $oldCachedData = $rootView->getFileInfo('foo.txt');
+
+ $rootView->touch('foo.txt', 500);
+
+ $cachedData = $rootView->getFileInfo('foo.txt');
+ $this->assertEquals(500, $cachedData['mtime']);
+ $this->assertEquals($oldCachedData['storage_mtime'], $cachedData['storage_mtime']);
+
+ $rootView->putFileInfo('foo.txt', array('storage_mtime' => 1000)); //make sure the watcher detects the change
+ $rootView->file_put_contents('foo.txt', 'asd');
+ $cachedData = $rootView->getFileInfo('foo.txt');
+ $this->assertGreaterThanOrEqual($oldCachedData['mtime'], $cachedData['mtime']);
+ $this->assertEquals($cachedData['storage_mtime'], $cachedData['mtime']);
+ }
+
+ /**
+ * @medium
+ */
+ public function testViewHooks() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $defaultRoot = \OC\Files\Filesystem::getRoot();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '/substorage');
+ \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
+
+ $rootView = new \OC\Files\View('');
+ $subView = new \OC\Files\View($defaultRoot . '/substorage');
+ $this->hookPath = null;
+
+ $rootView->file_put_contents('/foo.txt', 'asd');
+ $this->assertNull($this->hookPath);
+
+ $subView->file_put_contents('/foo.txt', 'asd');
+ $this->assertEquals('/substorage/foo.txt', $this->hookPath);
+ }
+
+ private $hookPath;
+
+ public function dummyHook($params) {
+ $this->hookPath = $params['path'];
+ }
+
+ public function testSearchNotOutsideView() {
+ $storage1 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ $storage1->rename('folder', 'foo');
+ $scanner = $storage1->getScanner();
+ $scanner->scan('');
+
+ $view = new \OC\Files\View('/foo');
+
+ $result = $view->search('.txt');
+ $this->assertCount(1, $result);
+ }
+
+ /**
+ * @param bool $scan
+ * @param string $class
+ * @return \OC\Files\Storage\Storage
+ */
+ private function getTestStorage($scan = true, $class = '\OC\Files\Storage\Temporary') {
+ /**
+ * @var \OC\Files\Storage\Storage $storage
+ */
+ $storage = new $class(array());
+ $textData = "dummy file data\n";
+ $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo.png');
+ $storage->mkdir('folder');
+ $storage->file_put_contents('foo.txt', $textData);
+ $storage->file_put_contents('foo.png', $imgData);
+ $storage->file_put_contents('folder/bar.txt', $textData);
+
+ if ($scan) {
+ $scanner = $storage->getScanner();
+ $scanner->scan('');
+ }
+ $this->storages[] = $storage;
+ return $storage;
+ }
+
+ /**
+ * @medium
+ */
+ public function testViewHooksIfRootStartsTheSame() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $defaultRoot = \OC\Files\Filesystem::getRoot();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), $defaultRoot . '_substorage');
+ \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
+
+ $subView = new \OC\Files\View($defaultRoot . '_substorage');
+ $this->hookPath = null;
+
+ $subView->file_put_contents('/foo.txt', 'asd');
+ $this->assertNull($this->hookPath);
+ }
+
+ private $hookWritePath;
+ private $hookCreatePath;
+ private $hookUpdatePath;
+
+ public function dummyHookWrite($params) {
+ $this->hookWritePath = $params['path'];
+ }
+
+ public function dummyHookUpdate($params) {
+ $this->hookUpdatePath = $params['path'];
+ }
+
+ public function dummyHookCreate($params) {
+ $this->hookCreatePath = $params['path'];
+ }
+
+ public function testEditNoCreateHook() {
+ $storage1 = $this->getTestStorage();
+ $storage2 = $this->getTestStorage();
+ $defaultRoot = \OC\Files\Filesystem::getRoot();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+ \OC\Files\Filesystem::mount($storage2, array(), $defaultRoot);
+ \OC_Hook::connect('OC_Filesystem', 'post_create', $this, 'dummyHookCreate');
+ \OC_Hook::connect('OC_Filesystem', 'post_update', $this, 'dummyHookUpdate');
+ \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHookWrite');
+
+ $view = new \OC\Files\View($defaultRoot);
+ $this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
+
+ $view->file_put_contents('/asd.txt', 'foo');
+ $this->assertEquals('/asd.txt', $this->hookCreatePath);
+ $this->assertNull($this->hookUpdatePath);
+ $this->assertEquals('/asd.txt', $this->hookWritePath);
+
+ $this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
+
+ $view->file_put_contents('/asd.txt', 'foo');
+ $this->assertNull($this->hookCreatePath);
+ $this->assertEquals('/asd.txt', $this->hookUpdatePath);
+ $this->assertEquals('/asd.txt', $this->hookWritePath);
+
+ \OC_Hook::clear('OC_Filesystem', 'post_create');
+ \OC_Hook::clear('OC_Filesystem', 'post_update');
+ \OC_Hook::clear('OC_Filesystem', 'post_write');
+ }
+
+ /**
+ * @dataProvider resolvePathTestProvider
+ */
+ public function testResolvePath($expected, $pathToTest) {
+ $storage1 = $this->getTestStorage();
+ \OC\Files\Filesystem::mount($storage1, array(), '/');
+
+ $view = new \OC\Files\View('');
+
+ $result = $view->resolvePath($pathToTest);
+ $this->assertEquals($expected, $result[1]);
+
+ $exists = $view->file_exists($pathToTest);
+ $this->assertTrue($exists);
+
+ $exists = $view->file_exists($result[1]);
+ $this->assertTrue($exists);
+ }
+
+ function resolvePathTestProvider() {
+ return array(
+ array('foo.txt', 'foo.txt'),
+ array('foo.txt', '/foo.txt'),
+ array('folder', 'folder'),
+ array('folder', '/folder'),
+ array('folder', 'folder/'),
+ array('folder', '/folder/'),
+ array('folder/bar.txt', 'folder/bar.txt'),
+ array('folder/bar.txt', '/folder/bar.txt'),
+ array('', ''),
+ array('', '/'),
+ );
+ }
+
+ public function testUTF8Names() {
+ $names = array('虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا');
+
+ $storage = new \OC\Files\Storage\Temporary(array());
+ \OC\Files\Filesystem::mount($storage, array(), '/');
+
+ $rootView = new \OC\Files\View('');
+ foreach ($names as $name) {
+ $rootView->file_put_contents('/' . $name, 'dummy content');
+ }
+
+ $list = $rootView->getDirectoryContent('/');
+
+ $this->assertCount(count($names), $list);
+ foreach ($list as $item) {
+ $this->assertContains($item['name'], $names);
+ }
+
+ $cache = $storage->getCache();
+ $scanner = $storage->getScanner();
+ $scanner->scan('');
+
+ $list = $cache->getFolderContents('');
+
+ $this->assertCount(count($names), $list);
+ foreach ($list as $item) {
+ $this->assertContains($item['name'], $names);
+ }
+ }
+
+ public function xtestLongPath() {
+
+ $storage = new \OC\Files\Storage\Temporary(array());
+ \OC\Files\Filesystem::mount($storage, array(), '/');
+
+ $rootView = new \OC\Files\View('');
+
+ $longPath = '';
+ $ds = DIRECTORY_SEPARATOR;
+ /*
+ * 4096 is the maximum path length in file_cache.path in *nix
+ * 1024 is the max path length in mac
+ * 228 is the max path length in windows
+ */
+ $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
+ $tmpdirLength = strlen(\OC::$server->getTempManager()->getTemporaryFolder());
+ if (\OC_Util::runningOnWindows()) {
+ $this->markTestSkipped('[Windows] ');
+ $depth = ((260 - $tmpdirLength) / 57);
+ } elseif (\OC_Util::runningOnMac()) {
+ $depth = ((1024 - $tmpdirLength) / 57);
+ } else {
+ $depth = ((4000 - $tmpdirLength) / 57);
+ }
+ foreach (range(0, $depth - 1) as $i) {
+ $longPath .= $ds . $folderName;
+ $result = $rootView->mkdir($longPath);
+ $this->assertTrue($result, "mkdir failed on $i - path length: " . strlen($longPath));
+
+ $result = $rootView->file_put_contents($longPath . "{$ds}test.txt", 'lorem');
+ $this->assertEquals(5, $result, "file_put_contents failed on $i");
+
+ $this->assertTrue($rootView->file_exists($longPath));
+ $this->assertTrue($rootView->file_exists($longPath . "{$ds}test.txt"));
+ }
+
+ $cache = $storage->getCache();
+ $scanner = $storage->getScanner();
+ $scanner->scan('');
+
+ $longPath = $folderName;
+ foreach (range(0, $depth - 1) as $i) {
+ $cachedFolder = $cache->get($longPath);
+ $this->assertTrue(is_array($cachedFolder), "No cache entry for folder at $i");
+ $this->assertEquals($folderName, $cachedFolder['name'], "Wrong cache entry for folder at $i");
+
+ $cachedFile = $cache->get($longPath . '/test.txt');
+ $this->assertTrue(is_array($cachedFile), "No cache entry for file at $i");
+ $this->assertEquals('test.txt', $cachedFile['name'], "Wrong cache entry for file at $i");
+
+ $longPath .= $ds . $folderName;
+ }
+ }
+
+ public function testTouchNotSupported() {
+ $storage = new TemporaryNoTouch(array());
+ $scanner = $storage->getScanner();
+ \OC\Files\Filesystem::mount($storage, array(), '/test/');
+ $past = time() - 100;
+ $storage->file_put_contents('test', 'foobar');
+ $scanner->scan('');
+ $view = new \OC\Files\View('');
+ $info = $view->getFileInfo('/test/test');
+
+ $view->touch('/test/test', $past);
+ $scanner->scanFile('test', \OC\Files\Cache\Scanner::REUSE_ETAG);
+
+ $info2 = $view->getFileInfo('/test/test');
+ $this->assertSame($info['etag'], $info2['etag']);
+ }
+
+ public function testWatcherEtagCrossStorage() {
+ $storage1 = new Temporary(array());
+ $storage2 = new Temporary(array());
+ $scanner1 = $storage1->getScanner();
+ $scanner2 = $storage2->getScanner();
+ $storage1->mkdir('sub');
+ \OC\Files\Filesystem::mount($storage1, array(), '/test/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
+
+ $past = time() - 100;
+ $storage2->file_put_contents('test.txt', 'foobar');
+ $scanner1->scan('');
+ $scanner2->scan('');
+ $view = new \OC\Files\View('');
+
+ $storage2->getWatcher('')->setPolicy(Watcher::CHECK_ALWAYS);
+
+ $oldFileInfo = $view->getFileInfo('/test/sub/storage/test.txt');
+ $oldFolderInfo = $view->getFileInfo('/test');
+
+ $storage2->getCache()->update($oldFileInfo->getId(), array(
+ 'storage_mtime' => $past
+ ));
+
+ $view->getFileInfo('/test/sub/storage/test.txt');
+ $newFolderInfo = $view->getFileInfo('/test');
+
+ $this->assertNotEquals($newFolderInfo->getEtag(), $oldFolderInfo->getEtag());
+ }
+
+ /**
+ * @dataProvider absolutePathProvider
+ */
+ public function testGetAbsolutePath($expectedPath, $relativePath) {
+ $view = new \OC\Files\View('/files');
+ $this->assertEquals($expectedPath, $view->getAbsolutePath($relativePath));
+ }
+
+ public function testPartFileInfo() {
+ $storage = new Temporary(array());
+ $scanner = $storage->getScanner();
+ \OC\Files\Filesystem::mount($storage, array(), '/test/');
+ $storage->file_put_contents('test.part', 'foobar');
+ $scanner->scan('');
+ $view = new \OC\Files\View('/test');
+ $info = $view->getFileInfo('test.part');
+
+ $this->assertInstanceOf('\OCP\Files\FileInfo', $info);
+ $this->assertNull($info->getId());
+ $this->assertEquals(6, $info->getSize());
+ }
+
+ function absolutePathProvider() {
+ return array(
+ array('/files/', ''),
+ array('/files/0', '0'),
+ array('/files/false', 'false'),
+ array('/files/true', 'true'),
+ array('/files/', '/'),
+ array('/files/test', 'test'),
+ array('/files/test', '/test'),
+ );
+ }
+
+ /**
+ * @dataProvider chrootRelativePathProvider
+ */
+ function testChrootGetRelativePath($root, $absolutePath, $expectedPath) {
+ $view = new \OC\Files\View('/files');
+ $view->chroot($root);
+ $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
+ }
+
+ public function chrootRelativePathProvider() {
+ return $this->relativePathProvider('/');
+ }
+
+ /**
+ * @dataProvider initRelativePathProvider
+ */
+ public function testInitGetRelativePath($root, $absolutePath, $expectedPath) {
+ $view = new \OC\Files\View($root);
+ $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
+ }
+
+ public function initRelativePathProvider() {
+ return $this->relativePathProvider(null);
+ }
+
+ public function relativePathProvider($missingRootExpectedPath) {
+ return array(
+ // No root - returns the path
+ array('', '/files', '/files'),
+ array('', '/files/', '/files/'),
+
+ // Root equals path - /
+ array('/files/', '/files/', '/'),
+ array('/files/', '/files', '/'),
+ array('/files', '/files/', '/'),
+ array('/files', '/files', '/'),
+
+ // False negatives: chroot fixes those by adding the leading slash.
+ // But setting them up with this root (instead of chroot($root))
+ // will fail them, although they should be the same.
+ // TODO init should be fixed, so it also adds the leading slash
+ array('files/', '/files/', $missingRootExpectedPath),
+ array('files', '/files/', $missingRootExpectedPath),
+ array('files/', '/files', $missingRootExpectedPath),
+ array('files', '/files', $missingRootExpectedPath),
+
+ // False negatives: Paths provided to the method should have a leading slash
+ // TODO input should be checked to have a leading slash
+ array('/files/', 'files/', null),
+ array('/files', 'files/', null),
+ array('/files/', 'files', null),
+ array('/files', 'files', null),
+
+ // with trailing slashes
+ array('/files/', '/files/0', '0'),
+ array('/files/', '/files/false', 'false'),
+ array('/files/', '/files/true', 'true'),
+ array('/files/', '/files/test', 'test'),
+ array('/files/', '/files/test/foo', 'test/foo'),
+
+ // without trailing slashes
+ // TODO false expectation: Should match "with trailing slashes"
+ array('/files', '/files/0', '/0'),
+ array('/files', '/files/false', '/false'),
+ array('/files', '/files/true', '/true'),
+ array('/files', '/files/test', '/test'),
+ array('/files', '/files/test/foo', '/test/foo'),
+
+ // leading slashes
+ array('/files/', '/files_trashbin/', null),
+ array('/files', '/files_trashbin/', null),
+ array('/files/', '/files_trashbin', null),
+ array('/files', '/files_trashbin', null),
+
+ // no leading slashes
+ array('files/', 'files_trashbin/', null),
+ array('files', 'files_trashbin/', null),
+ array('files/', 'files_trashbin', null),
+ array('files', 'files_trashbin', null),
+
+ // mixed leading slashes
+ array('files/', '/files_trashbin/', null),
+ array('/files/', 'files_trashbin/', null),
+ array('files', '/files_trashbin/', null),
+ array('/files', 'files_trashbin/', null),
+ array('files/', '/files_trashbin', null),
+ array('/files/', 'files_trashbin', null),
+ array('files', '/files_trashbin', null),
+ array('/files', 'files_trashbin', null),
+
+ array('files', 'files_trashbin/test', null),
+ array('/files', '/files_trashbin/test', null),
+ array('/files', 'files_trashbin/test', null),
+ );
+ }
+
+ public function testFileView() {
+ $storage = new Temporary(array());
+ $scanner = $storage->getScanner();
+ $storage->file_put_contents('foo.txt', 'bar');
+ \OC\Files\Filesystem::mount($storage, array(), '/test/');
+ $scanner->scan('');
+ $view = new \OC\Files\View('/test/foo.txt');
+
+ $this->assertEquals('bar', $view->file_get_contents(''));
+ $fh = tmpfile();
+ fwrite($fh, 'foo');
+ rewind($fh);
+ $view->file_put_contents('', $fh);
+ $this->assertEquals('foo', $view->file_get_contents(''));
+ }
+
+ /**
+ * @dataProvider tooLongPathDataProvider
+ * @expectedException \OCP\Files\InvalidPathException
+ */
+ public function testTooLongPath($operation, $param0 = null) {
+
+ $longPath = '';
+ // 4000 is the maximum path length in file_cache.path
+ $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
+ $depth = (4000 / 57);
+ foreach (range(0, $depth + 1) as $i) {
+ $longPath .= '/' . $folderName;
+ }
+
+ $storage = new \OC\Files\Storage\Temporary(array());
+ $this->tempStorage = $storage; // for later hard cleanup
+ \OC\Files\Filesystem::mount($storage, array(), '/');
+
+ $rootView = new \OC\Files\View('');
+
+ if ($param0 === '@0') {
+ $param0 = $longPath;
+ }
+
+ if ($operation === 'hash') {
+ $param0 = $longPath;
+ $longPath = 'md5';
+ }
+
+ call_user_func(array($rootView, $operation), $longPath, $param0);
+ }
+
+ public function tooLongPathDataProvider() {
+ return array(
+ array('getAbsolutePath'),
+ array('getRelativePath'),
+ array('getMountPoint'),
+ array('resolvePath'),
+ array('getLocalFile'),
+ array('getLocalFolder'),
+ array('mkdir'),
+ array('rmdir'),
+ array('opendir'),
+ array('is_dir'),
+ array('is_file'),
+ array('stat'),
+ array('filetype'),
+ array('filesize'),
+ array('readfile'),
+ array('isCreatable'),
+ array('isReadable'),
+ array('isUpdatable'),
+ array('isDeletable'),
+ array('isSharable'),
+ array('file_exists'),
+ array('filemtime'),
+ array('touch'),
+ array('file_get_contents'),
+ array('unlink'),
+ array('deleteAll'),
+ array('toTmpFile'),
+ array('getMimeType'),
+ array('free_space'),
+ array('getFileInfo'),
+ array('getDirectoryContent'),
+ array('getOwner'),
+ array('getETag'),
+ array('file_put_contents', 'ipsum'),
+ array('rename', '@0'),
+ array('copy', '@0'),
+ array('fopen', 'r'),
+ array('fromTmpFile', '@0'),
+ array('hash'),
+ array('hasUpdated', 0),
+ array('putFileInfo', array()),
+ );
+ }
+
+ public function testRenameCrossStoragePreserveMtime() {
+ $storage1 = new Temporary(array());
+ $storage2 = new Temporary(array());
+ $scanner1 = $storage1->getScanner();
+ $scanner2 = $storage2->getScanner();
+ $storage1->mkdir('sub');
+ $storage1->mkdir('foo');
+ $storage1->file_put_contents('foo.txt', 'asd');
+ $storage1->file_put_contents('foo/bar.txt', 'asd');
+ \OC\Files\Filesystem::mount($storage1, array(), '/test/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
+
+ $view = new \OC\Files\View('');
+ $time = time() - 200;
+ $view->touch('/test/foo.txt', $time);
+ $view->touch('/test/foo', $time);
+ $view->touch('/test/foo/bar.txt', $time);
+
+ $view->rename('/test/foo.txt', '/test/sub/storage/foo.txt');
+
+ $this->assertEquals($time, $view->filemtime('/test/sub/storage/foo.txt'));
+
+ $view->rename('/test/foo', '/test/sub/storage/foo');
+
+ $this->assertEquals($time, $view->filemtime('/test/sub/storage/foo/bar.txt'));
+ }
+
+ public function testRenameFailDeleteTargetKeepSource() {
+ $this->doTestCopyRenameFail('rename');
+ }
+
+ public function testCopyFailDeleteTargetKeepSource() {
+ $this->doTestCopyRenameFail('copy');
+ }
+
+ private function doTestCopyRenameFail($operation) {
+ $storage1 = new Temporary(array());
+ /** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
+ $storage2 = $this->getMockBuilder('\Test\Files\TemporaryNoCross')
+ ->setConstructorArgs([[]])
+ ->setMethods(['fopen'])
+ ->getMock();
+
+ $storage2->expects($this->any())
+ ->method('fopen')
+ ->will($this->returnCallback(function ($path, $mode) use ($storage2) {
+ /** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage2 */
+ $source = fopen($storage2->getSourcePath($path), $mode);
+ return \OC\Files\Stream\Quota::wrap($source, 9);
+ }));
+
+ $storage1->mkdir('sub');
+ $storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH');
+ $storage1->mkdir('dirtomove');
+ $storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits
+ $storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit
+ $storage2->file_put_contents('existing.txt', '0123');
+ $storage1->getScanner()->scan('');
+ $storage2->getScanner()->scan('');
+ \OC\Files\Filesystem::mount($storage1, array(), '/test/');
+ \OC\Files\Filesystem::mount($storage2, array(), '/test/sub/storage');
+
+ // move file
+ $view = new \OC\Files\View('');
+ $this->assertTrue($storage1->file_exists('foo.txt'));
+ $this->assertFalse($storage2->file_exists('foo.txt'));
+ $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/foo.txt'));
+ $this->assertFalse($storage2->file_exists('foo.txt'));
+ $this->assertFalse($storage2->getCache()->get('foo.txt'));
+ $this->assertTrue($storage1->file_exists('foo.txt'));
+
+ // if target exists, it will be deleted too
+ $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/existing.txt'));
+ $this->assertFalse($storage2->file_exists('existing.txt'));
+ $this->assertFalse($storage2->getCache()->get('existing.txt'));
+ $this->assertTrue($storage1->file_exists('foo.txt'));
+
+ // move folder
+ $this->assertFalse($view->$operation('/test/dirtomove/', '/test/sub/storage/dirtomove/'));
+ // since the move failed, the full source tree is kept
+ $this->assertTrue($storage1->file_exists('dirtomove/indir1.txt'));
+ $this->assertTrue($storage1->file_exists('dirtomove/indir2.txt'));
+ // second file not moved/copied
+ $this->assertFalse($storage2->file_exists('dirtomove/indir2.txt'));
+ $this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt'));
+
+ }
+
+ public function testDeleteFailKeepCache() {
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\Storage\Temporary $storage
+ */
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setConstructorArgs(array(array()))
+ ->setMethods(array('unlink'))
+ ->getMock();
+ $storage->expects($this->once())
+ ->method('unlink')
+ ->will($this->returnValue(false));
+ $scanner = $storage->getScanner();
+ $cache = $storage->getCache();
+ $storage->file_put_contents('foo.txt', 'asd');
+ $scanner->scan('');
+ \OC\Files\Filesystem::mount($storage, array(), '/test/');
+
+ $view = new \OC\Files\View('/test');
+
+ $this->assertFalse($view->unlink('foo.txt'));
+ $this->assertTrue($cache->inCache('foo.txt'));
+ }
+
+ function directoryTraversalProvider() {
+ return [
+ ['../test/'],
+ ['..\\test\\my/../folder'],
+ ['/test/my/../foo\\'],
+ ];
+ }
+
+ /**
+ * @dataProvider directoryTraversalProvider
+ * @expectedException \Exception
+ * @param string $root
+ */
+ public function testConstructDirectoryTraversalException($root) {
+ new \OC\Files\View($root);
+ }
+
+ public function testRenameOverWrite() {
+ $storage = new Temporary(array());
+ $scanner = $storage->getScanner();
+ $storage->mkdir('sub');
+ $storage->mkdir('foo');
+ $storage->file_put_contents('foo.txt', 'asd');
+ $storage->file_put_contents('foo/bar.txt', 'asd');
+ $scanner->scan('');
+ \OC\Files\Filesystem::mount($storage, array(), '/test/');
+ $view = new \OC\Files\View('');
+ $this->assertTrue($view->rename('/test/foo.txt', '/test/foo/bar.txt'));
+ }
+
+ public function testSetMountOptionsInStorage() {
+ $mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['foo' => 'bar']);
+ \OC\Files\Filesystem::getMountManager()->addMount($mount);
+ /** @var \OC\Files\Storage\Common $storage */
+ $storage = $mount->getStorage();
+ $this->assertEquals($storage->getMountOption('foo'), 'bar');
+ }
+
+ public function testSetMountOptionsWatcherPolicy() {
+ $mount = new MountPoint('\OC\Files\Storage\Temporary', '/asd/', [[]], \OC\Files\Filesystem::getLoader(), ['filesystem_check_changes' => Watcher::CHECK_NEVER]);
+ \OC\Files\Filesystem::getMountManager()->addMount($mount);
+ /** @var \OC\Files\Storage\Common $storage */
+ $storage = $mount->getStorage();
+ $watcher = $storage->getWatcher();
+ $this->assertEquals(Watcher::CHECK_NEVER, $watcher->getPolicy());
+ }
+
+ public function testGetAbsolutePathOnNull() {
+ $view = new \OC\Files\View();
+ $this->assertNull($view->getAbsolutePath(null));
+ }
+
+ public function testGetRelativePathOnNull() {
+ $view = new \OC\Files\View();
+ $this->assertNull($view->getRelativePath(null));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testNullAsRoot() {
+ new \OC\Files\View(null);
+ }
+
+ /**
+ * e.g. reading from a folder that's being renamed
+ *
+ * @expectedException \OCP\Lock\LockedException
+ *
+ * @dataProvider dataLockPaths
+ *
+ * @param string $rootPath
+ * @param string $pathPrefix
+ */
+ public function testReadFromWriteLockedPath($rootPath, $pathPrefix) {
+ $rootPath = str_replace('{folder}', 'files', $rootPath);
+ $pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
+
+ $view = new \OC\Files\View($rootPath);
+ $storage = new Temporary(array());
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
+ $view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED);
+ }
+
+ /**
+ * Reading from a files_encryption folder that's being renamed
+ *
+ * @dataProvider dataLockPaths
+ *
+ * @param string $rootPath
+ * @param string $pathPrefix
+ */
+ public function testReadFromWriteUnlockablePath($rootPath, $pathPrefix) {
+ $rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
+ $pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
+
+ $view = new \OC\Files\View($rootPath);
+ $storage = new Temporary(array());
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
+ $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED));
+ }
+
+ /**
+ * e.g. writing a file that's being downloaded
+ *
+ * @expectedException \OCP\Lock\LockedException
+ *
+ * @dataProvider dataLockPaths
+ *
+ * @param string $rootPath
+ * @param string $pathPrefix
+ */
+ public function testWriteToReadLockedFile($rootPath, $pathPrefix) {
+ $rootPath = str_replace('{folder}', 'files', $rootPath);
+ $pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
+
+ $view = new \OC\Files\View($rootPath);
+ $storage = new Temporary(array());
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
+ $view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
+ }
+
+ /**
+ * Writing a file that's being downloaded
+ *
+ * @dataProvider dataLockPaths
+ *
+ * @param string $rootPath
+ * @param string $pathPrefix
+ */
+ public function testWriteToReadUnlockableFile($rootPath, $pathPrefix) {
+ $rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
+ $pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
+
+ $view = new \OC\Files\View($rootPath);
+ $storage = new Temporary(array());
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
+ $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
+ }
+
+ /**
+ * Test that locks are on mount point paths instead of mount root
+ */
+ public function testLockLocalMountPointPathInsteadOfStorageRoot() {
+ $lockingProvider = \OC::$server->getLockingProvider();
+ $view = new \OC\Files\View('/testuser/files/');
+ $storage = new Temporary([]);
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $mountedStorage = new Temporary([]);
+ \OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
+
+ $this->assertTrue(
+ $view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, true),
+ 'Can lock mount point'
+ );
+
+ // no exception here because storage root was not locked
+ $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
+
+ $thrown = false;
+ try {
+ $storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
+ } catch (\OCP\Lock\LockedException $e) {
+ $thrown = true;
+ }
+ $this->assertTrue($thrown, 'Mount point path was locked on root storage');
+
+ $lockingProvider->releaseAll();
+ }
+
+ /**
+ * Test that locks are on mount point paths and also mount root when requested
+ */
+ public function testLockStorageRootButNotLocalMountPoint() {
+ $lockingProvider = \OC::$server->getLockingProvider();
+ $view = new \OC\Files\View('/testuser/files/');
+ $storage = new Temporary([]);
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $mountedStorage = new Temporary([]);
+ \OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
+
+ $this->assertTrue(
+ $view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, false),
+ 'Can lock mount point'
+ );
+
+ $thrown = false;
+ try {
+ $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
+ } catch (\OCP\Lock\LockedException $e) {
+ $thrown = true;
+ }
+ $this->assertTrue($thrown, 'Mount point storage root was locked on original storage');
+
+ // local mount point was not locked
+ $storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
+
+ $lockingProvider->releaseAll();
+ }
+
+ /**
+ * Test that locks are on mount point paths and also mount root when requested
+ */
+ public function testLockMountPointPathFailReleasesBoth() {
+ $lockingProvider = \OC::$server->getLockingProvider();
+ $view = new \OC\Files\View('/testuser/files/');
+ $storage = new Temporary([]);
+ \OC\Files\Filesystem::mount($storage, [], '/');
+ $mountedStorage = new Temporary([]);
+ \OC\Files\Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint.txt');
+
+ // this would happen if someone is writing on the mount point
+ $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
+
+ $thrown = false;
+ try {
+ // this actually acquires two locks, one on the mount point and one on the storage root,
+ // but the one on the storage root will fail
+ $view->lockFile('/mountpoint.txt', ILockingProvider::LOCK_SHARED);
+ } catch (\OCP\Lock\LockedException $e) {
+ $thrown = true;
+ }
+ $this->assertTrue($thrown, 'Cannot acquire shared lock because storage root is already locked');
+
+ // from here we expect that the lock on the local mount point was released properly
+ // so acquiring an exclusive lock will succeed
+ $storage->acquireLock('/testuser/files/mountpoint.txt', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
+
+ $lockingProvider->releaseAll();
+ }
+
+ public function dataLockPaths() {
+ return [
+ ['/testuser/{folder}', ''],
+ ['/testuser', '/{folder}'],
+ ['', '/testuser/{folder}'],
+ ];
+ }
+
+ public function pathRelativeToFilesProvider() {
+ return [
+ ['admin/files', ''],
+ ['admin/files/x', 'x'],
+ ['/admin/files', ''],
+ ['/admin/files/sub', 'sub'],
+ ['/admin/files/sub/', 'sub'],
+ ['/admin/files/sub/sub2', 'sub/sub2'],
+ ['//admin//files/sub//sub2', 'sub/sub2'],
+ ];
+ }
+
+ /**
+ * @dataProvider pathRelativeToFilesProvider
+ */
+ public function testGetPathRelativeToFiles($path, $expectedPath) {
+ $view = new \OC\Files\View();
+ $this->assertEquals($expectedPath, $view->getPathRelativeToFiles($path));
+ }
+
+ public function pathRelativeToFilesProviderExceptionCases() {
+ return [
+ [''],
+ ['x'],
+ ['files'],
+ ['/files'],
+ ['/admin/files_versions/abc'],
+ ];
+ }
+
+ /**
+ * @dataProvider pathRelativeToFilesProviderExceptionCases
+ * @expectedException \InvalidArgumentException
+ */
+ public function testGetPathRelativeToFilesWithInvalidArgument($path) {
+ $view = new \OC\Files\View();
+ $view->getPathRelativeToFiles($path);
+ }
+
+ public function testChangeLock() {
+ $view = new \OC\Files\View('/testuser/files/');
+ $storage = new Temporary(array());
+ \OC\Files\Filesystem::mount($storage, [], '/');
+
+ $view->lockFile('/test/sub', ILockingProvider::LOCK_SHARED);
+ $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
+ $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
+
+ $view->changeLock('//test/sub', ILockingProvider::LOCK_EXCLUSIVE);
+ $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
+
+ $view->changeLock('test/sub', ILockingProvider::LOCK_SHARED);
+ $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
+
+ $view->unlockFile('/test/sub/', ILockingProvider::LOCK_SHARED);
+
+ $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
+ $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
+
+ }
+
+ public function hookPathProvider() {
+ return [
+ ['/foo/files', '/foo', true],
+ ['/foo/files/bar', '/foo', true],
+ ['/foo', '/foo', false],
+ ['/foo', '/files/foo', true],
+ ['/foo', 'filesfoo', false],
+ ['', '/foo/files', true],
+ ['', '/foo/files/bar.txt', true]
+ ];
+ }
+
+ /**
+ * @dataProvider hookPathProvider
+ * @param $root
+ * @param $path
+ * @param $shouldEmit
+ */
+ public function testHookPaths($root, $path, $shouldEmit) {
+ $filesystemReflection = new \ReflectionClass('\OC\Files\Filesystem');
+ $defaultRootValue = $filesystemReflection->getProperty('defaultInstance');
+ $defaultRootValue->setAccessible(true);
+ $oldRoot = $defaultRootValue->getValue();
+ $defaultView = new \OC\Files\View('/foo/files');
+ $defaultRootValue->setValue($defaultView);
+ $view = new \OC\Files\View($root);
+ $result = $this->invokePrivate($view, 'shouldEmitHooks', [$path]);
+ $defaultRootValue->setValue($oldRoot);
+ $this->assertEquals($shouldEmit, $result);
+ }
+
+ /**
+ * Create test movable mount points
+ *
+ * @param array $mountPoints array of mount point locations
+ * @return array array of MountPoint objects
+ */
+ private function createTestMovableMountPoints($mountPoints) {
+ $mounts = [];
+ foreach ($mountPoints as $mountPoint) {
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([])
+ ->getMock();
+
+ $mounts[] = $this->getMock(
+ '\Test\TestMoveableMountPoint',
+ ['moveMount'],
+ [$storage, $mountPoint]
+ );
+ }
+
+ $mountProvider = $this->getMock('\OCP\Files\Config\IMountProvider');
+ $mountProvider->expects($this->any())
+ ->method('getMountsForUser')
+ ->will($this->returnValue($mounts));
+
+ $mountProviderCollection = \OC::$server->getMountProviderCollection();
+ $mountProviderCollection->registerProvider($mountProvider);
+
+ return $mounts;
+ }
+
+ /**
+ * Test mount point move
+ */
+ public function testMountPointMove() {
+ $this->loginAsUser($this->user);
+
+ list($mount1, $mount2) = $this->createTestMovableMountPoints([
+ $this->user . '/files/mount1',
+ $this->user . '/files/mount2',
+ ]);
+ $mount1->expects($this->once())
+ ->method('moveMount')
+ ->will($this->returnValue(true));
+
+ $mount2->expects($this->once())
+ ->method('moveMount')
+ ->will($this->returnValue(true));
+
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+ $view->mkdir('sub');
+
+ $this->assertTrue($view->rename('mount1', 'renamed_mount'), 'Can rename mount point');
+ $this->assertTrue($view->rename('mount2', 'sub/moved_mount'), 'Can move a mount point into a subdirectory');
+ }
+
+ /**
+ * Test that moving a mount point into another is forbidden
+ */
+ public function testMoveMountPointIntoAnother() {
+ $this->loginAsUser($this->user);
+
+ list($mount1, $mount2) = $this->createTestMovableMountPoints([
+ $this->user . '/files/mount1',
+ $this->user . '/files/mount2',
+ ]);
+
+ $mount1->expects($this->never())
+ ->method('moveMount');
+
+ $mount2->expects($this->never())
+ ->method('moveMount');
+
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $this->assertFalse($view->rename('mount1', 'mount2'), 'Cannot overwrite another mount point');
+ $this->assertFalse($view->rename('mount1', 'mount2/sub'), 'Cannot move a mount point into another');
+ }
+
+ /**
+ * Test that moving a mount point into a shared folder is forbidden
+ */
+ public function testMoveMountPointIntoSharedFolder() {
+ $this->loginAsUser($this->user);
+
+ list($mount1) = $this->createTestMovableMountPoints([
+ $this->user . '/files/mount1',
+ ]);
+
+ $mount1->expects($this->never())
+ ->method('moveMount');
+
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+ $view->mkdir('shareddir');
+ $view->mkdir('shareddir/sub');
+ $view->mkdir('shareddir/sub2');
+
+ $fileId = $view->getFileInfo('shareddir')->getId();
+ $userObject = \OC::$server->getUserManager()->createUser('test2', 'IHateNonMockableStaticClasses');
+ $this->assertTrue(\OCP\Share::shareItem('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2', \OCP\Constants::PERMISSION_READ));
+
+ $this->assertFalse($view->rename('mount1', 'shareddir'), 'Cannot overwrite shared folder');
+ $this->assertFalse($view->rename('mount1', 'shareddir/sub'), 'Cannot move mount point into shared folder');
+ $this->assertFalse($view->rename('mount1', 'shareddir/sub/sub2'), 'Cannot move mount point into shared subfolder');
+
+ $this->assertTrue(\OCP\Share::unshare('folder', $fileId, \OCP\Share::SHARE_TYPE_USER, 'test2'));
+ $userObject->delete();
+ }
+
+ public function basicOperationProviderForLocks() {
+ return [
+ // --- write hook ----
+ [
+ 'touch',
+ ['touch-create.txt'],
+ 'touch-create.txt',
+ 'create',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_EXCLUSIVE,
+ ILockingProvider::LOCK_SHARED,
+ ],
+ [
+ 'fopen',
+ ['test-write.txt', 'w'],
+ 'test-write.txt',
+ 'write',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_EXCLUSIVE,
+ null,
+ // exclusive lock stays until fclose
+ ILockingProvider::LOCK_EXCLUSIVE,
+ ],
+ [
+ 'mkdir',
+ ['newdir'],
+ 'newdir',
+ 'write',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_EXCLUSIVE,
+ ILockingProvider::LOCK_SHARED,
+ ],
+ [
+ 'file_put_contents',
+ ['file_put_contents.txt', 'blah'],
+ 'file_put_contents.txt',
+ 'write',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_EXCLUSIVE,
+ ILockingProvider::LOCK_SHARED,
+ ],
+
+ // ---- delete hook ----
+ [
+ 'rmdir',
+ ['dir'],
+ 'dir',
+ 'delete',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_EXCLUSIVE,
+ ILockingProvider::LOCK_SHARED,
+ ],
+ [
+ 'unlink',
+ ['test.txt'],
+ 'test.txt',
+ 'delete',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_EXCLUSIVE,
+ ILockingProvider::LOCK_SHARED,
+ ],
+
+ // ---- read hook (no post hooks) ----
+ [
+ 'file_get_contents',
+ ['test.txt'],
+ 'test.txt',
+ 'read',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_SHARED,
+ null,
+ ],
+ [
+ 'fopen',
+ ['test.txt', 'r'],
+ 'test.txt',
+ 'read',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_SHARED,
+ null,
+ ],
+ [
+ 'opendir',
+ ['dir'],
+ 'dir',
+ 'read',
+ ILockingProvider::LOCK_SHARED,
+ ILockingProvider::LOCK_SHARED,
+ null,
+ ],
+
+ // ---- no lock, touch hook ---
+ ['touch', ['test.txt'], 'test.txt', 'touch', null, null, null],
+
+ // ---- no hooks, no locks ---
+ ['is_dir', ['dir'], 'dir', null],
+ ['is_file', ['dir'], 'dir', null],
+ ['stat', ['dir'], 'dir', null],
+ ['filetype', ['dir'], 'dir', null],
+ ['filesize', ['dir'], 'dir', null],
+ ['isCreatable', ['dir'], 'dir', null],
+ ['isReadable', ['dir'], 'dir', null],
+ ['isUpdatable', ['dir'], 'dir', null],
+ ['isDeletable', ['dir'], 'dir', null],
+ ['isSharable', ['dir'], 'dir', null],
+ ['file_exists', ['dir'], 'dir', null],
+ ['filemtime', ['dir'], 'dir', null],
+ ];
+ }
+
+ /**
+ * Test whether locks are set before and after the operation
+ *
+ * @dataProvider basicOperationProviderForLocks
+ *
+ * @param string $operation operation name on the view
+ * @param array $operationArgs arguments for the operation
+ * @param string $lockedPath path of the locked item to check
+ * @param string $hookType hook type
+ * @param int $expectedLockBefore expected lock during pre hooks
+ * @param int $expectedLockduring expected lock during operation
+ * @param int $expectedLockAfter expected lock during post hooks
+ * @param int $expectedStrayLock expected lock after returning, should
+ * be null (unlock) for most operations
+ */
+ public function testLockBasicOperation(
+ $operation,
+ $operationArgs,
+ $lockedPath,
+ $hookType,
+ $expectedLockBefore = ILockingProvider::LOCK_SHARED,
+ $expectedLockDuring = ILockingProvider::LOCK_SHARED,
+ $expectedLockAfter = ILockingProvider::LOCK_SHARED,
+ $expectedStrayLock = null
+ ) {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([$operation])
+ ->getMock();
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+
+ // work directly on disk because mkdir might be mocked
+ $realPath = $storage->getSourcePath('');
+ mkdir($realPath . '/files');
+ mkdir($realPath . '/files/dir');
+ file_put_contents($realPath . '/files/test.txt', 'blah');
+ $storage->getScanner()->scan('files');
+
+ $storage->expects($this->once())
+ ->method($operation)
+ ->will($this->returnCallback(
+ function () use ($view, $lockedPath, &$lockTypeDuring) {
+ $lockTypeDuring = $this->getFileLockType($view, $lockedPath);
+
+ return true;
+ }
+ ));
+
+ $this->assertNull($this->getFileLockType($view, $lockedPath), 'File not locked before operation');
+
+ $this->connectMockHooks($hookType, $view, $lockedPath, $lockTypePre, $lockTypePost);
+
+ // do operation
+ call_user_func_array(array($view, $operation), $operationArgs);
+
+ if ($hookType !== null) {
+ $this->assertEquals($expectedLockBefore, $lockTypePre, 'File locked properly during pre-hook');
+ $this->assertEquals($expectedLockAfter, $lockTypePost, 'File locked properly during post-hook');
+ $this->assertEquals($expectedLockDuring, $lockTypeDuring, 'File locked properly during operation');
+ } else {
+ $this->assertNull($lockTypeDuring, 'File not locked during operation');
+ }
+
+ $this->assertEquals($expectedStrayLock, $this->getFileLockType($view, $lockedPath));
+ }
+
+ /**
+ * Test locks for file_put_content with stream.
+ * This code path uses $storage->fopen instead
+ */
+ public function testLockFilePutContentWithStream() {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $path = 'test_file_put_contents.txt';
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods(['fopen'])
+ ->getMock();
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+ $storage->mkdir('files');
+
+ $storage->expects($this->once())
+ ->method('fopen')
+ ->will($this->returnCallback(
+ function () use ($view, $path, &$lockTypeDuring) {
+ $lockTypeDuring = $this->getFileLockType($view, $path);
+
+ return fopen('php://temp', 'r+');
+ }
+ ));
+
+ $this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
+
+ $this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
+
+ // do operation
+ $view->file_put_contents($path, fopen('php://temp', 'r+'));
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePost, 'File locked properly during post-hook');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
+
+ $this->assertNull($this->getFileLockType($view, $path));
+ }
+
+ /**
+ * Test locks for fopen with fclose at the end
+ */
+ public function testLockFopen() {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $path = 'test_file_put_contents.txt';
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods(['fopen'])
+ ->getMock();
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+ $storage->mkdir('files');
+
+ $storage->expects($this->once())
+ ->method('fopen')
+ ->will($this->returnCallback(
+ function () use ($view, $path, &$lockTypeDuring) {
+ $lockTypeDuring = $this->getFileLockType($view, $path);
+
+ return fopen('php://temp', 'r+');
+ }
+ ));
+
+ $this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
+
+ $this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
+
+ // do operation
+ $res = $view->fopen($path, 'w');
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
+ $this->assertNull($lockTypePost, 'No post hook, no lock check possible');
+
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File still locked after fopen');
+
+ fclose($res);
+
+ $this->assertNull($this->getFileLockType($view, $path), 'File unlocked after fclose');
+ }
+
+ /**
+ * Test locks for fopen with fclose at the end
+ *
+ * @dataProvider basicOperationProviderForLocks
+ *
+ * @param string $operation operation name on the view
+ * @param array $operationArgs arguments for the operation
+ * @param string $path path of the locked item to check
+ */
+ public function testLockBasicOperationUnlocksAfterException(
+ $operation,
+ $operationArgs,
+ $path
+ ) {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([$operation])
+ ->getMock();
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+
+ // work directly on disk because mkdir might be mocked
+ $realPath = $storage->getSourcePath('');
+ mkdir($realPath . '/files');
+ mkdir($realPath . '/files/dir');
+ file_put_contents($realPath . '/files/test.txt', 'blah');
+ $storage->getScanner()->scan('files');
+
+ $storage->expects($this->once())
+ ->method($operation)
+ ->will($this->returnCallback(
+ function () {
+ throw new \Exception('Simulated exception');
+ }
+ ));
+
+ $thrown = false;
+ try {
+ call_user_func_array(array($view, $operation), $operationArgs);
+ } catch (\Exception $e) {
+ $thrown = true;
+ $this->assertEquals('Simulated exception', $e->getMessage());
+ }
+ $this->assertTrue($thrown, 'Exception was rethrown');
+ $this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
+ }
+
+ /**
+ * Test locks for fopen with fclose at the end
+ *
+ * @dataProvider basicOperationProviderForLocks
+ *
+ * @param string $operation operation name on the view
+ * @param array $operationArgs arguments for the operation
+ * @param string $path path of the locked item to check
+ * @param string $hookType hook type
+ */
+ public function testLockBasicOperationUnlocksAfterCancelledHook(
+ $operation,
+ $operationArgs,
+ $path,
+ $hookType
+ ) {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([$operation])
+ ->getMock();
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+ $storage->mkdir('files');
+
+ \OCP\Util::connectHook(
+ \OC\Files\Filesystem::CLASSNAME,
+ $hookType,
+ '\Test\HookHelper',
+ 'cancellingCallback'
+ );
+
+ call_user_func_array(array($view, $operation), $operationArgs);
+
+ $this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
+ }
+
+ public function lockFileRenameOrCopyDataProvider() {
+ return [
+ ['rename', ILockingProvider::LOCK_EXCLUSIVE],
+ ['copy', ILockingProvider::LOCK_SHARED],
+ ];
+ }
+
+ /**
+ * Test locks for rename or copy operation
+ *
+ * @dataProvider lockFileRenameOrCopyDataProvider
+ *
+ * @param string $operation operation to be done on the view
+ * @param int $expectedLockTypeSourceDuring expected lock type on source file during
+ * the operation
+ */
+ public function testLockFileRename($operation, $expectedLockTypeSourceDuring) {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([$operation, 'filemtime'])
+ ->getMock();
+
+ $storage->expects($this->any())
+ ->method('filemtime')
+ ->will($this->returnValue(123456789));
+
+ $sourcePath = 'original.txt';
+ $targetPath = 'target.txt';
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+ $storage->mkdir('files');
+ $view->file_put_contents($sourcePath, 'meh');
+
+ $storage->expects($this->once())
+ ->method($operation)
+ ->will($this->returnCallback(
+ function () use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring) {
+ $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
+ $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
+
+ return true;
+ }
+ ));
+
+ $this->connectMockHooks($operation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
+ $this->connectMockHooks($operation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
+
+ $view->$operation($sourcePath, $targetPath);
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
+ $this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
+ }
+
+ /**
+ * simulate a failed copy operation.
+ * We expect that we catch the exception, free the lock and re-throw it.
+ *
+ * @expectedException \Exception
+ */
+ public function testLockFileCopyException() {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods(['copy'])
+ ->getMock();
+
+ $sourcePath = 'original.txt';
+ $targetPath = 'target.txt';
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+ $storage->mkdir('files');
+ $view->file_put_contents($sourcePath, 'meh');
+
+ $storage->expects($this->once())
+ ->method('copy')
+ ->will($this->returnCallback(
+ function () {
+ throw new \Exception();
+ }
+ ));
+
+ $this->connectMockHooks('copy', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
+ $this->connectMockHooks('copy', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
+
+ try {
+ $view->copy($sourcePath, $targetPath);
+ } catch (\Exception $e) {
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
+ throw $e;
+ }
+ }
+
+ /**
+ * Test rename operation: unlock first path when second path was locked
+ */
+ public function testLockFileRenameUnlockOnException() {
+ $this->loginAsUser('test');
+
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $sourcePath = 'original.txt';
+ $targetPath = 'target.txt';
+ $view->file_put_contents($sourcePath, 'meh');
+
+ // simulate that the target path is already locked
+ $view->lockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file is locked before operation');
+
+ $thrown = false;
+ try {
+ $view->rename($sourcePath, $targetPath);
+ } catch (\OCP\Lock\LockedException $e) {
+ $thrown = true;
+ }
+
+ $this->assertTrue($thrown, 'LockedException thrown');
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file still locked after operation');
+
+ $view->unlockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
+ }
+
+ /**
+ * Test rename operation: unlock first path when second path was locked
+ */
+ public function testGetOwner() {
+ $this->loginAsUser('test');
+
+ $view = new \OC\Files\View('/test/files/');
+
+ $path = 'foo.txt';
+ $view->file_put_contents($path, 'meh');
+
+ $this->assertEquals('test', $view->getFileInfo($path)->getOwner()->getUID());
+
+ $folderInfo = $view->getDirectoryContent('');
+ $folderInfo = array_values(array_filter($folderInfo, function (FileInfo $info) {
+ return $info->getName() === 'foo.txt';
+ }));
+
+ $this->assertEquals('test', $folderInfo[0]->getOwner()->getUID());
+
+ $subStorage = new Temporary();
+ \OC\Files\Filesystem::mount($subStorage, [], '/test/files/asd');
+
+ $folderInfo = $view->getDirectoryContent('');
+ $folderInfo = array_values(array_filter($folderInfo, function (FileInfo $info) {
+ return $info->getName() === 'asd';
+ }));
+
+ $this->assertEquals('test', $folderInfo[0]->getOwner()->getUID());
+ }
+
+ public function lockFileRenameOrCopyCrossStorageDataProvider() {
+ return [
+ ['rename', 'moveFromStorage', ILockingProvider::LOCK_EXCLUSIVE],
+ ['copy', 'copyFromStorage', ILockingProvider::LOCK_SHARED],
+ ];
+ }
+
+ /**
+ * Test locks for rename or copy operation cross-storage
+ *
+ * @dataProvider lockFileRenameOrCopyCrossStorageDataProvider
+ *
+ * @param string $viewOperation operation to be done on the view
+ * @param string $storageOperation operation to be mocked on the storage
+ * @param int $expectedLockTypeSourceDuring expected lock type on source file during
+ * the operation
+ */
+ public function testLockFileRenameCrossStorage($viewOperation, $storageOperation, $expectedLockTypeSourceDuring) {
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+
+ $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([$storageOperation])
+ ->getMock();
+ $storage2 = $this->getMockBuilder('\OC\Files\Storage\Temporary')
+ ->setMethods([$storageOperation, 'filemtime'])
+ ->getMock();
+
+ $storage2->expects($this->any())
+ ->method('filemtime')
+ ->will($this->returnValue(123456789));
+
+ $sourcePath = 'original.txt';
+ $targetPath = 'substorage/target.txt';
+
+ \OC\Files\Filesystem::mount($storage, array(), $this->user . '/');
+ \OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
+ $storage->mkdir('files');
+ $view->file_put_contents($sourcePath, 'meh');
+
+ $storage->expects($this->never())
+ ->method($storageOperation);
+ $storage2->expects($this->once())
+ ->method($storageOperation)
+ ->will($this->returnCallback(
+ function () use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring) {
+ $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
+ $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
+
+ return true;
+ }
+ ));
+
+ $this->connectMockHooks($viewOperation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
+ $this->connectMockHooks($viewOperation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
+
+ $view->$viewOperation($sourcePath, $targetPath);
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
+ $this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
+ }
+
+ /**
+ * Test locks when moving a mount point
+ */
+ public function testLockMoveMountPoint() {
+ $this->loginAsUser('test');
+
+ list($mount) = $this->createTestMovableMountPoints([
+ $this->user . '/files/substorage',
+ ]);
+
+ $view = new \OC\Files\View('/' . $this->user . '/files/');
+ $view->mkdir('subdir');
+
+ $sourcePath = 'substorage';
+ $targetPath = 'subdir/substorage_moved';
+
+ $mount->expects($this->once())
+ ->method('moveMount')
+ ->will($this->returnCallback(
+ function ($target) use ($mount, $view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring, &$lockTypeSharedRootDuring) {
+ $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath, true);
+ $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath, true);
+
+ $lockTypeSharedRootDuring = $this->getFileLockType($view, $sourcePath, false);
+
+ $mount->setMountPoint($target);
+
+ return true;
+ }
+ ));
+
+ $this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost, true);
+ $this->connectMockHooks('rename', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost, true);
+ // in pre-hook, mount point is still on $sourcePath
+ $this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSharedRootPre, $dummy, false);
+ // in post-hook, mount point is now on $targetPath
+ $this->connectMockHooks('rename', $view, $targetPath, $dummy, $lockTypeSharedRootPost, false);
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked before operation');
+ $this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked before operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked before operation');
+
+ $view->rename($sourcePath, $targetPath);
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source path locked properly during pre-hook');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeSourceDuring, 'Source path locked properly during operation');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source path locked properly during post-hook');
+
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target path locked properly during pre-hook');
+ $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target path locked properly during operation');
+ $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target path locked properly during post-hook');
+
+ $this->assertNull($lockTypeSharedRootPre, 'Shared storage root not locked during pre-hook');
+ $this->assertNull($lockTypeSharedRootDuring, 'Shared storage root not locked during move');
+ $this->assertNull($lockTypeSharedRootPost, 'Shared storage root not locked during post-hook');
+
+ $this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked after operation');
+ $this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked after operation');
+ $this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked after operation');
+ }
+
+ /**
+ * Connect hook callbacks for hook type
+ *
+ * @param string $hookType hook type or null for none
+ * @param \OC\Files\View $view view to check the lock on
+ * @param string $path path for which to check the lock
+ * @param int $lockTypePre variable to receive lock type that was active in the pre-hook
+ * @param int $lockTypePost variable to receive lock type that was active in the post-hook
+ * @param bool $onMountPoint true to check the mount point instead of the
+ * mounted storage
+ */
+ private function connectMockHooks($hookType, $view, $path, &$lockTypePre, &$lockTypePost, $onMountPoint = false) {
+ if ($hookType === null) {
+ return;
+ }
+
+ $eventHandler = $this->getMockBuilder('\stdclass')
+ ->setMethods(['preCallback', 'postCallback'])
+ ->getMock();
+
+ $eventHandler->expects($this->any())
+ ->method('preCallback')
+ ->will($this->returnCallback(
+ function () use ($view, $path, $onMountPoint, &$lockTypePre) {
+ $lockTypePre = $this->getFileLockType($view, $path, $onMountPoint);
+ }
+ ));
+ $eventHandler->expects($this->any())
+ ->method('postCallback')
+ ->will($this->returnCallback(
+ function () use ($view, $path, $onMountPoint, &$lockTypePost) {
+ $lockTypePost = $this->getFileLockType($view, $path, $onMountPoint);
+ }
+ ));
+
+ if ($hookType !== null) {
+ \OCP\Util::connectHook(
+ \OC\Files\Filesystem::CLASSNAME,
+ $hookType,
+ $eventHandler,
+ 'preCallback'
+ );
+ \OCP\Util::connectHook(
+ \OC\Files\Filesystem::CLASSNAME,
+ 'post_' . $hookType,
+ $eventHandler,
+ 'postCallback'
+ );
+ }
+ }
+
+ /**
+ * Returns the file lock type
+ *
+ * @param \OC\Files\View $view view
+ * @param string $path path
+ * @param bool $onMountPoint true to check the mount point instead of the
+ * mounted storage
+ *
+ * @return int lock type or null if file was not locked
+ */
+ private function getFileLockType(\OC\Files\View $view, $path, $onMountPoint = false) {
+ if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE, $onMountPoint)) {
+ return ILockingProvider::LOCK_EXCLUSIVE;
+ } else if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED, $onMountPoint)) {
+ return ILockingProvider::LOCK_SHARED;
+ }
+ return null;
+ }
+
+
+ public function testRemoveMoveableMountPoint() {
+ $mountPoint = '/' . $this->user . '/files/mount/';
+
+ // Mock the mount point
+ $mount = $this->getMockBuilder('\Test\TestMoveableMountPoint')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mount->expects($this->once())
+ ->method('getMountPoint')
+ ->willReturn($mountPoint);
+ $mount->expects($this->once())
+ ->method('removeMount')
+ ->willReturn('foo');
+ $mount->expects($this->any())
+ ->method('getInternalPath')
+ ->willReturn('');
+
+ // Register mount
+ \OC\Files\Filesystem::getMountManager()->addMount($mount);
+
+ // Listen for events
+ $eventHandler = $this->getMockBuilder('\stdclass')
+ ->setMethods(['umount', 'post_umount'])
+ ->getMock();
+ $eventHandler->expects($this->once())
+ ->method('umount')
+ ->with([\OC\Files\Filesystem::signal_param_path => '/mount']);
+ $eventHandler->expects($this->once())
+ ->method('post_umount')
+ ->with([\OC\Files\Filesystem::signal_param_path => '/mount']);
+ \OCP\Util::connectHook(
+ \OC\Files\Filesystem::CLASSNAME,
+ 'umount',
+ $eventHandler,
+ 'umount'
+ );
+ \OCP\Util::connectHook(
+ \OC\Files\Filesystem::CLASSNAME,
+ 'post_umount',
+ $eventHandler,
+ 'post_umount'
+ );
+
+ //Delete the mountpoint
+ $view = new \OC\Files\View('/' . $this->user . '/files');
+ $this->assertEquals('foo', $view->rmdir('mount'));
+ }
+
+ public function mimeFilterProvider() {
+ return [
+ [null, ['test1.txt', 'test2.txt', 'test3.md', 'test4.png']],
+ ['text/plain', ['test1.txt', 'test2.txt']],
+ ['text/markdown', ['test3.md']],
+ ['text', ['test1.txt', 'test2.txt', 'test3.md']],
+ ];
+ }
+
+ /**
+ * @param string $filter
+ * @param string[] $expected
+ * @dataProvider mimeFilterProvider
+ */
+ public function testGetDirectoryContentMimeFilter($filter, $expected) {
+ $storage1 = new Temporary();
+ $root = $this->getUniqueID('/');
+ \OC\Files\Filesystem::mount($storage1, array(), $root . '/');
+ $view = new \OC\Files\View($root);
+
+ $view->file_put_contents('test1.txt', 'asd');
+ $view->file_put_contents('test2.txt', 'asd');
+ $view->file_put_contents('test3.md', 'asd');
+ $view->file_put_contents('test4.png', '');
+
+ $content = $view->getDirectoryContent('', $filter);
+
+ $files = array_map(function(FileInfo $info) {
+ return $info->getName();
+ }, $content);
+ sort($files);
+
+ $this->assertEquals($expected, $files);
+ }
+
+ public function testFilePutContentsClearsChecksum() {
+ $storage = new Temporary(array());
+ $scanner = $storage->getScanner();
+ $storage->file_put_contents('foo.txt', 'bar');
+ \OC\Files\Filesystem::mount($storage, array(), '/test/');
+ $scanner->scan('');
+
+ $view = new \OC\Files\View('/test/foo.txt');
+ $view->putFileInfo('.', ['checksum' => '42']);
+
+ $this->assertEquals('bar', $view->file_get_contents(''));
+ $fh = tmpfile();
+ fwrite($fh, 'fooo');
+ rewind($fh);
+ $view->file_put_contents('', $fh);
+ $this->assertEquals('fooo', $view->file_get_contents(''));
+ $data = $view->getFileInfo('.');
+ $this->assertEquals('', $data->getChecksum());
+ }
+}