From 740ea0d30e8ed8967e5d940b971e8854812c627d Mon Sep 17 00:00:00 2001
From: Robin Appelman <robin@icewind.nl>
Date: Thu, 6 Dec 2018 13:58:57 +0100
Subject: [PATCH] try to grab the appdata folder directly without going trough
 the whole tree

Signed-off-by: Robin Appelman <robin@icewind.nl>
---
 lib/private/Files/AppData/AppData.php   | 88 ++++++++++++++++++-------
 lib/private/Files/AppData/Factory.php   |  7 +-
 tests/lib/Files/AppData/AppDataTest.php | 24 ++-----
 3 files changed, 76 insertions(+), 43 deletions(-)

diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php
index e25bf450446..3d098ad98ca 100644
--- a/lib/private/Files/AppData/AppData.php
+++ b/lib/private/Files/AppData/AppData.php
@@ -24,6 +24,7 @@ declare(strict_types=1);
 
 namespace OC\Files\AppData;
 
+use OC\Cache\CappedMemoryCache;
 use OC\Files\SimpleFS\SimpleFolder;
 use OCP\Files\IAppData;
 use OCP\Files\IRootFolder;
@@ -48,6 +49,9 @@ class AppData implements IAppData {
 	/** @var Folder */
 	private $folder;
 
+	/** @var (ISimpleFolder|NotFoundException)[]|CappedMemoryCache */
+	private $folders;
+
 	/**
 	 * AppData constructor.
 	 *
@@ -62,6 +66,32 @@ class AppData implements IAppData {
 		$this->rootFolder = $rootFolder;
 		$this->config = $systemConfig;
 		$this->appId = $appId;
+		$this->folders = new CappedMemoryCache();
+	}
+
+	private function getAppDataFolderName() {
+		$instanceId = $this->config->getValue('instanceid', null);
+		if ($instanceId === null) {
+			throw new \RuntimeException('no instance id!');
+		}
+
+		return 'appdata_' . $instanceId;
+	}
+
+	private function getAppDataRootFolder(): Folder {
+		$name = $this->getAppDataFolderName();
+
+		try {
+			/** @var Folder $node */
+			$node = $this->rootFolder->get($name);
+			return $node;
+		} catch (NotFoundException $e) {
+			try {
+				return $this->rootFolder->newFolder($name);
+			} catch (NotPermittedException $e) {
+				throw new \RuntimeException('Could not get appdata folder');
+			}
+		}
 	}
 
 	/**
@@ -70,56 +100,64 @@ class AppData implements IAppData {
 	 */
 	private function getAppDataFolder(): Folder {
 		if ($this->folder === null) {
-			$instanceId = $this->config->getValue('instanceid', null);
-			if ($instanceId === null) {
-				throw new \RuntimeException('no instance id!');
-			}
-
-			$name = 'appdata_' . $instanceId;
+			$name = $this->getAppDataFolderName();
 
 			try {
-				$appDataFolder = $this->rootFolder->get($name);
+				$this->folder = $this->rootFolder->get($name . '/' . $this->appId);
 			} catch (NotFoundException $e) {
-				try {
-					$appDataFolder = $this->rootFolder->newFolder($name);
-				} catch (NotPermittedException $e) {
-					throw new \RuntimeException('Could not get appdata folder');
-				}
-			}
+				$appDataRootFolder = $this->getAppDataRootFolder();
 
-			try {
-				$appDataFolder = $appDataFolder->get($this->appId);
-			} catch (NotFoundException $e) {
 				try {
-					$appDataFolder = $appDataFolder->newFolder($this->appId);
-				} catch (NotPermittedException $e) {
-					throw new \RuntimeException('Could not get appdata folder for ' . $this->appId);
+					$this->folder = $appDataRootFolder->get($this->appId);
+				} catch (NotFoundException $e) {
+					try {
+						$this->folder = $appDataRootFolder->newFolder($this->appId);
+					} catch (NotPermittedException $e) {
+						throw new \RuntimeException('Could not get appdata folder for ' . $this->appId);
+					}
 				}
 			}
-
-			$this->folder = $appDataFolder;
 		}
 
 		return $this->folder;
 	}
 
 	public function getFolder(string $name): ISimpleFolder {
-		$node = $this->getAppDataFolder()->get($name);
+		$key = $this->appId . '/' . $name;
+		if ($cachedFolder = $this->folders->get($key)) {
+			if ($cachedFolder instanceof \Exception) {
+				throw $cachedFolder;
+			} else {
+				return $cachedFolder;
+			}
+		}
+		try {
+			$path = $this->getAppDataFolderName() . '/' . $this->appId . '/' . $name;
+			$node = $this->rootFolder->get($path);
+		} catch (NotFoundException $e) {
+			$this->folders->set($key, $e);
+			throw $e;
+		}
 
 		/** @var Folder $node */
-		return new SimpleFolder($node);
+		$folder = new SimpleFolder($node);
+		$this->folders->set($key, $folder);
+		return $folder;
 	}
 
 	public function newFolder(string $name): ISimpleFolder {
+		$key = $this->appId . '/' . $name;
 		$folder = $this->getAppDataFolder()->newFolder($name);
 
-		return new SimpleFolder($folder);
+		$simpleFolder = new SimpleFolder($folder);
+		$this->folders->set($key, $simpleFolder);
+		return $simpleFolder;
 	}
 
 	public function getDirectoryListing(): array {
 		$listing = $this->getAppDataFolder()->getDirectoryListing();
 
-		$fileListing = array_map(function(Node $folder) {
+		$fileListing = array_map(function (Node $folder) {
 			if ($folder instanceof Folder) {
 				return new SimpleFolder($folder);
 			}
diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php
index fba2232db06..5c7d554ba5b 100644
--- a/lib/private/Files/AppData/Factory.php
+++ b/lib/private/Files/AppData/Factory.php
@@ -34,6 +34,8 @@ class Factory {
 	/** @var SystemConfig */
 	private $config;
 
+	private $folders = [];
+
 	public function __construct(IRootFolder $rootFolder,
 								SystemConfig $systemConfig) {
 
@@ -46,6 +48,9 @@ class Factory {
 	 * @return AppData
 	 */
 	public function get(string $appId): AppData {
-		return new AppData($this->rootFolder, $this->config, $appId);
+		if (!isset($this->folders[$appId])) {
+			$this->folders[$appId] = new AppData($this->rootFolder, $this->config, $appId);
+		}
+		return $this->folders[$appId];
 	}
 }
diff --git a/tests/lib/Files/AppData/AppDataTest.php b/tests/lib/Files/AppData/AppDataTest.php
index 3247ce7ba99..1d5cea0faf1 100644
--- a/tests/lib/Files/AppData/AppDataTest.php
+++ b/tests/lib/Files/AppData/AppDataTest.php
@@ -55,30 +55,22 @@ class AppDataTest extends \Test\TestCase {
 	}
 
 	private function setupAppFolder() {
-		$dataFolder = $this->createMock(Folder::class);
 		$appFolder = $this->createMock(Folder::class);
 
-		$this->rootFolder->expects($this->once())
-			->method('get')
-			->with($this->equalTo('appdata_iid'))
-			->willReturn($dataFolder);
-		$dataFolder->expects($this->once())
+		$this->rootFolder->expects($this->any())
 			->method('get')
-			->with($this->equalTo('myApp'))
+			->with($this->equalTo('appdata_iid/myApp'))
 			->willReturn($appFolder);
 
-		return [$dataFolder, $appFolder];
+		return $appFolder;
 	}
 
 	public function testGetFolder() {
-		$folders = $this->setupAppFolder();
-		$appFolder = $folders[1];
-
 		$folder = $this->createMock(Folder::class);
 
-		$appFolder->expects($this->once())
+		$this->rootFolder->expects($this->once())
 			->method('get')
-			->with($this->equalTo('folder'))
+			->with($this->equalTo('appdata_iid/myApp/folder'))
 			->willReturn($folder);
 
 		$result = $this->appData->getFolder('folder');
@@ -86,8 +78,7 @@ class AppDataTest extends \Test\TestCase {
 	}
 
 	public function testNewFolder() {
-		$folders = $this->setupAppFolder();
-		$appFolder = $folders[1];
+		$appFolder = $this->setupAppFolder();
 
 		$folder = $this->createMock(Folder::class);
 
@@ -101,8 +92,7 @@ class AppDataTest extends \Test\TestCase {
 	}
 
 	public function testGetDirectoryListing() {
-		$folders = $this->setupAppFolder();
-		$appFolder = $folders[1];
+		$appFolder = $this->setupAppFolder();
 
 		$file = $this->createMock(File::class);
 		$folder = $this->createMock(Folder::class);
-- 
GitLab