Added simple multithreading

This commit is contained in:
p11 2023-07-21 20:29:46 +02:00
parent fdfeae97b2
commit 1b039b0494

View File

@ -5,6 +5,8 @@ use std::{
io::{prelude::*, BufReader, Error, ErrorKind, Result}, io::{prelude::*, BufReader, Error, ErrorKind, Result},
net::{TcpListener, TcpStream}, net::{TcpListener, TcpStream},
path::Path, path::Path,
sync::{Arc, Mutex},
thread,
}; };
use data_stream::{ use data_stream::{
@ -43,12 +45,13 @@ fn fail(mut stream: TcpStream) {
let _ = writeln!(stream, "Page not found!"); let _ = writeln!(stream, "Page not found!");
} }
#[derive(Clone)]
struct Comment { struct Comment {
name: Box<str>, name: Box<str>,
text: Box<str>, text: Box<str>,
} }
#[derive(Default)] #[derive(Default, Clone)]
struct SiteInfo { struct SiteInfo {
comments: Vec<Comment>, comments: Vec<Comment>,
visits: usize, visits: usize,
@ -111,7 +114,7 @@ impl<S: SizeSettings> FromStream<S> for SiteInfo {
#[derive(Default)] #[derive(Default)]
struct Context { struct Context {
infos: HashMap<Box<str>, SiteInfo>, infos: HashMap<Box<str>, Arc<Mutex<SiteInfo>>>,
} }
impl Context { impl Context {
@ -192,205 +195,223 @@ impl Context {
use Entry::*; use Entry::*;
let info = match self.infos.entry(relative_path.clone().into_boxed_str()) { let info = match self.infos.entry(relative_path.clone().into_boxed_str()) {
Occupied(o) => o.into_mut(), Occupied(o) => o.get().clone(),
Vacant(v) => v.insert( Vacant(v) => v
File::open(&data_path) .insert(Arc::new(Mutex::new(
.map(|mut file| { File::open(&data_path)
from_stream::<PortableSettings, _, _>(&mut file).unwrap_or_default() .map(|mut file| {
}) from_stream::<PortableSettings, _, _>(&mut file).unwrap_or_default()
.unwrap_or_default(), })
), .unwrap_or_default(),
)))
.clone(),
}; };
info.handle_connection( thread::spawn(move || {
stream, handle_relative_connection(
&request.body, info,
&relative_path, stream,
&path, &request.body,
&pk_path, &relative_path,
pki_path.as_ref().map(|path| path.as_ref()), &path,
&data_path, &pk_path,
start_level, pki_path.as_ref().map(|path| path.as_ref()),
) &data_path,
start_level,
)
});
} }
} }
impl SiteInfo { fn handle_relative_connection(
fn handle_connection( info: Arc<Mutex<SiteInfo>>,
&mut self, mut stream: TcpStream,
mut stream: TcpStream, body: &str,
body: &str, relative_path: &str,
relative_path: &str, path: &Path,
path: &Path, pk_path: &Path,
pk_path: &Path, pki_path: Option<&Path>,
pki_path: Option<&Path>, data_path: &Path,
data_path: &Path, start_level: usize,
start_level: usize, ) {
) { let mut name = None;
let mut name = None; let mut text = None;
let mut text = None;
for entry in body.split('&') { let mut up = false;
let Some((key, input)) = entry.split_once('=') else { let mut down = false;
continue;
};
match key { for entry in body.split('&') {
"name" => { let Some((key, input)) = entry.split_once('=') else {
let name_value = input.replace('+', " "); continue;
let decoded_name = percent_decode_str(&name_value).decode_utf8_lossy();
eprintln!("Received comment by \"{decoded_name}\":");
name = Some(
decoded_name
.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.into(),
);
}
"text" => {
let text_value = input.replace('+', " ");
let decoded_text = percent_decode_str(&text_value).decode_utf8_lossy();
for line in decoded_text.lines() {
eprintln!(" {line}");
}
eprintln!();
let mut text_buf = Vec::new();
convert(decoded_text.lines(), &mut text_buf);
text = Some(
std::str::from_utf8(text_buf.as_slice())
.unwrap_or_default()
.into(),
);
}
"up" => self.up += 1,
"down" => self.down += 1,
_ => (),
}
}
if let (Some(name), Some(text)) = (name, text) {
self.comments.push(Comment { name, text });
}
self.visits += 1;
if let Ok(mut file) = File::create(data_path) {
if to_stream::<PortableSettings, _, _>(self, &mut file).is_err() {
eprintln!("Error saving data!");
eprintln!();
}
}
let _ = write!(stream, "HTTP/1.1 200 OK\r\n");
let _ = write!(stream, "Content-Type: text/html; charset=\"utf-8\"\r\n");
let _ = write!(stream, "\r\n");
let _ = writeln!(
stream,
"<p>👁️{} 💖️{} 💔️{}</p>",
self.visits, self.up, self.down
);
let title = relative_path
.rsplit_once('/')
.map(|(_, title)| title)
.unwrap_or(relative_path);
if !title.is_empty() {
let _ = writeln!(stream, "<h1>{title}</h1>");
}
let handle_entry = |mut entry: &str, output: &mut TcpStream, level: usize| {
let level = level + 1;
let mut path = path.to_path_buf();
if let Some((real_entry, _)) = entry.split_once(':') {
entry = real_entry
}
path.push(if relative_path.is_empty() {
format!("{entry}.pki")
} else {
format!("{relative_path}/{entry}.pki")
});
let Ok(file) = File::open(path) else {
return;
};
let _ = writeln!(
output,
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
);
convert_subheader(
BufReader::new(file)
.lines()
.map(|line| line.unwrap_or_default()),
output,
level,
);
}; };
if let Ok(pk_file) = File::open(pk_path) { match key {
convert_extended( "name" => {
BufReader::new(pk_file) let name_value = input.replace('+', " ");
let decoded_name = percent_decode_str(&name_value).decode_utf8_lossy();
eprintln!("Received comment by \"{decoded_name}\":");
name = Some(
decoded_name
.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.into(),
);
}
"text" => {
let text_value = input.replace('+', " ");
let decoded_text = percent_decode_str(&text_value).decode_utf8_lossy();
for line in decoded_text.lines() {
eprintln!(" {line}");
}
eprintln!();
let mut text_buf = Vec::new();
convert(decoded_text.lines(), &mut text_buf);
text = Some(
std::str::from_utf8(text_buf.as_slice())
.unwrap_or_default()
.into(),
);
}
"up" => up = true,
"down" => down = true,
_ => (),
}
}
let info = if let Ok(mut info) = info.lock() {
if let (Some(name), Some(text)) = (name, text) {
info.comments.push(Comment { name, text });
}
if up {
info.up += 1;
}
if down {
info.down += 1;
}
info.visits += 1;
info.clone()
} else {
return;
};
if let Ok(mut file) = File::create(data_path) {
if to_stream::<PortableSettings, _, _>(&info, &mut file).is_err() {
eprintln!("Error saving data!");
eprintln!();
}
}
let _ = write!(stream, "HTTP/1.1 200 OK\r\n");
let _ = write!(stream, "Content-Type: text/html; charset=\"utf-8\"\r\n");
let _ = write!(stream, "\r\n");
let _ = writeln!(
stream,
"<p>👁️{} 💖️{} 💔️{}</p>",
info.visits, info.up, info.down
);
let title = relative_path
.rsplit_once('/')
.map(|(_, title)| title)
.unwrap_or(relative_path);
if !title.is_empty() {
let _ = writeln!(stream, "<h1>{title}</h1>");
}
let handle_entry = |mut entry: &str, output: &mut TcpStream, level: usize| {
let level = level + 1;
let mut path = path.to_path_buf();
if let Some((real_entry, _)) = entry.split_once(':') {
entry = real_entry
}
path.push(if relative_path.is_empty() {
format!("{entry}.pki")
} else {
format!("{relative_path}/{entry}.pki")
});
let Ok(file) = File::open(path) else {
return;
};
let _ = writeln!(
output,
"<h{level}><a href=\"{relative_path}/{entry}\">{entry}</a></h{level}>"
);
convert_subheader(
BufReader::new(file)
.lines()
.map(|line| line.unwrap_or_default()),
output,
level,
);
};
if let Ok(pk_file) = File::open(pk_path) {
convert_extended(
BufReader::new(pk_file)
.lines()
.map(|line| line.unwrap_or_default()),
&mut stream,
Settings::default()
.with_handler(handle_entry)
.with_start_level(start_level)
.with_use_textboxes(true),
);
} else {
unreachable!();
}
let parent_path = relative_path
.rsplit_once('/')
.map(|(path, _)| path)
.unwrap_or_default();
let _ = writeln!(stream, "<hr>");
let _ = writeln!(stream, "<a href=\"/{parent_path}\">&lt;&lt; Back</a>");
if let Some(pki_path) = pki_path {
if let Ok(pki_file) = File::open(pki_path) {
let _ = writeln!(stream, "<h1>Description</h1>");
convert_subheader(
BufReader::new(pki_file)
.lines() .lines()
.map(|line| line.unwrap_or_default()), .map(|line| line.unwrap_or_default()),
&mut stream, &mut stream,
Settings::default() 1,
.with_handler(handle_entry)
.with_start_level(start_level)
.with_use_textboxes(true),
); );
let _ = writeln!(stream, "<hr>");
let _ = writeln!(stream, "<a href=\"/{parent_path}\">&lt;&lt; Back</a>");
} else { } else {
unreachable!(); unreachable!();
} }
let parent_path = relative_path
.rsplit_once('/')
.map(|(path, _)| path)
.unwrap_or_default();
let _ = writeln!(stream, "<hr>");
let _ = writeln!(stream, "<a href=\"/{parent_path}\">&lt;&lt; Back</a>");
if let Some(pki_path) = pki_path {
if let Ok(pki_file) = File::open(pki_path) {
let _ = writeln!(stream, "<h1>Description</h1>");
convert_subheader(
BufReader::new(pki_file)
.lines()
.map(|line| line.unwrap_or_default()),
&mut stream,
1,
);
let _ = writeln!(stream, "<hr>");
let _ = writeln!(stream, "<a href=\"/{parent_path}\">&lt;&lt; Back</a>");
} else {
unreachable!();
}
}
let html = html! {
h1 { "Comments" }
form method="POST" {
input type="text" name="name" value="anon" placeholder="Name";
br;
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
br;
input type="submit" value="Send!";
}
form method="POST" {
input type="submit" value="💖️" name="up";
input type="submit" value="💔️" name="down";
}
};
let _ = stream.write_all(html.into_string().as_bytes());
for Comment { name, text } in &self.comments {
let _ = writeln!(stream, "<fieldset><legend>{name}</legend>{text}</fieldset>");
}
let _ = writeln!(stream, "<hr>");
let _ = writeln!(stream, "<a href=\"/{parent_path}\">&lt;&lt; Back</a>");
} }
let html = html! {
h1 { "Comments" }
form method="POST" {
input type="text" name="name" value="anon" placeholder="Name";
br;
textarea rows="5" cols="60" name="text" placeholder="Enter comment..." {}
br;
input type="submit" value="Send!";
}
form method="POST" {
input type="submit" value="💖️" name="up";
input type="submit" value="💔️" name="down";
}
};
let _ = stream.write_all(html.into_string().as_bytes());
for Comment { name, text } in &info.comments {
let _ = writeln!(stream, "<fieldset><legend>{name}</legend>{text}</fieldset>");
}
let _ = writeln!(stream, "<hr>");
let _ = writeln!(stream, "<a href=\"/{parent_path}\">&lt;&lt; Back</a>");
} }