diff options
Diffstat (limited to 'library/Toplevelview/ViewConfig.php')
-rw-r--r-- | library/Toplevelview/ViewConfig.php | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/library/Toplevelview/ViewConfig.php b/library/Toplevelview/ViewConfig.php new file mode 100644 index 0000000..dc73b7f --- /dev/null +++ b/library/Toplevelview/ViewConfig.php @@ -0,0 +1,488 @@ +<?php +/* Copyright (C) 2017 Icinga Development Team <info@icinga.com> */ + +namespace Icinga\Module\Toplevelview; + +use Icinga\Application\Benchmark; +use Icinga\Application\Icinga; +use Icinga\Exception\InvalidPropertyException; +use Icinga\Exception\NotImplementedError; +use Icinga\Exception\NotReadableError; +use Icinga\Exception\NotWritableError; +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Toplevelview\Tree\TLVTree; +use Icinga\Util\DirectoryIterator; +use Icinga\Web\Session; + +class ViewConfig +{ + const FORMAT_YAML = 'yml'; + const SESSION_PREFIX = 'toplevelview_view_'; + + protected $config_dir; + + protected $name; + + protected $format; + + protected $file_path; + + protected $view; + + protected $raw; + + protected $tree; + + protected $hasBeenLoaded = false; + protected $hasBeenLoadedFromSession = false; + + /** + * Content of the file + * + * @var string + */ + protected $text; + + protected $textChecksum; + + /** + * @param $name + * @param string|null $config_dir + * @param string $format + * + * @return static + */ + public static function loadByName($name, $config_dir = null, $format = self::FORMAT_YAML) + { + $object = new static; + $object + ->setName($name) + ->setConfigDir($config_dir) + ->setFormat($format) + ->load(); + + return $object; + } + + /** + * @param string|null $config_dir + * @param string $format + * + * @return static[] + */ + public static function loadAll($config_dir = null, $format = self::FORMAT_YAML) + { + $suffix = '.' . $format; + + $config_dir = static::configDir($config_dir); + $directory = new DirectoryIterator($config_dir, $suffix); + + $views = array(); + foreach ($directory as $name => $path) { + if (is_dir($path)) { + // no not descend and ignore directories + continue; + } + $name = basename($name, $suffix); + $views[$name] = static::loadByName($name, $config_dir, $format); + } + + // try to load from session + $len = strlen(self::SESSION_PREFIX); + foreach (static::session()->getAll() as $k => $v) { + if (substr($k, 0, $len) === self::SESSION_PREFIX) { + $name = substr($k, $len); + if (! array_key_exists($name, $views)) { + $views[$name] = static::loadByName($name, $config_dir, $format); + } + } + } + + ksort($views); + + return $views; + } + + /** + * @return string + */ + public function getFilePath() + { + if ($this->file_path === null) { + if ($this->format === null) { + throw new ProgrammingError('format not set!'); + } + $this->file_path = $this->getConfigDir() . DIRECTORY_SEPARATOR . $this->name . '.' . $this->format; + } + return $this->file_path; + } + + /** + * @param string $file_path + * + * @return $this + */ + public function setFilePath($file_path) + { + $this->file_path = $file_path; + return $this; + } + + /** + * @return $this + */ + public function load() + { + if ($this->text === null) { + $this->loadFromSession(); + } + if ($this->text === null) { + $this->loadFromFile(); + } + return $this; + } + + public function loadFromFile() + { + $file_path = $this->getFilePath(); + $this->text = file_get_contents($file_path); + if ($this->text === false) { + throw new NotReadableError('Could not read file %s', $file_path); + } + $this->view = null; + $this->hasBeenLoadedFromSession = false; + $this->hasBeenLoaded = true; + return $this; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + public function getTextChecksum() + { + if ($this->textChecksum === null) { + $this->textChecksum = sha1($this->text); + } + return $this->textChecksum; + } + + /** + * @param $text + * + * @return $this + */ + public function setText($text) + { + $this->text = $text; + $this->textChecksum = null; + $this->raw = null; + $this->tree = null; + return $this; + } + + protected function writeFile($path, $content, $mode = '0660') + { + $existing = file_exists($path); + if (file_put_contents($path, $content) === false) { + throw new NotWritableError('Could not save to %s', $path); + } + + if ($existing === false) { + $octalMode = intval($mode, 8); + if ($mode !== null && false === @chmod($path, $octalMode)) { + throw new NotWritableError('Failed to set file mode "%s" on file "%s"', $mode, $path); + } + } + } + + protected function storeBackup($force = false) + { + $backupDir = $this->getConfigBackupDir(); + + $this->ensureConfigDir($backupDir); + + $ts = (string) time(); + $backup = $backupDir . DIRECTORY_SEPARATOR . $ts . '.' . $this->format; + + if (file_exists($backup)) { + throw new ProgrammingError('History file with timestamp already present: %s', $backup); + } + + $existingFile = $this->getFilePath(); + $oldText = file_get_contents($existingFile); + if ($oldText === false) { + throw new NotReadableError('Could not read file %s', $existingFile); + } + + // only save backup if changed or forced + if ($force || $oldText !== $this->text) { + $this->writeFile($backup, $oldText); + } + } + + public function store() + { + $config_dir = $this->getConfigDir(); + $file_path = $this->getFilePath(); + + $this->ensureConfigDir($config_dir); + + // ensure to save history + if (file_exists($file_path)) { + $this->storeBackup(); + } + + $this->writeFile($file_path, $this->text); + + $this->clearSession(); + return $this; + } + + /** + * @return string + * @throws ProgrammingError When dir is not yet set + */ + public function getConfigDir() + { + if ($this->config_dir === null) { + throw new ProgrammingError('config_dir not yet set!'); + } + return $this->config_dir; + } + + /** + * @return string + */ + public function getConfigBackupDir() + { + return $this->getConfigDir() . DIRECTORY_SEPARATOR . $this->name; + } + + /** + * @param string $config_dir + * + * @return $this + * @throws NotReadableError + */ + public function setConfigDir($config_dir = null) + { + $this->config_dir = static::configDir($config_dir); + $this->file_path = null; + return $this; + } + + protected static function ensureConfigDir($path, $mode = '2770') + { + if (! file_exists($path)) { + if (mkdir($path) !== true) { + throw new NotWritableError( + 'Config path did not exit, and it could not be created: %s', + $path + ); + } + + $octalMode = intval($mode, 8); + if ($mode !== null && false === @chmod($path, $octalMode)) { + throw new NotWritableError('Failed to set file mode "%s" on file "%s"', $mode, $path); + } + } + } + + public static function configDir($config_dir = null) + { + $config_dir_module = Icinga::app()->getModuleManager()->getModule('toplevelview')->getConfigDir(); + if ($config_dir === null) { + $config_dir = $config_dir_module . DIRECTORY_SEPARATOR . 'views'; + } + + static::ensureConfigDir($config_dir_module); + static::ensureConfigDir($config_dir); + + return $config_dir; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + $this->file_path = null; + return $this; + } + + /** + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * @param string $format + * + * @return $this + */ + public function setFormat($format) + { + $this->format = $format; + $this->file_path = null; + return $this; + } + + public function getMeta($key) + { + $this->ensureParsed(); + if ($key !== 'children' && array_key_exists($key, $this->raw)) { + return $this->raw[$key]; + } else { + return null; + } + } + + public function setMeta($key, $value) + { + if ($key === 'children') { + throw new ProgrammingError('You can not edit children here!'); + } + $this->raw[$key] = $value; + return $this; + } + + public function getMetaData() + { + $this->ensureParsed(); + $data = array(); + foreach ($this->raw as $key => $value) { + if ($key !== 'children') { + $data[$key] = $value; + } + } + return $data; + } + + protected function ensureParsed() + { + if ($this->raw === null) { + Benchmark::measure('Begin parsing YAML document'); + + $text = $this->getText(); + if ($text === null) { + // new ViewConfig + $this->raw = array(); + } elseif ($this->format == self::FORMAT_YAML) { + // TODO: use stdClass instead of Array? + $this->raw = yaml_parse($text); + if (! is_array($this->raw)) { + throw new InvalidPropertyException('Could not parse YAML config!'); + } + } else { + throw new NotImplementedError("Unknown format '%s'", $this->format); + } + + Benchmark::measure('Finished parsing YAML document'); + } + } + + /** + * Loads the Tree for this configuration + * + * @return TLVTree + */ + public function getTree() + { + if ($this->tree === null) { + $this->ensureParsed(); + $this->tree = $tree = TLVTree::fromArray($this->raw); + $tree->setConfig($this); + } + return $this->tree; + } + + protected function getSessionVarName() + { + return self::SESSION_PREFIX . $this->name; + } + + public static function session() + { + // TODO: is this CLI safe? + return Session::getSession(); + } + + public function loadFromSession() + { + if (($sessionConfig = $this->session()->get($this->getSessionVarName())) !== null) { + $this->text = $sessionConfig; + $this->hasBeenLoadedFromSession = true; + $this->hasBeenLoaded = true; + } + return $this; + } + + public function clearSession() + { + $this->session()->delete($this->getSessionVarName()); + } + + public function storeToSession() + { + $this->session()->set($this->getSessionVarName(), $this->text); + } + + /** + * @return bool + */ + public function hasBeenLoadedFromSession() + { + return $this->hasBeenLoadedFromSession; + } + + /** + * @return bool + */ + public function hasBeenLoaded() + { + return $this->hasBeenLoaded; + } + + public function __clone() + { + $this->name = null; + $this->raw = null; + $this->tree = null; + + $this->hasBeenLoaded = false; + $this->hasBeenLoadedFromSession = false; + } + + public function delete() + { + $file_path = $this->getFilePath(); + + $this->clearSession(); + + if (file_exists($file_path)) { + $this->storeBackup(true); + unlink($file_path); + } + + return $this; + } +} |