summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Protocol/File
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Protocol/File')
-rw-r--r--library/Icinga/Protocol/File/Exception/FileReaderException.php12
-rw-r--r--library/Icinga/Protocol/File/FileIterator.php81
-rw-r--r--library/Icinga/Protocol/File/FileQuery.php86
-rw-r--r--library/Icinga/Protocol/File/FileReader.php208
-rw-r--r--library/Icinga/Protocol/File/LogFileIterator.php149
5 files changed, 536 insertions, 0 deletions
diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php
new file mode 100644
index 0000000..237352c
--- /dev/null
+++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+namespace Icinga\Protocol\File;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown if a file reader specific error occurs
+ */
+class FileReaderException extends IcingaException
+{
+}
diff --git a/library/Icinga/Protocol/File/FileIterator.php b/library/Icinga/Protocol/File/FileIterator.php
new file mode 100644
index 0000000..64b6600
--- /dev/null
+++ b/library/Icinga/Protocol/File/FileIterator.php
@@ -0,0 +1,81 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Icinga\Util\EnumeratingFilterIterator;
+use Icinga\Util\File;
+
+/**
+ * Class FileIterator
+ *
+ * Iterate over a file, yielding only fields of non-empty lines which match a PCRE expression
+ */
+class FileIterator extends EnumeratingFilterIterator
+{
+ /**
+ * A PCRE string with the fields to extract from the file's lines as named subpatterns
+ *
+ * @var string
+ */
+ protected $fields;
+
+ /**
+ * An associative array of the current line's fields ($field => $value)
+ *
+ * @var array
+ */
+ protected $currentData;
+
+ public function __construct($filename, $fields)
+ {
+ $this->fields = $fields;
+ $f = new File($filename);
+ $f->setFlags(
+ File::DROP_NEW_LINE |
+ File::READ_AHEAD |
+ File::SKIP_EMPTY
+ );
+ parent::__construct($f);
+ }
+
+ /**
+ * Return the current data
+ *
+ * @return array
+ */
+ public function current(): array
+ {
+ return $this->currentData;
+ }
+
+ /**
+ * Accept lines matching the given PCRE pattern
+ *
+ * @return bool
+ *
+ * @throws FileReaderException If PHP failed parsing the PCRE pattern
+ */
+ public function accept(): bool
+ {
+ $data = array();
+ $matched = preg_match(
+ $this->fields,
+ $this->getInnerIterator()->current(),
+ $data
+ );
+
+ if ($matched === false) {
+ throw new FileReaderException('Failed parsing regular expression!');
+ } elseif ($matched === 1) {
+ foreach ($data as $key => $value) {
+ if (is_int($key)) {
+ unset($data[$key]);
+ }
+ }
+ $this->currentData = $data;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/library/Icinga/Protocol/File/FileQuery.php b/library/Icinga/Protocol/File/FileQuery.php
new file mode 100644
index 0000000..504de2e
--- /dev/null
+++ b/library/Icinga/Protocol/File/FileQuery.php
@@ -0,0 +1,86 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Icinga\Data\SimpleQuery;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Class FileQuery
+ *
+ * Query for Datasource Icinga\Protocol\File\FileReader
+ *
+ * @package Icinga\Protocol\File
+ */
+class FileQuery extends SimpleQuery
+{
+ /**
+ * Sort direction
+ *
+ * @var int
+ */
+ private $sortDir;
+
+ /**
+ * Filters to apply on result
+ *
+ * @var array
+ */
+ private $filters = array();
+
+ /**
+ * Nothing to do here
+ */
+ public function applyFilter(Filter $filter)
+ {
+ }
+
+ /**
+ * Sort query result chronological
+ *
+ * @param string $dir Sort direction, 'ASC' or 'DESC' (default)
+ *
+ * @return FileQuery
+ */
+ public function order($field, $direction = null)
+ {
+ $this->sortDir = (
+ $direction === null || strtoupper(trim($direction)) === 'DESC'
+ ) ? self::SORT_DESC : self::SORT_ASC;
+ return $this;
+ }
+
+ /**
+ * Return true if sorting descending, false otherwise
+ *
+ * @return bool
+ */
+ public function sortDesc()
+ {
+ return $this->sortDir === self::SORT_DESC;
+ }
+
+ /**
+ * Add an mandatory filter expression to be applied on this query
+ *
+ * @param string $expression the filter expression to be applied
+ *
+ * @return FileQuery
+ */
+ public function andWhere($expression)
+ {
+ $this->filters[] = $expression;
+ return $this;
+ }
+
+ /**
+ * Get filters currently applied on this query
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+}
diff --git a/library/Icinga/Protocol/File/FileReader.php b/library/Icinga/Protocol/File/FileReader.php
new file mode 100644
index 0000000..a06494c
--- /dev/null
+++ b/library/Icinga/Protocol/File/FileReader.php
@@ -0,0 +1,208 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Countable;
+use ArrayIterator;
+use Icinga\Data\Selectable;
+use Icinga\Data\ConfigObject;
+
+/**
+ * Read file line by line
+ */
+class FileReader implements Selectable, Countable
+{
+ /**
+ * A PCRE string with the fields to extract from the file's lines as named subpatterns
+ *
+ * @var string
+ */
+ protected $fields;
+
+ /**
+ * Name of the target file
+ *
+ * @var string
+ */
+ protected $filename;
+
+ /**
+ * Cache for static::count()
+ *
+ * @var int
+ */
+ protected $count = null;
+
+ /**
+ * Create a new reader
+ *
+ * @param ConfigObject $config
+ *
+ * @throws FileReaderException If a required $config directive (filename or fields) is missing
+ */
+ public function __construct(ConfigObject $config)
+ {
+ foreach (array('filename', 'fields') as $key) {
+ if (isset($config->{$key})) {
+ $this->{$key} = $config->{$key};
+ } else {
+ throw new FileReaderException('The directive `%s\' is required', $key);
+ }
+ }
+ }
+
+ /**
+ * Instantiate a FileIterator object with the target file
+ *
+ * @return FileIterator
+ */
+ public function iterate()
+ {
+ return new LogFileIterator($this->filename, $this->fields);
+ }
+
+ /**
+ * Instantiate a FileQuery object
+ *
+ * @return FileQuery
+ */
+ public function select()
+ {
+ return new FileQuery($this);
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result set using an iterator
+ *
+ * @param FileQuery $query
+ *
+ * @return ArrayIterator
+ */
+ public function query(FileQuery $query)
+ {
+ return new ArrayIterator($this->fetchAll($query));
+ }
+
+ /**
+ * Return the number of available valid lines.
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ if ($this->count === null) {
+ $this->count = iterator_count($this->iterate());
+ }
+ return $this->count;
+ }
+
+ /**
+ * Fetch result as an array of objects
+ *
+ * @param FileQuery $query
+ *
+ * @return array
+ */
+ public function fetchAll(FileQuery $query)
+ {
+ $all = array();
+ foreach ($this->fetchPairs($query) as $index => $value) {
+ $all[$index] = (object) $value;
+ }
+ return $all;
+ }
+
+ /**
+ * Fetch result as a key/value pair array
+ *
+ * @param FileQuery $query
+ *
+ * @return array
+ */
+ public function fetchPairs(FileQuery $query)
+ {
+ $skip = $query->getOffset();
+ $read = $query->getLimit();
+ if ($skip === null) {
+ $skip = 0;
+ }
+ $lines = array();
+ if ($query->sortDesc()) {
+ $count = $this->count();
+ if ($count <= $skip) {
+ return $lines;
+ } elseif ($count < ($skip + $read)) {
+ $read = $count - $skip;
+ $skip = 0;
+ } else {
+ $skip = $count - ($skip + $read);
+ }
+ }
+ foreach ($this->iterate() as $index => $line) {
+ if ($index >= $skip) {
+ if ($index >= $skip + $read) {
+ break;
+ }
+ $lines[] = $line;
+ }
+ }
+ if ($query->sortDesc()) {
+ $lines = array_reverse($lines);
+ }
+ return $lines;
+ }
+
+ /**
+ * Fetch first result row
+ *
+ * @param FileQuery $query
+ *
+ * @return object
+ */
+ public function fetchRow(FileQuery $query)
+ {
+ $all = $this->fetchAll($query);
+ if (isset($all[0])) {
+ return $all[0];
+ }
+ return null;
+ }
+
+ /**
+ * Fetch first result column
+ *
+ * @param FileQuery $query
+ *
+ * @return array
+ */
+ public function fetchColumn(FileQuery $query)
+ {
+ $column = array();
+ foreach ($this->fetchPairs($query) as $pair) {
+ foreach ($pair as $value) {
+ $column[] = $value;
+ break;
+ }
+ }
+ return $column;
+ }
+
+ /**
+ * Fetch first column value from first result row
+ *
+ * @param FileQuery $query
+ *
+ * @return mixed
+ */
+ public function fetchOne(FileQuery $query)
+ {
+ $pairs = $this->fetchPairs($query);
+ if (isset($pairs[0])) {
+ foreach ($pairs[0] as $value) {
+ return $value;
+ }
+ }
+ return null;
+ }
+}
diff --git a/library/Icinga/Protocol/File/LogFileIterator.php b/library/Icinga/Protocol/File/LogFileIterator.php
new file mode 100644
index 0000000..67a4d99
--- /dev/null
+++ b/library/Icinga/Protocol/File/LogFileIterator.php
@@ -0,0 +1,149 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Icinga\Exception\IcingaException;
+use SplFileObject;
+use Iterator;
+
+/**
+ * Iterate over a log file, yielding the regex fields of the log messages
+ */
+class LogFileIterator implements Iterator
+{
+ /**
+ * Log file
+ *
+ * @var SplFileObject
+ */
+ protected $file;
+
+ /**
+ * A PCRE string with the fields to extract
+ * from the log messages as named subpatterns
+ *
+ * @var string
+ */
+ protected $fields;
+
+ /**
+ * Value for static::current()
+ *
+ * @var array
+ */
+ protected $current;
+
+ /**
+ * Index for static::key()
+ *
+ * @var int
+ */
+ protected $index;
+
+ /**
+ * Value for static::valid()
+ *
+ * @var boolean
+ */
+ protected $valid;
+
+ /**
+ * @var string
+ */
+ protected $next = null;
+
+ /**
+ * @param string $filename The log file's name
+ * @param string $fields A PCRE string with the fields to extract
+ * from the log messages as named subpatterns
+ */
+ public function __construct($filename, $fields)
+ {
+ $this->file = new SplFileObject($filename);
+ $this->file->setFlags(
+ SplFileObject::DROP_NEW_LINE |
+ SplFileObject::READ_AHEAD
+ );
+ $this->fields = $fields;
+ }
+
+ public function rewind(): void
+ {
+ $this->file->rewind();
+ $this->index = 0;
+ $this->nextMessage();
+ }
+
+ public function next(): void
+ {
+ $this->file->next();
+ ++$this->index;
+ $this->nextMessage();
+ }
+
+ public function current(): array
+ {
+ return $this->current;
+ }
+
+ public function key(): int
+ {
+ return $this->index;
+ }
+
+ public function valid(): bool
+ {
+ return $this->valid;
+ }
+
+ protected function nextMessage()
+ {
+ $message = $this->next === null ? array() : array($this->next);
+ $this->valid = null;
+ while ($this->file->valid()) {
+ if (false === ($res = preg_match(
+ $this->fields,
+ $current = $this->file->current()
+ ))) {
+ throw new IcingaException('Failed at preg_match()');
+ }
+ if (empty($message)) {
+ if ($res === 1) {
+ $message[] = $current;
+ }
+ } elseif ($res === 1) {
+ $this->next = $current;
+ $this->valid = true;
+ break;
+ } else {
+ $message[] = $current;
+ }
+
+ $this->file->next();
+ }
+ if ($this->valid === null) {
+ $this->next = null;
+ $this->valid = ! empty($message);
+ }
+
+ if ($this->valid) {
+ while (! empty($message)) {
+ $matches = array();
+ if (false === ($res = preg_match(
+ $this->fields,
+ implode(PHP_EOL, $message),
+ $matches
+ ))) {
+ throw new IcingaException('Failed at preg_match()');
+ }
+ if ($res === 1) {
+ $this->current = $matches;
+ return;
+ }
+ array_pop($message);
+ }
+ $this->valid = false;
+ }
+ }
+}