Redo PS connection handling; ensure valid network connection before talking to server

This commit is contained in:
Dan Williams 2007-05-13 21:34:15 -04:00
parent 66dfd363ba
commit 5e2ea16e3a
3 changed files with 265 additions and 225 deletions

View File

@ -22,6 +22,7 @@ import dbus
import dbus.service import dbus.service
from dbus.gobject_service import ExportedGObject from dbus.gobject_service import ExportedGObject
from ConfigParser import ConfigParser, NoOptionError from ConfigParser import ConfigParser, NoOptionError
import psutils
from sugar import env, profile, util from sugar import env, profile, util
import logging import logging
@ -132,9 +133,19 @@ class Buddy(ExportedGObject):
logging.debug("Invalid init property '%s'; ignoring..." % key) logging.debug("Invalid init property '%s'; ignoring..." % key)
del kwargs[key] del kwargs[key]
# Set icon after superclass init, because it sends DBus and GObject
# signals when set
icon_data = None
if kwargs.has_key(_PROP_ICON):
icon_data = kwargs[_PROP_ICON]
del kwargs[_PROP_ICON]
ExportedGObject.__init__(self, bus_name, self._object_path, ExportedGObject.__init__(self, bus_name, self._object_path,
gobject_properties=kwargs) gobject_properties=kwargs)
if icon_data:
self.props.icon = icon_data
def do_get_property(self, pspec): def do_get_property(self, pspec):
"""Retrieve current value for the given property specifier """Retrieve current value for the given property specifier
@ -399,176 +410,6 @@ class Buddy(ExportedGObject):
except AttributeError: except AttributeError:
self._valid = False self._valid = False
NM_SERVICE = 'org.freedesktop.NetworkManager'
NM_IFACE = 'org.freedesktop.NetworkManager'
NM_IFACE_DEVICES = 'org.freedesktop.NetworkManager.Devices'
NM_PATH = '/org/freedesktop/NetworkManager'
class IP4AddressMonitor(gobject.GObject):
"""This class, and direct buddy IPv4 address access, will go away quite soon"""
__gsignals__ = {
'address-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
__gproperties__ = {
'address' : (str, None, None, None, gobject.PARAM_READABLE)
}
def __init__(self):
gobject.GObject.__init__(self)
self._nm_present = False
self._matches = []
self._addr = None
self._nm_obj = None
sys_bus = dbus.SystemBus()
bus_object = sys_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
try:
if bus_object.GetNameOwner(NM_SERVICE, dbus_interface='org.freedesktop.DBus'):
self._nm_present = True
except dbus.DBusException:
pass
if self._nm_present:
self._connect_to_nm()
else:
addr = self._get_address_fallback()
self._update_address(addr)
def do_get_property(self, pspec):
if pspec.name == "address":
return self._addr
def _update_address(self, new_addr):
if new_addr == "0.0.0.0":
new_addr = None
if new_addr == self._addr:
return
self._addr = new_addr
logging.debug("IP4 address now '%s'" % new_addr)
self.emit('address-changed', new_addr)
def _connect_to_nm(self):
"""Connect to NM device state signals to tell when the IPv4 address changes"""
try:
sys_bus = dbus.SystemBus()
proxy = sys_bus.get_object(NM_SERVICE, NM_PATH)
self._nm_obj = dbus.Interface(proxy, NM_IFACE)
except dbus.DBusException, err:
logging.debug("Error finding NetworkManager: %s" % err)
self._nm_present = False
return
sys_bus = dbus.SystemBus()
match = sys_bus.add_signal_receiver(self._nm_device_active_cb,
signal_name="DeviceNowActive",
dbus_interface=NM_IFACE)
self._matches.append(match)
match = sys_bus.add_signal_receiver(self._nm_device_no_longer_active_cb,
signal_name="DeviceNoLongerActive",
dbus_interface=NM_IFACE,
named_service=NM_SERVICE)
self._matches.append(match)
match = sys_bus.add_signal_receiver(self._nm_state_change_cb,
signal_name="StateChange",
dbus_interface=NM_IFACE,
named_service=NM_SERVICE)
self._matches.append(match)
state = self._nm_obj.state()
if state == 3: # NM_STATE_CONNECTED
self._query_devices()
def _device_properties_cb(self, *props):
active = props[4]
if not active:
return
act_stage = props[5]
# HACK: OLPC NM has an extra stage, so activated == 8 on OLPC
# but 7 everywhere else
if act_stage != 8 and act_stage != 7:
# not activated
return
self._update_address(props[6])
def _device_properties_error_cb(self, err):
logging.debug("Error querying device properties: %s" % err)
def _query_device_properties(self, device):
sys_bus = dbus.SystemBus()
proxy = sys_bus.get_object(NM_SERVICE, device)
dev = dbus.Interface(proxy, NM_IFACE_DEVICES)
dev.getProperties(reply_handler=self._device_properties_cb,
error_handler=self._device_properties_error_cb)
def _get_devices_cb(self, ops):
"""Query each device's properties"""
for op in ops:
self._query_device_properties(op)
def _get_devices_error_cb(self, err):
logging.debug("Error getting NetworkManager devices: %s" % err)
def _query_devices(self):
"""Query NM for a list of network devices"""
self._nm_obj.getDevices(reply_handler=self._get_devices_cb,
error_handler=self._get_devices_error_cb)
def _nm_device_active_cb(self, device, ssid=None):
self._query_device_properties(device)
def _nm_device_no_longer_active_cb(self, device):
self._update_address(None)
def _nm_state_change_cb(self, new_state):
if new_state == 4: # NM_STATE_DISCONNECTED
self._update_address(None)
def handle_name_owner_changed(self, name, old, new):
"""Clear state when NM goes away"""
if name != NM_SERVICE:
return
if (old and len(old)) and (not new and not len(new)):
# NM went away
self._nm_present = False
for match in self._matches:
match.remove()
self._matches = []
self._update_address(None)
elif (not old and not len(old)) and (new and len(new)):
# NM started up
self._nm_present = True
self._connect_to_nm()
def _get_iface_address(self, iface):
import socket
import fcntl
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
fd = s.fileno()
SIOCGIFADDR = 0x8915
addr = fcntl.ioctl(fd, SIOCGIFADDR, struct.pack('256s', iface[:15]))[20:24]
s.close()
return socket.inet_ntoa(addr)
def _get_address_fallback(self):
import commands
(s, o) = commands.getstatusoutput("/sbin/route -n")
if s != 0:
return
for line in o.split('\n'):
fields = line.split(" ")
if fields[0] == "0.0.0.0":
iface = fields[len(fields) - 1]
return self._get_iface_address(iface)
return None
class GenericOwner(Buddy): class GenericOwner(Buddy):
"""Common functionality for Local User-like objects """Common functionality for Local User-like objects
@ -617,7 +458,7 @@ class GenericOwner(Buddy):
signal_name="NameOwnerChanged", signal_name="NameOwnerChanged",
dbus_interface="org.freedesktop.DBus") dbus_interface="org.freedesktop.DBus")
self._ip4_addr_monitor = IP4AddressMonitor() self._ip4_addr_monitor = psutils.IP4AddressMonitor.get_instance()
self._ip4_addr_monitor.connect("address-changed", self._ip4_address_changed_cb) self._ip4_addr_monitor.connect("address-changed", self._ip4_address_changed_cb)
def _ip4_address_changed_cb(self, monitor, address): def _ip4_address_changed_cb(self, monitor, address):

View File

@ -14,6 +14,9 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import dbus, dbus.glib, gobject
import logging
def bytes_to_string(bytes): def bytes_to_string(bytes):
"""The function converts a D-BUS byte array provided by dbus to string format. """The function converts a D-BUS byte array provided by dbus to string format.
@ -28,3 +31,183 @@ def bytes_to_string(bytes):
# Python string # Python string
ret = ''.join([str(item) for item in bytes]) ret = ''.join([str(item) for item in bytes])
return ret return ret
NM_SERVICE = 'org.freedesktop.NetworkManager'
NM_IFACE = 'org.freedesktop.NetworkManager'
NM_IFACE_DEVICES = 'org.freedesktop.NetworkManager.Devices'
NM_PATH = '/org/freedesktop/NetworkManager'
_ip4am = None
class IP4AddressMonitor(gobject.GObject):
"""This class, and direct buddy IPv4 address access, will go away quite soon"""
__gsignals__ = {
'address-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
__gproperties__ = {
'address' : (str, None, None, None, gobject.PARAM_READABLE)
}
def get_instance():
"""Retrieve (or create) the IP4Address monitor singleton instance"""
global _ip4am
if not _ip4am:
_ip4am = IP4AddressMonitor()
return _ip4am
get_instance = staticmethod(get_instance)
def __init__(self):
gobject.GObject.__init__(self)
self._nm_present = False
self._matches = []
self._addr = None
self._nm_obj = None
sys_bus = dbus.SystemBus()
bus_object = sys_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
try:
if bus_object.GetNameOwner(NM_SERVICE, dbus_interface='org.freedesktop.DBus'):
self._nm_present = True
except dbus.DBusException:
pass
if self._nm_present:
self._connect_to_nm()
else:
addr = self._get_address_fallback()
self._update_address(addr)
def do_get_property(self, pspec):
if pspec.name == "address":
return self._addr
def _update_address(self, new_addr):
if new_addr == "0.0.0.0":
new_addr = None
if new_addr == self._addr:
return
self._addr = new_addr
logging.debug("IP4 address now '%s'" % new_addr)
self.emit('address-changed', new_addr)
def _connect_to_nm(self):
"""Connect to NM device state signals to tell when the IPv4 address changes"""
try:
sys_bus = dbus.SystemBus()
proxy = sys_bus.get_object(NM_SERVICE, NM_PATH)
self._nm_obj = dbus.Interface(proxy, NM_IFACE)
except dbus.DBusException, err:
logging.debug("Error finding NetworkManager: %s" % err)
self._nm_present = False
return
sys_bus = dbus.SystemBus()
match = sys_bus.add_signal_receiver(self._nm_device_active_cb,
signal_name="DeviceNowActive",
dbus_interface=NM_IFACE)
self._matches.append(match)
match = sys_bus.add_signal_receiver(self._nm_device_no_longer_active_cb,
signal_name="DeviceNoLongerActive",
dbus_interface=NM_IFACE,
named_service=NM_SERVICE)
self._matches.append(match)
match = sys_bus.add_signal_receiver(self._nm_state_change_cb,
signal_name="StateChange",
dbus_interface=NM_IFACE,
named_service=NM_SERVICE)
self._matches.append(match)
state = self._nm_obj.state()
if state == 3: # NM_STATE_CONNECTED
self._query_devices()
def _device_properties_cb(self, *props):
active = props[4]
if not active:
return
act_stage = props[5]
# HACK: OLPC NM has an extra stage, so activated == 8 on OLPC
# but 7 everywhere else
if act_stage != 8 and act_stage != 7:
# not activated
return
self._update_address(props[6])
def _device_properties_error_cb(self, err):
logging.debug("Error querying device properties: %s" % err)
def _query_device_properties(self, device):
sys_bus = dbus.SystemBus()
proxy = sys_bus.get_object(NM_SERVICE, device)
dev = dbus.Interface(proxy, NM_IFACE_DEVICES)
dev.getProperties(reply_handler=self._device_properties_cb,
error_handler=self._device_properties_error_cb)
def _get_devices_cb(self, ops):
"""Query each device's properties"""
for op in ops:
self._query_device_properties(op)
def _get_devices_error_cb(self, err):
logging.debug("Error getting NetworkManager devices: %s" % err)
def _query_devices(self):
"""Query NM for a list of network devices"""
self._nm_obj.getDevices(reply_handler=self._get_devices_cb,
error_handler=self._get_devices_error_cb)
def _nm_device_active_cb(self, device, ssid=None):
self._query_device_properties(device)
def _nm_device_no_longer_active_cb(self, device):
self._update_address(None)
def _nm_state_change_cb(self, new_state):
if new_state == 4: # NM_STATE_DISCONNECTED
self._update_address(None)
def handle_name_owner_changed(self, name, old, new):
"""Clear state when NM goes away"""
if name != NM_SERVICE:
return
if (old and len(old)) and (not new and not len(new)):
# NM went away
self._nm_present = False
for match in self._matches:
match.remove()
self._matches = []
self._update_address(None)
elif (not old and not len(old)) and (new and len(new)):
# NM started up
self._nm_present = True
self._connect_to_nm()
def _get_iface_address(self, iface):
import socket
import fcntl
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
fd = s.fileno()
SIOCGIFADDR = 0x8915
addr = fcntl.ioctl(fd, SIOCGIFADDR, struct.pack('256s', iface[:15]))[20:24]
s.close()
return socket.inet_ntoa(addr)
def _get_address_fallback(self):
import commands
(s, o) = commands.getstatusoutput("/sbin/route -n")
if s != 0:
return
for line in o.split('\n'):
fields = line.split(" ")
if fields[0] == "0.0.0.0":
iface = fields[len(fields) - 1]
return self._get_iface_address(iface)
return None

View File

@ -136,11 +136,22 @@ class ServerPlugin(gobject.GObject):
self._owner.connect("icon-changed", self._owner_icon_changed_cb) self._owner.connect("icon-changed", self._owner_icon_changed_cb)
self._account = self._get_account_info() self._account = self._get_account_info()
self._conn = self._init_connection()
self._conn_status = CONNECTION_STATUS_DISCONNECTED self._conn_status = CONNECTION_STATUS_DISCONNECTED
self._reconnect_id = 0 # Monitor IPv4 address as an indicator of the network connection
self._ip4am = psutils.IP4AddressMonitor.get_instance()
self._ip4am.connect('address-changed', self._ip4_address_changed_cb)
def _ip4_address_changed_cb(self, ip4am, address):
logging.debug("::: IP4 address now %s" % address)
if address:
logging.debug("::: valid IP4 address, conn_status %s" % self._conn_status)
if self._conn_status == CONNECTION_STATUS_DISCONNECTED:
logging.debug("::: will connect")
self.start()
else:
logging.debug("::: invalid IP4 address, will disconnect")
self.cleanup()
def _owner_property_changed_cb(self, owner, properties): def _owner_property_changed_cb(self, owner, properties):
"""Local user's configuration properties have changed """Local user's configuration properties have changed
@ -229,6 +240,14 @@ class ServerPlugin(gobject.GObject):
"""Retrieve our telepathy.client.Connection object""" """Retrieve our telepathy.client.Connection object"""
return self._conn return self._conn
def _connect_reply_cb(self):
"""Handle connection success"""
pass
def _connect_error_cb(self, exception):
"""Handle connection failure"""
logging.debug("Connect error: %s" % exception)
def _init_connection(self): def _init_connection(self):
"""Set up our connection """Set up our connection
@ -262,7 +281,12 @@ class ServerPlugin(gobject.GObject):
conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate', conn[CONN_INTERFACE_PRESENCE].connect_to_signal('PresenceUpdate',
self._presence_update_cb) self._presence_update_cb)
return conn self._conn = conn
status = self._conn[CONN_INTERFACE].GetStatus()
if status == CONNECTION_STATUS_DISCONNECTED:
self._conn[CONN_INTERFACE].Connect(reply_handler=self._connect_reply_cb,
error_handler=self._connect_error_cb)
self._handle_connection_status_change(self._conn, status)
def _request_list_channel(self, name): def _request_list_channel(self, name):
"""Request a contact-list channel from Telepathy """Request a contact-list channel from Telepathy
@ -297,16 +321,14 @@ class ServerPlugin(gobject.GObject):
if local_pending: if local_pending:
# accept pending subscriptions # accept pending subscriptions
#print 'pending: %r' % local_pending
publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '') publish[CHANNEL_INTERFACE_GROUP].AddMembers(local_pending, '')
not_subscribed = list(set(publish_handles) - set(subscribe_handles))
self_handle = self._conn[CONN_INTERFACE].GetSelfHandle() self_handle = self._conn[CONN_INTERFACE].GetSelfHandle()
self._online_contacts[self_handle] = self._account['account'] self._online_contacts[self_handle] = self._account['account']
for handle in not_subscribed: # request subscriptions from people subscribed to us if we're not subscribed to them
# request subscriptions from people subscribed to us if we're not subscribed to them not_subscribed = list(set(publish_handles) - set(subscribe_handles))
subscribe[CHANNEL_INTERFACE_GROUP].AddMembers([self_handle], '') subscribe[CHANNEL_INTERFACE_GROUP].AddMembers(not_subscribed, '')
if CONN_INTERFACE_BUDDY_INFO not in self._conn.get_valid_interfaces(): if CONN_INTERFACE_BUDDY_INFO not in self._conn.get_valid_interfaces():
logging.debug('OLPC information not available') logging.debug('OLPC information not available')
@ -334,8 +356,8 @@ class ServerPlugin(gobject.GObject):
self._set_self_avatar() self._set_self_avatar()
# Request presence for everyone on the channel # Request presence for everyone on the channel
self._conn[CONN_INTERFACE_PRESENCE].GetPresence(subscribe_handles, subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers()
ignore_reply=True) self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles)
return True return True
def _set_self_avatar_cb(self, token): def _set_self_avatar_cb(self, token):
@ -504,31 +526,38 @@ class ServerPlugin(gobject.GObject):
return handle return handle
return None return None
def _status_changed_cb(self, state, reason): def _handle_connection_status_change(self, status, reason):
"""Handle notification of connection-status change if status == self._conn_status:
return
state -- CONNECTION_STATUS_*
reason -- integer code describing the reason... if status == CONNECTION_STATUS_CONNECTING:
""" self._conn_status = status
if state == CONNECTION_STATUS_CONNECTING: logging.debug("status: connecting...")
self._conn_status = state elif status == CONNECTION_STATUS_CONNECTED:
logging.debug("State: connecting...")
elif state == CONNECTION_STATUS_CONNECTED:
if self._connected_cb(): if self._connected_cb():
logging.debug("State: connected") logging.debug("status: connected")
self._conn_status = state self._conn_status = status
else: else:
self.cleanup() self.cleanup()
logging.debug("State: was connected, but an error occurred") logging.debug("status: was connected, but an error occurred")
elif state == CONNECTION_STATUS_DISCONNECTED: elif status == CONNECTION_STATUS_DISCONNECTED:
self.cleanup() self.cleanup()
logging.debug("State: disconnected (reason %r)" % reason) logging.debug("status: disconnected (reason %r)" % reason)
if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED: if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED:
# FIXME: handle connection failure; retry later? # FIXME: handle connection failure; retry later?
pass pass
self.emit('status', self._conn_status, int(reason)) self.emit('status', self._conn_status, int(reason))
def _status_changed_cb(self, status, reason):
"""Handle notification of connection-status change
status -- CONNECTION_STATUS_*
reason -- integer code describing the reason...
"""
logging.debug("::: connection status changed to %s" % status)
self._handle_connection_status_change(status, reason)
def start(self): def start(self):
"""Start up the Telepathy networking connections """Start up the Telepathy networking connections
@ -541,35 +570,13 @@ class ServerPlugin(gobject.GObject):
_connect_reply_cb or _connect_error_cb _connect_reply_cb or _connect_error_cb
""" """
logging.debug("Starting up...") logging.debug("Starting up...")
# If the connection is already connected query initial contacts
conn_status = self._conn[CONN_INTERFACE].GetStatus() # Only init connection if we have a valid IP address
if conn_status == CONNECTION_STATUS_CONNECTED: if self._ip4am.props.address:
self._connected_cb() logging.debug("::: Have IP4 address %s, will connect" % self._ip4am.props.address)
subscribe = self._request_list_channel('subscribe') self._init_connection()
subscribe_handles = subscribe[CHANNEL_INTERFACE_GROUP].GetMembers()
self._conn[CONN_INTERFACE_PRESENCE].RequestPresence(subscribe_handles)
elif conn_status == CONNECTION_STATUS_CONNECTING:
pass
else: else:
self._conn[CONN_INTERFACE].Connect(reply_handler=self._connect_reply_cb, logging.debug("::: No IP4 address, postponing connection")
error_handler=self._connect_error_cb)
def _connect_reply_cb(self):
"""Handle connection success"""
if self._reconnect_id > 0:
gobject.source_remove(self._reconnect_id)
def _reconnect(self):
"""Reset number-of-attempted connections and re-attempt"""
self._reconnect_id = 0
self.start()
return False
def _connect_error_cb(self, exception):
"""Handle connection failure"""
logging.debug("Connect error: %s" % exception)
if not self._reconnect_id:
self._reconnect_id = gobject.timeout_add(10000, self._reconnect)
def cleanup(self): def cleanup(self):
"""If we still have a connection, disconnect it""" """If we still have a connection, disconnect it"""
@ -578,6 +585,12 @@ class ServerPlugin(gobject.GObject):
self._conn = None self._conn = None
self._conn_status = CONNECTION_STATUS_DISCONNECTED self._conn_status = CONNECTION_STATUS_DISCONNECTED
for handle in self._online_contacts.keys():
self._contact_offline(handle)
self._online_contacts = {}
self._joined_activites = []
self._activites = {}
def _contact_offline(self, handle): def _contact_offline(self, handle):
"""Handle contact going offline (send message, update set)""" """Handle contact going offline (send message, update set)"""
if not self._online_contacts.has_key(handle): if not self._online_contacts.has_key(handle):
@ -659,6 +672,9 @@ class ServerPlugin(gobject.GObject):
timestamp, statuses = presence[handle] timestamp, statuses = presence[handle]
online = handle in self._online_contacts online = handle in self._online_contacts
for status, params in statuses.items(): for status, params in statuses.items():
if not online and status == "offline":
# weren't online in the first place...
continue
jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0] jid = self._conn[CONN_INTERFACE].InspectHandles(CONNECTION_HANDLE_TYPE_CONTACT, [handle])[0]
olstr = "ONLINE" olstr = "ONLINE"
if not online: olstr = "OFFLINE" if not online: olstr = "OFFLINE"