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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Mark
						Mark