nsresult nsMsgAccountManager::LoadAccounts()

in mailnews/base/src/nsMsgAccountManager.cpp [1019:1374]


nsresult nsMsgAccountManager::LoadAccounts() {
  nsresult rv;

  // for now safeguard multiple calls to this function
  if (m_accountsLoaded) return NS_OK;

  // If we have code trying to do things after we've unloaded accounts,
  // ignore it.
  if (m_shutdownInProgress || m_haveShutdown) return NS_ERROR_FAILURE;

  nsCOMPtr<nsIMsgMailSession> mailSession =
      do_GetService("@mozilla.org/messenger/services/session;1", &rv);

  if (NS_SUCCEEDED(rv))
    mailSession->AddFolderListener(
        this, nsIFolderListener::added | nsIFolderListener::removed |
                  nsIFolderListener::intPropertyChanged);

  // Ensure biff service has started
  nsCOMPtr<nsIMsgBiffManager> biffService =
      do_GetService("@mozilla.org/messenger/biffManager;1", &rv);

  if (NS_SUCCEEDED(rv)) biffService->Init();

  // Ensure purge service has started
  nsCOMPtr<nsIMsgPurgeService> purgeService =
      do_GetService("@mozilla.org/messenger/purgeService;1", &rv);

  if (NS_SUCCEEDED(rv)) purgeService->Init();

  nsCOMPtr<nsIPrefService> prefservice(
      do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  // mail.accountmanager.accounts is the main entry point for all accounts
  nsCString accountList;
  rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, accountList);

  /**
   * Check to see if we need to add pre-configured accounts.
   * Following prefs are important to note in understanding the procedure here.
   *
   * 1. pref("mailnews.append_preconfig_accounts.version", version number);
   * This pref registers the current version in the user prefs file. A default
   * value is stored in mailnews.js file. If a given vendor needs to add more
   * preconfigured accounts, the default version number can be increased.
   * Comparing version number from user's prefs file and the default one from
   * mailnews.js, we can add new accounts and any other version level changes
   * that need to be done.
   *
   * 2. pref("mail.accountmanager.appendaccounts", <comma sep. account list>);
   * This pref contains the list of pre-configured accounts that ISP/Vendor
   * wants to add to the existing accounts list.
   */
  nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
  rv = prefservice->GetDefaultBranch(MAILNEWS_ROOT_PREF,
                                     getter_AddRefs(defaultsPrefBranch));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIPrefBranch> prefBranch;
  rv = prefservice->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch));
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t appendAccountsCurrentVersion = 0;
  int32_t appendAccountsDefaultVersion = 0;
  rv = prefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME,
                              &appendAccountsCurrentVersion);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = defaultsPrefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME,
                                      &appendAccountsDefaultVersion);
  NS_ENSURE_SUCCESS(rv, rv);

  // Update the account list if needed
  if ((appendAccountsCurrentVersion <= appendAccountsDefaultVersion)) {
    // Get a list of pre-configured accounts
    nsCString appendAccountList;
    rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS,
                              appendAccountList);
    appendAccountList.StripWhitespace();

    // If there are pre-configured accounts, we need to add them to the
    // existing list.
    if (!appendAccountList.IsEmpty()) {
      if (!accountList.IsEmpty()) {
        // Tokenize the data and add each account
        // in the user's current mailnews account list
        nsTArray<nsCString> accountsArray;
        ParseString(accountList, ACCOUNT_DELIMITER, accountsArray);
        uint32_t i = accountsArray.Length();

        // Append each account in the pre-configured account list
        ParseString(appendAccountList, ACCOUNT_DELIMITER, accountsArray);

        // Now add each account that does not already appear in the list
        for (; i < accountsArray.Length(); i++) {
          if (accountsArray.IndexOf(accountsArray[i]) == i) {
            accountList.Append(ACCOUNT_DELIMITER);
            accountList.Append(accountsArray[i]);
          }
        }
      } else {
        accountList = appendAccountList;
      }
      // Increase the version number so that updates will happen as and when
      // needed
      rv = prefBranch->SetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME,
                                  appendAccountsCurrentVersion + 1);
    }
  }

  // It is ok to return null accounts like when we create new profile.
  m_accountsLoaded = true;
  m_haveShutdown = false;

  if (accountList.IsEmpty()) return NS_OK;

  /* parse accountList and run loadAccount on each string, comma-separated */
  nsCOMPtr<nsIMsgAccount> account;
  // Tokenize the data and add each account
  // in the user's current mailnews account list
  nsTArray<nsCString> accountsArray;
  ParseString(accountList, ACCOUNT_DELIMITER, accountsArray);

  // These are the duplicate accounts we found. We keep track of these
  // because if any other server defers to one of these accounts, we need
  // to defer to the correct account.
  nsCOMArray<nsIMsgAccount> dupAccounts;

  // Now add each account that does not already appear in the list
  for (uint32_t i = 0; i < accountsArray.Length(); i++) {
    // if we've already seen this exact account, advance to the next account.
    // After the loop, we'll notice that we don't have as many actual accounts
    // as there were accounts in the pref, and rewrite the pref.
    if (accountsArray.IndexOf(accountsArray[i]) != i) continue;

    // get the "server" pref to see if we already have an account with this
    // server. If we do, we ignore this account.
    nsAutoCString serverKeyPref("mail.account.");
    serverKeyPref += accountsArray[i];

    nsCOMPtr<nsIPrefBranch> accountPrefBranch;
    rv = prefservice->GetBranch(serverKeyPref.get(),
                                getter_AddRefs(accountPrefBranch));
    NS_ENSURE_SUCCESS(rv, rv);

    serverKeyPref += ".server";
    nsCString serverKey;
    rv = m_prefs->GetCharPref(serverKeyPref.get(), serverKey);
    if (NS_FAILED(rv)) continue;

    nsCOMPtr<nsIMsgAccount> serverAccount;
    findAccountByServerKey(serverKey, getter_AddRefs(serverAccount));
    // If we have an existing account with the same server, ignore this account
    if (serverAccount) continue;

    if (NS_FAILED(createKeyedAccount(accountsArray[i], true,
                                     getter_AddRefs(account))) ||
        !account) {
      NS_WARNING("unexpected entry in account list; prefs corrupt?");
      continue;
    }

    // See nsIMsgAccount.idl for a description of the secondsToLeaveUnavailable
    //  and timeFoundUnavailable preferences
    nsAutoCString toLeavePref(PREF_MAIL_SERVER_PREFIX);
    toLeavePref.Append(serverKey);
    nsAutoCString unavailablePref(
        toLeavePref);  // this is the server-specific prefix
    unavailablePref.AppendLiteral(".timeFoundUnavailable");
    toLeavePref.AppendLiteral(".secondsToLeaveUnavailable");
    int32_t secondsToLeave = 0;
    int32_t timeUnavailable = 0;

    m_prefs->GetIntPref(toLeavePref.get(), &secondsToLeave);

    // force load of accounts (need to find a better way to do this)
    nsTArray<RefPtr<nsIMsgIdentity>> unused;
    account->GetIdentities(unused);

    rv = account->CreateServer();
    bool deleteAccount = NS_FAILED(rv);

    if (secondsToLeave) {    // we need to process timeUnavailable
      if (NS_SUCCEEDED(rv))  // clear the time if server is available
      {
        m_prefs->ClearUserPref(unavailablePref.get());
      }
      // NS_ERROR_NOT_AVAILABLE signifies a server that could not be
      // instantiated, presumably because of an invalid type.
      else if (rv == NS_ERROR_NOT_AVAILABLE) {
        m_prefs->GetIntPref(unavailablePref.get(), &timeUnavailable);
        if (!timeUnavailable) {  // we need to set it, this must be the first
                                 // time unavailable
          uint32_t nowSeconds;
          PRTime2Seconds(PR_Now(), &nowSeconds);
          m_prefs->SetIntPref(unavailablePref.get(), nowSeconds);
          deleteAccount = false;
        }
      }
    }

    // Our server is still unavailable. Have we timed out yet?
    if (rv == NS_ERROR_NOT_AVAILABLE && timeUnavailable != 0) {
      uint32_t nowSeconds;
      PRTime2Seconds(PR_Now(), &nowSeconds);
      if ((int32_t)nowSeconds < timeUnavailable + secondsToLeave)
        deleteAccount = false;
    }

    if (deleteAccount) {
      dupAccounts.AppendObject(account);
      m_accounts.RemoveElement(account);
    }
  }

  // Check if we removed one or more of the accounts in the pref string.
  // If so, rewrite the pref string.
  if (accountsArray.Length() != m_accounts.Length()) OutputAccountsPref();

  int32_t cnt = dupAccounts.Count();
  nsCOMPtr<nsIMsgAccount> dupAccount;

  // Go through the accounts seeing if any existing server is deferred to
  // an account we removed. If so, fix the deferral. Then clean up the prefs
  // for the removed account.
  for (int32_t i = 0; i < cnt; i++) {
    dupAccount = dupAccounts[i];
    for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
      /*
       * This loop gets run for every incoming server, and is passed a
       * duplicate account. It checks that the server is not deferred to the
       * duplicate account. If it is, then it looks up the information for the
       * duplicate account's server (username, hostName, type), and finds an
       * account with a server with the same username, hostname, and type, and
       * if it finds one, defers to that account instead. Generally, this will
       * be a Local Folders account, since 2.0 has a bug where duplicate Local
       * Folders accounts are created.
       */
      nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
      nsCString type;
      server->GetType(type);
      if (type.EqualsLiteral("pop3")) {
        nsCString deferredToAccount;
        // Get the pref directly, because the GetDeferredToAccount accessor
        // attempts to fix broken deferrals, but we know more about what the
        // deferred to account was.
        server->GetStringValue("deferred_to_account", deferredToAccount);
        if (!deferredToAccount.IsEmpty()) {
          nsCString dupAccountKey;
          dupAccount->GetKey(dupAccountKey);
          if (deferredToAccount.Equals(dupAccountKey)) {
            nsresult rv;
            nsCString accountPref("mail.account.");
            nsCString dupAccountServerKey;
            accountPref.Append(dupAccountKey);
            accountPref.AppendLiteral(".server");
            nsCOMPtr<nsIPrefService> prefservice(
                do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
            if (NS_FAILED(rv)) {
              continue;
            }
            nsCOMPtr<nsIPrefBranch> prefBranch(
                do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
            if (NS_FAILED(rv)) {
              continue;
            }
            rv =
                prefBranch->GetCharPref(accountPref.get(), dupAccountServerKey);
            if (NS_FAILED(rv)) {
              continue;
            }
            nsCOMPtr<nsIPrefBranch> serverPrefBranch;
            nsCString serverKeyPref(PREF_MAIL_SERVER_PREFIX);
            serverKeyPref.Append(dupAccountServerKey);
            serverKeyPref.Append('.');
            rv = prefservice->GetBranch(serverKeyPref.get(),
                                        getter_AddRefs(serverPrefBranch));
            if (NS_FAILED(rv)) {
              continue;
            }
            nsCString userName;
            nsCString hostName;
            nsCString type;
            serverPrefBranch->GetCharPref("userName", userName);
            serverPrefBranch->GetCharPref("hostname", hostName);
            serverPrefBranch->GetCharPref("type", type);
            // Find a server with the same info.
            nsCOMPtr<nsIMsgAccountManager> accountManager =
                do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
            if (NS_FAILED(rv)) {
              continue;
            }
            nsCOMPtr<nsIMsgIncomingServer> server;
            accountManager->FindServer(userName, hostName, type, 0,
                                       getter_AddRefs(server));
            if (server) {
              nsCOMPtr<nsIMsgAccount> replacement;
              accountManager->FindAccountForServer(server,
                                                   getter_AddRefs(replacement));
              if (replacement) {
                nsCString accountKey;
                replacement->GetKey(accountKey);
                if (!accountKey.IsEmpty())
                  server->SetStringValue("deferred_to_account", accountKey);
              }
            }
          }
        }
      }
    }

    nsAutoCString accountKeyPref("mail.account.");
    nsCString dupAccountKey;
    dupAccount->GetKey(dupAccountKey);
    if (dupAccountKey.IsEmpty()) continue;
    accountKeyPref.Append(dupAccountKey);
    accountKeyPref.Append('.');

    nsCOMPtr<nsIPrefBranch> accountPrefBranch;
    rv = prefservice->GetBranch(accountKeyPref.get(),
                                getter_AddRefs(accountPrefBranch));
    if (accountPrefBranch) {
      nsTArray<nsCString> prefNames;
      nsresult rv = accountPrefBranch->GetChildList("", prefNames);
      NS_ENSURE_SUCCESS(rv, rv);

      for (auto& prefName : prefNames) {
        accountPrefBranch->ClearUserPref(prefName.get());
      }
    }
  }

  // Make sure we have an account that points at the local folders server
  nsCString localFoldersServerKey;
  rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER,
                            localFoldersServerKey);

  if (!localFoldersServerKey.IsEmpty()) {
    nsCOMPtr<nsIMsgIncomingServer> server;
    rv = GetIncomingServer(localFoldersServerKey, getter_AddRefs(server));
    if (server) {
      nsCOMPtr<nsIMsgAccount> localFoldersAccount;
      findAccountByServerKey(localFoldersServerKey,
                             getter_AddRefs(localFoldersAccount));
      // If we don't have an existing account pointing at the local folders
      // server, we're going to add one.
      if (!localFoldersAccount) {
        nsCOMPtr<nsIMsgAccount> account;
        (void)CreateAccount(getter_AddRefs(account));
        if (account) account->SetIncomingServer(server);
      }
    }
  }
  return NS_OK;
}