Passed
Push — master ( 8ada0c...f8b4e0 )
by Robin
15:48 queued 13s
created

FileUtils::getNode()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 5
eloc 15
c 1
b 0
f 1
nc 5
nop 1
dl 0
loc 18
rs 9.4555
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * @copyright Copyright (c) 2023 Robin Appelman <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OC\Core\Command\Info;
25
26
use OC\Files\SetupManager;
27
use OCA\Circles\MountManager\CircleMount;
0 ignored issues
show
Bug introduced by
The type OCA\Circles\MountManager\CircleMount was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use OCA\Files_External\Config\ExternalMountPoint;
29
use OCA\Files_Sharing\SharedMount;
30
use OCA\GroupFolders\Mount\GroupMountPoint;
0 ignored issues
show
Bug introduced by
The type OCA\GroupFolders\Mount\GroupMountPoint was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
use OCP\Constants;
32
use OCP\Files\Config\IUserMountCache;
33
use OCP\Files\FileInfo;
34
use OCP\Files\IHomeStorage;
35
use OCP\Files\IRootFolder;
36
use OCP\Files\Mount\IMountManager;
37
use OCP\Files\Mount\IMountPoint;
38
use OCP\Files\Node;
39
use OCP\Files\NotFoundException;
40
use OCP\Share\IShare;
41
use OCP\Util;
42
use Symfony\Component\Console\Output\OutputInterface;
43
use OCP\Files\Folder;
44
45
class FileUtils {
46
	private IRootFolder $rootFolder;
47
	private IUserMountCache $userMountCache;
48
	private IMountManager $mountManager;
49
	private SetupManager $setupManager;
50
51
	public function __construct(
52
		IRootFolder $rootFolder,
53
		IUserMountCache $userMountCache,
54
		IMountManager $mountManager,
55
		SetupManager $setupManager
56
	) {
57
		$this->rootFolder = $rootFolder;
58
		$this->userMountCache = $userMountCache;
59
		$this->mountManager = $mountManager;
60
		$this->setupManager = $setupManager;
61
	}
62
63
	/**
64
	 * @param FileInfo $file
65
	 * @return array<string, Node[]>
66
	 * @throws \OCP\Files\NotPermittedException
67
	 * @throws \OC\User\NoUserException
68
	 */
69
	public function getFilesByUser(FileInfo $file): array {
70
		$id = $file->getId();
71
		if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
72
			return [];
73
		}
74
75
		$mounts = $this->userMountCache->getMountsForFileId($id);
76
		$result = [];
77
		foreach ($mounts as $mount) {
78
			if (isset($result[$mount->getUser()->getUID()])) {
79
				continue;
80
			}
81
82
			$userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID());
83
			$result[$mount->getUser()->getUID()] = $userFolder->getById($id);
84
		}
85
86
		return $result;
87
	}
88
89
	/**
90
	 * Get file by either id of path
91
	 *
92
	 * @param string $fileInput
93
	 * @return Node|null
94
	 */
95
	public function getNode(string $fileInput): ?Node {
96
		if (is_numeric($fileInput)) {
97
			$mounts = $this->userMountCache->getMountsForFileId((int)$fileInput);
98
			if (!$mounts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mounts of type OCP\Files\Config\ICachedMountFileInfo[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
99
				return null;
100
			}
101
			$mount = $mounts[0];
102
			$userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID());
103
			$nodes = $userFolder->getById((int)$fileInput);
104
			if (!$nodes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nodes of type OCP\Files\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
105
				return null;
106
			}
107
			return $nodes[0];
108
		} else {
109
			try {
110
				return $this->rootFolder->get($fileInput);
111
			} catch (NotFoundException $e) {
112
				return null;
113
			}
114
		}
115
	}
116
117
	public function formatPermissions(string $type, int $permissions): string {
118
		if ($permissions == Constants::PERMISSION_ALL || ($type === 'file' && $permissions == (Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE))) {
119
			return "full permissions";
120
		}
121
122
		$perms = [];
123
		$allPerms = [Constants::PERMISSION_READ => "read", Constants::PERMISSION_UPDATE => "update", Constants::PERMISSION_CREATE => "create", Constants::PERMISSION_DELETE => "delete", Constants::PERMISSION_SHARE => "share"];
124
		foreach ($allPerms as $perm => $name) {
125
			if (($permissions & $perm) === $perm) {
126
				$perms[] = $name;
127
			}
128
		}
129
130
		return implode(", ", $perms);
131
	}
132
133
	/**
134
	 * @psalm-suppress UndefinedClass
135
	 * @psalm-suppress UndefinedInterfaceMethod
136
	 */
137
	public function formatMountType(IMountPoint $mountPoint): string {
138
		$storage = $mountPoint->getStorage();
139
		if ($storage && $storage->instanceOfStorage(IHomeStorage::class)) {
140
			return "home storage";
141
		} elseif ($mountPoint instanceof SharedMount) {
142
			$share = $mountPoint->getShare();
143
			$shares = $mountPoint->getGroupedShares();
144
			$sharedBy = array_map(function (IShare $share) {
145
				$shareType = $this->formatShareType($share);
146
				if ($shareType) {
147
					return $share->getSharedBy() . " (via " . $shareType . " " . $share->getSharedWith() . ")";
148
				} else {
149
					return $share->getSharedBy();
150
				}
151
			}, $shares);
152
			$description = "shared by " . implode(', ', $sharedBy);
153
			if ($share->getSharedBy() !== $share->getShareOwner()) {
154
				$description .= " owned by " . $share->getShareOwner();
155
			}
156
			return $description;
157
		} elseif ($mountPoint instanceof GroupMountPoint) {
158
			return "groupfolder " . $mountPoint->getFolderId();
0 ignored issues
show
Bug introduced by
The method getFolderId() does not exist on OCP\Files\Mount\IMountPoint. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

158
			return "groupfolder " . $mountPoint->/** @scrutinizer ignore-call */ getFolderId();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
159
		} elseif ($mountPoint instanceof ExternalMountPoint) {
160
			return "external storage " . $mountPoint->getStorageConfig()->getId();
161
		} elseif ($mountPoint instanceof CircleMount) {
162
			return "circle";
163
		}
164
		return get_class($mountPoint);
165
	}
166
167
	public function formatShareType(IShare $share): ?string {
168
		switch ($share->getShareType()) {
169
			case IShare::TYPE_GROUP:
170
				return "group";
171
			case IShare::TYPE_CIRCLE:
172
				return "circle";
173
			case IShare::TYPE_DECK:
174
				return "deck";
175
			case IShare::TYPE_ROOM:
176
				return "room";
177
			case IShare::TYPE_USER:
178
				return null;
179
			default:
180
				return "Unknown (" . $share->getShareType() . ")";
181
		}
182
	}
183
184
	/**
185
	 * Print out the largest count($sizeLimits) files in the directory tree
186
	 *
187
	 * @param OutputInterface $output
188
	 * @param Folder $node
189
	 * @param string $prefix
190
	 * @param array $sizeLimits largest items that are still in the queue to be printed, ordered ascending
191
	 * @return int how many items we've printed
192
	 */
193
	public function outputLargeFilesTree(
194
		OutputInterface $output,
195
		Folder $node,
196
		string $prefix,
197
		array &$sizeLimits,
198
		bool $all,
199
	): int {
200
		/**
201
		 * Algorithm to print the N largest items in a folder without requiring to query or sort the entire three
202
		 *
203
		 * This is done by keeping a list ($sizeLimits) of size N that contain the largest items outside of this
204
		 * folders that are could be printed if there aren't enough items in this folder that are larger.
205
		 *
206
		 * We loop over the items in this folder by size descending until the size of the item falls before the smallest
207
		 * size in $sizeLimits (at that point there are enough items outside this folder to complete the N items).
208
		 *
209
		 * When encountering a folder, we create an updated $sizeLimits with the largest items in the current folder still
210
		 * remaining which we pass into the recursion. (We don't update the current $sizeLimits because that should only
211
		 * hold items *outside* of the current folder.)
212
		 *
213
		 * For every item printed we remove the first item of $sizeLimits are there is no longer room in the output to print
214
		 * items that small.
215
		 */
216
217
		$count = 0;
218
		$children = $node->getDirectoryListing();
219
		usort($children, function (Node $a, Node $b) {
220
			return $b->getSize() <=> $a->getSize();
221
		});
222
		foreach ($children as $i => $child) {
223
			if (!$all) {
224
				if (count($sizeLimits) === 0 || $child->getSize() < $sizeLimits[0]) {
225
					return $count;
226
				}
227
				array_shift($sizeLimits);
228
			}
229
			$count += 1;
230
231
			/** @var Node $child */
232
			$output->writeln("$prefix- " . $child->getName() . ": <info>" . Util::humanFileSize($child->getSize()) . "</info>");
233
			if ($child instanceof Folder) {
234
				$recurseSizeLimits = $sizeLimits;
235
				if (!$all) {
236
					for ($j = 0; $j < count($recurseSizeLimits); $j++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
237
						if (isset($children[$i + $j + 1])) {
238
							$nextChildSize = $children[$i + $j + 1]->getSize();
239
							if ($nextChildSize > $recurseSizeLimits[0]) {
240
								array_shift($recurseSizeLimits);
241
								$recurseSizeLimits[] = $nextChildSize;
242
							}
243
						}
244
					}
245
					sort($recurseSizeLimits);
246
				}
247
				$recurseCount = $this->outputLargeFilesTree($output, $child, $prefix . "  ", $recurseSizeLimits, $all);
248
				$sizeLimits = array_slice($sizeLimits, $recurseCount);
249
				$count += $recurseCount;
250
			}
251
		}
252
		return $count;
253
	}
254
}
255