aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2011-04-25 17:41:28 +0200
committerGuido Günther <agx@sigxcpu.org>2011-05-01 14:28:32 +0200
commit8981216d64d501530549836c49fc2844466f9de6 (patch)
treefd3f76c161b829ef72a522e16af6055a11b94030
parent26c0560f6c4a5869859f7daf7e41f73d739e49cd (diff)
Store known providers and balance in gsettings
based on the IMSI of the SIM card
-rw-r--r--.gitignore4
-rw-r--r--Makefile.am2
-rw-r--r--TODO2
-rw-r--r--configure.ac3
-rw-r--r--data/Makefile.am26
-rw-r--r--data/org.gnome.PrepaidManager.gschema.xml.in.in43
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/ppm/accountdb.py139
-rw-r--r--src/ppm/provider.py11
-rwxr-xr-xsrc/prepaid-manager-applet.py131
10 files changed, 332 insertions, 30 deletions
diff --git a/.gitignore b/.gitignore
index c81b2d7..e576237 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,7 @@ Makefile.in
missing
mkinstalldirs
prepaid-manager-applet-*.tar.gz
+
+data/gschemas.compiled
+data/org.gnome.PrepaidManager.gschema.valid
+data/org.gnome.PrepaidManager.gschema.xml
diff --git a/Makefile.am b/Makefile.am
index fb38ea7..d680d28 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
NULL =
-SUBDIRS = src po
+SUBDIRS = data src po
DISTCLEANFILES = \
intltool-extract \
diff --git a/TODO b/TODO
index 684b52a..f59b282 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,5 @@
* Add raw mode to send arbitrary USSD commands
-* Remember settings with gsettings
* Collect balance statistics in sqlitedb
* Handle multiple modems
* Add support for SMS top-up messages as used by some providers
+* Drop dbus.glib and use Gio.DBusProxy instead
diff --git a/configure.ac b/configure.ac
index 8c14dca..e35224f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12,10 +12,13 @@ AC_SUBST(GETTEXT_PACKAGE)
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package])
AM_GLIB_GNU_GETTEXT
+GLIB_GSETTINGS
+
GNOME_DOC_INIT
AC_CONFIG_FILES([
Makefile
+data/Makefile
po/Makefile.in
src/Makefile
src/ppm/Makefile
diff --git a/data/Makefile.am b/data/Makefile.am
new file mode 100644
index 0000000..d97af90
--- /dev/null
+++ b/data/Makefile.am
@@ -0,0 +1,26 @@
+NULL =
+
+gsettings_SCHEMAS = org.gnome.PrepaidManager.gschema.xml
+
+@INTLTOOL_XML_NOMERGE_RULE@
+@GSETTINGS_RULES@
+
+%.gschema.xml.in: %.gschema.xml.in.in Makefile
+ $(AM_V_GEN) sed -e 's^\@GETTEXT_PACKAGE\@^$(GETTEXT_PACKAGE)^g' < $< > $@
+
+# We need to compile schemas at make time
+# to run from source tree
+gschemas.compiled: $(gsettings_SCHEMAS:.xml=.valid)
+ $(AM_V_GEN) $(GLIB_COMPILE_SCHEMAS) --targetdir=. .
+
+all-local: gschemas.compiled
+
+EXTRA_DIST = \
+ org.gnome.PrepaidManager.gschema.xml.in.in \
+ $(NULL)
+
+CLEANFILES = \
+ $(gsettings_SCHEMAS) \
+ gschemas.compiled \
+ $(NULL)
+
diff --git a/data/org.gnome.PrepaidManager.gschema.xml.in.in b/data/org.gnome.PrepaidManager.gschema.xml.in.in
new file mode 100644
index 0000000..2b75c8e
--- /dev/null
+++ b/data/org.gnome.PrepaidManager.gschema.xml.in.in
@@ -0,0 +1,43 @@
+<schemalist>
+ <schema id="org.gnome.PrepaidManager" path="/apps/prepaid-manager/" gettext-domain="@GETTEXT_PACKAGE@">
+ <key name="accounts" type="as">
+ <default>[]</default>
+ <_summary>Accounts</_summary>
+ <_description>
+ List of configured accounts. The account at your GSM/UMTS provider is
+ identified by the imsi on your SIM card.
+ </_description>
+ </key>
+ </schema>
+
+ <schema id="org.gnome.PrepaidManager.account" gettext-domain="@GETTEXT_PACKAGE@">
+ <key name="provider" type="s">
+ <default>""</default>
+ <_summary>The providers name</_summary>
+ <_description>
+ The name of the mobile broadband provider.
+ </_description>
+ </key>
+ <key name="country" type="s">
+ <default>""</default>
+ <_summary>The country the provider operates in</_summary>
+ <_description>
+ This is the country where you bought the SIM card.
+ </_description>
+ </key>
+ <key name="balance" type="s">
+ <default>"The current balance is unknown."</default>
+ <_summary>Balance</_summary>
+ <_description>
+ The current balance of this account.
+ </_description>
+ </key>
+ <key name="timestamp" type="s">
+ <default>""</default>
+ <_summary>Update timestamp</_summary>
+ <_description>
+ The last time the balance got updated.
+ </_description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0b74d48..234ed71 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,3 +1,4 @@
+data/org.gnome.PrepaidManager.gschema.xml.in.in
[type: gettext/glade]src/ppm.ui
[type: gettext/glade]src/ppm-modem-request.ui
[type: gettext/glade]src/ppm-provider-assistant.ui
diff --git a/src/ppm/accountdb.py b/src/ppm/accountdb.py
new file mode 100644
index 0000000..744cd0b
--- /dev/null
+++ b/src/ppm/accountdb.py
@@ -0,0 +1,139 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2011 Guido Günther <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
+
+from gi.repository import Gio
+from gi.repository import GObject
+import logging
+
+from provider import Provider
+
+class Account(GObject.GObject):
+ identifier = GObject.property(type=str,
+ nick='account identifier',
+ blurb='the identifier of this account, usually the imsi')
+ name = GObject.property(type=str,
+ nick='provider name',
+ blurb='name of the provider')
+ code = GObject.property(type=str,
+ nick='country name',
+ blurb='counry code the provider is in')
+ balance = GObject.property(type=str,
+ nick='current balance',
+ blurb='current balance of this account')
+ timestamp = GObject.property(type=str,
+ nick='update timestamp',
+ blurb='last time the balance info got updated')
+
+ def update_provider(self, provider):
+ """Update the provider information"""
+ if provider.country != self.props.code:
+ self.props.code = provider.country
+ if provider.name != self.props.name:
+ self.props.name = provider.name
+
+ def update_balance(self, balance, timestamp):
+ """Update balance information"""
+ if timestamp != self.props.timestamp:
+ self.props.balance = balance
+ self.props.timestamp = timestamp
+
+GObject.type_register(Account)
+
+
+class AccountDB(object):
+ """
+ In Gsettings we keep the detailed provider information that's associated
+ with an account identified by the SIM card. A user can have several
+ accounts at the same provider.
+ """
+
+ PPM_GSETTINGS_ID = 'org.gnome.PrepaidManager'
+ PPM_GSETTINGS_ACCOUNT_ID = PPM_GSETTINGS_ID + '.account'
+
+ def __init__(self):
+ self.settings = Gio.Settings(self.PPM_GSETTINGS_ID)
+ self.accounts_path_prefix = self.settings.get_property("path") + 'accounts/'
+
+ def is_known_account(self, imsi):
+ """Do we know about this account in GSettings?"""
+ return self.imsi_to_identifier(imsi) in self.settings.get_strv('accounts')
+
+ def _account_path(self, imsi):
+ """
+ This key identifies the provider setup for a specific SIM card in
+ GSettings. Since we might not be allowed to get the imsi from the
+ phone other keys must be possible later
+ """
+ return '%s%s/' % (self.accounts_path_prefix,
+ self.imsi_to_identifier(imsi))
+
+ @classmethod
+ def imsi_to_identifier(klass, imsi):
+ """Turn an imsi into an identifier, this allows us to support non
+ imsi based accounts later"""
+ return 'imsi:%s' % imsi
+
+ def _bind_account(self, imsi):
+ """Bind a new account object to a gsettings path"""
+
+ path = self._account_path(imsi)
+ account = Account()
+ account.props.identifier = self.imsi_to_identifier(imsi)
+ gsettings_account = Gio.Settings(self.PPM_GSETTINGS_ACCOUNT_ID, path)
+ gsettings_account.bind('provider', account, 'name',
+ Gio.SettingsBindFlags.DEFAULT)
+ gsettings_account.bind('country', account, 'code',
+ Gio.SettingsBindFlags.DEFAULT)
+ gsettings_account.bind('balance', account, 'balance',
+ Gio.SettingsBindFlags.DEFAULT)
+ gsettings_account.bind('timestamp', account, 'timestamp',
+ Gio.SettingsBindFlags.DEFAULT)
+ return account
+
+ def fetch(self, imsi):
+ """Given an imsi check if we know about it"""
+
+ if self.is_known_account(imsi):
+ logging.debug("IMSI '%s' not known", imsi)
+ return None
+ else:
+ logging.debug("Fetching account information from '%s'",
+ self._account_path(imsi))
+
+ account = self._bind_account(imsi)
+ if account.props.name and account.props.code:
+ logging.debug("Provider '%s' in '%s'", account.props.name,
+ account.props.code)
+ return account
+ else:
+ logging.debug("Account not yet known.")
+ return None
+
+ def add(self, imsi, provider):
+ """Given an imsi and a provider persist a new account with that
+ information"""
+ path = self._account_path(imsi)
+
+ logging.debug("Persisting '%s' in '%s' at '%s'",
+ provider.name,
+ provider.country,
+ path)
+ account = self._bind_account(imsi)
+ account.props.name = provider.name
+ account.props.code = provider.country
+
+
diff --git a/src/ppm/provider.py b/src/ppm/provider.py
index 484270b..fbbd911 100644
--- a/src/ppm/provider.py
+++ b/src/ppm/provider.py
@@ -24,15 +24,20 @@ class ProviderError(Exception):
class Provider(object):
- """Keeps the information on howto top up the ballance and how to query the
- current ballance"""
+ """
+ Keeps the information on howto interact with a certain provider, that
+ his howto top up the ballance and how to query the current ballance
+
+ It doesn't keep any current balance information or similar since this is
+ associated with an account (a user can have several SIM cards form the same
+ provider)
+ """
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):
diff --git a/src/prepaid-manager-applet.py b/src/prepaid-manager-applet.py
index 23ee902..32f0cb3 100755
--- a/src/prepaid-manager-applet.py
+++ b/src/prepaid-manager-applet.py
@@ -33,6 +33,7 @@ import ppm
from ppm.modemproxy import (ModemManagerProxy, ModemError)
from ppm.provider import Provider
from ppm.providerdb import ProviderDB
+from ppm.accountdb import AccountDB
# The controller receives input and initiates a response by making calls on model
# objects. A controller accepts input from the user and instructs the model and
@@ -40,13 +41,18 @@ from ppm.providerdb import ProviderDB
class PPMController(GObject.GObject):
"""
@ivar providers: the possible providers
+ @ivar imsi: the imsi if we could fetch it from the modem
+ @ivar account: the account associated with the SIM card
@ivar provider: current provider
"""
__gsignals__ = {
# Emitted when we got the new account balance from the provider
- 'balance-info-fetched': (GObject.SignalFlags.RUN_FIRST, None,
+ 'balance-info-changed': (GObject.SignalFlags.RUN_FIRST, None,
[object]),
+ # Emitted when the provider changed
+ 'provider-changed': (GObject.SignalFlags.RUN_FIRST, None,
+ [object]),
}
def _connect_mm_signals(self):
@@ -56,14 +62,17 @@ class PPMController(GObject.GObject):
def __init__(self):
self.__gobject_init__()
self.mm = None
+ self.imsi = None
self.provider = None
+ self.account = None
self.view = None
self.providerdb = ProviderDB()
+ self.accountdb = AccountDB()
- def get_account_balance(self):
- return provider.balance
+ self.connect('provider-changed', self.on_provider_changed)
+ self.connect('balance-info-changed', self.on_balance_info_changed)
- def fetch_balance_info(self):
+ def fetch_balance(self):
"""Fetch the current account balance from the network"""
if not self.provider.fetch_balance(self.mm,
reply_func=self.on_balance_info_fetched,
@@ -81,20 +90,40 @@ class PPMController(GObject.GObject):
logging.error("No idea how to top up balance for "
"%s in %s.", self.provider.name, self.provider.country)
- def set_provider(self, provider=None, country_code=None, name=None):
- """Change the current provider to provider and inform the view"""
+ def set_provider(self, provider=None,
+ account=None,
+ country_code=None, name=None):
+ """
+ Change the current provider to provider and inform the view
+
+ Input can be a provder, an account or, (name, country_code)
+ Once finished we know how to access account balance, top up, etc.
+ """
+ if account:
+ name = account.props.name
+ country_code = account.props.code
+
if name and country_code:
provider = self.providerdb.get_provider(country_code, name)
if not provider:
- raise Exception("No valid provider")
-
- self.provider = provider
- self.view.update_provider_name(provider.name)
- # FIXME: fetch last stored balance from db
+ raise Exception("No valid account or provider")
- def _get_provider_from_mcc_mnc(self):
- mcc, mnc = self.mm.get_network_id()
+ self.provider = provider
+ self.emit('provider-changed', provider)
+
+ def _imsi_to_network_id(self, imsi):
+ """Extract mmc and mnc from imsi"""
+ mcc = imsi[0:3]
+ mnc = imsi[3:5]
+ return (mcc, mnc)
+
+ def _get_provider_interactive(self, imsi):
+ """
+ Given the imsi, determine the provider based on that information
+ from providerdb, request user input where ncessary
+ """
+ mcc, mnc = self._imsi_to_network_id(imsi)
self.providers = self.providerdb.get_providers(mcc, mnc)
if self.providers:
if len(self.providers) > 1:
@@ -105,25 +134,49 @@ class PPMController(GObject.GObject):
else:
self.view.show_provider_unknown(mcc, mnc)
- def init_current_provider(self):
+ def _get_account_from_accountdb(self, imsi):
+ """
+ Based on the imsi check if we already now all the account details
+ """
+ self.account = self.accountdb.fetch(imsi)
+ if self.account:
+ self.set_provider(account=self.account)
+ return self.account
+
+ def init_account_and_provider(self):
+ """Fetch the imsi deduce account and provider information"""
try:
- self._get_provider_from_mcc_mnc()
+ self.imsi = self.mm.get_imsi()
except ModemError as me:
- logging.warning("Can't get network id: %s", me.msg)
+ logging.warning("Can't get imsi: %s", me.msg)
if me.is_forbidden():
- # FIXME: fetch last provider from GSettings
self.view.show_provider_assistant()
return False
if not me.is_disabled():
self.view.show_modem_error(me.msg)
return False
+
logging.info("modem not enabled")
if self.view.show_modem_enable() != Gtk.ResponseType.YES:
return False
else:
- self.mm.modem_enable(reply_func=self.init_current_provider)
+ self.mm.modem_enable(reply_func=self.init_account_and_provider)
return True
+
+ if self._get_account_from_accountdb(self.imsi):
+ # Since we have the account in the db we can safely
+ # fetch the provider information from the providerdb
+ self.providerdb.get_provider(self.account.name,
+ self.account.code)
+ else:
+ # Account not known yet, get provider interactively
+ self.account = None
+ self._get_provider_interactive(self.imsi)
+
+ # Everything worked out, disable the timer.
+ return False
+
def setup(self):
logging.debug("Setting up")
@@ -135,7 +188,7 @@ class PPMController(GObject.GObject):
modem = modems[0] # FIXME: handle multiple modems
logging.debug("Using modem %s" % modem)
self.mm.set_modem(modem)
- GObject.timeout_add(500, self.init_current_provider)
+ GObject.timeout_add(500, self.init_account_and_provider)
else:
self.view.show_error("No modem found.")
self.quit()
@@ -162,17 +215,44 @@ class PPMController(GObject.GObject):
self.view.close_modem_response()
def on_balance_info_fetched(self, balance, *args):
- self.provider.balance = balance
- self.emit('balance-info-fetched', self.provider.balance)
- self.view.update_account_balance_information(self.provider.balance,
- time.asctime())
+ """Callback for succesful MM fetch balance info call"""
+ self.emit('balance-info-changed', balance)
def on_balance_topped_up(self, reply):
+ """Callback for succesful MM topup balance call"""
self.view.update_top_up_information(reply)
def on_modem_error(self, e):
self.view.show_modem_error(e.msg)
logging.error(e.msg)
+
+ def on_provider_changed(self, obj, provider):
+ """Act on provider-changed signal"""
+ logging.debug("Provider changed to '%s'", provider.name)
+
+ self.view.update_provider_name(provider.name)
+
+ if self.imsi and not self.account:
+ # We have an imsi and the user told us what provider to use:
+ self.account = self.accountdb.add(self.imsi, provider)
+ elif self.account:
+ # Update an existing account with the user provided information
+ self.account.update_provider(provider)
+ if self.account.props.timestamp:
+ self.view.update_account_balance_information(
+ self.account.balance,
+ self.account.timestamp)
+
+ def on_balance_info_changed(self, obj, balance):
+ """Act on balance-info-changed signal"""
+ logging.debug("Balance info changed")
+
+ timestamp = time.asctime()
+ if self.account:
+ self.account.update_balance(balance, timestamp)
+
+ self.view.update_account_balance_information(balance, timestamp)
+
GObject.type_register(PPMController)
@@ -245,7 +325,7 @@ class PPMDialog(GObject.GObject, PPMObject):
self.controller.top_up_balance()
def on_balance_info_renew_clicked(self, dummy):
- self.controller.fetch_balance_info()
+ self.controller.fetch_balance()
def on_provider_change_clicked(self, dummy):
# FIXME: allow to select provider
@@ -398,7 +478,8 @@ class PPMProviderAssistant(PPMObject):
else:
# List of possible providers given, all from the same country
self.country_code = self.possible_providers[0].country
- self.assistant.set_forward_page_func(self._providers_only_page_func, None)
+ self.assistant.set_forward_page_func(self._providers_only_page_func,
+ None)
self.assistant.show()
def close(self):