match cmd {
Noop => Fine,
- SetScope(wanted_scope) => {
+ SetAccount(wanted_account) => {
let authorised : AuthorisedSatisfactory =
- authorise_scope(cs, &wanted_scope)?;
- cs.scope = Some(authorised.into_inner());
+ authorise_scope(cs, &wanted_account.scope)?;
+ cs.account = ScopedName {
+ scope: Some(authorised.into_inner()),
+ scoped_nmae: wanted_account.scoped_name,
+ };
Fine
},
Resp::Info(info)
}),
- ResetPlayerAccesses { players } => {
- let tokens = ig.players_access_reset(&players)?
- .drain(0..).map(|token| vec![token]).collect();
+ ResetPlayerAccess(player) => {
+ let token = ig.players_access_reset(player)?;
(U{ pcs: vec![],
log: vec![],
raw: None },
- PlayerAccessTokens(tokens))
+ PlayerAccessToken(token))
}
- ReportPlayerAccesses { players } => {
- let tokens = ig.players_access_report(&players)?;
+ RedeliverPlayerAccess(player) => {
+ let token = ig.players_access_redeliver(player)?;
(U{ pcs: vec![],
log: vec![],
raw: None },
- PlayerAccessTokens(tokens))
+ PlayerAccessToken(token))
},
-
+/*
SetFixedPlayerAccess { player, token } => {
let authorised : AuthorisedSatisfactory =
authorise_scope(cs, &AS::Server)?;
raw: None},
Fine)
}
+*/
DeletePiece(piece) => {
let modperm = ig.modify_pieces();
struct CommandStream<'d> {
euid : Result<Uid, ConnectionEuidDiscoverEerror>,
desc : &'d str,
- scope : Option<AccountScope>,
+ account : Option<AccountName>,
chan : MgmtChannel,
who: Who,
}
pub struct Instance {
pub name : Arc<InstanceName>,
pub gs : GameState,
- pub pieces : PiecesLoaded,
+ pub ipieces : PiecesLoaded,
pub clients : DenseSlotMap<ClientId,Client>,
- pub players : SecondarySlotMap<PlayerId, PlayerRecord>,
+ pub iplayers : SecondarySlotMap<PlayerId, PlayerRecord>,
pub tokens_players : TokenRegistry<PlayerId>,
pub tokens_clients : TokenRegistry<ClientId>,
}
pub struct PlayerRecord {
pub u: PlayerUpdates,
- pub st: PlayerState,
+ pub pst: PlayerState,
}
+#[derive(Debug,Serialize,Deserialize)]
pub struct PlayerState {
pub account: AccountName,
pub nick: String,
pub tz: Timezone,
- pub tokens_revealed: Vec<String>,
}
#[derive(Debug,Serialize,Deserialize)]
struct InstanceSaveAccesses<RawTokenStr, PiecesLoadedRef> {
pieces: PiecesLoadedRef,
tokens_players: Vec<(RawTokenStr, PlayerId)>,
- aplayers: SecondarySlotMap<PlayerId, PlayerSaveAccess>,
-}
-
-#[derive(Debug,Default,Serialize,Deserialize)]
-struct PlayerSaveAccess {
- tz: Timezone,
+ aplayers: SecondarySlotMap<PlayerId, PlayerState>,
}
display_as_debug!{InstanceLockError}
let g = Instance {
name : name.clone(),
gs,
- pieces : PiecesLoaded(Default::default()),
+ ipieces : PiecesLoaded(Default::default()),
clients : Default::default(),
- updates : Default::default(),
+ iplayers : Default::default(),
tokens_players : Default::default(),
tokens_clients : Default::default(),
};
logentry: LogEntry) -> (PlayerId, LogEntry) {
// saving is fallible, but we can't attempt to save unless
// we have a thing to serialise with the player in it
- if self.c.g.gs.players.values().any(|pl| pl.nick == newplayer.nick) {
+ if self.c.g.gs.players.values().any(|a| a == &newplayer.account) {
Err(MgmtError::AlreadyExists)?;
}
- let player = self.c.g.gs.players.insert(newplayer);
- self.save_game_now().map_err(|e|{
+ if self.c.g.iplayers.values().any(|pl| pl.pst.nick == newplayer.nick) {
+ Err(MgmtError::NickCollision)?;
+ }
+ let player = self.c.g.gs.players.insert(newplayer.account.clone());
+ let u = PlayerUpdates::new_begin(&self.c.g.gs).new();
+ let record = PlayerRecord { u, pst: newplayer };
+ self.c.g.iplayers.insert(player, record);
+
+ (||{
+ self.save_game_now()?;
+ self.save_access_now()?;
+ Ok(())
+ })().map_err(|e|{
+ self.c.g.iplayers.remove(player);
self.c.g.gs.players.remove(player);
+ // Perhaps we leave the g-* file with this player recorded,
+ // but this will be ignored when we load.
e
})?;
(||{
- let pu_bc = PlayerUpdates::new_begin(&self.c.g.gs);
- self.c.g.updates.insert(player, pu_bc.new(tz));
+
})(); // <- No ?, ensures that IEFE is infallible (barring panics)
(player, logentry)
}
if remove { clients_to_remove.insert(k); }
!remove
});
- if let Some(mut updates) = self.updates.remove(oldplayer) {
- updates. push(PreparedUpdate {
+ if let Some(PlayerRecord { u: mut updates, .. })
+ = self.iplayers.remove(oldplayer) {
+ updates.push(PreparedUpdate {
gen: self.c.g.gs.gen,
when: Instant::now(),
us : vec![ PreparedUpdateEntry::Error(
player: PlayerId, token: RawToken,
_safe: Authorised<RawToken>
) {
+ // xxx call this function when access changes
+
self.tokens_deregister_for_id(|id:PlayerId| id==player);
let iad = InstanceAccessDetails {
gref : self.gref.clone(),
}
#[throws(MgmtError)]
- pub fn players_access_reset(&mut self, players: &[PlayerId])
- -> Vec<RawToken> {
- // xxx disconnect everyone when token changes
- for &player in players {
- self.c.g.gs.players.get(player).ok_or(MgmtError::PlayerNotFound)?;
- }
- self.save_access_now()?;
- let mut tokens = vec![];
- for &player in players {
- let iad = InstanceAccessDetails {
- gref : self.gref.clone(),
- ident : player
- };
- let token = RawToken::new_random();
- self.token_register(token.clone(), iad);
- tokens.push(token);
- }
+ pub fn player_access_reset(&mut self, player: PlayerId)
+ -> Option<AccessTokenReport> {
+ let pst = self.c.g.players.get(player)
+ .ok_or(MgmtError::PlayerNotFound)?
+ .pst;
self.save_access_now()?;
- // If the save fails, we don't return the token so no-one can use
- // it. Therefore we don't need to bother deleting it.
- tokens
+
+ let access = {
+ let acct = AccountRecord::lookup_mut(&pst.account)?
+ .ok_or(MgmtError::AccountNotFound)?;
+ let access = acct.access;
+ let desc = access.describe_html();
+ let now = Timestamp::now();
+ access.entry(desc)
+ .or_insert(TokenRevelation {
+ latest: now,
+ earliest: now,
+ })
+ .latest = now;
+ access.clone()
+ };
+
+ let token = access
+ .override_token()
+ .unwrap_or_else(||{
+ RawToken::new_random()
+ // xxx disconnect everyone else
+ });
+
+ let iad = InstanceAccessDetails {
+ gref : self.gref.clone(),
+ ident : player
+ };
+ self.token_register(token.clone(), iad);
+
+ let report = AccessTokenReport {
+ url: format!("http://localhost:8000/{}", token.url), // xxx
+ };
+ let report = access
+ .server_deliver(&pst, &report);
+ report.cloned()
}
#[throws(MgmtError)]
.flatten()
.collect()
};
- let aplayers = s.c.g.updates.iter().map(
- |(player, PlayerUpdates { tz, .. })|
- (player, PlayerSaveAccess { tz: tz.clone() })
+ let aplayers = s.c.g.iplayers.iter().map(
+ |(player, PlayerRecord { pst, .. })|
+ (player, pst)
).collect();
let isa = InstanceSaveAccesses { pieces, tokens_players, aplayers };
rmp_serde::encode::write_named(w, &isa)
}
}
let InstanceSaveAccesses::<String,ActualPiecesLoaded>
- { mut tokens_players, mut pieces, mut aplayers }
+ { mut tokens_players, mut ipieces, mut aplayers }
= Self::load_something(&name, "a-")
.or_else(|e| {
if let InternalError::Anyhow(ae) = &e {
Err(e)
})?;
- let gs = {
- let mut gs : GameState = Self::load_something(&name, "g-")?;
- for mut p in gs.pieces.values_mut() {
- p.lastclient = Default::default();
- }
- gs.pieces.0.retain(|k,_v| pieces.contains_key(k));
- pieces.retain(|k,_v| gs.pieces.0.contains_key(k));
+ let mut gs : GameState = Self::load_something(&name, "g-")?;
- gs
- };
+ fn discard_mismatches<K:slotmap::Key, V1, V2>(
+ primary: &mut DenseSlotMap<K, V1>,
+ secondary: &mut SecondarySlotMap<K, V2>,
+ ) {
+ primary.retain(|k,_v| secondary.contains_key(k));
+ secondary.retain(|k,_v| primary.contains_key(k));
+ }
- let mut updates : SecondarySlotMap<_,_> = Default::default();
+ discard_mismatches(&mut gs.players, &mut aplayers);
+ discard_mismatches(&mut gs.pieces, &mut ipieces);
+
let pu_bc = PlayerUpdates::new_begin(&gs);
- for player in gs.players.keys() {
- let aplayer = aplayers.remove(player).unwrap_or_default();
- updates.insert(player, pu_bc.new(aplayer.tz));
+
+ let iplayers = {
+ let a = aplayers;
+ a.drain()
+ }.map(|pst| {
+ let u = pu_bc.new();
+ PlayerRecord { u, pst }
+ }).collect();
+
+ for mut p in gs.pieces.values_mut() {
+ p.lastclient = Default::default();
+ if let Some(held) = p.held {
+ if !gs.players.has_key(held) { p.held = None }
+ }
}
+
let name = Arc::new(name);
tokens_players.retain(
|&(_,player)| gs.players.contains_key(player)
);
let g = Instance {
- gs, updates,
- pieces: PiecesLoaded(pieces),
+ gs, iplayers,
+ ipieces: PiecesLoaded(ipieces),
name: name.clone(),
clients : Default::default(),
tokens_clients : Default::default(),
use crate::gamestate::PieceSpec;
use std::fmt::Debug;
use std::collections::hash_set::HashSet;
-use implementation::PlayerAccessSpec;
use thiserror::Error;
use crate::error::display_as_debug;
use crate::accounts::AccountName;
+pub use implementation::PlayerAccessSpec;
+
//---------- common types ----------
pub type Coord = isize;
#[typetag::serde(tag="access")]
pub trait PlayerAccessSpec : Debug {
- fn client_mgi(&self, _player: &PlayerId) -> Option<MgmtGameInstruction> {
+ fn override_token(&self) -> Option<&RawToken> {
+ // xxx check this on setting access
None
}
- fn server_deliver(&self, ps: &PlayerState, token: &AccessTokenReport)
+ fn client_mgi(&self, _player: PlayerId) -> Option<MgmtGameInstruction> {
+ None
+ }
+ fn server_deliver(&self, pst: &PlayerState, token: &AccessTokenReport)
-> Result<Option<&AccessTokenReport>, AE> {
Ok(None)
}
- fn client_deliver(&self, ps: &PlayerState, token: &AccessTokenReport)
+ fn client_deliver(&self, pst: &PlayerState, token: &AccessTokenReport)
-> Result<(),AE> {
panic!()
}
#[typetag::serde]
impl PlayerAccessSpec for FixedToken {
+ fn override_token(&self) -> Option<&RawToken> {
+ Some(self.token)
+ }
fn client_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
Some(Insn::SetFixedPlayerAccess {
player,
#[typetag::serde]
impl PlayerAccessSpec for UrlOnStdout {
- #[throws(AE)]
fn client_mgi(&self, player: PlayerId) -> Option<MgmtGameInstruction> {
Some(Insn::ReportPlayerAccesses(player))
}
+ #[throws(AE)]
fn server_deliver(&self, ps: &PlayerState, token: &AccessTokenReport)
- -> Result<Option<&AccessTokenReport>, AE> {
+ -> Option<&AccessTokenReport> {
Some(token)
}
+ #[throws(AE)]
fn client_deliver(&self, ps: &PlayerState, token: &AccessTokenReport) {
println!("access account={} nick={:?} url:\n{}",
&ps.account, &ps.nick, token.url);