[88] | 1 | from __future__ import annotations
|
---|
| 2 | from abc import abstractmethod
|
---|
| 3 | from typing import List, Dict, Optional, TypeVar, Generic
|
---|
| 4 |
|
---|
| 5 | from geniusweb.actions.Action import Action
|
---|
| 6 | from geniusweb.actions.PartyId import PartyId
|
---|
| 7 | from geniusweb.progress.Progress import Progress
|
---|
| 8 | from geniusweb.protocol.ProtocolException import ProtocolException
|
---|
| 9 | from geniusweb.protocol.session.SessionSettings import SessionSettings
|
---|
| 10 | from geniusweb.protocol.session.SessionState import SessionState
|
---|
| 11 | from geniusweb.references.PartyWithProfile import PartyWithProfile
|
---|
| 12 | from geniusweb.utils import toTuple, val
|
---|
| 13 |
|
---|
| 14 | S = TypeVar('S', bound=SessionSettings)
|
---|
| 15 | P = TypeVar('P', bound="DefaultSessionState")
|
---|
| 16 |
|
---|
| 17 |
|
---|
| 18 | class DefaultSessionState (SessionState, Generic[P, S]):
|
---|
| 19 | '''
|
---|
| 20 | The default current state of the session. immutable.
|
---|
| 21 |
|
---|
| 22 | @param <P> the actual SessionState object
|
---|
| 23 | @param <S> the actual SessionSettings object
|
---|
| 24 | '''
|
---|
| 25 |
|
---|
| 26 | # error: Optional[ProtocolException]
|
---|
| 27 | # progress: Progress
|
---|
| 28 | # partyprofiles: Optional[Dict[PartyId, PartyWithProfile]]
|
---|
| 29 | # settings: S
|
---|
| 30 |
|
---|
| 31 | def __init__(self, actions:List[Action], connections:List[PartyId],
|
---|
| 32 | progress:Optional[Progress], settings:S,
|
---|
| 33 | partyprofiles:Optional[Dict[PartyId, PartyWithProfile]]=None,
|
---|
| 34 | error:Optional[ProtocolException]=None):
|
---|
| 35 | '''
|
---|
| 36 | @param actions value for actions done so far. None equals to empty
|
---|
| 37 | list
|
---|
| 38 | @param conns the currently existing connections. Can be empty. If
|
---|
| 39 | None it is assumed to be empty. Each connection
|
---|
| 40 | represents another party. Normally there is exactly
|
---|
| 41 | 1 connection for every party. The protocol should
|
---|
| 42 | check this.
|
---|
| 43 | @param progr the {@link Progress} that governs this session. Can
|
---|
| 44 | be null if session did not yet start.
|
---|
| 45 | @param settings the settings used for the session
|
---|
| 46 | @param partyprofiles map with the {@link PartyWithProfile} for connected
|
---|
| 47 | parties. None is equivalent to an empty map.
|
---|
| 48 | @param e the exception that occured, usually None. All errors
|
---|
| 49 | occuring due to faulty {@link Action}s translate to
|
---|
| 50 | {@link ProtocolException}s. All errors in our own
|
---|
| 51 | code are bugs (not ProtocolExceptions) and should
|
---|
| 52 | result in a throw and terminate the session.
|
---|
| 53 | '''
|
---|
| 54 |
|
---|
| 55 | self._partyprofiles = dict(partyprofiles) if partyprofiles else {}
|
---|
| 56 |
|
---|
| 57 | self._connections = list(connections) if connections else []
|
---|
| 58 |
|
---|
| 59 | self._actions = list(actions) if actions else []
|
---|
| 60 |
|
---|
| 61 | if len(self._connections) != len(set(self._connections)):
|
---|
| 62 | raise ValueError("There can not be multiple connections for a party:"
|
---|
| 63 | +str(self._connections))
|
---|
| 64 |
|
---|
| 65 | if not settings:
|
---|
| 66 | raise ValueError("Settings must be not null");
|
---|
| 67 | self._progress = progress;
|
---|
| 68 | self._settings = settings;
|
---|
| 69 | self._error = error;
|
---|
| 70 |
|
---|
| 71 | @abstractmethod
|
---|
| 72 | def With(self, actions1:List[Action] , conns:List[PartyId],
|
---|
| 73 | progress1:Optional[Progress] , settings1:S,
|
---|
| 74 | partyprofiles1:Dict[PartyId, PartyWithProfile] , e: Optional[ProtocolException]) -> P:
|
---|
| 75 | '''
|
---|
| 76 | Construct a new session state, where the DefaultSessionState changes
|
---|
| 77 | while the rest of the state remains unchanged. Notice the return type P.
|
---|
| 78 |
|
---|
| 79 | @param actions1 the new {@link Action}s
|
---|
| 80 | @param conns the new connected {@link PartyId}s
|
---|
| 81 | @param progress1 the new {@link Progress}, can be None still
|
---|
| 82 | @param settings1 the new {@link SessionSettings}. Normally this is
|
---|
| 83 | constant during a session.
|
---|
| 84 | @param partyprofiles1 the new {@link PartyWithProfile}s for all parties.
|
---|
| 85 | Normally this remains constant during a session.
|
---|
| 86 | @param e an error that occured that caused the session to
|
---|
| 87 | reach its terminated/final state. If an error does
|
---|
| 88 | not cause termination, only a warning should be
|
---|
| 89 | logged.
|
---|
| 90 | @return the new state of the derived sessionstate.
|
---|
| 91 | '''
|
---|
| 92 |
|
---|
| 93 | def getConnections(self) -> List[PartyId]:
|
---|
| 94 | '''
|
---|
| 95 | @return existing connections.
|
---|
| 96 | '''
|
---|
| 97 | return list(self._connections)
|
---|
| 98 |
|
---|
| 99 | def getPartyProfiles(self) -> Dict[PartyId, PartyWithProfile]:
|
---|
| 100 | '''
|
---|
| 101 | @return map with {@link PartyWithProfile} for the parties. May be an
|
---|
| 102 | incomplete map, as more parties with their profiles may be set
|
---|
| 103 | only later.
|
---|
| 104 | '''
|
---|
| 105 | return dict(self._partyprofiles)
|
---|
| 106 |
|
---|
| 107 | def getActions(self) -> List[Action]:
|
---|
| 108 | '''
|
---|
| 109 | @return unmodifyable list of actions done so far.
|
---|
| 110 | '''
|
---|
| 111 | return list(self._actions)
|
---|
| 112 |
|
---|
| 113 | def getProgress(self) -> Optional[Progress]:
|
---|
| 114 | return self._progress
|
---|
| 115 |
|
---|
| 116 | def getSettings(self) -> S:
|
---|
| 117 | return self._settings
|
---|
| 118 |
|
---|
| 119 | def isFinal(self, currentTimeMs:int) -> bool:
|
---|
| 120 | return self._error != None or \
|
---|
| 121 | (self._progress != None and self._progress.isPastDeadline(currentTimeMs)) # type:ignore
|
---|
| 122 |
|
---|
| 123 | def getError(self) -> Optional[ProtocolException]:
|
---|
| 124 | '''
|
---|
| 125 | @return an error that occured that caused the session to
|
---|
| 126 | reach its terminated/final state. If an error does
|
---|
| 127 | not cause termination, only a warning should be
|
---|
| 128 | logged. None if no error occured.
|
---|
| 129 | '''
|
---|
| 130 | return self._error
|
---|
| 131 |
|
---|
| 132 | def WithDeadlineReached(self) -> P:
|
---|
| 133 | return self.With(self._actions, self._connections, val(self._progress), self._settings,
|
---|
| 134 | self._partyprofiles, self._error)
|
---|
| 135 |
|
---|
| 136 | def WithoutParty(self, party:PartyId) -> P:
|
---|
| 137 | assert isinstance(party, PartyId)
|
---|
| 138 | newconn = list(self._connections)
|
---|
| 139 | newconn.remove(party);
|
---|
| 140 | return self.With(self._actions, newconn, val(self._progress), \
|
---|
| 141 | self._settings, self._partyprofiles, self._error)
|
---|
| 142 |
|
---|
| 143 | def WithException(self, e:ProtocolException) -> P:
|
---|
| 144 | '''
|
---|
| 145 | @param e the error that occured
|
---|
| 146 | @return a new state with the error set/updated.
|
---|
| 147 | '''
|
---|
| 148 | assert isinstance(e, ProtocolException)
|
---|
| 149 | return self.With(self._actions, self._connections, self._progress,
|
---|
| 150 | self._settings, self._partyprofiles, e)
|
---|
| 151 |
|
---|
| 152 | def WithParty(self, connection:PartyId, partyprofile:PartyWithProfile) -> P:
|
---|
| 153 | assert isinstance(connection, PartyId)
|
---|
| 154 | assert isinstance(partyprofile, PartyWithProfile)
|
---|
| 155 | newconns = list(self.getConnections())
|
---|
| 156 | newconns.append(connection)
|
---|
| 157 | newprofiles = dict(self.getPartyProfiles())
|
---|
| 158 | newprofiles[connection] = partyprofile
|
---|
| 159 | return self.With(self.getActions(), newconns, self._progress, self.getSettings(),
|
---|
| 160 | newprofiles, None)
|
---|
| 161 |
|
---|
| 162 | def WithProgress(self, newprogress:Progress) -> P:
|
---|
| 163 | '''
|
---|
| 164 | Sets the new progress for this session.
|
---|
| 165 |
|
---|
| 166 | @param newprogress the new progress
|
---|
| 167 | @return new SAOPState with the progress set to new value
|
---|
| 168 | '''
|
---|
| 169 | assert isinstance(newprogress, Progress)
|
---|
| 170 | if not newprogress:
|
---|
| 171 | raise ValueError("newprogress must be not null");
|
---|
| 172 | return self.With(self._actions, self._connections, newprogress, self._settings,
|
---|
| 173 | self._partyprofiles, self._error)
|
---|
| 174 |
|
---|
| 175 | def WithAction(self, actor:PartyId , action:Action) -> P:
|
---|
| 176 | '''
|
---|
| 177 | @param actor the actor that did this action. Can be used to check if
|
---|
| 178 | action is valid. NOTICE caller has to make sure the current
|
---|
| 179 | state is not final.
|
---|
| 180 | @param action the action that was proposed by actor.
|
---|
| 181 | @return new SAOPState with the action added as last action.
|
---|
| 182 | '''
|
---|
| 183 | msg = self.checkAction(actor, action)
|
---|
| 184 | if msg:
|
---|
| 185 | raise ValueError(msg)
|
---|
| 186 |
|
---|
| 187 | newactions = list(self.getActions())
|
---|
| 188 | newactions.append(action)
|
---|
| 189 | return self.With(newactions, self._connections, val(self._progress),
|
---|
| 190 | self._settings, self._partyprofiles, self._error)
|
---|
| 191 |
|
---|
| 192 | def checkAction(self, actor:PartyId , action:Action) -> Optional[str]:
|
---|
| 193 | '''
|
---|
| 194 | @param actor the known real actor that did this action
|
---|
| 195 | @param action an {@link Action}
|
---|
| 196 | @return null if action seems ok, or message explaining why not.
|
---|
| 197 | '''
|
---|
| 198 | if not actor:
|
---|
| 199 | return "actor must not be null"
|
---|
| 200 | if not action:
|
---|
| 201 | return "action is null"
|
---|
| 202 | if actor != action.getActor():
|
---|
| 203 | return "act contains wrong credentials: " + str(action)
|
---|
| 204 | return None
|
---|
| 205 |
|
---|
| 206 | def __repr__(self) -> str:
|
---|
| 207 | return type(self).__name__ + "[" + str(self._actions) + ","\
|
---|
| 208 | +str(self._connections) + "," + str(self._progress) + "," + \
|
---|
| 209 | str(self._settings) + "," + str(self._error);
|
---|
| 210 |
|
---|
| 211 | def __hash__(self):
|
---|
| 212 | return hash((tuple(self._actions), tuple(self._connections),
|
---|
| 213 | toTuple(self._partyprofiles),
|
---|
| 214 | self._progress, self._settings, self._error))
|
---|
| 215 |
|
---|
| 216 | def __eq__(self, other) -> bool:
|
---|
| 217 | return isinstance(other, self.__class__) and \
|
---|
| 218 | self._partyprofiles == other._partyprofiles and \
|
---|
| 219 | self._actions == other._actions and \
|
---|
| 220 | self._connections == other._connections and \
|
---|
| 221 | self._progress == other._progress and \
|
---|
| 222 | self._settings == other._settings and \
|
---|
| 223 | self._error == other._error
|
---|
| 224 |
|
---|