mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Improve tcp_server example a bit
This commit is contained in:
parent
7d81594c69
commit
5faf8d6c48
@ -1,8 +1,7 @@
|
|||||||
use cursive::traits::*;
|
use cursive::traits::*;
|
||||||
use cursive::views;
|
use cursive::views;
|
||||||
|
|
||||||
use std::io::Read as _;
|
use std::io::{Read as _, Write as _};
|
||||||
use std::io::Write as _;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
// This example builds a simple TCP server with some parameters and some output.
|
// This example builds a simple TCP server with some parameters and some output.
|
||||||
@ -12,8 +11,8 @@ fn main() {
|
|||||||
let mut siv = cursive::Cursive::default();
|
let mut siv = cursive::Cursive::default();
|
||||||
|
|
||||||
// Build a shared model
|
// Build a shared model
|
||||||
let model = Arc::new(Mutex::new(Model {
|
let model = Arc::new(Mutex::new(ModelData {
|
||||||
offset: 0,
|
offset: 10,
|
||||||
logs: Vec::new(),
|
logs: Vec::new(),
|
||||||
cb_sink: siv.cb_sink().clone(),
|
cb_sink: siv.cb_sink().clone(),
|
||||||
}));
|
}));
|
||||||
@ -30,18 +29,32 @@ fn main() {
|
|||||||
siv.run();
|
siv.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Model {
|
struct ModelData {
|
||||||
|
/// The offset will be controlled by the UI and used in the server
|
||||||
offset: u8,
|
offset: u8,
|
||||||
logs: Vec<(u8, u8)>,
|
/// Logs will be filled by the server and displayed on the UI
|
||||||
|
logs: Vec<LogEntry>,
|
||||||
|
/// A callback sink is used to control the UI from the server
|
||||||
|
/// (eg. force refresh, error popups)
|
||||||
cb_sink: cursive::CbSink,
|
cb_sink: cursive::CbSink,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_server(model: Arc<Mutex<Model>>) {
|
// Here we use a single mutex, but bigger models might
|
||||||
|
// prefer individual mutexes for different variables.
|
||||||
|
type Model = Arc<Mutex<ModelData>>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct LogEntry {
|
||||||
|
input: u8,
|
||||||
|
output: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts serving on a separate thread, and show a popup on error.
|
||||||
|
fn start_server(model: Model) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if let Err(err) = serve(Arc::clone(&model)) {
|
if let Err(err) = serve(Arc::clone(&model)) {
|
||||||
|
let model = model.lock().unwrap();
|
||||||
model
|
model
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.cb_sink
|
.cb_sink
|
||||||
.send(Box::new(move |s: &mut cursive::Cursive| {
|
.send(Box::new(move |s: &mut cursive::Cursive| {
|
||||||
s.add_layer(
|
s.add_layer(
|
||||||
@ -55,18 +68,30 @@ fn start_server(model: Arc<Mutex<Model>>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serve(model: Arc<Mutex<Model>>) -> std::io::Result<()> {
|
/// Starts a simple, single-threaded TCP server.
|
||||||
|
/// Adds a configurable offset to each byte received and sent it back.
|
||||||
|
fn serve(model: Model) -> std::io::Result<()> {
|
||||||
|
// Bind on some local address
|
||||||
let listener = std::net::TcpListener::bind("localhost:1234")?;
|
let listener = std::net::TcpListener::bind("localhost:1234")?;
|
||||||
|
|
||||||
|
// Handle each connection sequentially
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
let stream = stream?;
|
let stream = stream?;
|
||||||
|
|
||||||
|
// Process each byte according to the current model.
|
||||||
for byte in (&stream).bytes() {
|
for byte in (&stream).bytes() {
|
||||||
let byte = byte?;
|
let byte = byte?;
|
||||||
let mut model = model.lock().unwrap();
|
let mut model = model.lock().unwrap();
|
||||||
let response = byte.wrapping_add(model.offset);
|
let response = byte.wrapping_add(model.offset);
|
||||||
model.logs.push((byte, response));
|
|
||||||
(&stream).write_all(&[response])?;
|
(&stream).write_all(&[response])?;
|
||||||
|
|
||||||
|
// Save processed jobs
|
||||||
|
model.logs.push(LogEntry {
|
||||||
|
input: byte,
|
||||||
|
output: response,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a noop to refresh the display
|
||||||
model
|
model
|
||||||
.cb_sink
|
.cb_sink
|
||||||
.send(Box::new(cursive::Cursive::noop))
|
.send(Box::new(cursive::Cursive::noop))
|
||||||
@ -77,6 +102,42 @@ fn serve(model: Arc<Mutex<Model>>) -> std::io::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build the UI for the given model.
|
||||||
|
fn build_ui(model: Model) -> impl cursive::view::View {
|
||||||
|
// Build the UI in 3 parts, stacked together in a LinearLayout.
|
||||||
|
views::LinearLayout::vertical()
|
||||||
|
.child(build_selector(Arc::clone(&model)))
|
||||||
|
.child(build_tester(Arc::clone(&model)))
|
||||||
|
.child(views::DummyView.fixed_height(1))
|
||||||
|
.child(build_log_viewer(Arc::clone(&model)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a view that shows processed jobs from the model.
|
||||||
|
fn build_log_viewer(model: Model) -> impl cursive::view::View {
|
||||||
|
views::Canvas::new(model)
|
||||||
|
.with_draw(|model, printer| {
|
||||||
|
let model = model.lock().unwrap();
|
||||||
|
for (i, &log) in model.logs.iter().enumerate() {
|
||||||
|
printer.print(
|
||||||
|
(0, i),
|
||||||
|
&format!(
|
||||||
|
"{:3} '{}' -> {:3} '{}'",
|
||||||
|
log.input,
|
||||||
|
readable_char(log.input),
|
||||||
|
log.output,
|
||||||
|
readable_char(log.output),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_required_size(|model, _req| {
|
||||||
|
let model = model.lock().unwrap();
|
||||||
|
cursive::Vec2::new(20, model.logs.len())
|
||||||
|
})
|
||||||
|
.scrollable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pretty print an ascii u8 if possible.
|
||||||
fn readable_char(byte: u8) -> char {
|
fn readable_char(byte: u8) -> char {
|
||||||
if byte.is_ascii_control() {
|
if byte.is_ascii_control() {
|
||||||
'<27>'
|
'<27>'
|
||||||
@ -85,34 +146,13 @@ fn readable_char(byte: u8) -> char {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_log_viewer(model: Arc<Mutex<Model>>) -> impl cursive::view::View {
|
/// Build a view that can update the model.
|
||||||
views::Canvas::new(model)
|
fn build_selector(model: Model) -> impl cursive::view::View {
|
||||||
.with_draw(|model, printer| {
|
let offset = model.lock().unwrap().offset;
|
||||||
let model = model.lock().unwrap();
|
|
||||||
for (i, &(byte, answer)) in model.logs.iter().enumerate() {
|
|
||||||
printer.print(
|
|
||||||
(0, i),
|
|
||||||
&format!(
|
|
||||||
"{:3} '{}' -> {:3} '{}'",
|
|
||||||
byte,
|
|
||||||
readable_char(byte),
|
|
||||||
answer,
|
|
||||||
readable_char(answer),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.with_required_size(|model, _req| {
|
|
||||||
let model = model.lock().unwrap();
|
|
||||||
cursive::Vec2::new(10, model.logs.len())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_selector(model: Arc<Mutex<Model>>) -> impl cursive::view::View {
|
|
||||||
views::LinearLayout::horizontal()
|
views::LinearLayout::horizontal()
|
||||||
.child(
|
.child(
|
||||||
views::EditView::new()
|
views::EditView::new()
|
||||||
.content("0")
|
.content(format!("{}", offset))
|
||||||
.with_id("edit")
|
.with_id("edit")
|
||||||
.min_width(5),
|
.min_width(5),
|
||||||
)
|
)
|
||||||
@ -131,6 +171,23 @@ fn build_selector(model: Arc<Mutex<Model>>) -> impl cursive::view::View {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a view that can run test connections.
|
||||||
|
fn build_tester(model: Model) -> impl cursive::view::View {
|
||||||
|
views::LinearLayout::horizontal()
|
||||||
|
.child(views::TextView::new("Current value:"))
|
||||||
|
.child(views::DummyView.fixed_width(1))
|
||||||
|
.child(
|
||||||
|
views::Canvas::new(model)
|
||||||
|
.with_draw(|model, printer| {
|
||||||
|
printer.print(
|
||||||
|
(0, 0),
|
||||||
|
&format!("{}", model.lock().unwrap().offset),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.with_required_size(|_, _| cursive::Vec2::new(3, 1)),
|
||||||
|
)
|
||||||
.child(views::DummyView.fixed_width(1))
|
.child(views::DummyView.fixed_width(1))
|
||||||
.child(views::Button::new("Test", |s| {
|
.child(views::Button::new("Test", |s| {
|
||||||
if let Err(err) = test_server() {
|
if let Err(err) = test_server() {
|
||||||
@ -142,18 +199,13 @@ fn build_selector(model: Arc<Mutex<Model>>) -> impl cursive::view::View {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a test connection.
|
||||||
fn test_server() -> std::io::Result<()> {
|
fn test_server() -> std::io::Result<()> {
|
||||||
let mut stream = std::net::TcpStream::connect("localhost:1234")?;
|
let mut stream = std::net::TcpStream::connect("localhost:1234")?;
|
||||||
for &byte in &[1, 2, 3, b'a', b'c', b'd'] {
|
for &byte in b"cursive123" {
|
||||||
let mut buf = [0];
|
let mut buf = [0];
|
||||||
stream.write_all(&[byte])?;
|
stream.write_all(&[byte])?;
|
||||||
stream.read_exact(&mut buf)?;
|
stream.read_exact(&mut buf)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_ui(model: Arc<Mutex<Model>>) -> impl cursive::view::View {
|
|
||||||
views::LinearLayout::vertical()
|
|
||||||
.child(build_selector(Arc::clone(&model)))
|
|
||||||
.child(build_log_viewer(Arc::clone(&model)))
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user