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 |
|
---|