/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "prprf.h" #include "plstr.h" #include "prmem.h" #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsCRTGlue.h" #include "nsCOMPtr.h" #include "nsIMsgFolderNotificationService.h" #include "nsPrintfCString.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsMsgAccount.h" #include "nsIMsgAccount.h" #include "nsIMsgAccountManager.h" #include "nsIObserverService.h" #include "mozilla/Services.h" #include "nsServiceManagerUtils.h" #include "nsMemory.h" #include "nsComponentManagerUtils.h" #include "nsMsgUtils.h" NS_IMPL_ISUPPORTS(nsMsgAccount, nsIMsgAccount) nsMsgAccount::nsMsgAccount() : m_identitiesValid(false), mTriedToGetServer(false) {} nsMsgAccount::~nsMsgAccount() {} nsresult nsMsgAccount::getPrefService() { if (m_prefs) return NS_OK; nsresult rv; NS_ENSURE_FALSE(m_accountKey.IsEmpty(), NS_ERROR_NOT_INITIALIZED); nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString accountRoot("mail.account."); accountRoot.Append(m_accountKey); accountRoot.Append('.'); return prefs->GetBranch(accountRoot.get(), getter_AddRefs(m_prefs)); } NS_IMETHODIMP nsMsgAccount::GetIncomingServer(nsIMsgIncomingServer** aIncomingServer) { NS_ENSURE_ARG_POINTER(aIncomingServer); // create the incoming server lazily if (!mTriedToGetServer && !m_incomingServer) { mTriedToGetServer = true; // ignore the error (and return null), but it's still bad so warn mozilla::DebugOnly rv = createIncomingServer(); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "couldn't lazily create the server\n"); } NS_IF_ADDREF(*aIncomingServer = m_incomingServer); return NS_OK; } NS_IMETHODIMP nsMsgAccount::CreateServer() { if (m_incomingServer) return NS_ERROR_ALREADY_INITIALIZED; return createIncomingServer(); } nsresult nsMsgAccount::createIncomingServer() { // from here, load mail.account.myaccount.server // Load the incoming server // // ex) mail.account.myaccount.server = "myserver" nsresult rv = getPrefService(); NS_ENSURE_SUCCESS(rv, rv); // get the "server" pref nsCString serverKey; rv = m_prefs->GetCharPref("server", serverKey); NS_ENSURE_SUCCESS(rv, rv); // get the server from the account manager nsCOMPtr accountManager = do_GetService("@mozilla.org/messenger/account-manager;1", &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr server; rv = accountManager->GetIncomingServer(serverKey, getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCString hostname; rv = server->GetHostName(hostname); NS_ENSURE_SUCCESS(rv, rv); if (hostname.IsEmpty()) { NS_WARNING( nsPrintfCString("Server had no hostname; key=%s", serverKey.get()) .get()); return NS_ERROR_UNEXPECTED; } // store the server in this structure m_incomingServer = server; accountManager->NotifyServerLoaded(server); return NS_OK; } NS_IMETHODIMP nsMsgAccount::SetIncomingServer(nsIMsgIncomingServer* aIncomingServer) { NS_ENSURE_ARG_POINTER(aIncomingServer); nsCString key; nsresult rv = aIncomingServer->GetKey(key); if (NS_SUCCEEDED(rv)) { rv = getPrefService(); NS_ENSURE_SUCCESS(rv, rv); m_prefs->SetCharPref("server", key); } m_incomingServer = aIncomingServer; bool serverValid; (void)aIncomingServer->GetValid(&serverValid); // only notify server loaded if server is valid so // account manager only gets told about finished accounts. if (serverValid) { // this is the point at which we can notify listeners about the // creation of the root folder, which implies creation of the new server. nsCOMPtr rootFolder; rv = aIncomingServer->GetRootFolder(getter_AddRefs(rootFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr mailSession = do_GetService("@mozilla.org/messenger/services/session;1", &rv); NS_ENSURE_SUCCESS(rv, rv); mailSession->OnFolderAdded(nullptr, rootFolder); nsCOMPtr notifier( do_GetService("@mozilla.org/messenger/msgnotificationservice;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); notifier->NotifyFolderAdded(rootFolder); nsCOMPtr accountManager = do_GetService("@mozilla.org/messenger/account-manager;1", &rv); if (NS_SUCCEEDED(rv)) accountManager->NotifyServerLoaded(aIncomingServer); // Force built-in folders to be created and discovered. Then, notify // listeners about them. nsTArray> subFolders; rv = rootFolder->GetSubFolders(subFolders); NS_ENSURE_SUCCESS(rv, rv); for (nsIMsgFolder* msgFolder : subFolders) { mailSession->OnFolderAdded(rootFolder, msgFolder); notifier->NotifyFolderAdded(msgFolder); } } return NS_OK; } NS_IMETHODIMP nsMsgAccount::GetIdentities(nsTArray>& identities) { NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE); identities.Clear(); identities.AppendElements(m_identities); return NS_OK; } /* * set up the m_identities array * do not call this more than once or we'll leak. */ nsresult nsMsgAccount::createIdentities() { NS_ENSURE_FALSE(m_identitiesValid, NS_ERROR_FAILURE); nsresult rv; m_identities.Clear(); nsCString identityKey; rv = getPrefService(); NS_ENSURE_SUCCESS(rv, rv); m_prefs->GetCharPref("identities", identityKey); if (identityKey.IsEmpty()) { // not an error if no identities, but strtok will be unhappy. m_identitiesValid = true; return NS_OK; } // get the server from the account manager nsCOMPtr accountManager = do_GetService("@mozilla.org/messenger/account-manager;1", &rv); NS_ENSURE_SUCCESS(rv, rv); char* newStr = identityKey.BeginWriting(); char* token = NS_strtok(",", &newStr); // temporaries used inside the loop nsCOMPtr identity; nsAutoCString key; // iterate through id1,id2, etc while (token) { key = token; key.StripWhitespace(); // create the account rv = accountManager->GetIdentity(key, getter_AddRefs(identity)); if (NS_SUCCEEDED(rv)) { m_identities.AppendElement(identity); } // advance to next key, if any token = NS_strtok(",", &newStr); } m_identitiesValid = true; return rv; } /* attribute nsIMsgIdentity defaultIdentity; */ NS_IMETHODIMP nsMsgAccount::GetDefaultIdentity(nsIMsgIdentity** aDefaultIdentity) { NS_ENSURE_ARG_POINTER(aDefaultIdentity); NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_NOT_INITIALIZED); // Default identity is the first in the list. if (m_identities.IsEmpty()) { *aDefaultIdentity = nullptr; } else { NS_IF_ADDREF(*aDefaultIdentity = m_identities[0]); } return NS_OK; } NS_IMETHODIMP nsMsgAccount::SetDefaultIdentity(nsIMsgIdentity* aDefaultIdentity) { NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE); auto position = m_identities.IndexOf(aDefaultIdentity); if (position == m_identities.NoIndex) { return NS_ERROR_FAILURE; } // Move it to the front of the list. m_identities.RemoveElementAt(position); m_identities.InsertElementAt(0, aDefaultIdentity); nsresult rv = saveIdentitiesPref(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(aDefaultIdentity, "account-default-identity-changed", NS_ConvertUTF8toUTF16(m_accountKey).get()); } return NS_OK; } /* void addIdentity (in nsIMsgIdentity identity); */ NS_IMETHODIMP nsMsgAccount::AddIdentity(nsIMsgIdentity* identity) { NS_ENSURE_ARG_POINTER(identity); NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE); // hack hack - need to add this to the list of identities. // for now just treat this as a Setxxx accessor // when this is actually implemented, don't refcount the default identity nsCString key; nsresult rv = identity->GetKey(key); if (NS_SUCCEEDED(rv)) { nsCString identityList; m_prefs->GetCharPref("identities", identityList); nsAutoCString newIdentityList(identityList); nsAutoCString testKey; // temporary to strip whitespace bool foundIdentity = false; // if the input identity is found if (!identityList.IsEmpty()) { char* newStr = identityList.BeginWriting(); char* token = NS_strtok(",", &newStr); // look for the identity key that we're adding while (token) { testKey = token; testKey.StripWhitespace(); if (testKey.Equals(key)) foundIdentity = true; token = NS_strtok(",", &newStr); } } // if it didn't already exist, append it if (!foundIdentity) { if (newIdentityList.IsEmpty()) newIdentityList = key; else { newIdentityList.Append(','); newIdentityList.Append(key); } } m_prefs->SetCharPref("identities", newIdentityList); // now add it to the in-memory list m_identities.AppendElement(identity); nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(identity, "account-identity-added", NS_ConvertUTF8toUTF16(key).get()); } } return NS_OK; } /* void removeIdentity (in nsIMsgIdentity identity); */ NS_IMETHODIMP nsMsgAccount::RemoveIdentity(nsIMsgIdentity* aIdentity) { NS_ENSURE_ARG_POINTER(aIdentity); NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE); // At least one identity must stay after the delete. NS_ENSURE_TRUE(m_identities.Length() > 1, NS_ERROR_FAILURE); nsCString key; nsresult rv = aIdentity->GetKey(key); NS_ENSURE_SUCCESS(rv, rv); if (!m_identities.RemoveElement(aIdentity)) { return NS_ERROR_FAILURE; } // Notify before clearing the pref values, so we do not get the superfluous // update notifications. nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->NotifyObservers(aIdentity, "account-identity-removed", NS_ConvertUTF8toUTF16(key).get()); } // Clear out the actual pref values associated with the identity. aIdentity->ClearAllValues(); return saveIdentitiesPref(); } nsresult nsMsgAccount::saveIdentitiesPref() { nsAutoCString newIdentityList; // Iterate over the existing identities and build the pref value, // a string of identity keys: id1, id2, idX... nsCString key; bool first = true; for (auto identity : m_identities) { identity->GetKey(key); if (first) { newIdentityList = key; first = false; } else { newIdentityList.Append(','); newIdentityList.Append(key); } } // Save the pref. m_prefs->SetCharPref("identities", newIdentityList); return NS_OK; } NS_IMETHODIMP nsMsgAccount::GetKey(nsACString& accountKey) { accountKey = m_accountKey; return NS_OK; } NS_IMETHODIMP nsMsgAccount::SetKey(const nsACString& accountKey) { m_accountKey = accountKey; m_prefs = nullptr; m_identitiesValid = false; m_identities.Clear(); return createIdentities(); } NS_IMETHODIMP nsMsgAccount::ToString(nsAString& aResult) { nsAutoString val; aResult.AssignLiteral("[nsIMsgAccount: "); aResult.Append(NS_ConvertASCIItoUTF16(m_accountKey)); aResult.Append(']'); return NS_OK; } NS_IMETHODIMP nsMsgAccount::ClearAllValues() { nsTArray prefNames; nsresult rv = m_prefs->GetChildList("", prefNames); NS_ENSURE_SUCCESS(rv, rv); for (auto& prefName : prefNames) { m_prefs->ClearUserPref(prefName.get()); } return NS_OK; }