#!/usr/bin/python

import os,sys,re,logging,urlparse,base64,gconf,copy,urllib2
import xml.dom, xml.dom.minidom

import gobject, dbus, gnomevfs, gobject
import dbus,dbus.service,dbus.glib

from ddm import DataModel

# Custom modules
import nssdecrypt
import weblogindriver.keyring as keyring
import weblogindriver.accounts_dialog as accounts_dialog
from weblogindriver.gutil import *
import weblogindriver.ddm_util as ddm_util

_logger = logging.getLogger("WebLoginDriver")

GCONF_BASE_DIR = "/desktop/gnome/online-accounts"

_accounts_schema_dir = "/schemas" + GCONF_BASE_DIR + "/TEMPLATE"

def do_idle(f, *args):
  f(*args)
  return False

def on_pidgin_gmail(signon):
  p = None
  try:
      p = dbus.SessionBus().get_object('im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject')
  except dbus.DBusException, e:
      _logger.debug("PurpleService not available")
      return
  target_username = signon['username'] + '@gmail.com'
  a = None
  for acctid in p.PurpleAccountsGetAllActive():
    username = p.PurpleAccountGetUsername(acctid)
    try:
      (real_username, resource) = username.split('/', 1)    
    except:
      real_username = username   
    if real_username == target_username:
      a = acctid
      break
  if not a:
    _logger.debug("creating pidgin account for %s", target_username)
    a = p.PurpleAccountNew(target_username, 'prpl-jabber')
  p.PurpleAccountsAdd(a)     
  p.PurpleAccountSetPassword(a, base64.b64decode(signon['password']))
  p.PurpleAccountSetRememberPassword(a, dbus.UInt32(1))
  do_idle(p.PurpleAccountSetEnabled, a, "gtk-gaim", dbus.UInt32(1))

def associate_schema(schema_dir, new_dir):
  """

  This function is used when dynamically creating a directory in
  GConf that should be associated with a fixed schema.

  schema_dir -- the directory where the schema is stored
  new_dir -- the directory to associate the schema with

  """
    
  client = gconf.client_get_default()
  engine = gconf.engine_get_default()
  _logger.debug("associating schema schema_dir %s" % schema_dir)
  for entry in client.all_entries(schema_dir):
    _logger.debug("entry key %s" % entry.get_key())
    # There is a schema for each individual key, 
    key = os.path.join(new_dir, os.path.basename(entry.get_key()))
    engine.associate_schema(key, entry.get_key())

hints = {
'GMail': {'hostname': 'https://www.google.com', 'username_field': 'Email'},
'GAFYD-Mail': {'hostname': 'https://www.google.com', 'username_field': 'userName'},
}

internal_matchers = {
  #'GMail': on_pidgin_gmail, # this causes invalid gmail accounts that are stuck in Firefox to be added to pidgin
}

BUS_NAME_STR='org.gnome.WebLoginDriver'
BUS_IFACE_STR='org.gnome.WebLoginDriver'
OA_BUS_NAME_STR='org.gnome.OnlineAccounts'
OA_BUS_IFACE_STR='org.gnome.OnlineAccounts'

def iterdir(path):
  for fname in os.listdir(path):
    yield os.path.join(path, fname)

class VfsMonitor(object):
  """Avoid some locking oddities in gnomevfs monitoring"""
  def __init__(self, path, montype, cb):
    self.__path = path
    self.__cb = cb
    self.__idle_id = 0
    self.__monid = gnomevfs.monitor_add(path, montype, self.__on_vfsmon)
  
  def __idle_emit(self):
    self.__idle_id = 0
    self.__cb()

  def __on_vfsmon(self, *args):
    if not self.__monid:
      return
    if self.__idle_id == 0:
      self.__idle_id = gobject.timeout_add(300, self.__idle_emit)

  def cancel(self):
    if self.__idle_id:
      gobject.source_remove(self.__idle_id)
      self.__idle_id = 0
    if self.__monid:
      gnomevfs.monitor_cancel(self.__monid)
      self.__monid = None
    
class WebLoginDriver(dbus.service.Object):
  def __init__(self, bus_name):
    dbus.service.Object.__init__(self, bus_name, '/weblogindriver')  
      
    self.__ffpath = os.path.expanduser('~/.mozilla/firefox')
    self.__profpath = None
    self.__profile_monid = None
    self.__signons = {} # domain->(user,pass,{details})
    self.__signons_monid = None
    self.__profile_monid = None
    self.__uncreated_monid = None
    try:
      profpath = self.__get_profile_path()
    except KeyError, e:
      profpath = None
      _logger.debug("couldn't find default profile, awaiting creation")
      self.__uncreated_monid = VfsMonitor('file://' + self.__ffpath, gnomevfs.MONITOR_DIRECTORY, self.__on_profile_created)
    if profpath:
      self.__monitor_signons(profpath)

  def __monitor_signons(self, profpath):
    if profpath:
      _logger.debug("starting monitoring of signons in %s", profpath)
      self.__profpath = profpath
    signons_path = os.path.join(self.__profpath, 'signons2.txt')
    if (self.__signons_monid is None) and os.path.isfile(signons_path):
      _logger.debug("monitoring signons file")
      _logger.debug("initializing NSS in %s", self.__profpath)
      nssdecrypt.init(self.__profpath)
      self.__signons_monid = VfsMonitor('file://' + signons_path, gnomevfs.MONITOR_FILE, self.__on_signons_changed)
      if self.__profile_monid is not None:
        self.__profile_monid.cancel()
        self.__profile_monid = None
      gobject.idle_add(self.__idle_read_signons)
    elif self.__profile_monid is None:
      self.__profile_monid = VfsMonitor(profpath, gnomevfs.MONITOR_DIRECTORY, self.__on_profdir_changed)

  def __on_profdir_changed(self, *args):
    if self.__signons_monid is not None:
      return
    _logger.debug("profile dir changed: %s", args)
    self.__monitor_signons(None)
  
  def __on_signons_changed(self, *args):
    _logger.debug("signons changed: %s", args)
    if self.__idle_read_signons_id > 0:
      _logger.debug("canceling queued signons read")
      gobject.source_remove(self.__idle_read_signons_id)
    _logger.debug("queued signons read for 3s")
    self.__idle_read_signons_id = gobject.timeout_add(3000, self.__idle_read_signons)

  def __idle_read_signons(self):
    _logger.debug("in idle signons read")
    self.__idle_read_signons_id = 0

    new_signons = self.__read_signons()
    for signons in new_signons.itervalues():
      for signon in signons:
        for hintname,hintitems in hints.iteritems():
          matched = True
          for k,v in hintitems.iteritems():
            if signon[k] != v:
              matched = False
              break
          if matched:
            signon['hint'] = hintname
            break
    if len(self.__signons) > 0:
      changed_signons = []
      for hostname,signons in new_signons.iteritems():
        if (hostname not in self.__signons) or (self.__signons[hostname] != signons):
          self.SignonChanged(signons)
          changed_signons.append(signons)
    else:
      changed_signons = new_signons.itervalues()
      # todo signal on delete?
    for signons in changed_signons:
      _logger.debug("evaluating %s", signons)
      for signon in signons:
        for hintname,handler in internal_matchers.iteritems():
          if signon.get('hint', '') == hintname:
            try:
              _logger.debug("invoking handler %s for signon %s", handler, signon)
              handler(signon)
            except:
              _logger.error("handler %s failed", handler, exc_info=True)
    self.__signons = new_signons
  
  def __read_signons(self):
    f = open(os.path.join(self.__profpath, 'signons2.txt'))
    parse_states = dict(zip(['header', 'reject', 'realm', 'userfield', 'uservalue', 'passfield', 'passvalue', 'actionurl'],
                            xrange(100)))
    parse_state = 0

    realm_re = re.compile(r'^(.+?) \((.*)\)$')

    new_signons = {}

    # Manually adapted from mozilla/toolkit/components/passwordmgr/src/storage-Legacy.js 20071017
    signon = {}
    havemore = True
    while havemore:
      line = f.readline()
      havemore = not not line
      line = line.strip()
      process_entry = False
      if parse_state == parse_states['header']:
        if line != '#2d':
          raise ValueError("Invalid header: %s" % (line,))
        parse_state += 1
      elif parse_state == parse_states['reject']:
        if line == '.':
          parse_state += 1 # ignore disabled hosts stuff for now
      elif parse_state == parse_states['realm']:
        m = realm_re.match(line)
        signon = {}
        if not m:
          signon['hostname'] = line
        else:
          signon['hostname'] = m.group(1)
          signon['http_realm'] = m.group(2)
        parse_state += 1
      elif parse_state == parse_states['userfield']:
        if line == '.':
          parse_state = parse_states['realm']
        else:
          signon['username_field'] = line
          parse_state += 1 
      elif parse_state == parse_states['uservalue']:
        try:
          signon['username'] = nssdecrypt.decrypt(base64.b64decode(line))
        except KeyError, e:
          _logger.error("failed decrypt while processing host '%s'", signon.get('hostname', '(None)'))
        parse_state += 1
      elif parse_state == parse_states['passfield']:
        signon['password_field'] = line
        parse_state += 1
      elif parse_state == parse_states['passvalue']:
        signon['password'] = base64.b64encode(nssdecrypt.decrypt(base64.b64decode(line)))
        parse_state += 1
      elif parse_state == parse_states['actionurl']:
        signon['submiturl'] = line
        process_entry = True
        parse_state = parse_states['userfield']

      if process_entry:
        hostname = signon['hostname']
        if hostname not in new_signons:
          new_signons[hostname] = []
        l = new_signons[hostname]
        l.append(dict(signon))
        _logger.debug("processed signon for %s: %s", hostname, signon)
        process_entry = False

    return new_signons

  def __on_profile_created(self, *args):
    if self.__uncreated_monid is None:
      return
    _logger.debug("profile directory changed")
    try:
      profpath = self.__get_profile_path()
    except KeyError, e:
      return
    self.__uncreated_monid.cancel()
    self.__uncreated_monid = None
    self.__monitor_signons(profpath)

  def __get_profile_path(self):
    if not os.path.isdir(self.__ffpath):
      os.makedirs(self.__ffpath)
    for p in iterdir(self.__ffpath):
      if not p.endswith('.default'): continue
      return p
    raise KeyError("Couldn't find mozilla profile")

  @dbus.service.method(BUS_IFACE_STR,
                       out_signature="a{saa{sv}}")
  def GetSignons(self):
    return self.__signons

  @dbus.service.signal(BUS_IFACE_STR,
                       signature='aa{sv}')
  def SignonChanged(self, signon):
    pass

  @dbus.service.method(BUS_IFACE_STR,
                       in_signature="s",
                       out_signature="aa{sv}")
  def GetSignon(self, signon):
    return self.__signons[signon]

TYPE_GOOGLE = "google"

ONLINE_ACCOUNT_TYPES = {TYPE_GOOGLE : ("Google", "Gmail or Google Apps for Your Domain email", "http://gmail.com")}

ACCOUNT_TYPES_FROM_SERVER = set([TYPE_GOOGLE])

class OnlineAccount(dbus.service.Object):
  def __init__(self, account_id, bus_name, account_type, username, password='', enabled=True, gconf_dir=None, resource_id=None):
    dbus.service.Object.__init__(self, object_path = '/onlineaccount/' + str(account_id), bus_name = bus_name)
    # TODO: check if dbus.service.Object really doesn't have bus_name as a field
    self.__account_id = account_id
    self.__bus_name = bus_name 
    self.__account_type = account_type
    self.__username = username
    self.__password = password
    self.__enabled = enabled
    self.__gconf_dir = gconf_dir
    self.__resource_id = resource_id # present only for accounts we got through the data model

    _logger.debug("Created account %s" % (str(self)))

  # copy and deepcopy might not make much sense for this class, since each instance of the class should have
  # a unique account_id
  def __copy__(self):
    return OnlineAccount(self.__account_id, self.__bus_name, self.__account_type, self.__username, self.__password, self.__enabled, self.__gconf_dir, self.__resource_id)

  def __deepcopy__(self, memo):
    return OnlineAccount(copy.deepcopy(self.__account_id, memo), copy.deepcopy(self.__bus_name, memo), \
                         copy.deepcopy(self.__account_type, memo), copy.deepcopy(self.__username, memo), \
                         copy.deepcopy(self.__password, memo), copy.deepcopy(self.__enabled, memo), \
                         copy.deepcopy(self.__gconf_dir, memo), copy.deepcopy(self.__resource_id, memo))

  @dbus.service.method(OA_BUS_IFACE_STR,
                       out_signature="s")
  def GetType(self):
    return self.__account_type

  @dbus.service.method(OA_BUS_IFACE_STR,
                       out_signature="s")
  def GetUsername(self):
    return self.__username

  @dbus.service.method(OA_BUS_IFACE_STR,
                       out_signature="s")
  def GetPassword(self):
    return self.__password

  @dbus.service.method(OA_BUS_IFACE_STR,
                       out_signature="b")
  def GetEnabled(self):
    return self.__enabled

  @dbus.service.method(OA_BUS_IFACE_STR,
                       out_signature="o")
  def GetObjectPath(self):
    return self._object_path

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='')
  def Changed(self):
    pass

  def _get_gconf_dir(self):
    return self.__gconf_dir

  def _get_resource_id(self):
    if self.__resource_id is not None and len(self.__resource_id) < 1:
      return None
    return self.__resource_id

  def _update_from_origin(self, new_props):
    _logger.debug("updating account from origin")
    changed = False
    for (key,value) in new_props.items():
      if key not in ["password", "enabled", "gconf_dir", "resource_id"]:
        _logger.error("key %s is unknown or can't be changed" % key)
        continue    

      if value is None:
        value = ''

      if key != "password":
        _logger.debug("key %s, value %s" % (key, value))
      else:
        _logger.debug("key %s, value length %d" % (key, len(value)))

      old = getattr(self, '_OnlineAccount__' + key)
      if old != value:
        setattr(self, '_OnlineAccount__' + key, value)
        changed = True

    # TODO: figure out if it makes sense to emit a changed signal if gconf_dir changed
    if changed:
      _logger.debug("emitting change!")
      self.Changed()

  def __str__(self):
    return "<Account username: %s, type: %s, gconf_dir: %s, enabled: %s, resource_id: %s, object path: %s>" % (self.GetUsername(), self.GetType(), self._get_gconf_dir(), self.GetEnabled(), self._get_resource_id(), self.GetObjectPath())

_dialog = None

class OnlineAccounts(dbus.service.Object):
  def __init__(self, bus_name):
    dbus.service.Object.__init__(self, bus_name, '/onlineaccounts')
    self.__bus_name = bus_name 
    self.__received_account_types_from_server = False
    self.__received_response_from_server = False

    #TODO: do we need to pass in the server name, and if so how do we know here what to pass in? 
    self.__model = DataModel()
    self.__model.add_ready_handler(self.__on_ready)

    self.__server_accounts = set()
    self.__gconf_accounts = set()
    self.__weblogin_accounts = set()
    self.__enabled_accounts = set()

    self.__loved_data_model_accounts = set()

    # This is a mapping from object paths to OnlineAccount objects for all accounts.
    # It would have been nice to use the same id as the gconf key id, but we create
    # accounts before we add them to gconf, and also we create accounts for the
    # signons returned by web login driver, which we don't add to gconf.
    self.__all_accounts = {}
    self.__account_id = None

    try:
      self.__weblogindriver_proxy = dbus.SessionBus().get_object('org.gnome.WebLoginDriver', '/weblogindriver')
      self.__weblogindriver_proxy.connect_to_signal("SignonChanged",
                                                    self.__on_signon_changed)
    except dbus.DBusException, e:
      _logger.debug("weblogindriver not available")
      self.__weblogindriver_proxy = None
      
    if self.__weblogindriver_proxy:
      self.__recheck_signons()

    self.__gconf = gconf.client_get_default()
    self.__gconf.add_dir(GCONF_BASE_DIR, gconf.CLIENT_PRELOAD_RECURSIVE)
    self.__gconf.notify_add(GCONF_BASE_DIR, self.__on_gconf_change)
    # merge_mode can be set to True in two cases: if self.__save_online_flag got explicitly
    # enabled by the user and if we initially didn't get the self resource for the user,
    # but then got it later
    self.__merge_mode = False
    self.__save_online_flag = None
    self.__reload_save_online_flag_from_gconf()   

    ## a dict from gconf directory name underneath GCONF_BASE_DIR to
    ## a dict of the gconf values underneath that account directory
    self.__gconf_info = {}

    if self.__model.ready:
        self.__on_ready()

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='o')
  def AccountAdded(self, account_object_path):
    pass

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='o')
  def AccountRemoved(self, account_object_path):
    pass

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='o')
  def AccountEnabled(self, account_object_path):
    pass

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='o')
  def AccountDisabled(self, account_object_path):
    pass

  def __create_online_account(self, *args, **kwargs):
    if self.__account_id is None:
        self.__account_id = 0 
    else:
        self.__account_id = self.__account_id + 1
    account = OnlineAccount(self.__account_id, self.__bus_name, *args, **kwargs)
    self.__all_accounts[account.GetObjectPath()] = account   
    return account  

  def __find_weblogin_account(self, account_type, username):
    for a in self.__weblogin_accounts:
      _logger.debug("comparing to a weblogin account %s bool %s a.GetType() %s account_type %s" % (a, a.GetType() == account_type, a.GetType(), account_type))
      if a.GetType() == account_type and a.GetUsername() == username:
        return a

    return None

  def __find_account_by_gconf_dir(self, gconf_dir):
    for a in self.__gconf_accounts:
      if a._get_gconf_dir() == gconf_dir:
        return a

    return None

  def __get_gconf_accounts_by_type(self, account_type):
    gconf_accounts_by_type = set()
    for a in self.__gconf_accounts:
      if a.GetType() == account_type:
        gconf_accounts_by_type.add(a)
    return gconf_accounts_by_type 

  def __get_server_accounts_by_type(self, account_type):
    server_accounts_by_type = set()
    for a in self.__server_accounts:
      if a.GetType() == account_type:
        server_accounts_by_type.add(a)
    return server_accounts_by_type 

  def __update_account(self, account):
    _logger.debug("Updating account %s" % (str(account)))

    ## note that "type" and "username" are never updated (not allowed to change without
    ## making a new Account object)

    was_enabled = account in self.__enabled_accounts

    fields = {}

    gconf_dir = account._get_gconf_dir()

    if account in self.__server_accounts:
      self.__ensure_account_in_gconf(account)
    elif self.__received_response_from_server and account.GetType() in ACCOUNT_TYPES_FROM_SERVER:
      #remove gconf dir if one exists
      if gconf_dir:
        self.__remove_gconf_dir(gconf_dir)  

    ## first, look for the info from our local gconf storage
    if gconf_dir and gconf_dir in self.__gconf_info:
      gconf_info = self.__gconf_info[gconf_dir]

      if 'enabled' in gconf_info:
        fields['enabled'] = gconf_info['enabled']

    elif gconf_dir and gconf_dir not in self.__gconf_info:
      ## Account object originating with a gconf item
      ## apparently now deleted, so disable. This
      ## does mean if you create a gconf item corresponding
      ## to something from weblogin driver, then delete the
      ## gconf item, the weblogin account doesn't come back
      ## until you restart the process. Clunky.
      ## deleting a gconf dir isn't what we'd normally do though,
      ## we'd normally set enabled=False in gconf, which would
      ## persist across restarts.
      self.__gconf_accounts.remove(account)
      if was_enabled:
          self.__enabled_accounts.remove(account)
          self.AccountDisabled(account.GetObjectPath())
      del self.__all_accounts[account.GetObjectPath()]
      keyring.get_keyring().remove_logins(account.GetType(), account.GetUsername())
      _logger.debug("signalling AccountRemoved")    
      self.AccountRemoved(account.GetObjectPath())
      return

    ## after compositing all this information, update our account object
    account._update_from_origin(fields)

    ## second, look at weblogin driver, though we don't want to prefer
    ## its password over keyring, so there's some trickiness
    weblogin_password = None
    _logger.debug("will look for a weblogin account with type %s username %s" % (account.GetType(), account.GetUsername()))
    weblogin_account = self.__find_weblogin_account(account.GetType(), account.GetUsername()) 
    if weblogin_account:
      _logger.debug("weblogin account found")
      weblogin_password = weblogin_account.GetPassword()

    ## use updated information to find password
    fields = {}
        
    ## third, look for password in keyring
    _logger.debug("here 1")    
    k = keyring.get_keyring()
    _logger.debug("here 2")                
    password = k.get_password(account_type=account.GetType(),
                              username=account.GetUsername())
    _logger.debug("here 3")    
    if password:
      _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 with the password
    account._update_from_origin(fields)

    ## now add or remove the account from the set of enabled accounts
    if was_enabled and not account.GetEnabled():
      self.__enabled_accounts.remove(account)
      self.AccountDisabled(account.GetObjectPath())
    elif not was_enabled and account.GetEnabled():
      self.__enabled_accounts.add(account)
      _logger.debug("will emit account enabled signal")
      self.AccountEnabled(account.GetObjectPath())     
           
  def __remove_gconf_dir(self, gconf_dir):
    _logger.debug("will remove gconf dir")
    base_key = GCONF_BASE_DIR + '/' + gconf_dir
    success = self.__gconf.recursive_unset(base_key, gconf.UNSET_INCLUDING_SCHEMA_NAMES)
    _logger.debug("removed gconf dir %s success %s" % (base_key, success))
    self.__gconf.suggest_sync() 
    if self.__gconf_info.has_key(gconf_dir):
      del self.__gconf_info[gconf_dir]

  def __try_ensure_and_update_account_for_gconf_dir(self, gconf_dir):         
    account = self.__find_account_by_gconf_dir(gconf_dir)
    _logger.debug("ensuring account for %s gconf key %s", account, gconf_dir);
    if account:
      self.__update_account(account)
      return
        
    if gconf_dir not in self.__gconf_info:
      _logger.error("trying to create Account for a gconf dir that doesn't exist")
      return

    gconf_info = self.__gconf_info[gconf_dir]
    if 'type' not in gconf_info or 'username' not in gconf_info:
      _logger.error("gconf account has no type or username setting")
      self.__remove_gconf_dir(gconf_dir)
      return
        
    account_type = gconf_info['type']
    username = gconf_info['username']
    if self.__received_account_types_from_server and account_type not in ONLINE_ACCOUNT_TYPES.keys():
      _logger.error("unknown account type in gconf")
      self.__remove_gconf_dir(gconf_dir)
      return

    # account = self.__find_weblogin_account_by_type(account_type)
    # if account:
    #    account._update_from_origin({"gconf_dir" : gconf_dir})
    # else:
    account = self.__create_online_account(account_type, username, gconf_dir=gconf_dir, enabled=False)

    self.__gconf_accounts.add(account)
    self.AccountAdded(account.GetObjectPath()) 
        
    self.__update_account(account)

  def __remove_dirname(self, gconf_key):
    i = gconf_key.rfind('/')
    return gconf_key[i+1:]

  def __download_online_account_types(self):
    url = urlparse.urljoin(ddm_util.get_baseurl(), "/xml/supportedOnlineAccountTypes")
    _logger.debug("Sending http request for %s" % url)
    try:
      data = urllib2.urlopen(url).read()
      doc = xml.dom.minidom.parseString(data) 
      resp = doc.documentElement
      reply_root = resp.childNodes[0]
      expected_name = 'onlineAccountTypes'
      if not reply_root.nodeName == expected_name:
        _logger.warn("invalid root node, expected %s", expected_name)
      else:
        for node in reply_root.childNodes:
          if not (node.nodeType == xml.dom.Node.ELEMENT_NODE):
            continue
          name = node.getAttribute("name")
          full_name = node.getAttribute("fullName")
          user_info_type = node.getAttribute("userInfoType")
          link = node.getAttribute("helpfulLink")
          _logger.debug("parsing online account type name=%s full_name=%s" % (name, full_name))
          ONLINE_ACCOUNT_TYPES[name]= (full_name, user_info_type, link)
          ACCOUNT_TYPES_FROM_SERVER.add(name)
        self.__received_account_types_from_server = True    
    except urllib2.HTTPError, e:
      _logger.error("Failed to load %s : %s" % (url, e))
    except urllib2.URLError, e2:
      _logger.error("Failed to load %s : %s" % (url, e2))

  def __on_ready(self):
    _logger.debug("__on_ready got called")
    self.__download_online_account_types()
    # we should only reload stuff from gconf after we possibly know the account types 
    # and we use the data model in self.__download_online_account_types() to determine 
    # the base site link
    self.__reload_from_gconf()
    self.__get_online_accounts()

  def __get_online_accounts(self):
    if self.__model.global_resource.online and self.__model.self_resource != None and self.__save_online_flag:
      _logger.debug("will get online desktop accounts")
      query = self.__model.query_resource(self.__model.self_resource, "googleEnabledEmails; lovedAccounts +")
      query.add_handler(self.__on_datamodel_response)
      query.add_error_handler(self.__on_datamodel_error)        
      query.execute()

      query2 = self.__model.query_resource(self.__model.global_resource, "online")
      query2.add_handler(self.__on_datamodel_global_resource_response)
      query2.add_error_handler(self.__on_datamodel_error)        
      query2.execute()
    elif not self.__model.global_resource.online or self.__model.self_resource == None:
      self.__received_response_from_server = False
      self.HaveOnlineUserFlagChanged(self.GetHaveOnlineUserFlag())    
      self.ConnectedToServerFlagChanged(self.GetConnectedToServerFlag()) 
      # we will want to use the merge mode once we do get the self resource
      self.__merge_mode = True
  
  def __on_datamodel_error(self, code, str):
    _logger.error("datamodel error %s: %s", code, str)        
        
  def __on_datamodel_response(self, myself):    
    _logger.debug("received data model response")
    if not self.__received_account_types_from_server:
      self.__download_online_account_types()
    self.__received_response_from_server = True
    self.HaveOnlineUserFlagChanged(self.GetHaveOnlineUserFlag())
    self.ConnectedToServerFlagChanged(self.GetConnectedToServerFlag()) 
    myself.connect(self.__update_google_enabled_emails, 'googleEnabledEmails') 
    myself.connect(self.__update_loved_accounts, 'lovedAccounts') 
    self.__update_google_enabled_emails(myself)
    self.__update_loved_accounts(myself)
    if self.__save_online_flag:
      prev_merge_mode = self.__merge_mode
      self.__merge_mode = False
      if prev_merge_mode:
        # Get online accounts again, since some could have caused an error when being saved and are not actually
        # on the server. Also, Google e-mails might need to be verified before they can show up.
        self.__update_google_enabled_emails(myself)
        self.__update_loved_accounts(myself)    

  def __on_datamodel_global_resource_response(self, global_resource):
    global_resource.connect(self.__update_global_resource_online_status, "online")

  def __update_global_resource_online_status(self, global_resource):
    # __on_ready gets called again anyway when the server becomes available again,
    # so we only need to handle the server becoming unavailable here
    if not global_resource.online:  
      self.__received_response_from_server = False
      self.HaveOnlineUserFlagChanged(self.GetHaveOnlineUserFlag())
      self.ConnectedToServerFlagChanged(self.GetConnectedToServerFlag()) 
      self.__merge_mode = True

  # def __on_loved_accounts(self, myself):    
    # _logger.debug("received some loved accounts")
    # myself.connect(self.__update_loved_accounts, 'lovedAccounts') 
    # self.__update_loved_accounts(myself)

  def __on_server_google_account_added(self, result):
    self.AccountUpdateFeedback("Information Note", "If some Google enabled account is missing, check your e-mail to verify it.", False)

  def __on_server_account_add_failed(self, error_type, error_message):
    _logger.error("Add failed %s %s" % (str(error_type), error_message))
    self.AccountUpdateFeedback("Could not save an account on the server", error_message, True)

  def __update_google_enabled_emails(self, myself):
    if not self.__save_online_flag:
      _logger.warn("got into __update_google_enabled_emails when self.__save_online_flag is False")
      return 

    new_google_accounts = set() 
    if not hasattr(myself, 'googleEnabledEmails'):
      _logger.debug("No googleEnabledEmails in DDM identity")
    elif len(myself.googleEnabledEmails) == 0:
      _logger.debug("DDM identity has 0 googleEnabledEmails")
    else:
      for email in myself.googleEnabledEmails:                        
        username = str(email)
        _logger.debug("got a googleEnabledEmail %s", email)        
        new_google_accounts.add(self.__create_online_account(TYPE_GOOGLE, username))
                               
    self.update_accounts_from_server(TYPE_GOOGLE, new_google_accounts)

    if self.__merge_mode:
      existing_accounts_in_gconf = self.__get_gconf_accounts_by_type(TYPE_GOOGLE) 
      for gconf_account in existing_accounts_in_gconf:
        if gconf_account not in self.__server_accounts:
          # add the account on the server
          query = self.__model.update(("http://mugshot.org/p/accounts", "addOnlineAccount"), accountType=gconf_account.GetType(), username=gconf_account.GetUsername())
          query.add_handler(self.__on_server_google_account_added)
          query.add_error_handler(self.__on_server_account_add_failed)        
          query.execute()

  def __update_loved_accounts(self, myself):
    if not self.__save_online_flag:
      _logger.warn("got into __update_google_enabled_emails when self.__save_online_flag is False")
      return

    new_accounts_by_type = {} 
    new_loved_data_model_accounts = set()
    if not hasattr(myself, 'lovedAccounts'):
      _logger.debug("No lovedAcccounts in DDM identity")
    elif len(myself.lovedAccounts) == 0:
      _logger.debug("DDM identity has 0 lovedAccounts")
    else:
      for account in myself.lovedAccounts:  
        accountType = str(account.accountType).lower()
        _logger.debug("accountType is %s" % accountType)
        if accountType in ONLINE_ACCOUNT_TYPES:
          new_loved_data_model_accounts.add(account)
          if account not in self.__loved_data_model_accounts:
            _logger.debug("This is a new loved account %s" % account.resource_id)
            # makes sure we connect only once
            account.connect(self.__update_loved_account, "username")
          if account.username:
            username = str(account.username)
            if accountType not in new_accounts_by_type:
              new_accounts_by_type[accountType] = set()
            new_account = self.__create_online_account(accountType, username, resource_id=str(account.resource_id)) 
            new_accounts_by_type[accountType].add(new_account)          
            _logger.debug("got username %s", username)        
          else:
            _logger.warn("account.username for the account type %s we expect data for is None" % accountType)
    
    for (key, value) in new_accounts_by_type.items():   
        self.update_accounts_from_server(key, value)

    # remove accounts for all other types that are no longer returned by the server
    for account_type in ACCOUNT_TYPES_FROM_SERVER:
      if account_type != TYPE_GOOGLE and account_type not in new_accounts_by_type.keys():
        self.update_accounts_from_server(account_type, set())   

    gconf_accounts = copy.copy(self.__gconf_accounts) 
    _logger.debug("will go through existing accounts")
    if self.__merge_mode:
      for gconf_account in gconf_accounts:
        if gconf_account.GetType() != TYPE_GOOGLE and gconf_account not in self.__server_accounts:
          # add the account on the server
          query = self.__model.update(("http://mugshot.org/p/accounts", "addOnlineAccount"), accountType=gconf_account.GetType(), username=gconf_account.GetUsername())
          query.add_error_handler(self.__on_server_account_add_failed)        
          query.execute()

    for account in self.__loved_data_model_accounts:
      if account not in new_loved_data_model_accounts:
        _logger.debug("Some old account is not in new accounts %s" % account.resource_id)
        account.disconnect(self.__update_loved_account)

    self.__loved_data_model_accounts = new_loved_data_model_accounts

  def __update_loved_account(self, myself):    
    # because we consider two accounts with different usernames as different accounts,
    # we would have to associate accounts in self.__server_accounts with data model accounts
    # to process this change individually, so at the moment it seems easier to just refresh 
    # all accounts 
    query = self.__model.query_resource(self.__model.self_resource, "lovedAccounts +")
    query.add_handler(self.__update_loved_accounts)
    query.add_error_handler(self.__on_datamodel_error)       
    query.execute()

  def update_accounts_from_server(self, account_type, new_accounts): 
    existing_accounts = self.__get_server_accounts_by_type(account_type) 
    
    for new_account in new_accounts:                        
      # if acccount already in the list of server_accounts, don't do anything
      # if it is new, add it, and update information for it
      # if it is no longer returned by the server, remove it and remove it from other
      # lists, including gconf
      account_found = False
      for existing_account in existing_accounts:
        if (existing_account._get_resource_id() is not None and existing_account._get_resource_id() == new_account._get_resource_id()) or (existing_account._get_resource_id() is None and existing_account.GetUsername() == new_account.GetUsername()):
          # we found it, make sure it has the resource_id set
          existing_account._update_from_origin({"resource_id" : new_account._get_resource_id()})
          account_found = True
          existing_accounts.remove(existing_account)
          break
             
      if not account_found:             
        new_account_to_add = self.__find_account_in_gconf(new_account.GetType(), new_account.GetUsername())

        if new_account_to_add is None or (new_account_to_add._get_resource_id() is not None and new_account_to_add._get_resource_id() != new_account._get_resource_id()):
          new_account_to_add = new_account # we used to use copy.deepcopy here, not sure we need it
        else:
          new_account_to_add._update_from_origin({"resource_id" : new_account._get_resource_id()})
 
        self.__server_accounts.add(new_account_to_add)
        # this will add the account to gconf and enabled accounts, and check if 
        # we have a password for it
        self.__update_account(new_account_to_add)

    _logger.debug("merge mode is %s" % self.__merge_mode) 
    if not self.__merge_mode:
      # clear out accounts that are no longer found in the list of accounts of this type from the server
      for existing_account in existing_accounts:
        self.__server_accounts.remove(existing_account)
        # this should remove the account from gconf and gconf_accounts
        self.__update_account(existing_account)

      # make sure that all accounts in gconf correspond to the ones returned from the server;
      # this will remove old accounts from gconf 
      existing_accounts_in_gconf = self.__get_gconf_accounts_by_type(account_type) 
      _logger.debug("existing_accounts_in_gconf of type %s: %d" % (account_type, len(existing_accounts_in_gconf)))
      for gconf_account in existing_accounts_in_gconf:
        self.__update_account(gconf_account) 

  def __get_gconf_info(self, gconf_dir):
    base_key = GCONF_BASE_DIR + "/" + gconf_dir
    gconf_info = {}
    def get_account_prop(gconf, gconf_info, base_key, prop):
      try:
        value = gconf.get_value(base_key + '/' + prop)
      except ValueError:
        value = None
      if value:
        # _logger.debug("key %s, value %s" % (prop, value))  
        gconf_info[prop] = value
    get_account_prop(self.__gconf, gconf_info, base_key, 'type')
    get_account_prop(self.__gconf, gconf_info, base_key, 'username')
    get_account_prop(self.__gconf, gconf_info, base_key, 'enabled')
    # GConf returns None for get_value if the vale is False  
    if 'enabled' not in gconf_info:
      gconf_info['enabled'] = False   
    return gconf_info
            
  def __reload_save_online_flag_from_gconf(self):
    try:
      new_save_online_flag = self.__gconf.get_value(GCONF_BASE_DIR + "/save_online")
      if self.__save_online_flag is None:
        self.__save_online_flag = new_save_online_flag
        self.SaveOnlineFlagChanged(self.__save_online_flag)
        self.ConnectedToServerFlagChanged(self.GetConnectedToServerFlag()) 
        return
 
      if new_save_online_flag != self.__save_online_flag:
        self.__save_online_flag = new_save_online_flag
        self.SaveOnlineFlagChanged(self.__save_online_flag)
        self.ConnectedToServerFlagChanged(self.GetConnectedToServerFlag()) 
        if self.__save_online_flag:
          _logger.debug("will enter the merge mode")
          self.__merge_mode = True 
          self.__get_online_accounts()
          #do merge if have self resource
        else:
          self.__server_accounts.clear() 
          self.__received_response_from_server = False
          self.HaveOnlineUserFlagChanged(self.GetHaveOnlineUserFlag())
          if self.__model.self_resource != None:
            self.__model.self_resource.disconnect(self.__update_google_enabled_emails) 
            self.__model.self_resource.disconnect(self.__update_loved_accounts)
            self.__model.global_resource.disconnect(self.__update_global_resource_online_status) 
            for account in self.__loved_data_model_accounts:
              account.disconnect(self.__update_loved_account) 
            self.__loved_data_model_accounts.clear()
    except ValueError:
      self.__save_online_flag = False
      self.__set_save_online_flag_in_gconf(False)

  def __set_save_online_flag_in_gconf(self, save_online_flag):
    self.__gconf.set_bool(GCONF_BASE_DIR + "/save_online", save_online_flag)

  def __reload_from_gconf(self):
    gconf_dirs = self.__gconf.all_dirs(GCONF_BASE_DIR)

    _logger.debug("Reloading %s from gconf" % (str(gconf_dirs)))

    new_gconf_infos = {}
    for gconf_dir in gconf_dirs:
      base_key = gconf_dir
      gconf_dir = self.__remove_dirname(gconf_dir)            
      new_gconf_infos[gconf_dir] = self.__get_gconf_info(gconf_dir)

      # GConf might already have some settings that did not have a schema.
      # This portion is only for bootstrapping these settings to have a schema.
      # We check if one of the entries that must be there (username) has a schema,
      # and associate a schema with the whole directory if it doesn't.
      username_entry = self.__gconf.get_entry(base_key + '/username', '', True)
      if username_entry and not username_entry.get_schema_name():
        associate_schema(_accounts_schema_dir, base_key)              
      elif not username_entry:
        _logger.warn("We expected username entry to be in %s, but it was not found" % base_key)
 
    self.__gconf_info = new_gconf_infos

    ## create any new accounts
    gconf_info_keys = copy.copy(self.__gconf_info.keys())  
    for gconf_dir in gconf_info_keys:
      self.__try_ensure_and_update_account_for_gconf_dir(gconf_dir)

    ## now update any old accounts that are no longer in gconf
    gconf_accounts = copy.copy(self.__gconf_accounts) 
    for a in gconf_accounts:
      gconf_dir = a._get_gconf_dir()
      _logger.debug("processing gconf account for %s" % gconf_dir)
      if gconf_dir and gconf_dir not in self.__gconf_info:
        _logger.debug("needs update") 
        self.__update_account(a)
        
  @defer_idle_func(timeout=400)        
  def __on_gconf_change(self, *args):
    _logger.debug("gconf change notify for accounts")
    self.__reload_save_online_flag_from_gconf()
    self.__reload_from_gconf()

  def __check_signons(self, signons):
    # this processes signons for a specific hostname
    # TODO: extend this to store signons for different types of accounts once we are ready to support them;
    #       make sure we store signon information for GAFYD accounts
    # actually, it looks like GAFYD accounts are indistinguishable in Firefox from regular Google accounts
    for signon in signons: 
      if 'hint' not in signon: continue
      if signon['hint'] == 'GMail' or signon['hint'] == 'GAFYD-Mail':         
        username = str(signon['username'])
        if signon['hint'] == 'GMail':
            username = str(signon['username']) + "@gmail.com" 
        password = base64.b64decode(signon['password'])
        _logger.debug("got signon information %s" % str(signon))                
        account = self.__find_weblogin_account(TYPE_GOOGLE, username)
        _logger.debug("found weblogin account %s" % str(account))
        if not account:
          account = self.__create_online_account(TYPE_GOOGLE, username, enabled=False, password=password)
          self.__weblogin_accounts.add(account)
          self.__update_account(account)
        elif password != account.GetPassword():
          account._set_password(password)
          self.__update_account(account)
            
  def __recheck_signons(self):
    self.__weblogindriver_proxy.GetSignons(reply_handler=self.__on_get_signons_reply,
                                           error_handler=self.__on_dbus_error)

  def __on_get_signons_reply(self, signondata):
    _logger.debug("got signons reply")
    for hostname,signons in signondata.iteritems():
      self.__check_signons(signons)

  def __on_signon_changed(self, signons):
    # TODO: __on_signon_changed is called with all signons for a given hostname.
    # However, we need the hostname to be passed in as an argument, since it is needed
    # for the case when no more signons remain for a given hostname.
    # We should make this change, but for now, we just never remove existing weblogin accounts.
    # (The list will be re-created when the bigboard is restarted.) We will update a password
    # for an existing weblogin account if the updated signon information is passed in here.
    # Once we make this change, we'll need to make sure to associate the right hostnames with
    # weblogin accounts, in case they don't match what we store in the url.
    _logger.debug("signons changed: %s", signons)
    self.__check_signons(signons)

  def __on_dbus_error(self, err):
    self.__logger.error("D-BUS error: %s", err)    

  def __find_unused_gconf_dir(self, account_type):
    ## find an unused gconf dir
    i = 0
    while True:
      gconf_dir = account_type + "_" + str(i)
      if not self.__find_account_by_gconf_dir(gconf_dir):
        _logger.debug("returning %s for unused gconf_dir" % gconf_dir)
        return gconf_dir
      else:
        i = i + 1

  def __find_account_in_gconf(self, account_type, username):
    for a in self.__gconf_accounts:
      if a.GetType() == account_type and a.GetUsername() == username:  
        return a
    return None 

  # new_properties is optional, it should be passed in if there are changes
  # to an existing account and we will update other data constructs that contain
  # this account based on the information from gconf
  #
  # an account object passed in to this method must have the gconf_dir set or must not be found in gconf
  def __ensure_account_in_gconf(self, account, new_properties={}):       
    gconf_dir = account._get_gconf_dir()
    new_gconf_dir = False
                     
    # create a new GConf dir if it is a completely new account    
    if not gconf_dir:
      if self.__find_account_in_gconf(account.GetType(), account.GetUsername()):
        _logger.error("found an account in gconf that matches the passed in account object %s, but the passed in account object is missing a gconf_dir" % str(account))
        
        return
 
      gconf_dir = self.__find_unused_gconf_dir(account.GetType())
      new_gconf_dir = True
      account._update_from_origin({"gconf_dir" : gconf_dir})
      self.__gconf_accounts.add(account)
    elif gconf_dir not in self.__gconf_info: 
      new_gconf_dir = True   
            
    base_key = GCONF_BASE_DIR + '/' + gconf_dir

    _logger.debug("base_key is %s" % base_key)
        
    def set_account_prop(base_key, prop, value):
      _logger.debug("prop %s value %s" % (prop, str(value)))
      if isinstance(value, bool):
        self.__gconf.set_bool(base_key + '/' + prop, value)
      elif isinstance(value, str):
        self.__gconf.set_string(base_key + '/' + prop, value)
      else:
        _logger.error("prop %s with value %s has an unexpected type %s" % (prop, str(value), type(value)))

    if new_gconf_dir:
      set_account_prop(base_key, 'type', account.GetType())
      set_account_prop(base_key, 'username', account.GetUsername())
  
    ## enable it last, so we ignore the other settings until we do this
    if 'enabled' in new_properties:
      _logger.debug("setting new enabled property to be %s" % new_properties['enabled'])
      set_account_prop(base_key, 'enabled', new_properties['enabled'])
    elif new_gconf_dir:
      set_account_prop(base_key, 'enabled', account.GetEnabled())

    if new_gconf_dir:
      associate_schema(_accounts_schema_dir, base_key)
      self.__gconf_info[gconf_dir]=self.__get_gconf_info(gconf_dir)
      self.AccountAdded(account.GetObjectPath()) 

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       in_signature="b")
  def SetSaveOnlineFlag(self, save_online_flag): 
    # if save_online_flag became true: do merge, start using server accounts
    # if save_oline_flag became false: stop using server updates/accounts and stop sending adds/removals to the server 
    if save_online_flag == self.__save_online_flag:
      return

    # everything else will happen once we update the flag in gconf
    self.__set_save_online_flag_in_gconf(save_online_flag)

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       out_signature="b")
  def GetSaveOnlineFlag(self): 
    return self.__save_online_flag

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='b')
  def SaveOnlineFlagChanged(self, new_save_online_flag):
    pass

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       out_signature="b")
  def GetHaveOnlineUserFlag(self): 
    return self.__model is not None and self.__model.self_resource is not None and self.__save_online_flag 

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='b')
  def HaveOnlineUserFlagChanged(self, new_have_online_user_flag):
    pass

  def GetConnectedToServerFlag(self): 
    return self.__model is not None and self.__model.global_resource is not None and self.__model.global_resource.online and self.__save_online_flag

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='b')
  def ConnectedToServerFlagChanged(self, new_connected_to_server_flag):
    pass

  @dbus.service.signal(OA_BUS_IFACE_STR,
                       signature='ssb')
  def AccountUpdateFeedback(self, feedback_header, feedback_message, is_error_flag):
    pass

  # new_properties is a dictionary, valid properties are "password" and "enabled"
  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       in_signature="oa{ss}")
  def SaveAccountChanges(self, account_object_path, new_properties):

    properties = {}
    for (key, value) in new_properties.items():
      if key == 'enabled':   
          properties[str(key)] = (value == 'True')
      else:
          properties[str(key)] = str(value)

    account = None
    if self.__all_accounts.has_key(account_object_path):
      account = self.__all_accounts[account_object_path]
    else:
      _logger.error("SaveAccountChanges was called with an unknown object path %s" % account_object_path)
      return
 
    _logger.debug("Saving new props for account %s: %s" % (str(account), str(properties.keys())))

    ## special-case handling of password since it goes in the keyring
    if 'password' in properties:
      username = account.GetUsername()       
      _logger.debug("here 4")    
      k = keyring.get_keyring()
      _logger.debug("here 5")    
      k.store_login(account_type=account.GetType(),
                    username=username,
                    password=properties['password'])
      _logger.debug("here 6")
    
    ## now do everything else by stuffing it in gconf
    self.__ensure_account_in_gconf(account, properties)
      
    ## keyring doesn't have change notification so we have to do the work for it
    ## if the password was the only thing that got updated, otherwise we should wait 
    ## till gconf is reloaded
    if 'password' in properties and (len(properties) == 1 or ('enabled' in properties and properties['enabled'] == account.GetEnabled())):
      ## this should notice a new password
      self.__update_account(account)
 
  # returns a tuple with an account object path as the first element, and a boolean indicating if a new account was created as a second element, and a feedback message as a third element
  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       in_signature="ss",
                       out_signature="(obs)",
                       async_callbacks=('return_cb', 'error_cb'))
  def GetOrCreateAccount(self, account_type, username, return_cb, error_cb):        
    def on_server_account_added(result):
      # TODO: might want to double check existing server accounts for an account with this type and username if it is possible it was added
      # faster with an update to lovedAccounts
      # Also, we are currently not preventing accounts with the same type and username from existing on the server, but we are not expecting accounts
      # with the same type and username to be in account sets here, so we need to sort that out
      
      if account_type == TYPE_GOOGLE:
        # we should not create a Google account right away because it needs to be verified
        return_cb((None, True, "Please check your e-mail to verify the account."))
      else:
        account = self.__find_account_in_gconf(account_type, username)
        _logger.debug("Found account %s " % account) 
        if not account:
            account = self.__create_online_account(account_type, username)
        self.__server_accounts.add(account)
        self.__update_account(account) # this will ensure that the account is in gconf, try to find a password for the account, and emit AccountEnabled signal 
        return_cb((account.GetObjectPath(), True, "")) 

    def on_server_account_add_failed(error_type, error_message):
      # we use return_cb instead of error_cb because the error_message we return 
      # would normally be a message with feedback
      _logger.error("Add failed %s %s" % (str(error_type), error_message))
      return_cb((None, False, error_message))

    account_type = str(account_type)
    username = str(username) 

    if account_type == TYPE_GOOGLE and username.find("@") < 0:
        username = username + "@gmail.com" 

    account = self.__find_account_in_gconf(account_type, username)
    if account:         
      return_cb((account.GetObjectPath(), False, None))
    
    if self.__save_online_flag and self.__received_response_from_server and account_type in ACCOUNT_TYPES_FROM_SERVER:      
      query = self.__model.update(("http://mugshot.org/p/accounts", "addOnlineAccount"), accountType=account_type, username=username)
      query.add_handler(on_server_account_added)
      query.add_error_handler(on_server_account_add_failed)        
      query.execute()
    else:
      account = self.__create_online_account(account_type, username)
      self.__ensure_account_in_gconf(account)   
      self.__update_account(account) # this will try to find a password for the account, and emit AccountEnabled signal 
      return_cb((account.GetObjectPath(), True, "")) 

  def get_existing_account(self, account_object_path):
    if self.__all_accounts.has_key(account_object_path):
      return self.__all_accounts[account_object_path]
    else:
      _logger.error("get_existing_account was called with an unknown object path %s" % account_object_path)
      return None 

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       in_signature="o",
                       async_callbacks=('return_cb', 'error_cb'))
  def RemoveAccount(self, account_object_path, return_cb, error_cb):
    def on_server_account_removed(result):
      return_cb()

    def on_server_account_remove_failed(error_type, error_message):
      _logger.error("Remove failed %s %s" % (str(error_type), error_message))
      error_cb(error_message)

    account = None
    if self.__all_accounts.has_key(account_object_path):
      account = self.__all_accounts[account_object_path]
    else:
      error_message = "RemoveAccount was called with an unknown object path %s" % (account_object_path,)
      _logger.error(error_message)
      error_cb(error_message)

    _logger.debug("will remove account with gconf_dir %s" % account._get_gconf_dir())      
    account_type = account.GetType()
    resource_id = account._get_resource_id()
    if account_type == TYPE_GOOGLE:
      resource_id = account.GetUsername()

    if resource_id is not None and self.__save_online_flag and self.__received_response_from_server and account_type in ACCOUNT_TYPES_FROM_SERVER:    
      query = self.__model.update(("http://mugshot.org/p/accounts", "removeOnlineAccount"), accountType=account_type, resourceId=resource_id)
      query.add_handler(on_server_account_removed)
      query.add_error_handler(on_server_account_remove_failed)        
      query.execute()
    else:
      if account._get_gconf_dir():
        self.__remove_gconf_dir(account._get_gconf_dir())
      if account in self.__server_accounts:
        _logger.warn("Account %s was in self.__server_accounts, but did not have a resource_id or we were in the mode when we should not be kepping track of server accounts" % str(account)) 
        self.__server_accounts.remove(account)
      self.__update_account(account) # this will remove the account from self.__all_accounts 
      return_cb()

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       out_signature="ao")
  def GetEnabledAccounts(self):
    object_paths_list = [a.GetObjectPath() for a in self.__enabled_accounts]
    return object_paths_list
         
  @dbus.service.method(OA_BUS_IFACE_STR,
                         in_signature="as",                      
                       out_signature="ao")
  def GetEnabledAccountsWithTypes(self, account_types):
    object_paths_list = [a.GetObjectPath() for a in \
                         filter(lambda account: account.GetType() in account_types, self.__enabled_accounts)]
    return object_paths_list

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       out_signature="ao")
  def GetAllAccounts(self):
    # we use self.__gconf_accounts here, because self.__all_accounts also has random accounts 
    # from the WebLoginDriver
    object_paths_list = [a.GetObjectPath() for a in self.__gconf_accounts]
    return object_paths_list

  @dbus.service.method(OA_BUS_IFACE_STR,
                       in_signature="as",                      
                       out_signature="ao")
  def GetAllAccountsWithTypes(self, account_types):
    object_paths_list = [a.GetObjectPath() for a in \
                         filter(lambda account: account.GetType() in account_types, self.__gconf_accounts)]
    return object_paths_list

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       out_signature="a{s(sss)}")
  def GetAllAccountTypes(self):
    _logger.debug("in GetAllAccountTypes")
    return ONLINE_ACCOUNT_TYPES

  @dbus.service.method(OA_BUS_IFACE_STR,                      
                       in_signature="ssss")
  def EnsureAccountType(self, type_name, type_full_name, type_user_info_type, type_link):  
    # we don't overwrite the information we already have (that was possibly received from
    # the server or added by another application), but we add an account type if it doesn't
    # exist
    if type_name not in ONLINE_ACCOUNT_TYPES:
      for (name, (full_name, user_info_type, link)) in ONLINE_ACCOUNT_TYPES.items():
        if type_full_name == full_name:
          raise dbus.DBusException("Account type with this full name already exists and is associated with a short name " +  name) 
      ONLINE_ACCOUNT_TYPES[type_name]= (type_full_name, type_user_info_type, type_link)
      
  @dbus.service.method(OA_BUS_IFACE_STR,
                       in_signature="u")
  def OpenAccountsDialog(self, event_time):
    if not self.__received_account_types_from_server:
      self.__download_online_account_types()
    global _dialog
    if _dialog:
      _dialog.destroy()
    _dialog = accounts_dialog.Dialog(None, self)    
    _dialog.present_with_time(event_time) 

  @dbus.service.method(OA_BUS_IFACE_STR,
                       in_signature="asu")
  def OpenAccountsDialogWithTypes(self, account_types, event_time):
    if not self.__received_account_types_from_server:
      self.__download_online_account_types()
    global _dialog
    if _dialog:
      _dialog.destroy()
    _dialog = accounts_dialog.Dialog(account_types, self)    
    _dialog.present_with_time(event_time) 

_driver = None
_online_accounts = None
def modmain():
  bus = dbus.SessionBus() 
  bus_name = dbus.service.BusName(BUS_NAME_STR, bus=bus)
  global _driver
  _driver = WebLoginDriver(bus_name)
  bus_name = dbus.service.BusName(OA_BUS_NAME_STR, bus=bus)  
  global _online_accounts # so that it doesn't get garbage collected
  _online_accounts = OnlineAccounts(bus_name)

def main(): 
  gobject.set_application_name("WebLoginDriver")
  logging.basicConfig(level=logging.DEBUG)
  gobject.threads_init()
  dbus.glib.threads_init()
  modmain()
  m = gobject.MainLoop()
  m.run()

if __name__ == '__main__':
  main()
