Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Input machine implementation #36

Merged
merged 19 commits into from
May 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b749129
list: command will return list of active nameplates
copyninja May 2, 2018
b07b02c
input machine: initial implementation (not tested)
copyninja May 2, 2018
2b8a18a
Temporary fix for now.
copyninja May 2, 2018
c074949
Add 2 new events to InputEvents for tab completion
copyninja May 4, 2018
a56857c
Introduce InputHelper for allowing clients to query completions
copyninja May 7, 2018
349e37b
Provide `get_completions` API in Wormhole object.
copyninja May 7, 2018
1c6ef49
Just send Start and then RefreshNameplates to Input machine
copyninja May 7, 2018
191fc1f
Add HelperChoseWord event: Invoked when user has input the word
copyninja May 8, 2018
289b9ac
Add rustyline to dev dependency
copyninja May 8, 2018
d3e8495
Sample to check using InputHelper: currently panics
copyninja May 8, 2018
263ea33
Make get_completions return actions
copyninja May 9, 2018
9829bf8
Make struct implementing Completer handle the actions
copyninja May 9, 2018
d1cf1a6
Use scoping to handle run time panic on RefCell::borrow_mut
copyninja May 9, 2018
af919db
Wormhole's get_completions returns vector of actions
copyninja May 11, 2018
e8238ef
Derive debug and partialeq for APIEvent
copyninja May 11, 2018
a3470f2
Partially working readline example
copyninja May 11, 2018
8b95145
Fix typo in state name, not sure how it compiled
copyninja May 11, 2018
123fa95
Process GotNameplates even in S3_typing_with_wordlist?
copyninja May 11, 2018
e559961
Use splitn with 2 to get correct split. (hopefully).
copyninja May 11, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ rustc-serialize = "0.3"
[dev-dependencies]
ws = "0.7"
url = "1.7"
rustyline = "1.0"
205 changes: 205 additions & 0 deletions core/examples/readline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
extern crate magic_wormhole_core;
extern crate rustyline;
#[macro_use]
extern crate serde_json;
extern crate url;
extern crate ws;

use magic_wormhole_core::{APIAction, APIEvent, Action, IOAction, IOEvent,
TimerHandle, WSHandle, WormholeCore};

use std::sync::{Arc, Mutex, mpsc::{channel, Sender}};
use std::thread::{sleep, spawn};
use std::time::Duration;

use url::Url;
use rustyline::completion::{extract_word, Completer};

const MAILBOX_SERVER: &'static str = "ws://localhost:4000/v1";
const APPID: &'static str = "lothar.com/wormhole/text-or-file-xfer";

struct Factory {
wsh: WSHandle,
wcr: Arc<Mutex<WormholeCore>>,
}

struct WSHandler {
wsh: WSHandle,
wcr: Arc<Mutex<WormholeCore>>,
out: ws::Sender,
}

impl ws::Factory for Factory {
type Handler = WSHandler;
fn connection_made(&mut self, out: ws::Sender) -> WSHandler {
WSHandler {
wsh: self.wsh,
wcr: Arc::clone(&self.wcr),
out: out,
}
}
}

struct CodeCompleter {
wcr: Arc<Mutex<WormholeCore>>,
tx: Sender<Vec<Action>>,
}

static BREAK_CHARS: [char; 1] = [' '];

impl Completer for CodeCompleter {
fn complete(
&self,
line: &str,
pos: usize,
) -> rustyline::Result<(usize, Vec<String>)> {
let (start, word) =
extract_word(line, pos, &BREAK_CHARS.iter().cloned().collect());
let mwc = Arc::clone(&self.wcr);
let mut wc = mwc.lock().unwrap();
let (actions, completions) = wc.get_completions(word);
self.tx.send(actions).unwrap();
Ok((start, completions))
}
}

impl ws::Handler for WSHandler {
fn on_open(&mut self, _: ws::Handshake) -> Result<(), ws::Error> {
// println!("On_open");
let mwc = Arc::clone(&self.wcr);
{
let mut wc = mwc.lock().unwrap();
let actions = wc.do_io(IOEvent::WebSocketConnectionMade(self.wsh));
process_actions(&self.out, actions);

// TODO: This is for experiment we are starting the Input machine
// manually
let actions = wc.do_api(APIEvent::InputCode);
process_actions(&self.out, actions);
}

let (tx_action, rx_action) = channel();
let completer = CodeCompleter {
wcr: Arc::clone(&self.wcr),
tx: tx_action,
};

let (tx, rx) = channel();
let handle = spawn(move || {
let mut rl = rustyline::Editor::new();
rl.set_completer(Some(completer));
loop {
match rl.readline("Enter receive wormhole code: ") {
Ok(line) => {
if line.trim().is_empty() {
// Wait till user enter the code
continue;
}

tx.send(line.to_string()).unwrap();
break;
}
Err(rustyline::error::ReadlineError::Interrupted) => {
// println!("Interrupted");
continue;
}
Err(rustyline::error::ReadlineError::Eof) => {
break;
}
Err(err) => {
// println!("Error: {:?}", err);
break;
}
}
}
});

let out_actions = self.out.clone();
let amwc = Arc::clone(&self.wcr);

spawn(move || {
for actions in rx_action {
// println!("{:?}", actions);
let mut wc = amwc.lock().unwrap();
process_actions(&out_actions, actions);
}
});

let out_events = self.out.clone();
let emwc = Arc::clone(&self.wcr);
spawn(move || {
for received in rx {
let mut wc = emwc.lock().unwrap();
let actions = wc.do_api(APIEvent::HelperChoseWord(received));
process_actions(&out_events, actions);
}
});

Ok(())
}

fn on_message(&mut self, msg: ws::Message) -> Result<(), ws::Error> {
// println!("got message {}", msg);
let mwc = Arc::clone(&self.wcr);
let text = msg.as_text()?.to_string();
let rx = IOEvent::WebSocketMessageReceived(self.wsh, text);
let mut wc = mwc.lock().unwrap();
let actions = wc.do_io(rx);
process_actions(&self.out, actions);
Ok(())
}
}

fn process_actions(out: &ws::Sender, actions: Vec<Action>) {
for a in actions {
match a {
Action::IO(io) => match io {
IOAction::WebSocketSendMessage(_wsh, msg) => {
// println!("sending {:?}", msg);
out.send(msg).unwrap();
}
IOAction::WebSocketClose(_wsh) => {
out.close(ws::CloseCode::Normal).unwrap();
}
_ => {
// println!("action: {:?}", io);
}
},
Action::API(api) => match api {
APIAction::GotMessage(msg) => println!(
"API Got Message: {}",
String::from_utf8(msg).unwrap()
),
_ => println!("action {:?}", api),
},
}
}
}

fn main() {
println!("Receive start");

let mut wc = WormholeCore::new(APPID, MAILBOX_SERVER);
let wsh;
let ws_url;
let mut actions = wc.start();

if let Action::IO(IOAction::WebSocketOpen(handle, url)) =
actions.pop().unwrap()
{
wsh = handle;
ws_url = Url::parse(&url).unwrap();
} else {
panic!();
}

let f = Factory {
wsh: wsh,
wcr: Arc::new(Mutex::new(wc)),
};

let b = ws::Builder::new();
let mut w1 = b.build(f).unwrap();
w1.connect(ws_url).unwrap();
w1.run().unwrap();
}
2 changes: 2 additions & 0 deletions core/src/api.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::collections::HashMap;

#[derive(Debug, PartialEq)]
pub enum APIEvent {
// from application to IO glue to WormholeCore
AllocateCode,
InputCode,
HelperChoseWord(String),
SetCode(String),
Close,
Send(Vec<u8>),
Expand Down
12 changes: 12 additions & 0 deletions core/src/boss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use events::CodeEvent::{AllocateCode as C_AllocateCode,
use events::RendezvousEvent::Stop as RC_Stop;
use events::SendEvent::Send as S_Send;
use events::TerminatorEvent::Close as T_Close;
use events::InputEvent::ChooseWords as I_ChooseWords;

#[derive(Debug, PartialEq, Copy, Clone)]
enum State {
Expand Down Expand Up @@ -53,6 +54,7 @@ impl Boss {
AllocateCode => self.allocate_code(), // TODO: len, wordlist
InputCode => self.input_code(), // TODO: return Helper
SetCode(code) => self.set_code(&code),
HelperChoseWord(word) => self.choose_word(&word),
Close => events![RC_Stop], // eventually signals GotClosed
Send(plaintext) => self.send(plaintext),
}
Expand Down Expand Up @@ -100,6 +102,16 @@ impl Boss {
actions
}

fn choose_word(&mut self, word: &str) -> Events {
use self::State::*;
let (actions, newstate) = match self.state {
Coding(i) => (events![I_ChooseWords(word.to_string())], Coding(i)),
_ => panic!(),
};
self.state = newstate;
actions
}

fn input_code(&mut self) -> Events {
// TODO: validate code, maybe signal KeyFormatError
// TODO: return Helper somehow
Expand Down
25 changes: 22 additions & 3 deletions core/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,20 @@ pub enum CodeEvent {
#[derive(Debug, PartialEq)]
pub enum InputEvent {
Start,
GotNameplates,
GotWordlist,
GotNameplates(Vec<String>),
GotWordlist(Wordlist),
ChooseNameplate(String),
ChooseWords(String),
RefreshNameplates,
}

#[derive(Debug, PartialEq)]
pub enum InputHelperEvent {
RefreshNameplates,
GotNameplates(Vec<String>),
GotWordlist(Wordlist),
ChooseNameplate(String),
ChooseWords(String),
}

#[derive(Debug, PartialEq)]
Expand All @@ -65,7 +77,7 @@ pub enum KeyEvent {
pub enum ListerEvent {
Connected,
Lost,
RxNameplates,
RxNameplates(Vec<String>),
Refresh,
}

Expand Down Expand Up @@ -157,6 +169,7 @@ pub enum Event {
Boss(BossEvent),
Code(CodeEvent),
Input(InputEvent),
InputHelper(InputHelperEvent),
Key(KeyEvent),
Lister(ListerEvent),
Mailbox(MailboxEvent),
Expand Down Expand Up @@ -206,6 +219,12 @@ impl From<InputEvent> for Event {
}
}

impl From<InputHelperEvent> for Event {
fn from(r: InputHelperEvent) -> Self {
Event::InputHelper(r)
}
}

impl From<KeyEvent> for Event {
fn from(r: KeyEvent) -> Self {
Event::Key(r)
Expand Down
Loading