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")?;
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::*;
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")?;
<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(
},
}
+#[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 { },
// 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)]
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),
#[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);
}
}