diff options
author | Louis Chemineau <louis@chmn.me> | 2022-12-08 11:26:58 +0100 |
---|---|---|
committer | Louis (Rebase PR Action) <artonge@users.noreply.github.com> | 2023-01-26 10:12:23 +0000 |
commit | c88328e68e50341fa3e22181ffb9b25ae7071ed7 (patch) | |
tree | f8703ffaf691dfb264d261dc491ddde8e1e87543 /apps/files_versions | |
parent | d8b479752d327f8901a84454c17fd3c6f701a98d (diff) | |
download | nextcloud-server-c88328e68e50341fa3e22181ffb9b25ae7071ed7.tar.gz nextcloud-server-c88328e68e50341fa3e22181ffb9b25ae7071ed7.zip |
Handle empty files in version creation logic
Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'apps/files_versions')
-rw-r--r-- | apps/files_versions/lib/AppInfo/Application.php | 6 | ||||
-rw-r--r-- | apps/files_versions/lib/Db/VersionsMapper.php | 17 | ||||
-rw-r--r-- | apps/files_versions/lib/Listener/FileEventsListener.php | 119 | ||||
-rw-r--r-- | apps/files_versions/lib/Sabre/VersionFile.php | 2 | ||||
-rw-r--r-- | apps/files_versions/lib/Versions/LegacyVersionsBackend.php | 16 | ||||
-rw-r--r-- | apps/files_versions/tests/StorageTest.php | 2 |
6 files changed, 133 insertions, 29 deletions
diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index 7ab6d0ddc34..a07058d04a7 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -47,10 +47,13 @@ use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\Files\Events\Node\BeforeNodeCopiedEvent; use OCP\Files\Events\Node\BeforeNodeDeletedEvent; use OCP\Files\Events\Node\BeforeNodeRenamedEvent; +use OCP\Files\Events\Node\BeforeNodeTouchedEvent; use OCP\Files\Events\Node\NodeCopiedEvent; use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\Events\Node\NodeRenamedEvent; use OCP\Files\Events\Node\BeforeNodeWrittenEvent; +use OCP\Files\Events\Node\NodeCreatedEvent; +use OCP\Files\Events\Node\NodeTouchedEvent; use OCP\Files\Events\Node\NodeWrittenEvent; use OCP\IConfig; use OCP\IGroupManager; @@ -105,6 +108,9 @@ class Application extends App implements IBootstrap { $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class); $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); + $context->registerEventListener(NodeCreatedEvent::class, FileEventsListener::class); + $context->registerEventListener(BeforeNodeTouchedEvent::class, FileEventsListener::class); + $context->registerEventListener(NodeTouchedEvent::class, FileEventsListener::class); $context->registerEventListener(BeforeNodeWrittenEvent::class, FileEventsListener::class); $context->registerEventListener(NodeWrittenEvent::class, FileEventsListener::class); $context->registerEventListener(BeforeNodeDeletedEvent::class, FileEventsListener::class); diff --git a/apps/files_versions/lib/Db/VersionsMapper.php b/apps/files_versions/lib/Db/VersionsMapper.php index b1cf202a6ce..86a0be82668 100644 --- a/apps/files_versions/lib/Db/VersionsMapper.php +++ b/apps/files_versions/lib/Db/VersionsMapper.php @@ -52,13 +52,28 @@ class VersionsMapper extends QBMapper { return $this->findEntities($qb); } + /** + * @return VersionEntity + */ + public function findCurrentVersionForFileId(int $fileId): VersionEntity { + $qb = $this->db->getQueryBuilder(); + + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) + ->orderBy('timestamp', 'DESC') + ->setMaxResults(1); + + return $this->findEntity($qb); + } + public function findVersionForFileId(int $fileId, int $timestamp): VersionEntity { $qb = $this->db->getQueryBuilder(); $qb->select('*') ->from($this->getTableName()) ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) - ->where($qb->expr()->eq('timestamp', $qb->createNamedParameter($timestamp))); + ->andWhere($qb->expr()->eq('timestamp', $qb->createNamedParameter($timestamp))); return $this->findEntity($qb); } diff --git a/apps/files_versions/lib/Listener/FileEventsListener.php b/apps/files_versions/lib/Listener/FileEventsListener.php index 4e476a1c948..6cc4b0d9dcc 100644 --- a/apps/files_versions/lib/Listener/FileEventsListener.php +++ b/apps/files_versions/lib/Listener/FileEventsListener.php @@ -43,11 +43,15 @@ use OCP\EventDispatcher\IEventListener; use OCP\Files\Events\Node\BeforeNodeCopiedEvent; use OCP\Files\Events\Node\BeforeNodeDeletedEvent; use OCP\Files\Events\Node\BeforeNodeRenamedEvent; +use OCP\Files\Events\Node\BeforeNodeTouchedEvent; use OCP\Files\Events\Node\BeforeNodeWrittenEvent; use OCP\Files\Events\Node\NodeCopiedEvent; +use OCP\Files\Events\Node\NodeCreatedEvent; use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\Events\Node\NodeRenamedEvent; +use OCP\Files\Events\Node\NodeTouchedEvent; use OCP\Files\Events\Node\NodeWrittenEvent; +use OCP\Files\Folder; use OCP\Files\IMimeTypeLoader; use OCP\Files\IRootFolder; use OCP\Files\Node; @@ -56,9 +60,13 @@ class FileEventsListener implements IEventListener { private IRootFolder $rootFolder; private VersionsMapper $versionsMapper; /** - * @var array<int, bool> + * @var array<int, array> */ - private array $versionsCreated = []; + private array $writeHookInfo = []; + /** + * @var array<int, Node> + */ + private array $nodesTouched = []; /** * @var array<string, Node> */ @@ -76,6 +84,18 @@ class FileEventsListener implements IEventListener { } public function handle(Event $event): void { + if ($event instanceof NodeCreatedEvent) { + $this->created($event->getNode()); + } + + if ($event instanceof BeforeNodeTouchedEvent) { + $this->pre_touch_hook($event->getNode()); + } + + if ($event instanceof NodeTouchedEvent) { + $this->touch_hook($event->getNode()); + } + if ($event instanceof BeforeNodeWrittenEvent) { $this->write_hook($event->getNode()); } @@ -109,12 +129,57 @@ class FileEventsListener implements IEventListener { } } + public function pre_touch_hook(Node $node): void { + // Do not handle folders. + if ($node instanceof Folder) { + return; + } + + // $node is a non-existing on file creation. + if ($node instanceof NonExistingFile) { + return; + } + + $this->nodesTouched[$node->getId()] = $node; + } + + public function touch_hook(Node $node): void { + $previousNode = $this->nodesTouched[$node->getId()] ?? null; + + if ($previousNode === null) { + return; + } + + unset($this->nodesTouched[$node->getId()]); + + // We update the timestamp of the version entity associated with the previousNode. + $versionEntity = $this->versionsMapper->findVersionForFileId($previousNode->getId(), $previousNode->getMTime()); + // Create a version in the DB for the current content. + $versionEntity->setTimestamp($node->getMTime()); + $this->versionsMapper->update($versionEntity); + } + + public function created(Node $node): void { + $versionEntity = new VersionEntity(); + $versionEntity->setFileId($node->getId()); + $versionEntity->setTimestamp($node->getMTime()); + $versionEntity->setSize($node->getSize()); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($node->getMimetype())); + $versionEntity->setMetadata([]); + $this->versionsMapper->insert($versionEntity); + } + /** * listen to write event. */ public function write_hook(Node $node): void { - // $node is be nonexisting on file creation. - if ($node instanceof NonExistingFolder || $node instanceof NonExistingFile) { + // Do not handle folders. + if ($node instanceof Folder) { + return; + } + + // $node is a non-existing on file creation. + if ($node instanceof NonExistingFile) { return; } @@ -122,31 +187,48 @@ class FileEventsListener implements IEventListener { $path = $userFolder->getRelativePath($node->getPath()); $result = Storage::store($path); - if ($result === false) { - return; - } - // Store the result of the version creation so it can be used in post_write_hook. - $this->versionsCreated[$node->getId()] = true; + $this->writeHookInfo[$node->getId()] = [ + 'previousNode' => $node, + 'versionCreated' => $result !== false + ]; } /** * listen to post_write event. */ public function post_write_hook(Node $node): void { - if (!array_key_exists($node->getId(), $this->versionsCreated)) { + // Do not handle folders. + if ($node instanceof Folder) { return; } - unset($this->versionsCreated[$node->getId()]); + $writeHookInfo = $this->writeHookInfo[$node->getId()] ?? null; - $versionEntity = new VersionEntity(); - $versionEntity->setFileId($node->getId()); - $versionEntity->setTimestamp($node->getMTime()); - $versionEntity->setSize($node->getSize()); - $versionEntity->setMimetype($this->mimeTypeLoader->getId($node->getMimetype())); - $versionEntity->setMetadata([]); - $this->versionsMapper->insert($versionEntity); + if ($writeHookInfo === null) { + return; + } + + if ($writeHookInfo['versionCreated']) { + // If a new version was created, insert a version in the DB for the current content. + $versionEntity = new VersionEntity(); + $versionEntity->setFileId($node->getId()); + $versionEntity->setTimestamp($node->getMTime()); + $versionEntity->setSize($node->getSize()); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($node->getMimetype())); + $versionEntity->setMetadata([]); + $this->versionsMapper->insert($versionEntity); + } else { + // If no new version was stored in the FS, no new version should be added in the DB. + // So we simply update the associated version. + $currentVersionEntity = $this->versionsMapper->findVersionForFileId($node->getId(), $writeHookInfo['previousNode']->getMtime()); + $currentVersionEntity->setTimestamp($node->getMTime()); + $currentVersionEntity->setSize($node->getSize()); + $currentVersionEntity->setMimetype($this->mimeTypeLoader->getId($node->getMimetype())); + $this->versionsMapper->update($currentVersionEntity); + } + + unset($this->versionsCreated[$node->getId()]); } /** @@ -156,6 +238,7 @@ class FileEventsListener implements IEventListener { * cleanup the versions directory if the actual file gets deleted */ public function remove_hook(Node $node): void { + // Need to normalize the path as there is an issue with path concatenation in View.php::getAbsolutePath. $path = Filesystem::normalizePath($node->getPath()); if (!array_key_exists($path, $this->versionsDeleted)) { return; diff --git a/apps/files_versions/lib/Sabre/VersionFile.php b/apps/files_versions/lib/Sabre/VersionFile.php index 12590fe9efd..20ae25a7623 100644 --- a/apps/files_versions/lib/Sabre/VersionFile.php +++ b/apps/files_versions/lib/Sabre/VersionFile.php @@ -89,7 +89,7 @@ class VersionFile implements IFile { } public function getLabel(): ?string { - if ($this->version instanceof INameableVersion) { + if ($this->version instanceof INameableVersion && $this->version->getSourceFile()->getSize() > 0) { return $this->version->getLabel(); } else { return null; diff --git a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php index d5ca4d2a11c..cbfbc001e0c 100644 --- a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php +++ b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php @@ -82,15 +82,13 @@ class LegacyVersionsBackend implements IVersionBackend, INameableVersionBackend, } // Insert the entry in the DB for the current version. - if ($file2->getSize() > 0) { - $versionEntity = new VersionEntity(); - $versionEntity->setFileId($file2->getId()); - $versionEntity->setTimestamp($file2->getMTime()); - $versionEntity->setSize($file2->getSize()); - $versionEntity->setMimetype($this->mimeTypeLoader->getId($file2->getMimetype())); - $versionEntity->setMetadata([]); - $this->versionsMapper->insert($versionEntity); - } + $versionEntity = new VersionEntity(); + $versionEntity->setFileId($file2->getId()); + $versionEntity->setTimestamp($file2->getMTime()); + $versionEntity->setSize($file2->getSize()); + $versionEntity->setMimetype($this->mimeTypeLoader->getId($file2->getMimetype())); + $versionEntity->setMetadata([]); + $this->versionsMapper->insert($versionEntity); // Insert entries in the DB for existing versions. $versionsOnFS = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file2->getPath())); diff --git a/apps/files_versions/tests/StorageTest.php b/apps/files_versions/tests/StorageTest.php index ef599a3e479..c355776451a 100644 --- a/apps/files_versions/tests/StorageTest.php +++ b/apps/files_versions/tests/StorageTest.php @@ -44,6 +44,8 @@ class StorageTest extends TestCase { protected function setUp(): void { parent::setUp(); + \OC::$server->boot(); + $expiration = $this->createMock(Expiration::class); $expiration->method('getMaxAgeAsTimestamp') ->willReturnCallback(function () { |