r6852 - in bigboard/trunk: . bigboard bigboard/stocks bigboard/stocks/files bigboard/stocks/google_calendar
- From: commits mugshot org
- To: online-desktop-list gnome org
- Subject: r6852 - in bigboard/trunk: . bigboard bigboard/stocks bigboard/stocks/files bigboard/stocks/google_calendar
- Date: Tue, 30 Oct 2007 15:34:18 -0500 (CDT)
Author: hp
Date: 2007-10-30 15:34:15 -0500 (Tue, 30 Oct 2007)
New Revision: 6852
Added:
bigboard/trunk/bigboard/accounts_dialog.py
Removed:
bigboard/trunk/bigboard/stocks/docs_stock.py
Modified:
bigboard/trunk/bigboard/accounts.py
bigboard/trunk/bigboard/google.py
bigboard/trunk/bigboard/google_stock.py
bigboard/trunk/bigboard/keyring.py
bigboard/trunk/bigboard/stocks/files/FilesStock.py
bigboard/trunk/bigboard/stocks/files/filebrowser.py
bigboard/trunk/bigboard/stocks/google_calendar/CalendarStock.py
bigboard/trunk/main.py
Log:
Cleanups of google.py and the stocks using it... make it use accounts.py to know what Google accounts exist, and try to rationalize how authentication works.
The previous code assumed there was a separate login step, distinct from making the API calls.
So if the mail checker hadn't existed, we never would have logged in at all. The correct approach
is to simply try making the API calls and fail appropriately if we don't authenticate when we do.
There is a lot left to do to get all this working reliably.
Modified: bigboard/trunk/bigboard/accounts.py
===================================================================
--- bigboard/trunk/bigboard/accounts.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/accounts.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -22,7 +22,7 @@
KIND_GOOGLE = AccountKind("google")
def kind_from_string(s):
- for kind in (KIND_GOOGLE):
+ for kind in [KIND_GOOGLE]:
if s == kind.get_id():
return kind
return None
@@ -34,6 +34,7 @@
def __init__(self, kind, username='', password='', url='', enabled=True, gconf_dir=None):
super(Account, self).__init__()
+
self.__kind = kind
self.__username = username
self.__password = password
@@ -41,12 +42,22 @@
self.__enabled = enabled
self.__gconf_dir = gconf_dir
+ _logger.debug("Created account %s" % (str(self)))
+
def get_kind(self):
return self.__kind
def get_username(self):
return self.__username
+ def get_username_as_google_email(self):
+ if self.__username == '':
+ return self.__username
+ elif '@' not in self.__username:
+ return self.__username + '@gmail.com'
+ else:
+ return self.__username
+
def get_password(self):
return self.__password
@@ -62,15 +73,17 @@
def _set_gconf_dir(self, gconf_dir):
self.__gconf_dir = gconf_dir
- def _update_from_origin(self, **kwargs):
+ def _update_from_origin(self, new_props):
"""This is the only way to modify an Account object. It should be invoked only on change notification or refreshed data from the original origin of the account."""
## check it out!
changed = False
- for (key,value) in kwargs.items():
- old = self.__dict__['__' + key]
+ for (key,value) in new_props.items():
+ if value is None:
+ value = ''
+ old = getattr(self, '_Account__' + key)
if old != value:
- self.__dict__['__' + key] = value
+ setattr(self, '_Account__' + key, value)
changed = True
if changed:
@@ -123,12 +136,14 @@
def __update_account(self, account):
+ _logger.debug("Updating account %s" % (str(account)))
+
## note that "kind" is never updated (not allowed to change without
## making a new Account object)
was_enabled = account in self.__enabled_accounts
if was_enabled != account.get_enabled():
- raise Error("account enabled state messed up")
+ raise Exception("account enabled state messed up")
fields = { }
@@ -172,7 +187,8 @@
if 'enabled' not in fields:
fields['enabled'] = True
else:
- self.__weblogin_accounts.remove(account)
+ if account in self.__weblogin_accounts:
+ self.__weblogin_accounts.remove(account)
## after compositing all this information, update our account object
account._update_from_origin(fields)
@@ -185,13 +201,19 @@
password = k.get_password(kind=account.get_kind().get_id(),
username=account.get_username(),
url=account.get_url())
- if 'password' not in fields:
+ if password and 'password' not in fields:
+ _logger.debug("using password from keyring")
fields['password'] = password
## fourth, if no password in keyring, use the weblogin one
if weblogin_password and 'password' not in fields:
+ _logger.debug("using password from weblogin")
fields['password'] = weblogin_password
+ ## if no password found, the password has to be set to empty
+ if 'password' not in fields:
+ fields['password'] = ''
+
## update account object again if we might have the password
if 'password' in fields:
account._update_from_origin(fields)
@@ -208,8 +230,9 @@
account = self.__find_weblogin_account_by_kind(kind)
added = False
if not account:
- account = Account(kind)
+ account = Account(kind, enabled=True)
self.__weblogin_accounts.add(account)
+ self.__enabled_accounts.add(account)
self.__update_account(account)
def __try_ensure_and_update_account_for_gconf_dir(self, gconf_dir):
@@ -236,22 +259,32 @@
if account:
account._set_gconf_dir(gconf_dir)
else:
- account = Account(kind, gconf_dir=gconf_dir)
+ account = Account(kind, gconf_dir=gconf_dir, enabled=False)
self.__gconf_accounts.add(account)
self.__update_account(account)
+
+ def __remove_dirname(self, gconf_key):
+ i = gconf_key.rfind('/')
+ return gconf_key[i+1:]
def __reload_from_gconf(self):
gconf_dirs = self.__gconf.all_dirs('/apps/bigboard/accounts')
+ _logger.debug("Reloading %s from gconf" % (str(gconf_dirs)))
+
new_gconf_infos = {}
for gconf_dir in gconf_dirs:
- base_key = '/apps/bigboard/accounts/' + gconf_dir
-
+ base_key = gconf_dir
+ gconf_dir = self.__remove_dirname(gconf_dir)
+
gconf_info = {}
def get_account_prop(gconf, gconf_info, base_key, prop):
- value = gconf.get_value(base_key + '/' + prop)
+ try:
+ value = gconf.get_value(base_key + '/' + prop)
+ except ValueError:
+ value = None
if value:
gconf_info[prop] = value
get_account_prop(self.__gconf, gconf_info, base_key, 'kind')
@@ -319,6 +352,9 @@
def save_account_changes(self, account, new_properties):
+ _logger.debug("Saving new props for account %s: %s" % (str(account), str(new_properties.keys())))
+ set_password = False
+
## special-case handling of password since it goes in the keyring
if 'password' in new_properties:
if 'username' in new_properties:
@@ -331,12 +367,15 @@
else:
url = account.get_url()
- k = keyring.get_keyring()
+ k = keyring.get_keyring()
+
k.store_login(kind=account.get_kind().get_id(),
username=username,
url=url,
password=new_properties['password'])
+ set_password = True
+
## now do everything else by stuffing it in gconf
gconf_dir = account._get_gconf_dir()
@@ -356,6 +395,9 @@
base_key = '/apps/bigboard/accounts/' + gconf_dir
def set_account_prop(gconf, base_key, prop, value):
+ _logger.debug("prop %s value %s" % (prop, str(value)))
+ if isinstance(value, AccountKind):
+ value = value.get_id()
gconf.set_value(base_key + '/' + prop, value)
set_account_prop(self.__gconf, base_key, 'kind', account.get_kind())
@@ -369,6 +411,11 @@
if 'enabled' in new_properties:
set_account_prop(self.__gconf, base_key, 'enabled', new_properties['enabled'])
+ ## keyring doesn't have change notification so we have to do the work for it
+ if set_password:
+ ## this should notice a new password
+ self.__update_account(account)
+
def create_account(self, kind):
gconf_dir = self.__find_unused_gconf_dir(kind)
@@ -386,3 +433,11 @@
accounts.add(a)
return accounts
+__accounts = None
+
+def get_accounts():
+ global __accounts
+ if not __accounts:
+ __accounts = Accounts()
+
+ return __accounts
Added: bigboard/trunk/bigboard/accounts_dialog.py
===================================================================
--- bigboard/trunk/bigboard/accounts_dialog.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/accounts_dialog.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -0,0 +1,132 @@
+import sys, logging
+
+import gobject, gtk
+import bigboard.accounts as accounts
+
+import bigboard.globals as globals
+import libbig.logutil
+from libbig.logutil import log_except
+
+_logger = logging.getLogger("bigboard.AccountsDialog")
+
+class AccountEditor(gtk.VBox):
+ def __init__(self, *args, **kwargs):
+ if 'account' in kwargs:
+ self.__account = kwargs['account']
+ del kwargs['account']
+ else:
+ raise Error("must provide account to AccountEditor")
+
+ super(AccountEditor, self).__init__(*args, **kwargs)
+
+ self.__username_entry = gtk.Entry()
+ self.__password_entry = gtk.Entry()
+ self.__password_entry.set_visibility(False)
+
+ self.__username_entry.connect('changed',
+ self.__on_username_entry_changed)
+ self.__password_entry.connect('changed',
+ self.__on_password_entry_changed)
+
+ hbox = gtk.HBox(spacing=10)
+ label = gtk.Label("Email")
+ label.set_alignment(0.0, 0.5)
+ hbox.pack_start(label)
+ hbox.pack_end(self.__username_entry, False)
+ self.pack_start(hbox)
+
+ hbox = gtk.HBox(spacing=10)
+ label = gtk.Label("Password")
+ label.set_alignment(0.0, 0.5)
+ hbox.pack_start(label)
+ hbox.pack_end(self.__password_entry, False)
+ self.pack_start(hbox)
+
+ self.show_all()
+
+ self.__on_account_changed(self.__account)
+ self.__account.connect('changed', self.__on_account_changed)
+
+ self.__password_entry.set_activates_default(True)
+
+ def __on_account_changed(self, account):
+ self.__username_entry.set_text(account.get_username())
+ self.__password_entry.set_text(account.get_password())
+
+ def __on_username_entry_changed(self, entry):
+ text = entry.get_text()
+ accounts.get_accounts().save_account_changes(self.__account,
+ { 'username' : text })
+
+ def __on_password_entry_changed(self, entry):
+ text = entry.get_text()
+ accounts.get_accounts().save_account_changes(self.__account,
+ { 'password' : text })
+
+class Dialog(gtk.Dialog):
+ def __init__(self, *args, **kwargs):
+ super(Dialog, self).__init__(*args, **kwargs)
+
+ self.set_title('Google Accounts')
+
+ self.connect('delete-event', self.__on_delete_event)
+ self.connect('response', self.__on_response)
+
+ self.add_button(gtk.STOCK_CLOSE,
+ gtk.RESPONSE_OK)
+ self.set_default_response(gtk.RESPONSE_OK)
+
+ self.__editors_by_account = {}
+
+ accts = accounts.get_accounts().get_accounts_with_kind(accounts.KIND_GOOGLE)
+ if len(accts) == 0:
+ accounts.get_accounts().create_account(accounts.KIND_GOOGLE)
+ else:
+ for a in accts:
+ self.__editors_by_account[a] = AccountEditor(account=a)
+ self.vbox.pack_start(self.__editors_by_account[a])
+
+ def __on_delete_event(self, dialog, event):
+ self.hide()
+ return True
+
+ def __on_response(self, dialog, response_id):
+ _logger.debug("response = %d" % response_id)
+ self.hide()
+
+__dialog = None
+
+def open_dialog():
+ global __dialog
+ if not __dialog:
+ __dialog = Dialog()
+ __dialog.present()
+
+
+if __name__ == '__main__':
+
+ import gtk, gtk.gdk
+
+ import bigboard.libbig
+ try:
+ import bigboard.bignative as bignative
+ except:
+ import bignative
+
+ import dbus.glib
+
+ import bigboard.google as google
+
+ gtk.gdk.threads_init()
+
+ libbig.logutil.init('DEBUG', ['AsyncHTTP2LibFetcher', 'bigboard.Keyring', 'bigboard.Google', 'bigboard.Accounts', 'bigboard.AccountsDialog'])
+
+ bignative.set_application_name("BigBoard")
+ bignative.set_program_name("bigboard")
+
+ google.init()
+
+ open_dialog()
+
+ gtk.main()
+
Modified: bigboard/trunk/bigboard/google.py
===================================================================
--- bigboard/trunk/bigboard/google.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/google.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -3,7 +3,6 @@
import hippo, gobject, gtk, dbus, dbus.glib
-from ddm import DataModel
import bigboard.globals as globals
from bigboard.libbig.singletonmixin import Singleton
from bigboard.libbig.http import AsyncHTTPFetcher
@@ -15,6 +14,8 @@
from libbig.struct import AutoStruct, AutoSignallingStruct
import libbig.polling
import htmllib
+import bigboard.accounts as accounts
+import gdata.docs as gdocs
_logger = logging.getLogger("bigboard.Google")
@@ -66,58 +67,6 @@
def fmt_date_for_feed_request(date):
return datetime.datetime.utcfromtimestamp(time.mktime(date.timetuple())).strftime("%Y-%m-%dT%H:%M:%S")
-class AbstractDocument(AutoStruct):
- def __init__(self):
- AutoStruct.__init__(self, { 'title' : 'Untitled', 'link' : None })
-
-class SpreadsheetDocument(AbstractDocument):
- def __init__(self):
- AbstractDocument.__init__(self)
-
-class WordProcessorDocument(AbstractDocument):
- def __init__(self):
- AbstractDocument.__init__(self)
-
-class DocumentsParser(xml.sax.ContentHandler):
- def __init__(self):
- self.__docs = []
- self.__inside_title = False
-
- def startElement(self, name, attrs):
- #print "<" + name + ">"
- #print attrs.getNames() # .getValue('foo')
-
- if name == 'entry':
- d = SpreadsheetDocument()
- self.__docs.append(d)
- elif len(self.__docs) > 0:
- d = self.__docs[-1]
- if name == 'title':
- self.__inside_title = True
- elif name == 'link':
- rel = attrs.getValue('rel')
- href = attrs.getValue('href')
- type = attrs.getValue('type')
- #print str((rel, href, type))
- if rel == 'alternate' and type == 'text/html':
- d.update({'link' : href})
-
- def endElement(self, name):
- #print "</" + name + ">"
-
- if name == 'title':
- self.__inside_title = False
-
- def characters(self, content):
- #print content
- if len(self.__docs) > 0:
- d = self.__docs[-1]
- if self.__inside_title:
- d.update({'title' : content})
-
- def get_documents(self):
- return self.__docs
-
#class RemoveTagsParser(xml.sax.ContentHandler):
# def __init__(self):
# self.__content = ''
@@ -265,10 +214,20 @@
# in my experience sys.exc_info() is some kind of junk here, while "e" is useful
gobject.idle_add(lambda: errcb(url, response) and False)
-class CheckMailTask(libbig.polling.Task):
+class GooglePollAction(object):
def __init__(self, google):
- libbig.polling.Task.__init__(self, 1000 * 120, initial_interval=1000*5)
self.__google = google
+
+ def get_google(self):
+ return self.__google
+
+ def update(self):
+ pass
+
+class MailPollAction(GooglePollAction):
+ def __init__(self, google):
+ super(MailPollAction, self).__init__(google)
+
self.__ids_seen = {}
self.__newest_modified_seen = None
@@ -366,195 +325,155 @@
def __on_fetch_error(self, exc_info):
pass
+ def update(self):
+ self.get_google().fetch_new_mail(self.__on_fetched_mail, self.__on_fetch_error)
+
+class GenericPollAction(GooglePollAction):
+ def __init__(self, google, func):
+ super(GenericPollAction, self).__init__(google)
+ self.__func = func
+
+ def update(self):
+ self.__func(self.get_google())
+
+class CheckGoogleTask(libbig.polling.Task):
+ def __init__(self, google):
+ libbig.polling.Task.__init__(self, 1000 * 120, initial_interval=1000*5)
+ self.__google = google
+ self.__actions = {} ## hash from id to [add count, GooglePollAction object]
+
+ def add_action(self, id, action_constructor):
+ if id not in self.__actions:
+ action = action_constructor()
+ self.__actions[id] = [1, action]
+ else:
+ self.__actions[id][0] = self.__actions[id][0] + 1
+
+ def remove_action(self, id):
+ if id not in self.__actions:
+ raise Exception("removing action id that wasn't added")
+ self.__actions[id][0] = self.__actions[id][0] - 1
+ if self.__actions[id][0] == 0:
+ del self.__actions[id]
+
def do_periodic_task(self):
- self.__google.fetch_new_mail(self.__on_fetched_mail, self.__on_fetch_error)
+ for (id, a) in self.__actions.values():
+ a.update()
class Google(gobject.GObject):
__gsignals__ = {
- "auth" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,))
+ ## "auth-badness-changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,))
}
- def __init__(self, login_label, storage_key, default_domain='gmail.com', type_hint=None):
+ def __init__(self, account):
super(Google, self).__init__()
self.__logger = logging.getLogger("bigboard.Google")
- self.__username = None
- self.__password = None
+ self.__last_username_tried = ''
+ self.__last_password_tried = ''
+ self.__last_auth_attempt_failed = False
self.__fetcher = AsyncHTTPFetcherWithAuth()
- self.__auth_requested = False
- self.__post_auth_hooks = []
- self.__login_label = login_label
- self.__storage_key = storage_key
- self.__default_domain = default_domain
- self.__type_hint = type_hint
+ self.__account = account
- self.__model = DataModel(globals.server_name)
-
- self.__ddm_identity = None
- self.__model.add_connected_handler(self.__on_data_model_connected)
- if self.__model.self_id:
- self.__on_data_model_connected()
- else:
- _logger.debug("datamodel not connected, deferring")
+ self.__checker = CheckGoogleTask(self)
+ self.add_poll_action('mail', lambda: MailPollAction(self))
- self.__weblogindriver_proxy = dbus.SessionBus().get_object('org.gnome.WebLoginDriver', '/weblogindriver')
- self.__weblogindriver_proxy.connect_to_signal("SignonChanged",
- self.__on_signon_changed)
- self.__recheck_signons()
+ self.__account.connect('changed', lambda account: self.__on_account_changed())
+ self.__on_account_changed()
- # this line allows to enter new Google account information on bigboard restarts
- # k.remove_logins(self.__default_domain)
- self.__mail_checker = None
+ def get_account(self):
+ return self.__account
- self.__username = None
- self.__password = None
- self.__on_auth_cancel()
+ def destroy(self):
+ ## FIXME close down all the polling tasks and remove all signal handlers
+ self.__checker.stop()
- def __consider_checking_mail(self):
- if self.__username and self.__password:
- if not self.__mail_checker:
- self.__mail_checker = CheckMailTask(self)
- self.__mail_checker.start()
- elif self.__mail_checker:
- self.__mail_checker.stop()
+ def add_poll_action(self, id, action_constructor):
+ self.__checker.add_action(id, action_constructor)
- def __handle_matched_signon(self, username, password):
- if self.__type_hint == 'GMail':
- username = username + "@gmail.com"
- _logger.debug("using GMail identity %s", username)
- self.__on_auth_ok(username, password)
- elif self.__type_hint == 'GAFYD-Mail':
- if not self.__ddm_identity:
- _logger.debug("no DDM identity, skipping GAFYD mail")
- return
- if not hasattr(self.__ddm_identity, 'googleEnabledEmails'):
- _logger.debug("No googleEnabledEmails in DDM identity")
- return
- for email in self.__ddm_identity.googleEnabledEmails:
- (emailuser, emailhost) = email.split('@', 1) # TODO is this right?
- if emailuser == username:
- _logger.debug("matched username %s (%s) with googleEnabledEmail", emailuser, email)
- self.__on_auth_ok(email, password)
- else:
- _logger.error("unknown type hint %s...oops!", self.__type_hint)
+ def remove_poll_action(self, id):
+ self.__checker.remove_action(id)
- def __check_signons(self, signons):
- for signon in signons:
- if 'hint' not in signon: continue
- if signon['hint'] == self.__type_hint:
- _logger.debug("hint %s matched signon %s", self.__type_hint, signon)
- self.__handle_matched_signon(signon['username'], base64.b64decode(signon['password']))
- return
-
- def __recheck_signons(self):
- self.__weblogindriver_proxy.GetSignons(reply_handler=self.__on_get_signons_reply,
- error_handler=self.__on_dbus_error)
-
- def __on_data_model_connected(self, *args):
- _logger.debug("got data model connection")
- query = self.__model.query_resource(self.__model.self_id, "+;googleEnabledEmails +")
- query.add_handler(self.__on_google_enabled_emails)
- query.add_error_handler(self.__on_datamodel_error)
- query.execute()
-
- def __on_datamodel_error(self, code, str):
- _logger.error("datamodel error %s: %s", code, str)
-
- def __on_google_enabled_emails(self, myself):
- self.__ddm_identity = myself
- myself.connect(self.__on_ddm_identity_changed)
- self.__on_ddm_identity_changed(myself)
-
- def __on_ddm_identity_changed(self, myself):
- _logger.debug("ddm identity (%s) changed", myself.resource_id)
- call_idle_once(self.__recheck_signons)
+ def add_poll_action_func(self, id, func):
+ self.__checker.add_action(id, lambda: GenericPollAction(self, func))
- @log_except(_logger)
- def __on_get_signons_reply(self, signondata):
- _logger.debug("got signons reply")
- for hostname,signons in signondata.iteritems():
- self.__check_signons(signons)
+ def __consider_checking(self):
+ if self.get_current_auth_credentials_known_bad():
+ _logger.debug("Disabling google polling since auth credentials known bad")
+ self.__checker.stop()
+ else:
+ _logger.debug("Enabling google polling since auth credentials maybe good")
+ self.__checker.start()
- @log_except(_logger)
- def __on_signon_changed(self, signons):
- _logger.debug("signons changed: %s", signons)
- self.__check_signons(signons)
+ def __auth_needs_retry(self):
- @log_except(_logger)
- def __on_dbus_error(self, err):
- self.__logger.error("D-BUS error: %s", err)
+ username = self.__account.get_username_as_google_email()
+ password = self.__account.get_password()
- def __on_auth_ok(self, username, password):
- if '@' not in username:
- username = username + '@' + self.__default_domain
- self.__username = username
- self.__password = password
- self.__auth_requested = False
+ _logger.debug("auth retry for google username %s" % (username))
- hooks = self.__post_auth_hooks
- self.__post_auth_hooks = []
- for h in hooks:
- h()
- self.emit("auth", True)
+ self.__last_username_tried = username
+ self.__last_password_tried = password
+ self.__last_auth_attempt_failed = False
- self.__consider_checking_mail()
+ self.__consider_checking()
- def __on_auth_cancel(self):
- self.__username = None
- self.__password = None
+ def __on_auth_failed(self, failed_username, failed_password, errcb):
+ _logger.debug("bad auth for google")
- ## FIXME delete our stored password or mark it as failed somehow (in-process)
-
- self.emit("auth", False)
- self.__consider_checking_mail()
- self.__auth_requested = False
- self.__with_login_info(lambda: True)
+ if failed_username == self.__last_username_tried and \
+ failed_password == self.__last_password_tried:
+ self.__last_auth_attempt_failed = True
+ self.__consider_checking()
- def have_auth(self):
- return (self.__username is not None) and (self.__password is not None)
+ errcb({ 'status' : 401, 'message' : 'Bad or missing username or password' })
- def get_auth(self):
- return (self.__username, self.__password)
+ def get_current_auth_credentials_known_bad(self):
+ return self.__account.get_username_as_google_email() == '' or \
+ self.__account.get_password() == '' or \
+ self.__last_auth_attempt_failed
- def get_storage_key(self):
- return self.__storage_key
+ def __on_account_changed(self):
+ auth_changed = False
+ if self.__last_username_tried != self.__account.get_username_as_google_email():
+ auth_changed = True
+ if self.__last_password_tried != self.__account.get_password():
+ auth_changed = True
- def __with_login_info(self, func, reauth=False):
- """Call func after we get username and password"""
+ _logger.debug("google account changed, auth_changed=%d" % (auth_changed))
- if self.__username and self.__password:
- # _logger.debug("auth looks valid")
- func()
- return
+ if auth_changed:
+ self.__auth_needs_retry()
+ def __call_if_may_have_auth(self, func, errfunc):
+ if self.get_current_auth_credentials_known_bad():
+ errfunc({ 'status' : 401, 'message' : 'Bad or missing username or password' })
else:
- _logger.debug("auth request pending; not resending")
- self.__post_auth_hooks.append(func)
+ func()
- def __on_bad_auth(self):
- _logger.debug("got bad auth; invoking reauth")
- # don't null username, leave it filled in
- self.__password = None
- self.__auth_requested = False
- self.__with_login_info(lambda: True, reauth=True)
-
### Calendar
def __have_login_fetch_calendar_list(self, cb, errcb):
+ username = self.__account.get_username_as_google_email()
+ password = self.__account.get_password()
+
# there is a chance that someone might have access to more than 25 calendars, so let's
# specify 1000 for max-results to make sure we get information about all calendars
- uri = 'http://www.google.com/calendar/feeds/' + self.__username + '?max-results=1000'
+ uri = 'http://www.google.com/calendar/feeds/' + username + '?max-results=1000'
- self.__fetcher.fetch(uri, self.__username, self.__password,
+ self.__fetcher.fetch(uri, username, password,
lambda url, data: cb(url, data, self),
lambda url, resp: errcb(resp),
- lambda url: self.__on_bad_auth())
+ lambda url: self.__on_auth_failed(username, password, errcb))
def fetch_calendar_list(self, cb, errcb):
- self.__with_login_info(lambda: self.__have_login_fetch_calendar_list(cb, errcb))
+ self.__call_if_may_have_auth(lambda: self.__have_login_fetch_calendar_list(cb, errcb), errcb)
def __have_login_fetch_calendar(self, cb, errcb, calendar_feed_url, event_range_start, event_range_end):
+ username = self.__account.get_username_as_google_email()
+ password = self.__account.get_password()
+
min_and_max_str = ""
if event_range_start is not None and event_range_end is not None:
# just specifying start-min and start-max includes multiple "when" tags in the response for the recurrent events,
@@ -564,57 +483,55 @@
min_and_max_str = "?start-min=" + fmt_date_for_feed_request(event_range_start) + "&start-max=" + fmt_date_for_feed_request(event_range_end) + "&singleevents=true" + "&max-results=1000"
if calendar_feed_url is None:
- uri = 'http://www.google.com/calendar/feeds/' + self.__username + '/private/full' + min_and_max_str
+ uri = 'http://www.google.com/calendar/feeds/' + username + '/private/full' + min_and_max_str
else:
uri = calendar_feed_url + min_and_max_str
- self.__fetcher.fetch(uri, self.__username, self.__password,
+ self.__fetcher.fetch(uri, username, password,
lambda url, data: cb(url, data, calendar_feed_url, event_range_start, event_range_end, self),
lambda url, resp: errcb(resp),
- lambda url: self.__on_bad_auth())
+ lambda url: self.__on_auth_failed(username, password, errcb))
def fetch_calendar(self, cb, errcb, calendar_feed_url = None, event_range_start = None, event_range_end = None):
- self.__with_login_info(lambda: self.__have_login_fetch_calendar(cb, errcb, calendar_feed_url, event_range_start, event_range_end))
+ self.__call_if_may_have_auth(lambda: self.__have_login_fetch_calendar(cb, errcb, calendar_feed_url, event_range_start, event_range_end), errcb)
- def request_auth(self):
- self.__with_login_info(lambda: True)
-
### Recent Documents
def __on_documents_load(self, url, data, cb, errcb):
self.__logger.debug("loaded documents from " + url)
- try:
- p = DocumentsParser()
- xml.sax.parseString(data, p)
- cb(p.get_documents())
- except xml.sax.SAXException, e:
- errcb(sys.exc_info())
+ document_list = gdocs.DocumentListFeedFromString(data)
+ cb(document_list.entry)
def __on_documents_error(self, url, exc_info, errcb):
self.__logger.debug("error loading documents from " + url)
errcb(exc_info)
def __have_login_fetch_documents(self, cb, errcb):
+ username = self.__account.get_username_as_google_email()
+ password = self.__account.get_password()
+
uri = 'http://docs.google.com/feeds/documents/private/full'
# uri = 'http://spreadsheets.google.com/feeds/spreadsheets/private/full'
- self.__fetcher.fetch(uri, self.__username, self.__password,
- lambda url, data: cb(url, data, self),
- lambda url, resp: errcb(resp),
- lambda url: self.__on_bad_auth())
+ self.__fetcher.fetch(uri, username, password,
+ lambda url, data: self.__on_documents_load(url, data, cb, errcb),
+ lambda url, resp: self.__on_documents_error(url, resp, errcb),
+ lambda url: self.__on_auth_failed(username, password, errcb))
def fetch_documents(self, cb, errcb):
- self.__with_login_info(lambda: self.__have_login_fetch_documents(cb, errcb))
+ self.__call_if_may_have_auth(lambda: self.__have_login_fetch_documents(cb, errcb), errcb)
### New Mail
def get_mail_base_url(self):
- if not self.__username:
+ username = self.__account.get_username_as_google_email()
+
+ if not username:
return None
- at_sign = self.__username.find('@')
+ at_sign = username.find('@')
- domain = self.__username[at_sign+1:]
+ domain = username[at_sign+1:]
if not domain.endswith('gmail.com'):
uri = 'http://mail.google.com/a/' + domain
@@ -639,44 +556,63 @@
def __have_login_fetch_new_mail(self, cb, errcb):
+ username = self.__account.get_username_as_google_email()
+ password = self.__account.get_password()
+
uri = self.get_mail_base_url() + '/feed/atom'
- self.__fetcher.fetch(uri, self.__username, self.__password,
+ self.__fetcher.fetch(uri, username, password,
lambda url, data: self.__on_new_mail_load(url, data, cb, errcb),
lambda url, exc_info: self.__on_new_mail_error(url, exc_info, errcb),
- lambda url: self.__on_bad_auth())
+ lambda url: self.__on_auth_failed(username, password, errcb))
def fetch_new_mail(self, cb, errcb):
- self.__with_login_info(lambda: self.__have_login_fetch_new_mail(cb, errcb))
+ self.__call_if_may_have_auth(lambda: self.__have_login_fetch_new_mail(cb, errcb), errcb)
-_google_personal_instance = None
-def get_google_at_personal():
- global _google_personal_instance
- if _google_personal_instance is None:
- _google_personal_instance = Google(login_label='Google Personal', storage_key='google', type_hint='GMail')
- return _google_personal_instance
+__googles_by_account = {}
+__initialized = False
-_google_work_instance = None
-def get_google_at_work():
- global _google_work_instance
- if _google_work_instance is None:
- _google_work_instance = Google(login_label='Google Work', storage_key='google-work', type_hint='GAFYD-Mail')
- return _google_work_instance
-
def get_googles():
- return [get_google_at_personal(), get_google_at_work()]
+ global __googles_by_account
+ return __googles_by_account.values()
-## this is a hack to allow incrementally porting code to multiple
-## google accounts, it doesn't really make any sense long-term
-def get_google():
- personal = get_google_at_personal()
- work = get_google_at_work()
- if personal.have_auth():
- return personal
- elif work.have_auth():
- return work
- else:
- return personal
+def get_google_for_account(account):
+ global __googles_by_account
+ return __googles_by_account[account]
+
+def __refresh_googles(a):
+ global __googles_by_account
+ gaccounts = a.get_accounts_with_kind(accounts.KIND_GOOGLE)
+ new_googles = {}
+ for g in gaccounts:
+ if g in __googles_by_account:
+ new_googles[g] = __googles_by_account[g]
+ else:
+ new_googles[g] = Google(g)
+
+ for (g, old) in __googles_by_account.items():
+ if g not in new_googles:
+ old.destroy()
+
+ __googles_by_account = new_googles
+
+def __on_account_added(a, account):
+ __refresh_googles(a)
+
+def __on_account_removed(a, account):
+ __refresh_googles(a)
+
+def init():
+ global __initialized
+
+ if __initialized:
+ return
+ __initialized = True
+
+ a = accounts.get_accounts()
+ __refresh_googles(a)
+ a.connect('account-added', __on_account_added)
+ a.connect('account-removed', __on_account_removed)
if __name__ == '__main__':
Modified: bigboard/trunk/bigboard/google_stock.py
===================================================================
--- bigboard/trunk/bigboard/google_stock.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/google_stock.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -1,36 +1,72 @@
+import logging
+import hippo
import bigboard.google as google
-import bigboard.libbig.polling as polling
+import bigboard.accounts as accounts
+import accounts_dialog
-polling_periodicity_seconds = 120
+_logger = logging.getLogger("bigboard.Google")
-class GoogleStock(polling.Task):
- def __init__(self, *args, **kwargs):
+## this base class is messed up, because the polling and account data
+## tracking should be in a model object, not in the view (stock).
+## Some stuff in here is view-specific though, like the _create_login_button
+## method.
+
+class GoogleStock(object):
+ def __init__(self, action_id, **kwargs):
+ super(GoogleStock, self).__init__(**kwargs)
+
# A dictionary of authenticated google accounts, with keys that are used
# to identify those accounts within the stock.
self.googles = set()
+ self.__googles_by_account = {} ## map accounts.Account => google.Google
- polling.Task.__init__(self, polling_periodicity_seconds * 1000)
+ self.__action_id = action_id
- gobj_list = google.get_googles()
- for gobj in gobj_list:
- gobj.connect("auth", self.on_google_auth)
- if gobj.have_auth():
- self.on_google_auth(gobj, True)
- else:
- gobj.request_auth()
+ accts = accounts.get_accounts()
+ for a in accts.get_accounts_with_kind(accounts.KIND_GOOGLE):
+ self.__on_account_added(a)
+ accts.connect('account-added', self.__on_account_added)
+ accts.connect('account-removed', self.__on_account_removed)
- def on_google_auth(self, gobj, have_auth):
- if have_auth:
- self.googles.add(gobj)
- self.update_google_data(gobj)
- if not self.is_running():
- self.start()
- elif gobj in self.googles:
- self.stop()
- self.remove_google_data(gobj)
- self.googles.remove(gobj)
+ ## FIXME need to unhook everything when stock is removed
- def do_periodic_task(self):
- self.update_google_data()
+ def __on_account_added(self, acct):
+ gobj = google.get_google_for_account(acct)
+ gobj.add_poll_action_func(self.__action_id, lambda gobj: self.update_google_data(gobj))
+ self.googles.add(gobj)
+ self.__googles_by_account[acct] = gobj
+ ## update_google_data() should be called in the poll action
+
+ def __on_account_removed(self, acct):
+ ## we keep our own __googles_by_account because google.get_google_for_account()
+ ## will have dropped the Google before this point
+ gobj = self.__googles_by_account[acct]
+ gobj.remove_poll_action(self.__action_id)
+ self.googles.remove(gobj)
+ del self.__googles_by_account[acct]
+ ## hook for derived classes
+ self.remove_google_data(gobj)
+
+ def have_one_good_google(self):
+ for g in self.googles:
+ if not g.get_current_auth_credentials_known_bad():
+ return True
+
+ return False
+
+ def __open_login_dialog(self):
+ accounts_dialog.open_dialog()
+
+ def _create_login_button(self):
+ button = hippo.CanvasButton(text="Login to Google")
+ button.connect('activated', lambda button: self.__open_login_dialog())
+ return button
+
+ def update_google_data(self, gobj=None):
+ pass
+
+ def remove_google_data(self, gobj):
+ pass
+
Modified: bigboard/trunk/bigboard/keyring.py
===================================================================
--- bigboard/trunk/bigboard/keyring.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/keyring.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -89,12 +89,14 @@
def get_password(self, kind, username, url):
logins = self.get_logins(kind, username, url)
+ _logger.debug("got logins: %s" % (str(logins)))
if len(logins) > 0:
return logins.pop().get_password()
else:
return None
def remove_logins(self, kind, username, url):
+ _logger.debug("removing login (%s, %s, %s)" % (kind, username, url))
new_fallbacks = set()
for ki in self.__fallback_items:
if ki.get_kind() == kind and \
@@ -120,7 +122,12 @@
gnomekeyring.item_delete_sync('session', f.item_id)
def store_login(self, kind, username, url, password):
- _logger.debug("storing login " + username)
+
+ if not password:
+ self.remove_logins(kind, username, url)
+ return
+
+ _logger.debug("storing login (%s, %s, %s)" % (kind, username, url))
if not self.is_available():
found = None
for ki in self.__fallback_items:
Deleted: bigboard/trunk/bigboard/stocks/docs_stock.py
===================================================================
--- bigboard/trunk/bigboard/stocks/docs_stock.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/stocks/docs_stock.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -1,93 +0,0 @@
-import logging
-
-import gobject
-import hippo
-
-import bigboard, mugshot, google, pango, os
-from big_widgets import CanvasMugshotURLImage, PhotoContentItem
-
-class DocDisplay(PhotoContentItem):
- def __init__(self, doc):
- PhotoContentItem.__init__(self, border_right=6)
- self.__doc = None
-
- self.__photo = CanvasMugshotURLImage(scale_width=30, scale_height=30)
- self.set_photo(self.__photo)
- self.__box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL, spacing=2,
- border_right=4)
- self.__title = hippo.CanvasText(xalign=hippo.ALIGNMENT_START, size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END)
- self.__description = hippo.CanvasText(xalign=hippo.ALIGNMENT_START, size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END)
- attrs = pango.AttrList()
- attrs.insert(pango.AttrForeground(0x6666, 0x6666, 0x6666, 0, 0xFFFF))
- self.__description.set_property("attributes", attrs)
- self.__box.append(self.__title)
- self.__box.append(self.__description)
- self.set_child(self.__box)
-
- self.connect("button-press-event", lambda self, event: self.__on_button_press(event))
-
- self.set_doc(doc)
-
- def set_doc(self, doc):
- self.__doc = doc
- #self.__doc.connect("changed", lambda doc: self.__doc_display_sync())
- self.__doc_display_sync()
-
- def __get_title(self):
- if self.__doc is None:
- return "unknown"
- return self.__doc.get_title()
-
- def __str__(self):
- return '<DocDisplay name="%s">' % (self.__get_title())
-
- def __doc_display_sync(self):
- self.__title.set_property("text", self.__doc.get_title())
- #self.__photo.set_url(self.__doc.get_icon_url())
-
- def __on_button_press(self, event):
- if event.button != 1:
- return False
-
- logging.debug("activated doc %s", self)
-
- os.spawnlp(os.P_NOWAIT, 'gnome-open', 'gnome-open', self.__doc.get_link())
-
-class DocsStock(bigboard.AbstractMugshotStock):
- def __init__(self):
- super(DocsStock, self).__init__("Documents")
- self._box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL, spacing=3)
- self._docs = {}
-
- self.__update_docs()
-
- def _on_mugshot_initialized(self):
- super(DocsStock, self)._on_mugshot_initialized()
-
- def get_content(self, size):
- return self._box
-
- def _set_item_size(self, item, size):
- if size == bigboard.Stock.SIZE_BULL:
- item.set_property('xalign', hippo.ALIGNMENT_FILL)
- else:
- item.set_property('xalign', hippo.ALIGNMENT_CENTER)
- item.set_size(size)
-
- def set_size(self, size):
- super(DocsStock, self).set_size(size)
- for child in self._box.get_children():
- self._set_item_size(child, size)
-
- def __on_load_docs(self, docs):
- self._box.remove_all()
- for doc in docs:
- display = DocDisplay(doc)
- self._box.append(display)
-
- def __on_failed_load(self, exc_info):
- pass
-
- def __update_docs(self):
- logging.debug("retrieving documents")
- google.Google().fetch_documents(self.__on_load_docs, self.__on_failed_load)
Modified: bigboard/trunk/bigboard/stocks/files/FilesStock.py
===================================================================
--- bigboard/trunk/bigboard/stocks/files/FilesStock.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/stocks/files/FilesStock.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -9,7 +9,6 @@
from pyonlinedesktop.fsutil import VfsMonitor
-import gdata.docs as gdocs
import bigboard.libbig as libbig
from bigboard.libbig.logutil import log_except
from bigboard.libbig.gutil import *
@@ -209,7 +208,7 @@
"""Shows recent files."""
def __init__(self, *args, **kwargs):
Stock.__init__(self, *args, **kwargs)
- google_stock.GoogleStock.__init__(self, *args, **kwargs)
+ google_stock.GoogleStock.__init__(self, 'files', **kwargs)
# files in this list are either LocalFile or GoogleFile
self.__files = []
@@ -236,10 +235,10 @@
def update_google_data(self, selected_gobj = None):
if selected_gobj is not None:
- selected_gobj.fetch_documents(self.__on_documents_load, self.__on_failed_load)
+ selected_gobj.fetch_documents(lambda entries: self.__on_documents_load(entries, selected_gobj), self.__on_failed_load)
else:
for gobj in self.googles:
- gobj.fetch_documents(self.__on_documents_load, self.__on_failed_load)
+ gobj.fetch_documents(lambda entries: self.__on_documents_load(entries, gobj), self.__on_failed_load)
def __remove_files_for_key(self, source_key):
files_to_keep = []
@@ -256,11 +255,11 @@
self._panel.action_taken()
subprocess.Popen(['gnome-open', fobj.get_url()])
- def __on_documents_load(self, url, data, gobj):
- document_list = gdocs.DocumentListFeedFromString(data)
+ def __on_documents_load(self, document_entries, gobj):
self.__remove_files_for_key(gobj)
- for document_entry in document_list.entry:
- google_file = GoogleFile(gobj, gobj.get_auth()[0], document_entry)
+ for document_entry in document_entries:
+ google_file = GoogleFile(gobj, gobj.get_account().get_username_as_google_email(),
+ document_entry)
google_file.connect('activated', self.__on_file_activated)
self.__files.append(google_file)
self.__files.sort(compare_by_date)
Modified: bigboard/trunk/bigboard/stocks/files/filebrowser.py
===================================================================
--- bigboard/trunk/bigboard/stocks/files/filebrowser.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/stocks/files/filebrowser.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -60,10 +60,10 @@
for google_account in self.__stock.googles:
# don't list invalid accounts we might have picked up from the signons file
- if not google_account.have_auth():
+ if google_account.get_current_auth_credentials_known_bad():
continue
- google_docs_link = ActionLink(text=google_account.get_auth()[0] + " Docs", font="14px", padding_bottom=4, xalign=hippo.ALIGNMENT_START, yalign=hippo.ALIGNMENT_START)
- google_docs_link.connect("activated", webbrowser.open, create_account_url(google_account.get_auth()[0]))
+ google_docs_link = ActionLink(text=google_account.get_account().get_username_as_google_email() + " Docs", font="14px", padding_bottom=4, xalign=hippo.ALIGNMENT_START, yalign=hippo.ALIGNMENT_START)
+ google_docs_link.connect("activated", webbrowser.open, create_account_url(google_account.get_account().get_username_as_google_email()))
browse_options.append(google_docs_link)
self.__search_box = CanvasHBox(padding_top=4, padding_bottom=4)
Modified: bigboard/trunk/bigboard/stocks/google_calendar/CalendarStock.py
===================================================================
--- bigboard/trunk/bigboard/stocks/google_calendar/CalendarStock.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/bigboard/stocks/google_calendar/CalendarStock.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -438,7 +438,7 @@
# these are at the end since they have the side effect of calling on_mugshot_ready it seems?
AbstractMugshotStock.__init__(self, *args, **kwargs)
- google_stock.GoogleStock.__init__(self, *args, **kwargs)
+ google_stock.GoogleStock.__init__(self, 'calendar', **kwargs)
bus = dbus.SessionBus()
o = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
@@ -548,7 +548,7 @@
# you must first close the existing Firefox process, or restart your system."
time.sleep(2)
done_with_sleep_state = 2
- libbig.show_url(create_account_url(google_account.get_auth()[0]))
+ libbig.show_url(create_account_url(google_account.get_account().get_username_as_google_email()))
if done_with_sleep_state == 0:
done_with_sleep_state = 1
@@ -591,7 +591,7 @@
self.__refresh_events()
def __on_calendar_list_load(self, url, data, gobj):
- _logger.debug("loaded calendar list %s", data)
+ _logger.debug("loaded calendar list %d chars" % (len(data)))
google_key = gobj
if google_key is None:
_logger.warn("didn't find google_key for %s", gobj)
@@ -738,8 +738,9 @@
def __refresh_events(self):
self.__box.remove_all()
- if not self.is_running():
- self.__box.append(hippo.CanvasText(xalign=hippo.ALIGNMENT_CENTER, text="Sign into GMail"))
+ if not self.have_one_good_google():
+ button = self._create_login_button()
+ self.__box.append(button)
return
title = hippo.CanvasText(xalign=hippo.ALIGNMENT_START, size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END)
@@ -970,8 +971,9 @@
def __on_failed_load(self, response):
_logger.debug("load failed")
- pass
-
+ ## this displays the "need to log in" thing
+ self.__refresh_events()
+
def __update_calendar_list_and_events(self, selected_gobj = None):
_logger.debug("retrieving calendar list")
# we update events in __on_calendar_list_load()
Modified: bigboard/trunk/main.py
===================================================================
--- bigboard/trunk/main.py 2007-10-29 21:42:41 UTC (rev 6851)
+++ bigboard/trunk/main.py 2007-10-30 20:34:15 UTC (rev 6852)
@@ -819,7 +819,7 @@
panel.show()
- bigboard.google.get_google() # for side effect of creating the Google object
+ bigboard.google.init()
#bigboard.presence.get_presence() # for side effect of creating Presence object
gtk.gdk.threads_enter()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]