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