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

Feature/version metadata #39126

Merged
merged 5 commits into from
Nov 18, 2021
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
24 changes: 24 additions & 0 deletions apps/dav/lib/Meta/MetaFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OCA\DAV\Files\ICopySource;
use OCA\DAV\Files\IProvidesAdditionalHeaders;
use OCA\DAV\Files\IFileNode;
use OCP\Files\IProvidesVersionAuthor;
use OCP\Files\Node;
use Sabre\DAV\File;

Expand Down Expand Up @@ -126,4 +127,27 @@ public function getContentDispositionFileName() {
public function getNode() {
return $this->file;
}

/**
* @return string
*/
public function getVersionAuthor() : string {
if ($this->file instanceof IProvidesVersionAuthor) {
return $this->file->getEditedBy();
}
return '';
}

/**
* @return string
*/
public function getVersionAuthorName() : string {
if ($this->file instanceof IProvidesVersionAuthor) {
$uid = $this->file->getEditedBy();
$manager = \OC::$server->getUserManager();
$user = $manager->get($uid);
return $user !== null ? $user->getDisplayName() : '';
}
return '';
}
}
9 changes: 9 additions & 0 deletions apps/dav/lib/Meta/MetaPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class MetaPlugin extends ServerPlugin {
public const NS_OWNCLOUD = 'http://owncloud.org/ns';
public const PATH_FOR_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}meta-path-for-user';

public const VERSION_EDITED_BY_PROPERTYNAME = '{http://owncloud.org/ns}meta-version-edited-by';
public const VERSION_EDITED_BY_PROPERTYNAME_NAME = '{http://owncloud.org/ns}meta-version-edited-by-name';
/**
* Reference to main server object
*
Expand Down Expand Up @@ -97,6 +99,13 @@ public function handleGetProperties(PropFind $propFind, INode $node) {
$file = \current($files);
return $baseFolder->getRelativePath($file->getPath());
});
} elseif ($node instanceof MetaFile) {
$propFind->handle(self::VERSION_EDITED_BY_PROPERTYNAME, function () use ($node) {
return $node->getVersionAuthor();
});
$propFind->handle(self::VERSION_EDITED_BY_PROPERTYNAME_NAME, function () use ($node) {
return $node->getVersionAuthorName();
});
}
}
}
16 changes: 16 additions & 0 deletions apps/files_trashbin/lib/Trashbin.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,19 +400,35 @@ private static function retainVersions($filename, $owner, $ownerPath, $timestamp
$rootView = new View('/');

if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
$metadataFileExists = $rootView->file_exists($owner . '/files_versions/' . $ownerPath . '.json');
Copy link
Member

Choose a reason for hiding this comment

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

With the IVersionedStorage support in mind this needs to be implemented differently - but this is out of scope of this PR


if ($owner !== $user || $forceCopy) {
self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . \basename($ownerPath) . '.d' . $timestamp, $rootView);
if ($metadataFileExists) {
self::copy_recursive($owner . '/files_versions/' . $ownerPath . '.json', $owner . '/files_trashbin/versions/' . \basename($ownerPath) . '.json' . '.d' . $timestamp, $rootView);
}
}
if (!$forceCopy) {
self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
if ($metadataFileExists) {
self::move($rootView, $owner . '/files_versions/' . $ownerPath . '.json', $user . '/files_trashbin/versions/' . $filename . '.json' . '.d' . $timestamp);
}
}
} elseif ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {
foreach ($versions as $v) {
$metaVersionExists = $rootView->file_exists($owner . '/files_versions' . $v['path'] . '.v' . $v['version'] . '.json');

if ($owner !== $user || $forceCopy) {
self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
if ($metaVersionExists) {
self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'] . '.json', $owner . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.json' . '.d' . $timestamp);
}
}
if (!$forceCopy) {
self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
if ($metaVersionExists) {
self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'] . '.json', $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.json' . '.d' . $timestamp);
}
}
}
}
Expand Down
26 changes: 23 additions & 3 deletions apps/files_versions/js/versioncollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,29 @@
/* global moment */

(function() {

_.extend(OC.Files.Client, {
PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id',
PROPERTY_VERSION_EDITED_BY: '{' + OC.Files.Client.NS_OWNCLOUD + '}meta-version-edited-by',
PROPERTY_VERSION_EDITED_BY_NAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}meta-version-edited-by-name',
});

/**
* @memberof OCA.Versions
*/
var VersionCollection = OC.Backbone.Collection.extend({
sync: OC.Backbone.davSync,

davProperties: {
'meta-version-edited-by': OC.Files.Client.PROPERTY_VERSION_EDITED_BY,
'meta-version-edited-by-name': OC.Files.Client.PROPERTY_VERSION_EDITED_BY_NAME,
'id': OC.Files.Client.PROPERTY_FILEID,
'getlastmodified': OC.Files.Client.PROPERTY_GETLASTMODIFIED,
'getcontentlength': OC.Files.Client.PROPERTY_GETCONTENTLENGTH,
'resourcetype': OC.Files.Client.PROPERTY_RESOURCETYPE,
'getcontenttype': OC.Files.Client.PROPERTY_GETCONTENTTYPE,
},

model: OCA.Versions.VersionModel,

/**
Expand Down Expand Up @@ -46,9 +63,12 @@
id: revision,
name: revision,
fullPath: fullPath,
timestamp: moment(new Date(version['{DAV:}getlastmodified'])).format('X'),
size: version['{DAV:}getcontentlength'],
mimetype: version['{DAV:}getcontenttype'],
timestamp: moment(new Date(version.getlastmodified)).format('X'),
relativeTimestamp: moment(new Date(version.getlastmodified)).fromNow(),
size: version.getcontentlength,
mimetype: version.getcontenttype,
editedBy: version['meta-version-edited-by'],
editedByName: version['meta-version-edited-by-name'],
fileId: fileId
};
});
Expand Down
5 changes: 4 additions & 1 deletion apps/files_versions/js/versionstabview.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
'{{#hasDetails}}' +
'<div class="version-details">' +
'<span class="size has-tooltip" title="{{altSize}}">{{humanReadableSize}}</span>' +
'<span title="{{editedBy}}">{{editedByName}}</span>' +
'</div>' +
'{{/hasDetails}}' +
'</div>' +
Expand Down Expand Up @@ -213,7 +214,9 @@
revertIconUrl: OC.imagePath('core', 'actions/history'),
previewUrl: getPreviewUrl(version),
revertLabel: t('files_versions', 'Restore'),
canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0
canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0,
editedBy: version.has('editedBy'),
editedByName: version.has('editedByName')
}, version.attributes);
},

Expand Down
1 change: 1 addition & 0 deletions apps/files_versions/lib/Hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Hooks {
public static function connectHooks() {
// Listen to write signals
\OCP\Util::connectHook('OC_Filesystem', 'write', 'OCA\Files_Versions\Hooks', 'write_hook');

// Listen to delete and rename signals
\OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Files_Versions\Hooks', 'remove_hook');
\OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Versions\Hooks', 'pre_remove_hook');
Expand Down
64 changes: 59 additions & 5 deletions apps/files_versions/lib/Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

use OC\Files\Filesystem;
use OC\Files\View;
use OC\Share\Constants;
use OCA\DAV\Meta\MetaPlugin;
use OCA\Files_Versions\AppInfo\Application;
use OCA\Files_Versions\Command\Expire;
use OCP\Files\NotFoundException;
Expand Down Expand Up @@ -192,13 +194,26 @@ public static function store($filename) {
// store a new version of a file
$mtime = $users_view->filemtime('files/' . $filename);
$sourceFileInfo = $users_view->getFileInfo("files/$filename");
if ($users_view->copy("files/$filename", "files_versions/$filename.v$mtime")) {

$versionFileName = "files_versions/$filename.v$mtime";
if ($users_view->copy("files/$filename", $versionFileName)) {
// call getFileInfo to enforce a file cache entry for the new version
$users_view->getFileInfo("files_versions/$filename.v$mtime");
$users_view->getFileInfo($versionFileName);
// update checksum of the version
$users_view->putFileInfo("files_versions/$filename.v$mtime", [
$users_view->putFileInfo($versionFileName, [
'checksum' => $sourceFileInfo->getChecksum(),
]);

$config = \OC::$server->getConfig();
if ($config->getSystemValue('file_storage.save_version_author', false) === true) {
$user = \OC::$server->getUserSession()->getUser();
if ($user !== null && !$users_view->file_exists($versionFileName . '.json')) {
$metaDataKey = MetaPlugin::VERSION_EDITED_BY_PROPERTYNAME;
$metadata = [$metaDataKey => $user->getUID()];
$metadataJsonObject = \json_encode($metadata);
$users_view->file_put_contents($versionFileName . '.json', $metadataJsonObject);
}
}
}
}
}
Expand Down Expand Up @@ -254,6 +269,9 @@ public static function delete($path) {
];
\OC_Hook::emit('\OCP\Versions', 'preDelete', $hookData);
self::deleteVersion($view, $filename . '.v' . $v['version']);
if ($view->file_exists($path . ".json")) {
$view->unlink($path . ".json");
}
\OC_Hook::emit('\OCP\Versions', 'delete', $hookData);
}
}
Expand Down Expand Up @@ -310,6 +328,14 @@ public static function renameOrCopy($sourcePath, $targetPath, $operation) {
'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
);
// move each version json file that holds the name of the user that've made an edit
$sourceMetaDataFile = '/' . $sourceOwner . '/files_versions/' . $sourcePath . '.v' . $v['version'] . '.json';
if ($rootView->file_exists($sourceMetaDataFile)) {
$rootView->$operation(
$sourceMetaDataFile,
'/' . $targetOwner . '/files_versions/' . $targetPath . '.v' . $v['version'] . '.json'
);
}
}
}

Expand All @@ -328,13 +354,21 @@ public static function restoreVersion($uid, $filename, $fileToRestore, $revision
return false;
}

$metaDataEnabled = \OC::$server->getConfig()->getSystemValue('file_storage.save_version_author', false);
$versionCreated = false;

//first create a new version
//first create a new version and metadata if enabled
$version = 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename);
if (!$users_view->file_exists($version)) {
$users_view->copy('files'.$filename, 'files_versions'.$filename.'.v'.$users_view->filemtime('files'.$filename));
$users_view->copy('files'.$filename, $version);
$versionCreated = true;

if ($metaDataEnabled === true) {
$metaTargetPath = $version . '.json';
$metaDataKey = MetaPlugin::VERSION_EDITED_BY_PROPERTYNAME;
$metadataJsonObject = \json_encode([$metaDataKey => $uid]);
$users_view->file_put_contents($metaTargetPath, $metadataJsonObject);
}
}

// Restore encrypted version of the old file for the newly restored file
Expand All @@ -356,11 +390,20 @@ public static function restoreVersion($uid, $filename, $fileToRestore, $revision
if (self::copyFileContents($users_view, $fileToRestore, 'files' . $filename)) {
$users_view->touch("/files$filename", $revision);
Storage::scheduleExpire($uid, $filename);

if ($metaDataEnabled && $users_view->file_exists($fileToRestore . '.json')) {
$users_view->unlink($fileToRestore . '.json');
list($storage, $internalPath) = $users_view->resolvePath($fileToRestore . '.json');
$cache = $storage->getCache($internalPath);
$cache->remove($internalPath);
}

\OC_Hook::emit('\OCP\Versions', 'rollback', [
'path' => $filename,
'user' => $uid,
'revision' => $revision,
]);

return true;
} elseif ($versionCreated) {
self::deleteVersion($users_view, $version);
Expand Down Expand Up @@ -456,6 +499,16 @@ public static function getVersions($uid, $filename) {
$versions[$key]['etag'] = $view->getETag($dir . '/' . $entryName);
$versions[$key]['storage_location'] = "$dir/$entryName";
$versions[$key]['owner'] = $uid;

$jsonMetadataFile = $dir . '/' . $entryName . '.json';
if ($view->file_exists($jsonMetadataFile)) {
$metaDataFileContents = $view->file_get_contents($jsonMetadataFile);
if ($decoded = \json_decode($metaDataFileContents, true)) {
if (isset($decoded[MetaPlugin::VERSION_EDITED_BY_PROPERTYNAME])) {
$versions[$key]['edited_by'] = $decoded[MetaPlugin::VERSION_EDITED_BY_PROPERTYNAME];
}
}
}
}
}
}
Expand Down Expand Up @@ -561,6 +614,7 @@ protected static function getExpireList($time, $versions, $quotaExceeded = false

/**
* get list of files we want to expire
*
* @param array $versions list of versions
* @param integer $time
* @return array containing the list of to deleted versions and the size of them
Expand Down
Loading