aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Async/Model
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Async/Model')
-rw-r--r--lib/private/Async/Model/Block.php142
-rw-r--r--lib/private/Async/Model/BlockInterface.php183
-rw-r--r--lib/private/Async/Model/SessionInterface.php83
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);
+ }
+}