, parameters:array}> */ public function availableBackends(): array { return $this->factory()->describeBackends(); } /** * Add a target: connect, create the repository there (or adopt an existing one made with this * installation's key), then save it. * * @param array $options * @return array{target: Target, created: bool} */ public function add(string $name, string $backend, string $auth, array $options, string $basePath): array { if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9 _.-]{0,63}$/', $name)) { throw new \InvalidArgumentException('Name: letters, digits, space, _ . - (max 64)'); } try { $this->mapper->findByName($name); throw new \InvalidArgumentException("A target named '$name' already exists"); } catch (\OCP\AppFramework\Db\DoesNotExistException) { } $keys = $this->keys->keyRing(); $storageBackend = $this->buildBackend($backend, $auth, $options, $basePath); if ($storageBackend->exists('config')) { $repo = Repository::openWithKey($storageBackend, $keys); $created = false; } else { $repo = Repository::initWithKey($storageBackend, $keys, $this->keys->wrappedKey()); $created = true; } foreach ($this->mapper->findAll() as $existing) { if ($existing->getRepositoryId() === $repo->id()) { throw new \InvalidArgumentException("This repository is already registered as '{$existing->getName()}'"); } } $target = new Target(); $target->setName($name); $target->setBackend($backend); $target->setAuth($auth); $target->setOptions($this->crypto->encrypt(json_encode($options, JSON_THROW_ON_ERROR))); $target->setBasePath(trim($basePath, '/')); $target->setRepositoryId($repo->id()); $target->setCreatedAt(time()); return ['target' => $this->mapper->insert($target), 'created' => $created]; } /** @return list */ public function list(): array { return $this->mapper->findAll(); } public function get(string $nameOrId): Target { return ctype_digit($nameOrId) ? $this->mapper->find((int)$nameOrId) : $this->mapper->findByName($nameOrId); } /** Remove the target from NG Backup. The backups on the target itself are not touched. */ public function remove(Target $target): void { $this->mapper->delete($target); } public function backend(Target $target): IBackend { return $this->buildBackend($target->getBackend(), $target->getAuth(), $this->options($target), $target->getBasePath()); } public function repository(Target $target): Repository { $repo = Repository::openWithKey($this->backend($target), $this->keys->keyRing(), $this->indexCache); if ($target->getRepositoryId() !== null && $repo->id() !== $target->getRepositoryId()) { throw new \RuntimeException('The target now contains a different repository than when it was added'); } return $repo; } /** @return array */ public function options(Target $target): array { return json_decode($this->crypto->decrypt($target->getOptions()), true, 512, JSON_THROW_ON_ERROR); } private function buildBackend(string $backend, string $auth, array $options, string $basePath): StorageBackend { return new StorageBackend($this->factory()->create($backend, $auth, $options), $basePath); } private function factory(): ExternalStorageFactory { // Load files_external's classes without enabling the app (its state stays as the admin set it). if (!class_exists(\OCA\Files_External\Service\BackendService::class)) { $this->appManager->loadApp('files_external'); } return new ExternalStorageFactory(Server::get(\OCA\Files_External\Service\BackendService::class)); } }