From b759c7ec2ac5411f7dd254bb19a20c27a166e7e2 Mon Sep 17 00:00:00 2001 From: Max Verevkin Date: Mon, 4 Apr 2022 16:53:49 +0300 Subject: dont use line buffering when using json protocol --- src/i3bar_protocol.rs | 157 +++++++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 79 deletions(-) (limited to 'src/i3bar_protocol.rs') diff --git a/src/i3bar_protocol.rs b/src/i3bar_protocol.rs index 6bc0533..4ef68ba 100644 --- a/src/i3bar_protocol.rs +++ b/src/i3bar_protocol.rs @@ -1,9 +1,9 @@ use crate::color::Color; use crate::pointer_btn::PointerBtn; use crate::text::Align; +use crate::utils::{de_first_json, de_last_json, last_line}; use serde::Deserialize; use serde::Serialize; -use serde_json::{Deserializer, Result as SerdeResult}; use std::io::{Error, ErrorKind, Result}; #[derive(Deserialize, Default)] @@ -80,63 +80,104 @@ pub struct JsonHeader { pub enum Protocol { Unknown, - PlainText(Option), + PlainText { + line: Option, + buf: Vec, + }, JsonNotStarted(JsonHeader), - Json(JsonHeader, Option>, Option), + Json { + header: JsonHeader, + blocks: Option>, + buf: Vec, + }, } impl Protocol { - pub fn process_line(&mut self, line: String) -> Result<()> { + pub fn process_new_bytes(&mut self, bytes: &[u8]) -> Result<()> { macro_rules! invalid { - ($fmt:expr) => { - return Err(Error::new(ErrorKind::InvalidData, format!($fmt))) + ($fmt:expr $(,$t:expr)*) => { + return Err(Error::new(ErrorKind::InvalidData, format!($fmt $(,$t)*))) }; } match self { Self::Unknown => { - if let Ok(header) = serde_json::from_str::(&line) { + if let Ok((Some(header), rem)) = de_first_json::(bytes) { if header.version == 1 { - *self = Protocol::JsonNotStarted(header); - return Ok(()); + *self = Self::JsonNotStarted(header); + return self.process_new_bytes(rem); + } + } + *self = Self::PlainText { + line: None, + buf: Vec::new(), + }; + return self.process_new_bytes(bytes); + } + Self::PlainText { line, buf } => { + if buf.is_empty() { + if let Some((new_line, rem)) = last_line(bytes) { + *line = Some(String::from_utf8_lossy(new_line).into()); + buf.extend_from_slice(rem); + } else { + buf.extend_from_slice(bytes); + } + } else { + buf.extend_from_slice(bytes); + if let Some((new_line, rem)) = last_line(buf) { + *line = Some(String::from_utf8_lossy(new_line).into()); + let used = buf.len() - rem.len(); + buf.drain(..used); } } - *self = Self::PlainText(Some(line)) } - Self::PlainText(s) => *s = Some(line), Self::JsonNotStarted(header) => { - let line = line.trim_start(); - if !line.is_empty() { - if !line.starts_with('[') { - invalid!("Expected '['"); + let mut bytes = bytes; + while bytes.first().map_or(false, |&x| x == b' ' || x == b'\n') { + bytes = &bytes[1..]; + } + match bytes.first() { + Some(b'[') => { + *self = Self::Json { + header: *header, + blocks: None, + buf: Vec::new(), + }; + return self.process_new_bytes(&bytes[1..]); } - *self = Self::Json(*header, None, None); - self.process_line(line[1..].to_owned())?; + Some(other) => invalid!("invalid json: expected '[', got '{}'", *other as char), + _ => (), } } - Self::Json(_, blocks, start_orig) => match start_orig { - Some(start) => { - start.push_str(&line); - match de_last::>(start) { - Err(e) => invalid!("Invalid JSON: {e}"), - Ok((rem, new_blocks)) => { - *start_orig = (!rem.is_empty()).then(|| rem.to_owned()); + Self::Json { + header: _, + blocks, + buf, + } => { + if buf.is_empty() { + match de_last_json(bytes) { + Err(e) => invalid!("invalid json: {e}"), + Ok((new_blocks, rem)) => { if let Some(new_blocks) = new_blocks { *blocks = Some(new_blocks); } + buf.extend_from_slice(rem); } } - } - None => match de_last::>(&line) { - Err(e) => invalid!("Invalid JSON: {e}"), - Ok((rem, new_blocks)) => { - *start_orig = (!rem.is_empty()).then(|| rem.to_owned()); - if let Some(new_blocks) = new_blocks { - *blocks = Some(new_blocks); + } else { + buf.extend_from_slice(bytes); + match de_last_json(buf) { + Err(e) => invalid!("invalid json: {e}"), + Ok((new_blocks, rem)) => { + if let Some(new_blocks) = new_blocks { + *blocks = Some(new_blocks); + } + let used = buf.len() - rem.len(); + buf.drain(..used); } } - }, - }, + } + } } Ok(()) } @@ -144,62 +185,20 @@ impl Protocol { pub fn get_blocks(&mut self) -> Option> { match self { Self::Unknown => None, - Self::PlainText(text) => Some(vec![Block { - full_text: text.take()?, + Self::PlainText { line, .. } => Some(vec![Block { + full_text: line.take()?, ..Default::default() }]), Self::JsonNotStarted(_) => None, - Self::Json(_, blocks, _) => blocks.take(), + Self::Json { blocks, .. } => blocks.take(), } } pub fn supports_clicks(&self) -> bool { match self { Self::JsonNotStarted(h) => h.click_events, - Self::Json(h, _, _) => h.click_events, + Self::Json { header, .. } => header.click_events, _ => false, } } } - -/// Deserialize the last complete object. Returns (`remaining`, `object`). See tests for examples. -fn de_last<'a, T: Deserialize<'a>>(mut s: &'a str) -> SerdeResult<(&'a str, Option)> { - let mut last = None; - let mut tmp; - loop { - (s, tmp) = de_once(s)?; - last = match tmp { - Some(obj) => Some(obj), - None => return Ok((s, last)), - }; - } -} - -/// Deserialize the first complete object. Returns (`remaining`, `object`). -fn de_once<'a, T: Deserialize<'a>>(s: &'a str) -> SerdeResult<(&'a str, Option)> { - let s = s.trim_start_matches(|x: char| x.is_ascii_whitespace() || x == ','); - let mut de = Deserializer::from_str(s).into_iter(); - match de.next() { - Some(Ok(obj)) => Ok((&s[de.byte_offset()..], Some(obj))), - Some(Err(e)) if e.is_eof() => Ok((&s[de.byte_offset()..], None)), - Some(Err(e)) => Err(e), - None => Ok((&s[de.byte_offset()..], None)), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn streaming_json() { - let s = ",[2], [3], [4, 3],[32][3] "; - assert_eq!(de_last::>(s).unwrap(), ("", Some(vec![3]))); - - let s = ",[2], [3], [4, 3],[32][3] [2, 4"; - assert_eq!(de_last::>(s).unwrap(), ("[2, 4", Some(vec![3]))); - - let s = ",[2], [3], [4, 3],[32] invalid"; - assert!(de_last::>(s).is_err()); - } -} -- cgit v1.3.1