diff --git a/src/main.rs b/src/main.rs index d064d5f..3735506 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ use std::{ io::{prelude::*, BufReader, Error, ErrorKind, Result}, net::{TcpListener, TcpStream}, path::Path, + sync::{Arc, Mutex}, + thread, }; use data_stream::{ @@ -43,12 +45,13 @@ fn fail(mut stream: TcpStream) { let _ = writeln!(stream, "Page not found!"); } +#[derive(Clone)] struct Comment { name: Box, text: Box, } -#[derive(Default)] +#[derive(Default, Clone)] struct SiteInfo { comments: Vec, visits: usize, @@ -111,7 +114,7 @@ impl FromStream for SiteInfo { #[derive(Default)] struct Context { - infos: HashMap, SiteInfo>, + infos: HashMap, Arc>>, } impl Context { @@ -192,205 +195,223 @@ impl Context { use Entry::*; let info = match self.infos.entry(relative_path.clone().into_boxed_str()) { - Occupied(o) => o.into_mut(), - Vacant(v) => v.insert( - File::open(&data_path) - .map(|mut file| { - from_stream::(&mut file).unwrap_or_default() - }) - .unwrap_or_default(), - ), + Occupied(o) => o.get().clone(), + Vacant(v) => v + .insert(Arc::new(Mutex::new( + File::open(&data_path) + .map(|mut file| { + from_stream::(&mut file).unwrap_or_default() + }) + .unwrap_or_default(), + ))) + .clone(), }; - info.handle_connection( - stream, - &request.body, - &relative_path, - &path, - &pk_path, - pki_path.as_ref().map(|path| path.as_ref()), - &data_path, - start_level, - ) + thread::spawn(move || { + handle_relative_connection( + info, + stream, + &request.body, + &relative_path, + &path, + &pk_path, + pki_path.as_ref().map(|path| path.as_ref()), + &data_path, + start_level, + ) + }); } } -impl SiteInfo { - fn handle_connection( - &mut self, - mut stream: TcpStream, - body: &str, - relative_path: &str, - path: &Path, - pk_path: &Path, - pki_path: Option<&Path>, - data_path: &Path, - start_level: usize, - ) { - let mut name = None; - let mut text = None; +fn handle_relative_connection( + info: Arc>, + mut stream: TcpStream, + body: &str, + relative_path: &str, + path: &Path, + pk_path: &Path, + pki_path: Option<&Path>, + data_path: &Path, + start_level: usize, +) { + let mut name = None; + let mut text = None; - for entry in body.split('&') { - let Some((key, input)) = entry.split_once('=') else { - continue; - }; + let mut up = false; + let mut down = false; - match key { - "name" => { - 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('&', "&") - .replace('<', "<") - .replace('>', ">") - .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::(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, - "

👁️{} 💖️{} 💔️{}

", - 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, "

{title}

"); - } - - 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, - "{entry}" - ); - convert_subheader( - BufReader::new(file) - .lines() - .map(|line| line.unwrap_or_default()), - output, - level, - ); + for entry in body.split('&') { + let Some((key, input)) = entry.split_once('=') else { + continue; }; - if let Ok(pk_file) = File::open(pk_path) { - convert_extended( - BufReader::new(pk_file) + match key { + "name" => { + 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('&', "&") + .replace('<', "<") + .replace('>', ">") + .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::(&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, + "

👁️{} 💖️{} 💔️{}

", + 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, "

{title}

"); + } + + 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, + "{entry}" + ); + 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, "
"); + let _ = writeln!(stream, "<< Back"); + + if let Some(pki_path) = pki_path { + if let Ok(pki_file) = File::open(pki_path) { + let _ = writeln!(stream, "

Description

"); + + convert_subheader( + BufReader::new(pki_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), + 1, ); + + let _ = writeln!(stream, "
"); + let _ = writeln!(stream, "<< Back"); } else { unreachable!(); } - - let parent_path = relative_path - .rsplit_once('/') - .map(|(path, _)| path) - .unwrap_or_default(); - - let _ = writeln!(stream, "
"); - let _ = writeln!(stream, "<< Back"); - - if let Some(pki_path) = pki_path { - if let Ok(pki_file) = File::open(pki_path) { - let _ = writeln!(stream, "

Description

"); - - convert_subheader( - BufReader::new(pki_file) - .lines() - .map(|line| line.unwrap_or_default()), - &mut stream, - 1, - ); - - let _ = writeln!(stream, "
"); - let _ = writeln!(stream, "<< Back"); - } 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, "
{name}{text}
"); - } - - let _ = writeln!(stream, "
"); - let _ = writeln!(stream, "<< Back"); } + + 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, "
{name}{text}
"); + } + + let _ = writeln!(stream, "
"); + let _ = writeln!(stream, "<< Back"); }