Use a cached function
[valheim_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 valheim_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 nb_of_players: u32,
25 }
26
27 const VALUE_UNKNOWN: &str = "-";
28
29 #[cached(size = 1, time = 10)]
30 fn get_valheim_executable_information_cached(world_path: String) -> Option<valheim_controller::ValheimExe> {
31 valheim_controller::get_valheim_executable_information(&world_path)
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_valheim_executable_information_cached(config.world_path.clone()) {
39 Some(info) =>
40 MainTemplate {
41 text_status: String::from("Valheim server is up and running :)"),
42 memory: info.format_memory(),
43 load_average: info.format_load_average(),
44 uptime: info.format_uptime(),
45 world_size: info.format_world_size(),
46 nb_of_players: info.get_nb_of_player()
47 },
48 None => {
49 let value_unknown = String::from(VALUE_UNKNOWN);
50 MainTemplate { text_status: String::from("Valheim server is down :("), memory: value_unknown.clone(), load_average: value_unknown.clone(), uptime: value_unknown.clone(), world_size: value_unknown.clone(), nb_of_players: 0 }
51 }
52 }
53 }
54
55 #[derive(Debug, Deserialize, Serialize)]
56 struct Config {
57 port: u16,
58 #[serde(default = "empty_string")]
59 world_path: String,
60 }
61
62 fn empty_string() -> String { "".to_owned() }
63
64 impl Config {
65 fn default() -> Self {
66 Config { port: 8082, world_path: String::from("") }
67 }
68 }
69
70 fn get_exe_name() -> String {
71 let first_arg = std::env::args().next().unwrap();
72 let sep: &[_] = &['\\', '/'];
73 first_arg[first_arg.rfind(sep).unwrap()+1..].to_string()
74 }
75
76 fn load_config() -> Config {
77 // unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF));
78 match File::open(consts::FILE_CONF) {
79 Ok(file) => from_reader(file).unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF)),
80 Err(_) => {
81 let mut file = File::create(consts::FILE_CONF) .unwrap();
82 let default_config = Config::default();
83 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.
84 default_config
85 }
86 }
87 }
88
89 #[actix_rt::main]
90 async fn main() -> std::io::Result<()> {
91 let config = load_config();
92 let port = config.port;
93
94 if process_args(&config) { return Ok(()) }
95
96 println!("Starting Valheim Admin as web server...");
97
98 println!("Configuration: {:?}", config);
99
100 let config_shared = web::Data::new(Mutex::new(config));
101
102 let server =
103 HttpServer::new(
104 move || {
105 App::new()
106 .app_data(config_shared.clone())
107 .wrap(middleware::Compress::default())
108 .wrap(middleware::Logger::default())
109 .service(main_page)
110 .service(fs::Files::new("/static", "static").show_files_listing())
111 }
112 )
113 .bind(&format!("0.0.0.0:{}", port))
114 .unwrap();
115
116 server.run().await
117 }
118
119 fn process_args(config: &Config) -> bool {
120 fn print_usage() {
121 println!("Usage:");
122 println!(" {} [--help] [--status]", get_exe_name());
123 }
124
125 let args: Vec<String> = args().collect();
126
127 if args.iter().any(|arg| arg == "--help") {
128 print_usage();
129 return true
130 } else if args.iter().any(|arg| arg == "--status") {
131 println!("{:?}", valheim_controller::get_valheim_executable_information(&config.world_path));
132 return true
133 }
134
135 false
136 }