From b74912950f0726717f7451b15d7be6f9058de5f0 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Wed, 2 May 2018 16:52:19 +0530 Subject: [PATCH 01/19] list: command will return list of active nameplates - Make sure we can pass this list of nameplates using RxNameplates event. - From lister this list of nameplates should go to input machine - rendezvous machine should handle nameplates message and generate list of nameplates from it. Then it should emit RxNameplates event for lister machine Misc changes to make sure we can access id key of nameplates message by making it public. --- core/src/events.rs | 2 +- core/src/lister.rs | 13 ++++++++++--- core/src/rendezvous.rs | 8 +++++++- core/src/server_messages.rs | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/src/events.rs b/core/src/events.rs index 9f349653..11c2da23 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -65,7 +65,7 @@ pub enum KeyEvent { pub enum ListerEvent { Connected, Lost, - RxNameplates, + RxNameplates(Vec), Refresh, } diff --git a/core/src/lister.rs b/core/src/lister.rs index dfa55a70..8c23d13a 100644 --- a/core/src/lister.rs +++ b/core/src/lister.rs @@ -56,7 +56,9 @@ impl Lister { match event { Refresh => (State::S1B, events![RC_TxList]), Lost => (State::S0A, events![]), - RxNameplates => (State::S0B, events![I_GotNameplates]), + RxNameplates(nids) => { + (State::S0B, events![I_GotNameplates(nids.clone())]) + } Connected => (State::S0B, events![]), } } @@ -73,7 +75,9 @@ impl Lister { match event { Lost => (State::S1A, events![]), Refresh => (State::S1B, events![RC_TxList]), - RxNameplates => (State::S0B, events![I_GotNameplates]), + RxNameplates(nids) => { + (State::S0B, events![I_GotNameplates(nids.clone())]) + } Connected => (State::S1B, events![]), } } @@ -98,7 +102,10 @@ mod test { assert_eq!(lister.state, State::S0A); lister.state = State::S0B; - assert_eq!(lister.process(RxNameplates), events![GotNameplates]); + assert_eq!( + lister.process(RxNameplates(vec!["3".to_string()])), + events![GotNameplates(vec!["3".to_string()])] + ); assert_eq!(lister.state, State::S0B); assert_eq!(lister.process(Refresh), events![TxList]); diff --git a/core/src/rendezvous.rs b/core/src/rendezvous.rs index b6fb057e..cd71e56b 100644 --- a/core/src/rendezvous.rs +++ b/core/src/rendezvous.rs @@ -24,7 +24,8 @@ use events::NameplateEvent::{Connected as N_Connected, use events::AllocatorEvent::{Connected as A_Connected, RxAllocated as A_RxAllocated}; use events::MailboxEvent::{Connected as M_Connected, RxMessage as M_RxMessage}; -use events::ListerEvent::Connected as L_Connected; +use events::ListerEvent::{Connected as L_Connected, + RxNameplates as L_RxNamePlates}; use events::RendezvousEvent::TxBind as RC_TxBind; // loops around #[derive(Debug, PartialEq)] @@ -158,6 +159,11 @@ impl Rendezvous { Message::Allocated { nameplate } => { events![A_RxAllocated(nameplate)] } + Message::Nameplates { nameplates } => { + let nids: Vec = + nameplates.iter().map(|n| n.id.to_owned()).collect(); + events![L_RxNamePlates(nids)] + } _ => events![], // TODO } } diff --git a/core/src/server_messages.rs b/core/src/server_messages.rs index eb4345f0..40c15c03 100644 --- a/core/src/server_messages.rs +++ b/core/src/server_messages.rs @@ -5,12 +5,12 @@ use util; #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct Nameplate { - id: String, + pub id: String, } #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct WelcomeMsg { - motd: String, + pub motd: String, } // convert an optional field (which may result in deserialization error) From b07b02c37b622db5f2a069f318c6f10e03378597 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Wed, 2 May 2018 16:56:02 +0530 Subject: [PATCH 02/19] input machine: initial implementation (not tested) --- core/src/events.rs | 7 +++- core/src/input.rs | 97 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/core/src/events.rs b/core/src/events.rs index 11c2da23..33766173 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -50,8 +50,11 @@ pub enum CodeEvent { #[derive(Debug, PartialEq)] pub enum InputEvent { Start, - GotNameplates, - GotWordlist, + GotNameplates(Vec), + GotWordlist(Wordlist), + ChooseNameplate(String), + ChooseWords(String), + RefreshNameplates, } #[derive(Debug, PartialEq)] diff --git a/core/src/input.rs b/core/src/input.rs index e1a5e052..0d9e5d9f 100644 --- a/core/src/input.rs +++ b/core/src/input.rs @@ -1,21 +1,104 @@ use events::Events; // we process these -use events::InputEvent; +use events::InputEvent::{self, ChooseNameplate, ChooseWords, GotNameplates, + GotWordlist, RefreshNameplates, Start}; // we emit these +use events::ListerEvent::Refresh as L_Refresh; +use events::CodeEvent::{FinishedInput as C_FinishedInput, + GotNameplate as C_GotNameplate}; -pub struct Input {} +pub struct Input { + state: State, + _all_nameplates: Vec, + _nameplate: String, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +enum State { + S0_Idle, + S1_typing_nameplate, + S2_typing_code_without_wordlist, + S3_typing_code_with_wordlist, + S4_done, +} impl Input { pub fn new() -> Input { - Input {} + Input { + state: State::S0_Idle, + _all_nameplates: Vec::new(), + _nameplate: String::new(), + } } pub fn process(&mut self, event: InputEvent) -> Events { - use events::InputEvent::*; + let (newstate, actions) = match self.state { + S0_idle => self.in_idle(event), + S1_typing_nameplate => self.in_typing_nameplate(event), + S2_typing_code_without_wordlist => { + self.in_type_without_wordlist(event) + } + S3_typing_code_with_wordlist => self.in_type_with_wordlist(event), + S4_done => (self.state, events![]), + }; + + self.state = newstate; + actions + } + + fn in_idle(&mut self, event: InputEvent) -> (State, Events) { + match event { + Start => (State::S1_typing_nameplate, events![L_Refresh]), + _ => (self.state, events![]), + } + } + + fn in_typing_nameplate(&mut self, event: InputEvent) -> (State, Events) { + match event { + GotNameplates(nameplates) => { + self._all_nameplates = nameplates; + (State::S1_typing_nameplate, events![]) + } + ChooseNameplate(nameplate) => { + self._nameplate = nameplate.to_owned(); + ( + State::S2_typing_code_without_wordlist, + events![C_GotNameplate(nameplate)], + ) + } + RefreshNameplates => (self.state, events![L_Refresh]), + _ => (self.state, events![]), + } + } + + fn in_type_without_wordlist( + &mut self, + event: InputEvent, + ) -> (State, Events) { + match event { + GotNameplates(nameplates) => { + self._all_nameplates = nameplates; + (State::S2_typing_code_without_wordlist, events![]) + } + GotWordlist(wordlist) => { + (State::S3_typing_code_with_wordlist, events![]) + } + ChooseWords(words) => { + let code = format!("{}-{}", self._nameplate, words); + (State::S4_done, events![C_FinishedInput(code)]) + } + _ => (self.state, events![]), + } + } + + fn in_type_with_wordlist(&mut self, event: InputEvent) -> (State, Events) { match event { - Start => events![], - GotNameplates => events![], - GotWordlist => events![], + GotNameplates(..) => (self.state, events![]), + ChooseWords(words) => { + let code = format!{"{}-{}", self._nameplate, words}; + (State::S4_done, events![C_FinishedInput(code)]) + } + _ => (self.state, events![]), } } } From 2b8a18a0111a487c18f6795c54729345965499f4 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Wed, 2 May 2018 17:06:36 +0530 Subject: [PATCH 03/19] Temporary fix for now. --- core/src/nameplate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/nameplate.rs b/core/src/nameplate.rs index 1b983dd7..b79f55e2 100644 --- a/core/src/nameplate.rs +++ b/core/src/nameplate.rs @@ -1,4 +1,4 @@ -use events::Events; +use events::{Events, Wordlist}; // we process these use events::NameplateEvent; // we emit these @@ -154,7 +154,7 @@ impl Nameplate { RxClaimed(mailbox) => ( Some(State::S3B(nameplate.to_string())), events![ - I_GotWordlist, // TODO: ->wordlist + I_GotWordlist(Wordlist {}), // TODO: ->Wordlist is just placeholder should use PGPWordList instead I guess M_GotMailbox(mailbox) ], ), From c0749491c93825b2a926310e670be6ab2192b539 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 4 May 2018 16:21:48 +0530 Subject: [PATCH 04/19] Add 2 new events to InputEvents for tab completion --- core/src/events.rs | 5 +++++ core/src/input.rs | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/src/events.rs b/core/src/events.rs index 33766173..5745128a 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -55,6 +55,11 @@ pub enum InputEvent { ChooseNameplate(String), ChooseWords(String), RefreshNameplates, + + // These 2 are specifically for the input helper for generating during tab + // completions + GetNameplateCompletions(String), + GetWordCompletions(String), } #[derive(Debug, PartialEq)] diff --git a/core/src/input.rs b/core/src/input.rs index 0d9e5d9f..b3a55385 100644 --- a/core/src/input.rs +++ b/core/src/input.rs @@ -1,7 +1,8 @@ use events::Events; // we process these -use events::InputEvent::{self, ChooseNameplate, ChooseWords, GotNameplates, - GotWordlist, RefreshNameplates, Start}; +use events::InputEvent::{self, ChooseNameplate, ChooseWords, + GetNameplateCompletions, GetWordCompletions, + GotNameplates, GotWordlist, RefreshNameplates, Start}; // we emit these use events::ListerEvent::Refresh as L_Refresh; use events::CodeEvent::{FinishedInput as C_FinishedInput, @@ -67,6 +68,11 @@ impl Input { ) } RefreshNameplates => (self.state, events![L_Refresh]), + GetNameplateCompletions(prefix) => { + // TODO: How do we send back set of possible nameplates back to + // caller? and do we need to generate any events? + (self.state, events![]) + } _ => (self.state, events![]), } } @@ -87,6 +93,11 @@ impl Input { let code = format!("{}-{}", self._nameplate, words); (State::S4_done, events![C_FinishedInput(code)]) } + GetWordCompletions(prefix) => { + // TODO: We can't do any word completions here we should raise + // error as this should not happen. + (self.state, events![]) + } _ => (self.state, events![]), } } @@ -98,6 +109,11 @@ impl Input { let code = format!{"{}-{}", self._nameplate, words}; (State::S4_done, events![C_FinishedInput(code)]) } + GetWordCompletions(prefix) => { + // TODO: Here we need to use wordlist to create possible set of + // completions based on user input but how do we pass it to user? + (self.state, events![]) + } _ => (self.state, events![]), } } From a56857c6f8be2be9d822255395deb5032298de88 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Mon, 7 May 2018 16:44:46 +0530 Subject: [PATCH 05/19] Introduce InputHelper for allowing clients to query completions - New APIEvent called InputHelperEvent which will be emitted by Input machine when it receives nameplates or wordlist. - New InputHelper object which can process the event and stash the nameplates and wordlist provided to it by Input machine. - InputHelper provides `get_completions` API which can be used by input tab completion interface to get possible word completions and emits events for InputMachine at some point. InputHelper is just a helper module and not a statemachine. --- core/src/events.rs | 19 ++++++++--- core/src/input.rs | 47 ++++++++++----------------- core/src/inputhelper.rs | 71 +++++++++++++++++++++++++++++++++++++++++ core/src/lib.rs | 4 +++ 4 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 core/src/inputhelper.rs diff --git a/core/src/events.rs b/core/src/events.rs index 5745128a..551592fa 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -55,11 +55,15 @@ pub enum InputEvent { ChooseNameplate(String), ChooseWords(String), RefreshNameplates, +} - // These 2 are specifically for the input helper for generating during tab - // completions - GetNameplateCompletions(String), - GetWordCompletions(String), +#[derive(Debug, PartialEq)] +pub enum InputHelperEvent { + RefreshNameplates, + GotNameplates(Vec), + GotWordlist(Wordlist), + ChooseNameplate(String), + ChooseWords(String), } #[derive(Debug, PartialEq)] @@ -165,6 +169,7 @@ pub enum Event { Boss(BossEvent), Code(CodeEvent), Input(InputEvent), + InputHelper(InputHelperEvent), Key(KeyEvent), Lister(ListerEvent), Mailbox(MailboxEvent), @@ -214,6 +219,12 @@ impl From for Event { } } +impl From for Event { + fn from(r: InputHelperEvent) -> Self { + Event::InputHelper(r) + } +} + impl From for Event { fn from(r: KeyEvent) -> Self { Event::Key(r) diff --git a/core/src/input.rs b/core/src/input.rs index b3a55385..9e19f88a 100644 --- a/core/src/input.rs +++ b/core/src/input.rs @@ -1,16 +1,16 @@ use events::Events; // we process these -use events::InputEvent::{self, ChooseNameplate, ChooseWords, - GetNameplateCompletions, GetWordCompletions, - GotNameplates, GotWordlist, RefreshNameplates, Start}; +use events::InputEvent::{self, ChooseNameplate, ChooseWords, GotNameplates, + GotWordlist, RefreshNameplates, Start}; // we emit these use events::ListerEvent::Refresh as L_Refresh; use events::CodeEvent::{FinishedInput as C_FinishedInput, GotNameplate as C_GotNameplate}; +use events::InputHelperEvent::{GotNameplates as IH_GotNameplates, + GotWordlist as IH_GotWordlist}; pub struct Input { state: State, - _all_nameplates: Vec, _nameplate: String, } @@ -27,7 +27,6 @@ impl Input { pub fn new() -> Input { Input { state: State::S0_Idle, - _all_nameplates: Vec::new(), _nameplate: String::new(), } } @@ -56,10 +55,10 @@ impl Input { fn in_typing_nameplate(&mut self, event: InputEvent) -> (State, Events) { match event { - GotNameplates(nameplates) => { - self._all_nameplates = nameplates; - (State::S1_typing_nameplate, events![]) - } + GotNameplates(nameplates) => ( + State::S1_typing_nameplate, + events![IH_GotNameplates(nameplates)], + ), ChooseNameplate(nameplate) => { self._nameplate = nameplate.to_owned(); ( @@ -68,11 +67,6 @@ impl Input { ) } RefreshNameplates => (self.state, events![L_Refresh]), - GetNameplateCompletions(prefix) => { - // TODO: How do we send back set of possible nameplates back to - // caller? and do we need to generate any events? - (self.state, events![]) - } _ => (self.state, events![]), } } @@ -82,22 +76,18 @@ impl Input { event: InputEvent, ) -> (State, Events) { match event { - GotNameplates(nameplates) => { - self._all_nameplates = nameplates; - (State::S2_typing_code_without_wordlist, events![]) - } - GotWordlist(wordlist) => { - (State::S3_typing_code_with_wordlist, events![]) - } + GotNameplates(nameplates) => ( + State::S2_typing_code_without_wordlist, + events![IH_GotNameplates(nameplates)], + ), + GotWordlist(wordlist) => ( + State::S3_typing_code_with_wordlist, + events![IH_GotWordlist(wordlist)], + ), ChooseWords(words) => { let code = format!("{}-{}", self._nameplate, words); (State::S4_done, events![C_FinishedInput(code)]) } - GetWordCompletions(prefix) => { - // TODO: We can't do any word completions here we should raise - // error as this should not happen. - (self.state, events![]) - } _ => (self.state, events![]), } } @@ -109,11 +99,6 @@ impl Input { let code = format!{"{}-{}", self._nameplate, words}; (State::S4_done, events![C_FinishedInput(code)]) } - GetWordCompletions(prefix) => { - // TODO: Here we need to use wordlist to create possible set of - // completions based on user input but how do we pass it to user? - (self.state, events![]) - } _ => (self.state, events![]), } } diff --git a/core/src/inputhelper.rs b/core/src/inputhelper.rs new file mode 100644 index 00000000..5c337587 --- /dev/null +++ b/core/src/inputhelper.rs @@ -0,0 +1,71 @@ +use events::{Events, Wordlist}; +use wordlist::PGPWordlist; +// We process these events +use events::InputHelperEvent::{self, ChooseNameplate, ChooseWords, + GotNameplates, GotWordlist, RefreshNameplates}; +// We emit the following events +use events::InputEvent::{ChooseNameplate as I_ChooseNameplate, + ChooseWords as I_ChooseWords, + RefreshNameplates as I_RefreshNameplates}; + +pub struct InputHelper { + _all_nameplates: Option>, + _wordlist: Option, +} + +impl InputHelper { + pub fn new() -> Self { + InputHelper { + _all_nameplates: None, + _wordlist: None, + } + } + + pub fn process(&mut self, event: InputHelperEvent) -> Events { + match event { + RefreshNameplates => events![I_RefreshNameplates], + GotNameplates(nameplates) => { + self._all_nameplates = Some(nameplates); + events![] + } + GotWordlist(wordlist) => { + self._wordlist = Some(wordlist); + events![] + } + ChooseWords(words) => events![I_ChooseWords(words)], + ChooseNameplate(nameplate) => events![I_ChooseNameplate(nameplate)], + } + } + + fn get_word_completions(&self, prefix: &str) -> HashSet { + let wordlist = PGPWordlist::new(); + wordlist.get_completions(prefix, 2) + } + + pub fn get_completions(&self, prefix: &str) -> (Events, Vec) { + // If we find '-' then there is a nameplate already entered + let got_nameplate = prefix.find('-').is_some(); + + if got_nameplate { + let ns: Vec<&str> = prefix.splitn(1, '-').collect(); + let nameplate = ns[0]; + let words = ns.join(""); + + // We have already the nameplate hence we need to emit event telling + // input machine about nameplate + let completions: Vec = + self.get_word_completions(words).iter().collect(); + ( + events![I_ChooseNameplate(nameplate.to_string())], + completions.iter().map(|w| nameplate + "-" + w).collect(), + ) + } else { + let completions: Vec = self._all_nameplates + .iter() + .filter(|n| n.starts_with(prefix)) + .map(|n| n + "-") + .collect(); + (events![], completions) + } + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 3c62c75d..5cc386b7 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -17,6 +17,7 @@ mod allocator; mod boss; mod code; mod input; +mod inputhelper; mod key; mod lister; mod mailbox; @@ -45,6 +46,7 @@ pub struct WormholeCore { boss: boss::Boss, code: code::Code, input: input::Input, + inputhelper: inputhelper::InputHelper, key: key::Key, lister: lister::Lister, mailbox: mailbox::Mailbox, @@ -75,6 +77,7 @@ impl WormholeCore { boss: boss::Boss::new(), code: code::Code::new(), input: input::Input::new(), + inputhelper: inputhelper::InputHelper::new(), key: key::Key::new(appid, side.as_str()), lister: lister::Lister::new(), mailbox: mailbox::Mailbox::new(&side), @@ -137,6 +140,7 @@ impl WormholeCore { Boss(e) => self.boss.process(e), Code(e) => self.code.process(e), Input(e) => self.input.process(e), + InputHelper(e) => self.inputhelper.process(e), Key(e) => self.key.process(e), Lister(e) => self.lister.process(e), Mailbox(e) => self.mailbox.process(e), From 349e37bed1f4742cff7e856655f6099074b31220 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Mon, 7 May 2018 16:50:43 +0530 Subject: [PATCH 06/19] Provide `get_completions` API in Wormhole object. This API should be used by readline implementations on cli to get possible completions for user input when user hits TAB key. --- core/src/inputhelper.rs | 27 ++++++++++++++++++--------- core/src/lib.rs | 12 ++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/core/src/inputhelper.rs b/core/src/inputhelper.rs index 5c337587..f0dcd58c 100644 --- a/core/src/inputhelper.rs +++ b/core/src/inputhelper.rs @@ -1,5 +1,6 @@ use events::{Events, Wordlist}; use wordlist::PGPWordlist; +use std::collections::HashSet; // We process these events use events::InputHelperEvent::{self, ChooseNameplate, ChooseWords, GotNameplates, GotWordlist, RefreshNameplates}; @@ -53,19 +54,27 @@ impl InputHelper { // We have already the nameplate hence we need to emit event telling // input machine about nameplate - let completions: Vec = - self.get_word_completions(words).iter().collect(); + // let completions: Vec<_> = + // self.get_word_completions(&words).iter().collect(); ( events![I_ChooseNameplate(nameplate.to_string())], - completions.iter().map(|w| nameplate + "-" + w).collect(), + self.get_word_completions(&words) + .iter() + .map(|w| nameplate.to_string() + "-" + w) + .collect(), ) } else { - let completions: Vec = self._all_nameplates - .iter() - .filter(|n| n.starts_with(prefix)) - .map(|n| n + "-") - .collect(); - (events![], completions) + if let Some(ref _all_nameplates) = self._all_nameplates { + let completions: Vec = _all_nameplates + .iter() + .filter(|n| n.starts_with(prefix)) + .map(|n| n.to_string() + "-") + .collect(); + (events![], completions) + } else { + // TODO: might not be correct needs fixing. + (events![I_RefreshNameplates], Vec::new()) + } } } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 5cc386b7..192d7e29 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -118,6 +118,18 @@ impl WormholeCore { Vec::new() } + pub fn get_completions(&mut self, prefix: &str) -> Vec { + // We call inputhelper for name plate completions and then execute the + // event it returned to us. Thus we try to link inputhelper with input + // machine. + let (events, completions) = self.inputhelper.get_completions(prefix); + + // TODO: This should return empty queue if not we are doing something + // wrong here + assert_eq!(self._execute(events).len(), 0); + completions + } + fn _execute(&mut self, events: Events) -> Vec { let mut action_queue: Vec = Vec::new(); // returned let mut event_queue: VecDeque = VecDeque::new(); From 1c6ef490eea012933ee2473de0d2c55c6410c42b Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Mon, 7 May 2018 17:14:23 +0530 Subject: [PATCH 07/19] Just send Start and then RefreshNameplates to Input machine Currently this is fine as if I_Start is got for in wrong state nothing happens we just ignore it in machine. There is no way for helper to know the current state of Input machine for now. --- core/src/inputhelper.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/inputhelper.rs b/core/src/inputhelper.rs index f0dcd58c..e4af5d86 100644 --- a/core/src/inputhelper.rs +++ b/core/src/inputhelper.rs @@ -7,7 +7,8 @@ use events::InputHelperEvent::{self, ChooseNameplate, ChooseWords, // We emit the following events use events::InputEvent::{ChooseNameplate as I_ChooseNameplate, ChooseWords as I_ChooseWords, - RefreshNameplates as I_RefreshNameplates}; + RefreshNameplates as I_RefreshNameplates, + Start as I_Start}; pub struct InputHelper { _all_nameplates: Option>, @@ -73,7 +74,7 @@ impl InputHelper { (events![], completions) } else { // TODO: might not be correct needs fixing. - (events![I_RefreshNameplates], Vec::new()) + (events![I_Start, I_RefreshNameplates], Vec::new()) } } } From 191fc1f224c4992ef7a0c80ca90d4435d310338b Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Tue, 8 May 2018 17:29:09 +0530 Subject: [PATCH 08/19] Add HelperChoseWord event: Invoked when user has input the word --- core/src/api.rs | 1 + core/src/boss.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/core/src/api.rs b/core/src/api.rs index 950a14e2..4c3c5f75 100644 --- a/core/src/api.rs +++ b/core/src/api.rs @@ -4,6 +4,7 @@ pub enum APIEvent { // from application to IO glue to WormholeCore AllocateCode, InputCode, + HelperChoseWord(String), SetCode(String), Close, Send(Vec), diff --git a/core/src/boss.rs b/core/src/boss.rs index a563fdf1..b868638c 100644 --- a/core/src/boss.rs +++ b/core/src/boss.rs @@ -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 { @@ -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), } @@ -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 From 289b9ace296c785e4413b0e0adf138bb9be831cf Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Tue, 8 May 2018 17:29:50 +0530 Subject: [PATCH 09/19] Add rustyline to dev dependency --- core/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Cargo.toml b/core/Cargo.toml index 7ac5463d..6d470441 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,3 +19,4 @@ rustc-serialize = "0.3" [dev-dependencies] ws = "0.7" url = "1.7" +rustyline = "1.0" From d3e8495c78555b7b4221b6ff1293a44c65c412b8 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Tue, 8 May 2018 17:30:07 +0530 Subject: [PATCH 10/19] Sample to check using InputHelper: currently panics Since I'm doing double mutable borrow on refcell this implementation panics on running. --- core/examples/readline.rs | 170 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 core/examples/readline.rs diff --git a/core/examples/readline.rs b/core/examples/readline.rs new file mode 100644 index 00000000..2864fd26 --- /dev/null +++ b/core/examples/readline.rs @@ -0,0 +1,170 @@ +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::cell::{RefCell, RefMut}; +use std::rc::Rc; +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: Rc>, +} + +struct WSHandler { + wsh: WSHandle, + wcr: Rc>, + 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: Rc::clone(&self.wcr), + out: out, + } + } +} + +struct CodeCompleter { + wcr: Rc>, +} + +static BREAK_CHARS: [char; 1] = [' ']; + +impl Completer for CodeCompleter { + fn complete( + &self, + line: &str, + pos: usize, + ) -> rustyline::Result<(usize, Vec)> { + let (start, word) = + extract_word(line, pos, &BREAK_CHARS.iter().cloned().collect()); + let mut wc = self.wcr.borrow_mut(); + let completions = wc.get_completions(word); + Ok((start, completions)) + } +} + +impl ws::Handler for WSHandler { + fn on_open(&mut self, _: ws::Handshake) -> Result<(), ws::Error> { + println!("On_open"); + let mut wc = self.wcr.borrow_mut(); + let actions = wc.do_io(IOEvent::WebSocketConnectionMade(self.wsh)); + // 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 completer = CodeCompleter { + wcr: Rc::clone(&self.wcr), + }; + + 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; + } + + // We got full code lets inform input about it. + let actions = wc.do_api(APIEvent::HelperChoseWord(line)); + process_actions(&self.out, actions); + break; + } + Err(rustyline::error::ReadlineError::Interrupted) => { + println!("Interrupted"); + continue; + } + Err(rustyline::error::ReadlineError::Eof) => { + break; + } + Err(err) => { + println!("Error: {:?}", err); + break; + } + } + } + + Ok(()) + } + + fn on_message(&mut self, msg: ws::Message) -> Result<(), ws::Error> { + println!("got message {}", msg); + let mut wc = self.wcr.borrow_mut(); + let text = msg.as_text()?.to_string(); + let rx = IOEvent::WebSocketMessageReceived(self.wsh, text); + let actions = wc.do_io(rx); + process_actions(&self.out, actions); + Ok(()) + } +} + +fn process_actions(out: &ws::Sender, actions: Vec) { + 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: Rc::new(RefCell::new(wc)), + }; + + let b = ws::Builder::new(); + let mut w1 = b.build(f).unwrap(); + w1.connect(ws_url).unwrap(); + w1.run().unwrap(); +} From 263ea33e4a946c523ea20e8ee28662413ea5ce39 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Wed, 9 May 2018 16:49:37 +0530 Subject: [PATCH 11/19] Make get_completions return actions There is no other way for wormhole to send the actions back to core which happens or tab completions. It has to be taken care by the application to communicate it back to core. --- core/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 192d7e29..5145256e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -118,16 +118,15 @@ impl WormholeCore { Vec::new() } - pub fn get_completions(&mut self, prefix: &str) -> Vec { + pub fn get_completions(&mut self, prefix: &str) -> (Vec, Vec) { // We call inputhelper for name plate completions and then execute the // event it returned to us. Thus we try to link inputhelper with input // machine. let (events, completions) = self.inputhelper.get_completions(prefix); - // TODO: This should return empty queue if not we are doing something - // wrong here - assert_eq!(self._execute(events).len(), 0); - completions + // TODO: We might get some actions how do we communicate it with app? + let actions = self._execute(events); + (actions, completions) } fn _execute(&mut self, events: Events) -> Vec { From 9829bf8cb73311019d14bde2da6a01e843dd68e9 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Wed, 9 May 2018 16:51:34 +0530 Subject: [PATCH 12/19] Make struct implementing Completer handle the actions Mostly TxList and TxClaim may be --- core/examples/readline.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/examples/readline.rs b/core/examples/readline.rs index 2864fd26..85b3ff9e 100644 --- a/core/examples/readline.rs +++ b/core/examples/readline.rs @@ -38,13 +38,14 @@ impl ws::Factory for Factory { } } -struct CodeCompleter { +struct CodeCompleter<'a> { wcr: Rc>, + out: &'a ws::Sender, } static BREAK_CHARS: [char; 1] = [' ']; -impl Completer for CodeCompleter { +impl<'a> Completer for CodeCompleter<'a> { fn complete( &self, line: &str, @@ -53,7 +54,8 @@ impl Completer for CodeCompleter { let (start, word) = extract_word(line, pos, &BREAK_CHARS.iter().cloned().collect()); let mut wc = self.wcr.borrow_mut(); - let completions = wc.get_completions(word); + let (actions, completions) = wc.get_completions(word); + process_actions(&self.out, actions); Ok((start, completions)) } } From d1cf1a6855cdd38e5893c6eef450b5d2a1789043 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Wed, 9 May 2018 16:52:33 +0530 Subject: [PATCH 13/19] Use scoping to handle run time panic on RefCell::borrow_mut By making scopes we are trying to make sure we do not due multiple borrow mut on RefCell. slightly ugly but looks like only possible way. --- core/examples/readline.rs | 77 ++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/core/examples/readline.rs b/core/examples/readline.rs index 85b3ff9e..8816141f 100644 --- a/core/examples/readline.rs +++ b/core/examples/readline.rs @@ -63,46 +63,57 @@ impl<'a> Completer for CodeCompleter<'a> { impl ws::Handler for WSHandler { fn on_open(&mut self, _: ws::Handshake) -> Result<(), ws::Error> { println!("On_open"); - let mut wc = self.wcr.borrow_mut(); - let actions = wc.do_io(IOEvent::WebSocketConnectionMade(self.wsh)); - // 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 mut wc = self.wcr.borrow_mut(); + 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 completer = CodeCompleter { - wcr: Rc::clone(&self.wcr), - }; - - 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 + let mut line_out = String::new(); + { + let completer = CodeCompleter { + wcr: Rc::clone(&self.wcr), + out: &self.out, + }; + + 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; + } + + // We got full code lets inform input about it. + line_out = line.to_string(); + break; + } + Err(rustyline::error::ReadlineError::Interrupted) => { + println!("Interrupted"); continue; } - - // We got full code lets inform input about it. - let actions = wc.do_api(APIEvent::HelperChoseWord(line)); - process_actions(&self.out, actions); - break; - } - Err(rustyline::error::ReadlineError::Interrupted) => { - println!("Interrupted"); - continue; - } - Err(rustyline::error::ReadlineError::Eof) => { - break; - } - Err(err) => { - println!("Error: {:?}", err); - break; + Err(rustyline::error::ReadlineError::Eof) => { + break; + } + Err(err) => { + println!("Error: {:?}", err); + break; + } } } } + let mut wc = self.wcr.borrow_mut(); + let actions = wc.do_api(APIEvent::HelperChoseWord(line_out)); + process_actions(&self.out, actions); + Ok(()) } From af919db89747fa3cb43163ed51408df63bd17a53 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 11 May 2018 14:17:48 +0530 Subject: [PATCH 14/19] Wormhole's get_completions returns vector of actions This is needed as some one needs to process the output from the core else we will be stuck in wormhole. --- core/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 5145256e..6dd58bc2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -118,7 +118,10 @@ impl WormholeCore { Vec::new() } - pub fn get_completions(&mut self, prefix: &str) -> (Vec, Vec) { + pub fn get_completions( + &mut self, + prefix: &str, + ) -> (Vec, Vec) { // We call inputhelper for name plate completions and then execute the // event it returned to us. Thus we try to link inputhelper with input // machine. From e8238ef49d4e9c5ba77148e4ee9f90d70a746ae4 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 11 May 2018 14:32:07 +0530 Subject: [PATCH 15/19] Derive debug and partialeq for APIEvent --- core/src/api.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/api.rs b/core/src/api.rs index 4c3c5f75..b52dbb9d 100644 --- a/core/src/api.rs +++ b/core/src/api.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +#[derive(Debug, PartialEq)] pub enum APIEvent { // from application to IO glue to WormholeCore AllocateCode, From a3470f2419c43bed826b10caeece8e0ba3c223f2 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 11 May 2018 16:10:38 +0530 Subject: [PATCH 16/19] Partially working readline example Currently the co-ordination between inputhelper - wormhole core - and application is partially working. Input completion logic is broken and needs further debugging. --- core/examples/readline.rs | 86 ++++++++++++++++++++++++--------------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/core/examples/readline.rs b/core/examples/readline.rs index 8816141f..b5661d7b 100644 --- a/core/examples/readline.rs +++ b/core/examples/readline.rs @@ -8,8 +8,10 @@ extern crate ws; use magic_wormhole_core::{APIAction, APIEvent, Action, IOAction, IOEvent, TimerHandle, WSHandle, WormholeCore}; -use std::cell::{RefCell, RefMut}; -use std::rc::Rc; +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}; @@ -18,12 +20,12 @@ const APPID: &'static str = "lothar.com/wormhole/text-or-file-xfer"; struct Factory { wsh: WSHandle, - wcr: Rc>, + wcr: Arc>, } struct WSHandler { wsh: WSHandle, - wcr: Rc>, + wcr: Arc>, out: ws::Sender, } @@ -32,20 +34,20 @@ impl ws::Factory for Factory { fn connection_made(&mut self, out: ws::Sender) -> WSHandler { WSHandler { wsh: self.wsh, - wcr: Rc::clone(&self.wcr), + wcr: Arc::clone(&self.wcr), out: out, } } } -struct CodeCompleter<'a> { - wcr: Rc>, - out: &'a ws::Sender, +struct CodeCompleter { + wcr: Arc>, + tx: Sender>, } static BREAK_CHARS: [char; 1] = [' ']; -impl<'a> Completer for CodeCompleter<'a> { +impl Completer for CodeCompleter { fn complete( &self, line: &str, @@ -53,18 +55,20 @@ impl<'a> Completer for CodeCompleter<'a> { ) -> rustyline::Result<(usize, Vec)> { let (start, word) = extract_word(line, pos, &BREAK_CHARS.iter().cloned().collect()); - let mut wc = self.wcr.borrow_mut(); + let mwc = Arc::clone(&self.wcr); + let mut wc = mwc.lock().unwrap(); let (actions, completions) = wc.get_completions(word); - process_actions(&self.out, actions); + 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"); + // println!("On_open"); + let mwc = Arc::clone(&self.wcr); { - let mut wc = self.wcr.borrow_mut(); + let mut wc = mwc.lock().unwrap(); let actions = wc.do_io(IOEvent::WebSocketConnectionMade(self.wsh)); process_actions(&self.out, actions); @@ -74,13 +78,14 @@ impl ws::Handler for WSHandler { process_actions(&self.out, actions); } - let mut line_out = String::new(); - { - let completer = CodeCompleter { - wcr: Rc::clone(&self.wcr), - out: &self.out, - }; + 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 { @@ -91,37 +96,54 @@ impl ws::Handler for WSHandler { continue; } - // We got full code lets inform input about it. - line_out = line.to_string(); + tx.send(line.to_string()).unwrap(); break; } Err(rustyline::error::ReadlineError::Interrupted) => { - println!("Interrupted"); + // println!("Interrupted"); continue; } Err(rustyline::error::ReadlineError::Eof) => { break; } Err(err) => { - println!("Error: {:?}", err); + // println!("Error: {:?}", err); break; } } } - } + }); - let mut wc = self.wcr.borrow_mut(); - let actions = wc.do_api(APIEvent::HelperChoseWord(line_out)); - process_actions(&self.out, actions); + 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 mut wc = self.wcr.borrow_mut(); + // 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(()) @@ -133,14 +155,14 @@ fn process_actions(out: &ws::Sender, actions: Vec) { match a { Action::IO(io) => match io { IOAction::WebSocketSendMessage(_wsh, msg) => { - println!("sending {:?}", msg); + // println!("sending {:?}", msg); out.send(msg).unwrap(); } IOAction::WebSocketClose(_wsh) => { out.close(ws::CloseCode::Normal).unwrap(); } _ => { - println!("action: {:?}", io); + // println!("action: {:?}", io); } }, Action::API(api) => match api { @@ -173,7 +195,7 @@ fn main() { let f = Factory { wsh: wsh, - wcr: Rc::new(RefCell::new(wc)), + wcr: Arc::new(Mutex::new(wc)), }; let b = ws::Builder::new(); From 8b95145b45f6ef24c352b09bbf4f0e64fa5febb8 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 11 May 2018 16:53:02 +0530 Subject: [PATCH 17/19] Fix typo in state name, not sure how it compiled --- core/src/input.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/input.rs b/core/src/input.rs index 9e19f88a..bfe7a4c9 100644 --- a/core/src/input.rs +++ b/core/src/input.rs @@ -32,14 +32,15 @@ impl Input { } pub fn process(&mut self, event: InputEvent) -> Events { + use self::State::*; let (newstate, actions) = match self.state { - S0_idle => self.in_idle(event), + S0_Idle => self.in_idle(event), S1_typing_nameplate => self.in_typing_nameplate(event), S2_typing_code_without_wordlist => { self.in_type_without_wordlist(event) } S3_typing_code_with_wordlist => self.in_type_with_wordlist(event), - S4_done => (self.state, events![]), + State::S4_done => (self.state, events![]), }; self.state = newstate; From 123fa95adbf6c34e4cbe9d4e1bce337dd728d104 Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 11 May 2018 16:53:45 +0530 Subject: [PATCH 18/19] Process GotNameplates even in S3_typing_with_wordlist? --- core/src/input.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/input.rs b/core/src/input.rs index bfe7a4c9..8c8a7e78 100644 --- a/core/src/input.rs +++ b/core/src/input.rs @@ -95,7 +95,9 @@ impl Input { fn in_type_with_wordlist(&mut self, event: InputEvent) -> (State, Events) { match event { - GotNameplates(..) => (self.state, events![]), + GotNameplates(nameplates) => { + (self.state, events![IH_GotNameplates(nameplates)]) + } ChooseWords(words) => { let code = format!{"{}-{}", self._nameplate, words}; (State::S4_done, events![C_FinishedInput(code)]) From e5599612324e2c8faf07f31af6bd2dc00cbbe0ac Mon Sep 17 00:00:00 2001 From: Vasudev Kamath Date: Fri, 11 May 2018 17:37:46 +0530 Subject: [PATCH 19/19] Use splitn with 2 to get correct split. (hopefully). --- core/src/inputhelper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/inputhelper.rs b/core/src/inputhelper.rs index e4af5d86..47061048 100644 --- a/core/src/inputhelper.rs +++ b/core/src/inputhelper.rs @@ -49,7 +49,7 @@ impl InputHelper { let got_nameplate = prefix.find('-').is_some(); if got_nameplate { - let ns: Vec<&str> = prefix.splitn(1, '-').collect(); + let ns: Vec<&str> = prefix.splitn(2, '-').collect(); let nameplate = ns[0]; let words = ns.join("");