diff options
Diffstat (limited to 'lib/private/Async/Model')
-rw-r--r-- | lib/private/Async/Model/Block.php | 142 | ||||
-rw-r--r-- | lib/private/Async/Model/BlockInterface.php | 183 | ||||
-rw-r--r-- | lib/private/Async/Model/SessionInterface.php | 83 |
3 files changed, 408 insertions, 0 deletions
diff --git a/lib/private/Async/Model/Block.php b/lib/private/Async/Model/Block.php new file mode 100644 index 00000000000..9c66ec13ad8 --- /dev/null +++ b/lib/private/Async/Model/Block.php @@ -0,0 +1,142 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Async\Model; + +use OC\Async\Enum\BlockType; +use OCP\AppFramework\Db\Entity; +use OCP\Async\Enum\ProcessExecutionTime; +use OCP\Async\Enum\BlockStatus; + +/** + * @method void setToken(string $token) + * @method string getToken() + * @method void setSessionToken(string $sessionToken) + * @method string getSessionToken() + * @method void setType(int $type) + * @method int getType() + * @method void setCode(string $code) + * @method string getCode() + * @method void setParams(array $params) + * @method ?array getParams() + * @method void setDataset(array $dataset) + * @method ?array getDataset() + * @method void setMetadata(array $metadata) + * @method ?array getMetadata() + * @method void setLinks(array $params) + * @method ?array getLinks() + * @method void setOrig(array $orig) + * @method ?array getOrig() + * @method void setResult(array $result) + * @method ?array getResult() + * @method void setStatus(int $status) + * @method int getStatus() + * @method void setExecutionTime(int $status) + * @method int getExecutionTime() + * @method void setLockToken(string $lockToken) + * @method string getLockToken() + * @method void setCreation(int $creation) + * @method int getCreation() + * @method void setLastRun(int $lastRun) + * @method int getLastRun() + * @method void setNextRun(int $nextRun) + * @method int getNextRun() + * @psalm-suppress PropertyNotSetInConstructor + */ +class Block extends Entity { + protected string $token = ''; + protected string $sessionToken = ''; + protected int $type = 0; + protected string $code = ''; + protected ?array $params = []; + protected ?array $dataset = []; + protected ?array $metadata = []; + protected ?array $links = []; + protected ?array $orig = []; + protected ?array $result = []; + protected int $status = 0; + protected int $executionTime = 0; + protected string $lockToken = ''; + protected int $creation = 0; + protected int $lastRun = 0; + protected int $nextRun = 0; + + public function __construct() { + $this->addType('token', 'string'); + $this->addType('sessionToken', 'string'); + $this->addType('type', 'integer'); + $this->addType('code', 'string'); + $this->addType('params', 'json'); + $this->addType('dataset', 'json'); + $this->addType('metadata', 'json'); + $this->addType('links', 'json'); + $this->addType('orig', 'json'); + $this->addType('result', 'json'); + $this->addType('status', 'integer'); + $this->addType('executionTime', 'integer'); + $this->addType('lockToken', 'string'); + $this->addType('creation', 'integer'); + $this->addType('lastRun', 'integer'); + $this->addType('nextRun', 'integer'); + } + + public function setBlockType(BlockType $type): void { + $this->setType($type->value); + } + + public function getBlockType(): BlockType { + return BlockType::from($this->getType()); + } + + public function setBlockStatus(BlockStatus $status): void { + $this->setStatus($status->value); + } + + public function getBlockStatus(): BlockStatus { + return BlockStatus::from($this->getStatus()); + } + + public function setProcessExecutionTime(ProcessExecutionTime $time): void { + $this->setExecutionTime($time->value); + } + + public function getProcessExecutionTime(): ProcessExecutionTime { + return ProcessExecutionTime::from($this->getExecutionTime()); + } + + public function addMetadata(array $metadata): self { + $metadata = array_merge($this->metadata, $metadata); + $this->setMetadata($metadata); + return $this; + } + + public function addMetadataEntry(string $key, string|int|array|bool $value): self { + $metadata = $this->metadata; + $metadata[$key] = $value; + $this->setMetadata($metadata); + return $this; + } + + public function replay(bool $now = false): void { + $count = $this->getMetadata()['_replay'] ?? 0; + $next = time() + floor(6 ** ($count + 1)); // calculate delay based on count of retry + $count = min(++$count, 5); // limit to 6^6 seconds (13h) + + if ($now) { + $next = time(); + } + + $this->addMetadataEntry('_replay', $count); + $this->setNextRun($next); + } + + public function getReplayCount(): int { + return $this->getMetadata()['_replay'] ?? 0; + } +} diff --git a/lib/private/Async/Model/BlockInterface.php b/lib/private/Async/Model/BlockInterface.php new file mode 100644 index 00000000000..36ba58194f7 --- /dev/null +++ b/lib/private/Async/Model/BlockInterface.php @@ -0,0 +1,183 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Async\Model; + +use OC\Async\AsyncManager; +use OC\Async\IBlockInterface; +use OCP\Async\Enum\ProcessExecutionTime; +use OCP\Async\Enum\BlockStatus; + +class BlockInterface implements IBlockInterface, \JsonSerializable { + private string $id = ''; + private string $name = ''; + private bool $blocker = false; + private bool $replayable = false; + + /** @var string[] */ + private array $require = []; + private int $delay = 0; + private ?ProcessExecutionTime $executionTime = null; + private array $dataset = []; + + public function __construct( + private readonly ?AsyncManager $asyncManager, + private readonly Block $block, + ) { + $this->import($block->getMetadata()['_iface'] ?? []); + } + + public function getBlock(): Block { + return $this->block; + } + + public function getToken(): string { + return $this->block->getToken(); + } + + public function getResult(): ?array { + if ($this->block->getBlockStatus() !== BlockStatus::SUCCESS) { + return null; + } + + return $this->block->getResult()['result']; + } + + public function getError(): ?array { + if ($this->block->getBlockStatus() !== BlockStatus::ERROR) { + return null; + } + + return $this->block->getResult()['error']; + } + + public function getStatus(): BlockStatus { + return $this->block->getBlockStatus(); + } + + public function id(string $id): self { + $this->id = strtoupper($id); + return $this; + } + + public function getId(): string { + return $this->id; + } + public function name(string $name): self { + $this->name = $name; + return $this; + } + + public function getName(): string { + return $this->name; + } + + public function blocker(bool $blocker = true): self { + $this->blocker = $blocker; + return $this; + } + + public function isBlocker(): bool { + return $this->blocker; + } + + public function replayable(bool $replayable = true): self { + $this->replayable = $replayable; + return $this; + } + + public function isReplayable(): bool { + return $this->replayable; + } + + public function require(string $id): self { + $this->require[] = strtoupper($id); + return $this; + } + + public function getRequire(): array { + return $this->require; + } + + public function delay(int $delay): self { + $this->delay = $delay; + return $this; + } + + public function getDelay(): int { + return $this->delay; + } + + public function getExecutionTime(): ?ProcessExecutionTime { + return $this->executionTime; + } + + /** + * @param array<array> $dataset + * + * @return $this + */ + public function dataset(array $dataset): self { + $this->dataset = $dataset; + return $this; + } + + public function getDataset(): array { + return $this->dataset; + } + +// public function getReplayCount(): int { +// return $this->get +// } + /** + * only available during the creation of the session + * + * @return $this + */ + public function async(ProcessExecutionTime $time = ProcessExecutionTime::NOW): self { + $this->asyncManager?->async($time); + return $this; + } + + public function import(array $data): void { + $this->token = $data['token'] ?? ''; + $this->id($data['id'] ?? ''); + $this->name($data['name'] ?? ''); + $this->delay($data['delay'] ?? 0); + $this->require = $data['require'] ?? []; + $this->blocker($data['blocker'] ?? false); + $this->replayable($data['replayable'] ?? false); + } + + public function jsonSerialize(): array { + return [ + 'token' => $this->getToken(), + 'id' => $this->getId(), + 'name' => $this->getName(), + 'require' => $this->getRequire(), + 'delay' => $this->getDelay(), + 'blocker' => $this->isBlocker(), + 'replayable' => $this->isReplayable(), + ]; + } + + /** + * @param Block[] $processes + * + * @return BlockInterface[] + */ + public static function asBlockInterfaces(array $processes): array { + $interfaces = []; + foreach ($processes as $process) { + $interfaces[] = new BlockInterface(null, $process); + } + + return $interfaces; + } +} diff --git a/lib/private/Async/Model/SessionInterface.php b/lib/private/Async/Model/SessionInterface.php new file mode 100644 index 00000000000..c481fc545f6 --- /dev/null +++ b/lib/private/Async/Model/SessionInterface.php @@ -0,0 +1,83 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OC\Async\Model; + +use OC\Async\ISessionInterface; +use OCP\Async\Enum\BlockStatus; + +class SessionInterface implements ISessionInterface { + public function __construct( + /** @var BlockInterface[] */ + private array $interfaces, + ) { + } + + public function getAll(): array { + return $this->interfaces; + } + + public function byToken(string $token): ?BlockInterface { + foreach ($this->interfaces as $iface) { + if ($iface->getToken() === $token) { + return $iface; + } + } + + return null; + } + + public function byId(string $id): ?BlockInterface { + foreach($this->interfaces as $iface) { + if ($iface->getId() === $id) { + return $iface; + } + } + + return null; + } + + /** + * return a global status of the session based on the status of each process + * - if one entry is still at ::PREP stage, we return ::PREP + * - if one ::BLOCKER, returns ::BLOCKER. + * - if all ::SUCCESS, returns ::SUCCESS, else ignored. + * - if all ::ERROR+::SUCCESS, returns ::ERROR, else ignored. + * - session status is the biggest value between all left process status (::STANDBY, ::RUNNING) + */ + public function getGlobalStatus(): BlockStatus { + $current = -1; + $groupedStatus = []; + foreach($this->interfaces as $iface) { + // returns ::PREP if one process is still ::PREP + if ($iface->getStatus() === BlockStatus::PREP) { + return BlockStatus::PREP; + } + // returns ::BLOCKER if one process is marked ::BLOCKER + if ($iface->getStatus() === BlockStatus::BLOCKER) { + return BlockStatus::BLOCKER; + } + // we keep trace if process is marked as ::ERROR or ::SUCCESS + // if not, we keep the highest value + if (in_array($iface->getStatus(), [BlockStatus::ERROR, BlockStatus::SUCCESS], true)) { + $groupedStatus[$iface->getStatus()->value] = true; + } else { + $current = max($current, $iface->getStatus()->value); + } + } + + // in case the all interface were ::ERROR or ::SUCCESS, we check + // if there was at least one ::ERROR. if none we return ::SUCCESS + if ($current === -1) { + return (array_key_exists(BlockStatus::ERROR->value, $groupedStatus)) ? BlockStatus::ERROR : BlockStatus::SUCCESS; + } + + return BlockStatus::from($current); + } +} |