aboutsummaryrefslogtreecommitdiff
path: root/src/ppm
diff options
context:
space:
mode:
Diffstat (limited to 'src/ppm')
-rw-r--r--src/ppm/.gitignore2
-rw-r--r--src/ppm/Makefile.am23
-rw-r--r--src/ppm/__init__.py.in5
-rw-r--r--src/ppm/modemproxy.py162
-rw-r--r--src/ppm/provider.py78
-rw-r--r--src/ppm/providerdb.py140
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