vzlogger/backend/lib/model/channel.php
2010-06-14 07:41:41 +02:00

206 lines
No EOL
6.5 KiB
PHP

<?php
/*
* Copyright (c) 2010 by Justin Otherguy <justin@justinotherguy.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License (either version 2 or
* version 3) as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* For more information on the GPL, please go to:
* http://www.gnu.org/copyleft/gpl.html
*/
interface ChannelInterface {
// data management
public function addData($data);
public function getData($from = NULL, $to = NULL, $groupBy = NULL);
public function reset();
// some statistical functions
public function getMin($from = NULL, $to = NULL);
public function getMax($from = NULL, $to = NULL);
public function getAverage($from = NULL, $to = NULL);
}
abstract class Channel extends DatabaseObject implements ChannelInterface {
const table = 'channels';
public function delete() {
$this->reset(); // delete all data if database doesn't support foreign keys
parent::delete();
}
/*
* deletes all data from database
*/
public function reset($from = NULL, $to = NULL) {
$this->dbh->execute('DELETE FROM data WHERE channel_id = ' . (int) $this->id) . $this->buildTimeFilter($from, $to);
}
/*
* add a new data to the database
*/
public function addData($data) {
$sql = 'INSERT INTO data (channel_id, timestamp, value) VALUES(' . $this->dbh->escape($this) . ', ' . $this->dbh->escape($data['timestamp']) . ', ' . $this->dbh->escape($data['value']) . ')';
$this->dbh->execute($sql);
}
/*
* This function retrieve data from the database. If desired it groups it into packages ($groupBy parameter)
*
* @return array() Array with timestamps => value (sorted by timestamp from newest to oldest)
* @param $groupBy determines how readings are grouped. Possible values are: year, month, day, hour, minute or an integer for the desired size of the returned array
*/
public function getData($from = NULL, $to = NULL, $groupBy = NULL) {
$ts = 'FROM_UNIXTIME(timestamp/1000)'; // just for saving space
switch ($groupBy) {
case 'year':
$sqlGroupBy = 'YEAR(' . $ts . ')';
break;
case 'month':
$sqlGroupBy = 'YEAR(' . $ts . '), MONTH(' . $ts . ')';
break;
case 'week':
$sqlGroupBy = 'YEAR(' . $ts . '), WEEKOFYEAR(' . $ts . ')';
break;
case 'day':
$sqlGroupBy = 'YEAR(' . $ts . '), DAYOFYEAR(' . $ts . ')';
break;
case 'hour':
$sqlGroupBy = 'YEAR(' . $ts . '), DAYOFYEAR(' . $ts . '), HOUR(' . $ts . ')';
break;
case 'minute':
$sqlGroupBy = 'YEAR(' . $ts . '), DAYOFYEAR(' . $ts . '), HOUR(' . $ts . '), MINUTE(' . $ts . ')';
break;
default:
if (is_numeric($groupBy)) {
$groupBy = (int) $groupBy;
}
$sqlGroupBy = false;
}
$sql = 'SELECT';
$sql .= ($sqlGroupBy === false) ? ' timestamp, value' : ' MAX(timestamp) AS timestamp, SUM(value) AS value, COUNT(timestamp) AS count';
$sql .= ' FROM data WHERE channel_id = ' . (int) $this->id . $this->buildFilterTime($from, $to);
if ($sqlGroupBy !== false) {
$sql .= ' GROUP BY ' . $sqlGroupBy;
}
$sql .= ' ORDER BY timestamp DESC';
$result = $this->dbh->query($sql);
$totalCount = $result->count();
if (is_int($groupBy) && $groupBy < $totalCount) { // return $groupBy values
$packageSize = floor($totalCount / $groupBy);
$packageCount = $groupBy;
}
else { // return all values or grouped by year, month, week...
$packageSize = 1;
$packageCount = $totalCount;
}
$packages = array();
$reading = $result->rewind();
for ($i = 1; $i <= $packageCount; $i++) {
$package = array('timestamp' => $reading['timestamp'], // last timestamp in package
'value' => (float) $reading['value'], // sum of values
'count' => ($sqlGroupBy === false) ? 1 : $reading['count']); // total count of values or pulses in the package
while ($package['count'] < $packageSize) {
$reading = $result->next();
$package['value'] += $reading['value'];
$package['count']++;
}
$packages[] = $package;
$reading = $result->next();
}
return array_reverse($packages); // start with oldest ts and ends with newest ts (reverse array order due to descending order in sql statement)
}
/*
* simple self::getByFilter() wrapper
*/
static public function getByType($type) {
return self::getByFilter(array('type' => $type));
}
/*
* create new channel instance by given database query result
*/
final static protected function factory($object) {
if (!class_exists($object['type']) || !is_subclass_of($object['type'], 'Channel')) {
throw new InvalidArgumentException('\'' . $object['type'] . '\' is not a valid channel type');
}
// code duplication from DatabaseObject::factory()
if (!isset(self::$instances[self::table])) {
self::$instances[self::table] = array();
}
if (!isset(self::$instances[self::table][$object['id']])) {
self::$instances[self::table][$object['id']] = new $object['type']($object); // create singleton instance of database object
}
return self::$instances[self::table][$object['id']]; // return singleton instance of database object
}
public function __get($key) {
if ($key == 'unit') { // TODO ugly code
return static::unit;
}
else {
return parent::__get($key);
}
}
/*
* build simple timeframe filter
*/
static protected function buildFilterTime($from = NULL, $to = NULL) {
$sql = '';
if (is_int($to) && $to <= time() * 1000) {
$sql .= ' && timestamp < ' . $to;
}
if (is_int($from) && $from > 0) {
$sql .= ' && timestamp > ' . $from;
}
return $sql;
}
static protected function buildFilterQuery($filters, $conjunction, $columns = array('id')) {
$sql = 'SELECT ' . self::table . '.* FROM ' . self::table;
// join groups
if (preg_match('/^group\.([a-z_]+)$/', $filters)) {
$sql .= ' LEFT JOIN channels_in_groups ON channels_in_groups.channel_id = ' . self::table . '.id';
$sql .= ' LEFT JOIN groups ON groups.id = channels_in_groups.group_id';
$filters = preg_replace('/^group\.([a-z_]+)$/', 'groups.$1', $filters);
}
$sql .= self::buildFilterCondition($filters, $conjunction);
return $sql;
}
}