From 1ff2bc4f3c694b7c76df8e57056c51ca39a23a34 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 20 Jan 2015 18:07:24 +0100 Subject: config module structure completed. Many other fixes. Hidden pw field support. --- inc/configmodule.inc.php | 419 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 inc/configmodule.inc.php (limited to 'inc/configmodule.inc.php') diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php new file mode 100644 index 00000000..5eff92ba --- /dev/null +++ b/inc/configmodule.inc.php @@ -0,0 +1,419 @@ + $title, + 'description' => $description, + 'group' => $group, + 'unique' => $unique, + 'sortOrder' => $sortOrder, + 'moduleClass' => $moduleClass, + 'wizardClass' => $wizardClass + ); + } + + /** + * Get fresh instance of ConfigModule subclass for given module type. + * + * @param string $moduleType name of module type + * @return \ConfigModule module instance + */ + public static function getInstance($moduleType) + { + self::loadDb(); + if (!isset(self::$moduleTypes[$moduleType])) + return false; + return new self::$moduleTypes[$moduleType]['moduleClass']; + } + + /** + * Get module instance from id. + * + * @param int $moduleId module id to get + * @return ConfigModule The requested module from DB, or false on error + */ + public static function get($moduleId) + { + $ret = Database::queryFirst("SELECT title, moduletype, filepath, contents, version FROM configtgz_module " + . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleId)); + if ($ret === false) + return false; + $instance = self::getInstance($ret['moduletype']); + if ($instance === false) + return false; + $instance->currentVersion = $ret['version']; + $instance->moduleArchive = $ret['filepath']; + $instance->moduleData = json_decode($ret['contents'], true); + $instance->moduleId = $moduleId; + $instance->moduleTitle = $ret['title']; + return $instance; + } + + /** + * Get the module version. + * + * @return int module version + */ + protected abstract function moduleVersion(); + + /** + * Validate the module's configuration. + * + * @return boolean ok or not + */ + protected abstract function validateConfig(); + + /** + * Set module specific data. + * + * @param string $key key, name or id of data being set + * @param mixed $value Module specific data + * @return boolean true if data was successfully set, false otherwise (i.e. invalid data being set) + */ + public abstract function setData($key, $value); + + /** + * Module specific version of generate. + * + * @param string $tgz File name of tgz module to write final output to + * @return array|boolean true if generation is completed immediately, + * a task struct if some task needs to be run for generation, + * false on error + */ + protected abstract function generateInternal($tgz, $parent); + + private final function createFileName() + { + return CONFIG_TGZ_LIST_DIR . '/modules/' + . $this->moduleType() . '_id-' . $this->moduleId . '__' . mt_rand() . '-' . time() . '.tgz'; + } + + /** + * Get module id (in db) + * + * @return int id + */ + public final function id() + { + return $this->moduleId; + } + + /** + * Get module archive file name. + * + * @return string tgz file absolute path + */ + public final function archive() + { + return $this->moduleArchive; + } + + /** + * Get the module type. + * + * @return string module type + */ + public final function moduleType() + { + $name = get_class($this); + if ($name === false) + Util::traceError('ConfigModule::moduleType: get_class($this) returned false!'); + // ConfigModule_* + if (!preg_match('/^ConfigModule_(\w+)$/', $name, $out)) + Util::traceError('ConfigModule::moduleType: get_class($this) returned "' . $name . '"'); + return $out[1]; + } + + /** + * Insert this config module into DB. Only + * valid if the object was created using the creating constructor, + * not if the instance was created using a database entry (static get method). + * + * @param string $title display name of the module + * @return boolean true if inserted successfully, false if module config is invalid + */ + public final function insert($title) + { + if ($this->moduleId !== 0) + Util::traceError('ConfigModule::insert called when moduleId != 0'); + if (!$this->validateConfig()) + return false; + $this->moduleTitle = $title; + // Insert + Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents, version, status) " + . " VALUES (:title, :type, '', :contents, :version, :status)", array( + 'title' => $title, + 'type' => $this->moduleType(), + 'contents' => json_encode($this->moduleData), + 'version' => 0, + 'status' => 'MISSING' + )); + $this->moduleId = Database::lastInsertId(); + if (!is_numeric($this->moduleId)) + Util::traceError('Inserting new config module into DB did not yield a numeric insert id'); + $this->moduleArchive = $this->createFileName(); + Database::exec("UPDATE configtgz_module SET filepath = :path WHERE moduleid = :moduleid LIMIT 1", array( + 'path' => $this->moduleArchive, + 'moduleid' => $this->moduleId + )); + return true; + } + + /** + * Generate the module's tgz, don't wait for completion. + * Updating the database etc. will happen later through a callback. + * + * @param boolean $deleteOnError if true, the db entry will be deleted if generation failed + * @param int $timeoutMs maximum time in milliseconds we wait for completion + * @return string|boolean task id if deferred generation was started, + * true if generation succeeded (without using a task or within $timeoutMs) + * false on error + */ + public final function generate($deleteOnError, $parent = NULL, $timeoutMs = 0) + { + if ($this->moduleId === 0 || $this->moduleTitle === false) + Util::traceError('ConfigModule::generateAsync called on uninitialized/uninserted module!'); + $tmpTgz = '/tmp/bwlp-id-' . $this->moduleId . '_' . mt_rand() . '_' . time() . '.tgz'; + $ret = $this->generateInternal($tmpTgz, $parent); + // Wait for generation if requested + if ($timeoutMs > 0 && isset($ret['id']) && !Taskmanager::isFinished($ret)) + $ret = Taskmanager::waitComplete($ret, $timeoutMs); + if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === TASK_FINISHED)) + return $this->markUpdated($tmpTgz); // Finished + if (!is_array($ret) || !isset($ret['id']) || Taskmanager::isFailed($ret)) { + if (is_array($ret)) // Failed + Taskmanager::addErrorMessage($ret); + if ($deleteOnError) + $this->delete(); + else + $this->markFailed(); + return false; + } + // Still running, add callback + TaskmanagerCallback::addCallback($ret, 'cbConfModCreated', array( + 'moduleid' => $this->moduleId, + 'deleteOnError' => $deleteOnError, + 'tmpTgz' => $tmpTgz + )); + return $ret['id']; + } + + /** + * Delete the module. + */ + public final function delete() + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::delete called with invalid module id!'); + $ret = Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array( + 'moduleid' => $id + )) !== false; + if ($ret !== false) { + if ($this->moduleArchive) + Taskmanager::submit('DeleteFile', array('file' => $this->moduleArchive), true); + $this->moduleId = 0; + $this->moduleData = false; + $this->moduleTitle = false; + $this->moduleArchive = false; + } + } + + private final function markUpdated($tmpTgz) + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::markUpdated called with invalid module id!'); + if ($this->moduleArchive === false) + $this->moduleArchive = $this->createFileName(); + // Move file + if ($tmpTgz === false) { + if (!file_exists($this->moduleArchive)) { + EventLog::failure('ConfigModule::markUpdated for "' . $this->moduleTitle . '" called with no tmpTgz and no existing tgz!'); + $this->markFailed(); + return false; + } + } else { + $task = Taskmanager::submit('MoveFile', array( + 'source' => $tmpTgz, + 'destination' => $this->moduleArchive + )); + $task = Taskmanager::waitComplete($task, 5000); + if (Taskmanager::isFailed($task) || !Taskmanager::isFinished($task)) { + if (!API && !AJAX) + Taskmanager::addErrorMessage($task); + else + EventLog::failure('Could not move ' . $tmpTgz . ' to ' . $this->moduleArchive . ' while generating "' . $this->moduleTitle . '"'); + $this->markFailed(); + return false; + } + } + // Update DB entry + return Database::exec("UPDATE configtgz_module SET filepath = :filename, version = :version, status = 'OK' WHERE moduleid = :id LIMIT 1", array( + 'id' => $this->moduleId, + 'filename' => $this->moduleArchive, + 'version' => $this->moduleVersion() + )) !== false; + } + + private final function markFailed() + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::markFailed called with invalid module id!'); + if ($this->moduleArchive === false) + $this->moduleArchive = $this->createFileName(); + if (!file_exists($this->moduleArchive)) + $status = 'MISSING'; + else + $status = 'OUTDATED'; + return Database::exec("UPDATE configtgz_module SET filepath = :filename, status = :status WHERE moduleid = :id LIMIT 1", array( + 'id' => $this->moduleId, + 'filename' => $this->moduleArchive, + 'status' => $status + )) !== false; + } + + ################# Callbacks ############## + + /** + * Event callback for when the server ip changed. + * Override this if you need to handle this, otherwise + * the base implementation does nothing. + */ + public function event_serverIpChanged() + { + // Do::Nothing() + } + + ##################### STATIC CALLBACKS ##################### + + /** + * Will be called if the server's IP address changes. The event will be propagated + * to all config module classes so action can be taken if appropriate. + */ + public static function serverIpChanged() + { + self::loadDb(); + foreach (self::$moduleTypes as $module) { + $instance = new $module['moduleClass']; + $instance->event_serverIpChanged(); + } + } + + /** + * Called when (re)generating a config module failed, so we can + * update the status in the DB and add a server log entry. + * + * @param array $task + * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz' + */ + public static function generateFailed($task, $args) + { + if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) { + EventLog::warning('Ignoring generateFailed event as it has no moduleid assigned.'); + return; + } + $module = self::get($args['moduleid']); + if ($module === false) { + EventLog::warning('generateFailed callback for module id ' . $args['moduleid'] . ', but no instance could be generated.'); + return; + } + if (isset($task['data']['error'])) + $error = $task['data']['error']; + elseif (isset($task['data']['messages'])) + $error = $task['data']['messages']; + else + $error = ''; + EventLog::failure("Generating module '" . $module->moduleTitle . "' failed.", $error); + if ($args['deleteOnError']) + $module->delete(); + else + $module->markFailed(); + } + + /** + * (Re)generating a config module succeeded. Update db entry. + * + * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz' + */ + public static function generateSucceeded($args) + { + if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) { + EventLog::warning('Ignoring generateSucceeded event as it has no moduleid assigned.'); + return; + } + $module = self::get($args['moduleid']); + if ($module === false) { + EventLog::warning('generateSucceeded callback for module id ' . $args['moduleid'] . ', but no instance could be generated.'); + return; + } + if (isset($args['tmpTgz'])) + $module->markUpdated($args['tmpTgz']); + else + $module->markUpdated(false); + } + +} -- cgit v1.2.3-55-g7522