chiark / gitweb /
wip new game joining, remove players
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 8 Nov 2020 13:10:48 +0000 (13:10 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 8 Nov 2020 13:10:48 +0000 (13:10 +0000)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/accounts.rs
src/bin/otter.rs
src/cmdlistener.rs
src/global.rs
src/imports.rs
src/spec.rs

index e87cb490e95048880239fd300ec739b6fccc73b5..fe017f5d3cbd98f2bec480cc16b2adefeba0e0a8 100644 (file)
@@ -426,7 +426,7 @@ pub mod loaded_acl {
   pub struct PermSet<P: Perm> (u64, PhantomData<&'static P>);
 
   #[derive(Debug,Clone)]
-  pub struct EffectiveAcl<'i, P: Perm> {
+  pub struct EffectiveACL<'i, P: Perm> {
     pub owner_account: Option<&'i str>,
     pub acl: &'i LoadedAcl<P>,
   }
@@ -457,7 +457,7 @@ pub mod loaded_acl {
     ptype: PhantomData<&'static P>,
   }
 
-  impl<'e, P:Perm> EffectiveAcl<'e, P> {
+  impl<'e, P:Perm> EffectiveACL<'e, P> {
     fn entries(&self) -> impl Iterator<Item=AclEntryRef<'_, P>> {
       self.owner_account.iter().map(
         |owner|
index cff7739d2d65250002e1048579783b05f4a50b43..face07adfe969f67e94897ed54bcd30bf118884e 100644 (file)
@@ -126,6 +126,8 @@ fn parse_args<T:Default,U>(
   completed
 }
 
+pub fn ok_id<T,E>(t: T) -> Result<T,E> { Ok(t) }
+
 fn main() {
   #[derive(Default,Debug)]
   struct RawMainArgs {
@@ -365,8 +367,8 @@ const PLAYER_DEFAULT_PERMS : &[TablePermission] = &[
   TP::ChangePieces,
 ];
 
-fn setup_table(ma: &MainOpts, chan: &mut ConnForGame,
-               spec: &TableSpec, purge_old_players: bool) -> Result<(),AE> {
+#[throws(AE)]
+fn setup_table(ma: &MainOpts, spec: &TableSpec) -> Vec<Insn> {
   let TableSpec { players, player_perms, acl } = spec;
   let mut player_perms = player_perms.clone()
     .unwrap_or(PLAYER_DEFAULT_PERMS.iter().cloned().collect());
@@ -388,6 +390,10 @@ fn setup_table(ma: &MainOpts, chan: &mut ConnForGame,
   let mut insns = vec![];
   insns.push(Insn::ClearLog);
   insns.push(Insn::SetACL { acl });
+  insns
+}
+
+
 
 /*
 
@@ -452,8 +458,6 @@ fn setup_table(ma: &MainOpts, chan: &mut ConnForGame,
     }
   }
 */
-  Ok(())
-}
 
 #[throws(AE)]
 fn read_spec<T: DeserializeOwned>(filename: &str, what: &str) -> T {
@@ -489,22 +493,19 @@ mod create_table {
 
   #[throws(E)]
   fn call(_sc: &Subcommand, ma: MainOpts, args: Vec<String>) {
-    let args = parse_args::<Args,_>(args, &subargs, None, None);
+    let args = parse_args::<Args,_>(args, &subargs, &ok_id, None);
     let spec : TableSpec = read_spec(&args.file, "table spec")?;
     let mut chan = connect(&ma)?;
+    let insns = setup_table(&ma, &spec)?;
 
     chan.cmd(&MgmtCommand::CreateGame {
-      name: args.name.clone(), insns: vec![]
+      insns,
+      game: InstanceName {
+        game: args.name.clone(),
+        account: ma.account.clone(),
+      },
     })?;
 
-    let mut chan = ConnForGame {
-      conn: chan,
-      name: args.name.clone(),
-      how: MgmtGameUpdateMode::Bulk,
-    };
-
-    setup_table(&ma, &mut chan, &spec)?;
-
     if ma.verbose >= 0 {
       eprintln!("create-table successful.  game still needs setup.");
     }
@@ -563,8 +564,8 @@ mod reset_game {
         Err(e)
       })?;
 
-      setup_table(&ma, &mut chan, &table_spec)?;
-    }
+      setup_table(&ma, &table_spec)?; 
+   }
 
     let mut insns = vec![];
 
index cc1454f159c3559e926f9b2397b12d54560e0091..a904232f417d2bcae7950f041b4264efa5b474c9 100644 (file)
@@ -300,7 +300,7 @@ fn execute_game_insn<'cs, 'igr, 'ig : 'igr>(
     } => {
       let account = &cs.current_account()?.notional_account;
       let (_arecord, acctid) = ag.lookup(account)?;
-      let (ig, auth) = cs.check_acl(ag, ig, PCH::Instance, &[TP::AddPlayer])?;
+      let (ig, auth) = cs.check_acl(ag, ig, PCH::Instance, &[TP::Play])?;
       let nick = nick.ok_or(ME::ParameterMissing)?;
       let logentry = LogEntry {
         html: Html(format!("{} ({}) joined the game",
@@ -352,23 +352,6 @@ fn execute_game_insn<'cs, 'igr, 'ig : 'igr>(
       Ok(Resp::Pieces(pieces))
     })?,
 
-    RemovePlayer { player } => {
-      let ig = cs.check_acl_modify_player(ag, ig, player,
-                                          &[TP::RemovePlayer])?.0;
-      let (gpl, _ipl) = ig.player_remove(player)?;
-      let show = if let Some(gpl) = gpl {
-        htmlescape::encode_minimal(&gpl.nick)
-      } else {
-        "<i>partial data?!</i>".to_string()
-      };
-      (U{ pcs: vec![],
-          log: vec![ LogEntry {
-            html: Html(format!("{} removed a player: {}", &who, &show)),
-          }],
-          raw: None},
-       Fine, ig)
-    },
-
     UpdatePlayer {
       player,
       details: MgmtPlayerDetails { nick, timezone },
@@ -518,11 +501,50 @@ fn execute_game_insn<'cs, 'igr, 'ig : 'igr>(
       let ag = AccountsGuard::lock();
       let (ig, _) = cs.check_acl(&ag, ig, PCH::Instance, &[TP::Super])?;
       ig.acl = acl.into();
+      let mut log = vec![ LogEntry {
+        html: Html(format!("{} set the table access control list",
+                           &who)),
+      } ];
+
+      let eacl = EffectiveACL {
+        owner_account: &ig.account,
+        acl: &ig.acl,
+      };
+
+      #[throws(InternalError)]
+      fn remove_old_players(ag: &AccountsGuard, ig: &mut InstanceGuard,
+                            who: &str, eacl: &EffectiveACL<'_, TP>,
+                            log: &mut Vec<LogEntry>) {
+        let mut remove = vec![];
+        for (player, ipr) in ig.players {
+          if_chain! {
+            let acctid = ipr.ipl.acctid;
+            if let Some(record,_) = ag.lookup(acctid);
+            if let Ok(_) = eacl.check(&*record.name, &[TP::Play]);
+            then {
+              /* ok */
+            }
+            else {
+              remove.push(player);
+            }
+          };
+        };
+        for (gpl, _ipl) in ig.players_remove(&remove)? {
+          let show = if let Some(gpl) = gpl {
+            htmlescape::encode_minimal(&gpl.nick)
+          } else {
+            "<i>partial data?!</i>".to_string()
+          };
+          log.push(LogEntry {
+            html: Html(format!("{} removed a player {}", &who, &show)),
+          });
+        }
+      }
+
+      remove_old_players(ag, ig, &who, &eacl, &mut log)?;
+
       (U{ pcs: vec![ ],
-          log: vec![ LogEntry {
-            html: Html(format!("{} set the table access control list",
-                               &who)),
-          } ],
+          log,
           raw: None },
        Fine, ig)
     },
@@ -878,7 +900,7 @@ impl CommandStream<'_> {
         };
         let owner_account = owner.to_string();
         let owner_account = Some(owner_account.as_str());
-        let eacl = EffectiveAcl { owner_account, acl };
+        let eacl = EffectiveACL { owner_account, acl };
         eacl.check(subject, p)?
       };
       auth
index db86fe56a0fb64eab1c84026ffc676efac99c1b6..834f58562e09e7927a4967dc2725ceb2174f3411 100644 (file)
@@ -499,18 +499,21 @@ impl<'ig> InstanceGuard<'ig> {
     self.tokens_deregister_for_id(|id| clients_to_remove.contains(&id));
   }
 
-  //  #[throws(ServerFailure)]
+  //  #[throws(InternalError)]
   //  https://github.com/withoutboats/fehler/issues/62
-  pub fn player_remove(&mut self, oldplayer: PlayerId)
-                       -> Result<(Option<GPlayerState>, Option<IPlayerState>),
-                                 InternalError> {
+  pub fn players_remove(&mut self, oldplayers: HashSet<PlayerId>)
+                        ->
+    Result<Vec<
+        (Option<GPlayerState>, Option<IPlayerState>)
+        >, InternalError>
+  {
     // We have to filter this player out of everything
     // Then save
     // Then send updates
     // We make a copy so if the save fails, we can put everything back
 
     let mut players = self.c.g.gs.players.clone();
-    let old_gpl = players.remove(oldplayer);
+    let old_gpl = players.remove(oldplayers);
 
     // New state
     let mut gs = GameState {
@@ -530,16 +533,23 @@ impl<'ig> InstanceGuard<'ig> {
     // after all the things it will reference
     let mut undo : Vec<Box<dyn FnOnce(&mut InstanceGuard)>> = vec![];
 
+    let held_by_old = |p: PieceState| if_chain! {
+      if let Some(held) = p.held;
+      if oldplayers.contains(held);
+      then { true }
+      else { false }
+    };
+
     // Arrange gs.pieces
     for (piece,p) in &mut self.c.g.gs.pieces {
-      if p.held == Some(oldplayer) {
+      if held_by_old(p) {
         p.held = None;
         updated_pieces.push(piece);
       }
     }
     undo.push(Box::new(|ig| for &piece in &updated_pieces {
       (||Some({
-        ig.c.g.gs.pieces.get_mut(piece)?.held = Some(oldplayer);
+        held_by_old(ig.c.g.gs.pieces.get_mut(piece)?);
       }))();
     }));
 
@@ -578,9 +588,9 @@ impl<'ig> InstanceGuard<'ig> {
       }
       buf.finish();
 
-      self.remove_clients(oldplayer, ErrorSignaledViaUpdate::PlayerRemoved);
-      self.tokens_deregister_for_id(|id:PlayerId| id==oldplayer);
-      let iplayer = self.iplayers.remove(oldplayer);
+      self.remove_clients(oldplayers, ErrorSignaledViaUpdate::PlayerRemoved);
+      self.tokens_deregister_for_id(|id:PlayerId| oldplayers.contains(id));
+      let iplayer = self.iplayers.remove(oldplayers);
       let ipl = iplayer.map(|iplayer| iplayer.ipl);
       self.save_access_now().unwrap_or_else(
         |e| warn!(
index a673c5d6d129a2dac35166d65bcb3f6d4483791e..68cdfeac47541d4f505c5ac880f9bc23900ecfca 100644 (file)
@@ -128,7 +128,7 @@ pub use crate::debugreader::DebugReader;
 pub use crate::shapelib;
 pub use crate::tz::*;
 pub use crate::accounts::*;
-pub use crate::accounts::loaded_acl::{self,LoadedAcl,EffectiveAcl,PermSet};
+pub use crate::accounts::loaded_acl::{self,LoadedAcl,EffectiveACL,PermSet};
 
 pub use zcoord::{self, ZCoord};
 
index 04c2f1e7feb3264c4e2331e3dfcd88fbb30f2055..0f8ffdc1f40af4d8cdbb44da6ed234eda6541560 100644 (file)
@@ -94,12 +94,11 @@ pub struct AclEntry<Perm: Eq + Hash> {
 pub enum TablePermission {
   TestExistence,
   ViewPublic,
-  AddPlayer,
+  Play,
   ChangePieces,
   ResetOthersAccess,
   RedeliverOthersAccess,
   ModifyOtherPlayer,
-  RemovePlayer,
   Super,
 }