aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_versions
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2022-12-08 11:26:58 +0100
committerLouis (Rebase PR Action) <artonge@users.noreply.github.com>2023-01-26 10:12:23 +0000
commitc88328e68e50341fa3e22181ffb9b25ae7071ed7 (patch)
treef8703ffaf691dfb264d261dc491ddde8e1e87543 /apps/files_versions
parentd8b479752d327f8901a84454c17fd3c6f701a98d (diff)
downloadnextcloud-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.php6
-rw-r--r--apps/files_versions/lib/Db/VersionsMapper.php17
-rw-r--r--apps/files_versions/lib/Listener/FileEventsListener.php119
-rw-r--r--apps/files_versions/lib/Sabre/VersionFile.php2
-rw-r--r--apps/files_versions/lib/Versions/LegacyVersionsBackend.php16
-rw-r--r--apps/files_versions/tests/StorageTest.php2
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 () {