diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index 819452d64abe..80dce8bea026 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -28,9 +28,11 @@ namespace OCA\Files_Trashbin\Tests; +use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\Storage\Temporary; use OC\Files\Filesystem; use OC\Files\View; +use OCP\Files\Storage; use Test\TestCase; use Test\Traits\UserTrait; @@ -196,6 +198,7 @@ public function testCrossStorageDeleteFolder() { * Test that deleted versions properly land in the trashbin. */ public function testDeleteVersionsOfFile() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); // trigger a version (multiple would not work because of the expire logic) @@ -225,6 +228,7 @@ public function testDeleteVersionsOfFile() { * Test that deleted versions properly land in the trashbin. */ public function testDeleteVersionsOfFolder() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); // trigger a version (multiple would not work because of the expire logic) @@ -260,6 +264,7 @@ public function testDeleteVersionsOfFolder() { * Test that deleted versions properly land in the trashbin when deleting as share recipient. */ public function testDeleteVersionsOfFileAsRecipient() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); $this->userView->mkdir('share'); @@ -312,6 +317,7 @@ public function testDeleteVersionsOfFileAsRecipient() { * Test that deleted versions properly land in the trashbin when deleting as share recipient. */ public function testDeleteVersionsOfFolderAsRecipient() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); $this->userView->mkdir('share'); @@ -380,6 +386,7 @@ public function testDeleteVersionsOfFolderAsRecipient() { * unlink() which should NOT trigger the version deletion logic. */ public function testKeepFileAndVersionsWhenMovingFileBetweenStorages() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); $storage2 = new Temporary([]); @@ -421,6 +428,7 @@ public function testKeepFileAndVersionsWhenMovingFileBetweenStorages() { * unlink() which should NOT trigger the version deletion logic. */ public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); $storage2 = new Temporary([]); @@ -461,6 +469,7 @@ public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() { * out of the shared folder */ public function testOwnerBackupWhenMovingFileOutOfShare() { + $this->markTestSkippedIfStorageHasOwnVersioning(); \OCA\Files_Versions\Hooks::connectHooks(); $this->userView->mkdir('share'); @@ -643,4 +652,12 @@ public function testSingleStorageDeleteFileLoggedOut() { $this->userView->unlink('test.txt'); } } + + private function markTestSkippedIfStorageHasOwnVersioning() { + /** @var Storage $storage */ + list($storage, $internalPath) = $this->userView->resolvePath('folder/inside.txt'); + if ($storage->instanceOfStorage(ObjectStoreStorage::class)) { + $this->markTestSkipped(); + } + } } diff --git a/apps/files_versions/js/versioncollection.js b/apps/files_versions/js/versioncollection.js index 5ec4e21586ea..47a26eeb8a04 100644 --- a/apps/files_versions/js/versioncollection.js +++ b/apps/files_versions/js/versioncollection.js @@ -38,19 +38,17 @@ parse: function(result) { var fullPath = this._fileInfo.getFullPath(); var fileId = this._fileInfo.get('id'); - var results = _.map(result, function(version) { - var revision = parseInt(version.id, 10); + return _.map(result, function(version) { + var revision = version.id; return { id: revision, name: revision, fullPath: fullPath, - timestamp: revision, - versionId: revision, + timestamp: moment(new Date(version['{DAV:}getlastmodified'])).format('X'), size: version['{DAV:}getcontentlength'], fileId: fileId }; }); - return results; } }); diff --git a/apps/files_versions/js/versionmodel.js b/apps/files_versions/js/versionmodel.js index 5551aa2430d2..5310cd719696 100644 --- a/apps/files_versions/js/versionmodel.js +++ b/apps/files_versions/js/versionmodel.js @@ -57,7 +57,7 @@ getDownloadUrl: function() { return OC.linkToRemoteBase('dav') + '/meta/' + encodeURIComponent(this.get('fileId')) + '/v/' + - encodeURIComponent(this.get('versionId')); + encodeURIComponent(this.get('id')); } }); diff --git a/apps/files_versions/js/versionstabview.js b/apps/files_versions/js/versionstabview.js index 1faaf2d89884..8a77f1bfc075 100644 --- a/apps/files_versions/js/versionstabview.js +++ b/apps/files_versions/js/versionstabview.js @@ -10,7 +10,7 @@ (function() { var TEMPLATE_ITEM = - '
  • ' + + '
  • ' + '
    ' + '
    ' + '' + @@ -186,6 +186,7 @@ var timestamp = version.get('timestamp') * 1000; var size = version.has('size') ? version.get('size') : 0; return _.extend({ + versionId: version.get('id'), formattedTimestamp: OC.Util.formatDate(timestamp), relativeTimestamp: OC.Util.relativeModifiedDate(timestamp), humanReadableSize: OC.Util.humanFileSize(size, true), diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index 130be95b22e3..37cb5b023f67 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -46,6 +46,7 @@ use OCA\Files_Versions\AppInfo\Application; use OCA\Files_Versions\Command\Expire; use OCP\Files\NotFoundException; +use OCP\Files\Storage\IVersionedStorage; use OCP\Lock\ILockingProvider; use OCP\User; @@ -174,7 +175,16 @@ public static function store($filename) { } list($uid, $filename) = self::getUidAndFilename($filename); + /** @var \OCP\Files\Storage\IStorage $storage */ + list($storage, $internalPath) = Filesystem::resolvePath($filename); + if ($storage->instanceOfStorage(IVersionedStorage::class)) { + /** @var IVersionedStorage $storage */ + if ($storage->saveVersion($internalPath)) { + return true; + } + } + // fallback implementation below - need to go into class Common $files_view = new View('/'.$uid .'/files'); $users_view = new View('/'.$uid); diff --git a/apps/files_versions/tests/VersioningTest.php b/apps/files_versions/tests/VersioningTest.php index 13c1e8f5a492..3e5e3865ed39 100644 --- a/apps/files_versions/tests/VersioningTest.php +++ b/apps/files_versions/tests/VersioningTest.php @@ -34,7 +34,9 @@ require_once __DIR__ . '/../appinfo/app.php'; +use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\Storage\Temporary; +use OCP\Files\Storage; use Test\TestCase; /** @@ -676,6 +678,8 @@ public function testStoreVersionAsAnonymous() { * @param string $path */ private function createAndCheckVersions(\OC\Files\View $view, $path) { + $this->markTestSkippedIfStorageHasOwnVersioning(); + $view->file_put_contents($path, 'test file'); $view->file_put_contents($path, 'version 1'); $view->file_put_contents($path, 'version 2'); @@ -721,6 +725,13 @@ public static function loginHelper($user, $create = false) { \OC::$server->getUserFolder($user); } + private function markTestSkippedIfStorageHasOwnVersioning() { + /** @var Storage $storage */ + list($storage, $internalPath) = $this->rootView->resolvePath(self::USERS_VERSIONS_ROOT); + if ($storage->instanceOfStorage(ObjectStoreStorage::class)) { + $this->markTestSkipped(); + } + } } // extend the original class to make it possible to test protected methods diff --git a/apps/files_versions/tests/js/versionmodelSpec.js b/apps/files_versions/tests/js/versionmodelSpec.js index 3aeaecab7cec..ef4e07c23638 100644 --- a/apps/files_versions/tests/js/versionmodelSpec.js +++ b/apps/files_versions/tests/js/versionmodelSpec.js @@ -18,13 +18,12 @@ describe('OCA.Versions.VersionModel', function() { beforeEach(function() { model = new VersionModel({ - id: 10000000, + id: 123456789, fileId: 10000000, timestamp: 10000000, fullPath: '/subdir/some file.txt', name: 'some file.txt', size: 150, - versionId: 123456789 }); OC.currentUser = 'user0'; diff --git a/core/js/files/client.js b/core/js/files/client.js index 222f5cadf55a..66bcf7b3ce23 100644 --- a/core/js/files/client.js +++ b/core/js/files/client.js @@ -797,7 +797,7 @@ * @param {Object} [headers=null] additional headers * * @return {Promise} promise - * @since 10.1.0 + * @since 10.0.5 */ copy: function(path, destinationPath, allowOverwrite, headers, options) { return this._moveOrCopy('COPY', path, destinationPath, allowOverwrite, headers, options); diff --git a/lib/private/Files/Meta/MetaFileVersionNode.php b/lib/private/Files/Meta/MetaFileVersionNode.php index 8aa245baefff..4ab1b29320d7 100644 --- a/lib/private/Files/Meta/MetaFileVersionNode.php +++ b/lib/private/Files/Meta/MetaFileVersionNode.php @@ -106,8 +106,7 @@ public function copy($targetPath) { if (!$target->isUpdateable()) { throw new ForbiddenException("Cannot write to $targetPath", false); } - $this->storage->restoreVersion($this->internalPath, $this->versionId); - return true; + return $this->storage->restoreVersion($this->internalPath, $this->versionId); } // for now we only allow restoring of a version @@ -119,7 +118,7 @@ public function getMTime() { } public function getMimetype() { - return isset($this->versionInfo['mime-type']) ? $this->versionInfo['mime-type'] : 'application/octet-stream'; + return isset($this->versionInfo['mimetype']) ? $this->versionInfo['mimetype'] : 'application/octet-stream'; } public function getEtag() { diff --git a/lib/private/Files/Meta/MetaRootNode.php b/lib/private/Files/Meta/MetaRootNode.php index dbf9a1f55f44..0162b309d7f1 100644 --- a/lib/private/Files/Meta/MetaRootNode.php +++ b/lib/private/Files/Meta/MetaRootNode.php @@ -118,7 +118,7 @@ public function isCreatable() { * @inheritdoc */ public function getPath() { - return $this->getName(); + return '/meta'; } /** diff --git a/lib/private/Files/Meta/MetaVersionCollection.php b/lib/private/Files/Meta/MetaVersionCollection.php index 6b042643db84..8bcdb4985f21 100644 --- a/lib/private/Files/Meta/MetaVersionCollection.php +++ b/lib/private/Files/Meta/MetaVersionCollection.php @@ -81,9 +81,13 @@ public function getDirectoryListing() { } /** @var IVersionedStorage | Storage $storage */ $versions = $storage->getVersions($internalPath); - return array_map(function($version) use ($storage, $internalPath) { + return array_values(array_map(function($version) use ($storage, $internalPath, $view, $path) { + if (!isset($version['mimetype'])) { + $version['mimetype'] = $view->getMimeType($path); + } + return new MetaFileVersionNode($this, $this->root, $version, $storage, $internalPath); - }, $versions); + }, $versions)); } /** @@ -107,6 +111,9 @@ public function get($path) { if ($version === null) { throw new NotFoundException(); } + if (!isset($version['mimetype'])) { + $version['mimetype'] = $view->getMimeType($path); + } return new MetaFileVersionNode($this, $this->root, $version, $storage, $internalPath); } @@ -120,4 +127,8 @@ public function getId() { public function getName() { return "v"; } + + public function getPath() { + return "/meta/{$this->fileId}/v"; + } } diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 735ff6fd9cb0..e7c487992e18 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -27,7 +27,9 @@ use Icewind\Streams\IteratorDirectory; use OC\Files\Cache\CacheEntry; +use OCP\Files\NotFoundException; use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\ObjectStore\IVersionedObjectStorage; class ObjectStoreStorage extends \OC\Files\Storage\Common { @@ -410,4 +412,64 @@ public function writeBack($tmpFile) { public function hasUpdated($path, $time) { return false; } + + public function saveVersion($internalPath) { + if ($this->objectStore instanceof IVersionedObjectStorage) { + $stat = $this->stat($internalPath); + // There are cases in the current implementation where saveVersion + // is called before the file was even written. + // There is nothing to be done in this case. + // We return true to not trigger the fallback implementation + if ($stat === false) { + return true; + } + return $this->objectStore->saveVersion($this->getURN($stat['fileid'])); + } + return parent::saveVersion($internalPath); + } + + public function getVersions($internalPath) { + if ($this->objectStore instanceof IVersionedObjectStorage) { + $stat = $this->stat($internalPath); + if ($stat === false) { + throw new NotFoundException(); + } + return $this->objectStore->getVersions($this->getURN($stat['fileid'])); + } + return parent::getVersions($internalPath); + } + + public function getVersion($internalPath, $versionId) { + if ($this->objectStore instanceof IVersionedObjectStorage) { + $stat = $this->stat($internalPath); + if ($stat === false) { + throw new NotFoundException(); + } + return $this->objectStore->getVersion($this->getURN($stat['fileid']), $versionId); + } + return parent::getVersion($internalPath, $versionId); + } + + public function getContentOfVersion($internalPath, $versionId) { + if ($this->objectStore instanceof IVersionedObjectStorage) { + $stat = $this->stat($internalPath); + if ($stat === false) { + throw new NotFoundException(); + } + return $this->objectStore->getContentOfVersion($this->getURN($stat['fileid']), $versionId); + } + return parent::getContentOfVersion($internalPath, $versionId); + } + + public function restoreVersion($internalPath, $versionId) { + if ($this->objectStore instanceof IVersionedObjectStorage) { + $stat = $this->stat($internalPath); + if ($stat === false) { + throw new NotFoundException(); + } + return $this->objectStore->restoreVersion($this->getURN($stat['fileid']), $versionId); + } + return parent::restoreVersion($internalPath, $versionId); + } + } diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index ebca69b0c969..161ecda60de1 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -695,7 +695,7 @@ public function getVersions($internalPath) { list ($uid, $filename) = $this->convertInternalPathToGlobalPath($internalPath); return array_map(function ($version) use ($internalPath) { - $version['mime-type'] = $this->getMimeType($internalPath); + $version['mimetype'] = $this->getMimeType($internalPath); return $version; }, array_values( \OCA\Files_Versions\Storage::getVersions($uid, $filename))); @@ -738,4 +738,9 @@ public function restoreVersion($internalPath, $versionId) { $v = $this->getVersion($internalPath, $versionId); return \OCA\Files_Versions\Storage::restoreVersion($v['owner'], $v['path'], $v['storage_location'], $versionId); } + + public function saveVersion($internalPath) { + // returning false here will trigger the fallback implementation + return false; + } } diff --git a/lib/public/Files/ObjectStore/IVersionedObjectStorage.php b/lib/public/Files/ObjectStore/IVersionedObjectStorage.php new file mode 100644 index 000000000000..9cd048c3470e --- /dev/null +++ b/lib/public/Files/ObjectStore/IVersionedObjectStorage.php @@ -0,0 +1,80 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + + +namespace OCP\Files\ObjectStore; + +/** + * Interface IVersionedObjectStorage - storage layer to access version of an + * object living in an object store + * + * @package OCP\Files\ObjectStore + * @since 10.0.5 + */ +interface IVersionedObjectStorage { + + /** + * List all versions for the given file + * + * @param string $urn the unified resource name used to identify the object + * @return array + * @since 10.0.5 + */ + public function getVersions($urn); + + /** + * Get one explicit version for the given file + * + * @param string $urn the unified resource name used to identify the object + * @param string $versionId + * @return array + * @since 10.0.5 + */ + public function getVersion($urn, $versionId); + + /** + * Get the content of a given version of a given file as stream resource + * + * @param string $urn the unified resource name used to identify the object + * @param string $versionId + * @return resource + * @since 10.0.5 + */ + public function getContentOfVersion($urn, $versionId); + + /** + * Restore the given version of a given file + * + * @param string $urn the unified resource name used to identify the object + * @param string $versionId + * @return boolean + * @since 10.0.5 + */ + public function restoreVersion($urn, $versionId); + + /** + * Tells the storage to explicitly create a version of a given file + * @return boolean + * @since 10.0.5 + */ + public function saveVersion($internalPath); + +} diff --git a/lib/public/Files/Storage/IVersionedStorage.php b/lib/public/Files/Storage/IVersionedStorage.php index 55e60b501b83..a08035a4afaa 100644 --- a/lib/public/Files/Storage/IVersionedStorage.php +++ b/lib/public/Files/Storage/IVersionedStorage.php @@ -26,7 +26,7 @@ * Interface IVersionedStorage - storage layer to access version of a file * * @package OCP\Files\Storage - * @since 10.1.0 + * @since 10.0.5 */ interface IVersionedStorage { @@ -35,7 +35,7 @@ interface IVersionedStorage { * * @param string $internalPath * @return array - * @since 10.1.0 + * @since 10.0.5 */ public function getVersions($internalPath); @@ -45,7 +45,7 @@ public function getVersions($internalPath); * @param string $internalPath * @param string $versionId * @return array - * @since 10.1.0 + * @since 10.0.5 */ public function getVersion($internalPath, $versionId); @@ -55,7 +55,7 @@ public function getVersion($internalPath, $versionId); * @param string $internalPath * @param string $versionId * @return resource - * @since 10.1.0 + * @since 10.0.5 */ public function getContentOfVersion($internalPath, $versionId); @@ -64,9 +64,18 @@ public function getContentOfVersion($internalPath, $versionId); * * @param string $internalPath * @param string $versionId - * @return void - * @since 10.1.0 + * @return boolean + * @since 10.0.5 */ public function restoreVersion($internalPath, $versionId); + /** + * Tells the storage to explicitly create a version of a given file + * + * @param string $internalPath + * @return bool + * @since 10.0.5 + */ + public function saveVersion($internalPath); + } diff --git a/tests/lib/Files/MetaFilesTest.php b/tests/lib/Files/MetaFilesTest.php index 651736b4e427..0a01b3d0138e 100644 --- a/tests/lib/Files/MetaFilesTest.php +++ b/tests/lib/Files/MetaFilesTest.php @@ -69,15 +69,21 @@ public function testMetaInNodeAPI() { $metaNodeOfFile = \OC::$server->getRootFolder()->get("meta"); $this->assertInstanceOf(MetaRootNode::class, $metaNodeOfFile); $this->assertEquals([], $metaNodeOfFile->getDirectoryListing()); + $this->assertEquals("/meta", $metaNodeOfFile->getPath()); + $this->assertEquals("meta", $metaNodeOfFile->getName()); $metaNodeOfFile = \OC::$server->getRootFolder()->get("meta/{$info->getId()}"); $this->assertInstanceOf(MetaFileIdNode::class, $metaNodeOfFile); + $this->assertEquals("/meta/{$info->getId()}", $metaNodeOfFile->getPath()); + $this->assertEquals("{$info->getId()}", $metaNodeOfFile->getName()); $children = $metaNodeOfFile->getDirectoryListing(); $this->assertEquals(1, count($children)); $this->assertInstanceOf(MetaVersionCollection::class, $children[0]); $metaNodeOfFile = \OC::$server->getRootFolder()->get("meta/{$info->getId()}/v"); $this->assertInstanceOf(MetaVersionCollection::class, $metaNodeOfFile); + $this->assertEquals("/meta/{$info->getId()}/v", $metaNodeOfFile->getPath()); + $this->assertEquals("v", $metaNodeOfFile->getName()); $children = $metaNodeOfFile->getDirectoryListing(); $this->assertEquals(0, count($children)); @@ -97,8 +103,8 @@ public function testMetaInNodeAPI() { $this->assertEquals($file, $metaNodeOfFile->getContentDispositionFileName()); $this->assertEquals('text/plain', $metaNodeOfFile->getMimetype()); $this->assertEquals($info->getMTime(), $metaNodeOfFile->getMTime()); - $this->assertTrue(is_string($metaNodeOfFile->getMTime())); - $this->assertTrue(strlen($metaNodeOfFile->getMTime()) > 0); + $this->assertTrue(is_string($metaNodeOfFile->getEtag())); + $this->assertTrue(strlen($metaNodeOfFile->getEtag()) > 0); /** @var MetaFileVersionNode $metaNodeOfFile */ $this->assertEquals('1234', $metaNodeOfFile->getContent()); diff --git a/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php b/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php new file mode 100644 index 000000000000..8408af5ffefc --- /dev/null +++ b/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php @@ -0,0 +1,148 @@ + + * + */ + +namespace Test\Files\ObjectStore; + +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\ObjectStore\Swift; + +/** + * Class ObjectStoreStorageTest + * + * @group DB + * + * @package Test\Files\Cache\ObjectStore + */ +abstract class ObjectStoreStorageTest extends \Test\Files\Storage\Storage { + + public function testStat() { + + $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; + $ctimeStart = time(); + $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); + $this->assertTrue($this->instance->isReadable('/lorem.txt')); + $ctimeEnd = time(); + $mTime = $this->instance->filemtime('/lorem.txt'); + + // check that ($ctimeStart - 5) <= $mTime <= ($ctimeEnd + 1) + $this->assertGreaterThanOrEqual(($ctimeStart - 5), $mTime); + $this->assertLessThanOrEqual(($ctimeEnd + 1), $mTime); + $this->assertEquals(filesize($textFile), $this->instance->filesize('/lorem.txt')); + + $stat = $this->instance->stat('/lorem.txt'); + //only size and mtime are required in the result + $this->assertEquals($stat['size'], $this->instance->filesize('/lorem.txt')); + $this->assertEquals($stat['mtime'], $mTime); + + if ($this->instance->touch('/lorem.txt', 100) !== false) { + $mTime = $this->instance->filemtime('/lorem.txt'); + $this->assertEquals($mTime, 100); + } + } + + public function testCheckUpdate() { + $this->markTestSkipped('Detecting external changes is not supported on object storages'); + } + + /** + * @dataProvider copyAndMoveProvider + */ + public function testMove($source, $target) { + $this->initSourceAndTarget($source); + $sourceId = $this->instance->getCache()->getId(ltrim('/',$source)); + $this->assertNotEquals(-1, $sourceId); + + $this->instance->rename($source, $target); + + $this->assertTrue($this->instance->file_exists($target), $target.' was not created'); + $this->assertFalse($this->instance->file_exists($source), $source.' still exists'); + $this->assertSameAsLorem($target); + + $targetId = $this->instance->getCache()->getId(ltrim('/',$target)); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } + + public function testRenameDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $this->instance->file_put_contents('source/test2.txt', 'qwerty'); + $this->instance->mkdir('source/subfolder'); + $this->instance->file_put_contents('source/subfolder/test.txt', 'bar'); + $sourceId = $this->instance->getCache()->getId('source'); + $this->assertNotEquals(-1, $sourceId); + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertFalse($this->instance->file_exists('source/test2.txt')); + $this->assertFalse($this->instance->file_exists('source/subfolder')); + $this->assertFalse($this->instance->file_exists('source/subfolder/test.txt')); + + $this->assertTrue($this->instance->file_exists('target')); + $this->assertTrue($this->instance->file_exists('target/test1.txt')); + $this->assertTrue($this->instance->file_exists('target/test2.txt')); + $this->assertTrue($this->instance->file_exists('target/subfolder')); + $this->assertTrue($this->instance->file_exists('target/subfolder/test.txt')); + + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $this->assertEquals('qwerty', $this->instance->file_get_contents('target/test2.txt')); + $this->assertEquals('bar', $this->instance->file_get_contents('target/subfolder/test.txt')); + $targetId = $this->instance->getCache()->getId('target'); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } + + public function testRenameOverWriteDirectory() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $sourceId = $this->instance->getCache()->getId('source'); + $this->assertNotEquals(-1, $sourceId); + + $this->instance->mkdir('target'); + $this->instance->file_put_contents('target/test1.txt', 'bar'); + $this->instance->file_put_contents('target/test2.txt', 'bar'); + + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertFalse($this->instance->file_exists('target/test2.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $targetId = $this->instance->getCache()->getId('target'); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } + + public function testRenameOverWriteDirectoryOverFile() { + $this->instance->mkdir('source'); + $this->instance->file_put_contents('source/test1.txt', 'foo'); + $sourceId = $this->instance->getCache()->getId('source'); + $this->assertNotEquals(-1, $sourceId); + + $this->instance->file_put_contents('target', 'bar'); + + $this->instance->rename('source', 'target'); + + $this->assertFalse($this->instance->file_exists('source')); + $this->assertFalse($this->instance->file_exists('source/test1.txt')); + $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); + $targetId = $this->instance->getCache()->getId('target'); + $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); + } +} diff --git a/tests/lib/Files/ObjectStore/ObjectStoreTest.php b/tests/lib/Files/ObjectStore/ObjectStoreTest.php new file mode 100644 index 000000000000..0db7ad94efd4 --- /dev/null +++ b/tests/lib/Files/ObjectStore/ObjectStoreTest.php @@ -0,0 +1,150 @@ + + * + * @copyright Copyright (c) 2017, ownCloud GmbH + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace Test\Files\ObjectStore; + +use OC\Files\Cache\CacheEntry; +use OC\Files\ObjectStore\NoopScanner; +use OC\Files\ObjectStore\ObjectStoreStorage; +use OCP\Files\NotFoundException; +use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\ObjectStore\IVersionedObjectStorage; +use Test\TestCase; + +/** + * Class ObjectStoreTest + * + * @group DB + * + * @package Test\Files\ObjectStore + */ +class ObjectStoreTest extends TestCase { + + /** @var IObjectStore | IVersionedObjectStorage | \PHPUnit_Framework_MockObject_MockObject */ + private $impl; + /** @var ObjectStoreStorage | \PHPUnit_Framework_MockObject_MockObject */ + private $objectStore; + + public function setUp() { + parent::setUp(); + $this->impl = $this->createMock([IObjectStore::class, IVersionedObjectStorage::class]); + $this->impl->expects($this->any()) + ->method('getStorageId') + ->willReturn('object-store-test'); + $this->objectStore = new ObjectStoreStorage([ + 'objectstore' => $this->impl + ]); + } + + public function testMkDir() { + $this->assertTrue($this->objectStore->mkdir('test')); + $cacheData = $this->objectStore->getCache()->get('test'); + $this->assertInstanceOf(CacheEntry::class, $cacheData); + $this->assertEquals('test', $cacheData->getPath()); + $this->assertEquals('httpd/unix-directory', $cacheData->getMimeType()); + } + + /** + * @depends testMkDir + */ + public function testRmdir() { + $this->assertTrue($this->objectStore->rmdir('test')); + $this->assertFalse($this->objectStore->file_exists('test')); + } + + public function testGetAndPutContents() { + $this->impl->expects($this->once())->method('writeObject'); + $this->assertEquals(5, $this->objectStore->file_put_contents('test.txt', 'lorem')); + + $stream = $this->createStreamFor('123456'); + + $this->impl->expects($this->once())->method('readObject')->willReturn($stream); + $this->assertEquals('123456', $this->objectStore->file_get_contents('test.txt')); + } + + public function testGetScanner() { + $this->assertInstanceOf(NoopScanner::class, $this->objectStore->getScanner()); + } + + public function testUnlink() { + $this->impl->expects($this->once())->method('writeObject'); + $this->assertTrue($this->objectStore->touch('to-unlink.txt')); + + $this->impl->expects($this->once())->method('deleteObject'); + $this->assertTrue($this->objectStore->unlink('to-unlink.txt')); + } + + /** + * @dataProvider providersPathAndMime + * @param string $expectedMime + * @param string $path + */ + public function testGetMime($expectedMime, $path) { + $this->assertTrue($this->objectStore->touch($path)); + $this->assertEquals($expectedMime, $this->objectStore->getMimeType($path)); + + } + + public function providersPathAndMime() { + return [ + ['image/jpeg', 'sample.jpeg'], + ]; + } + + public function testRename() { + $this->assertTrue($this->objectStore->touch('alice.txt')); + $this->assertTrue($this->objectStore->rename('alice.txt', 'bob.txt')); + $this->assertFalse($this->objectStore->file_exists('alice.txt')); + $this->assertTrue($this->objectStore->file_exists('bob.txt')); + } + + /** + * @dataProvider providesMethods + * @expectedException \OCP\Files\NotFoundException + */ + public function testGetVersionsOfUnknownFile($method, $ignore = false) { + if ($ignore) { + throw new NotFoundException(); + } + $this->impl->expects($this->never())->method($method)->willReturn([]); + $this->assertEquals([], $this->objectStore->$method('unknown-file.txt', '1')); + } + + /** + * @dataProvider providesMethods + */ + public function testGetVersions($method) { + $path = 'file-with-versions.txt'; + $this->assertTrue($this->objectStore->touch($path)); + $this->impl->expects($this->once())->method($method)->willReturn([]); + $this->assertEquals([], $this->objectStore->$method($path, '1')); + } + + public function providesMethods() { + return [ + 'saveVersion' => ['saveVersion', true], + 'getVersions' => ['getVersions'], + 'getVersion' => ['getVersion'], + 'getContentOfVersion' => ['getContentOfVersion'], + 'restoreVersion' => ['restoreVersion'], + ]; + } +} diff --git a/tests/lib/Files/ObjectStore/SwiftTest.php b/tests/lib/Files/ObjectStore/SwiftTest.php index cb89ed5ffac4..a83bcd8e9a7e 100644 --- a/tests/lib/Files/ObjectStore/SwiftTest.php +++ b/tests/lib/Files/ObjectStore/SwiftTest.php @@ -30,7 +30,7 @@ * * @package Test\Files\Cache\ObjectStore */ -class SwiftTest extends \Test\Files\Storage\Storage { +class SwiftTest extends ObjectStoreStorageTest { /** * @var Swift @@ -82,117 +82,4 @@ protected function tearDown() { } parent::tearDown(); } - - public function testStat() { - - $textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt'; - $ctimeStart = time(); - $this->instance->file_put_contents('/lorem.txt', file_get_contents($textFile)); - $this->assertTrue($this->instance->isReadable('/lorem.txt')); - $ctimeEnd = time(); - $mTime = $this->instance->filemtime('/lorem.txt'); - - // check that ($ctimeStart - 5) <= $mTime <= ($ctimeEnd + 1) - $this->assertGreaterThanOrEqual(($ctimeStart - 5), $mTime); - $this->assertLessThanOrEqual(($ctimeEnd + 1), $mTime); - $this->assertEquals(filesize($textFile), $this->instance->filesize('/lorem.txt')); - - $stat = $this->instance->stat('/lorem.txt'); - //only size and mtime are required in the result - $this->assertEquals($stat['size'], $this->instance->filesize('/lorem.txt')); - $this->assertEquals($stat['mtime'], $mTime); - - if ($this->instance->touch('/lorem.txt', 100) !== false) { - $mTime = $this->instance->filemtime('/lorem.txt'); - $this->assertEquals($mTime, 100); - } - } - - public function testCheckUpdate() { - $this->markTestSkipped('Detecting external changes is not supported on object storages'); - } - - /** - * @dataProvider copyAndMoveProvider - */ - public function testMove($source, $target) { - $this->initSourceAndTarget($source); - $sourceId = $this->instance->getCache()->getId(ltrim('/',$source)); - $this->assertNotEquals(-1, $sourceId); - - $this->instance->rename($source, $target); - - $this->assertTrue($this->instance->file_exists($target), $target.' was not created'); - $this->assertFalse($this->instance->file_exists($source), $source.' still exists'); - $this->assertSameAsLorem($target); - - $targetId = $this->instance->getCache()->getId(ltrim('/',$target)); - $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); - } - - public function testRenameDirectory() { - $this->instance->mkdir('source'); - $this->instance->file_put_contents('source/test1.txt', 'foo'); - $this->instance->file_put_contents('source/test2.txt', 'qwerty'); - $this->instance->mkdir('source/subfolder'); - $this->instance->file_put_contents('source/subfolder/test.txt', 'bar'); - $sourceId = $this->instance->getCache()->getId('source'); - $this->assertNotEquals(-1, $sourceId); - $this->instance->rename('source', 'target'); - - $this->assertFalse($this->instance->file_exists('source')); - $this->assertFalse($this->instance->file_exists('source/test1.txt')); - $this->assertFalse($this->instance->file_exists('source/test2.txt')); - $this->assertFalse($this->instance->file_exists('source/subfolder')); - $this->assertFalse($this->instance->file_exists('source/subfolder/test.txt')); - - $this->assertTrue($this->instance->file_exists('target')); - $this->assertTrue($this->instance->file_exists('target/test1.txt')); - $this->assertTrue($this->instance->file_exists('target/test2.txt')); - $this->assertTrue($this->instance->file_exists('target/subfolder')); - $this->assertTrue($this->instance->file_exists('target/subfolder/test.txt')); - - $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); - $this->assertEquals('qwerty', $this->instance->file_get_contents('target/test2.txt')); - $this->assertEquals('bar', $this->instance->file_get_contents('target/subfolder/test.txt')); - $targetId = $this->instance->getCache()->getId('target'); - $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); - } - - public function testRenameOverWriteDirectory() { - $this->instance->mkdir('source'); - $this->instance->file_put_contents('source/test1.txt', 'foo'); - $sourceId = $this->instance->getCache()->getId('source'); - $this->assertNotEquals(-1, $sourceId); - - $this->instance->mkdir('target'); - $this->instance->file_put_contents('target/test1.txt', 'bar'); - $this->instance->file_put_contents('target/test2.txt', 'bar'); - - $this->instance->rename('source', 'target'); - - $this->assertFalse($this->instance->file_exists('source')); - $this->assertFalse($this->instance->file_exists('source/test1.txt')); - $this->assertFalse($this->instance->file_exists('target/test2.txt')); - $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); - $targetId = $this->instance->getCache()->getId('target'); - $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); - } - - public function testRenameOverWriteDirectoryOverFile() { - $this->instance->mkdir('source'); - $this->instance->file_put_contents('source/test1.txt', 'foo'); - $sourceId = $this->instance->getCache()->getId('source'); - $this->assertNotEquals(-1, $sourceId); - - $this->instance->file_put_contents('target', 'bar'); - - $this->instance->rename('source', 'target'); - - $this->assertFalse($this->instance->file_exists('source')); - $this->assertFalse($this->instance->file_exists('source/test1.txt')); - $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); - $targetId = $this->instance->getCache()->getId('target'); - $this->assertSame($sourceId, $targetId, 'fileid must be stable on move or shares will break'); - } } diff --git a/tests/lib/TestCase.php b/tests/lib/TestCase.php index f44b9877973b..23066994efd2 100644 --- a/tests/lib/TestCase.php +++ b/tests/lib/TestCase.php @@ -521,4 +521,15 @@ public function getCurrentUser() { return $processUser['name']; } + /** + * @param string $string + * @return bool|resource + */ + protected function createStreamFor($string) { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $string); + rewind($stream); + return $stream; + } + }