taskman_atf/atf.inc.php

470 lines
13 KiB
PHP
Raw Permalink Normal View History

2022-07-26 17:44:58 +03:00
<?php
2023-11-07 15:42:40 +03:00
namespace ATF;
2022-08-05 18:23:05 +03:00
use Exception;
2023-11-07 15:42:40 +03:00
use Amp;
use taskman;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function host_exec_async(string $atf_host, string $cmd, $opts = 0, $timeout = 10) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $cmd, $opts, $timeout) {
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$timeout_cmd = "timeout -k 5s $timeout $cmd";
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$res = yield taskman\deploy_ssh_exec_async($atf_host, $timeout_cmd, $opts);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
list($status, $lines) = current($res);
//in case of timeout we set status to false explicitely
if($status === 124)
$status = false;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
return array($status, $lines);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
});
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function host_exec(string $atf_host, string $cmd, $opts = 0, $timeout = 10)
{
$timeout_cmd = "timeout -k 5s $timeout $cmd";
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$res = taskman\deploy_ssh_exec($atf_host, $timeout_cmd, $opts);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
list($status, $lines) = current($res);
//in case of timeout we set status to false explicitely
if($status === 124)
$status = false;
2022-08-18 20:37:36 +03:00
2023-11-07 15:42:40 +03:00
return array($status, $lines);
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function host_get_file(string $atf_host, string $file_name)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$file_data = current(taskman\deploy_get_file($atf_host, $file_name));
return $file_data;
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function host_put_file(string $atf_host, string $local_path, string $remote_path)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
taskman\deploy_rsync($atf_host, $local_path, $remote_path);
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function host_put_file_async(string $atf_host, string $local_path, string $remote_path)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return taskman\deploy_rsync_async($atf_host, $local_path, $remote_path);
2022-07-26 17:44:58 +03:00
}
2023-11-07 16:08:42 +03:00
function spread(array $items, $num)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$k = sizeof($items) / $num;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$res = array();
for($i=0;$i<$num;++$i)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$tmp = array_slice($items, (int)floor($k*$i), (int)ceil($k));
$res[$i] = $tmp;
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
return $res;
2022-07-26 17:44:58 +03:00
}
2023-11-07 16:08:42 +03:00
function slice(array $items, $max)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$sliced = array();
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$offset = 0;
while(true)
2022-08-04 14:59:17 +03:00
{
2023-11-07 15:42:40 +03:00
$tmp = array_slice($items, $offset, $max);
if(!$tmp)
break;
$sliced[] = $tmp;
$offset += $max;
2022-08-04 14:59:17 +03:00
}
2023-11-07 15:42:40 +03:00
return $sliced;
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function _trim($txt, $max_len)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return strlen($txt) > $max_len ? substr($txt, 0, $max_len) . "..." : $txt;
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function _trim_start($txt, $max_len)
{
return strlen($txt) > $max_len ? "..." . substr($txt, strlen($txt) - $max_len) : $txt;
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function _trim_lines($txt, $max_lines)
{
$lines = explode("\n", $txt, $max_lines+1);
$res = '';
for($i=0;$i<sizeof($lines) && $i<$max_lines;++$i)
$res .= $lines[$i] . "\n";
return $res;
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function _try_lz4_replay(string $txt)
{
if(!$txt)
return $txt;
//let's check if it's already archieved
if(strlen($txt) > 2 && $txt[1] === ':')
return $txt;
if(!function_exists('lz4_compress'))
return $txt;
return "4:" . base64_encode(lz4_compress(base64_decode($txt)));
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function log($msg)
{
echo date("Y-m-d H:i:s") . " " . $msg . "\n";
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function err($msg)
{
taskman\stderr(date("Y-m-d H:i:s") . " " . $msg . "\n");
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function _retry($max_tries, $func)
{
for($i=0;$i<$max_tries;++$i)
2022-07-26 17:44:58 +03:00
{
try
{
2023-11-07 15:42:40 +03:00
$func();
return;
2022-07-26 17:44:58 +03:00
}
catch(Exception $e)
{
2023-11-07 15:42:40 +03:00
if(($i+1) == $max_tries)
throw $e;
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
sleep(1);
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function adb_reboot(string $atf_host)
{
host_exec($atf_host, "killall adb", DEPLOY_OPT_ERR_OK);
_retry(3, function() use($atf_host) {
host_exec($atf_host, "%{adb}% kill-server");
});
host_exec($atf_host, "%{adb}% start-server");
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function reboot_devices(string $atf_host, array $devices, int $wait_after = 40)
{
log("Rebooting devices...");
//let's reboot devices
$ces = array();
foreach($devices as $device)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$ces[] = Amp\call(function() use($atf_host, $device, $wait_after) {
yield host_exec_async($atf_host, "%{adb}% -s $device reboot", DEPLOY_OPT_ERR_OK);
yield Amp\delay($wait_after*1000);
});
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
Amp\Promise\wait(Amp\Promise\all($ces));
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function get_ext_status_async(string $atf_host, string $device, int $timeout = 20) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $timeout) {
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$file_data = yield device_pull_file_async($atf_host, $device, "atf_status.js", $timeout, /*throw on timeout*/true);
if($file_data === null)
return null;
$file_data = preg_replace('/[[:cntrl:]]/', '', $file_data);
return json_decode($file_data, true);
2022-07-26 17:44:58 +03:00
});
}
function stop_app_async(string $atf_host, string $device) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device) {
yield host_exec_async($atf_host, "%{adb}% -s $device shell am force-stop %{package_id}%", DEPLOY_OPT_ERR_OK, 30);
});
}
function start_app_async(string $atf_host, string $device) : Amp\Promise
{
return Amp\call(function() use($atf_host, $device) {
yield host_exec_async($atf_host, "%{adb}% -s $device shell am start -n %{package_id}%/%{activity}%", 0, 30);
});
}
2023-11-07 15:42:40 +03:00
function start_ext_cmd_on_device_async(string $atf_host, string $device, Cmd $cmd, array $cmd_args) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $cmd, $cmd_args) {
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
yield device_del_file_async($atf_host, $device, 'atf_status.js', DEPLOY_OPT_ERR_OK);
yield stop_app_async($atf_host, $device);
2023-11-07 15:42:40 +03:00
yield update_ext_cmd_async($atf_host, $device, $cmd, $cmd_args);
yield start_app_async($atf_host, $device);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
});
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function get_devices(string $atf_host, bool $extended = false)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$devices = array();
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
list($_, $lines) = host_exec($atf_host, "%{adb}% devices -l", DEPLOY_OPT_SILENT);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
foreach($lines as $line)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
if(preg_match('~^(\S+)\s+device(.*)~', $line, $m))
{
if($extended)
{
$devices[$m[1]] = array();
$items = explode(" ", trim($m[2]));
foreach($items as $item)
{
list($k, $v) = explode(":", $item);
$devices[$m[1]][$k] = $v;
}
}
else
$devices[] = $m[1];
}
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
return $devices;
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function update_ext_cmd_async(string $atf_host, string $device, Cmd $cmd, array $args)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$ext_cmd = array(
"device_id" => $device,
"module" => $cmd->module,
"func" => $cmd->func,
"args" => $args,
);
return device_put_file_async($atf_host, $device, json_encode($ext_cmd), 'atf_cmd.js');
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function package_dir(string $atf_host)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return "/storage/emulated/0/Android/data/".taskman\deploy_get($atf_host, 'package_id')."/files/";
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function device_put_file_async(string $atf_host, string $device, string $data_or_file, string $device_path, bool $is_file = false, $timeout = 30) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $data_or_file, $device_path, $is_file, $timeout) {
2022-07-26 17:44:58 +03:00
if($device_path[0] !== "/")
2023-11-07 15:42:40 +03:00
$device_path = package_dir($atf_host) . $device_path;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$tmp_remote_file = '%{dir}%'.uniqid(basename($device_path)."_");
2022-07-26 17:44:58 +03:00
if(!$is_file)
{
$local_path = tempnam("/tmp", "atf_");
2023-11-07 15:42:40 +03:00
taskman\ensure_write($local_path, $data_or_file);
2022-07-26 17:44:58 +03:00
}
else
2023-11-07 15:42:40 +03:00
{
2022-07-26 17:44:58 +03:00
$local_path = $data_or_file;
2023-11-07 15:42:40 +03:00
if(!is_file($local_path))
throw new Exception("No such local file: $local_path");
}
2022-07-26 17:44:58 +03:00
try
{
//1. let's copy local file to the ATF host as temp one
2023-11-07 15:42:40 +03:00
yield host_put_file_async($atf_host, $local_path, $tmp_remote_file);
2022-07-26 17:44:58 +03:00
}
finally
{
if(!$is_file)
2023-11-07 15:42:40 +03:00
taskman\ensure_rm($local_path);
2022-07-26 17:44:58 +03:00
}
//2. let's push the temp file to the device
$device_dir = dirname($device_path);
2023-11-07 15:42:40 +03:00
yield host_exec_async($atf_host, "%{adb}% -s $device shell mkdir -p $device_dir");
yield host_exec_async($atf_host, "%{adb}% -s $device push $tmp_remote_file $device_path", 0, $timeout);
yield host_exec_async($atf_host, "%{adb}% -s $device shell chmod -R 777 $device_dir", DEPLOY_OPT_ERR_OK);
yield host_exec_async($atf_host, "rm -rf $tmp_remote_file");
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
});
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function device_del_file(string $atf_host, string $device, string $device_path, $opts = 0)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
if($device_path[0] !== "/")
$device_path = package_dir($atf_host) . $device_path;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
host_exec($atf_host, "%{adb}% -s $device shell rm -rf $device_path", $opts);
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function device_del_file_async(string $atf_host, string $device, string $device_path, $opts = 0)
2022-07-26 17:44:58 +03:00
{
if($device_path[0] !== "/")
2023-11-07 15:42:40 +03:00
$device_path = package_dir($atf_host) . $device_path;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
return host_exec_async($atf_host, "%{adb}% -s $device shell rm -rf $device_path", $opts);
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function device_blink($atf_host, string $device, int $times = 5)
2022-08-18 20:37:36 +03:00
{
//disable auto brightness
2023-11-07 15:42:40 +03:00
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness_mode 0");
2022-08-18 20:37:36 +03:00
for($i=0;$i<$times;++$i)
{
2023-11-07 15:42:40 +03:00
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness 255");
2022-08-18 20:37:36 +03:00
sleep(1);
2023-11-07 15:42:40 +03:00
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness 5");
2022-08-18 20:37:36 +03:00
}
//enable auto brightness
2023-11-07 15:42:40 +03:00
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness 100");
host_exec($atf_host, "%{adb}% -s $device shell settings put system screen_brightness_mode 1");
2022-08-18 20:37:36 +03:00
}
2023-11-07 15:42:40 +03:00
function device_put_dir_async(string $atf_host, string $device, string $folder_path, string $device_parent_path, int $timeout = 60) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $folder_path, $device_parent_path, $timeout) {
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
if($device_parent_path[0] !== "/")
$device_parent_path = package_dir($atf_host) . $device_parent_path;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$remote_path = '/tmp/' . basename($folder_path);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
yield taskman\deploy_rsync_async($atf_host, "$folder_path/", "$remote_path/", '--delete');
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
yield host_exec_async($atf_host, "%{adb}% -s $device push --sync $remote_path $device_parent_path", 0, $timeout);
2022-07-26 17:44:58 +03:00
});
}
2023-11-07 15:42:40 +03:00
function device_pull_file_async(string $atf_host, string $device, string $device_path, int $timeout = 10, bool $throw_error_on_timeout = false) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $device_path, $throw_error_on_timeout, $timeout) {
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
if($device_path[0] !== "/")
$device_path = package_dir($atf_host) . $device_path;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$tmp_remote_file = '%{dir}%/'.uniqid(basename($device_path)."_");
2022-09-26 17:27:46 +03:00
2023-11-07 15:42:40 +03:00
list($status, $_) = yield host_exec_async($atf_host, "%{adb}% -s $device pull $device_path $tmp_remote_file", DEPLOY_OPT_ERR_OK, $timeout);
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
if($status !== 0)
{
if($status === false && $throw_error_on_timeout)
throw new Exception("Could not pull from device '$device' file '$device_path' due to timeout");
return null;
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
$file_data = host_get_file($atf_host, $tmp_remote_file);
yield host_exec_async($atf_host, "rm -rf $tmp_remote_file");
return $file_data;
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
});
2022-08-03 17:40:44 +03:00
}
2023-11-07 15:42:40 +03:00
function get_last_replay_async(string $atf_host, string $device, string $device_replay_path) : Amp\Promise
2022-08-03 17:40:44 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $device_replay_path) {
$file = package_dir($atf_host) . $device_replay_path;
list($status, $contents) = yield host_exec_async($atf_host, "%{adb}% -s $device shell cat $file", DEPLOY_OPT_ERR_OK);
return array($status, implode("\n", $contents));
});
2022-08-03 17:40:44 +03:00
}
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
function _multi_curl_async($ch, $get_content) : Amp\Promise
2022-08-18 20:37:36 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($ch, $get_content) {
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch);
2022-08-18 20:37:36 +03:00
2023-11-07 15:42:40 +03:00
do {
$status = curl_multi_exec($mh, $active);
yield Amp\delay(1);
} while ($active && $status == CURLM_OK);
2022-08-18 20:37:36 +03:00
2023-11-07 15:42:40 +03:00
$result = null;
if($get_content)
$result = curl_multi_getcontent($ch);
2022-08-18 20:37:36 +03:00
2023-11-07 15:42:40 +03:00
curl_multi_remove_handle($mh, $ch);
curl_multi_close($mh);
2022-08-18 20:37:36 +03:00
2023-11-07 15:42:40 +03:00
return $result;
2022-08-18 20:37:36 +03:00
});
}
2023-11-07 15:42:40 +03:00
function del_external_files_async(string $atf_host, string $device, array $external_files) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $external_files) {
foreach($external_files as $real_path => $remote_path)
yield device_del_file_async($atf_host, $device, $remote_path);
2022-07-26 17:44:58 +03:00
});
}
2023-11-07 15:42:40 +03:00
function put_external_files_async(string $atf_host, string $device, array $external_files) : Amp\Promise
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
return Amp\call(function() use($atf_host, $device, $external_files) {
2022-07-26 17:44:58 +03:00
2023-11-07 15:42:40 +03:00
foreach($external_files as $real_path => $remote_path)
yield device_put_file_async($atf_host, $device, $real_path, $remote_path, true);
});
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function get_locat_errors_since(string $atf_host, string $device, int $since_stamp)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$time_spec = gmdate('m-d h:m:s.0', $since_stamp);
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -v UTC -d -t '$time_spec' AndroidRuntime:E Unity:E", DEPLOY_OPT_ERR_OK);
_filter_error_logs($lines);
return implode("\n", $lines);
2022-07-26 17:44:58 +03:00
}
2023-11-07 15:42:40 +03:00
function get_logcat_errors(string $atf_host, string $device, int $limit = 0)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -d ".($limit > 0 ? "-t $limit" : "")." AndroidRuntime:E Unity:E", DEPLOY_OPT_ERR_OK);
_filter_error_logs($lines);
2022-07-26 17:44:58 +03:00
return implode("\n", $lines);
}
2023-11-07 15:42:40 +03:00
function get_locat_unity_since(string $atf_host, string $device, int $since_stamp)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
$time_spec = gmdate('m-d h:m:s.0', $since_stamp);
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -v UTC -d -t '$time_spec' Unity:'*'", DEPLOY_OPT_ERR_OK);
2022-07-26 17:44:58 +03:00
return implode("\n", $lines);
}
2023-11-07 15:42:40 +03:00
function get_logcat_unity(string $atf_host, string $device, int $limit = 0)
2022-07-26 17:44:58 +03:00
{
2023-11-07 15:42:40 +03:00
list($_, $lines) = host_exec($atf_host, "%{adb}% -s $device logcat -s -d Unity:'*'", DEPLOY_OPT_ERR_OK);
2022-07-26 17:44:58 +03:00
if($limit > 0)
array_splice($lines, 0, sizeof($lines) - $limit);
return implode("\n", $lines);
}
2023-11-07 15:42:40 +03:00
function _filter_error_logs(array &$lines)
2022-07-26 17:44:58 +03:00
{
$exception_lines = 0;
$filtered = array();
for($i=0;$i<sizeof($lines);++$i)
{
$line = $lines[$i];
if(strpos($line, 'E AndroidRuntime') !== false)
$filtered[] = $line;
else if(strpos($line, 'E Unity') !== false)
{
if(strpos($line, '[EXCEPTION]') !== false)
$exception_lines = 15;
if(--$exception_lines > 0)
$filtered[] = $line;
}
}
$lines = $filtered;
}