CapabilitiesManager.php 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-only
  7. */
  8. namespace OC;
  9. use OCP\AppFramework\QueryException;
  10. use OCP\Capabilities\ICapability;
  11. use OCP\Capabilities\IInitialStateExcludedCapability;
  12. use OCP\Capabilities\IPublicCapability;
  13. use Psr\Log\LoggerInterface;
  14. class CapabilitiesManager {
  15. /**
  16. * Anything above 0.1s to load the capabilities of an app qualifies for bad code
  17. * and should be cached within the app.
  18. */
  19. public const ACCEPTABLE_LOADING_TIME = 0.1;
  20. /** @var \Closure[] */
  21. private $capabilities = [];
  22. /** @var LoggerInterface */
  23. private $logger;
  24. public function __construct(LoggerInterface $logger) {
  25. $this->logger = $logger;
  26. }
  27. /**
  28. * Get an array of al the capabilities that are registered at this manager
  29. *
  30. * @param bool $public get public capabilities only
  31. * @throws \InvalidArgumentException
  32. * @return array<string, mixed>
  33. */
  34. public function getCapabilities(bool $public = false, bool $initialState = false) : array {
  35. $capabilities = [];
  36. foreach ($this->capabilities as $capability) {
  37. try {
  38. $c = $capability();
  39. } catch (QueryException $e) {
  40. $this->logger->error('CapabilitiesManager', [
  41. 'exception' => $e,
  42. ]);
  43. continue;
  44. }
  45. if ($c instanceof ICapability) {
  46. if (!$public || $c instanceof IPublicCapability) {
  47. if ($initialState && ($c instanceof IInitialStateExcludedCapability)) {
  48. // Remove less important capabilities information that are expensive to query
  49. // that we would otherwise inject to every page load
  50. continue;
  51. }
  52. $startTime = microtime(true);
  53. $capabilities = array_replace_recursive($capabilities, $c->getCapabilities());
  54. $endTime = microtime(true);
  55. $timeSpent = $endTime - $startTime;
  56. if ($timeSpent > self::ACCEPTABLE_LOADING_TIME) {
  57. $logLevel = match (true) {
  58. $timeSpent > self::ACCEPTABLE_LOADING_TIME * 16 => \OCP\ILogger::FATAL,
  59. $timeSpent > self::ACCEPTABLE_LOADING_TIME * 8 => \OCP\ILogger::ERROR,
  60. $timeSpent > self::ACCEPTABLE_LOADING_TIME * 4 => \OCP\ILogger::WARN,
  61. $timeSpent > self::ACCEPTABLE_LOADING_TIME * 2 => \OCP\ILogger::INFO,
  62. default => \OCP\ILogger::DEBUG,
  63. };
  64. $this->logger->log(
  65. $logLevel,
  66. 'Capabilities of {className} took {duration} seconds to generate.',
  67. [
  68. 'className' => get_class($c),
  69. 'duration' => round($timeSpent, 2),
  70. ]
  71. );
  72. }
  73. }
  74. } else {
  75. throw new \InvalidArgumentException('The given Capability (' . get_class($c) . ') does not implement the ICapability interface');
  76. }
  77. }
  78. return $capabilities;
  79. }
  80. /**
  81. * In order to improve lazy loading a closure can be registered which will be called in case
  82. * capabilities are actually requested
  83. *
  84. * $callable has to return an instance of OCP\Capabilities\ICapability
  85. *
  86. * @param \Closure $callable
  87. */
  88. public function registerCapability(\Closure $callable) {
  89. $this->capabilities[] = $callable;
  90. }
  91. }