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