feat: show the id of last author in versions metadatatags/v29.0.0beta2
@@ -21,6 +21,7 @@ return array( | |||
'OCA\\Files_Versions\\Listener\\FileEventsListener' => $baseDir . '/../lib/Listener/FileEventsListener.php', | |||
'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php', | |||
'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', | |||
'OCA\\Files_Versions\\Listener\\MetadataFileEvents' => $baseDir . '/../lib/Listener/MetadataFileEvents.php', | |||
'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => $baseDir . '/../lib/Migration/Version1020Date20221114144058.php', | |||
'OCA\\Files_Versions\\Sabre\\Plugin' => $baseDir . '/../lib/Sabre/Plugin.php', | |||
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => $baseDir . '/../lib/Sabre/RestoreFolder.php', | |||
@@ -32,6 +33,8 @@ return array( | |||
'OCA\\Files_Versions\\Storage' => $baseDir . '/../lib/Storage.php', | |||
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => $baseDir . '/../lib/Versions/BackendNotFoundException.php', | |||
'OCA\\Files_Versions\\Versions\\IDeletableVersionBackend' => $baseDir . '/../lib/Versions/IDeletableVersionBackend.php', | |||
'OCA\\Files_Versions\\Versions\\IMetadataVersion' => $baseDir . '/../lib/Versions/IMetadataVersion.php', | |||
'OCA\\Files_Versions\\Versions\\IMetadataVersionBackend' => $baseDir . '/../lib/Versions/IMetadataVersionBackend.php', | |||
'OCA\\Files_Versions\\Versions\\INameableVersion' => $baseDir . '/../lib/Versions/INameableVersion.php', | |||
'OCA\\Files_Versions\\Versions\\INameableVersionBackend' => $baseDir . '/../lib/Versions/INameableVersionBackend.php', | |||
'OCA\\Files_Versions\\Versions\\INeedSyncVersionBackend' => $baseDir . '/../lib/Versions/INeedSyncVersionBackend.php', |
@@ -36,6 +36,7 @@ class ComposerStaticInitFiles_Versions | |||
'OCA\\Files_Versions\\Listener\\FileEventsListener' => __DIR__ . '/..' . '/../lib/Listener/FileEventsListener.php', | |||
'OCA\\Files_Versions\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php', | |||
'OCA\\Files_Versions\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', | |||
'OCA\\Files_Versions\\Listener\\MetadataFileEvents' => __DIR__ . '/..' . '/../lib/Listener/MetadataFileEvents.php', | |||
'OCA\\Files_Versions\\Migration\\Version1020Date20221114144058' => __DIR__ . '/..' . '/../lib/Migration/Version1020Date20221114144058.php', | |||
'OCA\\Files_Versions\\Sabre\\Plugin' => __DIR__ . '/..' . '/../lib/Sabre/Plugin.php', | |||
'OCA\\Files_Versions\\Sabre\\RestoreFolder' => __DIR__ . '/..' . '/../lib/Sabre/RestoreFolder.php', | |||
@@ -47,6 +48,8 @@ class ComposerStaticInitFiles_Versions | |||
'OCA\\Files_Versions\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php', | |||
'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Versions/BackendNotFoundException.php', | |||
'OCA\\Files_Versions\\Versions\\IDeletableVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IDeletableVersionBackend.php', | |||
'OCA\\Files_Versions\\Versions\\IMetadataVersion' => __DIR__ . '/..' . '/../lib/Versions/IMetadataVersion.php', | |||
'OCA\\Files_Versions\\Versions\\IMetadataVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IMetadataVersionBackend.php', | |||
'OCA\\Files_Versions\\Versions\\INameableVersion' => __DIR__ . '/..' . '/../lib/Versions/INameableVersion.php', | |||
'OCA\\Files_Versions\\Versions\\INameableVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/INameableVersionBackend.php', | |||
'OCA\\Files_Versions\\Versions\\INeedSyncVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/INeedSyncVersionBackend.php', |
@@ -3,7 +3,7 @@ | |||
'name' => '__root__', | |||
'pretty_version' => 'dev-master', | |||
'version' => 'dev-master', | |||
'reference' => 'a820e3d036741ad1194361eca11bc1cbcdda0a47', | |||
'reference' => '84930a207a8d5f0ef32320796fe188892b63fa19', | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../', | |||
'aliases' => array(), | |||
@@ -13,7 +13,7 @@ | |||
'__root__' => array( | |||
'pretty_version' => 'dev-master', | |||
'version' => 'dev-master', | |||
'reference' => 'a820e3d036741ad1194361eca11bc1cbcdda0a47', | |||
'reference' => '84930a207a8d5f0ef32320796fe188892b63fa19', | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../', | |||
'aliases' => array(), |
@@ -36,6 +36,7 @@ use OCA\Files_Versions\Capabilities; | |||
use OCA\Files_Versions\Listener\FileEventsListener; | |||
use OCA\Files_Versions\Listener\LoadAdditionalListener; | |||
use OCA\Files_Versions\Listener\LoadSidebarListener; | |||
use OCA\Files_Versions\Listener\MetadataFileEvents; | |||
use OCA\Files_Versions\Versions\IVersionManager; | |||
use OCA\Files_Versions\Versions\VersionManager; | |||
use OCP\Accounts\IAccountManager; | |||
@@ -119,6 +120,8 @@ class Application extends App implements IBootstrap { | |||
$context->registerEventListener(NodeCopiedEvent::class, FileEventsListener::class); | |||
$context->registerEventListener(BeforeNodeRenamedEvent::class, FileEventsListener::class); | |||
$context->registerEventListener(BeforeNodeCopiedEvent::class, FileEventsListener::class); | |||
$context->registerEventListener(NodeWrittenEvent::class, MetadataFileEvents::class); | |||
} | |||
public function boot(IBootContext $context): void { |
@@ -78,4 +78,23 @@ class VersionEntity extends Entity implements JsonSerializable { | |||
$this->metadata['label'] = $label; | |||
$this->markFieldUpdated('metadata'); | |||
} | |||
/** | |||
* @abstract given a key, return the value associated with the key in the metadata column | |||
* if nothing is found, we return an empty string | |||
* @param string $key key associated with the value | |||
*/ | |||
public function getMetadataValue(string $key): ?string { | |||
return $this->metadata[$key] ?? null; | |||
} | |||
/** | |||
* @abstract sets a key value pair in the metadata column | |||
* @param string $key key associated with the value | |||
* @param string $value value associated with the key | |||
*/ | |||
public function setMetadataValue(string $key, string $value): void { | |||
$this->metadata[$key] = $value; | |||
$this->markFieldUpdated('metadata'); | |||
} | |||
} |
@@ -85,7 +85,7 @@ class VersionsMapper extends QBMapper { | |||
->executeStatement(); | |||
} | |||
public function deleteAllVersionsForUser(int $storageId, string $path = null): void { | |||
public function deleteAllVersionsForUser(int $storageId, ?string $path = null): void { | |||
$fileIdsGenerator = $this->getFileIdsGenerator($storageId, $path); | |||
$versionEntitiesDeleteQuery = $this->db->getQueryBuilder(); |
@@ -0,0 +1,68 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @author Eduardo Morales emoral435@gmail.com> | |||
* | |||
* @license GNU AGPL-3.0-or-later | |||
* | |||
* This code is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License, version 3, | |||
* as published by the Free Software Foundation. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License, version 3, | |||
* along with this program. If not, see <http://www.gnu.org/licenses/> | |||
* | |||
*/ | |||
namespace OCA\Files_Versions\Listener; | |||
use OC\Files\Node\Folder; | |||
use OCA\Files_Versions\Versions\IMetadataVersionBackend; | |||
use OCA\Files_Versions\Versions\IVersionManager; | |||
use OCP\EventDispatcher\Event; | |||
use OCP\EventDispatcher\IEventListener; | |||
use OCP\Files\Events\Node\NodeWrittenEvent; | |||
use OCP\Files\Node; | |||
use OCP\IUserSession; | |||
/** @template-implements IEventListener<NodeWrittenEvent> */ | |||
class MetadataFileEvents implements IEventListener { | |||
public function __construct( | |||
private IVersionManager $versionManager, | |||
private IUserSession $userSession, | |||
) { | |||
} | |||
/** | |||
* @abstract handles events from a nodes version being changed | |||
* @param Event $event the event that triggered this listener to activate | |||
*/ | |||
public function handle(Event $event): void { | |||
if ($event instanceof NodeWrittenEvent) { | |||
$this->post_write_hook($event->getNode()); | |||
} | |||
} | |||
/** | |||
* @abstract handles the NodeWrittenEvent, and sets the metadata for the associated node | |||
* @param Node $node the node that is currently being written | |||
*/ | |||
public function post_write_hook(Node $node): void { | |||
$user = $this->userSession->getUser(); | |||
// Do not handle folders or users that we cannot get metadata from | |||
if ($node instanceof Folder || is_null($user)) { | |||
return; | |||
} | |||
// check if our version manager supports setting the metadata | |||
if ($this->versionManager instanceof IMetadataVersionBackend) { | |||
$author = $user->getUID(); | |||
$this->versionManager->setMetadataValue($node, 'author', $author); | |||
} | |||
} | |||
} |
@@ -44,6 +44,8 @@ class Plugin extends ServerPlugin { | |||
public const VERSION_LABEL = '{http://nextcloud.org/ns}version-label'; | |||
public const VERSION_AUTHOR = '{http://nextcloud.org/ns}version-author'; // dav property for author | |||
public function __construct( | |||
private IRequest $request, | |||
private IPreview $previewManager, | |||
@@ -93,6 +95,7 @@ class Plugin extends ServerPlugin { | |||
public function propFind(PropFind $propFind, INode $node): void { | |||
if ($node instanceof VersionFile) { | |||
$propFind->handle(self::VERSION_LABEL, fn () => $node->getLabel()); | |||
$propFind->handle(self::VERSION_AUTHOR, fn () => $node->getMetadataValue("author")); | |||
$propFind->handle(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, fn () => $this->previewManager->isMimeSupported($node->getContentType())); | |||
} | |||
} |
@@ -27,6 +27,7 @@ declare(strict_types=1); | |||
namespace OCA\Files_Versions\Sabre; | |||
use OCA\Files_Versions\Versions\IDeletableVersionBackend; | |||
use OCA\Files_Versions\Versions\IMetadataVersion; | |||
use OCA\Files_Versions\Versions\INameableVersion; | |||
use OCA\Files_Versions\Versions\INameableVersionBackend; | |||
use OCA\Files_Versions\Versions\IVersion; | |||
@@ -109,6 +110,13 @@ class VersionFile implements IFile { | |||
} | |||
} | |||
public function getMetadataValue(string $key): ?string { | |||
if ($this->version instanceof IMetadataVersion) { | |||
return $this->version->getMetadataValue($key); | |||
} | |||
return null; | |||
} | |||
public function getLastModified(): int { | |||
return $this->version->getTimestamp(); | |||
} |
@@ -0,0 +1,38 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2024 Eduardo Morales <emoral435@gmail.com> | |||
* | |||
* @license GNU AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* 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 | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCA\Files_Versions\Versions; | |||
/** | |||
* This interface allows for just direct accessing of the metadata column JSON | |||
* @since 29.0.0 | |||
*/ | |||
interface IMetadataVersion { | |||
/** | |||
* retrieves the metadata value from our $key param | |||
* | |||
* @param string $key the key for the json value of the metadata column | |||
* @since 29.0.0 | |||
*/ | |||
public function getMetadataValue(string $key): ?string; | |||
} |
@@ -0,0 +1,52 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2024 Eduardo Morales <emoral435@gmail.com> | |||
* | |||
* @license GNU AGPL-3.0-or-later | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* 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 | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OCA\Files_Versions\Versions; | |||
use OCP\Files\Node; | |||
/** | |||
* This interface edits the metadata column of a node. | |||
* Each column of the metadata has a key => value mapping. | |||
* @since 29.0.0 | |||
*/ | |||
interface IMetadataVersionBackend { | |||
/** | |||
* Sets a key value pair in the metadata column corresponding to the node's version. | |||
* | |||
* @param Node $node the node that triggered the Metadata event listener, aka, the file version | |||
* @param string $key the key for the json value of the metadata column | |||
* @param string $value the value that corresponds to the key in the metadata column | |||
* @since 29.0.0 | |||
*/ | |||
public function setMetadataValue(Node $node, string $key, string $value): void; | |||
/** | |||
* Retrieves a corresponding value from the metadata column using the key. | |||
* | |||
* @param Node $node the node that triggered the Metadata event listener, aka, the file version | |||
* @param string $key the key for the json value of the metadata column | |||
* @since 29.0.0 | |||
*/ | |||
public function getMetadataValue(Node $node, string $key): ?string; | |||
} |
@@ -24,12 +24,13 @@ declare(strict_types=1); | |||
namespace OCA\Files_Versions\Versions; | |||
/** | |||
* @deprecated 29.0.0 | |||
* @since 26.0.0 | |||
*/ | |||
interface INameableVersion { | |||
/** | |||
* Get the user created label | |||
* | |||
* @deprecated 29.0.0 | |||
* @return string | |||
* @since 26.0.0 | |||
*/ |
@@ -24,12 +24,13 @@ declare(strict_types=1); | |||
namespace OCA\Files_Versions\Versions; | |||
/** | |||
* @deprecated 29.0.0 | |||
* @since 26.0.0 | |||
*/ | |||
interface INameableVersionBackend { | |||
/** | |||
* Set the label for a version. | |||
* | |||
* @deprecated 29.0.0 | |||
* @since 26.0.0 | |||
*/ | |||
public function setVersionLabel(IVersion $version, string $label): void; |
@@ -39,13 +39,14 @@ use OCP\Files\FileInfo; | |||
use OCP\Files\Folder; | |||
use OCP\Files\IMimeTypeLoader; | |||
use OCP\Files\IRootFolder; | |||
use OCP\Files\Node; | |||
use OCP\Files\NotFoundException; | |||
use OCP\Files\Storage\IStorage; | |||
use OCP\IUser; | |||
use OCP\IUserManager; | |||
use OCP\IUserSession; | |||
class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend { | |||
class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend { | |||
private IRootFolder $rootFolder; | |||
private IUserManager $userManager; | |||
private VersionsMapper $versionsMapper; | |||
@@ -312,4 +313,33 @@ class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, | |||
return ($sourceFile->getPermissions() & $permissions) === $permissions; | |||
} | |||
public function setMetadataValue(Node $node, string $key, string $value): void { | |||
// Do not handle folders. | |||
if ($node instanceof File) { | |||
try { | |||
$versionEntity = $this->versionsMapper->findVersionForFileId($node->getId(), $node->getMTime()); | |||
} catch (\Exception $e) { | |||
throw $e; // the version does not exist or too many versions exist | |||
} | |||
$currentMetadata = $versionEntity->getMetadata() ?? []; | |||
$currentMetadata[$key] = $value; | |||
$versionEntity->setMetadata($currentMetadata); | |||
$this->versionsMapper->update($versionEntity); | |||
} | |||
} | |||
public function getMetadataValue(Node $node, string $key): ?string { | |||
try { | |||
$versionEntity = $this->versionsMapper->findVersionForFileId($node->getId(), $node->getMTime()); | |||
return $versionEntity->getMetadataValue($key); | |||
} catch (\InvalidArgumentException $e) { | |||
// we tried to find a version or key that doesn't exist | |||
return null; | |||
} | |||
} | |||
} |
@@ -26,9 +26,10 @@ declare(strict_types=1); | |||
namespace OCA\Files_Versions\Versions; | |||
use OCP\Files\FileInfo; | |||
use OCP\Files\Node; | |||
use OCP\IUser; | |||
class Version implements IVersion, INameableVersion { | |||
class Version implements IVersion, INameableVersion, IMetadataVersion { | |||
/** @var int */ | |||
private $timestamp; | |||
@@ -121,4 +122,11 @@ class Version implements IVersion, INameableVersion { | |||
public function getUser(): IUser { | |||
return $this->user; | |||
} | |||
public function getMetadataValue(string $key): ?string { | |||
if ($this->backend instanceof IMetadataVersionBackend && $this->sourceFileInfo instanceof Node) { | |||
return $this->backend->getMetadataValue($this->sourceFileInfo, "author"); | |||
} | |||
return null; | |||
} | |||
} |
@@ -31,11 +31,12 @@ use OCP\Files\IRootFolder; | |||
use OCP\Files\Lock\ILock; | |||
use OCP\Files\Lock\ILockManager; | |||
use OCP\Files\Lock\LockContext; | |||
use OCP\Files\Node; | |||
use OCP\Files\Storage\IStorage; | |||
use OCP\IUser; | |||
use OCP\Lock\ManuallyLockedException; | |||
class VersionManager implements IVersionManager, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend { | |||
class VersionManager implements IVersionManager, INameableVersionBackend, IDeletableVersionBackend, INeedSyncVersionBackend, IMetadataVersionBackend { | |||
/** @var (IVersionBackend[])[] */ | |||
private $backends = []; | |||
@@ -160,6 +161,21 @@ class VersionManager implements IVersionManager, INameableVersionBackend, IDelet | |||
} | |||
} | |||
public function setMetadataValue(Node $node, string $key, string $value): void { | |||
$backend = $this->getBackendForStorage($node->getStorage()); | |||
if ($backend instanceof IMetadataVersionBackend) { | |||
$backend->setMetadataValue($node, $key, $value); | |||
} | |||
} | |||
public function getMetadataValue(Node $node, string $key): ?string { | |||
$backend = $this->getBackendForStorage($node->getStorage()); | |||
if ($backend instanceof IMetadataVersionBackend) { | |||
return $backend->getMetadataValue($node, $key); | |||
} | |||
return null; | |||
} | |||
/** | |||
* Catch ManuallyLockedException and retry in app context if possible. | |||
* | |||
@@ -184,7 +200,7 @@ class VersionManager implements IVersionManager, INameableVersionBackend, IDelet | |||
return $callback(); | |||
} catch (ManuallyLockedException $e) { | |||
$owner = (string) $e->getOwner(); | |||
$appsThatHandleUpdates = array("text", "richdocuments"); | |||
$appsThatHandleUpdates = ["text", "richdocuments"]; | |||
if (!in_array($owner, $appsThatHandleUpdates)) { | |||
throw $e; | |||
} |
@@ -31,6 +31,7 @@ export default `<?xml version="1.0"?> | |||
<d:getlastmodified /> | |||
<d:getetag /> | |||
<nc:version-label /> | |||
<nc:version-author /> | |||
<nc:has-preview /> | |||
</d:prop> | |||
</d:propfind>` |
@@ -17,6 +17,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
'OC\\' => 3, | |||
'OCP\\' => 4, | |||
), | |||
'B' => | |||
array ( | |||
'Bamarni\\Composer\\Bin\\' => 21, | |||
), | |||
); | |||
public static $prefixDirsPsr4 = array ( | |||
@@ -32,6 +36,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 | |||
array ( | |||
0 => __DIR__ . '/../../..' . '/lib/public', | |||
), | |||
'Bamarni\\Composer\\Bin\\' => | |||
array ( | |||
0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src', | |||
), | |||
); | |||
public static $fallbackDirsPsr4 = array ( |