1 | <?php |
---|
2 | /** |
---|
3 | * |
---|
4 | * @url $URL$ |
---|
5 | * @date $Date$ |
---|
6 | * @version $Revision$ |
---|
7 | * @author $Author$ |
---|
8 | * @license GPL |
---|
9 | * |
---|
10 | * @package MythTV |
---|
11 | * |
---|
12 | /**/ |
---|
13 | |
---|
14 | class MythBackend { |
---|
15 | |
---|
16 | // MYTH_PROTO_VERSION is defined in libmyth in mythtv/libs/libmyth/mythcontext.h |
---|
17 | // and should be the current MythTV protocol version. |
---|
18 | static $protocol_version = '59'; |
---|
19 | |
---|
20 | // The character string used by the backend to separate records |
---|
21 | static $backend_separator = '[]:[]'; |
---|
22 | |
---|
23 | // NUMPROGRAMLINES is defined in mythtv/libs/libmythtv/programinfo.h and is |
---|
24 | // the number of items in a ProgramInfo QStringList group used by |
---|
25 | // ProgramInfo::ToSringList and ProgramInfo::FromStringList. |
---|
26 | static $program_line_number = 41; |
---|
27 | |
---|
28 | private $fp = null; |
---|
29 | private $connected = false; |
---|
30 | private $host = '127.0.0.1'; |
---|
31 | private $ip = '127.0.0.1'; |
---|
32 | private $port = null; |
---|
33 | private $port_http = null; |
---|
34 | |
---|
35 | static function find($host = null, $port = null) { |
---|
36 | static $Backends = array(); |
---|
37 | |
---|
38 | // Looking for the master backend? |
---|
39 | if (is_null($host)) { |
---|
40 | $host = setting('MasterServerIP'); |
---|
41 | $port = setting('MasterServerPort'); |
---|
42 | if (!$host || !$port) |
---|
43 | trigger_error("MasterServerIP or MasterServerPort not found! You may" |
---|
44 | ."need to check your mythweb.conf.* file, re-run mythtv-setup," |
---|
45 | ."or insert rows into the MythTV database's 'settings' table defining" |
---|
46 | ."MasterServerIP and MasterServerPort for the NULL host." |
---|
47 | FATAL); |
---|
48 | } |
---|
49 | |
---|
50 | if (!isset($Backend[$host][$port])) |
---|
51 | $Backend[$host][$port] = new MythBackend($host, $port); |
---|
52 | return $Backend[$host][$port]; |
---|
53 | } |
---|
54 | |
---|
55 | function __construct($host, $port = null) { |
---|
56 | $this->host = $host; |
---|
57 | $this->ip = _or(setting('BackendServerIP', $this->host), $host); |
---|
58 | $this->port = _or($port, _or(setting('BackendServerPort', $this->host), 6543)); |
---|
59 | $this->port_http = _or(setting('BackendStatusPort', $this->host), _or(setting('BackendStatusPort'), 6544)); |
---|
60 | } |
---|
61 | |
---|
62 | function __destruct() { |
---|
63 | $this->disconnect(); |
---|
64 | } |
---|
65 | |
---|
66 | private function connect() { |
---|
67 | if ($this->fp) |
---|
68 | return; |
---|
69 | $this->fp = @fsockopen($this->ip, $this->port, $errno, $errstr, 25); |
---|
70 | if (!$this->fp) |
---|
71 | custom_error("Unable to connect to the master backend at {$this->ip}:{$this->port}".(($this->host == $this->ip)?'':" (hostname: {$this->host})").".\nIs it running?"); |
---|
72 | socket_set_timeout($this->fp, 30); |
---|
73 | $this->checkProtocolVersion(); |
---|
74 | $this->announce(); |
---|
75 | } |
---|
76 | |
---|
77 | private function disconnect() { |
---|
78 | if (!$this->fp) |
---|
79 | return; |
---|
80 | $this->sendCommand('DONE'); |
---|
81 | fclose($this->fp); |
---|
82 | } |
---|
83 | |
---|
84 | private function checkProtocolVersion() { |
---|
85 | // Allow overriding this check |
---|
86 | if ($_SERVER['ignore_proto'] == true ) |
---|
87 | return true; |
---|
88 | |
---|
89 | if ( time() - $_SESSION['backend'][$this->host]['proto_version']['last_check_time'] < 60*60*2 |
---|
90 | && $_SESSION['backend'][$this->host]['proto_version']['last_check_version'] == MythBackend::$protocol_version ) |
---|
91 | return true; |
---|
92 | |
---|
93 | $response = $this->sendCommand('MYTH_PROTO_VERSION '.MythBackend::$protocol_version); |
---|
94 | $_SESSION['backend'][$this->host]['proto_version']['last_check_version'] = @$response[1]; |
---|
95 | |
---|
96 | if ($response[0] == 'ACCEPT') { |
---|
97 | $_SESSION['backend'][$this->host]['proto_version']['last_check_time'] = time(); |
---|
98 | return true; |
---|
99 | } |
---|
100 | |
---|
101 | if ($response[0] == 'REJECT') |
---|
102 | trigger_error("Incompatible protocol version (mythweb=" . MythBackend::$protocol_version . ", backend=" . @$response[1] . ")"); |
---|
103 | else |
---|
104 | trigger_error("Unexpected response to MYTH_PROTO_VERSION '".MythBackend::$protocol_version."': ".print_r($response, true)); |
---|
105 | return false; |
---|
106 | } |
---|
107 | |
---|
108 | private function announce() { |
---|
109 | $response = $this->sendCommand('ANN Monitor '.hostname.' 2' ); |
---|
110 | if ($response == 'OK') |
---|
111 | return true; |
---|
112 | return false; |
---|
113 | } |
---|
114 | |
---|
115 | public function setTimezone() { |
---|
116 | if (!is_string($_SESSION['backend']['timezone']['value']) || time() - $_SESSION['backend']['timezone']['last_check_time'] > 60*60*24) { |
---|
117 | $response = $this->sendCommand('QUERY_TIME_ZONE'); |
---|
118 | $timezone = str_replace(' ', '_', $response[0]); |
---|
119 | $_SESSION['backend']['timezone']['value'] = $timezone; |
---|
120 | $_SESSION['backend']['timezone']['last_check_time'] = time(); |
---|
121 | } |
---|
122 | |
---|
123 | if (!@date_default_timezone_set($_SESSION['backend']['timezone']['value'])) { |
---|
124 | $attempted_value = $_SESSION['backend']['timezone']['value']; |
---|
125 | unset($_SESSION['backend']['timezone']); |
---|
126 | trigger_error('Failed to set php timezone to '.$attempted_value.(is_array($response) ? ' Response from backend was '.print_r($response, true) : '')); |
---|
127 | } |
---|
128 | } |
---|
129 | |
---|
130 | public function sendCommand($command = null) { |
---|
131 | $this->connect(); |
---|
132 | // The format should be <length + whitespace to 8 total bytes><data> |
---|
133 | if (is_array($command)) |
---|
134 | $command = implode(MythBackend::$backend_separator, $command); |
---|
135 | $command = strlen($command) . str_repeat(' ', 8 - strlen(strlen($command))) . $command; |
---|
136 | fputs($this->fp, $command); |
---|
137 | return $this->receiveData(); |
---|
138 | } |
---|
139 | |
---|
140 | public function receiveData($timeout = 30) { |
---|
141 | $this->connect(); |
---|
142 | stream_set_timeout($this->fp, $timeout); |
---|
143 | |
---|
144 | // Read the response header to find out how much data we'll be grabbing |
---|
145 | $length = rtrim(fread($this->fp, 8)); |
---|
146 | |
---|
147 | // Read and return any data that was returned |
---|
148 | $response = ''; |
---|
149 | while ($length > 0) { |
---|
150 | $data = fread($this->fp, min(8192, $length)); |
---|
151 | if (strlen($data) < 1) |
---|
152 | break; // EOF |
---|
153 | $response .= $data; |
---|
154 | $length -= strlen($data); |
---|
155 | } |
---|
156 | $response = explode(MythBackend::$backend_separator, $response); |
---|
157 | if (count($response) == 1) |
---|
158 | return $response[0]; |
---|
159 | if (count($response) == 0) |
---|
160 | return false; |
---|
161 | return $response; |
---|
162 | } |
---|
163 | |
---|
164 | public function listenForEvent($event, $timeout = 120) { |
---|
165 | $endtime = time() + $timeout; |
---|
166 | do { |
---|
167 | $response = $this->receiveData(); |
---|
168 | } while ($response[1] != $event && $endtime < time()); |
---|
169 | if ($response[1] == $event) |
---|
170 | return $response; |
---|
171 | return false; |
---|
172 | } |
---|
173 | |
---|
174 | public function queryProgramRows($query = null, $offset = 1) { |
---|
175 | $records = $this->sendCommand($query); |
---|
176 | // Parse the records, starting at the offset point |
---|
177 | $row = 0; |
---|
178 | $col = 0; |
---|
179 | $count = count($records); |
---|
180 | for($i = $offset; $i < $count; $i++) { |
---|
181 | $rows[$row][$col] = $records[$i]; |
---|
182 | // Every $NUMPROGRAMLINES fields (0 through ($NUMPROGRAMLINES-1)) means |
---|
183 | // a new row. Please note that this changes between myth versions |
---|
184 | if ($col == (MythBackend::$program_line_number - 1)) { |
---|
185 | $col = 0; |
---|
186 | $row++; |
---|
187 | } |
---|
188 | // Otherwise, just increment the column |
---|
189 | else |
---|
190 | $col++; |
---|
191 | } |
---|
192 | // Lastly, grab the offset data (if there is any) |
---|
193 | for ($i=0; $i < $offset; $i++) { |
---|
194 | $rows['offset'][$i] = $recs[$i]; |
---|
195 | } |
---|
196 | // Return the data |
---|
197 | return $rows; |
---|
198 | } |
---|
199 | |
---|
200 | /** |
---|
201 | * Tell the backend to reschedule a particular record entry. If the change |
---|
202 | * isn't specific to a single record entry (e.g. channel or record type |
---|
203 | * priorities), then use 0. I don't think mythweb should need it, but if you |
---|
204 | * need to indicate every record rule is affected, then use -1. |
---|
205 | /**/ |
---|
206 | public function rescheduleRecording($recordid = -1) { |
---|
207 | $this->sendCommand('RESCHEDULE_RECORDINGS '.$recordid); |
---|
208 | Cache::clear(); |
---|
209 | if ($this->listenForEvent('SCHEDULE_CHANGE')) |
---|
210 | return true; |
---|
211 | return false; |
---|
212 | } |
---|
213 | |
---|
214 | public function httpRequest($path, $args = array()) { |
---|
215 | $url = "http://{$this->ip}:{$this->port_http}/Myth/{$path}?"; |
---|
216 | foreach ($args as $key => $value) |
---|
217 | $url .= $key.'='.urlencode($value).'&'; |
---|
218 | return @file_get_contents($url); |
---|
219 | } |
---|
220 | } |
---|