QueueItem.php
| 1 | <?php |
| 2 | |
| 3 | namespace JetBackup\Queue; |
| 4 | |
| 5 | use JetBackup\BackupJob\BackupJob; |
| 6 | use JetBackup\CLI\CLI; |
| 7 | use JetBackup\Data\Engine; |
| 8 | use JetBackup\Data\SleekStore; |
| 9 | use JetBackup\Entities\Util; |
| 10 | use JetBackup\Exception\FieldsValidationException; |
| 11 | use JetBackup\Exception\TaskException; |
| 12 | use JetBackup\Factory; |
| 13 | use JetBackup\JetBackup; |
| 14 | use JetBackup\Log\LogController; |
| 15 | use JetBackup\ResumableTask\ResumableTask; |
| 16 | use SleekDB\Exceptions\InvalidArgumentException; |
| 17 | use SleekDB\Exceptions\IOException; |
| 18 | use SleekDB\QueryBuilder; |
| 19 | |
| 20 | if (!defined( '__JETBACKUP__')) die('Direct access is not allowed'); |
| 21 | |
| 22 | class QueueItem extends Engine { |
| 23 | |
| 24 | const COLLECTION = 'queue'; |
| 25 | |
| 26 | const UNIQUE_ID = 'unique_id'; |
| 27 | const ITEM_ID = 'item_id'; |
| 28 | const CREATED = 'created'; |
| 29 | const STARTED = 'started'; |
| 30 | const ENDED = 'ended'; |
| 31 | const TYPE = 'type'; |
| 32 | const TYPE_NAME = 'type_name'; |
| 33 | const STATUS = 'status'; |
| 34 | const STATUS_NAME = 'status_name'; |
| 35 | const STATUS_TIME = 'status_time'; |
| 36 | const PROGRESS = 'progress'; |
| 37 | const ERRORS = 'errors'; |
| 38 | const ITEM_DATA = 'item_data'; |
| 39 | const LOG_FILE = 'log_file'; |
| 40 | const EXEC_TIME = 'exec_time'; |
| 41 | |
| 42 | const PROGRESS_LAST_STEP = -1; |
| 43 | |
| 44 | private ?Progress $_progress=null; |
| 45 | private ?aQueueItem $_item_data=null; |
| 46 | private ?ResumableTask $_resumable_task=null; |
| 47 | |
| 48 | public function __construct($_id=null) { |
| 49 | parent::__construct(self::COLLECTION); |
| 50 | if($_id) $this->_loadById((int) $_id); |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * @param $unique_id |
| 55 | * |
| 56 | * @return void |
| 57 | * @throws IOException |
| 58 | * @throws InvalidArgumentException |
| 59 | */ |
| 60 | public function loadByUniqueId($unique_id) { |
| 61 | $this->_load([[self::UNIQUE_ID, '=', $unique_id]]); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * @return void |
| 66 | * @throws IOException |
| 67 | * @throws InvalidArgumentException |
| 68 | */ |
| 69 | public function addError(): void { |
| 70 | $this->set(self::ERRORS, $this->getErrors() + 1); |
| 71 | $this->save(); |
| 72 | } |
| 73 | |
| 74 | public function getErrors(): int { |
| 75 | return $this->get(self::ERRORS, 0); |
| 76 | } |
| 77 | |
| 78 | |
| 79 | public function setUniqueId($id) { $this->set(self::UNIQUE_ID, $id); } |
| 80 | public function getUniqueId():string { return $this->get(self::UNIQUE_ID); } |
| 81 | |
| 82 | public function setCreated($value):void { $this->set(self::CREATED, $value); } |
| 83 | public function getCreated() { return $this->get(self::CREATED); } |
| 84 | |
| 85 | public function setStarted(int $value):void { $this->set(self::STARTED, $value); } |
| 86 | public function getStarted():int { return (int) $this->get(self::STARTED, 0); } |
| 87 | |
| 88 | public function setEnded(int $value):void { $this->set(self::ENDED, $value); } |
| 89 | public function getEnded():int { return (int) $this->get(self::ENDED, 0); } |
| 90 | |
| 91 | public function setType($value):void { |
| 92 | $this->set(self::TYPE, $value); |
| 93 | $this->_item_data = null; |
| 94 | } |
| 95 | |
| 96 | public function getType() { return $this->get(self::TYPE); } |
| 97 | |
| 98 | public function setStatus($value):void { |
| 99 | $this->set(self::STATUS, $value); |
| 100 | $this->setStatusTime(time()); |
| 101 | } |
| 102 | public function getStatus() { |
| 103 | return $this->get(self::STATUS); // this will return from memory |
| 104 | } |
| 105 | |
| 106 | public function setProgress(Progress $value):void { |
| 107 | $this->_progress = $value; |
| 108 | $this->set(self::PROGRESS, $value->getData()); |
| 109 | } |
| 110 | |
| 111 | public function getProgress():Progress { |
| 112 | if(!$this->_progress) $this->_progress = new Progress($this->get(self::PROGRESS, [])); |
| 113 | return $this->_progress; |
| 114 | } |
| 115 | |
| 116 | public function updateProgress(string $message, ?int $current_item=null) { |
| 117 | $progress = $this->getProgress(); |
| 118 | $currentProgress = $progress->getCurrentItem(); |
| 119 | |
| 120 | if($current_item !== null) $currentProgress = $current_item <= $progress->getTotalItems() ? $current_item : $progress->getTotalItems(); |
| 121 | if($current_item === self::PROGRESS_LAST_STEP) $currentProgress = $progress->getTotalItems(); |
| 122 | else $currentProgress++; |
| 123 | $progress->setMessage($message); |
| 124 | $progress->setCurrentItem($currentProgress); |
| 125 | $this->save(); |
| 126 | } |
| 127 | |
| 128 | public function updateStatus($status) { |
| 129 | if($this->getStatus() == Queue::STATUS_PENDING) $this->setStarted(time()); |
| 130 | $this->setStatus($status); |
| 131 | $this->setStatusTime(time()); |
| 132 | if($status >= Queue::STATUS_DONE) { |
| 133 | $this->getResumableTask()->delete(); |
| 134 | $this->setEnded(time()); |
| 135 | } |
| 136 | $this->save(); |
| 137 | if (in_array($this->getStatus(), Queue::REQUIRES_CLEANUP)) Util::rm($this->getWorkspace()); |
| 138 | } |
| 139 | |
| 140 | public function setItemData(aQueueItem $value):void { |
| 141 | $this->_item_data = $value; |
| 142 | $this->set(self::ITEM_DATA, $value->getData()); |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * @return QueueItemBackup|QueueItemRestore|QueueItemDownload|QueueItemReindex|QueueItemRetentionCleanup|QueueItemSystem|QueueItemExport|QueueItemExtract|null |
| 147 | */ |
| 148 | public function getItemData():?aQueueItem { |
| 149 | |
| 150 | if(!$this->_item_data) { |
| 151 | $data = (array) $this->get(self::ITEM_DATA, []); |
| 152 | switch ($this->getType()) { |
| 153 | case Queue::QUEUE_TYPE_BACKUP: $this->_item_data = new QueueItemBackup($data); break; |
| 154 | case Queue::QUEUE_TYPE_RESTORE: $this->_item_data = new QueueItemRestore($data); break; |
| 155 | case Queue::QUEUE_TYPE_DOWNLOAD: $this->_item_data = new QueueItemDownload($data); break; |
| 156 | case Queue::QUEUE_TYPE_DOWNLOAD_BACKUP_LOG: $this->_item_data = new QueueItemDownload($data); break; |
| 157 | case Queue::QUEUE_TYPE_REINDEX: $this->_item_data = new QueueItemReindex($data); break; |
| 158 | case Queue::QUEUE_TYPE_RETENTION_CLEANUP: $this->_item_data = new QueueItemRetentionCleanup($data); break; |
| 159 | case Queue::QUEUE_TYPE_SYSTEM: $this->_item_data = new QueueItemSystem($data); break; |
| 160 | case Queue::QUEUE_TYPE_EXPORT: $this->_item_data = new QueueItemExport($data); break; |
| 161 | case Queue::QUEUE_TYPE_EXTRACT: $this->_item_data = new QueueItemExtract($data); break; |
| 162 | |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | return $this->_item_data; |
| 167 | } |
| 168 | |
| 169 | public function setLogFile($value):void { $this->set(self::LOG_FILE, $value); } |
| 170 | public function getLogFile() { return $this->get(self::LOG_FILE); } |
| 171 | |
| 172 | public function setItemId($value):void { $this->set(self::ITEM_ID, $value); } |
| 173 | public function getItemId() { return $this->get(self::ITEM_ID); } |
| 174 | |
| 175 | public function setStatusTime($value):void { $this->set(self::STATUS_TIME, $value); } |
| 176 | public function getStatusTime() { return $this->get(self::STATUS_TIME); } |
| 177 | |
| 178 | public static function db():SleekStore { |
| 179 | return new SleekStore(self::COLLECTION); |
| 180 | } |
| 181 | |
| 182 | public static function query():QueryBuilder { |
| 183 | return self::db()->createQueryBuilder(); |
| 184 | } |
| 185 | |
| 186 | /** |
| 187 | * @throws InvalidArgumentException |
| 188 | */ |
| 189 | public function delete():void { |
| 190 | if(!$this->getId()) return; |
| 191 | $this->getDB()->clearCache(); |
| 192 | $this->getDB()->deleteById($this->getId()); |
| 193 | } |
| 194 | |
| 195 | public function save():void { |
| 196 | if(!$this->getUniqueId()) $this->setUniqueId(Util::generateUniqueId()); |
| 197 | $this->setProgress($this->getProgress()); |
| 198 | $this->setItemData($this->getItemData()); |
| 199 | parent::save(); |
| 200 | } |
| 201 | |
| 202 | public function getResumableTask():ResumableTask { |
| 203 | if(!$this->_resumable_task) $this->_resumable_task = new ResumableTask($this->getUniqueId(), $this->getWorkspace()); |
| 204 | return $this->_resumable_task; |
| 205 | } |
| 206 | |
| 207 | public function getWorkspace(): string { |
| 208 | return Factory::getLocations()->getTempDir() . JetBackup::SEP . $this->getUniqueId(); |
| 209 | } |
| 210 | |
| 211 | public function getAbortFileLocation(): string { |
| 212 | return $this->getWorkspace() . JetBackup::SEP . $this->getUniqueId() . '.abort'; |
| 213 | } |
| 214 | /** |
| 215 | * @return void |
| 216 | * @throws TaskException |
| 217 | */ |
| 218 | public function abort() { |
| 219 | if ($this->getStatus() == Queue::STATUS_PENDING) { |
| 220 | $this->updateStatus(Queue::STATUS_ABORTED); |
| 221 | return; |
| 222 | } |
| 223 | if(!file_exists($this->getWorkspace())) throw new TaskException('Queue item working folder not set yet'); |
| 224 | $this->updateStatus(Queue::STATUS_ABORTED); |
| 225 | touch($this->getAbortFileLocation()); |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * @return void |
| 230 | * @throws InvalidArgumentException |
| 231 | * @throws IOException |
| 232 | */ |
| 233 | public function startOver() { |
| 234 | $this->setUniqueId(''); |
| 235 | $this->setStatus(Queue::STATUS_PENDING); |
| 236 | $progress = $this->getProgress(); |
| 237 | $progress->setMessage('Starting over queue item'); |
| 238 | $progress->resetSub(); |
| 239 | $this->setProgress($progress); |
| 240 | if(file_exists($this->getAbortFileLocation())) unlink($this->getAbortFileLocation()); |
| 241 | $this->save(); |
| 242 | } |
| 243 | |
| 244 | public function getExecutionTime():string { |
| 245 | if ($this->getStarted() == 0) return '00:00:00'; |
| 246 | if ($this->getEnded() > 0) return gmdate('H:i:s', $this->getEnded() - $this->getStarted()); |
| 247 | return gmdate('H:i:s', time() - $this->getStarted()); |
| 248 | } |
| 249 | |
| 250 | public static function prepare():QueueItem { |
| 251 | $item = new QueueItem(); |
| 252 | $item->setStatus(Queue::STATUS_PENDING); |
| 253 | $item->setCreated(time()); |
| 254 | $item->setEngine(Engine::ENGINE_WP); |
| 255 | |
| 256 | $progress = $item->getProgress(); |
| 257 | $progress->setCurrentItem(0); |
| 258 | $progress->setMessage('Waiting for cron...'); |
| 259 | |
| 260 | return $item; |
| 261 | } |
| 262 | |
| 263 | public function getDisplay():array { |
| 264 | return [ |
| 265 | JetBackup::ID_FIELD => $this->getId(), |
| 266 | self::UNIQUE_ID => $this->getUniqueId(), |
| 267 | self::ITEM_ID => $this->getItemId(), |
| 268 | self::TYPE => $this->getType(), |
| 269 | self::TYPE_NAME => Queue::QUEUE_TYPES_NAMES[$this->getType()] ?? 'Unknown', |
| 270 | Engine::ENGINE => $this->getEngine(), |
| 271 | self::CREATED => $this->getCreated(), |
| 272 | self::STARTED => $this->getStarted(), |
| 273 | self::ENDED => $this->getEnded(), |
| 274 | self::ITEM_DATA => $this->getItemData()->getDisplay() ?: [], |
| 275 | self::STATUS => $this->getStatus(), |
| 276 | self::STATUS_NAME => Queue::QUEUE_STATUS_NAMES[$this->getType()][$this->getStatus()] ?? '', |
| 277 | self::STATUS_TIME => $this->getStatusTime(), |
| 278 | self::PROGRESS => $this->getProgress()->getDisplay(), |
| 279 | self::LOG_FILE => $this->getLogFile(), |
| 280 | self::EXEC_TIME => $this->getExecutionTime(), |
| 281 | ]; |
| 282 | } |
| 283 | |
| 284 | public function getDisplayCLI():array { |
| 285 | $statuses = Queue::STATUS_NAMES; |
| 286 | switch($this->getType()) { |
| 287 | case Queue::QUEUE_TYPE_BACKUP: |
| 288 | /** @var QueueItemBackup $item */ |
| 289 | $item = $this->getItemData(); |
| 290 | $statuses += $item->getType() == BackupJob::TYPE_ACCOUNT ? Queue::STATUS_BACKUP_ACCOUNT_NAMES : Queue::STATUS_BACKUP_CONFIG_NAMES; |
| 291 | break; |
| 292 | case Queue::QUEUE_TYPE_RESTORE: $statuses += Queue::STATUS_PRE_RESTORE_NAMES; break; |
| 293 | case Queue::QUEUE_TYPE_DOWNLOAD: $statuses += Queue::STATUS_DOWNLOAD_NAMES; break; |
| 294 | case Queue::QUEUE_TYPE_REINDEX: $statuses += Queue::STATUS_REINDEX_NAMES; break; |
| 295 | case Queue::QUEUE_TYPE_RETENTION_CLEANUP: $statuses += Queue::STATUS_CLEANUP_NAMES; break; |
| 296 | case Queue::QUEUE_TYPE_SYSTEM: $statuses += Queue::STATUS_SYSTEM_NAMES; break; |
| 297 | case Queue::QUEUE_TYPE_EXPORT: $statuses += Queue::STATUS_EXPORT_NAMES; break; |
| 298 | case Queue::QUEUE_TYPE_EXTRACT: $statuses += Queue::STATUS_EXTRACT_NAMES; break; |
| 299 | } |
| 300 | |
| 301 | return [ |
| 302 | 'ID' => $this->getId(), |
| 303 | 'Item ID' => $this->getItemId(), |
| 304 | 'Type' => Queue::QUEUE_TYPES_NAMES[$this->getType()] . ' (' . $this->getType() . ')', |
| 305 | 'Engine' => $this->getEngineName(), |
| 306 | 'Created' => CLI::date($this->getCreated()), |
| 307 | 'Started' => $this->getStarted() ? CLI::date($this->getStarted()) : 'Never', |
| 308 | 'Ended' => $this->getEnded() ? CLI::date($this->getEnded()) : 'Never', |
| 309 | 'Status' => $statuses[$this->getStatus()], |
| 310 | 'Status Time' => $this->getStatusTime() ? CLI::date($this->getStatusTime()) : 'Never', |
| 311 | 'Log File' => $this->getLogFile(), |
| 312 | ]; |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * @throws FieldsValidationException |
| 317 | */ |
| 318 | public function validateFields():void { |
| 319 | if(!$this->getType()) throw new FieldsValidationException("Queue type must be set"); |
| 320 | } |
| 321 | |
| 322 | |
| 323 | } |