mirror of
https://github.com/hubHarmony/servii-backend.git
synced 2024-11-17 21:40:31 +00:00
2e6e13a676
Now the get_sdk_version won't be called on modded servers, preventing a non base 10 error (because the modpack's version name won't be represented as a minecraft version, but as the modpack name itself.
184 lines
6.7 KiB
Python
184 lines
6.7 KiB
Python
import shlex
|
|
import subprocess
|
|
import time
|
|
from typing import Union, Callable
|
|
|
|
import mcipc.query
|
|
import mcipc.query.client
|
|
|
|
import file_manager
|
|
import firebase_manager
|
|
|
|
|
|
class MinecraftServerManager:
|
|
allowed_properties: list[str] = ["difficulty", "gamemode", "force-gamemode", "hardcore", "generate-structures",
|
|
"motd", "pvp", "online-mode", "max-players", "enable-command-block"]
|
|
|
|
def __init__(self):
|
|
self.servers: dict = {}
|
|
self.servers_count: int = 0
|
|
self.cooldowns = {}
|
|
self.offline_ports: list[int] = []
|
|
|
|
def start_server(self, server_directory: str, port: int, user_id: str, server_name: str,
|
|
version: str, jar_file: str = 'server.jar',
|
|
memory_size: str ='4G', modded: bool = False) -> Union[int, None]:
|
|
|
|
if port in self.servers:
|
|
return None
|
|
|
|
reg_flags: str = ("-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200"
|
|
" -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch"
|
|
" -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M"
|
|
" -XX:G1ReservePercent=20 -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4"
|
|
" -XX:InitiatingHeapOccupancyPercent=15 -XX:G1MixedGCLiveThresholdPercent=90"
|
|
" -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem"
|
|
" -XX:MaxTenuringThreshold=1 -Daikars.new.flags=true"
|
|
" -Dusing.aikars.flags=https://mcutils.com")
|
|
|
|
command: str
|
|
|
|
if not modded:
|
|
java: str = f"/usr/lib/jvm/java-{get_sdk_version(version)}-openjdk-amd64/bin/java"
|
|
command = f"{java} -Xmx{memory_size} {reg_flags} -jar {jar_file} --nogui"
|
|
else:
|
|
command = "./start.sh"
|
|
|
|
process = subprocess.Popen(shlex.split(command), cwd=server_directory, stdin=subprocess.PIPE)
|
|
#TODO: Track process behavior and stderr, while excepting Advanced Terminal features not to be avail.
|
|
|
|
self.servers_count = len(self.servers) + 1
|
|
self.servers[port] = {
|
|
'process': process,
|
|
'directory': server_directory,
|
|
'port': port,
|
|
'user_id': user_id,
|
|
'name': server_name,
|
|
'version': version,
|
|
'time': time.time(),
|
|
}
|
|
return port
|
|
|
|
def execute_server_command(self, port, command) -> bool:
|
|
if port not in self.servers:
|
|
return False
|
|
process = self.servers[port]['process']
|
|
process.stdin.write(command.encode('utf-8') + b'\n')
|
|
process.stdin.flush()
|
|
return True
|
|
|
|
def stop_server(self, port: int) -> bool:
|
|
if port not in self.servers:
|
|
return False
|
|
process = self.servers[port]['process']
|
|
process.communicate(input=b"stop\n")
|
|
del self.servers[port]
|
|
if port in self.offline_ports:
|
|
self.offline_ports.remove(port)
|
|
return True
|
|
|
|
def stop_server_forcefully(self, port) -> bool:
|
|
if port not in self.servers:
|
|
return False
|
|
process = self.servers[port]['process']
|
|
process.terminate()
|
|
del self.servers[port]
|
|
return True
|
|
|
|
def get_servers(self):
|
|
return self.servers.values()
|
|
|
|
def get_server_id_by_port(self, port):
|
|
for server_id, server_info in self.servers.items():
|
|
if server_info['port'] == port:
|
|
return server_id
|
|
return None
|
|
|
|
def get_online_players(self, port) -> int:
|
|
if not self.servers[port]:
|
|
return 0
|
|
try:
|
|
with mcipc.query.Client('127.0.0.1', port) as client:
|
|
stats: int = client.stats(full=False).num_players
|
|
return stats
|
|
except Exception as e:
|
|
file_manager.log_error(type(e).__name__, str(e))
|
|
return 0
|
|
|
|
def set_cooldown(self, user_id):
|
|
expiry_timestamp = time.time() + 30
|
|
self.cooldowns[user_id] = expiry_timestamp
|
|
|
|
def has_cooldown(self, user_id):
|
|
expiry_timestamp = self.cooldowns.get(user_id)
|
|
if expiry_timestamp is None:
|
|
return False
|
|
current_time = time.time()
|
|
if current_time < expiry_timestamp:
|
|
return True
|
|
else:
|
|
del self.cooldowns[user_id]
|
|
return False
|
|
|
|
def check_servers_idle(self) -> None:
|
|
servers: dict = self.servers.copy()
|
|
|
|
for port, server_info in servers.items():
|
|
online_players = self.get_online_players(port)
|
|
|
|
if online_players == 0:
|
|
if port not in self.offline_ports:
|
|
self.offline_ports.append(port)
|
|
else:
|
|
user_id = server_info.get("user_id", None)
|
|
name = server_info.get("name", None)
|
|
server_stamp = server_info.get("time", None)
|
|
firebase_manager.close_idle_server(
|
|
user_id=user_id,
|
|
name=name,
|
|
server_stamp=server_stamp,
|
|
port=port,
|
|
)
|
|
else:
|
|
if port in self.offline_ports:
|
|
self.offline_ports.remove(port)
|
|
return
|
|
|
|
|
|
def version_range_checker(*ranges: tuple[str, str, str], default_sdk: str = "java") -> Callable[[str], str]:
|
|
def compare_mc_versions(version1: str):
|
|
def inner(version2: str) -> int:
|
|
v1_parts: list[int] = list(map(int, version1.split('.')))
|
|
v2_parts: list[int] = list(map(int, version2.split('.')))
|
|
for v1, v2 in zip(v1_parts, v2_parts):
|
|
if v1 > v2:
|
|
return 1
|
|
elif v1 < v2:
|
|
return -1
|
|
if len(v1_parts) > len(v2_parts):
|
|
return 1
|
|
elif len(v1_parts) < len(v2_parts):
|
|
return -1
|
|
return 0
|
|
return inner
|
|
|
|
def check_range(lower_bound: str, upper_bound: str, version: str) -> bool:
|
|
return compare_mc_versions(lower_bound)(version) <= 0 <= compare_mc_versions(upper_bound)(version)
|
|
|
|
def check_version(version: str) -> str:
|
|
for lower_bound, upper_bound, sdk in ranges:
|
|
if check_range(lower_bound, upper_bound, version):
|
|
return sdk
|
|
return default_sdk
|
|
|
|
return check_version
|
|
|
|
|
|
get_sdk_version: Callable[[str], str] = version_range_checker(("1.0", "1.13.2", "8"),
|
|
("1.14.1", "1.16.5", "11"),
|
|
("1.17.1", "1.18.1", "17"),
|
|
default_sdk="21")
|
|
|
|
if __name__ == "__main__":
|
|
pass
|