init
This commit is contained in:
commit
606e7d03d0
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1615
Cargo.lock
generated
Normal file
1615
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "tomatenmharksite"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
html-escape = "0.2.13"
|
||||||
|
rocket = "0.5.1"
|
||||||
|
tokio = { version = "1.42.0", features = ["fs", "process"] }
|
55
src/data.rs
Normal file
55
src/data.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use tokio::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
|
use crate::status::Status;
|
||||||
|
|
||||||
|
pub struct Data {
|
||||||
|
pub status: Mutex<Status>,
|
||||||
|
pub status_updated: Mutex<(Instant, bool)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
pub fn new_sync() -> Self {
|
||||||
|
Self {
|
||||||
|
status: Mutex::new(Status::query_sync(false)),
|
||||||
|
status_updated: Mutex::new((Instant::now(), false)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn new_async() -> Self {
|
||||||
|
Self {
|
||||||
|
status: Mutex::new(Status::query_async(false).await),
|
||||||
|
status_updated: Mutex::new((Instant::now(), false)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn status_sync(&self, dbg: bool) -> MutexGuard<Status> {
|
||||||
|
let mut updated = self.status_updated.blocking_lock();
|
||||||
|
let now = Instant::now();
|
||||||
|
if (now - updated.0).as_secs_f32() > 3.0 || (dbg && !updated.1) {
|
||||||
|
updated.0 = now;
|
||||||
|
updated.1 = dbg;
|
||||||
|
drop(updated);
|
||||||
|
let mut lock = self.status.blocking_lock();
|
||||||
|
*lock = Status::query_sync(dbg);
|
||||||
|
lock
|
||||||
|
} else {
|
||||||
|
drop(updated);
|
||||||
|
self.status.blocking_lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn status_async(&self, dbg: bool) -> MutexGuard<Status> {
|
||||||
|
let mut updated = self.status_updated.lock().await;
|
||||||
|
let now = Instant::now();
|
||||||
|
if (now - updated.0).as_secs_f32() > 3.0 || (dbg && !updated.1) {
|
||||||
|
updated.0 = now;
|
||||||
|
updated.1 = dbg;
|
||||||
|
drop(updated);
|
||||||
|
let mut lock = self.status.lock().await;
|
||||||
|
*lock = Status::query_async(dbg).await;
|
||||||
|
lock
|
||||||
|
} else {
|
||||||
|
drop(updated);
|
||||||
|
self.status.lock().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/int.html
Normal file
44
src/int.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>tomatenmharksite documentation</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>tomatenmharksite documentation</h2>
|
||||||
|
<div>documentation of how the tomatenmharksite server works for people who care or people who keep forgetting (me)</div>
|
||||||
|
<div>go to <a href="/">index without</a> or <a href="/dbg">with debugging information</a></div>
|
||||||
|
|
||||||
|
<h4>links</h4>
|
||||||
|
In the "running" section on the website, the ids may or may not be links.
|
||||||
|
If a <code>/srv/tomatenmhark-slashinfo/*</code> entry exists, the link will point to that info site,
|
||||||
|
If only a <code>/srv/tomatenmhark-redirect/*</code> entry exists, the link will follow that redirect,
|
||||||
|
and if neither exist, there will be no link.<br>
|
||||||
|
In the "everything else" section, every entry links to its info page.
|
||||||
|
|
||||||
|
<h4>/tmp/tomatenmhark-status-*</h4>
|
||||||
|
For each file <code>/tmp/tomatenmhark-status-*</code>, an entry will be created on <a href="/">tomatenmhark.org</a>.
|
||||||
|
This entry will have the id <code>*</code> and the file's contents will be shown after that.
|
||||||
|
|
||||||
|
<h4>/srv/tomatenmhark-dystatus/*</h4>
|
||||||
|
Each file in <code>/srv/tomatenmhark-dystatus/</code> acts like a <code>/tmp/tomatenmhark-status-*</code> file,
|
||||||
|
but instead of displaying its contents, the tomatenmharksite server will execute the file and display its output.
|
||||||
|
|
||||||
|
<h4>/srv/tomatenmhark-slashinfo/*</h4>
|
||||||
|
The files in <code>/srv/tomatenmhark-slashinfo/</code> are served under <a href="/info">/info</a>, and <code>index.html</code> will be served
|
||||||
|
when a directory is requested (<code>/</code> → <code>/index.html</code>, <code>/thing</code> → <code>/thing/index.html</code>).<br>
|
||||||
|
For all directories in <code>/srv/tomatenmhark-slashinfo/</code> which contain a <code>desc</code>
|
||||||
|
and are not already listed as "running", an entry is created in the "everything else" section.
|
||||||
|
|
||||||
|
<h4>/srv/tomatenmhark-redirect/*</h4>
|
||||||
|
Each file in <code>/srv/tomatenmhark-redirect/</code> contains a port number or a domain, leading/trailing whitespace characters are ignored.<br>
|
||||||
|
If the file contains a port number, <code>/filename/...</code> will be redirected to <code>samedomain:portnumber/...</code>.<br>
|
||||||
|
If the file contains anything else, <code>/filename/...</code> will be redirected to <code>filecontent/...</code>.<br>
|
||||||
|
In the port number case, <code>http</code> is used. Otherwise, the protocol must be included in the file.
|
||||||
|
<code>%DOMAIN%</code> will be replaced with <code>tomatenmhark.org</code> or whatever domain was used in the request.
|
||||||
|
To redirect to port <code>8000</code> using <code>https</code>, use <code>https://%DOMAIN%:8000</code>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
172
src/main.rs
Normal file
172
src/main.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
mod data;
|
||||||
|
mod redirecter;
|
||||||
|
mod status;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use data::Data;
|
||||||
|
use redirecter::Redirecter;
|
||||||
|
use rocket::{
|
||||||
|
fs::{FileServer, Options},
|
||||||
|
get,
|
||||||
|
response::content::RawHtml,
|
||||||
|
routes, State,
|
||||||
|
};
|
||||||
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index(data: &State<Data>) -> RawHtml<String> {
|
||||||
|
index_gen(data, false).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/dbg")]
|
||||||
|
async fn index_dbg(data: &State<Data>) -> RawHtml<String> {
|
||||||
|
index_gen(data, true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index_gen(data: &State<Data>, dbg: bool) -> RawHtml<String> {
|
||||||
|
let mut o = "<!DOCTYPE html><html><head>".to_owned();
|
||||||
|
o.push_str(r#"<meta charset="UTF-8">"#);
|
||||||
|
o.push_str(r#"<meta name="color-scheme" content="light dark">"#);
|
||||||
|
o.push_str(r#"<meta name="viewport" content="width=device-width, initial-scale=1">"#);
|
||||||
|
o.push_str("<title>tomatenmhark</title>");
|
||||||
|
o.push_str(
|
||||||
|
"<style>
|
||||||
|
a:link {
|
||||||
|
color: DarkCyan;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: DarkCyan;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
a:visited {
|
||||||
|
color: DarkCyan;
|
||||||
|
text-decoration: underline;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: SeaGreen;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.ohs {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.oht:hover + .ohs, .ohs:hover {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
",
|
||||||
|
);
|
||||||
|
o.push_str("</head><body>");
|
||||||
|
o.push_str("<h2>:) tomatenmhark :)</h2>");
|
||||||
|
let status = data.status_async(dbg).await;
|
||||||
|
let time_queried = Instant::now();
|
||||||
|
if !status.0.is_empty() {
|
||||||
|
o.push_str("<h3>things running on the server</h3>");
|
||||||
|
o.push_str("<ul>");
|
||||||
|
for (id, (info, redirect, status, additional, dur)) in status.0.iter() {
|
||||||
|
o.push_str("<li>");
|
||||||
|
if *info {
|
||||||
|
o.push_str(r#"<a href="/info/"#);
|
||||||
|
o.push_str(&html_escape::encode_double_quoted_attribute(&id));
|
||||||
|
o.push_str(r#"">"#);
|
||||||
|
} else if *redirect {
|
||||||
|
o.push_str(r#"<a href="/"#);
|
||||||
|
o.push_str(&html_escape::encode_double_quoted_attribute(&id));
|
||||||
|
o.push_str(r#"">"#);
|
||||||
|
} else {
|
||||||
|
o.push_str("<span>");
|
||||||
|
}
|
||||||
|
o.push_str(&html_escape::encode_text(&id));
|
||||||
|
if *info || *redirect {
|
||||||
|
o.push_str("</a>");
|
||||||
|
} else {
|
||||||
|
o.push_str("</span>");
|
||||||
|
}
|
||||||
|
o.push_str(": ");
|
||||||
|
if additional.is_some() {
|
||||||
|
o.push_str(r#"<em class="oht">"#);
|
||||||
|
}
|
||||||
|
o.push_str(&html_escape::encode_text(&status));
|
||||||
|
if let Some(additional) = additional {
|
||||||
|
o.push_str(r#"</em><div class="ohs">"#);
|
||||||
|
o.push_str(
|
||||||
|
&html_escape::encode_text(additional)
|
||||||
|
.replace('\r', "")
|
||||||
|
.replace('\n', "<br>"),
|
||||||
|
);
|
||||||
|
o.push_str("</div>");
|
||||||
|
}
|
||||||
|
if dbg {
|
||||||
|
if let Some(dur) = *dur {
|
||||||
|
o.push_str(&format!(" | {}ms", dur.as_millis()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.push_str("</li>");
|
||||||
|
}
|
||||||
|
o.push_str("</ul>");
|
||||||
|
}
|
||||||
|
if !status.1.is_empty() {
|
||||||
|
o.push_str("<h3>everything else</h3>");
|
||||||
|
o.push_str("<ul>");
|
||||||
|
for (id, text) in status.1.iter() {
|
||||||
|
o.push_str("<li>");
|
||||||
|
o.push_str(r#"<a href="/info/"#);
|
||||||
|
o.push_str(&html_escape::encode_double_quoted_attribute(&id));
|
||||||
|
o.push_str(r#"">"#);
|
||||||
|
o.push_str(&html_escape::encode_text(&id));
|
||||||
|
o.push_str("</a>");
|
||||||
|
o.push_str(": ");
|
||||||
|
o.push_str(&html_escape::encode_text(&text));
|
||||||
|
o.push_str("</li>");
|
||||||
|
}
|
||||||
|
o.push_str("</ul>");
|
||||||
|
}
|
||||||
|
o.push_str(
|
||||||
|
r#"<br><small><small><a href="/int">tomatenmharksite documentation</a></small></small>"#,
|
||||||
|
);
|
||||||
|
o.push_str(r#"<br><br><br><br><br><hl><br><footer><p>tomatenmhark.org und subdomains: <a href="/info/me">Mark</a>"#);
|
||||||
|
if dbg {
|
||||||
|
o.push_str(r#"<br><small><small>"#);
|
||||||
|
let time_pagegen = Instant::now();
|
||||||
|
o.push_str(&format!(
|
||||||
|
"{}/{}ms querying + {:.2}ms pagegen",
|
||||||
|
status
|
||||||
|
.0
|
||||||
|
.values()
|
||||||
|
.filter_map(|v| v.4)
|
||||||
|
.sum::<Duration>()
|
||||||
|
.as_millis(),
|
||||||
|
status.2.as_millis(),
|
||||||
|
(time_pagegen - time_queried).as_micros() as f32 / 1000.0,
|
||||||
|
));
|
||||||
|
o.push_str(r#"</small></small>"#);
|
||||||
|
}
|
||||||
|
o.push_str(r#"</p></footer>"#);
|
||||||
|
o.push_str("</body></html>");
|
||||||
|
RawHtml(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/int")]
|
||||||
|
async fn int() -> RawHtml<&'static str> {
|
||||||
|
RawHtml(include_str!("int.html"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
let data = Data::new_sync();
|
||||||
|
rocket::build()
|
||||||
|
.manage(data)
|
||||||
|
.mount("/", routes![index, index_dbg, int])
|
||||||
|
.mount(
|
||||||
|
"/info",
|
||||||
|
FileServer::new(
|
||||||
|
"/srv/tomatenmhark-slashinfo/",
|
||||||
|
Options::Index | Options::NormalizeDirs | Options::Missing,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.mount("/", Redirecter)
|
||||||
|
}
|
97
src/redirecter.rs
Normal file
97
src/redirecter.rs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
use rocket::{
|
||||||
|
http::{Method, Status},
|
||||||
|
outcome::Outcome,
|
||||||
|
response::{Redirect, Responder},
|
||||||
|
route::Handler,
|
||||||
|
Data, Request, Response, Route,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Redirecter;
|
||||||
|
|
||||||
|
impl From<Redirecter> for Vec<Route> {
|
||||||
|
fn from(redirecter: Redirecter) -> Self {
|
||||||
|
let mut out = vec![];
|
||||||
|
for method in [
|
||||||
|
Method::Get,
|
||||||
|
Method::Put,
|
||||||
|
Method::Post,
|
||||||
|
Method::Delete,
|
||||||
|
Method::Options,
|
||||||
|
Method::Head,
|
||||||
|
Method::Trace,
|
||||||
|
Method::Connect,
|
||||||
|
Method::Patch,
|
||||||
|
] {
|
||||||
|
let mut route = Route::ranked(50, method, "/<path..>", redirecter);
|
||||||
|
route.name = Some("TomatenmharksiteRedirecter".into());
|
||||||
|
out.push(route);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl Handler for Redirecter {
|
||||||
|
// fn respond_to(self, request: &'r Request<'_>) -> Result<'o> {
|
||||||
|
async fn handle<'r>(
|
||||||
|
&self,
|
||||||
|
request: &'r Request<'_>,
|
||||||
|
_data: Data<'r>,
|
||||||
|
) -> Outcome<Response<'r>, Status, (Data<'r>, Status)> {
|
||||||
|
let uri = request.uri();
|
||||||
|
let mut path = uri.path().raw_segments();
|
||||||
|
if let Some(which) = path.next().and_then(|v| v.percent_decode().ok()) {
|
||||||
|
if which
|
||||||
|
.chars()
|
||||||
|
.all(|ch| ch.is_alphanumeric() || ch == '_' || ch == '-')
|
||||||
|
{
|
||||||
|
let path = path.map(|v| format!("/{v}")).collect::<String>();
|
||||||
|
if let Some(port_or_domain) =
|
||||||
|
tokio::fs::read_to_string(format!("/srv/tomatenmhark-redirect/{}", which))
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.map(|c| c.trim().parse::<u16>().map_err(|_| c))
|
||||||
|
{
|
||||||
|
let redirect_target = match port_or_domain {
|
||||||
|
Ok(port) => {
|
||||||
|
let domain = request
|
||||||
|
.host()
|
||||||
|
.map(|host| host.domain().as_str())
|
||||||
|
.unwrap_or("tomatenmhark.org");
|
||||||
|
format!("http://{domain}:{port}{path}")
|
||||||
|
}
|
||||||
|
Err(domain) => {
|
||||||
|
let domain = domain.trim();
|
||||||
|
if !domain.contains("%DOMAIN%") {
|
||||||
|
format!("{domain}{path}")
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{}/{path}",
|
||||||
|
domain.replace(
|
||||||
|
"%DOMAIN%",
|
||||||
|
request
|
||||||
|
.host()
|
||||||
|
.map(|host| host.domain().as_str())
|
||||||
|
.unwrap_or("tomatenmhark.org")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Ok(redirect) = Redirect::temporary(redirect_target).respond_to(request) {
|
||||||
|
Outcome::Success(redirect)
|
||||||
|
} else {
|
||||||
|
Outcome::error(Status::InternalServerError)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Outcome::error(Status::NotFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Outcome::error(Status::NotFound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Outcome::error(Status::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
221
src/status.rs
Normal file
221
src/status.rs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
use std::{collections::BTreeMap, os::unix::fs::PermissionsExt, path::Path, time::Duration};
|
||||||
|
|
||||||
|
use tokio::time::Instant;
|
||||||
|
|
||||||
|
const SLASHINFO: &'static str = "/srv/tomatenmhark-slashinfo/";
|
||||||
|
const REDIRECT: &'static str = "/srv/tomatenmhark-redirect/";
|
||||||
|
|
||||||
|
pub struct Status(
|
||||||
|
pub BTreeMap<String, (bool, bool, String, Option<String>, Option<Duration>)>,
|
||||||
|
pub BTreeMap<String, String>,
|
||||||
|
pub Duration,
|
||||||
|
);
|
||||||
|
impl Status {
|
||||||
|
pub fn query_sync(dbg: bool) -> Self {
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
query_status_sync(
|
||||||
|
|k, e, r, v, dur| {
|
||||||
|
let (v, add) = v
|
||||||
|
.split_once('\n')
|
||||||
|
.map(|(v, add)| (v, add.trim()))
|
||||||
|
.unwrap_or((v, ""));
|
||||||
|
map.insert(
|
||||||
|
k.to_owned(),
|
||||||
|
(
|
||||||
|
e,
|
||||||
|
r,
|
||||||
|
v.trim().to_owned(),
|
||||||
|
if add.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(add.to_owned())
|
||||||
|
},
|
||||||
|
dur,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dbg,
|
||||||
|
);
|
||||||
|
let mut rest = BTreeMap::new();
|
||||||
|
if let Ok(rd) = std::fs::read_dir(SLASHINFO) {
|
||||||
|
for f in rd {
|
||||||
|
if let Ok(f) = f {
|
||||||
|
if let Some(id) = f.file_name().to_str() {
|
||||||
|
if !map.contains_key(id) {
|
||||||
|
let mut p = f.path();
|
||||||
|
p.push("desc");
|
||||||
|
if let Ok(desc) = std::fs::read_to_string(&p) {
|
||||||
|
rest.insert(id.to_owned(), desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self(map, rest, start.elapsed())
|
||||||
|
}
|
||||||
|
pub async fn query_async(dbg: bool) -> Self {
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
query_status_async(
|
||||||
|
|k, e, r, v, dur| {
|
||||||
|
let (v, add) = v
|
||||||
|
.split_once('\n')
|
||||||
|
.map(|(v, add)| (v, add.trim()))
|
||||||
|
.unwrap_or((v, ""));
|
||||||
|
map.insert(
|
||||||
|
k.to_owned(),
|
||||||
|
(
|
||||||
|
e,
|
||||||
|
r,
|
||||||
|
v.trim().to_owned(),
|
||||||
|
if add.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(add.to_owned())
|
||||||
|
},
|
||||||
|
dur,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dbg,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let mut rest = BTreeMap::new();
|
||||||
|
if let Ok(mut rd) = tokio::fs::read_dir(SLASHINFO).await {
|
||||||
|
while let Ok(Some(f)) = rd.next_entry().await {
|
||||||
|
if let Some(id) = f.file_name().to_str() {
|
||||||
|
if !map.contains_key(id) {
|
||||||
|
let mut p = f.path();
|
||||||
|
p.push("desc");
|
||||||
|
if let Ok(desc) = tokio::fs::read_to_string(&p).await {
|
||||||
|
rest.insert(id.to_owned(), desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self(map, rest, start.elapsed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_status_sync(mut func: impl FnMut(&str, bool, bool, &str, Option<Duration>), dbg: bool) {
|
||||||
|
if let Ok(rd) = std::fs::read_dir("/tmp/") {
|
||||||
|
for f in rd {
|
||||||
|
if let Ok(f) = f {
|
||||||
|
if let Some(name) = f.file_name().to_str() {
|
||||||
|
if name.starts_with("tomatenmhark-status-") {
|
||||||
|
let id = name["tomatenmhark-status-".len()..].trim();
|
||||||
|
if !id.is_empty() {
|
||||||
|
if let Ok(status) = std::fs::read_to_string(f.path())
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.trim_end())
|
||||||
|
{
|
||||||
|
if !status.is_empty() {
|
||||||
|
let info = Path::new(SLASHINFO).join(id);
|
||||||
|
let info = info.starts_with(SLASHINFO)
|
||||||
|
&& info.try_exists().ok() == Some(true);
|
||||||
|
let redirect = Path::new(REDIRECT).join(id);
|
||||||
|
let redirect = redirect.starts_with(REDIRECT)
|
||||||
|
&& redirect.try_exists().ok() == Some(true);
|
||||||
|
func(id, info, redirect, status, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(rd) = std::fs::read_dir("/srv/tomatenmhark-dystatus/") {
|
||||||
|
for f in rd {
|
||||||
|
if let Ok(f) = f {
|
||||||
|
if let Some(name) = f.file_name().to_str() {
|
||||||
|
if f.metadata()
|
||||||
|
.is_ok_and(|meta| meta.is_file() && meta.permissions().mode() & 1 == 1)
|
||||||
|
{
|
||||||
|
let id = name.trim();
|
||||||
|
if !id.is_empty() {
|
||||||
|
let start = dbg.then(Instant::now);
|
||||||
|
if let Ok(output) = std::process::Command::new(f.path()).output() {
|
||||||
|
let elapsed = start.map(|v| v.elapsed());
|
||||||
|
let out = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let out = out.trim_end();
|
||||||
|
if dbg || !out.is_empty() {
|
||||||
|
let info = Path::new(SLASHINFO).join(id);
|
||||||
|
let info = info.starts_with(SLASHINFO)
|
||||||
|
&& info.try_exists().ok() == Some(true);
|
||||||
|
let redirect = Path::new(REDIRECT).join(id);
|
||||||
|
let redirect = redirect.starts_with(REDIRECT)
|
||||||
|
&& redirect.try_exists().ok() == Some(true);
|
||||||
|
func(id, info, redirect, out, elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn query_status_async(
|
||||||
|
mut func: impl FnMut(&str, bool, bool, &str, Option<Duration>),
|
||||||
|
dbg: bool,
|
||||||
|
) {
|
||||||
|
if let Ok(mut rd) = tokio::fs::read_dir("/tmp/").await {
|
||||||
|
while let Ok(Some(f)) = rd.next_entry().await {
|
||||||
|
if let Some(name) = f.file_name().to_str() {
|
||||||
|
if name.starts_with("tomatenmhark-status-") {
|
||||||
|
let id = name["tomatenmhark-status-".len()..].trim();
|
||||||
|
if !id.is_empty() {
|
||||||
|
if let Ok(status) = tokio::fs::read_to_string(f.path())
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.trim_end())
|
||||||
|
{
|
||||||
|
if !status.is_empty() {
|
||||||
|
let info = Path::new(SLASHINFO).join(id);
|
||||||
|
let info = info.starts_with(SLASHINFO)
|
||||||
|
&& tokio::fs::try_exists(info).await.ok() == Some(true);
|
||||||
|
let redirect = Path::new(REDIRECT).join(id);
|
||||||
|
let redirect = redirect.starts_with(REDIRECT)
|
||||||
|
&& tokio::fs::try_exists(redirect).await.ok() == Some(true);
|
||||||
|
func(id, info, redirect, status, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(mut rd) = tokio::fs::read_dir("/srv/tomatenmhark-dystatus/").await {
|
||||||
|
while let Ok(Some(f)) = rd.next_entry().await {
|
||||||
|
if let Some(name) = f.file_name().to_str() {
|
||||||
|
if f.metadata()
|
||||||
|
.await
|
||||||
|
.is_ok_and(|meta| meta.is_file() && meta.permissions().mode() & 1 == 1)
|
||||||
|
{
|
||||||
|
let id = name.trim();
|
||||||
|
if !id.is_empty() {
|
||||||
|
let start = dbg.then(Instant::now);
|
||||||
|
if let Ok(output) = tokio::process::Command::new(f.path()).output().await {
|
||||||
|
let elapsed = start.map(|v| v.elapsed());
|
||||||
|
let out = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let out = out.trim_end();
|
||||||
|
if dbg || !out.is_empty() {
|
||||||
|
let info = Path::new(SLASHINFO).join(id);
|
||||||
|
let info = info.starts_with(SLASHINFO)
|
||||||
|
&& tokio::fs::try_exists(info).await.ok() == Some(true);
|
||||||
|
let redirect = Path::new(REDIRECT).join(id);
|
||||||
|
let redirect = redirect.starts_with(REDIRECT)
|
||||||
|
&& tokio::fs::try_exists(redirect).await.ok() == Some(true);
|
||||||
|
func(id, info, redirect, out, elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user