1#!/usr/bin/env php
2<?php
3/***********************************************
4* File      :   z-push-top.php
5* Project   :   Z-Push
6* Descr     :   Shows realtime information about
7*               connected devices and active
8*               connections in a top-style format.
9*
10* Created   :   07.09.2011
11*
12* Copyright 2007 - 2016 Zarafa Deutschland GmbH
13*
14* This program is free software: you can redistribute it and/or modify
15* it under the terms of the GNU Affero General Public License, version 3,
16* as published by the Free Software Foundation.
17*
18* This program is distributed in the hope that it will be useful,
19* but WITHOUT ANY WARRANTY; without even the implied warranty of
20* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21* GNU Affero General Public License for more details.
22*
23* You should have received a copy of the GNU Affero General Public License
24* along with this program.  If not, see <http://www.gnu.org/licenses/>.
25*
26* Consult LICENSE file for details
27************************************************/
28
29require_once 'vendor/autoload.php';
30
31/************************************************
32 * MAIN
33 */
34    declare(ticks = 1);
35    define('BASE_PATH_CLI',  dirname(__FILE__) ."/");
36    set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI);
37
38    if (!defined('ZPUSH_CONFIG')) define('ZPUSH_CONFIG', BASE_PATH_CLI . 'config.php');
39    include_once(ZPUSH_CONFIG);
40
41    try {
42        ZPush::CheckConfig();
43        if (!function_exists("pcntl_signal"))
44            throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system.");
45
46        if (php_sapi_name() != "cli")
47            throw new FatalException("This script can only be called from the CLI.");
48
49        $zpt = new ZPushTop();
50
51        // check if help was requested from CLI
52        if (in_array('-h', $argv) || in_array('--help', $argv)) {
53            echo $zpt->UsageInstructions();
54            exit(0);
55        }
56
57        if ($zpt->IsAvailable()) {
58            pcntl_signal(SIGINT, array($zpt, "SignalHandler"));
59            $zpt->run();
60            $zpt->scrClear();
61            system("stty sane");
62        }
63        else
64            echo "Z-Push interprocess communication (IPC) is not available or TopCollector is disabled.\n";
65    }
66    catch (ZPushException $zpe) {
67        fwrite(STDERR, get_class($zpe) . ": ". $zpe->getMessage() . "\n");
68        exit(1);
69    }
70
71    echo "terminated\n";
72
73
74/************************************************
75 * Z-Push-Top
76 */
77class ZPushTop {
78    // show options
79    const SHOW_DEFAULT = 0;
80    const SHOW_ACTIVE_ONLY = 1;
81    const SHOW_UNKNOWN_ONLY = 2;
82    const SHOW_TERM_DEFAULT_TIME = 5; // 5 secs
83
84    private $topCollector;
85    private $starttime;
86    private $action;
87    private $filter;
88    private $status;
89    private $statusexpire;
90    private $wide;
91    private $wasEnabled;
92    private $terminate;
93    private $scrSize;
94    private $pingInterval;
95    private $showPush;
96    private $showTermSec;
97
98    private $linesActive = array();
99    private $linesOpen = array();
100    private $linesUnknown = array();
101    private $linesTerm = array();
102    private $pushConn = 0;
103    private $activeConn = array();
104    private $activeHosts = array();
105    private $activeUsers = array();
106    private $activeDevices = array();
107
108    /**
109     * Constructor
110     *
111     * @access public
112     */
113    public function __construct() {
114        $this->starttime = time();
115        $this->currenttime = time();
116        $this->action = "";
117        $this->filter = false;
118        $this->status = false;
119        $this->statusexpire = 0;
120        $this->helpexpire = 0;
121        $this->doingTail = false;
122        $this->wide = false;
123        $this->terminate = false;
124        $this->showPush = true;
125        $this->showOption = self::SHOW_DEFAULT;
126        $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
127        $this->scrSize = array('width' => 80, 'height' => 24);
128        $this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12;
129
130        // get a TopCollector
131        $this->topCollector = new TopCollector();
132    }
133
134    /**
135     * Requests data from the running Z-Push processes
136     *
137     * @access private
138     * @return
139     */
140    private function initialize() {
141        // request feedback from active processes
142        $this->wasEnabled = $this->topCollector->CollectData();
143
144        // remove obsolete data
145        $this->topCollector->ClearLatest(true);
146
147        // start with default colours
148        $this->scrDefaultColors();
149    }
150
151    /**
152     * Main loop of Z-Push-top
153     * Runs until termination is requested
154     *
155     * @access public
156     * @return
157     */
158    public function run() {
159        $this->initialize();
160
161        do {
162            $this->currenttime = time();
163
164            // see if shared memory is active
165            if (!$this->IsAvailable())
166                $this->terminate = true;
167
168            // active processes should continue sending data
169            $this->topCollector->CollectData();
170
171            // get and process data from processes
172            $this->topCollector->ClearLatest();
173            $topdata = $this->topCollector->ReadLatest();
174            $this->processData($topdata);
175
176            // clear screen
177            $this->scrClear();
178
179            // check if screen size changed
180            $s = $this->scrGetSize();
181            if ($this->scrSize['width'] != $s['width']) {
182                if ($s['width'] > 180)
183                    $this->wide = true;
184                else
185                    $this->wide = false;
186            }
187            $this->scrSize = $s;
188
189            // print overview
190            $this->scrOverview();
191
192            // wait for user input
193            $this->readLineProcess();
194        }
195        while($this->terminate != true);
196    }
197
198    /**
199     * Indicates if TopCollector is available collecting data
200     *
201     * @access public
202     * @return boolean
203     */
204    public function IsAvailable() {
205        if (defined('TOPCOLLECTOR_DISABLED') && constant('TOPCOLLECTOR_DISABLED') === true) {
206            return false;
207        }
208        return $this->topCollector->IsActive();
209    }
210
211    /**
212     * Processes data written by the running processes
213     *
214     * @param array $data
215     *
216     * @access private
217     * @return
218     */
219    private function processData($data) {
220        $this->linesActive = array();
221        $this->linesOpen = array();
222        $this->linesUnknown = array();
223        $this->linesTerm = array();
224        $this->pushConn = 0;
225        $this->activeConn = array();
226        $this->activeHosts = array();
227        $this->activeUsers = array();
228        $this->activeDevices = array();
229
230        if (!is_array($data))
231            return;
232
233        foreach ($data as $devid=>$users) {
234            foreach ($users as $user=>$pids) {
235                foreach ($pids as $pid=>$line) {
236                    if (!is_array($line))
237                        continue;
238
239                    $line['command'] = Utils::GetCommandFromCode($line['command']);
240
241                    if ($line["ended"] == 0) {
242                        $this->activeDevices[$devid] = 1;
243                        $this->activeUsers[$user] = 1;
244                        $this->activeConn[$pid] = 1;
245                        $this->activeHosts[$line['ip']] = 1;
246
247                        $line["time"] = $this->currenttime - $line['start'];
248                        if ($line['push'] === true) $this->pushConn += 1;
249
250                        // ignore push connections
251                        if ($line['push'] === true && ! $this->showPush)
252                            continue;
253
254                        if ($this->filter !== false) {
255                            $f = $this->filter;
256                            if (!($line["pid"] == $f || $line["ip"] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?$f.*?/i", $line['user']) ||
257                                preg_match("/.*?$f.*?/i", $line['devagent']) || preg_match("/.*?$f.*?/i", $line['devid']) || preg_match("/.*?$f.*?/i", $line['addinfo']) ))
258                                continue;
259                        }
260
261                        $lastUpdate = $this->currenttime - $line["update"];
262                        if ($this->currenttime - $line["update"] < 2)
263                            $this->linesActive[$line["update"].$line["pid"]] = $line;
264                        else if (($line['push'] === true  && $lastUpdate > ($this->pingInterval+2)) || ($line['push'] !== true  && $lastUpdate > 4))
265                            $this->linesUnknown[$line["update"].$line["pid"]] = $line;
266                        else
267                            $this->linesOpen[$line["update"].$line["pid"]] = $line;
268                    }
269                    else {
270                        // do not show terminated + expired connections
271                        if ($line['ended'] + $this->showTermSec < $this->currenttime)
272                            continue;
273
274                        if ($this->filter !== false) {
275                            $f = $this->filter;
276                            if (!($line['pid'] == $f || $line['ip'] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?$f.*?/i", $line['user']) ||
277                                preg_match("/.*?$f.*?/i", $line['devagent']) || preg_match("/.*?$f.*?/i", $line['devid']) || preg_match("/.*?$f.*?/i", $line['addinfo']) ))
278                                continue;
279                        }
280
281                        $line['time'] = $line['ended'] - $line['start'];
282                        $this->linesTerm[$line['update'].$line['pid']] = $line;
283                    }
284                }
285            }
286        }
287
288        // sort by execution time
289        krsort($this->linesActive);
290        krsort($this->linesOpen);
291        krsort($this->linesUnknown);
292        krsort($this->linesTerm);
293    }
294
295    /**
296     * Prints data to the terminal
297     *
298     * @access private
299     * @return
300     */
301    private function scrOverview() {
302        $linesAvail = $this->scrSize['height'] - 8;
303        $lc = 1;
304        $this->scrPrintAt($lc,0, "\033[1mZ-Push top live statistics\033[0m\t\t\t\t\t". @strftime("%d/%m/%Y %T")."\n"); $lc++;
305
306        $this->scrPrintAt($lc,0, sprintf("Open connections: %d\t\t\t\tUsers:\t %d\tZ-Push:   %s ",count($this->activeConn),count($this->activeUsers), $this->getVersion())); $lc++;
307        $this->scrPrintAt($lc,0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices),phpversion("mapi"))); $lc++;
308        $this->scrPrintAt($lc,0, sprintf("                                                Hosts:\t %d", count($this->activeHosts))); $lc++;
309        $lc++;
310
311        $this->scrPrintAt($lc,0, "\033[4m". $this->getLine(array('pid'=>'PID', 'ip'=>'IP', 'user'=>'USER', 'command'=>'COMMAND', 'time'=>'TIME', 'devagent'=>'AGENT', 'devid'=>'DEVID', 'addinfo'=>'Additional Information')). str_repeat(" ",20)."\033[0m"); $lc++;
312
313        // print help text if requested
314        $hl = 0;
315        if ($this->helpexpire > $this->currenttime) {
316            $help = $this->scrHelp();
317            $linesAvail -= count($help);
318            $hl = $this->scrSize['height'] - count($help) -1;
319            foreach ($help as $h) {
320                $this->scrPrintAt($hl,0, $h);
321                $hl++;
322            }
323        }
324
325        $toPrintActive = $linesAvail;
326        $toPrintOpen = $linesAvail;
327        $toPrintUnknown = $linesAvail;
328        $toPrintTerm = $linesAvail;
329
330        // default view: show all unknown, no terminated and half active+open
331        if (count($this->linesActive) + count($this->linesOpen) + count($this->linesUnknown) > $linesAvail) {
332            $toPrintUnknown = count($this->linesUnknown);
333            $toPrintActive = count($this->linesActive);
334            $toPrintOpen = $linesAvail-$toPrintUnknown-$toPrintActive;
335            $toPrintTerm = 0;
336        }
337
338        if ($this->showOption == self::SHOW_ACTIVE_ONLY) {
339            $toPrintActive = $linesAvail;
340            $toPrintOpen = 0;
341            $toPrintUnknown = 0;
342            $toPrintTerm = 0;
343        }
344
345        if ($this->showOption == self::SHOW_UNKNOWN_ONLY) {
346            $toPrintActive = 0;
347            $toPrintOpen = 0;
348            $toPrintUnknown = $linesAvail;
349            $toPrintTerm = 0;
350        }
351
352        $linesprinted = 0;
353        foreach ($this->linesActive as $time=>$l) {
354            if ($linesprinted >= $toPrintActive)
355                break;
356
357            $this->scrPrintAt($lc,0, "\033[01m" . $this->getLine($l)  ."\033[0m");
358            $lc++;
359            $linesprinted++;
360        }
361
362        $linesprinted = 0;
363        foreach ($this->linesOpen as $time=>$l) {
364            if ($linesprinted >= $toPrintOpen)
365                break;
366
367            $this->scrPrintAt($lc,0, $this->getLine($l));
368            $lc++;
369            $linesprinted++;
370        }
371
372        $linesprinted = 0;
373        foreach ($this->linesUnknown as $time=>$l) {
374            if ($linesprinted >= $toPrintUnknown)
375                break;
376
377            $color = "0;31m";
378            if ($l['push'] == false && $time - $l["start"] > 30)
379                $color = "1;31m";
380            $this->scrPrintAt($lc,0, "\033[0". $color . $this->getLine($l)  ."\033[0m");
381            $lc++;
382            $linesprinted++;
383        }
384
385        if ($toPrintTerm > 0)
386            $toPrintTerm = $linesAvail - $lc +6;
387
388        $linesprinted = 0;
389        foreach ($this->linesTerm as $time=>$l){
390            if ($linesprinted >= $toPrintTerm)
391                break;
392
393            $this->scrPrintAt($lc,0, "\033[01;30m" . $this->getLine($l)  ."\033[0m");
394            $lc++;
395            $linesprinted++;
396        }
397
398        // add the lines used when displaying the help text
399        $lc += $hl;
400        $this->scrPrintAt($lc,0, "\033[K"); $lc++;
401        $this->scrPrintAt($lc,0, "Colorscheme: \033[01mActive  \033[0mOpen  \033[01;31mUnknown  \033[01;30mTerminated\033[0m");
402
403        // remove old status
404        if ($this->statusexpire < $this->currenttime)
405            $this->status = false;
406
407        // show request information and help command
408        if ($this->starttime + 6 > $this->currenttime) {
409            $this->status = sprintf("Requesting information (takes up to %dsecs)", $this->pingInterval). str_repeat(".", ($this->currenttime-$this->starttime)) . "  type \033[01;31mh\033[00;31m or \033[01;31mhelp\033[00;31m for usage instructions";
410            $this->statusexpire = $this->currenttime+1;
411        }
412
413
414        $str = "";
415        if (! $this->showPush)
416            $str .= "\033[00;32mPush: \033[01;32mNo\033[0m   ";
417
418        if ($this->showOption == self::SHOW_ACTIVE_ONLY)
419            $str .= "\033[01;32mActive only\033[0m   ";
420
421        if ($this->showOption == self::SHOW_UNKNOWN_ONLY)
422            $str .= "\033[01;32mUnknown only\033[0m   ";
423
424        if ($this->showTermSec != self::SHOW_TERM_DEFAULT_TIME)
425            $str .= "\033[01;32mTerminated: ". $this->showTermSec. "s\033[0m   ";
426
427        if ($this->filter !== false || ($this->status !== false && $this->statusexpire > $this->currenttime)) {
428            // print filter in green
429            if ($this->filter !== false)
430                $str .= "\033[00;32mFilter: \033[01;32m$this->filter\033[0m   ";
431            // print status in red
432            if ($this->status !== false)
433                $str .= "\033[00;31m$this->status\033[0m";
434        }
435        $this->scrPrintAt(5,0, $str);
436
437        $this->scrPrintAt(4,0,"Action: \033[01m".$this->action . "\033[0m");
438    }
439
440    /**
441     * Waits for a keystroke and processes the requested command
442     *
443     * @access private
444     * @return
445     */
446    private function readLineProcess() {
447        $ans = explode("^^", `bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"`);
448
449        if ($ans[0] < 128) {
450            if (isset($ans[1]) && bin2hex(trim($ans[1])) == "7f") {
451                $this->action = substr($this->action,0,-1);
452            }
453
454            if (isset($ans[1]) && $ans[1] != "" ){
455                $this->action .= trim(preg_replace("/[^A-Za-z0-9:]/","",$ans[1]));
456            }
457
458            if (bin2hex($ans[0]) == "30" && bin2hex($ans[1]) == "0a")  {
459                $cmds = explode(':', $this->action);
460                if ($cmds[0] == "quit" || $cmds[0] == "q" || (isset($cmds[1]) && $cmds[0] == "" && $cmds[1] == "q")) {
461                    $this->topCollector->CollectData(true);
462                    $this->topCollector->ClearLatest(true);
463
464                    $this->terminate = true;
465                }
466                else if ($cmds[0] == "clear" ) {
467                    $this->topCollector->ClearLatest(true);
468                    $this->topCollector->CollectData(true);
469                    $this->topCollector->ReInitIPC();
470                }
471                else if ($cmds[0] == "filter" || $cmds[0] == "f") {
472                    if (!isset($cmds[1]) || $cmds[1] == "") {
473                        $this->filter = false;
474                        $this->status = "No filter";
475                        $this->statusexpire = $this->currenttime+5;
476                    }
477                    else {
478                        $this->filter = $cmds[1];
479                        $this->status = false;
480                    }
481                }
482                else if ($cmds[0] == "option" || $cmds[0] == "o") {
483                    if (!isset($cmds[1]) || $cmds[1] == "") {
484                        $this->status = "Option value needs to be specified. See 'help' or 'h' for instructions";
485                        $this->statusexpire = $this->currenttime+5;
486                    }
487                    else if ($cmds[1] == "p" || $cmds[1] == "push" || $cmds[1] == "ping")
488                        $this->showPush = !$this->showPush;
489                    else if ($cmds[1] == "a" || $cmds[1] == "active")
490                        $this->showOption = self::SHOW_ACTIVE_ONLY;
491                    else if ($cmds[1] == "u" || $cmds[1] == "unknown")
492                        $this->showOption = self::SHOW_UNKNOWN_ONLY;
493                    else if ($cmds[1] == "d" || $cmds[1] == "default") {
494                        $this->showOption = self::SHOW_DEFAULT;
495                        $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
496                        $this->showPush = true;
497                    }
498                    else if (is_numeric($cmds[1]))
499                        $this->showTermSec = $cmds[1];
500                    else {
501                        $this->status = sprintf("Option '%s' unknown", $cmds[1]);
502                        $this->statusexpire = $this->currenttime+5;
503                    }
504                }
505                else if ($cmds[0] == "reset" || $cmds[0] == "r") {
506                    $this->filter = false;
507                    $this->wide = false;
508                    $this->helpexpire = 0;
509                    $this->status = "resetted";
510                    $this->statusexpire = $this->currenttime+2;
511                }
512                // enable/disable wide view
513                else if ($cmds[0] == "wide" || $cmds[0] == "w") {
514                    $this->wide = ! $this->wide;
515                    $this->status = ($this->wide)?"w i d e  view" : "normal view";
516                    $this->statusexpire = $this->currenttime+2;
517                }
518                else if ($cmds[0] == "help" || $cmds[0] == "h") {
519                    $this->helpexpire = $this->currenttime+20;
520                }
521                // grep the log file
522                else if (($cmds[0] == "log" || $cmds[0] == "l") && isset($cmds[1]) ) {
523                    if (!file_exists(LOGFILE)) {
524                        $this->status = "Logfile can not be found: ". LOGFILE;
525                    }
526                    else {
527                        system('bash -c "fgrep -a '.escapeshellarg($cmds[1]).' '. LOGFILE .' | less +G" > `tty`');
528                        $this->status = "Returning from log, updating data";
529                    }
530                    $this->statusexpire = time()+5; // it might be much "later" now
531                }
532                // tail the log file
533                else if (($cmds[0] == "tail" || $cmds[0] == "t")) {
534                    if (!file_exists(LOGFILE)) {
535                        $this->status = "Logfile can not be found: ". LOGFILE;
536                    }
537                    else {
538                        $this->doingTail = true;
539                        $this->scrClear();
540                        $this->scrPrintAt(1,0,$this->scrAsBold("Press CTRL+C to return to Z-Push-Top\n\n"));
541                        $secondary = "";
542                        if (isset($cmds[1])) $secondary =  " -n 200 | grep ".escapeshellarg($cmds[1]);
543                        system('bash -c "tail -f '. LOGFILE . $secondary . '" > `tty`');
544                        $this->doingTail = false;
545                        $this->status = "Returning from tail, updating data";
546                    }
547                    $this->statusexpire = time()+5; // it might be much "later" now
548                }
549                // tail the error log file
550                else if (($cmds[0] == "error" || $cmds[0] == "e")) {
551                    if (!file_exists(LOGERRORFILE)) {
552                        $this->status = "Error logfile can not be found: ". LOGERRORFILE;
553                    }
554                    else {
555                        $this->doingTail = true;
556                        $this->scrClear();
557                        $this->scrPrintAt(1,0,$this->scrAsBold("Press CTRL+C to return to Z-Push-Top\n\n"));
558                        $secondary = "";
559                        if (isset($cmds[1])) $secondary =  " -n 200 | grep ".escapeshellarg($cmds[1]);
560                        system('bash -c "tail -f '. LOGERRORFILE . $secondary . '" > `tty`');
561                        $this->doingTail = false;
562                        $this->status = "Returning from tail, updating data";
563                    }
564                    $this->statusexpire = time()+5; // it might be much "later" now
565                }
566
567                else if ($cmds[0] != "") {
568                    $this->status = sprintf("Command '%s' unknown", $cmds[0]);
569                    $this->statusexpire = $this->currenttime+8;
570                }
571                $this->action = "";
572            }
573        }
574    }
575
576    /**
577     * Signal handler function
578     *
579     * @param int   $signo      signal number
580     *
581     * @access public
582     * @return
583     */
584    public function SignalHandler($signo) {
585        // don't terminate if the signal was sent by terminating tail
586        if (!$this->doingTail) {
587            $this->topCollector->CollectData(true);
588            $this->topCollector->ClearLatest(true);
589            $this->terminate = true;
590        }
591    }
592
593    /**
594     * Returns usage instructions
595     *
596     * @return string
597     * @access public
598     */
599    public function UsageInstructions() {
600        $help = "Usage:\n\tz-push-top.php\n\n" .
601                "  Z-Push-Top is a live top-like overview of what Z-Push is doing. It does not have specific command line options.\n\n".
602                "  When Z-Push-Top is running you can specify certain actions and options which can be executed (listed below).\n".
603                "  This help information can also be shown inside Z-Push-Top by hitting 'help' or 'h'.\n\n";
604        $scrhelp = $this->scrHelp();
605        unset($scrhelp[0]);
606
607        $help .= implode("\n", $scrhelp);
608        $help .= "\n\n";
609        return $help;
610    }
611
612
613    /**
614     * Prints a 'help' text at the end of the page
615     *
616     * @access private
617     * @return array        with help lines
618     */
619    private function scrHelp() {
620        $h = array();
621        $secs = $this->helpexpire - $this->currenttime;
622        $h[] = "Actions supported by Z-Push-Top (help page still displayed for ".$secs."secs)";
623        $h[] = "  ".$this->scrAsBold("Action")."\t\t".$this->scrAsBold("Comment");
624        $h[] = "  ".$this->scrAsBold("h")." or ".$this->scrAsBold("help")."\t\tDisplays this information.";
625        $h[] = "  ".$this->scrAsBold("q").", ".$this->scrAsBold("quit")." or ".$this->scrAsBold(":q")."\t\tExits Z-Push-Top.";
626        $h[] = "  ".$this->scrAsBold("w")." or ".$this->scrAsBold("wide")."\t\tTries not to truncate data. Automatically done if more than 180 columns available.";
627        $h[] = "  ".$this->scrAsBold("f:VAL")." or ".$this->scrAsBold("filter:VAL")."\tOnly display connections which contain VAL. This value is case-insensitive.";
628        $h[] = "  ".$this->scrAsBold("f:")." or ".$this->scrAsBold("filter:")."\t\tWithout a search word: resets the filter.";
629        $h[] = "  ".$this->scrAsBold("l:STR")." or ".$this->scrAsBold("log:STR")."\tIssues 'less +G' on the logfile, after grepping on the optional STR.";
630        $h[] = "  ".$this->scrAsBold("t:STR")." or ".$this->scrAsBold("tail:STR")."\tIssues 'tail -f' on the logfile, grepping for optional STR.";
631        $h[] = "  ".$this->scrAsBold("e:STR")." or ".$this->scrAsBold("error:STR")."\tIssues 'tail -f' on the error logfile, grepping for optional STR.";
632        $h[] = "  ".$this->scrAsBold("r")." or ".$this->scrAsBold("reset")."\t\tResets 'wide' or 'filter'.";
633        $h[] = "  ".$this->scrAsBold("o:")." or ".$this->scrAsBold("option:")."\t\tSets display options. Valid options specified below";
634        $h[] = "  ".$this->scrAsBold("  p")." or ".$this->scrAsBold("push")."\t\tLists/not lists active and open push connections.";
635        $h[] = "  ".$this->scrAsBold("  a")." or ".$this->scrAsBold("action")."\t\tLists only active connections.";
636        $h[] = "  ".$this->scrAsBold("  u")." or ".$this->scrAsBold("unknown")."\tLists only unknown connections.";
637        $h[] = "  ".$this->scrAsBold("  10")." or ".$this->scrAsBold("20")."\t\tLists terminated connections for 10 or 20 seconds. Any other number can be used.";
638        $h[] = "  ".$this->scrAsBold("  d")." or ".$this->scrAsBold("default")."\tUses default options";
639
640        return $h;
641    }
642
643    /**
644     * Encapsulates string with different color escape characters
645     *
646     * @param string        $text
647     *
648     * @access private
649     * @return string       same text as bold
650     */
651    private function scrAsBold($text) {
652        return "\033[01m" . $text  ."\033[0m";
653    }
654
655    /**
656     * Prints one line of precessed data
657     *
658     * @param array     $l      line information
659     *
660     * @access private
661     * @return string
662     */
663    private function getLine($l) {
664        if ($this->wide === true)
665            return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'],6), $this->ptStr($l['ip'],16), $this->ptStr($l['user'],24), $this->ptStr($l['command'],16), $this->ptStr($this->sec2min($l['time']),8), $this->ptStr($l['devagent'],28), $this->ptStr($l['devid'],33, true), $l['addinfo']);
666        else
667            return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'],6), $this->ptStr($l['ip'],16), $this->ptStr($l['user'],8), $this->ptStr($l['command'],8), $this->ptStr($this->sec2min($l['time']),6), $this->ptStr($l['devagent'],20), $this->ptStr($l['devid'],12, true), $l['addinfo']);
668    }
669
670    /**
671     * Pads and trims string
672     *
673     * @param string    $str        to be trimmed/padded
674     * @param int       $size       characters to be considered
675     * @param boolean   $cutmiddle  (optional) indicates where to long information should
676     *                              be trimmed of, false means at the end
677     *
678     * @access private
679     * @return string
680     */
681    private function ptStr($str, $size, $cutmiddle = false) {
682        if (strlen($str) < $size)
683            return str_pad($str, $size);
684        else if ($cutmiddle == true) {
685            $cut = ($size-2)/2;
686            return $this->ptStr(substr($str,0,$cut) ."..". substr($str,(-1)*($cut-1)), $size);
687        }
688        else {
689            return substr($str,0,$size-3).".. ";
690        }
691    }
692
693    /**
694     * Tries to discover the size of the current terminal
695     *
696     * @access private
697     * @return array        'width' and 'height' as keys
698     */
699    private function scrGetSize() {
700        $tty = strtolower(exec('stty -a | fgrep columns'));
701        if (preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", $tty, $output) ||
702            preg_match_all("/([0-9]+).rows;.([0-9]+).columns;/", $tty, $output))
703            return array('width' => $output[2][0], 'height' => $output[1][0]);
704
705        return array('width' => 80, 'height' => 24);
706    }
707
708    /**
709     * Returns the version of the current Z-Push installation
710     *
711     * @access private
712     * @return string
713     */
714    private function getVersion() {
715        if (ZPUSH_VERSION == "SVN checkout" && file_exists(REAL_BASE_PATH.".svn/entries")) {
716            $svn = file(REAL_BASE_PATH.".svn/entries");
717            return "SVN " . substr(trim($svn[4]),stripos($svn[4],"z-push")+7) ." r".trim($svn[3]);
718        }
719        return ZPUSH_VERSION;
720    }
721
722    /**
723     * Converts seconds in MM:SS
724     *
725     * @param int   $s      seconds
726     *
727     * @access private
728     * @return string
729     */
730    private function sec2min($s) {
731        if (!is_int($s))
732            return $s;
733        return sprintf("%02.2d:%02.2d", floor($s/60), $s%60);
734    }
735
736    /**
737     * Resets the default colors of the terminal
738     *
739     * @access private
740     * @return
741     */
742    private function scrDefaultColors() {
743        echo "\033[0m";
744    }
745
746    /**
747     * Clears screen of the terminal
748     *
749     * @param array $data
750     *
751     * @access private
752     * @return
753     */
754    public function scrClear() {
755        echo "\033[2J";
756    }
757
758    /**
759     * Prints a text at a specific screen/terminal coordinates
760     *
761     * @param int       $row        row number
762     * @param int       $col        column number
763     * @param string    $text       to be printed
764     *
765     * @access private
766     * @return
767     */
768    private function scrPrintAt($row, $col, $text="") {
769        echo "\033[".$row.";".$col."H".$text;
770    }
771
772}
773