[Sugar-devel] [sugar 0.98 PATCH] sl#1630, sl#3325: Support for mobile broadband provider database, plus ensure that all settings are persisted.
Ajay Garg
ajay at activitycentral.com
Tue Jul 31 16:14:43 EDT 2012
Hi all.
Just a gentle reminder :)
Or, at least a status update will be highly appreciated :)
Thanks and Regards,
Ajay
On Sun, Jul 29, 2012 at 2:49 PM, Ajay Garg <ajay at activitycentral.com> wrote:
> Hi all.
>
> Kindly review the patch, so that progress may be made in pushing this :)
>
>
> Thanks and Regards,
> Ajay
>
>
>
> On Fri, Jul 20, 2012 at 5:02 PM, Ajay Garg <ajay at activitycentral.com>wrote:
>
>>
>> First of all, please note that the first, major part of adding the
>> database support
>> has been done by ::
>>
>> Andres Ambrois <andresambrois at gmail.com>
>>
>> Details of Andres's work are present at
>> http://bugs.sugarlabs.org/ticket/1630
>>
>>
>>
>> ===============================================================================
>>
>>
>> So, this patch ::
>>
>> a)
>> Ports Andres's patch from Sugar-0.88 to Sugar-0.98.
>>
>> b)
>> Adds the functionality, to keep the "Country, Provider, Plan" settings
>> persisted as well.
>>
>> c)
>> Also, some refactoring has been done, to avoid duplicate code as much
>> as possible.
>>
>>
>>
>> ================================================================================
>>
>>
>> Other relevant literature/discussions/logs at ::
>>
>> a) Wiki Feature Page ::
>> http://wiki.sugarlabs.org/go/Features/3G_Support/Database_Support
>>
>> b)
>> Feature Design ML ::
>> http://lists.sugarlabs.org/archive/sugar-devel/2012-July/038429.html
>>
>> c)
>> Brain-storming / Go-ahead meeting logs ::
>>
>> http://meeting.sugarlabs.org/sugar-meeting/meetings/2012-07-18T15:00:17
>>
>>
>>
>> ================================================================================
>>
>>
>> This patch has been tested to work on NM 0.9.
>>
>> The 3G modem gets connected, only and only when the correct entries are
>> entered
>> (by choosing the correct plan, amongst the vast number of available
>> plans).
>>
>>
>>
>> ================================================================================
>>
>>
>>
>>
>>
>>
>>
>>
>> .../cpsection/modemconfiguration/Makefile.am | 2 +
>> extensions/cpsection/modemconfiguration/config.py | 25 +++
>> extensions/cpsection/modemconfiguration/model.py | 163
>> ++++++++++++++++
>> extensions/cpsection/modemconfiguration/view.py | 198
>> +++++++++++++++++---
>> 4 files changed, 365 insertions(+), 23 deletions(-)
>> create mode 100644 extensions/cpsection/modemconfiguration/config.py
>>
>> diff --git a/extensions/cpsection/modemconfiguration/Makefile.am
>> b/extensions/cpsection/modemconfiguration/Makefile.am
>> index 3e2613e..525e02e 100644
>> --- a/extensions/cpsection/modemconfiguration/Makefile.am
>> +++ b/extensions/cpsection/modemconfiguration/Makefile.am
>> @@ -4,3 +4,5 @@ sugar_PYTHON = \
>> __init__.py \
>> model.py \
>> view.py
>> +
>> +nodist_sugar_PYTHON = config.py
>> diff --git a/extensions/cpsection/modemconfiguration/config.py
>> b/extensions/cpsection/modemconfiguration/config.py
>> new file mode 100644
>> index 0000000..963616d
>> --- /dev/null
>> +++ b/extensions/cpsection/modemconfiguration/config.py
>> @@ -0,0 +1,25 @@
>> +# -*- encoding: utf-8 -*-
>> +# Copyright (C) 2010 Andres Ambrois
>> +#
>> +# 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
>> US
>> +
>> +
>> +PROVIDERS_PATH =
>> "/usr/share/mobile-broadband-provider-info/serviceproviders.xml"
>> +PROVIDERS_FORMAT_SUPPORTED = "2.0"
>> +COUNTRY_CODES_PATH = "/usr/share/zoneinfo/iso3166.tab"
>> +
>> +GSM_COUNTRY_PATH = '/desktop/sugar/network/gsm/country'
>> +GSM_PROVIDERS_PATH = '/desktop/sugar/network/gsm/providers'
>> +GSM_PLAN_PATH = '/desktop/sugar/network/gsm/plan'
>> diff --git a/extensions/cpsection/modemconfiguration/model.py
>> b/extensions/cpsection/modemconfiguration/model.py
>> index 969b5d9..2e4cd7d 100755
>> --- a/extensions/cpsection/modemconfiguration/model.py
>> +++ b/extensions/cpsection/modemconfiguration/model.py
>> @@ -1,4 +1,8 @@
>> # Copyright (C) 2009 Paraguay Educa, Martin Abente
>> +# Copyright (C) 2010 Andres Ambrois <andresambrois at gmail.com>
>> +# Copyright (C) 2010 Anish Mangal <anish at activitycentral.com>
>> +# Copyright (C) 2012 Ajay Garg <ajay at activitycentral.com>
>> +
>> #
>> # 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
>> @@ -18,10 +22,22 @@ import logging
>>
>> import dbus
>> import gtk
>> +import os
>> +import locale
>> +import logging
>> +import gconf
>> +
>> +from xml.etree.cElementTree import ElementTree
>> +from gettext import gettext as _
>>
>> from jarabe.model import network
>>
>>
>> +from cpsection.modemconfiguration.config import PROVIDERS_PATH, \
>> +
>> PROVIDERS_FORMAT_SUPPORTED, \
>> + COUNTRY_CODES_PATH
>> +
>> +
>> def get_connection():
>> return network.find_gsm_connection()
>>
>> @@ -98,3 +114,150 @@ def set_modem_settings(modem_settings):
>> _set_or_clear(gsm_settings, 'apn', apn)
>> _set_or_clear(gsm_settings, 'pin', pin)
>> connection.update_settings(settings)
>> +
>> +
>> +def has_providers_db():
>> + if not os.path.isfile(COUNTRY_CODES_PATH):
>> + logging.debug("Mobile broadband provider database: Country " \
>> + "codes path %s not found.", COUNTRY_CODES_PATH)
>> + return False
>> + try:
>> + tree = ElementTree(file=PROVIDERS_PATH)
>> + except (IOError, SyntaxError), e:
>> + logging.debug("Mobile broadband provider database: Could not
>> read " \
>> + "provider information %s error=%s",
>> PROVIDERS_PATH)
>> + return False
>> + else:
>> + elem = tree.getroot()
>> + if elem is None or elem.get('format') !=
>> PROVIDERS_FORMAT_SUPPORTED:
>> + logging.debug("Mobile broadband provider database: Could not
>> " \
>> + "read provider information. %s is wrong
>> format.",
>> + elem.get('format'))
>> + return False
>> + return True
>> +
>> +
>> +class CountryListStore(gtk.ListStore):
>> + COUNTRY_CODE = locale.getdefaultlocale()[0][3:5].lower()
>> +
>> + def __init__(self):
>> + gtk.ListStore.__init__(self, str, object)
>> + codes = {}
>> + with open(COUNTRY_CODES_PATH) as codes_file:
>> + for line in codes_file:
>> + if line.startswith('#'):
>> + continue
>> + code, name = line.split('\t')[:2]
>> + codes[code.lower()] = name.strip()
>> + etree = ElementTree(file=PROVIDERS_PATH).getroot()
>> + self._country_idx = None
>> + i = 0
>> + for elem in etree.findall('.//country'):
>> + code = elem.attrib['code']
>> + if code == self.COUNTRY_CODE:
>> + self._country_idx = i
>> + else:
>> + i += 1
>> + if code in codes:
>> + self.append((codes[code], elem))
>> + else:
>> + self.append((code, elem))
>> +
>> + def get_row_providers(self, row):
>> + return self[row][1]
>> +
>> + def guess_country_row(self):
>> + if self._country_idx is not None:
>> + return self._country_idx
>> + else:
>> + return 0
>> +
>> + def search_index_by_code(self, code):
>> + for index in range(0, len(self)):
>> + if self[index][0] == code:
>> + return index
>> + return -1
>> +
>> +
>> +class ProviderListStore(gtk.ListStore):
>> + def __init__(self, elem):
>> + gtk.ListStore.__init__(self, str, object)
>> + for provider_elem in elem.findall('.//provider'):
>> + apns = provider_elem.findall('.//apn')
>> + if not apns:
>> + # Skip carriers with CDMA entries only
>> + continue
>> + self.append((provider_elem.find('.//name').text, apns))
>> +
>> + def get_row_plans(self, row):
>> + return self[row][1]
>> +
>> + def guess_providers_row(self):
>> + # Simply return the first entry as the default.
>> + return 0
>> +
>> + def search_index_by_code(self, code):
>> + for index in range(0, len(self)):
>> + if self[index][0] == code:
>> + return index
>> + return -1
>> +
>> +
>> +class PlanListStore(gtk.ListStore):
>> + LANG_NS_ATTR = '{http://www.w3.org/XML/1998/namespace}lang'
>> + LANG = locale.getdefaultlocale()[0][:2]
>> + DEFAULT_NUMBER = '*99#'
>> +
>> + def __init__(self, elems):
>> + gtk.ListStore.__init__(self, str, object)
>> + for apn_elem in elems:
>> + plan = {}
>> + names = apn_elem.findall('.//name')
>> + if names:
>> + for name in names:
>> + if name.get(self.LANG_NS_ATTR) is None:
>> + # serviceproviders.xml default value
>> + plan['name'] = name.text
>> + elif name.get(self.LANG_NS_ATTR) == self.LANG:
>> + # Great! We found a name value for our locale!
>> + plan['name'] = name.text
>> + break
>> + else:
>> + plan['name'] = _('Default')
>> + plan['apn'] = apn_elem.get('value')
>> + user = apn_elem.find('.//username')
>> + if user is not None:
>> + plan['username'] = user.text
>> + else:
>> + plan['username'] = ''
>> + passwd = apn_elem.find('.//password')
>> + if passwd is not None:
>> + plan['password'] = passwd.text
>> + else:
>> + plan['password'] = ''
>> +
>> + plan['number'] = self.DEFAULT_NUMBER
>> +
>> + self.append((plan['name'], plan))
>> +
>> + def get_row_plan(self, row):
>> + return self[row][1]
>> +
>> + def guess_plan_row(self):
>> + # Simply return the first entry as the default.
>> + return 0
>> +
>> + def search_index_by_code(self, code):
>> + for index in range(0, len(self)):
>> + if self[index][0] == code:
>> + return index
>> + return -1
>> +
>> +
>> +def get_gconf_setting_string(gconf_key):
>> + client = gconf.client_get_default()
>> + return client.get_string(gconf_key) or ''
>> +
>> +def set_gconf_setting_string(gconf_key, gconf_setting_string_value):
>> + client = gconf.client_get_default()
>> + client.set_string(gconf_key, gconf_setting_string_value)
>> diff --git a/extensions/cpsection/modemconfiguration/view.py
>> b/extensions/cpsection/modemconfiguration/view.py
>> index 4ce6c0d..68a4b3d 100644
>> --- a/extensions/cpsection/modemconfiguration/view.py
>> +++ b/extensions/cpsection/modemconfiguration/view.py
>> @@ -1,4 +1,7 @@
>> # Copyright (C) 2009 Paraguay Educa, Martin Abente
>> +# Copyright (C) 2010 Andres Ambrois <andresambrois at gmail.com>
>> +# Copyright (C) 2010 Anish Mangal <anish at activitycentral.com>
>> +# Copyright (C) 2012 Ajay Garg <ajay at activitycentral.com>
>> #
>> # 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
>> @@ -24,6 +27,11 @@ from sugar.graphics import style
>>
>> from jarabe.controlpanel.sectionview import SectionView
>>
>> +from cpsection.modemconfiguration.config import GSM_COUNTRY_PATH, \
>> + GSM_PROVIDERS_PATH, \
>> + GSM_PLAN_PATH
>> +
>> +
>>
>> APPLY_TIMEOUT = 1000
>>
>> @@ -63,6 +71,17 @@ class ModemConfiguration(SectionView):
>> self.set_border_width(style.DEFAULT_SPACING)
>> self.set_spacing(style.DEFAULT_SPACING)
>> self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
>> + self._combo_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
>> +
>> + scrolled_win = gtk.ScrolledWindow()
>> + scrolled_win.set_policy(gtk.POLICY_AUTOMATIC,
>> gtk.POLICY_AUTOMATIC)
>> + scrolled_win.show()
>> + self.add(scrolled_win)
>> +
>> + main_box = gtk.VBox(spacing=style.DEFAULT_SPACING)
>> + main_box.set_border_width(style.DEFAULT_SPACING)
>> + main_box.show()
>> + scrolled_win.add_with_viewport(main_box)
>>
>> explanation = _('You will need to provide the following
>> information'
>> ' to set up a mobile broadband connection to a'
>> @@ -71,41 +90,85 @@ class ModemConfiguration(SectionView):
>> self._text.set_width_chars(100)
>> self._text.set_line_wrap(True)
>> self._text.set_alignment(0, 0)
>> - self.pack_start(self._text, False)
>> + main_box.pack_start(self._text, False)
>> self._text.show()
>>
>> +
>> +
>> + if model.has_providers_db():
>> + self._upper_box = gtk.VBox(spacing=style.DEFAULT_SPACING)
>> + self._upper_box.set_border_width(style.DEFAULT_SPACING)
>> + main_box.pack_start(self._upper_box, expand=False)
>> + self._upper_box.show()
>> +
>> + # Do not attach any 'change'-handlers for now.
>> + # They will be attached (once per combobox), once the
>> + # individual combobox is processed at startup.
>> + self._country_store = model.CountryListStore()
>> + self._country_combo = gtk.ComboBox(self._country_store)
>> + self._attach_combobox_widget(_('Country:'),
>> + self._country_combo)
>> +
>> + self._providers_combo = gtk.ComboBox()
>> + self._attach_combobox_widget(_('Provider:'),
>> + self._providers_combo)
>> +
>> + self._plan_combo = gtk.ComboBox()
>> + self._attach_combobox_widget(_('Plan:'),
>> + self._plan_combo)
>> +
>> + separator = gtk.HSeparator()
>> + main_box.pack_start(separator, False)
>> + separator.show()
>> +
>> + self._lower_box = gtk.VBox(spacing=style.DEFAULT_SPACING)
>> + self._lower_box.set_border_width(style.DEFAULT_SPACING)
>> + main_box.pack_start(self._lower_box, expand=False)
>> + self._lower_box.show()
>> +
>> self._username_entry = EntryWithLabel(_('Username:'))
>> - self._username_entry.entry.connect('changed',
>> self.__entry_changed_cb)
>> - self._group.add_widget(self._username_entry.label)
>> - self.pack_start(self._username_entry, expand=False)
>> - self._username_entry.show()
>> + self._attach_entry_widget(self._username_entry)
>>
>> self._password_entry = EntryWithLabel(_('Password:'))
>> - self._password_entry.entry.connect('changed',
>> self.__entry_changed_cb)
>> - self._group.add_widget(self._password_entry.label)
>> - self.pack_start(self._password_entry, expand=False)
>> - self._password_entry.show()
>> + self._attach_entry_widget(self._password_entry)
>>
>> self._number_entry = EntryWithLabel(_('Number:'))
>> - self._number_entry.entry.connect('changed',
>> self.__entry_changed_cb)
>> - self._group.add_widget(self._number_entry.label)
>> - self.pack_start(self._number_entry, expand=False)
>> - self._number_entry.show()
>> + self._attach_entry_widget(self._number_entry)
>>
>> self._apn_entry = EntryWithLabel(_('Access Point Name (APN):'))
>> - self._apn_entry.entry.connect('changed', self.__entry_changed_cb)
>> - self._group.add_widget(self._apn_entry.label)
>> - self.pack_start(self._apn_entry, expand=False)
>> - self._apn_entry.show()
>> + self._attach_entry_widget(self._apn_entry)
>>
>> self._pin_entry = EntryWithLabel(_('Personal Identity Number
>> (PIN):'))
>> - self._pin_entry.entry.connect('changed', self.__entry_changed_cb)
>> - self._group.add_widget(self._pin_entry.label)
>> - self.pack_start(self._pin_entry, expand=False)
>> - self._pin_entry.show()
>> + self._attach_entry_widget(self._pin_entry)
>>
>> self.setup()
>>
>> + def _attach_combobox_widget(self, label_text, combobox_obj):
>> + box = gtk.HBox(spacing=style.DEFAULT_SPACING)
>> + label = gtk.Label(label_text)
>> + self._group.add_widget(label)
>> + label.set_alignment(1, 0.5)
>> + box.pack_start(label, False)
>> + label.show()
>> +
>> + self._combo_group.add_widget(combobox_obj)
>> + cell = gtk.CellRendererText()
>> + cell.props.xalign = 0.5
>> + cell.set_property('width-chars', 25)
>> + combobox_obj.pack_start(cell)
>> + combobox_obj.add_attribute(cell, 'text', 0)
>> +
>> + box.pack_start(combobox_obj, False)
>> + combobox_obj.show()
>> + self._upper_box.pack_start(box, False)
>> + box.show()
>> +
>> + def _attach_entry_widget(self, entry_with_label_obj):
>> + entry_with_label_obj.entry.connect('changed',
>> self.__entry_changed_cb)
>> + self._group.add_widget(entry_with_label_obj.label)
>> + self._lower_box.pack_start(entry_with_label_obj, expand=False)
>> + entry_with_label_obj.show()
>> +
>> def undo(self):
>> self._model.undo()
>>
>> @@ -113,11 +176,28 @@ class ModemConfiguration(SectionView):
>> """Populate an entry with text, without triggering its 'changed'
>> handler."""
>> entry = entrywithlabel.entry
>> - entry.handler_block_by_func(self.__entry_changed_cb)
>> +
>> + # Do not block/unblock the callback functions.
>> + #
>> + # Thus, the savings will be persisted to the NM settings,
>> + # whenever any setting on the UI changes (by user-intervention,
>> + # or otherwise).
>> + #entry.handler_block_by_func(self.__entry_changed_cb)
>> entry.set_text(text)
>> - entry.handler_unblock_by_func(self.__entry_changed_cb)
>> + #entry.handler_unblock_by_func(self.__entry_changed_cb)
>>
>> def setup(self):
>> + if self._model.has_providers_db():
>> + persisted_country =
>> self._model.get_gconf_setting_string(GSM_COUNTRY_PATH)
>> + if (self._model.has_providers_db()) and (persisted_country
>> != ''):
>> +
>> self._country_combo.set_active(self._country_store.search_index_by_code(persisted_country))
>> + else:
>> +
>> self._country_combo.set_active(self._country_store.guess_country_row())
>> +
>> + # Call the selected callback anyway, so as to chain-set the
>> + # default values for providers and the plans.
>> + self.__country_selected_cb(self._country_combo, setup=True)
>> +
>> settings = self._model.get_modem_settings()
>> self._populate_entry(self._username_entry,
>> settings.get('username', ''))
>> @@ -133,6 +213,78 @@ class ModemConfiguration(SectionView):
>> self._timeout_sid = gobject.timeout_add(APPLY_TIMEOUT,
>> self.__timeout_cb)
>>
>> + def _get_selected_text(self, combo):
>> + active_iter = combo.get_active_iter()
>> + return combo.get_model().get(active_iter, 0)[0]
>> +
>> + def __country_selected_cb(self, combo, setup=False):
>> + country = self._get_selected_text(combo)
>> + self._model.set_gconf_setting_string(GSM_COUNTRY_PATH, country)
>> +
>> + model = combo.get_model()
>> + providers = model.get_row_providers(combo.get_active())
>> + self._providers_liststore =
>> self._model.ProviderListStore(providers)
>> + self._providers_combo.set_model(self._providers_liststore)
>> +
>> + # Set the default provider as well.
>> + if setup:
>> + persisted_provider =
>> self._model.get_gconf_setting_string(GSM_PROVIDERS_PATH)
>> + if persisted_provider == '':
>> +
>> self._providers_combo.set_active(self._providers_liststore.guess_providers_row())
>> + else:
>> +
>> self._providers_combo.set_active(self._providers_liststore.search_index_by_code(persisted_provider))
>> + else:
>> +
>> self._providers_combo.set_active(self._providers_liststore.guess_providers_row())
>> +
>> + # Country-combobox processed once at startip; now, attach the
>> + # change-handler.
>> + self._country_combo.connect('changed',
>> self.__country_selected_cb, False)
>> +
>> + # Call the callback, so that default provider may be set.
>> + self.__provider_selected_cb(self._providers_combo, setup)
>> +
>> + def __provider_selected_cb(self, combo, setup=False):
>> + provider = self._get_selected_text(combo)
>> + self._model.set_gconf_setting_string(GSM_PROVIDERS_PATH,
>> provider)
>> +
>> + model = combo.get_model()
>> + plans = model.get_row_plans(combo.get_active())
>> + self._plan_liststore = self._model.PlanListStore(plans)
>> + self._plan_combo.set_model(self._plan_liststore)
>> +
>> + # Set the default plan as well.
>> + if setup:
>> + persisted_plan =
>> self._model.get_gconf_setting_string(GSM_PLAN_PATH)
>> + if persisted_plan == '':
>> +
>> self._plan_combo.set_active(self._plan_liststore.guess_plan_row())
>> + else:
>> +
>> self._plan_combo.set_active(self._plan_liststore.search_index_by_code(persisted_plan))
>> + else:
>> +
>> self._plan_combo.set_active(self._plan_liststore.guess_plan_row())
>> +
>> + # Providers-combobox processed once at startip; now, attach the
>> + # change-handler.
>> + self._providers_combo.connect('changed',
>> self.__provider_selected_cb, False)
>> +
>> + # Call the callback, so that the default plan is set.
>> + self.__plan_selected_cb(self._plan_combo, setup)
>> +
>> + def __plan_selected_cb(self, combo, setup=False):
>> + plan = self._get_selected_text(combo)
>> + self._model.set_gconf_setting_string(GSM_PLAN_PATH, plan)
>> +
>> + # Plan-combobox processed once at startip; now, attach the
>> + # change-handler.
>> + self._plan_combo.connect('changed', self.__plan_selected_cb,
>> False)
>> +
>> + model = combo.get_model()
>> + plan = model.get_row_plan(combo.get_active())
>> +
>> + self._populate_entry(self._username_entry, plan['username'])
>> + self._populate_entry(self._password_entry, plan['password'])
>> + self._populate_entry(self._apn_entry, plan['apn'])
>> + self._populate_entry(self._number_entry, plan['number'])
>> +
>> def __timeout_cb(self):
>> self._timeout_sid = 0
>> settings = {}
>> --
>> 1.7.10.2
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.sugarlabs.org/archive/sugar-devel/attachments/20120801/dfc86c70/attachment-0001.html>
More information about the Sugar-devel
mailing list