Compare commits

...

16 Commits

Author SHA1 Message Date
6e46af4c13 Prepare for Upload mods 2023-07-13 02:35:38 +03:00
d21798aaf1 Minor fix 2023-07-13 02:34:56 +03:00
22105b2030 Minor fix 2023-07-13 02:34:25 +03:00
19c121f208 Refactor Client ID 2023-07-13 02:33:45 +03:00
85c379bd9e Minor fixes 2023-07-13 01:17:01 +03:00
a15eb316bb Update TODOs 2023-07-13 01:16:10 +03:00
cecd6f13d6 Handle FastApi Log in file 2023-07-13 00:44:51 +03:00
df171aaa70 Minor fix 2023-07-13 00:31:02 +03:00
5a1cb8a133 Handle web logs 2023-07-13 00:23:57 +03:00
d44cff1116 Update version 2023-07-12 23:58:35 +03:00
bc6cf60099 Update TODOs 2023-07-12 23:58:23 +03:00
fc886ef415 Create log history 2023-07-12 23:58:14 +03:00
bd7b988b01 logs 2023-07-12 23:39:52 +03:00
4f5a6edc48 Update TODOs 2023-07-12 20:56:24 +03:00
541849642c BEAMP -> BeamMP 2023-07-12 20:56:14 +03:00
f364f29d79 Update TODOs 2023-07-12 20:49:39 +03:00
16 changed files with 184 additions and 118 deletions

1
.gitignore vendored
View File

@ -137,3 +137,4 @@ dmypy.json
/src/plugins /src/plugins
/test/ /test/
*test.py *test.py
logs/

View File

@ -7,43 +7,48 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
## TODOs ## TODOs
- [ ] Server core - [ ] Server core
- [x] BEAMP System - [x] BeamMP System
- [x] Private access without key (Direct connect) - [x] Private access (Without key, Direct connect)
- [x] Server authentication (For public access) - [x] Public access (With key, listing in Launcher)
- [X] Player authentication - [X] Player authentication
- [ ] TCP Server part: - [ ] TCP Server part:
- [x] Handle code - [x] Handle code
- [x] Understanding beamp header - [x] Understanding BeamMP header
- [ ] Upload mods - [ ] Upload mods
- [x] Connecting to the world - [x] Connecting to the world
- [x] Chat - [x] Chat
- [ ] Player counter _(Code: Ss)_
- [ ] Car state synchronizations _(Codes: We, Vi)_
- [ ] "ABG:" (compressed data) - [ ] "ABG:" (compressed data)
- [x] Decompress data - [x] Decompress data
- [ ] Vehicle data - [ ] Vehicle data
- [ ] Players synchronizations
- [ ] UDP Server part: - [ ] UDP Server part:
- [ ] Players synchronizations - [ ] Players synchronizations _(Code: Zp)_
- [ ] Ping - [ ] Ping _(Code: p)_
- [ ] Player counter
- [x] Additional: - [x] Additional:
- [x] Logger
- [x] Just logging
- [x] Log in file
- [x] Log history (.1.log, .2.log, ...)
- [x] Console: - [x] Console:
- [x] Tabulation - [x] Tabulation
- [ ] _(Deferred)_ Static text - [ ] _(Deferred)_ Static text (bug)
- [x] Events System - [x] Events System
- [x] Call events - [x] Call events
- [x] Create custom events - [x] Create custom events
- [ ] Return from events - [ ] Return from events
- [x] Plugins support - [x] Plugins support
- [x] Load Python plugins - [x] Load Python plugins
- [ ] Load Lua plugins (Original BEAMP compatibility) - [ ] Load Lua plugins (Original BeamMP compatibility)
- [x] MultiLanguage (i18n support) - [x] MultiLanguage (i18n support)
- [x] Core - [x] Core
- [x] Console - [x] Console
- [x] WebAPI - [x] WebAPI
- [x] HTTP API Server (fastapi) - [x] HTTP API Server (fastapi)
- [x] Stop and Start with core - [x] Stop and Start with core
- [x] Custom logger - [x] Configure FastAPI logger
- [ ] Sync with event system - [ ] Sync with event system
- [ ] Add methods...
- [ ] [Documentation](docs/en/readme.md) - [ ] [Documentation](docs/en/readme.md)
## Installation ## Installation

View File

@ -7,8 +7,8 @@
"stop": "Сервер остановлен!", "stop": "Сервер остановлен!",
"": "Server auth", "": "Server auth",
"auth_need_key": "Нужен BEAMP ключ для запуска!", "auth_need_key": "Нужен BeamMP ключ для запуска!",
"auth_empty_key": "BEAMP ключ пустой!", "auth_empty_key": "BeamMP ключ пустой!",
"auth_cannot_open_browser": "Не получилось открыть браузер: {}", "auth_cannot_open_browser": "Не получилось открыть браузер: {}",
"auth_use_link": "Используй эту ссылку: {}", "auth_use_link": "Используй эту ссылку: {}",
@ -17,7 +17,7 @@
"GUI_no": "Нет", "GUI_no": "Нет",
"GUI_ok": "Окей", "GUI_ok": "Окей",
"GUI_cancel": "Отмена", "GUI_cancel": "Отмена",
"GUI_need_key_message": "Нужен BEAMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?", "GUI_need_key_message": "Нужен BeamMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
"GUI_enter_key_message": "Пожалуйста введите ключ:", "GUI_enter_key_message": "Пожалуйста введите ключ:",
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}", "GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",

View File

@ -43,7 +43,7 @@ Server:
server_ip: 0.0.0.0 server_ip: 0.0.0.0
server_port: 30814 server_port: 30814
``` ```
* Если поставить `private: false` и не установить `key`, то сервер запросит BEAMP ключ, без него не запустится. * Если поставить `private: false` и не установить `key`, то сервер запросит BeamMP ключ, без него не запустится.
* Введя BEAMP ключ сервер появится в списке лаунчера. * Введя BeamMP ключ сервер появится в списке лаунчера.
* Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys) * Взять ключ можно тут: [https://beammp.com/k/keys](https://beammp.com/k/keys)

View File

@ -67,13 +67,13 @@ ev.builtins_hook()
log.info(i18n.hello) log.info(i18n.hello)
log.info(i18n.config_path.format(config_path)) log.info(i18n.config_path.format(config_path))
log.debug("Initializing BEAMP Server system...") log.debug("Initializing BeamMP Server system...")
# Key handler.. # Key handler..
if not config.Auth['private'] and not config.Auth['key']: if not config.Auth['private'] and not config.Auth['key']:
log.warn(i18n.auth_need_key) log.warn(i18n.auth_need_key)
url = "https://beammp.com/k/keys" url = "https://beammp.com/k/keys"
if shortcuts.yes_no_dialog( if shortcuts.yes_no_dialog(
title='BEAMP Server Key', title='BeamMP Server Key',
text=i18n.GUI_need_key_message, text=i18n.GUI_need_key_message,
yes_text=i18n.GUI_yes, yes_text=i18n.GUI_yes,
no_text=i18n.GUI_no).run(): no_text=i18n.GUI_no).run():
@ -84,12 +84,12 @@ if not config.Auth['private'] and not config.Auth['key']:
log.error(i18n.auth_cannot_open_browser.format(e)) log.error(i18n.auth_cannot_open_browser.format(e))
log.info(i18n.auth_use_link.format(url)) log.info(i18n.auth_use_link.format(url))
shortcuts.message_dialog( shortcuts.message_dialog(
title='BEAMP Server Key', title='BeamMP Server Key',
text=i18n.GUI_cannot_open_browser.format(url), text=i18n.GUI_cannot_open_browser.format(url),
ok_text=i18n.GUI_ok).run() ok_text=i18n.GUI_ok).run()
config.Auth['key'] = shortcuts.input_dialog( config.Auth['key'] = shortcuts.input_dialog(
title='BEAMP Server Key', title='BeamMP Server Key',
text=i18n.GUI_enter_key_message, text=i18n.GUI_enter_key_message,
ok_text=i18n.GUI_ok, ok_text=i18n.GUI_ok,
cancel_text=i18n.GUI_cancel).run() cancel_text=i18n.GUI_cancel).run()

View File

@ -23,6 +23,7 @@ class Client:
def __init__(self, reader, writer, core): def __init__(self, reader, writer, core):
self.reader = reader self.reader = reader
self.writer = writer self.writer = writer
self.down_rw = (None, None)
self.log = utils.get_logger("client(None:0)") self.log = utils.get_logger("client(None:0)")
self.addr = writer.get_extra_info("sockname") self.addr = writer.get_extra_info("sockname")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
@ -61,7 +62,7 @@ class Client:
async def tcp_send(self, data): async def tcp_send(self, data):
# TNetwork.cpp; Line: 383 # TNetwork.cpp; Line: 383
# BEAMP TCP protocol sends a header of 4 bytes, followed by the data. # BeamMP TCP protocol sends a header of 4 bytes, followed by the data.
# [][][][][][]...[] # [][][][][][]...[]
# ^------^^---...-^ # ^------^^---...-^
# size data # size data
@ -108,18 +109,31 @@ class Client:
return data return data
async def sync_resources(self): async def sync_resources(self):
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) while True:
data = await self.recv() data = await self.recv()
if data.startswith(b"SR"): if data.startswith(b"f"):
await self.tcp_send(b"-") # Cannot handle mods for now. # TODO: SendFile
data = await self.recv() pass
if data == b"Done": elif data.startswith(b"SR"):
await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") # TODO: Create mods list
await self.last_handle() self.log.debug("Sending Mod Info")
mods = []
mod_list = b''
# * code *
if len(mods) == 0:
await self.tcp_send(b"-")
else:
await self.tcp_send(mod_list)
data = await self.recv()
if data == b"Done":
await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
break
async def last_handle(self): async def looper(self):
# self.is_disconnected() # self.is_disconnected()
self.log.debug(f"Alive: {self.alive}") self.log.debug(f"Alive: {self.alive}")
await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8"))
await self.sync_resources()
while self.alive: while self.alive:
data = await self.recv() data = await self.recv()
if data == b"": if data == b"":
@ -147,8 +161,9 @@ class Core:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.run = False self.run = False
self.direct = False self.direct = False
self.clients = {} self.clients = []
self.clients_counter = 0 self.clients_by_id = {}
self.clients_by_nick = {}
self.mods_dir = "./mods" self.mods_dir = "./mods"
self.mods_list = [0, ] self.mods_list = [0, ]
self.server_ip = config.Server["server_ip"] self.server_ip = config.Server["server_ip"]
@ -160,33 +175,42 @@ class Core:
self.web_stop = None self.web_stop = None
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BEAMP_version = "3.2.0" self.BeamMP_version = "3.2.0"
def get_client(self, sock=None, cid=None): def get_client(self, sock=None, cid=None, nick=None):
if cid: if cid:
return self.clients.get(cid) return self.clients_by_id.get(cid)
if nick:
return self.clients_by_nick.get(nick)
if sock: if sock:
return self.clients.get(sock.getsockname()) return self.clients_by_nick.get(sock.getsockname())
def insert_client(self, client): def insert_client(self, client):
self.log.debug(f"Inserting client: {client.cid}") self.log.debug(f"Inserting client: {client.cid}")
self.clients.update({client.cid: client, client.nick: client}) self.clients_by_nick.update({client.nick: client})
self.clients_by_id.update({client.cid: client})
self.clients[client.cid] = client
def create_client(self, *args, **kwargs): def create_client(self, *args, **kwargs):
client = Client(*args, **kwargs) client = Client(*args, **kwargs)
self.clients_counter += 1 cid = 1
client.id = self.clients_counter for client in self.clients:
if client.cid == cid:
cid += 1
else:
break
client.cid = cid
client._update_logger() client._update_logger()
self.log.debug(f"Create client: {client.cid}; clients_counter: {self.clients_counter}") self.log.debug(f"Create client; client.cid: {client.cid};")
return client return client
async def check_alive(self): async def check_alive(self):
await asyncio.sleep(5) await asyncio.sleep(5)
self.log.debug(f"Checking if clients is alive") self.log.debug(f"Checking if clients is alive")
for cl in self.clients.values(): for client in self.clients:
d = await cl.is_disconnected() d = client.is_disconnected()
if d: if d:
self.log.debug(f"Client ID: {cl.id} died...") self.log.debug(f"Client ID: {client.cid} died...")
@staticmethod @staticmethod
def start_web(): def start_web():
@ -224,7 +248,7 @@ class Core:
while self.run: while self.run:
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"], data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"],
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json", "port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
"private": config.Auth['private'], "version": self.BEAMP_version, "private": config.Auth['private'], "version": self.BeamMP_version,
"clientversion": self.client_major_version, "clientversion": self.client_major_version,
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize, "name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False} "modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
@ -337,5 +361,6 @@ class Core:
def stop(self): def stop(self):
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
asyncio.run(self.web_stop()) if config.WebAPI["enabled"]:
asyncio.run(self.web_stop())
exit(0) exit(0)

View File

@ -7,7 +7,7 @@
import asyncio import asyncio
from asyncio import StreamWriter, StreamReader from asyncio import StreamWriter, StreamReader
from threading import Thread from threading import Thread
from typing import Callable from typing import Callable, List, Dict, Tuple
from core import utils from core import utils
from .tcp_server import TCPServer from .tcp_server import TCPServer
@ -19,6 +19,7 @@ class Client:
def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client": def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client":
self.reader = reader self.reader = reader
self.writer = writer self.writer = writer
self.down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None)
self.log = utils.get_logger("client(id: )") self.log = utils.get_logger("client(id: )")
self.addr = writer.get_extra_info("sockname") self.addr = writer.get_extra_info("sockname")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
@ -34,7 +35,7 @@ class Client:
async def tcp_send(self, data: bytes) -> None: ... async def tcp_send(self, data: bytes) -> None: ...
async def sync_resources(self) -> None: ... async def sync_resources(self) -> None: ...
async def recv(self) -> bytes: ... async def recv(self) -> bytes: ...
async def last_handle(self) -> bytes: ... async def looper(self) -> None: ...
def _update_logger(self) -> None: ... def _update_logger(self) -> None: ...
@ -44,7 +45,9 @@ class Core:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.run = False self.run = False
self.direct = False self.direct = False
self.clients = dict() self.clients: List[Client]= []
self.clients_by_id: Dict[{int: Client}]= {}
self.clients_by_nick: Dict[{str: Client}] = {}
self.clients_counter: int = 0 self.clients_counter: int = 0
self.mods_dir: str = "mods" self.mods_dir: str = "mods"
self.mods_list: list = [] self.mods_list: list = []
@ -55,7 +58,7 @@ class Core:
self.web_thread: Thread = None self.web_thread: Thread = None
self.web_stop: Callable = lambda: None self.web_stop: Callable = lambda: None
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BEAMP_version = "3.2.0" self.BeamMP_version = "3.2.0"
def insert_client(self, client: Client) -> None: ... def insert_client(self, client: Client) -> None: ...
def create_client(self, *args, **kwargs) -> Client: ... def create_client(self, *args, **kwargs) -> Client: ...
async def check_alive(self) -> None: ... async def check_alive(self) -> None: ...

View File

@ -25,10 +25,7 @@ class TCPServer:
self.log.info(f"Identifying new ClientConnection...") self.log.info(f"Identifying new ClientConnection...")
data = await client.recv() data = await client.recv()
self.log.debug(f"recv1 data: {data}") self.log.debug(f"recv1 data: {data}")
if len(data) > 50: if data.decode("utf-8") != f"VC{self.Core.client_major_version}":
await client.kick("Too long data")
return False, None
if "VC2.0" not in data.decode("utf-8"):
await client.kick("Outdated Version.") await client.kick("Outdated Version.")
return False, None return False, None
else: else:
@ -57,8 +54,9 @@ class TCPServer:
self.log.error(f"Auth error: {e}") self.log.error(f"Auth error: {e}")
await client.kick('Invalid authentication data! Try to connect in 5 minutes.') await client.kick('Invalid authentication data! Try to connect in 5 minutes.')
# TODO: Password party for _client in self.Core.clients:
# await client.tcp_send(b"S") # Ask client key (How?) if _client.nick == client.nick and _client.guest == client.guest:
await client.kick('Stale Client (replaced by new client)')
ev.call_event("on_auth", client) ev.call_event("on_auth", client)
@ -70,22 +68,29 @@ class TCPServer:
return True, client return True, client
async def handle_download(self, writer): async def set_down_rw(self, reader, writer):
# TODO: HandleDownload try:
self.log.debug(f"Client: \"IP: {0!r}; ID: {0}\" - HandleDownload!") cid = (await reader.read(1)).decode() # FIXME: wtf? 1 byte?
return False self.log.debug(f"Client: \"ID: {cid}\" - HandleDownload!")
if not cid.isdigit():
return False
for _client in self.Core.clients:
if _client.cid == cid:
_client.down_rw = (reader, writer)
return True
finally:
return False
async def handle_code(self, code, reader, writer): async def handle_code(self, code, reader, writer):
match code: match code:
case "C": case "C":
result, client = await self.auth_client(reader, writer) result, client = await self.auth_client(reader, writer)
if result: if result:
await client.sync_resources() await client.looper()
# await client.kick("Authentication success! Server not ready.")
return True return True
return False return False
case "D": case "D":
return await self.handle_download(writer) return await self.set_down_rw(reader, writer)
case "P": case "P":
writer.write(b"P") writer.write(b"P")
await writer.drain() await writer.drain()
@ -115,7 +120,7 @@ class TCPServer:
self.log.debug("Starting TCP server.") self.log.debug("Starting TCP server.")
try: try:
server = await asyncio.start_server(self.handle_client, self.host, self.port, server = await asyncio.start_server(self.handle_client, self.host, self.port,
backlog=config.Game["players"] + 1) backlog=int(config.Game["players"] * 1.3))
except OSError as e: except OSError as e:
self.log.error(f"Error: {e}") self.log.error(f"Error: {e}")
self.Core.run = False self.Core.run = False

View File

@ -20,7 +20,7 @@ class TCPServer:
self.port = port self.port = port
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
async def auth_client(self, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ... async def auth_client(self, reader: StreamReader, writer: StreamWriter) -> Tuple[bool, Client]: ...
async def handle_download(self, writer: StreamWriter) -> bool: ... async def set_down_rw(self, reader: StreamReader, writer: StreamWriter) -> bool: ...
async def handle_code(self, code: str, reader: StreamReader, writer: StreamWriter) -> bool: ... async def handle_code(self, code: str, reader: StreamReader, writer: StreamWriter) -> bool: ...
async def handle_client(self, reader: StreamReader, writer: StreamWriter) -> None: ... async def handle_client(self, reader: StreamReader, writer: StreamWriter) -> None: ...
async def start(self) -> None: ... async def start(self) -> None: ...

View File

@ -1,21 +1,37 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.utils.py # File core.utils.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.0 # Version 1.1
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import datetime
import logging import logging
import os
import tarfile
log_format = "[%(asctime)s | %(name)-14s | %(levelname)-5s] %(message)s" log_format = "[%(asctime)s | %(name)-14s | %(levelname)-5s] %(message)s"
log_format_access = '[%(asctime)s | %(name)-14s | %(levelname)-5s] %(client_addr)s - "%(request_line)s" %(status_code)s' log_dir = "./logs/"
log_file = "server.log" log_file = log_dir + "server.log"
log_level = logging.INFO log_level = logging.INFO
# Инициализируем логирование # Инициализируем логирование
logging.basicConfig(level=log_level, format=log_format) logging.basicConfig(level=log_level, format=log_format)
# Настройка логирование в файл. # Настройка логирование в файл.
# if os.path.exists(log_file): if not os.path.exists(log_dir):
# os.remove(log_file) os.mkdir(log_dir)
if os.path.exists(log_file):
mtime = os.path.getmtime(log_file)
gz_path = log_dir + datetime.datetime.fromtimestamp(mtime).strftime('%d.%m.%Y') + "-%s.tar.gz"
index = 1
while True:
if not os.path.exists(gz_path % index):
break
index += 1
with tarfile.open(gz_path % index, "w:gz") as tar:
logs_files = [log_file, "./logs/web.log", "./logs/web_access.log"]
for file in logs_files:
if os.path.exists(file):
tar.add(file, os.path.basename(file))
os.remove(file)
fh = logging.FileHandler(log_file, encoding='utf-8') fh = logging.FileHandler(log_file, encoding='utf-8')
fh.setFormatter(logging.Formatter(log_format)) fh.setFormatter(logging.Formatter(log_format))

View File

@ -14,6 +14,7 @@ from prompt_toolkit import PromptSession, print_formatted_text, HTML
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import NestedCompleter from prompt_toolkit.completion import NestedCompleter
from prompt_toolkit.history import FileHistory from prompt_toolkit.history import FileHistory
from prompt_toolkit.patch_stdout import patch_stdout
from core import get_logger from core import get_logger
@ -186,8 +187,12 @@ class Console:
session = PromptSession(history=FileHistory('./.cmdhistory')) session = PromptSession(history=FileHistory('./.cmdhistory'))
while True: while True:
try: try:
cmd_in = await session.prompt_async(self.__prompt_in, with patch_stdout():
completer=self.completer, auto_suggest=AutoSuggestFromHistory()) cmd_in = await session.prompt_async(
self.__prompt_in,
completer=self.completer,
auto_suggest=AutoSuggestFromHistory()
)
cmd_s = cmd_in.split(" ") cmd_s = cmd_in.split(" ")
cmd = cmd_s[0] cmd = cmd_s[0]
if cmd == "": if cmd == "":
@ -210,13 +215,3 @@ class Console:
def stop(self, *args, **kwargs): def stop(self, *args, **kwargs):
self.__is_run = False self.__is_run = False
raise KeyboardInterrupt raise KeyboardInterrupt
# if __name__ == '__main__':
# c = Console()
# c.logger_hook()
# c.builtins_hook()
# log = logging.getLogger(name="name")
# log.info("Starting console")
# print("Starting console")
# asyncio.run(c.start())

View File

@ -21,7 +21,7 @@ class EventsSystem:
self.log.debug(f"register_event({event_name}, {event_func}):") self.log.debug(f"register_event({event_name}, {event_func}):")
if not callable(event_func): if not callable(event_func):
self.log.error(f"Cannot add event '{event_name}'. " self.log.error(f"Cannot add event '{event_name}'. "
f"Use `BEAMP.add_event({event_name}', function)` instead. Skipping it...") f"Use `KuiToi.add_event({event_name}', function)` instead. Skipping it...")
return return
if event_name not in self.__events: if event_name not in self.__events:
self.__events.update({str(event_name): [event_func]}) self.__events.update({str(event_name): [event_func]})

View File

@ -8,7 +8,7 @@ class KuiToi:
def __init__(self, name=None): def __init__(self, name=None):
if name is None: if name is None:
raise Exception("BEAMP: Name is required") raise Exception("BeamMP: Name is required")
self.log = get_logger(f"PluginsLoader | {name}") self.log = get_logger(f"PluginsLoader | {name}")
self.name = name self.name = name
@ -43,7 +43,7 @@ class PluginsLoader:
plugin.print = print plugin.print = print
file = os.path.join(self.__plugins_dir, file) file = os.path.join(self.__plugins_dir, file)
with open(f'{file}', 'r') as f: with open(f'{file}', 'r') as f:
code = f.read().replace("import BEAMP\n", "") code = f.read().replace("import KuiToi\n", "")
exec(code, plugin.__dict__) exec(code, plugin.__dict__)
plugin.load() plugin.load()
self.__plugins.update({file[:-3]: plugin}) self.__plugins.update({file[:-3]: plugin})

View File

@ -7,7 +7,6 @@ from fastapi.exceptions import RequestValidationError
from starlette import status from starlette import status
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from uvicorn.config import LOGGING_CONFIG
import core.utils import core.utils
from . import utils from . import utils
@ -21,30 +20,6 @@ uvserver = None
data_pool = [] data_pool = []
data_run = [True] data_run = [True]
LOGGING_CONFIG["formatters"]["default"]['fmt'] = core.utils.log_format
LOGGING_CONFIG["formatters"]["access"]["fmt"] = core.utils.log_format_access
LOGGING_CONFIG["formatters"].update({
"file_default": {
"fmt": core.utils.log_format
},
"file_access": {
"fmt": core.utils.log_format_access
}
})
LOGGING_CONFIG["handlers"]["default"]['stream'] = "ext://sys.stdout"
LOGGING_CONFIG["handlers"].update({
"file_default": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "webserver.log"
},
"file_access": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "webserver.log"
}
})
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None): def response(data=None, code=status.HTTP_200_OK, error_code=0, error_message=None):
if 200 >= code <= 300: if 200 >= code <= 300:
@ -78,7 +53,8 @@ async def _method(method, secret_key: str = None):
async def _stop(): async def _stop():
await asyncio.sleep(1) await asyncio.sleep(1)
uvserver.should_exit = True if uvserver is not None:
uvserver.should_exit = True
data_run[0] = False data_run[0] = False

View File

@ -2,10 +2,17 @@ import asyncio
import sys import sys
import click import click
from uvicorn.server import Server, logger import uvicorn.server as uvs
from uvicorn.config import LOGGING_CONFIG
from uvicorn.lifespan import on from uvicorn.lifespan import on
import core.utils
# logger = core.utils.get_logger("uvicorn")
# uvs.logger = logger
logger = uvs.logger
def ev_log_started_message(self, listeners) -> None: def ev_log_started_message(self, listeners) -> None:
cfg = self.config cfg = self.config
@ -42,7 +49,7 @@ async def ev_shutdown(self, sockets=None) -> None:
try: try:
await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown) await asyncio.wait_for(self._wait_tasks_to_complete(), timeout=self.config.timeout_graceful_shutdown)
except asyncio.TimeoutError: except asyncio.TimeoutError:
logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded",len(self.server_state.tasks)) logger.error("Cancel %s running task(s), timeout graceful shutdown exceeded", len(self.server_state.tasks))
for t in self.server_state.tasks: for t in self.server_state.tasks:
if sys.version_info < (3, 9): if sys.version_info < (3, 9):
t.cancel() t.cancel()
@ -81,7 +88,40 @@ async def on_shutdown(self) -> None:
def hack_fastapi(): def hack_fastapi():
Server.shutdown = ev_shutdown uvs.Server.shutdown = ev_shutdown
Server._log_started_message = ev_log_started_message uvs.Server._log_started_message = ev_log_started_message
on.LifespanOn.startup = on_startup on.LifespanOn.startup = on_startup
on.LifespanOn.shutdown = on_shutdown on.LifespanOn.shutdown = on_shutdown
LOGGING_CONFIG["formatters"]["default"]['fmt'] = core.utils.log_format
LOGGING_CONFIG["formatters"]["access"]["fmt"] = core.utils.log_format
LOGGING_CONFIG["formatters"].update({
"file_default": {
"()": "logging.Formatter",
"fmt": core.utils.log_format
},
"file_access": {
"()": "logging.Formatter",
"fmt": core.utils.log_format
}
})
LOGGING_CONFIG["handlers"]["default"]['stream'] = "ext://sys.stdout"
LOGGING_CONFIG["handlers"].update({
"file_default": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "./logs/web.log",
"encoding": "utf-8",
"formatter": "file_default"
},
"file_access": {
"class": "logging.handlers.RotatingFileHandler",
"filename": "./logs/web_access.log",
"encoding": "utf-8",
"formatter": "file_access"
}
})
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
print(LOGGING_CONFIG)

View File

@ -7,8 +7,8 @@
"stop": "Сервер остановлен!", "stop": "Сервер остановлен!",
"": "Server auth", "": "Server auth",
"auth_need_key": "Нужен BEAMP ключ для запуска!", "auth_need_key": "Нужен BeamMP ключ для запуска!",
"auth_empty_key": "BEAMP ключ пустой!", "auth_empty_key": "BeamMP ключ пустой!",
"auth_cannot_open_browser": "Не получилось открыть браузер: {}", "auth_cannot_open_browser": "Не получилось открыть браузер: {}",
"auth_use_link": "Используй эту ссылку: {}", "auth_use_link": "Используй эту ссылку: {}",
@ -17,7 +17,7 @@
"GUI_no": "Нет", "GUI_no": "Нет",
"GUI_ok": "Окей", "GUI_ok": "Окей",
"GUI_cancel": "Отмена", "GUI_cancel": "Отмена",
"GUI_need_key_message": "Нужен BEAMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?", "GUI_need_key_message": "Нужен BeamMP ключ для запуска!\nХотите открыть ссылку в браузере для получения ключа?",
"GUI_enter_key_message": "Пожалуйста введите ключ:", "GUI_enter_key_message": "Пожалуйста введите ключ:",
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}", "GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",