Parse player list from 'list' command.
[minecraft_web.git] / backend / src / minecraft_controller.rs
1 use sysinfo::{ ProcessExt, SystemExt };
2
3 use std::{ fs, time::SystemTime };
4
5 use chrono::{ DateTime, offset::Local };
6
7 #[derive(Clone, Debug)]
8 pub struct MinecraftExe {
9 memory: u64, // [kB].
10 load_average_5min: f64, // [%].
11 uptime: u64, // [s].
12 world_size: u64, // [B].
13 active_players: Vec<String>,
14 last_backup: Option<SystemTime>,
15 }
16
17 impl MinecraftExe {
18 pub fn format_memory(&self) -> String {
19 format_byte_size(self.memory * 1024, 2)
20 }
21
22 pub fn format_load_average(&self) -> String {
23 format!("{:.2} %", self.load_average_5min)
24 }
25
26 pub fn format_uptime(&self) -> String {
27 let mins = self.uptime / 60;
28 let hours = mins / 60;
29 let days = hours / 24;
30 format!("{}d {}h {}min", days, hours - 24 * days, mins - 60 * hours)
31 }
32
33 pub fn format_world_size(&self) -> String {
34 format_byte_size(self.world_size, 2)
35 }
36
37 pub fn format_active_players(&self) -> String {
38 /* Commented because the player list isn't correct (the number is).
39 if self.active_players.len() == 0 {
40 String::from("<none>")
41 } else {
42 self.active_players.join(", ")
43 }*/
44 self.active_players.len().to_string()
45 }
46
47 pub fn format_last_backup(&self) -> String {
48 match self.last_backup {
49 Some(t) => {
50 let datetime: DateTime<Local> = t.into();
51 datetime.format("%d/%m/%Y %T").to_string()
52 },
53 None => String::from("?")
54 }
55 }
56 }
57
58 const BINARY_PREFIXES: [&str; 8] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"];
59
60 fn format_byte_size(bytes: u64, precision: usize) -> String {
61 for i in 0 .. 8 {
62 let mut size: u64 = 1;
63 size *= 1024u64.pow(i as u32);
64
65 if bytes < 1024 {
66 return format!("{} {}", std::cmp::max(0u64, bytes), BINARY_PREFIXES[i]);
67 }
68 else if bytes < 1024 * size {
69 return format!("{:.prec$} {}", bytes as f64 / size as f64, BINARY_PREFIXES[i], prec = precision);
70 }
71 }
72
73 String::from("")
74 }
75
76 const MINECRAFT_PROCESS_NAME: &str = "java";
77
78 // It doesn't work for the moment, it only scan the connection event and do not treat disconnections.
79 fn get_active_players(rcon_password: &str) -> Vec<String> {
80 let mut client = minecraft_client_rs::Client::new("127.0.0.1:25575".to_string()).unwrap();
81
82 let players =
83 match client.authenticate(rcon_password.to_string()) {
84 Ok(_) => {
85 match client.send_command("list".to_string()) {
86 Ok(resp) => {
87 resp.body
88 .split('\n')
89 .skip(1)
90 .filter(|n| !n.is_empty())
91 .map(|n| n.to_string())
92 .collect()
93 },
94 Err(_e) => {
95 println!("Error from 'list' command");
96 Vec::new()
97 },
98 }
99 },
100 Err(_e) => {
101 println!("Authentication error");
102 Vec::new()
103 },
104 };
105
106 client.close().unwrap();
107
108 players
109 }
110
111 fn get_last_backup_datetime(backup_path: &str) -> Option<SystemTime> {
112 let mut times =
113 fs::read_dir(backup_path).ok()?.filter_map(
114 |e| {
115 let dir = e.ok()?;
116 if dir.path().is_file() { Some(dir.metadata().ok()?.modified().ok()?) } else { None }
117 }
118 )
119 .collect::<Vec<SystemTime>>();
120
121 times.sort();
122
123 Some(times.last()?.clone())
124 }
125
126 pub fn get_minecraft_executable_information(world_path: &str, backup_path: &str, rcon_password: &str) -> Option<MinecraftExe> {
127 let mut system = sysinfo::System::new_all();
128 system.refresh_system();
129 let processes = system.get_process_by_name(MINECRAFT_PROCESS_NAME);
130
131 // TODO: find the correct process by checking the correct jar name in parameters.
132 if processes.len() >= 1 {
133 let process = processes.first().unwrap();
134
135 let world_size = match std::fs::metadata(world_path) { Ok(f) => f.len(), Err(_) => 0u64 };
136
137 Some(
138 MinecraftExe {
139 memory: process.memory(),
140 load_average_5min: system.get_load_average().five / system.get_processors().len() as f64 * 100.,
141 uptime: std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - process.start_time(),
142 world_size,
143 active_players: get_active_players(rcon_password),
144 last_backup: get_last_backup_datetime(backup_path)
145 }
146 )
147 } else {
148 None
149 }
150 }