144 lines
5.0 KiB
Rust
144 lines
5.0 KiB
Rust
use core::fmt::Write;
|
|
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex};
|
|
use heapless::{String, Vec};
|
|
use pico_website::unwrap;
|
|
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
|
|
|
|
use crate::socket::{HttpRequestType, HttpResCode};
|
|
|
|
use super::App;
|
|
|
|
const MEMORY_SIZE: usize = 16;
|
|
static MESSAGES: Mutex<ThreadModeRawMutex, Messages> = Mutex::new(Messages::new());
|
|
|
|
pub struct ChatApp {
|
|
res_buf: Vec<u8, 2048>,
|
|
}
|
|
impl ChatApp {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
res_buf: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
impl App for ChatApp {
|
|
fn socket_name(&self) -> &'static str {
|
|
"chat"
|
|
}
|
|
async fn handle_request<'a>(
|
|
&'a mut self,
|
|
path: &str,
|
|
req_type: HttpRequestType,
|
|
content: &str,
|
|
) -> (HttpResCode, &'static str, &'a [u8]) {
|
|
match (req_type, path) {
|
|
(HttpRequestType::Get, "/" | "/index" | "/index.html" | "/chat" | "/chat.html") => {
|
|
(HttpResCode::Ok, "html", include_bytes!("./chat.html"))
|
|
}
|
|
(_, path) => {
|
|
let (path, args) = path.split_once('?').unwrap_or((path, ""));
|
|
let mut load = None;
|
|
let mut username = None;
|
|
for arg in args.split('&').chain(content.split('&')) {
|
|
match arg.split_once('=') {
|
|
Some(("load", n)) => {
|
|
let n: u16 = match n.parse() {
|
|
Ok(v) => v,
|
|
Err(_) => return (HttpResCode::BadRequest, "", &[]),
|
|
};
|
|
if n > 0 {
|
|
load = Some(n);
|
|
}
|
|
}
|
|
Some(("username", u)) => {
|
|
if u.len() < 3 || u.len() > 16 {
|
|
return (HttpResCode::BadRequest, "", &[]);
|
|
}
|
|
username = Some(u);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
if path == "/chat" && username.is_some() {
|
|
self.res_buf.clear();
|
|
unwrap(write!(
|
|
&mut self.res_buf,
|
|
"<div \
|
|
class=\"message\" \
|
|
hx-get=\"/chat/message/abs/0?load={}\" \
|
|
hx-target=\"this\" \
|
|
hx-swap=\"outerHTML\" \
|
|
hx-trigger=\"load\" \
|
|
></div>",
|
|
MEMORY_SIZE
|
|
))
|
|
.await;
|
|
return (HttpResCode::Ok, "html", &self.res_buf);
|
|
} else if path.starts_with("/chat/message/abs/") && path.len() > 18 {
|
|
let msg_id: u16 = match path[18..].parse() {
|
|
Ok(n) => n,
|
|
Err(_) => return (HttpResCode::BadRequest, "", &[]),
|
|
};
|
|
let msgs = MESSAGES.lock().await;
|
|
if msg_id >= msgs.next {
|
|
return (HttpResCode::BadRequest, "", &[]);
|
|
}
|
|
if msg_id < msgs.next.saturating_sub(MEMORY_SIZE as u16 + 1) {
|
|
return (HttpResCode::NoContent, "", &[]);
|
|
}
|
|
self.res_buf.clear();
|
|
unwrap(write!(&mut self.res_buf, "<div class=\"message\"")).await;
|
|
if let Some(n) = load {
|
|
unwrap(write!(
|
|
&mut self.res_buf,
|
|
" hx-get=\"/chat/message/abs/{}?load={}\" \
|
|
hx-target=\"this\" \
|
|
hx-swap=\"afterend\" \
|
|
hx-trigger=\"load\"",
|
|
msg_id + 1,
|
|
n - 1
|
|
))
|
|
.await;
|
|
}
|
|
let msg = match msgs.get_abs(msg_id) {
|
|
Some(msg) => msg,
|
|
None => return (HttpResCode::NoContent, "", &[]),
|
|
};
|
|
unwrap(write!(
|
|
&mut self.res_buf,
|
|
"><b>{}</b>: {}</div>",
|
|
msg.author, msg.content
|
|
))
|
|
.await;
|
|
return (HttpResCode::Ok, "html", &self.res_buf);
|
|
} else {
|
|
(HttpResCode::NotFound, "", &[])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Message {
|
|
author: String<16>,
|
|
content: String<256>,
|
|
}
|
|
|
|
struct Messages {
|
|
inner: ConstGenericRingBuffer<Message, MEMORY_SIZE>,
|
|
next: u16,
|
|
}
|
|
impl Messages {
|
|
const fn new() -> Self {
|
|
Self {
|
|
inner: ConstGenericRingBuffer::new(),
|
|
next: 2,
|
|
}
|
|
}
|
|
fn get_abs(&self, id: u16) -> Option<&Message> {
|
|
self.inner
|
|
.get_signed((id as isize) + 1 - (self.next as isize))
|
|
}
|
|
}
|