diff options
Diffstat (limited to 'src/jarabe/model')
-rw-r--r-- | src/jarabe/model/Makefile.am | 2 | ||||
-rw-r--r-- | src/jarabe/model/adhoc.py | 280 | ||||
-rw-r--r-- | src/jarabe/model/bundleregistry.py | 34 | ||||
-rw-r--r-- | src/jarabe/model/network.py | 287 | ||||
-rw-r--r-- | src/jarabe/model/olpcmesh.py | 214 |
5 files changed, 786 insertions, 31 deletions
diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am index 399db65..1df2cde 100644 --- a/src/jarabe/model/Makefile.am +++ b/src/jarabe/model/Makefile.am @@ -1,11 +1,13 @@ sugardir = $(pythondir)/jarabe/model sugar_PYTHON = \ + adhoc.py \ __init__.py \ buddy.py \ bundleregistry.py \ filetransfer.py \ friends.py \ invites.py \ + olpcmesh.py \ owner.py \ neighborhood.py \ network.py \ diff --git a/src/jarabe/model/adhoc.py b/src/jarabe/model/adhoc.py new file mode 100644 index 0000000..5c9d6f5 --- /dev/null +++ b/src/jarabe/model/adhoc.py @@ -0,0 +1,280 @@ +# Copyright (C) 2010 One Laptop per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import dbus +import gobject + +from jarabe.model import network +from jarabe.model.network import Settings +from sugar.util import unique_id +from jarabe.model.network import IP4Config + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' + + +_adhoc_manager_instance = None +def get_adhoc_manager_instance(): + global _adhoc_manager_instance + if _adhoc_manager_instance is None: + _adhoc_manager_instance = AdHocManager() + return _adhoc_manager_instance + + +class AdHocManager(gobject.GObject): + """To mimic the mesh behavior on devices where mesh hardware is + not available we support the creation of an Ad-hoc network on + three channels 1, 6, 11. If Sugar sees no "known" network when it + starts, it does autoconnect to an Ad-hoc network. + + """ + + __gsignals__ = { + 'members-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), + 'state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) + } + + _AUTOCONNECT_TIMEOUT = 30 + _CHANNEL_1 = 1 + _CHANNEL_6 = 6 + _CHANNEL_11 = 11 + + def __init__(self): + gobject.GObject.__init__(self) + + self._bus = dbus.SystemBus() + self._device = None + self._idle_source = 0 + self._listening_called = 0 + self._device_state = network.DEVICE_STATE_UNKNOWN + + self._current_channel = None + self._networks = {self._CHANNEL_1: None, + self._CHANNEL_6: None, + self._CHANNEL_11: None} + + def start_listening(self, device): + self._listening_called += 1 + if self._listening_called > 1: + raise RuntimeError('The start listening method can' \ + ' only be called once.') + + self._device = device + props = dbus.Interface(device, 'org.freedesktop.DBus.Properties') + self._device_state = props.Get(_NM_DEVICE_IFACE, 'State') + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + def stop_listening(self): + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update_state() + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveAccessPoint' in properties and \ + properties['ActiveAccessPoint'] != '/': + active_ap = self._bus.get_object(_NM_SERVICE, + properties['ActiveAccessPoint']) + props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE) + props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True, + reply_handler=self.__get_all_ap_props_reply_cb, + error_handler=self.__get_all_ap_props_error_cb) + + def __get_all_ap_props_reply_cb(self, properties): + if properties['Mode'] == network.NM_802_11_MODE_ADHOC and \ + 'Frequency' in properties: + frequency = properties['Frequency'] + self._current_channel = network.frequency_to_channel(frequency) + else: + self._current_channel = None + self._update_state() + + def __get_all_ap_props_error_cb(self, err): + logging.error('Error getting the access point properties: %s', err) + + def _update_state(self): + self.emit('state-changed', self._current_channel, self._device_state) + + def autoconnect(self): + """Start a timer which basically looks for 30 seconds of inactivity + on the device, then does autoconnect to an Ad-hoc network. + + """ + if self._idle_source != 0: + gobject.source_remove(self._idle_source) + self._idle_source = gobject.timeout_add_seconds( \ + self._AUTOCONNECT_TIMEOUT, self.__idle_check_cb) + + def __idle_check_cb(self): + if self._device_state == network.DEVICE_STATE_DISCONNECTED: + logging.debug("Connect to Ad-hoc network due to inactivity.") + self._autoconnect_adhoc() + return False + + def _autoconnect_adhoc(self): + """First we try if there is an Ad-hoc network that is used by other + learners in the area, if not we default to channel 1. + + """ + if self._networks[self._CHANNEL_1] is not None: + self._connect(self._CHANNEL_1) + elif self._networks[self._CHANNEL_6] is not None: + self._connect(self._CHANNEL_6) + elif self._networks[self._CHANNEL_11] is not None: + self._connect(self._CHANNEL_11) + else: + self._connect(self._CHANNEL_1) + + def activate_channel(self, channel): + """Activate a sugar Ad-hoc network. + + Keyword arguments: + channel -- Channel to connect to (should be 1, 6, 11) + + """ + self._connect(channel) + + def _connect(self, channel): + name = "Ad-hoc Network %d" % channel + connection = network.find_connection_by_ssid(name) + if connection is None: + settings = Settings() + settings.connection.id = name + settings.connection.uuid = unique_id() + settings.connection.type = '802-11-wireless' + settings.wireless.ssid = dbus.ByteArray(name) + settings.wireless.band = 'bg' + settings.wireless.channel = channel + settings.wireless.mode = 'adhoc' + settings.ip4_config = IP4Config() + settings.ip4_config.method = 'link-local' + + connection = network.add_connection(name, settings) + + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + netmgr.ActivateConnection(network.SETTINGS_SERVICE, + connection.path, + self._device.object_path, + '/', + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def deactivate_active_channel(self): + """Deactivate the current active channel.""" + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE) + netmgr_props.Get(_NM_IFACE, 'ActiveConnections', \ + reply_handler=self.__get_active_connections_reply_cb, + error_handler=self.__get_active_connections_error_cb) + + def __get_active_connections_reply_cb(self, active_connections_o): + for connection_o in active_connections_o: + obj = self._bus.get_object(_NM_IFACE, connection_o) + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) + state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') + if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + access_point_o = props.Get(_NM_ACTIVE_CONN_IFACE, + 'SpecificObject') + if access_point_o != '/': + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + netmgr.DeactivateConnection(connection_o) + + def __get_active_connections_error_cb(self, err): + logging.error('Error getting the active connections: %s', err) + + def __activate_reply_cb(self, connection): + logging.debug('Ad-hoc network created: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to create Ad-hoc network: %s', err) + + def add_access_point(self, access_point): + """Add an access point to a network and notify the view to idicate + the member change. + + Keyword arguments: + access_point -- Access Point + + """ + if access_point.name.endswith(' 1'): + self._networks[self._CHANNEL_1] = access_point + self.emit('members-changed', self._CHANNEL_1, True) + elif access_point.name.endswith(' 6'): + self._networks[self._CHANNEL_6] = access_point + self.emit('members-changed', self._CHANNEL_6, True) + elif access_point.name.endswith('11'): + self._networks[self._CHANNEL_11] = access_point + self.emit('members-changed', self._CHANNEL_11, True) + + def is_sugar_adhoc_access_point(self, ap_object_path): + """Checks whether an access point is part of a sugar Ad-hoc network. + + Keyword arguments: + ap_object_path -- Access Point object path + + Return: Boolean + + """ + for access_point in self._networks.values(): + if access_point is not None: + if access_point.model.object_path == ap_object_path: + return True + return False + + def remove_access_point(self, ap_object_path): + """Remove an access point from a sugar Ad-hoc network. + + Keyword arguments: + ap_object_path -- Access Point object path + + """ + for channel in self._networks: + if self._networks[channel] is not None: + if self._networks[channel].model.object_path == ap_object_path: + self.emit('members-changed', channel, False) + self._networks[channel] = None + break diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py index ac785fd..924c18f 100644 --- a/src/jarabe/model/bundleregistry.py +++ b/src/jarabe/model/bundleregistry.py @@ -20,6 +20,7 @@ import logging import traceback import sys +import gconf import gobject import gio import simplejson @@ -27,6 +28,7 @@ import simplejson from sugar.bundle.activitybundle import ActivityBundle from sugar.bundle.contentbundle import ContentBundle from jarabe.journal.journalentrybundle import JournalEntryBundle +from sugar.bundle.bundleversion import NormalizedVersion from sugar.bundle.bundle import MalformedBundleException, \ AlreadyInstalledException, RegistrationException from sugar import env @@ -62,6 +64,14 @@ class BundleRegistry(gobject.GObject): self._last_defaults_mtime = -1 self._favorite_bundles = {} + client = gconf.client_get_default() + self._protected_activities = client.get_list( + '/desktop/sugar/protected_activities', + gconf.VALUE_STRING) + + if self._protected_activities is None: + self._protected_activities = [] + try: self._load_favorites() except Exception: @@ -141,14 +151,16 @@ class BundleRegistry(gobject.GObject): return for bundle_id in default_activities: - max_version = -1 + max_version = '0' for bundle in self._bundles: if bundle.get_bundle_id() == bundle_id and \ - max_version < bundle.get_activity_version(): + NormalizedVersion(max_version) < \ + NormalizedVersion(bundle.get_activity_version()): max_version = bundle.get_activity_version() key = self._get_favorite_key(bundle_id, max_version) - if max_version > -1 and key not in self._favorite_bundles: + if NormalizedVersion(max_version) > NormalizedVersion('0') and \ + key not in self._favorite_bundles: self._favorite_bundles[key] = None logging.debug('After merging: %r' % self._favorite_bundles) @@ -272,6 +284,9 @@ class BundleRegistry(gobject.GObject): key = self._get_favorite_key(bundle_id, version) return key in self._favorite_bundles + def is_activity_protected(self, bundle_id): + return bundle_id in self._protected_activities + def set_bundle_position(self, bundle_id, version, x, y): key = self._get_favorite_key(bundle_id, version) if key not in self._favorite_bundles: @@ -324,8 +339,8 @@ class BundleRegistry(gobject.GObject): for installed_bundle in self._bundles: if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \ - bundle.get_activity_version() == \ - installed_bundle.get_activity_version(): + NormalizedVersion(bundle.get_activity_version()) == \ + NormalizedVersion(installed_bundle.get_activity_version()): return True return False @@ -338,15 +353,15 @@ class BundleRegistry(gobject.GObject): for installed_bundle in self._bundles: if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \ - bundle.get_activity_version() == \ - installed_bundle.get_activity_version(): + NormalizedVersion(bundle.get_activity_version()) <= \ + NormalizedVersion(installed_bundle.get_activity_version()): raise AlreadyInstalledException elif bundle.get_bundle_id() == installed_bundle.get_bundle_id(): self.uninstall(installed_bundle, force=True) install_dir = env.get_user_activities_path() if isinstance(bundle, JournalEntryBundle): - install_path = bundle.install(install_dir, uid) + install_path = bundle.install(uid) else: install_path = bundle.install(install_dir) @@ -371,7 +386,8 @@ class BundleRegistry(gobject.GObject): act = self.get_bundle(bundle.get_bundle_id()) if not force and \ - act.get_activity_version() != bundle.get_activity_version(): + NormalizedVersion(act.get_activity_version()) != \ + NormalizedVersion(bundle.get_activity_version()): logging.warning('Not uninstalling, different bundle present') return elif not act.get_path().startswith(env.get_user_activities_path()): diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py index c1f7969..f0297c9 100644 --- a/src/jarabe/model/network.py +++ b/src/jarabe/model/network.py @@ -1,6 +1,7 @@ # Copyright (C) 2008 Red Hat, Inc. # Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer -# Copyright (C) 2009 One Laptop per Child +# Copyright (C) 2009-2010 One Laptop per Child +# Copyright (C) 2009 Paraguay Educa, Martin Abente # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,14 +22,20 @@ import os import time import dbus +import dbus.service import gobject import ConfigParser +import gconf +import ctypes from sugar import dispatch from sugar import env +from sugar.util import unique_id DEVICE_TYPE_802_3_ETHERNET = 1 DEVICE_TYPE_802_11_WIRELESS = 2 +DEVICE_TYPE_GSM_MODEM = 3 +DEVICE_TYPE_802_11_OLPC_MESH = 6 DEVICE_STATE_UNKNOWN = 0 DEVICE_STATE_UNMANAGED = 1 @@ -41,6 +48,9 @@ DEVICE_STATE_IP_CONFIG = 7 DEVICE_STATE_ACTIVATED = 8 DEVICE_STATE_FAILED = 9 +NM_CONNECTION_TYPE_802_11_WIRELESS = '802-11-wireless' +NM_CONNECTION_TYPE_GSM = 'gsm' + NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0 NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1 NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2 @@ -80,9 +90,48 @@ NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection' NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets' NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +GSM_USERNAME_PATH = '/desktop/sugar/network/gsm/username' +GSM_PASSWORD_PATH = '/desktop/sugar/network/gsm/password' +GSM_NUMBER_PATH = '/desktop/sugar/network/gsm/number' +GSM_APN_PATH = '/desktop/sugar/network/gsm/apn' +GSM_PIN_PATH = '/desktop/sugar/network/gsm/pin' +GSM_PUK_PATH = '/desktop/sugar/network/gsm/puk' + _nm_settings = None _conn_counter = 0 +def frequency_to_channel(frequency): + """Returns the channel matching a given radio channel frequency. If a + frequency is not in the dictionary channel 1 will be returned. + + Keyword arguments: + frequency -- The radio channel frequency in MHz. + + Return: Channel + + """ + ftoc = {2412: 1, 2417: 2, 2422: 3, 2427: 4, + 2432: 5, 2437: 6, 2442: 7, 2447: 8, + 2452: 9, 2457: 10, 2462: 11, 2467: 12, + 2472: 13} + if frequency not in ftoc: + logging.warning("The frequency %s can not be mapped to a channel, " \ + "defaulting to channel 1.", frequency) + return 1 + return ftoc[frequency] + +def is_sugar_adhoc_network(ssid): + """Checks whether an access point is a sugar Ad-hoc network. + + Keyword arguments: + ssid -- Ssid of the access point. + + Return: Boolean + + """ + return ssid.startswith('Ad-hoc Network') + + class WirelessSecurity(object): def __init__(self): self.key_mgmt = None @@ -103,11 +152,14 @@ class WirelessSecurity(object): return wireless_security class Wireless(object): + nm_name = "802-11-wireless" + def __init__(self): self.ssid = None self.security = None self.mode = None self.band = None + self.channel = None def get_dict(self): wireless = {'ssid': self.ssid} @@ -117,8 +169,27 @@ class Wireless(object): wireless['mode'] = self.mode if self.band: wireless['band'] = self.band + if self.channel: + wireless['channel'] = self.channel return wireless +class OlpcMesh(object): + nm_name = "802-11-olpc-mesh" + + def __init__(self, channel, anycast_addr): + self.channel = channel + self.anycast_addr = anycast_addr + + def get_dict(self): + ret = { + "ssid": dbus.ByteArray("olpc-mesh"), + "channel": self.channel, + } + + if self.anycast_addr: + ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr) + return ret + class Connection(object): def __init__(self): self.id = None @@ -146,17 +217,60 @@ class IP4Config(object): ip4_config['method'] = self.method return ip4_config -class Settings(object): +class Serial(object): + def __init__(self): + self.baud = None + + def get_dict(self): + serial = {} + + if self.baud is not None: + serial['baud'] = self.baud + + return serial + +class Ppp(object): def __init__(self): + pass + + def get_dict(self): + ppp = {} + return ppp + +class Gsm(object): + def __init__(self): + self.apn = None + self.number = None + self.username = None + + def get_dict(self): + gsm = {} + + if self.apn is not None: + gsm['apn'] = self.apn + if self.number is not None: + gsm['number'] = self.number + if self.username is not None: + gsm['username'] = self.username + + return gsm + +class Settings(object): + def __init__(self, wireless_cfg=None): self.connection = Connection() self.wireless = Wireless() self.ip4_config = None self.wireless_security = None + if wireless_cfg is not None: + self.wireless = wireless_cfg + else: + self.wireless = Wireless() + def get_dict(self): settings = {} settings['connection'] = self.connection.get_dict() - settings['802-11-wireless'] = self.wireless.get_dict() + settings[self.wireless.nm_name] = self.wireless.get_dict() if self.wireless_security is not None: settings['802-11-wireless-security'] = \ self.wireless_security.get_dict() @@ -189,6 +303,41 @@ class Secrets(object): return settings +class SettingsGsm(object): + def __init__(self): + self.connection = Connection() + self.ip4_config = IP4Config() + self.serial = Serial() + self.ppp = Ppp() + self.gsm = Gsm() + + def get_dict(self): + settings = {} + + settings['connection'] = self.connection.get_dict() + settings['serial'] = self.serial.get_dict() + settings['ppp'] = self.ppp.get_dict() + settings['gsm'] = self.gsm.get_dict() + settings['ipv4'] = self.ip4_config.get_dict() + + return settings + +class SecretsGsm(object): + def __init__(self): + self.password = None + self.pin = None + self.puk = None + + def get_dict(self): + secrets = {} + if self.password is not None: + secrets['password'] = self.password + if self.pin is not None: + secrets['pin'] = self.pin + if self.puk is not None: + secrets['puk'] = self.puk + return {'gsm': secrets} + class NMSettings(dbus.service.Object): def __init__(self): bus = dbus.SystemBus() @@ -207,8 +356,8 @@ class NMSettings(dbus.service.Object): def NewConnection(self, connection_path): pass - def add_connection(self, ssid, conn): - self.connections[ssid] = conn + def add_connection(self, uuid, conn): + self.connections[uuid] = conn conn.secrets_request.connect(self.__secrets_request_cb) self.NewConnection(conn.path) @@ -216,6 +365,11 @@ class NMSettings(dbus.service.Object): self.secrets_request.send(self, connection=sender, response=kwargs['response']) + def clear_connections(self): + for connection in self.connections.values(): + connection.Removed() + self.connections = {} + class SecretsResponse(object): ''' Intermediate object to report the secrets from the dialog back to the connection object and which will inform NM @@ -244,10 +398,40 @@ class NMSettingsConnection(dbus.service.Object): self._settings = settings self._secrets = secrets + @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE, + signature='') + def Removed(self): + pass + + @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE, + signature='a{sa{sv}}') + def Updated(self, settings): + pass + def set_connected(self): - if not self._settings.connection.autoconnect: - self._settings.connection.autoconnect = True + if self._settings.connection.type == NM_CONNECTION_TYPE_GSM: + self._settings.connection.timestamp = int(time.time()) + elif not self._settings.connection.autoconnect: self._settings.connection.timestamp = int(time.time()) + self._settings.connection.autoconnect = True + self.Updated(self._settings.get_dict()) + self.save() + + try: + # try to flush resolver cache - SL#1940 + # ctypes' syntactic sugar does not work + # so we must get the func ptr explicitly + libc = ctypes.CDLL('libc.so.6') + res_init = getattr(libc, '__res_init') + res_init(None) + except: + logging.exception('Error calling libc.__res_init') + + def set_disconnected(self): + if self._settings.connection.autoconnect: + self._settings.connection.autoconnect = False + self._settings.connection.timestamp = None + self.Updated(self._settings.get_dict()) self.save() def set_secrets(self, secrets): @@ -258,6 +442,10 @@ class NMSettingsConnection(dbus.service.Object): return self._settings def save(self): + # We only save wifi settins + if self._settings.connection.type != NM_CONNECTION_TYPE_802_11_WIRELESS: + return + profile_path = env.get_profile_path() config_path = os.path.join(profile_path, 'nm', 'connections.cfg') @@ -336,7 +524,6 @@ class NMSettingsConnection(dbus.service.Object): else: reply(self._secrets.get_dict()) - class AccessPoint(gobject.GObject): __gsignals__ = { 'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, @@ -357,10 +544,10 @@ class AccessPoint(gobject.GObject): self.wpa_flags = 0 self.rsn_flags = 0 self.mode = 0 + self.channel = 0 def initialize(self): - model_props = dbus.Interface(self.model, - 'org.freedesktop.DBus.Properties') + model_props = dbus.Interface(self.model, dbus.PROPERTIES_IFACE) model_props.GetAll(NM_ACCESSPOINT_IFACE, byte_arrays=True, reply_handler=self._ap_properties_changed_cb, error_handler=self._get_all_props_error_cb) @@ -426,6 +613,8 @@ class AccessPoint(gobject.GObject): self.rsn_flags = properties['RsnFlags'] if 'Mode' in properties: self.mode = properties['Mode'] + if 'Frequency' in properties: + self.channel = frequency_to_channel(properties['Frequency']) self._initialized = True self.emit('props-changed', old_hash) @@ -452,36 +641,38 @@ def get_settings(): load_connections() return _nm_settings -def find_connection(ssid): +def find_connection_by_ssid(ssid): connections = get_settings().connections - if ssid in connections: - return connections[ssid] - else: - return None -def add_connection(ssid, settings, secrets=None): + for conn_index in connections: + connection = connections[conn_index] + if connection._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: + if connection._settings.wireless.ssid == ssid: + return connection + + return None + +def add_connection(uuid, settings, secrets=None): global _conn_counter path = NM_SETTINGS_PATH + '/' + str(_conn_counter) _conn_counter += 1 conn = NMSettingsConnection(path, settings, secrets) - _nm_settings.add_connection(ssid, conn) + _nm_settings.add_connection(uuid, conn) return conn -def load_connections(): +def load_wifi_connections(): profile_path = env.get_profile_path() config_path = os.path.join(profile_path, 'nm', 'connections.cfg') - config = ConfigParser.ConfigParser() - if not os.path.exists(config_path): if not os.path.exists(os.path.dirname(config_path)): os.makedirs(os.path.dirname(config_path), 0755) f = open(config_path, 'w') - config.write(f) f.close() + config = ConfigParser.ConfigParser() try: if not config.read(config_path): logging.error('Error reading the nm config file') @@ -534,4 +725,56 @@ def load_connections(): except ConfigParser.Error, e: logging.error('Error reading section: %s' % e) else: - add_connection(ssid, settings, secrets) + add_connection(uuid, settings, secrets) + +def count_connections(): + return len(get_settings().connections) + +def clear_connections(): + _nm_settings.clear_connections() + + profile_path = env.get_profile_path() + config_path = os.path.join(profile_path, 'nm', 'connections.cfg') + + if not os.path.exists(os.path.dirname(config_path)): + os.makedirs(os.path.dirname(config_path), 0755) + f = open(config_path, 'w') + f.close() + +def load_gsm_connection(): + client = gconf.client_get_default() + + settings = SettingsGsm() + settings.gsm.username = client.get_string(GSM_USERNAME_PATH) or '' + settings.gsm.number = client.get_string(GSM_NUMBER_PATH) or '' + settings.gsm.apn = client.get_string(GSM_APN_PATH) or '' + + secrets = SecretsGsm() + secrets.pin = client.get_string(GSM_PIN_PATH) or '' + secrets.puk = client.get_string(GSM_PUK_PATH) or '' + secrets.password = client.get_string(GSM_PASSWORD_PATH) or '' + + settings.connection.id = 'gsm' + settings.connection.type = NM_CONNECTION_TYPE_GSM + uuid = settings.connection.uuid = unique_id() + settings.connection.autoconnect = False + settings.ip4_config.method = 'auto' + settings.serial.baud = 115200 + + try: + add_connection(uuid, settings, secrets) + except Exception: + logging.exception('While adding gsm connection') + +def load_connections(): + load_wifi_connections() + load_gsm_connection() + +def find_gsm_connection(): + connections = get_settings().connections + + for connection in connections.values(): + if connection.get_settings().connection.type == NM_CONNECTION_TYPE_GSM: + return connection + + return None diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py new file mode 100644 index 0000000..7873692 --- /dev/null +++ b/src/jarabe/model/olpcmesh.py @@ -0,0 +1,214 @@ +# Copyright (C) 2009, 2010 One Laptop per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import dbus +import gobject + +from jarabe.model import network +from jarabe.model.network import Settings +from jarabe.model.network import OlpcMesh as OlpcMeshSettings +from sugar.util import unique_id + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' + +_XS_ANYCAST = "\xc0\x27\xc0\x27\xc0\x00" + +DEVICE_STATE_UNKNOWN = 0 +DEVICE_STATE_UNMANAGED = 1 +DEVICE_STATE_UNAVAILABLE = 2 +DEVICE_STATE_DISCONNECTED = 3 +DEVICE_STATE_PREPARE = 4 +DEVICE_STATE_CONFIG = 5 +DEVICE_STATE_NEED_AUTH = 6 +DEVICE_STATE_IP_CONFIG = 7 +DEVICE_STATE_ACTIVATED = 8 +DEVICE_STATE_FAILED = 9 + +class OlpcMeshManager(object): + def __init__(self, mesh_device): + self._bus = dbus.SystemBus() + + self.mesh_device = mesh_device + self.eth_device = self._get_companion_device() + + self._connection_queue = [] + """Stack of connections that we'll iterate through until we find one + that works. + + """ + + props = dbus.Interface(self.mesh_device, + 'org.freedesktop.DBus.Properties') + props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_mesh_state_reply_cb, + error_handler=self.__get_state_error_cb) + + props = dbus.Interface(self.eth_device, + 'org.freedesktop.DBus.Properties') + props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_eth_state_reply_cb, + error_handler=self.__get_state_error_cb) + + self._bus.add_signal_receiver(self.__eth_device_state_changed_cb, + signal_name='StateChanged', + path=self.eth_device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._bus.add_signal_receiver(self.__mshdev_state_changed_cb, + signal_name='StateChanged', + path=self.mesh_device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._idle_source = 0 + self._mesh_device_state = DEVICE_STATE_UNKNOWN + self._eth_device_state = DEVICE_STATE_UNKNOWN + + if self._have_configured_connections(): + self._start_automesh_timer() + else: + self._start_automesh() + + def _get_companion_device(self): + props = dbus.Interface(self.mesh_device, + 'org.freedesktop.DBus.Properties') + eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion') + return self._bus.get_object(_NM_SERVICE, eth_device_o) + + def _have_configured_connections(self): + return len(network.get_settings().connections) > 0 + + def _start_automesh_timer(self): + """Start our timer system which basically looks for 10 seconds of + inactivity on both devices, then starts automesh. + + """ + if self._idle_source != 0: + gobject.source_remove(self._idle_source) + self._idle_source = gobject.timeout_add_seconds(10, self._idle_check) + + def __get_state_error_cb(self, err): + logging.debug('Error getting the device state: %s', err) + + def __get_mesh_state_reply_cb(self, state): + self._mesh_device_state = state + self._maybe_schedule_idle_check() + + def __get_eth_state_reply_cb(self, state): + self._eth_device_state = state + self._maybe_schedule_idle_check() + + def __eth_device_state_changed_cb(self, new_state, old_state, reason): + """If a connection is activated on the eth device, stop trying our + automatic connections. + + """ + self._eth_device_state = new_state + self._maybe_schedule_idle_check() + + if new_state >= DEVICE_STATE_PREPARE \ + and new_state <= DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + self._connection_queue = [] + + def __mshdev_state_changed_cb(self, new_state, old_state, reason): + self._mesh_device_state = new_state + self._maybe_schedule_idle_check() + + if new_state == DEVICE_STATE_FAILED: + self._try_next_connection_from_queue() + elif new_state == DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + self._empty_connection_queue() + + def _maybe_schedule_idle_check(self): + if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ + and self._eth_device_state == DEVICE_STATE_DISCONNECTED: + self._start_automesh_timer() + + def _idle_check(self): + if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ + and self._eth_device_state == DEVICE_STATE_DISCONNECTED: + logging.debug("starting automesh due to inactivity") + self._start_automesh() + return False + + def _make_connection(self, channel, anycast_address=None): + wireless_config = OlpcMeshSettings(channel, anycast_address) + settings = Settings(wireless_cfg=wireless_config) + if not anycast_address: + settings.ip4_config = network.IP4Config() + settings.ip4_config.method = 'link-local' + settings.connection.id = 'olpc-mesh-' + str(channel) + settings.connection.uuid = unique_id() + settings.connection.type = '802-11-olpc-mesh' + connection = network.add_connection(settings.connection.id, settings) + return connection + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to activate connection: %s', err) + + def _activate_connection(self, channel, anycast_address=None): + logging.debug("activate channel %d anycast %r", + channel, anycast_address) + proxy = self._bus.get_object(_NM_SERVICE, _NM_PATH) + network_manager = dbus.Interface(proxy, _NM_IFACE) + connection = self._make_connection(channel, anycast_address) + + network_manager.ActivateConnection(network.SETTINGS_SERVICE, + connection.path, + self.mesh_device.object_path, + self.mesh_device.object_path, + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def _try_next_connection_from_queue(self): + if len(self._connection_queue) == 0: + return + + channel, anycast = self._connection_queue.pop() + self._activate_connection(channel, anycast) + + def _empty_connection_queue(self): + self._connection_queue = [] + + def user_activate_channel(self, channel): + """Activate a mesh connection on a user-specified channel. + Looks for XS first, then resorts to simple mesh.""" + self._empty_connection_queue() + self._connection_queue.append((channel, None)) + self._connection_queue.append((channel, _XS_ANYCAST)) + self._try_next_connection_from_queue() + + def _start_automesh(self): + """Start meshing automatically, intended when there are no better + networks to connect to. First looks for XS on all channels, then falls + back to simple mesh on channel 1.""" + self._empty_connection_queue() + self._connection_queue.append((1, None)) + self._connection_queue.append((11, _XS_ANYCAST)) + self._connection_queue.append((6, _XS_ANYCAST)) + self._connection_queue.append((1, _XS_ANYCAST)) + self._try_next_connection_from_queue() + |