diff options
Diffstat (limited to 'src/ppm')
-rw-r--r-- | src/ppm/.gitignore | 2 | ||||
-rw-r--r-- | src/ppm/Makefile.am | 23 | ||||
-rw-r--r-- | src/ppm/__init__.py.in | 5 | ||||
-rw-r--r-- | src/ppm/modemproxy.py | 162 | ||||
-rw-r--r-- | src/ppm/provider.py | 78 | ||||
-rw-r--r-- | src/ppm/providerdb.py | 140 |
6 files changed, 410 insertions, 0 deletions
diff --git a/src/ppm/.gitignore b/src/ppm/.gitignore new file mode 100644 index 0000000..1a38d62 --- /dev/null +++ b/src/ppm/.gitignore @@ -0,0 +1,2 @@ +__init__.py +*.pyc diff --git a/src/ppm/Makefile.am b/src/ppm/Makefile.am new file mode 100644 index 0000000..7649cbb --- /dev/null +++ b/src/ppm/Makefile.am @@ -0,0 +1,23 @@ +NULL = + +pythondir = $(pkgdatadir)/ppm/ +python_DATA_IN = __init__.py.in +python_DATA = \ + __init__.py \ + modemproxy.py \ + provider.py \ + providerdb.py \ + $(NULL) + + +EXTRA_DIST = $(python_DATA) $(python_DATA_IN) + +%.py: $(srcdir)/%.py.in + sed -e "s,::PACKAGE::,$(PACKAGE)," \ + -e "s,::PYLIBDIR::,$(pkgdatadir)," \ + -e "s,::GETTEXTDIR::,$(datadir)/locale," \ + -e "s,::DATADIR::,$(datadir)," \ + -e "s,::PKGDATADIR::,$(pkgdatadir)," \ + < $< > $@ + +CLEANFILES = *.pyc diff --git a/src/ppm/__init__.py.in b/src/ppm/__init__.py.in new file mode 100644 index 0000000..ab3c59d --- /dev/null +++ b/src/ppm/__init__.py.in @@ -0,0 +1,5 @@ +# Paths for prepaid-manager-applet + +gettext_app = '::PACKAGE::' +gettext_dir = '::GETTEXTDIR::' +ui_dir = '::PKGDATADIR::' diff --git a/src/ppm/modemproxy.py b/src/ppm/modemproxy.py new file mode 100644 index 0000000..352b539 --- /dev/null +++ b/src/ppm/modemproxy.py @@ -0,0 +1,162 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2010 Guido Guenther <agx@sigxcpu.org> +# 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 3 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import dbus +import dbus.glib +import dbus.service +import gobject + + +class ModemError(Exception): + def __init__(self, msg): + self.msg = msg + + def is_forbidden(self): + return [False, True][self.msg.find("Operation not allowed") != -1] + + def is_disabled(self): + return [False, True][self.msg.find("not enabled") != -1] + + +class ModemManagerProxy(gobject.GObject): + """Interface to ModemManager DBus API + @ivar request: current pending request to ModemManager + @type request: string + @ivar modem: dbus path of modem we're currently acting on + @type modem: string + """ + + MM_DBUS_SERVICE='org.freedesktop.ModemManager' + MM_DBUS_INTERFACE_MODEM_MANAGER='org.freedesktop.ModemManager' + MM_DBUS_OBJECT_MODEM_MANAGER='/org/freedesktop/ModemManager' + MM_DBUS_INTERFACE_MODEM='org.freedesktop.ModemManager.Modem' + MM_DBUS_INTERFACE_MODEM_GSM_CARD='org.freedesktop.ModemManager.Modem.Gsm.Card' + MM_DBUS_INTERFACE_MODEM_GSM_USSD='org.freedesktop.ModemManager.Modem.Gsm.Ussd' + + __gsignals__ = { + # Emitted when we got the new account balance from the provider + 'request-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + [object]), + 'request-finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + [object]), + } + + def __init__(self): + self.__gobject_init__() + self.bus = dbus.SystemBus() + self.request = None + self.reply_func = None + self.error_func = None + self.modem = None + self.obj = None + + def set_modem(self, modem): + self.modem = modem + self.obj = self.bus.get_object(self.MM_DBUS_SERVICE, self.modem) + + def mm_request(func): + def wrapped_f( self, *args, **kw) : + self.request = "%s" % func.func_name + if kw.has_key('reply_func'): + self.reply_func = kw['reply_func'] + if kw.has_key('error_func'): + self.error_func = kw['error_func'] + self.emit('request-started', self) + ret = func(self, *args, **kw) + return ret + wrapped_f.__name__= func.__name__ + wrapped_f.__doc__= func.__doc__ + return wrapped_f + + def mm_request_done(func): + def wrapped_f( self, *args, **kw): + self.emit('request-finished', self) + ret = func(self, *args, **kw) + self.reply_func = None + self.error_func = None + self.request = None + return ret + wrapped_f.__name__= func.__name__ + wrapped_f.__doc__= func.__doc__ + return wrapped_f + + def request_pending(self): + if self.request: + return True + else: + return False + + @mm_request_done + def handle_dbus_reply(self, *args): + if self.reply_func: + self.reply_func(*args) + + @mm_request_done + def handle_dbus_error(self, e): + if self.error_func: + me = ModemError("%s failed: %s" % (self.request, e)) + self.error_func(me) + + def get_modems(self): + modems = [] + proxy = self.bus.get_object(self.MM_DBUS_SERVICE, + self.MM_DBUS_OBJECT_MODEM_MANAGER) + mm = dbus.Interface(proxy, + dbus_interface=self.MM_DBUS_INTERFACE_MODEM_MANAGER) + ret = mm.EnumerateDevices() + for modem in ret: + modems.append(modem) + return modems + + def get_imsi(self): + card = dbus.Interface(self.obj, + dbus_interface=self.MM_DBUS_INTERFACE_MODEM_GSM_CARD) + try: + return card.GetImsi() + except dbus.exceptions.DBusException as msg: + raise ModemError("Getting IMSI failed: %s" % msg) + + def get_network_id(self): + imsi = self.get_imsi() + mcc = imsi[0:3] + mnc = imsi[3:5] + return (mcc, mnc) + + @mm_request + def ussd_initiate(self, command, reply_func=None, error_func=None): + ussd = dbus.Interface(self.obj, + dbus_interface=self.MM_DBUS_INTERFACE_MODEM_GSM_USSD) + return ussd.Initiate(command, + reply_handler=self.handle_dbus_reply, + error_handler=self.handle_dbus_error) + + @mm_request + def _modem__enable(self, enable, reply_func=None, error_func=None): + ussd = dbus.Interface(self.obj, + dbus_interface=self.MM_DBUS_INTERFACE_MODEM) + ussd.Enable(enable, + reply_handler=self.handle_dbus_reply, + error_handler=self.handle_dbus_error) + + def modem_enable(self, reply_func=None, error_func=None): + self._modem__enable(True) + + def modem_disable(self, reply_func=None, error_func=None): + self._modem_enable(False) + +gobject.type_register(ModemManagerProxy) diff --git a/src/ppm/provider.py b/src/ppm/provider.py new file mode 100644 index 0000000..484270b --- /dev/null +++ b/src/ppm/provider.py @@ -0,0 +1,78 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2010 Guido Guenther <agx@sigxcpu.org> +# 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 3 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import logging + +class ProviderError(Exception): + def __init__(self, msg): + self.msg = msg + + +class Provider(object): + """Keeps the information on howto top up the ballance and how to query the + current ballance""" + + def __init__(self, country, name): + self.country = country + self.name = name + self.fetch_balance_cmds = {} + self.top_up_cmds = {} + self.balance = None + logging.debug("New provider: %s, %s", country, name) + + def add_fetch_balance_cmd(self, cmd): + self.fetch_balance_cmds.update(cmd) + logging.debug("Adding balance check %s" % cmd) + + def add_top_up_cmd(self, cmd): + self.top_up_cmds.update(cmd) + logging.debug("Adding top up %s" % cmd) + + def has_fetch_balance_cmd(self): + # Only USSD for now + if self.fetch_balance_cmds.has_key('ussd'): + return True + else: + return False + + def has_top_up_cmd(self): + # Only USSD for now + if self.top_up_cmds.has_key('ussd'): + return True + else: + return False + + def fetch_balance(self, mm, reply_func=None, error_func=None): + if self.has_fetch_balance_cmd(): + mm.ussd_initiate (self.fetch_balance_cmds['ussd'], + reply_func=reply_func, + error_func=error_func) + return True + else: + return False + + def top_up(self, mm, code, reply_func=None, error_func=None): + if self.has_top_up_cmd(): + cmd = self.top_up_cmds['ussd'][0].replace( + self.top_up_cmds['ussd'][1], + code) + logging.debug("Top up cmd: %s", cmd) + mm.ussd_initiate (cmd, reply_func=reply_func, error_func=error_func) + return True + else: + return False diff --git a/src/ppm/providerdb.py b/src/ppm/providerdb.py new file mode 100644 index 0000000..a1bc394 --- /dev/null +++ b/src/ppm/providerdb.py @@ -0,0 +1,140 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2010 Guido Guenther <agx@sigxcpu.org> +# 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 3 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import logging +from lxml import etree + +from provider import Provider + +class ProviderDB(object): + """Proxy to mobile broadband provider database""" + + provider_info = os.getenv('PPM_PROVIDER_DB', + '/usr/share/mobile-broadband-provider-info/' + 'serviceproviders.xml') + country_codes = '/usr/share/zoneinfo/iso3166.tab' + + def __init__(self): + self.__tree = None + self.__countries = {} + + @property + def tree(self): + if self.__tree: + return self.__tree + else: + self.__tree = etree.parse(self.provider_info) + return self.__tree + + @property + def countries(self): + if self.__countries: + return self.__countries + else: + self._tree = self._load_countries() + return self.__countries + + def _load_countries(self): + try: + for line in file(self.country_codes, 'r'): + if line[0] != '#': + (code, country) = line.split('\t',2) + self.__countries[code.lower()] = country.strip() + except IOError as msg: + logging.warning("Loading country code database failed: %s" % msg) + + def _fill_provider_info(self, provider_elem): + """Fill a provider object with data from the XML""" + name = provider_elem.xpath("./name")[0].text + country = provider_elem.getparent().attrib['code'] + gsm_elem = provider_elem.xpath("./gsm") + provider = Provider(country = country, + name = name) + if gsm_elem: + self._fill_balance_check_cmd(gsm_elem[0], provider) + self._fill_top_up_cmd(gsm_elem[0], provider) + return provider + + def _fill_balance_check_cmd(self, xmlelemnt, provider): + """Fetch balance check method from XML and add it to the provider""" + for child in xmlelemnt.iter(tag='balance-check'): + check_types = child.getchildren() + for t in check_types: + if t.tag == 'ussd': + sequence = t.text + provider.add_fetch_balance_cmd({'ussd': sequence}) + if t.tag == 'sms': + number = t.text + text = t.attrib['text'] + provider.add_fetch_balance_cmd({'sms': (number, text)}) + + def _fill_top_up_cmd(self, xmlelement, provider): + for child in xmlelement.iter(tag='balance-top-up'): + check_types = child.getchildren() + for t in check_types: + if t.tag == 'ussd': + sequence = t.text + replacement = t.attrib['replacement'] + provider.add_top_up_cmd({'ussd': [sequence, replacement]}) + if t.tag == 'sms': + number = t.text + text = t.attrib['text'] + provicer.add_top_up_cmd({'sms': (number, text)}) + + def get_providers(self, mcc, mnc): + """ + Get possible providers for the current mcc and mnc from the database + """ + path = "//network-id[@mcc='%s' and @mnc='%s']" % (mcc, mnc) + searcher = etree.ETXPath(path) + providers = [] + + for r in searcher(self.tree): + provider_elem = r.getparent().getparent() + providers.append(self._fill_provider_info(provider_elem)) + return providers + + def get_provider(self, country_code, name): + path = "//country[@code='%s']/provider[name='%s']" % (country_code, name) + searcher = etree.ETXPath(path) + + for r in searcher(self.tree): + return self._fill_provider_info(r) + return None + + def get_country_codes(self): + path = "/serviceproviders/country" + searcher = etree.ETXPath(path) + + for r in searcher(self.tree): + yield r.attrib['code'] + + def get_countries(self): + for code in self.get_country_codes(): + try: + yield (self.countries[code], code) + except KeyError: + yield (None, code) + + def get_providers_by_code(self, country_code): + path = ("/serviceproviders/country[@code='%s']/provider/name" % + country_code) + searcher = etree.ETXPath(path) + + for r in searcher(self.tree): + yield r.text |