Passed
Push — master ( 068fe9...0fc7d4 )
by
unknown
05:57 queued 02:39
created

GSyncTop   F

Complexity

Total Complexity 143

Size/Duplication

Total Lines 693
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 369
dl 0
loc 693
rs 2
c 0
b 0
f 0
wmc 143

19 Methods

Rating   Name   Duplication   Size   Complexity  
A IsAvailable() 0 6 3
A __construct() 0 19 3
D processData() 0 82 32
A scrPrintAt() 0 2 1
A scrAsBold() 0 2 1
F scrOverview() 0 155 28
A sec2min() 0 6 2
A scrClear() 0 2 1
A scrHelp() 0 22 1
A getVersion() 0 2 1
A ptStr() 0 11 3
D readLineProcess() 0 117 52
A getLine() 0 6 2
A scrGetSize() 0 8 3
A UsageInstructions() 0 12 1
A initialize() 0 9 1
A scrDefaultColors() 0 2 1
A run() 0 39 5
A SignalHandler() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like GSyncTop often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GSyncTop, and based on these observations, apply Extract Interface, too.

1
#!/usr/bin/env php
2
<?php
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
7
 *
8
 * Shows realtime information about connected devices and active connections
9
 * in a top-style format.
10
 */
11
12
require_once 'vendor/autoload.php';
13
if (!defined('GSYNC_CONFIG')) {
14
    define('GSYNC_CONFIG', 'config.php');
15
}
16
17
include_once GSYNC_CONFIG;
18
19
/*
20
 * MAIN
21
 */
22
    declare(ticks=1);
23
    define('BASE_PATH_CLI', dirname(__FILE__) . '/');
24
    set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI);
25
26
    if (!defined('GSYNC_CONFIG')) {
27
        define('GSYNC_CONFIG', BASE_PATH_CLI . 'config.php');
28
    }
29
30
    include_once GSYNC_CONFIG;
31
32
    try {
33
        GSync::CheckConfig();
34
        if (!function_exists('pcntl_signal')) {
35
            throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system.");
36
        }
37
38
        if ('cli' != php_sapi_name()) {
39
            throw new FatalException('This script can only be called from the CLI.');
40
        }
41
42
        $zpt = new GSyncTop();
43
44
        // check if help was requested from CLI
45
        if (in_array('-h', $argv) || in_array('--help', $argv)) {
46
            echo $zpt->UsageInstructions();
47
48
            exit(0);
49
        }
50
51
        if ($zpt->IsAvailable()) {
52
            pcntl_signal(SIGINT, [$zpt, 'SignalHandler']);
53
            $zpt->run();
54
            $zpt->scrClear();
55
            system('stty sane');
56
        } else {
57
            echo "grommunio-sync interprocess communication (IPC) is not available or TopCollector is disabled.\n";
58
        }
59
    } catch (GSyncException $zpe) {
60
        fwrite(STDERR, get_class($zpe) . ': ' . $zpe->getMessage() . "\n");
61
62
        exit(1);
63
    }
64
65
    echo "terminated\n";
66
67
/*
68
 * grommunio-sync-top
69
 */
70
class GSyncTop {
71
    // show options
72
    public const SHOW_DEFAULT = 0;
73
    public const SHOW_ACTIVE_ONLY = 1;
74
    public const SHOW_UNKNOWN_ONLY = 2;
75
    public const SHOW_TERM_DEFAULT_TIME = 5; // 5 secs
76
77
    private $topCollector;
78
    private $starttime;
79
    private $action;
80
    private $filter;
81
    private $status;
82
    private $statusexpire;
83
    private $wide;
84
    private $wasEnabled;
85
    private $terminate;
86
    private $scrSize;
87
    private $pingInterval;
88
    private $showPush;
89
    private $showTermSec;
90
91
    private $linesActive = [];
92
    private $linesOpen = [];
93
    private $linesUnknown = [];
94
    private $linesTerm = [];
95
    private $pushConn = 0;
96
    private $activeConn = [];
97
    private $activeHosts = [];
98
    private $activeUsers = [];
99
    private $activeDevices = [];
100
101
    /**
102
     * Constructor.
103
     */
104
    public function __construct() {
105
        $this->starttime = time();
106
        $this->currenttime = time();
0 ignored issues
show
Bug Best Practice introduced by
The property currenttime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
107
        $this->action = '';
108
        $this->filter = false;
109
        $this->status = false;
110
        $this->statusexpire = 0;
111
        $this->helpexpire = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property helpexpire does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
112
        $this->doingTail = false;
0 ignored issues
show
Bug Best Practice introduced by
The property doingTail does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
113
        $this->wide = false;
114
        $this->terminate = false;
115
        $this->showPush = true;
116
        $this->showOption = self::SHOW_DEFAULT;
0 ignored issues
show
Bug Best Practice introduced by
The property showOption does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
117
        $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
118
        $this->scrSize = ['width' => 80, 'height' => 24];
119
        $this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12;
120
121
        // get a TopCollector
122
        $this->topCollector = new TopCollector();
123
    }
124
125
    /**
126
     * Main loop of grommunio-sync-top
127
     * Runs until termination is requested.
128
     *
129
     * @return
130
     */
131
    public function run() {
132
        $this->initialize();
133
134
        do {
135
            $this->currenttime = time();
0 ignored issues
show
Bug Best Practice introduced by
The property currenttime does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
136
137
            // see if shared memory is active
138
            if (!$this->IsAvailable()) {
139
                $this->terminate = true;
140
            }
141
142
            // active processes should continue sending data
143
            $this->topCollector->CollectData();
144
145
            // get and process data from processes
146
            $this->topCollector->ClearLatest();
147
            $topdata = $this->topCollector->ReadLatest();
148
            $this->processData($topdata);
149
150
            // clear screen
151
            $this->scrClear();
152
153
            // check if screen size changed
154
            $s = $this->scrGetSize();
155
            if ($this->scrSize['width'] != $s['width']) {
156
                if ($s['width'] > 180) {
157
                    $this->wide = true;
158
                } else {
159
                    $this->wide = false;
160
                }
161
            }
162
            $this->scrSize = $s;
163
164
            // print overview
165
            $this->scrOverview();
166
167
            // wait for user input
168
            $this->readLineProcess();
169
        } while (true != $this->terminate);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
170
    }
171
172
    /**
173
     * Indicates if TopCollector is available collecting data.
174
     *
175
     * @return bool
176
     */
177
    public function IsAvailable() {
178
        if (defined('TOPCOLLECTOR_DISABLED') && true === constant('TOPCOLLECTOR_DISABLED')) {
179
            return false;
180
        }
181
182
        return $this->topCollector->IsActive();
183
    }
184
185
    /**
186
     * Signal handler function.
187
     *
188
     * @param int $signo signal number
189
     *
190
     * @return
191
     */
192
    public function SignalHandler($signo) {
0 ignored issues
show
Unused Code introduced by
The parameter $signo is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

192
    public function SignalHandler(/** @scrutinizer ignore-unused */ $signo) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
193
        // don't terminate if the signal was sent by terminating tail
194
        if (!$this->doingTail) {
195
            $this->topCollector->CollectData(true);
196
            $this->topCollector->ClearLatest(true);
197
            $this->terminate = true;
198
        }
199
    }
200
201
    /**
202
     * Returns usage instructions.
203
     *
204
     * @return string
205
     */
206
    public function UsageInstructions() {
207
        $help = "Usage:\n\tgrommunio-sync-top.php\n\n" .
208
                "  grommunio-sync-top is a live top-like overview of what grommunio-sync is doing. It does not have specific command line options.\n\n" .
209
                "  When grommunio-sync-top is running you can specify certain actions and options which can be executed (listed below).\n" .
210
                "  This help information can also be shown inside grommunio-sync-top by hitting 'help' or 'h'.\n\n";
211
        $scrhelp = $this->scrHelp();
212
        unset($scrhelp[0]);
213
214
        $help .= implode("\n", $scrhelp);
215
        $help .= "\n\n";
216
217
        return $help;
218
    }
219
220
    /**
221
     * Clears screen of the terminal.
222
     *
223
     * @param array $data
224
     *
225
     * @return
226
     */
227
    public function scrClear() {
228
        echo "\033[2J";
229
    }
230
231
    /**
232
     * Requests data from the running grommunio-sync processes.
233
     *
234
     * @return
235
     */
236
    private function initialize() {
237
        // request feedback from active processes
238
        $this->wasEnabled = $this->topCollector->CollectData();
239
240
        // remove obsolete data
241
        $this->topCollector->ClearLatest(true);
242
243
        // start with default colours
244
        $this->scrDefaultColors();
245
    }
246
247
    /**
248
     * Processes data written by the running processes.
249
     *
250
     * @param array $data
251
     *
252
     * @return
253
     */
254
    private function processData($data) {
255
        $this->linesActive = [];
256
        $this->linesOpen = [];
257
        $this->linesUnknown = [];
258
        $this->linesTerm = [];
259
        $this->pushConn = 0;
260
        $this->activeConn = [];
261
        $this->activeHosts = [];
262
        $this->activeUsers = [];
263
        $this->activeDevices = [];
264
265
        if (!is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
266
            return;
267
        }
268
269
        foreach ($data as $devid => $users) {
270
            foreach ($users as $user => $pids) {
271
                foreach ($pids as $pid => $line) {
272
                    if (!is_array($line)) {
273
                        continue;
274
                    }
275
276
                    $line['command'] = Utils::GetCommandFromCode($line['command']);
277
278
                    if (0 == $line['ended']) {
279
                        $this->activeDevices[$devid] = 1;
280
                        $this->activeUsers[$user] = 1;
281
                        $this->activeConn[$pid] = 1;
282
                        $this->activeHosts[$line['ip']] = 1;
283
284
                        $line['time'] = $this->currenttime - $line['start'];
285
                        if (true === $line['push']) {
286
                            ++$this->pushConn;
287
                        }
288
289
                        // ignore push connections
290
                        if (true === $line['push'] && !$this->showPush) {
291
                            continue;
292
                        }
293
294
                        if (false !== $this->filter) {
295
                            $f = $this->filter;
296
                            if (!($line['pid'] == $f || $line['ip'] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?{$f}.*?/i", $line['user'])
0 ignored issues
show
Bug introduced by
$f of type true is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

296
                            if (!($line['pid'] == $f || $line['ip'] == $f || strtolower($line['command']) == strtolower(/** @scrutinizer ignore-type */ $f) || preg_match("/.*?{$f}.*?/i", $line['user'])
Loading history...
297
                                || preg_match("/.*?{$f}.*?/i", $line['devagent']) || preg_match("/.*?{$f}.*?/i", $line['devid']) || preg_match("/.*?{$f}.*?/i", $line['addinfo']))) {
298
                                continue;
299
                            }
300
                        }
301
302
                        $lastUpdate = $this->currenttime - $line['update'];
303
                        if ($this->currenttime - $line['update'] < 2) {
304
                            $this->linesActive[$line['update'] . $line['pid']] = $line;
305
                        } elseif ((true === $line['push'] && $lastUpdate > ($this->pingInterval + 2)) || (true !== $line['push'] && $lastUpdate > 4)) {
306
                            $this->linesUnknown[$line['update'] . $line['pid']] = $line;
307
                        } else {
308
                            $this->linesOpen[$line['update'] . $line['pid']] = $line;
309
                        }
310
                    } else {
311
                        // do not show terminated + expired connections
312
                        if ($line['ended'] + $this->showTermSec < $this->currenttime) {
313
                            continue;
314
                        }
315
316
                        if (false !== $this->filter) {
317
                            $f = $this->filter;
318
                            if (!($line['pid'] == $f || $line['ip'] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?{$f}.*?/i", $line['user'])
319
                                || preg_match("/.*?{$f}.*?/i", $line['devagent']) || preg_match("/.*?{$f}.*?/i", $line['devid']) || preg_match("/.*?{$f}.*?/i", $line['addinfo']))) {
320
                                continue;
321
                            }
322
                        }
323
324
                        $line['time'] = $line['ended'] - $line['start'];
325
                        $this->linesTerm[$line['update'] . $line['pid']] = $line;
326
                    }
327
                }
328
            }
329
        }
330
331
        // sort by execution time
332
        krsort($this->linesActive);
333
        krsort($this->linesOpen);
334
        krsort($this->linesUnknown);
335
        krsort($this->linesTerm);
336
    }
337
338
    /**
339
     * Prints data to the terminal.
340
     *
341
     * @return
342
     */
343
    private function scrOverview() {
344
        $linesAvail = $this->scrSize['height'] - 8;
345
        $lc = 1;
346
        $this->scrPrintAt($lc, 0, "\033[1mgrommunio-sync-top live statistics\033[0m\t\t\t\t\t" . @strftime('%d/%m/%Y %T') . "\n");
0 ignored issues
show
Bug introduced by
Are you sure @strftime('%d/%m/%Y %T') of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

346
        $this->scrPrintAt($lc, 0, "\033[1mgrommunio-sync-top live statistics\033[0m\t\t\t\t\t" . /** @scrutinizer ignore-type */ @strftime('%d/%m/%Y %T') . "\n");
Loading history...
347
        ++$lc;
348
349
        $this->scrPrintAt($lc, 0, sprintf("Open connections: %d\t\t\t\tUsers:\t %d\tgrommunio-sync:   %s ", count($this->activeConn), count($this->activeUsers), $this->getVersion()));
350
        ++$lc;
351
        $this->scrPrintAt($lc, 0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices), phpversion('mapi')));
352
        ++$lc;
353
        $this->scrPrintAt($lc, 0, sprintf("                                                Hosts:\t %d", count($this->activeHosts)));
354
        ++$lc;
355
        ++$lc;
356
357
        $this->scrPrintAt($lc, 0, "\033[4m" . $this->getLine(['pid' => 'PID', 'ip' => 'IP', 'user' => 'USER', 'command' => 'COMMAND', 'time' => 'TIME', 'devagent' => 'AGENT', 'devid' => 'DEVID', 'addinfo' => 'Additional Information']) . str_repeat(' ', 20) . "\033[0m");
358
        ++$lc;
359
360
        // print help text if requested
361
        $hl = 0;
362
        if ($this->helpexpire > $this->currenttime) {
363
            $help = $this->scrHelp();
364
            $linesAvail -= count($help);
365
            $hl = $this->scrSize['height'] - count($help) - 1;
366
            foreach ($help as $h) {
367
                $this->scrPrintAt($hl, 0, $h);
368
                ++$hl;
369
            }
370
        }
371
372
        $toPrintActive = $linesAvail;
373
        $toPrintOpen = $linesAvail;
374
        $toPrintUnknown = $linesAvail;
375
        $toPrintTerm = $linesAvail;
376
377
        // default view: show all unknown, no terminated and half active+open
378
        if (count($this->linesActive) + count($this->linesOpen) + count($this->linesUnknown) > $linesAvail) {
379
            $toPrintUnknown = count($this->linesUnknown);
380
            $toPrintActive = count($this->linesActive);
381
            $toPrintOpen = $linesAvail - $toPrintUnknown - $toPrintActive;
382
            $toPrintTerm = 0;
383
        }
384
385
        if (self::SHOW_ACTIVE_ONLY == $this->showOption) {
386
            $toPrintActive = $linesAvail;
387
            $toPrintOpen = 0;
388
            $toPrintUnknown = 0;
389
            $toPrintTerm = 0;
390
        }
391
392
        if (self::SHOW_UNKNOWN_ONLY == $this->showOption) {
393
            $toPrintActive = 0;
394
            $toPrintOpen = 0;
395
            $toPrintUnknown = $linesAvail;
396
            $toPrintTerm = 0;
397
        }
398
399
        $linesprinted = 0;
400
        foreach ($this->linesActive as $time => $l) {
401
            if ($linesprinted >= $toPrintActive) {
402
                break;
403
            }
404
405
            $this->scrPrintAt($lc, 0, "\033[01m" . $this->getLine($l) . "\033[0m");
406
            ++$lc;
407
            ++$linesprinted;
408
        }
409
410
        $linesprinted = 0;
411
        foreach ($this->linesOpen as $time => $l) {
412
            if ($linesprinted >= $toPrintOpen) {
413
                break;
414
            }
415
416
            $this->scrPrintAt($lc, 0, $this->getLine($l));
417
            ++$lc;
418
            ++$linesprinted;
419
        }
420
421
        $linesprinted = 0;
422
        foreach ($this->linesUnknown as $time => $l) {
423
            if ($linesprinted >= $toPrintUnknown) {
424
                break;
425
            }
426
427
            $color = '0;31m';
428
            if (false == $l['push'] && $time - $l['start'] > 30) {
429
                $color = '1;31m';
430
            }
431
            $this->scrPrintAt($lc, 0, "\033[0" . $color . $this->getLine($l) . "\033[0m");
432
            ++$lc;
433
            ++$linesprinted;
434
        }
435
436
        if ($toPrintTerm > 0) {
437
            $toPrintTerm = $linesAvail - $lc + 6;
438
        }
439
440
        $linesprinted = 0;
441
        foreach ($this->linesTerm as $time => $l) {
442
            if ($linesprinted >= $toPrintTerm) {
443
                break;
444
            }
445
446
            $this->scrPrintAt($lc, 0, "\033[01;30m" . $this->getLine($l) . "\033[0m");
447
            ++$lc;
448
            ++$linesprinted;
449
        }
450
451
        // add the lines used when displaying the help text
452
        $lc += $hl;
453
        $this->scrPrintAt($lc, 0, "\033[K");
454
        ++$lc;
455
        $this->scrPrintAt($lc, 0, "Colorscheme: \033[01mActive  \033[0mOpen  \033[01;31mUnknown  \033[01;30mTerminated\033[0m");
456
457
        // remove old status
458
        if ($this->statusexpire < $this->currenttime) {
459
            $this->status = false;
460
        }
461
462
        // show request information and help command
463
        if ($this->starttime + 6 > $this->currenttime) {
464
            $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";
465
            $this->statusexpire = $this->currenttime + 1;
466
        }
467
468
        $str = '';
469
        if (!$this->showPush) {
470
            $str .= "\033[00;32mPush: \033[01;32mNo\033[0m   ";
471
        }
472
473
        if (self::SHOW_ACTIVE_ONLY == $this->showOption) {
474
            $str .= "\033[01;32mActive only\033[0m   ";
475
        }
476
477
        if (self::SHOW_UNKNOWN_ONLY == $this->showOption) {
478
            $str .= "\033[01;32mUnknown only\033[0m   ";
479
        }
480
481
        if (self::SHOW_TERM_DEFAULT_TIME != $this->showTermSec) {
482
            $str .= "\033[01;32mTerminated: " . $this->showTermSec . "s\033[0m   ";
483
        }
484
485
        if (false !== $this->filter || (false !== $this->status && $this->statusexpire > $this->currenttime)) {
486
            // print filter in green
487
            if (false !== $this->filter) {
488
                $str .= "\033[00;32mFilter: \033[01;32m{$this->filter}\033[0m   ";
489
            }
490
            // print status in red
491
            if (false !== $this->status) {
492
                $str .= "\033[00;31m{$this->status}\033[0m";
493
            }
494
        }
495
        $this->scrPrintAt(5, 0, $str);
496
497
        $this->scrPrintAt(4, 0, "Action: \033[01m" . $this->action . "\033[0m");
498
    }
499
500
    /**
501
     * Waits for a keystroke and processes the requested command.
502
     *
503
     * @return
504
     */
505
    private function readLineProcess() {
506
        $ans = explode('^^', `bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"`);
507
508
        if ($ans[0] < 128) {
509
            if (isset($ans[1]) && '7f' == bin2hex(trim($ans[1]))) {
510
                $this->action = substr($this->action, 0, -1);
511
            }
512
513
            if (isset($ans[1]) && '' != $ans[1]) {
514
                $this->action .= trim(preg_replace('/[^A-Za-z0-9:]/', '', $ans[1]));
515
            }
516
517
            if ('30' == bin2hex($ans[0]) && '0a' == bin2hex($ans[1])) {
518
                $cmds = explode(':', $this->action);
519
                if ('quit' == $cmds[0] || 'q' == $cmds[0] || (isset($cmds[1]) && '' == $cmds[0] && 'q' == $cmds[1])) {
520
                    $this->topCollector->CollectData(true);
521
                    $this->topCollector->ClearLatest(true);
522
523
                    $this->terminate = true;
524
                } elseif ('clear' == $cmds[0]) {
525
                    $this->topCollector->ClearLatest(true);
526
                    $this->topCollector->CollectData(true);
527
                    $this->topCollector->ReInitIPC();
528
                } elseif ('filter' == $cmds[0] || 'f' == $cmds[0]) {
529
                    if (!isset($cmds[1]) || '' == $cmds[1]) {
530
                        $this->filter = false;
531
                        $this->status = 'No filter';
532
                        $this->statusexpire = $this->currenttime + 5;
533
                    } else {
534
                        $this->filter = $cmds[1];
535
                        $this->status = false;
536
                    }
537
                } elseif ('option' == $cmds[0] || 'o' == $cmds[0]) {
538
                    if (!isset($cmds[1]) || '' == $cmds[1]) {
539
                        $this->status = "Option value needs to be specified. See 'help' or 'h' for instructions";
540
                        $this->statusexpire = $this->currenttime + 5;
541
                    } elseif ('p' == $cmds[1] || 'push' == $cmds[1] || 'ping' == $cmds[1]) {
542
                        $this->showPush = !$this->showPush;
543
                    } elseif ('a' == $cmds[1] || 'active' == $cmds[1]) {
544
                        $this->showOption = self::SHOW_ACTIVE_ONLY;
0 ignored issues
show
Bug Best Practice introduced by
The property showOption does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
545
                    } elseif ('u' == $cmds[1] || 'unknown' == $cmds[1]) {
546
                        $this->showOption = self::SHOW_UNKNOWN_ONLY;
547
                    } elseif ('d' == $cmds[1] || 'default' == $cmds[1]) {
548
                        $this->showOption = self::SHOW_DEFAULT;
549
                        $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME;
550
                        $this->showPush = true;
551
                    } elseif (is_numeric($cmds[1])) {
552
                        $this->showTermSec = $cmds[1];
553
                    } else {
554
                        $this->status = sprintf("Option '%s' unknown", $cmds[1]);
555
                        $this->statusexpire = $this->currenttime + 5;
556
                    }
557
                } elseif ('reset' == $cmds[0] || 'r' == $cmds[0]) {
558
                    $this->filter = false;
559
                    $this->wide = false;
560
                    $this->helpexpire = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property helpexpire does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
561
                    $this->status = 'reset';
562
                    $this->statusexpire = $this->currenttime + 2;
563
                }
564
                // enable/disable wide view
565
                elseif ('wide' == $cmds[0] || 'w' == $cmds[0]) {
566
                    $this->wide = !$this->wide;
567
                    $this->status = ($this->wide) ? 'w i d e  view' : 'normal view';
568
                    $this->statusexpire = $this->currenttime + 2;
569
                } elseif ('help' == $cmds[0] || 'h' == $cmds[0]) {
570
                    $this->helpexpire = $this->currenttime + 20;
571
                }
572
                // grep the log file
573
                elseif (('log' == $cmds[0] || 'l' == $cmds[0]) && isset($cmds[1])) {
574
                    if (!file_exists(LOGFILE)) {
575
                        $this->status = 'Logfile can not be found: ' . LOGFILE;
576
                    } else {
577
                        system('bash -c "fgrep -a ' . escapeshellarg($cmds[1]) . ' ' . LOGFILE . ' | less +G" > `tty`');
578
                        $this->status = 'Returning from log, updating data';
579
                    }
580
                    $this->statusexpire = time() + 5; // it might be much "later" now
581
                }
582
                // tail the log file
583
                elseif (('tail' == $cmds[0] || 't' == $cmds[0])) {
584
                    if (!file_exists(LOGFILE)) {
585
                        $this->status = 'Logfile can not be found: ' . LOGFILE;
586
                    } else {
587
                        $this->doingTail = true;
0 ignored issues
show
Bug Best Practice introduced by
The property doingTail does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
588
                        $this->scrClear();
589
                        $this->scrPrintAt(1, 0, $this->scrAsBold("Press CTRL+C to return to grommunio-sync-top\n\n"));
590
                        $secondary = '';
591
                        if (isset($cmds[1])) {
592
                            $secondary = ' -n 200 | grep ' . escapeshellarg($cmds[1]);
593
                        }
594
                        system('bash -c "tail -f ' . LOGFILE . $secondary . '" > `tty`');
595
                        $this->doingTail = false;
596
                        $this->status = 'Returning from tail, updating data';
597
                    }
598
                    $this->statusexpire = time() + 5; // it might be much "later" now
599
                }
600
                // tail the error log file
601
                elseif (('error' == $cmds[0] || 'e' == $cmds[0])) {
602
                    if (!file_exists(LOGERRORFILE)) {
603
                        $this->status = 'Error logfile can not be found: ' . LOGERRORFILE;
604
                    } else {
605
                        $this->doingTail = true;
606
                        $this->scrClear();
607
                        $this->scrPrintAt(1, 0, $this->scrAsBold("Press CTRL+C to return to grommunio-sync-top\n\n"));
608
                        $secondary = '';
609
                        if (isset($cmds[1])) {
610
                            $secondary = ' -n 200 | grep ' . escapeshellarg($cmds[1]);
611
                        }
612
                        system('bash -c "tail -f ' . LOGERRORFILE . $secondary . '" > `tty`');
613
                        $this->doingTail = false;
614
                        $this->status = 'Returning from tail, updating data';
615
                    }
616
                    $this->statusexpire = time() + 5; // it might be much "later" now
617
                } elseif ('' != $cmds[0]) {
618
                    $this->status = sprintf("Command '%s' unknown", $cmds[0]);
619
                    $this->statusexpire = $this->currenttime + 8;
620
                }
621
                $this->action = '';
622
            }
623
        }
624
    }
625
626
    /**
627
     * Prints a 'help' text at the end of the page.
628
     *
629
     * @return array with help lines
630
     */
631
    private function scrHelp() {
632
        $h = [];
633
        $secs = $this->helpexpire - $this->currenttime;
634
        $h[] = 'Actions supported by grommunio-sync-top (help page still displayed for ' . $secs . 'secs)';
635
        $h[] = '  ' . $this->scrAsBold('Action') . "\t\t" . $this->scrAsBold('Comment');
636
        $h[] = '  ' . $this->scrAsBold('h') . ' or ' . $this->scrAsBold('help') . "\t\tDisplays this information.";
637
        $h[] = '  ' . $this->scrAsBold('q') . ', ' . $this->scrAsBold('quit') . ' or ' . $this->scrAsBold(':q') . "\t\tExits grommunio-sync-top.";
638
        $h[] = '  ' . $this->scrAsBold('w') . ' or ' . $this->scrAsBold('wide') . "\t\tTries not to truncate data. Automatically done if more than 180 columns available.";
639
        $h[] = '  ' . $this->scrAsBold('f:VAL') . ' or ' . $this->scrAsBold('filter:VAL') . "\tOnly display connections which contain VAL. This value is case-insensitive.";
640
        $h[] = '  ' . $this->scrAsBold('f:') . ' or ' . $this->scrAsBold('filter:') . "\t\tWithout a search word: resets the filter.";
641
        $h[] = '  ' . $this->scrAsBold('l:STR') . ' or ' . $this->scrAsBold('log:STR') . "\tIssues 'less +G' on the logfile, after grepping on the optional STR.";
642
        $h[] = '  ' . $this->scrAsBold('t:STR') . ' or ' . $this->scrAsBold('tail:STR') . "\tIssues 'tail -f' on the logfile, grepping for optional STR.";
643
        $h[] = '  ' . $this->scrAsBold('e:STR') . ' or ' . $this->scrAsBold('error:STR') . "\tIssues 'tail -f' on the error logfile, grepping for optional STR.";
644
        $h[] = '  ' . $this->scrAsBold('r') . ' or ' . $this->scrAsBold('reset') . "\t\tResets 'wide' or 'filter'.";
645
        $h[] = '  ' . $this->scrAsBold('o:') . ' or ' . $this->scrAsBold('option:') . "\t\tSets display options. Valid options specified below";
646
        $h[] = '  ' . $this->scrAsBold('  p') . ' or ' . $this->scrAsBold('push') . "\t\tLists/not lists active and open push connections.";
647
        $h[] = '  ' . $this->scrAsBold('  a') . ' or ' . $this->scrAsBold('action') . "\t\tLists only active connections.";
648
        $h[] = '  ' . $this->scrAsBold('  u') . ' or ' . $this->scrAsBold('unknown') . "\tLists only unknown connections.";
649
        $h[] = '  ' . $this->scrAsBold('  10') . ' or ' . $this->scrAsBold('20') . "\t\tLists terminated connections for 10 or 20 seconds. Any other number can be used.";
650
        $h[] = '  ' . $this->scrAsBold('  d') . ' or ' . $this->scrAsBold('default') . "\tUses default options";
651
652
        return $h;
653
    }
654
655
    /**
656
     * Encapsulates string with different color escape characters.
657
     *
658
     * @param string $text
659
     *
660
     * @return string same text as bold
661
     */
662
    private function scrAsBold($text) {
663
        return "\033[01m" . $text . "\033[0m";
664
    }
665
666
    /**
667
     * Prints one line of precessed data.
668
     *
669
     * @param array $l line information
670
     *
671
     * @return string
672
     */
673
    private function getLine($l) {
674
        if (true === $this->wide) {
675
            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']);
676
        }
677
678
        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']);
679
    }
680
681
    /**
682
     * Pads and trims string.
683
     *
684
     * @param string $str       to be trimmed/padded
685
     * @param int    $size      characters to be considered
686
     * @param bool   $cutmiddle (optional) indicates where to long information should
687
     *                          be trimmed of, false means at the end
688
     *
689
     * @return string
690
     */
691
    private function ptStr($str, $size, $cutmiddle = false) {
692
        if (strlen($str) < $size) {
693
            return str_pad($str, $size);
694
        }
695
        if (true == $cutmiddle) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
696
            $cut = ($size - 2) / 2;
697
698
            return $this->ptStr(substr($str, 0, $cut) . '..' . substr($str, (-1) * ($cut - 1)), $size);
699
        }
700
701
        return substr($str, 0, $size - 3) . '.. ';
702
    }
703
704
    /**
705
     * Tries to discover the size of the current terminal.
706
     *
707
     * @return array 'width' and 'height' as keys
708
     */
709
    private function scrGetSize() {
710
        $tty = strtolower(exec('stty -a | fgrep columns'));
711
        if (preg_match_all('/rows.([0-9]+);.columns.([0-9]+);/', $tty, $output)
712
            || preg_match_all('/([0-9]+).rows;.([0-9]+).columns;/', $tty, $output)) {
713
            return ['width' => $output[2][0], 'height' => $output[1][0]];
714
        }
715
716
        return ['width' => 80, 'height' => 24];
717
    }
718
719
    /**
720
     * Returns the version of the current grommunio-sync installation.
721
     *
722
     * @return string
723
     */
724
    private function getVersion() {
725
        return GROMMUNIOSYNC_VERSION;
726
    }
727
728
    /**
729
     * Converts seconds in MM:SS.
730
     *
731
     * @param int $s seconds
732
     *
733
     * @return string
734
     */
735
    private function sec2min($s) {
736
        if (!is_int($s)) {
0 ignored issues
show
introduced by
The condition is_int($s) is always true.
Loading history...
737
            return $s;
738
        }
739
740
        return sprintf('%02.2d:%02.2d', floor($s / 60), $s % 60);
741
    }
742
743
    /**
744
     * Resets the default colors of the terminal.
745
     *
746
     * @return
747
     */
748
    private function scrDefaultColors() {
749
        echo "\033[0m";
750
    }
751
752
    /**
753
     * Prints a text at a specific screen/terminal coordinates.
754
     *
755
     * @param int    $row  row number
756
     * @param int    $col  column number
757
     * @param string $text to be printed
758
     *
759
     * @return
760
     */
761
    private function scrPrintAt($row, $col, $text = '') {
762
        echo "\033[" . $row . ';' . $col . 'H' . $text;
763
    }
764
}
765