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