diff --git a/composer.json b/composer.json index 4df1d2fb..a043fc23 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,8 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-b3", + "version": "2.0.0-b4", + "extra": {}, "authors": [ { "name": "whale", @@ -21,30 +22,24 @@ ], "require": { "php": ">=7.2", - "ext-mbstring": "*", "doctrine/annotations": "~1.10", "ext-json": "*", - "ext-posix": "*", - "ext-ctype": "*", "psy/psysh": "@stable", + "symfony/polyfill-ctype": "^1.20", + "symfony/polyfill-mbstring": "^1.20", "symfony/console": "^5.1", - "symfony/polyfill-ctype": "^1.18", "zhamao/connection-manager": "*@dev", "zhamao/console": "*@dev", "zhamao/config": "*@dev", "zhamao/request": "*@dev", - "symfony/routing": "^5.1" - }, - "suggest": { - "ext-pdo": "Allows framework connecting with mysql server", - "ext-redis": "Allows framework connecting with redis server", - "ext-inotify": "Enable file watcher feature in framework" + "symfony/routing": "^5.1", + "symfony/polyfill-php80": "^1.20" }, "autoload": { "psr-4": { - "Custom\\": "src/Custom", "ZM\\": "src/ZM", - "Module\\": "src/Module" + "Module\\": "src/Module", + "Custom\\": "src/Custom" }, "files": [ "src/ZM/global_functions.php" @@ -61,11 +56,5 @@ "require-dev": { "phpunit/phpunit": "^9.3", "swoole/ide-helper": "@dev" - }, - "repositories": [ - { - "type": "path", - "url": "/Users/jerry/project/git-project/zhamao-console" - } - ] -} + } +} \ No newline at end of file diff --git a/config/global.php b/config/global.php index 00bffce1..eed3fef0 100644 --- a/config/global.php +++ b/config/global.php @@ -36,10 +36,10 @@ $config['swoole'] = [ /** 轻量字符串缓存,默认开启 */ $config['light_cache'] = [ - "size" => 1024, //最多允许储存的条数(需要2的倍数) - "max_strlen" => 16384, //单行字符串最大长度(需要2的倍数) - "hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) - "persistence_path" => $config['zm_data']."_cache.json", + 'size' => 1024, //最多允许储存的条数(需要2的倍数) + 'max_strlen' => 16384, //单行字符串最大长度(需要2的倍数) + 'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) + 'persistence_path' => $config['zm_data'].'_cache.json', 'auto_save_interval' => 900 ]; diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index d45540fd..b4b35baa 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -30,7 +30,6 @@ trait CQAPI public function processWebsocketAPI($connection, $reply, $function = false) { $api_id = ZMAtomic::get("wait_msg_id")->add(1); $reply["echo"] = $api_id; - //EventHandler::callCQAPISend($reply, $connection); SpinLock::lock("wait_api"); $r = LightCacheInside::get("wait_api", "wait_api"); $r[$api_id] = [ diff --git a/src/ZM/Annotation/CQ/CQAPISend.php b/src/ZM/Annotation/CQ/CQAPISend.php deleted file mode 100644 index bf4429c0..00000000 --- a/src/ZM/Annotation/CQ/CQAPISend.php +++ /dev/null @@ -1,43 +0,0 @@ -level; - } - - /** - * @param mixed $level - */ - public function setLevel($level) { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php index 4f1d09c2..6a6d4021 100644 --- a/src/ZM/Annotation/CQ/CQCommand.php +++ b/src/ZM/Annotation/CQ/CQCommand.php @@ -21,6 +21,10 @@ class CQCommand extends AnnotationBase implements Level public $pattern = ""; /** @var string */ public $regex = ""; + /** @var string */ + public $start_with = ""; + /** @var string */ + public $end_with = ""; /** @var string[] */ public $alias = []; /** @var string */ diff --git a/src/ZM/Annotation/Swoole/SwooleSetup.php b/src/ZM/Annotation/Swoole/ZMSetup.php similarity index 77% rename from src/ZM/Annotation/Swoole/SwooleSetup.php rename to src/ZM/Annotation/Swoole/ZMSetup.php index d0037379..b3165937 100644 --- a/src/ZM/Annotation/Swoole/SwooleSetup.php +++ b/src/ZM/Annotation/Swoole/ZMSetup.php @@ -8,11 +8,11 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class SwooleSetup + * Class ZMSetup * @package ZM\Annotation\Swoole * @Annotation * @Target("METHOD") */ -class SwooleSetup extends AnnotationBase +class ZMSetup extends AnnotationBase { } diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 2f063f8a..965f0765 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -45,7 +45,7 @@ class RunServerCommand extends Command // ... put here the code to run in your command // this method must return an integer number with the "exit status code" // of the command. You can also use these constants to make code more readable - new Framework($input->getOptions()); + (new Framework($input->getOptions()))->start(); // return this if there was no problem running the command // (it's equivalent to returning int(0)) return Command::SUCCESS; diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index dd6bb32b..08bece9a 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -18,18 +18,16 @@ use ZM\Utils\DataProvider; class ConsoleApplication extends Application { - public function __construct(string $name = 'UNKNOWN') { + public function __construct(string $name = 'UNKNOWN') + { $version = json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "UNKNOWN"; parent::__construct($name, $version); } - public function initEnv() { + public function initEnv() + { $this->selfCheck(); - $this->addCommands([ - new RunServerCommand(), //运行主服务的指令控制器 - new InitCommand(), //初始化用的,用于项目初始化和phar初始化 - new PureHttpCommand() - ]); + //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 if (LOAD_MODE === 0) define("WORKING_DIR", getcwd()); elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); @@ -47,20 +45,49 @@ class ConsoleApplication extends Application * @noinspection RedundantSuppression */ require_once WORKING_DIR . "/vendor/autoload.php"; + echo "* This is repository mode.\n"; + $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); + if (!isset($composer["autoload"]["psr-4"]["Module\\"])) { + echo "框架源码模式需要在autoload文件中添加Module目录为自动加载,是否添加?[Y/n] "; + $r = strtolower(trim(fgets(STDIN))); + if ($r === "" || $r === "y") { + $composer["autoload"]["psr-4"]["Module\\"] = "src/Module"; + $composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom"; + $r = file_put_contents(DataProvider::getWorkingDir() . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + if ($r !== false) { + echo "成功添加!请重新进行 composer update !\n"; + exit(1); + } else { + echo "添加失败!请按任意键继续!"; + fgets(STDIN); + exit(1); + } + } else { + exit(1); + } + } } if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { die("Unable to find source directory.\nMaybe you need to run \"init\"?"); } - ZMConfig::setDirectory(DataProvider::getWorkingDir().'/config'); + ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::env($args["env"] ?? ""); - if(ZMConfig::get("global") === false) die("Global config load failed: ".ZMConfig::$last_error); - - $command_register = ZMConfig::get("global", "command_register_class") ?? []; - foreach($command_register as $v) { - $obj = new $v(); - if(!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); - $this->add($obj); + if (ZMConfig::get("global") === false) { + echo ("Global config load failed: " . ZMConfig::$last_error."\nPlease init first!\n"); + $this->add(new InitCommand()); + } else { + $this->addCommands([ + new RunServerCommand(), //运行主服务的指令控制器 + new InitCommand(), //初始化用的,用于项目初始化和phar初始化 + new PureHttpCommand() //纯HTTP服务器指令 + ]); + $command_register = ZMConfig::get("global", "command_register_class") ?? []; + foreach ($command_register as $v) { + $obj = new $v(); + if (!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); + $this->add($obj); + } } } @@ -69,7 +96,8 @@ class ConsoleApplication extends Application * @param OutputInterface|null $output * @return int */ - public function run(InputInterface $input = null, OutputInterface $output = null) { + public function run(InputInterface $input = null, OutputInterface $output = null) + { try { return parent::run($input, $output); } catch (Exception $e) { @@ -77,12 +105,13 @@ class ConsoleApplication extends Application } } - private function selfCheck() { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + private function selfCheck() + { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19"); if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); - if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (substr(PHP_VERSION, 0, 1) < "7") die("PHP >=7 required.\n"); //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index c5cc0c0f..6ad6fa12 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -28,7 +28,7 @@ class Context implements ContextInterface /** * @return swoole_server|null */ - public function getServer() { return self::$context[$this->cid]["server"] ?? null; } + public function getServer() { return self::$context[$this->cid]["server"] ?? server(); } /** * @return Frame|null diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php index f5575ce2..d663eacc 100644 --- a/src/ZM/Exception/DbException.php +++ b/src/ZM/Exception/DbException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class DbException extends Exception +class DbException extends ZMException { } \ No newline at end of file diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php index b11016cd..f5d00aa8 100644 --- a/src/ZM/Exception/InterruptException.php +++ b/src/ZM/Exception/InterruptException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class InterruptException extends Exception +class InterruptException extends ZMException { } diff --git a/src/ZM/Exception/InvalidArgumentException.php b/src/ZM/Exception/InvalidArgumentException.php index eb4b47a5..7ccc3b70 100644 --- a/src/ZM/Exception/InvalidArgumentException.php +++ b/src/ZM/Exception/InvalidArgumentException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class InvalidArgumentException extends Exception +class InvalidArgumentException extends ZMException { } \ No newline at end of file diff --git a/src/ZM/Exception/NotInitializedException.php b/src/ZM/Exception/NotInitializedException.php index 0f30c04f..f1bf753d 100644 --- a/src/ZM/Exception/NotInitializedException.php +++ b/src/ZM/Exception/NotInitializedException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class NotInitializedException extends Exception +class NotInitializedException extends ZMException { } diff --git a/src/ZM/Exception/RobotNotFoundException.php b/src/ZM/Exception/RobotNotFoundException.php index 7e63c524..9b734260 100644 --- a/src/ZM/Exception/RobotNotFoundException.php +++ b/src/ZM/Exception/RobotNotFoundException.php @@ -12,7 +12,7 @@ use Throwable; * @package ZM\Exception * @since 1.2 */ -class RobotNotFoundException extends Exception +class RobotNotFoundException extends ZMException { public function __construct($message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/ZM/Exception/WaitTimeoutException.php b/src/ZM/Exception/WaitTimeoutException.php index 561453d6..c9d18255 100644 --- a/src/ZM/Exception/WaitTimeoutException.php +++ b/src/ZM/Exception/WaitTimeoutException.php @@ -7,7 +7,7 @@ namespace ZM\Exception; use Exception; use Throwable; -class WaitTimeoutException extends Exception +class WaitTimeoutException extends ZMException { public $module; diff --git a/src/ZM/Exception/ZMException.php b/src/ZM/Exception/ZMException.php new file mode 100644 index 00000000..01d8bf96 --- /dev/null +++ b/src/ZM/Exception/ZMException.php @@ -0,0 +1,12 @@ +registerServerEvents(); $r = ZMConfig::get("global", "light_cache") ?? [ - "size" => 1024, - "max_strlen" => 8192, - "hash_conflict_proportion" => 0.6, - "persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), - "auto_save_interval" => 900 - ]; + "size" => 1024, + "max_strlen" => 8192, + "hash_conflict_proportion" => 0.6, + "persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), + "auto_save_interval" => 900 + ]; LightCache::init($r); LightCacheInside::init(); SpinLock::init($r["size"]); - self::$server->start(); } catch (Exception $e) { Console::error("Framework初始化出现错误,请检查!"); Console::error($e->getMessage()); @@ -129,6 +127,10 @@ class Framework } } + public function start() { + self::$server->start(); + } + /** * 从全局配置文件里读取注入系统事件的类 * @throws ReflectionException @@ -152,7 +154,7 @@ class Framework $annotation->class = $v; $annotation->method = $vs->getName(); $event_list[strtolower($annotation->event)] = $annotation; - } elseif ($annotation instanceof SwooleSetup) { + } elseif ($annotation instanceof ZMSetup) { $annotation->class = $v; $annotation->method = $vs->getName(); $c = new $v(); @@ -213,14 +215,6 @@ class Framework case 'disable-console-input': if ($y) $terminal_id = null; break; - case 'remote-shell': - if ($y) { - $host = "127.0.0.1"; - $port = 9599; - RemoteShell::listen(self::$server, $host, $port); - Console::log(Console::setColor("正在监听" . $host . ":" . strval($port) . "的调试接口,请注意安全", "yellow")); - } - break; case 'log-error': if ($y) Console::setLevel(0); break; diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index 13f75c7d..902c1db7 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -88,7 +88,13 @@ class QQBot if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) { ctx()->setCache("match", $word); return true; - } elseif ($v->pattern != "") { + } elseif ($v->start_with != "" && mb_strpos(ctx()->getMessage(), $v->start_with) === 0) { + ctx()->setCache("match", [mb_substr(ctx()->getMessage(), mb_strlen($v->start_with))]); + return true; + } elseif ($v->end_with != "" && strlen(ctx()->getMessage()) == (strripos(ctx()->getMessage(), $v->end_with) + strlen($v->end_with))) { + ctx()->setCache("match", [substr(ctx()->getMessage(), 0, strripos(ctx()->getMessage(), $v->end_with))]); + return true; + }elseif ($v->pattern != "") { $match = matchArgs($v->pattern, ctx()->getMessage()); if($match !== false) { ctx()->setCache("match", $match); diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index 5607c2e3..b6f0c720 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -178,6 +178,7 @@ class LightCache $r[$k] = self::parseGet($v); } } + if(self::$config["persistence_path"] == "") return; $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); } diff --git a/src/ZM/Utils/RemoteShell.php b/src/ZM/Utils/RemoteShell.php deleted file mode 100644 index 701b3e74..00000000 --- a/src/ZM/Utils/RemoteShell.php +++ /dev/null @@ -1,263 +0,0 @@ -listen($host, $port, SWOOLE_SOCK_TCP); - if (!$port) { - throw new Exception("listen fail."); - } - $port->set(array( - "open_eof_split" => true, - 'package_eof' => "\r\n", - )); - $port->on("Connect", array(__CLASS__, 'onConnect')); - $port->on("Close", array(__CLASS__, 'onClose')); - $port->on("Receive", array(__CLASS__, 'onReceive')); - if (method_exists($serv, 'getCallback')) { - self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage'); - } - $serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage')); - self::$serv = $serv; - } - - static function onConnect($serv, $fd, $reactor_id) { - self::$contexts[$fd]['worker_id'] = $serv->worker_id; - self::output($fd, implode("\r\n", self::$menu)); - } - - static function output($fd, $msg) { - if (!isset(self::$contexts[$fd]['worker_id'])) { - $msg .= "\r\nworker#" . self::$serv->worker_id . "$ "; - } else { - $msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ "; - } - self::$serv->send($fd, $msg); - } - - static function onClose($serv, $fd, $reactor_id) { - unset(self::$contexts[$fd]); - } - - static function onPipeMessage($serv, $src_worker_id, $message) { - //不是 debug 消息 - if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) { - if (self::$oriPipeMessageCallback == null) { - trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING); - return true; - } - return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message); - } else { - $request = unserialize(substr($message, strlen(self::STX))); - self::call($request['fd'], $request['func'], $request['args']); - } - return true ; - } - - static protected function call($fd, $func, $args) { - ob_start(); - call_user_func_array($func, $args); - self::output($fd, ob_get_clean()); - } - - static protected function exec($fd, $func, $args) { - //不在当前Worker进程 - if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) { - self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']); - } else { - self::call($fd, $func, $args); - } - } - - static function getCoros() { - var_export(iterator_to_array(Coroutine::listCoroutines())); - } - - static function getBackTrace($_cid) { - $info = Co::getBackTrace($_cid); - if (!$info) { - echo "coroutine $_cid not found."; - } else { - echo get_debug_print_backtrace($info); - } - } - - static function printVariant($var) { - $var = ltrim($var, '$ '); - var_dump($var); - var_dump($$var); - } - - static function evalCode($code) { - eval($code . ';'); - } - - /** - * @param $serv server - * @param $fd - * @param $reactor_id - * @param $data - */ - static function onReceive($serv, $fd, $reactor_id, $data) { - $args = explode(" ", $data, 2); - $cmd = trim($args[0]); - unset($args[0]); - switch ($cmd) { - case 'w': - case 'worker': - if (!isset($args[1])) { - self::output($fd, "invalid command."); - break; - } - $dstWorkerId = intval($args[1]); - self::$contexts[$fd]['worker_id'] = $dstWorkerId; - self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]"); - break; - case 'e': - case 'exec': - if (!isset($args[1])) { - self::output($fd, "invalid command."); - break; - } - $var = trim($args[1]); - self::exec($fd, 'self::evalCode', [$var]); - break; - case 'p': - case 'print': - $var = trim($args[1]); - self::exec($fd, 'self::printVariant', [$var]); - break; - case 'h': - case 'help': - self::output($fd, implode("\r\n", self::$menu)); - break; - case 's': - case 'stats': - $stats = $serv->stats(); - self::output($fd, var_export($stats, true)); - break; - case 'c': - case 'coros': - self::exec($fd, 'self::getCoros', []); - break; - /** - * 查看协程堆栈 - */ - case 'bt': - case 'b': - case 'backtrace': - if (empty($args[1])) { - self::output($fd, "invalid command [" . trim($args[1]) . "]."); - break; - } - $_cid = intval($args[1]); - self::exec($fd, 'self::getBackTrace', [$_cid]); - break; - case 'i': - case 'info': - if (empty($args[1])) { - self::output($fd, "invalid command [" . trim($args[1]) . "]."); - break; - } - $_fd = intval($args[1]); - $info = $serv->getClientInfo($_fd); - if (!$info) { - self::output($fd, "connection $_fd not found."); - } else { - self::output($fd, var_export($info, true)); - } - break; - case 'l': - case 'list': - $tmp = array(); - foreach ($serv->connections as $fd) { - $tmp[] = $fd; - if (count($tmp) > self::PAGESIZE) { - self::output($fd, json_encode($tmp)); - $tmp = array(); - } - } - if (count($tmp) > 0) { - self::output($fd, json_encode($tmp)); - } - break; - case 'q': - case 'quit': - $serv->close($fd); - break; - default: - self::output($fd, "unknow command[$cmd]"); - break; - } - } -} - -function get_debug_print_backtrace($traces) { - $ret = array(); - foreach ($traces as $i => $call) { - $object = ''; - if (isset($call['class'])) { - $object = $call['class'] . $call['type']; - if (is_array($call['args'])) { - foreach ($call['args'] as &$arg) { - get_arg($arg); - } - } - } - $ret[] = '#' . str_pad($i, 3, ' ') - . $object . $call['function'] . '(' . implode(', ', $call['args']) - . ') called at [' . $call['file'] . ':' . $call['line'] . ']'; - } - return implode("\n", $ret); -} - -function get_arg(&$arg) { - if (is_object($arg)) { - $arr = (array)$arg; - $args = array(); - foreach ($arr as $key => $value) { - if (strpos($key, chr(0)) !== false) { - $key = ''; // Private variable found - } - $args[] = '[' . $key . '] => ' . get_arg($value); - } - $arg = get_class($arg) . ' Object (' . implode(',', $args) . ')'; - } -} diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 6cf136f4..cfa26725 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -40,6 +40,7 @@ class ZMUtil foreach (server()->connections as $v) { server()->close($v); } + LightCache::savePersistence(); //DataProvider::saveBuffer(); Timer::clearAll(); server()->reload();