zoneminder/web/includes/Monitor.php

557 lines
20 KiB
PHP
Raw Normal View History

2015-09-18 03:29:36 +08:00
<?php
namespace ZM;
require_once('database.php');
require_once('Server.php');
require_once('Object.php');
2019-09-20 02:55:27 +08:00
require_once('Control.php');
require_once('Storage.php');
2015-09-18 03:29:36 +08:00
class Monitor extends ZM_Object {
protected static $table = 'Monitors';
protected $defaults = array(
'Id' => null,
'Name' => '',
'ServerId' => 0,
'StorageId' => 0,
'Type' => 'Ffmpeg',
2019-09-21 22:40:24 +08:00
'Function' => 'Mocord',
2019-09-20 02:55:27 +08:00
'Enabled' => array('type'=>'boolean','default'=>1),
'LinkedMonitors' => array('type'=>'set', 'default'=>null),
'Triggers' => array('type'=>'set','default'=>''),
'Device' => '',
'Channel' => 0,
'Format' => '0',
'V4LMultiBuffer' => null,
'V4LCapturesPerFrame' => 1,
'Protocol' => null,
'Method' => '',
'Host' => null,
'Port' => '',
'SubPath' => '',
'Path' => null,
'Options' => null,
'User' => null,
'Pass' => null,
// These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME
'Width' => null,
'Height' => null,
2019-11-27 04:06:24 +08:00
'Colours' => 4,
'Palette' => '0',
'Orientation' => null,
'Deinterlacing' => 0,
2019-06-26 03:34:45 +08:00
'DecoderHWAccelName' => null,
'DecoderHWAccelDevice' => null,
'SaveJPEGs' => 3,
'VideoWriter' => '0',
'OutputCodec' => null,
'OutputContainer' => null,
2019-09-21 22:40:24 +08:00
'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
2019-09-20 02:55:27 +08:00
'RecordAudio' => array('type'=>'boolean', 'default'=>0),
'RTSPDescribe' => array('type'=>'boolean','default'=>0),
'Brightness' => -1,
'Contrast' => -1,
'Hue' => -1,
'Colour' => -1,
'EventPrefix' => 'Event-',
2019-09-21 22:40:24 +08:00
'LabelFormat' => '%N - %d/%m/%y %H:%M:%S',
'LabelX' => 0,
'LabelY' => 0,
'LabelSize' => 1,
'ImageBufferCount' => 100,
'WarmupCount' => 0,
'PreEventCount' => 0,
'PostEventCount' => 0,
'StreamReplayBuffer' => 0,
'AlarmFrameCount' => 1,
'SectionLength' => 600,
'MinSectionLength' => 10,
'FrameSkip' => 0,
2019-09-20 02:55:27 +08:00
'MotionFrameSkip' => 0,
'AnalysisFPSLimit' => null,
'AnalysisUpdateDelay' => 0,
'MaxFPS' => null,
'AlarmMaxFPS' => null,
2019-03-05 23:58:23 +08:00
'FPSReportInterval' => 100,
'RefBlendPerc' => 6,
'AlarmRefBlendPerc' => 6,
2019-09-20 02:55:27 +08:00
'Controllable' => array('type'=>'boolean','default'=>0),
'ControlId' => null,
'ControlDevice' => null,
'ControlAddress' => null,
'AutoStopTimeout' => null,
2019-09-20 02:55:27 +08:00
'TrackMotion' => array('type'=>'boolean','default'=>0),
'TrackDelay' => null,
'ReturnLocation' => -1,
'ReturnDelay' => null,
'DefaultRate' => 100,
'DefaultScale' => 100,
'SignalCheckPoints' => 0,
'SignalCheckColour' => '#0000BE',
'WebColour' => 'red',
2019-09-20 02:55:27 +08:00
'Exif' => array('type'=>'boolean','default'=>0),
'Sequence' => null,
2019-09-20 02:55:27 +08:00
'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'ZoneCount' => 0,
'Refresh' => null,
'DefaultCodec' => 'auto',
2019-09-20 02:55:27 +08:00
'GroupIds' => array('default'=>array(), 'do_not_update'=>1),
);
2018-01-29 23:53:48 +08:00
private $status_fields = array(
'Status' => null,
'AnalysisFPS' => null,
'CaptureFPS' => null,
2018-04-25 02:11:27 +08:00
'CaptureBandwidth' => null,
);
2016-05-06 03:30:24 +08:00
2019-09-20 02:55:27 +08:00
public function Control() {
if ( !array_key_exists('Control', $this) ) {
if ( $this->ControlId() )
$this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'}));
2019-09-23 09:06:54 +08:00
if ( !(array_key_exists('Control', $this) and $this->{'Control'}) )
2019-09-20 02:55:27 +08:00
$this->{'Control'} = new Control();
}
return $this->{'Control'};
}
2016-05-06 03:30:24 +08:00
public function Server() {
return new Server($this->{'ServerId'});
2016-05-06 03:30:24 +08:00
}
2019-09-20 02:55:27 +08:00
2017-10-27 09:56:10 +08:00
public function __call($fn, array $args){
if ( count($args) ) {
2019-09-20 02:55:27 +08:00
if ( is_array($this->defaults[$fn]) and $this->defaults[$fn]['type'] == 'set' ) {
$this->{$fn} = is_array($args[0]) ? implode(',',$args[0]) : $args[0];
} else {
$this->{$fn} = $args[0];
}
2017-06-06 03:21:27 +08:00
}
2017-10-27 09:56:10 +08:00
if ( array_key_exists($fn, $this) ) {
2016-05-06 03:30:24 +08:00
return $this->{$fn};
2019-09-20 02:55:27 +08:00
} else if ( array_key_exists($fn, $this->defaults) ) {
if ( is_array($this->defaults[$fn]) ) {
return $this->defaults[$fn]['default'];
}
return $this->defaults[$fn];
} else if ( array_key_exists($fn, $this->status_fields) ) {
2019-09-24 00:39:24 +08:00
$sql = 'SELECT `Status`,`CaptureFPS`,`AnalysisFPS`,`CaptureBandwidth`
FROM `Monitor_Status` WHERE `MonitorId`=?';
$row = dbFetchOne($sql, NULL, array($this->{'Id'}));
if ( !$row ) {
2019-09-20 02:55:27 +08:00
Error('Unable to load Monitor record for Id='.$this->{'Id'});
2019-09-25 22:13:56 +08:00
foreach ( $this->status_fields as $k => $v ) {
$this->{$k} = $v;
}
} else {
foreach ($row as $k => $v) {
$this->{$k} = $v;
}
}
return $this->{$fn};
} else {
$backTrace = debug_backtrace();
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
2019-09-20 02:55:27 +08:00
Warning("Unknown function call Monitor->$fn from $file:$line");
2016-06-15 00:38:17 +08:00
}
2016-05-06 03:30:24 +08:00
}
public function getStreamSrc($args, $querySep='&amp;') {
$streamSrc = $this->Server()->UrlToZMS(
ZM_MIN_STREAMING_PORT ?
ZM_MIN_STREAMING_PORT+$this->{'Id'} :
null);
2015-09-18 03:29:36 +08:00
$args['monitor'] = $this->{'Id'};
2015-09-18 03:29:36 +08:00
2016-05-06 03:30:24 +08:00
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
} elseif ( ZM_AUTH_RELAY == 'plain' ) {
$args['user'] = $_SESSION['username'];
$args['pass'] = $_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
$args['user'] = $_SESSION['username'];
2016-05-06 03:30:24 +08:00
}
}
if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) {
$args['connkey'] = $GLOBALS['connkey'];
2016-05-06 03:30:24 +08:00
}
if ( ZM_RAND_STREAM ) {
$args['rand'] = time();
2016-05-06 03:30:24 +08:00
}
2015-09-18 03:29:36 +08:00
2019-03-22 02:14:15 +08:00
$streamSrc .= '?'.http_build_query($args, '', $querySep);
2015-09-18 03:29:36 +08:00
return $streamSrc;
2016-05-06 03:30:24 +08:00
} // end function getStreamSrc
2016-06-15 00:38:17 +08:00
public function isPortrait() {
return $this->ViewWidth() > $this->ViewHeight();
}
public function isLandscape() {
return $this->ViewWidth() < $this->ViewHeight();
}
public function ViewWidth($new = null) {
2017-10-27 09:56:10 +08:00
if ( $new )
$this->{'Width'} = $new;
$field = ( $this->Orientation() == '90' or $this->Orientation() == '270' ) ? 'Height' : 'Width';
if ( array_key_exists($field, $this) )
return $this->{$field};
return $this->defaults{$field};
} // end function Width
2016-06-15 00:38:17 +08:00
public function ViewHeight($new=null) {
2017-10-27 09:56:10 +08:00
if ( $new )
$this->{'Height'} = $new;
$field = ( $this->Orientation() == '90' or $this->Orientation() == '270' ) ? 'Width' : 'Height';
if ( array_key_exists($field, $this) )
return $this->{$field};
return $this->defaults{$field};
} // end function Height
2016-06-15 00:38:17 +08:00
public function SignalCheckColour($new=null) {
$field = 'SignalCheckColour';
if ($new) {
$this->{$field} = $new;
}
// Validate that it's a valid colour (we seem to allow color names, not just hex).
// This also helps prevent XSS.
if (array_key_exists($field, $this) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) {
return $this->{$field};
}
return $this->defaults{$field};
} // end function SignalCheckColour
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(get_class(), $parameters, $options);
2016-05-06 03:30:24 +08:00
}
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
function zmcControl( $mode=false ) {
2019-09-20 02:55:27 +08:00
if ( ! $this->{'Id'} ) {
2019-09-24 00:39:24 +08:00
Warning('Attempt to control a monitor with no Id');
2019-09-20 02:55:27 +08:00
return;
}
if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
2019-09-20 02:55:27 +08:00
if ( $this->Type() == 'Local' ) {
$zmcArgs = '-d '.$this->{'Device'};
} else {
$zmcArgs = '-m '.$this->{'Id'};
}
if ( $mode == 'stop' ) {
daemonControl('stop', 'zmc', $zmcArgs);
} else {
if ( $mode == 'restart' ) {
daemonControl('stop', 'zmc', $zmcArgs);
}
if ( $this->{'Function'} != 'None' ) {
daemonControl('start', 'zmc', $zmcArgs);
}
}
} else if ( $this->ServerId() ) {
$Server = $this->Server();
$url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
2018-04-05 23:24:47 +08:00
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
} elseif ( ZM_AUTH_RELAY == 'plain' ) {
2018-04-05 23:24:47 +08:00
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
2018-04-05 23:24:47 +08:00
$url = '?user='.$_SESSION['username'];
}
}
Logger::Debug("sending command to $url");
$context = stream_context_create();
try {
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { /* Handle error */
Error("Error restarting zmc using $url");
}
} catch ( Exception $e ) {
Error("Except $e thrown trying to restart zmc");
}
} else {
2019-09-24 00:39:24 +08:00
Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.');
}
} // end function zmcControl
2019-09-20 02:55:27 +08:00
function zmaControl($mode=false) {
if ( ! $this->{'Id'} ) {
2019-09-24 00:39:24 +08:00
Warning('Attempt to control a monitor with no Id');
2019-09-20 02:55:27 +08:00
return;
}
if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
if ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) {
if ( ZM_OPT_CONTROL ) {
daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'});
}
daemonControl('stop', 'zma', '-m '.$this->{'Id'});
} else {
if ( $mode == 'restart' ) {
if ( ZM_OPT_CONTROL ) {
2019-09-20 02:55:27 +08:00
daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'});
}
2019-09-20 02:55:27 +08:00
daemonControl('stop', 'zma', '-m '.$this->{'Id'});
}
2019-09-20 02:55:27 +08:00
daemonControl('start', 'zma', '-m '.$this->{'Id'});
if ( ZM_OPT_CONTROL && $this->Controllable() && $this->TrackMotion() &&
( $this->{'Function'} == 'Modect' || $this->{'Function'} == 'Mocord' ) ) {
daemonControl('start', 'zmtrack.pl', '-m '.$this->{'Id'});
}
if ( $mode == 'reload' ) {
2019-09-20 02:55:27 +08:00
daemonControl('reload', 'zma', '-m '.$this->{'Id'});
}
}
} else if ( $this->ServerId() ) {
$Server = $this->Server();
$url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zma.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
} elseif ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
}
}
Logger::Debug("sending command to $url");
$context = stream_context_create();
try {
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { /* Handle error */
Error("Error restarting zma using $url");
}
} catch ( Exception $e ) {
Error("Except $e thrown trying to restart zma");
}
} else {
2019-09-20 02:55:27 +08:00
Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.');
} // end if we are on the recording server
} // end public function zmaControl
2019-09-20 02:55:27 +08:00
public function GroupIds( $new='' ) {
if ( $new != '' ) {
2019-09-20 02:55:27 +08:00
if ( !is_array($new) ) {
$this->{'GroupIds'} = array($new);
} else {
$this->{'GroupIds'} = $new;
}
}
if ( !array_key_exists('GroupIds', $this) ) {
if ( array_key_exists('Id', $this) and $this->{'Id'} ) {
2019-09-24 00:39:24 +08:00
$this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}) );
if ( ! $this->{'GroupIds'} )
$this->{'GroupIds'} = array();
} else {
2018-03-24 04:34:46 +08:00
$this->{'GroupIds'} = array();
}
}
return $this->{'GroupIds'};
}
2019-09-20 02:55:27 +08:00
public function delete() {
if ( ! $this->{'Id'} ) {
Warning("Attempt to delete a monitor without id.");
return;
}
$this->zmaControl('stop');
$this->zmcControl('stop');
// If fast deletes are on, then zmaudit will clean everything else up later
// If fast deletes are off and there are lots of events then this step may
// well time out before completing, in which case zmaudit will still tidy up
if ( !ZM_OPT_FAST_DELETE ) {
2018-03-22 02:32:54 +08:00
$markEids = dbFetchAll('SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($this->{'Id'}));
2019-09-20 02:55:27 +08:00
foreach ($markEids as $markEid)
2018-03-22 02:32:54 +08:00
deleteEvent($markEid);
if ( $this->{'Name'} )
deletePath(ZM_DIR_EVENTS.'/'.basename($this->{'Name'}));
deletePath(ZM_DIR_EVENTS.'/'.$this->{'Id'});
$Storage = $this->Storage();
if ( $Storage->Path() != ZM_DIR_EVENTS ) {
if ( $this->{'Name'} )
deletePath($Storage->Path().'/'.basename($this->{'Name'}));
deletePath($Storage->Path().'/'.$this->{'Id'});
}
2019-09-20 02:55:27 +08:00
} // end if !ZM_OPT_FAST_DELETE
// This is the important stuff
dbQuery('DELETE FROM Zones WHERE MonitorId = ?', array($this->{'Id'}));
if ( ZM_OPT_X10 )
dbQuery('DELETE FROM TriggersX10 WHERE MonitorId=?', array($this->{'Id'}));
dbQuery('DELETE FROM Monitors WHERE Id = ?', array($this->{'Id'}));
} // end function delete
2018-03-22 02:32:54 +08:00
2019-09-20 02:55:27 +08:00
public function Storage($new = null) {
2018-03-22 02:32:54 +08:00
if ( $new ) {
$this->{'Storage'} = $new;
}
if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) {
$this->{'Storage'} = isset($this->{'StorageId'}) ?
Storage::find_one(array('Id'=>$this->{'StorageId'})) :
new Storage(NULL);
if ( ! $this->{'Storage'} )
$this->{'Storage'} = new Storage(NULL);
2018-03-22 02:32:54 +08:00
}
return $this->{'Storage'};
}
public function Source( ) {
$source = '';
if ( $this->{'Type'} == 'Local' ) {
$source = $this->{'Device'}.' ('.$this->{'Channel'}.')';
} elseif ( $this->{'Type'} == 'Remote' ) {
$source = preg_replace( '/^.*@/', '', $this->{'Host'} );
if ( $this->{'Port'} != '80' and $this->{'Port'} != '554' ) {
$source .= ':'.$this->{'Port'};
}
} elseif ( $this->{'Type'} == 'File' || $this->{'Type'} == 'cURL' ) {
$source = preg_replace( '/^.*\//', '', $this->{'Path'} );
} elseif ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) {
$url_parts = parse_url( $this->{'Path'} );
if ( ZM_WEB_FILTER_SOURCE == 'Hostname' ) {
# Filter out everything but the hostname
if ( isset($url_parts['host']) ) {
$source = $url_parts['host'];
} else {
$source = $this->{'Path'};
}
2019-09-24 00:39:24 +08:00
} elseif ( ZM_WEB_FILTER_SOURCE == 'NoCredentials' ) {
# Filter out sensitive and common items
unset($url_parts['user']);
unset($url_parts['pass']);
#unset($url_parts['scheme']);
unset($url_parts['query']);
#unset($url_parts['path']);
if ( isset($url_parts['port']) and ( $url_parts['port'] == '80' or $url_parts['port'] == '554' ) )
unset($url_parts['port']);
$source = unparse_url($url_parts);
} else { # Don't filter anything
$source = $this->{'Path'};
}
}
if ( $source == '' ) {
$source = 'Monitor ' . $this->{'Id'};
}
return $source;
} // end function Source
public function UrlToIndex() {
return $this->Server()->UrlToIndex();
//ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null);
}
public function sendControlCommand($command) {
// command is generally a command option list like --command=blah but might be just the word quit
$options = array();
# Convert from a command line params to an option array
foreach ( explode(' ', $command) as $option ) {
if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) {
$options[$matches[1]] = $matches[2]?$matches[2]:1;
} else if ( $option != '' and $option != 'quit' ) {
Warning("Ignored command for zmcontrol $option in $command");
}
}
if ( !count($options) ) {
if ( $command == 'quit' ) {
$options['command'] = 'quit';
} else {
Warning("No commands to send to zmcontrol from $command");
return false;
}
}
if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
# Local
Logger::Debug('Trying to send options ' . print_r($options, true));
$optionString = jsonEncode($options);
Logger::Debug("Trying to send options $optionString");
// Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command.
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
if ( $socket < 0 ) {
Error('socket_create() failed: '.socket_strerror($socket));
return false;
}
$sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$this->{'Id'}.'.sock';
if ( @socket_connect($socket, $sockFile) ) {
if ( !socket_write($socket, $optionString) ) {
Error('Can\'t write to control socket: '.socket_strerror(socket_last_error($socket)));
return false;
}
} else if ( $command != 'quit' ) {
$command = ZM_PATH_BIN.'/zmcontrol.pl '.$command.' --id='.$this->{'Id'};
// Can't connect so use script
$ctrlOutput = exec(escapeshellcmd($command));
}
socket_close($socket);
} else if ( $this->ServerId() ) {
$Server = $this->Server();
$url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmcontrol.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} else if ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
}
}
Logger::Debug("sending command to $url");
$context = stream_context_create();
try {
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { /* Handle error */
Error("Error restarting zma using $url");
return false;
}
} catch ( Exception $e ) {
Error("Except $e thrown trying to restart zma");
return false;
}
} else {
Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.');
return false;
} // end if we are on the recording server
return true;
} // end function sendControlCommand($mid, $command)
} // end class Monitor
2015-09-18 03:29:36 +08:00
?>