diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index f1b063817c..3b7449ea00 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -120,12 +120,18 @@ public function checkAndEnableCODEServer() { $appConfig->setAppValue('wopi_url', $new_wopi_url); $appConfig->setAppValue('disable_certificate_verification', 'yes'); + /** @var DiscoveryService $discoveryService */ $discoveryService = $this->getContainer()->get(DiscoveryService::class); + /** @var CapabilitiesService $capabilitiesService */ $capabilitiesService = $this->getContainer()->get(CapabilitiesService::class); $discoveryService->resetCache(); $capabilitiesService->resetCache(); - $capabilitiesService->fetchFromRemote(); + try { + $capabilitiesService->fetch(); + $discoveryService->fetch(); + } catch (\Exception $e) { + } } } } diff --git a/lib/Backgroundjobs/ObtainCapabilities.php b/lib/Backgroundjobs/ObtainCapabilities.php index ff62e75183..3156d77b93 100644 --- a/lib/Backgroundjobs/ObtainCapabilities.php +++ b/lib/Backgroundjobs/ObtainCapabilities.php @@ -7,21 +7,34 @@ namespace OCA\Richdocuments\Backgroundjobs; use OCA\Richdocuments\Service\CapabilitiesService; +use OCA\Richdocuments\Service\DiscoveryService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; +use Psr\Log\LoggerInterface; class ObtainCapabilities extends TimedJob { - /** @var CapabilitiesService */ - private $capabilitiesService; - - public function __construct(ITimeFactory $time, CapabilitiesService $capabilitiesService) { + public function __construct( + ITimeFactory $time, + private LoggerInterface $logger, + private CapabilitiesService $capabilitiesService, + private DiscoveryService $discoveryService, + ) { parent::__construct($time); - $this->capabilitiesService = $capabilitiesService; $this->setInterval(60 * 60); } protected function run($argument) { - $this->capabilitiesService->fetchFromRemote(); + try { + $this->capabilitiesService->fetch(); + } catch (\Exception $e) { + $this->logger->error('Failed to fetch capabilities: ' . $e->getMessage(), ['exception' => $e]); + } + + try { + $this->discoveryService->fetch(); + } catch (\Exception $e) { + $this->logger->error('Failed to fetch discovery: ' . $e->getMessage(), ['exception' => $e]); + } } } diff --git a/lib/Service/CachedRequestService.php b/lib/Service/CachedRequestService.php new file mode 100644 index 0000000000..87f616d9cf --- /dev/null +++ b/lib/Service/CachedRequestService.php @@ -0,0 +1,163 @@ +cacheFactory->createDistributed('richdocuments'); + + if ($cached = $cache->get($this->cacheKey)) { + return $cached; + } + + $folder = $this->getAppDataFolder(); + if ($folder->fileExists($this->cacheKey)) { + $value = $folder->getFile($this->cacheKey)->getContent(); + $cache->set($this->cacheKey, $value, 3600); + return $value; + } + + return null; + } + + public function getLastUpdate(): ?int { + $folder = $this->getAppDataFolder(); + if (!$folder->fileExists($this->cacheKey)) { + return null; + } + return $folder->getFile($this->cacheKey)->getMTime(); + } + + /** + * Cached value will be kept if the request fails + * + * @return string + * @throws \Exception + */ + final public function fetch(): string { + $cache = $this->cacheFactory->createDistributed('richdocuments'); + $client = $this->clientService->newClient(); + + $startTime = microtime(true); + $response = $this->sendRequest($client); + $duration = round(((microtime(true) - $startTime)), 3); + $this->logger->info('Fetched remote endpoint from ' . $this->cacheKey . ' in ' . $duration . ' seconds'); + + $this->getAppDataFolder()->newFile($this->cacheKey, $response); + $cache->set($this->cacheKey, $response); + return $response; + } + + public function resetCache(): void { + $cache = $this->cacheFactory->createDistributed('richdocuments'); + $cache->remove($this->cacheKey); + $folder = $this->getAppDataFolder(); + if ($folder->fileExists($this->cacheKey)) { + $folder->getFile($this->cacheKey)->delete(); + } + } + + protected function getDefaultRequestOptions(): array { + $options = [ + 'timeout' => 5, + 'nextcloud' => [ + 'allow_local_address' => true + ] + ]; + + if ($this->appConfig->getValueString('richdocuments', 'disable_certificate_verification') === 'yes') { + $options['verify'] = false; + } + + if ($this->isProxyStarting()) { + $options['timeout'] = 180; + } + + return $options; + } + + private function getAppDataFolder(): ISimpleFolder { + $appData = $this->appDataFactory->get(Application::APPNAME); + try { + $folder = $appData->getFolder('remoteData'); + } catch (NotFoundException $e) { + $folder = $appData->newFolder('remoteData'); + } + return $folder; + } + + /** + * @return boolean indicating if proxy.php is in initialize or false otherwise + */ + private function isProxyStarting(): bool { + $url = $this->appConfig->getValueString('richdocuments', 'wopi_url', ''); + $usesProxy = false; + $proxyPos = strrpos($url, 'proxy.php'); + if ($proxyPos !== false) { + $usesProxy = true; + } + + if ($usesProxy === true) { + $statusUrl = substr($url, 0, $proxyPos); + $statusUrl = $statusUrl . 'proxy.php?status'; + + $client = $this->clientService->newClient(); + $options = ['timeout' => 5, 'nextcloud' => ['allow_local_address' => true]]; + + if ($this->appConfig->getValueString('richdocuments', 'disable_certificate_verification') === 'yes') { + $options['verify'] = false; + } + + try { + $response = $client->get($statusUrl, $options); + + if ($response->getStatusCode() === 200) { + $body = json_decode($response->getBody(), true); + + if ($body['status'] === 'starting' + || $body['status'] === 'stopped' + || $body['status'] === 'restarting') { + return true; + } + } + } catch (\Exception $e) { + // ignore + } + } + + return false; + } +} diff --git a/lib/Service/CapabilitiesService.php b/lib/Service/CapabilitiesService.php index 1bf4652733..f61cf0f0c4 100644 --- a/lib/Service/CapabilitiesService.php +++ b/lib/Service/CapabilitiesService.php @@ -8,52 +8,42 @@ use OCA\Richdocuments\AppInfo\Application; use OCP\App\IAppManager; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; -use OCP\ICache; +use OCP\IAppConfig; use OCP\ICacheFactory; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; -class CapabilitiesService { - /** @var IConfig */ - private $config; - /** @var IClientService */ - private $clientService; - /** @var ICache */ - private $cache; - /** @var IAppManager */ - private $appManager; - /** @var IL10N */ - private $l10n; - /** @var LoggerInterface */ - private $logger; - - /** @var array */ - private $capabilities; - - - public function __construct(IConfig $config, IClientService $clientService, ICacheFactory $cacheFactory, IAppManager $appManager, IL10N $l10n, LoggerInterface $logger) { - $this->config = $config; - $this->clientService = $clientService; - $this->cache = $cacheFactory->createDistributed('richdocuments'); - $this->appManager = $appManager; - $this->l10n = $l10n; - $this->logger = $logger; +class CapabilitiesService extends CachedRequestService { + + private ?array $capabilities = null; + + public function __construct( + private IClientService $clientService, + private ICacheFactory $cacheFactory, + private IAppDataFactory $appDataFactory, + private IAppConfig $appConfig, + private LoggerInterface $logger, + private IConfig $config, + private IAppManager $appManager, + private IL10N $l10n, + ) { + parent::__construct( + $this->clientService, + $this->cacheFactory, + $this->appDataFactory, + $this->appConfig, + $this->logger, + 'capabilities', + ); } public function getCapabilities() { if (!$this->capabilities) { - $this->capabilities = $this->cache->get('capabilities'); - } - - $isARM64 = php_uname('m') === 'aarch64'; - $CODEAppID = $isARM64 ? 'richdocumentscode_arm64' : 'richdocumentscode'; - $isCODEInstalled = $this->appManager->isEnabledForUser($CODEAppID); - $isCODEEnabled = strpos($this->config->getAppValue('richdocuments', 'wopi_url'), 'proxy.php?req=') !== false; - $shouldRecheckCODECapabilities = $isCODEInstalled && $isCODEEnabled && ($this->capabilities === null || count($this->capabilities) === 0); - if ($this->capabilities === null || $shouldRecheckCODECapabilities) { - $this->fetchFromRemote(); + $this->capabilities = $this->getParsedCapabilities(); } if (!is_array($this->capabilities)) { @@ -119,10 +109,6 @@ public function hasOtherOOXMLApps(): bool { return false; } - public function resetCache(): void { - $this->cache->remove('capabilities'); - } - public function getCapabilitiesEndpoint(): ?string { $remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url'); if ($remoteHost === '') { @@ -131,43 +117,13 @@ public function getCapabilitiesEndpoint(): ?string { return rtrim($remoteHost, '/') . '/hosting/capabilities'; } - public function fetchFromRemote($throw = false): void { - if (!$this->getCapabilitiesEndpoint()) { - return; - } - - $client = $this->clientService->newClient(); - $options = ['timeout' => 45, 'nextcloud' => ['allow_local_address' => true]]; - - if ($this->config->getAppValue('richdocuments', 'disable_certificate_verification') === 'yes') { - $options['verify'] = false; - } - - try { - $startTime = microtime(true); - $response = $client->get($this->getCapabilitiesEndpoint(), $options); - $duration = round(((microtime(true) - $startTime)), 3); - $this->logger->info('Fetched capabilities endpoint from ' . $this->getCapabilitiesEndpoint(). ' in ' . $duration . ' seconds'); - $responseBody = $response->getBody(); - $capabilities = \json_decode($responseBody, true); - - if (!is_array($capabilities)) { - $capabilities = []; - } - } catch (\Exception $e) { - $this->logger->error('Failed to fetch the Collabora capabilities endpoint: ' . $e->getMessage(), [ 'exception' => $e ]); - if ($throw) { - throw $e; - } - $capabilities = []; - } - - $this->capabilities = $capabilities; - $ttl = 3600; - if (count($capabilities) === 0) { - $ttl = 60; - } + protected function sendRequest(IClient $client): string { + $response = $client->get($this->getCapabilitiesEndpoint(), $this->getDefaultRequestOptions()); + return (string)$response->getBody(); + } - $this->cache->set('capabilities', $capabilities, $ttl); + private function getParsedCapabilities() { + $response = $this->get(); + return json_decode($response, true); } } diff --git a/lib/Service/ConnectivityService.php b/lib/Service/ConnectivityService.php index 8e687593fe..113c7dfff6 100644 --- a/lib/Service/ConnectivityService.php +++ b/lib/Service/ConnectivityService.php @@ -25,7 +25,7 @@ public function __construct( */ public function testDiscovery(OutputInterface $output): void { $this->discoveryService->resetCache(); - $this->discoveryService->fetchFromRemote(); + $this->discoveryService->fetch(); $output->writeln('✓ Fetched /hosting/discovery endpoint'); $this->parser->getUrlSrcValue('application/vnd.openxmlformats-officedocument.wordprocessingml.document'); @@ -38,7 +38,7 @@ public function testDiscovery(OutputInterface $output): void { public function testCapabilities(OutputInterface $output): void { $this->capabilitiesService->resetCache(); - $this->capabilitiesService->fetchFromRemote(true); + $this->capabilitiesService->fetch(true); $output->writeln('✓ Fetched /hosting/capabilities endpoint'); if ($this->capabilitiesService->getCapabilities() === []) { diff --git a/lib/Service/DiscoveryService.php b/lib/Service/DiscoveryService.php index 3d85055ecf..7cd10be775 100644 --- a/lib/Service/DiscoveryService.php +++ b/lib/Service/DiscoveryService.php @@ -28,120 +28,41 @@ namespace OCA\Richdocuments\Service; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; -use OCP\Http\Client\IResponse; -use OCP\ICache; +use OCP\IAppConfig; use OCP\ICacheFactory; use OCP\IConfig; use Psr\Log\LoggerInterface; -class DiscoveryService { - private IClientService $clientService; - private ICache $cache; - private IConfig $config; - private LoggerInterface $logger; - - private ?string $discovery = null; - +class DiscoveryService extends CachedRequestService { public function __construct( - IClientService $clientService, - ICacheFactory $cacheFactory, - IConfig $config, - LoggerInterface $logger + private IClientService $clientService, + private ICacheFactory $cacheFactory, + private IAppDataFactory $appDataFactory, + private IAppConfig $appConfig, + private LoggerInterface $logger, + private IConfig $config, ) { - $this->clientService = $clientService; - $this->cache = $cacheFactory->createDistributed('richdocuments'); - $this->config = $config; - $this->logger = $logger; + parent::__construct( + $this->clientService, + $this->cacheFactory, + $this->appDataFactory, + $this->appConfig, + $this->logger, + 'discovery', + ); } - public function get(): ?string { - if ($this->discovery) { - return $this->discovery; - } - - $this->discovery = $this->cache->get('discovery'); - if (!$this->discovery) { - $response = $this->fetchFromRemote(); - $responseBody = $response->getBody(); - $this->discovery = $responseBody; - $this->cache->set('discovery', $this->discovery, 3600); - } - - return $this->discovery; + protected function sendRequest(IClient $client): string { + $response = $client->get($this->getDiscoveryEndpoint(), $this->getDefaultRequestOptions()); + return (string)$response->getBody(); } - /** - * @throws \Exception if a network error occurs - */ - public function fetchFromRemote(): IResponse { + private function getDiscoveryEndpoint(): string { $remoteHost = $this->config->getAppValue('richdocuments', 'wopi_url'); - $wopiDiscovery = rtrim($remoteHost, '/') . '/hosting/discovery'; - - $client = $this->clientService->newClient(); - $options = ['timeout' => 45, 'nextcloud' => ['allow_local_address' => true]]; - - if ($this->config->getAppValue('richdocuments', 'disable_certificate_verification') === 'yes') { - $options['verify'] = false; - } - - if ($this->isProxyStarting($wopiDiscovery)) { - $options['timeout'] = 180; - } - - $startTime = microtime(true); - $response = $client->get($wopiDiscovery, $options); - $duration = round(((microtime(true) - $startTime)), 3); - $this->logger->info('Fetched discovery endpoint from ' . $wopiDiscovery . ' in ' . $duration . ' seconds'); - - return $response; - } - - public function resetCache(): void { - $this->cache->remove('discovery'); - $this->discovery = null; - } - - /** - * @return boolean indicating if proxy.php is in initialize or false otherwise - */ - private function isProxyStarting(string $url): bool { - $usesProxy = false; - $proxyPos = strrpos($url, 'proxy.php'); - if ($proxyPos === false) { - $usesProxy = false; - } else { - $usesProxy = true; - } - - if ($usesProxy === true) { - $statusUrl = substr($url, 0, $proxyPos); - $statusUrl = $statusUrl . 'proxy.php?status'; - - $client = $this->clientService->newClient(); - $options = ['timeout' => 5, 'nextcloud' => ['allow_local_address' => true]]; - - if ($this->config->getAppValue('richdocuments', 'disable_certificate_verification') === 'yes') { - $options['verify'] = false; - } - - try { - $response = $client->get($statusUrl, $options); - - if ($response->getStatusCode() === 200) { - $body = json_decode($response->getBody(), true); - - if ($body['status'] === 'starting' - || $body['status'] === 'stopped' - || $body['status'] === 'restarting') { - return true; - } - } - } catch (\Exception $e) { - // ignore - } - } + return rtrim($remoteHost, '/') . '/hosting/discovery'; - return false; } }