diff options
Diffstat (limited to 'sleekxmpp/xmlstream')
-rw-r--r-- | sleekxmpp/xmlstream/statemanager.py | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/sleekxmpp/xmlstream/statemanager.py b/sleekxmpp/xmlstream/statemanager.py new file mode 100644 index 00000000..c7f76e75 --- /dev/null +++ b/sleekxmpp/xmlstream/statemanager.py @@ -0,0 +1,139 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from __future__ import with_statement +import threading + + +class StateError(Exception): + """Raised whenever a state transition was attempted but failed.""" + + +class StateManager(object): + """ + At the very core of SleekXMPP there is a need to track various + library configuration settings, XML stream features, and the + network connection status. The state manager is responsible for + tracking this information in a thread-safe manner. + + State 'variables' store the current state of these items as simple + string values or booleans. Changing those values must be done + according to transitions defined when creating the state variable. + + If a state variable is given a value that is not allowed according + to the transition definitions, a StateError is raised. When a + valid value is assigned an event is raised named: + + _state_changed_nameofthestatevariable + + The event carries a dictionary containing the previous and the new + state values. + """ + + def __init__(self, event_func=None): + """ + Initialize the state manager. The parameter event_func should be + the event() method of a SleekXMPP object in order to enable + _state_changed_* events. + """ + self.main_lock = threading.Lock() + self.locks = {} + self.state_variables = {} + + if event_func is not None: + self.event = event_func + else: + self.event = lambda name, data: None + + def add(self, name, default=False, values=None, transitions=None): + """ + Create a new state variable. + + When transitions is specified, only those defined state change + transitions will be allowed. + + When values is specified (and not transitions), any state changes + between those values are allowed. + + If neither values nor transitions are defined, then the state variable + will be a binary switch between True and False. + """ + if name in self.state_variables: + raise IndexError("State variable %s already exists" % name) + + self.locks[name] = threading.Lock() + with self.locks[name]: + var = {'value': default, + 'default': default, + 'transitions': {}} + + if transitions is not None: + for start in transitions: + var['transitions'][start] = set(transitions[start]) + elif values is not None: + values = set(values) + for value in values: + var['transitions'][value] = values + elif values is None: + var['transitions'] = {True: [False], + False: [True]} + + self.state_variables[name] = var + + def addStates(self, var_defs): + """ + Create multiple state variables at once. + """ + for var, data in var_defs: + self.add(var, + default=data.get('default', False), + values=data.get('values', None), + transitions=data.get('transitions', None)) + + def force_set(self, name, val): + """ + Force setting a state variable's value by overriding transition checks. + """ + with self.locks[name]: + self.state_variables[name]['value'] = val + + def reset(self, name): + """ + Reset a state variable to its default value. + """ + with self.locks[name]: + default = self.state_variables[name]['default'] + self.state_variables[name]['value'] = default + + def __getitem__(self, name): + """ + Get the value of a state variable if it exists. + """ + with self.locks[name]: + if name not in self.state_variables: + raise IndexError("State variable %s does not exist" % name) + return self.state_variables[name]['value'] + + def __setitem__(self, name, val): + """ + Attempt to set the value of a state variable, but raise StateError + if the transition is undefined. + + A _state_changed_* event is triggered after a successful transition. + """ + with self.locks[name]: + if name not in self.state_variables: + raise IndexError("State variable %s does not exist" % name) + current = self.state_variables[name]['value'] + if current == val: + return + if val in self.state_variables[name]['transitions'][current]: + self.state_variables[name]['value'] = val + self.event('_state_changed_%s' % name, {'from': current, 'to': val}) + else: + raise StateError("Can not transition from '%s' to '%s'" % (str(current), str(val))) |