Use 'rcon' to get the player list
[minecraft_web.git] / backend / src / main.rs
1 extern crate askama;
2
3 use std::{ sync::Mutex, env::args, fs::File, io::prelude::* };
4
5 use actix_files as fs;
6 use actix_web::{ get, web, Responder, middleware, App, HttpServer };
7 use askama::Template;
8 use ron::{ de::from_reader, ser::{ to_string_pretty, PrettyConfig } };
9 use serde::{ Deserialize, Serialize };
10 use cached::proc_macro::cached;
11
12 mod consts;
13 mod tests;
14 mod minecraft_controller;
15
16 #[derive(Template)]
17 #[template(path = "main.html")]
18 struct MainTemplate {
19 text_status: String,
20 memory: String,
21 load_average: String,
22 uptime: String,
23 world_size: String,
24 active_players: String,
25 last_backup: String,
26 }
27
28 const VALUE_UNKNOWN: &str = "-";
29
30 #[cached(size = 1, time = 10)]
31 fn get_minecraft_executable_information_cached(world_path: String, backup_path: String, rcon_password: String) -> Option<minecraft_controller::MinecraftExe> {
32 minecraft_controller::get_minecraft_executable_information(&world_path, &backup_path, &rcon_password)
33 }
34
35 #[get("/")]
36 async fn main_page(config_shared: web::Data<Mutex<Config>>) -> impl Responder {
37 let config = config_shared.lock().unwrap();
38
39 match get_minecraft_executable_information_cached(config.world_path.clone(), config.backup_path.clone(), config.rcon_password.clone()) {
40 Some(info) =>
41 MainTemplate {
42 text_status: String::from("Minecraft server is up and running :)"),
43 memory: info.format_memory(),
44 load_average: info.format_load_average(),
45 uptime: info.format_uptime(),
46 world_size: info.format_world_size(),
47 active_players: info.format_active_players(),
48 last_backup: info.format_last_backup()
49 },
50 None => {
51 let value_unknown = String::from(VALUE_UNKNOWN);
52 MainTemplate {
53 text_status: String::from("Minecraft server is down :("),
54 memory: value_unknown.clone(),
55 load_average: value_unknown.clone(),
56 uptime: value_unknown.clone(),
57 world_size: value_unknown.clone(),
58 active_players: value_unknown.clone(),
59 last_backup: value_unknown.clone() }
60 }
61 }
62 }
63
64 #[derive(Debug, Deserialize, Serialize)]
65 struct Config {
66 port: u16,
67
68 #[serde(default = "empty_string")]
69 rcon_password: String,
70
71 #[serde(default = "empty_string")]
72 world_path: String,
73
74 #[serde(default = "empty_string")]
75 backup_path: String,
76 }
77
78 fn empty_string() -> String { "".to_owned() }
79
80 impl Config {
81 fn default() -> Self {
82 Config { port: 8083, rcon_password: String::from(""), world_path: String::from(""), backup_path: String::from("") }
83 }
84 }
85
86 fn get_exe_name() -> String {
87 let first_arg = std::env::args().next().unwrap();
88 let sep: &[_] = &['\\', '/'];
89 first_arg[first_arg.rfind(sep).unwrap()+1..].to_string()
90 }
91
92 fn load_config() -> Config {
93 // unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF));
94 match File::open(consts::FILE_CONF) {
95 Ok(file) => from_reader(file).unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF)),
96 Err(_) => {
97 let mut file = File::create(consts::FILE_CONF) .unwrap();
98 let default_config = Config::default();
99 file.write_all(to_string_pretty(&default_config, PrettyConfig::new()).unwrap().as_bytes()).unwrap(); // We do not use 'to_writer' because it can't pretty format the output.
100 default_config
101 }
102 }
103 }
104
105 #[actix_web::main]
106 async fn main() -> std::io::Result<()> {
107 let config = load_config();
108 let port = config.port;
109
110 if process_args(&config) { return Ok(()) }
111
112 println!("Starting Minecraft Admin as web server...");
113
114 println!("Configuration: {:?}", config);
115
116 let config_shared = web::Data::new(Mutex::new(config));
117
118 let server =
119 HttpServer::new(
120 move || {
121 App::new()
122 .app_data(config_shared.clone())
123 .wrap(middleware::Compress::default())
124 .wrap(middleware::Logger::default())
125 .service(main_page)
126 .service(fs::Files::new("/static", "static").show_files_listing())
127 }
128 )
129 .bind(&format!("0.0.0.0:{}", port))
130 .unwrap();
131
132 server.run().await
133 }
134
135 fn process_args(config: &Config) -> bool {
136 fn print_usage() {
137 println!("Usage:");
138 println!(" {} [--help] [--status]", get_exe_name());
139 }
140
141 let args: Vec<String> = args().collect();
142
143 if args.iter().any(|arg| arg == "--help") {
144 print_usage();
145 return true
146 } else if args.iter().any(|arg| arg == "--status") {
147 println!("{:?}", minecraft_controller::get_minecraft_executable_information(&config.world_path, &config.backup_path, &config.rcon_password));
148 return true
149 }
150
151 false
152 }