From feb96ff969f55f772d5f22ec776d993c755bc5be Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sat, 22 Aug 2020 12:55:52 +0100 Subject: [PATCH] wip otter create table --- src/bin/otter.rs | 172 +++++++++++++++++++++++++++++++++++++++------ src/cmdlistener.rs | 1 + src/commands.rs | 25 ++++--- src/spec.rs | 31 ++++++-- 4 files changed, 190 insertions(+), 39 deletions(-) diff --git a/src/bin/otter.rs b/src/bin/otter.rs index 1eaa972d..b6ce9227 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -178,28 +178,90 @@ fn main() { call(sc, ma.opts, subargs).expect("execution error"); } -type Conn = MgmtChannel; - -trait ConnExt { - fn cmd(&mut self, cmd: &MgmtCommand) -> Result; +struct Conn { + mc: MgmtChannel, } -impl ConnExt for Conn { + +impl Conn { #[throws(AE)] fn cmd(&mut self, cmd: &MgmtCommand) -> MgmtResponse { use MgmtResponse::*; - self.write(&cmd).context("send command")?; - let resp = self.read().context("read response")?; + self.mc.write(&cmd).context("send command")?; + let resp = self.mc.read().context("read response")?; match &resp { Fine{..} | GamesList{..} => { }, AlterGame { error: None, .. } => { }, - Error { error } | AlterGame { error: Some(error), .. } => { - Err(error.clone()).context(format!("response to: {:?}",cmd))?; + Error { error } => { + Err(error.clone()).context(format!("response to: {:?}",&cmd))?; + }, + AlterGame { error: Some(error), .. } => { + Err(error.clone()).context(format!( + "game alternations failed (maybe partially); response to: {:?}", + &cmd))?; }, }; resp } } +struct ConnForGame { + pub conn: Conn, + pub name: String, + pub how: MgmtGameUpdateMode, +}; +impl Deref for ConnForGame { + type Target = Conn; + fn deref(cg: &ConnForGame) -> Conn { &cg.conn } +} +impl DerefMut for ConnForGame { + fn deref_mut(cg: &mut ConnForGame) -> Conn { &mut cg.conn } +} + +impl ConnForGame { + #[throws(AE)] + fn alter_game(&mut self, insns: Vec, + f: &mut dyn FnMut(&MgmtGameInstruction, &MgmtGameResult) + -> Result) + -> Vec> { + let cmd = MgmtCommand::AlterGame { + name: self.name.clone(), how: self.how, + insns, + }; + let results = match self.cmd(&cmd) { + MgmtResponse::AlterGame { error: None, results } + if results.len() == cmd.insns.len() + => results, + wat => anyhow!("unexpected AlterGame response: {:?} => {:?}", + &cmd, &wat), + }; + for (insn, result) in insns.iter.zip(results.iter()) { + f(insn,result)?; + } + results + } + + #[throws(AE)] + fn game_state(&mut self) -> (GameState, HashMap) { + let gs = self.alter_game( + vec![ MgmtGameInstruction::GetState { } ], + |..|Ok(()) + )?; + let gs = match gs { + &[ GameState { gs } ] => gs, + wat => anyhow!("GetGstate got {:?}", &wat); + }; + let mut nick2id = HashMap::new(); + for (player, pstate) in &gs.players { + match nick2id.entry(pstate.nick.clone()) { + Occupied(oe) => anyhow!("game has duplicate nick {:?}, {} {}", + &nick, *oe.get(), player), + Vacant(ve) ve.insert(player), + } + } + (gs, nick2id) + } +} + #[throws(E)] fn connect(ma: &MainOpts) -> MgmtChannel { let unix = UnixStream::connect(SOCKET_PATH).context("connect to server")?; @@ -208,6 +270,73 @@ fn connect(ma: &MainOpts) -> MgmtChannel { chan } +#[throws(E)] +fn setup_table(game: &mut ConnForGame, spec: &TableSpec) { + // create missing players + let (added_players,) = { + let (gs, nick2id) = chan.game_state()?; + + let mut inss = vec![]; + for psec in &spec.players { + if !nick2id.has_key(pspec.nick) { + insns.push(AddPlayer { nick: ps.nick }); + } + } + let mut added_players = HashSet::new(); + chan.alter_game(name, insns, |insn,result| { + let player = match result { + MgmtGameInstruction::AddPlayer { player } => player, + _ => anyhow!("AddPlayer strange answer: {:?} => {:?}",&insn,&result), + }; + added_players.insert(player); + Ok(()) + })?; + + (added_players,) + }; + + // ensure players have access tokens + { + let (gs, nick2id) = chan.game_state()?; + let mut insns = vec![]; + let mut resetreport = vec![]; + let mut resetspecs = vec![]; + for pspec in &spec.players { + let player = nick2id.get(&pspec.nick) + .ok_or_else(anyhow!("player {:?} vanished or renamed!", + &pspec.nick))?; + match pspec.access.token_mgi(player) { + Some(insn) => insns.push(insn), + None if added_players.contains(player) => { + resetreport.push(player); + resetspecs.push(player); + }, + None => (), + }; + } + insns.push(MgmtGameInstruction::ResetPlayerAccesses { + players: resetreport.clone(), + }); + insns.push(MgmtGameInstruction::ReporPlayerAccesses { + players: resetreport.clone(), + }); + let got_tokens = None; + chan.alter_game(insns, &mut |insn,result| { + if let PlayerAccessTokens { tokens } = result { + got_tokens = Some(tokens.clone()), + } + }); + let got_tokens = match got_tokens { + Some(t) if t.len() == resetreport.len() => t, + wat => anyhow!("Did not get expected ReportPlayerAccesses! {:?}", &wat)?, + }; + for (pspec, ptokens) in resetspecs.iter().zip(got_tokens.iter()) { + pspec.access.deliver_tokens(&pspec, &ptokens) + .context("deliver tokens for nick={:?}", &pspec.nick)?; + } + } +} + mod create_table { use super::*; @@ -234,7 +363,9 @@ mod create_table { fn call(_sc: &Subcommand, ma: MainOpts, args: Vec) { let args = parse_args::(args, &subargs, &complete, None); - let _spec = (||{ + eprintln!("CREATE-TABLE {:?} {:?}", &ma, &args); + + let spec = (||{ let mut f = File::open(&args.file).context("open")?; let mut buf = String::new(); f.read_to_string(&mut buf).context("read")?; @@ -242,20 +373,19 @@ mod create_table { >::Ok(spec) })().with_context(|| args.file.to_owned()).context("read game spec")?; - let _chan = connect(&ma)?; - - /* + chan.cmd(&MgmtCommand::CreateGame { + name: args.name.clone(), insns: vec![] + })?; - chan.cmd(MgmtCommand::CreateGame { - CreateGame { - name: args.name, - insns: vec![ - MgmtGameInstruction { + let chan = ConnForGame { + conn: chan, + name: args.name.clone(), + how: MgmtGameUpdateMode::Bulk, + }; - }, - ]*/ + setup_table(&chan &spec.table)?; - eprintln!("CREATE-TABLE {:?} {:?}", &ma, &args); + eprintln!("CREATE-TABLE DID SETUP_TABLE NEEDS GAMESPEC", &ma, &args); } inventory::submit!{Subcommand( diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index 9db49c2b..50d740e6 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -373,6 +373,7 @@ fn execute_game_insn(ig: &mut InstanceGuard, update: MgmtGameInstruction) MgmtGameInstruction::AddPlayer(pl) => { let player = ig.player_new(pl)?; #[allow(clippy::useless_format)] // xxx below + // xxx check for duplicate players (vec![], vec![ LogEntry { html: format!("The facilitator added a player xxx"), diff --git a/src/commands.rs b/src/commands.rs index d11bcf51..27c92574 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -13,6 +13,15 @@ pub enum MgmtCommand { }, } +#[derive(Debug,Serialize,Deserialize)] +pub enum MgmtResponse { + Fine { }, + Error { error: MgmtError }, + AlterGame { error: Option, results: Vec }, + GamesList { games: Vec> }, + GameState { gs: GameState }, +} + #[derive(Debug,Serialize,Deserialize)] pub enum MgmtGameInstruction { Noop { }, @@ -20,16 +29,16 @@ pub enum MgmtGameInstruction { // todo: RemovePiece AddPlayer(PlayerState), RemovePlayer(PlayerId), + GetState { }, ResetPlayerAccesses { players: Vec }, ReportPlayerAccesses { players: Vec }, + SetFixedPlayerAccess { player: PlayerId, token: RawToken }, } #[derive(Debug,Serialize,Deserialize)] -pub enum MgmtResponse { - Fine { }, - Error { error: MgmtError }, - AlterGame { error: Option, results: Vec }, - GamesList { games: Vec> }, +pub enum MgmtGameUpdateMode { + Online, + Bulk, } #[derive(Debug,Serialize,Deserialize)] @@ -39,12 +48,6 @@ pub enum MgmtGameResult { PlayerAccessTokens { tokens: Vec> }, } -#[derive(Debug,Serialize,Deserialize)] -pub enum MgmtGameUpdateMode { - Online, - Bulk, -} - #[derive(Debug,Clone,Error,Serialize,Deserialize)] pub enum MgmtError { ParseFailed(String), diff --git a/src/spec.rs b/src/spec.rs index d6fae933..10a9aba3 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -33,17 +33,34 @@ pub struct PiecesSpec { #[typetag::serde(tag="access")] pub trait PlayerAccessSpec : Debug { - fn deliver_token_client(&self, conn: &mut ()/*xxx*/, nick: &str) - -> Result<(),anyhow::Error>; + fn token_mgi(&self, player: PlayerId) -> Option { + None + } + #[throws(AE)] + fn deliver_token(&self, ps: &PlayerSpec, tokens: &[RawToken]) { } +} + +#[derive(Debug,Serialize,Deserialize)] +struct FixedToken(RawToken); + +#[typetag::serde] +impl PlayerAccessSpec for FixedToken { + fn token_mgi(&self, player: PlayerId) -> Option { + Some(MgmtGmmeInstruction::SetFixedPlayerAccess { + player, + token: self.0.clone(), + }) + } } #[derive(Debug,Serialize,Deserialize)] -struct UrlOnStdout; +struct TokenOnStdout; #[typetag::serde] -impl PlayerAccessSpec for UrlOnStdout { - fn deliver_token_client(&self, _conn: &mut (), _nick: &str) - -> Result<(),anyhow::Error> { - todo!() +impl PlayerAccessSpec for FixedToken { + #[throws(AE)] + fn deliver_tokens(&self, ps: &PlayerSpec, token: &[RawToken]) { + -> Result<(),anyhow::Error> { + println!("access nick={:?} token={}", &ps.nick, token); } } -- 2.30.2