conn = new Redis(); // Afterwards connect to server $socketType = $parameters['socket_type']; if ($socketType == 'unix') $success = $this->conn->connect($parameters['socket']); elseif ($socketType == 'tcp') $success = $this->conn->connect($parameters['host'], $parameters['port'], $parameters['timeout']); else $success = false; // If unsuccessful, return false if (!$success) throw new TasksException("Could not construct RedisTaskStorage. Failed to connect."); // Otherwise attempt authentication, if needed if (isset($parameters['password']) && !$this->conn->auth($parameters['password'])) throw new TasksException("Could not construct RedisTaskStorage. Authentication failure."); // And select the DB index $this->conn->select($parameters['db_index']); } catch (RedisException $e) { throw new TasksException("Could not construct RedisTaskStorage. RedisException thrown: '" . $e->getMessage() . "'"); } } /** * @inheritDoc * @throws TasksException */ public function addTask(Task $task): bool { // Check if the task doesn't exist yet $taskId = $task->getId(); // Query the index $isMember = $this->conn->sIsMember($this->indexSet, $taskId); if ($isMember) throw new TasksException("Could not add Task to TaskStorage. Task '$taskId' already exists."); // Serialize the task and save it $taskData = serialize($task); // Register the task $this->conn->sAdd($this->indexSet, $taskId); if ($task->getStatus() !== Task::COMPLETED && $task->getStatus() !== Task::CANCELLED) $this->conn->sAdd($this->unfinishedSet, $taskId); // And create a hash for it if ($this->conn->hSet($this->key_prefix . $taskId, 'data', $taskData) === FALSE) return false; return true; } /** * @inheritDoc */ public function readTasks(bool $noIncludeDone = false): array { // First fetch an array of all tasks in the set if ($noIncludeDone) $taskList = $this->conn->sMembers($this->unfinishedSet); else $taskList = $this->conn->sMembers($this->indexSet); // Go over each taskId and fetch the specific task $tasks = []; foreach ($taskList as $taskId) $tasks[] = unserialize($this->conn->hGet($this->key_prefix . $taskId, 'data')); return $tasks; } /** * @inheritDoc */ public function getTaskById(string $identifier): Task { // Query the index $isMember = $this->conn->sIsMember($this->indexSet, $identifier); if (!$isMember) throw new TasksException("Could not get task by ID. Task not found."); // Fetch the task /** @var Task $task */ $task = unserialize($this->conn->hGet($this->key_prefix . $identifier, 'data')); // Return the task return $task; } /** * @inheritDoc */ public function modifyTask(Task $task): bool { // First get the task ID $taskId = $task->getId(); // Check if it exists $isMember = $this->conn->sIsMember($this->indexSet, $taskId); if (!$isMember) throw new TasksException("Could not modify task. Task '$taskId' does not exists."); // Fire the TaskModifyEvent /** @var TaskModifyEvent $event */ $event = Events::fireEvent(new TaskModifyEvent(), $task); if ($event->isCancelled()) { Logger::log("Did not modify task. Cancelled by taskModifyEvent."); return false; } // And write the data $taskData = serialize($event->getTask()); if ($this->conn->hSet($this->key_prefix . $taskId, 'data', $taskData) === FALSE) return false; // Modify the unfinished set if ($this->conn->sIsMember($this->unfinishedSet, $taskId) && ($task->getStatus() === Task::COMPLETED || $task->getStatus() === Task::CANCELLED )) $this->conn->sRem($this->unfinishedSet, $taskId); elseif (!$this->conn->sIsMember($this->unfinishedSet, $taskId) && $task->getStatus() !== Task::COMPLETED && $task->getStatus() !== Task::CANCELLED) $this->conn->sAdd($this->unfinishedSet, $taskId); return true; } /** * @inheritDoc * @throws TasksException */ public function deleteTask(Task $task): bool { // First get the task ID $taskId = $task->getId(); // Check if it exists $isMember = $this->conn->sIsMember($this->indexSet, $taskId); if (!$isMember) throw new TasksException("Could not delete task. Task '$taskId' does not exists."); // Delete the task from the index $this->conn->sRem($this->indexSet, $taskId); // And remove the task from the unfinishedSet if ($this->conn->sIsMember($this->unfinishedSet, $taskId)) $this->conn->sRem($this->unfinishedSet, $taskId); // Delete the task itself if ($this->conn->del($this->key_prefix . $taskId) > 0) return true; return false; } /** * @inheritDoc */ public function writeTaskOutput(Task $task, string $output, string $errors, int $statusCode): bool { // First get the task ID $taskId = $task->getId(); // Check if the task exists $isMember = $this->conn->sIsMember($this->indexSet, $taskId); if (!$isMember) throw new TasksException("Could not write task output. Task '$taskId' not found."); // Prepare the data $contents = ['taskId' => $taskId, 'output' => $output, 'errors' => $errors, 'statusCode' => $statusCode]; // Determine the attempt number $attempt = $this->conn->hIncrBy($this->key_prefix . $taskId, 'taskOutputAttempts', 1); // Then write this output if ($this->conn->hSet($this->key_prefix . $taskId, 'output' . $attempt, serialize($contents)) === FALSE) return false; return true; } /** * @inheritDoc */ public function writePostOutput(Task $task, string $output, string $errors, int $statusCode): bool { // First get the task ID $taskId = $task->getId(); // Check if the task exists $isMember = $this->conn->sIsMember($this->indexSet, $taskId); if (!$isMember) throw new TasksException("Could not write post output. Task '$taskId' not found."); // Prepare the data $contents = ['taskId' => $taskId, 'output' => $output, 'errors' => $errors, 'statusCode' => $statusCode]; // Determine the attempt number $attempt = $this->conn->hIncrBy($this->key_prefix . $taskId, 'taskPostAttempts', 1); // Then write this output if ($this->conn->hSet($this->key_prefix . $taskId, 'postOutput' . $attempt, serialize($contents)) === FALSE) return false; return true; } /** * @inheritDoc */ public function readTaskOutput(Task $task, int $attempt = 0): ?array { // First get the task ID $taskId = $task->getId(); // If a nothing in particular is requested, fetch the latest and select that as the attempt if ($attempt === 0) $attempt = $this->conn->hGet($this->key_prefix . $taskId, 'taskOutputAttempts'); // If -1 is requested, fetch all if ($attempt === -1) { // Get amount of attempts $totalAttempts = $this->conn->hGet($this->key_prefix . $taskId, 'taskOutputAttempts'); $output = []; for ($i=1;$i<=$totalAttempts;$i++) { // Check if this output exists if (!$this->conn->hExists($this->key_prefix . $taskId, 'output' . $i)) $output[] = null; // Load and convert the data $data = $this->conn->hGet($this->key_prefix . $taskId, 'output' . $i); $data = unserialize($data); $output[] = ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']]; } if (!empty($output)) return $output; } // If a specific one is requested, fetch that one else { // Check if this output exists if (!$this->conn->hExists($this->key_prefix . $taskId, 'output' . $attempt)) return null; // Load and convert the data $data = $this->conn->hGet($this->key_prefix . $taskId, 'output' . $attempt); $data = unserialize($data); return ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']]; } return null; } /** * @inheritDoc */ public function readPostOutput(Task $task, int $attempt = 0): ?array { // First get the task ID $taskId = $task->getId(); // If a nothing in particular is requested, fetch the latest and select that as the attempt if ($attempt === 0) $attempt = $this->conn->hGet($this->key_prefix . $taskId, 'taskPostAttempts'); // If -1 is requested, fetch all if ($attempt === -1) { // Get amount of attempts $totalAttempts = $this->conn->hGet($this->key_prefix . $taskId, 'taskPostAttempts'); $output = []; for ($i=1;$i<=$totalAttempts;$i++) { // Check if this output exists if (!$this->conn->hExists($this->key_prefix . $taskId, 'postOutput' . $i)) $output[] = null; // Load and convert the data $data = $this->conn->hGet($this->key_prefix . $taskId, 'postOutput' . $i); $data = unserialize($data); $output[] = ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']]; } if (!empty($output)) return $output; } // If a specific one is requested, fetch that one else { // Check if this output exists if (!$this->conn->hExists($this->key_prefix . $taskId, 'postOutput' . $attempt)) return null; // Load and convert the data $data = $this->conn->hGet($this->key_prefix . $taskId, 'postOutput' . $attempt); $data = unserialize($data); return ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']]; } return null; } /** * @inheritDoc */ public function reset(): bool { // Clear the current db return $this->conn->flushDB(); } }