source: geniuswebcore/geniusweb/protocol/session/DefaultSessionState.py@ 92

Last change on this file since 92 was 90, checked in by Bart Vastenhouw, 3 years ago

Refactor to help reusing partiesserver.

File size: 8.4 KB
Line 
1from __future__ import annotations
2from abc import abstractmethod
3from typing import List, Dict, Optional, TypeVar, Generic
4
5from geniusweb.actions.Action import Action
6from geniusweb.actions.PartyId import PartyId
7from geniusweb.progress.Progress import Progress
8from geniusweb.protocol.ProtocolException import ProtocolException
9from geniusweb.protocol.session.SessionSettings import SessionSettings
10from geniusweb.protocol.session.SessionState import SessionState
11from geniusweb.references.PartyWithProfile import PartyWithProfile
12from geniusweb.utils import toTuple, val
13
14S = TypeVar('S', bound=SessionSettings)
15P = TypeVar('P', bound="DefaultSessionState")
16
17
18class 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
Note: See TracBrowser for help on using the repository browser.