chiark / gitweb /
wip otter create table
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 11:55:52 +0000 (12:55 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 11:55:52 +0000 (12:55 +0100)
src/bin/otter.rs
src/cmdlistener.rs
src/commands.rs
src/spec.rs

index 1eaa972d6ec6be8c734c55b794c9a6aae8654239..b6ce9227fabb7d791f08215e309b9250a19ee076 100644 (file)
@@ -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<MgmtResponse,AE>;
+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<MgmtGameInstructions>,
+                f: &mut dyn FnMut(&MgmtGameInstruction, &MgmtGameResult)
+                                  -> Result<AE>)
+                     -> Vec<MgmtGameResult>> {
+    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<String,PlayerId>) {
+    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<String>) {
     let args = parse_args::<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 {
       <Result<_,AE>>::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(
index 9db49c2bec78ceaa95b548799cb17a2217997012..50d740e60ab732dc3c19e53d68af08468972dca8 100644 (file)
@@ -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"),
index d11bcf510fd0126fa50b82acfedb0f204d229ccf..27c92574e6892bfbf6d4640641ed35d39a73c799 100644 (file)
@@ -13,6 +13,15 @@ pub enum MgmtCommand {
   },
 }
 
+#[derive(Debug,Serialize,Deserialize)]
+pub enum MgmtResponse {
+  Fine { },
+  Error { error: MgmtError },
+  AlterGame { error: Option<MgmtError>, results: Vec<MgmtGameResult> },
+  GamesList { games: Vec<Arc<InstanceName>> },
+  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<PlayerId> },
   ReportPlayerAccesses { players: Vec<PlayerId> },
+  SetFixedPlayerAccess { player: PlayerId, token: RawToken },
 }
 
 #[derive(Debug,Serialize,Deserialize)]
-pub enum MgmtResponse {
-  Fine { },
-  Error { error: MgmtError },
-  AlterGame { error: Option<MgmtError>, results: Vec<MgmtGameResult> },
-  GamesList { games: Vec<Arc<InstanceName>> },
+pub enum MgmtGameUpdateMode {
+  Online,
+  Bulk,
 }
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -39,12 +48,6 @@ pub enum MgmtGameResult {
   PlayerAccessTokens { tokens: Vec<Vec<RawToken>> },
 }
 
-#[derive(Debug,Serialize,Deserialize)]
-pub enum MgmtGameUpdateMode {
-  Online,
-  Bulk,
-}
-
 #[derive(Debug,Clone,Error,Serialize,Deserialize)]
 pub enum MgmtError {
   ParseFailed(String),
index d6fae933a1cdcb6ff04f4372a328e657d6b09300..10a9aba3a077cd2c3f04dbac40dc48839f3ad47c 100644 (file)
@@ -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<MgmtGameInstructions> {
+    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<MgmtGameInstructions> {
+    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);
   }
 }