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] 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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
|
||||||
|
@ -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"]:
|
||||||
|
@ -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__}"')
|
||||||
|
@ -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): ...
|
@ -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__)
|
||||||
if type(plugin.kt) != KuiToi:
|
|
||||||
raise AttributeError(f'Attribute "kt" isn\'t KuiToi class. Plugin file: "{file_path}"')
|
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:
|
||||||
|
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)
|
||||||
|
@ -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)
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user