From 8981216d64d501530549836c49fc2844466f9de6 Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Mon, 25 Apr 2011 17:41:28 +0200 Subject: Store known providers and balance in gsettings based on the IMSI of the SIM card --- src/ppm/accountdb.py | 139 ++++++++++++++++++++++++++++++++++++++++++ src/ppm/provider.py | 11 +++- src/prepaid-manager-applet.py | 131 +++++++++++++++++++++++++++++++-------- 3 files changed, 253 insertions(+), 28 deletions(-) create mode 100644 src/ppm/accountdb.py (limited to 'src') 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 +# 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): -- cgit v1.2.3