mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-30 19:19:55 +00:00
Add StateManager as replacement for StateMachine.
This commit is contained in:
parent
73a3d07ad9
commit
ec860bf9e2
1 changed files with 139 additions and 0 deletions
139
sleekxmpp/xmlstream/statemanager.py
Normal file
139
sleekxmpp/xmlstream/statemanager.py
Normal file
|
@ -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)))
|
Loading…
Reference in a new issue