mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2025-08-17 08:15:42 +00:00
Compare commits
8 Commits
a5202edf83
...
5f8b70a2ee
Author | SHA1 | Date | |
---|---|---|---|
5f8b70a2ee | |||
a66f3d8b36 | |||
4c3da30a94 | |||
9c52e41b99 | |||
51f960f7c2 | |||
0cbed05d68 | |||
c6c6ec31b0 | |||
8feba0e085 |
@ -41,9 +41,11 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
||||
- [x] Call events
|
||||
- [x] Create custom events
|
||||
- [x] Return from events
|
||||
- [x] Async support
|
||||
- [x] Plugins support
|
||||
- [ ] KuiToi class
|
||||
- [x] Load Python plugins
|
||||
- [x] Async support
|
||||
- [ ] Load Lua plugins (Original BeamMP compatibility)
|
||||
- [x] MultiLanguage (i18n support)
|
||||
- [x] Core
|
||||
|
@ -70,7 +70,7 @@ class Client:
|
||||
if len(data) == 10:
|
||||
data += b"."
|
||||
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:
|
||||
writer.write(header + data)
|
||||
await writer.drain()
|
||||
@ -116,6 +116,7 @@ class Client:
|
||||
self.alive = False
|
||||
return b""
|
||||
|
||||
# TODO: Speed limiter
|
||||
async def _split_load(self, start, end, d_sock, filename):
|
||||
real_size = end - start
|
||||
writer = self.down_rw[1] if d_sock else self.writer
|
||||
|
@ -11,7 +11,7 @@ __title__ = 'KuiToi-Server'
|
||||
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
||||
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
||||
__version__ = '0.2.2'
|
||||
__build__ = 1113 # Я это считаю лог файлами
|
||||
__build__ = 1176 # Я это считаю лог файлами
|
||||
__author__ = 'SantaSpeen'
|
||||
__author_email__ = 'admin@kuitoi.su'
|
||||
__license__ = "FPA"
|
||||
@ -19,7 +19,6 @@ __copyright__ = 'Copyright 2023 © SantaSpeen (Maxim Khomutov)'
|
||||
|
||||
import asyncio
|
||||
import builtins
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
import prompt_toolkit.shortcuts as shortcuts
|
||||
@ -27,7 +26,7 @@ import prompt_toolkit.shortcuts as shortcuts
|
||||
from .utils import get_logger
|
||||
from core.core import Core
|
||||
from main import parser
|
||||
from modules import ConfigProvider, EventsSystem, PluginsLoader
|
||||
from modules import ConfigProvider, EventsSystem
|
||||
from modules import Console
|
||||
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("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.KB = B * 1024
|
||||
builtins.MB = KB * 1024
|
||||
builtins.GB = MB * 1024
|
||||
builtins.TB = GB * 1024
|
||||
|
||||
log.info(i18n.init_ok)
|
||||
|
@ -16,6 +16,7 @@ from core import utils
|
||||
from core.Client import Client
|
||||
from core.tcp_server import TCPServer
|
||||
from core.udp_server import UDPServer
|
||||
from modules import PluginsLoader
|
||||
from modules.WebAPISystem import app as webapp
|
||||
|
||||
|
||||
@ -199,13 +200,19 @@ class Core:
|
||||
self.log.error(f"Error in heartbeat: {e}")
|
||||
|
||||
async def main(self):
|
||||
self.run = True
|
||||
self.tcp = self.tcp(self, self.server_ip, self.server_port)
|
||||
self.udp = self.udp(self, self.server_ip, self.server_port)
|
||||
console.add_command(
|
||||
"list",
|
||||
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:
|
||||
# WebApi Start
|
||||
if config.WebAPI["enabled"]:
|
||||
@ -233,6 +240,7 @@ class Core:
|
||||
if len_mods > 0:
|
||||
# TODO: i18n
|
||||
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)
|
||||
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()))
|
||||
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
|
||||
|
||||
ev.call_event("_plugins_start")
|
||||
|
||||
self.run = True
|
||||
self.log.info(i18n.start)
|
||||
ev.call_event("on_started")
|
||||
ev.call_event("server_started")
|
||||
await t # Wait end.
|
||||
except Exception as e:
|
||||
self.log.error(f"Exception: {e}")
|
||||
@ -261,6 +272,8 @@ class Core:
|
||||
asyncio.run(self.main())
|
||||
|
||||
def stop(self):
|
||||
ev.call_event("server_stopped")
|
||||
ev.call_event("_plugins_unload")
|
||||
self.run = False
|
||||
self.log.info(i18n.stop)
|
||||
if config.WebAPI["enabled"]:
|
||||
|
@ -1,4 +1,7 @@
|
||||
import asyncio
|
||||
import builtins
|
||||
import inspect
|
||||
import time
|
||||
|
||||
from core import get_logger
|
||||
|
||||
@ -8,13 +11,17 @@ class EventsSystem:
|
||||
|
||||
def __init__(self):
|
||||
# TODO: default events
|
||||
self.log = get_logger("EventsSystem")
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.__events = {
|
||||
"on_started": [],
|
||||
"server_started": [],
|
||||
"_plugins_start": [],
|
||||
"auth_sent_key": [],
|
||||
"auth_ok": [],
|
||||
"chat_receive": [],
|
||||
"_plugins_unload": [],
|
||||
"server_stopped": [],
|
||||
}
|
||||
self.log = get_logger("EventsSystem")
|
||||
|
||||
def builtins_hook(self):
|
||||
self.log.debug("used builtins_hook")
|
||||
@ -39,7 +46,12 @@ class EventsSystem:
|
||||
if event_name in self.__events.keys():
|
||||
for func in self.__events[event_name]:
|
||||
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:
|
||||
# TODO: i18n
|
||||
self.log.error(f'Error while calling "{event_name}"; In function: "{func.__name__}"')
|
||||
|
@ -1,6 +1,6 @@
|
||||
class EventsSystem:
|
||||
@staticmethod
|
||||
def register_event(self, event_name, event_func): ...
|
||||
def register_event(event_name, event_func): ...
|
||||
@staticmethod
|
||||
def call_event(self, event_name, *data): ...
|
||||
def call_event(event_name, *data, **kwargs): ...
|
||||
class ev(EventsSystem): ...
|
@ -1,6 +1,9 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import os
|
||||
import types
|
||||
from contextlib import contextmanager
|
||||
from threading import Thread
|
||||
|
||||
from core import get_logger
|
||||
|
||||
@ -39,8 +42,9 @@ class KuiToi:
|
||||
@contextmanager
|
||||
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)
|
||||
if not os.path.exists(path):
|
||||
with open(path, 'x'): ...
|
||||
self.log.debug(f'Trying to open "{path}" with mode "{mode}"')
|
||||
# if not os.path.exists(path):
|
||||
# with open(path, 'x'): ...
|
||||
f = None
|
||||
try:
|
||||
f = open(path, mode, buffering, encoding, errors, newline, closefd, opener)
|
||||
@ -51,23 +55,27 @@ class KuiToi:
|
||||
if f is not None:
|
||||
f.close()
|
||||
|
||||
@staticmethod
|
||||
def register_event(event_name, event_func):
|
||||
def register_event(self, event_name, event_func):
|
||||
self.log.debug(f"Registering event {event_name}")
|
||||
ev.register_event(event_name, event_func)
|
||||
|
||||
@staticmethod
|
||||
def call_event(event_name, *data):
|
||||
ev.call_event(event_name, *data)
|
||||
def call_event(self, event_name, *data, **kwargs):
|
||||
self.log.debug(f"Called event {event_name}")
|
||||
ev.call_event(event_name, *data, **kwargs)
|
||||
|
||||
|
||||
class PluginsLoader:
|
||||
|
||||
def __init__(self, plugins_dir):
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.plugins = {}
|
||||
self.plugins_tasks = []
|
||||
self.plugins_dir = plugins_dir
|
||||
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...")
|
||||
files = os.listdir(self.plugins_dir)
|
||||
for file in files:
|
||||
@ -77,24 +85,99 @@ class PluginsLoader:
|
||||
plugin = types.ModuleType(file[:-3])
|
||||
plugin.KuiToi = KuiToi
|
||||
plugin.KuiToi._plugins_dir = self.plugins_dir
|
||||
plugin.kt = None
|
||||
plugin.print = print
|
||||
file_path = os.path.join(self.plugins_dir, file)
|
||||
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()
|
||||
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:
|
||||
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
|
||||
if self.plugins.get(pl_name) is not None:
|
||||
raise NameError(f'Having plugins with identical names is not allowed; '
|
||||
f'Plugin name: "{pl_name}"; Plugin file "{file_path}"')
|
||||
|
||||
plugin.open = plugin.kt.open
|
||||
plugin.load()
|
||||
self.plugins.update({pl_name: plugin})
|
||||
self.log.debug(f"Plugin loaded: {file}")
|
||||
iscorfunc = inspect.iscoroutinefunction
|
||||
self.plugins.update(
|
||||
{
|
||||
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:
|
||||
# TODO: i18n
|
||||
self.log.error(f"Error while loading plugin: {file}; Error: {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)
|
||||
|
@ -122,6 +122,5 @@ def hack_fastapi():
|
||||
})
|
||||
LOGGING_CONFIG["loggers"]["uvicorn"]["handlers"].append("file_default")
|
||||
LOGGING_CONFIG["loggers"]["uvicorn.access"]["handlers"].append("file_access")
|
||||
print(LOGGING_CONFIG)
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user