Compare commits

...

8 Commits

Author SHA1 Message Date
5f8b70a2ee Update version 2023-07-15 11:03:05 +03:00
a66f3d8b36 Update TODOs 2023-07-15 11:00:46 +03:00
4c3da30a94 Add new events. 2023-07-15 10:59:50 +03:00
9c52e41b99 Add new logs to class KuiToi;
load:
    Add encoding to open();
    Add plugin attributes error;
    Add plugin.start();
    Add plugin.unload();
    Recreate plugin settings;
    Add async support;
2023-07-15 10:59:33 +03:00
51f960f7c2 Remove print() 2023-07-15 10:53:38 +03:00
0cbed05d68 Move plugins loader to core. 2023-07-15 10:52:58 +03:00
c6c6ec31b0 Add new stock events;
Add async support to call_event();
Add **kwargs t0 call_event();
Sends new data to events;
2023-07-15 10:52:25 +03:00
8feba0e085 Minor update 2023-07-15 09:17:52 +03:00
8 changed files with 137 additions and 36 deletions

View File

@ -41,9 +41,11 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Call events - [x] Call events
- [x] Create custom events - [x] Create custom events
- [x] Return from events - [x] Return from events
- [x] Async support
- [x] Plugins support - [x] Plugins support
- [ ] KuiToi class - [ ] KuiToi class
- [x] Load Python plugins - [x] Load Python plugins
- [x] Async support
- [ ] Load Lua plugins (Original BeamMP compatibility) - [ ] Load Lua plugins (Original BeamMP compatibility)
- [x] MultiLanguage (i18n support) - [x] MultiLanguage (i18n support)
- [x] Core - [x] Core

View File

@ -70,7 +70,7 @@ class Client:
if len(data) == 10: if len(data) == 10:
data += b"." data += b"."
header = len(data).to_bytes(4, "little", signed=True) header = len(data).to_bytes(4, "little", signed=True)
self.log.debug(f'len: {len(data)}; send: {header + data}') self.log.debug(f'len: {len(data)}; send: {header + data!r}')
try: try:
writer.write(header + data) writer.write(header + data)
await writer.drain() await writer.drain()
@ -116,6 +116,7 @@ class Client:
self.alive = False self.alive = False
return b"" return b""
# TODO: Speed limiter
async def _split_load(self, start, end, d_sock, filename): async def _split_load(self, start, end, d_sock, filename):
real_size = end - start real_size = end - start
writer = self.down_rw[1] if d_sock else self.writer writer = self.down_rw[1] if d_sock else self.writer

View File

@ -11,7 +11,7 @@ __title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.' __description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server' __url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.2.2' __version__ = '0.2.2'
__build__ = 1113 # Я это считаю лог файлами __build__ = 1176 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"
@ -19,7 +19,6 @@ __copyright__ = 'Copyright 2023 © SantaSpeen (Maxim Khomutov)'
import asyncio import asyncio
import builtins import builtins
import os
import webbrowser import webbrowser
import prompt_toolkit.shortcuts as shortcuts import prompt_toolkit.shortcuts as shortcuts
@ -27,7 +26,7 @@ import prompt_toolkit.shortcuts as shortcuts
from .utils import get_logger from .utils import get_logger
from core.core import Core from core.core import Core
from main import parser from main import parser
from modules import ConfigProvider, EventsSystem, PluginsLoader from modules import ConfigProvider, EventsSystem
from modules import Console from modules import Console
from modules import MultiLanguage from modules import MultiLanguage
@ -107,16 +106,8 @@ console.builtins_hook()
console.add_command("stop", console.stop, i18n.man_message_stop, i18n.help_message_stop) console.add_command("stop", console.stop, i18n.man_message_stop, i18n.help_message_stop)
console.add_command("exit", console.stop, i18n.man_message_exit, i18n.help_message_exit) console.add_command("exit", console.stop, i18n.man_message_exit, i18n.help_message_exit)
log.debug("Initializing PluginsLoader...")
if not os.path.exists("plugins"):
os.mkdir("plugins")
pl = PluginsLoader("plugins")
pl.load_plugins()
builtins.B = 1 builtins.B = 1
builtins.KB = B * 1024 builtins.KB = B * 1024
builtins.MB = KB * 1024 builtins.MB = KB * 1024
builtins.GB = MB * 1024 builtins.GB = MB * 1024
builtins.TB = GB * 1024 builtins.TB = GB * 1024
log.info(i18n.init_ok)

View File

@ -16,6 +16,7 @@ from core import utils
from core.Client import Client from core.Client import Client
from core.tcp_server import TCPServer from core.tcp_server import TCPServer
from core.udp_server import UDPServer from core.udp_server import UDPServer
from modules import PluginsLoader
from modules.WebAPISystem import app as webapp from modules.WebAPISystem import app as webapp
@ -199,13 +200,19 @@ class Core:
self.log.error(f"Error in heartbeat: {e}") self.log.error(f"Error in heartbeat: {e}")
async def main(self): async def main(self):
self.run = True
self.tcp = self.tcp(self, self.server_ip, self.server_port) self.tcp = self.tcp(self, self.server_ip, self.server_port)
self.udp = self.udp(self, self.server_ip, self.server_port) self.udp = self.udp(self, self.server_ip, self.server_port)
console.add_command( console.add_command(
"list", "list",
lambda x: f"Players list: {self.get_clients_list(True)}" lambda x: f"Players list: {self.get_clients_list(True)}"
) )
self.log.debug("Initializing PluginsLoader...")
if not os.path.exists("plugins"):
os.mkdir("plugins")
pl = PluginsLoader("plugins")
await pl.load()
try: try:
# WebApi Start # WebApi Start
if config.WebAPI["enabled"]: if config.WebAPI["enabled"]:
@ -233,6 +240,7 @@ class Core:
if len_mods > 0: if len_mods > 0:
# TODO: i18n # TODO: i18n
self.log.info(f"Loaded {len_mods} mods: {round(self.mods_list[0] / MB, 2)}mb") self.log.info(f"Loaded {len_mods} mods: {round(self.mods_list[0] / MB, 2)}mb")
self.log.info(i18n.init_ok)
await self.heartbeat(True) await self.heartbeat(True)
for i in range(int(config.Game["players"] * 2.3)): # * 2.3 For down sock and buffer. for i in range(int(config.Game["players"] * 2.3)): # * 2.3 For down sock and buffer.
@ -244,8 +252,11 @@ class Core:
tasks.append(asyncio.create_task(task())) tasks.append(asyncio.create_task(task()))
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
ev.call_event("_plugins_start")
self.run = True
self.log.info(i18n.start) self.log.info(i18n.start)
ev.call_event("on_started") ev.call_event("server_started")
await t # Wait end. await t # Wait end.
except Exception as e: except Exception as e:
self.log.error(f"Exception: {e}") self.log.error(f"Exception: {e}")
@ -261,6 +272,8 @@ class Core:
asyncio.run(self.main()) asyncio.run(self.main())
def stop(self): def stop(self):
ev.call_event("server_stopped")
ev.call_event("_plugins_unload")
self.run = False self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
if config.WebAPI["enabled"]: if config.WebAPI["enabled"]:

View File

@ -1,4 +1,7 @@
import asyncio
import builtins import builtins
import inspect
import time
from core import get_logger from core import get_logger
@ -8,13 +11,17 @@ class EventsSystem:
def __init__(self): def __init__(self):
# TODO: default events # TODO: default events
self.log = get_logger("EventsSystem")
self.loop = asyncio.get_event_loop()
self.__events = { self.__events = {
"on_started": [], "server_started": [],
"_plugins_start": [],
"auth_sent_key": [], "auth_sent_key": [],
"auth_ok": [], "auth_ok": [],
"chat_receive": [], "chat_receive": [],
"_plugins_unload": [],
"server_stopped": [],
} }
self.log = get_logger("EventsSystem")
def builtins_hook(self): def builtins_hook(self):
self.log.debug("used builtins_hook") self.log.debug("used builtins_hook")
@ -39,7 +46,12 @@ class EventsSystem:
if event_name in self.__events.keys(): if event_name in self.__events.keys():
for func in self.__events[event_name]: for func in self.__events[event_name]:
try: try:
funcs_data.append(func({"event_name": event_name, "args": args, "kwargs": kwargs})) event_data = {"event_name": event_name, "args": args, "kwargs": kwargs}
if inspect.iscoroutinefunction(func):
d = self.loop.run_until_complete(func(event_data))
else:
d = func(event_data)
funcs_data.append(d)
except Exception as e: except Exception as e:
# TODO: i18n # TODO: i18n
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"') self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')

View File

@ -1,6 +1,6 @@
class EventsSystem: class EventsSystem:
@staticmethod @staticmethod
def register_event(self, event_name, event_func): ... def register_event(event_name, event_func): ...
@staticmethod @staticmethod
def call_event(self, event_name, *data): ... def call_event(event_name, *data, **kwargs): ...
class ev(EventsSystem): ... class ev(EventsSystem): ...

View File

@ -1,6 +1,9 @@
import asyncio
import inspect
import os import os
import types import types
from contextlib import contextmanager from contextlib import contextmanager
from threading import Thread
from core import get_logger from core import get_logger
@ -39,8 +42,9 @@ class KuiToi:
@contextmanager @contextmanager
def open(self, file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None): def open(self, file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None):
path = os.path.join(self.__dir, file) path = os.path.join(self.__dir, file)
if not os.path.exists(path): self.log.debug(f'Trying to open "{path}" with mode "{mode}"')
with open(path, 'x'): ... # if not os.path.exists(path):
# with open(path, 'x'): ...
f = None f = None
try: try:
f = open(path, mode, buffering, encoding, errors, newline, closefd, opener) f = open(path, mode, buffering, encoding, errors, newline, closefd, opener)
@ -51,23 +55,27 @@ class KuiToi:
if f is not None: if f is not None:
f.close() f.close()
@staticmethod def register_event(self, event_name, event_func):
def register_event(event_name, event_func): self.log.debug(f"Registering event {event_name}")
ev.register_event(event_name, event_func) ev.register_event(event_name, event_func)
@staticmethod def call_event(self, event_name, *data, **kwargs):
def call_event(event_name, *data): self.log.debug(f"Called event {event_name}")
ev.call_event(event_name, *data) ev.call_event(event_name, *data, **kwargs)
class PluginsLoader: class PluginsLoader:
def __init__(self, plugins_dir): def __init__(self, plugins_dir):
self.loop = asyncio.get_event_loop()
self.plugins = {} self.plugins = {}
self.plugins_tasks = []
self.plugins_dir = plugins_dir self.plugins_dir = plugins_dir
self.log = get_logger("PluginsLoader") self.log = get_logger("PluginsLoader")
ev.register_event("_plugins_start", self.start)
ev.register_event("_plugins_unload", self.unload)
def load_plugins(self): async def load(self):
self.log.debug("Loading plugins...") self.log.debug("Loading plugins...")
files = os.listdir(self.plugins_dir) files = os.listdir(self.plugins_dir)
for file in files: for file in files:
@ -77,24 +85,99 @@ class PluginsLoader:
plugin = types.ModuleType(file[:-3]) plugin = types.ModuleType(file[:-3])
plugin.KuiToi = KuiToi plugin.KuiToi = KuiToi
plugin.KuiToi._plugins_dir = self.plugins_dir plugin.KuiToi._plugins_dir = self.plugins_dir
plugin.kt = None
plugin.print = print plugin.print = print
file_path = os.path.join(self.plugins_dir, file) file_path = os.path.join(self.plugins_dir, file)
plugin.__file__ = file_path plugin.__file__ = file_path
with open(f'{file_path}', 'r') as f: with open(f'{file_path}', 'r', encoding="utf-8") as f:
code = f.read() code = f.read()
exec(code, plugin.__dict__) exec(code, plugin.__dict__)
ok = True
try:
isfunc = inspect.isfunction
if not isfunc(plugin.load):
self.log.error('Function "def load():" not found.')
ok = False
if not isfunc(plugin.start):
self.log.error('Function "def start():" not found.')
ok = False
if not isfunc(plugin.unload):
self.log.error('Function "def unload():" not found.')
ok = False
if type(plugin.kt) != KuiToi: if type(plugin.kt) != KuiToi:
raise AttributeError(f'Attribute "kt" isn\'t KuiToi class. Plugin file: "{file_path}"') self.log.error(f'Attribute "kt" isn\'t KuiToi class. Plugin file: "{file_path}"')
ok = False
except AttributeError:
ok = False
if not ok:
self.log.error(f'Plugin file: "{file_path}" is not a valid KuiToi plugin.')
return
pl_name = plugin.kt.name pl_name = plugin.kt.name
if self.plugins.get(pl_name) is not None: if self.plugins.get(pl_name) is not None:
raise NameError(f'Having plugins with identical names is not allowed; ' raise NameError(f'Having plugins with identical names is not allowed; '
f'Plugin name: "{pl_name}"; Plugin file "{file_path}"') f'Plugin name: "{pl_name}"; Plugin file "{file_path}"')
plugin.open = plugin.kt.open plugin.open = plugin.kt.open
plugin.load() iscorfunc = inspect.iscoroutinefunction
self.plugins.update({pl_name: plugin}) self.plugins.update(
self.log.debug(f"Plugin loaded: {file}") {
pl_name: {
"plugin": plugin,
"load": {
"func": plugin.load,
"async": iscorfunc(plugin.load)
},
"start": {
"func": plugin.start,
"async": iscorfunc(plugin.start)
},
"unload": {
"func": plugin.unload,
"async": iscorfunc(plugin.unload)
}
}
}
)
if self.plugins[pl_name]["load"]['async']:
plugin.log.debug(f"I'm async")
await plugin.load()
else:
plugin.log.debug(f"I'm sync")
th = Thread(target=plugin.load, name=f"{pl_name}.load()")
th.start()
th.join()
self.log.debug(f"Plugin loaded: {file}. Settings: {self.plugins[pl_name]}")
except Exception as e: except Exception as e:
# TODO: i18n # TODO: i18n
self.log.error(f"Error while loading plugin: {file}; Error: {e}") self.log.error(f"Error while loading plugin: {file}; Error: {e}")
self.log.exception(e) self.log.exception(e)
async def start(self, _):
for pl_name, pl_data in self.plugins.items():
try:
if pl_data['start']['async']:
self.log.debug(f"Start async plugin: {pl_name}")
t = self.loop.create_task(pl_data['start']['func']())
self.plugins_tasks.append(t)
else:
self.log.debug(f"Start sync plugin: {pl_name}")
th = Thread(target=pl_data['start']['func'], name=f"Thread {pl_name}")
th.start()
self.plugins_tasks.append(th)
except Exception as e:
self.log.exception(e)
async def unload(self, _):
for pl_name, pl_data in self.plugins.items():
try:
if pl_data['unload']['async']:
self.log.debug(f"Unload async plugin: {pl_name}")
await pl_data['unload']['func']()
else:
self.log.debug(f"Unload sync plugin: {pl_name}")
th = Thread(target=pl_data['unload']['func'], name=f"Thread {pl_name}")
th.start()
th.join()
except Exception as e:
self.log.exception(e)

View File

@ -122,6 +122,5 @@ def hack_fastapi():
}) })
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default") LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access") LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
print(LOGGING_CONFIG)