/** * Force deletion on a cached resource. * * @param {string} $key Identifier of target cache. * @param {?string} $hash Target revision to delete, all revisions will be deleted if omitted. */ public static function delete($key, $hash = '*') { $res = self::resolve($key, $hash); // Skip the delete if nothing is found. if ($res === null) { return; } if ($res->isFile()) { // Remove target revision(s). if (!$res->isWritable()) { Log::warning('Target file is not writable, deletion skipped.'); } else { $path = $res->getRealPath(); unlink($path); $path = dirname($path); // Remove the directory if empty. $res = new \RecursiveDirectoryIterator($path, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS); if ($res->isDir() && !$res->hasChildren()) { rmdir($path); } } } else { if ($res->isDir()) { $cacheDirectory = $res->getRealPath(); foreach ($res as $file) { if ($file->isFile()) { unlink($file->getRealPath()); } else { if ($file->isDir()) { rmdir($file->getRealPath()); } } } rmdir($cacheDirectory); } } }
Database::unlockTables(true); Database::rollback(); Log::debug('No more jobs to do, suicide.'); die; } $processContents = (array) ContentDecoder::json($process[Node::FIELD_VIRTUAL], 1); unset($process[Node::FIELD_VIRTUAL]); $process += $processContents; unset($processContents); $res = Database::query('UPDATE `' . FRAMEWORK_COLLECTION_PROCESS . '` SET `pid` = ? WHERE `id` = ? AND `pid` IS NULL LIMIT 1', [getmypid(), $process['id']]); // Commit transaction Database::unlockTables(true); Database::commit(); if ($res->rowCount() < 1) { Log::warning('Unable to update process pid, worker exits.'); die; } else { $process['pid'] = getmypid(); $process[Node::FIELD_COLLECTION] = FRAMEWORK_COLLECTION_PROCESS; } // Check if $env specified in option if (@$_SERVER['env']) { $_SERVER['env'] = ContentDecoder::json($_SERVER['env'], 1); } // More debug logs Log::debug("Execute process: {$process['command']}"); // Spawn process and retrieve the pid $proc = false; do { $proc = proc_open($process['command'], array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'e')), $pipes, null, @$_SERVER['env']);
/** * Process next task with POST data. */ public function process() { // note: Some tasks can work without post data, but request method must be POST. if (!$this->__isSuperUser && $this->__request->method() != 'post') { $this->__response->status(405); // Method not allowed return; } // WorkInstance if (!$this->identity()) { $this->__response->status(404); // WorkInstance not found return; } // TaskInstance $instance = $this->nextTask(); if (!$instance) { $this->__response->status(404); // TaskInstance not foudn return; } // release mutable lock for work instance initialization. $this->_immutable = false; // remove tasks to prevent unwanted change. $tasks = $this->tasks; unset($this->tasks); // creates $this->dataStore if not yet. if (empty($this->dataStore)) { $this->dataStore = array(); } unset($this->lastError); // immutable marker to prevent direct modifications to the internal data. $this->_immutable = true; // note: Send bayeux message to groups with permission to this task. $userGroups = $instance->userGroups(); try { // Note: Since $this->dataStore is an array, it is mutable itself. $promise = $instance->process(); } catch (\Exception $e) { Log::warning('Task process exception.', array_filter(array('message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTrace()))); $lastError = array('message' => $this->__response->__($e->getMessage()), 'code' => $e->getCode()); // note: Failure on Headless tasks will revert to previous task. if (@$instance->type == 'Headless') { $deferred = new Deferred(); $deferred->reject($lastError['message'], $lastError['code']); $promise = $deferred->promise(); unset($deferred); } else { $this->_immutable = false; $this->lastError = $lastError; } unset($lastError); } $this->_immutable = false; $result = array(); $saveFunc = function () use(&$result) { unset($this->timestamp); $this->save($result); }; if (isset($promise)) { // note: rejection here means revert to previous task $promise->fail(function ($error, $code = null) use($instance, $tasks) { $this->lastError = array_filter(array('message' => $error, 'code' => $code)); // revert to previous task $prevTask = array_search($instance->identity(), array_map(invokes('identity'), $tasks)); $prevTask = @$tasks[$prevTask - 1]; // fallback to the first task if (!$prevTask) { $prevTask = reset($tasks); } $this->nextTask = util::packUuid($prevTask->identity()); }); // note: resolution always advances to next task $promise->done(function () use($instance, $tasks) { $nextTask = array_search($instance->identity(), array_map(invokes('identity'), $tasks)); $nextTask = @$tasks[$nextTask + 1]; if ($nextTask) { $this->nextTask = util::packUuid($nextTask->identity()); } else { $this->state = static::STATE_CLOSE; $this->nextTask = null; } }); // note: controller script must call resolve() or reject() to make this happen. $promise->always($saveFunc); } else { $saveFunc(); } unset($saveFunc); // note: Merge user groups before and after task processing if ($this->nextTask) { $userGroups = array_unique(array_merge($userGroups, $this->nextTask()->userGroups())); } foreach ($userGroups as $userGroup) { Bayeux::sendMessage("/group/{$userGroup}", array('action' => 'update', '@collection' => 'WorkInstance', 'timestamp' => $this->timestamp)); } if (@$result['error']) { $this->__response->status(500); return $result; } else { // note; User might no longer has access to the updated work instance. if ($this->data()) { $this->__response->status(200); return $this; } else { $this->__response->status(204); } } }