Skip to content

Commit 4952e12

Browse files
committed
Improve iterators
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
1 parent 347765d commit 4952e12

8 files changed

Lines changed: 93 additions & 122 deletions

File tree

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ updater.phar: updater.php lib/*.php buildVersionFile.php
1010
clean:
1111
rm updater.phar index.php
1212

13-
index.php: lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorWithoutData.php lib/Updater.php index.web.php
13+
index.php: lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorFilter.php lib/Updater.php index.web.php
1414
# First put openining php tag and license
1515
awk '/^<\?php$$/,/\*\//' index.web.php > index.php
1616
# Then concat all files while filtering php tag and license
17-
cat lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorWithoutData.php lib/Updater.php index.web.php| grep -v "^namespace" | awk '/^<\?php$$/,/\*\//{next} 1' >> index.php
17+
cat lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorFilter.php lib/Updater.php index.web.php| grep -v "^namespace" | awk '/^<\?php$$/,/\*\//{next} 1' >> index.php
1818

1919
test/vendor:
2020
cd tests && composer install

index.php

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -41,28 +41,25 @@ class LogException extends \Exception {
4141
}
4242

4343

44-
class RecursiveDirectoryIteratorWithoutData extends \RecursiveFilterIterator {
45-
public function accept(): bool {
46-
$excludes = [
47-
'.rnd',
48-
'.well-known',
49-
'data',
50-
'..',
51-
];
44+
class RecursiveDirectoryIteratorFilter extends \RecursiveFilterIterator {
45+
private array $excludedPaths;
5246

53-
/** @var \SplFileInfo|false */
54-
$current = $this->current();
55-
if (!$current) {
56-
return false;
57-
}
47+
public function __construct(
48+
\RecursiveDirectoryIterator $iterator,
49+
array $excludedPaths = ['data'],
50+
) {
51+
parent::__construct($iterator);
52+
$this->excludedPaths = array_flip($excludedPaths);
53+
}
5854

59-
return !(in_array($current->getFilename(), $excludes, true) || $current->isDir());
55+
public function accept(): bool {
56+
return !isset($this->excludedPaths[$this->current()->getFilename()]);
6057
}
6158
}
6259

6360

6461
class Updater {
65-
private string $baseDir;
62+
private string $nextcloudDir;
6663
private array $configValues = [];
6764
private string $currentVersion = 'unknown';
6865
private string $buildTime;
@@ -75,13 +72,15 @@ class Updater {
7572
* @param string $baseDir the absolute path to the /updater/ directory in the Nextcloud root
7673
* @throws \Exception
7774
*/
78-
public function __construct(string $baseDir) {
79-
$this->baseDir = $baseDir;
75+
public function __construct(
76+
private string $baseDir
77+
) {
78+
$this->nextcloudDir = realpath(dirname($baseDir));
8079

8180
if ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
82-
$configFileName = rtrim($dir, '/') . '/config.php';
81+
$configFileName = realpath($dir . '/config.php');
8382
} else {
84-
$configFileName = $this->baseDir . '/../config/config.php';
83+
$configFileName = $this->nextcloudDir . '/config/config.php';
8584
}
8685
if (!file_exists($configFileName)) {
8786
throw new \Exception('Could not find config.php. Is this file in the "updater" subfolder of Nextcloud?');
@@ -102,7 +101,7 @@ public function __construct(string $baseDir) {
102101
throw new \Exception('Could not read data directory from config.php.');
103102
}
104103

105-
$versionFileName = $this->baseDir . '/../version.php';
104+
$versionFileName = $this->nextcloudDir . '/version.php';
106105
if (!file_exists($versionFileName)) {
107106
// fallback to version in config.php
108107
$version = $this->getConfigOptionString('version');
@@ -133,27 +132,23 @@ public function __construct(string $baseDir) {
133132

134133
/**
135134
* Returns whether the web updater is disabled
136-
*
137-
* @return bool
138135
*/
139-
public function isDisabled() {
136+
public function isDisabled(): bool {
140137
return $this->disabled;
141138
}
142139

143140
/**
144141
* Returns current version or "unknown" if this could not be determined.
145-
*
146-
* @return string
147142
*/
148-
public function getCurrentVersion() {
143+
public function getCurrentVersion(): string {
149144
return $this->currentVersion;
150145
}
151146

152147
/**
153148
* Returns currently used release channel
154149
*/
155150
private function getCurrentReleaseChannel(): string {
156-
return ($this->getConfigOptionString('updater.release.channel') ?? 'stable');
151+
return $this->getConfigOptionString('updater.release.channel') ?? 'stable';
157152
}
158153

159154
/**
@@ -323,16 +318,19 @@ private function getAppDirectories(): array {
323318
/**
324319
* Gets the recursive directory iterator over the Nextcloud folder
325320
*
326-
* @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator>
321+
* @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator|RecursiveDirectoryIteratorFilter>
327322
*/
328-
private function getRecursiveDirectoryIterator(?string $folder = null): \RecursiveIteratorIterator {
323+
private function getRecursiveDirectoryIterator(?string $folder = null, array $excludedPaths = []): \RecursiveIteratorIterator {
329324
if ($folder === null) {
330325
$folder = $this->baseDir . '/../';
331326
}
332-
return new \RecursiveIteratorIterator(
333-
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
334-
\RecursiveIteratorIterator::CHILD_FIRST
335-
);
327+
328+
$iterator = new \RecursiveDirectoryIterator($folder, \FilesystemIterator::SKIP_DOTS);
329+
if (!empty($excludedPaths)) {
330+
$iterator = new RecursiveDirectoryIteratorFilter($iterator, $excludedPaths);
331+
}
332+
333+
return new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
336334
}
337335

338336
/**
@@ -343,7 +341,7 @@ public function checkForExpectedFilesAndFolders(): void {
343341

344342
$expectedElements = $this->getExpectedElementsList();
345343
$unexpectedElements = [];
346-
foreach (new \DirectoryIterator($this->baseDir . '/../') as $fileInfo) {
344+
foreach (new \DirectoryIterator($this->nextcloudDir) as $fileInfo) {
347345
if (array_search($fileInfo->getFilename(), $expectedElements) === false) {
348346
$unexpectedElements[] = $fileInfo->getFilename();
349347
}
@@ -361,15 +359,21 @@ public function checkForExpectedFilesAndFolders(): void {
361359
public function checkWritePermissions(): void {
362360
$this->silentLog('[info] checkWritePermissions()');
363361

364-
$notWritablePaths = array();
365-
$dir = new \RecursiveDirectoryIterator($this->baseDir . '/../');
366-
$filter = new RecursiveDirectoryIteratorWithoutData($dir);
367-
/** @var iterable<string, \SplFileInfo> */
368-
$it = new \RecursiveIteratorIterator($filter);
362+
$excludedPaths = [
363+
'.rnd' => true,
364+
'.well-known' => true,
365+
'data' => true,
366+
];
367+
368+
$it = new \DirectoryIterator($this->nextcloudDir);
369369

370-
foreach ($it as $path => $dir) {
371-
if (!is_writable($path)) {
372-
$notWritablePaths[] = $path;
370+
$notWritablePaths = [];
371+
foreach ($it as $path => $fileInfo) {
372+
if ($fileInfo->isDot() || isset($excludedPaths[$fileInfo->getFilename()])) {
373+
continue;
374+
}
375+
if (!$fileInfo->isWritable()) {
376+
$notWritablePaths[] = $fileInfo->getFilename();
373377
}
374378
}
375379
if (count($notWritablePaths) > 0) {
@@ -442,7 +446,7 @@ public function createBackup(): void {
442446
* @var string $path
443447
* @var \SplFileInfo $fileInfo
444448
*/
445-
foreach ($this->getRecursiveDirectoryIterator($currentDir) as $path => $fileInfo) {
449+
foreach ($this->getRecursiveDirectoryIterator($currentDir, $excludedElements) as $path => $fileInfo) {
446450
$fileName = explode($currentDir, $path)[1];
447451
$folderStructure = explode('/', $fileName, -1);
448452

@@ -971,8 +975,9 @@ public function deleteOldFiles(): void {
971975
}
972976

973977
/**
974-
* Moves the specified filed except the excluded elements to the correct position
978+
* Moves the specified files except the excluded elements to the correct position
975979
*
980+
* @param string[] $excludedElements
976981
* @throws \Exception
977982
*/
978983
private function moveWithExclusions(string $dataLocation, array $excludedElements): void {

lib/RecursiveDirectoryIteratorWithoutData.php

Lines changed: 0 additions & 42 deletions
This file was deleted.

lib/Updater.php

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
namespace NC\Updater;
2525

2626
class Updater {
27-
private string $baseDir;
27+
private string $nextcloudDir;
2828
private array $configValues = [];
2929
private string $currentVersion = 'unknown';
3030
private string $buildTime;
@@ -37,13 +37,15 @@ class Updater {
3737
* @param string $baseDir the absolute path to the /updater/ directory in the Nextcloud root
3838
* @throws \Exception
3939
*/
40-
public function __construct(string $baseDir) {
41-
$this->baseDir = $baseDir;
40+
public function __construct(
41+
private string $baseDir
42+
) {
43+
$this->nextcloudDir = realpath(dirname($baseDir));
4244

4345
if ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
44-
$configFileName = rtrim($dir, '/') . '/config.php';
46+
$configFileName = realpath($dir . '/config.php');
4547
} else {
46-
$configFileName = $this->baseDir . '/../config/config.php';
48+
$configFileName = $this->nextcloudDir . '/config/config.php';
4749
}
4850
if (!file_exists($configFileName)) {
4951
throw new \Exception('Could not find config.php. Is this file in the "updater" subfolder of Nextcloud?');
@@ -64,7 +66,7 @@ public function __construct(string $baseDir) {
6466
throw new \Exception('Could not read data directory from config.php.');
6567
}
6668

67-
$versionFileName = $this->baseDir . '/../version.php';
69+
$versionFileName = $this->nextcloudDir . '/version.php';
6870
if (!file_exists($versionFileName)) {
6971
// fallback to version in config.php
7072
$version = $this->getConfigOptionString('version');
@@ -95,27 +97,23 @@ public function __construct(string $baseDir) {
9597

9698
/**
9799
* Returns whether the web updater is disabled
98-
*
99-
* @return bool
100100
*/
101-
public function isDisabled() {
101+
public function isDisabled(): bool {
102102
return $this->disabled;
103103
}
104104

105105
/**
106106
* Returns current version or "unknown" if this could not be determined.
107-
*
108-
* @return string
109107
*/
110-
public function getCurrentVersion() {
108+
public function getCurrentVersion(): string {
111109
return $this->currentVersion;
112110
}
113111

114112
/**
115113
* Returns currently used release channel
116114
*/
117115
private function getCurrentReleaseChannel(): string {
118-
return ($this->getConfigOptionString('updater.release.channel') ?? 'stable');
116+
return $this->getConfigOptionString('updater.release.channel') ?? 'stable';
119117
}
120118

121119
/**
@@ -285,16 +283,19 @@ private function getAppDirectories(): array {
285283
/**
286284
* Gets the recursive directory iterator over the Nextcloud folder
287285
*
288-
* @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator>
286+
* @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator|RecursiveDirectoryIteratorFilter>
289287
*/
290-
private function getRecursiveDirectoryIterator(?string $folder = null): \RecursiveIteratorIterator {
288+
private function getRecursiveDirectoryIterator(?string $folder = null, array $excludedPaths = []): \RecursiveIteratorIterator {
291289
if ($folder === null) {
292290
$folder = $this->baseDir . '/../';
293291
}
294-
return new \RecursiveIteratorIterator(
295-
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
296-
\RecursiveIteratorIterator::CHILD_FIRST
297-
);
292+
293+
$iterator = new \RecursiveDirectoryIterator($folder, \FilesystemIterator::SKIP_DOTS);
294+
if (!empty($excludedPaths)) {
295+
$iterator = new RecursiveDirectoryIteratorFilter($iterator, $excludedPaths);
296+
}
297+
298+
return new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
298299
}
299300

300301
/**
@@ -305,7 +306,7 @@ public function checkForExpectedFilesAndFolders(): void {
305306

306307
$expectedElements = $this->getExpectedElementsList();
307308
$unexpectedElements = [];
308-
foreach (new \DirectoryIterator($this->baseDir . '/../') as $fileInfo) {
309+
foreach (new \DirectoryIterator($this->nextcloudDir) as $fileInfo) {
309310
if (array_search($fileInfo->getFilename(), $expectedElements) === false) {
310311
$unexpectedElements[] = $fileInfo->getFilename();
311312
}
@@ -323,15 +324,21 @@ public function checkForExpectedFilesAndFolders(): void {
323324
public function checkWritePermissions(): void {
324325
$this->silentLog('[info] checkWritePermissions()');
325326

326-
$notWritablePaths = array();
327-
$dir = new \RecursiveDirectoryIterator($this->baseDir . '/../');
328-
$filter = new RecursiveDirectoryIteratorWithoutData($dir);
329-
/** @var iterable<string, \SplFileInfo> */
330-
$it = new \RecursiveIteratorIterator($filter);
327+
$excludedPaths = [
328+
'.rnd' => true,
329+
'.well-known' => true,
330+
'data' => true,
331+
];
332+
333+
$it = new \DirectoryIterator($this->nextcloudDir);
331334

332-
foreach ($it as $path => $dir) {
333-
if (!is_writable($path)) {
334-
$notWritablePaths[] = $path;
335+
$notWritablePaths = [];
336+
foreach ($it as $path => $fileInfo) {
337+
if ($fileInfo->isDot() || isset($excludedPaths[$fileInfo->getFilename()])) {
338+
continue;
339+
}
340+
if (!$fileInfo->isWritable()) {
341+
$notWritablePaths[] = $fileInfo->getFilename();
335342
}
336343
}
337344
if (count($notWritablePaths) > 0) {
@@ -404,7 +411,7 @@ public function createBackup(): void {
404411
* @var string $path
405412
* @var \SplFileInfo $fileInfo
406413
*/
407-
foreach ($this->getRecursiveDirectoryIterator($currentDir) as $path => $fileInfo) {
414+
foreach ($this->getRecursiveDirectoryIterator($currentDir, $excludedElements) as $path => $fileInfo) {
408415
$fileName = explode($currentDir, $path)[1];
409416
$folderStructure = explode('/', $fileName, -1);
410417

@@ -933,8 +940,9 @@ public function deleteOldFiles(): void {
933940
}
934941

935942
/**
936-
* Moves the specified filed except the excluded elements to the correct position
943+
* Moves the specified files except the excluded elements to the correct position
937944
*
945+
* @param string[] $excludedElements
938946
* @throws \Exception
939947
*/
940948
private function moveWithExclusions(string $dataLocation, array $excludedElements): void {

updater.phar

334 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)