FileUtils.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
  5. *
  6. * @license GNU AGPL version 3 or any later version
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as
  10. * published by the Free Software Foundation, either version 3 of the
  11. * License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. namespace OC\Core\Command\Info;
  23. use OCA\Circles\MountManager\CircleMount;
  24. use OCA\Files_External\Config\ExternalMountPoint;
  25. use OCA\Files_Sharing\SharedMount;
  26. use OCA\GroupFolders\Mount\GroupMountPoint;
  27. use OCP\Constants;
  28. use OCP\Files\Config\IUserMountCache;
  29. use OCP\Files\FileInfo;
  30. use OCP\Files\Folder;
  31. use OCP\Files\IHomeStorage;
  32. use OCP\Files\IRootFolder;
  33. use OCP\Files\Mount\IMountPoint;
  34. use OCP\Files\Node;
  35. use OCP\Files\NotFoundException;
  36. use OCP\Share\IShare;
  37. use OCP\Util;
  38. use Symfony\Component\Console\Output\OutputInterface;
  39. class FileUtils {
  40. public function __construct(
  41. private IRootFolder $rootFolder,
  42. private IUserMountCache $userMountCache,
  43. ) {
  44. }
  45. /**
  46. * @param FileInfo $file
  47. * @return array<string, Node[]>
  48. * @throws \OCP\Files\NotPermittedException
  49. * @throws \OC\User\NoUserException
  50. */
  51. public function getFilesByUser(FileInfo $file): array {
  52. $id = $file->getId();
  53. if (!$id) {
  54. return [];
  55. }
  56. $mounts = $this->userMountCache->getMountsForFileId($id);
  57. $result = [];
  58. foreach ($mounts as $mount) {
  59. if (isset($result[$mount->getUser()->getUID()])) {
  60. continue;
  61. }
  62. $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID());
  63. $result[$mount->getUser()->getUID()] = $userFolder->getById($id);
  64. }
  65. return $result;
  66. }
  67. /**
  68. * Get file by either id of path
  69. *
  70. * @param string $fileInput
  71. * @return Node|null
  72. */
  73. public function getNode(string $fileInput): ?Node {
  74. if (is_numeric($fileInput)) {
  75. $mounts = $this->userMountCache->getMountsForFileId((int)$fileInput);
  76. if (!$mounts) {
  77. return null;
  78. }
  79. $mount = $mounts[0];
  80. $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID());
  81. return $userFolder->getFirstNodeById((int)$fileInput);
  82. } else {
  83. try {
  84. return $this->rootFolder->get($fileInput);
  85. } catch (NotFoundException $e) {
  86. return null;
  87. }
  88. }
  89. }
  90. public function formatPermissions(string $type, int $permissions): string {
  91. if ($permissions == Constants::PERMISSION_ALL || ($type === 'file' && $permissions == (Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE))) {
  92. return "full permissions";
  93. }
  94. $perms = [];
  95. $allPerms = [Constants::PERMISSION_READ => "read", Constants::PERMISSION_UPDATE => "update", Constants::PERMISSION_CREATE => "create", Constants::PERMISSION_DELETE => "delete", Constants::PERMISSION_SHARE => "share"];
  96. foreach ($allPerms as $perm => $name) {
  97. if (($permissions & $perm) === $perm) {
  98. $perms[] = $name;
  99. }
  100. }
  101. return implode(", ", $perms);
  102. }
  103. /**
  104. * @psalm-suppress UndefinedClass
  105. * @psalm-suppress UndefinedInterfaceMethod
  106. */
  107. public function formatMountType(IMountPoint $mountPoint): string {
  108. $storage = $mountPoint->getStorage();
  109. if ($storage && $storage->instanceOfStorage(IHomeStorage::class)) {
  110. return "home storage";
  111. } elseif ($mountPoint instanceof SharedMount) {
  112. $share = $mountPoint->getShare();
  113. $shares = $mountPoint->getGroupedShares();
  114. $sharedBy = array_map(function (IShare $share) {
  115. $shareType = $this->formatShareType($share);
  116. if ($shareType) {
  117. return $share->getSharedBy() . " (via " . $shareType . " " . $share->getSharedWith() . ")";
  118. } else {
  119. return $share->getSharedBy();
  120. }
  121. }, $shares);
  122. $description = "shared by " . implode(', ', $sharedBy);
  123. if ($share->getSharedBy() !== $share->getShareOwner()) {
  124. $description .= " owned by " . $share->getShareOwner();
  125. }
  126. return $description;
  127. } elseif ($mountPoint instanceof GroupMountPoint) {
  128. return "groupfolder " . $mountPoint->getFolderId();
  129. } elseif ($mountPoint instanceof ExternalMountPoint) {
  130. return "external storage " . $mountPoint->getStorageConfig()->getId();
  131. } elseif ($mountPoint instanceof CircleMount) {
  132. return "circle";
  133. }
  134. return get_class($mountPoint);
  135. }
  136. public function formatShareType(IShare $share): ?string {
  137. switch ($share->getShareType()) {
  138. case IShare::TYPE_GROUP:
  139. return "group";
  140. case IShare::TYPE_CIRCLE:
  141. return "circle";
  142. case IShare::TYPE_DECK:
  143. return "deck";
  144. case IShare::TYPE_ROOM:
  145. return "room";
  146. case IShare::TYPE_USER:
  147. return null;
  148. default:
  149. return "Unknown (" . $share->getShareType() . ")";
  150. }
  151. }
  152. /**
  153. * Print out the largest count($sizeLimits) files in the directory tree
  154. *
  155. * @param OutputInterface $output
  156. * @param Folder $node
  157. * @param string $prefix
  158. * @param array $sizeLimits largest items that are still in the queue to be printed, ordered ascending
  159. * @return int how many items we've printed
  160. */
  161. public function outputLargeFilesTree(
  162. OutputInterface $output,
  163. Folder $node,
  164. string $prefix,
  165. array &$sizeLimits,
  166. bool $all,
  167. ): int {
  168. /**
  169. * Algorithm to print the N largest items in a folder without requiring to query or sort the entire three
  170. *
  171. * This is done by keeping a list ($sizeLimits) of size N that contain the largest items outside of this
  172. * folders that are could be printed if there aren't enough items in this folder that are larger.
  173. *
  174. * We loop over the items in this folder by size descending until the size of the item falls before the smallest
  175. * size in $sizeLimits (at that point there are enough items outside this folder to complete the N items).
  176. *
  177. * When encountering a folder, we create an updated $sizeLimits with the largest items in the current folder still
  178. * remaining which we pass into the recursion. (We don't update the current $sizeLimits because that should only
  179. * hold items *outside* of the current folder.)
  180. *
  181. * For every item printed we remove the first item of $sizeLimits are there is no longer room in the output to print
  182. * items that small.
  183. */
  184. $count = 0;
  185. $children = $node->getDirectoryListing();
  186. usort($children, function (Node $a, Node $b) {
  187. return $b->getSize() <=> $a->getSize();
  188. });
  189. foreach ($children as $i => $child) {
  190. if (!$all) {
  191. if (count($sizeLimits) === 0 || $child->getSize() < $sizeLimits[0]) {
  192. return $count;
  193. }
  194. array_shift($sizeLimits);
  195. }
  196. $count += 1;
  197. /** @var Node $child */
  198. $output->writeln("$prefix- " . $child->getName() . ": <info>" . Util::humanFileSize($child->getSize()) . "</info>");
  199. if ($child instanceof Folder) {
  200. $recurseSizeLimits = $sizeLimits;
  201. if (!$all) {
  202. for ($j = 0; $j < count($recurseSizeLimits); $j++) {
  203. if (isset($children[$i + $j + 1])) {
  204. $nextChildSize = $children[$i + $j + 1]->getSize();
  205. if ($nextChildSize > $recurseSizeLimits[0]) {
  206. array_shift($recurseSizeLimits);
  207. $recurseSizeLimits[] = $nextChildSize;
  208. }
  209. }
  210. }
  211. sort($recurseSizeLimits);
  212. }
  213. $recurseCount = $this->outputLargeFilesTree($output, $child, $prefix . " ", $recurseSizeLimits, $all);
  214. $sizeLimits = array_slice($sizeLimits, $recurseCount);
  215. $count += $recurseCount;
  216. }
  217. }
  218. return $count;
  219. }
  220. }