mirror of
https://github.com/YGGverse/hlstate-rs.git
synced 2026-03-31 17:15:37 +00:00
implement state update on background (basics for subscription feature)
This commit is contained in:
parent
e77d101b70
commit
8daa729bf9
8 changed files with 167 additions and 96 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -208,7 +208,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -688,6 +690,7 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
name = "hlstate-httpd"
|
name = "hlstate-httpd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_dyn_templates",
|
"rocket_dyn_templates",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ categories = ["parsing", "text-processing", "value-formatting"]
|
||||||
repository = "https://github.com/YGGverse/hlstate-rs"
|
repository = "https://github.com/YGGverse/hlstate-rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = "0.4.44"
|
||||||
#observer = { git = "https://github.com/YGGverse/xash3d-master.git", package = "xash3d-observer", branch = "ip6-only" }
|
#observer = { git = "https://github.com/YGGverse/xash3d-master.git", package = "xash3d-observer", branch = "ip6-only" }
|
||||||
#protocol = { git = "https://github.com/YGGverse/xash3d-master.git", package = "xash3d-protocol", branch = "ip6-only" }
|
#protocol = { git = "https://github.com/YGGverse/xash3d-master.git", package = "xash3d-protocol", branch = "ip6-only" }
|
||||||
#observer = { path = "../../../xash3d-master/observer", package = "xash3d-observer" }
|
#observer = { path = "../../../xash3d-master/observer", package = "xash3d-observer" }
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,11 @@ debug = true
|
||||||
# Path to `xash3d-query` bin
|
# Path to `xash3d-query` bin
|
||||||
query = "xash3d-query"
|
query = "xash3d-query"
|
||||||
|
|
||||||
|
# Interval to update master scrape, in seconds
|
||||||
|
refresh = 60
|
||||||
|
|
||||||
# Define at least one master to scrape game servers from
|
# Define at least one master to scrape game servers from
|
||||||
masters = ["[202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148]:27010", "[300:dee4:d3c0:953b::2019]:27010"]
|
masters = [
|
||||||
|
"[202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148]:27010",
|
||||||
|
"[300:dee4:d3c0:953b::2019]:27010"
|
||||||
|
]
|
||||||
|
|
@ -14,4 +14,5 @@ pub struct Config {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub query: PathBuf,
|
pub query: PathBuf,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
pub refresh: u64,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,64 +7,82 @@ mod global;
|
||||||
mod meta;
|
mod meta;
|
||||||
mod scrape;
|
mod scrape;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use global::Global;
|
use global::Global;
|
||||||
use meta::Meta;
|
use meta::Meta;
|
||||||
use rocket::{State, http::Status};
|
use rocket::{
|
||||||
|
State,
|
||||||
|
http::Status,
|
||||||
|
tokio::{spawn, sync::RwLock, time::sleep},
|
||||||
|
};
|
||||||
use rocket_dyn_templates::{Template, context};
|
use rocket_dyn_templates::{Template, context};
|
||||||
|
use std::{collections::HashSet, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
struct Online {
|
||||||
|
scrape: scrape::Result,
|
||||||
|
update: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
type Snap = Arc<RwLock<Option<Online>>>;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(meta: &State<Meta>, global: &State<Global>) -> Result<Template, Status> {
|
async fn index(
|
||||||
// @TODO: requires library impl
|
meta: &State<Meta>,
|
||||||
// https://github.com/FWGS/xash3d-master/issues/4
|
global: &State<Global>,
|
||||||
let scrape = std::process::Command::new(&global.query)
|
online: &State<Snap>,
|
||||||
.arg("all")
|
) -> Result<Template, Status> {
|
||||||
.arg("-M")
|
let snap = online.read().await;
|
||||||
.arg(
|
|
||||||
global
|
|
||||||
.masters
|
|
||||||
.iter()
|
|
||||||
.map(|a| a.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(","),
|
|
||||||
)
|
|
||||||
.arg("-j")
|
|
||||||
.output()
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Make sure `xash3d-query` is installed: {e}");
|
|
||||||
Status::InternalServerError
|
|
||||||
})?;
|
|
||||||
if scrape.status.success() {
|
|
||||||
let result: scrape::Result = rocket::serde::json::serde_json::from_str(
|
|
||||||
str::from_utf8(&scrape.stdout).map_err(|e| {
|
|
||||||
error!("stdout parse error: {e}");
|
|
||||||
Status::InternalServerError
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("JSON parse error: {e}");
|
|
||||||
Status::InternalServerError
|
|
||||||
})?;
|
|
||||||
Ok(Template::render(
|
Ok(Template::render(
|
||||||
"index",
|
"index",
|
||||||
context! {
|
context! {
|
||||||
masters: &global.masters,
|
masters: &global.masters,
|
||||||
title: &meta.title,
|
title: &meta.title,
|
||||||
version: &meta.version,
|
version: &meta.version,
|
||||||
servers: result.servers,
|
servers: snap.as_ref().map(|s|s.scrape.servers.clone()),
|
||||||
|
updated: snap.as_ref().map(|s|s.update.to_rfc2822())
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
} else {
|
|
||||||
error!("Make sure `xash3d-query` is installed!");
|
|
||||||
Err(Status::InternalServerError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
async fn rocket() -> _ {
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
let argument = argument::Argument::parse();
|
let argument = argument::Argument::parse();
|
||||||
let config: config::Config =
|
let config: config::Config =
|
||||||
toml::from_str(&std::fs::read_to_string(argument.config).unwrap()).unwrap();
|
toml::from_str(&std::fs::read_to_string(argument.config).unwrap()).unwrap();
|
||||||
|
let online: Snap = Arc::new(RwLock::new(None));
|
||||||
|
spawn({
|
||||||
|
let online = online.clone();
|
||||||
|
let query = config.query.clone();
|
||||||
|
let masters = config.masters.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
match scrape(&query, &masters) {
|
||||||
|
Ok(s) => match str::from_utf8(&s.stdout) {
|
||||||
|
Ok(r) => {
|
||||||
|
if s.status.success() {
|
||||||
|
*online.write().await =
|
||||||
|
match rocket::serde::json::serde_json::from_str(r) {
|
||||||
|
Ok(scrape) => Some(Online {
|
||||||
|
scrape,
|
||||||
|
update: Utc::now(),
|
||||||
|
}),
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not decode scrape response: `{e}`");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Scrape request failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => error!("Could not decode UTF-8: `{e}`"),
|
||||||
|
},
|
||||||
|
Err(e) => error!("Scrape error: `{e}`"),
|
||||||
|
}
|
||||||
|
sleep(Duration::from_secs(config.refresh)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
.configure(rocket::Config {
|
.configure(rocket::Config {
|
||||||
|
|
@ -76,6 +94,7 @@ fn rocket() -> _ {
|
||||||
rocket::Config::release_default()
|
rocket::Config::release_default()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.manage(online)
|
||||||
.manage(Global {
|
.manage(Global {
|
||||||
masters: config.masters,
|
masters: config.masters,
|
||||||
query: config.query,
|
query: config.query,
|
||||||
|
|
@ -86,3 +105,24 @@ fn rocket() -> _ {
|
||||||
})
|
})
|
||||||
.mount("/", routes![index])
|
.mount("/", routes![index])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get servers online using `bin` from given `masters`
|
||||||
|
fn scrape(
|
||||||
|
bin: &PathBuf,
|
||||||
|
masters: &HashSet<SocketAddr>,
|
||||||
|
) -> Result<std::process::Output, std::io::Error> {
|
||||||
|
// @TODO: requires library impl
|
||||||
|
// https://github.com/FWGS/xash3d-master/issues/4
|
||||||
|
std::process::Command::new(bin)
|
||||||
|
.arg("all")
|
||||||
|
.arg("-M")
|
||||||
|
.arg(
|
||||||
|
masters
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(","),
|
||||||
|
)
|
||||||
|
.arg("-j")
|
||||||
|
.output()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct Result {
|
||||||
pub servers: Vec<Info>,
|
pub servers: Vec<Info>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct Info {
|
pub struct Info {
|
||||||
pub time: i64,
|
pub time: i64,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Game</h2>
|
<h2>Game</h2>
|
||||||
{% if servers %}
|
{% if servers %}
|
||||||
|
<form name="subscribe" method="get" action="/">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
{# @TODO subscribe<th></th>#}
|
||||||
<th>Address</th>
|
<th>Address</th>
|
||||||
<th>Host</th>
|
<th>Host</th>
|
||||||
<th>Ping</th>
|
<th>Ping</th>
|
||||||
|
|
@ -24,6 +26,7 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
{% for server in servers %}
|
{% for server in servers %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{# @TODO subscribe<td><input type="checkbox" name="server[]" value="{{ server.address }}" /></td>#}
|
||||||
<td>{{ server.address }}</td>
|
<td>{{ server.address }}</td>
|
||||||
<td>{{ server.host }}</td>
|
<td>{{ server.host }}</td>
|
||||||
<td>{{ server.ping }}</td>
|
<td>{{ server.ping }}</td>
|
||||||
|
|
@ -42,6 +45,10 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{# @TODO subscribe
|
||||||
|
<input type="number" name="online" value="1" />
|
||||||
|
<input type="submit" value="Subscribe" />#}
|
||||||
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div>
|
<div>
|
||||||
<p>Nobody.</p>
|
<p>Nobody.</p>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
--container-max-width: 1024px;
|
--container-max-width: 1128px;
|
||||||
--color-success: #4bc432;
|
--color-success: #4bc432;
|
||||||
--color-warning: #f37b21;
|
--color-warning: #f37b21;
|
||||||
--color-error: #ff6363;
|
--color-error: #ff6363;
|
||||||
|
|
@ -75,6 +75,19 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > div:first-child {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
header > div:last-child {
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 16px auto;
|
margin: 16px auto;
|
||||||
|
|
@ -101,10 +114,7 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1></h1>
|
<h1></h1>
|
||||||
<div class="float-left">
|
<div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="float-right">
|
|
||||||
<strong>
|
<strong>
|
||||||
<a href="/">{{ title }}</a> |
|
<a href="/">{{ title }}</a> |
|
||||||
<a href="https://store.steampowered.com/app/70/HalfLife/" target="_blank">Game</a> |
|
<a href="https://store.steampowered.com/app/70/HalfLife/" target="_blank">Game</a> |
|
||||||
|
|
@ -113,18 +123,21 @@
|
||||||
<a href="https://github.com/YGGverse/hlstate-rs" target="_blank" title="v{{ version }}">GitHub</a>
|
<a href="https://github.com/YGGverse/hlstate-rs" target="_blank" title="v{{ version }}">GitHub</a>
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{% if updated %}{{ updated }}{% endif %}
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
{% block content %}{% endblock content %}
|
{% block content %}{% endblock content %}
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
<footer>
|
||||||
<h2>Master</h2>
|
<h2>Masters</h2>
|
||||||
{% if masters %}
|
{% if masters %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for master in masters %}
|
{% for master in masters %}
|
||||||
<li{# class="online" #}>{{ master }}</li>
|
<li{# class="online" #}>{{ master }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue