From ece471de360ebbebee6b098a20d65042dd69928c Mon Sep 17 00:00:00 2001
From: Joas Schilling <coding@schilljs.com>
Date: Thu, 7 Feb 2019 15:43:20 +0100
Subject: [PATCH] Start implementing access cache

Signed-off-by: Joas Schilling <coding@schilljs.com>
---
 ...php => Version16000Date20190207141427.php} | 26 +++++-
 .../Collaboration/Resources/Collection.php    | 18 +++--
 .../Collaboration/Resources/Manager.php       | 79 +++++++++++++++++++
 .../Collaboration/Resources/Resource.php      | 13 ++-
 4 files changed, 127 insertions(+), 9 deletions(-)
 rename core/Migrations/{Version15000Date20180917092725.php => Version16000Date20190207141427.php} (73%)

diff --git a/core/Migrations/Version15000Date20180917092725.php b/core/Migrations/Version16000Date20190207141427.php
similarity index 73%
rename from core/Migrations/Version15000Date20180917092725.php
rename to core/Migrations/Version16000Date20190207141427.php
index 1bcc6382745..44e09a8463b 100644
--- a/core/Migrations/Version15000Date20180917092725.php
+++ b/core/Migrations/Version16000Date20190207141427.php
@@ -28,7 +28,7 @@ use OCP\DB\ISchemaWrapper;
 use OCP\Migration\SimpleMigrationStep;
 use OCP\Migration\IOutput;
 
-class Version15000Date20180917092725 extends SimpleMigrationStep {
+class Version16000Date20190207141427 extends SimpleMigrationStep {
 
 
 	/**
@@ -74,6 +74,30 @@ class Version15000Date20180917092725 extends SimpleMigrationStep {
 			$table->addUniqueIndex(['collection_id', 'resource_type', 'resource_id'], 'collres_unique_res');
 		}
 
+		if (!$schema->hasTable('collres_accesscache')) {
+			$table = $schema->createTable('collres_accesscache');
+
+			$table->addColumn('user_id', Type::STRING, [
+				'notnull' => true,
+				'length' => 64,
+			]);
+			$table->addColumn('collection_id', Type::BIGINT, [
+				'notnull' => false,
+			]);
+			$table->addColumn('resource_id', Type::STRING, [
+				'notnull' => false,
+				'length' => 64,
+			]);
+			$table->addColumn('access', Type::SMALLINT, [
+				'notnull' => true,
+				'default' => 0,
+			]);
+
+			$table->addUniqueIndex(['user_id', 'collection_id', 'resource_id'], 'collres_unique_user');
+			$table->addIndex(['user_id', 'resource_id'], 'collres_user_res');
+			$table->addIndex(['user_id', 'collection_id'], 'collres_user_coll');
+		}
+
 		return $schema;
 	}
 
diff --git a/lib/private/Collaboration/Resources/Collection.php b/lib/private/Collaboration/Resources/Collection.php
index 5770e8918fd..c538580b8f8 100644
--- a/lib/private/Collaboration/Resources/Collection.php
+++ b/lib/private/Collaboration/Resources/Collection.php
@@ -46,6 +46,9 @@ class Collection implements ICollection {
 	/** @var string */
 	protected $name;
 
+	/** @var bool|null */
+	protected $access;
+
 	/** @var IResource[] */
 	protected $resources;
 
@@ -53,12 +56,14 @@ class Collection implements ICollection {
 		IManager $manager,
 		IDBConnection $connection,
 		int $id,
-		string $name
+		string $name,
+		?bool $access
 	) {
 		$this->manager = $manager;
 		$this->connection = $connection;
 		$this->id = $id;
 		$this->name = $name;
+		$this->access = $access;
 		$this->resources = [];
 	}
 
@@ -161,13 +166,16 @@ class Collection implements ICollection {
 	 * @since 16.0.0
 	 */
 	public function canAccess(IUser $user = null): bool {
-		foreach ($this->getResources() as $resource) {
-			if ($resource->canAccess($user)) {
-				return true;
+		if ($this->access === null) {
+			$this->access = false;
+			foreach ($this->getResources() as $resource) {
+				if ($resource->canAccess($user)) {
+					$this->access = true;
+				}
 			}
 		}
 
-		return false;
+		return $this->access;
 	}
 
 	protected function isSameResource(IResource $resource1, IResource $resource2): bool {
diff --git a/lib/private/Collaboration/Resources/Manager.php b/lib/private/Collaboration/Resources/Manager.php
index 55274432d76..ae42f272c05 100644
--- a/lib/private/Collaboration/Resources/Manager.php
+++ b/lib/private/Collaboration/Resources/Manager.php
@@ -193,6 +193,85 @@ class Manager implements IManager {
 		return false;
 	}
 
+	public function cacheAccessForResource(IResource $resource, ?IUser $user, bool $access): void {
+		$query = $this->connection->getQueryBuilder();
+		$userId = $user instanceof IUser ? $user->getUID() : '';
+
+		$query->insert('collres_accesscache')
+			->values([
+				'user_id' => $query->createNamedParameter($userId),
+				'resource_id' => $query->createNamedParameter($resource->getId()),
+				'access' => $query->createNamedParameter($access),
+			]);
+		$query->execute();
+	}
+
+	public function cacheAccessForCollection(ICollection $collection, ?IUser $user, bool $access): void {
+		$query = $this->connection->getQueryBuilder();
+		$userId = $user instanceof IUser ? $user->getUID() : '';
+
+		$query->insert('collres_accesscache')
+			->values([
+				'user_id' => $query->createNamedParameter($userId),
+				'collection_id' => $query->createNamedParameter($collection->getId()),
+				'access' => $query->createNamedParameter($access),
+			]);
+		$query->execute();
+	}
+
+	public function invalidateAccessCacheForUser(?IUser $user): void {
+		$query = $this->connection->getQueryBuilder();
+		$userId = $user instanceof IUser ? $user->getUID() : '';
+
+		$query->delete('collres_accesscache')
+			->where($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+		$query->execute();
+	}
+
+	public function invalidateAccessCacheForResource(IResource $resource): void {
+		$query = $this->connection->getQueryBuilder();
+
+		$query->delete('collres_accesscache')
+			->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())));
+		$query->execute();
+
+		foreach ($resource->getCollections() as $collection) {
+			$this->invalidateAccessCacheForCollection($collection);
+		}
+	}
+
+	protected function invalidateAccessCacheForCollection(ICollection $collection): void {
+		$query = $this->connection->getQueryBuilder();
+
+		$query->delete('collres_accesscache')
+			->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())));
+		$query->execute();
+	}
+
+	public function invalidateAccessCacheForResourceByUser(IResource $resource, ?IUser $user): void {
+		$query = $this->connection->getQueryBuilder();
+		$userId = $user instanceof IUser ? $user->getUID() : '';
+
+		$query->delete('collres_accesscache')
+			->where($query->expr()->eq('resource_id', $query->createNamedParameter($resource->getId())))
+			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+		$query->execute();
+
+		foreach ($resource->getCollections() as $collection) {
+			$this->invalidateAccessCacheForCollectionByUser($collection, $user);
+		}
+	}
+
+	protected function invalidateAccessCacheForCollectionByUser(ICollection $collection, ?IUser $user): void {
+		$query = $this->connection->getQueryBuilder();
+		$userId = $user instanceof IUser ? $user->getUID() : '';
+
+		$query->delete('collres_accesscache')
+			->where($query->expr()->eq('collection_id', $query->createNamedParameter($collection->getId())))
+			->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($userId)));
+		$query->execute();
+	}
+
 	/**
 	 * @param IProvider $provider
 	 */
diff --git a/lib/private/Collaboration/Resources/Resource.php b/lib/private/Collaboration/Resources/Resource.php
index c31843bdd06..bfc63fefdfc 100644
--- a/lib/private/Collaboration/Resources/Resource.php
+++ b/lib/private/Collaboration/Resources/Resource.php
@@ -26,7 +26,6 @@ namespace OC\Collaboration\Resources;
 use OCP\Collaboration\Resources\ICollection;
 use OCP\Collaboration\Resources\IManager;
 use OCP\Collaboration\Resources\IResource;
-use OCP\Collaboration\Resources\ResourceException;
 use OCP\IDBConnection;
 use OCP\IUser;
 
@@ -44,6 +43,9 @@ class Resource implements IResource {
 	/** @var string */
 	protected $id;
 
+	/** @var bool|null */
+	protected $access;
+
 	/** @var string|null */
 	protected $name;
 
@@ -57,12 +59,14 @@ class Resource implements IResource {
 		IManager $manager,
 		IDBConnection $connection,
 		string $type,
-		string $id
+		string $id,
+		?bool $access
 	) {
 		$this->manager = $manager;
 		$this->connection = $connection;
 		$this->type = $type;
 		$this->id = $id;
+		$this->access = $access;
 	}
 
 	/**
@@ -122,7 +126,10 @@ class Resource implements IResource {
 	 * @since 16.0.0
 	 */
 	public function canAccess(IUser $user = null): bool {
-		return $this->manager->canAccess($this, $user);
+		if ($this->access === null) {
+			$this->access = $this->manager->canAccess($this, $user);
+		}
+		return $this->access;
 	}
 
 	/**
-- 
GitLab