Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add versioning to objectstore #29607

Merged
merged 2 commits into from
Nov 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/files_trashbin/tests/StorageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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([]);
Expand Down Expand Up @@ -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([]);
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't it be IVersionedStorage here or whatever we called the interface ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no Common implements IVersionedStorage - this is a temporal hack until versioning for regular storages is moved into Common

$this->markTestSkipped();
}
}
}
8 changes: 3 additions & 5 deletions apps/files_versions/js/versioncollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, so was this broken in the other PR ? I missed it when testing the versions panel

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no - for the regular versioning the revision is the timestamp and it worked.
but for s3 the version id is no longer the timestamp -> 💥

size: version['{DAV:}getcontentlength'],
fileId: fileId
};
});
return results;
}
});

Expand Down
2 changes: 1 addition & 1 deletion apps/files_versions/js/versionmodel.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
getDownloadUrl: function() {
return OC.linkToRemoteBase('dav') + '/meta/' +
encodeURIComponent(this.get('fileId')) + '/v/' +
encodeURIComponent(this.get('versionId'));
encodeURIComponent(this.get('id'));
}
});

Expand Down
3 changes: 2 additions & 1 deletion apps/files_versions/js/versionstabview.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

(function() {
var TEMPLATE_ITEM =
'<li data-revision="{{timestamp}}">' +
'<li data-revision="{{versionId}}">' +
'<div>' +
'<div class="preview-container">' +
'<img class="preview" src="{{previewUrl}}"/>' +
Expand Down Expand Up @@ -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),
Expand Down
10 changes: 10 additions & 0 deletions apps/files_versions/lib/Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down
11 changes: 11 additions & 0 deletions apps/files_versions/tests/VersioningTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions apps/files_versions/tests/js/versionmodelSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion core/js/files/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 2 additions & 3 deletions lib/private/Files/Meta/MetaFileVersionNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Files/Meta/MetaRootNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public function isCreatable() {
* @inheritdoc
*/
public function getPath() {
return $this->getName();
return '/meta';
}

/**
Expand Down
15 changes: 13 additions & 2 deletions lib/private/Files/Meta/MetaVersionCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

/**
Expand All @@ -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);
}

Expand All @@ -120,4 +127,8 @@ public function getId() {
public function getName() {
return "v";
}

public function getPath() {
return "/meta/{$this->fileId}/v";
}
}
62 changes: 62 additions & 0 deletions lib/private/Files/ObjectStore/ObjectStoreStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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);
}

}
7 changes: 6 additions & 1 deletion lib/private/Files/Storage/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down Expand Up @@ -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;
}
}
Loading