Skip to content

Commit 6788e6e

Browse files
Merge pull request #12883 from nextcloud/appdata-performance
try to grab the appdata folder directly without going trough the whole tree
2 parents 2d07c58 + 036475f commit 6788e6e

File tree

4 files changed

+81
-44
lines changed

4 files changed

+81
-44
lines changed

lib/private/Files/AppData/AppData.php

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
namespace OC\Files\AppData;
2626

27+
use OC\Cache\CappedMemoryCache;
2728
use OC\Files\SimpleFS\SimpleFolder;
2829
use OCP\Files\IAppData;
2930
use OCP\Files\IRootFolder;
@@ -48,6 +49,9 @@ class AppData implements IAppData {
4849
/** @var Folder */
4950
private $folder;
5051

52+
/** @var (ISimpleFolder|NotFoundException)[]|CappedMemoryCache */
53+
private $folders;
54+
5155
/**
5256
* AppData constructor.
5357
*
@@ -62,6 +66,32 @@ public function __construct(IRootFolder $rootFolder,
6266
$this->rootFolder = $rootFolder;
6367
$this->config = $systemConfig;
6468
$this->appId = $appId;
69+
$this->folders = new CappedMemoryCache();
70+
}
71+
72+
private function getAppDataFolderName() {
73+
$instanceId = $this->config->getValue('instanceid', null);
74+
if ($instanceId === null) {
75+
throw new \RuntimeException('no instance id!');
76+
}
77+
78+
return 'appdata_' . $instanceId;
79+
}
80+
81+
private function getAppDataRootFolder(): Folder {
82+
$name = $this->getAppDataFolderName();
83+
84+
try {
85+
/** @var Folder $node */
86+
$node = $this->rootFolder->get($name);
87+
return $node;
88+
} catch (NotFoundException $e) {
89+
try {
90+
return $this->rootFolder->newFolder($name);
91+
} catch (NotPermittedException $e) {
92+
throw new \RuntimeException('Could not get appdata folder');
93+
}
94+
}
6595
}
6696

6797
/**
@@ -70,56 +100,64 @@ public function __construct(IRootFolder $rootFolder,
70100
*/
71101
private function getAppDataFolder(): Folder {
72102
if ($this->folder === null) {
73-
$instanceId = $this->config->getValue('instanceid', null);
74-
if ($instanceId === null) {
75-
throw new \RuntimeException('no instance id!');
76-
}
77-
78-
$name = 'appdata_' . $instanceId;
103+
$name = $this->getAppDataFolderName();
79104

80105
try {
81-
$appDataFolder = $this->rootFolder->get($name);
106+
$this->folder = $this->rootFolder->get($name . '/' . $this->appId);
82107
} catch (NotFoundException $e) {
83-
try {
84-
$appDataFolder = $this->rootFolder->newFolder($name);
85-
} catch (NotPermittedException $e) {
86-
throw new \RuntimeException('Could not get appdata folder');
87-
}
88-
}
108+
$appDataRootFolder = $this->getAppDataRootFolder();
89109

90-
try {
91-
$appDataFolder = $appDataFolder->get($this->appId);
92-
} catch (NotFoundException $e) {
93110
try {
94-
$appDataFolder = $appDataFolder->newFolder($this->appId);
95-
} catch (NotPermittedException $e) {
96-
throw new \RuntimeException('Could not get appdata folder for ' . $this->appId);
111+
$this->folder = $appDataRootFolder->get($this->appId);
112+
} catch (NotFoundException $e) {
113+
try {
114+
$this->folder = $appDataRootFolder->newFolder($this->appId);
115+
} catch (NotPermittedException $e) {
116+
throw new \RuntimeException('Could not get appdata folder for ' . $this->appId);
117+
}
97118
}
98119
}
99-
100-
$this->folder = $appDataFolder;
101120
}
102121

103122
return $this->folder;
104123
}
105124

106125
public function getFolder(string $name): ISimpleFolder {
107-
$node = $this->getAppDataFolder()->get($name);
126+
$key = $this->appId . '/' . $name;
127+
if ($cachedFolder = $this->folders->get($key)) {
128+
if ($cachedFolder instanceof \Exception) {
129+
throw $cachedFolder;
130+
} else {
131+
return $cachedFolder;
132+
}
133+
}
134+
try {
135+
$path = $this->getAppDataFolderName() . '/' . $this->appId . '/' . $name;
136+
$node = $this->rootFolder->get($path);
137+
} catch (NotFoundException $e) {
138+
$this->folders->set($key, $e);
139+
throw $e;
140+
}
108141

109142
/** @var Folder $node */
110-
return new SimpleFolder($node);
143+
$folder = new SimpleFolder($node);
144+
$this->folders->set($key, $folder);
145+
return $folder;
111146
}
112147

113148
public function newFolder(string $name): ISimpleFolder {
149+
$key = $this->appId . '/' . $name;
114150
$folder = $this->getAppDataFolder()->newFolder($name);
115151

116-
return new SimpleFolder($folder);
152+
$simpleFolder = new SimpleFolder($folder);
153+
$this->folders->set($key, $simpleFolder);
154+
return $simpleFolder;
117155
}
118156

119157
public function getDirectoryListing(): array {
120158
$listing = $this->getAppDataFolder()->getDirectoryListing();
121159

122-
$fileListing = array_map(function(Node $folder) {
160+
$fileListing = array_map(function (Node $folder) {
123161
if ($folder instanceof Folder) {
124162
return new SimpleFolder($folder);
125163
}

lib/private/Files/AppData/Factory.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class Factory {
3434
/** @var SystemConfig */
3535
private $config;
3636

37+
private $folders = [];
38+
3739
public function __construct(IRootFolder $rootFolder,
3840
SystemConfig $systemConfig) {
3941

@@ -46,6 +48,9 @@ public function __construct(IRootFolder $rootFolder,
4648
* @return AppData
4749
*/
4850
public function get(string $appId): AppData {
49-
return new AppData($this->rootFolder, $this->config, $appId);
51+
if (!isset($this->folders[$appId])) {
52+
$this->folders[$appId] = new AppData($this->rootFolder, $this->config, $appId);
53+
}
54+
return $this->folders[$appId];
5055
}
5156
}

tests/lib/Files/AppData/AppDataTest.php

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,30 @@ public function setUp() {
5555
}
5656

5757
private function setupAppFolder() {
58-
$dataFolder = $this->createMock(Folder::class);
5958
$appFolder = $this->createMock(Folder::class);
6059

61-
$this->rootFolder->expects($this->once())
62-
->method('get')
63-
->with($this->equalTo('appdata_iid'))
64-
->willReturn($dataFolder);
65-
$dataFolder->expects($this->once())
60+
$this->rootFolder->expects($this->any())
6661
->method('get')
67-
->with($this->equalTo('myApp'))
62+
->with($this->equalTo('appdata_iid/myApp'))
6863
->willReturn($appFolder);
6964

70-
return [$dataFolder, $appFolder];
65+
return $appFolder;
7166
}
7267

7368
public function testGetFolder() {
74-
$folders = $this->setupAppFolder();
75-
$appFolder = $folders[1];
76-
7769
$folder = $this->createMock(Folder::class);
7870

79-
$appFolder->expects($this->once())
71+
$this->rootFolder->expects($this->once())
8072
->method('get')
81-
->with($this->equalTo('folder'))
73+
->with($this->equalTo('appdata_iid/myApp/folder'))
8274
->willReturn($folder);
8375

8476
$result = $this->appData->getFolder('folder');
8577
$this->assertInstanceOf(ISimpleFolder::class, $result);
8678
}
8779

8880
public function testNewFolder() {
89-
$folders = $this->setupAppFolder();
90-
$appFolder = $folders[1];
81+
$appFolder = $this->setupAppFolder();
9182

9283
$folder = $this->createMock(Folder::class);
9384

@@ -101,8 +92,7 @@ public function testNewFolder() {
10192
}
10293

10394
public function testGetDirectoryListing() {
104-
$folders = $this->setupAppFolder();
105-
$appFolder = $folders[1];
95+
$appFolder = $this->setupAppFolder();
10696

10797
$file = $this->createMock(File::class);
10898
$folder = $this->createMock(Folder::class);

tests/lib/Preview/BackgroundCleanupJobTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use OC\Files\AppData\Factory;
2626
use OC\Preview\BackgroundCleanupJob;
2727
use OC\PreviewManager;
28+
use OC\SystemConfig;
2829
use OCP\Files\IRootFolder;
2930
use OCP\IDBConnection;
3031
use Test\Traits\MountProviderTrait;
@@ -77,7 +78,10 @@ public function setUp() {
7778
$this->trashEnabled = $appManager->isEnabledForUser('files_trashbin', $this->userId);
7879
$appManager->disableApp('files_trashbin');
7980

80-
$this->appDataFactory = \OC::$server->query(Factory::class);
81+
$this->appDataFactory = new Factory(
82+
\OC::$server->getRootFolder(),
83+
\OC::$server->getSystemConfig()
84+
);
8185
$this->connection = \OC::$server->getDatabaseConnection();
8286
$this->previewManager = \OC::$server->getPreviewManager();
8387
$this->rootFolder = \OC::$server->getRootFolder();

0 commit comments

Comments
 (0)