diff options
Diffstat (limited to 'mailnews/base/src')
101 files changed, 45455 insertions, 0 deletions
diff --git a/mailnews/base/src/MailNewsDLF.cpp b/mailnews/base/src/MailNewsDLF.cpp new file mode 100644 index 0000000000..839d86006f --- /dev/null +++ b/mailnews/base/src/MailNewsDLF.cpp @@ -0,0 +1,101 @@ +/* -*- 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 "nsCOMPtr.h" +#include "MailNewsDLF.h" +#include "nsIChannel.h" +#include "plstr.h" +#include "nsStringGlue.h" +#include "nsICategoryManager.h" +#include "nsIServiceManager.h" +#include "nsIStreamConverterService.h" +#include "nsIStreamListener.h" +#include "nsNetCID.h" +#include "nsMsgUtils.h" + +namespace mozilla { +namespace mailnews { +NS_IMPL_ISUPPORTS(MailNewsDLF, nsIDocumentLoaderFactory) + +MailNewsDLF::MailNewsDLF() +{ +} + +MailNewsDLF::~MailNewsDLF() +{ +} + +NS_IMETHODIMP +MailNewsDLF::CreateInstance(const char* aCommand, + nsIChannel* aChannel, + nsILoadGroup* aLoadGroup, + const nsACString& aContentType, + nsIDocShell* aContainer, + nsISupports* aExtraInfo, + nsIStreamListener** aDocListener, + nsIContentViewer** aDocViewer) +{ + nsresult rv; + + bool viewSource = (PL_strstr(PromiseFlatCString(aContentType).get(), + "view-source") != 0); + + aChannel->SetContentType(NS_LITERAL_CSTRING(TEXT_HTML)); + + // Get the HTML category + nsCOMPtr<nsICategoryManager> catMan( + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString contractID; + rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", TEXT_HTML, + getter_Copies(contractID)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID.get(), + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStreamListener> listener; + + if (viewSource) { + rv = factory->CreateInstance("view-source", aChannel, aLoadGroup, + NS_LITERAL_CSTRING(TEXT_HTML "; x-view-type=view-source"), + aContainer, aExtraInfo, getter_AddRefs(listener), + aDocViewer); + } else { + rv = factory->CreateInstance("view", aChannel, aLoadGroup, NS_LITERAL_CSTRING(TEXT_HTML), + aContainer, aExtraInfo, getter_AddRefs(listener), + aDocViewer); + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStreamConverterService> scs( + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return scs->AsyncConvertData(MESSAGE_RFC822, TEXT_HTML, listener, aChannel, + aDocListener); +} + +NS_IMETHODIMP +MailNewsDLF::CreateInstanceForDocument(nsISupports* aContainer, + nsIDocument* aDocument, + const char* aCommand, + nsIContentViewer** aDocViewer) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +MailNewsDLF::CreateBlankDocument(nsILoadGroup* aLoadGroup, + nsIPrincipal* aPrincipal, + nsIDocument** aDocument) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +} +} diff --git a/mailnews/base/src/MailNewsDLF.h b/mailnews/base/src/MailNewsDLF.h new file mode 100644 index 0000000000..b0bd77b30b --- /dev/null +++ b/mailnews/base/src/MailNewsDLF.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +#ifndef MailNewsDLF_h__ +#define MailNewsDLF_h__ + +#include "nsIDocumentLoaderFactory.h" +#include "nsMimeTypes.h" +#include "nsMsgBaseCID.h" + +namespace mozilla { +namespace mailnews { + +/* + * This factory is a thin wrapper around the text/html loader factory. All it + * does is convert message/rfc822 to text/html and delegate the rest of the + * work to the text/html factory. + */ +class MailNewsDLF : public nsIDocumentLoaderFactory +{ +public: + MailNewsDLF(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOCUMENTLOADERFACTORY + +private: + virtual ~MailNewsDLF(); +}; +} +} + +#define MAILNEWSDLF_CATEGORIES \ + { "Gecko-Content-Viewers", MESSAGE_RFC822, NS_MAILNEWSDLF_CONTRACTID }, \ + +#endif diff --git a/mailnews/base/src/MailnewsLoadContextInfo.cpp b/mailnews/base/src/MailnewsLoadContextInfo.cpp new file mode 100644 index 0000000000..89aa56672a --- /dev/null +++ b/mailnews/base/src/MailnewsLoadContextInfo.cpp @@ -0,0 +1,55 @@ +/* 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/. */ + +// This was copied from netwerk/base/LoadContextInfo.cpp + +#include "MailnewsLoadContextInfo.h" + +#include "mozilla/dom/ToJSValue.h" +#include "nsIChannel.h" +#include "nsILoadContext.h" +#include "nsIWebNavigation.h" +#include "nsNetUtil.h" + +// MailnewsLoadContextInfo + +NS_IMPL_ISUPPORTS(MailnewsLoadContextInfo, nsILoadContextInfo) + +MailnewsLoadContextInfo::MailnewsLoadContextInfo(bool aIsPrivate, bool aIsAnonymous, mozilla::NeckoOriginAttributes aOriginAttributes) + : mIsPrivate(aIsPrivate) + , mIsAnonymous(aIsAnonymous) + , mOriginAttributes(aOriginAttributes) +{ + mOriginAttributes.SyncAttributesWithPrivateBrowsing(mIsPrivate); +} + +MailnewsLoadContextInfo::~MailnewsLoadContextInfo() +{ +} + +NS_IMETHODIMP MailnewsLoadContextInfo::GetIsPrivate(bool *aIsPrivate) +{ + *aIsPrivate = mIsPrivate; + return NS_OK; +} + +NS_IMETHODIMP MailnewsLoadContextInfo::GetIsAnonymous(bool *aIsAnonymous) +{ + *aIsAnonymous = mIsAnonymous; + return NS_OK; +} + +mozilla::NeckoOriginAttributes const* MailnewsLoadContextInfo::OriginAttributesPtr() +{ + return &mOriginAttributes; +} + +NS_IMETHODIMP MailnewsLoadContextInfo::GetOriginAttributes(JSContext *aCx, + JS::MutableHandle<JS::Value> aVal) +{ + if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} diff --git a/mailnews/base/src/MailnewsLoadContextInfo.h b/mailnews/base/src/MailnewsLoadContextInfo.h new file mode 100644 index 0000000000..6a127bf4a0 --- /dev/null +++ b/mailnews/base/src/MailnewsLoadContextInfo.h @@ -0,0 +1,32 @@ +/* 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/. */ + +// This was copied from netwerk/base/LoadContextInfo.h + +#ifndef MailnewsLoadContextInfo_h__ +#define MailnewsLoadContextInfo_h__ + +#include "nsILoadContextInfo.h" + +class nsIChannel; +class nsILoadContext; + +class MailnewsLoadContextInfo : public nsILoadContextInfo +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSILOADCONTEXTINFO + + MailnewsLoadContextInfo(bool aIsPrivate, bool aIsAnonymous, mozilla::NeckoOriginAttributes aOriginAttributes); + +private: + virtual ~MailnewsLoadContextInfo(); + +protected: + bool mIsPrivate : 1; + bool mIsAnonymous : 1; + mozilla::NeckoOriginAttributes mOriginAttributes; +}; + +#endif diff --git a/mailnews/base/src/folderLookupService.js b/mailnews/base/src/folderLookupService.js new file mode 100644 index 0000000000..9c64b5ef04 --- /dev/null +++ b/mailnews/base/src/folderLookupService.js @@ -0,0 +1,99 @@ +/* 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/. */ + +/** + * This module implements the folder lookup service. Presently, this uses RDF as + * the backing store, but the intent is that this will eventually become the + * authoritative map. + */ + +"use strict"; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function isValidFolder(folder) { + // RDF is liable to return folders that don't exist, and we may be working + // with a deleted folder but we're still holding on to the reference. For + // valid folders, one of two scenarios is true: either the folder has a parent + // (the deletion code clears the parent to indicate its nonvalidity), or the + // folder is a root folder of some server. Getting the root folder may throw + // an exception if we attempted to create a server that doesn't exist, so we + // need to guard for that error. + try { + return folder.parent != null || folder.rootFolder == folder; + } catch (e) { + return false; + } +} + +// This insures that the service is only created once +var gCreated = false; + +function folderLookupService() { + if (gCreated) + throw Cr.NS_ERROR_ALREADY_INITIALIZED; + this._map = new Map(); + gCreated = true; +} +folderLookupService.prototype = { + // XPCOM registration stuff + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFolderLookupService]), + classID: Components.ID("{a30be08c-afc8-4fed-9af7-79778a23db23}"), + + // nsIFolderLookupService impl + getFolderForURL: function (aUrl) { + let folder = null; + // First, see if the folder is in our cache. + if (this._map.has(aUrl)) { + let valid = false; + try { + folder = this._map.get(aUrl).QueryReferent(Ci.nsIMsgFolder); + valid = isValidFolder(folder); + } catch (e) { + // The object was deleted, so it's not valid + } + + if (valid) + return folder; + + // Don't keep around invalid folders. + this._map.delete(aUrl); + folder = null; + } + + // If we get here, then the folder was not in our map. It could be that the + // folder was created by somebody else, so try to find that folder. + // For now, we use the RDF service, since it results in minimal changes. But + // RDF has a tendency to create objects without checking to see if they + // really exist---use the parent property to see if the folder is a real + // folder. + if (folder == null) { + let rdf = Cc["@mozilla.org/rdf/rdf-service;1"] + .getService(Ci.nsIRDFService); + try { + folder = rdf.GetResource(aUrl) + .QueryInterface(Ci.nsIMsgFolder); + } catch (e) { + // If the QI fails, then we somehow picked up an RDF resource that isn't + // a folder. Return null in this case. + return null; + } + } + if (!isValidFolder(folder)) + return null; + + // Add the new folder to our map. Store a weak reference instead, so that + // the folder can be closed when necessary. + let weakRef = folder.QueryInterface(Ci.nsISupportsWeakReference) + .GetWeakReference(); + this._map.set(aUrl, weakRef); + return folder; + }, +}; + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([folderLookupService]); diff --git a/mailnews/base/src/moz.build b/mailnews/base/src/moz.build new file mode 100644 index 0000000000..b84839e9ec --- /dev/null +++ b/mailnews/base/src/moz.build @@ -0,0 +1,82 @@ +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'nsMailDirServiceDefs.h', + 'nsMsgRDFDataSource.h', + 'nsMsgRDFUtils.h', +] + +SOURCES += [ + 'MailNewsDLF.cpp', + 'MailnewsLoadContextInfo.cpp', + 'nsCidProtocolHandler.cpp', + 'nsCopyMessageStreamListener.cpp', + 'nsMailDirProvider.cpp', + 'nsMessenger.cpp', + 'nsMessengerBootstrap.cpp', + 'nsMessengerContentHandler.cpp', + 'nsMsgAccount.cpp', + 'nsMsgAccountManager.cpp', + 'nsMsgAccountManagerDS.cpp', + 'nsMsgBiffManager.cpp', + 'nsMsgContentPolicy.cpp', + 'nsMsgCopyService.cpp', + 'nsMsgDBView.cpp', + 'nsMsgFolderCache.cpp', + 'nsMsgFolderCacheElement.cpp', + 'nsMsgFolderCompactor.cpp', + 'nsMsgFolderDataSource.cpp', + 'nsMsgFolderNotificationService.cpp', + 'nsMsgGroupThread.cpp', + 'nsMsgGroupView.cpp', + 'nsMsgMailSession.cpp', + 'nsMsgOfflineManager.cpp', + 'nsMsgProgress.cpp', + 'nsMsgPurgeService.cpp', + 'nsMsgQuickSearchDBView.cpp', + 'nsMsgRDFDataSource.cpp', + 'nsMsgRDFUtils.cpp', + 'nsMsgSearchDBView.cpp', + 'nsMsgServiceProvider.cpp', + 'nsMsgSpecialViews.cpp', + 'nsMsgStatusFeedback.cpp', + 'nsMsgTagService.cpp', + 'nsMsgThreadedDBView.cpp', + 'nsMsgWindow.cpp', + 'nsMsgXFViewThread.cpp', + 'nsMsgXFVirtualFolderDBView.cpp', + 'nsSpamSettings.cpp', + 'nsStatusBarBiffManager.cpp', + 'nsSubscribableServer.cpp', + 'nsSubscribeDataSource.cpp', +] + +if CONFIG['NS_PRINTING']: + SOURCES += ['nsMsgPrintEngine.cpp'] + +if CONFIG['OS_ARCH'] == 'WINNT': + SOURCES += ['nsMessengerWinIntegration.cpp'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'qt'): + SOURCES += ['nsMessengerUnixIntegration.cpp'] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += ['nsMessengerOSXIntegration.mm'] + +EXTRA_COMPONENTS += [ + 'folderLookupService.js', + 'msgAsyncPrompter.js', + 'msgBase.manifest', + 'msgOAuth2Module.js', + 'newMailNotificationService.js', + 'nsMailNewsCommandLineHandler.js', +] + +EXTRA_JS_MODULES += [ + 'virtualFolderWrapper.js', +] + +FINAL_LIBRARY = 'mail' + diff --git a/mailnews/base/src/msgAsyncPrompter.js b/mailnews/base/src/msgAsyncPrompter.js new file mode 100644 index 0000000000..58b5288e97 --- /dev/null +++ b/mailnews/base/src/msgAsyncPrompter.js @@ -0,0 +1,126 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource:///modules/gloda/log4moz.js"); + +var Ci = Components.interfaces; +var Cc = Components.classes; + +function runnablePrompter(asyncPrompter, hashKey) { + this._asyncPrompter = asyncPrompter; + this._hashKey = hashKey; +} + +runnablePrompter.prototype = { + _asyncPrompter: null, + _hashKey: null, + + run: Task.async(function *() { + yield Services.logins.initializationPromise; + this._asyncPrompter._log.debug("Running prompt for " + this._hashKey); + let prompter = this._asyncPrompter._pendingPrompts[this._hashKey]; + let ok = false; + try { + ok = prompter.first.onPromptStart(); + } + catch (ex) { + Components.utils.reportError("runnablePrompter:run: " + ex + "\n"); + } + + delete this._asyncPrompter._pendingPrompts[this._hashKey]; + + for (var consumer of prompter.consumers) { + try { + if (ok) + consumer.onPromptAuthAvailable(); + else + consumer.onPromptCanceled(); + } + catch (ex) { + // Log the error for extension devs and others to pick up. + Components.utils.reportError("runnablePrompter:run: consumer.onPrompt* reported an exception: " + ex + "\n"); + } + } + this._asyncPrompter._asyncPromptInProgress--; + + this._asyncPrompter._log.debug("Finished running prompter for " + this._hashKey); + this._asyncPrompter._doAsyncAuthPrompt(); + }) +}; + +function msgAsyncPrompter() { + this._pendingPrompts = {}; + // By default, only log warnings to the error console and errors to dump(). + // You can use the preferences: + // msgAsyncPrompter.logging.console + // msgAsyncPrompter.logging.dump + // To change this up. Values should be one of: + // Fatal/Error/Warn/Info/Config/Debug/Trace/All + this._log = Log4Moz.getConfiguredLogger("msgAsyncPrompter", + Log4Moz.Level.Warn, + Log4Moz.Level.Warn); +} + +msgAsyncPrompter.prototype = { + classID: Components.ID("{49b04761-23dd-45d7-903d-619418a4d319}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgAsyncPrompter]), + + _pendingPrompts: null, + _asyncPromptInProgress: 0, + _log: null, + + queueAsyncAuthPrompt: function(aKey, aJumpQueue, aCaller) { + if (aKey in this._pendingPrompts) { + this._log.debug("Prompt bound to an existing one in the queue, key: " + aKey); + this._pendingPrompts[aKey].consumers.push(aCaller); + return; + } + + this._log.debug("Adding new prompt to the queue, key: " + aKey); + let asyncPrompt = { + first: aCaller, + consumers: [] + }; + + this._pendingPrompts[aKey] = asyncPrompt; + if (aJumpQueue) { + this._asyncPromptInProgress++; + + this._log.debug("Forcing runnablePrompter for " + aKey); + + let runnable = new runnablePrompter(this, aKey); + Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); + } + else + this._doAsyncAuthPrompt(); + }, + + _doAsyncAuthPrompt: function() { + if (this._asyncPromptInProgress > 0) { + this._log.debug("_doAsyncAuthPrompt bypassed - prompt already in progress"); + return; + } + + // Find the first prompt key we have in the queue. + let hashKey = null; + for (hashKey in this._pendingPrompts) + break; + + if (!hashKey) + return; + + this._asyncPromptInProgress++; + + this._log.debug("Dispatching runnablePrompter for " + hashKey); + + let runnable = new runnablePrompter(this, hashKey); + Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); + } +}; + +var components = [msgAsyncPrompter]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/base/src/msgBase.manifest b/mailnews/base/src/msgBase.manifest new file mode 100644 index 0000000000..e7af18bdf9 --- /dev/null +++ b/mailnews/base/src/msgBase.manifest @@ -0,0 +1,12 @@ +component {49b04761-23dd-45d7-903d-619418a4d319} msgAsyncPrompter.js +contract @mozilla.org/messenger/msgAsyncPrompter;1 {49b04761-23dd-45d7-903d-619418a4d319} +component {2f86d554-f9d9-4e76-8eb7-243f047333ee} nsMailNewsCommandLineHandler.js +contract @mozilla.org/commandlinehandler/general-startup;1?type=mail {2f86d554-f9d9-4e76-8eb7-243f047333ee} +category command-line-handler m-mail @mozilla.org/commandlinehandler/general-startup;1?type=mail +component {740880E6-E299-4165-B82F-DF1DCAB3AE22} newMailNotificationService.js +contract @mozilla.org/newMailNotificationService;1 {740880E6-E299-4165-B82F-DF1DCAB3AE22} +category profile-after-change NewMailNotificationService @mozilla.org/newMailNotificationService;1 +component {a30be08c-afc8-4fed-9af7-79778a23db23} folderLookupService.js +contract @mozilla.org/mail/folder-lookup;1 {a30be08c-afc8-4fed-9af7-79778a23db23} +component {b63d8e4c-bf60-439b-be0e-7c9f67291042} msgOAuth2Module.js +contract @mozilla.org/mail/oauth2-module;1 {b63d8e4c-bf60-439b-be0e-7c9f67291042} diff --git a/mailnews/base/src/msgOAuth2Module.js b/mailnews/base/src/msgOAuth2Module.js new file mode 100644 index 0000000000..407ab05193 --- /dev/null +++ b/mailnews/base/src/msgOAuth2Module.js @@ -0,0 +1,150 @@ +/* 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/. */ + +"use strict"; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +Components.utils.import("resource://gre/modules/OAuth2.jsm"); +Components.utils.import("resource://gre/modules/OAuth2Providers.jsm"); +Components.utils.import("resource://gre/modules/Preferences.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function OAuth2Module() { + this._refreshToken = ''; +} +OAuth2Module.prototype = { + // XPCOM registration stuff + QueryInterface: XPCOMUtils.generateQI([Ci.msgIOAuth2Module]), + classID: Components.ID("{b63d8e4c-bf60-439b-be0e-7c9f67291042}"), + + _loadOAuthClientDetails(aIssuer) { + let details = OAuth2Providers.getIssuerDetails(aIssuer); + if (details) + [this._appKey, this._appSecret, this._authURI, this._tokenURI] = details; + else + throw Cr.NS_ERROR_INVALID_ARGUMENT; + }, + initFromSmtp(aServer) { + return this._initPrefs("mail.smtpserver." + aServer.key + ".", + aServer.username, aServer.hostname); + }, + initFromMail(aServer) { + return this._initPrefs("mail.server." + aServer.key + ".", + aServer.username, aServer.realHostName); + }, + _initPrefs(root, aUsername, aHostname) { + // Load all of the parameters from preferences. + let issuer = Preferences.get(root + "oauth2.issuer", ""); + let scope = Preferences.get(root + "oauth2.scope", ""); + + // These properties are absolutely essential to OAuth2 support. If we don't + // have them, we don't support OAuth2. + if (!issuer || !scope) { + // Since we currently only support gmail, init values if server matches. + let details = OAuth2Providers.getHostnameDetails(aHostname); + if (details) + { + [issuer, scope] = details; + Preferences.set(root + "oauth2.issuer", issuer); + Preferences.set(root + "oauth2.scope", scope); + } + else + return false; + } + + // Find the app key we need for the OAuth2 string. Eventually, this should + // be using dynamic client registration, but there are no current + // implementations that we can test this with. + this._loadOAuthClientDetails(issuer); + + // Username is needed to generate the XOAUTH2 string. + this._username = aUsername; + // LoginURL is needed to save the refresh token in the password manager. + this._loginUrl = "oauth://" + issuer; + // We use the scope to indicate the realm. + this._scope = scope; + + // Define the OAuth property and store it. + this._oauth = new OAuth2(this._authURI, scope, this._appKey, + this._appSecret); + this._oauth.authURI = this._authURI; + this._oauth.tokenURI = this._tokenURI; + + // Try hinting the username... + this._oauth.extraAuthParams = [ + ["login_hint", aUsername] + ]; + + // Set the window title to something more useful than "Unnamed" + this._oauth.requestWindowTitle = + Services.strings.createBundle("chrome://messenger/locale/messenger.properties") + .formatStringFromName("oauth2WindowTitle", + [aUsername, aHostname], 2); + + // This stores the refresh token in the login manager. + Object.defineProperty(this._oauth, "refreshToken", { + get: () => this.refreshToken, + set: (token) => this.refreshToken = token + }); + + return true; + }, + + get refreshToken() { + let loginMgr = Cc["@mozilla.org/login-manager;1"] + .getService(Ci.nsILoginManager); + let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope); + for (let login of logins) { + if (login.username == this._username) + return login.password; + } + return ''; + }, + set refreshToken(token) { + let loginMgr = Cc["@mozilla.org/login-manager;1"] + .getService(Ci.nsILoginManager); + + // Check if we already have a login with this username, and modify the + // password on that, if we do. + let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope); + for (let login of logins) { + if (login.username == this._username) { + if (token) { + let propBag = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); + propBag.setProperty("password", token); + loginMgr.modifyLogin(login, propBag); + } + else + loginMgr.removeLogin(login); + return token; + } + } + + // Otherwise, we need a new login, so create one and fill it in. + let login = Cc["@mozilla.org/login-manager/loginInfo;1"] + .createInstance(Ci.nsILoginInfo); + login.init(this._loginUrl, null, this._scope, this._username, token, + '', ''); + loginMgr.addLogin(login); + return token; + }, + + connect(aWithUI, aListener) { + this._oauth.connect(() => aListener.onSuccess(this._oauth.accessToken), + x => aListener.onFailure(x), + aWithUI, false); + }, + + buildXOAuth2String() { + return btoa("user=" + this._username + "\x01auth=Bearer " + + this._oauth.accessToken + "\x01\x01"); + }, +}; + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([OAuth2Module]); diff --git a/mailnews/base/src/newMailNotificationService.js b/mailnews/base/src/newMailNotificationService.js new file mode 100644 index 0000000000..4fe72f5543 --- /dev/null +++ b/mailnews/base/src/newMailNotificationService.js @@ -0,0 +1,377 @@ +/* -*- Mode: Java; 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/. */ + +/* platform-independent code to count new and unread messages and pass the information to + * platform-specific notification modules + * + * Logging for this module uses the TB version of log4moz. Default logging is at the Warn + * level. Other possibly interesting messages are at Error, Info and Debug. To configure, set the + * preferences "mail.notification.logging.console" (for the error console) or + * "mail.notification.logging.dump" (for stderr) to the string indicating the level you want. + */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import("resource:///modules/gloda/log4moz.js"); +Cu.import("resource:///modules/iteratorUtils.jsm"); +Cu.import("resource:///modules/mailServices.js"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +var NMNS = Ci.mozINewMailNotificationService; + +var countInboxesPref = "mail.notification.count.inbox_only"; +// Old name for pref +var countNewMessagesPref = "mail.biff.use_new_count_in_mac_dock"; +// When we go cross-platform we should migrate to +// const countNewMessagesPref = "mail.notification.count.new"; + +// Helper function to retrieve a boolean preference with a default +function getBoolPref(pref, defaultValue) { + try { + return Services.prefs.getBoolPref(pref); + } + catch(e) { + return defaultValue; + } +} + + +// constructor +function NewMailNotificationService() { + this._mUnreadCount = 0; + this._mNewCount = 0; + this._listeners = []; + this.wrappedJSObject = this; + + this._log = Log4Moz.getConfiguredLogger("mail.notification", + Log4Moz.Level.Warn, + Log4Moz.Level.Warn, + Log4Moz.Level.Warn); + + // Listen for mail-startup-done to do the rest of our setup after folders are initialized + Services.obs.addObserver(this, "mail-startup-done", false); +} + +NewMailNotificationService.prototype = { + classDescription: "Maintain counts of new and unread messages", + classID: Components.ID("{740880E6-E299-4165-B82F-DF1DCAB3AE22}"), + contractID: "@mozilla.org/newMailNotificationService;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIFolderListener, Ci.mozINewMailNotificationService]), + _xpcom_factory: XPCOMUtils.generateSingletonFactory(NewMailNotificationService), + + _mUnreadCount: 0, + _mNewCount: 0, + _listeners: null, + _log: null, + + get countNew() { + return getBoolPref(countNewMessagesPref, false); + }, + + observe: function NMNS_Observe(aSubject, aTopic, aData) { + // Set up to catch updates to unread count + this._log.info("NMNS_Observe: " + aTopic); + + try { + if (aTopic == "mail-startup-done") { + try { + Services.obs.removeObserver(this, "mail-startup-done"); + } + catch (e) { + this._log.error("NMNS_Observe: unable to deregister mail-startup-done listener: " + e); + } + Services.obs.addObserver(this, "profile-before-change", false); + MailServices.mailSession.AddFolderListener(this, Ci.nsIFolderListener.intPropertyChanged | + Ci.nsIFolderListener.added | + Ci.nsIFolderListener.removed | + Ci.nsIFolderListener.propertyFlagChanged); + this._initUnreadCount(); + } + else if (aTopic == "profile-before-change") { + try { + MailServices.mailSession.RemoveFolderListener(this); + Services.obs.removeObserver(this, "profile-before-change"); + } + catch (e) { + this._log.error("NMNS_Observe: unable to deregister listeners at shutdown: " + e); + } + } + } catch (error) { + this._log.error("NMNS_Observe failed: " + error); + } + }, + + _initUnreadCount: function NMNS_initUnreadCount() { + let total = 0; + let allServers = MailServices.accounts.allServers; + for (let i = 0; i < allServers.length; i++) { + let currentServer = allServers.queryElementAt(i, Ci.nsIMsgIncomingServer); + this._log.debug("NMNS_initUnread: server " + currentServer.prettyName + " type " + currentServer.type); + // Don't bother counting RSS or NNTP servers + let type = currentServer.type; + if (type == "rss" || type == "nntp") + continue; + + let rootFolder = currentServer.rootFolder; + if (rootFolder) { + total += this._countUnread(rootFolder); + } + } + this._mUnreadCount = total; + if (!this.countNew) { + this._log.info("NMNS_initUnread notifying listeners: " + total + " total unread messages"); + this._notifyListeners(NMNS.count, "onCountChanged", total); + } + }, + + // Count all the unread messages below the given folder + _countUnread: function NMNS_countUnread(folder) { + this._log.trace("NMNS_countUnread: parent folder " + folder.URI); + let unreadCount = 0; + + if (this.confirmShouldCount(folder)) { + let count = folder.getNumUnread(false); + this._log.debug("NMNS_countUnread: folder " + folder.URI + ", " + count + " unread"); + if (count > 0) + unreadCount += count; + } + + let allFolders = folder.descendants; + for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) { + if (this.confirmShouldCount(folder)) { + let count = folder.getNumUnread(false); + this._log.debug("NMNS_countUnread: folder " + folder.URI + ", " + count + " unread"); + if (count > 0) + unreadCount += count; + } + } + return unreadCount; + }, + + // Filter out special folders and then ask for observers to see if + // we should monitor unread messages in this folder + confirmShouldCount: function NMNS_confirmShouldCount(aFolder) { + let shouldCount = Cc['@mozilla.org/supports-PRBool;1'].createInstance(Ci.nsISupportsPRBool); + shouldCount.data = true; + this._log.trace("NMNS_confirmShouldCount: folder " + aFolder.URI + " flags " + aFolder.flags); + let srv = null; + + // If it's not a mail folder we don't count it by default + if (!(aFolder.flags & Ci.nsMsgFolderFlags.Mail)) + shouldCount.data = false; + + // For whatever reason, RSS folders have the 'Mail' flag + else if ((srv = aFolder.server) && (srv.type == "rss")) + shouldCount.data = false; + + // If it's a special folder *other than the inbox* we don't count it by default + else if ((aFolder.flags & Ci.nsMsgFolderFlags.SpecialUse) + && !(aFolder.flags & Ci.nsMsgFolderFlags.Inbox)) + shouldCount.data = false; + + else if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual) + shouldCount.data = false; + + // if we're only counting inboxes and it's not an inbox... + else + try { + // If we can't get this pref, just leave it as the default + let onlyCountInboxes = Services.prefs.getBoolPref(countInboxesPref); + if (onlyCountInboxes && !(aFolder.flags & Ci.nsMsgFolderFlags.Inbox)) + shouldCount.data = false; + } catch (error) {} + + this._log.trace("NMNS_confirmShouldCount: before observers " + shouldCount.data); + Services.obs.notifyObservers(shouldCount, "before-count-unread-for-folder", aFolder.URI); + this._log.trace("NMNS_confirmShouldCount: after observers " + shouldCount.data); + + return shouldCount.data; + }, + + OnItemIntPropertyChanged: function NMNS_OnItemIntPropertyChanged(folder, property, oldValue, newValue) { + try { + if (property == "FolderSize") + return; + this._log.trace("NMNS_OnItemIntPropertyChanged: folder " + folder.URI + " " + property + " " + oldValue + " " + newValue); + if (property == "BiffState") { + this._biffStateChanged(folder, oldValue, newValue); + } + else if (property == "TotalUnreadMessages") { + this._updateUnreadCount(folder, oldValue, newValue); + } + else if (property == "NewMailReceived") { + this._newMailReceived(folder, oldValue, newValue); + } + } catch (error) { + this._log.error("NMNS_OnItemIntPropertyChanged: exception " + error); + } + }, + + _biffStateChanged: function NMNS_biffStateChanged(folder, oldValue, newValue) { + if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail) { + if (folder.server && !folder.server.performingBiff) { + this._log.debug("NMNS_biffStateChanged: folder " + folder.URI + " notified, but server not performing biff"); + return; + } + + // Biff notifications come in for the top level of the server, we need to look for + // the folder that actually contains the new mail + + let allFolders = folder.descendants; + let numFolders = allFolders.length; + + this._log.trace("NMNS_biffStateChanged: folder " + folder.URI + " New mail, " + numFolders + " subfolders"); + let newCount = 0; + + if (this.confirmShouldCount(folder)) { + let folderNew = folder.getNumNewMessages(false); + this._log.debug("NMNS_biffStateChanged: folder " + folder.URI + " new messages: " + folderNew); + if (folderNew > 0) + newCount += folderNew; + } + + for (let folder in fixIterator(allFolders, Ci.nsIMsgFolder)) { + if (this.confirmShouldCount(folder)) { + let folderNew = folder.getNumNewMessages(false); + this._log.debug("NMNS_biffStateChanged: folder " + folder.URI + " new messages: " + folderNew); + if (folderNew > 0) + newCount += folderNew; + } + } + if (newCount > 0) { + this._mNewCount += newCount; + this._log.debug("NMNS_biffStateChanged: " + folder.URI + " New mail count " + this._mNewCount); + if (this.countNew) + this._notifyListeners(NMNS.count, "onCountChanged", this._mNewCount); + } + } + else if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NoMail) { + // Dodgy - when any folder tells us it has no mail, clear all unread mail + this._mNewCount = 0; + this._log.debug("NMNS_biffStateChanged: " + folder.URI + " New mail count 0"); + if (this.countNew) + this._notifyListeners(NMNS.count, "onCountChanged", this._mNewCount); + } + }, + + _newMailReceived: function NMNS_newMailReceived(folder, oldValue, newValue) { + if (!this.confirmShouldCount(folder)) + return; + + if (!oldValue || (oldValue < 0)) + oldValue = 0; + let oldTotal = this._mNewCount; + this._mNewCount += (newValue - oldValue); + this._log.debug("NMNS_newMailReceived: " + folder.URI + + " Old folder " + oldValue + " New folder " + newValue + + " Old total " + oldTotal + " New total " + this._mNewCount); + if (this.countNew) + this._notifyListeners(NMNS.count, "onCountChanged", this._mNewCount); + }, + + _updateUnreadCount: function NMNS_updateUnreadCount(folder, oldValue, newValue) { + if (!this.confirmShouldCount(folder)) + return; + + // treat "count unknown" as zero + if (oldValue < 0) + oldValue = 0; + if (newValue < 0) + newValue = 0; + + this._mUnreadCount += (newValue - oldValue); + if (!this.countNew) { + this._log.info("NMNS_updateUnreadCount notifying listeners: unread count " + this._mUnreadCount); + this._notifyListeners(NMNS.count, "onCountChanged", this._mUnreadCount); + } + }, + + OnItemAdded: function NMNS_OnItemAdded(parentItem, item) { + if (item instanceof Ci.nsIMsgDBHdr) { + if (this.confirmShouldCount(item.folder)) { + this._log.trace("NMNS_OnItemAdded: item " + item.folder.getUriForMsg(item) + " added to " + item.folder.folderURL); + } + } + }, + + OnItemPropertyFlagChanged: function NMNS_OnItemPropertyFlagChanged(item, + property, + oldFlag, + newFlag) { + if (item instanceof Ci.nsIMsgDBHdr) { + if ((oldFlag & Ci.nsMsgMessageFlags.New) + && !(newFlag & Ci.nsMsgMessageFlags.New)) { + this._log.trace("NMNS_OnItemPropertyFlagChanged: item " + item.folder.getUriForMsg(item) + " marked read"); + } + else if (newFlag & Ci.nsMsgMessageFlags.New) { + this._log.trace("NMNS_OnItemPropertyFlagChanged: item " + item.folder.getUriForMsg(item) + " marked unread"); + } + } + }, + + OnItemRemoved: function NMNS_OnItemRemoved(parentItem, item) { + if (item instanceof Ci.nsIMsgDBHdr && !item.isRead) { + this._log.trace("NMNS_OnItemRemoved: unread item " + item.folder.getUriForMsg(item) + " removed from " + item.folder.folderURL); + } + }, + + + // Implement mozINewMailNotificationService + + get messageCount() { + if (this.countNew) + return this._mNewCount; + return this._mUnreadCount; + }, + + addListener: function NMNS_addListener(aListener, flags) { + this._log.trace("NMNS_addListener: listener " + aListener.toSource + " flags " + flags); + for (let i = 0; i < this._listeners.length; i++) { + let l = this._listeners[i]; + if (l.obj === aListener) { + l.flags = flags; + return; + } + } + // If we get here, the listener wasn't already in the list + this._listeners.push({obj: aListener, flags: flags}); + }, + + removeListener: function NMNS_removeListener(aListener) { + this._log.trace("NMNS_removeListener: listener " + aListener.toSource); + for (let i = 0; i < this._listeners.length; i++) { + let l = this._listeners[i]; + if (l.obj === aListener) { + this._listeners.splice(i, 1); + return; + } + } + }, + + _listenersForFlag: function NMNS_listenersForFlag(flag) { + this._log.trace("NMNS_listenersForFlag " + flag + " length " + this._listeners.length + " " + this._listeners.toSource()); + let list = []; + for (let i = 0; i < this._listeners.length; i++) { + let l = this._listeners[i]; + if (l.flags & flag) { + list.push(l.obj); + } + } + return list; + }, + + _notifyListeners: function NMNS_notifyListeners(flag, func, value) { + let list = this._listenersForFlag(flag); + for (let i = 0; i < list.length; i++) { + this._log.debug("NMNS_notifyListeners " + flag + " " + func + " " + value); + list[i][func].call(list[i], value); + } + } +}; + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([NewMailNotificationService]); diff --git a/mailnews/base/src/nsCidProtocolHandler.cpp b/mailnews/base/src/nsCidProtocolHandler.cpp new file mode 100644 index 0000000000..8f3f520be7 --- /dev/null +++ b/mailnews/base/src/nsCidProtocolHandler.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "nsCidProtocolHandler.h" +#include "nsStringGlue.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsComponentManagerUtils.h" + +nsCidProtocolHandler::nsCidProtocolHandler() +{ +} + +nsCidProtocolHandler::~nsCidProtocolHandler() +{ +} + +NS_IMPL_ISUPPORTS(nsCidProtocolHandler, nsIProtocolHandler) + +NS_IMETHODIMP nsCidProtocolHandler::GetScheme(nsACString & aScheme) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCidProtocolHandler::GetDefaultPort(int32_t *aDefaultPort) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCidProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags) +{ + // XXXbz so why does this protocol handler exist, exactly? + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCidProtocolHandler::NewURI(const nsACString & aSpec, const char *aOriginCharset, nsIURI *aBaseURI, nsIURI **_retval) +{ + nsresult rv; + nsCOMPtr <nsIURI> url = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // the right fix is to use the baseSpec (or aBaseUri) + // and specify the cid, and then fix mime + // to handle that, like it does with "...&part=1.2" + // for now, do about blank to prevent spam + // from popping up annoying alerts about not implementing the cid + // protocol + rv = url->SetSpec(nsDependentCString("about:blank")); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = url); + return NS_OK; +} + +NS_IMETHODIMP nsCidProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCidProtocolHandler::NewChannel2(nsIURI *aURI, + nsILoadInfo* aLoadInfo, + nsIChannel **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCidProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + diff --git a/mailnews/base/src/nsCidProtocolHandler.h b/mailnews/base/src/nsCidProtocolHandler.h new file mode 100644 index 0000000000..035763d48d --- /dev/null +++ b/mailnews/base/src/nsCidProtocolHandler.h @@ -0,0 +1,24 @@ +/* -*- 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/. */ + +#ifndef nsCidProtocolHandler_h__ +#define nsCidProtocolHandler_h__ + +#include "nsCOMPtr.h" +#include "nsIProtocolHandler.h" + +class nsCidProtocolHandler : public nsIProtocolHandler +{ +public: + nsCidProtocolHandler(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLHANDLER + +private: + virtual ~nsCidProtocolHandler(); +}; + +#endif /* nsCidProtocolHandler_h__ */ diff --git a/mailnews/base/src/nsCopyMessageStreamListener.cpp b/mailnews/base/src/nsCopyMessageStreamListener.cpp new file mode 100644 index 0000000000..ed41aa51bc --- /dev/null +++ b/mailnews/base/src/nsCopyMessageStreamListener.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsCopyMessageStreamListener.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMailboxUrl.h" +#include "nsIMsgHdr.h" +#include "nsIMsgImapMailFolder.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "netCore.h" + +NS_IMPL_ISUPPORTS(nsCopyMessageStreamListener, nsIStreamListener, + nsIRequestObserver, nsICopyMessageStreamListener) + +static nsresult GetMessage(nsIURI *aURL, nsIMsgDBHdr **message) +{ + NS_ENSURE_ARG_POINTER(message); + + nsCOMPtr<nsIMsgMessageUrl> uriURL; + nsresult rv; + + //Need to get message we are about to copy + uriURL = do_QueryInterface(aURL, &rv); + if(NS_FAILED(rv)) + return rv; + + // get the uri. first try and use the original message spec + // if that fails, use the spec of nsIURI that we're called with + nsCString uri; + rv = uriURL->GetOriginalSpec(getter_Copies(uri)); + if (NS_FAILED(rv) || uri.IsEmpty()) { + rv = uriURL->GetUri(getter_Copies(uri)); + NS_ENSURE_SUCCESS(rv,rv); + } + + nsCOMPtr <nsIMsgMessageService> msgMessageService; + rv = GetMessageServiceFromURI(uri, getter_AddRefs(msgMessageService)); + NS_ENSURE_SUCCESS(rv,rv); + if (!msgMessageService) + return NS_ERROR_FAILURE; + + rv = msgMessageService->MessageURIToMsgHdr(uri.get(), message); + return rv; +} + +nsCopyMessageStreamListener::nsCopyMessageStreamListener() +{ +} + +nsCopyMessageStreamListener::~nsCopyMessageStreamListener() +{ + //All member variables are nsCOMPtr's. +} + +NS_IMETHODIMP nsCopyMessageStreamListener::Init(nsIMsgFolder *srcFolder, nsICopyMessageListener *destination, nsISupports *listenerData) +{ + mSrcFolder = srcFolder; + mDestination = destination; + mListenerData = listenerData; + return NS_OK; +} + +NS_IMETHODIMP nsCopyMessageStreamListener::StartMessage() +{ + if (mDestination) + mDestination->StartMessage(); + + return NS_OK; +} + +NS_IMETHODIMP nsCopyMessageStreamListener::EndMessage(nsMsgKey key) +{ + if (mDestination) + mDestination->EndMessage(key); + + return NS_OK; +} + + +NS_IMETHODIMP nsCopyMessageStreamListener::OnDataAvailable(nsIRequest * /* request */, nsISupports *ctxt, nsIInputStream *aIStream, uint64_t sourceOffset, uint32_t aLength) +{ + nsresult rv; + rv = mDestination->CopyData(aIStream, aLength); + return rv; +} + +NS_IMETHODIMP nsCopyMessageStreamListener::OnStartRequest(nsIRequest * request, nsISupports *ctxt) +{ + nsCOMPtr<nsIMsgDBHdr> message; + nsresult rv = NS_OK; + nsCOMPtr<nsIURI> uri = do_QueryInterface(ctxt, &rv); + + NS_ASSERTION(NS_SUCCEEDED(rv), "ahah...someone didn't pass in the expected context!!!"); + + if (NS_SUCCEEDED(rv)) + rv = GetMessage(uri, getter_AddRefs(message)); + if(NS_SUCCEEDED(rv)) + rv = mDestination->BeginCopy(message); + + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP nsCopyMessageStreamListener::EndCopy(nsISupports *url, nsresult aStatus) +{ + nsresult rv; + nsCOMPtr<nsIURI> uri = do_QueryInterface(url, &rv); + + NS_ENSURE_SUCCESS(rv, rv); + bool copySucceeded = (aStatus == NS_BINDING_SUCCEEDED); + rv = mDestination->EndCopy(copySucceeded); + //If this is a move and we finished the copy, delete the old message. + bool moveMessage = false; + + nsCOMPtr<nsIMsgMailNewsUrl> mailURL(do_QueryInterface(uri)); + if (mailURL) + rv = mailURL->IsUrlType(nsIMsgMailNewsUrl::eMove, &moveMessage); + + if (NS_FAILED(rv)) + moveMessage = false; + + // OK, this is wrong if we're moving to an imap folder, for example. This really says that + // we were able to pull the message from the source, NOT that we were able to + // put it in the destination! + if (moveMessage) + { + // don't do this if we're moving to an imap folder - that's handled elsewhere. + nsCOMPtr<nsIMsgImapMailFolder> destImap = do_QueryInterface(mDestination); + // if the destination is a local folder, it will handle the delete from the source in EndMove + if (!destImap) + rv = mDestination->EndMove(copySucceeded); + } + // Even if the above actions failed we probably still want to return NS_OK. + // There should probably be some error dialog if either the copy or delete failed. + return NS_OK; +} + +NS_IMETHODIMP nsCopyMessageStreamListener::OnStopRequest(nsIRequest* request, nsISupports *ctxt, nsresult aStatus) +{ + return EndCopy(ctxt, aStatus); +} + diff --git a/mailnews/base/src/nsCopyMessageStreamListener.h b/mailnews/base/src/nsCopyMessageStreamListener.h new file mode 100644 index 0000000000..b857401edc --- /dev/null +++ b/mailnews/base/src/nsCopyMessageStreamListener.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef NSCOPYMESSAGESTREAMLISTENER_H +#define NSCOPYMESSAGESTREAMLISTENER_H + +#include "nsICopyMsgStreamListener.h" +#include "nsIStreamListener.h" +#include "nsIMsgFolder.h" +#include "nsICopyMessageListener.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" + +class nsCopyMessageStreamListener : public nsIStreamListener, public nsICopyMessageStreamListener { + +public: + nsCopyMessageStreamListener(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICOPYMESSAGESTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + +protected: + virtual ~nsCopyMessageStreamListener(); + + nsCOMPtr<nsICopyMessageListener> mDestination; + nsCOMPtr<nsISupports> mListenerData; + nsCOMPtr<nsIMsgFolder> mSrcFolder; + +}; + + + +#endif diff --git a/mailnews/base/src/nsMailDirProvider.cpp b/mailnews/base/src/nsMailDirProvider.cpp new file mode 100644 index 0000000000..6df8d89e1a --- /dev/null +++ b/mailnews/base/src/nsMailDirProvider.cpp @@ -0,0 +1,206 @@ +/* -*- 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 "nsMailDirProvider.h" +#include "nsMailDirServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsMsgBaseCID.h" +#include "nsArrayEnumerator.h" +#include "nsCOMArray.h" +#include "nsEnumeratorUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIChromeRegistry.h" +#include "nsICategoryManager.h" +#include "nsServiceManagerUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Services.h" + +#define MAIL_DIR_50_NAME "Mail" +#define IMAP_MAIL_DIR_50_NAME "ImapMail" +#define NEWS_DIR_50_NAME "News" +#define MSG_FOLDER_CACHE_DIR_50_NAME "panacea.dat" + +nsresult +nsMailDirProvider::EnsureDirectory(nsIFile *aDirectory) +{ + bool exists; + nsresult rv = aDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) + rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700); + + return rv; +} + +NS_IMPL_ISUPPORTS(nsMailDirProvider, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +NS_IMETHODIMP +nsMailDirProvider::GetFile(const char *aKey, bool *aPersist, + nsIFile **aResult) +{ + // NOTE: This function can be reentrant through the NS_GetSpecialDirectory + // call, so be careful not to cause infinite recursion. + // i.e. the check for supported files must come first. + const char* leafName = nullptr; + bool isDirectory = true; + + if (!strcmp(aKey, NS_APP_MAIL_50_DIR)) + leafName = MAIL_DIR_50_NAME; + else if (!strcmp(aKey, NS_APP_IMAP_MAIL_50_DIR)) + leafName = IMAP_MAIL_DIR_50_NAME; + else if (!strcmp(aKey, NS_APP_NEWS_50_DIR)) + leafName = NEWS_DIR_50_NAME; + else if (!strcmp(aKey, NS_APP_MESSENGER_FOLDER_CACHE_50_FILE)) { + isDirectory = false; + leafName = MSG_FOLDER_CACHE_DIR_50_NAME; + } + else + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> parentDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(parentDir)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> file; + rv = parentDir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + nsDependentCString leafStr(leafName); + rv = file->AppendNative(leafStr); + if (NS_FAILED(rv)) + return rv; + + bool exists; + if (isDirectory && NS_SUCCEEDED(file->Exists(&exists)) && !exists) + rv = EnsureDirectory(file); + + *aPersist = true; + file.swap(*aResult); + + return rv; +} + +NS_IMETHODIMP +nsMailDirProvider::GetFiles(const char *aKey, + nsISimpleEnumerator **aResult) +{ + if (strcmp(aKey, ISP_DIRECTORY_LIST) != 0) + return NS_ERROR_FAILURE; + + // The list of isp directories includes the isp directory + // in the current process dir (i.e. <path to thunderbird.exe>\isp and + // <path to thunderbird.exe>\isp\locale + // and isp and isp\locale for each active extension + + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + if (!dirSvc) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> currentProcessDir; + nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), getter_AddRefs(currentProcessDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> directoryEnumerator; + rv = NS_NewSingletonEnumerator(getter_AddRefs(directoryEnumerator), currentProcessDir); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> combinedEnumerator; + nsCOMPtr<nsISimpleEnumerator> extensionsEnum; + + // xpcshell-tests don't have XRE_EXTENSIONS_DIR_LIST, so accept a null return here. + dirSvc->Get(XRE_EXTENSIONS_DIR_LIST, + NS_GET_IID(nsISimpleEnumerator), + getter_AddRefs(extensionsEnum)); + + rv = NS_NewUnionEnumerator(getter_AddRefs(combinedEnumerator), directoryEnumerator, extensionsEnum); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aResult = new AppendingEnumerator(combinedEnumerator)); + return NS_SUCCESS_AGGREGATE_RESULT; +} + +NS_IMPL_ISUPPORTS(nsMailDirProvider::AppendingEnumerator, + nsISimpleEnumerator) + +NS_IMETHODIMP +nsMailDirProvider::AppendingEnumerator::HasMoreElements(bool *aResult) +{ + *aResult = mNext || mNextWithLocale ? true : false; + return NS_OK; +} + +NS_IMETHODIMP +nsMailDirProvider::AppendingEnumerator::GetNext(nsISupports* *aResult) +{ + // Set the return value to the next directory we want to enumerate over + if (aResult) + NS_ADDREF(*aResult = mNext); + + if (mNextWithLocale) + { + mNext = mNextWithLocale; + mNextWithLocale = nullptr; + return NS_OK; + } + + mNext = nullptr; + + // Ignore all errors + + bool more; + while (NS_SUCCEEDED(mBase->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> nextbasesupp; + mBase->GetNext(getter_AddRefs(nextbasesupp)); + + nsCOMPtr<nsIFile> nextbase(do_QueryInterface(nextbasesupp)); + if (!nextbase) + continue; + + nextbase->Clone(getter_AddRefs(mNext)); + if (!mNext) + continue; + + mNext->AppendNative(NS_LITERAL_CSTRING("isp")); + bool exists; + nsresult rv = mNext->Exists(&exists); + if (NS_SUCCEEDED(rv) && exists) + { + if (!mLocale.IsEmpty()) + { + mNext->Clone(getter_AddRefs(mNextWithLocale)); + mNextWithLocale->AppendNative(mLocale); + rv = mNextWithLocale->Exists(&exists); + if (NS_FAILED(rv) || !exists) + mNextWithLocale = nullptr; // clear out mNextWithLocale, so we don't try to iterate over it + } + break; + } + + mNext = nullptr; + } + + return NS_OK; +} + +nsMailDirProvider::AppendingEnumerator::AppendingEnumerator + (nsISimpleEnumerator* aBase) : + mBase(aBase) +{ + nsCOMPtr<nsIXULChromeRegistry> packageRegistry = + mozilla::services::GetXULChromeRegistryService(); + if (packageRegistry) + packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"), false, mLocale); + // Initialize mNext to begin + GetNext(nullptr); +} diff --git a/mailnews/base/src/nsMailDirProvider.h b/mailnews/base/src/nsMailDirProvider.h new file mode 100644 index 0000000000..d12876d894 --- /dev/null +++ b/mailnews/base/src/nsMailDirProvider.h @@ -0,0 +1,43 @@ +/* -*- 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/. */ + +#ifndef nsMailDirProvider_h__ +#define nsMailDirProvider_h__ + +#include "nsIDirectoryService.h" +#include "nsISimpleEnumerator.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" + +class nsMailDirProvider final : public nsIDirectoryServiceProvider2 +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + +private: + ~nsMailDirProvider() {} + + nsresult EnsureDirectory(nsIFile *aDirectory); + + class AppendingEnumerator final : public nsISimpleEnumerator + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + + AppendingEnumerator(nsISimpleEnumerator* aBase); + + private: + ~AppendingEnumerator() {} + nsCOMPtr<nsISimpleEnumerator> mBase; + nsCOMPtr<nsIFile> mNext; + nsCOMPtr<nsIFile> mNextWithLocale; + nsCString mLocale; + }; +}; + +#endif // nsMailDirProvider_h__ diff --git a/mailnews/base/src/nsMailDirServiceDefs.h b/mailnews/base/src/nsMailDirServiceDefs.h new file mode 100644 index 0000000000..fe12ed0d6b --- /dev/null +++ b/mailnews/base/src/nsMailDirServiceDefs.h @@ -0,0 +1,31 @@ +/* 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/. */ + + +#ifndef nsMailDirectoryServiceDefs_h___ +#define nsMailDirectoryServiceDefs_h___ + +//============================================================================= +// +// Defines property names for directories available from the mail-specific +// nsMailDirProvider. +// +// System and XPCOM properties are defined in nsDirectoryServiceDefs.h. +// General application properties are defined in nsAppDirectoryServiceDefs.h. +// +//============================================================================= + +// ---------------------------------------------------------------------------- +// Files and directories that exist on a per-profile basis. +// ---------------------------------------------------------------------------- + +#define NS_APP_MAIL_50_DIR "MailD" +#define NS_APP_IMAP_MAIL_50_DIR "IMapMD" +#define NS_APP_NEWS_50_DIR "NewsD" + +#define NS_APP_MESSENGER_FOLDER_CACHE_50_FILE "MFCaF" + +#define ISP_DIRECTORY_LIST "ISPDL" + +#endif diff --git a/mailnews/base/src/nsMailNewsCommandLineHandler.js b/mailnews/base/src/nsMailNewsCommandLineHandler.js new file mode 100644 index 0000000000..3a85ec3364 --- /dev/null +++ b/mailnews/base/src/nsMailNewsCommandLineHandler.js @@ -0,0 +1,170 @@ +/* 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/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/AppConstants.jsm"); + +var MAPI_STARTUP_ARG = "MapiStartup"; +var MESSAGE_ID_PARAM = "?messageid="; + +var CMDLINEHANDLER_CID = Components.ID("{2f86d554-f9d9-4e76-8eb7-243f047333ee}"); +var CMDLINEHANDLER_CONTRACTID = "@mozilla.org/commandlinehandler/general-startup;1?type=mail"; + +var nsMailNewsCommandLineHandler = +{ + get _messenger() { + delete this._messenger; + return this._messenger = Cc["@mozilla.org/messenger;1"] + .createInstance(Ci.nsIMessenger); + }, + + /* nsICommandLineHandler */ + + /** + * Handles the following command line arguments: + * - -mail: opens the mail folder view + * - -MapiStartup: indicates that this startup is due to MAPI. + * Don't do anything for now. + */ + handle: function nsMailNewsCommandLineHandler_handle(aCommandLine) { + // Do this here because xpcshell isn't too happy with this at startup + Components.utils.import("resource:///modules/MailUtils.js"); + // -mail <URL> + let mailURL = null; + try { + mailURL = aCommandLine.handleFlagWithParam("mail", false); + } + catch (e) { + // We're going to cover -mail without a parameter later + } + + if (mailURL && mailURL.length > 0) { + let msgHdr = null; + if (/^(mailbox|imap|news)-message:\/\//.test(mailURL)) { + // This might be a standard message URI, or one with a messageID + // parameter. Handle both cases. + let messageIDIndex = mailURL.toLowerCase().indexOf(MESSAGE_ID_PARAM); + if (messageIDIndex != -1) { + // messageID parameter + // Convert the message URI into a folder URI + let folderURI = mailURL.slice(0, messageIDIndex) + .replace("-message", ""); + // Get the message ID + let messageID = mailURL.slice(messageIDIndex + MESSAGE_ID_PARAM.length); + // Make sure the folder tree is initialized + MailUtils.discoverFolders(); + + let folder = MailUtils.getFolderForURI(folderURI, true); + // The folder might not exist, so guard against that + if (folder && messageID.length > 0) + msgHdr = folder.msgDatabase.getMsgHdrForMessageID(messageID); + } + else { + // message URI + msgHdr = this._messenger.msgHdrFromURI(mailURL); + } + } + else { + // Necko URL, so convert it into a message header + let neckoURL = null; + try { + neckoURL = Services.io.newURI(mailURL, null, null); + } + catch (e) { + // We failed to convert the URI. Oh well. + } + + if (neckoURL instanceof Ci.nsIMsgMessageUrl) + msgHdr = neckoURL.messageHeader; + } + + if (msgHdr) { + aCommandLine.preventDefault = true; + MailUtils.displayMessage(msgHdr); + } + else if (AppConstants.MOZ_APP_NAME == "seamonkey" && + /\.(eml|msg)$/i.test(mailURL)) { + try { + let file = aCommandLine.resolveFile(mailURL);
+ // No point in trying to open a file if it doesn't exist or is empty
+ if (file.exists() && file.fileSize > 0) {
+ // Get the URL for this file
+ let fileURL = Services.io.newFileURI(file)
+ .QueryInterface(Ci.nsIFileURL);
+ fileURL.query = "?type=application/x-message-display";
+ // Open this file in a new message window.
+ Services.ww.openWindow(null,
+ "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar",
+ fileURL);
+ aCommandLine.preventDefault = true; + } + } + catch (e) { + } + } + else { + dump("Unrecognized URL: " + mailURL + "\n"); + Services.console.logStringMessage("Unrecognized URL: " + mailURL); + } + } + + // -mail (no parameter) + let mailFlag = aCommandLine.handleFlag("mail", false); + if (mailFlag) { + // Focus the 3pane window if one is present, else open one + let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane"); + if (mail3PaneWindow) { + mail3PaneWindow.focus(); + } + else { + Services.ww.openWindow(null, "chrome://messenger/content/", "_blank", + "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar,dialog=no", + null); + } + aCommandLine.preventDefault = true; + } + + // -MapiStartup + aCommandLine.handleFlag(MAPI_STARTUP_ARG, false); + }, + + helpInfo: " -mail Open the mail folder view.\n" + + " -mail <URL> Open the message specified by this URL.\n", + + classInfo: XPCOMUtils.generateCI({classID: CMDLINEHANDLER_CID, + contractID: CMDLINEHANDLER_CONTRACTID, + interfaces: [Ci.nsICommandLineHandler], + flags: Ci.nsIClassInfo.SINGLETON}), + + /* nsIFactory */ + createInstance: function(outer, iid) { + if (outer != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + + return this.QueryInterface(iid); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler, + Ci.nsIFactory]) +}; + +function mailNewsCommandLineHandlerModule() {} +mailNewsCommandLineHandlerModule.prototype = +{ + // XPCOM registration + classID: CMDLINEHANDLER_CID, + + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIModule]), + + _xpcom_factory: nsMailNewsCommandLineHandler +}; + +var components = [mailNewsCommandLineHandlerModule]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/base/src/nsMessenger.cpp b/mailnews/base/src/nsMessenger.cpp new file mode 100644 index 0000000000..82fb5b68aa --- /dev/null +++ b/mailnews/base/src/nsMessenger.cpp @@ -0,0 +1,3072 @@ +/* -*- 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 "prsystem.h" + +#include "nsMessenger.h" + +// xpcom +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIStringStream.h" +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsQuickSort.h" +#include "nsNativeCharsetUtils.h" +#include "nsIMutableArray.h" +#include "mozilla/Services.h" + +// necko +#include "nsMimeTypes.h" +#include "nsIURL.h" +#include "nsIPrompt.h" +#include "nsIStreamListener.h" +#include "nsIStreamConverterService.h" +#include "nsNetUtil.h" +#include "nsIFileURL.h" +#include "nsIMIMEInfo.h" + +// rdf +#include "nsIRDFResource.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" + +// gecko +#include "nsLayoutCID.h" +#include "nsIContentViewer.h" + +// embedding +#ifdef NS_PRINTING +#include "nsIWebBrowserPrint.h" +#include "nsMsgPrintEngine.h" +#endif + +/* for access to docshell */ +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIDocShellTreeItem.h" +#include "nsIWebNavigation.h" + +// mail +#include "nsIMsgMailNewsUrl.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgMailSession.h" +#include "nsIMailboxUrl.h" +#include "nsIMsgFolder.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgIncomingServer.h" + +#include "nsIMsgMessageService.h" +#include "nsMsgRDFUtils.h" + +#include "nsIMsgHdr.h" +#include "nsIMimeMiscStatus.h" +// compose +#include "nsMsgCompCID.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" + +// draft/folders/sendlater/etc +#include "nsIMsgCopyService.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIUrlListener.h" + +// undo +#include "nsITransaction.h" +#include "nsMsgTxn.h" + +// charset conversions +#include "nsMsgMimeCID.h" +#include "nsIMimeConverter.h" + +// Save As +#include "nsIFilePicker.h" +#include "nsIStringBundle.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsCExternalHandlerService.h" +#include "nsIExternalProtocolService.h" +#include "nsIMIMEService.h" +#include "nsITransfer.h" + +#include "nsILinkHandler.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +#define MESSENGER_SAVE_DIR_PREF_NAME "messenger.save.dir" +#define MIMETYPE_DELETED "text/x-moz-deleted" +#define ATTACHMENT_PERMISSION 00664 + +// +// Convert an nsString buffer to plain text... +// +#include "nsMsgUtils.h" +#include "nsCharsetSource.h" +#include "nsIChannel.h" +#include "nsIOutputStream.h" +#include "nsIPrincipal.h" + +static void ConvertAndSanitizeFileName(const char * displayName, nsString& aResult) +{ + nsCString unescapedName; + + /* we need to convert the UTF-8 fileName to platform specific character set. + The display name is in UTF-8 because it has been escaped from JS + */ + MsgUnescapeString(nsDependentCString(displayName), 0, unescapedName); + CopyUTF8toUTF16(unescapedName, aResult); + + // replace platform specific path separator and illegale characters to avoid any confusion + MsgReplaceChar(aResult, FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-'); +} + +// *************************************************** +// jefft - this is a rather obscured class serves for Save Message As File, +// Save Message As Template, and Save Attachment to a file +// +class nsSaveAllAttachmentsState; + +class nsSaveMsgListener : public nsIUrlListener, + public nsIMsgCopyServiceListener, + public nsIStreamListener, + public nsICancelable +{ +public: + nsSaveMsgListener(nsIFile *file, nsMessenger *aMessenger, nsIUrlListener *aListener); + + NS_DECL_ISUPPORTS + + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGCOPYSERVICELISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSICANCELABLE + + nsCOMPtr<nsIFile> m_file; + nsCOMPtr<nsIOutputStream> m_outputStream; + char m_dataBuffer[FILE_IO_BUFFER_SIZE]; + nsCOMPtr<nsIChannel> m_channel; + nsCString m_templateUri; + nsMessenger *m_messenger; // not ref counted + nsSaveAllAttachmentsState *m_saveAllAttachmentsState; + + // rhp: For character set handling + bool m_doCharsetConversion; + nsString m_charset; + enum { + eUnknown, + ePlainText, + eHTML + } m_outputFormat; + nsCString m_msgBuffer; + + nsCString m_contentType; // used only when saving attachment + + nsCOMPtr<nsITransfer> mTransfer; + nsCOMPtr<nsIUrlListener> mListener; + nsCOMPtr<nsIURI> mListenerUri; + int64_t mProgress; + int64_t mMaxProgress; + bool mCanceled; + bool mInitialized; + bool mUrlHasStopped; + bool mRequestHasStopped; + nsresult InitializeDownload(nsIRequest * aRequest); + +private: + virtual ~nsSaveMsgListener(); +}; + +class nsSaveAllAttachmentsState +{ +public: + nsSaveAllAttachmentsState(uint32_t count, + const char **contentTypeArray, + const char **urlArray, + const char **displayNameArray, + const char **messageUriArray, + const char *directoryName, + bool detachingAttachments); + virtual ~nsSaveAllAttachmentsState(); + + uint32_t m_count; + uint32_t m_curIndex; + char* m_directoryName; + char** m_contentTypeArray; + char** m_urlArray; + char** m_displayNameArray; + char** m_messageUriArray; + bool m_detachingAttachments; + + // if detaching, do without warning? Will create unique files instead of + // prompting if duplicate files exist. + bool m_withoutWarning; + nsTArray<nsCString> m_savedFiles; // if detaching first, remember where we saved to. +}; + +// +// nsMessenger +// +nsMessenger::nsMessenger() +{ + mCurHistoryPos = -2; // first message selected goes at position 0. + // InitializeFolderRoot(); +} + +nsMessenger::~nsMessenger() +{ +} + + +NS_IMPL_ISUPPORTS(nsMessenger, nsIMessenger, nsISupportsWeakReference, nsIFolderListener) + +NS_IMETHODIMP nsMessenger::SetWindow(mozIDOMWindowProxy *aWin, nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (aWin) + { + mMsgWindow = aMsgWindow; + mWindow = aWin; + + rv = mailSession->AddFolderListener(this, nsIFolderListener::removed); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(aWin, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWin); + + nsIDocShell *docShell = win->GetDocShell(); + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(docShell)); + NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShellTreeItem> rootDocShellAsItem; + docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootDocShellAsItem)); + + nsCOMPtr<nsIDocShellTreeItem> childAsItem; + rv = rootDocShellAsItem->FindChildWithName(NS_LITERAL_STRING("messagepane"), true, false, + nullptr, nullptr, getter_AddRefs(childAsItem)); + + mDocShell = do_QueryInterface(childAsItem); + if (NS_SUCCEEDED(rv) && mDocShell) { + mCurrentDisplayCharset = ""; // Important! Clear out mCurrentDisplayCharset so we reset a default charset on mDocshell the next time we try to load something into it. + + if (aMsgWindow) + aMsgWindow->GetTransactionManager(getter_AddRefs(mTxnMgr)); + } + + // we don't always have a message pane, like in the addressbook + // so if we don't have a docshell, use the one for the xul window. + // we do this so OpenURL() will work. + if (!mDocShell) + mDocShell = docShell; + } // if aWin + else + { + // Remove the folder listener if we added it, i.e. if mWindow is non-null + if (mWindow) + { + rv = mailSession->RemoveFolderListener(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + mWindow = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMessenger::SetDisplayCharset(const nsACString& aCharset) +{ + // libmime always converts to UTF-8 (both HTML and XML) + if (mDocShell) + { + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) + { + cv->SetHintCharacterSet(aCharset); + cv->SetHintCharacterSetSource(kCharsetFromChannel); + + mCurrentDisplayCharset = aCharset; + } + } + + return NS_OK; +} + +nsresult +nsMessenger::PromptIfFileExists(nsIFile *file) +{ + nsresult rv = NS_ERROR_FAILURE; + bool exists; + file->Exists(&exists); + if (exists) + { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell)); + if (!dialog) return rv; + nsAutoString path; + bool dialogResult = false; + nsString errorMessage; + + file->GetPath(path); + const char16_t *pathFormatStrings[] = { path.get() }; + + if (!mStringBundle) + { + rv = InitStringBundle(); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = mStringBundle->FormatStringFromName(u"fileExists", + pathFormatStrings, 1, + getter_Copies(errorMessage)); + NS_ENSURE_SUCCESS(rv, rv); + rv = dialog->Confirm(nullptr, errorMessage.get(), &dialogResult); + NS_ENSURE_SUCCESS(rv, rv); + + if (dialogResult) + { + return NS_OK; // user says okay to replace + } + else + { + // if we don't re-init the path for redisplay the picker will + // show the full path, not just the file name + nsCOMPtr<nsIFile> currentFile = do_CreateInstance("@mozilla.org/file/local;1"); + if (!currentFile) return NS_ERROR_FAILURE; + + rv = currentFile->InitWithPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString leafName; + currentFile->GetLeafName(leafName); + if (!leafName.IsEmpty()) + path.Assign(leafName); // path should be a copy of leafName + + nsCOMPtr<nsIFilePicker> filePicker = + do_CreateInstance("@mozilla.org/filepicker;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsString saveAttachmentStr; + GetString(NS_LITERAL_STRING("SaveAttachment"), saveAttachmentStr); + filePicker->Init(mWindow, + saveAttachmentStr, + nsIFilePicker::modeSave); + filePicker->SetDefaultString(path); + filePicker->AppendFilters(nsIFilePicker::filterAll); + + nsCOMPtr <nsIFile> lastSaveDir; + rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir)); + if (NS_SUCCEEDED(rv) && lastSaveDir) { + filePicker->SetDisplayDirectory(lastSaveDir); + } + + int16_t dialogReturn; + rv = filePicker->Show(&dialogReturn); + if (NS_FAILED(rv) || dialogReturn == nsIFilePicker::returnCancel) { + // XXX todo + // don't overload the return value like this + // change this function to have an out boolean + // that we check to see if the user cancelled + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> localFile; + + rv = filePicker->GetFile(getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetLastSaveDirectory(localFile); + NS_ENSURE_SUCCESS(rv,rv); + + // reset the file to point to the new path + return file->InitWithFile(localFile); + } + } + else + { + return NS_OK; + } + return rv; +} + +NS_IMETHODIMP +nsMessenger::AddMsgUrlToNavigateHistory(const nsACString& aURL) +{ + // mNavigatingToUri is set to a url if we're already doing a back/forward, + // in which case we don't want to add the url to the history list. + // Or if the entry at the cur history pos is the same as what we're loading, don't + // add it to the list. + if (!mNavigatingToUri.Equals(aURL) && (mCurHistoryPos < 0 || !mLoadedMsgHistory[mCurHistoryPos].Equals(aURL))) + { + mNavigatingToUri = aURL; + nsCString curLoadedFolderUri; + nsCOMPtr <nsIMsgFolder> curLoadedFolder; + + mMsgWindow->GetOpenFolder(getter_AddRefs(curLoadedFolder)); + // for virtual folders, we want to select the right folder, + // which isn't the same as the folder specified in the msg uri. + // So add the uri for the currently loaded folder to the history list. + if (curLoadedFolder) + curLoadedFolder->GetURI(curLoadedFolderUri); + + mLoadedMsgHistory.InsertElementAt(mCurHistoryPos++ + 2, mNavigatingToUri); + mLoadedMsgHistory.InsertElementAt(mCurHistoryPos++ + 2, curLoadedFolderUri); + // we may want to prune this history if it gets large, but I think it's + // more interesting to prune the back and forward menu. + } + return NS_OK; +} + +NS_IMETHODIMP +nsMessenger::OpenURL(const nsACString& aURL) +{ + // This is to setup the display DocShell as UTF-8 capable... + SetDisplayCharset(NS_LITERAL_CSTRING("UTF-8")); + + nsCOMPtr <nsIMsgMessageService> messageService; + nsresult rv = GetMessageServiceFromURI(aURL, getter_AddRefs(messageService)); + + if (NS_SUCCEEDED(rv) && messageService) + { + nsCOMPtr<nsIURI> dummyNull; + messageService->DisplayMessage(PromiseFlatCString(aURL).get(), mDocShell, + mMsgWindow, nullptr, nullptr, getter_AddRefs(dummyNull)); + AddMsgUrlToNavigateHistory(aURL); + mLastDisplayURI = aURL; // remember the last uri we displayed.... + return NS_OK; + } + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + if(!webNav) + return NS_ERROR_FAILURE; + rv = webNav->LoadURI(NS_ConvertASCIItoUTF16(aURL).get(), // URI string + nsIWebNavigation::LOAD_FLAGS_IS_LINK, // Load flags + nullptr, // Referring URI + nullptr, // Post stream + nullptr); // Extra headers + return rv; +} + +NS_IMETHODIMP nsMessenger::LaunchExternalURL(const nsACString& aURL) +{ + nsresult rv; + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), PromiseFlatCString(aURL).get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIExternalProtocolService> extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + return extProtService->LoadUrl(uri); +} + +NS_IMETHODIMP +nsMessenger::LoadURL(mozIDOMWindowProxy *aWin, const nsACString& aURL) +{ + nsresult rv; + + SetDisplayCharset(NS_LITERAL_CSTRING("UTF-8")); + + NS_ConvertASCIItoUTF16 uriString(aURL); + // Cleanup the empty spaces that might be on each end. + uriString.Trim(" "); + // Eliminate embedded newlines, which single-line text fields now allow: + uriString.StripChars("\r\n"); + NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE); + + bool loadingFromFile = false; + bool getDummyMsgHdr = false; + int64_t fileSize; + + if (StringBeginsWith(uriString, NS_LITERAL_STRING("file:"))) + { + nsCOMPtr<nsIURI> fileUri; + rv = NS_NewURI(getter_AddRefs(fileUri), uriString); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFile> file; + rv = fileUrl->GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + file->GetFileSize(&fileSize); + uriString.Replace(0, 5, NS_LITERAL_STRING("mailbox:")); + uriString.Append(NS_LITERAL_STRING("&number=0")); + loadingFromFile = true; + getDummyMsgHdr = true; + } + else if (StringBeginsWith(uriString, NS_LITERAL_STRING("mailbox:")) && + (CaseInsensitiveFindInReadable(NS_LITERAL_STRING(".eml?"), uriString))) + { + // if we have a mailbox:// url that points to an .eml file, we have to read + // the file size as well + uriString.Replace(0, 8, NS_LITERAL_STRING("file:")); + nsCOMPtr<nsIURI> fileUri; + rv = NS_NewURI(getter_AddRefs(fileUri), uriString); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFileURL> fileUrl = do_QueryInterface(fileUri, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFile> file; + rv = fileUrl->GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + file->GetFileSize(&fileSize); + uriString.Replace(0, 5, NS_LITERAL_STRING("mailbox:")); + loadingFromFile = true; + getDummyMsgHdr = true; + } + else if (uriString.Find("type=application/x-message-display") >= 0) + getDummyMsgHdr = true; + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), uriString); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + nsCOMPtr<nsIMsgMailNewsUrl> msgurl = do_QueryInterface(uri); + if (msgurl) + { + msgurl->SetMsgWindow(mMsgWindow); + if (loadingFromFile || getDummyMsgHdr) + { + if (loadingFromFile) + { + nsCOMPtr <nsIMailboxUrl> mailboxUrl = do_QueryInterface(msgurl, &rv); + mailboxUrl->SetMessageSize((uint32_t) fileSize); + } + if (getDummyMsgHdr) + { + nsCOMPtr <nsIMsgHeaderSink> headerSink; + // need to tell the header sink to capture some headers to create a fake db header + // so we can do reply to a .eml file or a rfc822 msg attachment. + mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + { + nsCOMPtr <nsIMsgDBHdr> dummyHeader; + headerSink->GetDummyMsgHeader(getter_AddRefs(dummyHeader)); + if (dummyHeader && loadingFromFile) + dummyHeader->SetMessageSize((uint32_t) fileSize); + } + } + } + } + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + rv = mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_SUCCESS(rv, rv); + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormal); + AddMsgUrlToNavigateHistory(aURL); + mNavigatingToUri.Truncate(); + mLastDisplayURI = aURL; // Remember the last uri we displayed. + return mDocShell->LoadURI(uri, loadInfo, 0, true); +} + +NS_IMETHODIMP nsMessenger::SaveAttachmentToFile(nsIFile *aFile, + const nsACString &aURL, + const nsACString &aMessageUri, + const nsACString &aContentType, + nsIUrlListener *aListener) +{ + return SaveAttachment(aFile, aURL, aMessageUri, aContentType, nullptr, aListener); +} + +NS_IMETHODIMP +nsMessenger::DetachAttachmentsWOPrompts(nsIFile* aDestFolder, + uint32_t aCount, + const char **aContentTypeArray, + const char **aUrlArray, + const char **aDisplayNameArray, + const char **aMessageUriArray, + nsIUrlListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aDestFolder); + NS_ENSURE_ARG_POINTER(aContentTypeArray); + NS_ENSURE_ARG_POINTER(aUrlArray); + NS_ENSURE_ARG_POINTER(aMessageUriArray); + NS_ENSURE_ARG_POINTER(aDisplayNameArray); + if (!aCount) + return NS_OK; + nsSaveAllAttachmentsState *saveState; + nsCOMPtr<nsIFile> attachmentDestination; + nsresult rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString path; + rv = attachmentDestination->GetNativePath(path); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString unescapedFileName; + ConvertAndSanitizeFileName(aDisplayNameArray[0], unescapedFileName); + rv = attachmentDestination->Append(unescapedFileName); + NS_ENSURE_SUCCESS(rv, rv); + rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION); + NS_ENSURE_SUCCESS(rv, rv); + + saveState = new nsSaveAllAttachmentsState(aCount, + aContentTypeArray, + aUrlArray, + aDisplayNameArray, + aMessageUriArray, + path.get(), + true); + + // This method is used in filters, where we don't want to warn + saveState->m_withoutWarning = true; + rv = SaveAttachment(attachmentDestination, + nsDependentCString(aUrlArray[0]), + nsDependentCString(aMessageUriArray[0]), + nsDependentCString(aContentTypeArray[0]), + (void *)saveState, + aListener); + return rv; +} + +nsresult nsMessenger::SaveAttachment(nsIFile *aFile, + const nsACString &aURL, + const nsACString &aMessageUri, + const nsACString &aContentType, + void *closure, + nsIUrlListener *aListener) +{ + nsCOMPtr<nsIMsgMessageService> messageService; + nsSaveAllAttachmentsState *saveState= (nsSaveAllAttachmentsState*) closure; + nsCOMPtr<nsIMsgMessageFetchPartService> fetchService; + nsAutoCString urlString; + nsCOMPtr<nsIURI> URL; + nsAutoCString fullMessageUri(aMessageUri); + + // This instance will be held onto by the listeners, and will be released once + // the transfer has been completed. + RefPtr<nsSaveMsgListener> saveListener(new nsSaveMsgListener(aFile, this, aListener)); + if (!saveListener) + return NS_ERROR_OUT_OF_MEMORY; + + saveListener->m_contentType = aContentType; + if (saveState) + { + saveListener->m_saveAllAttachmentsState = saveState; + if (saveState->m_detachingAttachments) + { + nsCOMPtr<nsIURI> outputURI; + nsresult rv = NS_NewFileURI(getter_AddRefs(outputURI), aFile); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString fileUriSpec; + rv = outputURI->GetSpec(fileUriSpec); + NS_ENSURE_SUCCESS(rv, rv); + saveState->m_savedFiles.AppendElement(fileUriSpec); + } + } + + urlString = aURL; + // strip out ?type=application/x-message-display because it confuses libmime + + int32_t typeIndex = urlString.Find("?type=application/x-message-display"); + if (typeIndex != kNotFound) + { + urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1); + // we also need to replace the next '&' with '?' + int32_t firstPartIndex = urlString.FindChar('&'); + if (firstPartIndex != kNotFound) + urlString.SetCharAt('?', firstPartIndex); + } + + MsgReplaceSubstring(urlString, "/;section", "?section"); + nsresult rv = CreateStartupUrl(urlString.get(), getter_AddRefs(URL)); + + if (NS_SUCCEEDED(rv)) + { + rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService)); + if (NS_SUCCEEDED(rv)) + { + fetchService = do_QueryInterface(messageService); + // if the message service has a fetch part service then we know we can fetch mime parts... + if (fetchService) + { + int32_t partPos = urlString.FindChar('?'); + if (partPos == kNotFound) + return NS_ERROR_FAILURE; + fullMessageUri.Append(Substring(urlString, partPos)); + } + + nsCOMPtr<nsIStreamListener> convertedListener; + saveListener->QueryInterface(NS_GET_IID(nsIStreamListener), + getter_AddRefs(convertedListener)); + +#ifndef XP_MACOSX + // if the content type is bin hex we are going to do a hokey hack and make sure we decode the bin hex + // when saving an attachment to disk.. + if (MsgLowerCaseEqualsLiteral(aContentType, APPLICATION_BINHEX)) + { + nsCOMPtr<nsIStreamListener> listener (do_QueryInterface(convertedListener)); + nsCOMPtr<nsIStreamConverterService> streamConverterService = do_GetService("@mozilla.org/streamConverters;1", &rv); + nsCOMPtr<nsISupports> channelSupport = do_QueryInterface(saveListener->m_channel); + + rv = streamConverterService->AsyncConvertData(APPLICATION_BINHEX, + "*/*", + listener, + channelSupport, + getter_AddRefs(convertedListener)); + } +#endif + nsCOMPtr<nsIURI> dummyNull; + if (fetchService) + rv = fetchService->FetchMimePart(URL, fullMessageUri.get(), + convertedListener, mMsgWindow, + saveListener, getter_AddRefs(dummyNull)); + else + rv = messageService->DisplayMessage(fullMessageUri.get(), + convertedListener, mMsgWindow, + nullptr, nullptr, + getter_AddRefs(dummyNull)); + } // if we got a message service + } // if we created a url + + if (NS_FAILED(rv)) + Alert("saveAttachmentFailed"); + + return rv; +} + +NS_IMETHODIMP +nsMessenger::OpenAttachment(const nsACString& aContentType, const nsACString& aURL, + const nsACString& aDisplayName, const nsACString& aMessageUri, bool aIsExternalAttachment) +{ + nsresult rv = NS_OK; + + // open external attachments inside our message pane which in turn should trigger the + // helper app dialog... + if (aIsExternalAttachment) + rv = OpenURL(aURL); + else + { + nsCOMPtr <nsIMsgMessageService> messageService; + rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService)); + if (messageService) + rv = messageService->OpenAttachment(PromiseFlatCString(aContentType).get(), PromiseFlatCString(aDisplayName).get(), + PromiseFlatCString(aURL).get(), PromiseFlatCString(aMessageUri).get(), + mDocShell, mMsgWindow, nullptr); + } + + return rv; +} + +NS_IMETHODIMP +nsMessenger::SaveAttachmentToFolder(const nsACString& contentType, const nsACString& url, const nsACString& displayName, + const nsACString& messageUri, nsIFile * aDestFolder, nsIFile ** aOutFile) +{ + NS_ENSURE_ARG_POINTER(aDestFolder); + nsresult rv; + + nsCOMPtr<nsIFile> attachmentDestination; + rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString unescapedFileName; + ConvertAndSanitizeFileName(PromiseFlatCString(displayName).get(), unescapedFileName); + rv = attachmentDestination->Append(unescapedFileName); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef XP_MACOSX + rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION); + NS_ENSURE_SUCCESS(rv, rv); +#endif + + rv = SaveAttachment(attachmentDestination, url, messageUri, contentType, nullptr, nullptr); + attachmentDestination.swap(*aOutFile); + return rv; +} + +NS_IMETHODIMP +nsMessenger::SaveAttachment(const nsACString& aContentType, const nsACString& aURL, + const nsACString& aDisplayName, const nsACString& aMessageUri, bool aIsExternalAttachment) +{ + // open external attachments inside our message pane which in turn should trigger the + // helper app dialog... + if (aIsExternalAttachment) + return OpenURL(aURL); + return SaveOneAttachment(PromiseFlatCString(aContentType).get(), + PromiseFlatCString(aURL).get(), + PromiseFlatCString(aDisplayName).get(), + PromiseFlatCString(aMessageUri).get(), + false); +} + +nsresult +nsMessenger::SaveOneAttachment(const char * aContentType, const char * aURL, + const char * aDisplayName, const char * aMessageUri, + bool detaching) +{ + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr<nsIFilePicker> filePicker = + do_CreateInstance("@mozilla.org/filepicker;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + int16_t dialogResult; + nsCOMPtr<nsIFile> localFile; + nsCOMPtr<nsIFile> lastSaveDir; + nsCString filePath; + nsString saveAttachmentStr; + nsString defaultDisplayString; + ConvertAndSanitizeFileName(aDisplayName, defaultDisplayString); + + GetString(NS_LITERAL_STRING("SaveAttachment"), saveAttachmentStr); + filePicker->Init(mWindow, saveAttachmentStr, + nsIFilePicker::modeSave); + filePicker->SetDefaultString(defaultDisplayString); + + // Check if the attachment file name has an extension (which must not + // contain spaces) and set it as the default extension for the attachment. + int32_t extensionIndex = defaultDisplayString.RFindChar('.'); + if (extensionIndex > 0 && + defaultDisplayString.FindChar(' ', extensionIndex) == kNotFound) + { + nsString extension; + extension = Substring(defaultDisplayString, extensionIndex + 1); + filePicker->SetDefaultExtension(extension); + if (!mStringBundle) + { + rv = InitStringBundle(); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsString filterName; + const char16_t *extensionParam[] = { extension.get() }; + rv = mStringBundle->FormatStringFromName( + u"saveAsType", extensionParam, 1, getter_Copies(filterName)); + NS_ENSURE_SUCCESS(rv, rv); + + extension.Insert(NS_LITERAL_STRING("*."), 0); + filePicker->AppendFilter(filterName, extension); + } + + filePicker->AppendFilters(nsIFilePicker::filterAll); + + rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir)); + if (NS_SUCCEEDED(rv) && lastSaveDir) + filePicker->SetDisplayDirectory(lastSaveDir); + + rv = filePicker->Show(&dialogResult); + if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) + return rv; + + rv = filePicker->GetFile(getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + SetLastSaveDirectory(localFile); + + nsCString dirName; + rv = localFile->GetNativePath(dirName); + NS_ENSURE_SUCCESS(rv, rv); + + nsSaveAllAttachmentsState *saveState = + new nsSaveAllAttachmentsState(1, + &aContentType, + &aURL, + &aDisplayName, + &aMessageUri, + dirName.get(), + detaching); + + return SaveAttachment(localFile, nsDependentCString(aURL), nsDependentCString(aMessageUri), + nsDependentCString(aContentType), (void *)saveState, nullptr); +} + + +NS_IMETHODIMP +nsMessenger::SaveAllAttachments(uint32_t count, + const char **contentTypeArray, + const char **urlArray, + const char **displayNameArray, + const char **messageUriArray) +{ + if (!count) + return NS_ERROR_INVALID_ARG; + return SaveAllAttachments(count, contentTypeArray, urlArray, displayNameArray, messageUriArray, false); +} + +nsresult +nsMessenger::SaveAllAttachments(uint32_t count, + const char **contentTypeArray, + const char **urlArray, + const char **displayNameArray, + const char **messageUriArray, + bool detaching) +{ + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr<nsIFilePicker> filePicker = + do_CreateInstance("@mozilla.org/filepicker;1", &rv); + nsCOMPtr<nsIFile> localFile; + nsCOMPtr<nsIFile> lastSaveDir; + int16_t dialogResult; + nsString saveAttachmentStr; + + NS_ENSURE_SUCCESS(rv, rv); + GetString(NS_LITERAL_STRING("SaveAllAttachments"), saveAttachmentStr); + filePicker->Init(mWindow, + saveAttachmentStr, + nsIFilePicker::modeGetFolder); + + rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir)); + if (NS_SUCCEEDED(rv) && lastSaveDir) + filePicker->SetDisplayDirectory(lastSaveDir); + + rv = filePicker->Show(&dialogResult); + if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) + return rv; + + rv = filePicker->GetFile(getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetLastSaveDirectory(localFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString dirName; + nsSaveAllAttachmentsState *saveState = nullptr; + rv = localFile->GetNativePath(dirName); + NS_ENSURE_SUCCESS(rv, rv); + + saveState = new nsSaveAllAttachmentsState(count, + contentTypeArray, + urlArray, + displayNameArray, + messageUriArray, + dirName.get(), + detaching); + nsString unescapedName; + ConvertAndSanitizeFileName(displayNameArray[0], unescapedName); + rv = localFile->Append(unescapedName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = PromptIfFileExists(localFile); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SaveAttachment(localFile, nsDependentCString(urlArray[0]), nsDependentCString(messageUriArray[0]), + nsDependentCString(contentTypeArray[0]), (void *)saveState, nullptr); + return rv; +} + +enum MESSENGER_SAVEAS_FILE_TYPE +{ + EML_FILE_TYPE = 0, + HTML_FILE_TYPE = 1, + TEXT_FILE_TYPE = 2, + ANY_FILE_TYPE = 3 +}; +#define HTML_FILE_EXTENSION ".htm" +#define HTML_FILE_EXTENSION2 ".html" +#define TEXT_FILE_EXTENSION ".txt" + +/** + * Adjust the file name, removing characters from the middle of the name if + * the name would otherwise be too long - too long for what file systems + * usually support. + */ +nsresult nsMessenger::AdjustFileIfNameTooLong(nsIFile* aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + nsAutoString path; + nsresult rv = aFile->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + // Most common file systems have a max filename length of 255. On windows, the + // total path length is (at least for all practical purposees) limited to 255. + // Let's just don't allow paths longer than that elsewhere either for + // simplicity. + uint32_t MAX = 255; + if (path.Length() > MAX) { + nsAutoString leafName; + rv = aFile->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t pathLengthUpToLeaf = path.Length() - leafName.Length(); + if (pathLengthUpToLeaf >= MAX - 8) { // want at least 8 chars for name + return NS_ERROR_FILE_NAME_TOO_LONG; + } + uint32_t x = MAX - pathLengthUpToLeaf; // x = max leaf size + nsAutoString truncatedLeaf; + truncatedLeaf.Append(Substring(leafName, 0, x/2)); + truncatedLeaf.AppendLiteral("..."); + truncatedLeaf.Append(Substring(leafName, leafName.Length() - x/2 + 3, + leafName.Length())); + rv = aFile->SetLeafName(truncatedLeaf); + } + return rv; +} + +NS_IMETHODIMP +nsMessenger::SaveAs(const nsACString& aURI, bool aAsFile, + nsIMsgIdentity *aIdentity, const nsAString& aMsgFilename, + bool aBypassFilePicker) +{ + nsCOMPtr<nsIMsgMessageService> messageService; + nsCOMPtr<nsIUrlListener> urlListener; + nsSaveMsgListener *saveListener = nullptr; + nsCOMPtr<nsIURI> url; + nsCOMPtr<nsIStreamListener> convertedListener; + int32_t saveAsFileType = EML_FILE_TYPE; + + nsresult rv = GetMessageServiceFromURI(aURI, getter_AddRefs(messageService)); + if (NS_FAILED(rv)) + goto done; + + if (aAsFile) + { + nsCOMPtr<nsIFile> saveAsFile; + // show the file picker if BypassFilePicker is not specified (null) or false + if (!aBypassFilePicker) { + rv = GetSaveAsFile(aMsgFilename, &saveAsFileType, getter_AddRefs(saveAsFile)); + // A null saveAsFile means that the user canceled the save as + if (NS_FAILED(rv) || !saveAsFile) + goto done; + } + else { + saveAsFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + rv = saveAsFile->InitWithPath(aMsgFilename); + if (NS_FAILED(rv)) + goto done; + if (StringEndsWith(aMsgFilename, NS_LITERAL_STRING(TEXT_FILE_EXTENSION), + nsCaseInsensitiveStringComparator())) + saveAsFileType = TEXT_FILE_TYPE; + else if ((StringEndsWith(aMsgFilename, + NS_LITERAL_STRING(HTML_FILE_EXTENSION), + nsCaseInsensitiveStringComparator())) || + (StringEndsWith(aMsgFilename, + NS_LITERAL_STRING(HTML_FILE_EXTENSION2), + nsCaseInsensitiveStringComparator()))) + saveAsFileType = HTML_FILE_TYPE; + else + saveAsFileType = EML_FILE_TYPE; + } + + rv = AdjustFileIfNameTooLong(saveAsFile); + NS_ENSURE_SUCCESS(rv, rv); + + rv = PromptIfFileExists(saveAsFile); + if (NS_FAILED(rv)) { + goto done; + } + + // After saveListener goes out of scope, the listener will be owned by + // whoever the listener is registered with, usually a URL. + RefPtr<nsSaveMsgListener> saveListener = new nsSaveMsgListener(saveAsFile, this, nullptr); + if (!saveListener) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto done; + } + rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener)); + if (NS_FAILED(rv)) + goto done; + + if (saveAsFileType == EML_FILE_TYPE) + { + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->SaveMessageToDisk(PromiseFlatCString(aURI).get(), saveAsFile, false, + urlListener, getter_AddRefs(dummyNull), + true, mMsgWindow); + } + else + { + nsAutoCString urlString(aURI); + + // we can't go RFC822 to TXT until bug #1775 is fixed + // so until then, do the HTML to TXT conversion in + // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText() + // + // Setup the URL for a "Save As..." Operation... + // For now, if this is a save as TEXT operation, then do + // a "printing" operation + if (saveAsFileType == TEXT_FILE_TYPE) + { + saveListener->m_outputFormat = nsSaveMsgListener::ePlainText; + saveListener->m_doCharsetConversion = true; + urlString.AppendLiteral("?header=print"); + } + else + { + saveListener->m_outputFormat = nsSaveMsgListener::eHTML; + saveListener->m_doCharsetConversion = false; + urlString.AppendLiteral("?header=saveas"); + } + + rv = CreateStartupUrl(urlString.get(), getter_AddRefs(url)); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateStartupUrl failed"); + if (NS_FAILED(rv)) + goto done; + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed"); + if (NS_FAILED(rv)) + goto done; + + saveListener->m_channel = nullptr; + rv = NS_NewInputStreamChannel(getter_AddRefs(saveListener->m_channel), + url, + nullptr, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed"); + if (NS_FAILED(rv)) + goto done; + + nsCOMPtr<nsIStreamConverterService> streamConverterService = do_GetService("@mozilla.org/streamConverters;1"); + nsCOMPtr<nsISupports> channelSupport = do_QueryInterface(saveListener->m_channel); + + // we can't go RFC822 to TXT until bug #1775 is fixed + // so until then, do the HTML to TXT conversion in + // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText() + rv = streamConverterService->AsyncConvertData(MESSAGE_RFC822, + TEXT_HTML, + saveListener, + channelSupport, + getter_AddRefs(convertedListener)); + NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncConvertData failed"); + if (NS_FAILED(rv)) + goto done; + + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->DisplayMessage(urlString.get(), convertedListener, mMsgWindow, + nullptr, nullptr, getter_AddRefs(dummyNull)); + } + } + else + { + // ** save as Template + nsCOMPtr <nsIFile> tmpFile; + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "nsmail.tmp", + getter_AddRefs(tmpFile)); + + NS_ENSURE_SUCCESS(rv, rv); + + // For temp file, we should use restrictive 00600 instead of ATTACHMENT_PERMISSION + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) goto done; + + // The saveListener is owned by whoever we ultimately register the + // listener with, generally a URL. + saveListener = new nsSaveMsgListener(tmpFile, this, nullptr); + if (!saveListener) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto done; + } + + if (aIdentity) + rv = aIdentity->GetStationeryFolder(saveListener->m_templateUri); + if (NS_FAILED(rv)) + goto done; + + bool needDummyHeader = StringBeginsWith(saveListener->m_templateUri, NS_LITERAL_CSTRING("mailbox://")); + bool canonicalLineEnding = StringBeginsWith(saveListener->m_templateUri, NS_LITERAL_CSTRING("imap://")); + + rv = saveListener->QueryInterface( + NS_GET_IID(nsIUrlListener), + getter_AddRefs(urlListener)); + if (NS_FAILED(rv)) + goto done; + + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->SaveMessageToDisk(PromiseFlatCString(aURI).get(), tmpFile, + needDummyHeader, + urlListener, getter_AddRefs(dummyNull), + canonicalLineEnding, mMsgWindow); + } + +done: + if (NS_FAILED(rv)) + { + NS_IF_RELEASE(saveListener); + Alert("saveMessageFailed"); + } + return rv; +} + +nsresult +nsMessenger::GetSaveAsFile(const nsAString& aMsgFilename, int32_t *aSaveAsFileType, + nsIFile **aSaveAsFile) +{ + nsresult rv; + nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsString saveMailAsStr; + GetString(NS_LITERAL_STRING("SaveMailAs"), saveMailAsStr); + filePicker->Init(mWindow, saveMailAsStr, nsIFilePicker::modeSave); + + // if we have a non-null filename use it, otherwise use default save message one + if (aMsgFilename.IsEmpty()) + { + nsString saveMsgStr; + GetString(NS_LITERAL_STRING("defaultSaveMessageAsFileName"), saveMsgStr); + filePicker->SetDefaultString(saveMsgStr); + } + else + { + filePicker->SetDefaultString(aMsgFilename); + } + + // because we will be using GetFilterIndex() + // we must call AppendFilters() one at a time, + // in MESSENGER_SAVEAS_FILE_TYPE order + nsString emlFilesStr; + GetString(NS_LITERAL_STRING("EMLFiles"), emlFilesStr); + filePicker->AppendFilter(emlFilesStr, + NS_LITERAL_STRING("*.eml")); + filePicker->AppendFilters(nsIFilePicker::filterHTML); + filePicker->AppendFilters(nsIFilePicker::filterText); + filePicker->AppendFilters(nsIFilePicker::filterAll); + + // Save as the "All Files" file type by default. We want to save as .eml by + // default, but the filepickers on some platforms don't switch extensions + // based on the file type selected (bug 508597). + filePicker->SetFilterIndex(ANY_FILE_TYPE); + // Yes, this is fine even if we ultimately save as HTML or text. On Windows, + // this actually is a boolean telling the file picker to automatically add + // the correct extension depending on the filter. On Mac or Linux this is a + // no-op. + filePicker->SetDefaultExtension(NS_LITERAL_STRING("eml")); + + int16_t dialogResult; + + nsCOMPtr <nsIFile> lastSaveDir; + rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir)); + if (NS_SUCCEEDED(rv) && lastSaveDir) + filePicker->SetDisplayDirectory(lastSaveDir); + + nsCOMPtr<nsIFile> localFile; + rv = filePicker->Show(&dialogResult); + NS_ENSURE_SUCCESS(rv, rv); + if (dialogResult == nsIFilePicker::returnCancel) + { + // We'll indicate this by setting the outparam to null. + *aSaveAsFile = nullptr; + return NS_OK; + } + + rv = filePicker->GetFile(getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetLastSaveDirectory(localFile); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t selectedSaveAsFileType; + rv = filePicker->GetFilterIndex(&selectedSaveAsFileType); + NS_ENSURE_SUCCESS(rv, rv); + + // If All Files was selected, look at the extension + if (selectedSaveAsFileType == ANY_FILE_TYPE) + { + nsAutoString fileName; + rv = localFile->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + if (StringEndsWith(fileName, NS_LITERAL_STRING(HTML_FILE_EXTENSION), + nsCaseInsensitiveStringComparator()) || + StringEndsWith(fileName, NS_LITERAL_STRING(HTML_FILE_EXTENSION2), + nsCaseInsensitiveStringComparator())) + *aSaveAsFileType = HTML_FILE_TYPE; + else if (StringEndsWith(fileName, NS_LITERAL_STRING(TEXT_FILE_EXTENSION), + nsCaseInsensitiveStringComparator())) + *aSaveAsFileType = TEXT_FILE_TYPE; + else + // The default is .eml + *aSaveAsFileType = EML_FILE_TYPE; + } + else + { + *aSaveAsFileType = selectedSaveAsFileType; + } + + if (dialogResult == nsIFilePicker::returnReplace) + { + // be extra safe and only delete when the file is really a file + bool isFile; + rv = localFile->IsFile(&isFile); + if (NS_SUCCEEDED(rv) && isFile) + { + rv = localFile->Remove(false /* recursive delete */); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + // We failed, or this isn't a file. We can't do anything about it. + return NS_ERROR_FAILURE; + } + } + + *aSaveAsFile = nullptr; + localFile.swap(*aSaveAsFile); + return NS_OK; +} + +/** + * Show a Save All dialog allowing the user to pick which folder to save + * messages to. + * @param [out] aSaveDir directory to save to. Will be null on cancel. + */ +nsresult +nsMessenger::GetSaveToDir(nsIFile **aSaveDir) +{ + nsresult rv; + nsCOMPtr<nsIFilePicker> filePicker = + do_CreateInstance("@mozilla.org/filepicker;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsString chooseFolderStr; + GetString(NS_LITERAL_STRING("ChooseFolder"), chooseFolderStr); + filePicker->Init(mWindow, chooseFolderStr, nsIFilePicker::modeGetFolder); + + nsCOMPtr<nsIFile> lastSaveDir; + rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir)); + if (NS_SUCCEEDED(rv) && lastSaveDir) + filePicker->SetDisplayDirectory(lastSaveDir); + + int16_t dialogResult; + rv = filePicker->Show(&dialogResult); + if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) + { + // We'll indicate this by setting the outparam to null. + *aSaveDir = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIFile> dir; + rv = filePicker->GetFile(getter_AddRefs(dir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetLastSaveDirectory(dir); + NS_ENSURE_SUCCESS(rv, rv); + + *aSaveDir = nullptr; + dir.swap(*aSaveDir); + return NS_OK; +} + +NS_IMETHODIMP +nsMessenger::SaveMessages(uint32_t aCount, + const char16_t **aFilenameArray, + const char **aMessageUriArray) +{ + NS_ENSURE_ARG_MIN(aCount, 1); + NS_ENSURE_ARG_POINTER(aFilenameArray); + NS_ENSURE_ARG_POINTER(aMessageUriArray); + + nsresult rv; + + nsCOMPtr<nsIFile> saveDir; + rv = GetSaveToDir(getter_AddRefs(saveDir)); + NS_ENSURE_SUCCESS(rv, rv); + if (!saveDir) // A null saveDir means that the user canceled the save. + return NS_OK; + + for (uint32_t i = 0; i < aCount; i++) { + if (!aFilenameArray[i]) // just to be sure + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> saveToFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = saveToFile->InitWithFile(saveDir); + NS_ENSURE_SUCCESS(rv, rv); + + rv = saveToFile->Append(nsDependentString(aFilenameArray[i])); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AdjustFileIfNameTooLong(saveToFile); + NS_ENSURE_SUCCESS(rv, rv); + + rv = PromptIfFileExists(saveToFile); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr<nsIMsgMessageService> messageService; + nsCOMPtr<nsIUrlListener> urlListener; + + rv = GetMessageServiceFromURI(nsDependentCString(aMessageUriArray[i]), + getter_AddRefs(messageService)); + if (NS_FAILED(rv)) { + Alert("saveMessageFailed"); + return rv; + } + + nsSaveMsgListener *saveListener = new nsSaveMsgListener(saveToFile, this, nullptr); + if (!saveListener) { + NS_IF_RELEASE(saveListener); + Alert("saveMessageFailed"); + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(saveListener); + + rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener), + getter_AddRefs(urlListener)); + if (NS_FAILED(rv)) { + NS_IF_RELEASE(saveListener); + Alert("saveMessageFailed"); + return rv; + } + + // Ok, now save the message. + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->SaveMessageToDisk(aMessageUriArray[i], + saveToFile, false, + urlListener, getter_AddRefs(dummyNull), + true, mMsgWindow); + if (NS_FAILED(rv)) { + NS_IF_RELEASE(saveListener); + Alert("saveMessageFailed"); + return rv; + } + } + return rv; +} + +nsresult +nsMessenger::Alert(const char *stringName) +{ + nsresult rv = NS_OK; + + if (mDocShell) + { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell)); + + if (dialog) + { + nsString alertStr; + GetString(NS_ConvertASCIItoUTF16(stringName), alertStr); + rv = dialog->Alert(nullptr, alertStr.get()); + } + } + return rv; +} + +NS_IMETHODIMP +nsMessenger::MessageServiceFromURI(const nsACString& aUri, nsIMsgMessageService **aMsgService) +{ + NS_ENSURE_ARG_POINTER(aMsgService); + return GetMessageServiceFromURI(aUri, aMsgService); +} + +NS_IMETHODIMP +nsMessenger::MsgHdrFromURI(const nsACString& aUri, nsIMsgDBHdr **aMsgHdr) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + nsCOMPtr <nsIMsgMessageService> msgService; + nsresult rv; + + + if (mMsgWindow && + (StringBeginsWith(aUri, NS_LITERAL_CSTRING("file:")) || + PromiseFlatCString(aUri).Find("type=application/x-message-display") >= 0)) + { + nsCOMPtr <nsIMsgHeaderSink> headerSink; + mMsgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + { + rv = headerSink->GetDummyMsgHeader(aMsgHdr); + // Is there a way to check if they're asking for the hdr currently + // displayed in a stand-alone msg window from a .eml file? + // (pretty likely if this is a file: uri) + return rv; + } + } + + rv = GetMessageServiceFromURI(aUri, getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, rv); + return msgService->MessageURIToMsgHdr(PromiseFlatCString(aUri).get(), aMsgHdr); +} + +NS_IMETHODIMP nsMessenger::GetUndoTransactionType(uint32_t *txnType) +{ + NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER); + + nsresult rv; + *txnType = nsMessenger::eUnknown; + nsCOMPtr<nsITransaction> txn; + rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn)); + if (NS_SUCCEEDED(rv) && txn) + { + nsCOMPtr <nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return propertyBag->GetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType); + } + return rv; +} + +NS_IMETHODIMP nsMessenger::CanUndo(bool *bValue) +{ + NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER); + + nsresult rv; + *bValue = false; + int32_t count = 0; + rv = mTxnMgr->GetNumberOfUndoItems(&count); + if (NS_SUCCEEDED(rv) && count > 0) + *bValue = true; + return rv; +} + +NS_IMETHODIMP nsMessenger::GetRedoTransactionType(uint32_t *txnType) +{ + NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER); + + nsresult rv; + *txnType = nsMessenger::eUnknown; + nsCOMPtr<nsITransaction> txn; + rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn)); + if (NS_SUCCEEDED(rv) && txn) + { + nsCOMPtr <nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return propertyBag->GetPropertyAsUint32(NS_LITERAL_STRING("type"), txnType); + } + return rv; +} + +NS_IMETHODIMP nsMessenger::CanRedo(bool *bValue) +{ + NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER); + + nsresult rv; + *bValue = false; + int32_t count = 0; + rv = mTxnMgr->GetNumberOfRedoItems(&count); + if (NS_SUCCEEDED(rv) && count > 0) + *bValue = true; + return rv; +} + +NS_IMETHODIMP +nsMessenger::Undo(nsIMsgWindow *msgWindow) +{ + nsresult rv = NS_OK; + if (mTxnMgr) + { + int32_t numTxn = 0; + rv = mTxnMgr->GetNumberOfUndoItems(&numTxn); + if (NS_SUCCEEDED(rv) && numTxn > 0) + { + nsCOMPtr<nsITransaction> txn; + rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn)); + if (NS_SUCCEEDED(rv) && txn) + { + static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))->SetMsgWindow(msgWindow); + } + mTxnMgr->UndoTransaction(); + } + } + return rv; +} + +NS_IMETHODIMP +nsMessenger::Redo(nsIMsgWindow *msgWindow) +{ + nsresult rv = NS_OK; + if (mTxnMgr) + { + int32_t numTxn = 0; + rv = mTxnMgr->GetNumberOfRedoItems(&numTxn); + if (NS_SUCCEEDED(rv) && numTxn > 0) + { + nsCOMPtr<nsITransaction> txn; + rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn)); + if (NS_SUCCEEDED(rv) && txn) + { + static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))->SetMsgWindow(msgWindow); + } + mTxnMgr->RedoTransaction(); + } + } + return rv; +} + +NS_IMETHODIMP +nsMessenger::GetTransactionManager(nsITransactionManager* *aTxnMgr) +{ + NS_ENSURE_TRUE(mTxnMgr && aTxnMgr, NS_ERROR_NULL_POINTER); + + *aTxnMgr = mTxnMgr; + NS_ADDREF(*aTxnMgr); + + return NS_OK; +} + +NS_IMETHODIMP nsMessenger::SetDocumentCharset(const nsACString& aCharacterSet) +{ + // We want to redisplay the currently selected message (if any) but forcing the + // redisplay to use characterSet + if (!mLastDisplayURI.IsEmpty()) + { + SetDisplayCharset(NS_LITERAL_CSTRING("UTF-8")); + + nsCOMPtr <nsIMsgMessageService> messageService; + nsresult rv = GetMessageServiceFromURI(mLastDisplayURI, getter_AddRefs(messageService)); + + if (NS_SUCCEEDED(rv) && messageService) + { + nsCOMPtr<nsIURI> dummyNull; + messageService->DisplayMessage(mLastDisplayURI.get(), mDocShell, mMsgWindow, nullptr, + PromiseFlatCString(aCharacterSet).get(), getter_AddRefs(dummyNull)); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMessenger::GetLastDisplayedMessageUri(nsACString& aLastDisplayedMessageUri) +{ + aLastDisplayedMessageUri = mLastDisplayURI; + return NS_OK; +} + +nsSaveMsgListener::nsSaveMsgListener(nsIFile* aFile, nsMessenger *aMessenger, nsIUrlListener *aListener) +{ + m_file = do_QueryInterface(aFile); + m_messenger = aMessenger; + mListener = aListener; + mUrlHasStopped = false; + mRequestHasStopped = false; + + // rhp: for charset handling + m_doCharsetConversion = false; + m_saveAllAttachmentsState = nullptr; + mProgress = 0; + mMaxProgress = -1; + mCanceled = false; + m_outputFormat = eUnknown; + mInitialized = false; +} + +nsSaveMsgListener::~nsSaveMsgListener() +{ +} + +// +// nsISupports +// +NS_IMPL_ISUPPORTS(nsSaveMsgListener, + nsIUrlListener, + nsIMsgCopyServiceListener, + nsIStreamListener, + nsIRequestObserver, + nsICancelable) + +NS_IMETHODIMP +nsSaveMsgListener::Cancel(nsresult status) +{ + mCanceled = true; + return NS_OK; +} + +// +// nsIUrlListener +// +NS_IMETHODIMP +nsSaveMsgListener::OnStartRunningUrl(nsIURI* url) +{ + if (mListener) + mListener->OnStartRunningUrl(url); + return NS_OK; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnStopRunningUrl(nsIURI *url, nsresult exitCode) +{ + nsresult rv = exitCode; + mUrlHasStopped = true; + + // ** save as template goes here + if (!m_templateUri.IsEmpty()) + { + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv)); + if (NS_FAILED(rv)) goto done; + nsCOMPtr<nsIRDFResource> res; + rv = rdf->GetResource(m_templateUri, getter_AddRefs(res)); + if (NS_FAILED(rv)) goto done; + nsCOMPtr<nsIMsgFolder> templateFolder; + templateFolder = do_QueryInterface(res, &rv); + if (NS_FAILED(rv)) goto done; + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID); + if (copyService) + { + nsCOMPtr<nsIFile> clone; + m_file->Clone(getter_AddRefs(clone)); + rv = copyService->CopyFileMessage(clone, templateFolder, nullptr, + true, nsMsgMessageFlags::Read, + EmptyCString(), this, nullptr); + // Clear this so we don't end up in a loop if OnStopRunningUrl gets + // called again. + m_templateUri.Truncate(); + } + } + else if (m_outputStream && mRequestHasStopped) + { + m_outputStream->Close(); + m_outputStream = nullptr; + } + +done: + if (NS_FAILED(rv)) + { + if (m_file) + m_file->Remove(false); + if (m_messenger) + m_messenger->Alert("saveMessageFailed"); + } + + if (mRequestHasStopped && mListener) + mListener->OnStopRunningUrl(url, exitCode); + else + mListenerUri = url; + + return rv; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnStartCopy(void) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSaveMsgListener::SetMessageKey(nsMsgKey aKey) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSaveMsgListener::GetMessageId(nsACString& aMessageId) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnStopCopy(nsresult aStatus) +{ + if (m_file) + m_file->Remove(false); + return aStatus; +} + +// initializes the progress window if we are going to show one +// and for OSX, sets creator flags on the output file +nsresult nsSaveMsgListener::InitializeDownload(nsIRequest * aRequest) +{ + nsresult rv = NS_OK; + + mInitialized = true; + nsCOMPtr<nsIChannel> channel (do_QueryInterface(aRequest)); + + if (!channel) + return rv; + + // Get the max progress from the URL if we haven't already got it. + if (mMaxProgress == -1) + { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri)); + if (mailnewsUrl) + mailnewsUrl->GetMaxProgress(&mMaxProgress); + } + + if (!m_contentType.IsEmpty()) + { + nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID)); + nsCOMPtr<nsIMIMEInfo> mimeinfo; + + mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(), getter_AddRefs(mimeinfo)); + + // create a download progress window + + // Set saveToDisk explicitly to avoid launching the saved file. + // See http://hg.mozilla.org/mozilla-central/file/814a6f071472/toolkit/components/jsdownloads/src/DownloadLegacy.js#l164 + mimeinfo->SetPreferredAction(nsIHandlerInfo::saveToDisk); + + // When we don't allow warnings, also don't show progress, as this + // is an environment (typically filters) where we don't want + // interruption. + bool allowProgress = true; + if (m_saveAllAttachmentsState) + allowProgress = !m_saveAllAttachmentsState->m_withoutWarning; + if (allowProgress) + { + nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv); + if (tr && m_file) + { + PRTime timeDownloadStarted = PR_Now(); + + nsCOMPtr<nsIURI> outputURI; + NS_NewFileURI(getter_AddRefs(outputURI), m_file); + + nsCOMPtr<nsIURI> url; + channel->GetURI(getter_AddRefs(url)); + rv = tr->Init(url, outputURI, EmptyString(), mimeinfo, + timeDownloadStarted, nullptr, this, false); + + // now store the web progresslistener + mTransfer = tr; + } + } + } + return rv; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnStartRequest(nsIRequest* request, nsISupports* aSupport) +{ + if (m_file) + MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), m_file, -1, ATTACHMENT_PERMISSION); + if (!m_outputStream) + { + mCanceled = true; + if (m_messenger) + m_messenger->Alert("saveAttachmentFailed"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnStopRequest(nsIRequest* request, nsISupports* aSupport, + nsresult status) +{ + nsresult rv = NS_OK; + mRequestHasStopped = true; + + // rhp: If we are doing the charset conversion magic, this is different + // processing, otherwise, its just business as usual. + // If we need text/plain, then we need to convert the HTML and then convert + // to the systems charset. + if (m_doCharsetConversion && m_outputStream) + { + // For HTML, code is emitted immediately in OnDataAvailable. + MOZ_ASSERT(m_outputFormat == ePlainText, + "For HTML, m_doCharsetConversion shouldn't be set"); + NS_ConvertUTF8toUTF16 utf16Buffer(m_msgBuffer); + ConvertBufToPlainText(utf16Buffer, false, false, false, false); + + nsCString outCString; + rv = nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), + utf16Buffer, outCString, false, true); + if (rv == NS_ERROR_UENC_NOMAPPING) { + // If we can't encode with the preferred charset, use UTF-8. + CopyUTF16toUTF8(utf16Buffer, outCString); + rv = NS_OK; + } + if (NS_SUCCEEDED(rv)) + { + uint32_t writeCount; + rv = m_outputStream->Write(outCString.get(), outCString.Length(), &writeCount); + if (outCString.Length() != writeCount) + rv = NS_ERROR_FAILURE; + } + } + + if (m_outputStream) + { + m_outputStream->Close(); + m_outputStream = nullptr; + } + + if (m_saveAllAttachmentsState) + { + m_saveAllAttachmentsState->m_curIndex++; + if (!mCanceled && m_saveAllAttachmentsState->m_curIndex < m_saveAllAttachmentsState->m_count) + { + nsSaveAllAttachmentsState *state = m_saveAllAttachmentsState; + uint32_t i = state->m_curIndex; + nsString unescapedName; + nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv)) goto done; + rv = localFile->InitWithNativePath(nsDependentCString(state->m_directoryName)); + + if (NS_FAILED(rv)) goto done; + + ConvertAndSanitizeFileName(state->m_displayNameArray[i], unescapedName); + rv = localFile->Append(unescapedName); + if (NS_FAILED(rv)) + goto done; + + // When we are running with no warnings (typically filters and other automatic + // uses), then don't prompt for duplicates, but create a unique file + // instead. + if (!m_saveAllAttachmentsState->m_withoutWarning) + { + rv = m_messenger->PromptIfFileExists(localFile); + if (NS_FAILED(rv)) goto done; + } + else + { + rv = localFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION); + if (NS_FAILED(rv)) goto done; + } + rv = m_messenger->SaveAttachment(localFile, + nsDependentCString(state->m_urlArray[i]), + nsDependentCString(state->m_messageUriArray[i]), + nsDependentCString(state->m_contentTypeArray[i]), + (void *)state, nullptr); + done: + if (NS_FAILED(rv)) + { + delete state; + m_saveAllAttachmentsState = nullptr; + } + } + else + { + // check if we're saving attachments prior to detaching them. + if (m_saveAllAttachmentsState->m_detachingAttachments && !mCanceled) + { + nsSaveAllAttachmentsState *state = m_saveAllAttachmentsState; + m_messenger->DetachAttachments(state->m_count, + (const char **) state->m_contentTypeArray, + (const char **) state->m_urlArray, + (const char **) state->m_displayNameArray, + (const char **) state->m_messageUriArray, + &state->m_savedFiles, + state->m_withoutWarning); + } + + delete m_saveAllAttachmentsState; + m_saveAllAttachmentsState = nullptr; + } + } + + if(mTransfer) + { + mTransfer->OnProgressChange64(nullptr, nullptr, mMaxProgress, mMaxProgress, mMaxProgress, mMaxProgress); + mTransfer->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); + mTransfer = nullptr; // break any circular dependencies between the progress dialog and use + } + + if (mUrlHasStopped && mListener) + mListener->OnStopRunningUrl(mListenerUri, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsSaveMsgListener::OnDataAvailable(nsIRequest* request, + nsISupports* aSupport, + nsIInputStream* inStream, + uint64_t srcOffset, + uint32_t count) +{ + nsresult rv = NS_ERROR_FAILURE; + // first, check to see if we've been canceled.... + if (mCanceled) // then go cancel our underlying channel too + return request->Cancel(NS_BINDING_ABORTED); + + if (!mInitialized) + InitializeDownload(request); + + if (m_outputStream) + { + mProgress += count; + uint64_t available; + uint32_t readCount, maxReadCount = sizeof(m_dataBuffer); + uint32_t writeCount; + rv = inStream->Available(&available); + while (NS_SUCCEEDED(rv) && available) + { + if (maxReadCount > available) + maxReadCount = (uint32_t)available; + rv = inStream->Read(m_dataBuffer, maxReadCount, &readCount); + + // rhp: + // Ok, now we do one of two things. If we are sending out HTML, then + // just write it to the HTML stream as it comes along...but if this is + // a save as TEXT operation, we need to buffer this up for conversion + // when we are done. When the stream converter for HTML-TEXT gets in place, + // this magic can go away. + // + if (NS_SUCCEEDED(rv)) + { + if ( (m_doCharsetConversion) && (m_outputFormat == ePlainText) ) + m_msgBuffer.Append(Substring(m_dataBuffer, m_dataBuffer + readCount)); + else + rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount); + + available -= readCount; + } + } + + if (NS_SUCCEEDED(rv) && mTransfer) // Send progress notification. + mTransfer->OnProgressChange64(nullptr, request, mProgress, mMaxProgress, mProgress, mMaxProgress); + } + return rv; +} + +#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties" + +nsresult +nsMessenger::InitStringBundle() +{ + if (mStringBundle) + return NS_OK; + + const char propertyURL[] = MESSENGER_STRING_URL; + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + return sBundleService->CreateBundle(propertyURL, + getter_AddRefs(mStringBundle)); +} + +void +nsMessenger::GetString(const nsString& aStringName, nsString& aValue) +{ + nsresult rv; + aValue.Truncate(); + + if (!mStringBundle) + rv = InitStringBundle(); + + if (mStringBundle) + rv = mStringBundle->GetStringFromName(aStringName.get(), getter_Copies(aValue)); + else + rv = NS_ERROR_FAILURE; + + if (NS_FAILED(rv) || aValue.IsEmpty()) + aValue = aStringName; + return; +} + +nsSaveAllAttachmentsState::nsSaveAllAttachmentsState(uint32_t count, + const char **contentTypeArray, + const char **urlArray, + const char **nameArray, + const char **uriArray, + const char *dirName, + bool detachingAttachments) + : m_withoutWarning(false) +{ + uint32_t i; + NS_ASSERTION(count && urlArray && nameArray && uriArray && dirName, + "fatal - invalid parameters\n"); + + m_count = count; + m_curIndex = 0; + m_contentTypeArray = new char*[count]; + m_urlArray = new char*[count]; + m_displayNameArray = new char*[count]; + m_messageUriArray = new char*[count]; + for (i = 0; i < count; i++) + { + m_contentTypeArray[i] = strdup(contentTypeArray[i]); + m_urlArray[i] = strdup(urlArray[i]); + m_displayNameArray[i] = strdup(nameArray[i]); + m_messageUriArray[i] = strdup(uriArray[i]); + } + m_directoryName = strdup(dirName); + m_detachingAttachments = detachingAttachments; +} + +nsSaveAllAttachmentsState::~nsSaveAllAttachmentsState() +{ + uint32_t i; + for (i = 0; i < m_count; i++) + { + NS_Free(m_contentTypeArray[i]); + NS_Free(m_urlArray[i]); + NS_Free(m_displayNameArray[i]); + NS_Free(m_messageUriArray[i]); + } + delete[] m_contentTypeArray; + delete[] m_urlArray; + delete[] m_displayNameArray; + delete[] m_messageUriArray; + NS_Free(m_directoryName); +} + +nsresult +nsMessenger::GetLastSaveDirectory(nsIFile **aLastSaveDir) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // this can fail, and it will, on the first time we call it, as there is no default for this pref. + nsCOMPtr <nsIFile> localFile; + rv = prefBranch->GetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsIFile), getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + NS_IF_ADDREF(*aLastSaveDir = localFile); + } + return rv; +} + +nsresult +nsMessenger::SetLastSaveDirectory(nsIFile *aLocalFile) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIFile> file = do_QueryInterface(aLocalFile, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // if the file is a directory, just use it for the last dir chosen + // otherwise, use the parent of the file as the last dir chosen. + // IsDirectory() will return error on saving a file, as the + // file doesn't exist yet. + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + if (NS_SUCCEEDED(rv) && isDirectory) { + rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsIFile), aLocalFile); + NS_ENSURE_SUCCESS(rv,rv); + } + else { + nsCOMPtr <nsIFile> parent; + rv = file->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME, NS_GET_IID(nsIFile), parent); + NS_ENSURE_SUCCESS(rv,rv); + } + return NS_OK; +} + +/* void getUrisAtNavigatePos (in long aPos, out ACString aFolderUri, out ACString aMsgUri); */ +// aPos is relative to the current history cursor - 1 is forward, -1 is back. +NS_IMETHODIMP nsMessenger::GetMsgUriAtNavigatePos(int32_t aPos, nsACString& aMsgUri) +{ + int32_t desiredArrayIndex = (mCurHistoryPos + (aPos << 1)); + if (desiredArrayIndex >= 0 && desiredArrayIndex < (int32_t)mLoadedMsgHistory.Length()) + { + mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex]; + aMsgUri = mNavigatingToUri; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMessenger::SetNavigatePos(int32_t aPos) +{ + if ((aPos << 1) < (int32_t)mLoadedMsgHistory.Length()) + { + mCurHistoryPos = aPos << 1; + return NS_OK; + } + else + return NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP nsMessenger::GetNavigatePos(int32_t *aPos) +{ + NS_ENSURE_ARG_POINTER(aPos); + *aPos = mCurHistoryPos >> 1; + return NS_OK; +} + +// aPos is relative to the current history cursor - 1 is forward, -1 is back. +NS_IMETHODIMP nsMessenger::GetFolderUriAtNavigatePos(int32_t aPos, nsACString& aFolderUri) +{ + int32_t desiredArrayIndex = (mCurHistoryPos + (aPos << 1)); + if (desiredArrayIndex >= 0 && desiredArrayIndex < (int32_t)mLoadedMsgHistory.Length()) + { + mNavigatingToUri = mLoadedMsgHistory[desiredArrayIndex + 1]; + aFolderUri = mNavigatingToUri; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMessenger::GetNavigateHistory(uint32_t *aCurPos, uint32_t *aCount, char *** aHistoryUris) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aCurPos); + + *aCurPos = mCurHistoryPos >> 1; + *aCount = mLoadedMsgHistory.Length(); + // for just enabling commands, we don't need the history uris. + if (!aHistoryUris) + return NS_OK; + + char **outArray, **next; + next = outArray = (char **)moz_xmalloc(*aCount * sizeof(char *)); + if (!outArray) return NS_ERROR_OUT_OF_MEMORY; + for (uint32_t i = 0; i < *aCount; i++) + { + *next = ToNewCString(mLoadedMsgHistory[i]); + if (!*next) + return NS_ERROR_OUT_OF_MEMORY; + next++; + } + *aHistoryUris = outArray; + return NS_OK; +} + +NS_IMETHODIMP +nsMessenger::FormatFileSize(uint64_t aSize, bool aUseKB, nsAString& aFormattedSize) +{ + return ::FormatFileSize(aSize, aUseKB, aFormattedSize); +} + + +/* void OnItemAdded (in nsIMsgFolder parentItem, in nsISupports item); */ +NS_IMETHODIMP nsMessenger::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnItemRemoved (in nsIMsgFolder parentItem, in nsISupports item); */ +NS_IMETHODIMP nsMessenger::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item) +{ + // check if this item is a message header that's in our history list. If so, + // remove it from the history list. + nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryInterface(item); + if (msgHdr) + { + nsCOMPtr <nsIMsgFolder> folder; + msgHdr->GetFolder(getter_AddRefs(folder)); + if (folder) + { + nsCString msgUri; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + folder->GenerateMessageURI(msgKey, msgUri); + // need to remove the correspnding folder entry, and + // adjust the current history pos. + size_t uriPos = mLoadedMsgHistory.IndexOf(msgUri); + if (uriPos != mLoadedMsgHistory.NoIndex) + { + mLoadedMsgHistory.RemoveElementAt(uriPos); + mLoadedMsgHistory.RemoveElementAt(uriPos); // and the folder uri entry + if (mCurHistoryPos >= (int32_t)uriPos) + mCurHistoryPos -= 2; + } + } + } + return NS_OK; +} + +/* void OnItemPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in string oldValue, in string newValue); */ +NS_IMETHODIMP nsMessenger::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnItemIntPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in long long oldValue, in long long newValue); */ +NS_IMETHODIMP nsMessenger::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnItemBoolPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in boolean oldValue, in boolean newValue); */ +NS_IMETHODIMP nsMessenger::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnItemUnicharPropertyChanged (in nsIMsgFolder item, in nsIAtom property, in wstring oldValue, in wstring newValue); */ +NS_IMETHODIMP nsMessenger::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnItemPropertyFlagChanged (in nsIMsgDBHdr item, in nsIAtom property, in unsigned long oldFlag, in unsigned long newFlag); */ +NS_IMETHODIMP nsMessenger::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void OnItemEvent (in nsIMsgFolder item, in nsIAtom event); */ +NS_IMETHODIMP nsMessenger::OnItemEvent(nsIMsgFolder *item, nsIAtom *event) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/////////////////////////////////////////////////////////////////////////////// +// Detach/Delete Attachments +/////////////////////////////////////////////////////////////////////////////// + +static const char * GetAttachmentPartId(const char * aAttachmentUrl) +{ + static const char partIdPrefix[] = "part="; + const char * partId = PL_strstr(aAttachmentUrl, partIdPrefix); + return partId ? (partId + sizeof(partIdPrefix) - 1) : nullptr; +} + +static int CompareAttachmentPartId(const char * aAttachUrlLeft, const char * aAttachUrlRight) +{ + // part ids are numbers separated by periods, like "1.2.3.4". + // we sort by doing a numerical comparison on each item in turn. e.g. "1.4" < "1.25" + // shorter entries come before longer entries. e.g. "1.4" < "1.4.1.2" + // return values: + // -2 left is a parent of right + // -1 left is less than right + // 0 left == right + // 1 right is greater than left + // 2 right is a parent of left + + const char * partIdLeft = GetAttachmentPartId(aAttachUrlLeft); + const char * partIdRight = GetAttachmentPartId(aAttachUrlRight); + + // for detached attachments the URL does not contain any "part=xx" + if(!partIdLeft) + partIdLeft = "0"; + + if(!partIdRight) + partIdRight = "0"; + + long idLeft, idRight; + do + { + MOZ_ASSERT(partIdLeft && IS_DIGIT(*partIdLeft), "Invalid character in part id string"); + MOZ_ASSERT(partIdRight && IS_DIGIT(*partIdRight), "Invalid character in part id string"); + + // if the part numbers are different then the numerically smaller one is first + char *fixConstLoss; + idLeft = strtol(partIdLeft, &fixConstLoss, 10); + partIdLeft = fixConstLoss; + idRight = strtol(partIdRight, &fixConstLoss, 10); + partIdRight = fixConstLoss; + if (idLeft != idRight) + return idLeft < idRight ? -1 : 1; + + // if one part id is complete but the other isn't, then the shortest one + // is first (parents before children) + if (*partIdLeft != *partIdRight) + return *partIdRight ? -2 : 2; + + // if both part ids are complete (*partIdLeft == *partIdRight now) then + // they are equal + if (!*partIdLeft) + return 0; + + MOZ_ASSERT(*partIdLeft == '.', "Invalid character in part id string"); + MOZ_ASSERT(*partIdRight == '.', "Invalid character in part id string"); + + ++partIdLeft; + ++partIdRight; + } + while (true); + return 0; +} + +// ------------------------------------ + +// struct on purpose -> show that we don't ever want a vtable +struct msgAttachment +{ + msgAttachment() + : mContentType(nullptr), + mUrl(nullptr), + mDisplayName(nullptr), + mMessageUri(nullptr) + { + } + + ~msgAttachment() + { + Clear(); + } + + void Clear() + { + NS_Free(mContentType); + NS_Free(mUrl); + NS_Free(mDisplayName); + NS_Free(mMessageUri); + } + + bool Init(const char * aContentType, const char * aUrl, + const char * aDisplayName, const char * aMessageUri) + { + Clear(); + mContentType = strdup(aContentType); + mUrl = strdup(aUrl); + mDisplayName = strdup(aDisplayName); + mMessageUri = strdup(aMessageUri); + return (mContentType && mUrl && mDisplayName && mMessageUri); + } + + // take the pointers from aSource + void Adopt(msgAttachment & aSource) + { + Clear(); + + mContentType = aSource.mContentType; + mUrl = aSource.mUrl; + mDisplayName = aSource.mDisplayName; + mMessageUri = aSource.mMessageUri; + + aSource.mContentType = nullptr; + aSource.mUrl = nullptr; + aSource.mDisplayName = nullptr; + aSource.mMessageUri = nullptr; + } + + char* mContentType; + char* mUrl; + char* mDisplayName; + char* mMessageUri; + +private: + // disable by not implementing + msgAttachment(const msgAttachment & rhs); + msgAttachment & operator=(const msgAttachment & rhs); +}; + +// ------------------------------------ + +class nsAttachmentState +{ +public: + nsAttachmentState(); + ~nsAttachmentState(); + nsresult Init(uint32_t aCount, + const char **aContentTypeArray, + const char **aUrlArray, + const char **aDisplayNameArray, + const char **aMessageUriArray); + nsresult PrepareForAttachmentDelete(); + +private: + static int SortAttachmentsByPartId(const void * aLeft, const void * aRight, void *); + +public: + uint32_t mCount; + uint32_t mCurIndex; + msgAttachment* mAttachmentArray; +}; + +nsAttachmentState::nsAttachmentState() + : mCount(0), + mCurIndex(0), + mAttachmentArray(nullptr) +{ +} + +nsAttachmentState::~nsAttachmentState() +{ + delete[] mAttachmentArray; +} + +nsresult +nsAttachmentState::Init(uint32_t aCount, const char ** aContentTypeArray, + const char ** aUrlArray, const char ** aDisplayNameArray, + const char ** aMessageUriArray) +{ + MOZ_ASSERT(aCount > 0, "count is invalid"); + + mCount = aCount; + mCurIndex = 0; + delete[] mAttachmentArray; + + mAttachmentArray = new msgAttachment[aCount]; + if (!mAttachmentArray) + return NS_ERROR_OUT_OF_MEMORY; + + for(uint32_t u = 0; u < aCount; ++u) + { + if (!mAttachmentArray[u].Init(aContentTypeArray[u], aUrlArray[u], + aDisplayNameArray[u], aMessageUriArray[u])) + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +nsAttachmentState::PrepareForAttachmentDelete() +{ + // this must be called before any processing + if (mCurIndex != 0) + return NS_ERROR_FAILURE; + + // this prepares the attachment list for use in deletion. In order to prepare, we + // sort the attachments in numerical ascending order on their part id, remove all + // duplicates and remove any subparts which will be removed automatically by the + // removal of the parent. + // + // e.g. the attachment list processing (showing only part ids) + // before: 1.11, 1.3, 1.2, 1.2.1.3, 1.4.1.2 + // sorted: 1.2, 1.2.1.3, 1.3, 1.4.1.2, 1.11 + // after: 1.2, 1.3, 1.4.1.2, 1.11 + + // sort + NS_QuickSort(mAttachmentArray, mCount, sizeof(msgAttachment), SortAttachmentsByPartId, nullptr); + + // remove duplicates and sub-items + int nCompare; + for(uint32_t u = 1; u < mCount;) + { + nCompare = ::CompareAttachmentPartId(mAttachmentArray[u-1].mUrl, mAttachmentArray[u].mUrl); + if (nCompare == 0 || nCompare == -2) // [u-1] is the same as or a parent of [u] + { + // shuffle the array down (and thus keeping the sorted order) + // this will get rid of the current unnecessary element + for (uint32_t i = u + 1; i < mCount; ++i) + { + mAttachmentArray[i-1].Adopt(mAttachmentArray[i]); + } + --mCount; + } + else + { + ++u; + } + } + + return NS_OK; +} + +int +nsAttachmentState::SortAttachmentsByPartId(const void * aLeft, const void * aRight, void *) +{ + msgAttachment & attachLeft = *((msgAttachment*) aLeft); + msgAttachment & attachRight = *((msgAttachment*) aRight); + return ::CompareAttachmentPartId(attachLeft.mUrl, attachRight.mUrl); +} + +// ------------------------------------ + +class nsDelAttachListener : public nsIStreamListener, + public nsIUrlListener, + public nsIMsgCopyServiceListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGCOPYSERVICELISTENER + +public: + nsDelAttachListener(); + nsresult StartProcessing(nsMessenger * aMessenger, nsIMsgWindow * aMsgWindow, + nsAttachmentState * aAttach, bool aSaveFirst); + nsresult DeleteOriginalMessage(); + void SelectNewMessage(); + +public: + nsAttachmentState * mAttach; // list of attachments to process + bool mSaveFirst; // detach (true) or delete (false) + nsCOMPtr<nsIFile> mMsgFile; // temporary file (processed mail) + nsCOMPtr<nsIOutputStream> mMsgFileStream; // temporary file (processed mail) + nsCOMPtr<nsIMsgMessageService> mMessageService; // original message service + nsCOMPtr<nsIMsgDBHdr> mOriginalMessage; // original message header + nsCOMPtr<nsIMsgFolder> mMessageFolder; // original message folder + nsCOMPtr<nsIMessenger> mMessenger; // our messenger instance + nsCOMPtr<nsIMsgWindow> mMsgWindow; // our UI window + nsMsgKey mNewMessageKey; // new message key + uint32_t mOrigMsgFlags; + + + enum { + eStarting, + eCopyingNewMsg, + eUpdatingFolder, // for IMAP + eDeletingOldMessage, + eSelectingNewMessage + } m_state; + // temp + bool mWrittenExtra; + bool mDetaching; + nsTArray<nsCString> mDetachedFileUris; + +private: + virtual ~nsDelAttachListener(); +}; + +// +// nsISupports +// +NS_IMPL_ISUPPORTS(nsDelAttachListener, + nsIStreamListener, + nsIRequestObserver, + nsIUrlListener, + nsIMsgCopyServiceListener) + +// +// nsIRequestObserver +// +NS_IMETHODIMP +nsDelAttachListener::OnStartRequest(nsIRequest * aRequest, nsISupports * aContext) +{ + // called when we start processing the StreamMessage request. + // This is called after OnStartRunningUrl(). + return NS_OK; +} + +NS_IMETHODIMP +nsDelAttachListener::OnStopRequest(nsIRequest * aRequest, nsISupports * aContext, nsresult aStatusCode) +{ + // called when we have completed processing the StreamMessage request. + // This is called after OnStopRequest(). This means that we have now + // received all data of the message and we have completed processing. + // We now start to copy the processed message from the temporary file + // back into the message store, replacing the original message. + + mMessageFolder->CopyDataDone(); + if (NS_FAILED(aStatusCode)) + return aStatusCode; + + // called when we complete processing of the StreamMessage request. + // This is called before OnStopRunningUrl(). + nsresult rv; + + // copy the file back into the folder. Note: setting msgToReplace only copies + // metadata, so we do the delete ourselves + nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService; + rv = this->QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) ); + NS_ENSURE_SUCCESS(rv,rv); + + mMsgFileStream->Close(); + mMsgFileStream = nullptr; + mNewMessageKey = nsMsgKey_None; + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID); + m_state = eCopyingNewMsg; + // clone file because nsIFile on Windows caches the wrong file size. + nsCOMPtr <nsIFile> clone; + mMsgFile->Clone(getter_AddRefs(clone)); + if (copyService) + { + nsCString originalKeys; + mOriginalMessage->GetStringProperty("keywords", getter_Copies(originalKeys)); + rv = copyService->CopyFileMessage(clone, mMessageFolder, mOriginalMessage, false, + mOrigMsgFlags, originalKeys, listenerCopyService, mMsgWindow); + } + return rv; +} + +// +// nsIStreamListener +// + +NS_IMETHODIMP +nsDelAttachListener::OnDataAvailable(nsIRequest * aRequest, nsISupports * aSupport, + nsIInputStream * aInStream, uint64_t aSrcOffset, + uint32_t aCount) +{ + if (!mMsgFileStream) + return NS_ERROR_NULL_POINTER; + return mMessageFolder->CopyDataToOutputStreamForAppend(aInStream, aCount, mMsgFileStream); +} + +// +// nsIUrlListener +// + +NS_IMETHODIMP +nsDelAttachListener::OnStartRunningUrl(nsIURI * aUrl) +{ + // called when we start processing the StreamMessage request. This is + // called before OnStartRequest(). + return NS_OK; +} + +nsresult nsDelAttachListener::DeleteOriginalMessage() +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = messageArray->AppendElement(mOriginalMessage, false); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService; + + QueryInterface( NS_GET_IID(nsIMsgCopyServiceListener), getter_AddRefs(listenerCopyService) ); + + mOriginalMessage = nullptr; + m_state = eDeletingOldMessage; + return mMessageFolder->DeleteMessages( + messageArray, // messages + mMsgWindow, // msgWindow + true, // deleteStorage + false, // isMove + listenerCopyService, // listener + false); // allowUndo +} + +void nsDelAttachListener::SelectNewMessage() +{ + nsCString displayUri; + // all attachments refer to the same message + const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri; + mMessenger->GetLastDisplayedMessageUri(displayUri); + if (displayUri.Equals(messageUri)) + { + mMessageFolder->GenerateMessageURI(mNewMessageKey, displayUri); + if (!displayUri.IsEmpty() && mMsgWindow) + { + nsCOMPtr<nsIMsgWindowCommands> windowCommands; + mMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands)); + if (windowCommands) + windowCommands->SelectMessage(displayUri); + } + } + mNewMessageKey = nsMsgKey_None; +} + +NS_IMETHODIMP +nsDelAttachListener::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) +{ + nsresult rv = NS_OK; + const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri; + if (mOriginalMessage && !strncmp(messageUri, "imap-message:", 13)) + { + if (m_state == eUpdatingFolder) + rv = DeleteOriginalMessage(); + } + // check if we've deleted the original message, and we know the new msg id. + else if (m_state == eDeletingOldMessage && mMsgWindow) + SelectNewMessage(); + + return rv; +} + +// +// nsIMsgCopyServiceListener +// + +NS_IMETHODIMP +nsDelAttachListener::OnStartCopy(void) +{ + // never called? + return NS_OK; +} + +NS_IMETHODIMP +nsDelAttachListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + // never called? + return NS_OK; +} + +NS_IMETHODIMP +nsDelAttachListener::SetMessageKey(nsMsgKey aKey) +{ + // called during the copy of the modified message back into the message + // store to notify us of the message key of the newly created message. + mNewMessageKey = aKey; + return NS_OK; +} + +NS_IMETHODIMP +nsDelAttachListener::GetMessageId(nsACString& aMessageId) +{ + // never called? + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDelAttachListener::OnStopCopy(nsresult aStatus) +{ + // only if the currently selected message is the one that we are about to delete then we + // change the selection to the new message that we just added. Failures in this code are not fatal. + // Note that can only do this if we have the new message key, which we don't always get from IMAP. + // delete the original message + if (NS_FAILED(aStatus)) + return aStatus; + + // check if we've deleted the original message, and we know the new msg id. + if (m_state == eDeletingOldMessage && mMsgWindow) + SelectNewMessage(); + // do this for non-imap messages - for imap, we'll do the delete in + // OnStopRunningUrl. For local messages, we won't get an OnStopRunningUrl + // notification. And for imap, it's too late to delete the message here, + // because we'll be updating the folder naturally as a result of + // running an append url. If we delete the header here, that folder + // update will think we need to download the header...If we do it + // in OnStopRunningUrl, we'll issue the delete before we do the + // update....all nasty stuff. + const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri; + if (mOriginalMessage && strncmp(messageUri, "imap-message:", 13)) + return DeleteOriginalMessage(); + else + m_state = eUpdatingFolder; + return NS_OK; +} + +// +// local methods +// + +nsDelAttachListener::nsDelAttachListener() +{ + mAttach = nullptr; + mSaveFirst = false; + mWrittenExtra = false; + mNewMessageKey = nsMsgKey_None; + m_state = eStarting; +} + +nsDelAttachListener::~nsDelAttachListener() +{ + if (mAttach) + { + delete mAttach; + } + if (mMsgFileStream) + { + mMsgFileStream->Close(); + mMsgFileStream = nullptr; + } + if (mMsgFile) + { + mMsgFile->Remove(false); + } +} + +nsresult +nsDelAttachListener::StartProcessing(nsMessenger * aMessenger, nsIMsgWindow * aMsgWindow, + nsAttachmentState * aAttach, bool detaching) +{ + aMessenger->QueryInterface(NS_GET_IID(nsIMessenger), getter_AddRefs(mMessenger)); + mMsgWindow = aMsgWindow; + mAttach = aAttach; + mDetaching = detaching; + + nsresult rv; + + // all attachments refer to the same message + const char * messageUri = mAttach->mAttachmentArray[0].mMessageUri; + + // get the message service, original message and folder for this message + rv = GetMessageServiceFromURI(nsDependentCString(messageUri), getter_AddRefs(mMessageService)); + NS_ENSURE_SUCCESS(rv,rv); + rv = mMessageService->MessageURIToMsgHdr(messageUri, getter_AddRefs(mOriginalMessage)); + NS_ENSURE_SUCCESS(rv,rv); + rv = mOriginalMessage->GetFolder(getter_AddRefs(mMessageFolder)); + NS_ENSURE_SUCCESS(rv,rv); + mOriginalMessage->GetFlags(&mOrigMsgFlags); + + // ensure that we can store and delete messages in this folder, if we + // can't then we can't do attachment deleting + bool canDelete = false; + mMessageFolder->GetCanDeleteMessages(&canDelete); + bool canFile = false; + mMessageFolder->GetCanFileMessages(&canFile); + if (!canDelete || !canFile) + return NS_ERROR_FAILURE; + + // create an output stream on a temporary file. This stream will save the modified + // message data to a file which we will later use to replace the existing message. + // The file is removed in the destructor. + rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nsmail.tmp", + getter_AddRefs(mMsgFile)); + NS_ENSURE_SUCCESS(rv,rv); + + // For temp file, we should use restrictive 00600 instead of ATTACHMENT_PERMISSION + rv = mMsgFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + NS_ENSURE_SUCCESS(rv,rv); + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mMsgFileStream), mMsgFile, -1, ATTACHMENT_PERMISSION); + + // create the additional header for data conversion. This will tell the stream converter + // which MIME emitter we want to use, and it will tell the MIME emitter which attachments + // should be deleted. + const char * partId; + const char * nextField; + nsAutoCString sHeader("attach&del="); + nsAutoCString detachToHeader("&detachTo="); + for (uint32_t u = 0; u < mAttach->mCount; ++u) + { + if (u > 0) + { + sHeader.Append(","); + if (detaching) + detachToHeader.Append(","); + } + partId = GetAttachmentPartId(mAttach->mAttachmentArray[u].mUrl); + nextField = PL_strchr(partId, '&'); + sHeader.Append(partId, nextField ? nextField - partId : -1); + if (detaching) + detachToHeader.Append(mDetachedFileUris[u]); + } + + if (detaching) + sHeader.Append(detachToHeader); + // stream this message to our listener converting it via the attachment mime + // converter. The listener will just write the converted message straight to disk. + nsCOMPtr<nsISupports> listenerSupports; + rv = this->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports)); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIUrlListener> listenerUrlListener = do_QueryInterface(listenerSupports, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIURI> dummyNull; + rv = mMessageService->StreamMessage(messageUri, listenerSupports, mMsgWindow, + listenerUrlListener, true, sHeader, + false, getter_AddRefs(dummyNull)); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_OK; +} + +// ------------------------------------ + +NS_IMETHODIMP +nsMessenger::DetachAttachment(const char * aContentType, const char * aUrl, + const char * aDisplayName, const char * aMessageUri, + bool aSaveFirst, bool withoutWarning = false) +{ + NS_ENSURE_ARG_POINTER(aContentType); + NS_ENSURE_ARG_POINTER(aUrl); + NS_ENSURE_ARG_POINTER(aDisplayName); + NS_ENSURE_ARG_POINTER(aMessageUri); + + if (aSaveFirst) + return SaveOneAttachment(aContentType, aUrl, aDisplayName, aMessageUri, true); + return DetachAttachments(1, &aContentType, &aUrl, &aDisplayName, &aMessageUri, nullptr, withoutWarning); +} + +NS_IMETHODIMP +nsMessenger::DetachAllAttachments(uint32_t aCount, + const char ** aContentTypeArray, + const char ** aUrlArray, + const char ** aDisplayNameArray, + const char ** aMessageUriArray, + bool aSaveFirst, + bool withoutWarning = false) +{ + NS_ENSURE_ARG_MIN(aCount, 1); + NS_ENSURE_ARG_POINTER(aContentTypeArray); + NS_ENSURE_ARG_POINTER(aUrlArray); + NS_ENSURE_ARG_POINTER(aDisplayNameArray); + NS_ENSURE_ARG_POINTER(aMessageUriArray); + + if (aSaveFirst) + return SaveAllAttachments(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray, true); + else + return DetachAttachments(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray, nullptr, withoutWarning); +} + +nsresult +nsMessenger::DetachAttachments(uint32_t aCount, + const char ** aContentTypeArray, + const char ** aUrlArray, + const char ** aDisplayNameArray, + const char ** aMessageUriArray, + nsTArray<nsCString> *saveFileUris, + bool withoutWarning) +{ + // if withoutWarning no dialog for user + if (!withoutWarning && NS_FAILED(PromptIfDeleteAttachments(saveFileUris != nullptr, aCount, aDisplayNameArray))) + return NS_OK; + + nsresult rv = NS_OK; + + // ensure that our arguments are valid +// char * partId; + for (uint32_t u = 0; u < aCount; ++u) + { + // ensure all of the message URI are the same, we cannot process + // attachments from different messages + if (u > 0 && 0 != strcmp(aMessageUriArray[0], aMessageUriArray[u])) + { + rv = NS_ERROR_INVALID_ARG; + break; + } + + // ensure that we don't have deleted messages in this list + if (0 == strcmp(aContentTypeArray[u], MIMETYPE_DELETED)) + { + rv = NS_ERROR_INVALID_ARG; + break; + } + + // for the moment we prevent any attachments other than root level + // attachments being deleted (i.e. you can't delete attachments from a + // email forwarded as an attachment). We do this by ensuring that the + // part id only has a single period in it (e.g. "1.2"). + //TODO: support non-root level attachment delete +// partId = ::GetAttachmentPartId(aUrlArray[u]); +// if (!partId || PL_strchr(partId, '.') != PL_strrchr(partId, '.')) +// { +// rv = NS_ERROR_INVALID_ARG; +// break; +// } + } + if (NS_FAILED(rv)) + { + Alert("deleteAttachmentFailure"); + return rv; + } + + //TODO: ensure that nothing else is processing this message uri at the same time + + //TODO: if any of the selected attachments are messages that contain other + // attachments we need to warn the user that all sub-attachments of those + // messages will also be deleted. Best to display a list of them. + + // get the listener for running the url + nsDelAttachListener * listener = new nsDelAttachListener; + if (!listener) + return NS_ERROR_OUT_OF_MEMORY; + nsCOMPtr<nsISupports> listenerSupports; // auto-delete of the listener with error + listener->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports)); + + if (saveFileUris) + listener->mDetachedFileUris = *saveFileUris; + // create the attachments for use by the listener + nsAttachmentState * attach = new nsAttachmentState; + if (!attach) + return NS_ERROR_OUT_OF_MEMORY; + rv = attach->Init(aCount, aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray); + if (NS_SUCCEEDED(rv)) + rv = attach->PrepareForAttachmentDelete(); + if (NS_FAILED(rv)) + { + delete attach; + return rv; + } + + // initialize our listener with the attachments and details. The listener takes ownership + // of 'attach' immediately irrespective of the return value (error or not). + return listener->StartProcessing(this, mMsgWindow, attach, saveFileUris != nullptr); +} + +nsresult +nsMessenger::PromptIfDeleteAttachments(bool aSaveFirst, + uint32_t aCount, + const char ** aDisplayNameArray) +{ + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell)); + if (!dialog) return rv; + + if (!mStringBundle) + { + rv = InitStringBundle(); + NS_ENSURE_SUCCESS(rv, rv); + } + + // create the list of attachments we are removing + nsString displayString; + nsString attachmentList; + for (uint32_t u = 0; u < aCount; ++u) + { + ConvertAndSanitizeFileName(aDisplayNameArray[u], displayString); + attachmentList.Append(displayString); + attachmentList.Append(char16_t('\n')); + } + const char16_t *formatStrings[] = { attachmentList.get() }; + + // format the message and display + nsString promptMessage; + const char16_t * propertyName = aSaveFirst ? + u"detachAttachments" : u"deleteAttachments"; + rv = mStringBundle->FormatStringFromName(propertyName, formatStrings, 1,getter_Copies(promptMessage)); + NS_ENSURE_SUCCESS(rv, rv); + + bool dialogResult = false; + rv = dialog->Confirm(nullptr, promptMessage.get(), &dialogResult); + NS_ENSURE_SUCCESS(rv, rv); + + return dialogResult ? NS_OK : NS_ERROR_FAILURE; +} + diff --git a/mailnews/base/src/nsMessenger.h b/mailnews/base/src/nsMessenger.h new file mode 100644 index 0000000000..9ba22e26e7 --- /dev/null +++ b/mailnews/base/src/nsMessenger.h @@ -0,0 +1,102 @@ +/* -*- 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/. */ + +#ifndef __nsMsgAppCore_h +#define __nsMsgAppCore_h + +#include "nscore.h" +#include "nsIMessenger.h" +#include "nsCOMPtr.h" +#include "nsITransactionManager.h" +#include "nsIFile.h" +#include "nsIDocShell.h" +#include "nsIStringBundle.h" +#include "nsIFile.h" +#include "nsWeakReference.h" +#include "mozIDOMWindow.h" +#include "nsTArray.h" +#include "nsIFolderListener.h" + +class nsMessenger : public nsIMessenger, public nsSupportsWeakReference, public nsIFolderListener +{ + +public: + nsMessenger(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMESSENGER + NS_DECL_NSIFOLDERLISTENER + + nsresult Alert(const char * stringName); + + nsresult SaveAttachment(nsIFile *file, const nsACString& unescapedUrl, + const nsACString& messageUri, const nsACString& contentType, + void *closure, nsIUrlListener *aListener); + nsresult PromptIfFileExists(nsIFile *file); + nsresult DetachAttachments(uint32_t aCount, + const char ** aContentTypeArray, + const char ** aUrlArray, + const char ** aDisplayNameArray, + const char ** aMessageUriArray, + nsTArray<nsCString> *saveFileUris, + bool withoutWarning = false); + nsresult SaveAllAttachments(uint32_t count, + const char **contentTypeArray, + const char **urlArray, + const char **displayNameArray, + const char **messageUriArray, + bool detaching); + nsresult SaveOneAttachment(const char* aContentType, + const char* aURL, + const char* aDisplayName, + const char* aMessageUri, + bool detaching); + +protected: + virtual ~nsMessenger(); + + void GetString(const nsString& aStringName, nsString& stringValue); + nsresult InitStringBundle(); + nsresult PromptIfDeleteAttachments(bool saveFirst, uint32_t count, const char **displayNameArray); + +private: + nsresult GetLastSaveDirectory(nsIFile **aLastSaveAsDir); + // if aLocalFile is a dir, we use it. otherwise, we use the parent of aLocalFile. + nsresult SetLastSaveDirectory(nsIFile *aLocalFile); + + nsresult AdjustFileIfNameTooLong(nsIFile* aFile); + + nsresult GetSaveAsFile(const nsAString& aMsgFilename, int32_t *aSaveAsFileType, + nsIFile **aSaveAsFile); + + nsresult GetSaveToDir(nsIFile **aSaveToDir); + + nsString mId; + nsCOMPtr<nsITransactionManager> mTxnMgr; + + /* rhp - need this to drive message display */ + nsCOMPtr<mozIDOMWindowProxy> mWindow; + nsCOMPtr<nsIMsgWindow> mMsgWindow; + nsCOMPtr<nsIDocShell> mDocShell; + + // String bundles... + nsCOMPtr<nsIStringBundle> mStringBundle; + + nsCString mCurrentDisplayCharset; + + nsCOMPtr<nsISupports> mSearchContext; + nsCString mLastDisplayURI; // this used when the user attempts to force a charset reload of a message...we need to get the last displayed + // uri so we can re-display it.. + nsCString mNavigatingToUri; + nsTArray<nsCString> mLoadedMsgHistory; + int32_t mCurHistoryPos; +}; + +#define NS_MESSENGER_CID \ +{ /* f436a174-e2c0-4955-9afe-e3feb68aee56 */ \ + 0xf436a174, 0xe2c0, 0x4955, \ + {0x9a, 0xfe, 0xe3, 0xfe, 0xb6, 0x8a, 0xee, 0x56}} + +#endif diff --git a/mailnews/base/src/nsMessengerBootstrap.cpp b/mailnews/base/src/nsMessengerBootstrap.cpp new file mode 100644 index 0000000000..a78ba3b7c0 --- /dev/null +++ b/mailnews/base/src/nsMessengerBootstrap.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsMessengerBootstrap.h" +#include "nsCOMPtr.h" + +#include "nsIMutableArray.h" +#include "nsIMsgFolder.h" +#include "nsIWindowWatcher.h" +#include "nsMsgUtils.h" +#include "nsISupportsPrimitives.h" +#include "mozIDOMWindow.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +NS_IMPL_ISUPPORTS(nsMessengerBootstrap, nsIMessengerWindowService) + +nsMessengerBootstrap::nsMessengerBootstrap() +{ +} + +nsMessengerBootstrap::~nsMessengerBootstrap() +{ +} + +NS_IMETHODIMP nsMessengerBootstrap::OpenMessengerWindowWithUri(const char *windowType, const char * aFolderURI, nsMsgKey aMessageKey) +{ + bool standAloneMsgWindow = false; + nsAutoCString chromeUrl("chrome://messenger/content/"); + if (windowType && !strcmp(windowType, "mail:messageWindow")) + { + chromeUrl.Append("messageWindow.xul"); + standAloneMsgWindow = true; + } + nsresult rv; + nsCOMPtr<nsIMutableArray> argsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // create scriptable versions of our strings that we can store in our nsIMutableArray.... + if (aFolderURI) + { + if (standAloneMsgWindow) + { + nsCOMPtr <nsIMsgFolder> folder; + rv = GetExistingFolder(nsDependentCString(aFolderURI), getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString msgUri; + folder->GetBaseMessageURI(msgUri); + + nsCOMPtr<nsISupportsCString> scriptableMsgURI (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableMsgURI, NS_ERROR_FAILURE); + msgUri.Append('#'); + msgUri.AppendInt(aMessageKey, 10); + scriptableMsgURI->SetData(msgUri); + argsArray->AppendElement(scriptableMsgURI, false); + } + nsCOMPtr<nsISupportsCString> scriptableFolderURI (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); + NS_ENSURE_TRUE(scriptableFolderURI, NS_ERROR_FAILURE); + + scriptableFolderURI->SetData(nsDependentCString(aFolderURI)); + argsArray->AppendElement(scriptableFolderURI, false); + + if (!standAloneMsgWindow) + { + nsCOMPtr<nsISupportsPRUint32> scriptableMessageKey (do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID)); + NS_ENSURE_TRUE(scriptableMessageKey, NS_ERROR_FAILURE); + scriptableMessageKey->SetData(aMessageKey); + argsArray->AppendElement(scriptableMessageKey, false); + } + } + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // we need to use the "mailnews.reuse_thread_window2" pref + // to determine if we should open a new window, or use an existing one. + nsCOMPtr<mozIDOMWindowProxy> newWindow; + return wwatch->OpenWindow(0, chromeUrl.get(), "_blank", + "chrome,all,dialog=no", argsArray, + getter_AddRefs(newWindow)); +} diff --git a/mailnews/base/src/nsMessengerBootstrap.h b/mailnews/base/src/nsMessengerBootstrap.h new file mode 100644 index 0000000000..94016a0eab --- /dev/null +++ b/mailnews/base/src/nsMessengerBootstrap.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + + +#ifndef __nsMessenger_h +#define __nsMessenger_h + +#include "nscore.h" +#include "nsIMessengerWindowService.h" + +#define NS_MESSENGERBOOTSTRAP_CID \ +{ /* 4a85a5d0-cddd-11d2-b7f6-00805f05ffa5 */ \ + 0x4a85a5d0, 0xcddd, 0x11d2, \ + {0xb7, 0xf6, 0x00, 0x80, 0x5f, 0x05, 0xff, 0xa5}} + +class nsMessengerBootstrap : + public nsIMessengerWindowService +{ +public: + nsMessengerBootstrap(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMESSENGERWINDOWSERVICE + +private: + virtual ~nsMessengerBootstrap(); +}; + +#endif diff --git a/mailnews/base/src/nsMessengerContentHandler.cpp b/mailnews/base/src/nsMessengerContentHandler.cpp new file mode 100644 index 0000000000..5c6460965e --- /dev/null +++ b/mailnews/base/src/nsMessengerContentHandler.cpp @@ -0,0 +1,81 @@ +/* -*- 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 "nsMessengerContentHandler.h" +#include "nsIChannel.h" +#include "nsPIDOMWindow.h" +#include "nsIServiceManager.h" +#include "nsIWindowWatcher.h" +#include "nsIDocShell.h" +#include "nsIWebNavigation.h" +#include "nsIURL.h" +#include "nsStringGlue.h" +#include "nsMsgBaseCID.h" +#include "plstr.h" +#include "nsIURL.h" +#include "nsServiceManagerUtils.h" + +nsMessengerContentHandler::nsMessengerContentHandler() +{ +} + +/* the following macro actually implement addref, release and query interface for our component. */ +NS_IMPL_ISUPPORTS(nsMessengerContentHandler, nsIContentHandler) + +nsMessengerContentHandler::~nsMessengerContentHandler() +{ +} + +NS_IMETHODIMP nsMessengerContentHandler::HandleContent(const char * aContentType, + nsIInterfaceRequestor* aWindowContext, nsIRequest *request) +{ + nsresult rv = NS_OK; + if (!request) + return NS_ERROR_NULL_POINTER; + + // First of all, get the content type and make sure it is a content type we know how to handle! + if (PL_strcasecmp(aContentType, "application/x-message-display") == 0) { + nsCOMPtr<nsIURI> aUri; + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if (!aChannel) return NS_ERROR_FAILURE; + + rv = aChannel->GetURI(getter_AddRefs(aUri)); + if (aUri) + { + rv = request->Cancel(NS_ERROR_ABORT); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIURL> aUrl = do_QueryInterface(aUri); + if (aUrl) + { + nsAutoCString queryPart; + aUrl->GetQuery(queryPart); + queryPart.Replace(queryPart.Find("type=message/rfc822"), + sizeof("type=message/rfc822") - 1, + "type=application/x-message-display"); + aUrl->SetQuery(queryPart); + rv = OpenWindow(aUri); + } + } + } + } + + return rv; +} + +// Utility function to open a message display window and and load the message in it. +nsresult nsMessengerContentHandler::OpenWindow(nsIURI* aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIWindowWatcher> wwatch = do_GetService("@mozilla.org/embedcomp/window-watcher;1"); + if (!wwatch) + return NS_ERROR_FAILURE; + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + return wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul", + "_blank", "all,chrome,dialog=no,status,toolbar", aURI, + getter_AddRefs(newWindow)); +} diff --git a/mailnews/base/src/nsMessengerContentHandler.h b/mailnews/base/src/nsMessengerContentHandler.h new file mode 100644 index 0000000000..dad69493b9 --- /dev/null +++ b/mailnews/base/src/nsMessengerContentHandler.h @@ -0,0 +1,20 @@ +/* -*- 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 "nsIContentHandler.h" +#include "nsIURI.h" + +class nsMessengerContentHandler : public nsIContentHandler +{ +public: + nsMessengerContentHandler(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTHANDLER + +private: + virtual ~nsMessengerContentHandler(); + nsresult OpenWindow(nsIURI* aURI); +}; diff --git a/mailnews/base/src/nsMessengerOSXIntegration.h b/mailnews/base/src/nsMessengerOSXIntegration.h new file mode 100644 index 0000000000..91f42f2a2a --- /dev/null +++ b/mailnews/base/src/nsMessengerOSXIntegration.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef __nsMessengerOSXIntegration_h +#define __nsMessengerOSXIntegration_h + +#include "nsIMessengerOSIntegration.h" +#include "nsIFolderListener.h" +#include "nsIAtom.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsIObserver.h" +#include "nsIAlertsService.h" +#include "mozINewMailListener.h" + +#define NS_MESSENGEROSXINTEGRATION_CID \ + {0xaa83266, 0x4225, 0x4c4b, \ + {0x93, 0xf8, 0x94, 0xb1, 0x82, 0x58, 0x6f, 0x93}} + +class nsIStringBundle; + +class nsMessengerOSXIntegration : public nsIMessengerOSIntegration, + public nsIFolderListener, + public nsIObserver, + public mozINewMailListener +{ +public: + nsMessengerOSXIntegration(); + virtual nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMESSENGEROSINTEGRATION + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_MOZINEWMAILLISTENER + +private: + virtual ~nsMessengerOSXIntegration(); + + nsCOMPtr<nsIAtom> mBiffStateAtom; + nsCOMPtr<nsIAtom> mNewMailReceivedAtom; + nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI); + nsresult OnAlertFinished(); + nsresult OnAlertClicked(const char16_t * aAlertCookie); +#ifdef MOZ_SUITE + nsresult OnAlertClickedSimple(); +#endif + nsresult GetStringBundle(nsIStringBundle **aBundle); + void FillToolTipInfo(nsIMsgFolder *aFolder, int32_t aNewCount); + nsresult GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCString& aFolderURI); + nsresult BadgeDockIcon(); + nsresult RestoreDockIcon(); + nsresult BounceDockIcon(); + nsresult GetNewMailAuthors(nsIMsgFolder* aFolder, nsString& aAuthors, int32_t aNewCount, int32_t* aNotDisplayed); + + int32_t mUnreadTotal; + int32_t mUnreadChat; +}; + +#endif // __nsMessengerOSXIntegration_h diff --git a/mailnews/base/src/nsMessengerOSXIntegration.mm b/mailnews/base/src/nsMessengerOSXIntegration.mm new file mode 100644 index 0000000000..286c760440 --- /dev/null +++ b/mailnews/base/src/nsMessengerOSXIntegration.mm @@ -0,0 +1,730 @@ +/* -*- 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 "nscore.h" +#include "nsMsgUtils.h" +#include "nsArrayUtils.h" +#include "nsMessengerOSXIntegration.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgIdentity.h" +#include "nsIMsgAccount.h" +#include "nsIMsgFolder.h" +#include "nsCOMPtr.h" +#include "nsMsgBaseCID.h" +#include "nsMsgFolderFlags.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIDirectoryService.h" +#include "MailNewsTypes.h" +#include "nsIWindowMediator.h" +#include "nsIDOMChromeWindow.h" +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsIObserverService.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMessengerWindowService.h" +#include "prprf.h" +#include "nsIAlertsService.h" +#include "nsIStringBundle.h" +#include "nsToolkitCompsCID.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsISupportsPrimitives.h" +#include "nsIWindowWatcher.h" +#include "nsMsgLocalCID.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgWindow.h" +#include "nsIMsgAccountManager.h" +#include "nsIMessenger.h" +#include "nsObjCExceptions.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozINewMailNotificationService.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +#include <Carbon/Carbon.h> +#import <Cocoa/Cocoa.h> + +#define kChatEnabledPref "mail.chat.enabled" +#define kBiffAnimateDockIconPref "mail.biff.animate_dock_icon" +#define kMaxDisplayCount 10 +#define kNewChatMessageTopic "new-directed-incoming-message" +#define kUnreadImCountChangedTopic "unread-im-count-changed" + +using namespace mozilla::mailnews; + +// HACK: Limitations in Focus/SetFocus on Mac (see bug 465446) +nsresult FocusAppNative() +{ + ProcessSerialNumber psn; + + if (::GetCurrentProcess(&psn) != 0) + return NS_ERROR_FAILURE; + + if (::SetFrontProcess(&psn) != 0) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +static void openMailWindow(const nsCString& aUri) +{ + nsresult rv; + nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIMsgWindow> topMostMsgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow)); + if (topMostMsgWindow) + { + if (!aUri.IsEmpty()) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgUri(do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + rv = msgUri->SetSpec(aUri); + if (NS_FAILED(rv)) + return; + + bool isMessageUri = false; + msgUri->GetIsMessageUri(&isMessageUri); + if (isMessageUri) + { + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + // SeaMonkey only supports message uris, whereas Thunderbird only + // supports message headers. This should be simplified/removed when + // bug 507593 is implemented. +#ifdef MOZ_SUITE + nsCOMPtr<mozIDOMWindowProxy> newWindow; + wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul", + "_blank", "all,chrome,dialog=no,status,toolbar", msgUri, + getter_AddRefs(newWindow)); +#else + nsCOMPtr<nsIMessenger> messenger(do_CreateInstance(NS_MESSENGER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + messenger->MsgHdrFromURI(aUri, getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsCOMPtr<mozIDOMWindowProxy> newWindow; + wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul", + "_blank", "all,chrome,dialog=no,status,toolbar", msgHdr, + getter_AddRefs(newWindow)); + } +#endif + } + else + { + nsCOMPtr<nsIMsgWindowCommands> windowCommands; + topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands)); + if (windowCommands) + windowCommands->SelectFolder(aUri); + } + } + + FocusAppNative(); + nsCOMPtr<mozIDOMWindowProxy> domWindow; + topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow)); + if (domWindow) { + nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow); + privateWindow->Focus(); + } + } + else + { + // the user doesn't have a mail window open already so open one for them... + nsCOMPtr<nsIMessengerWindowService> messengerWindowService = + do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID); + // if we want to preselect the first account with new mail, + // here is where we would try to generate a uri to pass in + // (and add code to the messenger window service to make that work) + if (messengerWindowService) + messengerWindowService->OpenMessengerWindowWithUri( + "mail:3pane", aUri.get(), nsMsgKey_None); + } +} + +nsMessengerOSXIntegration::nsMessengerOSXIntegration() +{ + mBiffStateAtom = MsgGetAtom("BiffState"); + mNewMailReceivedAtom = MsgGetAtom("NewMailReceived"); + mUnreadTotal = 0; + mUnreadChat = 0; +} + +nsMessengerOSXIntegration::~nsMessengerOSXIntegration() +{ + RestoreDockIcon(); +} + +NS_IMPL_ADDREF(nsMessengerOSXIntegration) +NS_IMPL_RELEASE(nsMessengerOSXIntegration) + +NS_INTERFACE_MAP_BEGIN(nsMessengerOSXIntegration) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration) + NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration) + NS_INTERFACE_MAP_ENTRY(nsIFolderListener) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(mozINewMailListener) +NS_INTERFACE_MAP_END + + +nsresult +nsMessengerOSXIntegration::Init() +{ + nsresult rv; + nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return observerService->AddObserver(this, "mail-startup-done", false); +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) +{ + if (!strcmp(aTopic, "alertfinished")) + return OnAlertFinished(); + + if (!strcmp(aTopic, "alertclickcallback")) + return OnAlertClicked(aData); + +#ifdef MOZ_SUITE + // SeaMonkey does most of the GUI work in JS code when clicking on a mail + // notification, so it needs an extra function here + if (!strcmp(aTopic, "alertclicksimplecallback")) + return OnAlertClickedSimple(); +#endif + + if (!strcmp(aTopic, "mail-startup-done")) { + nsresult rv; + nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + observerService->RemoveObserver(this, "mail-startup-done"); + + bool chatEnabled = false; + nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + rv = pref->GetBoolPref(kChatEnabledPref, &chatEnabled); + if (NS_SUCCEEDED(rv) && chatEnabled) { + observerService->AddObserver(this, kNewChatMessageTopic, false); + observerService->AddObserver(this, kUnreadImCountChangedTopic, false); + } + } + + // Register with the new mail service for changes to the unread message count + nsCOMPtr<mozINewMailNotificationService> newmail + = do_GetService(MOZ_NEWMAILNOTIFICATIONSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); // This should really be an assert with a helpful message + rv = newmail->AddListener(this, mozINewMailNotificationService::count); + NS_ENSURE_SUCCESS(rv, rv); // This should really be an assert with a helpful message + + // Get the initial unread count. Ignore return value; if code above didn't fail, this won't + rv = newmail->GetMessageCount(&mUnreadTotal); + BadgeDockIcon(); + + // register with the mail sesson for folder events + // we care about new count, biff status + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged); + } + + if (!strcmp(aTopic, kNewChatMessageTopic)) { + // We don't have to bother about checking if the window is already focused + // before attempting to bounce the dock icon, as BounceDockIcon is + // implemented by a getAttention call which won't do anything if the window + // requesting attention is already focused. + return BounceDockIcon(); + } + + if (!strcmp(aTopic, kUnreadImCountChangedTopic)) { + nsresult rv; + nsCOMPtr<nsISupportsPRInt32> unreadCount = do_QueryInterface(aSubject, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = unreadCount->GetData(&mUnreadChat); + NS_ENSURE_SUCCESS(rv, rv); + + return BadgeDockIcon(); + } + + return NS_OK; +} + +nsresult +nsMessengerOSXIntegration::GetStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + nsCOMPtr<nsIStringBundle> bundle; + if (bundleService && NS_SUCCEEDED(rv)) + bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle)); + bundle.swap(*aBundle); + return rv; +} + +void +nsMessengerOSXIntegration::FillToolTipInfo(nsIMsgFolder *aFolder, int32_t aNewCount) +{ + if (aFolder) + { + nsString authors; + int32_t numNotDisplayed; + nsresult rv = GetNewMailAuthors(aFolder, authors, aNewCount, &numNotDisplayed); + + // If all senders are vetoed, the authors string will be empty. + if (NS_FAILED(rv) || authors.IsEmpty()) + return; + + // If this isn't the root folder, get it so we can report for it. + // GetRootFolder always returns the server's root, so calling on the root itself is fine. + nsCOMPtr<nsIMsgFolder> rootFolder; + aFolder->GetRootFolder(getter_AddRefs(rootFolder)); + if (!rootFolder) + return; + + nsString accountName; + rootFolder->GetPrettiestName(accountName); + + nsCOMPtr<nsIStringBundle> bundle; + GetStringBundle(getter_AddRefs(bundle)); + if (bundle) + { + nsAutoString numNewMsgsText; + numNewMsgsText.AppendInt(aNewCount); + nsString finalText; + nsCString uri; + aFolder->GetURI(uri); + + if (numNotDisplayed > 0) + { + nsAutoString numNotDisplayedText; + numNotDisplayedText.AppendInt(numNotDisplayed); + const char16_t *formatStrings[3] = { numNewMsgsText.get(), authors.get(), numNotDisplayedText.get() }; + bundle->FormatStringFromName(u"macBiffNotification_messages_extra", + formatStrings, + 3, + getter_Copies(finalText)); + } + else + { + const char16_t *formatStrings[2] = { numNewMsgsText.get(), authors.get() }; + + if (aNewCount == 1) + { + bundle->FormatStringFromName(u"macBiffNotification_message", + formatStrings, + 2, + getter_Copies(finalText)); + // Since there is only 1 message, use the most recent mail's URI instead of the folder's + nsCOMPtr<nsIMsgDatabase> db; + rv = aFolder->GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + { + uint32_t numNewKeys; + uint32_t *newMessageKeys; + rv = db->GetNewList(&numNewKeys, &newMessageKeys); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = db->GetMsgHdrForKey(newMessageKeys[numNewKeys - 1], + getter_AddRefs(hdr)); + if (NS_SUCCEEDED(rv) && hdr) + aFolder->GetUriForMsg(hdr, uri); + } + NS_Free(newMessageKeys); + } + } + else + bundle->FormatStringFromName(u"macBiffNotification_messages", + formatStrings, + 2, + getter_Copies(finalText)); + } + ShowAlertMessage(accountName, finalText, uri); + } // if we got a bundle + } // if we got a folder +} + +nsresult +nsMessengerOSXIntegration::ShowAlertMessage(const nsAString& aAlertTitle, + const nsAString& aAlertText, + const nsACString& aFolderURI) +{ + nsresult rv; + + nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv)); + // If we have an nsIAlertsService implementation, use it: + if (NS_SUCCEEDED(rv)) + { + alertsService->ShowAlertNotification(EmptyString(), + aAlertTitle, aAlertText, true, + NS_ConvertASCIItoUTF16(aFolderURI), + this, EmptyString(), + NS_LITERAL_STRING("auto"), + EmptyString(), EmptyString(), + nullptr, + false, + false); + } + + BounceDockIcon(); + + if (NS_FAILED(rv)) + OnAlertFinished(); + + return rv; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder, + nsIAtom *aProperty, + int64_t aOldValue, + int64_t aNewValue) +{ + // if we got new mail show an alert + if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail) + { + bool performingBiff = false; + nsCOMPtr<nsIMsgIncomingServer> server; + aFolder->GetServer(getter_AddRefs(server)); + if (server) + server->GetPerformingBiff(&performingBiff); + if (!performingBiff) + return NS_OK; // kick out right now... + + // Biff happens for the root folder, but we want info for the child with new mail + nsCString folderUri; + GetFirstFolderWithNewMail(aFolder, folderUri); + nsCOMPtr<nsIMsgFolder> childFolder; + nsresult rv = aFolder->GetChildWithURI(folderUri, true, true, + getter_AddRefs(childFolder)); + if (NS_FAILED(rv) || !childFolder) + return NS_ERROR_FAILURE; + + int32_t numNewMessages = 0; + childFolder->GetNumNewMessages(true, &numNewMessages); + FillToolTipInfo(childFolder, numNewMessages); + } + else if (mNewMailReceivedAtom == aProperty) + { + FillToolTipInfo(aFolder, aNewValue); + } + return NS_OK; +} + +nsresult +nsMessengerOSXIntegration::OnAlertClicked(const char16_t* aAlertCookie) +{ + openMailWindow(NS_ConvertUTF16toUTF8(aAlertCookie)); + return NS_OK; +} + +#ifdef MOZ_SUITE +nsresult +nsMessengerOSXIntegration::OnAlertClickedSimple() +{ + // SeaMonkey only function; only focus the app here, rest of the work will + // be done in suite/mailnews/mailWidgets.xml + FocusAppNative(); + return NS_OK; +} +#endif + +nsresult +nsMessengerOSXIntegration::OnAlertFinished() +{ + return NS_OK; +} + +nsresult +nsMessengerOSXIntegration::BounceDockIcon() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool bounceDockIcon = false; + rv = prefBranch->GetBoolPref(kBiffAnimateDockIconPref, &bounceDockIcon); + NS_ENSURE_SUCCESS(rv, rv); + + if (!bounceDockIcon) + return NS_OK; + + nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (mediator) + { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + mediator->GetMostRecentWindow(u"mail:3pane", getter_AddRefs(domWindow)); + if (domWindow) + { + nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(domWindow)); + chromeWindow->GetAttention(); + } + } + return NS_OK; +} + +nsresult +nsMessengerOSXIntegration::RestoreDockIcon() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + id tile = [[NSApplication sharedApplication] dockTile]; + [tile setBadgeLabel: nil]; + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult +nsMessengerOSXIntegration::BadgeDockIcon() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + int32_t unreadCount = mUnreadTotal + mUnreadChat; + // If count is less than one, we should restore the original dock icon. + if (unreadCount < 1) + { + RestoreDockIcon(); + return NS_OK; + } + + // Draw the number, first giving extensions a chance to modify. + // Extensions might wish to transform "1000" into "100+" or some + // other short string. Getting back the empty string will cause + // nothing to be drawn and us to return early. + nsresult rv; + nsCOMPtr<nsIObserverService> os + (do_GetService("@mozilla.org/observer-service;1", &rv)); + if (NS_FAILED(rv)) + { + RestoreDockIcon(); + return rv; + } + + nsCOMPtr<nsISupportsString> str + (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + { + RestoreDockIcon(); + return rv; + } + + nsAutoString total; + total.AppendInt(unreadCount); + str->SetData(total); + os->NotifyObservers(str, "before-unread-count-display", + total.get()); + nsAutoString badgeString; + str->GetData(badgeString); + if (badgeString.IsEmpty()) + { + RestoreDockIcon(); + return NS_OK; + } + + id tile = [[NSApplication sharedApplication] dockTile]; + [tile setBadgeLabel:[NSString stringWithFormat:@"%S", (const unichar*)badgeString.get()]]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + bool aOldValue, + bool aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerOSXIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *) +{ + return NS_OK; +} + +nsresult +nsMessengerOSXIntegration::GetNewMailAuthors(nsIMsgFolder* aFolder, + nsString& aAuthors, + int32_t aNewCount, + int32_t* aNotDisplayed) +{ + // Get a list of names or email addresses for the folder's authors + // with new mail. Note that we only process the most recent "new" + // mail (aNewCount), working from most recently added. Duplicates + // are removed, and names are displayed to a set limit + // (kMaxDisplayCount) with the remaining count being returned in + // aNotDisplayed. Extension developers can listen for + // "newmail-notification-requested" and then make a decision about + // including a given author or not. As a result, it is possible that + // the resulting length of aAuthors will be 0. + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db)); + uint32_t numNewKeys = 0; + if (NS_SUCCEEDED(rv) && db) + { + nsCOMPtr<nsIObserverService> os = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get proper l10n list separator -- ", " in English + nsCOMPtr<nsIStringBundle> bundle; + GetStringBundle(getter_AddRefs(bundle)); + if (!bundle) + return NS_ERROR_FAILURE; + + uint32_t *newMessageKeys; + rv = db->GetNewList(&numNewKeys, &newMessageKeys); + if (NS_SUCCEEDED(rv)) + { + nsString listSeparator; + bundle->GetStringFromName(u"macBiffNotification_separator", getter_Copies(listSeparator)); + + int32_t displayed = 0; + for (int32_t i = numNewKeys - 1; i >= 0; i--, aNewCount--) + { + if (0 == aNewCount || displayed == kMaxDisplayCount) + break; + + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = db->GetMsgHdrForKey(newMessageKeys[i], + getter_AddRefs(hdr)); + if (NS_SUCCEEDED(rv) && hdr) + { + nsString author; + rv = hdr->GetMime2DecodedAuthor(author); + if (NS_FAILED(rv)) + continue; + + nsString name; + ExtractName(DecodedHeader(author), name); + + // Give extensions a chance to suppress notifications for this author + nsCOMPtr<nsISupportsPRBool> notify = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + + notify->SetData(true); + os->NotifyObservers(notify, "newmail-notification-requested", + author.get()); + + bool includeSender; + notify->GetData(&includeSender); + + // Don't add unwanted or duplicate names + if (includeSender && aAuthors.Find(name, true) == -1) + { + if (displayed > 0) + aAuthors.Append(listSeparator); + aAuthors.Append(name); + displayed++; + } + } + } + } + NS_Free(newMessageKeys); + } + *aNotDisplayed = aNewCount; + return rv; +} + +nsresult +nsMessengerOSXIntegration::GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCString& aFolderURI) +{ + // Find the subfolder in aFolder with new mail and return the folderURI + if (aFolder) + { + nsCOMPtr<nsIMsgFolder> msgFolder; + // enumerate over the folders under this root folder till we find one with new mail.... + nsCOMPtr<nsIArray> allFolders; + nsresult rv = aFolder->GetDescendants(getter_AddRefs(allFolders)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = allFolders->Enumerate(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(rv) && enumerator) + { + nsCOMPtr<nsISupports> supports; + int32_t numNewMessages = 0; + bool hasMore = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = enumerator->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + msgFolder = do_QueryInterface(supports, &rv); + if (msgFolder) + { + numNewMessages = 0; + msgFolder->GetNumNewMessages(false, &numNewMessages); + if (numNewMessages) + break; // kick out of the while loop + } + } // if we have a folder + } // if we have more potential folders to enumerate + } // if enumerator + + if (msgFolder) + msgFolder->GetURI(aFolderURI); + } + + return NS_OK; +} + +/* + * Method implementations for mozINewMailListener + */ +NS_IMETHODIMP +nsMessengerOSXIntegration::OnCountChanged(uint32_t count) +{ + mUnreadTotal = count; + BadgeDockIcon(); + return NS_OK; +} diff --git a/mailnews/base/src/nsMessengerUnixIntegration.cpp b/mailnews/base/src/nsMessengerUnixIntegration.cpp new file mode 100644 index 0000000000..0e4dd14051 --- /dev/null +++ b/mailnews/base/src/nsMessengerUnixIntegration.cpp @@ -0,0 +1,762 @@ +/* -*- 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 "nsMessengerUnixIntegration.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgIdentity.h" +#include "nsIMsgAccount.h" +#include "nsIMsgFolder.h" +#include "nsIMsgWindow.h" +#include "nsCOMPtr.h" +#include "nsMsgBaseCID.h" +#include "nsMsgFolderFlags.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIDirectoryService.h" +#include "nsIWindowWatcher.h" +#include "nsIWindowMediator.h" +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "MailNewsTypes.h" +#include "nsIMessengerWindowService.h" +#include "prprf.h" +#include "nsIWeakReference.h" +#include "nsIStringBundle.h" +#include "nsIAlertsService.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsISupportsPrimitives.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsAutoPtr.h" +#include "prmem.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIWeakReferenceUtils.h" + +#include "nsNativeCharsetUtils.h" +#include "nsToolkitCompsCID.h" +#include "nsMsgUtils.h" +#include "msgCore.h" +#include "nsCOMArray.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsMemory.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +#define ALERT_CHROME_URL "chrome://messenger/content/newmailalert.xul" +#define NEW_MAIL_ALERT_ICON "chrome://messenger/skin/icons/new-mail-alert.png" +#define SHOW_ALERT_PREF "mail.biff.show_alert" +#define SHOW_ALERT_PREVIEW_LENGTH "mail.biff.alert.preview_length" +#define SHOW_ALERT_PREVIEW_LENGTH_DEFAULT 40 +#define SHOW_ALERT_PREVIEW "mail.biff.alert.show_preview" +#define SHOW_ALERT_SENDER "mail.biff.alert.show_sender" +#define SHOW_ALERT_SUBJECT "mail.biff.alert.show_subject" +#define SHOW_ALERT_SYSTEM "mail.biff.use_system_alert" + +using namespace mozilla::mailnews; + +static void openMailWindow(const nsACString& aFolderUri) +{ + nsresult rv; + nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIMsgWindow> topMostMsgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow)); + if (topMostMsgWindow) + { + if (!aFolderUri.IsEmpty()) + { + nsCOMPtr<nsIMsgWindowCommands> windowCommands; + topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands)); + if (windowCommands) + windowCommands->SelectFolder(aFolderUri); + } + + nsCOMPtr<mozIDOMWindowProxy> domWindow; + topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow)); + if (domWindow) { + nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow); + privateWindow->Focus(); + } + } + else + { + // the user doesn't have a mail window open already so open one for them... + nsCOMPtr<nsIMessengerWindowService> messengerWindowService = + do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID); + // if we want to preselect the first account with new mail, + // here is where we would try to generate a uri to pass in + // (and add code to the messenger window service to make that work) + if (messengerWindowService) + messengerWindowService->OpenMessengerWindowWithUri( + "mail:3pane", nsCString(aFolderUri).get(), nsMsgKey_None); + } +} + +nsMessengerUnixIntegration::nsMessengerUnixIntegration() +{ + mBiffStateAtom = MsgGetAtom("BiffState"); + mNewMailReceivedAtom = MsgGetAtom("NewMailReceived"); + mAlertInProgress = false; + mFoldersWithNewMail = do_CreateInstance(NS_ARRAY_CONTRACTID); +} + +NS_IMPL_ISUPPORTS(nsMessengerUnixIntegration, nsIFolderListener, nsIObserver, + nsIMessengerOSIntegration, nsIUrlListener) + +nsresult +nsMessengerUnixIntegration::Init() +{ + nsresult rv; + + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + return mailSession->AddFolderListener(this, nsIFolderListener::intPropertyChanged); +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +nsresult nsMessengerUnixIntegration::GetStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + bundle.swap(*aBundle); + return NS_OK; +} + +bool +nsMessengerUnixIntegration::BuildNotificationTitle(nsIMsgFolder *aFolder, nsIStringBundle *aBundle, nsString &aTitle) +{ + nsString accountName; + aFolder->GetPrettiestName(accountName); + + int32_t numNewMessages = 0; + aFolder->GetNumNewMessages(true, &numNewMessages); + + if (!numNewMessages) + return false; + + nsAutoString numNewMsgsText; + numNewMsgsText.AppendInt(numNewMessages); + + const char16_t *formatStrings[] = + { + accountName.get(), numNewMsgsText.get() + }; + + aBundle->FormatStringFromName(numNewMessages == 1 ? + u"newMailNotification_message" : + u"newMailNotification_messages", + formatStrings, 2, getter_Copies(aTitle)); + return true; +} + +/* This comparator lets us sort an nsCOMArray of nsIMsgDBHdr's by + * their dateInSeconds attributes in ascending order. + */ +static int +nsMsgDbHdrTimestampComparator(nsIMsgDBHdr *aElement1, + nsIMsgDBHdr *aElement2, + void *aData) +{ + uint32_t aElement1Timestamp; + nsresult rv = aElement1->GetDateInSeconds(&aElement1Timestamp); + if (NS_FAILED(rv)) + return 0; + + uint32_t aElement2Timestamp; + rv = aElement2->GetDateInSeconds(&aElement2Timestamp); + if (NS_FAILED(rv)) + return 0; + + return aElement1Timestamp - aElement2Timestamp; +} + + +bool +nsMessengerUnixIntegration::BuildNotificationBody(nsIMsgDBHdr *aHdr, + nsIStringBundle *aBundle, + nsString &aBody) +{ + nsAutoString alertBody; + + bool showPreview = true; + bool showSubject = true; + bool showSender = true; + int32_t previewLength = SHOW_ALERT_PREVIEW_LENGTH_DEFAULT; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (!prefBranch) + return false; + + prefBranch->GetBoolPref(SHOW_ALERT_PREVIEW, &showPreview); + prefBranch->GetBoolPref(SHOW_ALERT_SENDER, &showSender); + prefBranch->GetBoolPref(SHOW_ALERT_SUBJECT, &showSubject); + prefBranch->GetIntPref(SHOW_ALERT_PREVIEW_LENGTH, &previewLength); + + nsCOMPtr<nsIMsgFolder> folder; + aHdr->GetFolder(getter_AddRefs(folder)); + + if (!folder) + return false; + + nsCString msgURI; + folder->GetUriForMsg(aHdr, msgURI); + + bool localOnly; + + size_t msgURIIndex = mFetchingURIs.IndexOf(msgURI); + if (msgURIIndex == mFetchingURIs.NoIndex) + { + localOnly = false; + mFetchingURIs.AppendElement(msgURI); + } + else + localOnly = true; + + nsMsgKey messageKey; + if (NS_FAILED(aHdr->GetMessageKey(&messageKey))) + return false; + + bool asyncResult = false; + nsresult rv = folder->FetchMsgPreviewText(&messageKey, 1, + localOnly, this, + &asyncResult); + // If we're still waiting on getting the message previews, + // bail early. We'll come back later when the async operation + // finishes. + if (NS_FAILED(rv) || asyncResult) + return false; + + // If we got here, that means that we've retrieved the message preview, + // so we can stop tracking it with our mFetchingURIs array. + if (msgURIIndex != mFetchingURIs.NoIndex) + mFetchingURIs.RemoveElementAt(msgURIIndex); + + nsCString utf8previewString; + if (showPreview && + NS_FAILED(aHdr->GetStringProperty("preview", getter_Copies(utf8previewString)))) + return false; + + // need listener that mailbox is remote such as IMAP + // to generate preview message + nsString previewString; + CopyUTF8toUTF16(utf8previewString, previewString); + + nsString subject; + if (showSubject && NS_FAILED(aHdr->GetMime2DecodedSubject(subject))) + return false; + + nsString author; + if (showSender) + { + nsString fullHeader; + if (NS_FAILED(aHdr->GetMime2DecodedAuthor(fullHeader))) + return false; + + ExtractName(DecodedHeader(fullHeader), author); + } + + if (showSubject && showSender) + { + nsString msgTitle; + const char16_t *formatStrings[] = + { + subject.get(), author.get() + }; + aBundle->FormatStringFromName(u"newMailNotification_messagetitle", + formatStrings, 2, getter_Copies(msgTitle)); + alertBody.Append(msgTitle); + } + else if (showSubject) + alertBody.Append(subject); + else if (showSender) + alertBody.Append(author); + + if (showPreview && (showSubject || showSender)) + { + alertBody.AppendLiteral("\n"); + } + + if (showPreview) + alertBody.Append(StringHead(previewString, previewLength)); + + if (alertBody.IsEmpty()) + return false; + + aBody.Assign(alertBody); + return true; +} + +nsresult nsMessengerUnixIntegration::ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI) +{ + nsresult rv; + // if we are already in the process of showing an alert, don't try to show another.... + if (mAlertInProgress) + return NS_OK; + + mAlertInProgress = true; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // determine if we should use libnotify or the built-in alert system + bool useSystemAlert = true; + prefBranch->GetBoolPref(SHOW_ALERT_SYSTEM, &useSystemAlert); + + if (useSystemAlert) { + nsCOMPtr<nsIAlertsService> alertsService(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = alertsService->ShowAlertNotification(NS_LITERAL_STRING(NEW_MAIL_ALERT_ICON), + aAlertTitle, + aAlertText, + false, + NS_ConvertASCIItoUTF16(aFolderURI), + this, + EmptyString(), + NS_LITERAL_STRING("auto"), + EmptyString(), + EmptyString(), + nullptr, + false, + false); + if (NS_SUCCEEDED(rv)) + return rv; + } + } + AlertFinished(); + rv = ShowNewAlertNotification(false); + + if (NS_FAILED(rv)) // go straight to showing the system tray icon. + AlertFinished(); + + return rv; +} + +// Opening Thunderbird's new mail alert notification window for not supporting libnotify +// aUserInitiated --> true if we are opening the alert notification in response to a user action +// like clicking on the biff icon +nsresult nsMessengerUnixIntegration::ShowNewAlertNotification(bool aUserInitiated) +{ + + nsresult rv; + + // if we are already in the process of showing an alert, don't try to show another.... + if (mAlertInProgress) + return NS_OK; + + nsCOMPtr<nsIMutableArray> argsArray = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (!argsArray) + return NS_ERROR_FAILURE; + + // pass in the array of folders with unread messages + nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + ifptr->SetData(mFoldersWithNewMail); + ifptr->SetDataIID(&NS_GET_IID(nsIArray)); + argsArray->AppendElement(ifptr, false); + + // pass in the observer + ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMessengerOSIntegration*>(this)); + ifptr->SetData(supports); + ifptr->SetDataIID(&NS_GET_IID(nsIObserver)); + argsArray->AppendElement(ifptr, false); + + // pass in the animation flag + nsCOMPtr<nsISupportsPRBool> scriptableUserInitiated (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + scriptableUserInitiated->SetData(aUserInitiated); + argsArray->AppendElement(scriptableUserInitiated, false); + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + nsCOMPtr<mozIDOMWindowProxy> newWindow; + + mAlertInProgress = true; + rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank", + "chrome,dialog=yes,titlebar=no,popup=yes", argsArray, + getter_AddRefs(newWindow)); + + if (NS_FAILED(rv)) + AlertFinished(); + + return rv; +} + +nsresult nsMessengerUnixIntegration::AlertFinished() +{ + mAlertInProgress = false; + return NS_OK; +} + +nsresult nsMessengerUnixIntegration::AlertClicked() +{ + nsCString folderURI; + GetFirstFolderWithNewMail(folderURI); + openMailWindow(folderURI); + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) +{ + if (strcmp(aTopic, "alertfinished") == 0) + return AlertFinished(); + if (strcmp(aTopic, "alertclickcallback") == 0) + return AlertClicked(); + + return NS_OK; +} + +void nsMessengerUnixIntegration::FillToolTipInfo() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,); + + bool showAlert = true; + prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert); + if (!showAlert) + return; + + nsCString folderUri; + GetFirstFolderWithNewMail(folderUri); + + uint32_t count = 0; + NS_ENSURE_SUCCESS_VOID(mFoldersWithNewMail->GetLength(&count)); + + nsCOMPtr<nsIWeakReference> weakReference; + nsCOMPtr<nsIMsgFolder> folder = nullptr; + nsCOMPtr<nsIMsgFolder> folderWithNewMail = nullptr; + + uint32_t i; + for (i = 0; i < count && !folderWithNewMail; i++) + { + weakReference = do_QueryElementAt(mFoldersWithNewMail, i); + folder = do_QueryReferent(weakReference); + folder->GetChildWithURI(folderUri, true, true, + getter_AddRefs(folderWithNewMail)); + } + + if (folder && folderWithNewMail) + { + nsCOMPtr<nsIStringBundle> bundle; + GetStringBundle(getter_AddRefs(bundle)); + + if (!bundle) + return; + + // Create the notification title + nsString alertTitle; + if (!BuildNotificationTitle(folder, bundle, alertTitle)) + return; + + // Let's get the new mail for this folder + nsCOMPtr<nsIMsgDatabase> db; + if (NS_FAILED(folderWithNewMail->GetMsgDatabase(getter_AddRefs(db)))) + return; + + uint32_t numNewKeys = 0; + uint32_t *newMessageKeys; + db->GetNewList(&numNewKeys, &newMessageKeys); + + // If we had new messages, we *should* have new keys, but we'll + // check just in case. + if (numNewKeys <= 0) { + NS_Free(newMessageKeys); + return; + } + + // Find the rootFolder that folder belongs to, and find out + // what MRUTime it maps to. Assign this to lastMRUTime. + uint32_t lastMRUTime = 0; + if (NS_FAILED(GetMRUTimestampForFolder(folder, &lastMRUTime))) + lastMRUTime = 0; + + // Next, add the new message headers to an nsCOMArray. We + // only add message headers that are newer than lastMRUTime. + nsCOMArray<nsIMsgDBHdr> newMsgHdrs; + for (unsigned int i = 0; i < numNewKeys; ++i) { + nsCOMPtr<nsIMsgDBHdr> hdr; + if (NS_FAILED(db->GetMsgHdrForKey(newMessageKeys[i], getter_AddRefs(hdr)))) + continue; + + uint32_t dateInSeconds = 0; + hdr->GetDateInSeconds(&dateInSeconds); + + if (dateInSeconds > lastMRUTime) + newMsgHdrs.AppendObject(hdr); + + } + + // At this point, we don't need newMessageKeys any more, + // so let's free it. + NS_Free(newMessageKeys); + + // If we didn't happen to add any message headers, bail out + if (!newMsgHdrs.Count()) + return; + + // Sort the message headers by dateInSeconds, in ascending + // order + newMsgHdrs.Sort(nsMsgDbHdrTimestampComparator, nullptr); + + nsString alertBody; + + // Build the body text of the notification. + if (!BuildNotificationBody(newMsgHdrs[0], bundle, alertBody)) + return; + + // Show the notification + ShowAlertMessage(alertTitle, alertBody, EmptyCString()); + + // Find the last, and therefore newest message header + // in our nsCOMArray + nsCOMPtr<nsIMsgDBHdr> lastMsgHdr = newMsgHdrs[newMsgHdrs.Count() - 1]; + + uint32_t dateInSeconds = 0; + if (NS_FAILED(lastMsgHdr->GetDateInSeconds(&dateInSeconds))) + return; + + // Write the newest message timestamp to the appropriate + // mapping in our hashtable of MRUTime's. + PutMRUTimestampForFolder(folder, dateInSeconds); + } // if we got a folder +} + +// Get the first top level folder which we know has new mail, then enumerate over +// all the subfolders looking for the first real folder with new mail. +// Return the folderURI for that folder. +nsresult nsMessengerUnixIntegration::GetFirstFolderWithNewMail(nsACString& aFolderURI) +{ + NS_ENSURE_TRUE(mFoldersWithNewMail, NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgFolder> folder; + nsCOMPtr<nsIWeakReference> weakReference; + + uint32_t count = 0; + nsresult rv = mFoldersWithNewMail->GetLength(&count); + if (NS_FAILED(rv) || !count) // kick out if we don't have any folders with new mail + return NS_OK; + + uint32_t i; + for(i = 0; i < count; i++) + { + weakReference = do_QueryElementAt(mFoldersWithNewMail, i); + folder = do_QueryReferent(weakReference); + + // We only want to find folders which haven't been notified + // yet. This is specific to Thunderbird. In Seamonkey, we + // just return 0, and we don't care about timestamps anymore. + uint32_t lastMRUTime = 0; + rv = GetMRUTimestampForFolder(folder, &lastMRUTime); + if (NS_FAILED(rv)) + lastMRUTime = 0; + + if (!folder) + continue; + // enumerate over the folders under this root folder till we find one with new mail.... + nsCOMPtr<nsIMsgFolder> msgFolder; + nsCOMPtr<nsIArray> allFolders; + rv = folder->GetDescendants(getter_AddRefs(allFolders)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t subfolderCount = 0; + allFolders->GetLength(&subfolderCount); + uint32_t j; + for (j = 0; j < subfolderCount; j++) + { + nsCOMPtr<nsIMsgFolder> msgFolder = do_QueryElementAt(allFolders, j); + + if (!msgFolder) + continue; + + uint32_t flags; + rv = msgFolder->GetFlags(&flags); + + if (NS_FAILED(rv)) + continue; + + // Unless we're dealing with an Inbox, we don't care + // about Drafts, Queue, SentMail, Template, or Junk folders + if (!(flags & nsMsgFolderFlags::Inbox) && + (flags & (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Inbox))) + continue; + + nsCString folderURI; + msgFolder->GetURI(folderURI); + bool hasNew = false; + rv = msgFolder->GetHasNewMessages(&hasNew); + + if (NS_FAILED(rv)) + continue; + + // Grab the MRUTime property from the folder + nsCString dateStr; + msgFolder->GetStringProperty(MRU_TIME_PROPERTY, dateStr); + uint32_t MRUTime = (uint32_t) dateStr.ToInteger(&rv, 10); + if (NS_FAILED(rv)) + MRUTime = 0; + + if (hasNew && MRUTime > lastMRUTime) + { + rv = msgFolder->GetURI(aFolderURI); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + } // if we have more potential folders to enumerate + } + + // If we got here, then something when pretty wrong. + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + bool aOldValue, + bool aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, int64_t aOldValue, int64_t aNewValue) +{ + nsCString atomName; + // if we got new mail show an icon in the system tray + if (mBiffStateAtom == aProperty && mFoldersWithNewMail) + { + nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aItem); + uint32_t indexInNewArray; + nsresult rv = mFoldersWithNewMail->IndexOf(0, weakFolder, &indexInNewArray); + bool folderFound = NS_SUCCEEDED(rv); + + if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail) + { + // only show a system tray icon iff we are performing biff + // (as opposed to the user getting new mail) + bool performingBiff = false; + nsCOMPtr<nsIMsgIncomingServer> server; + aItem->GetServer(getter_AddRefs(server)); + if (server) + server->GetPerformingBiff(&performingBiff); + if (!performingBiff) + return NS_OK; // kick out right now... + + if (!folderFound) + mFoldersWithNewMail->AppendElement(weakFolder, false); + // now regenerate the tooltip + FillToolTipInfo(); + } + else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail) + { + if (folderFound) { + mFoldersWithNewMail->RemoveElementAt(indexInNewArray); + } + } + } // if the biff property changed + else if (mNewMailReceivedAtom == aProperty) + { + FillToolTipInfo(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnStartRunningUrl(nsIURI *aUrl) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerUnixIntegration::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) +{ + if (NS_SUCCEEDED(aExitCode)) + // preview fetch is done. + FillToolTipInfo(); + return NS_OK; +} + +nsresult +nsMessengerUnixIntegration::GetMRUTimestampForFolder(nsIMsgFolder *aFolder, + uint32_t *aLastMRUTime) +{ + nsCOMPtr<nsIMsgFolder> rootFolder = nullptr; + nsresult rv = aFolder->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString rootFolderURI; + rootFolder->GetURI(rootFolderURI); + if (!mLastMRUTimes.Get(rootFolderURI, aLastMRUTime)) + aLastMRUTime = 0; + + return NS_OK; +} + +nsresult +nsMessengerUnixIntegration::PutMRUTimestampForFolder(nsIMsgFolder *aFolder, + uint32_t aLastMRUTime) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> rootFolder = nullptr; + rv = aFolder->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString rootFolderURI; + rootFolder->GetURI(rootFolderURI); + mLastMRUTimes.Put(rootFolderURI, aLastMRUTime); + + return NS_OK; +} diff --git a/mailnews/base/src/nsMessengerUnixIntegration.h b/mailnews/base/src/nsMessengerUnixIntegration.h new file mode 100644 index 0000000000..ba9f0f3f53 --- /dev/null +++ b/mailnews/base/src/nsMessengerUnixIntegration.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef __nsMessengerUnixIntegration_h +#define __nsMessengerUnixIntegration_h + +#include "nsIMessengerOSIntegration.h" +#include "nsIFolderListener.h" +#include "nsIUrlListener.h" +#include "nsIMutableArray.h" +#include "nsIStringBundle.h" +#include "nsIObserver.h" +#include "nsIAtom.h" +#include "nsDataHashtable.h" +#include "nsTArray.h" + +#define NS_MESSENGERUNIXINTEGRATION_CID \ + {0xf62f3d3a, 0x1dd1, 0x11b2, \ + {0xa5, 0x16, 0xef, 0xad, 0xb1, 0x31, 0x61, 0x5c}} + +class nsIStringBundle; + +class nsMessengerUnixIntegration : public nsIFolderListener, + public nsIObserver, + public nsIUrlListener, + public nsIMessengerOSIntegration +{ +public: + nsMessengerUnixIntegration(); + virtual nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMESSENGEROSINTEGRATION + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSIURLLISTENER + +private: + virtual ~nsMessengerUnixIntegration() {} + nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI); + nsresult GetFirstFolderWithNewMail(nsACString& aFolderURI); + nsresult GetStringBundle(nsIStringBundle **aBundle); + nsresult AlertFinished(); + nsresult AlertClicked(); + void FillToolTipInfo(); + nsresult GetMRUTimestampForFolder(nsIMsgFolder *aFolder, uint32_t *aLastMRUTime); + + bool BuildNotificationBody(nsIMsgDBHdr *aHdr, nsIStringBundle *Bundle, nsString &aBody); + bool BuildNotificationTitle(nsIMsgFolder *aFolder, nsIStringBundle *aBundle, nsString &aTitle); + nsresult ShowNewAlertNotification(bool aUserInitiated); + nsresult PutMRUTimestampForFolder(nsIMsgFolder *aFolder, uint32_t aLastMRUTime); + + nsCOMPtr<nsIMutableArray> mFoldersWithNewMail; // keep track of all the root folders with pending new mail + nsCOMPtr<nsIAtom> mBiffStateAtom; + nsCOMPtr<nsIAtom> mNewMailReceivedAtom; + bool mAlertInProgress; + nsDataHashtable<nsCStringHashKey, uint32_t> mLastMRUTimes; // We keep track of the last time we did a new mail notification for each account + nsTArray<nsCString> mFetchingURIs; +}; + +#endif // __nsMessengerUnixIntegration_h diff --git a/mailnews/base/src/nsMessengerWinIntegration.cpp b/mailnews/base/src/nsMessengerWinIntegration.cpp new file mode 100644 index 0000000000..a014b9737a --- /dev/null +++ b/mailnews/base/src/nsMessengerWinIntegration.cpp @@ -0,0 +1,1191 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +/* 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 <windows.h> +#include <shellapi.h> + +#include "nsMessengerWinIntegration.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgIdentity.h" +#include "nsIMsgAccount.h" +#include "nsIMsgFolder.h" +#include "nsIMsgWindow.h" +#include "nsCOMPtr.h" +#include "nsMsgBaseCID.h" +#include "nsMsgFolderFlags.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIDirectoryService.h" +#include "nsIWindowWatcher.h" +#include "nsIWindowMediator.h" +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsWidgetsCID.h" +#include "MailNewsTypes.h" +#include "nsIMessengerWindowService.h" +#include "prprf.h" +#include "nsIWeakReference.h" +#include "nsIStringBundle.h" +#include "nsIAlertsService.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIProperties.h" +#include "nsISupportsPrimitives.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWeakReferenceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsNativeCharsetUtils.h" +#include "nsMsgUtils.h" +#ifdef MOZILLA_INTERNAL_API +#include "mozilla/LookAndFeel.h" +#endif +#include "mozilla/Services.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" + +#include "nsToolkitCompsCID.h" +#include <stdlib.h> +#define PROFILE_COMMANDLINE_ARG " -profile " + +#define NOTIFICATIONCLASSNAME "MailBiffNotificationMessageWindow" +#define UNREADMAILNODEKEY "Software\\Microsoft\\Windows\\CurrentVersion\\UnreadMail\\" +#define SHELL32_DLL L"shell32.dll" +#define DOUBLE_QUOTE "\"" +#define MAIL_COMMANDLINE_ARG " -mail" +#define IDI_MAILBIFF 32576 +#define UNREAD_UPDATE_INTERVAL (20 * 1000) // 20 seconds +#define ALERT_CHROME_URL "chrome://messenger/content/newmailalert.xul" +#define NEW_MAIL_ALERT_ICON "chrome://messenger/skin/icons/new-mail-alert.png" +#define SHOW_ALERT_PREF "mail.biff.show_alert" +#define SHOW_TRAY_ICON_PREF "mail.biff.show_tray_icon" +#define SHOW_BALLOON_PREF "mail.biff.show_balloon" +#define SHOW_NEW_ALERT_PREF "mail.biff.show_new_alert" +#define ALERT_ORIGIN_PREF "ui.alertNotificationOrigin" + +// since we are including windows.h in this file, undefine get user name.... +#ifdef GetUserName +#undef GetUserName +#endif + +#ifndef NIIF_USER +#define NIIF_USER 0x00000004 +#endif + +#ifndef NIIF_NOSOUND +#define NIIF_NOSOUND 0x00000010 +#endif + +#ifndef NIN_BALOONUSERCLICK +#define NIN_BALLOONUSERCLICK (WM_USER + 5) +#endif + +#ifndef MOZILLA_INTERNAL_API +// from LookAndFeel.h +#define NS_ALERT_HORIZONTAL 1 +#define NS_ALERT_LEFT 2 +#define NS_ALERT_TOP 4 +#endif + +using namespace mozilla; + +// begin shameless copying from nsNativeAppSupportWin +HWND hwndForDOMWindow( mozIDOMWindowProxy *window ) +{ + if ( !window ) { + return 0; + } + nsCOMPtr<nsPIDOMWindowOuter> pidomwindow = nsPIDOMWindowOuter::From(window); + + nsCOMPtr<nsIBaseWindow> ppBaseWindow = + do_QueryInterface( pidomwindow->GetDocShell() ); + if (!ppBaseWindow) + return 0; + + nsCOMPtr<nsIWidget> ppWidget; + ppBaseWindow->GetMainWidget( getter_AddRefs( ppWidget ) ); + + return (HWND)( ppWidget->GetNativeData( NS_NATIVE_WIDGET ) ); +} + +static void activateWindow( mozIDOMWindowProxy *win ) +{ + // Try to get native window handle. + HWND hwnd = hwndForDOMWindow( win ); + if ( hwnd ) + { + // Restore the window if it is minimized. + if ( ::IsIconic( hwnd ) ) + ::ShowWindow( hwnd, SW_RESTORE ); + // Use the OS call, if possible. + ::SetForegroundWindow( hwnd ); + } else { + // Use internal method. + nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(win); + privateWindow->Focus(); + } +} +// end shameless copying from nsNativeAppWinSupport.cpp + +static void openMailWindow(const nsACString& aFolderUri) +{ + nsresult rv; + nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIMsgWindow> topMostMsgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow)); + if (topMostMsgWindow) + { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow)); + if (domWindow) + { + if (!aFolderUri.IsEmpty()) + { + nsCOMPtr<nsIMsgWindowCommands> windowCommands; + topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands)); + if (windowCommands) + windowCommands->SelectFolder(aFolderUri); + } + activateWindow(domWindow); + return; + } + } + + { + // the user doesn't have a mail window open already so open one for them... + nsCOMPtr<nsIMessengerWindowService> messengerWindowService = + do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID); + // if we want to preselect the first account with new mail, + // here is where we would try to generate a uri to pass in + // (and add code to the messenger window service to make that work) + if (messengerWindowService) + messengerWindowService->OpenMessengerWindowWithUri( + "mail:3pane", nsCString(aFolderUri).get(), nsMsgKey_None); + } +} + +static void CALLBACK delayedSingleClick(HWND msgWindow, UINT msg, INT_PTR idEvent, DWORD dwTime) +{ + ::KillTimer(msgWindow, idEvent); + + // single clicks on the biff icon should re-open the alert notification + nsresult rv = NS_OK; + nsCOMPtr<nsIMessengerOSIntegration> integrationService = + do_GetService(NS_MESSENGEROSINTEGRATION_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + // we know we are dealing with the windows integration object + nsMessengerWinIntegration * winIntegrationService = static_cast<nsMessengerWinIntegration*> + (static_cast<nsIMessengerOSIntegration*>(integrationService.get())); + winIntegrationService->ShowNewAlertNotification(true, EmptyString(), EmptyString()); + } +} + +// Window proc. +static LRESULT CALLBACK MessageWindowProc( HWND msgWindow, UINT msg, WPARAM wp, LPARAM lp ) +{ + if (msg == WM_USER) + { + if (lp == WM_LBUTTONDOWN) + { + // the only way to tell a single left click + // from a double left click is to fire a timer which gets cleared if we end up getting + // a WM_LBUTTONDBLK event. + ::SetTimer(msgWindow, 1, GetDoubleClickTime(), (TIMERPROC) delayedSingleClick); + } + else if (lp == WM_LBUTTONDBLCLK || lp == NIN_BALLOONUSERCLICK) + { + ::KillTimer(msgWindow, 1); + openMailWindow(EmptyCString()); + } + } + + return TRUE; +} + +static HWND msgWindow; + +// Create: Register class and create window. +static nsresult Create() +{ + if (msgWindow) + return NS_OK; + + WNDCLASS classStruct = { 0, // style + &MessageWindowProc, // lpfnWndProc + 0, // cbClsExtra + 0, // cbWndExtra + 0, // hInstance + 0, // hIcon + 0, // hCursor + 0, // hbrBackground + 0, // lpszMenuName + NOTIFICATIONCLASSNAME }; // lpszClassName + + // Register the window class. + NS_ENSURE_TRUE( ::RegisterClass( &classStruct ), NS_ERROR_FAILURE ); + // Create the window. + NS_ENSURE_TRUE( msgWindow = ::CreateWindow( NOTIFICATIONCLASSNAME, + 0, // title + WS_CAPTION, // style + 0,0,0,0, // x, y, cx, cy + 0, // parent + 0, // menu + 0, // instance + 0 ), // create struct + NS_ERROR_FAILURE ); + return NS_OK; +} + + +nsMessengerWinIntegration::nsMessengerWinIntegration() +{ + mDefaultServerAtom = MsgGetAtom("DefaultServer"); + mTotalUnreadMessagesAtom = MsgGetAtom("TotalUnreadMessages"); + + mUnreadTimerActive = false; + + mBiffStateAtom = MsgGetAtom("BiffState"); + mBiffIconVisible = false; + mSuppressBiffIcon = false; + mAlertInProgress = false; + mBiffIconInitialized = false; + mFoldersWithNewMail = do_CreateInstance(NS_ARRAY_CONTRACTID); +} + +nsMessengerWinIntegration::~nsMessengerWinIntegration() +{ + if (mUnreadCountUpdateTimer) { + mUnreadCountUpdateTimer->Cancel(); + mUnreadCountUpdateTimer = nullptr; + } + + // one last attempt, update the registry + nsresult rv = UpdateRegistryWithCurrent(); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to update registry on shutdown"); + DestroyBiffIcon(); +} + +NS_IMPL_ADDREF(nsMessengerWinIntegration) +NS_IMPL_RELEASE(nsMessengerWinIntegration) + +NS_INTERFACE_MAP_BEGIN(nsMessengerWinIntegration) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration) + NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration) + NS_INTERFACE_MAP_ENTRY(nsIFolderListener) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END + + +nsresult +nsMessengerWinIntegration::ResetCurrent() +{ + mInboxURI.Truncate(); + mEmail.Truncate(); + + mCurrentUnreadCount = -1; + mLastUnreadCountWrittenToRegistry = -1; + + mDefaultAccountMightHaveAnInbox = true; + return NS_OK; +} + +NOTIFYICONDATAW sBiffIconData = { NOTIFYICONDATAW_V2_SIZE, + 0, + 2, + NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO, + WM_USER, + 0, + L"", + 0, + 0, + L"", + 30000, + L"", + NIIF_USER | NIIF_NOSOUND }; +// allow for the null terminator +static const uint32_t kMaxTooltipSize = sizeof(sBiffIconData.szTip) / + sizeof(sBiffIconData.szTip[0]) - 1; +static const uint32_t kMaxBalloonSize = sizeof(sBiffIconData.szInfo) / + sizeof(sBiffIconData.szInfo[0]) - 1; +static const uint32_t kMaxBalloonTitle = sizeof(sBiffIconData.szInfoTitle) / + sizeof(sBiffIconData.szInfoTitle[0]) - 1; + +void nsMessengerWinIntegration::InitializeBiffStatusIcon() +{ + // initialize our biff status bar icon + Create(); + + sBiffIconData.hWnd = (HWND) msgWindow; + sBiffIconData.hIcon = ::LoadIcon( ::GetModuleHandle( NULL ), MAKEINTRESOURCE(IDI_MAILBIFF) ); + + mBiffIconInitialized = true; +} + +nsresult +nsMessengerWinIntegration::Init() +{ + nsresult rv; + + // Get shell32.dll handle + HMODULE hModule = ::GetModuleHandleW(SHELL32_DLL); + + if (hModule) { + // SHQueryUserNotificationState is available from Vista + mSHQueryUserNotificationState = (fnSHQueryUserNotificationState)GetProcAddress(hModule, "SHQueryUserNotificationState"); + } + + nsCOMPtr <nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // because we care if the default server changes + rv = accountManager->AddRootFolderListener(this); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // because we care if the unread total count changes + rv = mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged); + NS_ENSURE_SUCCESS(rv,rv); + + // get current profile path for the commandliner + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIFile> profilePath; + rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(profilePath)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = profilePath->GetPath(mProfilePath); + NS_ENSURE_SUCCESS(rv, rv); + + // get application path + WCHAR appPath[_MAX_PATH] = {0}; + ::GetModuleFileNameW(nullptr, appPath, sizeof(appPath)); + mAppName.Assign((char16_t *)appPath); + + rv = ResetCurrent(); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +nsresult nsMessengerWinIntegration::GetStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_IF_ADDREF(*aBundle = bundle); + return NS_OK; +} + +#ifndef MOZ_THUNDERBIRD +nsresult nsMessengerWinIntegration::ShowAlertMessage(const nsString& aAlertTitle, + const nsString& aAlertText, + const nsACString& aFolderURI) +{ + nsresult rv; + + // if we are already in the process of showing an alert, don't try to show another.... + if (mAlertInProgress) + return NS_OK; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool showBalloon = false; + prefBranch->GetBoolPref(SHOW_BALLOON_PREF, &showBalloon); + sBiffIconData.szInfo[0] = '\0'; + if (showBalloon) { + ::wcsncpy( sBiffIconData.szInfoTitle, aAlertTitle.get(), kMaxBalloonTitle); + ::wcsncpy( sBiffIconData.szInfo, aAlertText.get(), kMaxBalloonSize); + } + + bool showAlert = true; + prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert); + + if (showAlert) + { + nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + rv = alertsService->ShowAlertNotification(NS_LITERAL_STRING(NEW_MAIL_ALERT_ICON), aAlertTitle, + aAlertText, true, + NS_ConvertASCIItoUTF16(aFolderURI), this, + EmptyString(), + NS_LITERAL_STRING("auto"), + EmptyString(), EmptyString(), + nullptr, + false, + false); + mAlertInProgress = true; + } + } + + if (!showAlert || NS_FAILED(rv)) // go straight to showing the system tray icon. + AlertFinished(); + + return rv; +} +#endif +// Opening Thunderbird's new mail alert notification window +// aUserInitiated --> true if we are opening the alert notification in response to a user action +// like clicking on the biff icon +nsresult nsMessengerWinIntegration::ShowNewAlertNotification(bool aUserInitiated, const nsString& aAlertTitle, const nsString& aAlertText) +{ + nsresult rv; + + // if we are already in the process of showing an alert, don't try to show another.... + if (mAlertInProgress) + return NS_OK; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool showBalloon = false; + prefBranch->GetBoolPref(SHOW_BALLOON_PREF, &showBalloon); + sBiffIconData.szInfo[0] = '\0'; + if (showBalloon) { + ::wcsncpy( sBiffIconData.szInfoTitle, aAlertTitle.get(), kMaxBalloonTitle); + ::wcsncpy( sBiffIconData.szInfo, aAlertText.get(), kMaxBalloonSize); + } + + bool showAlert = true; + + if (prefBranch) + prefBranch->GetBoolPref(SHOW_ALERT_PREF, &showAlert); + + // check if we are allowed to show a notification + if (showAlert && mSHQueryUserNotificationState) { + MOZ_QUERY_USER_NOTIFICATION_STATE qstate; + if (SUCCEEDED(mSHQueryUserNotificationState(&qstate))) { + if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) { + showAlert = false; + } + } + } + + if (showAlert) + { + nsCOMPtr<nsIMutableArray> argsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // pass in the array of folders with unread messages + nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + ifptr->SetData(mFoldersWithNewMail); + ifptr->SetDataIID(&NS_GET_IID(nsIArray)); + rv = argsArray->AppendElement(ifptr, false); + NS_ENSURE_SUCCESS(rv, rv); + + // pass in the observer + ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMessengerOSIntegration*>(this)); + ifptr->SetData(supports); + ifptr->SetDataIID(&NS_GET_IID(nsIObserver)); + rv = argsArray->AppendElement(ifptr, false); + NS_ENSURE_SUCCESS(rv, rv); + + // pass in the animation flag + nsCOMPtr<nsISupportsPRBool> scriptableUserInitiated (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + scriptableUserInitiated->SetData(aUserInitiated); + rv = argsArray->AppendElement(scriptableUserInitiated, false); + NS_ENSURE_SUCCESS(rv, rv); + + // pass in the alert origin + nsCOMPtr<nsISupportsPRUint8> scriptableOrigin (do_CreateInstance(NS_SUPPORTS_PRUINT8_CONTRACTID)); + NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE); + scriptableOrigin->SetData(0); + int32_t origin = 0; +#ifdef MOZILLA_INTERNAL_API + origin = LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin); +#else + // Get task bar window handle + HWND shellWindow = FindWindowW(L"Shell_TrayWnd", NULL); + + rv = prefBranch->GetIntPref(ALERT_ORIGIN_PREF, &origin); + if (NS_FAILED(rv) && (shellWindow != NULL)) + { + // Determine position + APPBARDATA appBarData; + appBarData.hWnd = shellWindow; + appBarData.cbSize = sizeof(appBarData); + if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) + { + // Set alert origin as a bit field - see LookAndFeel.h + // 0 represents bottom right, sliding vertically. + switch(appBarData.uEdge) + { + case ABE_LEFT: + origin = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT; + break; + case ABE_RIGHT: + origin = NS_ALERT_HORIZONTAL; + break; + case ABE_TOP: + origin = NS_ALERT_TOP; + // fall through for the right-to-left handling. + case ABE_BOTTOM: + // If the task bar is right-to-left, + // move the origin to the left + if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & + WS_EX_LAYOUTRTL) + origin |= NS_ALERT_LEFT; + break; + } + } + } +#endif + scriptableOrigin->SetData(origin); + + rv = argsArray->AppendElement(scriptableOrigin, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = wwatch->OpenWindow(0, ALERT_CHROME_URL, "_blank", + "chrome,dialog=yes,titlebar=no,popup=yes", argsArray, + getter_AddRefs(newWindow)); + + mAlertInProgress = true; + } + + // if the user has turned off the mail alert, or openWindow generated an error, + // then go straight to the system tray. + if (!showAlert || NS_FAILED(rv)) + AlertFinished(); + + return rv; +} + +nsresult nsMessengerWinIntegration::AlertFinished() +{ + // okay, we are done showing the alert + // now put an icon in the system tray, if allowed + bool showTrayIcon = !mSuppressBiffIcon || sBiffIconData.szInfo[0]; + if (showTrayIcon) + { + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) + prefBranch->GetBoolPref(SHOW_TRAY_ICON_PREF, &showTrayIcon); + } + if (showTrayIcon) + { + GenericShellNotify(NIM_ADD); + mBiffIconVisible = true; + } + mSuppressBiffIcon = false; + mAlertInProgress = false; + return NS_OK; +} + +nsresult nsMessengerWinIntegration::AlertClicked() +{ + nsresult rv; + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIMsgWindow> topMostMsgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow)); + if (topMostMsgWindow) + { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow)); + if (domWindow) + { + activateWindow(domWindow); + return NS_OK; + } + } + // make sure we don't insert the icon in the system tray since the user clicked on the alert. + mSuppressBiffIcon = true; + nsCString folderURI; + GetFirstFolderWithNewMail(folderURI); + openMailWindow(folderURI); + return NS_OK; +} + +#ifdef MOZ_SUITE +nsresult nsMessengerWinIntegration::AlertClickedSimple() +{ + mSuppressBiffIcon = true; + return NS_OK; +} +#endif MOZ_SUITE + +NS_IMETHODIMP +nsMessengerWinIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) +{ + if (strcmp(aTopic, "alertfinished") == 0) + return AlertFinished(); + + if (strcmp(aTopic, "alertclickcallback") == 0) + return AlertClicked(); + +#ifdef MOZ_SUITE + // SeaMonkey does most of the GUI work in JS code when clicking on a mail + // notification, so it needs an extra function here + if (strcmp(aTopic, "alertclicksimplecallback") == 0) + return AlertClickedSimple(); +#endif + + return NS_OK; +} + +static void EscapeAmpersands(nsString& aToolTip) +{ + // First, check to see whether we have any ampersands. + int32_t pos = aToolTip.FindChar('&'); + if (pos == kNotFound) + return; + + // Next, see if we only have bare ampersands. + pos = MsgFind(aToolTip, "&&", false, pos); + + // Windows tooltip code removes one ampersand from each run, + // then collapses pairs of amperands. This means that in the easy case, + // we need to replace each ampersand with three. + MsgReplaceSubstring(aToolTip, NS_LITERAL_STRING("&"), NS_LITERAL_STRING("&&&")); + if (pos == kNotFound) + return; + + // We inserted too many ampersands. Remove some. + for (;;) { + pos = MsgFind(aToolTip, "&&&&&&", false, pos); + if (pos == kNotFound) + return; + + aToolTip.Cut(pos, 1); + pos += 2; + } +} + +void nsMessengerWinIntegration::FillToolTipInfo() +{ + // iterate over all the folders in mFoldersWithNewMail + nsString accountName; + nsCString hostName; + nsString toolTipLine; + nsAutoString toolTipText; + nsAutoString animatedAlertText; + nsCOMPtr<nsIMsgFolder> folder; + nsCOMPtr<nsIWeakReference> weakReference; + int32_t numNewMessages = 0; + + uint32_t count = 0; + NS_ENSURE_SUCCESS_VOID(mFoldersWithNewMail->GetLength(&count)); + + for (uint32_t index = 0; index < count; index++) + { + weakReference = do_QueryElementAt(mFoldersWithNewMail, index); + folder = do_QueryReferent(weakReference); + if (folder) + { + folder->GetPrettiestName(accountName); + + numNewMessages = 0; + folder->GetNumNewMessages(true, &numNewMessages); + nsCOMPtr<nsIStringBundle> bundle; + GetStringBundle(getter_AddRefs(bundle)); + if (bundle) + { + nsAutoString numNewMsgsText; + numNewMsgsText.AppendInt(numNewMessages); + + const char16_t *formatStrings[] = + { + numNewMsgsText.get(), + }; + + nsString finalText; + if (numNewMessages == 1) + bundle->FormatStringFromName(u"biffNotification_message", formatStrings, 1, getter_Copies(finalText)); + else + bundle->FormatStringFromName(u"biffNotification_messages", formatStrings, 1, getter_Copies(finalText)); + + // the alert message is special...we actually only want to show the first account with + // new mail in the alert. + if (animatedAlertText.IsEmpty()) // if we haven't filled in the animated alert text yet + animatedAlertText = finalText; + + toolTipLine.Append(accountName); + toolTipLine.Append(' '); + toolTipLine.Append(finalText); + EscapeAmpersands(toolTipLine); + + // only add this new string if it will fit without truncation.... + if (toolTipLine.Length() + toolTipText.Length() <= kMaxTooltipSize) + toolTipText.Append(toolTipLine); + + // clear out the tooltip line for the next folder + toolTipLine.Assign('\n'); + } // if we got a bundle + } // if we got a folder + } // for each folder + + ::wcsncpy( sBiffIconData.szTip, toolTipText.get(), kMaxTooltipSize); + + if (!mBiffIconVisible) + { +#ifndef MOZ_THUNDERBIRD + nsresult rv; + bool showNewAlert = false; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + prefBranch->GetBoolPref(SHOW_NEW_ALERT_PREF, &showNewAlert); + if (!showNewAlert) + ShowAlertMessage(accountName, animatedAlertText, EmptyCString()); + else +#endif + ShowNewAlertNotification(false, accountName, animatedAlertText); + } + else + GenericShellNotify( NIM_MODIFY); +} + +// Get the first top level folder which we know has new mail, then enumerate over +// all the subfolders looking for the first real folder with new mail. +// Return the folderURI for that folder. +nsresult nsMessengerWinIntegration::GetFirstFolderWithNewMail(nsACString& aFolderURI) +{ + NS_ENSURE_TRUE(mFoldersWithNewMail, NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgFolder> folder; + nsCOMPtr<nsIWeakReference> weakReference; + int32_t numNewMessages = 0; + + uint32_t count = 0; + nsresult rv = mFoldersWithNewMail->GetLength(&count); + if (NS_FAILED(rv) || !count) // kick out if we don't have any folders with new mail + return NS_OK; + + weakReference = do_QueryElementAt(mFoldersWithNewMail, 0); + folder = do_QueryReferent(weakReference); + + if (folder) + { + nsCOMPtr<nsIMsgFolder> msgFolder; + // enumerate over the folders under this root folder till we find one with new mail.... + nsCOMPtr<nsIArray> allFolders; + rv = folder->GetDescendants(getter_AddRefs(allFolders)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = allFolders->Enumerate(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(rv) && enumerator) + { + nsCOMPtr<nsISupports> supports; + bool hasMore = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = enumerator->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + msgFolder = do_QueryInterface(supports, &rv); + if (msgFolder) + { + numNewMessages = 0; + msgFolder->GetNumNewMessages(false, &numNewMessages); + if (numNewMessages) + break; // kick out of the while loop + } + } // if we have a folder + } // if we have more potential folders to enumerate + } // if enumerator + + if (msgFolder) + msgFolder->GetURI(aFolderURI); + } + + return NS_OK; +} + +void nsMessengerWinIntegration::DestroyBiffIcon() +{ + GenericShellNotify(NIM_DELETE); + // Don't call DestroyIcon(). see http://bugzilla.mozilla.org/show_bug.cgi?id=134745 +} + +void nsMessengerWinIntegration::GenericShellNotify(DWORD aMessage) +{ + ::Shell_NotifyIconW( aMessage, &sBiffIconData ); +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + bool aOldValue, + bool aNewValue) +{ + if (aProperty == mDefaultServerAtom) { + nsresult rv; + + // this property changes multiple times + // on account deletion or when the user changes their + // default account. ResetCurrent() will set + // mInboxURI to null, so we use that + // to prevent us from attempting to remove + // something from the registry that has already been removed + if (!mInboxURI.IsEmpty() && !mEmail.IsEmpty()) { + rv = RemoveCurrentFromRegistry(); + NS_ENSURE_SUCCESS(rv,rv); + } + + // reset so we'll go get the new default server next time + rv = ResetCurrent(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = UpdateUnreadCount(); + NS_ENSURE_SUCCESS(rv,rv); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMessengerWinIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, int64_t aOldValue, int64_t aNewValue) +{ + // if we got new mail show a icon in the system tray + if (mBiffStateAtom == aProperty && mFoldersWithNewMail) + { + nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aItem); + uint32_t indexInNewArray; + nsresult rv = mFoldersWithNewMail->IndexOf(0, weakFolder, &indexInNewArray); + bool folderFound = NS_SUCCEEDED(rv); + + if (!mBiffIconInitialized) + InitializeBiffStatusIcon(); + + if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail) + { + // if the icon is not already visible, only show a system tray icon iff + // we are performing biff (as opposed to the user getting new mail) + if (!mBiffIconVisible) + { + bool performingBiff = false; + nsCOMPtr<nsIMsgIncomingServer> server; + aItem->GetServer(getter_AddRefs(server)); + if (server) + server->GetPerformingBiff(&performingBiff); + if (!performingBiff) + return NS_OK; // kick out right now... + } + if (!folderFound) + mFoldersWithNewMail->InsertElementAt(weakFolder, 0, false); + // now regenerate the tooltip + FillToolTipInfo(); + } + else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail) + { + // we are always going to remove the icon whenever we get our first no mail + // notification. + + // avoid a race condition where we are told to remove the icon before we've actually + // added it to the system tray. This happens when the user reads a new message before + // the animated alert has gone away. + if (mAlertInProgress) + mSuppressBiffIcon = true; + + if (folderFound) + mFoldersWithNewMail->RemoveElementAt(indexInNewArray); + if (mBiffIconVisible) + { + mBiffIconVisible = false; + GenericShellNotify(NIM_DELETE); + } + } + } // if the biff property changed + + if (aProperty == mTotalUnreadMessagesAtom) { + nsCString itemURI; + nsresult rv; + rv = aItem->GetURI(itemURI); + NS_ENSURE_SUCCESS(rv,rv); + + if (mInboxURI.Equals(itemURI)) + mCurrentUnreadCount = aNewValue; + + // If the timer isn't running yet, then we immediately update the + // registry and then start a one-shot timer. If the Unread counter + // has toggled zero / nonzero, we also update immediately. + // Otherwise, if the timer is running, defer the update. This means + // that all counter updates that occur within the timer interval are + // batched into a single registry update, to avoid hitting the + // registry too frequently. We also do a final update on shutdown, + // regardless of the timer. + if (!mUnreadTimerActive || + (!mCurrentUnreadCount && mLastUnreadCountWrittenToRegistry) || + (mCurrentUnreadCount && mLastUnreadCountWrittenToRegistry < 1)) { + rv = UpdateUnreadCount(); + NS_ENSURE_SUCCESS(rv,rv); + // If timer wasn't running, start it. + if (!mUnreadTimerActive) + rv = SetupUnreadCountUpdateTimer(); + } + } + return NS_OK; +} + +void +nsMessengerWinIntegration::OnUnreadCountUpdateTimer(nsITimer *timer, void *osIntegration) +{ + nsMessengerWinIntegration *winIntegration = (nsMessengerWinIntegration*)osIntegration; + + winIntegration->mUnreadTimerActive = false; + nsresult rv = winIntegration->UpdateUnreadCount(); + NS_ASSERTION(NS_SUCCEEDED(rv), "updating unread count failed"); +} + +nsresult +nsMessengerWinIntegration::RemoveCurrentFromRegistry() +{ + // If Windows XP, open the registry and get rid of old account registry entries + // If there is a email prefix, get it and use it to build the registry key. + // Otherwise, just the email address will be the registry key. + nsAutoString currentUnreadMailCountKey; + if (!mEmailPrefix.IsEmpty()) { + currentUnreadMailCountKey.Assign(mEmailPrefix); + currentUnreadMailCountKey.Append(NS_ConvertASCIItoUTF16(mEmail)); + } + else + CopyASCIItoUTF16(mEmail, currentUnreadMailCountKey); + + WCHAR registryUnreadMailCountKey[_MAX_PATH] = {0}; + // Enumerate through registry entries to delete the key matching + // currentUnreadMailCountKey + int index = 0; + while (SUCCEEDED(SHEnumerateUnreadMailAccountsW(HKEY_CURRENT_USER, + index, + registryUnreadMailCountKey, + sizeof(registryUnreadMailCountKey)))) + { + if (wcscmp(registryUnreadMailCountKey, currentUnreadMailCountKey.get())==0) { + nsAutoString deleteKey; + deleteKey.Assign(NS_LITERAL_STRING(UNREADMAILNODEKEY).get()); + deleteKey.Append(currentUnreadMailCountKey.get()); + + if (!deleteKey.IsEmpty()) { + // delete this key and berak out of the loop + RegDeleteKey(HKEY_CURRENT_USER, + NS_ConvertUTF16toUTF8(deleteKey).get()); + break; + } + else { + index++; + } + } + else { + index++; + } + } + return NS_OK; +} + +nsresult +nsMessengerWinIntegration::UpdateRegistryWithCurrent() +{ + if (mInboxURI.IsEmpty() || mEmail.IsEmpty()) + return NS_OK; + + // only update the registry if the count has changed + // and if the unread count is valid + if ((mCurrentUnreadCount < 0) || (mCurrentUnreadCount == mLastUnreadCountWrittenToRegistry)) + return NS_OK; + + // commandliner has to be built in the form of statement + // which can be open the mailer app to the default user account + // For given profile 'foo', commandliner will be built as + // ""<absolute path to application>" -p foo -mail" where absolute + // path to application is extracted from mAppName + nsAutoString commandLinerForAppLaunch; + commandLinerForAppLaunch.Assign(NS_LITERAL_STRING(DOUBLE_QUOTE)); + commandLinerForAppLaunch.Append(mAppName); + commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE)); + commandLinerForAppLaunch.Append(NS_LITERAL_STRING(PROFILE_COMMANDLINE_ARG)); + commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE)); + commandLinerForAppLaunch.Append(mProfilePath); + commandLinerForAppLaunch.Append(NS_LITERAL_STRING(DOUBLE_QUOTE)); + commandLinerForAppLaunch.Append(NS_LITERAL_STRING(MAIL_COMMANDLINE_ARG)); + + if (!commandLinerForAppLaunch.IsEmpty()) + { + nsAutoString pBuffer; + + if (!mEmailPrefix.IsEmpty()) { + pBuffer.Assign(mEmailPrefix); + pBuffer.Append(NS_ConvertASCIItoUTF16(mEmail)); + } + else + CopyASCIItoUTF16(mEmail, pBuffer); + + // Write the info into the registry + HRESULT hr = SHSetUnreadMailCountW(pBuffer.get(), + mCurrentUnreadCount, + commandLinerForAppLaunch.get()); + } + + // do this last + mLastUnreadCountWrittenToRegistry = mCurrentUnreadCount; + + return NS_OK; +} + +nsresult +nsMessengerWinIntegration::SetupInbox() +{ + nsresult rv; + + // get default account + nsCOMPtr <nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIMsgAccount> account; + rv = accountManager->GetDefaultAccount(getter_AddRefs(account)); + if (NS_FAILED(rv)) { + // this can happen if we launch mail on a new profile + // we don't have a default account yet + mDefaultAccountMightHaveAnInbox = false; + return NS_OK; + } + + // get incoming server + nsCOMPtr <nsIMsgIncomingServer> server; + rv = account->GetIncomingServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + if (!server) + return NS_ERROR_FAILURE; + + nsCString type; + rv = server->GetType(type); + NS_ENSURE_SUCCESS(rv,rv); + + // we only care about imap and pop3 + if (type.EqualsLiteral("imap") || type.EqualsLiteral("pop3")) { + // imap and pop3 account should have an Inbox + mDefaultAccountMightHaveAnInbox = true; + + mEmailPrefix.Truncate(); + + // Get user's email address + nsCOMPtr<nsIMsgIdentity> identity; + rv = account->GetDefaultIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv,rv); + + if (!identity) + return NS_ERROR_FAILURE; + + rv = identity->GetEmail(mEmail); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_SUCCESS(rv,rv); + + if (!rootMsgFolder) + return NS_ERROR_FAILURE; + + nsCOMPtr <nsIMsgFolder> inboxFolder; + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inboxFolder)); + NS_ENSURE_TRUE(inboxFolder, NS_ERROR_FAILURE); + + rv = inboxFolder->GetURI(mInboxURI); + NS_ENSURE_SUCCESS(rv,rv); + + rv = inboxFolder->GetNumUnread(false, &mCurrentUnreadCount); + NS_ENSURE_SUCCESS(rv,rv); + } + else { + // the default account is valid, but it's not something + // that we expect to have an inbox. (local folders, news accounts) + // set this flag to avoid calling SetupInbox() every time + // the timer goes off. + mDefaultAccountMightHaveAnInbox = false; + } + + return NS_OK; +} + +nsresult +nsMessengerWinIntegration::UpdateUnreadCount() +{ + nsresult rv; + + if (mDefaultAccountMightHaveAnInbox && mInboxURI.IsEmpty()) { + rv = SetupInbox(); + NS_ENSURE_SUCCESS(rv,rv); + } + + return UpdateRegistryWithCurrent(); +} + +nsresult +nsMessengerWinIntegration::SetupUnreadCountUpdateTimer() +{ + mUnreadTimerActive = true; + if (mUnreadCountUpdateTimer) + mUnreadCountUpdateTimer->Cancel(); + else + mUnreadCountUpdateTimer = do_CreateInstance("@mozilla.org/timer;1"); + + mUnreadCountUpdateTimer->InitWithFuncCallback(OnUnreadCountUpdateTimer, + (void *)this, UNREAD_UPDATE_INTERVAL, nsITimer::TYPE_ONE_SHOT); + + return NS_OK; +} diff --git a/mailnews/base/src/nsMessengerWinIntegration.h b/mailnews/base/src/nsMessengerWinIntegration.h new file mode 100644 index 0000000000..6c49a71c23 --- /dev/null +++ b/mailnews/base/src/nsMessengerWinIntegration.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef __nsMessengerWinIntegration_h +#define __nsMessengerWinIntegration_h + +#include <windows.h> + +// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN +#include <shellapi.h> + +#include "nsIMessengerOSIntegration.h" +#include "nsIFolderListener.h" +#include "nsIAtom.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsIMutableArray.h" +#include "nsIObserver.h" + +typedef enum tagMOZ_QUERY_USER_NOTIFICATION_STATE { + QUNS_NOT_PRESENT = 1, + QUNS_BUSY = 2, + QUNS_RUNNING_D3D_FULL_SCREEN = 3, + QUNS_PRESENTATION_MODE = 4, + QUNS_ACCEPTS_NOTIFICATIONS = 5, + QUNS_QUIET_TIME = 6 +} MOZ_QUERY_USER_NOTIFICATION_STATE; + +// this function is exported by shell32.dll on Windows Vista or later +extern "C" +{ +// Vista or later +typedef HRESULT (__stdcall *fnSHQueryUserNotificationState)(MOZ_QUERY_USER_NOTIFICATION_STATE *pquns); +} + +#define NS_MESSENGERWININTEGRATION_CID \ + {0xf62f3d3a, 0x1dd1, 0x11b2, \ + {0xa5, 0x16, 0xef, 0xad, 0xb1, 0x31, 0x61, 0x5c}} + +class nsIStringBundle; + +class nsMessengerWinIntegration : public nsIMessengerOSIntegration, + public nsIFolderListener, + public nsIObserver +{ +public: + nsMessengerWinIntegration(); + virtual nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMESSENGEROSINTEGRATION + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSIOBSERVER + + nsresult ShowNewAlertNotification(bool aUserInitiated, const nsString& aAlertTitle, const nsString& aAlertText); +#ifndef MOZ_THUNDERBIRD + nsresult ShowAlertMessage(const nsString& aAlertTitle, const nsString& aAlertText, const nsACString& aFolderURI); +#endif + +private: + virtual ~nsMessengerWinIntegration(); + nsresult AlertFinished(); + nsresult AlertClicked(); +#ifdef MOZ_SUITE + nsresult AlertClickedSimple(); +#endif + + void InitializeBiffStatusIcon(); + void FillToolTipInfo(); + void GenericShellNotify(DWORD aMessage); + void DestroyBiffIcon(); + + nsresult GetFirstFolderWithNewMail(nsACString& aFolderURI); + + nsresult GetStringBundle(nsIStringBundle **aBundle); + nsCOMPtr<nsIMutableArray> mFoldersWithNewMail; // keep track of all the root folders with pending new mail + nsCOMPtr<nsIAtom> mBiffStateAtom; + uint32_t mCurrentBiffState; + + bool mBiffIconVisible; + bool mBiffIconInitialized; + bool mSuppressBiffIcon; + bool mAlertInProgress; + + // "might" because we don't know until we check + // what type of server is associated with the default account + bool mDefaultAccountMightHaveAnInbox; + + // True if the timer is running + bool mUnreadTimerActive; + + nsresult ResetCurrent(); + nsresult RemoveCurrentFromRegistry(); + nsresult UpdateRegistryWithCurrent(); + nsresult SetupInbox(); + + nsresult SetupUnreadCountUpdateTimer(); + static void OnUnreadCountUpdateTimer(nsITimer *timer, void *osIntegration); + nsresult UpdateUnreadCount(); + + nsCOMPtr <nsIAtom> mDefaultServerAtom; + nsCOMPtr <nsIAtom> mTotalUnreadMessagesAtom; + nsCOMPtr <nsITimer> mUnreadCountUpdateTimer; + + fnSHQueryUserNotificationState mSHQueryUserNotificationState; + + nsCString mInboxURI; + nsCString mEmail; + + nsString mAppName; + nsString mEmailPrefix; + + nsString mProfilePath; + + int32_t mCurrentUnreadCount; + int32_t mLastUnreadCountWrittenToRegistry; +}; + +#endif // __nsMessengerWinIntegration_h diff --git a/mailnews/base/src/nsMsgAccount.cpp b/mailnews/base/src/nsMsgAccount.cpp new file mode 100644 index 0000000000..9022176bf3 --- /dev/null +++ b/mailnews/base/src/nsMsgAccount.cpp @@ -0,0 +1,435 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsMsgBaseCID.h" +#include "nsMsgAccount.h" +#include "nsIMsgAccount.h" +#include "nsIMsgAccountManager.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsArrayUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgAccount, nsIMsgAccount) + +nsMsgAccount::nsMsgAccount() + : 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<nsIPrefService> 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<nsresult> 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", getter_Copies(serverKey)); + NS_ENSURE_SUCCESS(rv, rv); + + // get the server from the account manager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = accountManager->GetIncomingServer(serverKey, getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + // 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.get()); + } + + 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<nsIMsgFolder> rootFolder; + rv = aIncomingServer->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFolderListener> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mailSession->OnItemAdded(nullptr, rootFolder); + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + notifier->NotifyFolderAdded(rootFolder); + + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + accountManager->NotifyServerLoaded(aIncomingServer); + + // Force built-in folders to be created and discovered. Then, notify listeners + // about them. + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = rootFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item)); + if (!msgFolder) + continue; + mailSession->OnItemAdded(rootFolder, msgFolder); + notifier->NotifyFolderAdded(msgFolder); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccount::GetIdentities(nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE); + + NS_IF_ADDREF(*_retval = 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_identities, NS_ERROR_FAILURE); + + nsresult rv; + m_identities = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString identityKey; + rv = getPrefService(); + NS_ENSURE_SUCCESS(rv, rv); + + m_prefs->GetCharPref("identities", getter_Copies(identityKey)); + if (identityKey.IsEmpty()) // not an error if no identities, but + return NS_OK; // strtok will be unhappy + // get the server from the account manager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + char* newStr = identityKey.BeginWriting(); + char* token = NS_strtok(",", &newStr); + + // temporaries used inside the loop + nsCOMPtr<nsIMsgIdentity> 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)) { + // ignore error from addIdentityInternal() - if it fails, it fails. + rv = addIdentityInternal(identity); + NS_ASSERTION(NS_SUCCEEDED(rv), "Couldn't create identity"); + } + + // advance to next key, if any + token = NS_strtok(",", &newStr); + } + + return rv; +} + + +/* attribute nsIMsgIdentity defaultIdentity; */ +NS_IMETHODIMP +nsMsgAccount::GetDefaultIdentity(nsIMsgIdentity **aDefaultIdentity) +{ + NS_ENSURE_ARG_POINTER(aDefaultIdentity); + NS_ENSURE_TRUE(m_identities, NS_ERROR_NOT_INITIALIZED); + + *aDefaultIdentity = nullptr; + uint32_t count; + nsresult rv = m_identities->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + if (count == 0) + return NS_OK; + + nsCOMPtr<nsIMsgIdentity> identity = do_QueryElementAt(m_identities, 0, &rv); + identity.swap(*aDefaultIdentity); + return rv; +} + +NS_IMETHODIMP +nsMsgAccount::SetDefaultIdentity(nsIMsgIdentity *aDefaultIdentity) +{ + NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE); + + uint32_t position = 0; + nsresult rv = m_identities->IndexOf(0, aDefaultIdentity, &position); + NS_ENSURE_SUCCESS(rv, rv); + + rv = m_identities->RemoveElementAt(position); + NS_ENSURE_SUCCESS(rv, rv); + + // The passed in identity is in the list, so we have at least one element. + rv = m_identities->InsertElementAt(aDefaultIdentity, 0, false); + NS_ENSURE_SUCCESS(rv, rv); + + return saveIdentitiesPref(); +} + +// add the identity to m_identities, but don't fiddle with the +// prefs. The assumption here is that the pref for this identity is +// already set. +nsresult +nsMsgAccount::addIdentityInternal(nsIMsgIdentity *identity) +{ + NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE); + + return m_identities->AppendElement(identity, false); +} + +/* void addIdentity (in nsIMsgIdentity identity); */ +NS_IMETHODIMP +nsMsgAccount::AddIdentity(nsIMsgIdentity *identity) +{ + NS_ENSURE_ARG_POINTER(identity); + + // 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", getter_Copies(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.get()); + } + + // now add it to the in-memory list + return addIdentityInternal(identity); +} + +/* void removeIdentity (in nsIMsgIdentity identity); */ +NS_IMETHODIMP +nsMsgAccount::RemoveIdentity(nsIMsgIdentity *aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + NS_ENSURE_TRUE(m_identities, NS_ERROR_FAILURE); + + uint32_t count = 0; + m_identities->GetLength(&count); + // At least one identity must stay after the delete. + NS_ENSURE_TRUE(count > 1, NS_ERROR_FAILURE); + + uint32_t pos = 0; + nsresult rv = m_identities->IndexOf(0, aIdentity, &pos); + NS_ENSURE_SUCCESS(rv, rv); + + // remove our identity + m_identities->RemoveElementAt(pos); + + // 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... + uint32_t count; + nsresult rv = m_identities->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString key; + for (uint32_t index = 0; index < count; index++) + { + nsCOMPtr<nsIMsgIdentity> identity = do_QueryElementAt(m_identities, index, &rv); + if (identity) + { + identity->GetKey(key); + + if (!index) { + newIdentityList = key; + } + else + { + newIdentityList.Append(','); + newIdentityList.Append(key); + } + } + } + + // Save the pref. + m_prefs->SetCharPref("identities", newIdentityList.get()); + + 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_identities = nullptr; + return createIdentities(); +} + +NS_IMETHODIMP +nsMsgAccount::ToString(nsAString& aResult) +{ + nsAutoString val; + aResult.AssignLiteral("[nsIMsgAccount: "); + aResult.Append(NS_ConvertASCIItoUTF16(m_accountKey)); + aResult.AppendLiteral("]"); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccount::ClearAllValues() +{ + nsresult rv = getPrefService(); + NS_ENSURE_SUCCESS(rv, rv); + + return m_prefs->DeleteBranch(""); +} diff --git a/mailnews/base/src/nsMsgAccount.h b/mailnews/base/src/nsMsgAccount.h new file mode 100644 index 0000000000..5a12dab1a6 --- /dev/null +++ b/mailnews/base/src/nsMsgAccount.h @@ -0,0 +1,38 @@ +/* -*- 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 "nscore.h" +#include "nsIMsgAccount.h" +#include "nsIPrefBranch.h" +#include "nsStringGlue.h" +#include "nsIMutableArray.h" + +class nsMsgAccount : public nsIMsgAccount +{ + +public: + nsMsgAccount(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGACCOUNT + +private: + virtual ~nsMsgAccount(); + nsCString m_accountKey; + nsCOMPtr<nsIPrefBranch> m_prefs; + nsCOMPtr<nsIMsgIncomingServer> m_incomingServer; + + nsCOMPtr<nsIMutableArray> m_identities; + + nsresult getPrefService(); + nsresult createIncomingServer(); + nsresult createIdentities(); + nsresult saveIdentitiesPref(); + nsresult addIdentityInternal(nsIMsgIdentity* identity); + + // Have we tried to get the server yet? + bool mTriedToGetServer; +}; + diff --git a/mailnews/base/src/nsMsgAccountManager.cpp b/mailnews/base/src/nsMsgAccountManager.cpp new file mode 100644 index 0000000000..5dd3893b67 --- /dev/null +++ b/mailnews/base/src/nsMsgAccountManager.cpp @@ -0,0 +1,3708 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +/* + * The account manager service - manages all accounts, servers, and identities + */ + +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" +#include "nsMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsMsgDBCID.h" +#include "prmem.h" +#include "prcmon.h" +#include "prthread.h" +#include "plstr.h" +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" +#include "nscore.h" +#include "prprf.h" +#include "nsIMsgFolderCache.h" +#include "nsMsgUtils.h" +#include "nsIFile.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsISmtpService.h" +#include "nsIMsgBiffManager.h" +#include "nsIMsgPurgeService.h" +#include "nsIObserverService.h" +#include "nsINoIncomingServer.h" +#include "nsIMsgMailSession.h" +#include "nsIDirectoryService.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsMailDirServiceDefs.h" +#include "nsMsgFolderFlags.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsIImapIncomingServer.h" +#include "nsIImapUrl.h" +#include "nsIMessengerOSIntegration.h" +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgFilter.h" +#include "nsIMsgSearchSession.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMutableArray.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgHdr.h" +#include "nsILineInputStream.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" +#include "nsIStringBundle.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFilterList.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Services.h" +#include <algorithm> +#include "nsIFileStreams.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" + +#define PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS "mail.accountmanager.accounts" +#define PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT "mail.accountmanager.defaultaccount" +#define PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER "mail.accountmanager.localfoldersserver" +#define PREF_MAIL_SERVER_PREFIX "mail.server." +#define ACCOUNT_PREFIX "account" +#define SERVER_PREFIX "server" +#define ID_PREFIX "id" +#define ABOUT_TO_GO_OFFLINE_TOPIC "network:offline-about-to-go-offline" +#define ACCOUNT_DELIMITER ',' +#define APPEND_ACCOUNTS_VERSION_PREF_NAME "append_preconfig_accounts.version" +#define MAILNEWS_ROOT_PREF "mailnews." +#define PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS "mail.accountmanager.appendaccounts" + +static NS_DEFINE_CID(kMsgAccountCID, NS_MSGACCOUNT_CID); +static NS_DEFINE_CID(kMsgFolderCacheCID, NS_MSGFOLDERCACHE_CID); + +#define SEARCH_FOLDER_FLAG "searchFolderFlag" +#define SEARCH_FOLDER_FLAG_LEN (sizeof(SEARCH_FOLDER_FLAG) - 1) + +const char *kSearchFolderUriProp = "searchFolderUri"; + +bool nsMsgAccountManager::m_haveShutdown = false; +bool nsMsgAccountManager::m_shutdownInProgress = false; + +NS_IMPL_ISUPPORTS(nsMsgAccountManager, + nsIMsgAccountManager, + nsIObserver, + nsISupportsWeakReference, + nsIUrlListener, + nsIFolderListener) + +nsMsgAccountManager::nsMsgAccountManager() : + m_accountsLoaded(false), + m_emptyTrashInProgress(false), + m_cleanupInboxInProgress(false), + m_userAuthenticated(false), + m_loadingVirtualFolders(false), + m_virtualFoldersLoaded(false) +{ +} + +nsMsgAccountManager::~nsMsgAccountManager() +{ + if(!m_haveShutdown) + { + Shutdown(); + //Don't remove from Observer service in Shutdown because Shutdown also gets called + //from xpcom shutdown observer. And we don't want to remove from the service in that case. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + { + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + observerService->RemoveObserver(this, "quit-application-granted"); + observerService->RemoveObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC); + observerService->RemoveObserver(this, "sleep_notification"); + } + } +} + +nsresult nsMsgAccountManager::Init() +{ + nsresult rv; + + m_prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + { + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + observerService->AddObserver(this, "quit-application-granted" , true); + observerService->AddObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC, true); + observerService->AddObserver(this, "profile-before-change", true); + observerService->AddObserver(this, "sleep_notification", true); + } + + // Make sure PSM gets initialized before any accounts use certificates. + net_EnsurePSMInit(); + + return NS_OK; +} + +nsresult nsMsgAccountManager::Shutdown() +{ + if (m_haveShutdown) // do not shutdown twice + return NS_OK; + + nsresult rv; + + SaveVirtualFolders(); + + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) + { + listener = iter.GetNext(); + msgDBService->UnregisterPendingListener(listener); + } + } + if(m_msgFolderCache) + WriteToFolderCache(m_msgFolderCache); + (void)ShutdownServers(); + (void)UnloadAccounts(); + + //shutdown removes nsIIncomingServer listener from biff manager, so do it after accounts have been unloaded + nsCOMPtr<nsIMsgBiffManager> biffService = do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && biffService) + biffService->Shutdown(); + + //shutdown removes nsIIncomingServer listener from purge service, so do it after accounts have been unloaded + nsCOMPtr<nsIMsgPurgeService> purgeService = do_GetService(NS_MSGPURGESERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && purgeService) + purgeService->Shutdown(); + + m_msgFolderCache = nullptr; + m_haveShutdown = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetShutdownInProgress(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_shutdownInProgress; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetUserNeedsToAuthenticate(bool *aRetval) +{ + NS_ENSURE_ARG_POINTER(aRetval); + if (!m_userAuthenticated) + return m_prefs->GetBoolPref("mail.password_protect_local_cache", aRetval); + *aRetval = !m_userAuthenticated; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetUserNeedsToAuthenticate(bool aUserNeedsToAuthenticate) +{ + m_userAuthenticated = !aUserNeedsToAuthenticate; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + if(!strcmp(aTopic,NS_XPCOM_SHUTDOWN_OBSERVER_ID)) + { + Shutdown(); + return NS_OK; + } + if (!strcmp(aTopic, "quit-application-granted")) + { + // CleanupOnExit will set m_shutdownInProgress to true. + CleanupOnExit(); + return NS_OK; + } + if (!strcmp(aTopic, ABOUT_TO_GO_OFFLINE_TOPIC)) + { + nsAutoString dataString(NS_LITERAL_STRING("offline")); + if (someData) + { + nsAutoString someDataString(someData); + if (dataString.Equals(someDataString)) + CloseCachedConnections(); + } + return NS_OK; + } + if (!strcmp(aTopic, "sleep_notification")) + return CloseCachedConnections(); + + if (!strcmp(aTopic, "profile-before-change")) + { + Shutdown(); + return NS_OK; + } + + return NS_OK; +} + +void +nsMsgAccountManager::getUniqueAccountKey(nsCString& aResult) +{ + int32_t lastKey = 0; + nsresult rv; + nsCOMPtr<nsIPrefService> prefservice(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIPrefBranch> prefBranch; + prefservice->GetBranch("", getter_AddRefs(prefBranch)); + + rv = prefBranch->GetIntPref("mail.account.lastKey", &lastKey); + if (NS_FAILED(rv) || lastKey == 0) { + // If lastKey pref does not contain a valid value, loop over existing + // pref names mail.account.* . + nsCOMPtr<nsIPrefBranch> prefBranchAccount; + rv = prefservice->GetBranch("mail.account.", getter_AddRefs(prefBranchAccount)); + if (NS_SUCCEEDED(rv)) { + uint32_t prefCount; + char **prefList; + rv = prefBranchAccount->GetChildList("", &prefCount, &prefList); + if (NS_SUCCEEDED(rv)) { + // Pref names are of the format accountX. + // Find the maximum value of 'X' used so far. + for (uint32_t i = 0; i < prefCount; i++) { + nsCString prefName; + prefName.Assign(prefList[i]); + if (StringBeginsWith(prefName, NS_LITERAL_CSTRING(ACCOUNT_PREFIX))) { + int32_t dotPos = prefName.FindChar('.'); + if (dotPos != kNotFound) { + nsCString keyString(Substring(prefName, strlen(ACCOUNT_PREFIX), + dotPos - strlen(ACCOUNT_PREFIX))); + int32_t thisKey = keyString.ToInteger(&rv); + if (NS_SUCCEEDED(rv)) + lastKey = std::max(lastKey, thisKey); + } + } + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); + } + } + } + + // Use next available key and store the value in the pref. + aResult.Assign(ACCOUNT_PREFIX); + aResult.AppendInt(++lastKey); + rv = prefBranch->SetIntPref("mail.account.lastKey", lastKey); + } else { + // If pref service is not working, try to find a free accountX key + // by checking which keys exist. + int32_t i = 1; + nsCOMPtr<nsIMsgAccount> account; + + do { + aResult = ACCOUNT_PREFIX; + aResult.AppendInt(i++); + GetAccount(aResult, getter_AddRefs(account)); + } while (account); + } +} + +void +nsMsgAccountManager::GetUniqueServerKey(nsACString& aResult) +{ + nsAutoCString prefResult; + bool usePrefsScan = true; + nsresult rv; + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + if (NS_FAILED(rv)) + usePrefsScan = false; + + // Loop over existing pref names mail.server.server(lastKey).type + nsCOMPtr<nsIPrefBranch> prefBranchServer; + if (prefService) + { + rv = prefService->GetBranch(PREF_MAIL_SERVER_PREFIX, getter_AddRefs(prefBranchServer)); + if (NS_FAILED(rv)) + usePrefsScan = false; + } + + if (usePrefsScan) + { + nsAutoCString type; + nsAutoCString typeKey; + for (uint32_t lastKey = 1; ; lastKey++) + { + aResult.AssignLiteral(SERVER_PREFIX); + aResult.AppendInt(lastKey); + typeKey.Assign(aResult); + typeKey.AppendLiteral(".type"); + prefBranchServer->GetCharPref(typeKey.get(), getter_Copies(type)); + if (type.IsEmpty()) // a server slot with no type is considered empty + return; + } + } + else + { + // If pref service fails, try to find a free serverX key + // by checking which keys exist. + nsAutoCString internalResult; + nsCOMPtr<nsIMsgIncomingServer> server; + uint32_t i = 1; + do { + aResult.AssignLiteral(SERVER_PREFIX); + aResult.AppendInt(i++); + m_incomingServers.Get(aResult, getter_AddRefs(server)); + } while (server); + return; + } +} + +nsresult +nsMsgAccountManager::CreateIdentity(nsIMsgIdentity **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv; + nsAutoCString key; + nsCOMPtr<nsIMsgIdentity> identity; + int32_t i = 1; + do { + key.AssignLiteral(ID_PREFIX); + key.AppendInt(i++); + m_identities.Get(key, getter_AddRefs(identity)); + } while (identity); + + rv = createKeyedIdentity(key, _retval); + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetIdentity(const nsACString& key, nsIMsgIdentity **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv = NS_OK; + *_retval = nullptr; + + if (!key.IsEmpty()) + { + nsCOMPtr<nsIMsgIdentity> identity; + m_identities.Get(key, getter_AddRefs(identity)); + if (identity) + identity.swap(*_retval); + else // identity doesn't exist. create it. + rv = createKeyedIdentity(key, _retval); + } + + return rv; +} + +/* + * the shared identity-creation code + * create an identity and add it to the accountmanager's list. + */ +nsresult +nsMsgAccountManager::createKeyedIdentity(const nsACString& key, + nsIMsgIdentity ** aIdentity) +{ + nsresult rv; + nsCOMPtr<nsIMsgIdentity> identity = + do_CreateInstance(NS_MSGIDENTITY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + identity->SetKey(key); + m_identities.Put(key, identity); + identity.swap(*aIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CreateIncomingServer(const nsACString& username, + const nsACString& hostname, + const nsACString& type, + nsIMsgIncomingServer **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString key; + GetUniqueServerKey(key); + rv = createKeyedServer(key, username, hostname, type, _retval); + if (*_retval) + { + nsCString defaultStore; + m_prefs->GetCharPref("mail.serverDefaultStoreContractID", getter_Copies(defaultStore)); + (*_retval)->SetCharValue("storeContractID", defaultStore); + + // From when we first create the account until we have created some folders, + // we can change the store type. + (*_retval)->SetBoolValue("canChangeStoreType", true); + } + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetIncomingServer(const nsACString& key, + nsIMsgIncomingServer **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv; + + if (m_incomingServers.Get(key, _retval)) + return NS_OK; + + // server doesn't exist, so create it + // this is really horrible because we are doing our own prefname munging + // instead of leaving it up to the incoming server. + // this should be fixed somehow so that we can create the incoming server + // and then read from the incoming server's attributes + + // in order to create the right kind of server, we have to look + // at the pref for this server to get the username, hostname, and type + nsAutoCString serverPrefPrefix(PREF_MAIL_SERVER_PREFIX); + serverPrefPrefix.Append(key); + + nsCString serverType; + nsAutoCString serverPref (serverPrefPrefix); + serverPref.AppendLiteral(".type"); + rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(serverType)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED); + + // + // .userName + serverPref = serverPrefPrefix; + serverPref.AppendLiteral(".userName"); + nsCString username; + rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(username)); + + // .hostname + serverPref = serverPrefPrefix; + serverPref.AppendLiteral(".hostname"); + nsCString hostname; + rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(hostname)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED); + + return createKeyedServer(key, username, hostname, serverType, _retval); +} + +NS_IMETHODIMP +nsMsgAccountManager::RemoveIncomingServer(nsIMsgIncomingServer *aServer, + bool aRemoveFiles) +{ + NS_ENSURE_ARG_POINTER(aServer); + + nsCString serverKey; + nsresult rv = aServer->GetKey(serverKey); + NS_ENSURE_SUCCESS(rv, rv); + + LogoutOfServer(aServer); // close cached connections and forget session password + + // invalidate the FindServer() cache if we are removing the cached server + if (m_lastFindServerResult == aServer) + SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, EmptyCString()); + + m_incomingServers.Remove(serverKey); + + nsCOMPtr<nsIMsgFolder> rootFolder; + nsCOMPtr<nsIArray> allDescendants; + + rv = aServer->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rootFolder->GetDescendants(getter_AddRefs(allDescendants)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t cnt = 0; + rv = allDescendants->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolderNotificationService> notifier = + do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID); + nsCOMPtr<nsIFolderListener> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID); + + for (uint32_t i = 0; i < cnt; i++) + { + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendants, i); + if (folder) + { + folder->ForceDBClosed(); + if (notifier) + notifier->NotifyFolderDeleted(folder); + if (mailSession) + { + nsCOMPtr<nsIMsgFolder> parentFolder; + folder->GetParent(getter_AddRefs(parentFolder)); + mailSession->OnItemRemoved(parentFolder, folder); + } + } + } + if (notifier) + notifier->NotifyFolderDeleted(rootFolder); + if (mailSession) + mailSession->OnItemRemoved(nullptr, rootFolder); + + removeListenersFromFolder(rootFolder); + NotifyServerUnloaded(aServer); + if (aRemoveFiles) + { + rv = aServer->RemoveFiles(); + NS_ENSURE_SUCCESS(rv, rv); + } + + // now clear out the server once and for all. + // watch out! could be scary + aServer->ClearAllValues(); + rootFolder->Shutdown(true); + return rv; +} + +/* + * create a server when you know the key and the type + */ +nsresult +nsMsgAccountManager::createKeyedServer(const nsACString& key, + const nsACString& username, + const nsACString& hostname, + const nsACString& type, + nsIMsgIncomingServer ** aServer) +{ + nsresult rv; + *aServer = nullptr; + + //construct the contractid + nsAutoCString serverContractID(NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX); + serverContractID += type; + + // finally, create the server + // (This will fail if type is from an extension that has been removed) + nsCOMPtr<nsIMsgIncomingServer> server = + do_CreateInstance(serverContractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE); + + int32_t port; + nsCOMPtr <nsIMsgIncomingServer> existingServer; + server->SetKey(key); + server->SetType(type); + server->SetUsername(username); + server->SetHostName(hostname); + server->GetPort(&port); + FindRealServer(username, hostname, type, port, getter_AddRefs(existingServer)); + // don't allow duplicate servers. + if (existingServer) + return NS_ERROR_FAILURE; + + m_incomingServers.Put(key, server); + + // now add all listeners that are supposed to be + // waiting on root folders + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTObserverArray<nsCOMPtr<nsIFolderListener> >::ForwardIterator iter(mFolderListeners); + while (iter.HasMore()) + { + rootFolder->AddFolderListener(iter.GetNext()); + } + + server.swap(*aServer); + return NS_OK; +} + +void +nsMsgAccountManager::removeListenersFromFolder(nsIMsgFolder *aFolder) +{ + nsTObserverArray<nsCOMPtr<nsIFolderListener> >::ForwardIterator iter(mFolderListeners); + while (iter.HasMore()) + { + aFolder->RemoveFolderListener(iter.GetNext()); + } +} + +NS_IMETHODIMP +nsMsgAccountManager::RemoveAccount(nsIMsgAccount *aAccount, + bool aRemoveFiles = false) +{ + NS_ENSURE_ARG_POINTER(aAccount); + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + bool accountRemoved = m_accounts.RemoveElement(aAccount); + + rv = OutputAccountsPref(); + // If we couldn't write out the pref, restore the account. + if (NS_FAILED(rv) && accountRemoved) + { + m_accounts.AppendElement(aAccount); + return rv; + } + + // if it's the default, clear the default account + if (m_defaultAccount.get() == aAccount) + SetDefaultAccount(nullptr); + + // XXX - need to figure out if this is the last time this server is + // being used, and only send notification then. + // (and only remove from hashtable then too!) + nsCOMPtr<nsIMsgIncomingServer> server; + rv = aAccount->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + RemoveIncomingServer(server, aRemoveFiles); + + nsCOMPtr<nsIArray> identityArray; + rv = aAccount->GetIdentities(getter_AddRefs(identityArray)); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + identityArray->GetLength(&count); + uint32_t i; + for (i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgIdentity> identity( do_QueryElementAt(identityArray, i, &rv)); + bool identityStillUsed = false; + // for each identity, see if any existing account still uses it, + // and if not, clear it. + // Note that we are also searching here accounts with missing servers from + // unloaded extension types. + if (NS_SUCCEEDED(rv)) + { + uint32_t index; + for (index = 0; index < m_accounts.Length() && !identityStillUsed; index++) + { + nsCOMPtr<nsIArray> existingIdentitiesArray; + + rv = m_accounts[index]->GetIdentities(getter_AddRefs(existingIdentitiesArray)); + uint32_t pos; + if (NS_SUCCEEDED(existingIdentitiesArray->IndexOf(0, identity, &pos))) + { + identityStillUsed = true; + break; + } + } + } + // clear out all identity information if no other account uses it. + if (!identityStillUsed) + identity->ClearAllValues(); + } + } + + // It is not a critical problem if this fails as the account was already + // removed from the list of accounts so should not ever be referenced. + // Just print it out for debugging. + rv = aAccount->ClearAllValues(); + NS_ASSERTION(NS_SUCCEEDED(rv), "removing of account prefs failed"); + return NS_OK; +} + +nsresult +nsMsgAccountManager::OutputAccountsPref() +{ + nsCString accountKey; + mAccountKeyList.Truncate(); + + for (uint32_t index = 0; index < m_accounts.Length(); index++) + { + m_accounts[index]->GetKey(accountKey); + if (index) + mAccountKeyList.Append(ACCOUNT_DELIMITER); + mAccountKeyList.Append(accountKey); + } + return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, + mAccountKeyList.get()); +} + +/* get the default account. If no default account, pick the first account */ +NS_IMETHODIMP +nsMsgAccountManager::GetDefaultAccount(nsIMsgAccount **aDefaultAccount) +{ + NS_ENSURE_ARG_POINTER(aDefaultAccount); + + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_defaultAccount) { + uint32_t count = m_accounts.Length(); + if (!count) { + *aDefaultAccount = nullptr; + return NS_ERROR_FAILURE; + } + + nsCString defaultKey; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, getter_Copies(defaultKey)); + + if (NS_SUCCEEDED(rv)) + rv = GetAccount(defaultKey, getter_AddRefs(m_defaultAccount)); + + if (NS_FAILED(rv) || !m_defaultAccount) { + nsCOMPtr<nsIMsgAccount> firstAccount; + uint32_t index; + bool foundValidDefaultAccount = false; + for (index = 0; index < count; index++) { + nsCOMPtr<nsIMsgAccount> account(m_accounts[index]); + + // get incoming server + nsCOMPtr <nsIMsgIncomingServer> server; + // server could be null if created by an unloaded extension + (void) account->GetIncomingServer(getter_AddRefs(server)); + + bool canBeDefaultServer = false; + if (server) + { + server->GetCanBeDefaultServer(&canBeDefaultServer); + if (!firstAccount) + firstAccount = account; + } + + // if this can serve as default server, set it as default and + // break out of the loop. + if (canBeDefaultServer) { + SetDefaultAccount(account); + foundValidDefaultAccount = true; + break; + } + } + + if (!foundValidDefaultAccount) { + // Get the first account and use it. + // We need to fix this scenario, e.g. in bug 342632. + NS_WARNING("No valid default account found."); + if (firstAccount) { + NS_WARNING("Just using the first one (FIXME)."); + SetDefaultAccount(firstAccount); + } + } + } + } + + if (!m_defaultAccount) { + // Absolutely no usable account found. Error out. + NS_ERROR("Default account is null, when not expected!"); + *aDefaultAccount = nullptr; + return NS_ERROR_FAILURE; + } + NS_ADDREF(*aDefaultAccount = m_defaultAccount); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetDefaultAccount(nsIMsgAccount *aDefaultAccount) +{ + if (m_defaultAccount != aDefaultAccount) + { + nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount; + m_defaultAccount = aDefaultAccount; + (void) setDefaultAccountPref(aDefaultAccount); + (void) notifyDefaultServerChange(oldAccount, aDefaultAccount); + } + return NS_OK; +} + +// fire notifications +nsresult +nsMsgAccountManager::notifyDefaultServerChange(nsIMsgAccount *aOldAccount, + nsIMsgAccount *aNewAccount) +{ + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + nsCOMPtr<nsIMsgFolder> rootFolder; + + // first tell old server it's no longer the default + if (aOldAccount) { + rv = aOldAccount->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + rootFolder->NotifyBoolPropertyChanged(kDefaultServerAtom, + true, false); + } + } + + // now tell new server it is. + if (aNewAccount) { + rv = aNewAccount->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + rootFolder->NotifyBoolPropertyChanged(kDefaultServerAtom, + false, true); + } + } + + if (aOldAccount && aNewAccount) //only notify if the user goes and changes default account + { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) + observerService->NotifyObservers(nullptr,"mailDefaultAccountChanged",nullptr); + } + + return NS_OK; +} + +nsresult +nsMsgAccountManager::setDefaultAccountPref(nsIMsgAccount* aDefaultAccount) +{ + nsresult rv; + + if (aDefaultAccount) { + nsCString key; + rv = aDefaultAccount->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, key.get()); + NS_ENSURE_SUCCESS(rv,rv); + } + else + m_prefs->ClearUserPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT); + + return NS_OK; +} + +void nsMsgAccountManager::LogoutOfServer(nsIMsgIncomingServer *aServer) +{ + if (!aServer) + return; + mozilla::DebugOnly<nsresult> rv = aServer->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Shutdown of server failed"); + rv = aServer->ForgetSessionPassword(); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove the password associated with server"); +} + +NS_IMETHODIMP nsMsgAccountManager::GetFolderCache(nsIMsgFolderCache* *aFolderCache) +{ + NS_ENSURE_ARG_POINTER(aFolderCache); + nsresult rv = NS_OK; + + if (!m_msgFolderCache) + { + m_msgFolderCache = do_CreateInstance(kMsgFolderCacheCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> cacheFile; + rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_FOLDER_CACHE_50_FILE, + getter_AddRefs(cacheFile)); + NS_ENSURE_SUCCESS(rv, rv); + m_msgFolderCache->Init(cacheFile); + } + + NS_IF_ADDREF(*aFolderCache = m_msgFolderCache); + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAccounts(nsIArray **_retval) +{ + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> accounts(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t index = 0; index < m_accounts.Length(); index++) + { + nsCOMPtr<nsIMsgAccount> existingAccount(m_accounts[index]); + nsCOMPtr<nsIMsgIncomingServer> server; + existingAccount->GetIncomingServer(getter_AddRefs(server)); + if (!server) + continue; + if (server) + { + bool hidden = false; + server->GetHidden(&hidden); + if (hidden) + continue; + } + accounts->AppendElement(existingAccount, false); + } + NS_IF_ADDREF(*_retval = accounts); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAllIdentities(nsIArray **_retval) +{ + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> result(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> identities; + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) { + rv = m_accounts[i]->GetIdentities(getter_AddRefs(identities)); + if (NS_FAILED(rv)) + continue; + + uint32_t idCount; + rv = identities->GetLength(&idCount); + if (NS_FAILED(rv)) + continue; + + for (uint32_t j = 0; j < idCount; ++j) { + nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, j, &rv)); + if (NS_FAILED(rv)) + continue; + + nsAutoCString key; + rv = identity->GetKey(key); + if (NS_FAILED(rv)) + continue; + + uint32_t resultCount; + rv = result->GetLength(&resultCount); + if (NS_FAILED(rv)) + continue; + + bool found = false; + for (uint32_t k = 0; k < resultCount && !found; ++k) { + nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(result, k, &rv)); + if (NS_FAILED(rv)) + continue; + + nsAutoCString thisKey; + rv = thisIdentity->GetKey(thisKey); + if (NS_FAILED(rv)) + continue; + + if (key == thisKey) + found = true; + } + + if (!found) + result->AppendElement(identity, false); + } + } + result.forget(_retval); + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAllServers(nsIArray **_retval) +{ + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> servers(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (!server) + continue; + + bool hidden = false; + server->GetHidden(&hidden); + if (hidden) + continue; + + nsCString type; + if (NS_FAILED(server->GetType(type))) { + NS_WARNING("server->GetType() failed"); + continue; + } + + if (!type.EqualsLiteral("im")) { + servers->AppendElement(server, false); + } + } + + servers.forget(_retval); + return rv; +} + +nsresult +nsMsgAccountManager::LoadAccounts() +{ + nsresult rv; + + // for now safeguard multiple calls to this function + if (m_accountsLoaded) + return NS_OK; + + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + mailSession->AddFolderListener(this, nsIFolderListener::added | + nsIFolderListener::removed | + nsIFolderListener::intPropertyChanged); + // If we have code trying to do things after we've unloaded accounts, + // ignore it. + if (m_shutdownInProgress || m_haveShutdown) + return NS_ERROR_FAILURE; + + kDefaultServerAtom = MsgGetAtom("DefaultServer"); + mFolderFlagAtom = MsgGetAtom("FolderFlag"); + + //Ensure biff service has started + nsCOMPtr<nsIMsgBiffManager> biffService = + do_GetService(NS_MSGBIFFMANAGER_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + biffService->Init(); + + //Ensure purge service has started + nsCOMPtr<nsIMsgPurgeService> purgeService = + do_GetService(NS_MSGPURGESERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + purgeService->Init(); + + nsCOMPtr<nsIPrefService> prefservice(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Ensure messenger OS integration service has started + // note, you can't expect the integrationService to be there + // we don't have OS integration on all platforms. + nsCOMPtr<nsIMessengerOSIntegration> integrationService = + do_GetService(NS_MESSENGEROSINTEGRATION_CONTRACTID, &rv); + + // mail.accountmanager.accounts is the main entry point for all accounts + nsCString accountList; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, getter_Copies(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 separated account list>); + * This pref contains the list of pre-configured accounts that ISP/Vendor wants to + * 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, + getter_Copies(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(), getter_Copies(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], + 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) + nsCOMPtr<nsIArray> identities; + account->GetIdentities(getter_AddRefs(identities)); + + 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; + } + } + } + + if (rv == NS_ERROR_NOT_AVAILABLE && timeUnavailable != 0) + { // Our server is still unavailable. Have we timed out yet? + 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(); // njn: rename + 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->GetCharValue("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.Append(".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(), + getter_Copies(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", getter_Copies(userName)); + serverPrefBranch->GetCharPref("hostname", getter_Copies(hostName)); + serverPrefBranch->GetCharPref("type", getter_Copies(type)); + // Find a server with the same info. + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + continue; + } + nsCOMPtr<nsIMsgIncomingServer> server; + accountManager->FindServer(userName, hostName, type, + 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->SetCharValue("deferred_to_account", accountKey); + } + } + } + } + } + } + + nsAutoCString accountKeyPref("mail.account."); + nsCString dupAccountKey; + dupAccount->GetKey(dupAccountKey); + if (dupAccountKey.IsEmpty()) + continue; + accountKeyPref += dupAccountKey; + + nsCOMPtr<nsIPrefBranch> accountPrefBranch; + rv = prefservice->GetBranch(accountKeyPref.get(), + getter_AddRefs(accountPrefBranch)); + if (accountPrefBranch) + accountPrefBranch->DeleteBranch(""); + } + + // Make sure we have an account that points at the local folders server + nsCString localFoldersServerKey; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, + getter_Copies(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; +} + +// this routine goes through all the identities and makes sure +// that the special folders for each identity have the +// correct special folder flags set, e.g, the Sent folder has +// the sent flag set. +// +// it also goes through all the spam settings for each account +// and makes sure the folder flags are set there, too +NS_IMETHODIMP +nsMsgAccountManager::SetSpecialFolders() +{ + nsresult rv; + nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIArray> identities; + GetAllIdentities(getter_AddRefs(identities)); + + uint32_t idCount = 0; + identities->GetLength(&idCount); + + uint32_t id; + nsCString identityKey; + + for (id = 0; id < idCount; id++) + { + nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, id, &rv)); + if (NS_FAILED(rv)) + continue; + + if (NS_SUCCEEDED(rv) && thisIdentity) + { + nsCString folderUri; + nsCOMPtr<nsIRDFResource> res; + nsCOMPtr<nsIMsgFolder> folder; + thisIdentity->GetFccFolder(folderUri); + if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res)))) + { + folder = do_QueryInterface(res, &rv); + nsCOMPtr <nsIMsgFolder> parent; + if (folder && NS_SUCCEEDED(rv)) + { + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) + rv = folder->SetFlag(nsMsgFolderFlags::SentMail); + } + } + thisIdentity->GetDraftFolder(folderUri); + if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res)))) + { + folder = do_QueryInterface(res, &rv); + nsCOMPtr <nsIMsgFolder> parent; + if (folder && NS_SUCCEEDED(rv)) + { + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) + rv = folder->SetFlag(nsMsgFolderFlags::Drafts); + } + } + thisIdentity->GetArchiveFolder(folderUri); + if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res)))) + { + folder = do_QueryInterface(res, &rv); + nsCOMPtr <nsIMsgFolder> parent; + if (folder && NS_SUCCEEDED(rv)) + { + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) + { + bool archiveEnabled; + thisIdentity->GetArchiveEnabled(&archiveEnabled); + if (archiveEnabled) + rv = folder->SetFlag(nsMsgFolderFlags::Archive); + else + rv = folder->ClearFlag(nsMsgFolderFlags::Archive); + } + } + } + thisIdentity->GetStationeryFolder(folderUri); + if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res)))) + { + folder = do_QueryInterface(res, &rv); + if (folder && NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIMsgFolder> parent; + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) // only set flag if folder is real + rv = folder->SetFlag(nsMsgFolderFlags::Templates); + } + } + } + } + + // XXX todo + // get all servers + // get all spam settings for each server + // set the JUNK folder flag on the spam folders, right? + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::UnloadAccounts() +{ + // release the default account + kDefaultServerAtom = nullptr; + mFolderFlagAtom = nullptr; + + m_defaultAccount=nullptr; + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (!server) + continue; + nsresult rv; + NotifyServerUnloaded(server); + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv)) { + removeListenersFromFolder(rootFolder); + + rootFolder->Shutdown(true); + } + } + + m_accounts.Clear(); // will release all elements + m_identities.Clear(); + m_incomingServers.Clear(); + mAccountKeyList.Truncate(); + SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, EmptyCString()); + + if (m_accountsLoaded) + { + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID); + if (mailSession) + mailSession->RemoveFolderListener(this); + m_accountsLoaded = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::ShutdownServers() +{ + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (server) + server->Shutdown(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CloseCachedConnections() +{ + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (server) + server->CloseCachedConnections(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CleanupOnExit() +{ + // This can get called multiple times, and potentially re-entrantly. + // So add some protection against that. + if (m_shutdownInProgress) + return NS_OK; + + m_shutdownInProgress = true; + + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + + bool emptyTrashOnExit = false; + bool cleanupInboxOnExit = false; + nsresult rv; + + if (WeAreOffline()) + break; + + if (!server) + continue; + + server->GetEmptyTrashOnExit(&emptyTrashOnExit); + nsCOMPtr <nsIImapIncomingServer> imapserver = do_QueryInterface(server); + if (imapserver) + { + imapserver->GetCleanupInboxOnExit(&cleanupInboxOnExit); + imapserver->SetShuttingDown(true); + } + if (emptyTrashOnExit || cleanupInboxOnExit) + { + nsCOMPtr<nsIMsgFolder> root; + server->GetRootFolder(getter_AddRefs(root)); + nsCString type; + server->GetType(type); + if (root) + { + nsCOMPtr<nsIMsgFolder> folder; + folder = do_QueryInterface(root); + if (folder) + { + nsCString passwd; + bool serverRequiresPasswordForAuthentication = true; + bool isImap = type.EqualsLiteral("imap"); + if (isImap) + { + server->GetServerRequiresPasswordForBiff(&serverRequiresPasswordForAuthentication); + server->GetPassword(passwd); + } + if (!isImap || (isImap && (!serverRequiresPasswordForAuthentication || !passwd.IsEmpty()))) + { + nsCOMPtr<nsIUrlListener> urlListener; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + continue; + + if (isImap) + urlListener = do_QueryInterface(accountManager, &rv); + + if (isImap && cleanupInboxOnExit) + { + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = folder->GetSubFolders(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(rv)) + { + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr<nsISupports> item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr<nsIMsgFolder> inboxFolder(do_QueryInterface(item)); + if (!inboxFolder) + continue; + + uint32_t flags; + inboxFolder->GetFlags(&flags); + if (flags & nsMsgFolderFlags::Inbox) + { + rv = inboxFolder->Compact(urlListener, nullptr /* msgwindow */); + if (NS_SUCCEEDED(rv)) + accountManager->SetFolderDoingCleanupInbox(inboxFolder); + break; + } + } + } + } + + if (emptyTrashOnExit) + { + rv = folder->EmptyTrash(nullptr, urlListener); + if (isImap && NS_SUCCEEDED(rv)) + accountManager->SetFolderDoingEmptyTrash(folder); + } + + if (isImap && urlListener) + { + nsCOMPtr<nsIThread> thread(do_GetCurrentThread()); + + bool inProgress = false; + if (cleanupInboxOnExit) + { + int32_t loopCount = 0; // used to break out after 5 seconds + accountManager->GetCleanupInboxInProgress(&inProgress); + while (inProgress && loopCount++ < 5000) + { + accountManager->GetCleanupInboxInProgress(&inProgress); + PR_CEnterMonitor(folder); + PR_CWait(folder, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(folder); + NS_ProcessPendingEvents(thread, PR_MicrosecondsToInterval(1000UL)); + } + } + if (emptyTrashOnExit) + { + accountManager->GetEmptyTrashInProgress(&inProgress); + int32_t loopCount = 0; + while (inProgress && loopCount++ < 5000) + { + accountManager->GetEmptyTrashInProgress(&inProgress); + PR_CEnterMonitor(folder); + PR_CWait(folder, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(folder); + NS_ProcessPendingEvents(thread, PR_MicrosecondsToInterval(1000UL)); + } + } + } + } + } + } + } + } + + // Try to do this early on in the shutdown process before + // necko shuts itself down. + CloseCachedConnections(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::WriteToFolderCache(nsIMsgFolderCache *folderCache) +{ + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + iter.Data()->WriteToFolderCache(folderCache); + } + return folderCache ? folderCache->Close() : NS_ERROR_FAILURE; +} + +nsresult +nsMsgAccountManager::createKeyedAccount(const nsCString& key, + nsIMsgAccount ** aAccount) +{ + + nsresult rv; + nsCOMPtr<nsIMsgAccount> account = do_CreateInstance(kMsgAccountCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + account->SetKey(key); + + m_accounts.AppendElement(account); + + // add to string list + if (mAccountKeyList.IsEmpty()) + mAccountKeyList = key; + else { + mAccountKeyList.Append(','); + mAccountKeyList.Append(key); + } + + m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, mAccountKeyList.get()); + account.swap(*aAccount); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CreateAccount(nsIMsgAccount **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsAutoCString key; + getUniqueAccountKey(key); + + return createKeyedAccount(key, _retval); +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAccount(const nsACString& aKey, nsIMsgAccount **aAccount) +{ + NS_ENSURE_ARG_POINTER(aAccount); + *aAccount = nullptr; + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) + { + nsCOMPtr<nsIMsgAccount> account(m_accounts[i]); + nsCString key; + account->GetKey(key); + if (key.Equals(aKey)) + { + account.swap(*aAccount); + break; + } + } + + // If not found, create on demand. + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::FindServerIndex(nsIMsgIncomingServer* server, int32_t* result) +{ + NS_ENSURE_ARG_POINTER(server); + NS_ENSURE_ARG_POINTER(result); + + nsCString key; + nsresult rv = server->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + // do this by account because the account list is in order + uint32_t i; + for (i = 0; i < m_accounts.Length(); ++i) + { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server)); + if (!server || NS_FAILED(rv)) + continue; + + nsCString serverKey; + rv = server->GetKey(serverKey); + if (NS_FAILED(rv)) + continue; + + // stop when found, + // index will be set to the current index + if (serverKey.Equals(key)) + break; + } + + // Even if the search failed, we can return index. + // This means that all servers not in the array return an index higher + // than all "registered" servers. + *result = i; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::AddIncomingServerListener(nsIIncomingServerListener *serverListener) +{ + m_incomingServerListeners.AppendObject(serverListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::RemoveIncomingServerListener(nsIIncomingServerListener *serverListener) +{ + m_incomingServerListeners.RemoveObject(serverListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::NotifyServerLoaded(nsIMsgIncomingServer *server) +{ + int32_t count = m_incomingServerListeners.Count(); + for(int32_t i = 0; i < count; i++) + { + nsIIncomingServerListener* listener = m_incomingServerListeners[i]; + listener->OnServerLoaded(server); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::NotifyServerUnloaded(nsIMsgIncomingServer *server) +{ + NS_ENSURE_ARG_POINTER(server); + + int32_t count = m_incomingServerListeners.Count(); + server->SetFilterList(nullptr); // clear this to cut shutdown leaks. we are always passing valid non-null server here. + + for(int32_t i = 0; i < count; i++) + { + nsIIncomingServerListener* listener = m_incomingServerListeners[i]; + listener->OnServerUnloaded(server); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::NotifyServerChanged(nsIMsgIncomingServer *server) +{ + int32_t count = m_incomingServerListeners.Count(); + for(int32_t i = 0; i < count; i++) + { + nsIIncomingServerListener* listener = m_incomingServerListeners[i]; + listener->OnServerChanged(server); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::FindServerByURI(nsIURI *aURI, bool aRealFlag, + nsIMsgIncomingServer** aResult) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + // Get username and hostname and port so we can get the server + nsAutoCString username; + nsAutoCString escapedUsername; + rv = aURI->GetUserPass(escapedUsername); + if (NS_SUCCEEDED(rv) && !escapedUsername.IsEmpty()) + MsgUnescapeString(escapedUsername, 0, username); + + nsAutoCString hostname; + nsAutoCString escapedHostname; + rv = aURI->GetHost(escapedHostname); + if (NS_SUCCEEDED(rv) && !escapedHostname.IsEmpty()) + MsgUnescapeString(escapedHostname, 0, hostname); + + nsAutoCString type; + rv = aURI->GetScheme(type); + if (NS_SUCCEEDED(rv) && !type.IsEmpty()) + { + // now modify type if pop or news + if (type.EqualsLiteral("pop")) + type.AssignLiteral("pop3"); + // we use "nntp" in the server list so translate it here. + else if (type.EqualsLiteral("news")) + type.AssignLiteral("nntp"); + // we use "any" as the wildcard type. + else if (type.EqualsLiteral("any")) + type.Truncate(); + } + + int32_t port = 0; + // check the port of the scheme is not 'none' or blank + if (!(type.EqualsLiteral("none") || type.IsEmpty())) + { + rv = aURI->GetPort(&port); + // Set the port to zero if we got a -1 (use default) + if (NS_SUCCEEDED(rv) && (port == -1)) + port = 0; + } + + return findServerInternal(username, hostname, type, port, aRealFlag, aResult); +} + +nsresult +nsMsgAccountManager::findServerInternal(const nsACString& username, + const nsACString& hostname, + const nsACString& type, + int32_t port, + bool aRealFlag, + nsIMsgIncomingServer** aResult) +{ + // If 'aRealFlag' is set then we want to scan all existing accounts + // to make sure there's no duplicate including those whose host and/or + // user names have been changed. + if (!aRealFlag && + (m_lastFindServerUserName.Equals(username)) && + (m_lastFindServerHostName.Equals(hostname)) && + (m_lastFindServerType.Equals(type)) && + (m_lastFindServerPort == port) && + m_lastFindServerResult) + { + NS_ADDREF(*aResult = m_lastFindServerResult); + return NS_OK; + } + + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + // Find matching server by user+host+type+port. + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + + if (!server) + continue; + + nsresult rv; + nsCString thisHostname; + if (aRealFlag) + rv = server->GetRealHostName(thisHostname); + else + rv = server->GetHostName(thisHostname); + if (NS_FAILED(rv)) + continue; + + nsCString thisUsername; + if (aRealFlag) + rv = server->GetRealUsername(thisUsername); + else + rv = server->GetUsername(thisUsername); + if (NS_FAILED(rv)) + continue; + + nsCString thisType; + rv = server->GetType(thisType); + if (NS_FAILED(rv)) + continue; + + int32_t thisPort = -1; // use the default port identifier + // Don't try and get a port for the 'none' scheme + if (!thisType.EqualsLiteral("none")) + { + rv = server->GetPort(&thisPort); + if (NS_FAILED(rv)) { + continue; + } + } + + // treat "" as a wild card, so if the caller passed in "" for the desired attribute + // treat it as a match + if ((type.IsEmpty() || thisType.Equals(type)) && + (hostname.IsEmpty() || thisHostname.Equals(hostname, nsCaseInsensitiveCStringComparator())) && + (!(port != 0) || (port == thisPort)) && + (username.IsEmpty() || thisUsername.Equals(username))) + { + // stop on first find; cache for next time + if (!aRealFlag) + SetLastServerFound(server, hostname, username, port, type); + + NS_ADDREF(*aResult = server); + return NS_OK; + } + } + + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsMsgAccountManager::FindServer(const nsACString& username, + const nsACString& hostname, + const nsACString& type, + nsIMsgIncomingServer** aResult) +{ + return findServerInternal(username, hostname, type, 0, false, aResult); +} + +// Interface called by UI js only (always return true). +NS_IMETHODIMP +nsMsgAccountManager::FindRealServer(const nsACString& username, + const nsACString& hostname, + const nsACString& type, + int32_t port, + nsIMsgIncomingServer** aResult) +{ + *aResult = nullptr; + findServerInternal(username, hostname, type, port, true, aResult); + return NS_OK; +} + +void +nsMsgAccountManager::findAccountByServerKey(const nsCString &aKey, + nsIMsgAccount **aResult) +{ + *aResult = nullptr; + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) + { + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server)); + if (!server || NS_FAILED(rv)) + continue; + + nsCString key; + rv = server->GetKey(key); + if (NS_FAILED(rv)) + continue; + + // if the keys are equal, the servers are equal + if (key.Equals(aKey)) + { + NS_ADDREF(*aResult = m_accounts[i]); + break; // stop on first found account + } + } +} + +NS_IMETHODIMP +nsMsgAccountManager::FindAccountForServer(nsIMsgIncomingServer *server, + nsIMsgAccount **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!server) + { + (*aResult) = nullptr; + return NS_OK; + } + + nsresult rv; + + nsCString key; + rv = server->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + findAccountByServerKey(key, aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetFirstIdentityForServer(nsIMsgIncomingServer *aServer, nsIMsgIdentity **aIdentity) +{ + NS_ENSURE_ARG_POINTER(aServer); + NS_ENSURE_ARG_POINTER(aIdentity); + + nsCOMPtr<nsIArray> identities; + nsresult rv = GetIdentitiesForServer(aServer, getter_AddRefs(identities)); + NS_ENSURE_SUCCESS(rv, rv); + + // not all servers have identities + // for example, Local Folders + uint32_t numIdentities; + rv = identities->GetLength(&numIdentities); + NS_ENSURE_SUCCESS(rv, rv); + + if (numIdentities > 0) + { + nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, 0, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + identity.swap(*aIdentity); + } + else + *aIdentity = nullptr; + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetIdentitiesForServer(nsIMsgIncomingServer *server, + nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(server); + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> identities(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString serverKey; + rv = server->GetKey(serverKey); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) + { + nsCOMPtr<nsIMsgAccount> account(m_accounts[i]); + + nsCOMPtr<nsIMsgIncomingServer> thisServer; + rv = account->GetIncomingServer(getter_AddRefs(thisServer)); + if (NS_FAILED(rv) || !thisServer) + continue; + + nsAutoCString thisServerKey; + rv = thisServer->GetKey(thisServerKey); + if (serverKey.Equals(thisServerKey)) + { + nsCOMPtr<nsIArray> theseIdentities; + rv = account->GetIdentities(getter_AddRefs(theseIdentities)); + if (NS_SUCCEEDED(rv)) + { + uint32_t theseLength; + rv = theseIdentities->GetLength(&theseLength); + if (NS_SUCCEEDED(rv)) + { + for (uint32_t j = 0; j < theseLength; ++j) + { + nsCOMPtr<nsISupports> id(do_QueryElementAt(theseIdentities, j, &rv)); + if (NS_SUCCEEDED(rv)) + identities->AppendElement(id, false); + } + } + } + } + } + + identities.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetServersForIdentity(nsIMsgIdentity *aIdentity, + nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + + nsresult rv; + rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> servers(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) + { + nsCOMPtr<nsIArray> identities; + if (NS_FAILED(m_accounts[i]->GetIdentities(getter_AddRefs(identities)))) + continue; + + uint32_t idCount = 0; + if (NS_FAILED(identities->GetLength(&idCount))) + continue; + + uint32_t id; + nsCString identityKey; + rv = aIdentity->GetKey(identityKey); + for (id = 0; id < idCount; id++) + { + nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, id, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCString thisIdentityKey; + rv = thisIdentity->GetKey(thisIdentityKey); + + if (NS_SUCCEEDED(rv) && identityKey.Equals(thisIdentityKey)) + { + nsCOMPtr<nsIMsgIncomingServer> thisServer; + rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(thisServer)); + if (thisServer && NS_SUCCEEDED(rv)) + { + servers->AppendElement(thisServer, false); + break; + } + } + } + } + } + servers.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::AddRootFolderListener(nsIFolderListener *aListener) +{ + NS_ENSURE_TRUE(aListener, NS_OK); + mFolderListeners.AppendElement(aListener); + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_FAILED(rv)) { + continue; + } + rv = rootFolder->AddFolderListener(aListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::RemoveRootFolderListener(nsIFolderListener *aListener) +{ + NS_ENSURE_TRUE(aListener, NS_OK); + mFolderListeners.RemoveElement(aListener); + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_FAILED(rv)) { + continue; + } + rv = rootFolder->RemoveFolderListener(aListener); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::SetLocalFoldersServer(nsIMsgIncomingServer *aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + nsCString key; + nsresult rv = aServer->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, key.get()); +} + +NS_IMETHODIMP nsMsgAccountManager::GetLocalFoldersServer(nsIMsgIncomingServer **aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + + nsCString serverKey; + + nsresult rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, getter_Copies(serverKey)); + + if (NS_SUCCEEDED(rv) && !serverKey.IsEmpty()) + { + rv = GetIncomingServer(serverKey, aServer); + if (NS_SUCCEEDED(rv)) + return rv; + // otherwise, we're going to fall through to looking for an existing local + // folders account, because now we fail creating one if one already exists. + } + + // try ("nobody","Local Folders","none"), and work down to any "none" server. + rv = FindServer(NS_LITERAL_CSTRING("nobody"), NS_LITERAL_CSTRING("Local Folders"), + NS_LITERAL_CSTRING("none"), aServer); + if (NS_FAILED(rv) || !*aServer) + { + rv = FindServer(NS_LITERAL_CSTRING("nobody"), EmptyCString(), NS_LITERAL_CSTRING("none"), aServer); + if (NS_FAILED(rv) || !*aServer) + { + rv = FindServer(EmptyCString(), NS_LITERAL_CSTRING("Local Folders"), + NS_LITERAL_CSTRING("none"), aServer); + if (NS_FAILED(rv) || !*aServer) + rv = FindServer(EmptyCString(), EmptyCString(), NS_LITERAL_CSTRING("none"), aServer); + } + } + + NS_ENSURE_SUCCESS(rv, rv); + if (!*aServer) + return NS_ERROR_FAILURE; + + // we don't want the Smart Mailboxes server to be the local server. + bool hidden; + (*aServer)->GetHidden(&hidden); + if (hidden) + return NS_ERROR_FAILURE; + + rv = SetLocalFoldersServer(*aServer); + return rv; +} + +nsresult nsMsgAccountManager::GetLocalFoldersPrettyName(nsString &localFoldersName) +{ + // we don't want "nobody at Local Folders" to show up in the + // folder pane, so we set the pretty name to a localized "Local Folders" + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv; + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + + rv = sBundleService->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + return bundle->GetStringFromName(u"localFolders", getter_Copies(localFoldersName)); +} + +NS_IMETHODIMP +nsMsgAccountManager::CreateLocalMailAccount() +{ + // create the server + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = CreateIncomingServer(NS_LITERAL_CSTRING("nobody"), + NS_LITERAL_CSTRING("Local Folders"), + NS_LITERAL_CSTRING("none"), getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + + nsString localFoldersName; + rv = GetLocalFoldersPrettyName(localFoldersName); + NS_ENSURE_SUCCESS(rv, rv); + server->SetPrettyName(localFoldersName); + + nsCOMPtr<nsINoIncomingServer> noServer; + noServer = do_QueryInterface(server, &rv); + if (NS_FAILED(rv)) return rv; + + // create the directory structure for old 4.x "Local Mail" + // under <profile dir>/Mail/Local Folders or + // <"mail.directory" pref>/Local Folders + nsCOMPtr <nsIFile> mailDir; + nsCOMPtr <nsIFile> localFile; + bool dirExists; + + // we want <profile>/Mail + rv = NS_GetSpecialDirectory(NS_APP_MAIL_50_DIR, getter_AddRefs(mailDir)); + if (NS_FAILED(rv)) return rv; + localFile = do_QueryInterface(mailDir); + + rv = mailDir->Exists(&dirExists); + if (NS_SUCCEEDED(rv) && !dirExists) + rv = mailDir->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + + // set the default local path for "none" + rv = server->SetDefaultLocalPath(localFile); + if (NS_FAILED(rv)) return rv; + + // Create an account when valid server values are established. + // This will keep the status of accounts sane by avoiding the addition of incomplete accounts. + nsCOMPtr<nsIMsgAccount> account; + rv = CreateAccount(getter_AddRefs(account)); + if (NS_FAILED(rv)) return rv; + + // notice, no identity for local mail + // hook the server to the account + // after we set the server's local path + // (see bug #66018) + account->SetIncomingServer(server); + + // remember this as the local folders server + return SetLocalFoldersServer(server); +} + + // nsIUrlListener methods + +NS_IMETHODIMP +nsMsgAccountManager::OnStartRunningUrl(nsIURI * aUrl) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) +{ + if (aUrl) + { + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl); + if (imapUrl) + { + nsImapAction imapAction = nsIImapUrl::nsImapTest; + imapUrl->GetImapAction(&imapAction); + switch(imapAction) + { + case nsIImapUrl::nsImapExpungeFolder: + if (m_folderDoingCleanupInbox) + { + PR_CEnterMonitor(m_folderDoingCleanupInbox); + PR_CNotifyAll(m_folderDoingCleanupInbox); + m_cleanupInboxInProgress = false; + PR_CExitMonitor(m_folderDoingCleanupInbox); + m_folderDoingCleanupInbox=nullptr; //reset to nullptr + } + break; + case nsIImapUrl::nsImapDeleteAllMsgs: + if (m_folderDoingEmptyTrash) + { + PR_CEnterMonitor(m_folderDoingEmptyTrash); + PR_CNotifyAll(m_folderDoingEmptyTrash); + m_emptyTrashInProgress = false; + PR_CExitMonitor(m_folderDoingEmptyTrash); + m_folderDoingEmptyTrash = nullptr; //reset to nullptr; + } + break; + default: + break; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetFolderDoingEmptyTrash(nsIMsgFolder *folder) +{ + m_folderDoingEmptyTrash = folder; + m_emptyTrashInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetEmptyTrashInProgress(bool *bVal) +{ + NS_ENSURE_ARG_POINTER(bVal); + *bVal = m_emptyTrashInProgress; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetFolderDoingCleanupInbox(nsIMsgFolder *folder) +{ + m_folderDoingCleanupInbox = folder; + m_cleanupInboxInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetCleanupInboxInProgress(bool *bVal) +{ + NS_ENSURE_ARG_POINTER(bVal); + *bVal = m_cleanupInboxInProgress; + return NS_OK; +} + +void +nsMsgAccountManager::SetLastServerFound(nsIMsgIncomingServer *server, const nsACString& hostname, + const nsACString& username, const int32_t port, const nsACString& type) +{ + m_lastFindServerResult = server; + m_lastFindServerHostName = hostname; + m_lastFindServerUserName = username; + m_lastFindServerPort = port; + m_lastFindServerType = type; +} + +NS_IMETHODIMP +nsMsgAccountManager::SaveAccountInfo() +{ + nsresult rv; + nsCOMPtr<nsIPrefService> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + return pref->SavePrefFile(nullptr); +} + +NS_IMETHODIMP +nsMsgAccountManager::GetChromePackageName(const nsACString& aExtensionName, nsACString& aChromePackageName) +{ + nsresult rv; + nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsISimpleEnumerator> e; + rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, getter_AddRefs(e)); + if(NS_SUCCEEDED(rv) && e) { + while (true) { + nsCOMPtr<nsISupports> supports; + rv = e->GetNext(getter_AddRefs(supports)); + nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(supports); + if (NS_FAILED(rv) || !catEntry) + break; + + nsAutoCString entryString; + rv = catEntry->GetData(entryString); + if (NS_FAILED(rv)) + break; + + nsCString contractidString; + rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, entryString.get(), + getter_Copies(contractidString)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr <nsIMsgAccountManagerExtension> extension = do_GetService(contractidString.get(), &rv); + if (NS_FAILED(rv) || !extension) + break; + + nsCString name; + rv = extension->GetName(name); + if (NS_FAILED(rv)) + break; + + if (name.Equals(aExtensionName)) + return extension->GetChromePackageName(aChromePackageName); + } + } + return NS_ERROR_UNEXPECTED; +} + +class VFChangeListenerEvent : public mozilla::Runnable +{ +public: + VFChangeListenerEvent(VirtualFolderChangeListener *vfChangeListener, + nsIMsgFolder *virtFolder, nsIMsgDatabase *virtDB) + : mVFChangeListener(vfChangeListener), mFolder(virtFolder), mDB(virtDB) + {} + + NS_IMETHOD Run() + { + if (mVFChangeListener) + mVFChangeListener->ProcessUpdateEvent(mFolder, mDB); + return NS_OK; + } + +private: + RefPtr<VirtualFolderChangeListener> mVFChangeListener; + nsCOMPtr<nsIMsgFolder> mFolder; + nsCOMPtr<nsIMsgDatabase> mDB; +}; + +NS_IMPL_ISUPPORTS(VirtualFolderChangeListener, nsIDBChangeListener) + +VirtualFolderChangeListener::VirtualFolderChangeListener() : + m_searchOnMsgStatus(false), m_batchingEvents(false) +{} + +nsresult VirtualFolderChangeListener::Init() +{ + nsCOMPtr <nsIMsgDatabase> msgDB; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + + nsresult rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(msgDB)); + if (NS_SUCCEEDED(rv) && msgDB) + { + nsCString searchTermString; + dbFolderInfo->GetCharProperty("searchStr", searchTermString); + nsCOMPtr<nsIMsgFilterService> filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + nsCOMPtr<nsIMsgFilterList> filterList; + rv = filterService->GetTempFilterList(m_virtualFolder, getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgFilter> tempFilter; + filterList->CreateFilter(NS_LITERAL_STRING("temp"), getter_AddRefs(tempFilter)); + NS_ENSURE_SUCCESS(rv, rv); + filterList->ParseCondition(tempFilter, searchTermString.get()); + NS_ENSURE_SUCCESS(rv, rv); + m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsArray> searchTerms; + rv = tempFilter->GetSearchTerms(getter_AddRefs(searchTerms)); + NS_ENSURE_SUCCESS(rv, rv); + + // we add the search scope right before we match the header, + // because we don't want the search scope caching the body input + // stream, because that holds onto the mailbox file, breaking + // compaction. + + // add each item in termsArray to the search session + uint32_t numTerms; + searchTerms->Count(&numTerms); + for (uint32_t i = 0; i < numTerms; i++) + { + nsCOMPtr <nsIMsgSearchTerm> searchTerm (do_QueryElementAt(searchTerms, i)); + nsMsgSearchAttribValue attrib; + searchTerm->GetAttrib(&attrib); + if (attrib == nsMsgSearchAttrib::MsgStatus) + m_searchOnMsgStatus = true; + m_searchSession->AppendTerm(searchTerm); + } + } + return rv; +} + + /** + * nsIDBChangeListener + */ + +NS_IMETHODIMP +VirtualFolderChangeListener::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrChanged, bool aPreChange, uint32_t *aStatus, + nsIDBChangeListener *aInstigator) +{ + const uint32_t kMatch = 0x1; + const uint32_t kRead = 0x2; + const uint32_t kNew = 0x4; + NS_ENSURE_ARG_POINTER(aHdrChanged); + NS_ENSURE_ARG_POINTER(aStatus); + + uint32_t flags; + bool match; + nsCOMPtr<nsIMsgDatabase> msgDB; + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + // we don't want any early returns from this function, until we've + // called ClearScopes on the search session. + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching); + rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &match); + m_searchSession->ClearScopes(); + NS_ENSURE_SUCCESS(rv, rv); + aHdrChanged->GetFlags(&flags); + + if (aPreChange) // We're looking at the old header, save status + { + *aStatus = 0; + if (match) + *aStatus |= kMatch; + if (flags & nsMsgMessageFlags::Read) + *aStatus |= kRead; + if (flags & nsMsgMessageFlags::New) + *aStatus |= kNew; + return NS_OK; + } + + // This is the post change section where changes are detected + + bool wasMatch = *aStatus & kMatch; + if (!match && !wasMatch) // header not in virtual folder + return NS_OK; + + int32_t totalDelta = 0, unreadDelta = 0, newDelta = 0; + + if (match) { + totalDelta++; + if (!(flags & nsMsgMessageFlags::Read)) + unreadDelta++; + if (flags & nsMsgMessageFlags::New) + newDelta++; + } + + if (wasMatch) { + totalDelta--; + if (!(*aStatus & kRead)) unreadDelta--; + if (*aStatus & kNew) newDelta--; + } + + if ( !(unreadDelta || totalDelta || newDelta) ) + return NS_OK; + + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + + if (unreadDelta) + dbFolderInfo->ChangeNumUnreadMessages(unreadDelta); + + if (newDelta) + { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + m_virtualFolder->SetNumNewMessages(numNewMessages + newDelta); + m_virtualFolder->SetHasNewMessages(numNewMessages + newDelta > 0); + } + + if (totalDelta) + { + dbFolderInfo->ChangeNumMessages(totalDelta); + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri.get(), aHdrChanged, totalDelta == 1); + } + + PostUpdateEvent(m_virtualFolder, virtDatabase); + + return NS_OK; +} + +void VirtualFolderChangeListener::DecrementNewMsgCount() +{ + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + if (numNewMessages > 0) + numNewMessages--; + m_virtualFolder->SetNumNewMessages(numNewMessages); + if (!numNewMessages) + m_virtualFolder->SetHasNewMessages(false); +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + nsCOMPtr <nsIMsgDatabase> msgDB; + + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + bool oldMatch = false, newMatch = false; + // we don't want any early returns from this function, until we've + // called ClearScopes 0n the search session. + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching); + rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &newMatch); + if (NS_SUCCEEDED(rv) && m_searchOnMsgStatus) + { + // if status is a search criteria, check if the header matched before + // it changed, in order to determine if we need to bump the counts. + aHdrChanged->SetFlags(aOldFlags); + rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &oldMatch); + aHdrChanged->SetFlags(aNewFlags); // restore new flags even on match failure. + } + else + oldMatch = newMatch; + m_searchSession->ClearScopes(); + NS_ENSURE_SUCCESS(rv, rv); + // we don't want to change the total counts if this virtual folder is open in a view, + // because we won't remove the header from view while it's open. On the other hand, + // it's hard to fix the count when the user clicks away to another folder, w/o re-running + // the search, or setting some sort of pending count change. + // Maybe this needs to be handled in the view code...the view could do the same calculation + // and also keep track of the counts changed. Then, when the view was closed, if it's a virtual + // folder, it could update the counts for the db. + if (oldMatch != newMatch || (oldMatch && (aOldFlags & nsMsgMessageFlags::Read) != (aNewFlags & nsMsgMessageFlags::Read))) + { + nsCOMPtr <nsIMsgDatabase> virtDatabase; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + int32_t totalDelta = 0, unreadDelta = 0; + if (oldMatch != newMatch) + { + // bool isOpen = false; +// nsCOMPtr <nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID); +// if (mailSession && aFolder) +// mailSession->IsFolderOpenInWindow(m_virtualFolder, &isOpen); + // we can't remove headers that no longer match - but we might add headers that newly match, someday. +// if (!isOpen) + totalDelta = (oldMatch) ? -1 : 1; + } + bool msgHdrIsRead; + aHdrChanged->GetIsRead(&msgHdrIsRead); + if (oldMatch == newMatch) // read flag changed state + unreadDelta = (msgHdrIsRead) ? -1 : 1; + else if (oldMatch) // else header should removed + unreadDelta = (aOldFlags & nsMsgMessageFlags::Read) ? 0 : -1; + else // header should be added + unreadDelta = (aNewFlags & nsMsgMessageFlags::Read) ? 0 : 1; + if (unreadDelta) + dbFolderInfo->ChangeNumUnreadMessages(unreadDelta); + if (totalDelta) + dbFolderInfo->ChangeNumMessages(totalDelta); + if (unreadDelta == -1 && aOldFlags & nsMsgMessageFlags::New) + DecrementNewMsgCount(); + + if (totalDelta) + { + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri.get(), aHdrChanged, totalDelta == 1); + } + + PostUpdateEvent(m_virtualFolder, virtDatabase); + } + else if (oldMatch && (aOldFlags & nsMsgMessageFlags::New) && + !(aNewFlags & nsMsgMessageFlags::New)) + DecrementNewMsgCount(); + + return rv; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + nsCOMPtr <nsIMsgDatabase> msgDB; + + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + bool match = false; + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching); + // Since the notifier went to the trouble of passing in the msg flags, + // we should use them when doing the match. + uint32_t msgFlags; + aHdrDeleted->GetFlags(&msgFlags); + aHdrDeleted->SetFlags(aFlags); + rv = m_searchSession->MatchHdr(aHdrDeleted, msgDB, &match); + aHdrDeleted->SetFlags(msgFlags); + m_searchSession->ClearScopes(); + if (match) + { + nsCOMPtr <nsIMsgDatabase> virtDatabase; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + bool msgHdrIsRead; + aHdrDeleted->GetIsRead(&msgHdrIsRead); + if (!msgHdrIsRead) + dbFolderInfo->ChangeNumUnreadMessages(-1); + dbFolderInfo->ChangeNumMessages(-1); + if (aFlags & nsMsgMessageFlags::New) + { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + m_virtualFolder->SetNumNewMessages(numNewMessages - 1); + if (numNewMessages == 1) + m_virtualFolder->SetHasNewMessages(false); + } + + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri.get(), aHdrDeleted, false); + + PostUpdateEvent(m_virtualFolder, virtDatabase); + } + return rv; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnHdrAdded(nsIMsgDBHdr *aNewHdr, nsMsgKey aParentKey, int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + nsCOMPtr<nsIMsgDatabase> msgDB; + + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + bool match = false; + if (!m_searchSession) + return NS_ERROR_NULL_POINTER; + + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, m_folderWatching); + rv = m_searchSession->MatchHdr(aNewHdr, msgDB, &match); + m_searchSession->ClearScopes(); + if (match) + { + nsCOMPtr <nsIMsgDatabase> virtDatabase; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + bool msgHdrIsRead; + uint32_t msgFlags; + aNewHdr->GetIsRead(&msgHdrIsRead); + aNewHdr->GetFlags(&msgFlags); + if (!msgHdrIsRead) + dbFolderInfo->ChangeNumUnreadMessages(1); + if (msgFlags & nsMsgMessageFlags::New) + { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + m_virtualFolder->SetHasNewMessages(true); + m_virtualFolder->SetNumNewMessages(numNewMessages + 1); + } + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri.get(), aNewHdr, true); + dbFolderInfo->ChangeNumMessages(1); + PostUpdateEvent(m_virtualFolder, virtDatabase); + } + return rv; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + nsCOMPtr <nsIMsgDatabase> msgDB = do_QueryInterface(instigator); + if (msgDB) + msgDB->RemoveListener(this); + return NS_OK; +} + +NS_IMETHODIMP +VirtualFolderChangeListener::OnEvent(nsIMsgDatabase *aDB, const char *aEvent) +{ + return NS_OK; +} + + +NS_IMETHODIMP VirtualFolderChangeListener::OnReadChanged(nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnJunkScoreChanged(nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +nsresult VirtualFolderChangeListener::PostUpdateEvent(nsIMsgFolder *virtualFolder, + nsIMsgDatabase *virtDatabase) +{ + if (m_batchingEvents) + return NS_OK; + m_batchingEvents = true; + nsCOMPtr<nsIRunnable> event = new VFChangeListenerEvent(this, virtualFolder, + virtDatabase); + return NS_DispatchToCurrentThread(event); +} + +void VirtualFolderChangeListener::ProcessUpdateEvent(nsIMsgFolder *virtFolder, + nsIMsgDatabase *virtDB) +{ + m_batchingEvents = false; + virtFolder->UpdateSummaryTotals(true); // force update from db. + virtDB->Commit(nsMsgDBCommitType::kLargeCommit); +} + +nsresult nsMsgAccountManager::GetVirtualFoldersFile(nsCOMPtr<nsIFile>& file) +{ + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = profileDir->AppendNative(nsDependentCString("virtualFolders.dat")); + if (NS_SUCCEEDED(rv)) + file = do_QueryInterface(profileDir, &rv); + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::LoadVirtualFolders() +{ + nsCOMPtr <nsIFile> file; + GetVirtualFoldersFile(file); + if (!file) + return NS_ERROR_FAILURE; + + if (m_virtualFoldersLoaded) + return NS_OK; + + m_loadingVirtualFolders = true; + + nsresult rv; + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fileStream->Init(file, PR_RDONLY, 0664, false); + nsCOMPtr <nsILineInputStream> lineInputStream(do_QueryInterface(fileStream)); + + bool isMore = true; + nsAutoCString buffer; + int32_t version = -1; + nsCOMPtr <nsIMsgFolder> virtualFolder; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr<nsIRDFResource> resource; + nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIArray> allFolders; + + while (isMore && + NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) + { + if (!buffer.IsEmpty()) + { + if (version == -1) + { + buffer.Cut(0, 8); + nsresult irv; + version = buffer.ToInteger(&irv); + continue; + } + if (Substring(buffer, 0, 4).Equals("uri=")) + { + buffer.Cut(0, 4); + dbFolderInfo = nullptr; + + rv = rdf->GetResource(buffer, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv, rv); + + virtualFolder = do_QueryInterface(resource); + if (!virtualFolder) + NS_WARNING("Failed to QI virtual folder, is this leftover from an optional account type?"); + else + { + nsCOMPtr <nsIMsgFolder> grandParent; + nsCOMPtr <nsIMsgFolder> oldParent; + nsCOMPtr <nsIMsgFolder> parentFolder; + bool isServer; + do + { + // need to add the folder as a sub-folder of its parent. + int32_t lastSlash = buffer.RFindChar('/'); + if (lastSlash == kNotFound) + break; + nsDependentCSubstring parentUri(buffer, 0, lastSlash); + // hold a reference so it won't get deleted before it's parented. + oldParent = parentFolder; + + rdf->GetResource(parentUri, getter_AddRefs(resource)); + parentFolder = do_QueryInterface(resource); + if (parentFolder) + { + nsAutoString currentFolderNameStr; + nsAutoCString currentFolderNameCStr; + MsgUnescapeString(nsCString(Substring(buffer, lastSlash + 1, buffer.Length())), 0, currentFolderNameCStr); + CopyUTF8toUTF16(currentFolderNameCStr, currentFolderNameStr); + nsCOMPtr <nsIMsgFolder> childFolder; + nsCOMPtr <nsIMsgDatabase> db; + // force db to get created. + virtualFolder->SetParent(parentFolder); + rv = virtualFolder->GetMsgDatabase(getter_AddRefs(db)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + msgDBService->CreateNewDB(virtualFolder, getter_AddRefs(db)); + if (db) + rv = db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + else + break; + + parentFolder->AddSubfolder(currentFolderNameStr, getter_AddRefs(childFolder)); + virtualFolder->SetFlag(nsMsgFolderFlags::Virtual); + if (childFolder) + parentFolder->NotifyItemAdded(childFolder); + // here we make sure if our parent is rooted - if not, we're + // going to loop and add our parent as a child of its grandparent + // and repeat until we get to the server, or a folder that + // has its parent set. + parentFolder->GetParent(getter_AddRefs(grandParent)); + parentFolder->GetIsServer(&isServer); + buffer.SetLength(lastSlash); + } + else + break; + } while (!grandParent && !isServer); + } + } + else if (dbFolderInfo && Substring(buffer, 0, 6).Equals("scope=")) + { + buffer.Cut(0, 6); + // if this is a cross folder virtual folder, we have a list of folders uris, + // and we have to add a pending listener for each of them. + if (!buffer.IsEmpty()) + { + ParseAndVerifyVirtualFolderScope(buffer, rdf); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, buffer); + AddVFListenersForVF(virtualFolder, buffer, rdf, msgDBService); + } + } + else if (dbFolderInfo && Substring(buffer, 0, 6).Equals("terms=")) + { + buffer.Cut(0, 6); + dbFolderInfo->SetCharProperty("searchStr", buffer); + } + else if (dbFolderInfo && Substring(buffer, 0, 13).Equals("searchOnline=")) + { + buffer.Cut(0, 13); + dbFolderInfo->SetBooleanProperty("searchOnline", buffer.Equals("true")); + } + else if (dbFolderInfo && + Substring(buffer, 0, SEARCH_FOLDER_FLAG_LEN + 1) + .Equals(SEARCH_FOLDER_FLAG"=")) + { + buffer.Cut(0, SEARCH_FOLDER_FLAG_LEN + 1); + dbFolderInfo->SetCharProperty(SEARCH_FOLDER_FLAG, buffer); + } + } + } + } + + m_loadingVirtualFolders = false; + m_virtualFoldersLoaded = true; + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::SaveVirtualFolders() +{ + if (!m_virtualFoldersLoaded) + return NS_OK; + + nsCOMPtr<nsIFile> file; + GetVirtualFoldersFile(file); + + // Open a buffered, safe output stream + nsCOMPtr<nsIOutputStream> outStream; + nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(outStream), + file, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0664); + NS_ENSURE_SUCCESS(rv, rv); + + WriteLineToOutputStream("version=", "1", outStream); + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (server) + { + nsCOMPtr <nsIMsgFolder> rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) + { + nsCOMPtr <nsIArray> virtualFolders; + nsresult rv = rootFolder->GetFoldersWithFlags(nsMsgFolderFlags::Virtual, + getter_AddRefs(virtualFolders)); + if (NS_FAILED(rv)) { + continue; + } + uint32_t vfCount; + virtualFolders->GetLength(&vfCount); + for (uint32_t folderIndex = 0; folderIndex < vfCount; folderIndex++) + { + nsCOMPtr <nsIRDFResource> folderRes (do_QueryElementAt(virtualFolders, folderIndex)); + nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(folderRes); + const char *uri; + nsCOMPtr <nsIMsgDatabase> db; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + rv = msgFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); // force db to get created. + if (dbFolderInfo) + { + nsCString srchFolderUri; + nsCString searchTerms; + nsCString regexScope; + nsCString vfFolderFlag; + bool searchOnline = false; + dbFolderInfo->GetBooleanProperty("searchOnline", false, &searchOnline); + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri); + dbFolderInfo->GetCharProperty("searchStr", searchTerms); + // logically searchFolderFlag is an int, but since we want to + // write out a string, get it as a string. + dbFolderInfo->GetCharProperty(SEARCH_FOLDER_FLAG, vfFolderFlag); + folderRes->GetValueConst(&uri); + if (!srchFolderUri.IsEmpty() && !searchTerms.IsEmpty()) + { + WriteLineToOutputStream("uri=", uri, outStream); + if (!vfFolderFlag.IsEmpty()) + WriteLineToOutputStream(SEARCH_FOLDER_FLAG"=", vfFolderFlag.get(), outStream); + WriteLineToOutputStream("scope=", srchFolderUri.get(), outStream); + WriteLineToOutputStream("terms=", searchTerms.get(), outStream); + WriteLineToOutputStream("searchOnline=", searchOnline ? "true" : "false", outStream); + } + } + } + } + } + } + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream, &rv); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save personal dictionary file! possible data loss"); + } + } + return rv; +} + +nsresult nsMsgAccountManager::WriteLineToOutputStream(const char *prefix, const char * line, nsIOutputStream *outputStream) +{ + uint32_t writeCount; + outputStream->Write(prefix, strlen(prefix), &writeCount); + outputStream->Write(line, strlen(line), &writeCount); + outputStream->Write("\n", 1, &writeCount); + return NS_OK; +} + +/** + * Parse the '|' separated folder uri string into individual folders, verify + * that the folders are real. If we were to add things like wildcards, we + * could implement the expansion into real folders here. + * + * @param buffer On input, list of folder uri's, on output, verified list. + * @param rdf rdf service + */ +void nsMsgAccountManager::ParseAndVerifyVirtualFolderScope(nsCString &buffer, + nsIRDFService *rdf) +{ + nsCString verifiedFolders; + nsTArray<nsCString> folderUris; + ParseString(buffer, '|', folderUris); + nsCOMPtr <nsIRDFResource> resource; + nsCOMPtr<nsIMsgIncomingServer> server; + nsCOMPtr<nsIMsgFolder> parent; + + for (uint32_t i = 0; i < folderUris.Length(); i++) + { + rdf->GetResource(folderUris[i], getter_AddRefs(resource)); + nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource); + if (!realFolder) + continue; + realFolder->GetParent(getter_AddRefs(parent)); + if (!parent) + continue; + realFolder->GetServer(getter_AddRefs(server)); + if (!server) + continue; + if (!verifiedFolders.IsEmpty()) + verifiedFolders.Append('|'); + verifiedFolders.Append(folderUris[i]); + } + buffer.Assign(verifiedFolders); +} + +// This conveniently works to add a single folder as well. +nsresult nsMsgAccountManager::AddVFListenersForVF(nsIMsgFolder *virtualFolder, + const nsCString& srchFolderUris, + nsIRDFService *rdf, + nsIMsgDBService *msgDBService) +{ + nsTArray<nsCString> folderUris; + ParseString(srchFolderUris, '|', folderUris); + nsCOMPtr <nsIRDFResource> resource; + + for (uint32_t i = 0; i < folderUris.Length(); i++) + { + rdf->GetResource(folderUris[i], getter_AddRefs(resource)); + nsCOMPtr <nsIMsgFolder> realFolder = do_QueryInterface(resource); + if (!realFolder) + continue; + RefPtr<VirtualFolderChangeListener> dbListener = new VirtualFolderChangeListener(); + NS_ENSURE_TRUE(dbListener, NS_ERROR_OUT_OF_MEMORY); + dbListener->m_virtualFolder = virtualFolder; + dbListener->m_folderWatching = realFolder; + if (NS_FAILED(dbListener->Init())) + { + dbListener = nullptr; + continue; + } + m_virtualFolderListeners.AppendElement(dbListener); + msgDBService->RegisterPendingListener(realFolder, dbListener); + } + return NS_OK; +} + +// This is called if a folder that's part of the scope of a saved search +// has gone away. +nsresult nsMsgAccountManager::RemoveVFListenerForVF(nsIMsgFolder *virtualFolder, + nsIMsgFolder *folder) +{ + nsresult rv; + nsCOMPtr<nsIMsgDBService> msgDBService(do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) + { + listener = iter.GetNext(); + if (listener->m_folderWatching == folder && + listener->m_virtualFolder == virtualFolder) + { + msgDBService->UnregisterPendingListener(listener); + m_virtualFolderListeners.RemoveElement(listener); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::GetAllFolders(nsIArray **aAllFolders) +{ + NS_ENSURE_ARG_POINTER(aAllFolders); + + nsCOMPtr<nsIArray> servers; + nsresult rv = GetAllServers(getter_AddRefs(servers)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numServers = 0; + rv = servers->GetLength(&numServers); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> allFolders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i; + for (i = 0; i < numServers; i++) + { + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(servers, i); + if (server) + { + nsCOMPtr<nsIMsgFolder> rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) + rootFolder->ListDescendants(allFolders); + } + } + + allFolders.forget(aAllFolders); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item); + // just kick out with a success code if the item in question is not a folder + if (!folder) + return NS_OK; + + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + bool addToSmartFolders = false; + folder->IsSpecialFolder(nsMsgFolderFlags::Inbox | + nsMsgFolderFlags::Templates | + nsMsgFolderFlags::Trash | + nsMsgFolderFlags::Drafts, false, + &addToSmartFolders); + // For Sent/Archives/Trash, we treat sub-folders of those folders as + // "special", and want to add them the smart folders search scope. + // So we check if this is a sub-folder of one of those special folders + // and set the corresponding folderFlag if so. + if (!addToSmartFolders) + { + bool isSpecial = false; + folder->IsSpecialFolder(nsMsgFolderFlags::SentMail, true, &isSpecial); + if (isSpecial) + { + addToSmartFolders = true; + folderFlags |= nsMsgFolderFlags::SentMail; + } + folder->IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isSpecial); + if (isSpecial) + { + addToSmartFolders = true; + folderFlags |= nsMsgFolderFlags::Archive; + } + folder->IsSpecialFolder(nsMsgFolderFlags::Trash, true, &isSpecial); + if (isSpecial) + { + addToSmartFolders = true; + folderFlags |= nsMsgFolderFlags::Trash; + } + } + nsresult rv = NS_OK; + // if this is a special folder, check if we have a saved search over + // folders with this flag, and if so, add this folder to the scope. + if (addToSmartFolders) + { + // quick way to enumerate the saved searches. + nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) + { + listener = iter.GetNext(); + nsCOMPtr <nsIMsgDatabase> db; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + listener->m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) + { + uint32_t vfFolderFlag; + dbFolderInfo->GetUint32Property("searchFolderFlag", 0, & vfFolderFlag); + // found a saved search over folders w/ the same flag as the new folder. + if (vfFolderFlag & folderFlags) + { + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + + // "normalize" searchURI so we can search for |folderURI|. + if (!searchURI.IsEmpty()) + { + searchURI.Insert('|', 0); + searchURI.Append('|'); + } + nsCString folderURI; + folder->GetURI(folderURI); + + int32_t index = searchURI.Find(folderURI); + if (index == kNotFound) + { + searchURI.Cut(0, 1); + searchURI.Append(folderURI); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + break; + } + // New sent or archive folder, need to add sub-folders to smart folder. + if (vfFolderFlag & (nsMsgFolderFlags::Archive | nsMsgFolderFlags::SentMail)) + { + nsCOMPtr<nsIArray> allDescendants; + rv = folder->GetDescendants(getter_AddRefs(allDescendants)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t cnt = 0; + rv = allDescendants->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> parent; + for (uint32_t j = 0; j < cnt; j++) + { + nsCOMPtr<nsIMsgFolder> subFolder = do_QueryElementAt(allDescendants, j); + if (subFolder) + { + subFolder->GetParent(getter_AddRefs(parent)); + OnItemAdded(parent, subFolder); + } + } + } + } + } + } + } + // need to make sure this isn't happening during loading of virtualfolders.dat + if (folderFlags & nsMsgFolderFlags::Virtual && !m_loadingVirtualFolders) + { + // When a new virtual folder is added, need to create a db Listener for it. + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + if (msgDBService) + { + nsCOMPtr <nsIMsgDatabase> virtDatabase; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString srchFolderUri; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri); + nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); + AddVFListenersForVF(folder, srchFolderUri, rdf, msgDBService); + } + rv = SaveVirtualFolders(); + } + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item) +{ + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(item); + // just kick out with a success code if the item in question is not a folder + if (!folder) + return NS_OK; + nsresult rv = NS_OK; + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + if (folderFlags & nsMsgFolderFlags::Virtual) // if we removed a VF, flush VF list to disk. + { + rv = SaveVirtualFolders(); + // clear flags on deleted folder if it's a virtual folder, so that creating a new folder + // with the same name doesn't cause confusion. + folder->SetFlags(0); + return rv; + } + // need to update the saved searches to check for a few things: + // 1. Folder removed was in the scope of a saved search - if so, remove the + // uri from the scope of the saved search. + // 2. If the scope is now empty, remove the saved search. + + // build a "normalized" uri that we can do a find on. + nsCString removedFolderURI; + folder->GetURI(removedFolderURI); + removedFolderURI.Insert('|', 0); + removedFolderURI.Append('|'); + + // Enumerate the saved searches. + nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) + { + listener = iter.GetNext(); + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr<nsIMsgFolder> savedSearch = listener->m_virtualFolder; + savedSearch->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) + { + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + // "normalize" searchURI so we can search for |folderURI|. + searchURI.Insert('|', 0); + searchURI.Append('|'); + int32_t index = searchURI.Find(removedFolderURI); + if (index != kNotFound) + { + RemoveVFListenerForVF(savedSearch, folder); + + // remove |folderURI + searchURI.Cut(index, removedFolderURI.Length() - 1); + // remove last '|' we added + searchURI.SetLength(searchURI.Length() - 1); + + // if saved search is empty now, delete it. + if (searchURI.IsEmpty()) + { + db = nullptr; + dbFolderInfo = nullptr; + + nsCOMPtr<nsIMsgFolder> parent; + rv = savedSearch->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!parent) + continue; + parent->PropagateDelete(savedSearch, true, nullptr); + } + else + { + // remove leading '|' we added (or one after |folderURI, if first URI) + searchURI.Cut(0, 1); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + } + } + } + } + + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgAccountManager::OnItemIntPropertyChanged(nsIMsgFolder *aFolder, + nsIAtom *aProperty, + int64_t oldValue, + int64_t newValue) +{ + if (aProperty == mFolderFlagAtom) + { + uint32_t smartFlagsChanged = (oldValue ^ newValue) & + (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Queue); + if (smartFlagsChanged) + { + if (smartFlagsChanged & newValue) + { + // if the smart folder flag was set, calling OnItemAdded will + // do the right thing. + nsCOMPtr<nsIMsgFolder> parent; + aFolder->GetParent(getter_AddRefs(parent)); + return OnItemAdded(parent, aFolder); + } + RemoveFolderFromSmartFolder(aFolder, smartFlagsChanged); + // sent|archive flag removed, remove sub-folders from smart folder. + if (smartFlagsChanged & (nsMsgFolderFlags::Archive | nsMsgFolderFlags::SentMail)) + { + nsCOMPtr<nsIArray> allDescendants; + nsresult rv = aFolder->GetDescendants(getter_AddRefs(allDescendants)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t cnt = 0; + rv = allDescendants->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> parent; + for (uint32_t j = 0; j < cnt; j++) + { + nsCOMPtr<nsIMsgFolder> subFolder = do_QueryElementAt(allDescendants, j); + if (subFolder) + RemoveFolderFromSmartFolder(subFolder, smartFlagsChanged); + } + } + } + } + return NS_OK; +} + +nsresult +nsMsgAccountManager::RemoveFolderFromSmartFolder(nsIMsgFolder *aFolder, + uint32_t flagsChanged) +{ + nsCString removedFolderURI; + aFolder->GetURI(removedFolderURI); + removedFolderURI.Insert('|', 0); + removedFolderURI.Append('|'); + uint32_t flags; + aFolder->GetFlags(&flags); + NS_ASSERTION(!(flags & flagsChanged), "smart folder flag should not be set"); + // Flag was removed. Look for smart folder based on that flag, + // and remove this folder from its scope. + nsTObserverArray<RefPtr<VirtualFolderChangeListener> >::ForwardIterator iter(m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) + { + listener = iter.GetNext(); + nsCOMPtr <nsIMsgDatabase> db; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + listener->m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) + { + uint32_t vfFolderFlag; + dbFolderInfo->GetUint32Property("searchFolderFlag", 0, & vfFolderFlag); + // found a smart folder over the removed flag + if (vfFolderFlag & flagsChanged) + { + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + // "normalize" searchURI so we can search for |folderURI|. + searchURI.Insert('|', 0); + searchURI.Append('|'); + int32_t index = searchURI.Find(removedFolderURI); + if (index != kNotFound) + { + RemoveVFListenerForVF(listener->m_virtualFolder, aFolder); + + // remove |folderURI + searchURI.Cut(index, removedFolderURI.Length() - 1); + // remove last '|' we added + searchURI.SetLength(searchURI.Length() - 1); + + // remove leading '|' we added (or one after |folderURI, if first URI) + searchURI.Cut(0, 1); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgAccountManager::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP nsMsgAccountManager::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgAccountManager::OnItemEvent(nsIMsgFolder *aFolder, nsIAtom *aEvent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgAccountManager::FolderUriForPath(nsIFile *aLocalPath, + nsACString &aMailboxUri) +{ + NS_ENSURE_ARG_POINTER(aLocalPath); + bool equals; + if (m_lastPathLookedUp && + NS_SUCCEEDED(aLocalPath->Equals(m_lastPathLookedUp, &equals)) && equals) + { + aMailboxUri = m_lastFolderURIForPath; + return NS_OK; + } + nsCOMPtr<nsIArray> folderArray; + nsresult rv = GetAllFolders(getter_AddRefs(folderArray)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + rv = folderArray->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(folderArray, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> folderPath; + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if we're equal + rv = folderPath->Equals(aLocalPath, &equals); + NS_ENSURE_SUCCESS(rv, rv); + + if (equals) + { + rv = folder->GetURI(aMailboxUri); + m_lastFolderURIForPath = aMailboxUri; + aLocalPath->Clone(getter_AddRefs(m_lastPathLookedUp)); + return rv; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetSortOrder(nsIMsgIncomingServer* aServer, int32_t* aSortOrder) +{ + NS_ENSURE_ARG_POINTER(aServer); + NS_ENSURE_ARG_POINTER(aSortOrder); + + // If the passed in server is the default, return its sort order as 0 regardless + // of its server sort order. + + nsCOMPtr<nsIMsgAccount> defaultAccount; + nsresult rv = GetDefaultAccount(getter_AddRefs(defaultAccount)); + if (NS_SUCCEEDED(rv) && defaultAccount) { + nsCOMPtr<nsIMsgIncomingServer> defaultServer; + rv = m_defaultAccount->GetIncomingServer(getter_AddRefs(defaultServer)); + if (NS_SUCCEEDED(rv) && defaultServer && (aServer == defaultServer)) { + *aSortOrder = 0; + return NS_OK; + } + // It is OK if there is no default account. + } + + // This function returns the sort order by querying the server object for its + // sort order value and then incrementing it by the position of the server in + // the accounts list. This ensures that even when several accounts have the + // same sort order value, the returned value is not the same and keeps + // their relative order in the account list when and unstable sort is run + // on the returned sort order values. + int32_t sortOrder; + int32_t serverIndex; + + rv = aServer->GetSortOrder(&sortOrder); + if (NS_SUCCEEDED(rv)) + rv = FindServerIndex(aServer, &serverIndex); + + if (NS_FAILED(rv)) { + *aSortOrder = 999999999; + } else { + *aSortOrder = sortOrder + serverIndex; + } + + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgAccountManager.h b/mailnews/base/src/nsMsgAccountManager.h new file mode 100644 index 0000000000..d5a116e577 --- /dev/null +++ b/mailnews/base/src/nsMsgAccountManager.h @@ -0,0 +1,216 @@ +/* -*- 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/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#include "nscore.h" +#include "nsIMsgAccountManager.h" +#include "nsCOMPtr.h" +#include "nsISmtpServer.h" +#include "nsIPrefBranch.h" +#include "nsIMsgFolderCache.h" +#include "nsIMsgFolder.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsIUrlListener.h" +#include "nsCOMArray.h" +#include "nsIMsgSearchSession.h" +#include "nsInterfaceHashtable.h" +#include "nsIMsgDatabase.h" +#include "nsIDBChangeListener.h" +#include "nsAutoPtr.h" +#include "nsTObserverArray.h" + +class nsIRDFService; + +class VirtualFolderChangeListener final : public nsIDBChangeListener +{ +public: + VirtualFolderChangeListener(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDBCHANGELISTENER + + nsresult Init(); + /** + * Posts an event to update the summary totals and commit the db. + * We post the event to avoid committing each time we're called + * in a synchronous loop. + */ + nsresult PostUpdateEvent(nsIMsgFolder *folder, nsIMsgDatabase *db); + /// Handles event posted to event queue to batch notifications. + void ProcessUpdateEvent(nsIMsgFolder *folder, nsIMsgDatabase *db); + + void DecrementNewMsgCount(); + + nsCOMPtr <nsIMsgFolder> m_virtualFolder; // folder we're listening to db changes on behalf of. + nsCOMPtr <nsIMsgFolder> m_folderWatching; // folder whose db we're listening to. + nsCOMPtr <nsIMsgSearchSession> m_searchSession; + bool m_searchOnMsgStatus; + bool m_batchingEvents; + +private: + ~VirtualFolderChangeListener() {} +}; + + +class nsMsgAccountManager: public nsIMsgAccountManager, + public nsIObserver, + public nsSupportsWeakReference, + public nsIUrlListener, + public nsIFolderListener +{ +public: + + nsMsgAccountManager(); + + NS_DECL_THREADSAFE_ISUPPORTS + + /* nsIMsgAccountManager methods */ + + NS_DECL_NSIMSGACCOUNTMANAGER + NS_DECL_NSIOBSERVER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIFOLDERLISTENER + + nsresult Init(); + nsresult Shutdown(); + void LogoutOfServer(nsIMsgIncomingServer *aServer); + +private: + virtual ~nsMsgAccountManager(); + + bool m_accountsLoaded; + nsCOMPtr <nsIMsgFolderCache> m_msgFolderCache; + nsCOMPtr<nsIAtom> kDefaultServerAtom; + nsCOMPtr<nsIAtom> mFolderFlagAtom; + nsTArray<nsCOMPtr<nsIMsgAccount> > m_accounts; + nsInterfaceHashtable<nsCStringHashKey, nsIMsgIdentity> m_identities; + nsInterfaceHashtable<nsCStringHashKey, nsIMsgIncomingServer> m_incomingServers; + nsCOMPtr<nsIMsgAccount> m_defaultAccount; + nsCOMArray<nsIIncomingServerListener> m_incomingServerListeners; + nsTObserverArray<RefPtr<VirtualFolderChangeListener> > m_virtualFolderListeners; + nsCOMPtr<nsIMsgFolder> m_folderDoingEmptyTrash; + nsCOMPtr<nsIMsgFolder> m_folderDoingCleanupInbox; + bool m_emptyTrashInProgress; + bool m_cleanupInboxInProgress; + + nsCString mAccountKeyList; + + // These are static because the account manager may go away during + // shutdown, and get recreated. + static bool m_haveShutdown; + static bool m_shutdownInProgress; + + bool m_userAuthenticated; + bool m_loadingVirtualFolders; + bool m_virtualFoldersLoaded; + + /* we call FindServer() a lot. so cache the last server found */ + nsCOMPtr <nsIMsgIncomingServer> m_lastFindServerResult; + nsCString m_lastFindServerHostName; + nsCString m_lastFindServerUserName; + int32_t m_lastFindServerPort; + nsCString m_lastFindServerType; + + void SetLastServerFound(nsIMsgIncomingServer *server, const nsACString& hostname, + const nsACString& username, const int32_t port, const nsACString& type); + + // Cache the results of the last call to FolderUriFromDirInProfile + nsCOMPtr<nsIFile> m_lastPathLookedUp; + nsCString m_lastFolderURIForPath; + + /* internal creation routines - updates m_identities and m_incomingServers */ + nsresult createKeyedAccount(const nsCString& key, + nsIMsgAccount **_retval); + nsresult createKeyedServer(const nsACString& key, + const nsACString& username, + const nsACString& password, + const nsACString& type, + nsIMsgIncomingServer **_retval); + + nsresult createKeyedIdentity(const nsACString& key, + nsIMsgIdentity **_retval); + + nsresult GetLocalFoldersPrettyName(nsString &localFoldersName); + + // sets the pref for the default server + nsresult setDefaultAccountPref(nsIMsgAccount *aDefaultAccount); + + // Write out the accounts pref from the m_accounts list of accounts. + nsresult OutputAccountsPref(); + + // fires notifications to the appropriate root folders + nsresult notifyDefaultServerChange(nsIMsgAccount *aOldAccount, + nsIMsgAccount *aNewAccount); + + // + // account enumerators + // ("element" is always an account) + // + + // find the servers that correspond to the given identity + static bool findServersForIdentity (nsISupports *element, void *aData); + + void findAccountByServerKey(const nsCString &aKey, + nsIMsgAccount **aResult); + + // + // server enumerators + // ("element" is always a server) + // + + nsresult findServerInternal(const nsACString& username, + const nsACString& hostname, + const nsACString& type, + int32_t port, + bool aRealFlag, + nsIMsgIncomingServer** aResult); + + // handle virtual folders + static nsresult GetVirtualFoldersFile(nsCOMPtr<nsIFile>& file); + static nsresult WriteLineToOutputStream(const char *prefix, const char * line, nsIOutputStream *outputStream); + void ParseAndVerifyVirtualFolderScope(nsCString &buffer, + nsIRDFService *rdf); + nsresult AddVFListenersForVF(nsIMsgFolder *virtualFolder, + const nsCString& srchFolderUris, + nsIRDFService *rdf, + nsIMsgDBService *msgDBService); + + nsresult RemoveVFListenerForVF(nsIMsgFolder *virtualFolder, + nsIMsgFolder *folder); + + void getUniqueAccountKey(nsCString& aResult); + + // Scan the preferences to find a unique server key + void GetUniqueServerKey(nsACString& aResult); + + nsresult RemoveFolderFromSmartFolder(nsIMsgFolder *aFolder, + uint32_t flagsChanged); + + nsresult SetSendLaterUriPref(nsIMsgIncomingServer *server); + + nsCOMPtr<nsIPrefBranch> m_prefs; + + // + // root folder listener stuff + // + + // this array is for folder listeners that are supposed to be listening + // on the root folders. + // When a new server is created, all of the the folder listeners + // should be added to the new server + // When a new listener is added, it should be added to all root folders. + // similar for when servers are deleted or listeners removed + nsTObserverArray<nsCOMPtr<nsIFolderListener> > mFolderListeners; + + void removeListenersFromFolder(nsIMsgFolder *aFolder); +}; diff --git a/mailnews/base/src/nsMsgAccountManagerDS.cpp b/mailnews/base/src/nsMsgAccountManagerDS.cpp new file mode 100644 index 0000000000..728aaabce1 --- /dev/null +++ b/mailnews/base/src/nsMsgAccountManagerDS.cpp @@ -0,0 +1,1183 @@ +/* -*- 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/. */ + +/* + * RDF datasource for the account manager + */ + +#include "nsMsgAccountManagerDS.h" +#include "rdf.h" +#include "nsRDFCID.h" +#include "nsIRDFDataSource.h" +#include "nsEnumeratorUtils.h" +#include "nsIServiceManager.h" +#include "nsMsgRDFUtils.h" +#include "nsIMsgFolder.h" +#include "nsMsgBaseCID.h" + +#include "nsICategoryManager.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsArrayEnumerator.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" +#include "nsArrayUtils.h" + +// turn this on to see useful output +#undef DEBUG_amds + +#define NC_RDF_PAGETITLE_PREFIX NC_NAMESPACE_URI "PageTitle" +#define NC_RDF_PAGETITLE_MAIN NC_RDF_PAGETITLE_PREFIX "Main" +#define NC_RDF_PAGETITLE_SERVER NC_RDF_PAGETITLE_PREFIX "Server" +#define NC_RDF_PAGETITLE_COPIES NC_RDF_PAGETITLE_PREFIX "Copies" +#define NC_RDF_PAGETITLE_SYNCHRONIZATION NC_RDF_PAGETITLE_PREFIX "Synchronization" +#define NC_RDF_PAGETITLE_DISKSPACE NC_RDF_PAGETITLE_PREFIX "DiskSpace" +#define NC_RDF_PAGETITLE_ADDRESSING NC_RDF_PAGETITLE_PREFIX "Addressing" +#define NC_RDF_PAGETITLE_SMTP NC_RDF_PAGETITLE_PREFIX "SMTP" +#define NC_RDF_PAGETITLE_JUNK NC_RDF_PAGETITLE_PREFIX "Junk" +#define NC_RDF_PAGETAG NC_NAMESPACE_URI "PageTag" + + +#define NC_RDF_ACCOUNTROOT "msgaccounts:/" + +// the root resource (msgaccounts:/) +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_AccountRoot=nullptr; + +// attributes of accounts +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Name=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_FolderTreeName=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_FolderTreeSimpleName=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_NameSort=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_FolderTreeNameSort=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTag=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_IsDefaultServer=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_SupportsFilters=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_CanGetMessages=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_CanGetIncomingMessages=nullptr; + +// containment +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Child=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Settings=nullptr; + + +// properties corresponding to interfaces +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Account=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Server=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Identity=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_Junk=nullptr; + +// individual pages +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleMain=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleServer=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleCopies=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleSynchronization=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleDiskSpace=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleAddressing=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleSMTP=nullptr; +nsIRDFResource* nsMsgAccountManagerDataSource::kNC_PageTitleJunk=nullptr; + +// common literals +nsIRDFLiteral* nsMsgAccountManagerDataSource::kTrueLiteral = nullptr; + +nsIAtom* nsMsgAccountManagerDataSource::kDefaultServerAtom = nullptr; + +nsrefcnt nsMsgAccountManagerDataSource::gAccountManagerResourceRefCnt = 0; + +// shared arc lists +nsCOMPtr<nsIMutableArray> nsMsgAccountManagerDataSource::mAccountArcsOut; +nsCOMPtr<nsIMutableArray> nsMsgAccountManagerDataSource::mAccountRootArcsOut; + + +// RDF to match +#define NC_RDF_ACCOUNT NC_NAMESPACE_URI "Account" +#define NC_RDF_SERVER NC_NAMESPACE_URI "Server" +#define NC_RDF_IDENTITY NC_NAMESPACE_URI "Identity" +#define NC_RDF_SETTINGS NC_NAMESPACE_URI "Settings" +#define NC_RDF_JUNK NC_NAMESPACE_URI "Junk" +#define NC_RDF_ISDEFAULTSERVER NC_NAMESPACE_URI "IsDefaultServer" +#define NC_RDF_SUPPORTSFILTERS NC_NAMESPACE_URI "SupportsFilters" +#define NC_RDF_CANGETMESSAGES NC_NAMESPACE_URI "CanGetMessages" +#define NC_RDF_CANGETINCOMINGMESSAGES NC_NAMESPACE_URI "CanGetIncomingMessages" + +nsMsgAccountManagerDataSource::nsMsgAccountManagerDataSource() +{ +#ifdef DEBUG_amds + printf("nsMsgAccountManagerDataSource() being created\n"); +#endif + + // do per-class initialization here + if (gAccountManagerResourceRefCnt++ == 0) { + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_CHILD), &kNC_Child); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME), &kNC_Name); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME), + &kNC_FolderTreeName); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREESIMPLENAME), + &kNC_FolderTreeSimpleName); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME_SORT), &kNC_NameSort); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME_SORT), + &kNC_FolderTreeNameSort); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETAG), &kNC_PageTag); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISDEFAULTSERVER), + &kNC_IsDefaultServer); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_SUPPORTSFILTERS), + &kNC_SupportsFilters); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANGETMESSAGES), + &kNC_CanGetMessages); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANGETINCOMINGMESSAGES), + &kNC_CanGetIncomingMessages); + + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_ACCOUNT), &kNC_Account); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_SERVER), &kNC_Server); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_IDENTITY), &kNC_Identity); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_JUNK), &kNC_Junk); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_MAIN), + &kNC_PageTitleMain); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_SERVER), + &kNC_PageTitleServer); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_COPIES), + &kNC_PageTitleCopies); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_SYNCHRONIZATION), + &kNC_PageTitleSynchronization); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_DISKSPACE), + &kNC_PageTitleDiskSpace); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_ADDRESSING), + &kNC_PageTitleAddressing); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_SMTP), + &kNC_PageTitleSMTP); + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_PAGETITLE_JUNK), + &kNC_PageTitleJunk); + + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_ACCOUNTROOT), + &kNC_AccountRoot); + + getRDFService()->GetLiteral(u"true", + &kTrueLiteral); + + // eventually these need to exist in some kind of array + // that's easily extensible + getRDFService()->GetResource(NS_LITERAL_CSTRING(NC_RDF_SETTINGS), &kNC_Settings); + + kDefaultServerAtom = MsgNewAtom("DefaultServer").take(); + } +} + +nsMsgAccountManagerDataSource::~nsMsgAccountManagerDataSource() +{ + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager); + if (am) + am->RemoveIncomingServerListener(this); + + if (--gAccountManagerResourceRefCnt == 0) { + NS_IF_RELEASE(kNC_Child); + NS_IF_RELEASE(kNC_Name); + NS_IF_RELEASE(kNC_FolderTreeName); + NS_IF_RELEASE(kNC_FolderTreeSimpleName); + NS_IF_RELEASE(kNC_NameSort); + NS_IF_RELEASE(kNC_FolderTreeNameSort); + NS_IF_RELEASE(kNC_PageTag); + NS_IF_RELEASE(kNC_IsDefaultServer); + NS_IF_RELEASE(kNC_SupportsFilters); + NS_IF_RELEASE(kNC_CanGetMessages); + NS_IF_RELEASE(kNC_CanGetIncomingMessages); + NS_IF_RELEASE(kNC_Account); + NS_IF_RELEASE(kNC_Server); + NS_IF_RELEASE(kNC_Identity); + NS_IF_RELEASE(kNC_Junk); + NS_IF_RELEASE(kNC_PageTitleMain); + NS_IF_RELEASE(kNC_PageTitleServer); + NS_IF_RELEASE(kNC_PageTitleCopies); + NS_IF_RELEASE(kNC_PageTitleSynchronization); + NS_IF_RELEASE(kNC_PageTitleDiskSpace); + NS_IF_RELEASE(kNC_PageTitleAddressing); + NS_IF_RELEASE(kNC_PageTitleSMTP); + NS_IF_RELEASE(kNC_PageTitleJunk); + NS_IF_RELEASE(kTrueLiteral); + + NS_IF_RELEASE(kNC_AccountRoot); + + // eventually these need to exist in some kind of array + // that's easily extensible + NS_IF_RELEASE(kNC_Settings); + + + NS_IF_RELEASE(kDefaultServerAtom); + mAccountArcsOut = nullptr; + mAccountRootArcsOut = nullptr; + } + +} + +NS_IMPL_ADDREF_INHERITED(nsMsgAccountManagerDataSource, nsMsgRDFDataSource) +NS_IMPL_RELEASE_INHERITED(nsMsgAccountManagerDataSource, nsMsgRDFDataSource) +NS_INTERFACE_MAP_BEGIN(nsMsgAccountManagerDataSource) + NS_INTERFACE_MAP_ENTRY(nsIIncomingServerListener) + NS_INTERFACE_MAP_ENTRY(nsIFolderListener) +NS_INTERFACE_MAP_END_INHERITING(nsMsgRDFDataSource) + +nsresult +nsMsgAccountManagerDataSource::Init() +{ + nsresult rv; + + rv = nsMsgRDFDataSource::Init(); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMsgAccountManager> am; + + // get a weak ref to the account manager + if (!mAccountManager) + { + am = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + mAccountManager = do_GetWeakReference(am); + } + else + am = do_QueryReferent(mAccountManager); + + if (am) + { + am->AddIncomingServerListener(this); + am->AddRootFolderListener(this); + } + + + return NS_OK; +} + +void nsMsgAccountManagerDataSource::Cleanup() +{ + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager); + + if (am) + { + am->RemoveIncomingServerListener(this); + am->RemoveRootFolderListener(this); + } + + nsMsgRDFDataSource::Cleanup(); +} + +/* nsIRDFNode GetTarget (in nsIRDFResource aSource, in nsIRDFResource property, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgAccountManagerDataSource::GetTarget(nsIRDFResource *source, + nsIRDFResource *property, + bool aTruthValue, + nsIRDFNode **target) +{ + nsresult rv; + + + rv = NS_RDF_NO_VALUE; + + nsAutoString str; + if (property == kNC_Name || property == kNC_FolderTreeName || + property == kNC_FolderTreeSimpleName) + { + rv = getStringBundle(); + NS_ENSURE_SUCCESS(rv, rv); + + nsString pageTitle; + if (source == kNC_PageTitleServer) + mStringBundle->GetStringFromName(u"prefPanel-server", + getter_Copies(pageTitle)); + + else if (source == kNC_PageTitleCopies) + mStringBundle->GetStringFromName(u"prefPanel-copies", + getter_Copies(pageTitle)); + else if (source == kNC_PageTitleSynchronization) + mStringBundle->GetStringFromName(u"prefPanel-synchronization", + getter_Copies(pageTitle)); + else if (source == kNC_PageTitleDiskSpace) + mStringBundle->GetStringFromName(u"prefPanel-diskspace", + getter_Copies(pageTitle)); + else if (source == kNC_PageTitleAddressing) + mStringBundle->GetStringFromName(u"prefPanel-addressing", + getter_Copies(pageTitle)); + else if (source == kNC_PageTitleSMTP) + mStringBundle->GetStringFromName(u"prefPanel-smtp", + getter_Copies(pageTitle)); + else if (source == kNC_PageTitleJunk) + mStringBundle->GetStringFromName(u"prefPanel-junk", + getter_Copies(pageTitle)); + + else { + // if it's a server, use the pretty name + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(source, &rv); + if (NS_SUCCEEDED(rv) && folder) { + bool isServer; + rv = folder->GetIsServer(&isServer); + if (NS_SUCCEEDED(rv) && isServer) + rv = folder->GetPrettyName(pageTitle); + } + else { + // allow for the accountmanager to be dynamically extended. + + nsCOMPtr<nsIStringBundleService> strBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(strBundleService, NS_ERROR_UNEXPECTED); + + const char *sourceValue; + rv = source->GetValueConst(&sourceValue); + NS_ENSURE_SUCCESS(rv,rv); + + // make sure the pointer math we're about to do is safe. + NS_ENSURE_TRUE(sourceValue && (strlen(sourceValue) > strlen(NC_RDF_PAGETITLE_PREFIX)), NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // turn NC#PageTitlefoobar into foobar, so we can get the am-foobar.properties bundle + nsCString chromePackageName; + rv = am->GetChromePackageName(nsCString(sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX)), chromePackageName); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString bundleURL; + bundleURL = "chrome://"; + bundleURL += chromePackageName; + bundleURL += "/locale/am-"; + bundleURL += (sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX)); + bundleURL += ".properties"; + + nsCOMPtr <nsIStringBundle> bundle; + rv = strBundleService->CreateBundle(bundleURL.get(), getter_AddRefs(bundle)); + + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoString panelTitleName; + panelTitleName.AssignLiteral("prefPanel-"); + panelTitleName.Append(NS_ConvertASCIItoUTF16(sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX))); + bundle->GetStringFromName(panelTitleName.get(), getter_Copies(pageTitle)); + } + } + str = pageTitle.get(); + } + else if (property == kNC_PageTag) { + // do NOT localize these strings. these are the urls of the XUL files + if (source == kNC_PageTitleServer) + str.AssignLiteral("am-server.xul"); + else if (source == kNC_PageTitleCopies) + str.AssignLiteral("am-copies.xul"); + else if ((source == kNC_PageTitleSynchronization) || + (source == kNC_PageTitleDiskSpace)) + str.AssignLiteral("am-offline.xul"); + else if (source == kNC_PageTitleAddressing) + str.AssignLiteral("am-addressing.xul"); + else if (source == kNC_PageTitleSMTP) + str.AssignLiteral("am-smtp.xul"); + else if (source == kNC_PageTitleJunk) + str.AssignLiteral("am-junk.xul"); + else { + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(source, &rv); + if (NS_SUCCEEDED(rv) && folder) { + /* if this is a server, with no identities, then we show a special panel */ + nsCOMPtr<nsIMsgIncomingServer> server; + rv = getServerForFolderNode(source, getter_AddRefs(server)); + if (server) + server->GetAccountManagerChrome(str); + else + str.AssignLiteral("am-main.xul"); + } + else { + // allow for the accountmanager to be dynamically extended + const char *sourceValue; + rv = source->GetValueConst(&sourceValue); + NS_ENSURE_SUCCESS(rv,rv); + + // make sure the pointer math we're about to do is safe. + NS_ENSURE_TRUE(sourceValue && (strlen(sourceValue) > strlen(NC_RDF_PAGETITLE_PREFIX)), NS_ERROR_UNEXPECTED); + + // turn NC#PageTitlefoobar into foobar, so we can get the am-foobar.xul file + str.AssignLiteral("am-"); + str.Append(NS_ConvertASCIItoUTF16(sourceValue + strlen(NC_RDF_PAGETITLE_PREFIX))); + str.AppendLiteral(".xul"); + } + } + } + + // handle sorting of servers + else if ((property == kNC_NameSort) || + (property == kNC_FolderTreeNameSort)) { + + // order for the folder pane + // and for the account manager tree is: + // + // - default mail account + // - <other mail accounts> + // - "Local Folders" account + // - news accounts + // - smtp settings (note, this is only in account manager tree) + + // make sure we're handling a root folder that is a server + nsCOMPtr<nsIMsgIncomingServer> server; + rv = getServerForFolderNode(source, getter_AddRefs(server)); + + if (NS_SUCCEEDED(rv) && server) { + int32_t accountNum; + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager); + + if (isDefaultServer(server)) + str.AssignLiteral("000000000"); + else { + rv = am->GetSortOrder(server, &accountNum); + NS_ENSURE_SUCCESS(rv, rv); + str.AppendInt(accountNum); + } + } + else { + const char *sourceValue; + rv = source->GetValueConst(&sourceValue); + NS_ENSURE_SUCCESS(rv, NS_RDF_NO_VALUE); + + // if this is a page (which we determine by the prefix of the URI) + // we want to generate a sort value + // so that we can sort the categories in the account manager tree + // (or the folder pane) + // + // otherwise, return NS_RDF_NO_VALUE + // so that the folder data source will take care of it. + if (sourceValue && (strncmp(sourceValue, NC_RDF_PAGETITLE_PREFIX, strlen(NC_RDF_PAGETITLE_PREFIX)) == 0)) { + if (source == kNC_PageTitleSMTP) + str.AssignLiteral("900000000"); + else if (source == kNC_PageTitleServer) + str.AssignLiteral("1"); + else if (source == kNC_PageTitleCopies) + str.AssignLiteral("2"); + else if (source == kNC_PageTitleAddressing) + str.AssignLiteral("3"); + else if (source == kNC_PageTitleSynchronization) + str.AssignLiteral("4"); + else if (source == kNC_PageTitleDiskSpace) + str.AssignLiteral("4"); + else if (source == kNC_PageTitleJunk) + str.AssignLiteral("5"); + else { + // allow for the accountmanager to be dynamically extended + // all the other pages come after the standard ones + // server, copies, addressing, disk space (or offline & disk space) + CopyASCIItoUTF16(nsDependentCString(sourceValue), str); + } + } + else { + return NS_RDF_NO_VALUE; + } + } + } + + // GetTargets() stuff - need to return a valid answer so that + // twisties will appear + else if (property == kNC_Settings) { + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(source,&rv); + if (NS_FAILED(rv)) + return NS_RDF_NO_VALUE; + + bool isServer=false; + folder->GetIsServer(&isServer); + // no need to localize this! + if (isServer) + str.AssignLiteral("ServerSettings"); + } + + else if (property == kNC_IsDefaultServer) { + nsCOMPtr<nsIMsgIncomingServer> thisServer; + rv = getServerForFolderNode(source, getter_AddRefs(thisServer)); + if (NS_FAILED(rv) || !thisServer) return NS_RDF_NO_VALUE; + + if (isDefaultServer(thisServer)) + str.AssignLiteral("true"); + } + + else if (property == kNC_SupportsFilters) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = getServerForFolderNode(source, getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_RDF_NO_VALUE; + + if (supportsFilters(server)) + str.AssignLiteral("true"); + } + else if (property == kNC_CanGetMessages) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = getServerForFolderNode(source, getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_RDF_NO_VALUE; + + if (canGetMessages(server)) + str.AssignLiteral("true"); + } + else if (property == kNC_CanGetIncomingMessages) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = getServerForFolderNode(source, getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_RDF_NO_VALUE; + + if (canGetIncomingMessages(server)) + str.AssignLiteral("true"); + } + + if (!str.IsEmpty()) + rv = createNode(str.get(), target, getRDFService()); + + //if we have an empty string and we don't have an error value, then + //we don't have a value for RDF. + else if(NS_SUCCEEDED(rv)) + rv = NS_RDF_NO_VALUE; + + return rv; +} + + + +/* nsISimpleEnumerator GetTargets (in nsIRDFResource aSource, in nsIRDFResource property, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgAccountManagerDataSource::GetTargets(nsIRDFResource *source, + nsIRDFResource *property, + bool aTruthValue, + nsISimpleEnumerator **_retval) +{ + nsresult rv = NS_RDF_NO_VALUE; + + // create array and enumerator + // even if we're not handling this we need to return something empty? + nsCOMArray<nsIRDFResource> nodes; + if (NS_FAILED(rv)) return rv; + if (source == kNC_AccountRoot) + rv = createRootResources(property, &nodes); + else if (property == kNC_Settings) + rv = createSettingsResources(source, &nodes); + + if (NS_FAILED(rv)) + return NS_RDF_NO_VALUE; + return NS_NewArrayEnumerator(_retval, nodes); +} + +// end of all arcs coming out of msgaccounts:/ +nsresult +nsMsgAccountManagerDataSource::createRootResources(nsIRDFResource *property, + nsCOMArray<nsIRDFResource> *aNodeArray) +{ + nsresult rv = NS_OK; + if (isContainment(property)) { + + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager); + if (!am) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIArray> servers; + rv = am->GetAllServers(getter_AddRefs(servers)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t length; + rv = servers->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < length; ++i) + { + nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(servers, i, &rv)); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr<nsIMsgFolder> serverFolder; + rv = server->GetRootFolder(getter_AddRefs(serverFolder)); + if (NS_FAILED(rv)) + continue; + + // add the resource to the array + nsCOMPtr<nsIRDFResource> serverResource = do_QueryInterface(serverFolder); + if (serverResource) + (void) aNodeArray->AppendObject(serverResource); + } + +#ifdef DEBUG_amds + uint32_t nodecount; + aNodeArray->GetLength(&nodecount); + printf("GetTargets(): added %d servers on %s\n", nodecount, + (const char*)property_arc); +#endif + // For the "settings" arc, we also want to add SMTP setting. + if (property == kNC_Settings) { + aNodeArray->AppendObject(kNC_PageTitleSMTP); + } + } + +#ifdef DEBUG_amds + else { + printf("unknown arc %s on msgaccounts:/\n", (const char*)property_arc); + } +#endif + + return rv; +} + +nsresult +nsMsgAccountManagerDataSource::appendGenericSettingsResources(nsIMsgIncomingServer *server, nsCOMArray<nsIRDFResource> *aNodeArray) +{ + nsresult rv; + + nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsISimpleEnumerator> e; + rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, getter_AddRefs(e)); + if(NS_SUCCEEDED(rv) && e) { + while (true) { + nsCOMPtr<nsISupports> supports; + rv = e->GetNext(getter_AddRefs(supports)); + nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(supports); + if (NS_FAILED(rv) || !catEntry) + break; + + nsAutoCString entryString; + rv = catEntry->GetData(entryString); + if (NS_FAILED(rv)) + break; + + nsCString contractidString; + rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, entryString.get(), getter_Copies(contractidString)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr <nsIMsgAccountManagerExtension> extension = + do_GetService(contractidString.get(), &rv); + if (NS_FAILED(rv) || !extension) + break; + + bool showPanel; + rv = extension->ShowPanel(server, &showPanel); + if (NS_FAILED(rv)) + break; + + if (showPanel) { + nsCString name; + rv = extension->GetName(name); + if (NS_FAILED(rv)) + break; + + rv = appendGenericSetting(name.get(), aNodeArray); + if (NS_FAILED(rv)) + break; + } + } + } + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::appendGenericSetting(const char *name, + nsCOMArray<nsIRDFResource> *aNodeArray) +{ + NS_ENSURE_ARG_POINTER(name); + NS_ENSURE_ARG_POINTER(aNodeArray); + + nsCOMPtr <nsIRDFResource> resource; + + nsAutoCString resourceStr; + resourceStr = NC_RDF_PAGETITLE_PREFIX; + resourceStr += name; + + nsresult rv = getRDFService()->GetResource(resourceStr, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv,rv); + + // AppendElement will addref. + aNodeArray->AppendObject(resource); + return NS_OK; +} + +// end of all #Settings arcs +nsresult +nsMsgAccountManagerDataSource::createSettingsResources(nsIRDFResource *aSource, + nsCOMArray<nsIRDFResource> *aNodeArray) +{ + // If this isn't a server, just return. + if (aSource == kNC_PageTitleSMTP) + return NS_OK; + + nsCOMPtr<nsIMsgIncomingServer> server; + getServerForFolderNode(aSource, getter_AddRefs(server)); + if (server) { + bool hasIdentities; + nsresult rv = serverHasIdentities(server, &hasIdentities); + + if (hasIdentities) { + aNodeArray->AppendObject(kNC_PageTitleServer); + aNodeArray->AppendObject(kNC_PageTitleCopies); + aNodeArray->AppendObject(kNC_PageTitleAddressing); + } + + // Junk settings apply for all server types except for news and rss. + nsCString serverType; + server->GetType(serverType); + if (!MsgLowerCaseEqualsLiteral(serverType, "nntp") && + !MsgLowerCaseEqualsLiteral(serverType, "rss")) + aNodeArray->AppendObject(kNC_PageTitleJunk); + + // Check the offline capability before adding + // offline item + int32_t offlineSupportLevel = 0; + rv = server->GetOfflineSupportLevel(&offlineSupportLevel); + NS_ENSURE_SUCCESS(rv,rv); + + bool supportsDiskSpace; + rv = server->GetSupportsDiskSpace(&supportsDiskSpace); + NS_ENSURE_SUCCESS(rv,rv); + + // currently there is no offline without diskspace + if (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR) + aNodeArray->AppendObject(kNC_PageTitleSynchronization); + else if (supportsDiskSpace) + aNodeArray->AppendObject(kNC_PageTitleDiskSpace); + + if (hasIdentities) { + // extensions come after the default panels + rv = appendGenericSettingsResources(server, aNodeArray); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add generic panels"); + } + } + + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::serverHasIdentities(nsIMsgIncomingServer* aServer, + bool *aResult) +{ + nsresult rv; + *aResult = false; + + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv); + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIArray> identities; + rv = am->GetIdentitiesForServer(aServer, getter_AddRefs(identities)); + + // no identities just means no arcs + if (NS_FAILED(rv)) return NS_OK; + + uint32_t count; + rv = identities->GetLength(&count); + if (NS_FAILED(rv)) return NS_OK; + + if (count >0) + *aResult = true; + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::getAccountArcs(nsIMutableArray **aResult) +{ + nsresult rv; + if (!mAccountArcsOut) { + mAccountArcsOut = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mAccountArcsOut->AppendElement(kNC_Settings, false); + mAccountArcsOut->AppendElement(kNC_Name, false); + mAccountArcsOut->AppendElement(kNC_FolderTreeName, false); + mAccountArcsOut->AppendElement(kNC_FolderTreeSimpleName, false); + mAccountArcsOut->AppendElement(kNC_NameSort, false); + mAccountArcsOut->AppendElement(kNC_FolderTreeNameSort, false); + mAccountArcsOut->AppendElement(kNC_PageTag, false); + } + + *aResult = mAccountArcsOut; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::getAccountRootArcs(nsIMutableArray **aResult) +{ + nsresult rv; + if (!mAccountRootArcsOut) { + mAccountRootArcsOut = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mAccountRootArcsOut->AppendElement(kNC_Server, false); + mAccountRootArcsOut->AppendElement(kNC_Child, false); + + mAccountRootArcsOut->AppendElement(kNC_Settings, false); + mAccountRootArcsOut->AppendElement(kNC_Name, false); + mAccountRootArcsOut->AppendElement(kNC_FolderTreeName, false); + mAccountRootArcsOut->AppendElement(kNC_FolderTreeSimpleName, false); + mAccountRootArcsOut->AppendElement(kNC_NameSort, false); + mAccountRootArcsOut->AppendElement(kNC_FolderTreeNameSort, false); + mAccountRootArcsOut->AppendElement(kNC_PageTag, false); + } + + *aResult = mAccountRootArcsOut; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManagerDataSource::HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc, bool *result) +{ + if (aArc == kNC_Settings) { + // based on createSettingsResources() + // we only have settings for local folders and servers with identities + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = getServerForFolderNode(source, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + // Check the offline capability before adding arc + int32_t offlineSupportLevel = 0; + (void) server->GetOfflineSupportLevel(&offlineSupportLevel); + if (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR) { + *result = true; + return NS_OK; + } + + bool supportsDiskSpace; + (void) server->GetSupportsDiskSpace(&supportsDiskSpace); + if (supportsDiskSpace) { + *result = true; + return NS_OK; + } + return serverHasIdentities(server, result); + } + } + + *result = false; + return NS_OK; +} + +/* nsISimpleEnumerator ArcLabelsOut (in nsIRDFResource aSource); */ +NS_IMETHODIMP +nsMsgAccountManagerDataSource::ArcLabelsOut(nsIRDFResource *source, + nsISimpleEnumerator **_retval) +{ + nsresult rv; + + // we have to return something, so always create the array/enumerators + nsCOMPtr<nsIMutableArray> arcs; + if (source == kNC_AccountRoot) + rv = getAccountRootArcs(getter_AddRefs(arcs)); + else + rv = getAccountArcs(getter_AddRefs(arcs)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewArrayEnumerator(_retval, arcs); + if (NS_FAILED(rv)) return rv; + +#ifdef DEBUG_amds_ + printf("GetArcLabelsOut(%s): Adding child, settings, and name arclabels\n", value); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManagerDataSource::HasAssertion(nsIRDFResource *aSource, + nsIRDFResource *aProperty, + nsIRDFNode *aTarget, + bool aTruthValue, + bool *_retval) +{ + nsresult rv=NS_ERROR_FAILURE; + + // + // msgaccounts:/ properties + // + if (aSource == kNC_AccountRoot) { + rv = HasAssertionAccountRoot(aProperty, aTarget, aTruthValue, _retval); + } + // + // server properties + // try to convert the resource to a folder, and then only + // answer if it's a server.. any failure falls through to the default case + // + // short-circuit on property, so objects like filters, etc, don't get queried + else if (aProperty == kNC_IsDefaultServer || aProperty == kNC_CanGetMessages || + aProperty == kNC_CanGetIncomingMessages || aProperty == kNC_SupportsFilters) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = getServerForFolderNode(aSource, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + rv = HasAssertionServer(server, aProperty, aTarget, + aTruthValue, _retval); + } + + // any failures above fallthrough to the parent class + if (NS_FAILED(rv)) + return nsMsgRDFDataSource::HasAssertion(aSource, aProperty, + aTarget, aTruthValue, _retval); + return NS_OK; +} + + + +nsresult +nsMsgAccountManagerDataSource::HasAssertionServer(nsIMsgIncomingServer *aServer, + nsIRDFResource *aProperty, + nsIRDFNode *aTarget, + bool aTruthValue, + bool *_retval) +{ + if (aProperty == kNC_IsDefaultServer) + *_retval = (aTarget == kTrueLiteral) ? isDefaultServer(aServer) : !isDefaultServer(aServer); + else if (aProperty == kNC_SupportsFilters) + *_retval = (aTarget == kTrueLiteral) ? supportsFilters(aServer) : !supportsFilters(aServer); + else if (aProperty == kNC_CanGetMessages) + *_retval = (aTarget == kTrueLiteral) ? canGetMessages(aServer) : !canGetMessages(aServer); + else if (aProperty == kNC_CanGetIncomingMessages) + *_retval = (aTarget == kTrueLiteral) ? canGetIncomingMessages(aServer) : !canGetIncomingMessages(aServer); + else + *_retval = false; + return NS_OK; +} + +bool +nsMsgAccountManagerDataSource::isDefaultServer(nsIMsgIncomingServer *aServer) +{ + nsresult rv; + if (!aServer) return false; + + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv); + NS_ENSURE_SUCCESS(rv, false); + + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = am->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, false); + if (!defaultAccount) return false; + + // in some weird case that there is no default and they asked + // for the default + nsCOMPtr<nsIMsgIncomingServer> defaultServer; + rv = defaultAccount->GetIncomingServer(getter_AddRefs(defaultServer)); + NS_ENSURE_SUCCESS(rv, false); + if (!defaultServer) return false; + + bool isEqual; + rv = defaultServer->Equals(aServer, &isEqual); + NS_ENSURE_SUCCESS(rv, false); + + return isEqual; +} + + +bool +nsMsgAccountManagerDataSource::supportsFilters(nsIMsgIncomingServer *aServer) +{ + bool supportsFilters; + nsresult rv = aServer->GetCanHaveFilters(&supportsFilters); + NS_ENSURE_SUCCESS(rv, false); + + return supportsFilters; +} + +bool +nsMsgAccountManagerDataSource::canGetMessages(nsIMsgIncomingServer *aServer) +{ + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + nsresult rv = aServer->GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, false); + + bool canGetMessages; + rv = protocolInfo->GetCanGetMessages(&canGetMessages); + NS_ENSURE_SUCCESS(rv, false); + + return canGetMessages; +} + +bool +nsMsgAccountManagerDataSource::canGetIncomingMessages(nsIMsgIncomingServer *aServer) +{ + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + nsresult rv = aServer->GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, false); + + bool canGetIncomingMessages; + rv = protocolInfo->GetCanGetIncomingMessages(&canGetIncomingMessages); + NS_ENSURE_SUCCESS(rv, false); + + return canGetIncomingMessages; +} + +nsresult +nsMsgAccountManagerDataSource::HasAssertionAccountRoot(nsIRDFResource *aProperty, + nsIRDFNode *aTarget, + bool aTruthValue, + bool *_retval) +{ + // set up default + *_retval = false; + + // for child and settings arcs, just make sure it's a valid server: + if (isContainment(aProperty)) { + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = getServerForFolderNode(aTarget, getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return rv; + + nsCString serverKey; + server->GetKey(serverKey); + + nsCOMPtr<nsIMsgAccountManager> am = do_QueryReferent(mAccountManager, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIArray> serverArray; + rv = am->GetAllServers(getter_AddRefs(serverArray)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t length; + rv = serverArray->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < length; ++i) + { + nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(serverArray, i, &rv)); + if (NS_FAILED(rv)) + continue; + + nsCString key; + server->GetKey(key); + if (key.Equals(serverKey)) + { + *_retval = true; + break; + } + } + } + + return NS_OK; +} + +bool +nsMsgAccountManagerDataSource::isContainment(nsIRDFResource *aProperty) +{ + + if (aProperty == kNC_Child || + aProperty == kNC_Settings) + return true; + return false; +} + +// returns failure if the object is not a root server +nsresult +nsMsgAccountManagerDataSource::getServerForFolderNode(nsIRDFNode *aResource, + nsIMsgIncomingServer **aResult) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(aResource, &rv); + if (NS_SUCCEEDED(rv)) + { + bool isServer; + rv = folder->GetIsServer(&isServer); + if (NS_SUCCEEDED(rv) && isServer) + return folder->GetServer(aResult); + } + return NS_ERROR_FAILURE; +} + +nsresult +nsMsgAccountManagerDataSource::getStringBundle() +{ + if (mStringBundle) return NS_OK; + + nsCOMPtr<nsIStringBundleService> strBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(strBundleService, NS_ERROR_UNEXPECTED); + + return strBundleService->CreateBundle("chrome://messenger/locale/prefs.properties", + getter_AddRefs(mStringBundle)); +} + +NS_IMETHODIMP +nsMsgAccountManagerDataSource::OnServerLoaded(nsIMsgIncomingServer* aServer) +{ + nsCOMPtr<nsIMsgFolder> serverFolder; + nsresult rv = aServer->GetRootFolder(getter_AddRefs(serverFolder)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIRDFResource> serverResource = do_QueryInterface(serverFolder,&rv); + if (NS_FAILED(rv)) return rv; + + NotifyObservers(kNC_AccountRoot, kNC_Child, serverResource, nullptr, true, false); + NotifyObservers(kNC_AccountRoot, kNC_Settings, serverResource, nullptr, true, false); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManagerDataSource::OnServerUnloaded(nsIMsgIncomingServer* aServer) +{ + nsCOMPtr<nsIMsgFolder> serverFolder; + nsresult rv = aServer->GetRootFolder(getter_AddRefs(serverFolder)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIRDFResource> serverResource = do_QueryInterface(serverFolder,&rv); + if (NS_FAILED(rv)) return rv; + + + NotifyObservers(kNC_AccountRoot, kNC_Child, serverResource, nullptr, false, false); + NotifyObservers(kNC_AccountRoot, kNC_Settings, serverResource, nullptr, false, false); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManagerDataSource::OnServerChanged(nsIMsgIncomingServer *server) +{ + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *) +{ + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *) +{ + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemRemoved(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemPropertyFlagChanged(nsIMsgDBHdr *, nsIAtom *, uint32_t, uint32_t) +{ + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemAdded(nsIMsgFolder *, nsISupports *) +{ + return NS_OK; +} + + +nsresult +nsMsgAccountManagerDataSource::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + bool aOldValue, + bool aNewValue) +{ + if (aProperty == kDefaultServerAtom) { + nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(aItem)); + NotifyObservers(resource, kNC_IsDefaultServer, kTrueLiteral, nullptr, aNewValue, false); + } + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemEvent(nsIMsgFolder *, nsIAtom *) +{ + return NS_OK; +} + +nsresult +nsMsgAccountManagerDataSource::OnItemIntPropertyChanged(nsIMsgFolder *, nsIAtom *, int64_t, int64_t) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManagerDataSource::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + nsMsgRDFDataSource::Observe(aSubject, aTopic, aData); + + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgAccountManagerDS.h b/mailnews/base/src/nsMsgAccountManagerDS.h new file mode 100644 index 0000000000..0a8d4cf42a --- /dev/null +++ b/mailnews/base/src/nsMsgAccountManagerDS.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef __nsMsgAccountManagerDS_h +#define __nsMsgAccountManagerDS_h + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsError.h" +#include "nsIID.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" + +#include "nsMsgRDFDataSource.h" +#include "nsIMsgAccountManager.h" +#include "nsIIncomingServerListener.h" +#include "nsIMsgProtocolInfo.h" +#include "nsWeakPtr.h" +#include "nsIMutableArray.h" +#include "nsCOMArray.h" + +/* {3f989ca4-f77a-11d2-969d-006008948010} */ +#define NS_MSGACCOUNTMANAGERDATASOURCE_CID \ + {0x3f989ca4, 0xf77a, 0x11d2, \ + {0x96, 0x9d, 0x00, 0x60, 0x08, 0x94, 0x80, 0x10}} + +class nsMsgAccountManagerDataSource : public nsMsgRDFDataSource, + public nsIFolderListener, + public nsIIncomingServerListener +{ + +public: + + nsMsgAccountManagerDataSource(); + virtual nsresult Init() override; + + virtual void Cleanup() override; + // service manager shutdown method + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSIINCOMINGSERVERLISTENER + NS_DECL_NSIOBSERVER + // RDF datasource methods + NS_IMETHOD GetTarget(nsIRDFResource *source, + nsIRDFResource *property, + bool aTruthValue, + nsIRDFNode **_retval) override; + NS_IMETHOD GetTargets(nsIRDFResource *source, + nsIRDFResource *property, + bool aTruthValue, + nsISimpleEnumerator **_retval) override; + NS_IMETHOD ArcLabelsOut(nsIRDFResource *source, + nsISimpleEnumerator **_retval) override; + + NS_IMETHOD HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, + nsIRDFNode *aTarget, bool aTruthValue, + bool *_retval) override; + NS_IMETHOD HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc, + bool *result) override; + +protected: + virtual ~nsMsgAccountManagerDataSource(); + + nsresult HasAssertionServer(nsIMsgIncomingServer *aServer, + nsIRDFResource *aProperty, + nsIRDFNode *aTarget, + bool aTruthValue, bool *_retval); + + nsresult HasAssertionAccountRoot(nsIRDFResource *aProperty, + nsIRDFNode *aTarget, + bool aTruthValue, bool *_retval); + + bool isDefaultServer(nsIMsgIncomingServer *aServer); + bool supportsFilters(nsIMsgIncomingServer *aServer); + bool canGetMessages(nsIMsgIncomingServer *aServer); + bool canGetIncomingMessages(nsIMsgIncomingServer *aServer); + + static bool isContainment(nsIRDFResource *aProperty); + nsresult getServerForFolderNode(nsIRDFNode *aResource, + nsIMsgIncomingServer **aResult); + + nsresult createRootResources(nsIRDFResource *aProperty, + nsCOMArray<nsIRDFResource> *aNodeArray); + nsresult createSettingsResources(nsIRDFResource *aSource, + nsCOMArray<nsIRDFResource> *aNodeArray); + nsresult appendGenericSettingsResources(nsIMsgIncomingServer *server,\ + nsCOMArray<nsIRDFResource> *aNodeArray); + nsresult appendGenericSetting(const char *name, + nsCOMArray<nsIRDFResource> *aNodeArray); + + static nsIRDFResource* kNC_Name; + static nsIRDFResource* kNC_FolderTreeName; + static nsIRDFResource* kNC_FolderTreeSimpleName; + static nsIRDFResource* kNC_NameSort; + static nsIRDFResource* kNC_FolderTreeNameSort; + static nsIRDFResource* kNC_PageTag; + static nsIRDFResource* kNC_IsDefaultServer; + static nsIRDFResource* kNC_SupportsFilters; + static nsIRDFResource* kNC_CanGetMessages; + static nsIRDFResource* kNC_CanGetIncomingMessages; + + static nsIRDFResource* kNC_Child; + static nsIRDFResource* kNC_AccountRoot; + + static nsIRDFResource* kNC_Account; + static nsIRDFResource* kNC_Server; + static nsIRDFResource* kNC_Identity; + static nsIRDFResource* kNC_Settings; + static nsIRDFResource* kNC_Junk; + + static nsIRDFResource* kNC_PageTitleMain; + static nsIRDFResource* kNC_PageTitleServer; + static nsIRDFResource* kNC_PageTitleCopies; + static nsIRDFResource* kNC_PageTitleSynchronization; + static nsIRDFResource* kNC_PageTitleDiskSpace; + static nsIRDFResource* kNC_PageTitleAddressing; + static nsIRDFResource* kNC_PageTitleSMTP; + static nsIRDFResource* kNC_PageTitleJunk; + + static nsIRDFLiteral* kTrueLiteral; + + static nsIAtom* kDefaultServerAtom; + + static nsrefcnt gAccountManagerResourceRefCnt; + + static nsresult getAccountArcs(nsIMutableArray **aResult); + static nsresult getAccountRootArcs(nsIMutableArray **aResult); + +private: + nsresult serverHasIdentities(nsIMsgIncomingServer *aServer, bool *aResult); + nsresult getStringBundle(); + + static nsCOMPtr<nsIMutableArray> mAccountArcsOut; + static nsCOMPtr<nsIMutableArray> mAccountRootArcsOut; + nsWeakPtr mAccountManager; + nsCOMPtr<nsIStringBundle> mStringBundle; +}; + +#endif /* __nsMsgAccountManagerDS_h */ diff --git a/mailnews/base/src/nsMsgBiffManager.cpp b/mailnews/base/src/nsMsgBiffManager.cpp new file mode 100644 index 0000000000..360600147f --- /dev/null +++ b/mailnews/base/src/nsMsgBiffManager.cpp @@ -0,0 +1,373 @@ +/* -*- 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 "nsMsgBiffManager.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsStatusBarBiffManager.h" +#include "nsCOMArray.h" +#include "mozilla/Logging.h" +#include "nspr.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIObserverService.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" +#include <algorithm> + +#define PREF_BIFF_JITTER "mail.biff.add_interval_jitter" + +static NS_DEFINE_CID(kStatusBarBiffManagerCID, NS_STATUSBARBIFFMANAGER_CID); + +static PRLogModuleInfo *MsgBiffLogModule = nullptr; + +NS_IMPL_ISUPPORTS(nsMsgBiffManager, nsIMsgBiffManager, + nsIIncomingServerListener, nsIObserver, + nsISupportsWeakReference) + +void OnBiffTimer(nsITimer *timer, void *aBiffManager) +{ + nsMsgBiffManager *biffManager = (nsMsgBiffManager*)aBiffManager; + biffManager->PerformBiff(); +} + +nsMsgBiffManager::nsMsgBiffManager() +{ + mHaveShutdown = false; + mInited = false; +} + +nsMsgBiffManager::~nsMsgBiffManager() +{ + if (mBiffTimer) + mBiffTimer->Cancel(); + + if (!mHaveShutdown) + Shutdown(); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + { + observerService->RemoveObserver(this, "wake_notification"); + observerService->RemoveObserver(this, "sleep_notification"); + } +} + +NS_IMETHODIMP nsMsgBiffManager::Init() +{ + if (mInited) + return NS_OK; + + mInited = true; + nsresult rv; + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + accountManager->AddIncomingServerListener(this); + + // in turbo mode on profile change we don't need to do anything below this + if (mHaveShutdown) + { + mHaveShutdown = false; + return NS_OK; + } + + // Ensure status bar biff service has started + nsCOMPtr<nsIFolderListener> statusBarBiffService = + do_GetService(kStatusBarBiffManagerCID, &rv); + + if (!MsgBiffLogModule) + MsgBiffLogModule = PR_NewLogModule("MsgBiff"); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + { + observerService->AddObserver(this, "sleep_notification", true); + observerService->AddObserver(this, "wake_notification", true); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgBiffManager::Shutdown() +{ + if (mBiffTimer) + { + mBiffTimer->Cancel(); + mBiffTimer = nullptr; + } + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + accountManager->RemoveIncomingServerListener(this); + + mHaveShutdown = true; + mInited = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgBiffManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + if (!strcmp(aTopic, "sleep_notification") && mBiffTimer) + { + mBiffTimer->Cancel(); + mBiffTimer = nullptr; + } + else if (!strcmp(aTopic, "wake_notification")) + { + // wait 10 seconds after waking up to start biffing again. + mBiffTimer = do_CreateInstance("@mozilla.org/timer;1"); + mBiffTimer->InitWithFuncCallback(OnBiffTimer, (void*)this, 10000, + nsITimer::TYPE_ONE_SHOT); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgBiffManager::AddServerBiff(nsIMsgIncomingServer *server) +{ + NS_ENSURE_ARG_POINTER(server); + + int32_t biffMinutes; + + nsresult rv = server->GetBiffMinutes(&biffMinutes); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't add if biffMinutes isn't > 0 + if (biffMinutes > 0) + { + int32_t serverIndex = FindServer(server); + // Only add it if it hasn't been added already. + if (serverIndex == -1) + { + nsBiffEntry biffEntry; + biffEntry.server = server; + rv = SetNextBiffTime(biffEntry, PR_Now()); + NS_ENSURE_SUCCESS(rv, rv); + + AddBiffEntry(biffEntry); + SetupNextBiff(); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgBiffManager::RemoveServerBiff(nsIMsgIncomingServer *server) +{ + int32_t pos = FindServer(server); + if (pos != -1) + mBiffArray.RemoveElementAt(pos); + + // Should probably reset biff time if this was the server that gets biffed + // next. + return NS_OK; +} + + +NS_IMETHODIMP nsMsgBiffManager::ForceBiff(nsIMsgIncomingServer *server) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgBiffManager::ForceBiffAll() +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgBiffManager::OnServerLoaded(nsIMsgIncomingServer *server) +{ + NS_ENSURE_ARG_POINTER(server); + + bool doBiff = false; + nsresult rv = server->GetDoBiff(&doBiff); + + if (NS_SUCCEEDED(rv) && doBiff) + rv = AddServerBiff(server); + + return rv; +} + +NS_IMETHODIMP nsMsgBiffManager::OnServerUnloaded(nsIMsgIncomingServer *server) +{ + return RemoveServerBiff(server); +} + +NS_IMETHODIMP nsMsgBiffManager::OnServerChanged(nsIMsgIncomingServer *server) +{ + // nothing required. If the hostname or username changed + // the next time biff fires, we'll ping the right server + return NS_OK; +} + +int32_t nsMsgBiffManager::FindServer(nsIMsgIncomingServer *server) +{ + uint32_t count = mBiffArray.Length(); + for (uint32_t i = 0; i < count; i++) + { + if (server == mBiffArray[i].server.get()) + return i; + } + return -1; +} + +nsresult nsMsgBiffManager::AddBiffEntry(nsBiffEntry &biffEntry) +{ + uint32_t i; + uint32_t count = mBiffArray.Length(); + for (i = 0; i < count; i++) + { + if (biffEntry.nextBiffTime < mBiffArray[i].nextBiffTime) + break; + } + MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("inserting biff entry at %d\n", i)); + mBiffArray.InsertElementAt(i, biffEntry); + return NS_OK; +} + +nsresult nsMsgBiffManager::SetNextBiffTime(nsBiffEntry &biffEntry, PRTime currentTime) +{ + nsIMsgIncomingServer *server = biffEntry.server; + NS_ENSURE_TRUE(server, NS_ERROR_FAILURE); + + int32_t biffInterval; + nsresult rv = server->GetBiffMinutes(&biffInterval); + NS_ENSURE_SUCCESS(rv, rv); + + // Add biffInterval, converted in microseconds, to current time. + // Force 64-bit multiplication. + PRTime chosenTimeInterval = biffInterval * 60000000LL; + biffEntry.nextBiffTime = currentTime + chosenTimeInterval; + + // Check if we should jitter. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) + { + bool shouldUseBiffJitter = false; + prefs->GetBoolPref(PREF_BIFF_JITTER, &shouldUseBiffJitter); + if (shouldUseBiffJitter) + { + // Calculate a jitter of +/-5% on chosenTimeInterval + // - minimum 1 second (to avoid a modulo with 0) + // - maximum 30 seconds (to avoid problems when biffInterval is very large) + int64_t jitter = (int64_t)(0.05 * (int64_t)chosenTimeInterval); + jitter = std::max<int64_t>(1000000LL, std::min<int64_t>(jitter, 30000000LL)); + jitter = ((rand() % 2) ? 1 : -1) * (rand() % jitter); + + biffEntry.nextBiffTime += jitter; + } + } + + return NS_OK; +} + +nsresult nsMsgBiffManager::SetupNextBiff() +{ + if (mBiffArray.Length() > 0) + { + // Get the next biff entry + const nsBiffEntry &biffEntry = mBiffArray[0]; + PRTime currentTime = PR_Now(); + int64_t biffDelay; + int64_t ms(1000); + + if (currentTime > biffEntry.nextBiffTime) + { + // Let's wait 30 seconds before firing biff again + biffDelay = 30 * PR_USEC_PER_SEC; + } + else + biffDelay = biffEntry.nextBiffTime - currentTime; + + // Convert biffDelay into milliseconds + int64_t timeInMS = biffDelay / ms; + uint32_t timeInMSUint32 = (uint32_t)timeInMS; + + // Can't currently reset a timer when it's in the process of + // calling Notify. So, just release the timer here and create a new one. + if (mBiffTimer) + mBiffTimer->Cancel(); + + MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("setting %d timer\n", timeInMSUint32)); + mBiffTimer = do_CreateInstance("@mozilla.org/timer;1"); + mBiffTimer->InitWithFuncCallback(OnBiffTimer, (void*)this, timeInMSUint32, + nsITimer::TYPE_ONE_SHOT); + + } + return NS_OK; +} + +//This is the function that does a biff on all of the servers whose time it is to biff. +nsresult nsMsgBiffManager::PerformBiff() +{ + PRTime currentTime = PR_Now(); + nsCOMArray<nsIMsgFolder> targetFolders; + MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("performing biffs\n")); + + uint32_t count = mBiffArray.Length(); + for (uint32_t i = 0; i < count; i++) + { + // Take a copy of the entry rather than the a reference so that we can + // remove and add if necessary, but keep the references and memory alive. + nsBiffEntry current = mBiffArray[i]; + if (current.nextBiffTime < currentTime) + { + bool serverBusy = false; + bool serverRequiresPassword = true; + bool passwordPromptRequired; + + current.server->GetPasswordPromptRequired(&passwordPromptRequired); + current.server->GetServerBusy(&serverBusy); + current.server->GetServerRequiresPasswordForBiff(&serverRequiresPassword); + // find the dest folder we're actually downloading to... + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + current.server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + int32_t targetFolderIndex = targetFolders.IndexOfObject(rootMsgFolder); + if (targetFolderIndex == kNotFound) + targetFolders.AppendObject(rootMsgFolder); + + // so if we need to be authenticated to biff, check that we are + // (since we don't want to prompt the user for password UI) + // and make sure the server isn't already in the middle of downloading + // new messages + if (!serverBusy && + (!serverRequiresPassword || !passwordPromptRequired) && + targetFolderIndex == kNotFound) + { + nsCString serverKey; + current.server->GetKey(serverKey); + nsresult rv = current.server->PerformBiff(nullptr); + MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("biffing server %s rv = %x\n", serverKey.get(), rv)); + } + else + { + MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("not biffing server serverBusy = %d requirespassword = %d password prompt required = %d targetFolderIndex = %d\n", + serverBusy, serverRequiresPassword, passwordPromptRequired, targetFolderIndex)); + } + // if we didn't do this server because the destination server was already being + // biffed into, leave this server in the biff array so it will fire next. + if (targetFolderIndex == kNotFound) + { + mBiffArray.RemoveElementAt(i); + i--; //Because we removed it we need to look at the one that just moved up. + SetNextBiffTime(current, currentTime); + AddBiffEntry(current); + } +#ifdef DEBUG_David_Bienvenu + else + printf("dest account performing biff\n"); +#endif + } + else + //since we're in biff order, there's no reason to keep checking + break; + } + SetupNextBiff(); + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgBiffManager.h b/mailnews/base/src/nsMsgBiffManager.h new file mode 100644 index 0000000000..909c2b4938 --- /dev/null +++ b/mailnews/base/src/nsMsgBiffManager.h @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +#ifndef NSMSGBIFFMANAGER_H +#define NSMSGBIFFMANAGER_H + +#include "msgCore.h" +#include "nsIMsgBiffManager.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsIIncomingServerListener.h" +#include "nsWeakReference.h" +#include "nsIObserver.h" + +typedef struct { + nsCOMPtr<nsIMsgIncomingServer> server; + PRTime nextBiffTime; +} nsBiffEntry; + + +class nsMsgBiffManager + : public nsIMsgBiffManager, + public nsIIncomingServerListener, + public nsIObserver, + public nsSupportsWeakReference +{ +public: + nsMsgBiffManager(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGBIFFMANAGER + NS_DECL_NSIINCOMINGSERVERLISTENER + NS_DECL_NSIOBSERVER + + nsresult PerformBiff(); + +protected: + virtual ~nsMsgBiffManager(); + + int32_t FindServer(nsIMsgIncomingServer *server); + nsresult SetNextBiffTime(nsBiffEntry &biffEntry, PRTime currentTime); + nsresult SetupNextBiff(); + nsresult AddBiffEntry(nsBiffEntry &biffEntry); + +protected: + nsCOMPtr<nsITimer> mBiffTimer; + nsTArray<nsBiffEntry> mBiffArray; + bool mHaveShutdown; + bool mInited; +}; + +#endif // NSMSGBIFFMANAGER_H diff --git a/mailnews/base/src/nsMsgContentPolicy.cpp b/mailnews/base/src/nsMsgContentPolicy.cpp new file mode 100644 index 0000000000..ebc02635f6 --- /dev/null +++ b/mailnews/base/src/nsMsgContentPolicy.cpp @@ -0,0 +1,1076 @@ +/* -*- 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 "nsMsgContentPolicy.h" +#include "nsIPermissionManager.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIAbManager.h" +#include "nsIAbDirectory.h" +#include "nsIAbCard.h" +#include "nsIMsgWindow.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgHdr.h" +#include "nsIEncryptedSMIMEURIsSrvc.h" +#include "nsNetUtil.h" +#include "nsIMsgComposeService.h" +#include "nsMsgCompCID.h" +#include "nsIDocShellTreeItem.h" +#include "nsIWebNavigation.h" +#include "nsContentPolicyUtils.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIFrameLoader.h" +#include "nsIWebProgress.h" +#include "nsMsgUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsINntpUrl.h" +#include "nsSandboxFlags.h" + +static const char kBlockRemoteImages[] = "mailnews.message_display.disable_remote_image"; +static const char kAllowPlugins[] = "mailnews.message_display.allow_plugins"; +static const char kTrustedDomains[] = "mail.trusteddomains"; + +using namespace mozilla::mailnews; + +// Per message headder flags to keep track of whether the user is allowing remote +// content for a particular message. +// if you change or add more values to these constants, be sure to modify +// the corresponding definitions in mailWindowOverlay.js +#define kNoRemoteContentPolicy 0 +#define kBlockRemoteContent 1 +#define kAllowRemoteContent 2 + +NS_IMPL_ISUPPORTS(nsMsgContentPolicy, + nsIContentPolicy, + nsIWebProgressListener, + nsIMsgContentPolicy, + nsIObserver, + nsISupportsWeakReference) + +nsMsgContentPolicy::nsMsgContentPolicy() +{ + mAllowPlugins = false; + mBlockRemoteImages = true; +} + +nsMsgContentPolicy::~nsMsgContentPolicy() +{ + // hey, we are going away...clean up after ourself....unregister our observer + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + prefInternal->RemoveObserver(kBlockRemoteImages, this); + prefInternal->RemoveObserver(kAllowPlugins, this); + } +} + +nsresult nsMsgContentPolicy::Init() +{ + nsresult rv; + + // register ourself as an observer on the mail preference to block remote images + nsCOMPtr<nsIPrefBranch> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + prefInternal->AddObserver(kBlockRemoteImages, this, true); + prefInternal->AddObserver(kAllowPlugins, this, true); + + prefInternal->GetBoolPref(kAllowPlugins, &mAllowPlugins); + prefInternal->GetCharPref(kTrustedDomains, getter_Copies(mTrustedMailDomains)); + prefInternal->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages); + + // Grab a handle on the PermissionManager service for managing allowed remote + // content senders. + mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/** + * @returns true if the sender referenced by aMsgHdr is explicitly allowed to + * load remote images according to the PermissionManager + */ +bool +nsMsgContentPolicy::ShouldAcceptRemoteContentForSender(nsIMsgDBHdr *aMsgHdr) +{ + if (!aMsgHdr) + return false; + + // extract the e-mail address from the msg hdr + nsCString author; + nsresult rv = aMsgHdr->GetAuthor(getter_Copies(author)); + NS_ENSURE_SUCCESS(rv, false); + + nsCString emailAddress; + ExtractEmail(EncodedHeader(author), emailAddress); + if (emailAddress.IsEmpty()) + return false; + + nsCOMPtr<nsIIOService> ios = do_GetService("@mozilla.org/network/io-service;1", &rv); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIURI> mailURI; + emailAddress.Insert("chrome://messenger/content/email=", 0); + rv = ios->NewURI(emailAddress, nullptr, nullptr, getter_AddRefs(mailURI)); + NS_ENSURE_SUCCESS(rv, false); + + // check with permission manager + uint32_t permission = 0; + rv = mPermissionManager->TestPermission(mailURI, "image", &permission); + NS_ENSURE_SUCCESS(rv, false); + + // Only return true if the permission manager has an explicit allow + return (permission == nsIPermissionManager::ALLOW_ACTION); +} + +/** + * Extract the host name from aContentLocation, and look it up in our list + * of trusted domains. + */ +bool nsMsgContentPolicy::IsTrustedDomain(nsIURI * aContentLocation) +{ + bool trustedDomain = false; + // get the host name of the server hosting the remote image + nsAutoCString host; + nsresult rv = aContentLocation->GetHost(host); + + if (NS_SUCCEEDED(rv) && !mTrustedMailDomains.IsEmpty()) + trustedDomain = MsgHostDomainIsTrusted(host, mTrustedMailDomains); + + return trustedDomain; +} + +NS_IMETHODIMP +nsMsgContentPolicy::ShouldLoad(uint32_t aContentType, + nsIURI *aContentLocation, + nsIURI *aRequestingLocation, + nsISupports *aRequestingContext, + const nsACString &aMimeGuess, + nsISupports *aExtra, + nsIPrincipal *aRequestPrincipal, + int16_t *aDecision) +{ + nsresult rv = NS_OK; + // The default decision at the start of the function is to accept the load. + // Once we have checked the content type and the requesting location, then + // we switch it to reject. + // + // Be very careful about returning error codes - if this method returns an + // NS_ERROR_*, any decision made here will be ignored, and the document could + // be accepted when we don't want it to be. + // + // In most cases if an error occurs, its something we didn't expect so we + // should be rejecting the document anyway. + *aDecision = nsIContentPolicy::ACCEPT; + + NS_ENSURE_ARG_POINTER(aContentLocation); + +#ifdef DEBUG_MsgContentPolicy + fprintf(stderr, "aContentType: %d\naContentLocation = %s\n", + aContentType, + aContentLocation->GetSpecOrDefault().get()); +#endif + +#ifndef MOZ_THUNDERBIRD + // Go find out if we are dealing with mailnews. Anything else + // isn't our concern and we accept content. + nsCOMPtr<nsIDocShell> rootDocShell; + rv = GetRootDocShellForContext(aRequestingContext, + getter_AddRefs(rootDocShell)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t appType; + rv = rootDocShell->GetAppType(&appType); + // We only want to deal with mailnews + if (NS_FAILED(rv) || appType != nsIDocShell::APP_TYPE_MAIL) + return NS_OK; +#endif + + switch(aContentType) { + // Plugins (nsIContentPolicy::TYPE_OBJECT) are blocked on document load. + case nsIContentPolicy::TYPE_DOCUMENT: + // At this point, we have no intention of supporting a different JS + // setting on a subdocument, so we don't worry about TYPE_SUBDOCUMENT here. + + // If the timing were right, we'd enable JavaScript on the docshell + // for non mailnews URIs here. However, at this point, the + // old document may still be around, so we can't do any enabling just yet. + // Instead, we apply the policy in nsIWebProgressListener::OnLocationChange. + // For now, we explicitly disable JavaScript in order to be safe rather than + // sorry, because OnLocationChange isn't guaranteed to necessarily be called + // soon enough to disable it in time (though bz says it _should_ be called + // soon enough "in all sane cases"). + rv = SetDisableItemsOnMailNewsUrlDocshells(aContentLocation, + aRequestingContext); + // if something went wrong during the tweaking, reject this content + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set disable items on docShells"); + *aDecision = nsIContentPolicy::REJECT_TYPE; + return NS_OK; + } + break; + + case nsIContentPolicy::TYPE_CSP_REPORT: + // We cannot block CSP reports. + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + break; + + default: + break; + } + + // NOTE: Not using NS_ENSURE_ARG_POINTER because this is a legitimate case + // that can happen. Also keep in mind that the default policy used for a + // failure code is ACCEPT. + if (!aRequestingLocation) + return NS_ERROR_INVALID_POINTER; + +#ifdef DEBUG_MsgContentPolicy + fprintf(stderr, "aRequestingLocation = %s\n", aRequestingLocation->GetSpecOrDefault().get()); +#endif + + // If the requesting location is safe, accept the content location request. + if (IsSafeRequestingLocation(aRequestingLocation)) + return rv; + + // Now default to reject so early returns via NS_ENSURE_SUCCESS + // cause content to be rejected. + *aDecision = nsIContentPolicy::REJECT_REQUEST; + + // We want to establish the following: + // \--------\ requester | | | + // content \------------\ | | | + // requested \| mail message | news message | http(s)/data etc. + // -------------------------+---------------+--------------+------------------ + // mail message content | load if same | don't load | don't load + // mailbox, imap, JsAccount | message (1) | (2) | (3) + // -------------------------+---------------+--------------+------------------ + // news message | don't load (4)| load (5) | load (6) + // -------------------------+---------------+--------------+------------------ + // http(s)/data, etc. | (default) | (default) | (default) + // -------------------------+---------------+--------------+------------------ + nsCOMPtr<nsIMsgMessageUrl> contentURL(do_QueryInterface(aContentLocation)); + if (contentURL) { + nsCOMPtr<nsINntpUrl> contentNntpURL(do_QueryInterface(aContentLocation)); + if (!contentNntpURL) { + // Mail message (mailbox, imap or JsAccount) content requested, for example + // a message part, like an image: + // To load mail message content the requester must have the same + // "normalised" principal. This is basically a "same origin" test, it + // protects against cross-loading of mail message content from + // other mail or news messages. + nsCOMPtr<nsIMsgMessageUrl> requestURL(do_QueryInterface(aRequestingLocation)); + // If the request URL is not also a message URL, then we don't accept. + if (requestURL) { + nsCString contentPrincipalSpec, requestPrincipalSpec; + nsresult rv1 = contentURL->GetPrincipalSpec(contentPrincipalSpec); + nsresult rv2 = requestURL->GetPrincipalSpec(requestPrincipalSpec); + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && + contentPrincipalSpec.Equals(requestPrincipalSpec)) + *aDecision = nsIContentPolicy::ACCEPT; // (1) + } + return NS_OK; // (2) and (3) + } + + // News message content requested. Don't accept request coming + // from a mail message since it would access the news server. + nsCOMPtr<nsIMsgMessageUrl> requestURL(do_QueryInterface(aRequestingLocation)); + if (requestURL) { + nsCOMPtr<nsINntpUrl> requestNntpURL(do_QueryInterface(aRequestingLocation)); + if (!requestNntpURL) + return NS_OK; // (4) + } + *aDecision = nsIContentPolicy::ACCEPT; // (5) and (6) + return NS_OK; + } + + // If exposed protocol not covered by the test above or protocol that has been + // specifically exposed by an add-on, or is a chrome url, then allow the load. + if (IsExposedProtocol(aContentLocation)) + { + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + } + + // never load unexposed protocols except for http, https and file. + // Protocols like ftp are always blocked. + if (ShouldBlockUnexposedProtocol(aContentLocation)) + return NS_OK; + + + // Find out the URI that originally initiated the set of requests for this + // context. + nsCOMPtr<nsIURI> originatorLocation; + if (!aRequestingContext && aRequestPrincipal) + { + // Can get the URI directly from the principal. + rv = aRequestPrincipal->GetURI(getter_AddRefs(originatorLocation)); + } + else + { + rv = GetOriginatingURIForContext(aRequestingContext, + getter_AddRefs(originatorLocation)); + } + NS_ENSURE_SUCCESS(rv, NS_OK); + +#ifdef DEBUG_MsgContentPolicy + fprintf(stderr, "originatorLocation = %s\n", originatorLocation->GetSpecOrDefault().get()); +#endif + + // Don't load remote content for encrypted messages. + nsCOMPtr<nsIEncryptedSMIMEURIsService> encryptedURIService = + do_GetService("@mozilla.org/messenger-smime/smime-encrypted-uris-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool isEncrypted; + rv = encryptedURIService->IsEncrypted(aRequestingLocation->GetSpecOrDefault(), &isEncrypted); + NS_ENSURE_SUCCESS(rv, rv); + if (isEncrypted) + { + *aDecision = nsIContentPolicy::REJECT_REQUEST; + NotifyContentWasBlocked(originatorLocation, aContentLocation, false); + return NS_OK; + } + + // If we are allowing all remote content... + if (!mBlockRemoteImages) + { + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + } + + // Extract the windowtype to handle compose windows separately from mail + if (aRequestingContext) + { + nsCOMPtr<nsIMsgCompose> msgCompose = + GetMsgComposeForContext(aRequestingContext); + // Work out if we're in a compose window or not. + if (msgCompose) + { + ComposeShouldLoad(msgCompose, aRequestingContext, aContentLocation, + aDecision); + return NS_OK; + } + } + + // Allow content when using a remote page. + bool isHttp; + bool isHttps; + rv = originatorLocation->SchemeIs("http", &isHttp); + nsresult rv2 = originatorLocation->SchemeIs("https", &isHttps); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && (isHttp || isHttps)) + { + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + } + + uint32_t permission; + mPermissionManager->TestPermission(aContentLocation, "image", &permission); + switch (permission) { + case nsIPermissionManager::UNKNOWN_ACTION: + { + // No exception was found for this location. + break; + } + case nsIPermissionManager::ALLOW_ACTION: + { + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + } + case nsIPermissionManager::DENY_ACTION: + { + *aDecision = nsIContentPolicy::REJECT_REQUEST; + return NS_OK; + } + } + + // The default decision is still to reject. + ShouldAcceptContentForPotentialMsg(originatorLocation, aContentLocation, + aDecision); + return NS_OK; +} + +/** + * Determines if the requesting location is a safe one, i.e. its under the + * app/user's control - so file, about, chrome etc. + */ +bool +nsMsgContentPolicy::IsSafeRequestingLocation(nsIURI *aRequestingLocation) +{ + if (!aRequestingLocation) + return false; + + // If aRequestingLocation is one of chrome, resource, file or view-source, + // allow aContentLocation to load. + bool isChrome; + bool isRes; + bool isFile; + bool isViewSource; + + nsresult rv = aRequestingLocation->SchemeIs("chrome", &isChrome); + NS_ENSURE_SUCCESS(rv, false); + rv = aRequestingLocation->SchemeIs("resource", &isRes); + NS_ENSURE_SUCCESS(rv, false); + rv = aRequestingLocation->SchemeIs("file", &isFile); + NS_ENSURE_SUCCESS(rv, false); + rv = aRequestingLocation->SchemeIs("view-source", &isViewSource); + NS_ENSURE_SUCCESS(rv, false); + + if (isChrome || isRes || isFile || isViewSource) + return true; + + // Only allow about: to load anything if the requesting location is not the + // special about:blank one. + bool isAbout; + rv = aRequestingLocation->SchemeIs("about", &isAbout); + NS_ENSURE_SUCCESS(rv, false); + + if (!isAbout) + return false; + + nsCString fullSpec; + rv = aRequestingLocation->GetSpec(fullSpec); + NS_ENSURE_SUCCESS(rv, false); + + return !fullSpec.EqualsLiteral("about:blank"); +} + +/** + * Determines if the content location is a scheme that we're willing to expose + * for unlimited loading of content. + */ +bool +nsMsgContentPolicy::IsExposedProtocol(nsIURI *aContentLocation) +{ + nsAutoCString contentScheme; + nsresult rv = aContentLocation->GetScheme(contentScheme); + NS_ENSURE_SUCCESS(rv, false); + + // Check some exposed protocols. Not all protocols in the list of + // network.protocol-handler.expose.* prefs in all-thunderbird.js are + // admitted purely based on their scheme. + // news, snews, nntp, imap and mailbox are checked before the call + // to this function by matching content location and requesting location. + if (MsgLowerCaseEqualsLiteral(contentScheme, "mailto") || + MsgLowerCaseEqualsLiteral(contentScheme, "addbook") || + MsgLowerCaseEqualsLiteral(contentScheme, "about")) + return true; + + // check if customized exposed scheme + if (mCustomExposedProtocols.Contains(contentScheme)) + return true; + + bool isData; + bool isChrome; + bool isRes; + rv = aContentLocation->SchemeIs("chrome", &isChrome); + NS_ENSURE_SUCCESS(rv, false); + rv = aContentLocation->SchemeIs("resource", &isRes); + NS_ENSURE_SUCCESS(rv, false); + rv = aContentLocation->SchemeIs("data", &isData); + NS_ENSURE_SUCCESS(rv, false); + + return isChrome || isRes || isData; +} + +/** + * We block most unexposed protocols - apart from http(s) and file. + */ +bool +nsMsgContentPolicy::ShouldBlockUnexposedProtocol(nsIURI *aContentLocation) +{ + bool isHttp; + bool isHttps; + bool isFile; + // Error condition - we must return true so that we block. + nsresult rv = aContentLocation->SchemeIs("http", &isHttp); + NS_ENSURE_SUCCESS(rv, true); + rv = aContentLocation->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, true); + rv = aContentLocation->SchemeIs("file", &isFile); + NS_ENSURE_SUCCESS(rv, true); + + return !isHttp && !isHttps && !isFile; +} + +/** + * The default for this function will be to reject the content request. + * When determining if to allow the request for a given msg hdr, the function + * will go through the list of remote content blocking criteria: + * + * #1 Allow if there is a db header for a manual override. + * #2 Allow if the message is in an RSS folder. + * #3 Allow if the domain for the remote image in our white list. + * #4 Allow if the author has been specifically white listed. + */ +int16_t +nsMsgContentPolicy::ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr *aMsgHdr, + nsIURI *aRequestingLocation, + nsIURI *aContentLocation) +{ + if (!aMsgHdr) + return static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST); + + // Case #1, check the db hdr for the remote content policy on this particular + // message. + uint32_t remoteContentPolicy = kNoRemoteContentPolicy; + aMsgHdr->GetUint32Property("remoteContentPolicy", &remoteContentPolicy); + + // Case #2, check if the message is in an RSS folder + bool isRSS = false; + IsRSSArticle(aRequestingLocation, &isRSS); + + // Case #3, the domain for the remote image is in our white list + bool trustedDomain = IsTrustedDomain(aContentLocation); + + // Case 4 means looking up items in the permissions database. So if + // either of the two previous items means we load the data, just do it. + if (isRSS || remoteContentPolicy == kAllowRemoteContent || trustedDomain) + return nsIContentPolicy::ACCEPT; + + // Case #4, author is in our white list.. + bool allowForSender = ShouldAcceptRemoteContentForSender(aMsgHdr); + + int16_t result = allowForSender ? + static_cast<int16_t>(nsIContentPolicy::ACCEPT) : + static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST); + + // kNoRemoteContentPolicy means we have never set a value on the message + if (result == nsIContentPolicy::REJECT_REQUEST && !remoteContentPolicy) + aMsgHdr->SetUint32Property("remoteContentPolicy", kBlockRemoteContent); + + return result; +} + +class RemoteContentNotifierEvent : public mozilla::Runnable +{ +public: + RemoteContentNotifierEvent(nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aMsgHdr, + nsIURI *aContentURI, bool aCanOverride = true) + : mMsgWindow(aMsgWindow), mMsgHdr(aMsgHdr), mContentURI(aContentURI), + mCanOverride(aCanOverride) + {} + + NS_IMETHOD Run() + { + if (mMsgWindow) + { + nsCOMPtr<nsIMsgHeaderSink> msgHdrSink; + (void)mMsgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink)); + if (msgHdrSink) + msgHdrSink->OnMsgHasRemoteContent(mMsgHdr, mContentURI, mCanOverride); + } + return NS_OK; + } + +private: + nsCOMPtr<nsIMsgWindow> mMsgWindow; + nsCOMPtr<nsIMsgDBHdr> mMsgHdr; + nsCOMPtr<nsIURI> mContentURI; + bool mCanOverride; +}; + +/** + * This function is used to show a blocked remote content notification. + */ +void +nsMsgContentPolicy::NotifyContentWasBlocked(nsIURI *aOriginatorLocation, + nsIURI *aContentLocation, + bool aCanOverride) +{ + // Is it a mailnews url? + nsresult rv; + nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(aOriginatorLocation, + &rv)); + if (NS_FAILED(rv)) + { + return; + } + + nsCString resourceURI; + rv = msgUrl->GetUri(getter_Copies(resourceURI)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(aOriginatorLocation, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(resourceURI.get(), getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) + { + // Maybe we can get a dummy header. + nsCOMPtr<nsIMsgWindow> msgWindow; + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr<nsIMsgHeaderSink> msgHdrSink; + rv = msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink)); + if (msgHdrSink) + rv = msgHdrSink->GetDummyMsgHeader(getter_AddRefs(msgHdr)); + } + } + + nsCOMPtr<nsIMsgWindow> msgWindow; + (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr<nsIRunnable> event = + new RemoteContentNotifierEvent(msgWindow, msgHdr, aContentLocation, aCanOverride); + // Post this as an event because it can cause dom mutations, and we + // get called at a bad time to be causing dom mutations. + if (event) + NS_DispatchToCurrentThread(event); + } +} + +/** + * This function is used to determine if we allow content for a remote message. + * If we reject loading remote content, then we'll inform the message window + * that this message has remote content (and hence we are not loading it). + * + * See ShouldAcceptRemoteContentForMsgHdr for the actual decisions that + * determine if we are going to allow remote content. + */ +void +nsMsgContentPolicy::ShouldAcceptContentForPotentialMsg(nsIURI *aOriginatorLocation, + nsIURI *aContentLocation, + int16_t *aDecision) +{ + NS_PRECONDITION(*aDecision == nsIContentPolicy::REJECT_REQUEST, + "AllowContentForPotentialMessage expects default decision to be reject!"); + + // Is it a mailnews url? + nsresult rv; + nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(aOriginatorLocation, + &rv)); + if (NS_FAILED(rv)) + { + // It isn't a mailnews url - so we accept the load here, and let other + // content policies make the decision if we should be loading it or not. + *aDecision = nsIContentPolicy::ACCEPT; + return; + } + + nsCString resourceURI; + rv = msgUrl->GetUri(getter_Copies(resourceURI)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(aOriginatorLocation, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(resourceURI.get(), getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) + { + // Maybe we can get a dummy header. + nsCOMPtr<nsIMsgWindow> msgWindow; + rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr<nsIMsgHeaderSink> msgHdrSink; + rv = msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink)); + if (msgHdrSink) + rv = msgHdrSink->GetDummyMsgHeader(getter_AddRefs(msgHdr)); + } + } + + // Get a decision on whether or not to allow remote content for this message + // header. + *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, aOriginatorLocation, + aContentLocation); + + // If we're not allowing the remote content, tell the nsIMsgWindow loading + // this url that this is the case, so that the UI knows to show the remote + // content header bar, so the user can override if they wish. + if (*aDecision == nsIContentPolicy::REJECT_REQUEST) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr<nsIRunnable> event = + new RemoteContentNotifierEvent(msgWindow, msgHdr, aContentLocation); + // Post this as an event because it can cause dom mutations, and we + // get called at a bad time to be causing dom mutations. + if (event) + NS_DispatchToCurrentThread(event); + } + } +} + +/** + * Content policy logic for compose windows + * + */ +void nsMsgContentPolicy::ComposeShouldLoad(nsIMsgCompose *aMsgCompose, + nsISupports *aRequestingContext, + nsIURI *aContentLocation, + int16_t *aDecision) +{ + NS_PRECONDITION(*aDecision == nsIContentPolicy::REJECT_REQUEST, + "ComposeShouldLoad expects default decision to be reject!"); + + nsCString originalMsgURI; + nsresult rv = aMsgCompose->GetOriginalMsgURI(getter_Copies(originalMsgURI)); + NS_ENSURE_SUCCESS_VOID(rv); + + MSG_ComposeType composeType; + rv = aMsgCompose->GetType(&composeType); + NS_ENSURE_SUCCESS_VOID(rv); + + // Only allow remote content for new mail compositions or mailto + // Block remote content for all other types (drafts, templates, forwards, replies, etc) + // unless there is an associated msgHdr which allows the load, or unless the image is being + // added by the user and not the quoted message content... + if (composeType == nsIMsgCompType::New || + composeType == nsIMsgCompType::MailToUrl) + *aDecision = nsIContentPolicy::ACCEPT; + else if (!originalMsgURI.IsEmpty()) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(originalMsgURI.get(), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS_VOID(rv); + *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, nullptr, + aContentLocation); + + // Special case image elements. When replying to a message, we want to allow + // the user to add remote images to the message. But we don't want remote + // images that are a part of the quoted content to load. Hence we block them + // while the reply is created (insertingQuotedContent==true), but allow them + // later when the user inserts them. + if (*aDecision == nsIContentPolicy::REJECT_REQUEST) + { + bool insertingQuotedContent = true; + aMsgCompose->GetInsertingQuotedContent(&insertingQuotedContent); + nsCOMPtr<nsIDOMHTMLImageElement> imageElement(do_QueryInterface(aRequestingContext)); + if (imageElement) + { + if (!insertingQuotedContent) + { + *aDecision = nsIContentPolicy::ACCEPT; + return; + } + + // Test whitelist. + uint32_t permission; + mPermissionManager->TestPermission(aContentLocation, "image", &permission); + if (permission == nsIPermissionManager::ALLOW_ACTION) + *aDecision = nsIContentPolicy::ACCEPT; + } + } + } +} + +already_AddRefed<nsIMsgCompose> nsMsgContentPolicy::GetMsgComposeForContext(nsISupports *aRequestingContext) +{ + nsresult rv; + + nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext); + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(shell, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + rv = docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootItem)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootItem, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIMsgComposeService> composeService(do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIMsgCompose> msgCompose; + // Don't bother checking rv, as GetMsgComposeForDocShell returns NS_ERROR_FAILURE + // for not found. + composeService->GetMsgComposeForDocShell(docShell, + getter_AddRefs(msgCompose)); + return msgCompose.forget(); +} + +nsresult nsMsgContentPolicy::SetDisableItemsOnMailNewsUrlDocshells( + nsIURI *aContentLocation, nsISupports *aRequestingContext) +{ + // XXX if this class changes so that this method can be called from + // ShouldProcess, and if it's possible for this to be null when called from + // ShouldLoad, but not in the corresponding ShouldProcess call, + // we need to re-think the assumptions underlying this code. + + // If there's no docshell to get to, there's nowhere for the JavaScript to + // run, so we're already safe and don't need to disable anything. + if (!aRequestingContext) { + return NS_OK; + } + + nsresult rv; + bool isAllowedContent = !ShouldBlockUnexposedProtocol(aContentLocation); + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aContentLocation, &rv); + if (NS_FAILED(rv) && !isAllowedContent) { + // If it's not a mailnews url or allowed content url (http[s]|file) then + // bail; otherwise set whether js and plugins are allowed. + return NS_OK; + } + + // since NS_CP_GetDocShellFromContext returns the containing docshell rather + // than the contained one we need, we can't use that here, so... + nsCOMPtr<nsIFrameLoaderOwner> flOwner = do_QueryInterface(aRequestingContext, + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFrameLoader> frameLoader; + rv = flOwner->GetFrameLoaderXPCOM(getter_AddRefs(frameLoader)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(frameLoader, NS_ERROR_INVALID_POINTER); + + nsCOMPtr<nsIDocShell> docShell; + rv = frameLoader->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(docShell, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // what sort of docshell is this? + int32_t itemType; + rv = docshellTreeItem->GetItemType(&itemType); + NS_ENSURE_SUCCESS(rv, rv); + + // we're only worried about policy settings in content docshells + if (itemType != nsIDocShellTreeItem::typeContent) { + return NS_OK; + } + + if (!isAllowedContent) { + // Disable JavaScript on message URLs. + rv = docShell->SetAllowJavascript(false); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowContentRetargetingOnChildren(false); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowPlugins(mAllowPlugins); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t sandboxFlags; + rv = docShell->GetSandboxFlags(&sandboxFlags); + sandboxFlags |= SANDBOXED_FORMS; + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetSandboxFlags(sandboxFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + // JavaScript and plugins are allowed on non-message URLs. + rv = docShell->SetAllowJavascript(true); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowContentRetargetingOnChildren(true); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowPlugins(true); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +/** + * Gets the root docshell from a requesting context. + */ +nsresult +nsMsgContentPolicy::GetRootDocShellForContext(nsISupports *aRequestingContext, + nsIDocShell **aDocShell) +{ + NS_ENSURE_ARG_POINTER(aRequestingContext); + nsresult rv; + + nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext); + nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(shell, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + rv = docshellTreeItem->GetRootTreeItem(getter_AddRefs(rootItem)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(rootItem, aDocShell); +} + +/** + * Gets the originating URI that started off a set of requests, accounting + * for multiple iframes. + * + * Navigates up the docshell tree from aRequestingContext and finds the + * highest parent with the same type docshell as aRequestingContext, then + * returns the URI associated with that docshell. + */ +nsresult +nsMsgContentPolicy::GetOriginatingURIForContext(nsISupports *aRequestingContext, + nsIURI **aURI) +{ + NS_ENSURE_ARG_POINTER(aRequestingContext); + nsresult rv; + + nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext); + nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(shell, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + rv = docshellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootItem)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWebNavigation> webNavigation(do_QueryInterface(rootItem, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return webNavigation->GetCurrentURI(aURI); +} + +NS_IMETHODIMP +nsMsgContentPolicy::ShouldProcess(uint32_t aContentType, + nsIURI *aContentLocation, + nsIURI *aRequestingLocation, + nsISupports *aRequestingContext, + const nsACString &aMimeGuess, + nsISupports *aExtra, + nsIPrincipal *aRequestPrincipal, + int16_t *aDecision) +{ + // XXX Returning ACCEPT is presumably only a reasonable thing to do if we + // think that ShouldLoad is going to catch all possible cases (i.e. that + // everything we use to make decisions is going to be available at + // ShouldLoad time, and not only become available in time for ShouldProcess). + // Do we think that's actually the case? + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; +} + +NS_IMETHODIMP nsMsgContentPolicy::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) + { + NS_LossyConvertUTF16toASCII pref(aData); + + nsresult rv; + + nsCOMPtr<nsIPrefBranch> prefBranchInt = do_QueryInterface(aSubject, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (pref.Equals(kBlockRemoteImages)) + prefBranchInt->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages); + if (pref.Equals(kAllowPlugins)) + prefBranchInt->GetBoolPref(kAllowPlugins, &mAllowPlugins); + } + + return NS_OK; +} + +/** + * We implement the nsIWebProgressListener interface in order to enforce + * settings at onLocationChange time. + */ +NS_IMETHODIMP +nsMsgContentPolicy::OnStateChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, uint32_t aStateFlags, + nsresult aStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::OnProgressChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::OnLocationChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, nsIURI *aLocation, + uint32_t aFlags) +{ + nsresult rv; + + // If anything goes wrong and/or there's no docshell associated with this + // request, just give up. The behavior ends up being "don't consider + // re-enabling JS on the docshell", which is the safe thing to do (and if + // the problem was that there's no docshell, that means that there was + // nowhere for any JavaScript to run, so we're already safe + + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress, &rv); + if (NS_FAILED(rv)) { + return NS_OK; + } + +#ifdef DEBUG + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocShell> docShell2; + NS_QueryNotificationCallbacks(channel, docShell2); + NS_ASSERTION(docShell == docShell2, "aWebProgress and channel callbacks" + " do not point to the same docshell"); + } +#endif + + nsCOMPtr<nsIMsgMessageUrl> messageUrl = do_QueryInterface(aLocation, &rv); + + if (NS_SUCCEEDED(rv)) { + // Disable javascript on message URLs. + rv = docShell->SetAllowJavascript(false); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set javascript disabled on docShell"); + // Also disable plugins if the preference requires it. + rv = docShell->SetAllowPlugins(mAllowPlugins); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set plugins disabled on docShell"); + } + else { + // Disable javascript and plugins are allowed on non-message URLs. + rv = docShell->SetAllowJavascript(true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set javascript allowed on docShell"); + rv = docShell->SetAllowPlugins(true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set plugins allowed on docShell"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::OnStatusChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, nsresult aStatus, + const char16_t *aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, uint32_t aState) +{ + return NS_OK; +} + +/** + * Implementation of nsIMsgContentPolicy + * + */ +NS_IMETHODIMP +nsMsgContentPolicy::AddExposedProtocol(const nsACString &aScheme) +{ + if (mCustomExposedProtocols.Contains(nsCString(aScheme))) + return NS_OK; + + mCustomExposedProtocols.AppendElement(aScheme); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::RemoveExposedProtocol(const nsACString &aScheme) +{ + mCustomExposedProtocols.RemoveElement(nsCString(aScheme)); + + return NS_OK; +} + diff --git a/mailnews/base/src/nsMsgContentPolicy.h b/mailnews/base/src/nsMsgContentPolicy.h new file mode 100644 index 0000000000..745708683d --- /dev/null +++ b/mailnews/base/src/nsMsgContentPolicy.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +/********************************************************************************** + * nsMsgContentPolicy enforces the specified content policy on images, js, plugins, etc. + * This is the class used to determine what elements in a message should be loaded. + * + * nsMsgCookiePolicy enforces our cookie policy for mail and RSS messages. + ***********************************************************************************/ + +#ifndef _nsMsgContentPolicy_H_ +#define _nsMsgContentPolicy_H_ + +#include "nsIContentPolicy.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsStringGlue.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIWebProgressListener.h" +#include "nsIMsgCompose.h" +#include "nsIDocShell.h" +#include "nsIPermissionManager.h" +#include "nsIMsgContentPolicy.h" +#include "nsTArray.h" + +/* DBFCFDF0-4489-4faa-8122-190FD1EFA16C */ +#define NS_MSGCONTENTPOLICY_CID \ +{ 0xdbfcfdf0, 0x4489, 0x4faa, { 0x81, 0x22, 0x19, 0xf, 0xd1, 0xef, 0xa1, 0x6c } } + +#define NS_MSGCONTENTPOLICY_CONTRACTID "@mozilla.org/messenger/content-policy;1" + +class nsIMsgDBHdr; +class nsIDocShell; + +class nsMsgContentPolicy : public nsIContentPolicy, + public nsIObserver, + public nsIWebProgressListener, + public nsIMsgContentPolicy, + public nsSupportsWeakReference +{ +public: + nsMsgContentPolicy(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPOLICY + NS_DECL_NSIOBSERVER + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIMSGCONTENTPOLICY + +protected: + virtual ~nsMsgContentPolicy(); + + bool mBlockRemoteImages; + bool mAllowPlugins; + nsCString mTrustedMailDomains; + nsCOMPtr<nsIPermissionManager> mPermissionManager; + + bool IsTrustedDomain(nsIURI * aContentLocation); + bool IsSafeRequestingLocation(nsIURI *aRequestingLocation); + bool IsExposedProtocol(nsIURI *aContentLocation); + bool IsExposedChromeProtocol(nsIURI *aContentLocation); + bool ShouldBlockUnexposedProtocol(nsIURI *aContentLocation); + + bool ShouldAcceptRemoteContentForSender(nsIMsgDBHdr *aMsgHdr); + int16_t ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr *aMsgHdr, + nsIURI *aRequestingLocation, + nsIURI *aContentLocation); + void NotifyContentWasBlocked(nsIURI *aOriginatorLocation, + nsIURI *aContentLocation, + bool aCanOverride); + void ShouldAcceptContentForPotentialMsg(nsIURI *aOriginatorLocation, + nsIURI *aContentLocation, + int16_t *aDecision); + void ComposeShouldLoad(nsIMsgCompose *aMsgCompose, + nsISupports *aRequestingContext, + nsIURI *aContentLocation, int16_t *aDecision); + already_AddRefed<nsIMsgCompose> GetMsgComposeForContext(nsISupports *aRequestingContext); + + nsresult GetRootDocShellForContext(nsISupports *aRequestingContext, + nsIDocShell **aDocShell); + nsresult GetOriginatingURIForContext(nsISupports *aRequestingContext, + nsIURI **aURI); + nsresult SetDisableItemsOnMailNewsUrlDocshells(nsIURI *aContentLocation, + nsISupports *aRequestingContext); + + nsTArray<nsCString> mCustomExposedProtocols; +}; + +#endif // _nsMsgContentPolicy_H_ diff --git a/mailnews/base/src/nsMsgCopyService.cpp b/mailnews/base/src/nsMsgCopyService.cpp new file mode 100644 index 0000000000..e7b79bd561 --- /dev/null +++ b/mailnews/base/src/nsMsgCopyService.cpp @@ -0,0 +1,708 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsMsgCopyService.h" +#include "nsCOMArray.h" +#include "nspr.h" +#include "nsIFile.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsMsgBaseCID.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Logging.h" + +static PRLogModuleInfo *gCopyServiceLog; + +// ******************** nsCopySource ****************** +// + +nsCopySource::nsCopySource() : m_processed(false) +{ + MOZ_COUNT_CTOR(nsCopySource); + m_messageArray = do_CreateInstance(NS_ARRAY_CONTRACTID); +} + +nsCopySource::nsCopySource(nsIMsgFolder* srcFolder) : + m_processed(false) +{ + MOZ_COUNT_CTOR(nsCopySource); + m_messageArray = do_CreateInstance(NS_ARRAY_CONTRACTID); + m_msgFolder = srcFolder; +} + +nsCopySource::~nsCopySource() +{ + MOZ_COUNT_DTOR(nsCopySource); +} + +void nsCopySource::AddMessage(nsIMsgDBHdr* aMsg) +{ + m_messageArray->AppendElement(aMsg, false); +} + +// ************ nsCopyRequest ***************** +// + +nsCopyRequest::nsCopyRequest() : + m_requestType(nsCopyMessagesType), + m_isMoveOrDraftOrTemplate(false), + m_processed(false), + m_newMsgFlags(0) +{ + MOZ_COUNT_CTOR(nsCopyRequest); +} + +nsCopyRequest::~nsCopyRequest() +{ + MOZ_COUNT_DTOR(nsCopyRequest); + + int32_t j = m_copySourceArray.Length(); + while(j-- > 0) + delete m_copySourceArray.ElementAt(j); +} + +nsresult +nsCopyRequest::Init(nsCopyRequestType type, nsISupports* aSupport, + nsIMsgFolder* dstFolder, + bool bVal, uint32_t newMsgFlags, + const nsACString &newMsgKeywords, + nsIMsgCopyServiceListener* listener, + nsIMsgWindow* msgWindow, bool allowUndo) +{ + nsresult rv = NS_OK; + m_requestType = type; + m_srcSupport = aSupport; + m_dstFolder = dstFolder; + m_isMoveOrDraftOrTemplate = bVal; + m_allowUndo = allowUndo; + m_newMsgFlags = newMsgFlags; + m_newMsgKeywords = newMsgKeywords; + + if (listener) + m_listener = listener; + if (msgWindow) + { + m_msgWindow = msgWindow; + if (m_allowUndo) + msgWindow->GetTransactionManager(getter_AddRefs(m_txnMgr)); + } + if (type == nsCopyFoldersType) + { + // To support multiple copy folder operations to the same destination, we + // need to save the leaf name of the src file spec so that FindRequest() is + // able to find the right request when copy finishes. + nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(aSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsString folderName; + rv = srcFolder->GetName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + m_dstFolderName = folderName; + } + + return rv; +} + +nsCopySource* +nsCopyRequest::AddNewCopySource(nsIMsgFolder* srcFolder) +{ + nsCopySource* newSrc = new nsCopySource(srcFolder); + if (newSrc) + { + m_copySourceArray.AppendElement(newSrc); + if (srcFolder == m_dstFolder) + newSrc->m_processed = true; + } + return newSrc; +} + +// ************* nsMsgCopyService **************** +// + + +nsMsgCopyService::nsMsgCopyService() +{ + gCopyServiceLog = PR_NewLogModule("MsgCopyService"); +} + +nsMsgCopyService::~nsMsgCopyService() +{ + int32_t i = m_copyRequests.Length(); + + while (i-- > 0) + ClearRequest(m_copyRequests.ElementAt(i), NS_ERROR_FAILURE); +} + +void nsMsgCopyService::LogCopyCompletion(nsISupports *aSrc, nsIMsgFolder *aDest) +{ + nsCString srcFolderUri, destFolderUri; + nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aSrc)); + if (srcFolder) + srcFolder->GetURI(srcFolderUri); + aDest->GetURI(destFolderUri); + MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info, + ("NotifyCompletion - src %s dest %s\n", + srcFolderUri.get(), destFolderUri.get())); +} + +void nsMsgCopyService::LogCopyRequest(const char *logMsg, nsCopyRequest* aRequest) +{ + nsCString srcFolderUri, destFolderUri; + nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aRequest->m_srcSupport)); + if (srcFolder) + srcFolder->GetURI(srcFolderUri); + aRequest->m_dstFolder->GetURI(destFolderUri); + uint32_t numMsgs = 0; + if (aRequest->m_requestType == nsCopyMessagesType && + aRequest->m_copySourceArray.Length() > 0 && + aRequest->m_copySourceArray[0]->m_messageArray) + aRequest->m_copySourceArray[0]->m_messageArray->GetLength(&numMsgs); + MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info, + ("request %lx %s - src %s dest %s numItems %d type=%d", + aRequest, logMsg, srcFolderUri.get(), + destFolderUri.get(), numMsgs, aRequest->m_requestType)); +} + +nsresult +nsMsgCopyService::ClearRequest(nsCopyRequest* aRequest, nsresult rv) +{ + if (aRequest) + { + if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info)) + LogCopyRequest(NS_SUCCEEDED(rv) ? "Clearing OK request" + : "Clearing failed request", aRequest); + + // Send notifications to nsIMsgFolderListeners + if (NS_SUCCEEDED(rv) && aRequest->m_requestType == nsCopyFoldersType) + { + nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + { + bool hasListeners; + notifier->GetHasListeners(&hasListeners); + if (hasListeners) + { + // Iterate over the copy sources and append their message arrays to this mutable array + // or in the case of folders, the source folder. + int32_t cnt, i; + cnt = aRequest->m_copySourceArray.Length(); + for (i = 0; i < cnt; i++) + { + nsCopySource *copySource = aRequest->m_copySourceArray.ElementAt(i); + notifier->NotifyFolderMoveCopyCompleted(aRequest->m_isMoveOrDraftOrTemplate, copySource->m_msgFolder, aRequest->m_dstFolder); + } + } + } + } + + // undo stuff + if (aRequest->m_allowUndo && + aRequest->m_copySourceArray.Length() > 1 && + aRequest->m_txnMgr) + aRequest->m_txnMgr->EndBatch(false); + + m_copyRequests.RemoveElement(aRequest); + if (aRequest->m_listener) + aRequest->m_listener->OnStopCopy(rv); + delete aRequest; + } + + return rv; +} + +nsresult +nsMsgCopyService::QueueRequest(nsCopyRequest* aRequest, bool *aCopyImmediately) +{ + NS_ENSURE_ARG_POINTER(aRequest); + NS_ENSURE_ARG_POINTER(aCopyImmediately); + *aCopyImmediately = true; + nsCopyRequest* copyRequest; + + uint32_t cnt = m_copyRequests.Length(); + for (uint32_t i = 0; i < cnt; i++) + { + copyRequest = m_copyRequests.ElementAt(i); + if (aRequest->m_requestType == nsCopyFoldersType) + { + // For copy folder, see if both destination folder (root) + // (ie, Local Folder) and folder name (ie, abc) are the same. + if (copyRequest->m_dstFolderName == aRequest->m_dstFolderName && + copyRequest->m_dstFolder.get() == aRequest->m_dstFolder.get()) + { + *aCopyImmediately = false; + break; + } + } + else if (copyRequest->m_dstFolder.get() == aRequest->m_dstFolder.get()) //if dst are same and we already have a request, we cannot copy immediately + { + *aCopyImmediately = false; + break; + } + } + return NS_OK; +} + +nsresult +nsMsgCopyService::DoCopy(nsCopyRequest* aRequest) +{ + NS_ENSURE_ARG(aRequest); + bool copyImmediately; + QueueRequest(aRequest, ©Immediately); + m_copyRequests.AppendElement(aRequest); + if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info)) + LogCopyRequest(copyImmediately ? "DoCopy" : "QueueRequest", aRequest); + + // if no active request for this dest folder then we can copy immediately + if (copyImmediately) + return DoNextCopy(); + + return NS_OK; +} + +nsresult +nsMsgCopyService::DoNextCopy() +{ + nsresult rv = NS_OK; + nsCopyRequest* copyRequest = nullptr; + nsCopySource* copySource = nullptr; + uint32_t i, j, scnt; + + uint32_t cnt = m_copyRequests.Length(); + if (cnt > 0) + { + nsCOMArray<nsIMsgFolder> activeTargets; + + // ** jt -- always FIFO + for (i = 0; i < cnt; i++) + { + copyRequest = m_copyRequests.ElementAt(i); + copySource = nullptr; + scnt = copyRequest->m_copySourceArray.Length(); + if (!copyRequest->m_processed) + { + // if the target folder of this request already has an active + // copy request, skip this request for now. + if (activeTargets.ContainsObject(copyRequest->m_dstFolder)) + { + copyRequest = nullptr; + continue; + } + if (scnt <= 0) + goto found; // must be CopyFileMessage + for (j = 0; j < scnt; j++) + { + copySource = copyRequest->m_copySourceArray.ElementAt(j); + if (!copySource->m_processed) + goto found; + } + if (j >= scnt) // all processed set the value + copyRequest->m_processed = true; + } + if (copyRequest->m_processed) // keep track of folders actively getting copied to. + activeTargets.AppendObject(copyRequest->m_dstFolder); + } + found: + if (copyRequest && !copyRequest->m_processed) + { + if (copyRequest->m_listener) + copyRequest->m_listener->OnStartCopy(); + if (copyRequest->m_requestType == nsCopyMessagesType && + copySource) + { + copySource->m_processed = true; + rv = copyRequest->m_dstFolder->CopyMessages + (copySource->m_msgFolder, copySource->m_messageArray, + copyRequest->m_isMoveOrDraftOrTemplate, + copyRequest->m_msgWindow, copyRequest->m_listener, false, copyRequest->m_allowUndo); //isFolder operation false + + } + else if (copyRequest->m_requestType == nsCopyFoldersType) + { + NS_ENSURE_STATE(copySource); + copySource->m_processed = true; + rv = copyRequest->m_dstFolder->CopyFolder + (copySource->m_msgFolder, + copyRequest->m_isMoveOrDraftOrTemplate, + copyRequest->m_msgWindow, copyRequest->m_listener); + // If it's a copy folder operation and the destination + // folder already exists, CopyFolder() returns an error w/o sending + // a completion notification, so clear it here. + if (NS_FAILED(rv)) + ClearRequest(copyRequest, rv); + + } + else if (copyRequest->m_requestType == nsCopyFileMessageType) + { + nsCOMPtr<nsIFile> aFile(do_QueryInterface(copyRequest->m_srcSupport, &rv)); + if (NS_SUCCEEDED(rv)) + { + // ** in case of saving draft/template; the very first + // time we may not have the original message to replace + // with; if we do we shall have an instance of copySource + nsCOMPtr<nsIMsgDBHdr> aMessage; + if (copySource) + { + aMessage = do_QueryElementAt(copySource->m_messageArray, + 0, &rv); + copySource->m_processed = true; + } + copyRequest->m_processed = true; + rv = copyRequest->m_dstFolder->CopyFileMessage + (aFile, aMessage, + copyRequest->m_isMoveOrDraftOrTemplate, + copyRequest->m_newMsgFlags, + copyRequest->m_newMsgKeywords, + copyRequest->m_msgWindow, + copyRequest->m_listener); + } + } + } + } + return rv; +} + +/** + * Find a request in m_copyRequests which matches the passed in source + * and destination folders. + * + * @param aSupport the iSupports of the source folder. + * @param dstFolder the destination folder of the copy request. + */ +nsCopyRequest* +nsMsgCopyService::FindRequest(nsISupports* aSupport, + nsIMsgFolder* dstFolder) +{ + nsCopyRequest* copyRequest = nullptr; + uint32_t cnt = m_copyRequests.Length(); + for (uint32_t i = 0; i < cnt; i++) + { + copyRequest = m_copyRequests.ElementAt(i); + if (copyRequest->m_requestType == nsCopyFoldersType) + { + // If the src is different then check next request. + if (copyRequest->m_srcSupport.get() != aSupport) + { + copyRequest = nullptr; + continue; + } + + // See if the parent of the copied folder is the same as the one when the request was made. + // Note if the destination folder is already a server folder then no need to get parent. + nsCOMPtr <nsIMsgFolder> parentMsgFolder; + nsresult rv = NS_OK; + bool isServer=false; + dstFolder->GetIsServer(&isServer); + if (!isServer) + rv = dstFolder->GetParent(getter_AddRefs(parentMsgFolder)); + if ((NS_FAILED(rv)) || (!parentMsgFolder && !isServer) || (copyRequest->m_dstFolder.get() != parentMsgFolder)) + { + copyRequest = nullptr; + continue; + } + + // Now checks if the folder name is the same. + nsString folderName; + rv = dstFolder->GetName(folderName); + if (NS_FAILED(rv)) + { + copyRequest = nullptr; + continue; + } + + if (copyRequest->m_dstFolderName == folderName) + break; + } + else if (copyRequest->m_srcSupport.get() == aSupport && + copyRequest->m_dstFolder.get() == dstFolder) + break; + else + copyRequest = nullptr; + } + + return copyRequest; +} + +NS_IMPL_ISUPPORTS(nsMsgCopyService, nsIMsgCopyService) + +NS_IMETHODIMP +nsMsgCopyService::CopyMessages(nsIMsgFolder* srcFolder, /* UI src folder */ + nsIArray* messages, + nsIMsgFolder* dstFolder, + bool isMove, + nsIMsgCopyServiceListener* listener, + nsIMsgWindow* window, + bool allowUndo) +{ + NS_ENSURE_ARG_POINTER(srcFolder); + NS_ENSURE_ARG_POINTER(messages); + NS_ENSURE_ARG_POINTER(dstFolder); + + MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Debug, ("CopyMessages")); + + if (srcFolder == dstFolder) + { + NS_ERROR("src and dest folders for msg copy can't be the same"); + return NS_ERROR_FAILURE; + } + nsCopyRequest* copyRequest; + nsCopySource* copySource = nullptr; + nsCOMArray<nsIMsgDBHdr> msgArray; + uint32_t cnt; + nsCOMPtr<nsIMsgDBHdr> msg; + nsCOMPtr<nsIMsgFolder> curFolder; + nsCOMPtr<nsISupports> aSupport; + nsresult rv; + + // XXX TODO + // JUNK MAIL RELATED + // make sure dest folder exists + // and has proper flags, before we start copying? + + // bail early if nothing to do + messages->GetLength(&cnt); + if (!cnt) + { + if (listener) + { + listener->OnStartCopy(); + listener->OnStopCopy(NS_OK); + } + return NS_OK; + } + + copyRequest = new nsCopyRequest(); + if (!copyRequest) + return NS_ERROR_OUT_OF_MEMORY; + + aSupport = do_QueryInterface(srcFolder, &rv); + + rv = copyRequest->Init(nsCopyMessagesType, aSupport, dstFolder, isMove, + 0 /* new msg flags, not used */, EmptyCString(), + listener, window, allowUndo); + if (NS_FAILED(rv)) + goto done; + + if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info)) + LogCopyRequest("CopyMessages request", copyRequest); + + // duplicate the message array so we could sort the messages by it's + // folder easily + for (uint32_t i = 0; i < cnt; i++) + { + nsCOMPtr<nsIMsgDBHdr> currMsg = do_QueryElementAt(messages, i); + msgArray.AppendObject(currMsg); + } + + cnt = msgArray.Count(); + + while (cnt-- > 0) + { + msg = msgArray[cnt]; + rv = msg->GetFolder(getter_AddRefs(curFolder)); + + if (NS_FAILED(rv)) + goto done; + if (!copySource) + { + copySource = copyRequest->AddNewCopySource(curFolder); + if (!copySource) + { + rv = NS_ERROR_OUT_OF_MEMORY; + goto done; + } + } + + if (curFolder == copySource->m_msgFolder) + { + copySource->AddMessage(msg); + msgArray.RemoveObjectAt(cnt); + } + + if (cnt == 0) + { + cnt = msgArray.Count(); + if (cnt > 0) + copySource = nullptr; // * force to create a new one and + // * continue grouping the messages + } + } + + // undo stuff + if (NS_SUCCEEDED(rv) && copyRequest->m_allowUndo && copyRequest->m_copySourceArray.Length() > 1 && + copyRequest->m_txnMgr) + copyRequest->m_txnMgr->BeginBatch(nullptr); + +done: + + if (NS_FAILED(rv)) + delete copyRequest; + else + rv = DoCopy(copyRequest); + + return rv; +} + +NS_IMETHODIMP +nsMsgCopyService::CopyFolders(nsIArray* folders, + nsIMsgFolder* dstFolder, + bool isMove, + nsIMsgCopyServiceListener* listener, + nsIMsgWindow* window) +{ + NS_ENSURE_ARG_POINTER(folders); + NS_ENSURE_ARG_POINTER(dstFolder); + nsCopyRequest* copyRequest; + nsCopySource* copySource = nullptr; + nsresult rv; + uint32_t cnt; + nsCOMPtr<nsIMsgFolder> curFolder; + nsCOMPtr<nsISupports> support; + + rv = folders->GetLength(&cnt); //if cnt is zero it cannot to get this point, will be detected earlier + if (cnt > 1) + NS_ASSERTION((NS_SUCCEEDED(rv)),"More than one folders to copy"); + + support = do_QueryElementAt(folders, 0); + + copyRequest = new nsCopyRequest(); + if (!copyRequest) return NS_ERROR_OUT_OF_MEMORY; + + rv = copyRequest->Init(nsCopyFoldersType, support, dstFolder, + isMove, 0 /* new msg flags, not used */ , EmptyCString(), listener, window, false); + NS_ENSURE_SUCCESS(rv, rv); + + curFolder = do_QueryInterface(support, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + copySource = copyRequest->AddNewCopySource(curFolder); + if (!copySource) + rv = NS_ERROR_OUT_OF_MEMORY; + + if (NS_FAILED(rv)) + { + delete copyRequest; + NS_ENSURE_SUCCESS(rv, rv); + } + else + rv = DoCopy(copyRequest); + + return rv; +} + +NS_IMETHODIMP +nsMsgCopyService::CopyFileMessage(nsIFile* file, + nsIMsgFolder* dstFolder, + nsIMsgDBHdr* msgToReplace, + bool isDraft, + uint32_t aMsgFlags, + const nsACString &aNewMsgKeywords, + nsIMsgCopyServiceListener* listener, + nsIMsgWindow* window) +{ + nsresult rv = NS_ERROR_NULL_POINTER; + nsCopyRequest* copyRequest; + nsCopySource* copySource = nullptr; + nsCOMPtr<nsISupports> fileSupport; + nsCOMPtr<nsITransactionManager> txnMgr; + + NS_ENSURE_ARG_POINTER(file); + NS_ENSURE_ARG_POINTER(dstFolder); + + if (window) + window->GetTransactionManager(getter_AddRefs(txnMgr)); + copyRequest = new nsCopyRequest(); + if (!copyRequest) return rv; + fileSupport = do_QueryInterface(file, &rv); + if (NS_FAILED(rv)) goto done; + + rv = copyRequest->Init(nsCopyFileMessageType, fileSupport, dstFolder, + isDraft, aMsgFlags, aNewMsgKeywords, listener, window, false); + if (NS_FAILED(rv)) goto done; + + if (msgToReplace) + { + // The actual source of the message is a file not a folder, but + // we still need an nsCopySource to reference the old message header + // which will be used to recover message metadata. + copySource = copyRequest->AddNewCopySource(nullptr); + if (!copySource) + { + rv = NS_ERROR_OUT_OF_MEMORY; + goto done; + } + copySource->AddMessage(msgToReplace); + } + +done: + if (NS_FAILED(rv)) + { + delete copyRequest; + } + else + { + rv = DoCopy(copyRequest); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgCopyService::NotifyCompletion(nsISupports* aSupport, + nsIMsgFolder* dstFolder, + nsresult result) +{ + if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info)) + LogCopyCompletion(aSupport, dstFolder); + nsCopyRequest* copyRequest = nullptr; + uint32_t numOrigRequests = m_copyRequests.Length(); + do + { + // loop for copy requests, because if we do a cross server folder copy, + // we'll have a copy request for the folder copy, which will in turn + // generate a copy request for the messages in the folder, which + // will have the same src support. + copyRequest = FindRequest(aSupport, dstFolder); + + if (copyRequest) + { + // ClearRequest can cause a new request to get added to m_copyRequests + // with matching source and dest folders if the copy listener starts + // a new copy. We want to ignore any such request here, because it wasn't + // the one that was completed. So we keep track of how many original + // requests there were. + if (m_copyRequests.IndexOf(copyRequest) >= numOrigRequests) + break; + // check if this copy request is done by making sure all the + // sources have been processed. + int32_t sourceIndex, sourceCount; + sourceCount = copyRequest->m_copySourceArray.Length(); + for (sourceIndex = 0; sourceIndex < sourceCount;) + { + if (!(copyRequest->m_copySourceArray.ElementAt(sourceIndex))->m_processed) + break; + sourceIndex++; + } + // if all sources processed, mark the request as processed + if (sourceIndex >= sourceCount) + copyRequest->m_processed = true; + // if this request is done, or failed, clear it. + if (copyRequest->m_processed || NS_FAILED(result)) + { + ClearRequest(copyRequest, result); + numOrigRequests--; + } + else + break; + } + else + break; + } + while (copyRequest); + + return DoNextCopy(); +} + diff --git a/mailnews/base/src/nsMsgCopyService.h b/mailnews/base/src/nsMsgCopyService.h new file mode 100644 index 0000000000..dfb9acc7a9 --- /dev/null +++ b/mailnews/base/src/nsMsgCopyService.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsMsgCopyService_h__ +#define nsMsgCopyService_h__ + +#include "nscore.h" +#include "nsIMsgCopyService.h" +#include "nsCOMPtr.h" +#include "nsIMsgFolder.h" +#include "nsIMsgHdr.h" +#include "nsIMsgWindow.h" +#include "nsIMutableArray.h" +#include "nsITransactionManager.h" +#include "nsTArray.h" + +typedef enum _nsCopyRequestType +{ + nsCopyMessagesType = 0x0, + nsCopyFileMessageType = 0x1, + nsCopyFoldersType = 0x2 +} nsCopyRequestType; + +class nsCopyRequest; + +class nsCopySource +{ +public: + nsCopySource(); + nsCopySource(nsIMsgFolder* srcFolder); + ~nsCopySource(); + void AddMessage(nsIMsgDBHdr* aMsg); + + nsCOMPtr<nsIMsgFolder> m_msgFolder; + nsCOMPtr<nsIMutableArray> m_messageArray; + bool m_processed; +}; + +class nsCopyRequest +{ +public: + nsCopyRequest(); + ~nsCopyRequest(); + + nsresult Init(nsCopyRequestType type, nsISupports* aSupport, + nsIMsgFolder* dstFolder, + bool bVal, uint32_t newMsgFlags, + const nsACString &newMsgKeywords, + nsIMsgCopyServiceListener* listener, + nsIMsgWindow *msgWindow, bool allowUndo); + nsCopySource* AddNewCopySource(nsIMsgFolder* srcFolder); + + nsCOMPtr<nsISupports> m_srcSupport; // ui source folder or file spec + nsCOMPtr<nsIMsgFolder> m_dstFolder; + nsCOMPtr<nsIMsgWindow> m_msgWindow; + nsCOMPtr<nsIMsgCopyServiceListener> m_listener; + nsCOMPtr<nsITransactionManager> m_txnMgr; + nsCopyRequestType m_requestType; + bool m_isMoveOrDraftOrTemplate; + bool m_allowUndo; + bool m_processed; + uint32_t m_newMsgFlags; + nsCString m_newMsgKeywords; + nsString m_dstFolderName; // used for copy folder. + nsTArray<nsCopySource*> m_copySourceArray; // array of nsCopySource +}; + +class nsMsgCopyService : public nsIMsgCopyService +{ +public: + nsMsgCopyService(); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIMSGCOPYSERVICE + +private: + virtual ~nsMsgCopyService(); + + nsresult ClearRequest(nsCopyRequest* aRequest, nsresult rv); + nsresult DoCopy(nsCopyRequest* aRequest); + nsresult DoNextCopy(); + nsCopyRequest* FindRequest(nsISupports* aSupport, nsIMsgFolder* dstFolder); + nsresult QueueRequest(nsCopyRequest* aRequest, bool *aCopyImmediately); + void LogCopyCompletion(nsISupports *aSrc, nsIMsgFolder *aDest); + void LogCopyRequest(const char *logMsg, nsCopyRequest* aRequest); + + nsTArray<nsCopyRequest*> m_copyRequests; +}; + + +#endif diff --git a/mailnews/base/src/nsMsgDBView.cpp b/mailnews/base/src/nsMsgDBView.cpp new file mode 100644 index 0000000000..f7856dd4d9 --- /dev/null +++ b/mailnews/base/src/nsMsgDBView.cpp @@ -0,0 +1,8066 @@ +/* -*- 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 "msgCore.h" +#include "prmem.h" +#include "nsArrayUtils.h" +#include "nsIMsgCustomColumnHandler.h" +#include "nsMsgDBView.h" +#include "nsISupports.h" +#include "nsIMsgFolder.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgFolder.h" +#include "MailNewsTypes2.h" +#include "nsMsgUtils.h" +#include "nsQuickSort.h" +#include "nsIMsgImapMailFolder.h" +#include "nsImapCore.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIDOMElement.h" +#include "nsDateTimeFormatCID.h" +#include "nsMsgMimeCID.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsIMsgSearchSession.h" +#include "nsIMsgCopyService.h" +#include "nsMsgBaseCID.h" +#include "nsISpamSettings.h" +#include "nsIMsgAccountManager.h" +#include "nsITreeColumns.h" +#include "nsTextFormatter.h" +#include "nsIMutableArray.h" +#include "nsIMimeConverter.h" +#include "nsMsgMessageFlags.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsMsgDBCID.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "nsAlgorithm.h" +#include "nsIAbManager.h" +#include "nsIAbDirectory.h" +#include "nsIAbCard.h" +#include "mozilla/Services.h" +#include "mozilla/Attributes.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsTArray.h" +#include <algorithm> + +using namespace mozilla::mailnews; +nsrefcnt nsMsgDBView::gInstanceCount = 0; + +nsIAtom * nsMsgDBView::kJunkMsgAtom = nullptr; +nsIAtom * nsMsgDBView::kNotJunkMsgAtom = nullptr; + +char16_t * nsMsgDBView::kHighestPriorityString = nullptr; +char16_t * nsMsgDBView::kHighPriorityString = nullptr; +char16_t * nsMsgDBView::kLowestPriorityString = nullptr; +char16_t * nsMsgDBView::kLowPriorityString = nullptr; +char16_t * nsMsgDBView::kNormalPriorityString = nullptr; +char16_t * nsMsgDBView::kReadString = nullptr; +char16_t * nsMsgDBView::kRepliedString = nullptr; +char16_t * nsMsgDBView::kForwardedString = nullptr; +char16_t * nsMsgDBView::kNewString = nullptr; + +nsDateFormatSelector nsMsgDBView::m_dateFormatDefault = kDateFormatShort; +nsDateFormatSelector nsMsgDBView::m_dateFormatThisWeek = kDateFormatShort; +nsDateFormatSelector nsMsgDBView::m_dateFormatToday = kDateFormatNone; + +static const uint32_t kMaxNumSortColumns = 2; + +static void GetCachedName(const nsCString& unparsedString, + int32_t displayVersion, nsACString& cachedName); + +static void UpdateCachedName(nsIMsgDBHdr *aHdr, const char *header_field, + const nsAString& newName); + +// this is passed into NS_QuickSort as custom data. +class viewSortInfo +{ +public: + nsMsgDBView *view; + nsIMsgDatabase *db; + bool isSecondarySort; + bool ascendingSort; +}; + + +NS_IMPL_ADDREF(nsMsgDBView) +NS_IMPL_RELEASE(nsMsgDBView) + +NS_INTERFACE_MAP_BEGIN(nsMsgDBView) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgDBView) + NS_INTERFACE_MAP_ENTRY(nsIMsgDBView) + NS_INTERFACE_MAP_ENTRY(nsIDBChangeListener) + NS_INTERFACE_MAP_ENTRY(nsITreeView) + NS_INTERFACE_MAP_ENTRY(nsIJunkMailClassificationListener) +NS_INTERFACE_MAP_END + +nsMsgDBView::nsMsgDBView() +{ + /* member initializers and constructor code */ + m_sortValid = false; + m_checkedCustomColumns = false; + m_sortOrder = nsMsgViewSortOrder::none; + m_viewFlags = nsMsgViewFlagsType::kNone; + m_secondarySort = nsMsgViewSortType::byId; + m_secondarySortOrder = nsMsgViewSortOrder::ascending; + m_cachedMsgKey = nsMsgKey_None; + m_currentlyDisplayedMsgKey = nsMsgKey_None; + m_currentlyDisplayedViewIndex = nsMsgViewIndex_None; + mNumSelectedRows = 0; + mSuppressMsgDisplay = false; + mSuppressCommandUpdating = false; + mSuppressChangeNotification = false; + mSummarizeFailed = false; + mSelectionSummarized = false; + mGoForwardEnabled = false; + mGoBackEnabled = false; + + mIsNews = false; + mIsRss = false; + mIsXFVirtual = false; + mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; + m_deletingRows = false; + mNumMessagesRemainingInBatch = 0; + mShowSizeInLines = false; + mSortThreadsByRoot = false; + + /* mCommandsNeedDisablingBecauseOfSelection - A boolean that tell us if we needed to disable commands because of what's selected. + If we're offline w/o a downloaded msg selected, or a dummy message was selected. + */ + + mCommandsNeedDisablingBecauseOfSelection = false; + mRemovingRow = false; + m_saveRestoreSelectionDepth = 0; + mRecentlyDeletedArrayIndex = 0; + // initialize any static atoms or unicode strings + if (gInstanceCount == 0) + { + InitializeAtomsAndLiterals(); + InitDisplayFormats(); + } + + InitLabelStrings(); + gInstanceCount++; +} + +void nsMsgDBView::InitializeAtomsAndLiterals() +{ + kJunkMsgAtom = MsgNewAtom("junk").take(); + kNotJunkMsgAtom = MsgNewAtom("notjunk").take(); + + // priority strings + kHighestPriorityString = GetString(u"priorityHighest"); + kHighPriorityString = GetString(u"priorityHigh"); + kLowestPriorityString = GetString(u"priorityLowest"); + kLowPriorityString = GetString(u"priorityLow"); + kNormalPriorityString = GetString(u"priorityNormal"); + + kReadString = GetString(u"read"); + kRepliedString = GetString(u"replied"); + kForwardedString = GetString(u"forwarded"); + kNewString = GetString(u"new"); +} + +nsMsgDBView::~nsMsgDBView() +{ + if (m_db) + m_db->RemoveListener(this); + + gInstanceCount--; + if (gInstanceCount <= 0) + { + NS_IF_RELEASE(kJunkMsgAtom); + NS_IF_RELEASE(kNotJunkMsgAtom); + + NS_Free(kHighestPriorityString); + NS_Free(kHighPriorityString); + NS_Free(kLowestPriorityString); + NS_Free(kLowPriorityString); + NS_Free(kNormalPriorityString); + + NS_Free(kReadString); + NS_Free(kRepliedString); + NS_Free(kForwardedString); + NS_Free(kNewString); + } +} + +nsresult nsMsgDBView::InitLabelStrings() +{ + nsresult rv = NS_OK; + nsCString prefString; + + for(int32_t i = 0; i < PREF_LABELS_MAX; i++) + { + prefString.Assign(PREF_LABELS_DESCRIPTION); + prefString.AppendInt(i + 1); + rv = GetPrefLocalizedString(prefString.get(), mLabelPrefDescriptions[i]); + } + return rv; +} + +// helper function used to fetch strings from the messenger string bundle +char16_t * nsMsgDBView::GetString(const char16_t *aStringName) +{ + nsresult res = NS_ERROR_UNEXPECTED; + char16_t *ptrv = nullptr; + + if (!mMessengerStringBundle) + { + static const char propertyURL[] = MESSENGER_STRING_URL; + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) + res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(mMessengerStringBundle)); + } + + if (mMessengerStringBundle) + res = mMessengerStringBundle->GetStringFromName(aStringName, &ptrv); + + if ( NS_SUCCEEDED(res) && (ptrv) ) + return ptrv; + else + return NS_strdup(aStringName); +} + +// helper function used to fetch localized strings from the prefs +nsresult nsMsgDBView::GetPrefLocalizedString(const char *aPrefName, nsString& aResult) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIPrefBranch> prefBranch; + nsCOMPtr<nsIPrefLocalizedString> pls; + nsString ucsval; + + prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = prefBranch->GetComplexValue(aPrefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls)); + NS_ENSURE_SUCCESS(rv, rv); + pls->ToString(getter_Copies(ucsval)); + aResult = ucsval.get(); + return rv; +} + +nsresult nsMsgDBView::AppendKeywordProperties(const nsACString& keywords, nsAString& properties, bool addSelectedTextProperty) +{ + // get the top most keyword's color and append that as a property. + nsresult rv; + if (!mTagService) + { + mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCString topKey; + rv = mTagService->GetTopKey(keywords, topKey); + NS_ENSURE_SUCCESS(rv, rv); + if (topKey.IsEmpty()) + return NS_OK; + + nsCString color; + rv = mTagService->GetColorForKey(topKey, color); + if (NS_SUCCEEDED(rv) && !color.IsEmpty()) + { + if (addSelectedTextProperty) + { + if (color.EqualsLiteral(LABEL_COLOR_WHITE_STRING)) + properties.AppendLiteral(" lc-black"); + else + properties.AppendLiteral(" lc-white"); + } + color.Replace(0, 1, NS_LITERAL_CSTRING(LABEL_COLOR_STRING)); + properties.AppendASCII(color.get()); + } + return rv; +} + +/////////////////////////////////////////////////////////////////////////// +// nsITreeView Implementation Methods (and helper methods) +/////////////////////////////////////////////////////////////////////////// + +static nsresult GetDisplayNameInAddressBook(const nsACString& emailAddress, + nsAString& displayName) +{ + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService("@mozilla.org/abmanager;1", + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = abManager->GetDirectories(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIAbDirectory> directory; + nsCOMPtr<nsIAbCard> cardForAddress; + bool hasMore; + + // Scan the addressbook to find out the card of the email address + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && + hasMore && !cardForAddress) + { + rv = enumerator->GetNext(getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + directory = do_QueryInterface(supports); + if (directory) + { + rv = directory->CardForEmailAddress(emailAddress, + getter_AddRefs(cardForAddress)); + + if (NS_SUCCEEDED(rv) && cardForAddress) + break; // the card is found,so stop looping + } + } + + if (cardForAddress) + { + bool preferDisplayName = true; + cardForAddress->GetPropertyAsBool("PreferDisplayName",&preferDisplayName); + + if (preferDisplayName) + rv = cardForAddress->GetDisplayName(displayName); + } + + return rv; +} + +/* The unparsedString has following format + * "version|displayname" + */ +static void GetCachedName(const nsCString& unparsedString, + int32_t displayVersion, nsACString& cachedName) +{ + nsresult err; + + //get verion # + int32_t cachedVersion = unparsedString.ToInteger(&err); + if (cachedVersion != displayVersion) + return; + + //get cached name + int32_t pos = unparsedString.FindChar('|'); + if (pos != kNotFound) + cachedName = Substring(unparsedString, pos + 1); +} + +static void UpdateCachedName(nsIMsgDBHdr *aHdr, const char *header_field, + const nsAString& newName) +{ + nsCString newCachedName; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + int32_t currentDisplayNameVersion = 0; + + prefs->GetIntPref("mail.displayname.version", ¤tDisplayNameVersion); + + // save version number + newCachedName.AppendInt(currentDisplayNameVersion); + newCachedName.Append("|"); + + // save name + newCachedName.Append(NS_ConvertUTF16toUTF8(newName)); + + aHdr->SetStringProperty(header_field,newCachedName.get()); +} + +nsresult nsMsgDBView::FetchAuthor(nsIMsgDBHdr * aHdr, nsAString &aSenderString) +{ + nsCString unparsedAuthor; + bool showCondensedAddresses = false; + int32_t currentDisplayNameVersion = 0; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + prefs->GetIntPref("mail.displayname.version", ¤tDisplayNameVersion); + prefs->GetBoolPref("mail.showCondensedAddresses", &showCondensedAddresses); + + aHdr->GetStringProperty("sender_name", getter_Copies(unparsedAuthor)); + + // if the author is already computed, use it + if (!unparsedAuthor.IsEmpty()) + { + nsCString cachedDisplayName; + + GetCachedName(unparsedAuthor, currentDisplayNameVersion, cachedDisplayName); + if (!cachedDisplayName.IsEmpty()) + { + CopyUTF8toUTF16(cachedDisplayName, aSenderString); + return NS_OK; + } + } + + nsCString author; + (void) aHdr->GetAuthor(getter_Copies(author)); + + nsCString headerCharset; + aHdr->GetEffectiveCharset(headerCharset); + + nsCString emailAddress; + nsString name; + ExtractFirstAddress(EncodedHeader(author, headerCharset.get()), name, + emailAddress); + + if (showCondensedAddresses) + GetDisplayNameInAddressBook(emailAddress, aSenderString); + + if (aSenderString.IsEmpty()) + { + // We can't use the display name in the card; use the name contained in + // the header or email address. + if (name.IsEmpty()) { + CopyUTF8toUTF16(emailAddress, aSenderString); + } else { + int32_t atPos; + if ((atPos = name.FindChar('@')) == kNotFound || + name.FindChar('.', atPos) == kNotFound) { + aSenderString = name; + } else { + // Found @ followed by a dot, so this looks like a spoofing case. + aSenderString = name; + aSenderString.AppendLiteral(" <"); + AppendUTF8toUTF16(emailAddress, aSenderString); + aSenderString.Append('>'); + } + } + } + + UpdateCachedName(aHdr, "sender_name", aSenderString); + + return NS_OK; +} + +nsresult nsMsgDBView::FetchAccount(nsIMsgDBHdr * aHdr, nsAString& aAccount) +{ + nsCString accountKey; + + nsresult rv = aHdr->GetAccountKey(getter_Copies(accountKey)); + + // Cache the account manager? + nsCOMPtr<nsIMsgAccountManager> accountManager( + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgAccount> account; + nsCOMPtr<nsIMsgIncomingServer> server; + if (!accountKey.IsEmpty()) + rv = accountManager->GetAccount(accountKey, getter_AddRefs(account)); + + if (account) + { + account->GetIncomingServer(getter_AddRefs(server)); + } + else + { + nsCOMPtr<nsIMsgFolder> folder; + aHdr->GetFolder(getter_AddRefs(folder)); + if (folder) + folder->GetServer(getter_AddRefs(server)); + } + if (server) + server->GetPrettyName(aAccount); + else + CopyASCIItoUTF16(accountKey, aAccount); + return NS_OK; +} + +nsresult nsMsgDBView::FetchRecipients(nsIMsgDBHdr * aHdr, nsAString &aRecipientsString) +{ + nsCString recipients; + int32_t currentDisplayNameVersion = 0; + bool showCondensedAddresses = false; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + prefs->GetIntPref("mail.displayname.version", ¤tDisplayNameVersion); + prefs->GetBoolPref("mail.showCondensedAddresses", &showCondensedAddresses); + + aHdr->GetStringProperty("recipient_names", getter_Copies(recipients)); + + if (!recipients.IsEmpty()) + { + nsCString cachedRecipients; + + GetCachedName(recipients, currentDisplayNameVersion, cachedRecipients); + + // recipients have already been cached,check if the addressbook + // was changed after cache. + if (!cachedRecipients.IsEmpty()) + { + CopyUTF8toUTF16(cachedRecipients, aRecipientsString); + return NS_OK; + } + } + + nsCString unparsedRecipients; + nsresult rv = aHdr->GetRecipients(getter_Copies(unparsedRecipients)); + + nsCString headerCharset; + aHdr->GetEffectiveCharset(headerCharset); + + nsTArray<nsString> names; + nsTArray<nsCString> emails; + ExtractAllAddresses(EncodedHeader(unparsedRecipients, headerCharset.get()), + names, UTF16ArrayAdapter<>(emails)); + + uint32_t numAddresses = names.Length(); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsCOMPtr<nsIAbManager> + abManager(do_GetService("@mozilla.org/abmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, NS_OK); + + // go through each email address in the recipients and + // compute its display name. + for (uint32_t i = 0; i < numAddresses; i++) + { + nsString recipient; + nsCString &curAddress = emails[i]; + nsString &curName = names[i]; + + if (showCondensedAddresses) + GetDisplayNameInAddressBook(curAddress, recipient); + + if (recipient.IsEmpty()) + { + // we can't use the display name in the card, + // use the name contained in the header or email address. + if (curName.IsEmpty()) { + CopyUTF8toUTF16(curAddress, recipient); + } else { + int32_t atPos; + if ((atPos = curName.FindChar('@')) == kNotFound || + curName.FindChar('.', atPos) == kNotFound) { + recipient = curName; + } else { + // Found @ followed by a dot, so this looks like a spoofing case. + recipient = curName; + recipient.AppendLiteral(" <"); + AppendUTF8toUTF16(curAddress, recipient); + recipient.Append('>'); + } + } + } + + // add ', ' between each recipient + if (i != 0) + aRecipientsString.Append(NS_LITERAL_STRING(", ")); + + aRecipientsString.Append(recipient); + } + + if (numAddresses == 0 && unparsedRecipients.FindChar(':') != kNotFound) { + // No addresses and a colon, so an empty group like "undisclosed-recipients: ;". + // Add group name so at least something displays. + nsString group; + CopyUTF8toUTF16(unparsedRecipients, group); + aRecipientsString.Assign(group); + } + + UpdateCachedName(aHdr, "recipient_names", aRecipientsString); + + return NS_OK; +} + +nsresult nsMsgDBView::FetchSubject(nsIMsgDBHdr * aMsgHdr, uint32_t aFlags, nsAString &aValue) +{ + if (aFlags & nsMsgMessageFlags::HasRe) + { + nsString subject; + aMsgHdr->GetMime2DecodedSubject(subject); + aValue.AssignLiteral("Re: "); + aValue.Append(subject); + } + else + aMsgHdr->GetMime2DecodedSubject(aValue); + return NS_OK; +} + +// in case we want to play around with the date string, I've broken it out into +// a separate routine. Set rcvDate to true to get the Received: date instead +// of the Date: date. +nsresult nsMsgDBView::FetchDate(nsIMsgDBHdr * aHdr, nsAString &aDateString, bool rcvDate) +{ + PRTime dateOfMsg; + PRTime dateOfMsgLocal; + uint32_t rcvDateSecs; + nsresult rv; + + if (!mDateFormatter) + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID); + + NS_ENSURE_TRUE(mDateFormatter, NS_ERROR_FAILURE); + // Silently return Date: instead if Received: is unavailable + if (rcvDate) + { + rv = aHdr->GetUint32Property("dateReceived", &rcvDateSecs); + if (rcvDateSecs != 0) + Seconds2PRTime(rcvDateSecs, &dateOfMsg); + } + if (!rcvDate || rcvDateSecs == 0) + rv = aHdr->GetDate(&dateOfMsg); + + PRTime currentTime = PR_Now(); + PRExplodedTime explodedCurrentTime; + PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &explodedCurrentTime); + PRExplodedTime explodedMsgTime; + PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime); + + // if the message is from today, don't show the date, only the time. (i.e. 3:15 pm) + // if the message is from the last week, show the day of the week. (i.e. Mon 3:15 pm) + // in all other cases, show the full date (03/19/01 3:15 pm) + + nsDateFormatSelector dateFormat = m_dateFormatDefault; + if (explodedCurrentTime.tm_year == explodedMsgTime.tm_year && + explodedCurrentTime.tm_month == explodedMsgTime.tm_month && + explodedCurrentTime.tm_mday == explodedMsgTime.tm_mday) + { + // same day... + dateFormat = m_dateFormatToday; + } + // the following chunk of code allows us to show a day instead of a number if the message was received + // within the last 7 days. i.e. Mon 5:10pm. (depending on the mail.ui.display.dateformat.thisweek pref) + // The concrete format used is dependent on a preference setting (see InitDisplayFormats). + else if (currentTime > dateOfMsg) + { + // Convert the times from GMT to local time + int64_t GMTLocalTimeShift = PR_USEC_PER_SEC * + int64_t(explodedCurrentTime.tm_params.tp_gmt_offset + + explodedCurrentTime.tm_params.tp_dst_offset); + currentTime += GMTLocalTimeShift; + dateOfMsgLocal = dateOfMsg + GMTLocalTimeShift; + + // Find the most recent midnight + int64_t todaysMicroSeconds = currentTime % PR_USEC_PER_DAY; + int64_t mostRecentMidnight = currentTime - todaysMicroSeconds; + + // most recent midnight minus 6 days + int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6); + + // was the message sent during the last week? + if (dateOfMsgLocal >= mostRecentWeek) + { // yes .... + dateFormat = m_dateFormatThisWeek; + } + } + + if (NS_SUCCEEDED(rv)) + rv = mDateFormatter->FormatPRTime(nullptr /* nsILocale* locale */, + dateFormat, + kTimeFormatNoSeconds, + dateOfMsg, + aDateString); + + return rv; +} + +nsresult nsMsgDBView::FetchStatus(uint32_t aFlags, nsAString &aStatusString) +{ + if (aFlags & nsMsgMessageFlags::Replied) + aStatusString = kRepliedString; + else if (aFlags & nsMsgMessageFlags::Forwarded) + aStatusString = kForwardedString; + else if (aFlags & nsMsgMessageFlags::New) + aStatusString = kNewString; + else if (aFlags & nsMsgMessageFlags::Read) + aStatusString = kReadString; + + return NS_OK; +} + +nsresult nsMsgDBView::FetchSize(nsIMsgDBHdr * aHdr, nsAString &aSizeString) +{ + nsresult rv; + nsAutoString formattedSizeString; + uint32_t msgSize = 0; + + // for news, show the line count, not the size if the user wants so + if (mShowSizeInLines) + { + aHdr->GetLineCount(&msgSize); + formattedSizeString.AppendInt(msgSize); + } + else + { + uint32_t flags = 0; + + aHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + aHdr->GetUint32Property("onlineSize", &msgSize); + + if (msgSize == 0) + aHdr->GetMessageSize(&msgSize); + + rv = FormatFileSize(msgSize, true, formattedSizeString); + NS_ENSURE_SUCCESS(rv, rv); + } + + aSizeString = formattedSizeString; + // the formattingString Length includes the null terminator byte! + if (!formattedSizeString.Last()) + aSizeString.SetLength(formattedSizeString.Length() - 1); + return NS_OK; +} + +nsresult nsMsgDBView::FetchPriority(nsIMsgDBHdr *aHdr, nsAString & aPriorityString) +{ + nsMsgPriorityValue priority = nsMsgPriority::notSet; + aHdr->GetPriority(&priority); + + switch (priority) + { + case nsMsgPriority::highest: + aPriorityString = kHighestPriorityString; + break; + case nsMsgPriority::high: + aPriorityString = kHighPriorityString; + break; + case nsMsgPriority::low: + aPriorityString = kLowPriorityString; + break; + case nsMsgPriority::lowest: + aPriorityString = kLowestPriorityString; + break; + case nsMsgPriority::normal: + aPriorityString = kNormalPriorityString; + break; + default: + break; + } + + return NS_OK; +} + +nsresult nsMsgDBView::FetchKeywords(nsIMsgDBHdr *aHdr, nsACString &keywordString) +{ + NS_ENSURE_ARG_POINTER(aHdr); + nsresult rv = NS_OK; + if (!mTagService) + { + mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsMsgLabelValue label = 0; + + rv = aHdr->GetLabel(&label); + nsCString keywords; + aHdr->GetStringProperty("keywords", getter_Copies(keywords)); + if (label > 0) + { + nsAutoCString labelStr("$label"); + labelStr.Append((char) (label + '0')); + if (keywords.Find(labelStr, CaseInsensitiveCompare) == -1) + { + if (!keywords.IsEmpty()) + keywords.Append(' '); + keywords.Append(labelStr); + } + } + keywordString = keywords; + return NS_OK; +} + +// If the row is a collapsed thread, we optionally roll-up the keywords in all +// the messages in the thread, otherwise, return just the keywords for the row. +nsresult nsMsgDBView::FetchRowKeywords(nsMsgViewIndex aRow, nsIMsgDBHdr *aHdr, + nsACString &keywordString) +{ + nsresult rv = FetchKeywords(aHdr,keywordString); + NS_ENSURE_SUCCESS(rv, rv); + + bool cascadeKeywordsUp = true; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + prefs->GetBoolPref("mailnews.display_reply_tag_colors_for_collapsed_threads", + &cascadeKeywordsUp); + + if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) && + cascadeKeywordsUp) + { + if ((m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD) + && (m_flags[aRow] & nsMsgMessageFlags::Elided)) + { + nsCOMPtr<nsIMsgThread> thread; + rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread)); + if (NS_SUCCEEDED(rv) && thread) + { + uint32_t numChildren; + thread->GetNumChildren(&numChildren); + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCString moreKeywords; + for (uint32_t index = 0; index < numChildren; index++) + { + thread->GetChildHdrAt(index, getter_AddRefs(msgHdr)); + rv = FetchKeywords(msgHdr, moreKeywords); + NS_ENSURE_SUCCESS(rv, rv); + + if (!keywordString.IsEmpty() && !moreKeywords.IsEmpty()) + keywordString.Append(' '); + keywordString.Append(moreKeywords); + } + } + } + } + return rv; +} + +nsresult nsMsgDBView::FetchTags(nsIMsgDBHdr *aHdr, nsAString &aTagString) +{ + NS_ENSURE_ARG_POINTER(aHdr); + nsresult rv = NS_OK; + if (!mTagService) + { + mTagService = do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsString tags; + nsCString keywords; + aHdr->GetStringProperty("keywords", getter_Copies(keywords)); + + nsMsgLabelValue label = 0; + rv = aHdr->GetLabel(&label); + if (label > 0) + { + nsAutoCString labelStr("$label"); + labelStr.Append((char) (label + '0')); + if (keywords.Find(labelStr, CaseInsensitiveCompare) == -1) + FetchLabel(aHdr, tags); + } + + nsTArray<nsCString> keywordsArray; + ParseString(keywords, ' ', keywordsArray); + nsAutoString tag; + + for (uint32_t i = 0; i < keywordsArray.Length(); i++) + { + rv = mTagService->GetTagForKey(keywordsArray[i], tag); + if (NS_SUCCEEDED(rv) && !tag.IsEmpty()) + { + if (!tags.IsEmpty()) + tags.Append((char16_t) ' '); + tags.Append(tag); + } + } + + aTagString = tags; + return NS_OK; +} + +nsresult nsMsgDBView::FetchLabel(nsIMsgDBHdr *aHdr, nsAString &aLabelString) +{ + nsresult rv = NS_OK; + nsMsgLabelValue label = 0; + + NS_ENSURE_ARG_POINTER(aHdr); + + rv = aHdr->GetLabel(&label); + NS_ENSURE_SUCCESS(rv, rv); + + // we don't care if label is not between 1 and PREF_LABELS_MAX inclusive. + if ((label < 1) || (label > PREF_LABELS_MAX)) + { + aLabelString.Truncate(); + return NS_OK; + } + + // We need to subtract 1 because mLabelPrefDescriptions is 0 based. + aLabelString = mLabelPrefDescriptions[label - 1]; + return NS_OK; +} + +bool nsMsgDBView::IsOutgoingMsg(nsIMsgDBHdr* aHdr) +{ + nsString author; + aHdr->GetMime2DecodedAuthor(author); + + nsCString emailAddress; + nsString name; + ExtractFirstAddress(DecodedHeader(author), name, emailAddress); + + return mEmails.Contains(emailAddress); +} + + +/*if you call SaveAndClearSelection make sure to call RestoreSelection otherwise +m_saveRestoreSelectionDepth will be incorrect and will lead to selection msg problems*/ + +nsresult nsMsgDBView::SaveAndClearSelection(nsMsgKey *aCurrentMsgKey, nsTArray<nsMsgKey> &aMsgKeyArray) +{ + // we don't do anything on nested Save / Restore calls. + m_saveRestoreSelectionDepth++; + if (m_saveRestoreSelectionDepth != 1) + return NS_OK; + + if (!mTreeSelection || !mTree) + return NS_OK; + + // first, freeze selection. + mTreeSelection->SetSelectEventsSuppressed(true); + + // second, save the current index. + if (aCurrentMsgKey) + { + int32_t currentIndex; + if (NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(¤tIndex)) && + currentIndex >= 0 && uint32_t(currentIndex) < GetSize()) + *aCurrentMsgKey = m_keys[currentIndex]; + else + *aCurrentMsgKey = nsMsgKey_None; + } + + // third, get an array of view indices for the selection. + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + int32_t numIndices = selection.Length(); + aMsgKeyArray.SetLength(numIndices); + + // now store the msg key for each selected item. + nsMsgKey msgKey; + for (int32_t index = 0; index < numIndices; index++) + { + msgKey = m_keys[selection[index]]; + aMsgKeyArray[index] = msgKey; + } + + // clear the selection, we'll manually restore it later. + if (mTreeSelection) + mTreeSelection->ClearSelection(); + + return NS_OK; +} + +nsresult nsMsgDBView::RestoreSelection(nsMsgKey aCurrentMsgKey, nsTArray<nsMsgKey> &aMsgKeyArray) +{ + // we don't do anything on nested Save / Restore calls. + m_saveRestoreSelectionDepth--; + if (m_saveRestoreSelectionDepth) + return NS_OK; + + if (!mTreeSelection) // don't assert. + return NS_OK; + + // turn our message keys into corresponding view indices + int32_t arraySize = aMsgKeyArray.Length(); + nsMsgViewIndex currentViewPosition = nsMsgViewIndex_None; + nsMsgViewIndex newViewPosition = nsMsgViewIndex_None; + + // if we are threaded, we need to do a little more work + // we need to find (and expand) all the threads that contain messages + // that we had selected before. + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + for (int32_t index = 0; index < arraySize; index ++) + { + FindKey(aMsgKeyArray[index], true /* expand */); + } + } + + for (int32_t index = 0; index < arraySize; index ++) + { + newViewPosition = FindKey(aMsgKeyArray[index], false); + // add the index back to the selection. + if (newViewPosition != nsMsgViewIndex_None) + mTreeSelection->ToggleSelect(newViewPosition); + } + + // make sure the currentView was preserved.... + if (aCurrentMsgKey != nsMsgKey_None) + currentViewPosition = FindKey(aCurrentMsgKey, true); + + if (mTree) + mTreeSelection->SetCurrentIndex(currentViewPosition); + + // make sure the current message is once again visible in the thread pane + // so we don't have to go search for it in the thread pane + if (mTree && currentViewPosition != nsMsgViewIndex_None) + mTree->EnsureRowIsVisible(currentViewPosition); + + // unfreeze selection. + mTreeSelection->SetSelectEventsSuppressed(false); + return NS_OK; +} + +nsresult nsMsgDBView::GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, nsACString & aURI) +{ + NS_ENSURE_ARG(folder); + return folder->GenerateMessageURI(aMsgKey, aURI); +} + +nsresult nsMsgDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator) +{ + return m_db->EnumerateMessages(enumerator); +} + +nsresult nsMsgDBView::CycleThreadedColumn(nsIDOMElement * aElement) +{ + nsAutoString currentView; + + // toggle threaded/unthreaded mode + aElement->GetAttribute(NS_LITERAL_STRING("currentView"), currentView); + if (currentView.EqualsLiteral("threaded")) + aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("unthreaded")); + else + aElement->SetAttribute(NS_LITERAL_STRING("currentView"), NS_LITERAL_STRING("threaded")); + + // i think we need to create a new view and switch it in this circumstance since + // we are toggline between threaded and non threaded mode. + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::IsEditable(int32_t row, nsITreeColumn* col, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(col); + NS_ENSURE_ARG_POINTER(_retval); + //attempt to retreive a custom column handler. If it exists call it and return + const char16_t* colID; + col->GetIdConst(&colID); + + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->IsEditable(row, col, _retval); + return NS_OK; + } + + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::IsSelectable(int32_t row, nsITreeColumn* col, bool* _retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetRowCount(int32_t *aRowCount) +{ + *aRowCount = GetSize(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSelection(nsITreeSelection * *aSelection) +{ + *aSelection = mTreeSelection; + NS_IF_ADDREF(*aSelection); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetSelection(nsITreeSelection * aSelection) +{ + mTreeSelection = aSelection; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::ReloadMessageWithAllParts() +{ + if (m_currentlyDisplayedMsgUri.IsEmpty() || mSuppressMsgDisplay) + return NS_OK; + + nsAutoCString forceAllParts(m_currentlyDisplayedMsgUri); + forceAllParts += (forceAllParts.FindChar('?') == kNotFound) ? '?' : '&'; + forceAllParts.AppendLiteral("fetchCompleteMessage=true"); + nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak)); + NS_ENSURE_TRUE(messenger, NS_ERROR_FAILURE); + + nsresult rv = messenger->OpenURL(forceAllParts); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateDisplayMessage(m_currentlyDisplayedViewIndex); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::ReloadMessage() +{ + if (m_currentlyDisplayedMsgUri.IsEmpty() || mSuppressMsgDisplay) + return NS_OK; + nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak)); + NS_ENSURE_TRUE(messenger, NS_ERROR_FAILURE); + + nsresult rv = messenger->OpenURL(m_currentlyDisplayedMsgUri); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateDisplayMessage(m_currentlyDisplayedViewIndex); + return NS_OK; +} + +nsresult nsMsgDBView::UpdateDisplayMessage(nsMsgViewIndex viewPosition) +{ + nsresult rv; + if (mCommandUpdater) + { + // get the subject and the folder for the message and inform the front end that + // we changed the message we are currently displaying. + if (viewPosition != nsMsgViewIndex_None) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgHdrForViewIndex(viewPosition, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + + nsString subject; + FetchSubject(msgHdr, m_flags[viewPosition], subject); + + nsCString keywords; + rv = msgHdr->GetStringProperty("keywords", getter_Copies(keywords)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgFolder> folder = m_viewFolder ? m_viewFolder : m_folder; + + mCommandUpdater->DisplayMessageChanged(folder, subject, keywords); + + if (folder) + { + rv = folder->SetLastMessageLoaded(m_keys[viewPosition]); + NS_ENSURE_SUCCESS(rv,rv); + } + } // if view position is valid + } // if we have an updater + return NS_OK; +} + +// given a msg key, we will load the message for it. +NS_IMETHODIMP nsMsgDBView::LoadMessageByMsgKey(nsMsgKey aMsgKey) +{ + return LoadMessageByViewIndex(FindKey(aMsgKey, false)); +} + +NS_IMETHODIMP nsMsgDBView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) +{ + NS_ASSERTION(aViewIndex != nsMsgViewIndex_None,"trying to load nsMsgViewIndex_None"); + if (aViewIndex == nsMsgViewIndex_None) return NS_ERROR_UNEXPECTED; + + nsCString uri; + nsresult rv = GetURIForViewIndex(aViewIndex, uri); + if (!mSuppressMsgDisplay && !m_currentlyDisplayedMsgUri.Equals(uri)) + { + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak)); + NS_ENSURE_TRUE(messenger, NS_ERROR_FAILURE); + messenger->OpenURL(uri); + m_currentlyDisplayedMsgKey = m_keys[aViewIndex]; + m_currentlyDisplayedMsgUri = uri; + m_currentlyDisplayedViewIndex = aViewIndex; + UpdateDisplayMessage(m_currentlyDisplayedViewIndex); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::LoadMessageByUrl(const char *aUrl) +{ + NS_ASSERTION(aUrl, "trying to load a null url"); + if (!mSuppressMsgDisplay) + { + nsresult rv; + nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + messenger->LoadURL(NULL, nsDependentCString(aUrl)); + m_currentlyDisplayedMsgKey = nsMsgKey_None; + m_currentlyDisplayedMsgUri = aUrl; + m_currentlyDisplayedViewIndex = nsMsgViewIndex_None; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SelectionChanged() +{ + // if the currentSelection changed then we have a message to display - not if we are in the middle of deleting rows + if (m_deletingRows) + return NS_OK; + + uint32_t numSelected = 0; + + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + nsMsgViewIndex *indices = selection.Elements(); + numSelected = selection.Length(); + + bool commandsNeedDisablingBecauseOfSelection = false; + + if(indices) + { + if (WeAreOffline()) + commandsNeedDisablingBecauseOfSelection = !OfflineMsgSelected(indices, numSelected); + if (!NonDummyMsgSelected(indices, numSelected)) + commandsNeedDisablingBecauseOfSelection = true; + } + bool selectionSummarized = false; + mSummarizeFailed = false; + // let the front-end adjust the message pane appropriately with either + // the message body, or a summary of the selection + if (mCommandUpdater) + { + mCommandUpdater->SummarizeSelection(&selectionSummarized); + // check if the selection was not summarized, but we expected it to be, + // and if so, remember it so GetHeadersFromSelection won't include + // the messages in collapsed threads. + if (!selectionSummarized && + (numSelected > 1 || (numSelected == 1 && + m_flags[indices[0]] & nsMsgMessageFlags::Elided && + OperateOnMsgsInCollapsedThreads()))) + mSummarizeFailed = true; + } + + bool summaryStateChanged = selectionSummarized != mSelectionSummarized; + + mSelectionSummarized = selectionSummarized; + // if only one item is selected then we want to display a message + if (mTreeSelection && numSelected == 1 && !selectionSummarized) + { + int32_t startRange; + int32_t endRange; + nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange); + NS_ENSURE_SUCCESS(rv, NS_OK); // tree doesn't care if we failed + + if (startRange >= 0 && startRange == endRange && + uint32_t(startRange) < GetSize()) + { + if (!mRemovingRow) + { + if (!mSuppressMsgDisplay) + LoadMessageByViewIndex(startRange); + else + UpdateDisplayMessage(startRange); + } + } + else + numSelected = 0; // selection seems bogus, so set to 0. + } + else { + // if we have zero or multiple items selected, we shouldn't be displaying any message + m_currentlyDisplayedMsgKey = nsMsgKey_None; + m_currentlyDisplayedMsgUri.Truncate(); + m_currentlyDisplayedViewIndex = nsMsgViewIndex_None; + } + + // determine if we need to push command update notifications out to the UI or not. + + // we need to push a command update notification iff, one of the following conditions are met + // (1) the selection went from 0 to 1 + // (2) it went from 1 to 0 + // (3) it went from 1 to many + // (4) it went from many to 1 or 0 + // (5) a different msg was selected - perhaps it was offline or not...matters only when we are offline + // (6) we did a forward/back, or went from having no history to having history - not sure how to tell this. + // (7) whether the selection was summarized or not changed. + + // I think we're going to need to keep track of whether forward/back were enabled/should be enabled, + // and when this changes, force a command update. + + bool enableGoForward = false; + bool enableGoBack = false; + + NavigateStatus(nsMsgNavigationType::forward, &enableGoForward); + NavigateStatus(nsMsgNavigationType::back, &enableGoBack); + if (!summaryStateChanged && + (numSelected == mNumSelectedRows || + (numSelected > 1 && mNumSelectedRows > 1)) && (commandsNeedDisablingBecauseOfSelection == mCommandsNeedDisablingBecauseOfSelection) + && enableGoForward == mGoForwardEnabled && enableGoBack == mGoBackEnabled) + { + } + // don't update commands if we're suppressing them, or if we're removing rows, unless it was the last row. + else if (!mSuppressCommandUpdating && mCommandUpdater && (!mRemovingRow || GetSize() == 0)) // o.t. push an updated + { + mCommandUpdater->UpdateCommandStatus(); + } + + mCommandsNeedDisablingBecauseOfSelection = commandsNeedDisablingBecauseOfSelection; + mGoForwardEnabled = enableGoForward; + mGoBackEnabled = enableGoBack; + mNumSelectedRows = numSelected; + return NS_OK; +} + +nsresult nsMsgDBView::GetSelectedIndices(nsMsgViewIndexArray& selection) +{ + if (mTreeSelection) + { + int32_t viewSize = GetSize(); + int32_t count; + mTreeSelection->GetCount(&count); + selection.SetLength(count); + count = 0; + int32_t selectionCount; + mTreeSelection->GetRangeCount(&selectionCount); + for (int32_t i = 0; i < selectionCount; i++) + { + int32_t startRange = -1; + int32_t endRange = -1; + mTreeSelection->GetRangeAt(i, &startRange, &endRange); + if (startRange >= 0 && startRange < viewSize) + { + for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < viewSize; rangeIndex++) + selection[count++] = rangeIndex; + } + } + NS_ASSERTION(selection.Length() == uint32_t(count), "selection count is wrong"); + selection.SetLength(count); + } + else + { + // if there is no tree selection object then we must be in stand alone message mode. + // in that case the selected indices are really just the current message key. + nsMsgViewIndex viewIndex = FindViewIndex(m_currentlyDisplayedMsgKey); + if (viewIndex != nsMsgViewIndex_None) + selection.AppendElement(viewIndex); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetRowProperties(int32_t index, nsAString& properties) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + // this is where we tell the tree to apply styles to a particular row + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = NS_OK; + + rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr)); + + if (NS_FAILED(rv) || !msgHdr) { + ClearHdrCache(); + return NS_MSG_INVALID_DBVIEW_INDEX; + } + + nsCString keywordProperty; + FetchRowKeywords(index, msgHdr, keywordProperty); + if (!keywordProperty.IsEmpty()) + AppendKeywordProperties(keywordProperty, properties, false); + + // give the custom column handlers a chance to style the row. + for (int i = 0; i < m_customColumnHandlers.Count(); i++) + { + nsString extra; + m_customColumnHandlers[i]->GetRowProperties(index, extra); + if (!extra.IsEmpty()) + { + properties.Append(' '); + properties.Append(extra); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetColumnProperties(nsITreeColumn* col, nsAString& properties) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetCellProperties(int32_t aRow, nsITreeColumn *col, nsAString& properties) +{ + if (!IsValidIndex(aRow)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + // this is where we tell the tree to apply styles to a particular row + // i.e. if the row is an unread message... + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = NS_OK; + + rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); + + if (NS_FAILED(rv) || !msgHdr) + { + ClearHdrCache(); + return NS_MSG_INVALID_DBVIEW_INDEX; + } + + const char16_t* colID; + col->GetIdConst(&colID); + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + if (colHandler != nullptr) + colHandler->GetCellProperties(aRow, col, properties); + else if (colID[0] == 'c') // correspondent + { + if (IsOutgoingMsg(msgHdr)) + properties.AssignLiteral("outgoing"); + else + properties.AssignLiteral("incoming"); + } + + if (!properties.IsEmpty()) + properties.Append(' '); + + properties.Append(mMessageType); + + uint32_t flags; + msgHdr->GetFlags(&flags); + + if (!(flags & nsMsgMessageFlags::Read)) + properties.AppendLiteral(" unread"); + else + properties.AppendLiteral(" read"); + + if (flags & nsMsgMessageFlags::Replied) + properties.AppendLiteral(" replied"); + + if (flags & nsMsgMessageFlags::Forwarded) + properties.AppendLiteral(" forwarded"); + + if (flags & nsMsgMessageFlags::New) + properties.AppendLiteral(" new"); + + if (m_flags[aRow] & nsMsgMessageFlags::Marked) + properties.AppendLiteral(" flagged"); + + // For threaded display add the ignoreSubthread property to the + // subthread top row (this row). For non-threaded add it to all rows. + if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) && + (flags & nsMsgMessageFlags::Ignored)) { + properties.AppendLiteral(" ignoreSubthread"); + } + else { + bool ignored; + msgHdr->GetIsKilled(&ignored); + if (ignored) + properties.AppendLiteral(" ignoreSubthread"); + } + + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder); + + if ((flags & nsMsgMessageFlags::Offline) || (localFolder && !(flags & nsMsgMessageFlags::Partial))) + properties.AppendLiteral(" offline"); + + if (flags & nsMsgMessageFlags::Attachment) + properties.AppendLiteral(" attach"); + + if ((mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) && (flags & nsMsgMessageFlags::IMAPDeleted)) + properties.AppendLiteral(" imapdeleted"); + + nsCString imageSize; + msgHdr->GetStringProperty("imageSize", getter_Copies(imageSize)); + if (!imageSize.IsEmpty()) + properties.AppendLiteral(" hasimage"); + + nsCString junkScoreStr; + msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (!junkScoreStr.IsEmpty()) { + if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE) + properties.AppendLiteral(" junk"); + else + properties.AppendLiteral(" notjunk"); + NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed."); + } + + nsCString keywords; + FetchRowKeywords(aRow, msgHdr, keywords); + if (!keywords.IsEmpty()) + AppendKeywordProperties(keywords, properties, true); + + // this is a double fetch of the keywords property since we also fetch + // it for the tags - do we want to do this? + // I'm not sure anyone uses the kw- property, though it could be nice + // for people wanting to extend the thread pane. + nsCString keywordProperty; + msgHdr->GetStringProperty("keywords", getter_Copies(keywordProperty)); + if (!keywordProperty.IsEmpty()) + { + NS_ConvertUTF8toUTF16 keywords(keywordProperty); + int32_t spaceIndex = 0; + do + { + spaceIndex = keywords.FindChar(' '); + int32_t endOfKeyword = (spaceIndex == -1) ? keywords.Length() : spaceIndex; + properties.AppendLiteral(" kw-"); + properties.Append(StringHead(keywords, endOfKeyword)); + if (spaceIndex > 0) + keywords.Cut(0, endOfKeyword + 1); + } + while (spaceIndex > 0); + } + +#ifdef SUPPORT_PRIORITY_COLORS + // add special styles for priority + nsMsgPriorityValue priority; + msgHdr->GetPriority(&priority); + switch (priority) + { + case nsMsgPriority::highest: + properties.append(" priority-highest"); + break; + case nsMsgPriority::high: + properties.append(" priority-high"); + break; + case nsMsgPriority::low: + properties.append(" priority-low"); + break; + case nsMsgPriority::lowest: + properties.append(" priority-lowest"); + break; + default: + break; + } +#endif + + + nsCOMPtr <nsIMsgThread> thread; + rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread)); + if (NS_SUCCEEDED(rv) && thread) + { + uint32_t numUnreadChildren; + thread->GetNumUnreadChildren(&numUnreadChildren); + if (numUnreadChildren > 0) + properties.AppendLiteral(" hasUnread"); + + // For threaded display add the ignore/watch properties to the + // thread top row. For non-threaded add it to all rows. + if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || + ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) && + (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD))) { + thread->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Watched) + properties.AppendLiteral(" watch"); + if (flags & nsMsgMessageFlags::Ignored) + properties.AppendLiteral(" ignore"); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::IsContainer(int32_t index, bool *_retval) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + uint32_t flags = m_flags[index]; + *_retval = !!(flags & MSG_VIEW_FLAG_HASCHILDREN); + } + else + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::IsContainerOpen(int32_t index, bool *_retval) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + uint32_t flags = m_flags[index]; + *_retval = (flags & MSG_VIEW_FLAG_HASCHILDREN) && !(flags & nsMsgMessageFlags::Elided); + } + else + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::IsContainerEmpty(int32_t index, bool *_retval) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + uint32_t flags = m_flags[index]; + *_retval = !(flags & MSG_VIEW_FLAG_HASCHILDREN); + } + else + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::IsSeparator(int32_t index, bool *_retval) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + *_retval = false; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetParentIndex(int32_t rowIndex, int32_t *_retval) +{ + *_retval = -1; + + int32_t rowIndexLevel; + nsresult rv = GetLevel(rowIndex, &rowIndexLevel); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t i; + for(i = rowIndex; i >= 0; i--) + { + int32_t l; + GetLevel(i, &l); + if (l < rowIndexLevel) + { + *_retval = i; + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval) +{ + *_retval = false; + + int32_t rowIndexLevel; + GetLevel(rowIndex, &rowIndexLevel); + + int32_t i; + int32_t count; + GetRowCount(&count); + for(i = afterIndex + 1; i < count; i++) + { + int32_t l; + GetLevel(i, &l); + if (l < rowIndexLevel) + break; + if (l == rowIndexLevel) + { + *_retval = true; + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetLevel(int32_t index, int32_t *_retval) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + *_retval = m_levels[index]; + else + *_retval = 0; + return NS_OK; +} + +// search view will override this since headers can span db's +nsresult nsMsgDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr) +{ + nsresult rv = NS_OK; + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + nsMsgKey key = m_keys[index]; + if (key == nsMsgKey_None || !m_db) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (key == m_cachedMsgKey) + { + *msgHdr = m_cachedHdr; + NS_IF_ADDREF(*msgHdr); + } + else + { + rv = m_db->GetMsgHdrForKey(key, msgHdr); + if (NS_SUCCEEDED(rv)) + { + m_cachedHdr = *msgHdr; + m_cachedMsgKey = key; + } + } + + return rv; +} + +void nsMsgDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr, + nsMsgKey msgKey, uint32_t flags, uint32_t level) +{ + if ((int32_t) index < 0 || index > m_keys.Length()) + { + // Something's gone wrong in a caller, but we have no clue why + // Return without adding the header to the view + NS_ERROR("Index for message header insertion out of array range!"); + return; + } + m_keys.InsertElementAt(index, msgKey); + m_flags.InsertElementAt(index, flags); + m_levels.InsertElementAt(index, level); +} + +void nsMsgDBView::SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, + nsMsgKey msgKey, uint32_t flags, uint32_t level) +{ + m_keys[index] = msgKey; + m_flags[index] = flags; + m_levels[index] = level; +} + +nsresult nsMsgDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **aFolder) +{ + NS_IF_ADDREF(*aFolder = m_folder); + return NS_OK; +} + +nsresult nsMsgDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db) +{ + NS_IF_ADDREF(*db = m_db); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue) +{ + NS_ENSURE_ARG_POINTER(aCol); + //attempt to retreive a custom column handler. If it exists call it and return + const char16_t* colID; + aCol->GetIdConst(&colID); + + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->GetImageSrc(aRow, aCol, aValue); + return NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue) +{ + if (!IsValidIndex(aRow)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); + + if (NS_FAILED(rv) || !msgHdr) + { + ClearHdrCache(); + return NS_MSG_INVALID_DBVIEW_INDEX; + } + + const char16_t* colID; + aCol->GetIdConst(&colID); + + uint32_t flags; + msgHdr->GetFlags(&flags); + + aValue.Truncate(); + // provide a string "value" for cells that do not normally have text. + // use empty string for the normal states "Read", "Not Starred", "No Attachment" and "Not Junk" + switch (colID[0]) + { + case 'a': // attachment column + if (flags & nsMsgMessageFlags::Attachment) { + nsString tmp_str; + tmp_str.Adopt(GetString(u"messageHasAttachment")); + aValue.Assign(tmp_str); + } + break; + case 'f': // flagged (starred) column + if (flags & nsMsgMessageFlags::Marked) { + nsString tmp_str; + tmp_str.Adopt(GetString(u"messageHasFlag")); + aValue.Assign(tmp_str); + } + break; + case 'j': // junk column + if (JunkControlsEnabled(aRow)) + { + nsCString junkScoreStr; + msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + // Only need to assing a real value for junk, it's empty already + // as it should be for non-junk. + if (!junkScoreStr.IsEmpty() && + (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE)) + aValue.AssignLiteral("messageJunk"); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed."); + } + break; + case 't': + if (colID[1] == 'h' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { // thread column + bool isContainer, isContainerEmpty, isContainerOpen; + IsContainer(aRow, &isContainer); + if (isContainer) + { + IsContainerEmpty(aRow, &isContainerEmpty); + if (!isContainerEmpty) + { + nsString tmp_str; + + IsContainerOpen(aRow, &isContainerOpen); + tmp_str.Adopt(GetString(isContainerOpen ? + u"messageExpanded" : + u"messageCollapsed")); + aValue.Assign(tmp_str); + } + } + } + break; + case 'u': // read/unread column + if (!(flags & nsMsgMessageFlags::Read)) { + nsString tmp_str; + tmp_str.Adopt(GetString(u"messageUnread")); + aValue.Assign(tmp_str); + } + break; + default: + aValue.Assign(colID); + break; + } + return rv; +} + +void nsMsgDBView::RememberDeletedMsgHdr(nsIMsgDBHdr *msgHdr) +{ + nsCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + if (mRecentlyDeletedArrayIndex >= mRecentlyDeletedMsgIds.Length()) + mRecentlyDeletedMsgIds.AppendElement(messageId); + else + mRecentlyDeletedMsgIds[mRecentlyDeletedArrayIndex] = messageId; + // only remember last 20 deleted msgs. + mRecentlyDeletedArrayIndex = (mRecentlyDeletedArrayIndex + 1) % 20; +} + +bool nsMsgDBView::WasHdrRecentlyDeleted(nsIMsgDBHdr *msgHdr) +{ + nsCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + return mRecentlyDeletedMsgIds.Contains(messageId); +} + +//add a custom column handler +NS_IMETHODIMP nsMsgDBView::AddColumnHandler(const nsAString& column, nsIMsgCustomColumnHandler* handler) +{ + bool custColInSort = false; + size_t index = m_customColumnHandlerIDs.IndexOf(column); + + nsAutoString strColID(column); + + //does not exist + if (index == m_customColumnHandlerIDs.NoIndex) + { + m_customColumnHandlerIDs.AppendElement(strColID); + m_customColumnHandlers.AppendObject(handler); + } + else + { + //insert new handler into the appropriate place in the COMPtr array + //no need to replace the column ID (it's the same) + m_customColumnHandlers.ReplaceObjectAt(handler, index); + + } + // Check if the column name matches any of the columns in + // m_sortColumns, and if so, set m_sortColumns[i].mColHandler + for (uint32_t i = 0; i < m_sortColumns.Length(); i++) + { + MsgViewSortColumnInfo &sortInfo = m_sortColumns[i]; + if (sortInfo.mSortType == nsMsgViewSortType::byCustom && + sortInfo.mCustomColumnName.Equals(column)) + { + custColInSort = true; + sortInfo.mColHandler = handler; + } + } + + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + // Grouped view has its own ways. + return NS_OK; + + // This cust col is in sort columns, and all are now registered, so sort. + if (custColInSort && !CustomColumnsInSortAndNotRegistered()) + Sort(m_sortType, m_sortOrder); + + return NS_OK; +} + +//remove a custom column handler +NS_IMETHODIMP nsMsgDBView::RemoveColumnHandler(const nsAString& aColID) +{ + + // here we should check if the column name matches any of the columns in + // m_sortColumns, and if so, clear m_sortColumns[i].mColHandler + size_t index = m_customColumnHandlerIDs.IndexOf(aColID); + + if (index != m_customColumnHandlerIDs.NoIndex) + { + m_customColumnHandlerIDs.RemoveElementAt(index); + m_customColumnHandlers.RemoveObjectAt(index); + // Check if the column name matches any of the columns in + // m_sortColumns, and if so, clear m_sortColumns[i].mColHandler + for (uint32_t i = 0; i < m_sortColumns.Length(); i++) + { + MsgViewSortColumnInfo &sortInfo = m_sortColumns[i]; + if (sortInfo.mSortType == nsMsgViewSortType::byCustom && + sortInfo.mCustomColumnName.Equals(aColID)) + sortInfo.mColHandler = nullptr; + } + + return NS_OK; + } + + return NS_ERROR_FAILURE; //can't remove a column that isn't currently custom handled +} + +//TODO: NS_ENSURE_SUCCESS +nsIMsgCustomColumnHandler* nsMsgDBView::GetCurColumnHandler() +{ + return GetColumnHandler(m_curCustomColumn.get()); +} + +NS_IMETHODIMP nsMsgDBView::SetCurCustomColumn(const nsAString& aColID) +{ + m_curCustomColumn = aColID; + + if (m_viewFolder) + { + nsCOMPtr <nsIMsgDatabase> db; + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv,rv); + folderInfo->SetProperty("customSortCol", aColID); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetCurCustomColumn(nsAString &result) +{ + result = m_curCustomColumn; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSecondaryCustomColumn(nsAString &result) +{ + result = m_secondaryCustomColumn; + return NS_OK; +} + +nsIMsgCustomColumnHandler* nsMsgDBView::GetColumnHandler(const char16_t *colID) +{ + size_t index = m_customColumnHandlerIDs.IndexOf(nsDependentString(colID)); + return (index != m_customColumnHandlerIDs.NoIndex) ? + m_customColumnHandlers[index] : nullptr; +} + +NS_IMETHODIMP nsMsgDBView::GetColumnHandler(const nsAString& aColID, nsIMsgCustomColumnHandler** aHandler) +{ + NS_ENSURE_ARG_POINTER(aHandler); + nsAutoString column(aColID); + NS_IF_ADDREF(*aHandler = GetColumnHandler(column.get())); + return (*aHandler) ? NS_OK : NS_ERROR_FAILURE; +} + +// Check if any active sort columns are custom. If none are custom, return false +// and go on as always. If any are custom, and all are not registered yet, +// return true (so that the caller can postpone sort). When the custom column +// observer is notified with MsgCreateDBView and registers the handler, +// AddColumnHandler will sort once all required handlers are set. +bool nsMsgDBView::CustomColumnsInSortAndNotRegistered() +{ + // The initial sort on view open has been started, subsequent user initiated + // sort callers can ignore verifying cust col registration. + m_checkedCustomColumns = true; + + // DecodeColumnSort must have already created m_sortColumns, otherwise we + // can't know, but go on anyway. + if (!m_sortColumns.Length()) + return false; + + bool custColNotRegistered = false; + for (uint32_t i = 0; i < m_sortColumns.Length() && !custColNotRegistered; i++) + { + if (m_sortColumns[i].mSortType == nsMsgViewSortType::byCustom && + m_sortColumns[i].mColHandler == nullptr) + custColNotRegistered = true; + } + + return custColNotRegistered; +} + +NS_IMETHODIMP nsMsgDBView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue) +{ + const char16_t* colID; + aCol->GetIdConst(&colID); + + if (!IsValidIndex(aRow)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + aValue.Truncate(); + + //attempt to retreive a custom column handler. If it exists call it and return + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->GetCellText(aRow, aCol, aValue); + return NS_OK; + } + + return CellTextForColumn(aRow, colID, aValue); +} + +NS_IMETHODIMP nsMsgDBView::CellTextForColumn(int32_t aRow, + const char16_t *aColumnName, + nsAString &aValue) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); + + if (NS_FAILED(rv) || !msgHdr) + { + ClearHdrCache(); + return NS_MSG_INVALID_DBVIEW_INDEX; + } + + nsCOMPtr<nsIMsgThread> thread; + + switch (aColumnName[0]) + { + case 's': + if (aColumnName[1] == 'u') // subject + rv = FetchSubject(msgHdr, m_flags[aRow], aValue); + else if (aColumnName[1] == 'e') // sender + rv = FetchAuthor(msgHdr, aValue); + else if (aColumnName[1] == 'i') // size + rv = FetchSize(msgHdr, aValue); + else if (aColumnName[1] == 't') // status + { + uint32_t flags; + msgHdr->GetFlags(&flags); + rv = FetchStatus(flags, aValue); + } + break; + case 'r': + if (aColumnName[3] == 'i') // recipient + rv = FetchRecipients(msgHdr, aValue); + else if (aColumnName[3] == 'e') // received + rv = FetchDate(msgHdr, aValue, true); + break; + case 'd': // date + rv = FetchDate(msgHdr, aValue); + break; + case 'c': // correspondent + if (IsOutgoingMsg(msgHdr)) + rv = FetchRecipients(msgHdr, aValue); + else + rv = FetchAuthor(msgHdr, aValue); + break; + case 'p': // priority + rv = FetchPriority(msgHdr, aValue); + break; + case 'a': // account + if (aColumnName[1] == 'c') // account + rv = FetchAccount(msgHdr, aValue); + break; + case 't': + // total msgs in thread column + if (aColumnName[1] == 'o' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD) + { + rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread)); + if (NS_SUCCEEDED(rv) && thread) + { + nsAutoString formattedCountString; + uint32_t numChildren; + thread->GetNumChildren(&numChildren); + formattedCountString.AppendInt(numChildren); + aValue.Assign(formattedCountString); + } + } + } + else if (aColumnName[1] == 'a') // tags + { + rv = FetchTags(msgHdr, aValue); + } + break; + case 'u': + // unread msgs in thread col + if (aColumnName[6] == 'C' && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD) + { + rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread)); + if (NS_SUCCEEDED(rv) && thread) + { + nsAutoString formattedCountString; + uint32_t numUnreadChildren; + thread->GetNumUnreadChildren(&numUnreadChildren); + if (numUnreadChildren > 0) + { + formattedCountString.AppendInt(numUnreadChildren); + aValue.Assign(formattedCountString); + } + } + } + } + break; + case 'j': + { + nsCString junkScoreStr; + msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + CopyASCIItoUTF16(junkScoreStr, aValue); + } + break; + case 'i': // id + { + nsAutoString keyString; + nsMsgKey key; + msgHdr->GetMessageKey(&key); + keyString.AppendInt((int64_t)key); + aValue.Assign(keyString); + } + break; + default: + break; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetTree(nsITreeBoxObject *tree) +{ + mTree = tree; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::ToggleOpenState(int32_t index) +{ + uint32_t numChanged; + nsresult rv = ToggleExpansion(index, &numChanged); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::CycleHeader(nsITreeColumn* aCol) +{ + // let HandleColumnClick() in threadPane.js handle it + // since it will set / clear the sort indicators. + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::CycleCell(int32_t row, nsITreeColumn* col) +{ + if (!IsValidIndex(row)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + const char16_t* colID; + col->GetIdConst(&colID); + + //attempt to retreive a custom column handler. If it exists call it and return + nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID); + + if (colHandler) + { + colHandler->CycleCell(row, col); + return NS_OK; + } + + // The cyclers below don't work for the grouped header dummy row, currently. + // A future implementation should consider both collapsed and expanded state. + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort && + m_flags[row] & MSG_VIEW_FLAG_DUMMY) + return NS_OK; + + switch (colID[0]) + { + case 'u': // unreadButtonColHeader + if (colID[6] == 'B') + ApplyCommandToIndices(nsMsgViewCommandType::toggleMessageRead, (nsMsgViewIndex *) &row, 1); + break; + case 't': // tag cell, threaded cell or total cell + if (colID[1] == 'h') + { + ExpandAndSelectThreadByIndex(row, false); + } + else if (colID[1] == 'a') + { + // ### Do we want to keep this behaviour but switch it to tags? + // We could enumerate over the tags and go to the next one - it looks + // to me like this wasn't working before tags landed, so maybe not + // worth bothering with. + } + break; + case 'f': // flagged column + // toggle the flagged status of the element at row. + if (m_flags[row] & nsMsgMessageFlags::Marked) + ApplyCommandToIndices(nsMsgViewCommandType::unflagMessages, (nsMsgViewIndex *) &row, 1); + else + ApplyCommandToIndices(nsMsgViewCommandType::flagMessages, (nsMsgViewIndex *) &row, 1); + break; + case 'j': // junkStatus column + { + if (!JunkControlsEnabled(row)) + return NS_OK; + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = GetMsgHdrForViewIndex(row, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv) && msgHdr) + { + nsCString junkScoreStr; + rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (junkScoreStr.IsEmpty() || (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_HAM_SCORE)) + ApplyCommandToIndices(nsMsgViewCommandType::junk, (nsMsgViewIndex *) &row, 1); + else + ApplyCommandToIndices(nsMsgViewCommandType::unjunk, (nsMsgViewIndex *) &row, 1); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed."); + } + } + break; + default: + break; + + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::PerformAction(const char16_t *action) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::PerformActionOnRow(const char16_t *action, int32_t row) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col) +{ + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////// +// end nsITreeView Implementation Methods +/////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsMsgDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) +{ + m_viewFlags = viewFlags; + m_sortOrder = sortOrder; + m_sortType = sortType; + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool userNeedsToAuthenticate = false; + // if we're PasswordProtectLocalCache, then we need to find out if the server is authenticated. + (void) accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate); + if (userNeedsToAuthenticate) + return NS_MSG_USER_NOT_AUTHENTICATED; + + if (folder) // search view will have a null folder + { + nsCOMPtr <nsIDBFolderInfo> folderInfo; + rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_db)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgDBService->RegisterPendingListener(folder, this); + m_folder = folder; + + if (!m_viewFolder) + // There is never a viewFolder already set except for the single folder + // saved search case, where the backing folder m_folder is different from + // the m_viewFolder with its own dbFolderInfo state. + m_viewFolder = folder; + + SetMRUTimeForFolder(m_viewFolder); + + RestoreSortInfo(); + + // determine if we are in a news folder or not. + // if yes, we'll show lines instead of size, and special icons in the thread pane + nsCOMPtr <nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + nsCString type; + rv = server->GetType(type); + NS_ENSURE_SUCCESS(rv,rv); + + // I'm not sure this is correct, because XF virtual folders with mixed news + // and mail can have this set. + mIsNews = MsgLowerCaseEqualsLiteral(type, "nntp"); + + // Default to a virtual folder if folder not set, since synthetic search + // views may not have a folder. + uint32_t folderFlags = nsMsgFolderFlags::Virtual; + if (folder) + folder->GetFlags(&folderFlags); + mIsXFVirtual = folderFlags & nsMsgFolderFlags::Virtual; + + if (!mIsXFVirtual && MsgLowerCaseEqualsLiteral(type, "rss")) + mIsRss = true; + + // special case nntp --> news since we'll break themes if we try to be consistent + if (mIsNews) + mMessageType.AssignLiteral("news"); + else + CopyUTF8toUTF16(type, mMessageType); + + GetImapDeleteModel(nullptr); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + { + prefs->GetBoolPref("mailnews.sort_threads_by_root", &mSortThreadsByRoot); + + if (mIsNews) + prefs->GetBoolPref("news.show_size_in_lines", &mShowSizeInLines); + } + } + + nsCOMPtr<nsIArray> identities; + rv = accountManager->GetAllIdentities(getter_AddRefs(identities)); + if (!identities) + return rv; + + uint32_t count; + identities->GetLength(&count); + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, i)); + if (!identity) + continue; + + nsCString email; + identity->GetEmail(email); + if (!email.IsEmpty()) + mEmails.PutEntry(email); + + identity->GetReplyTo(email); + if (!email.IsEmpty()) + mEmails.PutEntry(email); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::Close() +{ + int32_t oldSize = GetSize(); + // this is important, because the tree will ask us for our + // row count, which get determine from the number of keys. + m_keys.Clear(); + // be consistent + m_flags.Clear(); + m_levels.Clear(); + + // clear these out since they no longer apply if we're switching a folder + if (mJunkHdrs) + mJunkHdrs->Clear(); + + // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount() + if (mTree) + mTree->RowCountChanged(0, -oldSize); + + ClearHdrCache(); + if (m_db) + { + m_db->RemoveListener(this); + m_db = nullptr; + } + if (m_folder) + { + nsresult rv; + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgDBService->UnregisterPendingListener(this); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) +{ + NS_ASSERTION(false, "not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgDBView::Init(nsIMessenger * aMessengerInstance, nsIMsgWindow * aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) +{ + mMessengerWeak = do_GetWeakReference(aMessengerInstance); + mMsgWindowWeak = do_GetWeakReference(aMsgWindow); + mCommandUpdater = aCmdUpdater; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetSuppressCommandUpdating(bool aSuppressCommandUpdating) +{ + mSuppressCommandUpdating = aSuppressCommandUpdating; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSuppressCommandUpdating(bool * aSuppressCommandUpdating) +{ + *aSuppressCommandUpdating = mSuppressCommandUpdating; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetSuppressMsgDisplay(bool aSuppressDisplay) +{ + uint32_t numSelected = 0; + GetNumSelected(&numSelected); + + bool forceDisplay = false; + if (mSuppressMsgDisplay && !aSuppressDisplay && numSelected == 1) + forceDisplay = true; + + mSuppressMsgDisplay = aSuppressDisplay; + if (forceDisplay) + { + // get the view indexfor the currently selected message + nsMsgViewIndex viewIndex; + nsresult rv = GetViewIndexForFirstSelectedMsg(&viewIndex); + if (NS_SUCCEEDED(rv) && viewIndex != nsMsgViewIndex_None) + LoadMessageByViewIndex(viewIndex); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSuppressMsgDisplay(bool * aSuppressDisplay) +{ + *aSuppressDisplay = mSuppressMsgDisplay; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetUsingLines(bool * aUsingLines) +{ + *aUsingLines = mShowSizeInLines; + return NS_OK; +} + +int CompareViewIndices (const void *v1, const void *v2, void *) +{ + nsMsgViewIndex i1 = *(nsMsgViewIndex*) v1; + nsMsgViewIndex i2 = *(nsMsgViewIndex*) v2; + return i1 - i2; +} + +NS_IMETHODIMP nsMsgDBView::GetIndicesForSelection(uint32_t *length, nsMsgViewIndex **indices) +{ + NS_ENSURE_ARG_POINTER(length); + *length = 0; + NS_ENSURE_ARG_POINTER(indices); + *indices = nullptr; + + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + uint32_t numIndices = selection.Length(); + if (!numIndices) return NS_OK; + *length = numIndices; + + uint32_t datalen = numIndices * sizeof(nsMsgViewIndex); + *indices = (nsMsgViewIndex *)NS_Alloc(datalen); + if (!*indices) return NS_ERROR_OUT_OF_MEMORY; + memcpy(*indices, selection.Elements(), datalen); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSelectedMsgHdrs(uint32_t *aLength, nsIMsgDBHdr ***aResult) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(aLength); + *aLength = 0; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + uint32_t numIndices = selection.Length(); + if (!numIndices) return NS_OK; + + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numMsgsSelected; + messages->GetLength(&numMsgsSelected); + + nsIMsgDBHdr **headers = static_cast<nsIMsgDBHdr**>(NS_Alloc( + sizeof(nsIMsgDBHdr*) * numMsgsSelected)); + for (uint32_t i = 0; i < numMsgsSelected; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgHdr.forget(&headers[i]); // Already AddRefed + } + + *aLength = numMsgsSelected; + *aResult = headers; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetMsgHdrsForSelection(nsIMutableArray **aResult) +{ + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + uint32_t numIndices = selection.Length(); + + nsresult rv; + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages); + NS_ENSURE_SUCCESS(rv, rv); + messages.forget(aResult); + return rv; +} + +NS_IMETHODIMP nsMsgDBView::GetURIsForSelection(uint32_t *length, char ***uris) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(length); + *length = 0; + + NS_ENSURE_ARG_POINTER(uris); + *uris = nullptr; + + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + uint32_t numIndices = selection.Length(); + if (!numIndices) return NS_OK; + + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(selection.Elements(), numIndices, messages); + NS_ENSURE_SUCCESS(rv, rv); + messages->GetLength(length); + uint32_t numMsgsSelected = *length; + + char **outArray, **next; + next = outArray = (char **)moz_xmalloc(numMsgsSelected * sizeof(char *)); + if (!outArray) return NS_ERROR_OUT_OF_MEMORY; + for (uint32_t i = 0; i < numMsgsSelected; i++) + { + nsCString tmpUri; + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgFolder> folder; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFolder(getter_AddRefs(folder)); + rv = GenerateURIForMsgKey(msgKey, folder, tmpUri); + NS_ENSURE_SUCCESS(rv,rv); + *next = ToNewCString(tmpUri); + if (!*next) + return NS_ERROR_OUT_OF_MEMORY; + + next++; + } + + *uris = outArray; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetURIForViewIndex(nsMsgViewIndex index, nsACString &result) +{ + nsresult rv; + nsCOMPtr <nsIMsgFolder> folder = m_folder; + if (!folder) + { + rv = GetFolderForViewIndex(index, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv,rv); + } + if (index == nsMsgViewIndex_None || index >= m_flags.Length() || + m_flags[index] & MSG_VIEW_FLAG_DUMMY) + return NS_MSG_INVALID_DBVIEW_INDEX; + return GenerateURIForMsgKey(m_keys[index], folder, result); +} + +NS_IMETHODIMP nsMsgDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder) +{ + NS_ENSURE_ARG_POINTER(destFolder); + + nsMsgViewIndexArray selection; + + GetSelectedIndices(selection); + + nsMsgViewIndex *indices = selection.Elements(); + int32_t numIndices = selection.Length(); + + nsresult rv = NS_OK; + switch (command) { + case nsMsgViewCommandType::copyMessages: + case nsMsgViewCommandType::moveMessages: + NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); + rv = ApplyCommandToIndicesWithFolder(command, indices, numIndices, destFolder); + NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + break; + default: + NS_ASSERTION(false, "invalid command type"); + rv = NS_ERROR_UNEXPECTED; + break; + } + return rv; + +} + +NS_IMETHODIMP nsMsgDBView::DoCommand(nsMsgViewCommandTypeValue command) +{ + nsMsgViewIndexArray selection; + + GetSelectedIndices(selection); + + nsMsgViewIndex *indices = selection.Elements(); + int32_t numIndices = selection.Length(); + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + + nsresult rv = NS_OK; + switch (command) + { + case nsMsgViewCommandType::downloadSelectedForOffline: + return DownloadForOffline(msgWindow, indices, numIndices); + case nsMsgViewCommandType::downloadFlaggedForOffline: + return DownloadFlaggedForOffline(msgWindow); + case nsMsgViewCommandType::markMessagesRead: + case nsMsgViewCommandType::markMessagesUnread: + case nsMsgViewCommandType::toggleMessageRead: + case nsMsgViewCommandType::flagMessages: + case nsMsgViewCommandType::unflagMessages: + case nsMsgViewCommandType::deleteMsg: + case nsMsgViewCommandType::undeleteMsg: + case nsMsgViewCommandType::deleteNoTrash: + case nsMsgViewCommandType::markThreadRead: + case nsMsgViewCommandType::junk: + case nsMsgViewCommandType::unjunk: + NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); + rv = ApplyCommandToIndices(command, indices, numIndices); + NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + break; + case nsMsgViewCommandType::selectAll: + if (mTreeSelection) + { + // if in threaded mode, we need to expand all before selecting + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + rv = ExpandAll(); + mTreeSelection->SelectAll(); + if (mTree) + mTree->Invalidate(); + } + break; + case nsMsgViewCommandType::selectThread: + rv = ExpandAndSelectThread(); + break; + case nsMsgViewCommandType::selectFlagged: + if (!mTreeSelection) + rv = NS_ERROR_UNEXPECTED; + else + { + mTreeSelection->SetSelectEventsSuppressed(true); + mTreeSelection->ClearSelection(); + // XXX ExpandAll? + nsMsgViewIndex numIndices = GetSize(); + for (nsMsgViewIndex curIndex = 0; curIndex < numIndices; curIndex++) + { + if (m_flags[curIndex] & nsMsgMessageFlags::Marked) + mTreeSelection->ToggleSelect(curIndex); + } + mTreeSelection->SetSelectEventsSuppressed(false); + } + break; + case nsMsgViewCommandType::markAllRead: + if (m_folder) + { + SetSuppressChangeNotifications(true); + rv = m_folder->MarkAllMessagesRead(msgWindow); + SetSuppressChangeNotifications(false); + if (mTree) + mTree->Invalidate(); + } + break; + case nsMsgViewCommandType::toggleThreadWatched: + rv = ToggleWatched(indices, numIndices); + break; + case nsMsgViewCommandType::expandAll: + rv = ExpandAll(); + m_viewFlags |= nsMsgViewFlagsType::kExpandAll; + SetViewFlags(m_viewFlags); + if (mTree) + mTree->Invalidate(); + break; + case nsMsgViewCommandType::collapseAll: + rv = CollapseAll(); + m_viewFlags &= ~nsMsgViewFlagsType::kExpandAll; + SetViewFlags(m_viewFlags); + if(mTree) + mTree->Invalidate(); + break; + default: + NS_ASSERTION(false, "invalid command type"); + rv = NS_ERROR_UNEXPECTED; + break; + } + return rv; +} + +bool nsMsgDBView::ServerSupportsFilterAfterTheFact() +{ + if (!m_folder) // cross folder virtual folders might not have a folder set. + return false; + + nsCOMPtr <nsIMsgIncomingServer> server; + nsresult rv = m_folder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv)) + return false; // unexpected + + // filter after the fact is implement using search + // so if you can't search, you can't filter after the fact + bool canSearch; + rv = server->GetCanSearchMessages(&canSearch); + if (NS_FAILED(rv)) + return false; // unexpected + + return canSearch; +} + +NS_IMETHODIMP nsMsgDBView::GetCommandStatus(nsMsgViewCommandTypeValue command, bool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p) +{ + nsresult rv = NS_OK; + + bool haveSelection; + int32_t rangeCount; + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + int32_t numIndices = selection.Length(); + nsMsgViewIndex *indices = selection.Elements(); + // if range count is non-zero, we have at least one item selected, so we have a selection + if (mTreeSelection && NS_SUCCEEDED(mTreeSelection->GetRangeCount(&rangeCount)) && rangeCount > 0) + haveSelection = NonDummyMsgSelected(indices, numIndices); + else + // If we don't have a tree selection we must be in stand alone mode. + haveSelection = IsValidIndex(m_currentlyDisplayedViewIndex); + + switch (command) + { + case nsMsgViewCommandType::deleteMsg: + case nsMsgViewCommandType::deleteNoTrash: + { + bool canDelete; + if (m_folder && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && !canDelete) + *selectable_p = false; + else + *selectable_p = haveSelection; + } + break; + case nsMsgViewCommandType::applyFilters: + // disable if no messages + // XXX todo, check that we have filters, and at least one is enabled + *selectable_p = GetSize(); + if (*selectable_p) + *selectable_p = ServerSupportsFilterAfterTheFact(); + break; + case nsMsgViewCommandType::runJunkControls: + // disable if no messages + // XXX todo, check that we have JMC enabled? + *selectable_p = GetSize() && JunkControlsEnabled(nsMsgViewIndex_None); + break; + case nsMsgViewCommandType::deleteJunk: + { + // disable if no messages, or if we can't delete (like news and certain imap folders) + bool canDelete; + *selectable_p = GetSize() && (m_folder && NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && canDelete); + } + break; + case nsMsgViewCommandType::markMessagesRead: + case nsMsgViewCommandType::markMessagesUnread: + case nsMsgViewCommandType::toggleMessageRead: + case nsMsgViewCommandType::flagMessages: + case nsMsgViewCommandType::unflagMessages: + case nsMsgViewCommandType::toggleThreadWatched: + case nsMsgViewCommandType::markThreadRead: + case nsMsgViewCommandType::downloadSelectedForOffline: + *selectable_p = haveSelection; + break; + case nsMsgViewCommandType::junk: + case nsMsgViewCommandType::unjunk: + *selectable_p = haveSelection && numIndices && JunkControlsEnabled(selection[0]); + break; + case nsMsgViewCommandType::cmdRequiringMsgBody: + *selectable_p = haveSelection && (!WeAreOffline() || OfflineMsgSelected(indices, numIndices)); + break; + case nsMsgViewCommandType::downloadFlaggedForOffline: + case nsMsgViewCommandType::markAllRead: + *selectable_p = true; + break; + default: + NS_ASSERTION(false, "invalid command type"); + rv = NS_ERROR_FAILURE; + } + return rv; +} + +// This method needs to be overridden by the various view classes +// that have different kinds of threads. For example, in a +// threaded quick search db view, we'd only want to include children +// of the thread that fit the view (IMO). And when we have threaded +// cross folder views, we would include all the children of the +// cross-folder thread. +nsresult nsMsgDBView::ListCollapsedChildren(nsMsgViewIndex viewIndex, + nsIMutableArray *messageArray) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIMsgThread> thread; + GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr)); + if (!msgHdr) + { + NS_ASSERTION(false, "couldn't find message to expand"); + return NS_MSG_MESSAGE_NOT_FOUND; + } + nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numChildren; + thread->GetNumChildren(&numChildren); + for (uint32_t i = 1; i < numChildren && NS_SUCCEEDED(rv); i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = thread->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + if (!msgHdr) + continue; + rv = messageArray->AppendElement(msgHdr, false); + } + return rv; +} + +bool nsMsgDBView::OperateOnMsgsInCollapsedThreads() +{ + if (mTreeSelection) + { + nsCOMPtr<nsITreeBoxObject> selTree; + mTreeSelection->GetTree(getter_AddRefs(selTree)); + // no tree means stand-alone message window + if (!selTree) + return false; + } + + nsresult rv = NS_OK; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, false); + + bool includeCollapsedMsgs = false; + prefBranch->GetBoolPref("mail.operate_on_msgs_in_collapsed_threads", &includeCollapsedMsgs); + return includeCollapsedMsgs; +} + +nsresult nsMsgDBView::GetHeadersFromSelection(uint32_t *indices, + uint32_t numIndices, + nsIMutableArray *messageArray) +{ + nsresult rv = NS_OK; + + // Don't include collapsed messages if the front end failed to summarize + // the selection. + bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads() && + !mSummarizeFailed; + + for (uint32_t index = 0; + index < (nsMsgViewIndex) numIndices && NS_SUCCEEDED(rv); index++) + { + nsMsgViewIndex viewIndex = indices[index]; + if (viewIndex == nsMsgViewIndex_None) + continue; + uint32_t viewIndexFlags = m_flags[viewIndex]; + if (viewIndexFlags & MSG_VIEW_FLAG_DUMMY) + { + // if collapsed dummy header selected, list its children + if (includeCollapsedMsgs && viewIndexFlags & nsMsgMessageFlags::Elided && + m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + rv = ListCollapsedChildren(viewIndex, messageArray); + continue; + } + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv) && msgHdr) + { + rv = messageArray->AppendElement(msgHdr, false); + if (NS_SUCCEEDED(rv) && includeCollapsedMsgs && + viewIndexFlags & nsMsgMessageFlags::Elided && + viewIndexFlags & MSG_VIEW_FLAG_HASCHILDREN && + m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + rv = ListCollapsedChildren(viewIndex, messageArray); + } + } + } + return rv; +} + +nsresult +nsMsgDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder) +{ + if (m_deletingRows) + { + NS_ASSERTION(false, "Last move did not complete"); + return NS_OK; + } + + nsresult rv; + NS_ENSURE_ARG_POINTER(destFolder); + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(indices, numIndices, messageArray); + NS_ENSURE_SUCCESS(rv, rv); + + m_deletingRows = isMove && mDeleteModel != nsMsgImapDeleteModels::IMAPDelete; + if (m_deletingRows) + mIndicesToNoteChange.AppendElements(indices, numIndices); + + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return copyService->CopyMessages(m_folder /* source folder */, messageArray, destFolder, isMove, nullptr /* listener */, window, true /*allowUndo*/); +} + +nsresult +nsMsgDBView::ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, + int32_t numIndices, nsIMsgFolder *destFolder) +{ + nsresult rv = NS_OK; + NS_ENSURE_ARG_POINTER(destFolder); + + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + switch (command) { + case nsMsgViewCommandType::copyMessages: + NS_ASSERTION(!(m_folder == destFolder), "The source folder and the destination folder are the same"); + if (m_folder != destFolder) + rv = CopyMessages(msgWindow, indices, numIndices, false /* isMove */, destFolder); + break; + case nsMsgViewCommandType::moveMessages: + NS_ASSERTION(!(m_folder == destFolder), "The source folder and the destination folder are the same"); + if (m_folder != destFolder) + rv = CopyMessages(msgWindow, indices, numIndices, true /* isMove */, destFolder); + break; + default: + NS_ASSERTION(false, "unhandled command"); + rv = NS_ERROR_UNEXPECTED; + break; + } + return rv; +} + +nsresult +nsMsgDBView::ApplyCommandToIndices(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, + int32_t numIndices) +{ + NS_ASSERTION(numIndices >= 0, "nsMsgDBView::ApplyCommandToIndices(): " + "numIndices is negative!"); + + if (numIndices == 0) + return NS_OK; // return quietly, just in case + + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = GetFolderForViewIndex(indices[0], getter_AddRefs(folder)); + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + if (command == nsMsgViewCommandType::deleteMsg) + return DeleteMessages(msgWindow, indices, numIndices, false); + if (command == nsMsgViewCommandType::deleteNoTrash) + return DeleteMessages(msgWindow, indices, numIndices, true); + + nsTArray<nsMsgKey> imapUids; + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder); + bool thisIsImapFolder = (imapFolder != nullptr); + nsCOMPtr<nsIJunkMailPlugin> junkPlugin; + + // if this is a junk command, get the junk plugin. + if (command == nsMsgViewCommandType::junk || + command == nsMsgViewCommandType::unjunk) + { + // get the folder from the first item; we assume that + // all messages in the view are from the same folder (no + // more junk status column in the 'search messages' dialog + // like in earlier versions...) + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFilterPlugin> filterPlugin; + rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin)); + NS_ENSURE_SUCCESS(rv, rv); + + junkPlugin = do_QueryInterface(filterPlugin, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!mJunkHdrs) + { + mJunkHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, true /*dbBatching*/); + + // no sense going through the code that handles messages in collasped threads + // for mark thread read. + if (command == nsMsgViewCommandType::markThreadRead) + { + for (int32_t index = 0; index < numIndices; index++) + SetThreadOfMsgReadByIndex(indices[index], imapUids, true); + } + else + { + // Turn the selection into an array of msg hdrs. This may include messages + // in collapsed threads + uint32_t length; + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(indices, numIndices, messages); + NS_ENSURE_SUCCESS(rv, rv); + messages->GetLength(&length); + + if (thisIsImapFolder) + imapUids.SetLength(length); + + for (uint32_t i = 0; i < length; i++) + { + nsMsgKey msgKey; + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i); + msgHdr->GetMessageKey(&msgKey); + if (thisIsImapFolder) + imapUids[i] = msgKey; + + switch (command) + { + case nsMsgViewCommandType::junk: + mNumMessagesRemainingInBatch++; + mJunkHdrs->AppendElement(msgHdr, false); + rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr, + nsIJunkMailPlugin::JUNK); + break; + case nsMsgViewCommandType::unjunk: + mNumMessagesRemainingInBatch++; + mJunkHdrs->AppendElement(msgHdr, false); + rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr, + nsIJunkMailPlugin::GOOD); + break; + case nsMsgViewCommandType::toggleMessageRead: + case nsMsgViewCommandType::undeleteMsg: + case nsMsgViewCommandType::markMessagesRead: + case nsMsgViewCommandType::markMessagesUnread: + case nsMsgViewCommandType::unflagMessages: + case nsMsgViewCommandType::flagMessages: + break; // this is completely handled in the code below. + default: + NS_ERROR("unhandled command"); + break; + } + } + + switch (command) + { + case nsMsgViewCommandType::toggleMessageRead: + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, 0); + if (!msgHdr) + break; + uint32_t msgFlags; + msgHdr->GetFlags(&msgFlags); + folder->MarkMessagesRead(messages, + !(msgFlags & nsMsgMessageFlags::Read)); + } + break; + case nsMsgViewCommandType::markMessagesRead: + case nsMsgViewCommandType::markMessagesUnread: + folder->MarkMessagesRead(messages, + command == nsMsgViewCommandType::markMessagesRead); + break; + case nsMsgViewCommandType::unflagMessages: + case nsMsgViewCommandType::flagMessages: + folder->MarkMessagesFlagged(messages, + command == nsMsgViewCommandType::flagMessages); + break; + default: + break; + + } + // Provide junk-related batch notifications + if ((command == nsMsgViewCommandType::junk) || + (command == nsMsgViewCommandType::unjunk)) { + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyItemEvent(messages, + NS_LITERAL_CSTRING("JunkStatusChanged"), + (command == nsMsgViewCommandType::junk) ? + kJunkMsgAtom : kNotJunkMsgAtom); + } + } + + folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, true /*dbBatching*/); + + if (thisIsImapFolder) + { + imapMessageFlagsType flags = kNoImapMsgFlag; + bool addFlags = false; + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + switch (command) + { + case nsMsgViewCommandType::markThreadRead: + flags |= kImapMsgSeenFlag; + addFlags = true; + break; + case nsMsgViewCommandType::undeleteMsg: + flags = kImapMsgDeletedFlag; + addFlags = false; + break; + case nsMsgViewCommandType::junk: + return imapFolder->StoreCustomKeywords(msgWindow, + NS_LITERAL_CSTRING("Junk"), + NS_LITERAL_CSTRING("NonJunk"), + imapUids.Elements(), imapUids.Length(), + nullptr); + case nsMsgViewCommandType::unjunk: + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetHdrForFirstSelectedMessage(getter_AddRefs(msgHdr)); + uint32_t msgFlags = 0; + if (msgHdr) + msgHdr->GetFlags(&msgFlags); + if (msgFlags & nsMsgMessageFlags::IMAPDeleted) + imapFolder->StoreImapFlags(kImapMsgDeletedFlag, false, + imapUids.Elements(), + imapUids.Length(), nullptr); + return imapFolder->StoreCustomKeywords(msgWindow, + NS_LITERAL_CSTRING("NonJunk"), + NS_LITERAL_CSTRING("Junk"), + imapUids.Elements(), imapUids.Length(), + nullptr); + } + + default: + break; + } + + if (flags != kNoImapMsgFlag) // can't get here without thisIsImapThreadPane == TRUE + imapFolder->StoreImapFlags(flags, addFlags, imapUids.Elements(), imapUids.Length(), nullptr); + + } + + return rv; +} + +// view modifications methods by index + +// This method just removes the specified line from the view. It does +// NOT delete it from the database. +nsresult nsMsgDBView::RemoveByIndex(nsMsgViewIndex index) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + m_keys.RemoveElementAt(index); + m_flags.RemoveElementAt(index); + m_levels.RemoveElementAt(index); + + // the call to NoteChange() has to happen after we remove the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + if (!m_deletingRows) + NoteChange(index, -1, nsMsgViewNotificationCode::insertOrDelete); // an example where view is not the listener - D&D messages + + return NS_OK; +} + +nsresult nsMsgDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) +{ + if (m_deletingRows) + { + NS_WARNING("Last delete did not complete"); + return NS_OK; + } + nsresult rv; + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(indices, numIndices, messageArray); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numMsgs; + messageArray->GetLength(&numMsgs); + + const char *warnCollapsedPref = "mail.warn_on_collapsed_thread_operation"; + const char *warnShiftDelPref = "mail.warn_on_shift_delete"; + const char *warnNewsPref = "news.warn_on_delete"; + const char *warnTrashDelPref = "mail.warn_on_delete_from_trash"; + const char *activePref = nullptr; + nsString warningName; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool trashFolder = false; + rv = m_folder->GetFlag(nsMsgFolderFlags::Trash, &trashFolder); + NS_ENSURE_SUCCESS(rv, rv); + + if (trashFolder) + { + bool pref = false; + prefBranch->GetBoolPref(warnTrashDelPref, &pref); + if (pref) + { + activePref = warnTrashDelPref; + warningName.AssignLiteral("confirmMsgDelete.deleteFromTrash.desc"); + } + } + + if (!activePref && (uint32_t(numIndices) != numMsgs)) + { + bool pref = false; + prefBranch->GetBoolPref(warnCollapsedPref, &pref); + if (pref) + { + activePref = warnCollapsedPref; + warningName.AssignLiteral("confirmMsgDelete.collapsed.desc"); + } + } + + if (!activePref && deleteStorage && !trashFolder) + { + bool pref = false; + prefBranch->GetBoolPref(warnShiftDelPref, &pref); + if (pref) + { + activePref = warnShiftDelPref; + warningName.AssignLiteral("confirmMsgDelete.deleteNoTrash.desc"); + } + } + + if (!activePref && mIsNews) + { + bool pref = false; + prefBranch->GetBoolPref(warnNewsPref, &pref); + if (pref) + { + activePref = warnNewsPref; + warningName.AssignLiteral("confirmMsgDelete.deleteNoTrash.desc"); + } + } + + if (activePref) + { + nsCOMPtr<nsIPrompt> dialog; + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = wwatch->GetNewPrompter(0, getter_AddRefs(dialog)); + NS_ENSURE_SUCCESS(rv, rv); + bool dontAsk = false; // "Don't ask..." - unchecked by default. + int32_t buttonPressed = 0; + + nsString dialogTitle; + nsString confirmString; + nsString checkboxText; + nsString buttonApplyNowText; + dialogTitle.Adopt(GetString(u"confirmMsgDelete.title")); + checkboxText.Adopt(GetString(u"confirmMsgDelete.dontAsk.label")); + buttonApplyNowText.Adopt(GetString(u"confirmMsgDelete.delete.label")); + + confirmString.Adopt(GetString(warningName.get())); + + const uint32_t buttonFlags = + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1); + rv = dialog->ConfirmEx(dialogTitle.get(), confirmString.get(), buttonFlags, + buttonApplyNowText.get(), nullptr, nullptr, + checkboxText.get(), &dontAsk, &buttonPressed); + NS_ENSURE_SUCCESS(rv, rv); + if (buttonPressed) + return NS_ERROR_FAILURE; + if (dontAsk) + prefBranch->SetBoolPref(activePref, false); + } + + if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete) + m_deletingRows = true; + + if (m_deletingRows) + mIndicesToNoteChange.AppendElements(indices, numIndices); + + rv = m_folder->DeleteMessages(messageArray, window, deleteStorage, false, nullptr, true /*allow Undo*/ ); + if (NS_FAILED(rv)) + m_deletingRows = false; + return rv; +} + +nsresult nsMsgDBView::DownloadForOffline(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++) + { + nsMsgKey key = m_keys[indices[index]]; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + if (msgHdr) + { + uint32_t flags; + msgHdr->GetFlags(&flags); + if (!(flags & nsMsgMessageFlags::Offline)) + messageArray->AppendElement(msgHdr, false); + } + } + m_folder->DownloadMessagesForOffline(messageArray, window); + return rv; +} + +nsresult nsMsgDBView::DownloadFlaggedForOffline(nsIMsgWindow *window) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + nsCOMPtr <nsISimpleEnumerator> enumerator; + rv = GetMessageEnumerator(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(rv) && enumerator) + { + bool hasMore; + + while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr <nsISupports> supports; + rv = enumerator->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports); + if (pHeader && NS_SUCCEEDED(rv)) + { + uint32_t flags; + pHeader->GetFlags(&flags); + if ((flags & nsMsgMessageFlags::Marked) && !(flags & nsMsgMessageFlags::Offline)) + messageArray->AppendElement(pHeader, false); + } + } + } + m_folder->DownloadMessagesForOffline(messageArray, window); + return rv; +} + +// read/unread handling. +nsresult nsMsgDBView::ToggleReadByIndex(nsMsgViewIndex index) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + return SetReadByIndex(index, !(m_flags[index] & nsMsgMessageFlags::Read)); +} + +nsresult nsMsgDBView::SetReadByIndex(nsMsgViewIndex index, bool read) +{ + nsresult rv; + + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + if (read) + { + OrExtraFlag(index, nsMsgMessageFlags::Read); + // MarkRead() will clear this flag in the db + // and then call OnKeyChange(), but + // because we are the instigator of the change + // we'll ignore the change. + // + // so we need to clear it in m_flags + // to keep the db and m_flags in sync + AndExtraFlag(index, ~nsMsgMessageFlags::New); + } + else + { + AndExtraFlag(index, ~nsMsgMessageFlags::Read); + } + + nsCOMPtr <nsIMsgDatabase> dbToUse; + rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dbToUse->MarkRead(m_keys[index], read, this); + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + nsMsgViewIndex threadIndex = GetThreadIndex(index); + if (threadIndex != index) + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + } + return rv; +} + +nsresult nsMsgDBView::SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsTArray<nsMsgKey> &keysMarkedRead, bool /*read*/) +{ + nsresult rv; + + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + rv = MarkThreadOfMsgRead(m_keys[index], index, keysMarkedRead, true); + return rv; +} + +nsresult nsMsgDBView::SetFlaggedByIndex(nsMsgViewIndex index, bool mark) +{ + nsresult rv; + + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + nsCOMPtr <nsIMsgDatabase> dbToUse; + rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv, rv); + + if (mark) + OrExtraFlag(index, nsMsgMessageFlags::Marked); + else + AndExtraFlag(index, ~nsMsgMessageFlags::Marked); + + rv = dbToUse->MarkMarked(m_keys[index], mark, this); + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + return rv; +} + +nsresult nsMsgDBView::SetMsgHdrJunkStatus(nsIJunkMailPlugin *aJunkPlugin, + nsIMsgDBHdr *aMsgHdr, + nsMsgJunkStatus aNewClassification) +{ + // get the old junk score + // + nsCString junkScoreStr; + nsresult rv = aMsgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + + // and the old origin + // + nsCString oldOriginStr; + rv = aMsgHdr->GetStringProperty("junkscoreorigin", + getter_Copies(oldOriginStr)); + + // if this was not classified by the user, say so + // + nsMsgJunkStatus oldUserClassification; + if (oldOriginStr.get()[0] != 'u') { + oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED; + } + else { + // otherwise, pass the actual user classification + if (junkScoreStr.IsEmpty()) + oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED; + else if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE) + oldUserClassification = nsIJunkMailPlugin::JUNK; + else + oldUserClassification = nsIJunkMailPlugin::GOOD; + + NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed."); + } + + // get the URI for this message so we can pass it to the plugin + // + nsCString uri; + nsMsgKey msgKey; + nsCOMPtr<nsIMsgFolder> folder; + nsCOMPtr<nsIMsgDatabase> db; + aMsgHdr->GetMessageKey(&msgKey); + rv = aMsgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + GenerateURIForMsgKey(msgKey, folder, uri); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + + // tell the plugin about this change, so that it can (potentially) + // adjust its database appropriately + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + rv = aJunkPlugin->SetMessageClassification( + uri.get(), oldUserClassification, aNewClassification, msgWindow, this); + NS_ENSURE_SUCCESS(rv, rv); + + // this routine is only reached if the user someone touched the UI + // and told us the junk status of this message. + // Set origin first so that listeners on the junkscore will + // know the correct origin. + rv = db->SetStringProperty(msgKey, "junkscoreorigin", "user"); + NS_ASSERTION(NS_SUCCEEDED(rv), "SetStringPropertyByIndex failed"); + + // set the junk score on the message itself + + nsAutoCString msgJunkScore; + msgJunkScore.AppendInt(aNewClassification == nsIJunkMailPlugin::JUNK ? + nsIJunkMailPlugin::IS_SPAM_SCORE: + nsIJunkMailPlugin::IS_HAM_SCORE); + db->SetStringProperty(msgKey, "junkscore", msgJunkScore.get()); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +nsresult +nsMsgDBView::GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder) +{ + NS_IF_ADDREF(*aFolder = m_folder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::OnMessageClassified(const char *aMsgURI, + nsMsgJunkStatus aClassification, + uint32_t aJunkPercent) + +{ + // Note: we know all messages in a batch have the same + // classification, since unlike OnMessageClassified + // methods in other classes (such as nsLocalMailFolder + // and nsImapMailFolder), this class, nsMsgDBView, currently + // only triggers message classifications due to a command to + // mark some of the messages in the view as junk, or as not + // junk - so the classification is dictated to the filter, + // not suggested by it. + // + // for this reason the only thing we (may) have to do is + // perform the action on all of the junk messages + // + + uint32_t numJunk; + mJunkHdrs->GetLength(&numJunk); + NS_ASSERTION((aClassification == nsIJunkMailPlugin::GOOD) || + numJunk, + "the classification of a manually-marked junk message has" + "been classified as junk, yet there seem to be no such outstanding messages"); + + // is this the last message in the batch? + + if (--mNumMessagesRemainingInBatch == 0 && numJunk > 0) + { + PerformActionsOnJunkMsgs(aClassification == nsIJunkMailPlugin::JUNK); + mJunkHdrs->Clear(); + } + return NS_OK; +} + +nsresult +nsMsgDBView::PerformActionsOnJunkMsgs(bool msgsAreJunk) +{ + uint32_t numJunkHdrs; + mJunkHdrs->GetLength(&numJunkHdrs); + if (!numJunkHdrs) + { + NS_ERROR("no indices of marked-as-junk messages to act on"); + return NS_OK; + } + + nsCOMPtr<nsIMsgFolder> srcFolder; + nsCOMPtr<nsIMsgDBHdr> firstHdr(do_QueryElementAt(mJunkHdrs, 0)); + firstHdr->GetFolder(getter_AddRefs(srcFolder)); + + bool moveMessages, changeReadState; + nsCOMPtr<nsIMsgFolder> targetFolder; + + nsresult rv = DetermineActionsForJunkChange(msgsAreJunk, srcFolder, + moveMessages, changeReadState, + getter_AddRefs(targetFolder)); + NS_ENSURE_SUCCESS(rv,rv); + + // nothing to do, bail out + if (!(moveMessages || changeReadState)) + return NS_OK; + if (changeReadState) + { + // notes on marking junk as read: + // 1. there are 2 occasions on which junk messages are marked as + // read: after a manual marking (here and in the front end) and after + // automatic classification by the bayesian filter (see code for local + // mail folders and for imap mail folders). The server-specific + // markAsReadOnSpam pref only applies to the latter, the former is + // controlled by "mailnews.ui.junk.manualMarkAsJunkMarksRead". + // 2. even though move/delete on manual mark may be + // turned off, we might still need to mark as read + + NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); + rv = srcFolder->MarkMessagesRead(mJunkHdrs, msgsAreJunk); + NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + NS_ASSERTION(NS_SUCCEEDED(rv), "marking marked-as-junk messages as read failed"); + } + if (moveMessages) + { + // check if one of the messages to be junked is actually selected + // if more than one message being junked, one must be selected. + // if no tree selection at all, must be in stand-alone message window. + bool junkedMsgSelected = numJunkHdrs > 1 || !mTreeSelection; + for (nsMsgViewIndex junkIndex = 0; !junkedMsgSelected && junkIndex < numJunkHdrs; junkIndex++) + { + nsCOMPtr<nsIMsgDBHdr> junkHdr(do_QueryElementAt(mJunkHdrs, junkIndex, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsMsgViewIndex hdrIndex = FindHdr(junkHdr); + if (hdrIndex != nsMsgViewIndex_None) + mTreeSelection->IsSelected(hdrIndex, &junkedMsgSelected); + } + + // if a junked msg is selected, tell the FE to call SetNextMessageAfterDelete() because a delete is coming + if (junkedMsgSelected && mCommandUpdater) + { + rv = mCommandUpdater->UpdateNextMessageAfterDelete(); + NS_ENSURE_SUCCESS(rv,rv); + } + + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + NoteStartChange(nsMsgViewNotificationCode::none, 0, 0); + if (targetFolder) + { + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyService->CopyMessages(srcFolder , mJunkHdrs, targetFolder, + true, nullptr, msgWindow, true); + } + else if (msgsAreJunk) + { + if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) + { + // Unfortunately the DeleteMessages in this case is interpreted by IMAP as + // a delete toggle. So what we have to do is to assemble a new delete + // array, keeping only those that are not deleted. + // + nsCOMPtr<nsIMutableArray> hdrsToDelete = do_CreateInstance("@mozilla.org/array;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t cnt; + rv = mJunkHdrs->GetLength(&cnt); + for (uint32_t i = 0; i < cnt; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(mJunkHdrs, i); + if (msgHdr) + { + uint32_t flags; + msgHdr->GetFlags(&flags); + if (!(flags & nsMsgMessageFlags::IMAPDeleted)) + hdrsToDelete->AppendElement(msgHdr, false); + } + } + hdrsToDelete->GetLength(&cnt); + if (cnt) + rv = srcFolder->DeleteMessages(hdrsToDelete, msgWindow, false, false, + nullptr, true); + } + else + rv = srcFolder->DeleteMessages(mJunkHdrs, msgWindow, false, false, + nullptr, true); + + } + else if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) + { + nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(srcFolder)); + nsTArray<nsMsgKey> imapUids; + imapUids.SetLength(numJunkHdrs); + for (uint32_t i = 0; i < numJunkHdrs; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(mJunkHdrs, i); + msgHdr->GetMessageKey(&imapUids[i]); + } + + imapFolder->StoreImapFlags(kImapMsgDeletedFlag, false, imapUids.Elements(), + imapUids.Length(), nullptr); + } + NoteEndChange(nsMsgViewNotificationCode::none, 0, 0); + + NS_ASSERTION(NS_SUCCEEDED(rv), "move or deletion of message marked-as-junk/non junk failed"); + } + return rv; +} + +nsresult +nsMsgDBView::DetermineActionsForJunkChange(bool msgsAreJunk, + nsIMsgFolder *srcFolder, + bool &moveMessages, + bool &changeReadState, + nsIMsgFolder** targetFolder) +{ + // there are two possible actions which may be performed + // on messages marked as spam: marking as read and moving + // somewhere...When a message is marked as non junk, + // it may be moved to the inbox, and marked unread. + + moveMessages = false; + changeReadState = false; + + // ... the 'somewhere', junkTargetFolder, can be a folder, + // but if it remains null we'll delete the messages + + *targetFolder = nullptr; + + uint32_t folderFlags; + srcFolder->GetFlags(&folderFlags); + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = srcFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // handle the easy case of marking a junk message as good first... + // set the move target folder to the inbox, if any. + if (!msgsAreJunk) + { + if (folderFlags & nsMsgFolderFlags::Junk) + { + prefBranch->GetBoolPref("mail.spam.markAsNotJunkMarksUnRead", + &changeReadState); + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_SUCCESS(rv,rv); + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, targetFolder); + moveMessages = targetFolder != nullptr; + } + return NS_OK; + } + + nsCOMPtr <nsISpamSettings> spamSettings; + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + // When the user explicitly marks a message as junk, we can mark it as read, + // too. This is independent of the "markAsReadOnSpam" pref, which applies + // only to automatically-classified messages. + // Note that this behaviour should match the one in the front end for marking + // as junk via toolbar/context menu. + prefBranch->GetBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead", + &changeReadState); + + // now let's determine whether we'll be taking the second action, + // the move / deletion (and also determine which of these two) + + bool manualMark; + (void)spamSettings->GetManualMark(&manualMark); + if (!manualMark) + return NS_OK; + + int32_t manualMarkMode; + (void)spamSettings->GetManualMarkMode(&manualMarkMode); + NS_ASSERTION(manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE + || manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE, + "bad manual mark mode"); + + if (manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE) + { + // if this is a junk folder + // (not only "the" junk folder for this account) + // don't do the move + if (folderFlags & nsMsgFolderFlags::Junk) + return NS_OK; + + nsCString spamFolderURI; + rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI)); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(!spamFolderURI.IsEmpty(), "spam folder URI is empty, can't move"); + if (!spamFolderURI.IsEmpty()) + { + rv = GetExistingFolder(spamFolderURI, targetFolder); + if (NS_SUCCEEDED(rv) && *targetFolder) + { + moveMessages = true; + } + else + { + // XXX ToDo: GetOrCreateFolder will only create a folder with localized + // name "Junk" regardless of spamFolderURI. So if someone + // sets the junk folder to an existing folder of a different + // name, then deletes that folder, this will fail to create + // the correct folder. + rv = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */); + if (NS_SUCCEEDED(rv)) + rv = GetExistingFolder(spamFolderURI, targetFolder); + NS_ASSERTION(NS_SUCCEEDED(rv) && *targetFolder, "GetOrCreateFolder failed"); + } + } + return NS_OK; + } + + // at this point manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE) + + // if this is in the trash, let's not delete + if (folderFlags & nsMsgFolderFlags::Trash) + return NS_OK; + + return srcFolder->GetCanDeleteMessages(&moveMessages); +} + +// reversing threads involves reversing the threads but leaving the +// expanded messages ordered relative to the thread, so we +// make a copy of each array and copy them over. +void nsMsgDBView::ReverseThreads() +{ + nsTArray<uint32_t> newFlagArray; + nsTArray<nsMsgKey> newKeyArray; + nsTArray<uint8_t> newLevelArray; + + uint32_t viewSize = GetSize(); + uint32_t startThread = viewSize; + uint32_t nextThread = viewSize; + uint32_t destIndex = 0; + + newKeyArray.SetLength(m_keys.Length()); + newFlagArray.SetLength(m_flags.Length()); + newLevelArray.SetLength(m_levels.Length()); + + while (startThread) + { + startThread--; + + if (m_flags[startThread] & MSG_VIEW_FLAG_ISTHREAD) + { + for (uint32_t sourceIndex = startThread; sourceIndex < nextThread; sourceIndex++) + { + newKeyArray[destIndex] = m_keys[sourceIndex]; + newFlagArray[destIndex] = m_flags[sourceIndex]; + newLevelArray[destIndex] = m_levels[sourceIndex]; + destIndex++; + } + nextThread = startThread; // because we're copying in reverse order + } + } + m_keys.SwapElements(newKeyArray); + m_flags.SwapElements(newFlagArray); + m_levels.SwapElements(newLevelArray); +} + +void nsMsgDBView::ReverseSort() +{ + uint32_t topIndex = GetSize(); + + nsCOMArray<nsIMsgFolder> *folders = GetFolders(); + + // go up half the array swapping values + for (uint32_t bottomIndex = 0; bottomIndex < --topIndex; bottomIndex++) + { + // swap flags + uint32_t tempFlags = m_flags[bottomIndex]; + m_flags[bottomIndex] = m_flags[topIndex]; + m_flags[topIndex] = tempFlags; + + // swap keys + nsMsgKey tempKey = m_keys[bottomIndex]; + m_keys[bottomIndex] = m_keys[topIndex]; + m_keys[topIndex] = tempKey; + + if (folders) + { + // swap folders -- + // needed when search is done across multiple folders + nsIMsgFolder *bottomFolder = folders->ObjectAt(bottomIndex); + nsIMsgFolder *topFolder = folders->ObjectAt(topIndex); + folders->ReplaceObjectAt(topFolder, bottomIndex); + folders->ReplaceObjectAt(bottomFolder, topIndex); + } + // no need to swap elements in m_levels; since we only call + // ReverseSort in non-threaded mode, m_levels are all the same. + } +} +int +nsMsgDBView::FnSortIdKey(const void *pItem1, const void *pItem2, void *privateData) +{ + int32_t retVal = 0; + + IdKey** p1 = (IdKey**)pItem1; + IdKey** p2 = (IdKey**)pItem2; + viewSortInfo* sortInfo = (viewSortInfo *) privateData; + + nsIMsgDatabase *db = sortInfo->db; + + mozilla::DebugOnly<nsresult> rv = db->CompareCollationKeys((*p1)->dword, (*p1)->key, + (*p2)->dword, (*p2)->key, + &retVal); + NS_ASSERTION(NS_SUCCEEDED(rv), "compare failed"); + + if (retVal) + return sortInfo->ascendingSort ? retVal : -retVal; + if (sortInfo->view->m_secondarySort == nsMsgViewSortType::byId) + return (sortInfo->view->m_secondarySortOrder == nsMsgViewSortOrder::ascending && + (*p1)->id >= (*p2)->id) ? 1 : -1; + else + return sortInfo->view->SecondarySort((*p1)->id, (*p1)->folder, (*p2)->id, (*p2)->folder, sortInfo); + // here we'd use the secondary sort +} + +int +nsMsgDBView::FnSortIdKeyPtr(const void *pItem1, const void *pItem2, void *privateData) +{ + int32_t retVal = 0; + + IdKeyPtr** p1 = (IdKeyPtr**)pItem1; + IdKeyPtr** p2 = (IdKeyPtr**)pItem2; + viewSortInfo* sortInfo = (viewSortInfo *) privateData; + + nsIMsgDatabase *db = sortInfo->db; + + mozilla::DebugOnly<nsresult> rv = db->CompareCollationKeys((*p1)->dword, (*p1)->key, + (*p2)->dword, (*p2)->key, + &retVal); + NS_ASSERTION(NS_SUCCEEDED(rv), "compare failed"); + + if (retVal) + return sortInfo->ascendingSort ? retVal : -retVal; + + if (sortInfo->view->m_secondarySort == nsMsgViewSortType::byId) + return (sortInfo->view->m_secondarySortOrder == nsMsgViewSortOrder::ascending && + (*p1)->id >= (*p2)->id) ? 1 : -1; + else + return sortInfo->view->SecondarySort((*p1)->id, (*p1)->folder, (*p2)->id, (*p2)->folder, sortInfo); +} + +int +nsMsgDBView::FnSortIdUint32(const void *pItem1, const void *pItem2, void *privateData) +{ + IdUint32** p1 = (IdUint32**)pItem1; + IdUint32** p2 = (IdUint32**)pItem2; + viewSortInfo* sortInfo = (viewSortInfo *) privateData; + + if ((*p1)->dword > (*p2)->dword) + return (sortInfo->ascendingSort) ? 1 : -1; + else if ((*p1)->dword < (*p2)->dword) + return (sortInfo->ascendingSort) ? -1 : 1; + if (sortInfo->view->m_secondarySort == nsMsgViewSortType::byId) + return (sortInfo->view->m_secondarySortOrder == nsMsgViewSortOrder::ascending && + (*p1)->id >= (*p2)->id) ? 1 : -1; + else + return sortInfo->view->SecondarySort((*p1)->id, (*p1)->folder, (*p2)->id, (*p2)->folder, sortInfo); +} + + +// XXX are these still correct? +//To compensate for memory alignment required for +//systems such as HP-UX these values must be 4 bytes +//aligned. Don't break this when modify the constants +const int kMaxSubjectKey = 160; +const int kMaxLocationKey = 160; // also used for account +const int kMaxAuthorKey = 160; +const int kMaxRecipientKey = 80; + +// +// There are cases when pFieldType is not set: +// one case returns NS_ERROR_UNEXPECTED; +// the other case now return NS_ERROR_NULL_POINTER (this is only when +// colHandler below is null, but is very unlikely). +// The latter case used to return NS_OK, which was incorrect. +// +nsresult nsMsgDBView::GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType, + uint16_t *pMaxLen, + eFieldType *pFieldType, + nsIMsgCustomColumnHandler* colHandler) +{ + NS_ENSURE_ARG_POINTER(pMaxLen); + NS_ENSURE_ARG_POINTER(pFieldType); + + switch (sortType) + { + case nsMsgViewSortType::bySubject: + *pFieldType = kCollationKey; + *pMaxLen = kMaxSubjectKey; + break; + case nsMsgViewSortType::byAccount: + case nsMsgViewSortType::byTags: + case nsMsgViewSortType::byLocation: + *pFieldType = kCollationKey; + *pMaxLen = kMaxLocationKey; + break; + case nsMsgViewSortType::byRecipient: + case nsMsgViewSortType::byCorrespondent: + *pFieldType = kCollationKey; + *pMaxLen = kMaxRecipientKey; + break; + case nsMsgViewSortType::byAuthor: + *pFieldType = kCollationKey; + *pMaxLen = kMaxAuthorKey; + break; + case nsMsgViewSortType::byDate: + case nsMsgViewSortType::byReceived: + case nsMsgViewSortType::byPriority: + case nsMsgViewSortType::byThread: + case nsMsgViewSortType::byId: + case nsMsgViewSortType::bySize: + case nsMsgViewSortType::byFlagged: + case nsMsgViewSortType::byUnread: + case nsMsgViewSortType::byStatus: + case nsMsgViewSortType::byJunkStatus: + case nsMsgViewSortType::byAttachments: + *pFieldType = kU32; + *pMaxLen = 0; + break; + case nsMsgViewSortType::byCustom: + { + if (colHandler == nullptr) + { + NS_WARNING("colHandler is null. *pFieldType is not set."); + return NS_ERROR_NULL_POINTER; + } + + bool isString; + colHandler->IsString(&isString); + + if (isString) + { + *pFieldType = kCollationKey; + *pMaxLen = kMaxRecipientKey; //80 - do we need a seperate k? + } + else + { + *pFieldType = kU32; + *pMaxLen = 0; + } + break; + } + case nsMsgViewSortType::byNone: // bug 901948 + return NS_ERROR_INVALID_ARG; + default: + { + nsAutoCString message("unexpected switch value: sortType="); + message.AppendInt(sortType); + NS_WARNING(message.get()); + return NS_ERROR_UNEXPECTED; + } + } + + return NS_OK; +} + +#define MSG_STATUS_MASK (nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded) + +nsresult nsMsgDBView::GetStatusSortValue(nsIMsgDBHdr *msgHdr, uint32_t *result) +{ + NS_ENSURE_ARG_POINTER(msgHdr); + NS_ENSURE_ARG_POINTER(result); + + uint32_t messageFlags; + nsresult rv = msgHdr->GetFlags(&messageFlags); + NS_ENSURE_SUCCESS(rv,rv); + + if (messageFlags & nsMsgMessageFlags::New) + { + // happily, new by definition stands alone + *result = 0; + return NS_OK; + } + + switch (messageFlags & MSG_STATUS_MASK) + { + case nsMsgMessageFlags::Replied: + *result = 2; + break; + case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied: + *result = 1; + break; + case nsMsgMessageFlags::Forwarded: + *result = 3; + break; + default: + *result = (messageFlags & nsMsgMessageFlags::Read) ? 4 : 5; + break; + } + + return NS_OK; +} + +nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, uint32_t *result, nsIMsgCustomColumnHandler* colHandler) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(msgHdr); + NS_ENSURE_ARG_POINTER(result); + + bool isRead; + uint32_t bits; + + switch (sortType) + { + case nsMsgViewSortType::bySize: + rv = (mShowSizeInLines) ? msgHdr->GetLineCount(result) : msgHdr->GetMessageSize(result); + break; + case nsMsgViewSortType::byPriority: + nsMsgPriorityValue priority; + rv = msgHdr->GetPriority(&priority); + + // treat "none" as "normal" when sorting. + if (priority == nsMsgPriority::none) + priority = nsMsgPriority::normal; + + // we want highest priority to have lowest value + // so ascending sort will have highest priority first. + *result = nsMsgPriority::highest - priority; + break; + case nsMsgViewSortType::byStatus: + rv = GetStatusSortValue(msgHdr,result); + break; + case nsMsgViewSortType::byFlagged: + bits = 0; + rv = msgHdr->GetFlags(&bits); + *result = !(bits & nsMsgMessageFlags::Marked); //make flagged come out on top. + break; + case nsMsgViewSortType::byUnread: + rv = msgHdr->GetIsRead(&isRead); + if (NS_SUCCEEDED(rv)) + *result = !isRead; + break; + case nsMsgViewSortType::byJunkStatus: + { + nsCString junkScoreStr; + rv = msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + // unscored messages should come before messages that are scored + // junkScoreStr is "", and "0" - "100" + // normalize to 0 - 101 + *result = junkScoreStr.IsEmpty() ? (0) : atoi(junkScoreStr.get()) + 1; + } + break; + case nsMsgViewSortType::byAttachments: + bits = 0; + rv = msgHdr->GetFlags(&bits); + *result = !(bits & nsMsgMessageFlags::Attachment); + break; + case nsMsgViewSortType::byDate: + // when sorting threads by date, we may want the date of the newest msg + // in the thread + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay + && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + && ! mSortThreadsByRoot) + { + nsCOMPtr <nsIMsgThread> thread; + rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread)); + if (NS_SUCCEEDED(rv)) + { + thread->GetNewestMsgDate(result); + break; + } + } + rv = msgHdr->GetDateInSeconds(result); + break; + case nsMsgViewSortType::byReceived: + // when sorting threads by received date, we may want the received date + // of the newest msg in the thread + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay + && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + && ! mSortThreadsByRoot) + { + nsCOMPtr <nsIMsgThread> thread; + rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread)); + NS_ENSURE_SUCCESS(rv, rv); + thread->GetNewestMsgDate(result); + } + else + { + rv = msgHdr->GetUint32Property("dateReceived", result); // Already in seconds... + if (*result == 0) // Use Date instead, we have no Received property + rv = msgHdr->GetDateInSeconds(result); + } + break; + case nsMsgViewSortType::byCustom: + if (colHandler != nullptr) + { + colHandler->GetSortLongForRow(msgHdr, result); + rv = NS_OK; + } + else + { + NS_ASSERTION(false, "should not be here (Sort Type: byCustom (Long), but no custom handler)"); + rv = NS_ERROR_UNEXPECTED; + } + break; + case nsMsgViewSortType::byNone: // Bug 901948 + return NS_ERROR_INVALID_ARG; + + case nsMsgViewSortType::byId: + // handled by caller, since caller knows the key + default: + NS_ERROR("should not be here"); + rv = NS_ERROR_UNEXPECTED; + break; + } + + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +MsgViewSortColumnInfo::MsgViewSortColumnInfo(const MsgViewSortColumnInfo &other) +{ + mSortType = other.mSortType; + mSortOrder = other.mSortOrder; + mCustomColumnName = other.mCustomColumnName; + mColHandler = other.mColHandler; +} + +bool MsgViewSortColumnInfo::operator== (const MsgViewSortColumnInfo& other) const +{ + return (mSortType == nsMsgViewSortType::byCustom) ? + mCustomColumnName.Equals(other.mCustomColumnName) : mSortType == other.mSortType; +} + +nsresult nsMsgDBView::EncodeColumnSort(nsString &columnSortString) +{ + for (uint32_t i = 0; i < m_sortColumns.Length(); i++) + { + MsgViewSortColumnInfo &sortInfo = m_sortColumns[i]; + columnSortString.Append((char) sortInfo.mSortType); + columnSortString.Append((char) sortInfo.mSortOrder + '0'); + if (sortInfo.mSortType == nsMsgViewSortType::byCustom) + { + columnSortString.Append(sortInfo.mCustomColumnName); + columnSortString.Append((char16_t) '\r'); + } + } + return NS_OK; +} + +nsresult nsMsgDBView::DecodeColumnSort(nsString &columnSortString) +{ + const char16_t *stringPtr = columnSortString.BeginReading(); + while (*stringPtr) + { + MsgViewSortColumnInfo sortColumnInfo; + sortColumnInfo.mSortType = (nsMsgViewSortTypeValue) *stringPtr++; + sortColumnInfo.mSortOrder = (nsMsgViewSortOrderValue) (*stringPtr++) - '0'; + if (sortColumnInfo.mSortType == nsMsgViewSortType::byCustom) + { + while (*stringPtr && *stringPtr != '\r') + sortColumnInfo.mCustomColumnName.Append(*stringPtr++); + sortColumnInfo.mColHandler = GetColumnHandler(sortColumnInfo.mCustomColumnName.get()); + if (*stringPtr) // advance past '\r' + stringPtr++; + } + m_sortColumns.AppendElement(sortColumnInfo); + } + return NS_OK; +} + +// cf. Secondary Sort Key: when you select a column to sort, that +// becomes the new Primary sort key, and all previous sort keys +// become secondary. For example, if you first click on Date, +// the messages are sorted by Date; then click on From, and now the +// messages are sorted by From, and for each value of From the +// messages are in Date order. + +void nsMsgDBView::PushSort(const MsgViewSortColumnInfo &newSort) +{ + // DONE + // Handle byNone (bug 901948) ala a mail/base/modules/dbViewerWrapper.js + // where we don't push the secondary sort type if it's ::byNone; + // (and secondary sort type is NOT the same as the first sort type + // there.). This code should behave the same way. + + // We don't expect to be passed sort type ::byNone, + // but if we are it's safe to ignore it. + if (newSort.mSortType == nsMsgViewSortType::byNone) + return; + + // Date and ID are unique keys, so if we are sorting by them we don't + // need to keep any secondary sort keys + if (newSort.mSortType == nsMsgViewSortType::byDate || + newSort.mSortType == nsMsgViewSortType::byId ) + m_sortColumns.Clear(); + m_sortColumns.RemoveElement(newSort); + m_sortColumns.InsertElementAt(0, newSort); + if (m_sortColumns.Length() > kMaxNumSortColumns) + m_sortColumns.RemoveElementAt(kMaxNumSortColumns); +} + +nsresult +nsMsgDBView::GetCollationKey(nsIMsgDBHdr *msgHdr, nsMsgViewSortTypeValue sortType, uint8_t **result, uint32_t *len, nsIMsgCustomColumnHandler* colHandler) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + NS_ENSURE_ARG_POINTER(msgHdr); + NS_ENSURE_ARG_POINTER(result); + + switch (sortType) + { + case nsMsgViewSortType::bySubject: + rv = msgHdr->GetSubjectCollationKey(len, result); + break; + case nsMsgViewSortType::byLocation: + rv = GetLocationCollationKey(msgHdr, result, len); + break; + case nsMsgViewSortType::byRecipient: + { + nsString recipients; + rv = FetchRecipients(msgHdr, recipients); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + if (!dbToUse) // probably search view + { + rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv,rv); + } + rv = dbToUse->CreateCollationKey(recipients, len, result); + } + } + break; + case nsMsgViewSortType::byAuthor: + { + rv = msgHdr->GetAuthorCollationKey(len, result); + nsString author; + rv = FetchAuthor(msgHdr, author); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + if (!dbToUse) // probably search view + { + rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv,rv); + } + rv = dbToUse->CreateCollationKey(author, len, result); + } + } + break; + case nsMsgViewSortType::byAccount: + case nsMsgViewSortType::byTags: + { + nsString str; + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + + if (!dbToUse) // probably search view + GetDBForViewIndex(0, getter_AddRefs(dbToUse)); + + rv = (sortType == nsMsgViewSortType::byAccount) + ? FetchAccount(msgHdr, str) + : FetchTags(msgHdr, str); + + if (NS_SUCCEEDED(rv) && dbToUse) + rv = dbToUse->CreateCollationKey(str, len, result); + } + break; + case nsMsgViewSortType::byCustom: + if (colHandler != nullptr) + { + nsAutoString strKey; + rv = colHandler->GetSortStringForRow(msgHdr, strKey); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get sort string for custom row"); + nsAutoString strTemp(strKey); + + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + if (!dbToUse) // probably search view + { + rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv,rv); + } + rv = dbToUse->CreateCollationKey(strKey, len, result); + } + else + { + NS_ERROR("should not be here (Sort Type: byCustom (String), but no custom handler)"); + rv = NS_ERROR_UNEXPECTED; + } + break; + case nsMsgViewSortType::byCorrespondent: + { + nsString value; + if (IsOutgoingMsg(msgHdr)) + rv = FetchRecipients(msgHdr, value); + else + rv = FetchAuthor(msgHdr, value); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + if (!dbToUse) // probably search view + { + rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv,rv); + } + rv = dbToUse->CreateCollationKey(value, len, result); + } + } + break; + default: + rv = NS_ERROR_UNEXPECTED; + break; + } + + // bailing out with failure will stop the sort and leave us in + // a bad state. try to continue on, instead + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to get the collation key"); + if (NS_FAILED(rv)) + { + *result = nullptr; + *len = 0; + } + return NS_OK; +} + +// As the location collation key is created getting folder from the msgHdr, +// it is defined in this file and not from the db. +nsresult +nsMsgDBView::GetLocationCollationKey(nsIMsgDBHdr *msgHdr, uint8_t **result, uint32_t *len) +{ + nsCOMPtr <nsIMsgFolder> folder; + + nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr <nsIMsgDatabase> dbToUse; + rv = folder->GetMsgDatabase(getter_AddRefs(dbToUse)); + NS_ENSURE_SUCCESS(rv,rv); + + nsString locationString; + rv = folder->GetPrettiestName(locationString); + NS_ENSURE_SUCCESS(rv,rv); + + return dbToUse->CreateCollationKey(locationString, len, result); +} + +nsresult nsMsgDBView::SaveSortInfo(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) +{ + if (m_viewFolder) + { + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsCOMPtr <nsIMsgDatabase> db; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && folderInfo) + { + // save off sort type and order, view type and flags + folderInfo->SetSortType(sortType); + folderInfo->SetSortOrder(sortOrder); + + nsString sortColumnsString; + rv = EncodeColumnSort(sortColumnsString); + NS_ENSURE_SUCCESS(rv, rv); + folderInfo->SetProperty("sortColumns", sortColumnsString); + } + } + return NS_OK; +} + +nsresult nsMsgDBView::RestoreSortInfo() +{ + if (!m_viewFolder) + return NS_OK; + + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsCOMPtr <nsIMsgDatabase> db; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && folderInfo) + { + // Restore m_sortColumns from db. + nsString sortColumnsString; + folderInfo->GetProperty("sortColumns", sortColumnsString); + DecodeColumnSort(sortColumnsString); + if (m_sortColumns.Length() > 1) + { + m_secondarySort = m_sortColumns[1].mSortType; + m_secondarySortOrder = m_sortColumns[1].mSortOrder; + m_secondaryCustomColumn = m_sortColumns[1].mCustomColumnName; + } + + // Restore curCustomColumn from db. + folderInfo->GetProperty("customSortCol", m_curCustomColumn); + } + + return NS_OK; +} + +// Called by msgDBView::Sort, at which point any persisted active custom +// columns must be registered. If not, reset their m_sortColumns entries +// to byDate; Sort will fill in values if necessary based on new user sort. +void nsMsgDBView::EnsureCustomColumnsValid() +{ + if (!m_sortColumns.Length()) + return; + + for (uint32_t i = 0; i < m_sortColumns.Length(); i++) + { + if (m_sortColumns[i].mSortType == nsMsgViewSortType::byCustom && + m_sortColumns[i].mColHandler == nullptr) + { + m_sortColumns[i].mSortType = nsMsgViewSortType::byDate; + m_sortColumns[i].mCustomColumnName.Truncate(); + // There are only two... + if (i == 0 && m_sortType != nsMsgViewSortType::byCustom) + SetCurCustomColumn(EmptyString()); + if (i == 1) + m_secondaryCustomColumn.Truncate(); + } + } +} + +int32_t nsMsgDBView::SecondarySort(nsMsgKey key1, nsISupports *supports1, + nsMsgKey key2, nsISupports *supports2, + viewSortInfo *comparisonContext) +{ + // We need to make sure that in the case of the secondary sort field also + // matching, we don't recurse. + if (comparisonContext->isSecondarySort) + return key1 > key2; + + nsCOMPtr<nsIMsgFolder> folder1, folder2; + nsCOMPtr <nsIMsgDBHdr> hdr1, hdr2; + folder1 = do_QueryInterface(supports1); + folder2 = do_QueryInterface(supports2); + nsresult rv = folder1->GetMessageHeader(key1, getter_AddRefs(hdr1)); + NS_ENSURE_SUCCESS(rv, 0); + rv = folder2->GetMessageHeader(key2, getter_AddRefs(hdr2)); + NS_ENSURE_SUCCESS(rv, 0); + IdKeyPtr EntryInfo1, EntryInfo2; + EntryInfo1.key = nullptr; + EntryInfo2.key = nullptr; + + uint16_t maxLen; + eFieldType fieldType; + nsMsgViewSortTypeValue sortType = comparisonContext->view->m_secondarySort; + nsMsgViewSortOrderValue sortOrder = comparisonContext->view->m_secondarySortOrder; + + // Get the custom column handler for the *secondary* sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = nullptr; + if (sortType == nsMsgViewSortType::byCustom && + comparisonContext->view->m_sortColumns.Length() > 1) + colHandler = comparisonContext->view->m_sortColumns[1].mColHandler; + + // The following may leave fieldType undefined. + // In this case, we can return 0 right away since + // it is the value returned in the default case of + // switch (fieldType) statement below. + rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler); + NS_ENSURE_SUCCESS(rv, 0); + + const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2; + + int (* comparisonFun) (const void *pItem1, const void *pItem2, void *privateData) = nullptr; + int retStatus = 0; + hdr1->GetMessageKey(&EntryInfo1.id); + hdr2->GetMessageKey(&EntryInfo2.id); + + switch (fieldType) + { + case kCollationKey: + rv = GetCollationKey(hdr1, sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + comparisonFun = FnSortIdKeyPtr; + break; + case kU32: + if (sortType == nsMsgViewSortType::byId) + EntryInfo1.dword = EntryInfo1.id; + else + GetLongField(hdr1, sortType, &EntryInfo1.dword, colHandler); + comparisonFun = FnSortIdUint32; + break; + default: + return 0; + } + bool saveAscendingSort = comparisonContext->ascendingSort; + comparisonContext->isSecondarySort = true; + comparisonContext->ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending); + if (fieldType == kCollationKey) + { + PR_FREEIF(EntryInfo2.key); + rv = GetCollationKey(hdr2, sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + } + else if (fieldType == kU32) + { + if (sortType == nsMsgViewSortType::byId) + EntryInfo2.dword = EntryInfo2.id; + else + GetLongField(hdr2, sortType, &EntryInfo2.dword, colHandler); + } + retStatus = (*comparisonFun)(&pValue1, &pValue2, comparisonContext); + + comparisonContext->isSecondarySort = false; + comparisonContext->ascendingSort = saveAscendingSort; + + return retStatus; +} + + +NS_IMETHODIMP nsMsgDBView::Sort(nsMsgViewSortTypeValue sortType, + nsMsgViewSortOrderValue sortOrder) +{ + EnsureCustomColumnsValid(); + + // If we're doing a stable sort, we can't just reverse the messages. + // Check also that the custom column we're sorting on hasn't changed. + // Otherwise, to be on the safe side, resort. + // Note: m_curCustomColumn is the desired (possibly new) custom column name, + // while m_sortColumns[0].mCustomColumnName is the name for the last completed + // sort, since these are persisted after each sort. + if (m_sortType == sortType && m_sortValid && + (sortType != nsMsgViewSortType::byCustom || + (sortType == nsMsgViewSortType::byCustom && m_sortColumns.Length() && + m_sortColumns[0].mCustomColumnName.Equals(m_curCustomColumn))) && + m_sortColumns.Length() < 2) + { + // same as it ever was. do nothing + if (m_sortOrder == sortOrder) + return NS_OK; + + // for secondary sort, remember the sort order on a per column basis. + if (m_sortColumns.Length()) + m_sortColumns[0].mSortOrder = sortOrder; + SaveSortInfo(sortType, sortOrder); + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + ReverseThreads(); + else + ReverseSort(); + + m_sortOrder = sortOrder; + // we just reversed the sort order...we still need to invalidate the view + return NS_OK; + } + + if (sortType == nsMsgViewSortType::byThread) + return NS_OK; + + // If a sortType has changed, or the sortType is byCustom and a column has + // changed, this is the new primary sortColumnInfo. + // Note: m_curCustomColumn is the desired (possibly new) custom column name, + // while m_sortColumns[0].mCustomColumnName is the name for the last completed + // sort, since these are persisted after each sort. + if (m_sortType != sortType || + (sortType == nsMsgViewSortType::byCustom && m_sortColumns.Length() && + !m_sortColumns[0].mCustomColumnName.Equals(m_curCustomColumn))) + { + // For secondary sort, remember the sort order of the original primary sort! + if (m_sortColumns.Length()) + m_sortColumns[0].mSortOrder = m_sortOrder; + + MsgViewSortColumnInfo sortColumnInfo; + sortColumnInfo.mSortType = sortType; + sortColumnInfo.mSortOrder = sortOrder; + if (sortType == nsMsgViewSortType::byCustom) + { + GetCurCustomColumn(sortColumnInfo.mCustomColumnName); + sortColumnInfo.mColHandler = GetCurColumnHandler(); + } + + PushSort(sortColumnInfo); + } + else + { + // For primary sort, remember the sort order on a per column basis. + if (m_sortColumns.Length()) + m_sortColumns[0].mSortOrder = sortOrder; + } + + if (m_sortColumns.Length() > 1) + { + m_secondarySort = m_sortColumns[1].mSortType; + m_secondarySortOrder = m_sortColumns[1].mSortOrder; + m_secondaryCustomColumn = m_sortColumns[1].mCustomColumnName; + } + SaveSortInfo(sortType, sortOrder); + // figure out how much memory we'll need, and the malloc it + uint16_t maxLen; + eFieldType fieldType; + + // Get the custom column handler for the primary sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + + // If we did not obtain proper fieldType, it needs to be checked + // because the subsequent code does not handle it very well. + nsresult rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler); + + // Don't sort if the field type is not supported: Bug 901948 + if (NS_FAILED(rv)) + return NS_OK; + + nsTArray<void*> ptrs; + uint32_t arraySize = GetSize(); + + if (!arraySize) + return NS_OK; + + nsCOMArray<nsIMsgFolder> *folders = GetFolders(); + + IdKey** pPtrBase = (IdKey**)PR_Malloc(arraySize * sizeof(IdKey*)); + NS_ASSERTION(pPtrBase, "out of memory, can't sort"); + if (!pPtrBase) return NS_ERROR_OUT_OF_MEMORY; + ptrs.AppendElement((void *)pPtrBase); // remember this pointer so we can free it later + + // build up the beast, so we can sort it. + uint32_t numSoFar = 0; + const uint32_t keyOffset = offsetof(IdKey, key); + // calc max possible size needed for all the rest + uint32_t maxSize = (keyOffset + maxLen) * (arraySize - numSoFar); + + const uint32_t maxBlockSize = (uint32_t) 0xf000L; + uint32_t allocSize = std::min(maxBlockSize, maxSize); + char *pTemp = (char *) PR_Malloc(allocSize); + NS_ASSERTION(pTemp, "out of memory, can't sort"); + if (!pTemp) + { + FreeAll(&ptrs); + return NS_ERROR_OUT_OF_MEMORY; + } + + ptrs.AppendElement(pTemp); // remember this pointer so we can free it later + + char *pBase = pTemp; + bool more = true; + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + uint8_t *keyValue = nullptr; + uint32_t longValue; + while (more && numSoFar < arraySize) + { + nsMsgKey thisKey = m_keys[numSoFar]; + if (sortType != nsMsgViewSortType::byId) + { + rv = GetMsgHdrForViewIndex(numSoFar, getter_AddRefs(msgHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "header not found"); + if (NS_FAILED(rv) || !msgHdr) + { + FreeAll(&ptrs); + return NS_ERROR_UNEXPECTED; + } + } + else + { + msgHdr = nullptr; + } + + // Could be a problem here if the ones that appear here are different than + // the ones already in the array. + uint32_t actualFieldLen = 0; + + if (fieldType == kCollationKey) + { + rv = GetCollationKey(msgHdr, sortType, &keyValue, &actualFieldLen, colHandler); + NS_ENSURE_SUCCESS(rv,rv); + + longValue = actualFieldLen; + } + else + { + if (sortType == nsMsgViewSortType::byId) + { + longValue = thisKey; + } + else + { + rv = GetLongField(msgHdr, sortType, &longValue, colHandler); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + // check to see if this entry fits into the block we have allocated so far + // pTemp - pBase = the space we have used so far + // sizeof(EntryInfo) + fieldLen = space we need for this entry + // allocSize = size of the current block + if ((uint32_t)(pTemp - pBase) + (keyOffset + actualFieldLen) >= allocSize) + { + maxSize = (keyOffset + maxLen) * (arraySize - numSoFar); + allocSize = std::min(maxBlockSize, maxSize); + // make sure allocSize is big enough for the current value + allocSize = std::max(allocSize, keyOffset + actualFieldLen); + pTemp = (char *) PR_Malloc(allocSize); + NS_ASSERTION(pTemp, "out of memory, can't sort"); + if (!pTemp) + { + FreeAll(&ptrs); + return NS_ERROR_OUT_OF_MEMORY; + } + pBase = pTemp; + ptrs.AppendElement(pTemp); // remember this pointer so we can free it later + } + + // now store this entry away in the allocated memory + IdKey *info = (IdKey*)pTemp; + pPtrBase[numSoFar] = info; + info->id = thisKey; + info->bits = m_flags[numSoFar]; + info->dword = longValue; + //info->pad = 0; + info->folder = folders ? folders->ObjectAt(numSoFar) : m_folder.get(); + + memcpy(info->key, keyValue, actualFieldLen); + //In order to align memory for systems that require it, such as HP-UX + //calculate the correct value to pad the actualFieldLen value + const uint32_t align = sizeof(IdKey) - sizeof(IdUint32) - 1; + actualFieldLen = (actualFieldLen + align) & ~align; + + pTemp += keyOffset + actualFieldLen; + ++numSoFar; + PR_Free(keyValue); + } + + viewSortInfo qsPrivateData; + qsPrivateData.view = this; + qsPrivateData.isSecondarySort = false; + qsPrivateData.ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending); + + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + + if (!dbToUse) // probably search view + GetDBForViewIndex(0, getter_AddRefs(dbToUse)); + qsPrivateData.db = dbToUse; + if (dbToUse) + { + // do the sort + switch (fieldType) + { + case kCollationKey: + NS_QuickSort(pPtrBase, numSoFar, sizeof(IdKey*), FnSortIdKey, &qsPrivateData); + break; + case kU32: + NS_QuickSort(pPtrBase, numSoFar, sizeof(IdKey*), FnSortIdUint32, &qsPrivateData); + break; + default: + NS_ERROR("not supposed to get here"); + break; + } + } + + // now put the IDs into the array in proper order + for (uint32_t i = 0; i < numSoFar; i++) + { + m_keys[i] = pPtrBase[i]->id; + m_flags[i] = pPtrBase[i]->bits; + + if (folders) + folders->ReplaceObjectAt(pPtrBase[i]->folder, i); + } + + m_sortType = sortType; + m_sortOrder = sortOrder; + + // free all the memory we allocated + FreeAll(&ptrs); + + m_sortValid = true; + //m_db->SetSortInfo(sortType, sortOrder); + + return NS_OK; +} + +void nsMsgDBView::FreeAll(nsTArray<void*> *ptrs) +{ + int32_t i; + int32_t count = (int32_t) ptrs->Length(); + if (count == 0) + return; + + for (i=(count - 1);i>=0;i--) + PR_Free((void *) ptrs->ElementAt(i)); + ptrs->Clear(); +} + +nsMsgViewIndex nsMsgDBView::GetIndexOfFirstDisplayedKeyInThread( + nsIMsgThread *threadHdr, bool allowDummy) +{ + nsMsgViewIndex retIndex = nsMsgViewIndex_None; + uint32_t childIndex = 0; + // We could speed up the unreadOnly view by starting our search with the first + // unread message in the thread. Sometimes, that will be wrong, however, so + // let's skip it until we're sure it's necessary. + // (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) + // ? threadHdr->GetFirstUnreadKey(m_db) : threadHdr->GetChildAt(0); + uint32_t numThreadChildren; + threadHdr->GetNumChildren(&numThreadChildren); + while (retIndex == nsMsgViewIndex_None && childIndex < numThreadChildren) + { + nsCOMPtr<nsIMsgDBHdr> childHdr; + threadHdr->GetChildHdrAt(childIndex++, getter_AddRefs(childHdr)); + if (childHdr) + retIndex = FindHdr(childHdr, 0, allowDummy); + } + return retIndex; +} + +nsresult nsMsgDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result) +{ + nsresult rv; + + if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) + rv = threadHdr->GetFirstUnreadChild(result); + else + rv = threadHdr->GetChildHdrAt(0, result); + return rv; +} + +// Find the view index of the thread containing the passed msgKey, if +// the thread is in the view. MsgIndex is passed in as a shortcut if +// it turns out the msgKey is the first message in the thread, +// then we can avoid looking for the msgKey. +nsMsgViewIndex nsMsgDBView::ThreadIndexOfMsg(nsMsgKey msgKey, + nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */, + int32_t *pThreadCount /* = NULL */, + uint32_t *pFlags /* = NULL */) +{ + if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + return nsMsgViewIndex_None; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = m_db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None); + return ThreadIndexOfMsgHdr(msgHdr, msgIndex, pThreadCount, pFlags); +} + +nsMsgViewIndex nsMsgDBView::GetThreadIndex(nsMsgViewIndex msgIndex) +{ + if (!IsValidIndex(msgIndex)) + return nsMsgViewIndex_None; + + // scan up looking for level 0 message. + while (m_levels[msgIndex] && msgIndex) + --msgIndex; + return msgIndex; +} + +nsMsgViewIndex +nsMsgDBView::ThreadIndexOfMsgHdr(nsIMsgDBHdr *msgHdr, + nsMsgViewIndex msgIndex, + int32_t *pThreadCount, + uint32_t *pFlags) +{ + nsCOMPtr<nsIMsgThread> threadHdr; + nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None); + + nsMsgViewIndex retIndex = nsMsgViewIndex_None; + + if (threadHdr != nullptr) + { + if (msgIndex == nsMsgViewIndex_None) + msgIndex = FindHdr(msgHdr, 0, true); + + if (msgIndex == nsMsgViewIndex_None) // hdr is not in view, need to find by thread + { + msgIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr, true); + //nsMsgKey threadKey = (msgIndex == nsMsgViewIndex_None) ? nsMsgKey_None : GetAt(msgIndex); + if (pFlags) + threadHdr->GetFlags(pFlags); + } + nsMsgViewIndex startOfThread = msgIndex; + while ((int32_t) startOfThread >= 0 && m_levels[startOfThread] != 0) + startOfThread--; + retIndex = startOfThread; + if (pThreadCount) + { + int32_t numChildren = 0; + nsMsgViewIndex threadIndex = startOfThread; + do + { + threadIndex++; + numChildren++; + } + while (threadIndex < m_levels.Length() && m_levels[threadIndex] != 0); + *pThreadCount = numChildren; + } + } + return retIndex; +} + +nsMsgKey nsMsgDBView::GetKeyOfFirstMsgInThread(nsMsgKey key) +{ + // Just report no key for any failure. This can occur when a + // message is deleted from a threaded view + nsCOMPtr <nsIMsgThread> pThread; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) + return nsMsgKey_None; + rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread)); + if (NS_FAILED(rv)) + return nsMsgKey_None; + nsMsgKey firstKeyInThread = nsMsgKey_None; + + if (!pThread) + return firstKeyInThread; + + // ### dmb UnreadOnly - this is wrong. But didn't seem to matter in 4.x + pThread->GetChildKeyAt(0, &firstKeyInThread); + return firstKeyInThread; +} + +NS_IMETHODIMP nsMsgDBView::GetKeyAt(nsMsgViewIndex index, nsMsgKey *result) +{ + NS_ENSURE_ARG(result); + *result = GetAt(index); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetFlagsAt(nsMsgViewIndex aIndex, uint32_t *aResult) +{ + NS_ENSURE_ARG(aResult); + if (!IsValidIndex(aIndex)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + *aResult = m_flags[aIndex]; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetMsgHdrAt(nsMsgViewIndex aIndex, nsIMsgDBHdr **aResult) +{ + NS_ENSURE_ARG(aResult); + if (!IsValidIndex(aIndex)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + return GetMsgHdrForViewIndex(aIndex, aResult); +} + +nsMsgViewIndex nsMsgDBView::FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex, + bool allowDummy) +{ + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + nsMsgViewIndex viewIndex = m_keys.IndexOf(msgKey, startIndex); + if (viewIndex == nsMsgViewIndex_None) + return viewIndex; + // if we're supposed to allow dummies, and the previous index is a dummy that + // is not elided, then it must be the dummy corresponding to our node and + // we should return that instead. + if (allowDummy && viewIndex + && (m_flags[viewIndex-1] & MSG_VIEW_FLAG_DUMMY) + && !(m_flags[viewIndex-1] & nsMsgMessageFlags::Elided)) + viewIndex--; + // else if we're not allowing dummies, and we found a dummy, look again + // one past the dummy. + else if (!allowDummy && m_flags[viewIndex] & MSG_VIEW_FLAG_DUMMY) + return m_keys.IndexOf(msgKey, viewIndex + 1); + return viewIndex; +} + +nsMsgViewIndex nsMsgDBView::FindKey(nsMsgKey key, bool expand) +{ + nsMsgViewIndex retIndex = nsMsgViewIndex_None; + retIndex = (nsMsgViewIndex) (m_keys.IndexOf(key)); + // for dummy headers, try to expand if the caller says so. And if the thread is + // expanded, ignore the dummy header and return the real header index. + if (retIndex != nsMsgViewIndex_None && m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY && !(m_flags[retIndex] & nsMsgMessageFlags::Elided)) + return (nsMsgViewIndex) m_keys.IndexOf(key, retIndex + 1); + if (key != nsMsgKey_None && (retIndex == nsMsgViewIndex_None || m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY) + && expand && m_db) + { + nsMsgKey threadKey = GetKeyOfFirstMsgInThread(key); + if (threadKey != nsMsgKey_None) + { + nsMsgViewIndex threadIndex = FindKey(threadKey, false); + if (threadIndex != nsMsgViewIndex_None) + { + uint32_t flags = m_flags[threadIndex]; + if (((flags & nsMsgMessageFlags::Elided) && + NS_SUCCEEDED(ExpandByIndex(threadIndex, nullptr))) + || (flags & MSG_VIEW_FLAG_DUMMY)) + retIndex = (nsMsgViewIndex) m_keys.IndexOf(key, threadIndex + 1); + } + } + } + return retIndex; +} + +nsresult nsMsgDBView::GetThreadCount(nsMsgViewIndex index, uint32_t *pThreadCount) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIMsgThread> pThread; + rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread)); + if (NS_SUCCEEDED(rv) && pThread != nullptr) + rv = pThread->GetNumChildren(pThreadCount); + return rv; +} + +// This counts the number of messages in an expanded thread, given the +// index of the first message in the thread. +int32_t nsMsgDBView::CountExpandedThread(nsMsgViewIndex index) +{ + int32_t numInThread = 0; + nsMsgViewIndex startOfThread = index; + while ((int32_t) startOfThread >= 0 && m_levels[startOfThread] != 0) + startOfThread--; + nsMsgViewIndex threadIndex = startOfThread; + do + { + threadIndex++; + numInThread++; + } + while (threadIndex < m_levels.Length() && m_levels[threadIndex] != 0); + + return numInThread; +} + +// returns the number of lines that would be added (> 0) or removed (< 0) +// if we were to try to expand/collapse the passed index. +nsresult nsMsgDBView::ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta) +{ + uint32_t numChildren; + nsresult rv; + + *expansionDelta = 0; + if (index >= ((nsMsgViewIndex) m_keys.Length())) + return NS_MSG_MESSAGE_NOT_FOUND; + char flags = m_flags[index]; + + if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + return NS_OK; + + // The client can pass in the key of any message + // in a thread and get the expansion delta for the thread. + + if (flags & nsMsgMessageFlags::Elided) + { + rv = GetThreadCount(index, &numChildren); + NS_ENSURE_SUCCESS(rv, rv); + *expansionDelta = numChildren - 1; + } + else + { + numChildren = CountExpandedThread(index); + *expansionDelta = - (int32_t) (numChildren - 1); + } + + return NS_OK; +} + +nsresult nsMsgDBView::ToggleExpansion(nsMsgViewIndex index, uint32_t *numChanged) +{ + nsresult rv; + NS_ENSURE_ARG(numChanged); + *numChanged = 0; + nsMsgViewIndex threadIndex = GetThreadIndex(index); + if (threadIndex == nsMsgViewIndex_None) + { + NS_ASSERTION(false, "couldn't find thread"); + return NS_MSG_MESSAGE_NOT_FOUND; + } + int32_t flags = m_flags[threadIndex]; + + // if not a thread, or doesn't have children, no expand/collapse + // If we add sub-thread expand collapse, this will need to be relaxed + if (!(flags & MSG_VIEW_FLAG_ISTHREAD) || !(flags & MSG_VIEW_FLAG_HASCHILDREN)) + return NS_MSG_MESSAGE_NOT_FOUND; + if (flags & nsMsgMessageFlags::Elided) + rv = ExpandByIndex(threadIndex, numChanged); + else + rv = CollapseByIndex(threadIndex, numChanged); + + // if we collaps/uncollapse a thread, this changes the selected URIs + SelectionChanged(); + return rv; +} + +nsresult nsMsgDBView::ExpandAndSelectThread() +{ + nsresult rv; + + NS_ASSERTION(mTreeSelection, "no tree selection"); + if (!mTreeSelection) return NS_ERROR_UNEXPECTED; + + int32_t index; + rv = mTreeSelection->GetCurrentIndex(&index); + NS_ENSURE_SUCCESS(rv,rv); + + rv = ExpandAndSelectThreadByIndex(index, false); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsresult nsMsgDBView::ExpandAndSelectThreadByIndex(nsMsgViewIndex index, bool augment) +{ + nsresult rv; + + nsMsgViewIndex threadIndex; + bool inThreadedMode = (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay); + + if (inThreadedMode) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr)); + threadIndex = ThreadIndexOfMsgHdr(msgHdr, index); + if (threadIndex == nsMsgViewIndex_None) + { + NS_ASSERTION(false, "couldn't find thread"); + return NS_MSG_MESSAGE_NOT_FOUND; + } + } + else + { + threadIndex = index; + } + + int32_t flags = m_flags[threadIndex]; + int32_t count = 0; + + if (inThreadedMode && (flags & MSG_VIEW_FLAG_ISTHREAD) && (flags & MSG_VIEW_FLAG_HASCHILDREN)) + { + // if closed, expand this thread. + if (flags & nsMsgMessageFlags::Elided) + { + uint32_t numExpanded; + rv = ExpandByIndex(threadIndex, &numExpanded); + NS_ENSURE_SUCCESS(rv,rv); + } + + // get the number of messages in the expanded thread + // so we know how many to select + count = CountExpandedThread(threadIndex); + } + else + { + count = 1; + } + NS_ASSERTION(count > 0, "bad count"); + + // update the selection + + NS_ASSERTION(mTreeSelection, "no tree selection"); + if (!mTreeSelection) return NS_ERROR_UNEXPECTED; + + // the count should be 1 or greater. if there was only one message in the thread, we just select it. + // if more, we select all of them. + mTreeSelection->RangedSelect(threadIndex + count - 1, threadIndex, augment); + return NS_OK; +} + +nsresult nsMsgDBView::ExpandAll() +{ + if (mTree) + mTree->BeginUpdateBatch(); + for (int32_t i = GetSize() - 1; i >= 0; i--) + { + uint32_t numExpanded; + uint32_t flags = m_flags[i]; + if (flags & nsMsgMessageFlags::Elided) + ExpandByIndex(i, &numExpanded); + } + if (mTree) + mTree->EndUpdateBatch(); + SelectionChanged(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) +{ + return m_db->GetThreadContainingMsgHdr(msgHdr, pThread); +} + +nsresult nsMsgDBView::ExpandByIndex(nsMsgViewIndex index, uint32_t *pNumExpanded) +{ + if ((uint32_t) index >= m_keys.Length()) + return NS_MSG_MESSAGE_NOT_FOUND; + + uint32_t flags = m_flags[index]; + uint32_t numExpanded = 0; + + NS_ASSERTION(flags & nsMsgMessageFlags::Elided, "can't expand an already expanded thread"); + flags &= ~nsMsgMessageFlags::Elided; + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgThread> pThread; + nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(pThread)); + NS_ENSURE_SUCCESS(rv, rv); + if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) + { + if (flags & nsMsgMessageFlags::Read) + m_levels.AppendElement(0); // keep top level hdr in thread, even though read. + rv = ListUnreadIdsInThread(pThread, index, &numExpanded); + } + else + rv = ListIdsInThread(pThread, index, &numExpanded); + + if (numExpanded > 0) + { + m_flags[index] = flags; + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + } + NoteStartChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete); + NoteEndChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete); + if (pNumExpanded != nullptr) + *pNumExpanded = numExpanded; + return rv; +} + +nsresult nsMsgDBView::CollapseAll() +{ + for (uint32_t i = 0; i < GetSize(); i++) + { + uint32_t numExpanded; + uint32_t flags = m_flags[i]; + if (!(flags & nsMsgMessageFlags::Elided) && (flags & MSG_VIEW_FLAG_HASCHILDREN)) + CollapseByIndex(i, &numExpanded); + } + SelectionChanged(); + return NS_OK; +} + +nsresult nsMsgDBView::CollapseByIndex(nsMsgViewIndex index, uint32_t *pNumCollapsed) +{ + nsresult rv; + int32_t flags = m_flags[index]; + int32_t rowDelta = 0; + + if (flags & nsMsgMessageFlags::Elided || !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || !(flags & MSG_VIEW_FLAG_HASCHILDREN)) + return NS_OK; + + if (index > m_keys.Length()) + return NS_MSG_MESSAGE_NOT_FOUND; + + rv = ExpansionDelta(index, &rowDelta); + NS_ENSURE_SUCCESS(rv, rv); + + flags |= nsMsgMessageFlags::Elided; + + m_flags[index] = flags; + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + + int32_t numRemoved = -rowDelta; // don't count first header in thread + if (index + 1 + numRemoved > m_keys.Length()) + { + NS_ERROR("trying to remove too many rows"); + numRemoved -= (index + 1 + numRemoved) - m_keys.Length(); + if (numRemoved <= 0) + return NS_MSG_MESSAGE_NOT_FOUND; + } + NoteStartChange(index + 1, rowDelta, nsMsgViewNotificationCode::insertOrDelete); + // start at first id after thread. + RemoveRows(index + 1, numRemoved); + if (pNumCollapsed != nullptr) + *pNumCollapsed = numRemoved; + NoteEndChange(index + 1, rowDelta, nsMsgViewNotificationCode::insertOrDelete); + + return rv; +} + +nsresult nsMsgDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool /*ensureListed*/) +{ + nsresult rv = NS_OK; + // views can override this behaviour, which is to append to view. + // This is the mail behaviour, but threaded views will want + // to insert in order... + if (newHdr) + rv = AddHdr(newHdr); + return rv; +} + +NS_IMETHODIMP nsMsgDBView::GetThreadContainingIndex(nsMsgViewIndex index, nsIMsgThread **resultThread) +{ + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + return GetThreadContainingMsgHdr(msgHdr, resultThread); +} + +nsMsgViewIndex +nsMsgDBView::GetIndexForThread(nsIMsgDBHdr *msgHdr) +{ + // Take advantage of the fact that we're already sorted + // and find the insert index via a binary search, though expanded threads + // make that tricky. + + nsMsgViewIndex highIndex = m_keys.Length(); + nsMsgViewIndex lowIndex = 0; + IdKeyPtr EntryInfo1, EntryInfo2; + EntryInfo1.key = nullptr; + EntryInfo2.key = nullptr; + + nsresult rv; + uint16_t maxLen; + eFieldType fieldType; + + // Get the custom column handler for the primary sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + + // The following may leave fieldType undefined. + // In this case, we can return highIndex right away since + // it is the value returned in the default case of + // switch (fieldType) statement below. + rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler); + NS_ENSURE_SUCCESS(rv, highIndex); + + const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2; + + int retStatus = 0; + msgHdr->GetMessageKey(&EntryInfo1.id); + msgHdr->GetFolder(&EntryInfo1.folder); + EntryInfo1.folder->Release(); + + viewSortInfo comparisonContext; + comparisonContext.view = this; + comparisonContext.isSecondarySort = false; + comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending); + nsCOMPtr <nsIMsgDatabase> hdrDB; + EntryInfo1.folder->GetMsgDatabase(getter_AddRefs(hdrDB)); + comparisonContext.db = hdrDB.get(); + switch (fieldType) + { + case kCollationKey: + rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + break; + case kU32: + if (m_sortType == nsMsgViewSortType::byId) + EntryInfo1.dword = EntryInfo1.id; + else + GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler); + break; + default: + return highIndex; + } + while (highIndex > lowIndex) + { + nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2; + // need to adjust tryIndex if it's not a thread. + while (m_levels[tryIndex] && tryIndex) + tryIndex--; + + if (tryIndex < lowIndex) + { + NS_ERROR("try index shouldn't be less than low index"); + break; + } + EntryInfo2.id = m_keys[tryIndex]; + GetFolderForViewIndex(tryIndex, &EntryInfo2.folder); + EntryInfo2.folder->Release(); + + nsCOMPtr <nsIMsgDBHdr> tryHdr; + nsCOMPtr <nsIMsgDatabase> db; + // ### this should get the db from the folder... + GetDBForViewIndex(tryIndex, getter_AddRefs(db)); + if (db) + rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr)); + if (!tryHdr) + break; + if (tryHdr == msgHdr) + { + NS_WARNING("didn't expect header to already be in view"); + highIndex = tryIndex; + break; + } + if (fieldType == kCollationKey) + { + PR_FREEIF(EntryInfo2.key); + rv = GetCollationKey(tryHdr, m_sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext); + } + else if (fieldType == kU32) + { + if (m_sortType == nsMsgViewSortType::byId) + EntryInfo2.dword = EntryInfo2.id; + else + GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler); + retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext); + } + if (retStatus == 0) + { + highIndex = tryIndex; + break; + } + + if (retStatus < 0) + { + highIndex = tryIndex; + // we already made sure tryIndex was at a thread at the top of the loop. + } + else + { + lowIndex = tryIndex + 1; + while (lowIndex < GetSize() && m_levels[lowIndex]) + lowIndex++; + } + } + + PR_Free(EntryInfo1.key); + PR_Free(EntryInfo2.key); + return highIndex; +} + +nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsTArray<nsMsgKey> &keys, + nsCOMArray<nsIMsgFolder> *folders, + nsMsgViewSortOrderValue sortOrder, nsMsgViewSortTypeValue sortType) +{ + nsMsgViewIndex highIndex = keys.Length(); + nsMsgViewIndex lowIndex = 0; + IdKeyPtr EntryInfo1, EntryInfo2; + EntryInfo1.key = nullptr; + EntryInfo2.key = nullptr; + + nsresult rv; + uint16_t maxLen; + eFieldType fieldType; + + // Get the custom column handler for the primary sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + + // The following may leave fieldType undefined. + // In this case, we can return highIndex right away since + // it is the value returned in the default case of + // switch (fieldType) statement below. + rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler); + NS_ENSURE_SUCCESS(rv, highIndex); + + const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2; + + int (* comparisonFun) (const void *pItem1, const void *pItem2, void *privateData) = nullptr; + int retStatus = 0; + msgHdr->GetMessageKey(&EntryInfo1.id); + msgHdr->GetFolder(&EntryInfo1.folder); + EntryInfo1.folder->Release(); + + viewSortInfo comparisonContext; + comparisonContext.view = this; + comparisonContext.isSecondarySort = false; + comparisonContext.ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending); + rv = EntryInfo1.folder->GetMsgDatabase(&comparisonContext.db); + NS_ENSURE_SUCCESS(rv, highIndex); + comparisonContext.db->Release(); + switch (fieldType) + { + case kCollationKey: + rv = GetCollationKey(msgHdr, sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + comparisonFun = FnSortIdKeyPtr; + break; + case kU32: + if (sortType == nsMsgViewSortType::byId) + EntryInfo1.dword = EntryInfo1.id; + else + GetLongField(msgHdr, sortType, &EntryInfo1.dword, colHandler); + comparisonFun = FnSortIdUint32; + break; + default: + return highIndex; + } + while (highIndex > lowIndex) + { + nsMsgViewIndex tryIndex = (lowIndex + highIndex - 1) / 2; + EntryInfo2.id = keys[tryIndex]; + EntryInfo2.folder = folders ? folders->ObjectAt(tryIndex) : m_folder.get(); + + nsCOMPtr <nsIMsgDBHdr> tryHdr; + EntryInfo2.folder->GetMessageHeader(EntryInfo2.id, getter_AddRefs(tryHdr)); + if (!tryHdr) + break; + if (fieldType == kCollationKey) + { + PR_FREEIF(EntryInfo2.key); + rv = GetCollationKey(tryHdr, sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + } + else if (fieldType == kU32) + { + if (sortType == nsMsgViewSortType::byId) { + EntryInfo2.dword = EntryInfo2.id; + } + else { + GetLongField(tryHdr, sortType, &EntryInfo2.dword, colHandler); + } + } + retStatus = (*comparisonFun)(&pValue1, &pValue2, &comparisonContext); + if (retStatus == 0) + { + highIndex = tryIndex; + break; + } + + if (retStatus < 0) + { + highIndex = tryIndex; + } + else + { + lowIndex = tryIndex + 1; + } + } + + PR_Free(EntryInfo1.key); + PR_Free(EntryInfo2.key); + return highIndex; +} + +nsMsgViewIndex nsMsgDBView::GetInsertIndex(nsIMsgDBHdr *msgHdr) +{ + if (!GetSize()) + return 0; + + if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0 + && !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + && m_sortOrder != nsMsgViewSortType::byId) + return GetIndexForThread(msgHdr); + + return GetInsertIndexHelper(msgHdr, m_keys, GetFolders(), m_sortOrder, m_sortType); +} + +nsresult nsMsgDBView::AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex) +{ + uint32_t flags = 0; +#ifdef DEBUG_bienvenu + NS_ASSERTION(m_keys.Length() == m_flags.Length() && (int) m_keys.Length() == m_levels.Length(), "view arrays out of sync!"); +#endif + + if (resultIndex) + *resultIndex = nsMsgViewIndex_None; + + if (!GetShowingIgnored()) + { + nsCOMPtr <nsIMsgThread> thread; + GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread)); + if (thread) + { + thread->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Ignored) + return NS_OK; + } + + bool ignored; + msgHdr->GetIsKilled(&ignored); + if (ignored) + return NS_OK; + } + + nsMsgKey msgKey, threadId; + nsMsgKey threadParent; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetThreadId(&threadId); + msgHdr->GetThreadParent(&threadParent); + + msgHdr->GetFlags(&flags); + // ### this isn't quite right, is it? Should be checking that our thread parent key is none? + if (threadParent == nsMsgKey_None) + flags |= MSG_VIEW_FLAG_ISTHREAD; + nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr); + if (insertIndex == nsMsgViewIndex_None) + { + // if unreadonly, level is 0 because we must be the only msg in the thread. + int32_t levelToAdd = 0; + + if (m_sortOrder == nsMsgViewSortOrder::ascending) + { + InsertMsgHdrAt(GetSize(), msgHdr, msgKey, flags, levelToAdd); + if (resultIndex) + *resultIndex = GetSize() - 1; + + // the call to NoteChange() has to happen after we add the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete); + } + else + { + InsertMsgHdrAt(0, msgHdr, msgKey, flags, levelToAdd); + if (resultIndex) + *resultIndex = 0; + + // the call to NoteChange() has to happen after we insert the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + NoteChange(0, 1, nsMsgViewNotificationCode::insertOrDelete); + } + m_sortValid = false; + } + else + { + InsertMsgHdrAt(insertIndex, msgHdr, msgKey, flags, 0); + if (resultIndex) + *resultIndex = insertIndex; + // the call to NoteChange() has to happen after we add the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete); + } + OnHeaderAddedOrDeleted(); + return NS_OK; +} + +bool nsMsgDBView::WantsThisThread(nsIMsgThread * /*threadHdr*/) +{ + return true; // default is to want all threads. +} + +nsMsgViewIndex nsMsgDBView::FindParentInThread(nsMsgKey parentKey, nsMsgViewIndex startOfThreadViewIndex) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + while (parentKey != nsMsgKey_None) + { + nsMsgViewIndex parentIndex = m_keys.IndexOf(parentKey, startOfThreadViewIndex); + if (parentIndex != nsMsgViewIndex_None) + return parentIndex; + + if (NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(msgHdr)))) + break; + + msgHdr->GetThreadParent(&parentKey); + } + + return startOfThreadViewIndex; +} + +nsresult nsMsgDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr, nsMsgKey parentKey, + uint32_t level, nsMsgViewIndex *viewIndex, + uint32_t *pNumListed) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsISimpleEnumerator> msgEnumerator; + threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator)); + uint32_t numChildren; + (void) threadHdr->GetNumChildren(&numChildren); + NS_ASSERTION(numChildren, "Empty thread in view/db"); + if (!numChildren) + return NS_OK; // bogus, but harmless. + + numChildren--; // account for the existing thread root + + // skip the first one. + bool hasMore; + nsCOMPtr <nsISupports> supports; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = msgEnumerator->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + if (*pNumListed == numChildren) + { + NS_NOTREACHED("thread corrupt in db"); + // if we've listed more messages than are in the thread, then the db + // is corrupt, and we should invalidate it. + // we'll use this rv to indicate there's something wrong with the db + // though for now it probably won't get paid attention to. + m_db->SetSummaryValid(false); + rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + break; + } + + msgHdr = do_QueryInterface(supports); + if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) + { + bool ignored; + msgHdr->GetIsKilled(&ignored); + // We are not going to process subthreads, horribly invalidating the + // numChildren characteristic + if (ignored) + continue; + } + + nsMsgKey msgKey; + uint32_t msgFlags, newFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + AdjustReadFlag(msgHdr, &msgFlags); + SetMsgHdrAt(msgHdr, *viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level); + // turn off thread or elided bit if they got turned on (maybe from new only view?) + msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided), &newFlags); + (*pNumListed)++; + (*viewIndex)++; + rv = ListIdsInThreadOrder(threadHdr, msgKey, level + 1, viewIndex, pNumListed); + } + } + return rv; // we don't want to return the rv from the enumerator when it reaches the end, do we? +} + +bool nsMsgDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows) +{ + return m_keys.InsertElementsAt(viewIndex, numRows, 0) && + m_flags.InsertElementsAt(viewIndex, numRows, 0) && + m_levels.InsertElementsAt(viewIndex, numRows, 1); +} + +void nsMsgDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) +{ + m_keys.RemoveElementsAt(viewIndex, numRows); + m_flags.RemoveElementsAt(viewIndex, numRows); + m_levels.RemoveElementsAt(viewIndex, numRows); +} + +NS_IMETHODIMP nsMsgDBView::InsertTreeRows(nsMsgViewIndex aIndex, + uint32_t aNumRows, + nsMsgKey aKey, + nsMsgViewFlagsTypeValue aFlags, + uint32_t aLevel, + nsIMsgFolder *aFolder) +{ + if (GetSize() < aIndex) + return NS_ERROR_UNEXPECTED; + + nsCOMArray<nsIMsgFolder> *folders = GetFolders(); + if (folders) + { + // In a search/xfvf view only, a folder is required. + NS_ENSURE_ARG_POINTER(aFolder); + for (size_t i = 0; i < aNumRows; i++) + // Insert into m_folders. + if (!folders->InsertObjectAt(aFolder, aIndex + i)) + return NS_ERROR_UNEXPECTED; + } + + if (m_keys.InsertElementsAt(aIndex, aNumRows, aKey) && + m_flags.InsertElementsAt(aIndex, aNumRows, aFlags) && + m_levels.InsertElementsAt(aIndex, aNumRows, aLevel)) + return NS_OK; + + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP nsMsgDBView::RemoveTreeRows(nsMsgViewIndex aIndex, + uint32_t aNumRows) +{ + // Prevent a crash if attempting to remove rows which don't exist. + if (GetSize() < aIndex + aNumRows) + return NS_ERROR_UNEXPECTED; + + nsMsgDBView::RemoveRows(aIndex, aNumRows); + + nsCOMArray<nsIMsgFolder> *folders = GetFolders(); + if (folders) + // In a search/xfvf view only, remove from m_folders. + if (!folders->RemoveObjectsAt(aIndex, aNumRows)) + return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +nsresult nsMsgDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) +{ + NS_ENSURE_ARG(threadHdr); + // these children ids should be in thread order. + nsresult rv = NS_OK; + uint32_t i; + nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1; + *pNumListed = 0; + + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + NS_ASSERTION(numChildren, "Empty thread in view/db"); + if (!numChildren) + return NS_OK; + + numChildren--; // account for the existing thread root + if (!InsertEmptyRows(viewIndex, numChildren)) + return NS_ERROR_OUT_OF_MEMORY; + + // ### need to rework this when we implemented threading in group views. + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + { + nsMsgKey parentKey = m_keys[startOfThreadViewIndex]; + // If the thread is bigger than the hdr cache, expanding the thread + // can be slow. Increasing the hdr cache size will help a fair amount. + uint32_t hdrCacheSize; + m_db->GetMsgHdrCacheSize(&hdrCacheSize); + if (numChildren > hdrCacheSize) + m_db->SetMsgHdrCacheSize(numChildren); + // If this fails, *pNumListed will be 0, and we'll fall back to just + // enumerating the messages in the thread below. + rv = ListIdsInThreadOrder(threadHdr, parentKey, 1, &viewIndex, pNumListed); + if (numChildren > hdrCacheSize) + m_db->SetMsgHdrCacheSize(hdrCacheSize); + } + if (! *pNumListed) + { + uint32_t ignoredHeaders = 0; + // if we're not threaded, just list em out in db order + for (i = 1; i <= numChildren; i++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + + if (msgHdr != nullptr) + { + if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) + { + bool killed; + msgHdr->GetIsKilled(&killed); + if (killed) + { + ignoredHeaders++; + continue; + } + } + + nsMsgKey msgKey; + uint32_t msgFlags, newFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + AdjustReadFlag(msgHdr, &msgFlags); + SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, 1); + // here, we're either flat, or we're grouped - in either case, level is 1 + // turn off thread or elided bit if they got turned on (maybe from new only view?) + if (i > 0) + msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided), &newFlags); + (*pNumListed)++; + viewIndex++; + } + } + if (ignoredHeaders + *pNumListed < numChildren) + { + NS_NOTREACHED("thread corrupt in db"); + // if we've listed fewer messages than are in the thread, then the db + // is corrupt, and we should invalidate it. + // we'll use this rv to indicate there's something wrong with the db + // though for now it probably won't get paid attention to. + m_db->SetSummaryValid(false); + rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + } + + // We may have added too many elements (i.e., subthreads were cut) + // ### fix for cross folder view case. + if (*pNumListed < numChildren) + RemoveRows(viewIndex, numChildren - *pNumListed); + return rv; +} + +int32_t nsMsgDBView::FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex) +{ + nsCOMPtr <nsIMsgDBHdr> curMsgHdr = msgHdr; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + + // look through the ancestors of the passed in msgHdr in turn, looking for them in the view, up to the start of + // the thread. If we find an ancestor, then our level is one greater than the level of the ancestor. + while (curMsgHdr) + { + nsMsgKey parentKey; + curMsgHdr->GetThreadParent(&parentKey); + if (parentKey == nsMsgKey_None) + break; + + // scan up to find view index of ancestor, if any + for (nsMsgViewIndex indexToTry = viewIndex; indexToTry && indexToTry-- >= startOfThread;) + { + if (m_keys[indexToTry] == parentKey) + return m_levels[indexToTry] + 1; + } + + // if msgHdr's key is its parentKey, we'll loop forever, so protect + // against that corruption. + if (msgKey == parentKey || NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(curMsgHdr)))) + { + NS_ERROR("msgKey == parentKey, or GetMsgHdrForKey failed, this used to be an infinte loop condition"); + curMsgHdr = nullptr; + } + else + { + // need to update msgKey so the check for a msgHdr with matching + // key+parentKey will work after first time through loop + curMsgHdr->GetMessageKey(&msgKey); + } + } + return 1; +} + +// ### Can this be combined with GetIndexForThread?? +nsMsgViewIndex +nsMsgDBView::GetThreadRootIndex(nsIMsgDBHdr *msgHdr) +{ + if (!msgHdr) + { + NS_WARNING("null msgHdr parameter"); + return nsMsgViewIndex_None; + } + + // Take advantage of the fact that we're already sorted + // and find the thread root via a binary search. + + nsMsgViewIndex highIndex = m_keys.Length(); + nsMsgViewIndex lowIndex = 0; + IdKeyPtr EntryInfo1, EntryInfo2; + EntryInfo1.key = nullptr; + EntryInfo2.key = nullptr; + + nsresult rv; + uint16_t maxLen; + eFieldType fieldType; + + // Get the custom column handler for the primary sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + + // The following may leave fieldType undefined. + // In this case, we can return highIndex right away since + // it is the value returned in the default case of + // switch (fieldType) statement below. + rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler); + NS_ENSURE_SUCCESS(rv, highIndex); + + const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2; + + int retStatus = 0; + msgHdr->GetMessageKey(&EntryInfo1.id); + msgHdr->GetFolder(&EntryInfo1.folder); + EntryInfo1.folder->Release(); + + viewSortInfo comparisonContext; + comparisonContext.view = this; + comparisonContext.isSecondarySort = false; + comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending); + nsCOMPtr<nsIMsgDatabase> hdrDB; + EntryInfo1.folder->GetMsgDatabase(getter_AddRefs(hdrDB)); + comparisonContext.db = hdrDB.get(); + switch (fieldType) + { + case kCollationKey: + rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo1.key, &EntryInfo1.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + break; + case kU32: + if (m_sortType == nsMsgViewSortType::byId) + EntryInfo1.dword = EntryInfo1.id; + else + GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler); + break; + default: + return highIndex; + } + while (highIndex > lowIndex) + { + nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2; + // need to adjust tryIndex if it's not a thread. + while (m_levels[tryIndex] && tryIndex) + tryIndex--; + + if (tryIndex < lowIndex) + { + NS_ERROR("try index shouldn't be less than low index"); + break; + } + EntryInfo2.id = m_keys[tryIndex]; + GetFolderForViewIndex(tryIndex, &EntryInfo2.folder); + EntryInfo2.folder->Release(); + + nsCOMPtr<nsIMsgDBHdr> tryHdr; + nsCOMPtr<nsIMsgDatabase> db; + // ### this should get the db from the folder... + GetDBForViewIndex(tryIndex, getter_AddRefs(db)); + if (db) + rv = db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr)); + if (!tryHdr) + break; + if (tryHdr == msgHdr) + { + highIndex = tryIndex; + break; + } + if (fieldType == kCollationKey) + { + PR_FREEIF(EntryInfo2.key); + rv = GetCollationKey(tryHdr, m_sortType, &EntryInfo2.key, &EntryInfo2.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext); + } + else if (fieldType == kU32) + { + if (m_sortType == nsMsgViewSortType::byId) + EntryInfo2.dword = EntryInfo2.id; + else + GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler); + retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext); + } + if (retStatus == 0) + { + highIndex = tryIndex; + break; + } + + if (retStatus < 0) + { + highIndex = tryIndex; + // we already made sure tryIndex was at a thread at the top of the loop. + } + else + { + lowIndex = tryIndex + 1; + while (lowIndex < GetSize() && m_levels[lowIndex]) + lowIndex++; + } + } + + nsCOMPtr<nsIMsgDBHdr> resultHdr; + GetMsgHdrForViewIndex(highIndex, getter_AddRefs(resultHdr)); + + if (resultHdr != msgHdr) + { + NS_WARNING("didn't find hdr"); + highIndex = FindHdr(msgHdr); +#ifdef DEBUG_David_Bienvenu + if (highIndex != nsMsgViewIndex_None) + { + NS_WARNING("but find hdr did"); + printf("level of found hdr = %d\n", m_levels[highIndex]); + ValidateSort(); + } +#endif + return highIndex; + } + PR_Free(EntryInfo1.key); + PR_Free(EntryInfo2.key); + return msgHdr == resultHdr ? highIndex : nsMsgViewIndex_None; +} + +#ifdef DEBUG_David_Bienvenu + +void nsMsgDBView::InitEntryInfoForIndex(nsMsgViewIndex i, IdKeyPtr &EntryInfo) +{ + EntryInfo.key = nullptr; + + nsresult rv; + uint16_t maxLen; + eFieldType fieldType; + + // Get the custom column handler for the primary sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + + // The following may leave fieldType undefined. + rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to obtain fieldType"); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr)); + + msgHdr->GetMessageKey(&EntryInfo.id); + msgHdr->GetFolder(&EntryInfo.folder); + EntryInfo.folder->Release(); + + nsCOMPtr<nsIMsgDatabase> hdrDB; + EntryInfo.folder->GetMsgDatabase(getter_AddRefs(hdrDB)); + switch (fieldType) + { + case kCollationKey: + PR_FREEIF(EntryInfo.key); + rv = GetCollationKey(msgHdr, m_sortType, &EntryInfo.key, &EntryInfo.dword, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create collation key"); + break; + case kU32: + if (m_sortType == nsMsgViewSortType::byId) + EntryInfo.dword = EntryInfo.id; + else + GetLongField(msgHdr, m_sortType, &EntryInfo.dword, colHandler); + break; + default: + NS_ERROR("invalid field type"); + } +} + +void nsMsgDBView::ValidateSort() +{ + IdKeyPtr EntryInfo1, EntryInfo2; + nsCOMPtr<nsIMsgDBHdr> hdr1, hdr2; + + uint16_t maxLen; + eFieldType fieldType; + + // Get the custom column handler for the primary sort and pass it first + // to GetFieldTypeAndLenForSort to get the fieldType and then either + // GetCollationKey or GetLongField. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + + // It is not entirely clear what we should do since, + // if fieldType is not available, there is no way to know + // how to compare the field to check for sorting. + // So we bomb out here. It is OK since this is debug code + // inside #ifdef DEBUG_David_Bienvenu + rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to obtain fieldType"); + + viewSortInfo comparisonContext; + comparisonContext.view = this; + comparisonContext.isSecondarySort = false; + comparisonContext.ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending); + nsCOMPtr<nsIMsgDatabase> db; + GetDBForViewIndex(0, getter_AddRefs(db)); + // this is only for comparing collation keys - it could be any db. + comparisonContext.db = db.get(); + + for (nsMsgViewIndex i = 0; i < m_keys.Length();) + { + // ignore non threads + if (m_levels[i]) + { + i++; + continue; + } + + // find next header. + nsMsgViewIndex j = i + 1; + while (j < m_keys.Length() && m_levels[j]) + j++; + if (j == m_keys.Length()) + break; + + InitEntryInfoForIndex(i, EntryInfo1); + InitEntryInfoForIndex(j, EntryInfo2); + const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2; + int retStatus = 0; + if (fieldType == kCollationKey) + retStatus = FnSortIdKeyPtr(&pValue1, &pValue2, &comparisonContext); + else if (fieldType == kU32) + retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext); + + if (retStatus && (retStatus < 0) == (m_sortOrder == nsMsgViewSortOrder::ascending)) + { + NS_ERROR("view not sorted correctly"); + break; + } + // j is the new i. + i = j; + } +} + +#endif + +nsresult nsMsgDBView::ListUnreadIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) +{ + NS_ENSURE_ARG(threadHdr); + // these children ids should be in thread order. + nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1; + *pNumListed = 0; + nsMsgKey topLevelMsgKey = m_keys[startOfThreadViewIndex]; + + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + uint32_t i; + for (i = 0; i < numChildren; i++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + if (msgHdr != nullptr) + { + if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) + { + bool killed; + msgHdr->GetIsKilled(&killed); + if (killed) + continue; + } + + nsMsgKey msgKey; + uint32_t msgFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + bool isRead = AdjustReadFlag(msgHdr, &msgFlags); + if (!isRead) + { + // just make sure flag is right in db. + m_db->MarkHdrRead(msgHdr, false, nullptr); + if (msgKey != topLevelMsgKey) + { + InsertMsgHdrAt(viewIndex, msgHdr, msgKey, msgFlags, + FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex)); + viewIndex++; + (*pNumListed)++; + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + // if we're not the instigator, update flags if this key is in our view + if (aInstigator != this) + { + NS_ENSURE_ARG_POINTER(aHdrChanged); + nsMsgKey msgKey; + aHdrChanged->GetMessageKey(&msgKey); + nsMsgViewIndex index = FindHdr(aHdrChanged); + if (index != nsMsgViewIndex_None) + { + uint32_t viewOnlyFlags = m_flags[index] & (MSG_VIEW_FLAGS | nsMsgMessageFlags::Elided); + + // ### what about saving the old view only flags, like IsThread and HasChildren? + // I think we'll want to save those away. + m_flags[index] = aNewFlags | viewOnlyFlags; + // tell the view the extra flag changed, so it can + // update the previous view, if any. + OnExtraFlagChanged(index, aNewFlags); + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + } + + uint32_t deltaFlags = (aOldFlags ^ aNewFlags); + if (deltaFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::New)) + { + nsMsgViewIndex threadIndex = + ThreadIndexOfMsgHdr(aHdrChanged, index, nullptr, nullptr); + // may need to fix thread counts + if (threadIndex != nsMsgViewIndex_None && threadIndex != index) + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + } + } + // don't need to propagate notifications, right? + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrChanged, + nsMsgKey aParentKey, + int32_t aFlags, + nsIDBChangeListener *aInstigator) +{ + nsMsgViewIndex deletedIndex = FindHdr(aHdrChanged); + if (IsValidIndex(deletedIndex)) + { + // Check if this message is currently selected. If it is, tell the frontend + // to be prepared for a delete. + if (mTreeSelection && mCommandUpdater) + { + bool isMsgSelected = false; + mTreeSelection->IsSelected(deletedIndex, &isMsgSelected); + if (isMsgSelected) + mCommandUpdater->UpdateNextMessageAfterDelete(); + } + + RemoveByIndex(deletedIndex); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OnHdrAdded(nsIMsgDBHdr *aHdrChanged, nsMsgKey aParentKey, int32_t aFlags, + nsIDBChangeListener *aInstigator) +{ + return OnNewHeader(aHdrChanged, aParentKey, false); + // probably also want to pass that parent key in, since we went to the trouble + // of figuring out what it is. +} + +NS_IMETHODIMP +nsMsgDBView::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus, + nsIDBChangeListener * aInstigator) +{ + if (aPreChange) + return NS_OK; + + if (aHdrToChange) + { + nsMsgViewIndex index = FindHdr(aHdrToChange); + if (index != nsMsgViewIndex_None) + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + if (m_db) + { + m_db->RemoveListener(this); + m_db = nullptr; + } + + int32_t saveSize = GetSize(); + ClearHdrCache(); + + // this is important, because the tree will ask us for our + // row count, which get determine from the number of keys. + m_keys.Clear(); + // be consistent + m_flags.Clear(); + m_levels.Clear(); + + // tell the tree all the rows have gone away + if (mTree) + mTree->RowCountChanged(0, -saveSize); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::OnEvent(nsIMsgDatabase *aDB, const char *aEvent) +{ + if (!strcmp(aEvent, "DBOpened")) + m_db = aDB; + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBView::OnReadChanged(nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::OnJunkScoreChanged(nsIDBChangeListener *aInstigator) +{ + return NS_OK; +} + +void nsMsgDBView::ClearHdrCache() +{ + m_cachedHdr = nullptr; + m_cachedMsgKey = nsMsgKey_None; +} + +NS_IMETHODIMP nsMsgDBView::SetSuppressChangeNotifications(bool aSuppressChangeNotifications) +{ + mSuppressChangeNotification = aSuppressChangeNotifications; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSuppressChangeNotifications(bool * aSuppressChangeNotifications) +{ + NS_ENSURE_ARG_POINTER(aSuppressChangeNotifications); + *aSuppressChangeNotifications = mSuppressChangeNotification; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::NoteChange(nsMsgViewIndex firstLineChanged, + int32_t numChanged, + nsMsgViewNotificationCodeValue changeType) +{ + if (mTree && !mSuppressChangeNotification) + { + switch (changeType) + { + case nsMsgViewNotificationCode::changed: + mTree->InvalidateRange(firstLineChanged, firstLineChanged + numChanged - 1); + break; + case nsMsgViewNotificationCode::insertOrDelete: + if (numChanged < 0) + mRemovingRow = true; + // the caller needs to have adjusted m_keys before getting here, since + // RowCountChanged() will call our GetRowCount() + mTree->RowCountChanged(firstLineChanged, numChanged); + mRemovingRow = false; + MOZ_FALLTHROUGH; + case nsMsgViewNotificationCode::all: + ClearHdrCache(); + break; + } + } + return NS_OK; +} + +void nsMsgDBView::NoteStartChange(nsMsgViewIndex firstlineChanged, int32_t numChanged, + nsMsgViewNotificationCodeValue changeType) +{ +} +void nsMsgDBView::NoteEndChange(nsMsgViewIndex firstlineChanged, int32_t numChanged, + nsMsgViewNotificationCodeValue changeType) +{ + // send the notification now. + NoteChange(firstlineChanged, numChanged, changeType); +} + +NS_IMETHODIMP nsMsgDBView::GetSortOrder(nsMsgViewSortOrderValue *aSortOrder) +{ + NS_ENSURE_ARG_POINTER(aSortOrder); + *aSortOrder = m_sortOrder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSortType(nsMsgViewSortTypeValue *aSortType) +{ + NS_ENSURE_ARG_POINTER(aSortType); + *aSortType = m_sortType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetSortType(nsMsgViewSortTypeValue aSortType) +{ + m_sortType = aSortType; + return NS_OK; +} + + +NS_IMETHODIMP nsMsgDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ERROR("you should be overriding this"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP nsMsgDBView::GetSecondarySortOrder(nsMsgViewSortOrderValue *aSortOrder) +{ + NS_ENSURE_ARG_POINTER(aSortOrder); + *aSortOrder = m_secondarySortOrder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetSecondarySortOrder(nsMsgViewSortOrderValue aSortOrder) +{ + m_secondarySortOrder = aSortOrder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetSecondarySortType(nsMsgViewSortTypeValue *aSortType) +{ + NS_ENSURE_ARG_POINTER(aSortType); + *aSortType = m_secondarySort; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetSecondarySortType(nsMsgViewSortTypeValue aSortType) +{ + m_secondarySort = aSortType; + return NS_OK; +} + +nsresult nsMsgDBView::PersistFolderInfo(nsIDBFolderInfo **dbFolderInfo) +{ + nsresult rv = m_db->GetDBFolderInfo(dbFolderInfo); + NS_ENSURE_SUCCESS(rv, rv); + // save off sort type and order, view type and flags + (*dbFolderInfo)->SetSortType(m_sortType); + (*dbFolderInfo)->SetSortOrder(m_sortOrder); + (*dbFolderInfo)->SetViewFlags(m_viewFlags); + nsMsgViewTypeValue viewType; + GetViewType(&viewType); + (*dbFolderInfo)->SetViewType(viewType); + return rv; +} + + +NS_IMETHODIMP nsMsgDBView::GetViewFlags(nsMsgViewFlagsTypeValue *aViewFlags) +{ + NS_ENSURE_ARG_POINTER(aViewFlags); + *aViewFlags = m_viewFlags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) +{ + // if we're turning off threaded display, we need to expand all so that all + // messages will be displayed. + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (aViewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + ExpandAll(); + m_sortValid = false; // invalidate the sort so sorting will do something + } + m_viewFlags = aViewFlags; + + if (m_viewFolder) + { + nsCOMPtr <nsIMsgDatabase> db; + nsCOMPtr <nsIDBFolderInfo> folderInfo; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv,rv); + return folderInfo->SetViewFlags(aViewFlags); + } + else + return NS_OK; +} + +nsresult nsMsgDBView::MarkThreadOfMsgRead(nsMsgKey msgId, nsMsgViewIndex msgIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead) +{ + nsCOMPtr <nsIMsgThread> threadHdr; + nsresult rv = GetThreadContainingIndex(msgIndex, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgViewIndex threadIndex; + + NS_ASSERTION(threadHdr, "threadHdr is null"); + if (!threadHdr) + return NS_MSG_MESSAGE_NOT_FOUND; + + nsCOMPtr<nsIMsgDBHdr> firstHdr; + rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(firstHdr)); + NS_ENSURE_SUCCESS(rv, rv); + nsMsgKey firstHdrId; + firstHdr->GetMessageKey(&firstHdrId); + if (msgId != firstHdrId) + threadIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr); + else + threadIndex = msgIndex; + return MarkThreadRead(threadHdr, threadIndex, idsMarkedRead, bRead); +} + +nsresult nsMsgDBView::MarkThreadRead(nsIMsgThread *threadHdr, nsMsgViewIndex threadIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead) +{ + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + idsMarkedRead.SetCapacity(numChildren); + for (int32_t childIndex = 0; childIndex < (int32_t) numChildren ; childIndex++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(msgHdr)); + NS_ASSERTION(msgHdr, "msgHdr is null"); + if (!msgHdr) + continue; + + bool isRead; + + nsMsgKey hdrMsgId; + msgHdr->GetMessageKey(&hdrMsgId); + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + db->IsRead(hdrMsgId, &isRead); + + if (isRead != bRead) + { + // MarkHdrRead will change the unread count on the thread + db->MarkHdrRead(msgHdr, bRead, nullptr); + // insert at the front. should we insert at the end? + idsMarkedRead.InsertElementAt(0, hdrMsgId); + } + } + + return NS_OK; +} + +bool nsMsgDBView::AdjustReadFlag(nsIMsgDBHdr *msgHdr, uint32_t *msgFlags) +{ + // if we're a cross-folder view, just bail on this. + if (GetFolders()) + return *msgFlags & nsMsgMessageFlags::Read; + bool isRead = false; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + m_db->IsRead(msgKey, &isRead); + // just make sure flag is right in db. +#ifdef DEBUG_David_Bienvenu + NS_ASSERTION(isRead == ((*msgFlags & nsMsgMessageFlags::Read) != 0), "msgFlags out of sync"); +#endif + if (isRead) + *msgFlags |= nsMsgMessageFlags::Read; + else + *msgFlags &= ~nsMsgMessageFlags::Read; + m_db->MarkHdrRead(msgHdr, isRead, nullptr); + return isRead; +} + +// Starting from startIndex, performs the passed in navigation, including +// any marking read needed, and returns the resultId and resultIndex of the +// destination of the navigation. If no message is found in the view, +// it returns a resultId of nsMsgKey_None and an resultIndex of nsMsgViewIndex_None. +NS_IMETHODIMP nsMsgDBView::ViewNavigate(nsMsgNavigationTypeValue motion, nsMsgKey *pResultKey, nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, bool wrap) +{ + NS_ENSURE_ARG_POINTER(pResultKey); + NS_ENSURE_ARG_POINTER(pResultIndex); + NS_ENSURE_ARG_POINTER(pThreadIndex); + + int32_t currentIndex; + nsMsgViewIndex startIndex; + + if (!mTreeSelection) // we must be in stand alone message mode + { + currentIndex = FindViewIndex(m_currentlyDisplayedMsgKey); + } + else + { + nsresult rv = mTreeSelection->GetCurrentIndex(¤tIndex); + NS_ENSURE_SUCCESS(rv, rv); + } + startIndex = currentIndex; + return nsMsgDBView::NavigateFromPos(motion, startIndex, pResultKey, pResultIndex, pThreadIndex, wrap); +} + +nsresult nsMsgDBView::NavigateFromPos(nsMsgNavigationTypeValue motion, nsMsgViewIndex startIndex, nsMsgKey *pResultKey, nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, bool wrap) +{ + nsresult rv = NS_OK; + nsMsgKey resultThreadKey; + nsMsgViewIndex curIndex; + nsMsgViewIndex lastIndex = (GetSize() > 0) ? (nsMsgViewIndex) GetSize() - 1 : nsMsgViewIndex_None; + nsMsgViewIndex threadIndex = nsMsgViewIndex_None; + + // if there aren't any messages in the view, bail out. + if (GetSize() <= 0) + { + *pResultIndex = nsMsgViewIndex_None; + *pResultKey = nsMsgKey_None; + return NS_OK; + } + + switch (motion) + { + case nsMsgNavigationType::firstMessage: + *pResultIndex = 0; + *pResultKey = m_keys[0]; + break; + case nsMsgNavigationType::nextMessage: + // return same index and id on next on last message + *pResultIndex = std::min(startIndex + 1, lastIndex); + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::previousMessage: + *pResultIndex = (startIndex != nsMsgViewIndex_None && startIndex > 0) ? startIndex - 1 : 0; + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::lastMessage: + *pResultIndex = lastIndex; + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::firstFlagged: + rv = FindFirstFlagged(pResultIndex); + if (IsValidIndex(*pResultIndex)) + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::nextFlagged: + rv = FindNextFlagged(startIndex + 1, pResultIndex); + if (IsValidIndex(*pResultIndex)) + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::previousFlagged: + rv = FindPrevFlagged(startIndex, pResultIndex); + if (IsValidIndex(*pResultIndex)) + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::firstNew: + rv = FindFirstNew(pResultIndex); + if (IsValidIndex(*pResultIndex)) + *pResultKey = m_keys[*pResultIndex]; + break; + case nsMsgNavigationType::firstUnreadMessage: + startIndex = nsMsgViewIndex_None; // note fall thru - is this motion ever used? + MOZ_FALLTHROUGH; + case nsMsgNavigationType::nextUnreadMessage: + for (curIndex = (startIndex == nsMsgViewIndex_None) ? 0 : startIndex; curIndex <= lastIndex && lastIndex != nsMsgViewIndex_None; curIndex++) { + uint32_t flags = m_flags[curIndex]; + + // don't return start index since navigate should move + if (!(flags & (nsMsgMessageFlags::Read | MSG_VIEW_FLAG_DUMMY)) && (curIndex != startIndex)) + { + *pResultIndex = curIndex; + *pResultKey = m_keys[*pResultIndex]; + break; + } + // check for collapsed thread with new children + if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) && flags & MSG_VIEW_FLAG_ISTHREAD && flags & nsMsgMessageFlags::Elided) { + nsCOMPtr <nsIMsgThread> threadHdr; + GetThreadContainingIndex(curIndex, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(threadHdr, "threadHdr is null"); + if (!threadHdr) + continue; + uint32_t numUnreadChildren; + threadHdr->GetNumUnreadChildren(&numUnreadChildren); + if (numUnreadChildren > 0) + { + uint32_t numExpanded; + ExpandByIndex(curIndex, &numExpanded); + lastIndex += numExpanded; + if (pThreadIndex) + *pThreadIndex = curIndex; + } + } + } + if (curIndex > lastIndex) + { + // wrap around by starting at index 0. + if (wrap) + { + nsMsgKey startKey = GetAt(startIndex); + + rv = NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, nsMsgViewIndex_None, pResultKey, pResultIndex, pThreadIndex, false); + + if (*pResultKey == startKey) + { + // wrapped around and found start message! + *pResultIndex = nsMsgViewIndex_None; + *pResultKey = nsMsgKey_None; + } + } + else + { + *pResultIndex = nsMsgViewIndex_None; + *pResultKey = nsMsgKey_None; + } + } + break; + case nsMsgNavigationType::previousUnreadMessage: + if (startIndex == nsMsgViewIndex_None) + break; + rv = FindPrevUnread(m_keys[startIndex], pResultKey, + &resultThreadKey); + if (NS_SUCCEEDED(rv)) + { + *pResultIndex = FindViewIndex(*pResultKey); + if (*pResultKey != resultThreadKey && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + threadIndex = GetThreadIndex(*pResultIndex); + if (*pResultIndex == nsMsgViewIndex_None) + { + nsCOMPtr <nsIMsgThread> threadHdr; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = m_db->GetMsgHdrForKey(*pResultKey, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(threadHdr, "threadHdr is null"); + if (threadHdr) + break; + uint32_t numUnreadChildren; + threadHdr->GetNumUnreadChildren(&numUnreadChildren); + if (numUnreadChildren > 0) + { + uint32_t numExpanded; + ExpandByIndex(threadIndex, &numExpanded); + } + *pResultIndex = FindViewIndex(*pResultKey); + } + } + if (pThreadIndex) + *pThreadIndex = threadIndex; + } + break; + case nsMsgNavigationType::lastUnreadMessage: + break; + case nsMsgNavigationType::nextUnreadThread: + if (startIndex != nsMsgViewIndex_None) + ApplyCommandToIndices(nsMsgViewCommandType::markThreadRead, &startIndex, 1); + + return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, startIndex, pResultKey, pResultIndex, pThreadIndex, true); + case nsMsgNavigationType::toggleThreadKilled: + { + bool resultKilled; + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + ToggleIgnored(selection.Elements(), selection.Length(), &threadIndex, &resultKilled); + if (resultKilled) + { + return NavigateFromPos(nsMsgNavigationType::nextUnreadThread, threadIndex, pResultKey, pResultIndex, pThreadIndex, true); + } + else + { + *pResultIndex = nsMsgViewIndex_None; + *pResultKey = nsMsgKey_None; + return NS_OK; + } + } + case nsMsgNavigationType::toggleSubthreadKilled: + { + bool resultKilled; + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + ToggleMessageKilled(selection.Elements(), selection.Length(), + &threadIndex, &resultKilled); + if (resultKilled) + { + return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, threadIndex, pResultKey, pResultIndex, pThreadIndex, true); + } + else + { + *pResultIndex = nsMsgViewIndex_None; + *pResultKey = nsMsgKey_None; + return NS_OK; + } + } + // check where navigate says this will take us. If we have the message in the view, + // return it. Otherwise, return an error. + case nsMsgNavigationType::back: + case nsMsgNavigationType::forward: + { + nsCString folderUri, msgUri; + nsCString viewFolderUri; + nsCOMPtr<nsIMsgFolder> curFolder = m_viewFolder ? m_viewFolder : m_folder; + if (curFolder) + curFolder->GetURI(viewFolderUri); + int32_t relPos = (motion == nsMsgNavigationType::forward) + ? 1 : (m_currentlyDisplayedMsgKey != nsMsgKey_None) ? -1 : 0; + int32_t curPos; + nsresult rv; + nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = messenger->GetFolderUriAtNavigatePos(relPos, folderUri); + NS_ENSURE_SUCCESS(rv, rv); + // Empty viewFolderUri means we're in a search results view. + if (viewFolderUri.IsEmpty() || folderUri.Equals(viewFolderUri)) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = messenger->GetMsgUriAtNavigatePos(relPos, msgUri); + NS_ENSURE_SUCCESS(rv, rv); + messenger->MsgHdrFromURI(msgUri, getter_AddRefs(msgHdr)); + if (msgHdr) + { + messenger->GetNavigatePos(&curPos); + curPos += relPos; + *pResultIndex = FindHdr(msgHdr); + messenger->SetNavigatePos(curPos); + msgHdr->GetMessageKey(pResultKey); + return NS_OK; + } + } + *pResultIndex = nsMsgViewIndex_None; + *pResultKey = nsMsgKey_None; + break; + + } + default: + NS_ERROR("unsupported motion"); + break; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::NavigateStatus(nsMsgNavigationTypeValue motion, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + bool enable = false; + nsresult rv = NS_ERROR_FAILURE; + nsMsgKey resultKey = nsMsgKey_None; + int32_t index = nsMsgKey_None; + nsMsgViewIndex resultIndex = nsMsgViewIndex_None; + if (mTreeSelection) + (void) mTreeSelection->GetCurrentIndex(&index); + else + index = FindViewIndex(m_currentlyDisplayedMsgKey); + nsCOMPtr<nsIMessenger> messenger (do_QueryReferent(mMessengerWeak)); + // warning - we no longer validate index up front because fe passes in -1 for no + // selection, so if you use index, be sure to validate it before using it + // as an array index. + switch (motion) + { + case nsMsgNavigationType::firstMessage: + case nsMsgNavigationType::lastMessage: + if (GetSize() > 0) + enable = true; + break; + case nsMsgNavigationType::nextMessage: + if (IsValidIndex(index) && uint32_t(index) < GetSize() - 1) + enable = true; + break; + case nsMsgNavigationType::previousMessage: + if (IsValidIndex(index) && index != 0 && GetSize() > 1) + enable = true; + break; + case nsMsgNavigationType::firstFlagged: + rv = FindFirstFlagged(&resultIndex); + enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None); + break; + case nsMsgNavigationType::nextFlagged: + rv = FindNextFlagged(index + 1, &resultIndex); + enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None); + break; + case nsMsgNavigationType::previousFlagged: + if (IsValidIndex(index) && index != 0) + rv = FindPrevFlagged(index, &resultIndex); + enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None); + break; + case nsMsgNavigationType::firstNew: + rv = FindFirstNew(&resultIndex); + enable = (NS_SUCCEEDED(rv) && resultIndex != nsMsgViewIndex_None); + break; + case nsMsgNavigationType::readMore: + enable = true; // for now, always true. + break; + case nsMsgNavigationType::nextFolder: + case nsMsgNavigationType::nextUnreadThread: + case nsMsgNavigationType::nextUnreadMessage: + case nsMsgNavigationType::toggleThreadKilled: + enable = true; // always enabled + break; + case nsMsgNavigationType::previousUnreadMessage: + if (IsValidIndex(index)) + { + nsMsgKey threadId; + rv = FindPrevUnread(m_keys[index], &resultKey, &threadId); + enable = (resultKey != nsMsgKey_None); + } + break; + case nsMsgNavigationType::forward: + case nsMsgNavigationType::back: + { + uint32_t curPos; + uint32_t historyCount; + + if (messenger) + { + messenger->GetNavigateHistory(&curPos, &historyCount, nullptr); + int32_t desiredPos = (int32_t) curPos; + if (motion == nsMsgNavigationType::forward) + desiredPos++; + else + desiredPos--; //? operator code didn't work for me + enable = (desiredPos >= 0 && desiredPos < (int32_t) historyCount / 2); + } + } + break; + + default: + NS_ERROR("unexpected"); + break; + } + + *_retval = enable; + return NS_OK; +} + +// Note that these routines do NOT expand collapsed threads! This mimics the old behaviour, +// but it's also because we don't remember whether a thread contains a flagged message the +// same way we remember if a thread contains new messages. It would be painful to dive down +// into each collapsed thread to update navigate status. +// We could cache this info, but it would still be expensive the first time this status needs +// to get updated. +nsresult nsMsgDBView::FindNextFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex) +{ + nsMsgViewIndex lastIndex = (nsMsgViewIndex) GetSize() - 1; + nsMsgViewIndex curIndex; + + *pResultIndex = nsMsgViewIndex_None; + + if (GetSize() > 0) + { + for (curIndex = startIndex; curIndex <= lastIndex; curIndex++) + { + uint32_t flags = m_flags[curIndex]; + if (flags & nsMsgMessageFlags::Marked) + { + *pResultIndex = curIndex; + break; + } + } + } + + return NS_OK; +} + +nsresult nsMsgDBView::FindFirstNew(nsMsgViewIndex *pResultIndex) +{ + if (m_db) + { + nsMsgKey firstNewKey = nsMsgKey_None; + m_db->GetFirstNew(&firstNewKey); + *pResultIndex = (firstNewKey != nsMsgKey_None) + ? FindKey(firstNewKey, true) : nsMsgViewIndex_None; + } + return NS_OK; +} + +nsresult nsMsgDBView::FindPrevUnread(nsMsgKey startKey, nsMsgKey *pResultKey, + nsMsgKey *resultThreadId) +{ + nsMsgViewIndex startIndex = FindViewIndex(startKey); + nsMsgViewIndex curIndex = startIndex; + nsresult rv = NS_MSG_MESSAGE_NOT_FOUND; + + if (startIndex == nsMsgViewIndex_None) + return NS_MSG_MESSAGE_NOT_FOUND; + + *pResultKey = nsMsgKey_None; + if (resultThreadId) + *resultThreadId = nsMsgKey_None; + + for (; (int) curIndex >= 0 && (*pResultKey == nsMsgKey_None); curIndex--) + { + uint32_t flags = m_flags[curIndex]; + + if (curIndex != startIndex && flags & MSG_VIEW_FLAG_ISTHREAD && flags & nsMsgMessageFlags::Elided) + { + NS_ERROR("fix this"); + //nsMsgKey threadId = m_keys[curIndex]; + //rv = m_db->GetUnreadKeyInThread(threadId, pResultKey, resultThreadId); + if (NS_SUCCEEDED(rv) && (*pResultKey != nsMsgKey_None)) + break; + } + if (!(flags & (nsMsgMessageFlags::Read | MSG_VIEW_FLAG_DUMMY)) && (curIndex != startIndex)) + { + *pResultKey = m_keys[curIndex]; + rv = NS_OK; + break; + } + } + // found unread message but we don't know the thread + NS_ASSERTION(!(*pResultKey != nsMsgKey_None && resultThreadId && *resultThreadId == nsMsgKey_None), + "fix this"); + return rv; +} + +nsresult nsMsgDBView::FindFirstFlagged(nsMsgViewIndex *pResultIndex) +{ + return FindNextFlagged(0, pResultIndex); +} + +nsresult nsMsgDBView::FindPrevFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex) +{ + nsMsgViewIndex curIndex; + + *pResultIndex = nsMsgViewIndex_None; + + if (GetSize() > 0 && IsValidIndex(startIndex)) + { + curIndex = startIndex; + do + { + if (curIndex != 0) + curIndex--; + + uint32_t flags = m_flags[curIndex]; + if (flags & nsMsgMessageFlags::Marked) + { + *pResultIndex = curIndex; + break; + } + } + while (curIndex != 0); + } + return NS_OK; +} + +bool nsMsgDBView::IsValidIndex(nsMsgViewIndex index) +{ + return index != nsMsgViewIndex_None && + (index < (nsMsgViewIndex) m_keys.Length()); +} + +nsresult nsMsgDBView::OrExtraFlag(nsMsgViewIndex index, uint32_t orflag) +{ + uint32_t flag; + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + flag = m_flags[index]; + flag |= orflag; + m_flags[index] = flag; + OnExtraFlagChanged(index, flag); + return NS_OK; +} + +nsresult nsMsgDBView::AndExtraFlag(nsMsgViewIndex index, uint32_t andflag) +{ + uint32_t flag; + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + flag = m_flags[index]; + flag &= andflag; + m_flags[index] = flag; + OnExtraFlagChanged(index, flag); + return NS_OK; +} + +nsresult nsMsgDBView::SetExtraFlag(nsMsgViewIndex index, uint32_t extraflag) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + m_flags[index] = extraflag; + OnExtraFlagChanged(index, extraflag); + return NS_OK; +} + + +nsresult nsMsgDBView::ToggleIgnored(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState) +{ + nsCOMPtr <nsIMsgThread> thread; + + // Ignored state is toggled based on the first selected thread + nsMsgViewIndex threadIndex = GetThreadFromMsgIndex(indices[0], getter_AddRefs(thread)); + uint32_t threadFlags; + thread->GetFlags(&threadFlags); + uint32_t ignored = threadFlags & nsMsgMessageFlags::Ignored; + + // Process threads in reverse order + // Otherwise collapsing the threads will invalidate the indices + threadIndex = nsMsgViewIndex_None; + while (numIndices) + { + numIndices--; + if (indices[numIndices] < threadIndex) + { + threadIndex = GetThreadFromMsgIndex(indices[numIndices], getter_AddRefs(thread)); + thread->GetFlags(&threadFlags); + if ((threadFlags & nsMsgMessageFlags::Ignored) == ignored) + SetThreadIgnored(thread, threadIndex, !ignored); + } + } + + if (resultIndex) + *resultIndex = threadIndex; + if (resultToggleState) + *resultToggleState = !ignored; + + return NS_OK; +} + +nsresult nsMsgDBView::ToggleMessageKilled(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState) +{ + NS_ENSURE_ARG_POINTER(resultToggleState); + + nsCOMPtr <nsIMsgDBHdr> header; + // Ignored state is toggled based on the first selected message + nsresult rv = GetMsgHdrForViewIndex(indices[0], getter_AddRefs(header)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t msgFlags; + header->GetFlags(&msgFlags); + uint32_t ignored = msgFlags & nsMsgMessageFlags::Ignored; + + // Process messages in reverse order + // Otherwise the indices may be invalidated... + nsMsgViewIndex msgIndex = nsMsgViewIndex_None; + while (numIndices) + { + numIndices--; + if (indices[numIndices] < msgIndex) + { + msgIndex = indices[numIndices]; + rv = GetMsgHdrForViewIndex(msgIndex, getter_AddRefs(header)); + NS_ENSURE_SUCCESS(rv, rv); + header->GetFlags(&msgFlags); + if ((msgFlags & nsMsgMessageFlags::Ignored) == ignored) + SetSubthreadKilled(header, msgIndex, !ignored); + } + } + + if (resultIndex) + *resultIndex = msgIndex; + if (resultToggleState) + *resultToggleState = !ignored; + + return NS_OK; +} + +nsMsgViewIndex nsMsgDBView::GetThreadFromMsgIndex(nsMsgViewIndex index, + nsIMsgThread **threadHdr) +{ + nsMsgKey msgKey = GetAt(index); + nsMsgViewIndex threadIndex; + + if (threadHdr == nullptr) + return nsMsgViewIndex_None; + + nsresult rv = GetThreadContainingIndex(index, threadHdr); + NS_ENSURE_SUCCESS(rv,nsMsgViewIndex_None); + + if (*threadHdr == nullptr) + return nsMsgViewIndex_None; + + nsMsgKey threadKey; + (*threadHdr)->GetThreadKey(&threadKey); + if (msgKey !=threadKey) + threadIndex = GetIndexOfFirstDisplayedKeyInThread(*threadHdr); + else + threadIndex = index; + return threadIndex; +} + +nsresult nsMsgDBView::ToggleWatched( nsMsgViewIndex* indices, int32_t numIndices) +{ + nsCOMPtr <nsIMsgThread> thread; + + // Watched state is toggled based on the first selected thread + nsMsgViewIndex threadIndex = GetThreadFromMsgIndex(indices[0], getter_AddRefs(thread)); + uint32_t threadFlags; + thread->GetFlags(&threadFlags); + uint32_t watched = threadFlags & nsMsgMessageFlags::Watched; + + // Process threads in reverse order + // for consistency with ToggleIgnored + threadIndex = nsMsgViewIndex_None; + while (numIndices) + { + numIndices--; + if (indices[numIndices] < threadIndex) + { + threadIndex = GetThreadFromMsgIndex(indices[numIndices], getter_AddRefs(thread)); + thread->GetFlags(&threadFlags); + if ((threadFlags & nsMsgMessageFlags::Watched) == watched) + SetThreadWatched(thread, threadIndex, !watched); + } + } + + return NS_OK; +} + +nsresult nsMsgDBView::SetThreadIgnored(nsIMsgThread *thread, nsMsgViewIndex threadIndex, bool ignored) +{ + if (!IsValidIndex(threadIndex)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + if (ignored) + { + nsTArray<nsMsgKey> idsMarkedRead; + + MarkThreadRead(thread, threadIndex, idsMarkedRead, true); + CollapseByIndex(threadIndex, nullptr); + } + + if (!m_db) + return NS_ERROR_FAILURE; + return m_db->MarkThreadIgnored(thread, m_keys[threadIndex], ignored, this); +} + +nsresult nsMsgDBView::SetSubthreadKilled(nsIMsgDBHdr *header, nsMsgViewIndex msgIndex, bool ignored) +{ + if (!IsValidIndex(msgIndex)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + NoteChange(msgIndex, 1, nsMsgViewNotificationCode::changed); + + if (!m_db) + return NS_ERROR_FAILURE; + nsresult rv = m_db->MarkHeaderKilled(header, ignored, this); + NS_ENSURE_SUCCESS(rv, rv); + + if (ignored) + { + nsCOMPtr <nsIMsgThread> thread; + nsresult rv; + rv = GetThreadContainingMsgHdr(header, getter_AddRefs(thread)); + if (NS_FAILED(rv)) + return NS_OK; // So we didn't mark threads read + + uint32_t children, current; + thread->GetNumChildren(&children); + + nsMsgKey headKey; + header->GetMessageKey(&headKey); + + for (current = 0; current < children; current++) + { + nsMsgKey newKey; + thread->GetChildKeyAt(current, &newKey); + if (newKey == headKey) + break; + } + + // Process all messages, starting with this message. + for (; current < children; current++) + { + nsCOMPtr <nsIMsgDBHdr> nextHdr; + bool isKilled; + + thread->GetChildHdrAt(current, getter_AddRefs(nextHdr)); + nextHdr->GetIsKilled(&isKilled); + + // Ideally, the messages should stop processing here. + // However, the children are ordered not by thread... + if (isKilled) + nextHdr->MarkRead(true); + } + } + return NS_OK; +} + +nsresult nsMsgDBView::SetThreadWatched(nsIMsgThread *thread, nsMsgViewIndex index, bool watched) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + return m_db->MarkThreadWatched(thread, m_keys[index], watched, this); +} + +NS_IMETHODIMP nsMsgDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder) +{ + NS_ENSURE_ARG_POINTER(aMsgFolder); + NS_IF_ADDREF(*aMsgFolder = m_folder); + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SetViewFolder(nsIMsgFolder *aMsgFolder) +{ + m_viewFolder = aMsgFolder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetViewFolder(nsIMsgFolder **aMsgFolder) +{ + NS_ENSURE_ARG_POINTER(aMsgFolder); + NS_IF_ADDREF(*aMsgFolder = m_viewFolder); + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgDBView::GetNumSelected(uint32_t *aNumSelected) +{ + NS_ENSURE_ARG_POINTER(aNumSelected); + + if (!mTreeSelection) + { + // No tree selection can mean we're in the stand alone mode. + *aNumSelected = (m_currentlyDisplayedMsgKey != nsMsgKey_None) ? 1 : 0; + return NS_OK; + } + + bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads(); + + // We call this a lot from the front end JS, so make it fast. + nsresult rv = mTreeSelection->GetCount((int32_t*)aNumSelected); + if (!*aNumSelected || !includeCollapsedMsgs || + !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + return rv; + + int32_t numSelectedIncludingCollapsed = *aNumSelected; + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + int32_t numIndices = selection.Length(); + // iterate over the selection, counting up the messages in collapsed + // threads. + for (int32_t i = 0; i < numIndices; i++) + { + if (m_flags[selection[i]] & nsMsgMessageFlags::Elided) + { + int32_t collapsedCount; + ExpansionDelta(selection[i], &collapsedCount); + numSelectedIncludingCollapsed += collapsedCount; + } + } + *aNumSelected = numSelectedIncludingCollapsed; + return rv; +} + +NS_IMETHODIMP nsMsgDBView::GetNumMsgsInView(int32_t *aNumMsgs) +{ + NS_ENSURE_ARG_POINTER(aNumMsgs); + return (m_folder) ? m_folder->GetTotalMessages(false, aNumMsgs) : + NS_ERROR_FAILURE; +} +/** + * @note For the IMAP delete model, this applies to both deleting and + * undeleting a message. + */ +NS_IMETHODIMP +nsMsgDBView::GetMsgToSelectAfterDelete(nsMsgViewIndex *msgToSelectAfterDelete) +{ + NS_ENSURE_ARG_POINTER(msgToSelectAfterDelete); + *msgToSelectAfterDelete = nsMsgViewIndex_None; + + bool isMultiSelect = false; + int32_t startFirstRange = nsMsgViewIndex_None; + int32_t endFirstRange = nsMsgViewIndex_None; + if (!mTreeSelection) + { + // If we don't have a tree selection then we must be in stand alone mode. + // return the index of the current message key as the first selected index. + *msgToSelectAfterDelete = FindViewIndex(m_currentlyDisplayedMsgKey); + } + else + { + int32_t selectionCount; + int32_t startRange; + int32_t endRange; + nsresult rv = mTreeSelection->GetRangeCount(&selectionCount); + NS_ENSURE_SUCCESS(rv, rv); + for (int32_t i = 0; i < selectionCount; i++) + { + rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange); + NS_ENSURE_SUCCESS(rv, rv); + + // save off the first range in case we need it later + if (i == 0) { + startFirstRange = startRange; + endFirstRange = endRange; + } else { + // If the tree selection is goofy (eg adjacent or overlapping ranges), + // complain about it, but don't try and cope. Just live with the fact + // that one of the deleted messages is going to end up selected. + NS_WARNING_ASSERTION(endFirstRange != startRange, + "goofy tree selection state: two ranges are adjacent!"); + } + *msgToSelectAfterDelete = std::min(*msgToSelectAfterDelete, + (nsMsgViewIndex)startRange); + } + + // Multiple selection either using Ctrl, Shift, or one of the affordances + // to select an entire thread. + isMultiSelect = (selectionCount > 1 || (endRange-startRange) > 0); + } + + if (*msgToSelectAfterDelete == nsMsgViewIndex_None) + return NS_OK; + + nsCOMPtr<nsIMsgFolder> folder; + GetMsgFolder(getter_AddRefs(folder)); + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder); + bool thisIsImapFolder = (imapFolder != nullptr); + // Need to update the imap-delete model, can change more than once in a session. + if (thisIsImapFolder) + GetImapDeleteModel(nullptr); + + // If mail.delete_matches_sort_order is true, + // for views sorted in descending order (newest at the top), make msgToSelectAfterDelete + // advance in the same direction as the sort order. + bool deleteMatchesSort = false; + if (m_sortOrder == nsMsgViewSortOrder::descending && *msgToSelectAfterDelete) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + prefBranch->GetBoolPref("mail.delete_matches_sort_order", &deleteMatchesSort); + } + + if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) + { + if (isMultiSelect) + { + if (deleteMatchesSort) + *msgToSelectAfterDelete = startFirstRange - 1; + else + *msgToSelectAfterDelete = endFirstRange + 1; + } + else + { + if (deleteMatchesSort) + *msgToSelectAfterDelete -= 1; + else + *msgToSelectAfterDelete += 1; + } + } + else if (deleteMatchesSort) + { + *msgToSelectAfterDelete -= 1; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::GetRemoveRowOnMoveOrDelete(bool *aRemoveRowOnMoveOrDelete) +{ + NS_ENSURE_ARG_POINTER(aRemoveRowOnMoveOrDelete); + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder); + if (!imapFolder) + { + *aRemoveRowOnMoveOrDelete = true; + return NS_OK; + } + + // need to update the imap-delete model, can change more than once in a session. + GetImapDeleteModel(nullptr); + + // unlike the other imap delete models, "mark as deleted" does not remove rows on delete (or move) + *aRemoveRowOnMoveOrDelete = (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete); + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgDBView::GetCurrentlyDisplayedMessage(nsMsgViewIndex *currentlyDisplayedMessage) +{ + NS_ENSURE_ARG_POINTER(currentlyDisplayedMessage); + *currentlyDisplayedMessage = FindViewIndex(m_currentlyDisplayedMsgKey); + return NS_OK; +} + +// if nothing selected, return an NS_ERROR +NS_IMETHODIMP +nsMsgDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr) +{ + NS_ENSURE_ARG_POINTER(hdr); + + nsresult rv; + nsMsgKey key; + rv = GetKeyForFirstSelectedMessage(&key); + // don't assert, it is legal for nothing to be selected + if (NS_FAILED(rv)) return rv; + + if (!m_db) + return NS_MSG_MESSAGE_NOT_FOUND; + + rv = m_db->GetMsgHdrForKey(key, hdr); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +// if nothing selected, return an NS_ERROR +NS_IMETHODIMP +nsMsgDBView::GetURIForFirstSelectedMessage(nsACString &uri) +{ + nsresult rv; + nsMsgViewIndex viewIndex; + rv = GetViewIndexForFirstSelectedMsg(&viewIndex); + // don't assert, it is legal for nothing to be selected + if (NS_FAILED(rv)) return rv; + + return GetURIForViewIndex(viewIndex, uri); +} + +NS_IMETHODIMP +nsMsgDBView::OnDeleteCompleted(bool aSucceeded) +{ + if (m_deletingRows && aSucceeded) + { + uint32_t numIndices = mIndicesToNoteChange.Length(); + if (numIndices && mTree) + { + if (numIndices > 1) + mIndicesToNoteChange.Sort(); + + // the call to NoteChange() has to happen after we are done removing the keys + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + if (numIndices > 1) + mTree->BeginUpdateBatch(); + for (uint32_t i = 0; i < numIndices; i++) + NoteChange(mIndicesToNoteChange[i], -1, nsMsgViewNotificationCode::insertOrDelete); + if (numIndices > 1) + mTree->EndUpdateBatch(); + } + mIndicesToNoteChange.Clear(); + } + + m_deletingRows = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::GetDb(nsIMsgDatabase **aDB) +{ + NS_ENSURE_ARG_POINTER(aDB); + NS_IF_ADDREF(*aDB = m_db); + return NS_OK; +} + +bool nsMsgDBView::OfflineMsgSelected(nsMsgViewIndex * indices, int32_t numIndices) +{ + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder); + if (localFolder) + return true; + + for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++) + { + // For cross-folder saved searches, we need to check if any message + // is in a local folder. + if (!m_folder) + { + nsCOMPtr<nsIMsgFolder> folder; + GetFolderForViewIndex(indices[index], getter_AddRefs(folder)); + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder); + if (localFolder) + return true; + } + + uint32_t flags = m_flags[indices[index]]; + if ((flags & nsMsgMessageFlags::Offline)) + return true; + } + return false; +} + +bool nsMsgDBView::NonDummyMsgSelected(nsMsgViewIndex * indices, int32_t numIndices) +{ + bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads(); + + for (nsMsgViewIndex index = 0; index < (nsMsgViewIndex) numIndices; index++) + { + uint32_t flags = m_flags[indices[index]]; + // We now treat having a collapsed dummy message selected as if + // the whole group was selected so we can apply commands to the group. + if (!(flags & MSG_VIEW_FLAG_DUMMY) || + (flags & nsMsgMessageFlags::Elided && includeCollapsedMsgs)) + return true; + } + return false; +} + +NS_IMETHODIMP nsMsgDBView::GetViewIndexForFirstSelectedMsg(nsMsgViewIndex *aViewIndex) +{ + NS_ENSURE_ARG_POINTER(aViewIndex); + // If we don't have a tree selection we must be in stand alone mode... + if (!mTreeSelection) + { + *aViewIndex = m_currentlyDisplayedViewIndex; + return NS_OK; + } + + int32_t startRange; + int32_t endRange; + nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange); + // don't assert, it is legal for nothing to be selected + if (NS_FAILED(rv)) + return rv; + + // check that the first index is valid, it may not be if nothing is selected + if (startRange < 0 || uint32_t(startRange) >= GetSize()) + return NS_ERROR_UNEXPECTED; + + *aViewIndex = startRange; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::GetKeyForFirstSelectedMessage(nsMsgKey *key) +{ + NS_ENSURE_ARG_POINTER(key); + // If we don't have a tree selection we must be in stand alone mode... + if (!mTreeSelection) + { + *key = m_currentlyDisplayedMsgKey; + return NS_OK; + } + + int32_t startRange; + int32_t endRange; + nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange); + // don't assert, it is legal for nothing to be selected + if (NS_FAILED(rv)) + return rv; + + // check that the first index is valid, it may not be if nothing is selected + if (startRange < 0 || uint32_t(startRange) >= GetSize()) + return NS_ERROR_UNEXPECTED; + + if (m_flags[startRange] & MSG_VIEW_FLAG_DUMMY) + return NS_MSG_INVALID_DBVIEW_INDEX; + + *key = m_keys[startRange]; + return NS_OK; +} + +nsCOMArray<nsIMsgFolder>* nsMsgDBView::GetFolders() +{ + return nullptr; +} + +nsresult nsMsgDBView::AdjustRowCount(int32_t rowCountBeforeSort, int32_t rowCountAfterSort) +{ + int32_t rowChange = rowCountAfterSort - rowCountBeforeSort; + + if (rowChange) + { + // this is not safe to use when you have a selection + // RowCountChanged() will call AdjustSelection() + uint32_t numSelected = 0; + GetNumSelected(&numSelected); + NS_ASSERTION(numSelected == 0, "it is not save to call AdjustRowCount() when you have a selection"); + + if (mTree) + mTree->RowCountChanged(0, rowChange); + } + return NS_OK; +} + +nsresult nsMsgDBView::GetImapDeleteModel(nsIMsgFolder *folder) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsIMsgIncomingServer> server; + if (folder) //for the search view + folder->GetServer(getter_AddRefs(server)); + else if (m_folder) + m_folder->GetServer(getter_AddRefs(server)); + nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv); + if (NS_SUCCEEDED(rv) && imapServer ) + imapServer->GetDeleteModel(&mDeleteModel); + return rv; +} + + +// +// CanDrop +// +// Can't drop on the thread pane. +// +NS_IMETHODIMP nsMsgDBView::CanDrop(int32_t index, + int32_t orient, + nsIDOMDataTransfer *dataTransfer, + bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + + return NS_OK; +} + + +// +// Drop +// +// Can't drop on the thread pane. +// +NS_IMETHODIMP nsMsgDBView::Drop(int32_t row, + int32_t orient, + nsIDOMDataTransfer *dataTransfer) +{ + return NS_OK; +} + + +// +// IsSorted +// +// ... +// +NS_IMETHODIMP nsMsgDBView::IsSorted(bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SelectFolderMsgByKey(nsIMsgFolder *aFolder, nsMsgKey aKey) +{ + NS_ENSURE_ARG_POINTER(aFolder); + if (aKey == nsMsgKey_None) + return NS_ERROR_FAILURE; + + // this is OK for non search views. + + nsMsgViewIndex viewIndex = FindKey(aKey, true /* expand */); + + if (mTree) + mTreeSelection->SetCurrentIndex(viewIndex); + + // make sure the current message is once again visible in the thread pane + // so we don't have to go search for it in the thread pane + if (mTree && viewIndex != nsMsgViewIndex_None) + { + mTreeSelection->Select(viewIndex); + mTree->EnsureRowIsVisible(viewIndex); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgDBView::SelectMsgByKey(nsMsgKey aKey) +{ + NS_ASSERTION(aKey != nsMsgKey_None, "bad key"); + if (aKey == nsMsgKey_None) + return NS_OK; + + // use SaveAndClearSelection() + // and RestoreSelection() so that we'll clear the current selection + // but pass in a different key array so that we'll + // select (and load) the desired message + + AutoTArray<nsMsgKey, 1> preservedSelection; + nsresult rv = SaveAndClearSelection(nullptr, preservedSelection); + NS_ENSURE_SUCCESS(rv,rv); + + // now, restore our desired selection + AutoTArray<nsMsgKey, 1> keyArray; + keyArray.AppendElement(aKey); + + // if the key was not found + // (this can happen with "remember last selected message") + // nothing will be selected + rv = RestoreSelection(aKey, keyArray); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) +{ + nsMsgDBView* newMsgDBView = new nsMsgDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} + +nsresult nsMsgDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) +{ + NS_ENSURE_ARG_POINTER(aNewMsgDBView); + if (aMsgWindow) + { + aNewMsgDBView->mMsgWindowWeak = do_GetWeakReference(aMsgWindow); + aMsgWindow->SetOpenFolder(m_viewFolder? m_viewFolder : m_folder); + } + aNewMsgDBView->mMessengerWeak = do_GetWeakReference(aMessengerInstance); + aNewMsgDBView->mCommandUpdater = aCmdUpdater; + aNewMsgDBView->m_folder = m_folder; + aNewMsgDBView->m_viewFlags = m_viewFlags; + aNewMsgDBView->m_sortOrder = m_sortOrder; + aNewMsgDBView->m_sortType = m_sortType; + aNewMsgDBView->m_curCustomColumn = m_curCustomColumn; + aNewMsgDBView->m_secondarySort = m_secondarySort; + aNewMsgDBView->m_secondarySortOrder = m_secondarySortOrder; + aNewMsgDBView->m_secondaryCustomColumn = m_secondaryCustomColumn; + aNewMsgDBView->m_db = m_db; + aNewMsgDBView->mDateFormatter = mDateFormatter; + if (m_db) + aNewMsgDBView->m_db->AddListener(aNewMsgDBView); + aNewMsgDBView->mIsNews = mIsNews; + aNewMsgDBView->mIsRss = mIsRss; + aNewMsgDBView->mIsXFVirtual = mIsXFVirtual; + aNewMsgDBView->mShowSizeInLines = mShowSizeInLines; + aNewMsgDBView->mDeleteModel = mDeleteModel; + aNewMsgDBView->m_flags = m_flags; + aNewMsgDBView->m_levels = m_levels; + aNewMsgDBView->m_keys = m_keys; + + aNewMsgDBView->m_customColumnHandlerIDs = m_customColumnHandlerIDs; + aNewMsgDBView->m_customColumnHandlers.AppendObjects(m_customColumnHandlers); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::GetSearchSession(nsIMsgSearchSession* *aSession) +{ + NS_ASSERTION(false, "should be overriden by child class"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDBView::SetSearchSession(nsIMsgSearchSession *aSession) +{ + NS_ASSERTION(false, "should be overriden by child class"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgDBView::GetSupportsThreading(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::FindIndexFromKey(nsMsgKey aMsgKey, bool aExpand, nsMsgViewIndex *aIndex) +{ + NS_ENSURE_ARG_POINTER(aIndex); + + *aIndex = FindKey(aMsgKey, aExpand); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgDBView::FindIndexOfMsgHdr(nsIMsgDBHdr *aMsgHdr, bool aExpand, nsMsgViewIndex *aIndex) +{ + NS_ENSURE_ARG(aMsgHdr); + NS_ENSURE_ARG_POINTER(aIndex); + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(aMsgHdr); + if (threadIndex != nsMsgViewIndex_None) + { + if (aExpand && (m_flags[threadIndex] & nsMsgMessageFlags::Elided)) + ExpandByIndex(threadIndex, nullptr); + *aIndex = FindHdr(aMsgHdr, threadIndex); + } + else + *aIndex = nsMsgViewIndex_None; + } + else + *aIndex = FindHdr(aMsgHdr); + + return NS_OK; +} + +static void getDateFormatPref( nsIPrefBranch* _prefBranch, const char* _prefLocalName, nsDateFormatSelector& _format ) +{ + // read + int32_t nFormatSetting( 0 ); + nsresult result = _prefBranch->GetIntPref( _prefLocalName, &nFormatSetting ); + if ( NS_SUCCEEDED( result ) ) + { + // translate + nsDateFormatSelector res( nFormatSetting ); + // transfer if valid + if ( ( res >= kDateFormatNone ) && ( res <= kDateFormatWeekday ) ) + _format = res; + } +} + +nsresult nsMsgDBView::InitDisplayFormats() +{ + m_dateFormatDefault = kDateFormatShort; + m_dateFormatThisWeek = kDateFormatShort; + m_dateFormatToday = kDateFormatNone; + + nsresult rv = NS_OK; + nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIPrefBranch> dateFormatPrefs; + rv = prefs->GetBranch("mail.ui.display.dateformat.", getter_AddRefs(dateFormatPrefs)); + NS_ENSURE_SUCCESS(rv,rv); + + getDateFormatPref( dateFormatPrefs, "default", m_dateFormatDefault ); + getDateFormatPref( dateFormatPrefs, "thisweek", m_dateFormatThisWeek ); + getDateFormatPref( dateFormatPrefs, "today", m_dateFormatToday ); + return rv; +} + +void nsMsgDBView::SetMRUTimeForFolder(nsIMsgFolder *folder) +{ + uint32_t seconds; + PRTime2Seconds(PR_Now(), &seconds); + nsAutoCString nowStr; + nowStr.AppendInt(seconds); + folder->SetStringProperty(MRU_TIME_PROPERTY, nowStr); +} + + +NS_IMPL_ISUPPORTS(nsMsgDBView::nsMsgViewHdrEnumerator, nsISimpleEnumerator) + +nsMsgDBView::nsMsgViewHdrEnumerator::nsMsgViewHdrEnumerator(nsMsgDBView *view) +{ + // we need to clone the view because the caller may clear the + // current view immediately. It also makes it easier to expand all + // if we're working on a copy. + nsCOMPtr<nsIMsgDBView> clonedView; + view->CloneDBView(nullptr, nullptr, nullptr, getter_AddRefs(clonedView)); + m_view = static_cast<nsMsgDBView*>(clonedView.get()); + // make sure we enumerate over collapsed threads by expanding all. + m_view->ExpandAll(); + m_curHdrIndex = 0; +} + +nsMsgDBView::nsMsgViewHdrEnumerator::~nsMsgViewHdrEnumerator() +{ + if (m_view) + m_view->Close(); +} + +NS_IMETHODIMP nsMsgDBView::nsMsgViewHdrEnumerator::GetNext(nsISupports **aItem) +{ + NS_ENSURE_ARG_POINTER(aItem); + + if (m_curHdrIndex >= m_view->GetSize()) + return NS_ERROR_FAILURE; + + // Ignore dummy header. We won't have empty groups, so + // we know the view index is good. + if (m_view->m_flags[m_curHdrIndex] & MSG_VIEW_FLAG_DUMMY) + ++m_curHdrIndex; + + nsCOMPtr<nsIMsgDBHdr> nextHdr; + + nsresult rv = m_view->GetMsgHdrForViewIndex(m_curHdrIndex++, getter_AddRefs(nextHdr)); + NS_IF_ADDREF(*aItem = nextHdr); + return rv; +} + +NS_IMETHODIMP nsMsgDBView::nsMsgViewHdrEnumerator::HasMoreElements(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = m_curHdrIndex < m_view->GetSize(); + return NS_OK; +} + +nsresult nsMsgDBView::GetViewEnumerator(nsISimpleEnumerator **enumerator) +{ + NS_IF_ADDREF(*enumerator = new nsMsgViewHdrEnumerator(this)); + return (*enumerator) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult nsMsgDBView::GetDBForHeader(nsIMsgDBHdr *msgHdr, nsIMsgDatabase **db) +{ + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + return folder->GetMsgDatabase(db); +} + +/** + * Determine whether junk commands should be enabled on this view. + * Junk commands are always enabled for mail. For nntp and rss, they + * may be selectively enabled using an inherited folder property. + * + * @param aViewIndex view index of the message to check + * @return true if junk controls should be enabled + */ +bool nsMsgDBView::JunkControlsEnabled(nsMsgViewIndex aViewIndex) +{ + // For normal mail, junk commands are always enabled. + if (!(mIsNews || mIsRss || mIsXFVirtual)) + return true; + + // we need to check per message or folder + nsCOMPtr <nsIMsgFolder> folder = m_folder; + if (!folder && IsValidIndex(aViewIndex)) + GetFolderForViewIndex(aViewIndex, getter_AddRefs(folder)); + if (folder) + { + // Check if this is a mail message in search folders. + if (mIsXFVirtual) + { + nsCOMPtr <nsIMsgIncomingServer> server; + folder->GetServer(getter_AddRefs(server)); + nsAutoCString type; + if (server) + server->GetType(type); + if (!(MsgLowerCaseEqualsLiteral(type, "nntp") || MsgLowerCaseEqualsLiteral(type, "rss"))) + return true; + } + + // For rss and news, check the inherited folder property. + nsAutoCString junkEnableOverride; + folder->GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk", + junkEnableOverride); + if (junkEnableOverride.EqualsLiteral("true")) + return true; + } + + return false; +} diff --git a/mailnews/base/src/nsMsgDBView.h b/mailnews/base/src/nsMsgDBView.h new file mode 100644 index 0000000000..6dcbbea3b5 --- /dev/null +++ b/mailnews/base/src/nsMsgDBView.h @@ -0,0 +1,513 @@ +/* -*- 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/. */ + +#ifndef _nsMsgDBView_H_ +#define _nsMsgDBView_H_ + +#include "nsIMsgDBView.h" +#include "nsIMsgWindow.h" +#include "nsIMessenger.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "MailNewsTypes.h" +#include "nsIDBChangeListener.h" +#include "nsITreeView.h" +#include "nsITreeBoxObject.h" +#include "nsITreeSelection.h" +#include "nsIMsgFolder.h" +#include "nsIMsgThread.h" +#include "nsIDateTimeFormat.h" +#include "nsIDOMElement.h" +#include "nsIAtom.h" +#include "nsIImapIncomingServer.h" +#include "nsIWeakReference.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIStringBundle.h" +#include "nsMsgTagService.h" +#include "nsCOMArray.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsIMsgCustomColumnHandler.h" +#include "nsAutoPtr.h" +#include "nsIWeakReferenceUtils.h" +#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties" + +typedef AutoTArray<nsMsgViewIndex, 1> nsMsgViewIndexArray; +static_assert(nsMsgViewIndex(nsMsgViewIndexArray::NoIndex) == + nsMsgViewIndex_None, "These need to be the same value."); + +enum eFieldType { + kCollationKey, + kU32 +}; + +// this is used in an nsTArray<> to keep track of a multi-column sort +class MsgViewSortColumnInfo +{ +public: + MsgViewSortColumnInfo(const MsgViewSortColumnInfo &other); + MsgViewSortColumnInfo() {} + bool operator == (const MsgViewSortColumnInfo &other) const; + nsMsgViewSortTypeValue mSortType; + nsMsgViewSortOrderValue mSortOrder; + // if mSortType == byCustom, info about the custom column sort + nsString mCustomColumnName; + nsCOMPtr <nsIMsgCustomColumnHandler> mColHandler; +} ; + +// reserve the top 8 bits in the msg flags for the view-only flags. +#define MSG_VIEW_FLAGS 0xEE000000 +#define MSG_VIEW_FLAG_HASCHILDREN 0x40000000 +#define MSG_VIEW_FLAG_DUMMY 0x20000000 +#define MSG_VIEW_FLAG_ISTHREAD 0x8000000 +#define MSG_VIEW_FLAG_OUTGOING 0x2000000 +#define MSG_VIEW_FLAG_INCOMING 0x1000000 + +/* There currently only 5 labels defined */ +#define PREF_LABELS_MAX 5 +#define PREF_LABELS_DESCRIPTION "mailnews.labels.description." +#define PREF_LABELS_COLOR "mailnews.labels.color." + +#define LABEL_COLOR_STRING " lc-" +#define LABEL_COLOR_WHITE_STRING "#FFFFFF" + +struct IdUint32 +{ + nsMsgKey id; + uint32_t bits; + uint32_t dword; + nsIMsgFolder* folder; +}; + +struct IdKey : public IdUint32 +{ + // actually a variable length array, whose actual size is determined + // when the struct is allocated. + uint8_t key[1]; +}; + +struct IdKeyPtr : public IdUint32 +{ + uint8_t *key; +}; + +// This is an abstract implementation class. +// The actual view objects will be instances of sub-classes of this class +class nsMsgDBView : public nsIMsgDBView, public nsIDBChangeListener, + public nsITreeView, + public nsIJunkMailClassificationListener +{ +public: + nsMsgDBView(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGDBVIEW + NS_DECL_NSIDBCHANGELISTENER + NS_DECL_NSITREEVIEW + NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER + + nsMsgViewIndex GetInsertIndexHelper(nsIMsgDBHdr *msgHdr, nsTArray<nsMsgKey> &keys, + nsCOMArray<nsIMsgFolder> *folders, + nsMsgViewSortOrderValue sortOrder, + nsMsgViewSortTypeValue sortType); + int32_t SecondarySort(nsMsgKey key1, nsISupports *folder1, nsMsgKey key2, nsISupports *folder2, + class viewSortInfo *comparisonContext); + +protected: + virtual ~nsMsgDBView(); + + static nsrefcnt gInstanceCount; + + static char16_t* kHighestPriorityString; + static char16_t* kHighPriorityString; + static char16_t* kLowestPriorityString; + static char16_t* kLowPriorityString; + static char16_t* kNormalPriorityString; + + static nsIAtom* kJunkMsgAtom; + static nsIAtom* kNotJunkMsgAtom; + + static char16_t* kReadString; + static char16_t* kRepliedString; + static char16_t* kForwardedString; + static char16_t* kNewString; + + nsCOMPtr<nsITreeBoxObject> mTree; + nsCOMPtr<nsITreeSelection> mTreeSelection; + uint32_t mNumSelectedRows; // we cache this to determine when to push command status notifications. + bool mSuppressMsgDisplay; // set when the message pane is collapsed + bool mSuppressCommandUpdating; + bool mRemovingRow; // set when we're telling the outline a row is being removed. used to suppress msg loading. + // during delete/move operations. + bool mCommandsNeedDisablingBecauseOfSelection; + bool mSuppressChangeNotification; + bool mGoForwardEnabled; + bool mGoBackEnabled; + + virtual const char * GetViewName(void) {return "MsgDBView"; } + nsresult FetchAuthor(nsIMsgDBHdr * aHdr, nsAString &aAuthorString); + nsresult FetchRecipients(nsIMsgDBHdr * aHdr, nsAString &aRecipientsString); + nsresult FetchSubject(nsIMsgDBHdr * aMsgHdr, uint32_t aFlags, nsAString &aValue); + nsresult FetchDate(nsIMsgDBHdr * aHdr, nsAString & aDateString, bool rcvDate = false); + nsresult FetchStatus(uint32_t aFlags, nsAString &aStatusString); + nsresult FetchSize(nsIMsgDBHdr * aHdr, nsAString & aSizeString); + nsresult FetchPriority(nsIMsgDBHdr *aHdr, nsAString & aPriorityString); + nsresult FetchLabel(nsIMsgDBHdr *aHdr, nsAString & aLabelString); + nsresult FetchTags(nsIMsgDBHdr *aHdr, nsAString & aTagString); + nsresult FetchKeywords(nsIMsgDBHdr *aHdr, nsACString & keywordString); + nsresult FetchRowKeywords(nsMsgViewIndex aRow, nsIMsgDBHdr *aHdr, + nsACString & keywordString); + nsresult FetchAccount(nsIMsgDBHdr * aHdr, nsAString& aAccount); + bool IsOutgoingMsg(nsIMsgDBHdr * aHdr); + nsresult CycleThreadedColumn(nsIDOMElement * aElement); + + // The default enumerator is over the db, but things like + // quick search views will enumerate just the displayed messages. + virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator); + // this is a message enumerator that enumerates based on the view contents + virtual nsresult GetViewEnumerator(nsISimpleEnumerator **enumerator); + + // Save and Restore Selection are a pair of routines you should + // use when performing an operation which is going to change the view + // and you want to remember the selection. (i.e. for sorting). + // Call SaveAndClearSelection and we'll give you an array of msg keys for + // the current selection. We also freeze and clear the selection. + // When you are done changing the view, + // call RestoreSelection passing in the same array + // and we'll restore the selection AND unfreeze selection in the UI. + nsresult SaveAndClearSelection(nsMsgKey *aCurrentMsgKey, nsTArray<nsMsgKey> &aMsgKeyArray); + nsresult RestoreSelection(nsMsgKey aCurrentmsgKey, nsTArray<nsMsgKey> &aMsgKeyArray); + + // this is not safe to use when you have a selection + // RowCountChanged() will call AdjustSelection() + // it should be called after SaveAndClearSelection() and before + // RestoreSelection() + nsresult AdjustRowCount(int32_t rowCountBeforeSort, int32_t rowCountAfterSort); + + nsresult GetSelectedIndices(nsMsgViewIndexArray& selection); + nsresult GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder *folder, nsACString &aURI); +// routines used in building up view + virtual bool WantsThisThread(nsIMsgThread * thread); + virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex = nullptr); + bool GetShowingIgnored() {return (m_viewFlags & nsMsgViewFlagsType::kShowIgnored) != 0;} + bool OperateOnMsgsInCollapsedThreads(); + + virtual nsresult OnNewHeader(nsIMsgDBHdr *aNewHdr, nsMsgKey parentKey, bool ensureListed); + virtual nsMsgViewIndex GetInsertIndex(nsIMsgDBHdr *msgHdr); + nsMsgViewIndex GetIndexForThread(nsIMsgDBHdr *hdr); + nsMsgViewIndex GetThreadRootIndex(nsIMsgDBHdr *msgHdr); + virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr); + // given a view index, return the index of the top-level msg in the thread. + nsMsgViewIndex GetThreadIndex(nsMsgViewIndex msgIndex); + + virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr, + nsMsgKey msgKey, uint32_t flags, uint32_t level); + virtual void SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, + nsMsgKey msgKey, uint32_t flags, uint32_t level); + virtual bool InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows); + virtual void RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows); + nsresult ToggleExpansion(nsMsgViewIndex index, uint32_t *numChanged); + nsresult ExpandByIndex(nsMsgViewIndex index, uint32_t *pNumExpanded); + nsresult CollapseByIndex(nsMsgViewIndex index, uint32_t *pNumCollapsed); + nsresult ExpandAll(); + nsresult CollapseAll(); + nsresult ExpandAndSelectThread(); + + // helper routines for thread expanding and collapsing. + nsresult GetThreadCount(nsMsgViewIndex viewIndex, uint32_t *pThreadCount); + /** + * Retrieve the view index of the first displayed message in the given thread. + * @param threadHdr The thread you care about. + * @param allowDummy Should dummy headers be returned when the non-dummy + * header is available? If the root node of the thread is a dummy header + * and you pass false, then we will return the first child of the thread + * unless the thread is elided, in which case we will return the root. + * If you pass true, we will always return the root. + * @return the view index of the first message in the thread, if any. + */ + nsMsgViewIndex GetIndexOfFirstDisplayedKeyInThread(nsIMsgThread *threadHdr, + bool allowDummy=false); + virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result); + virtual nsMsgViewIndex ThreadIndexOfMsg(nsMsgKey msgKey, + nsMsgViewIndex msgIndex = nsMsgViewIndex_None, + int32_t *pThreadCount = nullptr, + uint32_t *pFlags = nullptr); + nsMsgViewIndex ThreadIndexOfMsgHdr(nsIMsgDBHdr *msgHdr, + nsMsgViewIndex msgIndex = nsMsgViewIndex_None, + int32_t *pThreadCount = nullptr, + uint32_t *pFlags = nullptr); + nsMsgKey GetKeyOfFirstMsgInThread(nsMsgKey key); + int32_t CountExpandedThread(nsMsgViewIndex index); + virtual nsresult ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta); + void ReverseSort(); + void ReverseThreads(); + nsresult SaveSortInfo(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder); + nsresult RestoreSortInfo(); + nsresult PersistFolderInfo(nsIDBFolderInfo **dbFolderInfo); + void SetMRUTimeForFolder(nsIMsgFolder *folder); + + nsMsgKey GetAt(nsMsgViewIndex index) + {return m_keys.SafeElementAt(index, nsMsgKey_None);} + nsMsgViewIndex FindViewIndex(nsMsgKey key) + {return FindKey(key, false);} + /** + * Find the message header if it is visible in this view. (Messages in + * threads/groups that are elided will not be + * @param msgHdr Message header to look for. + * @param startIndex The index to start looking from. + * @param allowDummy Are dummy headers acceptable? If yes, then for a group + * with a dummy header, we return the root of the thread (the dummy + * header), otherwise we return the actual "content" header for the + * message. + * @return The view index of the header found, if any. + */ + virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex = 0, + bool allowDummy=false); + virtual nsMsgViewIndex FindKey(nsMsgKey key, bool expand); + virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db); + virtual nsCOMArray<nsIMsgFolder>* GetFolders(); + virtual nsresult GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder); + + virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex viewIndex, uint32_t *pNumListed); + nsresult ListUnreadIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed); + nsMsgViewIndex FindParentInThread(nsMsgKey parentKey, nsMsgViewIndex startOfThreadViewIndex); + virtual nsresult ListIdsInThreadOrder(nsIMsgThread *threadHdr, + nsMsgKey parentKey, uint32_t level, + nsMsgViewIndex *viewIndex, + uint32_t *pNumListed); + uint32_t GetSize(void) {return(m_keys.Length());} + + // notification api's + void NoteStartChange(nsMsgViewIndex firstlineChanged, int32_t numChanged, + nsMsgViewNotificationCodeValue changeType); + void NoteEndChange(nsMsgViewIndex firstlineChanged, int32_t numChanged, + nsMsgViewNotificationCodeValue changeType); + + // for commands + virtual nsresult ApplyCommandToIndices(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, + int32_t numIndices); + virtual nsresult ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, + int32_t numIndices, nsIMsgFolder *destFolder); + virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder); + virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage); + nsresult GetHeadersFromSelection(uint32_t *indices, uint32_t numIndices, nsIMutableArray *messageArray); + virtual nsresult ListCollapsedChildren(nsMsgViewIndex viewIndex, + nsIMutableArray *messageArray); + + nsresult SetMsgHdrJunkStatus(nsIJunkMailPlugin *aJunkPlugin, + nsIMsgDBHdr *aMsgHdr, + nsMsgJunkStatus aNewClassification); + nsresult ToggleReadByIndex(nsMsgViewIndex index); + nsresult SetReadByIndex(nsMsgViewIndex index, bool read); + nsresult SetThreadOfMsgReadByIndex(nsMsgViewIndex index, nsTArray<nsMsgKey> &keysMarkedRead, bool read); + nsresult SetFlaggedByIndex(nsMsgViewIndex index, bool mark); + nsresult SetLabelByIndex(nsMsgViewIndex index, nsMsgLabelValue label); + nsresult OrExtraFlag(nsMsgViewIndex index, uint32_t orflag); + nsresult AndExtraFlag(nsMsgViewIndex index, uint32_t andflag); + nsresult SetExtraFlag(nsMsgViewIndex index, uint32_t extraflag); + virtual nsresult RemoveByIndex(nsMsgViewIndex index); + virtual void OnExtraFlagChanged(nsMsgViewIndex /*index*/, uint32_t /*extraFlag*/) {} + virtual void OnHeaderAddedOrDeleted() {} + nsresult ToggleWatched( nsMsgViewIndex* indices, int32_t numIndices); + nsresult SetThreadWatched(nsIMsgThread *thread, nsMsgViewIndex index, bool watched); + nsresult SetThreadIgnored(nsIMsgThread *thread, nsMsgViewIndex threadIndex, bool ignored); + nsresult SetSubthreadKilled(nsIMsgDBHdr *header, nsMsgViewIndex msgIndex, bool ignored); + nsresult DownloadForOffline(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices); + nsresult DownloadFlaggedForOffline(nsIMsgWindow *window); + nsMsgViewIndex GetThreadFromMsgIndex(nsMsgViewIndex index, nsIMsgThread **threadHdr); + /// Should junk commands be enabled for the current message in the view? + bool JunkControlsEnabled(nsMsgViewIndex aViewIndex); + + // for sorting + nsresult GetFieldTypeAndLenForSort(nsMsgViewSortTypeValue sortType, + uint16_t *pMaxLen, + eFieldType *pFieldType, + nsIMsgCustomColumnHandler* colHandler = nullptr); + nsresult GetCollationKey(nsIMsgDBHdr *msgHdr, + nsMsgViewSortTypeValue sortType, + uint8_t **result, + uint32_t *len, + nsIMsgCustomColumnHandler* colHandler = nullptr); + nsresult GetLongField(nsIMsgDBHdr *msgHdr, + nsMsgViewSortTypeValue sortType, + uint32_t *result, + nsIMsgCustomColumnHandler* colHandler = nullptr); + + static int FnSortIdKey(const void *pItem1, const void *pItem2, void *privateData); + static int FnSortIdKeyPtr(const void *pItem1, const void *pItem2, void *privateData); + static int FnSortIdUint32(const void *pItem1, const void *pItem2, void *privateData); + + nsresult GetStatusSortValue(nsIMsgDBHdr *msgHdr, uint32_t *result); + nsresult GetLocationCollationKey(nsIMsgDBHdr *msgHdr, uint8_t **result, uint32_t *len); + void PushSort(const MsgViewSortColumnInfo &newSort); + nsresult EncodeColumnSort(nsString &columnSortString); + nsresult DecodeColumnSort(nsString &columnSortString); + // for view navigation + nsresult NavigateFromPos(nsMsgNavigationTypeValue motion, nsMsgViewIndex startIndex, nsMsgKey *pResultKey, + nsMsgViewIndex *pResultIndex, nsMsgViewIndex *pThreadIndex, bool wrap); + nsresult FindNextFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex); + nsresult FindFirstNew(nsMsgViewIndex *pResultIndex); + nsresult FindPrevUnread(nsMsgKey startKey, nsMsgKey *pResultKey, nsMsgKey *resultThreadId); + nsresult FindFirstFlagged(nsMsgViewIndex *pResultIndex); + nsresult FindPrevFlagged(nsMsgViewIndex startIndex, nsMsgViewIndex *pResultIndex); + nsresult MarkThreadOfMsgRead(nsMsgKey msgId, nsMsgViewIndex msgIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead); + nsresult MarkThreadRead(nsIMsgThread *threadHdr, nsMsgViewIndex threadIndex, nsTArray<nsMsgKey> &idsMarkedRead, bool bRead); + bool IsValidIndex(nsMsgViewIndex index); + nsresult ToggleIgnored(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState); + nsresult ToggleMessageKilled(nsMsgViewIndex * indices, int32_t numIndices, nsMsgViewIndex *resultIndex, bool *resultToggleState); + bool OfflineMsgSelected(nsMsgViewIndex * indices, int32_t numIndices); + bool NonDummyMsgSelected(nsMsgViewIndex * indices, int32_t numIndices); + char16_t * GetString(const char16_t *aStringName); + nsresult GetPrefLocalizedString(const char *aPrefName, nsString& aResult); + nsresult GetLabelPrefStringAndAtom(const char *aPrefName, nsString& aColor, nsIAtom** aColorAtom); + nsresult AppendKeywordProperties(const nsACString& keywords, nsAString& properties, bool addSelectedTextProperty); + nsresult InitLabelStrings(void); + nsresult CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater); + void InitializeAtomsAndLiterals(); + virtual int32_t FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex); + nsresult GetImapDeleteModel(nsIMsgFolder *folder); + nsresult UpdateDisplayMessage(nsMsgViewIndex viewPosition); + nsresult GetDBForHeader(nsIMsgDBHdr *msgHdr, nsIMsgDatabase **db); + + bool AdjustReadFlag(nsIMsgDBHdr *msgHdr, uint32_t *msgFlags); + void FreeAll(nsTArray<void*> *ptrs); + void ClearHdrCache(); + nsTArray<nsMsgKey> m_keys; + nsTArray<uint32_t> m_flags; + nsTArray<uint8_t> m_levels; + nsMsgImapDeleteModel mDeleteModel; + + // cache the most recently asked for header and corresponding msgKey. + nsCOMPtr <nsIMsgDBHdr> m_cachedHdr; + nsMsgKey m_cachedMsgKey; + + // we need to store the message key for the message we are currenty displaying to ensure we + // don't try to redisplay the same message just because the selection changed (i.e. after a sort) + nsMsgKey m_currentlyDisplayedMsgKey; + nsCString m_currentlyDisplayedMsgUri; + nsMsgViewIndex m_currentlyDisplayedViewIndex; + // if we're deleting messages, we want to hold off loading messages on selection changed until the delete is done + // and we want to batch notifications. + bool m_deletingRows; + // for certain special folders + // and decendents of those folders + // (like the "Sent" folder, "Sent/Old Sent") + // the Sender column really shows recipients. + + // Server types for this view's folder + bool mIsNews; // we have special icons for news + bool mIsRss; // rss affects enabling of junk commands + bool mIsXFVirtual; // a virtual folder with multiple folders + + bool mShowSizeInLines; // for news we show lines instead of size when true + bool mSortThreadsByRoot; // as opposed to by the newest message + bool m_sortValid; + bool m_checkedCustomColumns; + bool mSelectionSummarized; + // we asked the front end to summarize the selection and it did not. + bool mSummarizeFailed; + uint8_t m_saveRestoreSelectionDepth; + + nsCOMPtr <nsIMsgDatabase> m_db; + nsCOMPtr <nsIMsgFolder> m_folder; + nsCOMPtr <nsIMsgFolder> m_viewFolder; // for virtual folders, the VF db. + nsString mMessageType; + nsTArray <MsgViewSortColumnInfo> m_sortColumns; + nsMsgViewSortTypeValue m_sortType; + nsMsgViewSortOrderValue m_sortOrder; + nsString m_curCustomColumn; + nsMsgViewSortTypeValue m_secondarySort; + nsMsgViewSortOrderValue m_secondarySortOrder; + nsString m_secondaryCustomColumn; + nsMsgViewFlagsTypeValue m_viewFlags; + + // I18N date formatter service which we'll want to cache locally. + nsCOMPtr<nsIDateTimeFormat> mDateFormatter; + nsCOMPtr<nsIMsgTagService> mTagService; + nsWeakPtr mMessengerWeak; + nsWeakPtr mMsgWindowWeak; + nsCOMPtr<nsIMsgDBViewCommandUpdater> mCommandUpdater; // we push command update notifications to the UI from this. + nsCOMPtr<nsIStringBundle> mMessengerStringBundle; + + // used for the preference labels + nsString mLabelPrefDescriptions[PREF_LABELS_MAX]; + nsString mLabelPrefColors[PREF_LABELS_MAX]; + // used to cache the atoms created for each color to be displayed + static nsIAtom* mLabelPrefColorAtoms[PREF_LABELS_MAX]; + + // used to determine when to start and end + // junk plugin batches + uint32_t mNumMessagesRemainingInBatch; + + // these are the headers of the messages in the current + // batch/series of batches of messages manually marked + // as junk + nsCOMPtr<nsIMutableArray> mJunkHdrs; + + nsTArray<uint32_t> mIndicesToNoteChange; + + nsTHashtable<nsCStringHashKey> mEmails; + + // the saved search views keep track of the XX most recently deleted msg ids, so that if the + // delete is undone, we can add the msg back to the search results, even if it no longer + // matches the search criteria (e.g., a saved search over unread messages). + // We use mRecentlyDeletedArrayIndex to treat the array as a list of the XX + // most recently deleted msgs. + nsTArray<nsCString> mRecentlyDeletedMsgIds; + uint32_t mRecentlyDeletedArrayIndex; + void RememberDeletedMsgHdr(nsIMsgDBHdr *msgHdr); + bool WasHdrRecentlyDeleted(nsIMsgDBHdr *msgHdr); + + //these hold pointers (and IDs) for the nsIMsgCustomColumnHandler object that constitutes the custom column handler + nsCOMArray <nsIMsgCustomColumnHandler> m_customColumnHandlers; + nsTArray<nsString> m_customColumnHandlerIDs; + + nsIMsgCustomColumnHandler* GetColumnHandler(const char16_t*); + nsIMsgCustomColumnHandler* GetCurColumnHandler(); + bool CustomColumnsInSortAndNotRegistered(); + void EnsureCustomColumnsValid(); + +#ifdef DEBUG_David_Bienvenu +void InitEntryInfoForIndex(nsMsgViewIndex i, IdKeyPtr &EntryInfo); +void ValidateSort(); +#endif + +protected: + static nsresult InitDisplayFormats(); + +private: + static nsDateFormatSelector m_dateFormatDefault; + static nsDateFormatSelector m_dateFormatThisWeek; + static nsDateFormatSelector m_dateFormatToday; + bool ServerSupportsFilterAfterTheFact(); + + nsresult PerformActionsOnJunkMsgs(bool msgsAreJunk); + nsresult DetermineActionsForJunkChange(bool msgsAreJunk, + nsIMsgFolder *srcFolder, + bool &moveMessages, + bool &changeReadState, + nsIMsgFolder** targetFolder); + + class nsMsgViewHdrEnumerator final : public nsISimpleEnumerator + { + public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + + // nsMsgThreadEnumerator methods: + nsMsgViewHdrEnumerator(nsMsgDBView *view); + + RefPtr<nsMsgDBView> m_view; + nsMsgViewIndex m_curHdrIndex; + + private: + ~nsMsgViewHdrEnumerator(); + }; +}; + +#endif diff --git a/mailnews/base/src/nsMsgFolderCache.cpp b/mailnews/base/src/nsMsgFolderCache.cpp new file mode 100644 index 0000000000..9510a6e3d2 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCache.cpp @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 4; 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 "msgCore.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgFolderCacheElement.h" +#include "nsMsgFolderCache.h" +#include "nsMorkCID.h" +#include "nsIMdbFactoryFactory.h" +#include "nsMsgBaseCID.h" +#include "nsServiceManagerUtils.h" + +const char *kFoldersScope = "ns:msg:db:row:scope:folders:all"; // scope for all folders table +const char *kFoldersTableKind = "ns:msg:db:table:kind:folders"; + +nsMsgFolderCache::nsMsgFolderCache() +{ + m_mdbEnv = nullptr; + m_mdbStore = nullptr; + m_mdbAllFoldersTable = nullptr; +} + +// should this, could this be an nsCOMPtr ? +static nsIMdbFactory *gMDBFactory = nullptr; + +nsMsgFolderCache::~nsMsgFolderCache() +{ + m_cacheElements.Clear(); // make sure the folder cache elements are released before we release our m_mdb objects... + if (m_mdbAllFoldersTable) + m_mdbAllFoldersTable->Release(); + if (m_mdbStore) + m_mdbStore->Release(); + NS_IF_RELEASE(gMDBFactory); + if (m_mdbEnv) + m_mdbEnv->Release(); +} + + +NS_IMPL_ISUPPORTS(nsMsgFolderCache, nsIMsgFolderCache) + +void nsMsgFolderCache::GetMDBFactory(nsIMdbFactory ** aMdbFactory) +{ + if (!mMdbFactory) + { + nsresult rv; + nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mdbFactoryService) + rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory)); + } + NS_IF_ADDREF(*aMdbFactory = mMdbFactory); +} + +// initialize the various tokens and tables in our db's env +nsresult nsMsgFolderCache::InitMDBInfo() +{ + nsresult err = NS_OK; + if (GetStore()) + { + err = GetStore()->StringToToken(GetEnv(), kFoldersScope, &m_folderRowScopeToken); + if (NS_SUCCEEDED(err)) + { + err = GetStore()->StringToToken(GetEnv(), kFoldersTableKind, &m_folderTableKindToken); + if (NS_SUCCEEDED(err)) + { + // The table of all message hdrs will have table id 1. + m_allFoldersTableOID.mOid_Scope = m_folderRowScopeToken; + m_allFoldersTableOID.mOid_Id = 1; + } + } + } + return err; +} + +// set up empty tables, dbFolderInfo, etc. +nsresult nsMsgFolderCache::InitNewDB() +{ + nsresult err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) + { + nsIMdbStore *store = GetStore(); + // create the unique table for the dbFolderInfo. + // TODO: this error assignment is suspicious and never checked. + (void) store->NewTable(GetEnv(), m_folderRowScopeToken, m_folderTableKindToken, + false, nullptr, &m_mdbAllFoldersTable); + } + return err; +} + +nsresult nsMsgFolderCache::InitExistingDB() +{ + nsresult err = InitMDBInfo(); + if (NS_FAILED(err)) + return err; + + err = GetStore()->GetTable(GetEnv(), &m_allFoldersTableOID, &m_mdbAllFoldersTable); + if (NS_SUCCEEDED(err) && m_mdbAllFoldersTable) + { + nsIMdbTableRowCursor* rowCursor = nullptr; + err = m_mdbAllFoldersTable->GetTableRowCursor(GetEnv(), -1, &rowCursor); + if (NS_SUCCEEDED(err) && rowCursor) + { + // iterate over the table rows and create nsMsgFolderCacheElements for each. + while (true) + { + nsresult rv; + nsIMdbRow* hdrRow; + mdb_pos rowPos; + + rv = rowCursor->NextRow(GetEnv(), &hdrRow, &rowPos); + if (NS_FAILED(rv) || !hdrRow) + break; + + rv = AddCacheElement(EmptyCString(), hdrRow, nullptr); + hdrRow->Release(); + if (NS_FAILED(rv)) + return rv; + } + rowCursor->Release(); + } + } + else + err = NS_ERROR_FAILURE; + + return err; +} + +nsresult nsMsgFolderCache::OpenMDB(const nsACString& dbName, bool exists) +{ + nsresult ret=NS_OK; + nsCOMPtr<nsIMdbFactory> mdbFactory; + GetMDBFactory(getter_AddRefs(mdbFactory)); + if (mdbFactory) + { + ret = mdbFactory->MakeEnv(nullptr, &m_mdbEnv); + if (NS_SUCCEEDED(ret)) + { + nsIMdbThumb *thumb = nullptr; + nsIMdbHeap* dbHeap = nullptr; + + if (m_mdbEnv) + m_mdbEnv->SetAutoClear(true); + if (exists) + { + mdbOpenPolicy inOpenPolicy; + mdb_bool canOpen; + mdbYarn outFormatVersion; + + nsIMdbFile* oldFile = nullptr; + ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, nsCString(dbName).get(), + mdbBool_kFalse, // not readonly, we want modifiable + &oldFile); + if ( oldFile ) + { + if (NS_SUCCEEDED(ret)) + { + ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // file to investigate + &canOpen, &outFormatVersion); + if (NS_SUCCEEDED(ret) && canOpen) + { + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->OpenFileStore(m_mdbEnv, NULL, oldFile, &inOpenPolicy, + &thumb); + } + else + ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + NS_RELEASE(oldFile); // always release our file ref, store has own + } + } + if (NS_SUCCEEDED(ret) && thumb) + { + mdb_count outTotal; // total somethings to do in operation + mdb_count outCurrent; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken; // is operation irreparably dead and broken? + do + { + ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken); + if (NS_FAILED(ret)) + { + outDone = true; + break; + } + } + while (NS_SUCCEEDED(ret) && !outBroken && !outDone); + // m_mdbEnv->ClearErrors(); // ### temporary... + if (NS_SUCCEEDED(ret) && outDone) + { + ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore); + if (NS_SUCCEEDED(ret) && m_mdbStore) + ret = InitExistingDB(); + } +#ifdef DEBUG_bienvenu1 + DumpContents(); +#endif + } + else // ### need error code saying why open file store failed + { + nsIMdbFile* newFile = 0; + ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, nsCString(dbName).get(), &newFile); + if ( newFile ) + { + if (NS_SUCCEEDED(ret)) + { + mdbOpenPolicy inOpenPolicy; + + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap, + newFile, &inOpenPolicy, &m_mdbStore); + if (NS_SUCCEEDED(ret)) + ret = InitNewDB(); + } + NS_RELEASE(newFile); // always release our file ref, store has own + } + + } + NS_IF_RELEASE(thumb); + } + } + return ret; +} + +NS_IMETHODIMP nsMsgFolderCache::Init(nsIFile *aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + + bool exists; + aFile->Exists(&exists); + + nsAutoCString dbPath; + aFile->GetNativePath(dbPath); + // ### evil cast until MDB supports file streams. + nsresult rv = OpenMDB(dbPath, exists); + // if this fails and panacea.dat exists, try blowing away the db and recreating it + if (NS_FAILED(rv) && exists) + { + if (m_mdbStore) + m_mdbStore->Release(); + aFile->Remove(false); + rv = OpenMDB(dbPath, false); + } + return rv; +} + +NS_IMETHODIMP nsMsgFolderCache::GetCacheElement(const nsACString& pathKey, bool createIfMissing, + nsIMsgFolderCacheElement **result) +{ + NS_ENSURE_ARG_POINTER(result); + NS_ENSURE_TRUE(!pathKey.IsEmpty(), NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl; + m_cacheElements.Get(pathKey, getter_AddRefs(folderCacheEl)); + folderCacheEl.swap(*result); + + if (*result) + return NS_OK; + else if (createIfMissing) + { + nsIMdbRow* hdrRow; + + if (GetStore()) + { + nsresult err = GetStore()->NewRow(GetEnv(), m_folderRowScopeToken, // row scope for row ids + &hdrRow); + if (NS_SUCCEEDED(err) && hdrRow) + { + m_mdbAllFoldersTable->AddRow(GetEnv(), hdrRow); + nsresult ret = AddCacheElement(pathKey, hdrRow, result); + if (*result) + (*result)->SetStringProperty("key", pathKey); + hdrRow->Release(); + return ret; + } + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgFolderCache::RemoveElement(const nsACString& key) +{ + nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl; + m_cacheElements.Get(key, getter_AddRefs(folderCacheEl)); + if (!folderCacheEl) + return NS_ERROR_FAILURE; + nsMsgFolderCacheElement *element = static_cast<nsMsgFolderCacheElement *>(static_cast<nsISupports *>(folderCacheEl.get())); // why the double cast?? + m_mdbAllFoldersTable->CutRow(GetEnv(), element->m_mdbRow); + m_cacheElements.Remove(key); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCache::Clear() +{ + m_cacheElements.Clear(); + if (m_mdbAllFoldersTable) + m_mdbAllFoldersTable->CutAllRows(GetEnv()); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCache::Close() +{ + return Commit(true); +} + +NS_IMETHODIMP nsMsgFolderCache::Commit(bool compress) +{ + nsresult ret = NS_OK; + nsIMdbThumb *commitThumb = nullptr; + if (m_mdbStore) + { + if (compress) + ret = m_mdbStore->CompressCommit(GetEnv(), &commitThumb); + else + ret = m_mdbStore->LargeCommit(GetEnv(), &commitThumb); + } + + if (commitThumb) + { + mdb_count outTotal = 0; // total somethings to do in operation + mdb_count outCurrent = 0; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken = false; // is operation irreparably dead and broken? + while (!outDone && !outBroken && NS_SUCCEEDED(ret)) + ret = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken); + NS_IF_RELEASE(commitThumb); + } + // ### do something with error, but clear it now because mork errors out on commits. + if (GetEnv()) + GetEnv()->ClearErrors(); + return ret; +} + +nsresult nsMsgFolderCache::AddCacheElement(const nsACString& key, nsIMdbRow *row, nsIMsgFolderCacheElement **result) +{ + nsMsgFolderCacheElement *cacheElement = new nsMsgFolderCacheElement; + NS_ENSURE_TRUE(cacheElement, NS_ERROR_OUT_OF_MEMORY); + nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl(do_QueryInterface(cacheElement)); + + cacheElement->SetMDBRow(row); + cacheElement->SetOwningCache(this); + nsCString hashStrKey(key); + // if caller didn't pass in key, try to get it from row. + if (key.IsEmpty()) + folderCacheEl->GetStringProperty("key", hashStrKey); + folderCacheEl->SetKey(hashStrKey); + m_cacheElements.Put(hashStrKey, folderCacheEl); + if (result) + folderCacheEl.swap(*result); + return NS_OK; +} + +nsresult nsMsgFolderCache::RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, nsACString& resultStr) +{ + nsresult err = NS_OK; + nsIMdbCell *hdrCell; + if (hdrRow) // ### probably should be an error if hdrRow is NULL... + { + err = hdrRow->GetCell(GetEnv(), columnToken, &hdrCell); + if (NS_SUCCEEDED(err) && hdrCell) + { + struct mdbYarn yarn; + hdrCell->AliasYarn(GetEnv(), &yarn); + resultStr.Assign((const char *)yarn.mYarn_Buf, yarn.mYarn_Fill); + resultStr.SetLength(yarn.mYarn_Fill); // ensure the string is null terminated. + hdrCell->Release(); // always release ref + } + } + return err; +} diff --git a/mailnews/base/src/nsMsgFolderCache.h b/mailnews/base/src/nsMsgFolderCache.h new file mode 100644 index 0000000000..88536084b7 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCache.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsMsgFolderCache_H +#define nsMsgFolderCache_H + +#include "nsIMsgFolderCache.h" +#include "nsIMsgFolderCacheElement.h" +#include "nsInterfaceHashtable.h" +#include "nsCOMPtr.h" +#include "mdb.h" + +class nsMsgFolderCache : public nsIMsgFolderCache +{ + +public: + friend class nsMsgFolderCacheElement; + + nsMsgFolderCache(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFOLDERCACHE + +protected: + virtual ~nsMsgFolderCache(); + + void GetMDBFactory(nsIMdbFactory ** aMdbFactory); + nsresult AddCacheElement(const nsACString& key, nsIMdbRow *row, nsIMsgFolderCacheElement **result); + nsresult RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, nsACString& resultPtr); + nsresult InitMDBInfo(); + nsresult InitNewDB(); + nsresult InitExistingDB(); + nsresult OpenMDB(const nsACString& dbName, bool create); + nsIMdbEnv *GetEnv() {return m_mdbEnv;} + nsIMdbStore *GetStore() {return m_mdbStore;} + nsInterfaceHashtable<nsCStringHashKey, nsIMsgFolderCacheElement> m_cacheElements; + // mdb stuff + nsIMdbEnv *m_mdbEnv; // to be used in all the db calls. + nsIMdbStore *m_mdbStore; + nsIMdbTable *m_mdbAllFoldersTable; + mdb_token m_folderRowScopeToken; + mdb_token m_folderTableKindToken; + nsCOMPtr<nsIMdbFactory> mMdbFactory; + + struct mdbOid m_allFoldersTableOID; +}; + +#endif diff --git a/mailnews/base/src/nsMsgFolderCacheElement.cpp b/mailnews/base/src/nsMsgFolderCacheElement.cpp new file mode 100644 index 0000000000..e1197c7ed6 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCacheElement.cpp @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; 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 "msgCore.h" +#include "nsMsgFolderCacheElement.h" +#include "prmem.h" +#include "nsMsgUtils.h" + +nsMsgFolderCacheElement::nsMsgFolderCacheElement() +{ + m_mdbRow = nullptr; + m_owningCache = nullptr; +} + +nsMsgFolderCacheElement::~nsMsgFolderCacheElement() +{ + NS_IF_RELEASE(m_mdbRow); + // circular reference, don't do it. + // NS_IF_RELEASE(m_owningCache); +} + +NS_IMPL_ISUPPORTS(nsMsgFolderCacheElement, nsIMsgFolderCacheElement) + +NS_IMETHODIMP nsMsgFolderCacheElement::GetKey(nsACString& aFolderKey) +{ + aFolderKey = m_folderKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCacheElement::SetKey(const nsACString& aFolderKey) +{ + m_folderKey = aFolderKey; + return NS_OK; +} + +void nsMsgFolderCacheElement::SetOwningCache(nsMsgFolderCache *owningCache) +{ + m_owningCache = owningCache; + // circular reference, don't do it. + // if (owningCache) + // NS_ADDREF(owningCache); +} + +NS_IMETHODIMP nsMsgFolderCacheElement::GetStringProperty(const char *propertyName, nsACString& result) +{ + NS_ENSURE_ARG_POINTER(propertyName); + NS_ENSURE_TRUE(m_mdbRow && m_owningCache, NS_ERROR_FAILURE); + + mdb_token property_token; + nsresult ret = m_owningCache->GetStore()->StringToToken(m_owningCache->GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(ret)) + ret = m_owningCache->RowCellColumnToCharPtr(m_mdbRow, property_token, result); + return ret; +} + +NS_IMETHODIMP nsMsgFolderCacheElement::GetInt32Property(const char *propertyName, int32_t *aResult) +{ + NS_ENSURE_ARG_POINTER(propertyName); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE); + + nsCString resultStr; + GetStringProperty(propertyName, resultStr); + if (resultStr.IsEmpty()) + return NS_ERROR_FAILURE; + + // This must be an inverse function to nsCString.AppentInt(), + // which uses snprintf("%x") internally, so that the wrapped negative numbers + // are decoded properly. + if (PR_sscanf(resultStr.get(), "%x", aResult) != 1) + { + NS_WARNING("Unexpected failure to decode hex string."); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCacheElement::GetInt64Property(const char *propertyName, int64_t *aResult) +{ + NS_ENSURE_ARG_POINTER(propertyName); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE); + + nsCString resultStr; + GetStringProperty(propertyName, resultStr); + if (resultStr.IsEmpty()) + return NS_ERROR_FAILURE; + + // This must be an inverse function to nsCString.AppentInt(), + // which uses snprintf("%x") internally, so that the wrapped negative numbers + // are decoded properly. + if (PR_sscanf(resultStr.get(), "%llx", aResult) != 1) + { + NS_WARNING("Unexpected failure to decode hex string."); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCacheElement::SetStringProperty(const char *propertyName, const nsACString& propertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE); + nsresult rv = NS_OK; + mdb_token property_token; + + if (m_owningCache) + { + rv = m_owningCache->GetStore()->StringToToken(m_owningCache->GetEnv(), propertyName, &property_token); + if (NS_SUCCEEDED(rv)) + { + struct mdbYarn yarn; + + yarn.mYarn_Grow = NULL; + if (m_mdbRow) + { + nsCString propertyVal (propertyValue); + yarn.mYarn_Buf = (void *) propertyVal.get(); + yarn.mYarn_Size = strlen((const char *) yarn.mYarn_Buf) + 1; + yarn.mYarn_Fill = yarn.mYarn_Size - 1; + yarn.mYarn_Form = 0; // what to do with this? we're storing csid in the msg hdr... + rv = m_mdbRow->AddColumn(m_owningCache->GetEnv(), property_token, &yarn); + return rv; + } + } + } + return rv; +} + +NS_IMETHODIMP nsMsgFolderCacheElement::SetInt32Property(const char *propertyName, int32_t propertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE); + + // This also supports encoding negative numbers into hex + // by integer wrapping them (e.g. -1 -> "ffffffff"). + nsAutoCString propertyStr; + propertyStr.AppendInt(propertyValue, 16); + return SetStringProperty(propertyName, propertyStr); +} + +NS_IMETHODIMP nsMsgFolderCacheElement::SetInt64Property(const char *propertyName, int64_t propertyValue) +{ + NS_ENSURE_ARG_POINTER(propertyName); + NS_ENSURE_TRUE(m_mdbRow, NS_ERROR_FAILURE); + + // This also supports encoding negative numbers into hex + // by integer wrapping them (e.g. -1 -> "ffffffffffffffff"). + nsAutoCString propertyStr; + propertyStr.AppendInt(propertyValue, 16); + return SetStringProperty(propertyName, propertyStr); +} + +void nsMsgFolderCacheElement::SetMDBRow(nsIMdbRow *row) +{ + if (m_mdbRow) + NS_RELEASE(m_mdbRow); + NS_IF_ADDREF(m_mdbRow = row); +} diff --git a/mailnews/base/src/nsMsgFolderCacheElement.h b/mailnews/base/src/nsMsgFolderCacheElement.h new file mode 100644 index 0000000000..4abf0f08f9 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCacheElement.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsMsgFolderCacheElement_H +#define nsMsgFolderCacheElement_H + +#include "nsIMsgFolderCacheElement.h" +#include "nsMsgFolderCache.h" +#include "mdb.h" + +class nsMsgFolderCacheElement : public nsIMsgFolderCacheElement +{ +public: + nsMsgFolderCacheElement(); + friend class nsMsgFolderCache; + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFOLDERCACHEELEMENT + + void SetMDBRow(nsIMdbRow *row); + void SetOwningCache(nsMsgFolderCache *owningCache); +protected: + virtual ~nsMsgFolderCacheElement(); + + nsIMdbRow *m_mdbRow; + + nsMsgFolderCache *m_owningCache; // this will be ref-counted. Is this going to be a problem? + // I want to avoid circular references, but since this is + // scriptable, I think I have to ref-count it. + nsCString m_folderKey; +}; + +#endif diff --git a/mailnews/base/src/nsMsgFolderCompactor.cpp b/mailnews/base/src/nsMsgFolderCompactor.cpp new file mode 100644 index 0000000000..4b0dc3ad5c --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCompactor.cpp @@ -0,0 +1,1348 @@ +/* -*- 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 "msgCore.h" // precompiled header... +#include "nsCOMPtr.h" +#include "nsIMsgFolder.h" +#include "nsAutoPtr.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsIMsgHdr.h" +#include "nsIStreamListener.h" +#include "nsIMsgMessageService.h" +#include "nsMsgDBCID.h" +#include "nsMsgUtils.h" +#include "nsISeekableStream.h" +#include "nsIDBFolderInfo.h" +#include "nsIDocShell.h" +#include "nsIPrompt.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgImapMailFolder.h" +#include "nsMailHeaders.h" +#include "nsMsgI18N.h" +#include "prprf.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsIMsgDatabase.h" +#include "nsArrayUtils.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgStatusFeedback.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsIMsgPluggableStore.h" +#include "nsMsgFolderCompactor.h" +#include <algorithm> +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsPrintfCString.h" + + +////////////////////////////////////////////////////////////////////////////// +// nsFolderCompactState +////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsFolderCompactState, nsIMsgFolderCompactor, nsIRequestObserver, nsIStreamListener, nsICopyMessageStreamListener, nsIUrlListener) + +nsFolderCompactState::nsFolderCompactState() +{ + m_fileStream = nullptr; + m_size = 0; + m_curIndex = 0; + m_status = NS_OK; + m_compactAll = false; + m_compactOfflineAlso = false; + m_compactingOfflineFolders = false; + m_parsingFolder=false; + m_folderIndex = 0; + m_startOfMsg = true; + m_needStatusLine = false; + m_totalExpungedBytes = 0; + m_alreadyWarnedDiskSpace = false; +} + +nsFolderCompactState::~nsFolderCompactState() +{ + CloseOutputStream(); + if (NS_FAILED(m_status)) + { + CleanupTempFilesAfterError(); + // if for some reason we failed remove the temp folder and database + } +} + +void nsFolderCompactState::CloseOutputStream() +{ + if (m_fileStream) + { + m_fileStream->Close(); + m_fileStream = nullptr; + } + +} + +void nsFolderCompactState::CleanupTempFilesAfterError() +{ + CloseOutputStream(); + if (m_db) + m_db->ForceClosed(); + nsCOMPtr <nsIFile> summaryFile; + GetSummaryFileLocation(m_file, getter_AddRefs(summaryFile)); + m_file->Remove(false); + summaryFile->Remove(false); +} + +nsresult nsFolderCompactState::BuildMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri) +{ + uri.Append(baseURI); + uri.Append('#'); + uri.AppendInt(key); + + return NS_OK; +} + + +nsresult +nsFolderCompactState::InitDB(nsIMsgDatabase *db) +{ + nsCOMPtr<nsIMsgDatabase> mailDBFactory; + nsresult rv = db->ListAllKeys(m_keyArray); + NS_ENSURE_SUCCESS(rv, rv); + m_size = m_keyArray->m_keys.Length(); + + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgDBService->OpenMailDBFromFile(m_file, m_folder, true, false, + getter_AddRefs(m_db)); + + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + // if it's out of date then reopen with upgrade. + return msgDBService->OpenMailDBFromFile(m_file, + m_folder, true, true, + getter_AddRefs(m_db)); + return rv; +} + +NS_IMETHODIMP nsFolderCompactState::CompactFolders(nsIArray *aArrayOfFoldersToCompact, + nsIArray *aOfflineFolderArray, + nsIUrlListener *aUrlListener, + nsIMsgWindow *aMsgWindow) +{ + m_window = aMsgWindow; + m_listener = aUrlListener; + if (aArrayOfFoldersToCompact) + m_folderArray = aArrayOfFoldersToCompact; + else if (aOfflineFolderArray) + { + m_folderArray = aOfflineFolderArray; + m_compactingOfflineFolders = true; + aOfflineFolderArray = nullptr; + } + if (!m_folderArray) + return NS_OK; + + m_compactAll = true; + m_compactOfflineAlso = aOfflineFolderArray != nullptr; + if (m_compactOfflineAlso) + m_offlineFolderArray = aOfflineFolderArray; + + m_folderIndex = 0; + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgFolder> firstFolder = do_QueryElementAt(m_folderArray, + m_folderIndex, &rv); + + if (NS_SUCCEEDED(rv) && firstFolder) + Compact(firstFolder, m_compactingOfflineFolders, aUrlListener, + aMsgWindow); //start with first folder from here. + + return rv; +} + +NS_IMETHODIMP +nsFolderCompactState::Compact(nsIMsgFolder *folder, bool aOfflineStore, + nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(folder); + m_listener = aListener; + if (!m_compactingOfflineFolders && !aOfflineStore) + { + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder); + if (imapFolder) + return imapFolder->Expunge(this, aMsgWindow); + } + + m_window = aMsgWindow; + nsresult rv; + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIFile> path; + nsCString baseMessageURI; + + nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder, &rv); + if (NS_SUCCEEDED(rv) && localFolder) + { + rv=localFolder->GetDatabaseWOReparse(getter_AddRefs(db)); + if (NS_FAILED(rv) || !db) + { + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) + { + m_folder = folder; //will be used to compact + m_parsingFolder = true; + rv = localFolder->ParseFolder(m_window, this); + } + return rv; + } + else + { + bool valid; + rv = db->GetSummaryValid(&valid); + if (!valid) //we are probably parsing the folder because we selected it. + { + folder->NotifyCompactCompleted(); + if (m_compactAll) + return CompactNextFolder(); + else + return NS_OK; + } + } + } + else + { + rv = folder->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = folder->GetFilePath(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + do { + bool exists = false; + rv = path->Exists(&exists); + if (!exists) { + // No need to compact if the local file does not exist. + // Can happen e.g. on IMAP when the folder is not marked for offline use. + break; + } + + int64_t expunged = 0; + folder->GetExpungedBytes(&expunged); + if (expunged == 0) { + // No need to compact if nothing would be expunged. + break; + } + + int64_t diskSize; + rv = folder->GetSizeOnDisk(&diskSize); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t diskFree; + rv = path->GetDiskSpaceAvailable(&diskFree); + if (NS_FAILED(rv)) { + // If GetDiskSpaceAvailable() failed, better bail out fast. + if (rv != NS_ERROR_NOT_IMPLEMENTED) + return rv; + // Some platforms do not have GetDiskSpaceAvailable implemented. + // In that case skip the preventive free space analysis and let it + // fail in compact later if space actually wasn't available. + } else { + // Let's try to not even start compact if there is really low free space. + // It may still fail later as we do not know how big exactly the folder DB will + // end up being. + // The DB already doesn't contain references to messages that are already deleted. + // So theoretically it shouldn't shrink with compact. But in practice, + // the automatic shrinking of the DB may still have not yet happened. + // So we cap the final size at 1KB per message. + db->Commit(nsMsgDBCommitType::kCompressCommit); + + int64_t dbSize; + rv = db->GetDatabaseSize(&dbSize); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t totalMsgs; + rv = folder->GetTotalMessages(false, &totalMsgs); + NS_ENSURE_SUCCESS(rv, rv); + int64_t expectedDBSize = std::min<int64_t>(dbSize, ((int64_t)totalMsgs) * 1024); + if (diskFree < diskSize - expunged + expectedDBSize) + { + if (!m_alreadyWarnedDiskSpace) + { + folder->ThrowAlertMsg("compactFolderInsufficientSpace", m_window); + m_alreadyWarnedDiskSpace = true; + } + break; + } + } + + rv = folder->GetBaseMessageURI(baseMessageURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Init(folder, baseMessageURI.get(), db, path, m_window); + NS_ENSURE_SUCCESS(rv, rv); + + bool isLocked = true; + m_folder->GetLocked(&isLocked); + if (isLocked) + { + CleanupTempFilesAfterError(); + m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window); + break; + } + + // If we got here start the real compacting. + nsCOMPtr<nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this)); + m_folder->AcquireSemaphore(supports); + m_totalExpungedBytes += expunged; + return StartCompacting(); + + } while(false); // block for easy skipping the compaction using 'break' + + folder->NotifyCompactCompleted(); + if (m_compactAll) + return CompactNextFolder(); + else + return NS_OK; +} + +nsresult nsFolderCompactState::ShowStatusMsg(const nsString& aMsg) +{ + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + if (m_window) + { + m_window->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (statusFeedback && !aMsg.IsEmpty()) + return statusFeedback->SetStatusString(aMsg); + } + return NS_OK; +} + +nsresult +nsFolderCompactState::Init(nsIMsgFolder *folder, const char *baseMsgUri, nsIMsgDatabase *db, + nsIFile *path, nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + + m_folder = folder; + m_baseMessageUri = baseMsgUri; + m_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_file->InitWithFile(path); + // need to make sure the temp file goes in the same real directory + // as the original file, so resolve sym links. + m_file->SetFollowLinks(true); + + m_file->SetNativeLeafName(NS_LITERAL_CSTRING("nstmp")); + rv = m_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); //make sure we are not crunching existing nstmp file + NS_ENSURE_SUCCESS(rv, rv); + + m_window = aMsgWindow; + m_keyArray = new nsMsgKeyArray; + m_size = 0; + m_totalMsgSize = 0; + rv = InitDB(db); + if (NS_FAILED(rv)) + { + CleanupTempFilesAfterError(); + return rv; + } + + m_curIndex = 0; + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_fileStream), m_file, -1, 00600); + if (NS_FAILED(rv)) + m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window); + else + rv = GetMessageServiceFromURI(nsDependentCString(baseMsgUri), + getter_AddRefs(m_messageService)); + if (NS_FAILED(rv)) + { + m_status = rv; + } + return rv; +} + +void nsFolderCompactState::ShowCompactingStatusMsg() +{ + nsString statusString; + nsresult rv = m_folder->GetStringWithFolderNameFromBundle("compactingFolder", statusString); + if (!statusString.IsEmpty() && NS_SUCCEEDED(rv)) + ShowStatusMsg(statusString); +} + +NS_IMETHODIMP nsFolderCompactState::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +NS_IMETHODIMP nsFolderCompactState::OnStopRunningUrl(nsIURI *url, nsresult status) +{ + if (m_parsingFolder) + { + m_parsingFolder = false; + if (NS_SUCCEEDED(status)) + status = Compact(m_folder, m_compactingOfflineFolders, m_listener, m_window); + else if (m_compactAll) + CompactNextFolder(); + } + else if (m_compactAll) // this should be the imap case only + { + nsCOMPtr <nsIMsgFolder> prevFolder = do_QueryElementAt(m_folderArray, + m_folderIndex); + if (prevFolder) + prevFolder->SetMsgDatabase(nullptr); + CompactNextFolder(); + } + else if (m_listener) + { + CompactCompleted(status); + } + return NS_OK; +} + +nsresult nsFolderCompactState::StartCompacting() +{ + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsCOMPtr<nsIMsgIncomingServer> server; + + nsresult rv = m_folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + // Notify that compaction is beginning. We do this even if there are no + // messages to be copied because the summary database still gets blown away + // which is still pretty interesting. (And we like consistency.) + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyItemEvent(m_folder, + NS_LITERAL_CSTRING("FolderCompactStart"), + nullptr); + + // TODO: test whether sorting the messages (m_keyArray) by messageOffset + // would improve performance on large files (less seeks). + // The m_keyArray is in the order as stored in DB and on IMAP or News + // the messages stored on the mbox file are not necessarily in the same order. + if (m_size > 0) + { + nsCOMPtr<nsIURI> notUsed; + ShowCompactingStatusMsg(); + AddRef(); + rv = m_messageService->CopyMessages(m_size, m_keyArray->m_keys.Elements(), + m_folder, this, + false, nullptr, m_window, + getter_AddRefs(notUsed)); + } + else + { // no messages to copy with + FinishCompact(); +// Release(); // we don't "own" ourselves yet. + } + return rv; +} + +nsresult +nsFolderCompactState::FinishCompact() +{ + NS_ENSURE_TRUE(m_folder, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(m_file, NS_ERROR_NOT_INITIALIZED); + + // All okay time to finish up the compact process + nsCOMPtr<nsIFile> path; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + + // get leaf name and database name of the folder + nsresult rv = m_folder->GetFilePath(getter_AddRefs(path)); + nsCOMPtr <nsIFile> folderPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = folderPath->InitWithFile(path); + NS_ENSURE_SUCCESS(rv, rv); + // need to make sure we put the .msf file in the same directory + // as the original mailbox, so resolve symlinks. + folderPath->SetFollowLinks(true); + + nsCOMPtr <nsIFile> oldSummaryFile; + rv = GetSummaryFileLocation(folderPath, getter_AddRefs(oldSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString dbName; + oldSummaryFile->GetNativeLeafName(dbName); + nsAutoCString folderName; + path->GetNativeLeafName(folderName); + + // close down the temp file stream; preparing for deleting the old folder + // and its database; then rename the temp folder and database + if (m_fileStream) + { + m_fileStream->Flush(); + m_fileStream->Close(); + m_fileStream = nullptr; + } + + // make sure the new database is valid. + // Close it so we can rename the .msf file. + if (m_db) + { + m_db->ForceClosed(); + m_db = nullptr; + } + + nsCOMPtr <nsIFile> newSummaryFile; + rv = GetSummaryFileLocation(m_file, getter_AddRefs(newSummaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIDBFolderInfo> transferInfo; + m_folder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + + // close down database of the original folder + m_folder->ForceDBClosed(); + + nsCOMPtr<nsIFile> cloneFile; + int64_t fileSize = 0; + rv = m_file->Clone(getter_AddRefs(cloneFile)); + if (NS_SUCCEEDED(rv)) + rv = cloneFile->GetFileSize(&fileSize); + bool tempFileRightSize = ((uint64_t)fileSize == m_totalMsgSize); + NS_WARNING_ASSERTION(tempFileRightSize, "temp file not of expected size in compact"); + + bool folderRenameSucceeded = false; + bool msfRenameSucceeded = false; + if (NS_SUCCEEDED(rv) && tempFileRightSize) + { + // First we're going to try and move the old summary file out the way. + // We don't delete it yet, as we want to keep the files in sync. + nsCOMPtr<nsIFile> tempSummaryFile; + rv = oldSummaryFile->Clone(getter_AddRefs(tempSummaryFile)); + if (NS_SUCCEEDED(rv)) + rv = tempSummaryFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + + nsAutoCString tempSummaryFileName; + if (NS_SUCCEEDED(rv)) + rv = tempSummaryFile->GetNativeLeafName(tempSummaryFileName); + + if (NS_SUCCEEDED(rv)) + rv = oldSummaryFile->MoveToNative((nsIFile*) nullptr, tempSummaryFileName); + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error moving compacted folder's db out of the way"); + if (NS_SUCCEEDED(rv)) + { + // Now we've successfully moved the summary file out the way, try moving + // the newly compacted message file over the old one. + rv = m_file->MoveToNative((nsIFile *) nullptr, folderName); + folderRenameSucceeded = NS_SUCCEEDED(rv); + NS_WARNING_ASSERTION(folderRenameSucceeded, "error renaming compacted folder"); + if (folderRenameSucceeded) + { + // That worked, so land the new summary file in the right place. + nsCOMPtr<nsIFile> renamedCompactedSummaryFile; + newSummaryFile->Clone(getter_AddRefs(renamedCompactedSummaryFile)); + if (renamedCompactedSummaryFile) + { + rv = renamedCompactedSummaryFile->MoveToNative((nsIFile *) nullptr, dbName); + msfRenameSucceeded = NS_SUCCEEDED(rv); + } + NS_WARNING_ASSERTION(msfRenameSucceeded, "error renaming compacted folder's db"); + } + + if (!msfRenameSucceeded) + { + // Do our best to put the summary file back to where it was + rv = tempSummaryFile->MoveToNative((nsIFile*) nullptr, dbName); + if (NS_SUCCEEDED(rv)) + tempSummaryFile = nullptr; // flagging that a renamed db no longer exists + else + NS_WARNING("error restoring uncompacted folder's db"); + } + } + // We don't want any temporarily renamed summary file to lie around + if (tempSummaryFile) + tempSummaryFile->Remove(false); + } + + NS_WARNING_ASSERTION(msfRenameSucceeded, "compact failed"); + nsresult rvReleaseFolderLock = ReleaseFolderLock(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvReleaseFolderLock),"folder lock not released successfully"); + rv = NS_FAILED(rv) ? rv : rvReleaseFolderLock; + + // Cleanup of nstmp-named compacted files if failure + if (!folderRenameSucceeded) + { + // remove the abandoned compacted version with the wrong name + m_file->Remove(false); + } + if (!msfRenameSucceeded) + { + // remove the abandoned compacted summary file + newSummaryFile->Remove(false); + } + + if (msfRenameSucceeded) + { + // Transfer local db information from transferInfo + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgDBService->OpenFolderDB(m_folder, true, getter_AddRefs(m_db)); + NS_ENSURE_TRUE(m_db, NS_FAILED(rv) ? rv : NS_ERROR_FAILURE); + // These errors are expected. + rv = (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) ? NS_OK : rv; + m_db->SetSummaryValid(true); + m_folder->SetDBTransferInfo(transferInfo); + + // since we're transferring info from the old db, we need to reset the expunged bytes + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if(dbFolderInfo) + dbFolderInfo->SetExpungedBytes(0); + } + if (m_db) + m_db->Close(true); + m_db = nullptr; + + // Notify that compaction of the folder is completed. + nsCOMPtr<nsIMsgFolderNotificationService> + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyItemEvent(m_folder, + NS_LITERAL_CSTRING("FolderCompactFinish"), + nullptr); + m_folder->NotifyCompactCompleted(); + + if (m_compactAll) + rv = CompactNextFolder(); + else + CompactCompleted(rv); + + return rv; +} + +nsresult +GetBaseStringBundle(nsIStringBundle **aBundle) +{ + NS_ENSURE_ARG_POINTER(aBundle); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + return bundleService->CreateBundle( + "chrome://messenger/locale/messenger.properties", aBundle); +} + +void nsFolderCompactState::CompactCompleted(nsresult exitCode) +{ + NS_WARNING_ASSERTION(NS_SUCCEEDED(exitCode), + "nsFolderCompactState::CompactCompleted failed"); + if (m_listener) + m_listener->OnStopRunningUrl(nullptr, exitCode); + ShowDoneStatus(); +} + +nsresult +nsFolderCompactState::ReleaseFolderLock() +{ + nsresult result = NS_OK; + if (!m_folder) return result; + bool haveSemaphore; + nsCOMPtr <nsISupports> supports = do_QueryInterface(static_cast<nsIMsgFolderCompactor*>(this)); + result = m_folder->TestSemaphore(supports, &haveSemaphore); + if(NS_SUCCEEDED(result) && haveSemaphore) + result = m_folder->ReleaseSemaphore(supports); + return result; +} + +void nsFolderCompactState::ShowDoneStatus() +{ + if (m_folder) + { + nsString statusString; + nsCOMPtr <nsIStringBundle> bundle; + nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS_VOID(rv); + nsAutoString expungedAmount; + FormatFileSize(m_totalExpungedBytes, true, expungedAmount); + const char16_t* params[] = { expungedAmount.get() }; + rv = bundle->FormatStringFromName(u"compactingDone", + params, 1, getter_Copies(statusString)); + + if (!statusString.IsEmpty() && NS_SUCCEEDED(rv)) + ShowStatusMsg(statusString); + } +} + +nsresult +nsFolderCompactState::CompactNextFolder() +{ + m_folderIndex++; + uint32_t cnt = 0; + nsresult rv = m_folderArray->GetLength(&cnt); + NS_ENSURE_SUCCESS(rv, rv); + // m_folderIndex might be > cnt if we compact offline stores, + // and get back here from OnStopRunningUrl. + if (m_folderIndex >= cnt) + { + if (!m_compactOfflineAlso || m_compactingOfflineFolders) + { + CompactCompleted(NS_OK); + return rv; + } + m_compactingOfflineFolders = true; + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray, + m_folderIndex-1, &rv); + if (NS_SUCCEEDED(rv) && folder) + return folder->CompactAllOfflineStores(this, m_window, m_offlineFolderArray); + else + NS_WARNING("couldn't get folder to compact offline stores"); + + } + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray, + m_folderIndex, &rv); + + if (NS_SUCCEEDED(rv) && folder) + rv = Compact(folder, m_compactingOfflineFolders, m_listener, m_window); + else + CompactCompleted(rv); + return rv; +} + +nsresult +nsFolderCompactState::GetMessage(nsIMsgDBHdr **message) +{ + return GetMsgDBHdrFromURI(m_messageUri.get(), message); +} + + +NS_IMETHODIMP +nsFolderCompactState::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + return StartMessage(); +} + +NS_IMETHODIMP +nsFolderCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + if (NS_FAILED(status)) + { + m_status = status; // set the m_status to status so the destructor can remove the + // temp folder and database + CleanupTempFilesAfterError(); + m_folder->NotifyCompactCompleted(); + ReleaseFolderLock(); + m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window); + } + else + { + EndCopy(nullptr, status); + if (m_curIndex >= m_size) + { + msgHdr = nullptr; + // no more to copy finish it up + FinishCompact(); + } + else + { + // in case we're not getting an error, we still need to pretend we did get an error, + // because the compact did not successfully complete. + m_folder->NotifyCompactCompleted(); + CleanupTempFilesAfterError(); + ReleaseFolderLock(); + } + } + Release(); // kill self + return status; +} + +NS_IMETHODIMP +nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + if (!m_fileStream || !inStr) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + uint32_t msgFlags; + bool checkForKeyword = m_startOfMsg; + bool addKeywordHdr = false; + uint32_t needToGrowKeywords = 0; + uint32_t statusOffset; + nsCString msgHdrKeywords; + + if (m_startOfMsg) + { + m_statusOffset = 0; + m_addedHeaderSize = 0; + m_messageUri.Truncate(); // clear the previous message uri + if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex], + m_messageUri))) + { + rv = GetMessage(getter_AddRefs(m_curSrcHdr)); + NS_ENSURE_SUCCESS(rv, rv); + if (m_curSrcHdr) + { + (void) m_curSrcHdr->GetFlags(&msgFlags); + (void) m_curSrcHdr->GetStatusOffset(&statusOffset); + + if (statusOffset == 0) + m_needStatusLine = true; + // x-mozilla-status lines should be at the start of the headers, and the code + // below assumes everything will fit in m_dataBuffer - if there's not + // room, skip the keyword stuff. + if (statusOffset > sizeof(m_dataBuffer) - 1024) + { + checkForKeyword = false; + NS_ASSERTION(false, "status offset past end of read buffer size"); + + } + } + } + m_startOfMsg = false; + } + uint32_t maxReadCount, readCount, writeCount; + uint32_t bytesWritten; + + while (NS_SUCCEEDED(rv) && (int32_t) count > 0) + { + maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count; + writeCount = 0; + rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount); + + // if status offset is past the number of bytes we read, it's probably bogus, + // and we shouldn't do any of the keyword stuff. + if (statusOffset + X_MOZILLA_STATUS_LEN > readCount) + checkForKeyword = false; + + if (NS_SUCCEEDED(rv)) + { + if (checkForKeyword) + { + // make sure that status offset really points to x-mozilla-status line + if (!strncmp(m_dataBuffer + statusOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN)) + { + const char *keywordHdr = PL_strnrstr(m_dataBuffer, HEADER_X_MOZILLA_KEYWORDS, readCount); + if (keywordHdr) + m_curSrcHdr->GetUint32Property("growKeywords", &needToGrowKeywords); + else + addKeywordHdr = true; + m_curSrcHdr->GetStringProperty("keywords", getter_Copies(msgHdrKeywords)); + } + checkForKeyword = false; + } + uint32_t blockOffset = 0; + if (m_needStatusLine) + { + m_needStatusLine = false; + // we need to parse out the "From " header, write it out, then + // write out the x-mozilla-status headers, and set the + // status offset of the dest hdr for later use + // in OnEndCopy). + if (!strncmp(m_dataBuffer, "From ", 5)) + { + blockOffset = 5; + // skip from line + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + char statusLine[50]; + m_fileStream->Write(m_dataBuffer, blockOffset, &writeCount); + m_statusOffset = blockOffset; + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF); + m_fileStream->Write(statusLine, strlen(statusLine), &m_addedHeaderSize); + PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000); + m_fileStream->Write(statusLine, strlen(statusLine), &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + else + { + NS_ASSERTION(false, "not an envelope"); + // try to mark the db as invalid so it will be reparsed. + nsCOMPtr <nsIMsgDatabase> srcDB; + m_folder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (srcDB) + { + srcDB->SetSummaryValid(false); + srcDB->ForceClosed(); + } + } + } +#define EXTRA_KEYWORD_HDR " " MSG_LINEBREAK + + // if status offset isn't in the first block, this code won't work. There's no good reason + // for the status offset not to be at the beginning of the message anyway. + if (addKeywordHdr) + { + // if blockOffset is set, we added x-mozilla-status headers so + // file pointer is already past them. + if (!blockOffset) + { + blockOffset = statusOffset; + // skip x-mozilla-status and status2 lines. + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + // need to rewrite the headers up to and including the x-mozilla-status2 header + m_fileStream->Write(m_dataBuffer, blockOffset, &writeCount); + } + // we should write out the existing keywords from the msg hdr, if any. + if (msgHdrKeywords.IsEmpty()) + { // no keywords, so write blank header + m_fileStream->Write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1, &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + else + { + if (msgHdrKeywords.Length() < sizeof(X_MOZILLA_KEYWORDS) - sizeof(HEADER_X_MOZILLA_KEYWORDS) + 10 /* allow some slop */) + { // keywords fit in normal blank header, so replace blanks in keyword hdr with keywords + nsAutoCString keywordsHdr(X_MOZILLA_KEYWORDS); + keywordsHdr.Replace(sizeof(HEADER_X_MOZILLA_KEYWORDS) + 1, msgHdrKeywords.Length(), msgHdrKeywords); + m_fileStream->Write(keywordsHdr.get(), keywordsHdr.Length(), &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + else + { // keywords don't fit, so write out keywords on one line and an extra blank line + nsCString newKeywordHeader(HEADER_X_MOZILLA_KEYWORDS ": "); + newKeywordHeader.Append(msgHdrKeywords); + newKeywordHeader.Append(MSG_LINEBREAK EXTRA_KEYWORD_HDR); + m_fileStream->Write(newKeywordHeader.get(), newKeywordHeader.Length(), &bytesWritten); + m_addedHeaderSize += bytesWritten; + } + } + addKeywordHdr = false; + } + else if (needToGrowKeywords) + { + blockOffset = statusOffset; + if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN)) + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status hdr + if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN)) + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status2 hdr + uint32_t preKeywordBlockOffset = blockOffset; + if (!strncmp(m_dataBuffer + blockOffset, HEADER_X_MOZILLA_KEYWORDS, sizeof(HEADER_X_MOZILLA_KEYWORDS) - 1)) + { + do + { + // skip x-mozilla-keywords hdr and any existing continuation headers + MsgAdvanceToNextLine(m_dataBuffer, blockOffset, readCount); + } + while (m_dataBuffer[blockOffset] == ' '); + } + int32_t oldKeywordSize = blockOffset - preKeywordBlockOffset; + + // rewrite the headers up to and including the x-mozilla-status2 header + m_fileStream->Write(m_dataBuffer, preKeywordBlockOffset, &writeCount); + // let's just rewrite all the keywords on several lines and add a blank line, + // instead of worrying about which are missing. + bool done = false; + nsAutoCString keywordHdr(HEADER_X_MOZILLA_KEYWORDS ": "); + int32_t nextBlankOffset = 0; + int32_t curHdrLineStart = 0; + int32_t newKeywordSize = 0; + while (!done) + { + nextBlankOffset = msgHdrKeywords.FindChar(' ', nextBlankOffset); + if (nextBlankOffset == kNotFound) + { + nextBlankOffset = msgHdrKeywords.Length(); + done = true; + } + if (nextBlankOffset - curHdrLineStart > 90 || done) + { + keywordHdr.Append(nsDependentCSubstring(msgHdrKeywords, curHdrLineStart, msgHdrKeywords.Length() - curHdrLineStart)); + keywordHdr.Append(MSG_LINEBREAK); + m_fileStream->Write(keywordHdr.get(), keywordHdr.Length(), &bytesWritten); + newKeywordSize += bytesWritten; + curHdrLineStart = nextBlankOffset; + keywordHdr.Assign(' '); + } + nextBlankOffset++; + } + m_fileStream->Write(EXTRA_KEYWORD_HDR, sizeof(EXTRA_KEYWORD_HDR) - 1, &bytesWritten); + newKeywordSize += bytesWritten; + m_addedHeaderSize += newKeywordSize - oldKeywordSize; + m_curSrcHdr->SetUint32Property("growKeywords", 0); + needToGrowKeywords = false; + writeCount += blockOffset - preKeywordBlockOffset; // fudge writeCount + + } + if (readCount <= blockOffset) + { + NS_ASSERTION(false, "bad block offset"); + // not sure what to do to handle this. + + } + m_fileStream->Write(m_dataBuffer + blockOffset, readCount - blockOffset, &bytesWritten); + writeCount += bytesWritten; + count -= readCount; + if (writeCount != readCount) + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + } + return rv; +} + +nsOfflineStoreCompactState::nsOfflineStoreCompactState() +{ +} + +nsOfflineStoreCompactState::~nsOfflineStoreCompactState() +{ +} + + +nsresult +nsOfflineStoreCompactState::InitDB(nsIMsgDatabase *db) +{ + // Start with the list of messages we have offline as the possible + // message to keep when compacting the offline store. + db->ListAllOfflineMsgs(m_keyArray); + m_size = m_keyArray->m_keys.Length(); + m_db = db; + return NS_OK; +} + +/** + * This will copy one message to the offline store, but if it fails to + * copy the next message, it will keep trying messages until it finds one + * it can copy, or it runs out of messages. + */ +nsresult nsOfflineStoreCompactState::CopyNextMessage(bool &done) +{ + while (m_curIndex < m_size) + { + // Filter out msgs that have the "pendingRemoval" attribute set. + nsCOMPtr<nsIMsgDBHdr> hdr; + nsString pendingRemoval; + nsresult rv = m_db->GetMsgHdrForKey(m_keyArray->m_keys[m_curIndex], getter_AddRefs(hdr)); + NS_ENSURE_SUCCESS(rv, rv); + hdr->GetProperty("pendingRemoval", pendingRemoval); + if (!pendingRemoval.IsEmpty()) + { + m_curIndex++; + // Turn off offline flag for message, since after the compact is completed; + // we won't have the message in the offline store. + uint32_t resultFlags; + hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags); + // We need to clear this in case the user changes the offline retention + // settings. + hdr->SetStringProperty("pendingRemoval", ""); + continue; + } + m_messageUri.Truncate(); // clear the previous message uri + rv = BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex], + m_messageUri); + NS_ENSURE_SUCCESS(rv, rv); + m_startOfMsg = true; + nsCOMPtr<nsISupports> thisSupports; + QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(thisSupports)); + nsCOMPtr<nsIURI> dummyNull; + rv = m_messageService->StreamMessage(m_messageUri.get(), thisSupports, m_window, nullptr, + false, EmptyCString(), true, getter_AddRefs(dummyNull)); + // if copy fails, we clear the offline flag on the source message. + if (NS_FAILED(rv)) + { + nsCOMPtr<nsIMsgDBHdr> hdr; + GetMessage(getter_AddRefs(hdr)); + if (hdr) + { + uint32_t resultFlags; + hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags); + } + m_curIndex++; + continue; + } + else + break; + } + done = m_curIndex >= m_size; + // In theory, we might be able to stream the next message, so + // return NS_OK, not rv. + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineStoreCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) +{ + nsresult rv = status; + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + bool done = false; + + // The NS_MSG_ERROR_MSG_NOT_OFFLINE error should allow us to continue, so we + // check for it specifically and don't terminate the compaction. + if (NS_FAILED(rv) && rv != NS_MSG_ERROR_MSG_NOT_OFFLINE) + goto done; + uri = do_QueryInterface(ctxt, &rv); + if (NS_FAILED(rv)) goto done; + rv = GetMessage(getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) goto done; + + // This is however an unexpected condition, so let's print a warning. + if (rv == NS_MSG_ERROR_MSG_NOT_OFFLINE) { + nsAutoCString spec; + uri->GetSpec(spec); + nsPrintfCString msg("Message expectedly not available offline: %s", spec.get()); + NS_WARNING(msg.get()); + } + + if (msgHdr) + { + if (NS_SUCCEEDED(status)) + { + msgHdr->SetMessageOffset(m_startOfNewMsg); + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_startOfNewMsg); + msgHdr->SetStringProperty("storeToken", storeToken); + msgHdr->SetOfflineMessageSize(m_offlineMsgSize); + } + else + { + uint32_t resultFlags; + msgHdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags); + } + } + + if (m_window) + { + m_window->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (statusFeedback) + statusFeedback->ShowProgress(100 * m_curIndex / m_size); + } + // advance to next message + m_curIndex++; + rv = CopyNextMessage(done); + if (done) + { + m_db->Commit(nsMsgDBCommitType::kCompressCommit); + msgHdr = nullptr; + // no more to copy finish it up + ReleaseFolderLock(); + FinishCompact(); + Release(); // kill self + } + +done: + if (NS_FAILED(rv)) { + m_status = rv; // set the status to rv so the destructor can remove the + // temp folder and database + ReleaseFolderLock(); + Release(); // kill self + return rv; + } + return rv; +} + + +nsresult +nsOfflineStoreCompactState::FinishCompact() +{ + // All okay time to finish up the compact process + nsCOMPtr<nsIFile> path; + uint32_t flags; + + // get leaf name and database name of the folder + m_folder->GetFlags(&flags); + nsresult rv = m_folder->GetFilePath(getter_AddRefs(path)); + + nsCString leafName; + path->GetNativeLeafName(leafName); + + if (m_fileStream) + { + // close down the temp file stream; preparing for deleting the old folder + // and its database; then rename the temp folder and database + m_fileStream->Flush(); + m_fileStream->Close(); + m_fileStream = nullptr; + } + + // make sure the new database is valid + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if (dbFolderInfo) + dbFolderInfo->SetExpungedBytes(0); + // this forces the m_folder to update mExpungedBytes from the db folder info. + int64_t expungedBytes; + m_folder->GetExpungedBytes(&expungedBytes); + m_folder->UpdateSummaryTotals(true); + m_db->SetSummaryValid(true); + + // remove the old folder + path->Remove(false); + + // rename the copied folder to be the original folder + m_file->MoveToNative((nsIFile *) nullptr, leafName); + + ShowStatusMsg(EmptyString()); + m_folder->NotifyCompactCompleted(); + if (m_compactAll) + rv = CompactNextFolder(); + return rv; +} + + +NS_IMETHODIMP +nsFolderCompactState::Init(nsIMsgFolder *srcFolder, nsICopyMessageListener *destination, nsISupports *listenerData) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsFolderCompactState::StartMessage() +{ + nsresult rv = NS_ERROR_FAILURE; + NS_ASSERTION(m_fileStream, "Fatal, null m_fileStream...\n"); + if (m_fileStream) + { + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(m_fileStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // this will force an internal flush, but not a sync. Tell should really do an internal flush, + // but it doesn't, and I'm afraid to change that nsIFileStream.cpp code anymore. + seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + // record the new message key for the message + int64_t curStreamPos; + seekableStream->Tell(&curStreamPos); + m_startOfNewMsg = curStreamPos; + rv = NS_OK; + } + return rv; +} + +NS_IMETHODIMP +nsFolderCompactState::EndMessage(nsMsgKey key) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsFolderCompactState::EndCopy(nsISupports *url, nsresult aStatus) +{ + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIMsgDBHdr> newMsgHdr; + + if (m_curIndex >= m_size) + { + NS_ASSERTION(false, "m_curIndex out of bounds"); + return NS_OK; + } + + /** + * Done with the current message; copying the existing message header + * to the new database. + */ + if (m_curSrcHdr) + { + nsMsgKey key; + m_curSrcHdr->GetMessageKey(&key); + m_db->CopyHdrFromExistingHdr(key, m_curSrcHdr, true, + getter_AddRefs(newMsgHdr)); + } + m_curSrcHdr = nullptr; + if (newMsgHdr) + { + if (m_statusOffset != 0) + newMsgHdr->SetStatusOffset(m_statusOffset); + + char storeToken[100]; + PR_snprintf(storeToken, sizeof(storeToken), "%lld", m_startOfNewMsg); + newMsgHdr->SetStringProperty("storeToken", storeToken); + newMsgHdr->SetMessageOffset(m_startOfNewMsg); + + uint32_t msgSize; + (void) newMsgHdr->GetMessageSize(&msgSize); + if (m_addedHeaderSize) + { + msgSize += m_addedHeaderSize; + newMsgHdr->SetMessageSize(msgSize); + } + m_totalMsgSize += msgSize; + } + +// m_db->Commit(nsMsgDBCommitType::kLargeCommit); // no sense commiting until the end + // advance to next message + m_curIndex ++; + m_startOfMsg = true; + nsCOMPtr <nsIMsgStatusFeedback> statusFeedback; + if (m_window) + { + m_window->GetStatusFeedback(getter_AddRefs(statusFeedback)); + if (statusFeedback) + statusFeedback->ShowProgress(100 * m_curIndex / m_size); + } + return NS_OK; +} + +nsresult nsOfflineStoreCompactState::StartCompacting() +{ + nsresult rv = NS_OK; + if (m_size > 0 && m_curIndex == 0) + { + AddRef(); // we own ourselves, until we're done, anyway. + ShowCompactingStatusMsg(); + bool done = false; + rv = CopyNextMessage(done); + if (!done) + return rv; + } + ReleaseFolderLock(); + FinishCompact(); + return rv; +} + +NS_IMETHODIMP +nsOfflineStoreCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + if (!m_fileStream || !inStr) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + + if (m_startOfMsg) + { + m_statusOffset = 0; + m_offlineMsgSize = 0; + m_messageUri.Truncate(); // clear the previous message uri + if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray->m_keys[m_curIndex], + m_messageUri))) + { + rv = GetMessage(getter_AddRefs(m_curSrcHdr)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + uint32_t maxReadCount, readCount, writeCount; + uint32_t bytesWritten; + + while (NS_SUCCEEDED(rv) && (int32_t) count > 0) + { + maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count; + writeCount = 0; + rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount); + + if (NS_SUCCEEDED(rv)) + { + if (m_startOfMsg) + { + m_startOfMsg = false; + // check if there's an envelope header; if not, write one. + if (strncmp(m_dataBuffer, "From ", 5)) + { + m_fileStream->Write("From " CRLF, 7, &bytesWritten); + m_offlineMsgSize += bytesWritten; + } + } + m_fileStream->Write(m_dataBuffer, readCount, &bytesWritten); + m_offlineMsgSize += bytesWritten; + writeCount += bytesWritten; + count -= readCount; + if (writeCount != readCount) + { + m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window); + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + } + } + return rv; +} + diff --git a/mailnews/base/src/nsMsgFolderCompactor.h b/mailnews/base/src/nsMsgFolderCompactor.h new file mode 100644 index 0000000000..50a69df812 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCompactor.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _nsMsgFolderCompactor_h +#define _nsMsgFolderCompactor_h + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIMsgFolder.h" +#include "nsIStreamListener.h" +#include "nsIMsgFolderCompactor.h" +#include "nsICopyMsgStreamListener.h" +#include "nsMsgKeyArray.h" +#include "nsIMsgWindow.h" +#include "nsIStringBundle.h" +#include "nsIMsgMessageService.h" + +#define COMPACTOR_READ_BUFF_SIZE 16384 + +class nsFolderCompactState : public nsIMsgFolderCompactor, + public nsIStreamListener, + public nsICopyMessageStreamListener, + public nsIUrlListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSICOPYMESSAGESTREAMLISTENER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGFOLDERCOMPACTOR + + nsFolderCompactState(void); +protected: + virtual ~nsFolderCompactState(void); + + virtual nsresult InitDB(nsIMsgDatabase *db); + virtual nsresult StartCompacting(); + virtual nsresult FinishCompact(); + void CloseOutputStream(); + void CleanupTempFilesAfterError(); + + nsresult Init(nsIMsgFolder *aFolder, const char* aBaseMsgUri, nsIMsgDatabase *aDb, + nsIFile *aPath, nsIMsgWindow *aMsgWindow); + nsresult GetMessage(nsIMsgDBHdr **message); + nsresult BuildMessageURI(const char *baseURI, nsMsgKey key, nsCString& uri); + nsresult ShowStatusMsg(const nsString& aMsg); + nsresult ReleaseFolderLock(); + void ShowCompactingStatusMsg(); + void CompactCompleted(nsresult exitCode); + void ShowDoneStatus(); + nsresult CompactNextFolder(); + + nsCString m_baseMessageUri; // base message uri + nsCString m_messageUri; // current message uri being copy + nsCOMPtr<nsIMsgFolder> m_folder; // current folder being compact + nsCOMPtr<nsIMsgDatabase> m_db; // new database for the compact folder + nsCOMPtr <nsIFile> m_file; // new mailbox for the compact folder + nsCOMPtr <nsIOutputStream> m_fileStream; // output file stream for writing + // all message keys that need to be copied over + RefPtr<nsMsgKeyArray> m_keyArray; + uint32_t m_size; + + // sum of the sizes of the messages, accumulated as we visit each msg. + uint64_t m_totalMsgSize; + // number of bytes that can be expunged while compacting. + uint64_t m_totalExpungedBytes; + + uint32_t m_curIndex; // index of the current copied message key in key array + uint64_t m_startOfNewMsg; // offset in mailbox of new message + char m_dataBuffer[COMPACTOR_READ_BUFF_SIZE + 1]; // temp data buffer for copying message + nsresult m_status; // the status of the copying operation + nsCOMPtr <nsIMsgMessageService> m_messageService; // message service for copying + nsCOMPtr<nsIArray> m_folderArray; // folders we are compacting, if compacting multiple. + nsCOMPtr <nsIMsgWindow> m_window; + nsCOMPtr <nsIMsgDBHdr> m_curSrcHdr; + uint32_t m_folderIndex; // tells which folder to compact in case of compact all + bool m_compactAll; //flag for compact all + bool m_compactOfflineAlso; //whether to compact offline also + bool m_compactingOfflineFolders; // are we in the offline folder compact phase + bool m_parsingFolder; //flag for parsing local folders; + // these members are used to add missing status lines to compacted messages. + bool m_needStatusLine; + bool m_startOfMsg; + int32_t m_statusOffset; + uint32_t m_addedHeaderSize; + nsCOMPtr<nsIArray> m_offlineFolderArray; + nsCOMPtr<nsIUrlListener> m_listener; + bool m_alreadyWarnedDiskSpace; +}; + +class nsOfflineStoreCompactState : public nsFolderCompactState +{ +public: + + nsOfflineStoreCompactState(void); + virtual ~nsOfflineStoreCompactState(void); + NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult status) override; + NS_IMETHODIMP OnDataAvailable(nsIRequest *request, nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) override; + +protected: + nsresult CopyNextMessage(bool &done); + virtual nsresult InitDB(nsIMsgDatabase *db) override; + virtual nsresult StartCompacting() override; + virtual nsresult FinishCompact() override; + + uint32_t m_offlineMsgSize; +}; + +#endif diff --git a/mailnews/base/src/nsMsgFolderDataSource.cpp b/mailnews/base/src/nsMsgFolderDataSource.cpp new file mode 100644 index 0000000000..391596ab4c --- /dev/null +++ b/mailnews/base/src/nsMsgFolderDataSource.cpp @@ -0,0 +1,2475 @@ +/* -*- 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 "msgCore.h" // precompiled header... +#include "prlog.h" +#include "prmem.h" +#include "nsMsgFolderDataSource.h" +#include "nsMsgFolderFlags.h" + +#include "nsMsgUtils.h" +#include "nsMsgRDFUtils.h" + +#include "rdf.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsIRDFNode.h" +#include "nsEnumeratorUtils.h" + +#include "nsStringGlue.h" +#include "nsCOMPtr.h" + +#include "nsIMsgMailSession.h" +#include "nsIMsgCopyService.h" +#include "nsMsgBaseCID.h" +#include "nsIInputStream.h" +#include "nsIMsgHdr.h" +#include "nsTraceRefcnt.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsIMutableArray.h" +#include "nsIPop3IncomingServer.h" +#include "nsINntpIncomingServer.h" +#include "nsTextFormatter.h" +#include "nsIStringBundle.h" +#include "nsIPrompt.h" +#include "nsIMsgAccountManager.h" +#include "nsArrayEnumerator.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties" + +nsIRDFResource* nsMsgFolderDataSource::kNC_Child = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Folder= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Name= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Open = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_FolderTreeName= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_FolderTreeSimpleName= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_NameSort= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_FolderTreeNameSort= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_SpecialFolder= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_ServerType = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_IsDeferred = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanCreateFoldersOnServer = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanFileMessagesOnServer = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_IsServer = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_IsSecure = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanSubscribe = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_SupportsOffline = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanFileMessages = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanCreateSubfolders = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanRename = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanCompact = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_TotalMessages= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_TotalUnreadMessages= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_FolderSize = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Charset = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_BiffState = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_HasUnreadMessages = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_NewMessages = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_SubfoldersHaveUnreadMessages = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_NoSelect = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_VirtualFolder = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_InVFEditSearchScope = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_ImapShared = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Synchronize = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_SyncDisabled = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CanSearchMessages = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_UnreadFolders = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_FavoriteFolders = nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_RecentFolders = nullptr; + +// commands +nsIRDFResource* nsMsgFolderDataSource::kNC_Delete= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_ReallyDelete= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_NewFolder= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_GetNewMessages= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Copy= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Move= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CopyFolder= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_MoveFolder= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_MarkAllMessagesRead= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Compact= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_CompactAll= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_Rename= nullptr; +nsIRDFResource* nsMsgFolderDataSource::kNC_EmptyTrash= nullptr; + +nsrefcnt nsMsgFolderDataSource::gFolderResourceRefCnt = 0; + +nsIAtom * nsMsgFolderDataSource::kBiffStateAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kSortOrderAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kNewMessagesAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kTotalMessagesAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kTotalUnreadMessagesAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kFolderSizeAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kNameAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kSynchronizeAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kOpenAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kIsDeferredAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kIsSecureAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kCanFileMessagesAtom = nullptr; +nsIAtom * nsMsgFolderDataSource::kInVFEditSearchScopeAtom = nullptr; + +static const uint32_t kDisplayBlankCount = 0xFFFFFFFE; +static const uint32_t kDisplayQuestionCount = 0xFFFFFFFF; +static const int64_t kDisplayBlankCount64 = -2; +static const int64_t kDisplayQuestionCount64 = -1; + +nsMsgFolderDataSource::nsMsgFolderDataSource() +{ + // one-time initialization here + nsIRDFService* rdf = getRDFService(); + + if (gFolderResourceRefCnt++ == 0) { + nsCOMPtr<nsIStringBundle> sMessengerStringBundle; + + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CHILD), &kNC_Child); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDER), &kNC_Folder); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME), &kNC_Name); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_OPEN), &kNC_Open); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME), &kNC_FolderTreeName); + rdf->GetResource(NS_LITERAL_CSTRING("mailnewsunreadfolders:/"), &kNC_UnreadFolders); + rdf->GetResource(NS_LITERAL_CSTRING("mailnewsfavefolders:/"), &kNC_FavoriteFolders); + rdf->GetResource(NS_LITERAL_CSTRING("mailnewsrecentfolders:/"), &kNC_RecentFolders); + + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREESIMPLENAME), &kNC_FolderTreeSimpleName); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NAME_SORT), &kNC_NameSort); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERTREENAME_SORT), &kNC_FolderTreeNameSort); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SPECIALFOLDER), &kNC_SpecialFolder); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SERVERTYPE), &kNC_ServerType); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISDEFERRED),&kNC_IsDeferred); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANCREATEFOLDERSONSERVER), &kNC_CanCreateFoldersOnServer); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANFILEMESSAGESONSERVER), &kNC_CanFileMessagesOnServer); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISSERVER), &kNC_IsServer); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_ISSECURE), &kNC_IsSecure); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANSUBSCRIBE), &kNC_CanSubscribe); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SUPPORTSOFFLINE), &kNC_SupportsOffline); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANFILEMESSAGES), &kNC_CanFileMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANCREATESUBFOLDERS), &kNC_CanCreateSubfolders); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANRENAME), &kNC_CanRename); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANCOMPACT), &kNC_CanCompact); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_TOTALMESSAGES), &kNC_TotalMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_TOTALUNREADMESSAGES), &kNC_TotalUnreadMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_FOLDERSIZE), &kNC_FolderSize); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CHARSET), &kNC_Charset); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_BIFFSTATE), &kNC_BiffState); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_HASUNREADMESSAGES), &kNC_HasUnreadMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NEWMESSAGES), &kNC_NewMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SUBFOLDERSHAVEUNREADMESSAGES), &kNC_SubfoldersHaveUnreadMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NOSELECT), &kNC_NoSelect); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_VIRTUALFOLDER), &kNC_VirtualFolder); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_INVFEDITSEARCHSCOPE), &kNC_InVFEditSearchScope); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_IMAPSHARED), &kNC_ImapShared); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SYNCHRONIZE), &kNC_Synchronize); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_SYNCDISABLED), &kNC_SyncDisabled); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_CANSEARCHMESSAGES), &kNC_CanSearchMessages); + + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_DELETE), &kNC_Delete); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_REALLY_DELETE), &kNC_ReallyDelete); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_NEWFOLDER), &kNC_NewFolder); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_GETNEWMESSAGES), &kNC_GetNewMessages); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COPY), &kNC_Copy); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_MOVE), &kNC_Move); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COPYFOLDER), &kNC_CopyFolder); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_MOVEFOLDER), &kNC_MoveFolder); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_MARKALLMESSAGESREAD), + &kNC_MarkAllMessagesRead); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COMPACT), &kNC_Compact); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_COMPACTALL), &kNC_CompactAll); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_RENAME), &kNC_Rename); + rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_EMPTYTRASH), &kNC_EmptyTrash); + + kTotalMessagesAtom = MsgNewAtom("TotalMessages").take(); + kTotalUnreadMessagesAtom = MsgNewAtom("TotalUnreadMessages").take(); + kFolderSizeAtom = MsgNewAtom("FolderSize").take(); + kBiffStateAtom = MsgNewAtom("BiffState").take(); + kSortOrderAtom = MsgNewAtom("SortOrder").take(); + kNewMessagesAtom = MsgNewAtom("NewMessages").take(); + kNameAtom = MsgNewAtom("Name").take(); + kSynchronizeAtom = MsgNewAtom("Synchronize").take(); + kOpenAtom = MsgNewAtom("open").take(); + kIsDeferredAtom = MsgNewAtom("isDeferred").take(); + kIsSecureAtom = MsgNewAtom("isSecure").take(); + kCanFileMessagesAtom = MsgNewAtom("canFileMessages").take(); + kInVFEditSearchScopeAtom = MsgNewAtom("inVFEditSearchScope").take(); + } + + CreateLiterals(rdf); + CreateArcsOutEnumerator(); +} + +nsMsgFolderDataSource::~nsMsgFolderDataSource (void) +{ + if (--gFolderResourceRefCnt == 0) + { + nsrefcnt refcnt; + NS_RELEASE2(kNC_Child, refcnt); + NS_RELEASE2(kNC_Folder, refcnt); + NS_RELEASE2(kNC_Name, refcnt); + NS_RELEASE2(kNC_Open, refcnt); + NS_RELEASE2(kNC_FolderTreeName, refcnt); + NS_RELEASE2(kNC_FolderTreeSimpleName, refcnt); + NS_RELEASE2(kNC_NameSort, refcnt); + NS_RELEASE2(kNC_FolderTreeNameSort, refcnt); + NS_RELEASE2(kNC_SpecialFolder, refcnt); + NS_RELEASE2(kNC_ServerType, refcnt); + NS_RELEASE2(kNC_IsDeferred, refcnt); + NS_RELEASE2(kNC_CanCreateFoldersOnServer, refcnt); + NS_RELEASE2(kNC_CanFileMessagesOnServer, refcnt); + NS_RELEASE2(kNC_IsServer, refcnt); + NS_RELEASE2(kNC_IsSecure, refcnt); + NS_RELEASE2(kNC_CanSubscribe, refcnt); + NS_RELEASE2(kNC_SupportsOffline, refcnt); + NS_RELEASE2(kNC_CanFileMessages, refcnt); + NS_RELEASE2(kNC_CanCreateSubfolders, refcnt); + NS_RELEASE2(kNC_CanRename, refcnt); + NS_RELEASE2(kNC_CanCompact, refcnt); + NS_RELEASE2(kNC_TotalMessages, refcnt); + NS_RELEASE2(kNC_TotalUnreadMessages, refcnt); + NS_RELEASE2(kNC_FolderSize, refcnt); + NS_RELEASE2(kNC_Charset, refcnt); + NS_RELEASE2(kNC_BiffState, refcnt); + NS_RELEASE2(kNC_HasUnreadMessages, refcnt); + NS_RELEASE2(kNC_NewMessages, refcnt); + NS_RELEASE2(kNC_SubfoldersHaveUnreadMessages, refcnt); + NS_RELEASE2(kNC_NoSelect, refcnt); + NS_RELEASE2(kNC_VirtualFolder, refcnt); + NS_RELEASE2(kNC_InVFEditSearchScope, refcnt); + NS_RELEASE2(kNC_ImapShared, refcnt); + NS_RELEASE2(kNC_Synchronize, refcnt); + NS_RELEASE2(kNC_SyncDisabled, refcnt); + NS_RELEASE2(kNC_CanSearchMessages, refcnt); + + NS_RELEASE2(kNC_Delete, refcnt); + NS_RELEASE2(kNC_ReallyDelete, refcnt); + NS_RELEASE2(kNC_NewFolder, refcnt); + NS_RELEASE2(kNC_GetNewMessages, refcnt); + NS_RELEASE2(kNC_Copy, refcnt); + NS_RELEASE2(kNC_Move, refcnt); + NS_RELEASE2(kNC_CopyFolder, refcnt); + NS_RELEASE2(kNC_MoveFolder, refcnt); + NS_RELEASE2(kNC_MarkAllMessagesRead, refcnt); + NS_RELEASE2(kNC_Compact, refcnt); + NS_RELEASE2(kNC_CompactAll, refcnt); + NS_RELEASE2(kNC_Rename, refcnt); + NS_RELEASE2(kNC_EmptyTrash, refcnt); + NS_RELEASE2(kNC_UnreadFolders, refcnt); + NS_RELEASE2(kNC_FavoriteFolders, refcnt); + NS_RELEASE2(kNC_RecentFolders, refcnt); + + NS_RELEASE(kTotalMessagesAtom); + NS_RELEASE(kTotalUnreadMessagesAtom); + NS_RELEASE(kFolderSizeAtom); + NS_RELEASE(kBiffStateAtom); + NS_RELEASE(kSortOrderAtom); + NS_RELEASE(kNewMessagesAtom); + NS_RELEASE(kNameAtom); + NS_RELEASE(kSynchronizeAtom); + NS_RELEASE(kOpenAtom); + NS_RELEASE(kIsDeferredAtom); + NS_RELEASE(kIsSecureAtom); + NS_RELEASE(kCanFileMessagesAtom); + NS_RELEASE(kInVFEditSearchScopeAtom); + } +} + +nsresult nsMsgFolderDataSource::CreateLiterals(nsIRDFService *rdf) +{ + createNode(u"true", + getter_AddRefs(kTrueLiteral), rdf); + createNode(u"false", + getter_AddRefs(kFalseLiteral), rdf); + + return NS_OK; +} + +nsresult nsMsgFolderDataSource::Init() +{ + nsresult rv; + + rv = nsMsgRDFDataSource::Init(); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + + if(NS_SUCCEEDED(rv)) + mailSession->AddFolderListener(this, + nsIFolderListener::added | + nsIFolderListener::removed | + nsIFolderListener::intPropertyChanged | + nsIFolderListener::boolPropertyChanged | + nsIFolderListener::unicharPropertyChanged); + + return NS_OK; +} + +void nsMsgFolderDataSource::Cleanup() +{ + nsresult rv; + if (!m_shuttingDown) + { + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + + if(NS_SUCCEEDED(rv)) + mailSession->RemoveFolderListener(this); + } + + nsMsgRDFDataSource::Cleanup(); +} + +nsresult nsMsgFolderDataSource::CreateArcsOutEnumerator() +{ + nsresult rv; + + rv = getFolderArcLabelsOut(kFolderArcsOutArray); + if(NS_FAILED(rv)) return rv; + + return rv; +} + +NS_IMPL_ADDREF_INHERITED(nsMsgFolderDataSource, nsMsgRDFDataSource) +NS_IMPL_RELEASE_INHERITED(nsMsgFolderDataSource, nsMsgRDFDataSource) + +NS_IMPL_QUERY_INTERFACE_INHERITED(nsMsgFolderDataSource, nsMsgRDFDataSource, nsIFolderListener) + + // nsIRDFDataSource methods +NS_IMETHODIMP nsMsgFolderDataSource::GetURI(char* *uri) +{ + if ((*uri = strdup("rdf:mailnewsfolders")) == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + else + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderDataSource::GetSource(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsIRDFResource** source /* out */) +{ + NS_ASSERTION(false, "not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgFolderDataSource::GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) +{ + nsresult rv = NS_RDF_NO_VALUE; + + // we only have positive assertions in the mail data source. + if (! tv) + return NS_RDF_NO_VALUE; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source)); + if (folder) + rv = createFolderNode(folder, property, target); + else + return NS_RDF_NO_VALUE; + return rv; +} + + +NS_IMETHODIMP nsMsgFolderDataSource::GetSources(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsISimpleEnumerator** sources) +{ + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP nsMsgFolderDataSource::GetTargets(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsISimpleEnumerator** targets) +{ + nsresult rv = NS_RDF_NO_VALUE; + if(!targets) + return NS_ERROR_NULL_POINTER; + + *targets = nullptr; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + if (NS_SUCCEEDED(rv)) + { + if (kNC_Child == property) + { + rv = folder->GetSubFolders(targets); + } + else if ((kNC_Name == property) || + (kNC_Open == property) || + (kNC_FolderTreeName == property) || + (kNC_FolderTreeSimpleName == property) || + (kNC_SpecialFolder == property) || + (kNC_IsServer == property) || + (kNC_IsSecure == property) || + (kNC_CanSubscribe == property) || + (kNC_SupportsOffline == property) || + (kNC_CanFileMessages == property) || + (kNC_CanCreateSubfolders == property) || + (kNC_CanRename == property) || + (kNC_CanCompact == property) || + (kNC_ServerType == property) || + (kNC_IsDeferred == property) || + (kNC_CanCreateFoldersOnServer == property) || + (kNC_CanFileMessagesOnServer == property) || + (kNC_NoSelect == property) || + (kNC_VirtualFolder == property) || + (kNC_InVFEditSearchScope == property) || + (kNC_ImapShared == property) || + (kNC_Synchronize == property) || + (kNC_SyncDisabled == property) || + (kNC_CanSearchMessages == property)) + { + return NS_NewSingletonEnumerator(targets, property); + } + } + if(!*targets) + { + //create empty cursor + rv = NS_NewEmptyEnumerator(targets); + } + + return rv; +} + +NS_IMETHODIMP nsMsgFolderDataSource::Assert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + //We don't handle tv = false at the moment. + if(NS_SUCCEEDED(rv) && tv) + return DoFolderAssert(folder, property, target); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgFolderDataSource::Unassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + return DoFolderUnassert(folder, property, target); +} + + +NS_IMETHODIMP nsMsgFolderDataSource::HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + if(NS_SUCCEEDED(rv)) + return DoFolderHasAssertion(folder, property, target, tv, hasAssertion); + else + *hasAssertion = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgFolderDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(aSource, &rv)); + if (NS_SUCCEEDED(rv)) + { + *result = (aArc == kNC_Name || + aArc == kNC_Open || + aArc == kNC_FolderTreeName || + aArc == kNC_FolderTreeSimpleName || + aArc == kNC_SpecialFolder || + aArc == kNC_ServerType || + aArc == kNC_IsDeferred || + aArc == kNC_CanCreateFoldersOnServer || + aArc == kNC_CanFileMessagesOnServer || + aArc == kNC_IsServer || + aArc == kNC_IsSecure || + aArc == kNC_CanSubscribe || + aArc == kNC_SupportsOffline || + aArc == kNC_CanFileMessages || + aArc == kNC_CanCreateSubfolders || + aArc == kNC_CanRename || + aArc == kNC_CanCompact || + aArc == kNC_TotalMessages || + aArc == kNC_TotalUnreadMessages || + aArc == kNC_FolderSize || + aArc == kNC_Charset || + aArc == kNC_BiffState || + aArc == kNC_Child || + aArc == kNC_NoSelect || + aArc == kNC_VirtualFolder || + aArc == kNC_InVFEditSearchScope || + aArc == kNC_ImapShared || + aArc == kNC_Synchronize || + aArc == kNC_SyncDisabled || + aArc == kNC_CanSearchMessages); + } + else + { + *result = false; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderDataSource::ArcLabelsIn(nsIRDFNode* node, + nsISimpleEnumerator** labels) +{ + return nsMsgRDFDataSource::ArcLabelsIn(node, labels); +} + +NS_IMETHODIMP nsMsgFolderDataSource::ArcLabelsOut(nsIRDFResource* source, + nsISimpleEnumerator** labels) +{ + nsresult rv = NS_RDF_NO_VALUE; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + if (NS_SUCCEEDED(rv)) + { + rv = NS_NewArrayEnumerator(labels, kFolderArcsOutArray); + } + else + { + rv = NS_NewEmptyEnumerator(labels); + } + + return rv; +} + +nsresult +nsMsgFolderDataSource::getFolderArcLabelsOut(nsCOMArray<nsIRDFResource> &aArcs) +{ + aArcs.AppendObject(kNC_Name); + aArcs.AppendObject(kNC_Open); + aArcs.AppendObject(kNC_FolderTreeName); + aArcs.AppendObject(kNC_FolderTreeSimpleName); + aArcs.AppendObject(kNC_SpecialFolder); + aArcs.AppendObject(kNC_ServerType); + aArcs.AppendObject(kNC_IsDeferred); + aArcs.AppendObject(kNC_CanCreateFoldersOnServer); + aArcs.AppendObject(kNC_CanFileMessagesOnServer); + aArcs.AppendObject(kNC_IsServer); + aArcs.AppendObject(kNC_IsSecure); + aArcs.AppendObject(kNC_CanSubscribe); + aArcs.AppendObject(kNC_SupportsOffline); + aArcs.AppendObject(kNC_CanFileMessages); + aArcs.AppendObject(kNC_CanCreateSubfolders); + aArcs.AppendObject(kNC_CanRename); + aArcs.AppendObject(kNC_CanCompact); + aArcs.AppendObject(kNC_TotalMessages); + aArcs.AppendObject(kNC_TotalUnreadMessages); + aArcs.AppendObject(kNC_FolderSize); + aArcs.AppendObject(kNC_Charset); + aArcs.AppendObject(kNC_BiffState); + aArcs.AppendObject(kNC_Child); + aArcs.AppendObject(kNC_NoSelect); + aArcs.AppendObject(kNC_VirtualFolder); + aArcs.AppendObject(kNC_InVFEditSearchScope); + aArcs.AppendObject(kNC_ImapShared); + aArcs.AppendObject(kNC_Synchronize); + aArcs.AppendObject(kNC_SyncDisabled); + aArcs.AppendObject(kNC_CanSearchMessages); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::GetAllResources(nsISimpleEnumerator** aCursor) +{ + NS_NOTYETIMPLEMENTED("sorry!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/*<nsIRDFResource>*/** commands) +{ + NS_NOTYETIMPLEMENTED("no one actually uses me"); + nsresult rv; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMutableArray> cmds = + do_CreateInstance(NS_ARRAY_CONTRACTID); + NS_ENSURE_STATE(cmds); + + cmds->AppendElement(kNC_Delete, false); + cmds->AppendElement(kNC_ReallyDelete, false); + cmds->AppendElement(kNC_NewFolder, false); + cmds->AppendElement(kNC_GetNewMessages, false); + cmds->AppendElement(kNC_Copy, false); + cmds->AppendElement(kNC_Move, false); + cmds->AppendElement(kNC_CopyFolder, false); + cmds->AppendElement(kNC_MoveFolder, false); + cmds->AppendElement(kNC_MarkAllMessagesRead, false); + cmds->AppendElement(kNC_Compact, false); + cmds->AppendElement(kNC_CompactAll, false); + cmds->AppendElement(kNC_Rename, false); + cmds->AppendElement(kNC_EmptyTrash, false); + + return cmds->Enumerate(commands); +} + +NS_IMETHODIMP +nsMsgFolderDataSource::IsCommandEnabled(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources, + nsIRDFResource* aCommand, + nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments, + bool* aResult) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder; + + nsCOMPtr<nsISupportsArray> sources = do_QueryInterface(aSources); + NS_ENSURE_STATE(sources); + + uint32_t cnt; + rv = sources->Count(&cnt); + if (NS_FAILED(rv)) return rv; + for (uint32_t i = 0; i < cnt; i++) + { + folder = do_QueryElementAt(sources, i, &rv); + if (NS_SUCCEEDED(rv)) + { + // we don't care about the arguments -- folder commands are always enabled + if (!((aCommand == kNC_Delete) || + (aCommand == kNC_ReallyDelete) || + (aCommand == kNC_NewFolder) || + (aCommand == kNC_Copy) || + (aCommand == kNC_Move) || + (aCommand == kNC_CopyFolder) || + (aCommand == kNC_MoveFolder) || + (aCommand == kNC_GetNewMessages) || + (aCommand == kNC_MarkAllMessagesRead) || + (aCommand == kNC_Compact) || + (aCommand == kNC_CompactAll) || + (aCommand == kNC_Rename) || + (aCommand == kNC_EmptyTrash))) + { + *aResult = false; + return NS_OK; + } + } + } + *aResult = true; + return NS_OK; // succeeded for all sources +} + +NS_IMETHODIMP +nsMsgFolderDataSource::DoCommand(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources, + nsIRDFResource* aCommand, + nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIMsgWindow> window; + + nsCOMPtr<nsISupportsArray> sources = do_QueryInterface(aSources); + NS_ENSURE_STATE(sources); + nsCOMPtr<nsISupportsArray> arguments = do_QueryInterface(aArguments); + + // callers can pass in the msgWindow as the last element of the arguments + // array. If they do, we'll use that as the msg window for progress, etc. + if (arguments) + { + uint32_t numArgs; + arguments->Count(&numArgs); + if (numArgs > 1) + window = do_QueryElementAt(arguments, numArgs - 1); + } + if (!window) + window = mWindow; + + // XXX need to handle batching of command applied to all sources + + uint32_t cnt = 0; + uint32_t i = 0; + + rv = sources->Count(&cnt); + if (NS_FAILED(rv)) return rv; + + for ( ; i < cnt; i++) + { + nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(sources, i, &rv); + if (NS_SUCCEEDED(rv)) + { + if (aCommand == kNC_Delete) + { + rv = DoDeleteFromFolder(folder, arguments, window, false); + } + if (aCommand == kNC_ReallyDelete) + { + rv = DoDeleteFromFolder(folder, arguments, window, true); + } + else if (aCommand == kNC_NewFolder) + { + rv = DoNewFolder(folder, arguments, window); + } + else if (aCommand == kNC_GetNewMessages) + { + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(arguments, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = server->GetNewMessages(folder, window, nullptr); + } + else if (aCommand == kNC_Copy) + { + rv = DoCopyToFolder(folder, arguments, window, false); + } + else if (aCommand == kNC_Move) + { + rv = DoCopyToFolder(folder, arguments, window, true); + } + else if (aCommand == kNC_CopyFolder) + { + rv = DoFolderCopyToFolder(folder, arguments, window, false); + } + else if (aCommand == kNC_MoveFolder) + { + rv = DoFolderCopyToFolder(folder, arguments, window, true); + } + else if (aCommand == kNC_MarkAllMessagesRead) + { + rv = folder->MarkAllMessagesRead(window); + } + else if (aCommand == kNC_Compact) + { + rv = folder->Compact(nullptr, window); + } + else if (aCommand == kNC_CompactAll) + { + // this will also compact offline stores for IMAP + rv = folder->CompactAll(nullptr, window, true); + } + else if (aCommand == kNC_EmptyTrash) + { + rv = folder->EmptyTrash(window, nullptr); + } + else if (aCommand == kNC_Rename) + { + nsCOMPtr<nsIRDFLiteral> literal = do_QueryElementAt(arguments, 0, &rv); + if(NS_SUCCEEDED(rv)) + { + nsString name; + literal->GetValue(getter_Copies(name)); + rv = folder->Rename(name, window); + } + } + } + else + { + rv = NS_ERROR_NOT_IMPLEMENTED; + } + } + //for the moment return NS_OK, because failure stops entire DoCommand process. + return rv; + //return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderDataSource::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + return OnItemAddedOrRemoved(parentItem, item, true); +} + +NS_IMETHODIMP nsMsgFolderDataSource::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item) +{ + return OnItemAddedOrRemoved(parentItem, item, false); +} + +nsresult nsMsgFolderDataSource::OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, bool added) +{ + nsCOMPtr<nsIRDFNode> itemNode(do_QueryInterface(item)); + if (itemNode) + { + nsCOMPtr<nsIRDFResource> parentResource(do_QueryInterface(parentItem)); + if (parentResource) // RDF is not happy about a null parent resource. + NotifyObservers(parentResource, kNC_Child, itemNode, nullptr, added, false); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::OnItemPropertyChanged(nsIMsgFolder *resource, + nsIAtom *property, + const char *oldValue, + const char *newValue) + +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::OnItemIntPropertyChanged(nsIMsgFolder *folder, + nsIAtom *property, + int64_t oldValue, + int64_t newValue) +{ + nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(folder)); + if (kTotalMessagesAtom == property) + OnTotalMessagePropertyChanged(resource, oldValue, newValue); + else if (kTotalUnreadMessagesAtom == property) + OnUnreadMessagePropertyChanged(resource, oldValue, newValue); + else if (kFolderSizeAtom == property) + OnFolderSizePropertyChanged(resource, oldValue, newValue); + else if (kSortOrderAtom == property) + OnFolderSortOrderPropertyChanged(resource, oldValue, newValue); + else if (kBiffStateAtom == property) { + // be careful about skipping if oldValue == newValue + // see the comment in nsMsgFolder::SetBiffState() about filters + + nsCOMPtr<nsIRDFNode> biffNode; + nsresult rv = createBiffStateNodeFromFlag(newValue, getter_AddRefs(biffNode)); + NS_ENSURE_SUCCESS(rv,rv); + + NotifyPropertyChanged(resource, kNC_BiffState, biffNode); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::OnItemUnicharPropertyChanged(nsIMsgFolder *folder, + nsIAtom *property, + const char16_t *oldValue, + const char16_t *newValue) +{ + nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(folder)); + if (kNameAtom == property) + { + int32_t numUnread; + folder->GetNumUnread(false, &numUnread); + NotifyFolderTreeNameChanged(folder, resource, numUnread); + NotifyFolderTreeSimpleNameChanged(folder, resource); + NotifyFolderNameChanged(folder, resource); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::OnItemBoolPropertyChanged(nsIMsgFolder *folder, + nsIAtom *property, + bool oldValue, + bool newValue) +{ + nsCOMPtr<nsIRDFResource> resource(do_QueryInterface(folder)); + if (newValue != oldValue) { + nsIRDFNode* literalNode = newValue?kTrueLiteral:kFalseLiteral; + nsIRDFNode* oldLiteralNode = oldValue?kTrueLiteral:kFalseLiteral; + if (kNewMessagesAtom == property) + NotifyPropertyChanged(resource, kNC_NewMessages, literalNode); + else if (kSynchronizeAtom == property) + NotifyPropertyChanged(resource, kNC_Synchronize, literalNode); + else if (kOpenAtom == property) + NotifyPropertyChanged(resource, kNC_Open, literalNode); + else if (kIsDeferredAtom == property) + NotifyPropertyChanged(resource, kNC_IsDeferred, literalNode, oldLiteralNode); + else if (kIsSecureAtom == property) + NotifyPropertyChanged(resource, kNC_IsSecure, literalNode, oldLiteralNode); + else if (kCanFileMessagesAtom == property) + NotifyPropertyChanged(resource, kNC_CanFileMessages, literalNode, oldLiteralNode); + else if (kInVFEditSearchScopeAtom == property) + NotifyPropertyChanged(resource, kNC_InVFEditSearchScope, literalNode); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, + nsIAtom *property, + uint32_t oldFlag, + uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderDataSource::OnItemEvent(nsIMsgFolder *aFolder, nsIAtom *aEvent) +{ + return NS_OK; +} + + +nsresult nsMsgFolderDataSource::createFolderNode(nsIMsgFolder* folder, + nsIRDFResource* property, + nsIRDFNode** target) +{ + nsresult rv = NS_RDF_NO_VALUE; + + if (kNC_NameSort == property) + rv = createFolderNameNode(folder, target, true); + else if(kNC_FolderTreeNameSort == property) + rv = createFolderNameNode(folder, target, true); + else if (kNC_Name == property) + rv = createFolderNameNode(folder, target, false); + else if(kNC_Open == property) + rv = createFolderOpenNode(folder, target); + else if (kNC_FolderTreeName == property) + rv = createFolderTreeNameNode(folder, target); + else if (kNC_FolderTreeSimpleName == property) + rv = createFolderTreeSimpleNameNode(folder, target); + else if (kNC_SpecialFolder == property) + rv = createFolderSpecialNode(folder,target); + else if (kNC_ServerType == property) + rv = createFolderServerTypeNode(folder, target); + else if (kNC_IsDeferred == property) + rv = createServerIsDeferredNode(folder, target); + else if (kNC_CanCreateFoldersOnServer == property) + rv = createFolderCanCreateFoldersOnServerNode(folder, target); + else if (kNC_CanFileMessagesOnServer == property) + rv = createFolderCanFileMessagesOnServerNode(folder, target); + else if (kNC_IsServer == property) + rv = createFolderIsServerNode(folder, target); + else if (kNC_IsSecure == property) + rv = createFolderIsSecureNode(folder, target); + else if (kNC_CanSubscribe == property) + rv = createFolderCanSubscribeNode(folder, target); + else if (kNC_SupportsOffline == property) + rv = createFolderSupportsOfflineNode(folder, target); + else if (kNC_CanFileMessages == property) + rv = createFolderCanFileMessagesNode(folder, target); + else if (kNC_CanCreateSubfolders == property) + rv = createFolderCanCreateSubfoldersNode(folder, target); + else if (kNC_CanRename == property) + rv = createFolderCanRenameNode(folder, target); + else if (kNC_CanCompact == property) + rv = createFolderCanCompactNode(folder, target); + else if (kNC_TotalMessages == property) + rv = createTotalMessagesNode(folder, target); + else if (kNC_TotalUnreadMessages == property) + rv = createUnreadMessagesNode(folder, target); + else if (kNC_FolderSize == property) + rv = createFolderSizeNode(folder, target); + else if (kNC_Charset == property) + rv = createCharsetNode(folder, target); + else if (kNC_BiffState == property) + rv = createBiffStateNodeFromFolder(folder, target); + else if (kNC_HasUnreadMessages == property) + rv = createHasUnreadMessagesNode(folder, false, target); + else if (kNC_NewMessages == property) + rv = createNewMessagesNode(folder, target); + else if (kNC_SubfoldersHaveUnreadMessages == property) + rv = createHasUnreadMessagesNode(folder, true, target); + else if (kNC_Child == property) + rv = createFolderChildNode(folder, target); + else if (kNC_NoSelect == property) + rv = createFolderNoSelectNode(folder, target); + else if (kNC_VirtualFolder == property) + rv = createFolderVirtualNode(folder, target); + else if (kNC_InVFEditSearchScope == property) + rv = createInVFEditSearchScopeNode(folder, target); + else if (kNC_ImapShared == property) + rv = createFolderImapSharedNode(folder, target); + else if (kNC_Synchronize == property) + rv = createFolderSynchronizeNode(folder, target); + else if (kNC_SyncDisabled == property) + rv = createFolderSyncDisabledNode(folder, target); + else if (kNC_CanSearchMessages == property) + rv = createCanSearchMessages(folder, target); + return NS_FAILED(rv) ? NS_RDF_NO_VALUE : rv; +} + +nsresult +nsMsgFolderDataSource::createFolderNameNode(nsIMsgFolder *folder, + nsIRDFNode **target, bool sort) +{ + nsresult rv; + if (sort) + { + uint8_t *sortKey=nullptr; + uint32_t sortKeyLength; + rv = folder->GetSortKey(&sortKeyLength, &sortKey); + NS_ENSURE_SUCCESS(rv, rv); + createBlobNode(sortKey, sortKeyLength, target, getRDFService()); + PR_Free(sortKey); + } + else + { + nsString name; + rv = folder->GetName(name); + if (NS_FAILED(rv)) + return rv; + createNode(name.get(), target, getRDFService()); + } + + return NS_OK; +} + +nsresult nsMsgFolderDataSource::GetFolderDisplayName(nsIMsgFolder *folder, nsString& folderName) +{ + return folder->GetAbbreviatedName(folderName); +} + +nsresult +nsMsgFolderDataSource::createFolderTreeNameNode(nsIMsgFolder *folder, + nsIRDFNode **target) +{ + nsString name; + nsresult rv = GetFolderDisplayName(folder, name); + if (NS_FAILED(rv)) return rv; + nsAutoString nameString(name); + int32_t unreadMessages; + + rv = folder->GetNumUnread(false, &unreadMessages); + if(NS_SUCCEEDED(rv)) + CreateUnreadMessagesNameString(unreadMessages, nameString); + + createNode(nameString.get(), target, getRDFService()); + return NS_OK; +} + +nsresult nsMsgFolderDataSource::createFolderTreeSimpleNameNode(nsIMsgFolder * folder, nsIRDFNode **target) +{ + nsString name; + nsresult rv = GetFolderDisplayName(folder, name); + if (NS_FAILED(rv)) return rv; + + createNode(name.get(), target, getRDFService()); + return NS_OK; +} + +nsresult nsMsgFolderDataSource::CreateUnreadMessagesNameString(int32_t unreadMessages, nsAutoString &nameString) +{ + //Only do this if unread messages are positive + if(unreadMessages > 0) + { + nameString.Append(NS_LITERAL_STRING(" (")); + nameString.AppendInt(unreadMessages); + nameString.Append(u')'); + } + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderSpecialNode(nsIMsgFolder *folder, + nsIRDFNode **target) +{ + uint32_t flags; + nsresult rv = folder->GetFlags(&flags); + if(NS_FAILED(rv)) + return rv; + + nsAutoString specialFolderString; + if (flags & nsMsgFolderFlags::Inbox) + specialFolderString.AssignLiteral("Inbox"); + else if (flags & nsMsgFolderFlags::Trash) + specialFolderString.AssignLiteral("Trash"); + else if (flags & nsMsgFolderFlags::Queue) + specialFolderString.AssignLiteral("Outbox"); + else if (flags & nsMsgFolderFlags::SentMail) + specialFolderString.AssignLiteral("Sent"); + else if (flags & nsMsgFolderFlags::Drafts) + specialFolderString.AssignLiteral("Drafts"); + else if (flags & nsMsgFolderFlags::Templates) + specialFolderString.AssignLiteral("Templates"); + else if (flags & nsMsgFolderFlags::Junk) + specialFolderString.AssignLiteral("Junk"); + else if (flags & nsMsgFolderFlags::Virtual) + specialFolderString.AssignLiteral("Virtual"); + else if (flags & nsMsgFolderFlags::Archive) + specialFolderString.AssignLiteral("Archives"); + else { + // XXX why do this at all? or just "" + specialFolderString.AssignLiteral("none"); + } + + createNode(specialFolderString.get(), target, getRDFService()); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderServerTypeNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE; + + nsCString serverType; + rv = server->GetType(serverType); + if (NS_FAILED(rv)) return rv; + + createNode(NS_ConvertASCIItoUTF16(serverType).get(), target, getRDFService()); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createServerIsDeferredNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + bool isDeferred = false; + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + folder->GetServer(getter_AddRefs(incomingServer)); + if (incomingServer) + { + nsCOMPtr <nsIPop3IncomingServer> pop3Server = do_QueryInterface(incomingServer); + if (pop3Server) + { + nsCString deferredToServer; + pop3Server->GetDeferredToAccount(deferredToServer); + isDeferred = !deferredToServer.IsEmpty(); + } + } + *target = (isDeferred) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderCanCreateFoldersOnServerNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE; + + bool canCreateFoldersOnServer; + rv = server->GetCanCreateFoldersOnServer(&canCreateFoldersOnServer); + if (NS_FAILED(rv)) return rv; + + if (canCreateFoldersOnServer) + *target = kTrueLiteral; + else + *target = kFalseLiteral; + NS_IF_ADDREF(*target); + + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderCanFileMessagesOnServerNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE; + + bool canFileMessagesOnServer; + rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer); + if (NS_FAILED(rv)) return rv; + + *target = (canFileMessagesOnServer) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + + return NS_OK; +} + + +nsresult +nsMsgFolderDataSource::createFolderIsServerNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool isServer; + rv = folder->GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + *target = nullptr; + + if (isServer) + *target = kTrueLiteral; + else + *target = kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderNoSelectNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool noSelect; + rv = folder->GetNoSelect(&noSelect); + if (NS_FAILED(rv)) return rv; + + *target = (noSelect) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createInVFEditSearchScopeNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + bool inVFEditSearchScope = false; + folder->GetInVFEditSearchScope(&inVFEditSearchScope); + + *target = inVFEditSearchScope ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderVirtualNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + + *target = (folderFlags & nsMsgFolderFlags::Virtual) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + + +nsresult +nsMsgFolderDataSource::createFolderImapSharedNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool imapShared; + rv = folder->GetImapShared(&imapShared); + if (NS_FAILED(rv)) return rv; + + *target = (imapShared) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + + +nsresult +nsMsgFolderDataSource::createFolderSynchronizeNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool sync; + rv = folder->GetFlag(nsMsgFolderFlags::Offline, &sync); + if (NS_FAILED(rv)) return rv; + + *target = nullptr; + + *target = (sync) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderSyncDisabledNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + + nsresult rv; + bool isServer; + nsCOMPtr<nsIMsgIncomingServer> server; + + rv = folder->GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + rv = folder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE; + + nsCString serverType; + rv = server->GetType(serverType); + if (NS_FAILED(rv)) return rv; + + *target = isServer || MsgLowerCaseEqualsLiteral(serverType, "none") || MsgLowerCaseEqualsLiteral(serverType, "pop3") ? + kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createCanSearchMessages(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) return NS_ERROR_FAILURE; + + bool canSearchMessages; + rv = server->GetCanSearchMessages(&canSearchMessages); + if (NS_FAILED(rv)) return rv; + + *target = (canSearchMessages) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderOpenNode(nsIMsgFolder *folder, nsIRDFNode **target) +{ + NS_ENSURE_ARG_POINTER(target); + + // call GetSubFolders() to ensure mFlags is set correctly + // from the folder cache on startup + nsCOMPtr<nsISimpleEnumerator> subFolders; + nsresult rv = folder->GetSubFolders(getter_AddRefs(subFolders)); + if (NS_FAILED(rv)) + return NS_RDF_NO_VALUE; + + bool closed; + rv = folder->GetFlag(nsMsgFolderFlags::Elided, &closed); + if (NS_FAILED(rv)) + return rv; + + *target = (closed) ? kFalseLiteral : kTrueLiteral; + + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderIsSecureNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool isSecure = false; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + + if (NS_SUCCEEDED(rv) && server) { + rv = server->GetIsSecure(&isSecure); + NS_ENSURE_SUCCESS(rv, rv); + } + + *target = (isSecure) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + + +nsresult +nsMsgFolderDataSource::createFolderCanSubscribeNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool canSubscribe; + rv = folder->GetCanSubscribe(&canSubscribe); + if (NS_FAILED(rv)) return rv; + + *target = (canSubscribe) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderSupportsOfflineNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool supportsOffline; + rv = folder->GetSupportsOffline(&supportsOffline); + NS_ENSURE_SUCCESS(rv,rv); + + *target = (supportsOffline) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderCanFileMessagesNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool canFileMessages; + rv = folder->GetCanFileMessages(&canFileMessages); + if (NS_FAILED(rv)) return rv; + + *target = (canFileMessages) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderCanCreateSubfoldersNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + nsresult rv; + bool canCreateSubfolders; + rv = folder->GetCanCreateSubfolders(&canCreateSubfolders); + if (NS_FAILED(rv)) return rv; + + *target = (canCreateSubfolders) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderCanRenameNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + bool canRename; + nsresult rv = folder->GetCanRename(&canRename); + if (NS_FAILED(rv)) return rv; + + *target = (canRename) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderCanCompactNode(nsIMsgFolder* folder, + nsIRDFNode **target) +{ + bool canCompact; + nsresult rv = folder->GetCanCompact(&canCompact); + if (NS_FAILED(rv)) return rv; + + *target = (canCompact) ? kTrueLiteral : kFalseLiteral; + NS_IF_ADDREF(*target); + return NS_OK; +} + + +nsresult +nsMsgFolderDataSource::createTotalMessagesNode(nsIMsgFolder *folder, + nsIRDFNode **target) +{ + + bool isServer; + nsresult rv = folder->GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + int32_t totalMessages; + if(isServer) + totalMessages = kDisplayBlankCount; + else + { + rv = folder->GetTotalMessages(false, &totalMessages); + if(NS_FAILED(rv)) return rv; + } + GetNumMessagesNode(totalMessages, target); + + return rv; +} + +nsresult +nsMsgFolderDataSource::createFolderSizeNode(nsIMsgFolder *folder, nsIRDFNode **target) +{ + bool isServer; + nsresult rv = folder->GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t folderSize; + if (isServer) { + folderSize = kDisplayBlankCount64; + } + else + { + // XXX todo, we are asserting here for news + // for offline news, we'd know the size on disk, right? Yes, bug 851275. + rv = folder->GetSizeOnDisk(&folderSize); + NS_ENSURE_SUCCESS(rv, rv); + } + + return GetFolderSizeNode(folderSize, target); +} + +nsresult +nsMsgFolderDataSource::createCharsetNode(nsIMsgFolder *folder, nsIRDFNode **target) +{ + nsCString charset; + nsresult rv = folder->GetCharset(charset); + if (NS_SUCCEEDED(rv)) + createNode(NS_ConvertASCIItoUTF16(charset).get(), target, getRDFService()); + else + createNode(EmptyString().get(), target, getRDFService()); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createBiffStateNodeFromFolder(nsIMsgFolder *folder, nsIRDFNode **target) +{ + uint32_t biffState; + nsresult rv = folder->GetBiffState(&biffState); + if(NS_FAILED(rv)) return rv; + + rv = createBiffStateNodeFromFlag(biffState, target); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createBiffStateNodeFromFlag(uint32_t flag, nsIRDFNode **target) +{ + const char16_t *biffStateStr; + + switch (flag) { + case nsIMsgFolder::nsMsgBiffState_NewMail: + biffStateStr = u"NewMail"; + break; + case nsIMsgFolder::nsMsgBiffState_NoMail: + biffStateStr = u"NoMail"; + break; + default: + biffStateStr = u"UnknownMail"; + break; + } + + createNode(biffStateStr, target, getRDFService()); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createUnreadMessagesNode(nsIMsgFolder *folder, + nsIRDFNode **target) +{ + bool isServer; + nsresult rv = folder->GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + int32_t totalUnreadMessages; + if(isServer) + totalUnreadMessages = kDisplayBlankCount; + else + { + rv = folder->GetNumUnread(false, &totalUnreadMessages); + if(NS_FAILED(rv)) return rv; + } + GetNumMessagesNode(totalUnreadMessages, target); + + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createHasUnreadMessagesNode(nsIMsgFolder *folder, bool aIncludeSubfolders, nsIRDFNode **target) +{ + bool isServer; + nsresult rv = folder->GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + *target = kFalseLiteral; + + int32_t totalUnreadMessages; + if(!isServer) + { + rv = folder->GetNumUnread(aIncludeSubfolders, &totalUnreadMessages); + if(NS_FAILED(rv)) return rv; + // if we're including sub-folders, we're trying to find out if child folders + // have unread. If so, we subtract the unread msgs in the current folder. + if (aIncludeSubfolders) + { + int32_t numUnreadInFolder; + rv = folder->GetNumUnread(false, &numUnreadInFolder); + NS_ENSURE_SUCCESS(rv, rv); + // don't subtract if numUnread is negative (which means we don't know the unread count) + if (numUnreadInFolder > 0) + totalUnreadMessages -= numUnreadInFolder; + } + *target = (totalUnreadMessages > 0) ? kTrueLiteral : kFalseLiteral; + } + + NS_IF_ADDREF(*target); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::OnUnreadMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue) +{ + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(folderResource); + if(folder) + { + //First send a regular unread message changed notification + nsCOMPtr<nsIRDFNode> newNode; + + GetNumMessagesNode(newValue, getter_AddRefs(newNode)); + NotifyPropertyChanged(folderResource, kNC_TotalUnreadMessages, newNode); + + //Now see if hasUnreadMessages has changed + if(oldValue <=0 && newValue >0) + { + NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, kTrueLiteral); + NotifyAncestors(folder, kNC_SubfoldersHaveUnreadMessages, kTrueLiteral); + } + else if(oldValue > 0 && newValue <= 0) + { + NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, kFalseLiteral); + // this isn't quite right - parents could still have other children with + // unread messages. NotifyAncestors will have to figure that out... + NotifyAncestors(folder, kNC_SubfoldersHaveUnreadMessages, kFalseLiteral); + } + + //We will have to change the folderTreeName if the unread column is hidden + NotifyFolderTreeNameChanged(folder, folderResource, newValue); + } + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::NotifyFolderNameChanged(nsIMsgFolder* aFolder, nsIRDFResource *folderResource) +{ + nsString name; + nsresult rv = aFolder->GetName(name); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIRDFNode> newNameNode; + createNode(name.get(), getter_AddRefs(newNameNode), getRDFService()); + NotifyPropertyChanged(folderResource, kNC_Name, newNameNode); + } + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::NotifyFolderTreeSimpleNameChanged(nsIMsgFolder* aFolder, nsIRDFResource *folderResource) +{ + nsString abbreviatedName; + nsresult rv = GetFolderDisplayName(aFolder, abbreviatedName); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIRDFNode> newNameNode; + createNode(abbreviatedName.get(), getter_AddRefs(newNameNode), getRDFService()); + NotifyPropertyChanged(folderResource, kNC_FolderTreeSimpleName, newNameNode); + } + + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::NotifyFolderTreeNameChanged(nsIMsgFolder* aFolder, + nsIRDFResource* aFolderResource, + int32_t aUnreadMessages) +{ + nsString name; + nsresult rv = GetFolderDisplayName(aFolder, name); + if (NS_SUCCEEDED(rv)) { + nsAutoString newNameString(name); + CreateUnreadMessagesNameString(aUnreadMessages, newNameString); + nsCOMPtr<nsIRDFNode> newNameNode; + createNode(newNameString.get(), getter_AddRefs(newNameNode), getRDFService()); + NotifyPropertyChanged(aFolderResource, kNC_FolderTreeName, newNameNode); + } + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::NotifyAncestors(nsIMsgFolder *aFolder, nsIRDFResource *aPropertyResource, nsIRDFNode *aNode) +{ + bool isServer = false; + nsresult rv = aFolder->GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv,rv); + + if (isServer) + // done, stop + return NS_OK; + + nsCOMPtr <nsIMsgFolder> parentMsgFolder; + rv = aFolder->GetParent(getter_AddRefs(parentMsgFolder)); + NS_ENSURE_SUCCESS(rv,rv); + if (!parentMsgFolder) + return NS_OK; + + rv = parentMsgFolder->GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv,rv); + + // don't need to notify servers either. + if (isServer) + // done, stop + return NS_OK; + + nsCOMPtr<nsIRDFResource> parentFolderResource = do_QueryInterface(parentMsgFolder,&rv); + NS_ENSURE_SUCCESS(rv,rv); + + // if we're setting the subFoldersHaveUnreadMessages property to false, check + // if the folder really doesn't have subfolders with unread messages. + if (aPropertyResource == kNC_SubfoldersHaveUnreadMessages && aNode == kFalseLiteral) + { + nsCOMPtr <nsIRDFNode> unreadMsgsNode; + createHasUnreadMessagesNode(parentMsgFolder, true, getter_AddRefs(unreadMsgsNode)); + aNode = unreadMsgsNode; + } + NotifyPropertyChanged(parentFolderResource, aPropertyResource, aNode); + + return NotifyAncestors(parentMsgFolder, aPropertyResource, aNode); +} + +// New Messages + +nsresult +nsMsgFolderDataSource::createNewMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target) +{ + + nsresult rv; + + bool isServer; + rv = folder->GetIsServer(&isServer); + if (NS_FAILED(rv)) return rv; + + *target = kFalseLiteral; + + //int32_t totalNewMessages; + bool isNewMessages; + if(!isServer) + { + rv = folder->GetHasNewMessages(&isNewMessages); + if(NS_FAILED(rv)) return rv; + *target = (isNewMessages) ? kTrueLiteral : kFalseLiteral; + } + NS_IF_ADDREF(*target); + return NS_OK; +} + +/** +nsresult +nsMsgFolderDataSource::OnUnreadMessagePropertyChanged(nsIMsgFolder *folder, int32_t oldValue, int32_t newValue) +{ + nsCOMPtr<nsIRDFResource> folderResource = do_QueryInterface(folder); + if(folderResource) + { + //First send a regular unread message changed notification + nsCOMPtr<nsIRDFNode> newNode; + + GetNumMessagesNode(newValue, getter_AddRefs(newNode)); + NotifyPropertyChanged(folderResource, kNC_TotalUnreadMessages, newNode); + + //Now see if hasUnreadMessages has changed + nsCOMPtr<nsIRDFNode> oldHasUnreadMessages; + nsCOMPtr<nsIRDFNode> newHasUnreadMessages; + if(oldValue <=0 && newValue >0) + { + oldHasUnreadMessages = kFalseLiteral; + newHasUnreadMessages = kTrueLiteral; + NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, newHasUnreadMessages); + } + else if(oldValue > 0 && newValue <= 0) + { + newHasUnreadMessages = kFalseLiteral; + NotifyPropertyChanged(folderResource, kNC_HasUnreadMessages, newHasUnreadMessages); + } + } + return NS_OK; +} + +**/ +nsresult +nsMsgFolderDataSource::OnFolderSortOrderPropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue) +{ + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(folderResource)); + if (folder) + { + nsCOMPtr<nsIRDFNode> newNode; + createFolderNameNode(folder, getter_AddRefs(newNode), true); + NotifyPropertyChanged(folderResource, kNC_FolderTreeNameSort, newNode); + } + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::OnFolderSizePropertyChanged(nsIRDFResource *folderResource, int64_t oldValue, int64_t newValue) +{ + nsCOMPtr<nsIRDFNode> newNode; + GetFolderSizeNode(newValue, getter_AddRefs(newNode)); + NotifyPropertyChanged(folderResource, kNC_FolderSize, newNode); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::OnTotalMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue) +{ + nsCOMPtr<nsIRDFNode> newNode; + GetNumMessagesNode(newValue, getter_AddRefs(newNode)); + NotifyPropertyChanged(folderResource, kNC_TotalMessages, newNode); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::GetNumMessagesNode(int32_t aNumMessages, nsIRDFNode **node) +{ + uint32_t numMessages = aNumMessages; + if(numMessages == kDisplayQuestionCount) + createNode(u"???", node, getRDFService()); + else if (numMessages == kDisplayBlankCount || numMessages == 0) + createNode(EmptyString().get(), node, getRDFService()); + else + createIntNode(numMessages, node, getRDFService()); + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::GetFolderSizeNode(int64_t aFolderSize, nsIRDFNode **aNode) +{ + nsresult rv; + if (aFolderSize == kDisplayBlankCount64 || aFolderSize == 0) + createNode(EmptyString().get(), aNode, getRDFService()); + else if (aFolderSize == kDisplayQuestionCount64) + createNode(u"???", aNode, getRDFService()); + else + { + nsAutoString sizeString; + rv = FormatFileSize(aFolderSize, true, sizeString); + NS_ENSURE_SUCCESS(rv, rv); + + createNode(sizeString.get(), aNode, getRDFService()); + } + return NS_OK; +} + +nsresult +nsMsgFolderDataSource::createFolderChildNode(nsIMsgFolder *folder, + nsIRDFNode **target) +{ + nsCOMPtr<nsISimpleEnumerator> subFolders; + nsresult rv = folder->GetSubFolders(getter_AddRefs(subFolders)); + if (NS_FAILED(rv)) + return NS_RDF_NO_VALUE; + + bool hasMore; + rv = subFolders->HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) + return NS_RDF_NO_VALUE; + + nsCOMPtr<nsISupports> firstFolder; + rv = subFolders->GetNext(getter_AddRefs(firstFolder)); + if (NS_FAILED(rv)) + return NS_RDF_NO_VALUE; + + return CallQueryInterface(firstFolder, target); +} + + +nsresult nsMsgFolderDataSource::DoCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments, + nsIMsgWindow *msgWindow, bool isMove) +{ + nsresult rv; + uint32_t itemCount; + rv = arguments->Count(&itemCount); + NS_ENSURE_SUCCESS(rv, rv); + + //need source folder and at least one item to copy + if(itemCount < 2) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryElementAt(arguments, 0)); + if(!srcFolder) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + // Remove first element + for(uint32_t i = 1; i < itemCount; i++) + { + nsCOMPtr<nsIMsgDBHdr> message(do_QueryElementAt(arguments, i)); + if (message) + { + messageArray->AppendElement(message, false); + } + } + + //Call copyservice with dstFolder, srcFolder, messages, isMove, and txnManager + nsCOMPtr<nsIMsgCopyService> copyService = + do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + return copyService->CopyMessages(srcFolder, messageArray, dstFolder, isMove, + nullptr, msgWindow, true/* allowUndo */); +} + +nsresult nsMsgFolderDataSource::DoFolderCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments, + nsIMsgWindow *msgWindow, bool isMoveFolder) +{ + nsresult rv; + uint32_t itemCount; + rv = arguments->Count(&itemCount); + NS_ENSURE_SUCCESS(rv, rv); + + //need at least one item to copy + if(itemCount < 1) + return NS_ERROR_FAILURE; + + if (!isMoveFolder) // copy folder not on the same server + { + // Create an nsIMutableArray from the nsISupportsArray + nsCOMPtr<nsIMutableArray> folderArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + for (uint32_t i = 0; i < itemCount; i++) + { + nsCOMPtr<nsISupports> element(do_QueryElementAt(arguments, i, &rv)); + if (NS_SUCCEEDED(rv)) + folderArray->AppendElement(element, false); + } + + //Call copyservice with dstFolder, srcFolder, folders and isMoveFolder + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + if(NS_SUCCEEDED(rv)) + { + rv = copyService->CopyFolders(folderArray, dstFolder, isMoveFolder, + nullptr, msgWindow); + + } + } + else //within the same server therefore no need for copy service + { + + nsCOMPtr<nsIMsgFolder> msgFolder; + for (uint32_t i=0;i< itemCount; i++) + { + msgFolder = do_QueryElementAt(arguments, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = dstFolder->CopyFolder(msgFolder, isMoveFolder , msgWindow, nullptr); + NS_ASSERTION((NS_SUCCEEDED(rv)),"Copy folder failed."); + } + } + } + + return rv; + //return NS_OK; +} + +nsresult nsMsgFolderDataSource::DoDeleteFromFolder(nsIMsgFolder *folder, nsISupportsArray *arguments, + nsIMsgWindow *msgWindow, bool reallyDelete) +{ + nsresult rv = NS_OK; + uint32_t itemCount; + rv = arguments->Count(&itemCount); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + nsCOMPtr<nsIMutableArray> folderArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + + //Split up deleted items into different type arrays to be passed to the folder + //for deletion. + for(uint32_t item = 0; item < itemCount; item++) + { + nsCOMPtr<nsISupports> supports(do_QueryElementAt(arguments, item)); + nsCOMPtr<nsIMsgDBHdr> deletedMessage(do_QueryInterface(supports)); + nsCOMPtr<nsIMsgFolder> deletedFolder(do_QueryInterface(supports)); + if (deletedMessage) + { + messageArray->AppendElement(supports, false); + } + else if(deletedFolder) + { + folderArray->AppendElement(supports, false); + } + } + uint32_t cnt; + rv = messageArray->GetLength(&cnt); + if (NS_FAILED(rv)) return rv; + if (cnt > 0) + rv = folder->DeleteMessages(messageArray, msgWindow, reallyDelete, false, nullptr, true /*allowUndo*/); + + rv = folderArray->GetLength(&cnt); + if (NS_FAILED(rv)) return rv; + if (cnt > 0) + { + nsCOMPtr<nsIMsgFolder> folderToDelete = do_QueryElementAt(folderArray, 0); + uint32_t folderFlags = 0; + if (folderToDelete) + { + folderToDelete->GetFlags(&folderFlags); + if (folderFlags & nsMsgFolderFlags::Virtual) + { + NS_ENSURE_ARG_POINTER(msgWindow); + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> sMessengerStringBundle; + nsString confirmMsg; + rv = sBundleService->CreateBundle(MESSENGER_STRING_URL, getter_AddRefs(sMessengerStringBundle)); + NS_ENSURE_SUCCESS(rv, rv); + sMessengerStringBundle->GetStringFromName(u"confirmSavedSearchDeleteMessage", getter_Copies(confirmMsg)); + + nsCOMPtr<nsIPrompt> dialog; + rv = msgWindow->GetPromptDialog(getter_AddRefs(dialog)); + if (NS_SUCCEEDED(rv)) + { + bool dialogResult; + rv = dialog->Confirm(nullptr, confirmMsg.get(), &dialogResult); + if (!dialogResult) + return NS_OK; + } + } + } + rv = folder->DeleteSubFolders(folderArray, msgWindow); + } + return rv; +} + +nsresult nsMsgFolderDataSource::DoNewFolder(nsIMsgFolder *folder, nsISupportsArray *arguments, nsIMsgWindow *window) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIRDFLiteral> literal = do_QueryElementAt(arguments, 0, &rv); + if(NS_SUCCEEDED(rv)) + { + nsString name; + literal->GetValue(getter_Copies(name)); + rv = folder->CreateSubfolder(name, window); + } + return rv; +} + +nsresult nsMsgFolderDataSource::DoFolderAssert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target) +{ + nsresult rv = NS_ERROR_FAILURE; + + if (kNC_Charset == property) + { + nsCOMPtr<nsIRDFLiteral> literal(do_QueryInterface(target)); + if(literal) + { + const char16_t* value; + rv = literal->GetValueConst(&value); + if(NS_SUCCEEDED(rv)) + rv = folder->SetCharset(NS_LossyConvertUTF16toASCII(value)); + } + else + rv = NS_ERROR_FAILURE; + } + else if (kNC_Open == property && target == kTrueLiteral) + rv = folder->ClearFlag(nsMsgFolderFlags::Elided); + + return rv; +} + +nsresult nsMsgFolderDataSource::DoFolderUnassert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target) +{ + nsresult rv = NS_ERROR_FAILURE; + + if((kNC_Open == property) && target == kTrueLiteral) + rv = folder->SetFlag(nsMsgFolderFlags::Elided); + + return rv; +} + +nsresult nsMsgFolderDataSource::DoFolderHasAssertion(nsIMsgFolder *folder, + nsIRDFResource *property, + nsIRDFNode *target, + bool tv, + bool *hasAssertion) +{ + nsresult rv = NS_OK; + if(!hasAssertion) + return NS_ERROR_NULL_POINTER; + + //We're not keeping track of negative assertions on folders. + if(!tv) + { + *hasAssertion = false; + return NS_OK; + } + + if (kNC_Child == property) + { + nsCOMPtr<nsIMsgFolder> childFolder(do_QueryInterface(target, &rv)); + if(NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgFolder> childsParent; + rv = childFolder->GetParent(getter_AddRefs(childsParent)); + *hasAssertion = (NS_SUCCEEDED(rv) && childsParent && folder + && (childsParent.get() == folder)); + } + } + else if ((kNC_Name == property) || + (kNC_Open == property) || + (kNC_FolderTreeName == property) || + (kNC_FolderTreeSimpleName == property) || + (kNC_SpecialFolder == property) || + (kNC_ServerType == property) || + (kNC_IsDeferred == property) || + (kNC_CanCreateFoldersOnServer == property) || + (kNC_CanFileMessagesOnServer == property) || + (kNC_IsServer == property) || + (kNC_IsSecure == property) || + (kNC_CanSubscribe == property) || + (kNC_SupportsOffline == property) || + (kNC_CanFileMessages == property) || + (kNC_CanCreateSubfolders == property) || + (kNC_CanRename == property) || + (kNC_CanCompact == property) || + (kNC_TotalMessages == property) || + (kNC_TotalUnreadMessages == property) || + (kNC_FolderSize == property) || + (kNC_Charset == property) || + (kNC_BiffState == property) || + (kNC_HasUnreadMessages == property) || + (kNC_NoSelect == property) || + (kNC_Synchronize == property) || + (kNC_SyncDisabled == property) || + (kNC_VirtualFolder == property) || + (kNC_CanSearchMessages == property)) + { + nsCOMPtr<nsIRDFResource> folderResource(do_QueryInterface(folder, &rv)); + + if(NS_FAILED(rv)) + return rv; + + rv = GetTargetHasAssertion(this, folderResource, property, tv, target, hasAssertion); + } + else + *hasAssertion = false; + return rv; +} + +nsMsgFlatFolderDataSource::nsMsgFlatFolderDataSource() +{ + m_builtFolders = false; +} + +nsMsgFlatFolderDataSource::~nsMsgFlatFolderDataSource() +{ +} + +nsresult nsMsgFlatFolderDataSource::Init() +{ + nsIRDFService* rdf = getRDFService(); + NS_ENSURE_TRUE(rdf, NS_ERROR_FAILURE); + nsCOMPtr<nsIRDFResource> source; + nsAutoCString dsUri(m_dsName); + dsUri.Append(":/"); + rdf->GetResource(dsUri, getter_AddRefs(m_rootResource)); + + return nsMsgFolderDataSource::Init(); +} + +void nsMsgFlatFolderDataSource::Cleanup() +{ + m_folders.Clear(); + m_builtFolders = false; + nsMsgFolderDataSource::Cleanup(); +} + +NS_IMETHODIMP nsMsgFlatFolderDataSource::GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) +{ + return (property == kNC_Child) + ? NS_RDF_NO_VALUE + : nsMsgFolderDataSource::GetTarget(source, property, tv, target); +} + + +NS_IMETHODIMP nsMsgFlatFolderDataSource::GetTargets(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsISimpleEnumerator** targets) +{ + if (kNC_Child != property) + return nsMsgFolderDataSource::GetTargets(source, property, tv, targets); + + if(!targets) + return NS_ERROR_NULL_POINTER; + + if (ResourceIsOurRoot(source)) + { + EnsureFolders(); + return NS_NewArrayEnumerator(targets, m_folders); + } + return NS_NewSingletonEnumerator(targets, property); +} + +void nsMsgFlatFolderDataSource::EnsureFolders() +{ + if (m_builtFolders) + return; + + m_builtFolders = true; // in case something goes wrong + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIArray> allFolders; + rv = accountManager->GetAllFolders(getter_AddRefs(allFolders)); + if (NS_FAILED(rv) || !allFolders) + return; + + uint32_t count; + rv = allFolders->GetLength(&count); + NS_ENSURE_SUCCESS_VOID(rv); + + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> curFolder = do_QueryElementAt(allFolders, i); + if (WantsThisFolder(curFolder)) + m_folders.AppendObject(curFolder); + } +} + + +NS_IMETHODIMP nsMsgFlatFolderDataSource::GetURI(char* *aUri) +{ + nsAutoCString uri("rdf:"); + uri.Append(m_dsName); + return (*aUri = ToNewCString(uri)) + ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgFlatFolderDataSource::HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) +{ + nsresult rv; + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + // we need to check if the folder belongs in this datasource. + if (NS_SUCCEEDED(rv) && property != kNC_Open && property != kNC_Child) + { + if (WantsThisFolder(folder) && (kNC_Child != property)) + return DoFolderHasAssertion(folder, property, target, tv, hasAssertion); + } + else if (property == kNC_Child && ResourceIsOurRoot(source)) // check if source is us + { + folder = do_QueryInterface(target); + if (folder) + { + nsCOMPtr<nsIMsgFolder> parentMsgFolder; + folder->GetParent(getter_AddRefs(parentMsgFolder)); + // a folder without a parent must be getting deleted as part of + // the rename operation and is thus a folder we are + // no longer interested in + if (parentMsgFolder && WantsThisFolder(folder)) + { + *hasAssertion = true; + return NS_OK; + } + } + } + *hasAssertion = false; + return NS_OK; +} + +nsresult nsMsgFlatFolderDataSource::OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, bool added) +{ + // When a folder is added or removed, parentItem is the parent folder and item is the folder being + // added or removed. In a flat data source, there is no arc in the graph between the parent folder + // and the folder being added or removed. Our flat data source root (i.e. mailnewsunreadfolders:/) has + // an arc with the child property to every folder in the data source. We must change parentItem + // to be our data source root before calling nsMsgFolderDataSource::OnItemAddedOrRemoved. This ensures + // that datasource listeners such as the template builder properly handle add and remove + // notifications on the flat datasource. + nsCOMPtr<nsIRDFNode> itemNode(do_QueryInterface(item)); + if (itemNode) + NotifyObservers(m_rootResource, kNC_Child, itemNode, nullptr, added, false); + return NS_OK; +} + +bool nsMsgFlatFolderDataSource::ResourceIsOurRoot(nsIRDFResource *resource) +{ + return m_rootResource.get() == resource; +} + +bool nsMsgFlatFolderDataSource::WantsThisFolder(nsIMsgFolder *folder) +{ + EnsureFolders(); + return m_folders.Contains(folder); +} + +nsresult nsMsgFlatFolderDataSource::GetFolderDisplayName(nsIMsgFolder *folder, nsString& folderName) +{ + folder->GetName(folderName); + uint32_t foldersCount = m_folders.Count(); + nsString otherFolderName; + for (uint32_t index = 0; index < foldersCount; index++) + { + if (folder == m_folders[index]) // ignore ourselves. + continue; + m_folders[index]->GetName(otherFolderName); + if (otherFolderName.Equals(folderName)) + { + nsCOMPtr <nsIMsgIncomingServer> server; + folder->GetServer(getter_AddRefs(server)); + if (server) + { + nsString serverName; + server->GetPrettyName(serverName); + folderName.AppendLiteral(" - "); + folderName.Append(serverName); + return NS_OK; + } + } + } + // check if folder name is unique - if not, append account name + return folder->GetAbbreviatedName(folderName); +} + + +bool nsMsgUnreadFoldersDataSource::WantsThisFolder(nsIMsgFolder *folder) +{ + int32_t numUnread; + folder->GetNumUnread(false, &numUnread); + return numUnread > 0; +} + +nsresult nsMsgUnreadFoldersDataSource::NotifyPropertyChanged(nsIRDFResource *resource, + nsIRDFResource *property, nsIRDFNode *newNode, + nsIRDFNode *oldNode) +{ + // check if it's the has unread property that's changed; if so, see if we need + // to add this folder to the view. + // Then, call base class. + if (kNC_HasUnreadMessages == property) + { + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(resource)); + if (folder) + { + int32_t numUnread; + folder->GetNumUnread(false, &numUnread); + if (numUnread > 0) + { + if (!m_folders.Contains(folder)) + m_folders.AppendObject(folder); + NotifyObservers(kNC_UnreadFolders, kNC_Child, resource, nullptr, true, false); + } + } + } + return nsMsgFolderDataSource::NotifyPropertyChanged(resource, property, + newNode, oldNode); +} + +bool nsMsgFavoriteFoldersDataSource::WantsThisFolder(nsIMsgFolder *folder) +{ + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + return folderFlags & nsMsgFolderFlags::Favorite; +} + + +void nsMsgRecentFoldersDataSource::Cleanup() +{ + m_cutOffDate = 0; + nsMsgFlatFolderDataSource::Cleanup(); +} + + +void nsMsgRecentFoldersDataSource::EnsureFolders() +{ + if (m_builtFolders) + return; + + m_builtFolders = true; // in case something goes wrong + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIArray> allFolders; + rv = accountManager->GetAllFolders(getter_AddRefs(allFolders)); + if (NS_FAILED(rv) || !allFolders) + return; + + uint32_t count; + rv = allFolders->GetLength(&count); + NS_ENSURE_SUCCESS_VOID(rv); + + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> curFolder = do_QueryElementAt(allFolders, i); + nsCString dateStr; + curFolder->GetStringProperty(MRU_TIME_PROPERTY, dateStr); + uint32_t curFolderDate = (uint32_t) dateStr.ToInteger(&rv); + if (NS_FAILED(rv)) + curFolderDate = 0; + + if (curFolderDate > m_cutOffDate) + { + // if m_folders is "full", replace oldest folder with this folder, + // and adjust m_cutOffDate so that it's the mrutime + // of the "new" oldest folder. + uint32_t curFaveFoldersCount = m_folders.Count(); + if (curFaveFoldersCount > m_maxNumFolders) + { + uint32_t indexOfOldestFolder = 0; + uint32_t oldestFaveDate = 0; + uint32_t newOldestFaveDate = 0; + for (uint32_t index = 0; index < curFaveFoldersCount; ) + { + nsCString curFaveFolderDateStr; + m_folders[index]->GetStringProperty(MRU_TIME_PROPERTY, curFaveFolderDateStr); + uint32_t curFaveFolderDate = (uint32_t) curFaveFolderDateStr.ToInteger(&rv); + if (!oldestFaveDate || curFaveFolderDate < oldestFaveDate) + { + indexOfOldestFolder = index; + newOldestFaveDate = oldestFaveDate; + oldestFaveDate = curFaveFolderDate; + } + if (!newOldestFaveDate || (index != indexOfOldestFolder + && curFaveFolderDate < newOldestFaveDate)) { + newOldestFaveDate = curFaveFolderDate; + } + index++; + } + if (curFolderDate > oldestFaveDate && !m_folders.Contains(curFolder)) + m_folders.ReplaceObjectAt(curFolder, indexOfOldestFolder); + + NS_ASSERTION(newOldestFaveDate >= m_cutOffDate, "cutoff date should be getting bigger"); + m_cutOffDate = newOldestFaveDate; + } + else if (!m_folders.Contains(curFolder)) + m_folders.AppendObject(curFolder); + } +#ifdef DEBUG_David_Bienvenu + else + { + for (uint32_t index = 0; index < m_folders.Count(); index++) + { + nsCString curFaveFolderDateStr; + m_folders[index]->GetStringProperty(MRU_TIME_PROPERTY, curFaveFolderDateStr); + uint32_t curFaveFolderDate = (uint32_t) curFaveFolderDateStr.ToInteger(&rv); + NS_ASSERTION(curFaveFolderDate > curFolderDate, "folder newer then faves but not added"); + } + } +#endif + } +} + +NS_IMETHODIMP nsMsgRecentFoldersDataSource::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + // if we've already built the recent folder array, we should add this item to the array + // since just added items are by definition new. + // I think this means newly discovered imap folders (ones w/o msf files) will + // get added, but maybe that's OK. + if (m_builtFolders) + { + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item)); + if (folder && !m_folders.Contains(folder)) + { + m_folders.AppendObject(folder); + nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(item); + NotifyObservers(kNC_RecentFolders, kNC_Child, resource, nullptr, true, false); + } + } + return nsMsgFlatFolderDataSource::OnItemAdded(parentItem, item); +} + +nsresult nsMsgRecentFoldersDataSource::NotifyPropertyChanged(nsIRDFResource *resource, + nsIRDFResource *property, nsIRDFNode *newNode, + nsIRDFNode *oldNode) +{ + // check if it's the has new property that's changed; if so, see if we need + // to add this folder to the view. + // Then, call base class. + if (kNC_NewMessages == property) + { + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(resource)); + if (folder) + { + bool hasNewMessages; + folder->GetHasNewMessages(&hasNewMessages); + if (hasNewMessages) + { + if (!m_folders.Contains(folder)) + { + m_folders.AppendObject(folder); + NotifyObservers(kNC_RecentFolders, kNC_Child, resource, nullptr, true, false); + } + } + } + } + return nsMsgFolderDataSource::NotifyPropertyChanged(resource, property, + newNode, oldNode); +} diff --git a/mailnews/base/src/nsMsgFolderDataSource.h b/mailnews/base/src/nsMsgFolderDataSource.h new file mode 100644 index 0000000000..c683274ac0 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderDataSource.h @@ -0,0 +1,356 @@ +/* -*- 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 "mozilla/Attributes.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFService.h" + +#include "nsIFolderListener.h" +#include "nsMsgRDFDataSource.h" + +#include "nsITransactionManager.h" +#include "nsCOMArray.h" +#include "nsIMutableArray.h" +// Disable deprecation warnings generated by nsISupportsArray and associated +// classes. +#if defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#elif defined(_MSC_VER) +#pragma warning (disable : 4996) +#endif +#include "nsISupportsArray.h" +/** + * The mail data source. + */ +class nsMsgFolderDataSource : public nsMsgRDFDataSource, + public nsIFolderListener +{ +public: + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFOLDERLISTENER + + nsMsgFolderDataSource(void); + virtual nsresult Init() override; + virtual void Cleanup() override; + + // nsIRDFDataSource methods + NS_IMETHOD GetURI(char* *uri) override; + + NS_IMETHOD GetSource(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsIRDFResource** source /* out */) override; + + NS_IMETHOD GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) override; + + NS_IMETHOD GetSources(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsISimpleEnumerator** sources) override; + + NS_IMETHOD GetTargets(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsISimpleEnumerator** targets) override; + + NS_IMETHOD Assert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv) override; + + NS_IMETHOD Unassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target) override; + + NS_IMETHOD HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) override; + + NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, + bool *result) override; + + NS_IMETHOD ArcLabelsIn(nsIRDFNode* node, + nsISimpleEnumerator** labels) override; + + NS_IMETHOD ArcLabelsOut(nsIRDFResource* source, + nsISimpleEnumerator** labels) override; + + NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) override; + + NS_IMETHOD GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/*<nsIRDFResource>*/** commands + ) override; + + NS_IMETHOD IsCommandEnabled(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources, + nsIRDFResource* aCommand, + nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments, + bool* aResult) override; + + NS_IMETHOD DoCommand(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources, + nsIRDFResource* aCommand, + nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments) override; +protected: + virtual ~nsMsgFolderDataSource(); + + nsresult GetSenderName(nsAutoString& sender, nsAutoString *senderUserName); + + nsresult createFolderNode(nsIMsgFolder *folder, nsIRDFResource* property, + nsIRDFNode **target); + nsresult createFolderNameNode(nsIMsgFolder *folder, nsIRDFNode **target, bool sort); + nsresult createFolderOpenNode(nsIMsgFolder *folder,nsIRDFNode **target); + nsresult createFolderTreeNameNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createFolderTreeSimpleNameNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createFolderSpecialNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createFolderServerTypeNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createServerIsDeferredNode(nsIMsgFolder* folder, + nsIRDFNode **target); + nsresult createFolderCanCreateFoldersOnServerNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderCanFileMessagesOnServerNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderIsServerNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderIsSecureNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderCanSubscribeNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderSupportsOfflineNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderCanFileMessagesNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderCanCreateSubfoldersNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderCanRenameNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderCanCompactNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createTotalMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createUnreadMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createFolderSizeNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createCharsetNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createBiffStateNodeFromFolder(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createBiffStateNodeFromFlag(uint32_t flag, nsIRDFNode **target); + nsresult createHasUnreadMessagesNode(nsIMsgFolder *folder, bool aIncludeSubfolders, nsIRDFNode **target); + nsresult createNewMessagesNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createFolderNoSelectNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderVirtualNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createInVFEditSearchScopeNode(nsIMsgFolder* folder, + nsIRDFNode **target); + nsresult createFolderImapSharedNode(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderSynchronizeNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createFolderSyncDisabledNode(nsIMsgFolder *folder, nsIRDFNode **target); + nsresult createCanSearchMessages(nsIMsgFolder *folder, + nsIRDFNode **target); + nsresult createFolderChildNode(nsIMsgFolder *folder, nsIRDFNode **target); + + nsresult getFolderArcLabelsOut(nsCOMArray<nsIRDFResource> &aArcs); + + nsresult DoDeleteFromFolder(nsIMsgFolder *folder, + nsISupportsArray *arguments, nsIMsgWindow *msgWindow, bool reallyDelete); + + nsresult DoCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments, + nsIMsgWindow *msgWindow, bool isMove); + + nsresult DoFolderCopyToFolder(nsIMsgFolder *dstFolder, nsISupportsArray *arguments, + nsIMsgWindow *msgWindow, bool isMoveFolder); + + nsresult DoNewFolder(nsIMsgFolder *folder, nsISupportsArray *arguments, + nsIMsgWindow *window); + + nsresult DoFolderAssert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target); + nsresult DoFolderUnassert(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target); + + nsresult DoFolderHasAssertion(nsIMsgFolder *folder, nsIRDFResource *property, nsIRDFNode *target, + bool tv, bool *hasAssertion); + + nsresult GetBiffStateString(uint32_t biffState, nsAutoCString & biffStateStr); + + nsresult CreateUnreadMessagesNameString(int32_t unreadMessages, nsAutoString &nameString); + nsresult CreateArcsOutEnumerator(); + + virtual nsresult OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, bool added); + + nsresult OnUnreadMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue); + nsresult OnTotalMessagePropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue); + nsresult OnFolderSizePropertyChanged(nsIRDFResource *folderResource, int64_t oldValue, int64_t newValue); + nsresult OnFolderSortOrderPropertyChanged(nsIRDFResource *folderResource, int32_t oldValue, int32_t newValue); + nsresult NotifyFolderTreeNameChanged(nsIMsgFolder *folder, nsIRDFResource *folderResource, int32_t aUnreadMessages); + nsresult NotifyFolderTreeSimpleNameChanged(nsIMsgFolder *folder, nsIRDFResource *folderResource); + nsresult NotifyFolderNameChanged(nsIMsgFolder *folder, nsIRDFResource *folderResource); + nsresult NotifyAncestors(nsIMsgFolder *aFolder, nsIRDFResource *aPropertyResource, nsIRDFNode *aNode); + nsresult GetNumMessagesNode(int32_t numMessages, nsIRDFNode **node); + nsresult GetFolderSizeNode(int64_t folderSize, nsIRDFNode **node); + nsresult CreateLiterals(nsIRDFService *rdf); + + virtual nsresult GetFolderDisplayName(nsIMsgFolder *folder, nsString& folderName); + + static nsIRDFResource* kNC_Child; + static nsIRDFResource* kNC_Folder; + static nsIRDFResource* kNC_Name; + static nsIRDFResource* kNC_Open; + static nsIRDFResource* kNC_FolderTreeName; + static nsIRDFResource* kNC_FolderTreeSimpleName; + static nsIRDFResource* kNC_NameSort; + static nsIRDFResource* kNC_FolderTreeNameSort; + static nsIRDFResource* kNC_Columns; + static nsIRDFResource* kNC_MSGFolderRoot; + static nsIRDFResource* kNC_SpecialFolder; + static nsIRDFResource* kNC_ServerType; + static nsIRDFResource* kNC_IsDeferred; + static nsIRDFResource* kNC_CanCreateFoldersOnServer; + static nsIRDFResource* kNC_CanFileMessagesOnServer; + static nsIRDFResource* kNC_IsServer; + static nsIRDFResource* kNC_IsSecure; + static nsIRDFResource* kNC_CanSubscribe; + static nsIRDFResource* kNC_SupportsOffline; + static nsIRDFResource* kNC_CanFileMessages; + static nsIRDFResource* kNC_CanCreateSubfolders; + static nsIRDFResource* kNC_CanRename; + static nsIRDFResource* kNC_CanCompact; + static nsIRDFResource* kNC_TotalMessages; + static nsIRDFResource* kNC_TotalUnreadMessages; + static nsIRDFResource* kNC_FolderSize; + static nsIRDFResource* kNC_Charset; + static nsIRDFResource* kNC_BiffState; + static nsIRDFResource* kNC_HasUnreadMessages; + static nsIRDFResource* kNC_NewMessages; + static nsIRDFResource* kNC_SubfoldersHaveUnreadMessages; + static nsIRDFResource* kNC_NoSelect; + static nsIRDFResource* kNC_ImapShared; + static nsIRDFResource* kNC_Synchronize; + static nsIRDFResource* kNC_SyncDisabled; + static nsIRDFResource* kNC_CanSearchMessages; + static nsIRDFResource* kNC_VirtualFolder; + static nsIRDFResource* kNC_InVFEditSearchScope; + static nsIRDFResource* kNC_UnreadFolders; // maybe should be in nsMsgFlatFolderDataSource? + static nsIRDFResource* kNC_FavoriteFolders; // maybe should be in nsMsgFlatFolderDataSource? + static nsIRDFResource* kNC_RecentFolders; // maybe should be in nsMsgFlatFolderDataSource? + // commands + static nsIRDFResource* kNC_Delete; + static nsIRDFResource* kNC_ReallyDelete; + static nsIRDFResource* kNC_NewFolder; + static nsIRDFResource* kNC_GetNewMessages; + static nsIRDFResource* kNC_Copy; + static nsIRDFResource* kNC_Move; + static nsIRDFResource* kNC_CopyFolder; + static nsIRDFResource* kNC_MoveFolder; + static nsIRDFResource* kNC_MarkAllMessagesRead; + static nsIRDFResource* kNC_Compact; + static nsIRDFResource* kNC_CompactAll; + static nsIRDFResource* kNC_Rename; + static nsIRDFResource* kNC_EmptyTrash; + static nsIRDFResource* kNC_DownloadFlagged; + //Cached literals + nsCOMPtr<nsIRDFNode> kTrueLiteral; + nsCOMPtr<nsIRDFNode> kFalseLiteral; + + // property atoms + static nsIAtom* kTotalMessagesAtom; + static nsIAtom* kTotalUnreadMessagesAtom; + static nsIAtom* kFolderSizeAtom; + static nsIAtom* kBiffStateAtom; + static nsIAtom* kSortOrderAtom; + static nsIAtom* kNewMessagesAtom; + static nsIAtom* kNameAtom; + static nsIAtom* kSynchronizeAtom; + static nsIAtom* kOpenAtom; + static nsIAtom* kIsDeferredAtom; + static nsIAtom* kIsSecureAtom; + static nsrefcnt gFolderResourceRefCnt; + static nsIAtom* kCanFileMessagesAtom; + static nsIAtom* kInVFEditSearchScopeAtom; + + nsCOMArray<nsIRDFResource> kFolderArcsOutArray; + +}; + + +class nsMsgFlatFolderDataSource : public nsMsgFolderDataSource +{ +public: + // constructor could take a filter to filter out folders. + nsMsgFlatFolderDataSource(); + virtual ~nsMsgFlatFolderDataSource(); + virtual nsresult Init() override; + virtual void Cleanup() override; + + NS_IMETHOD GetURI(char* *uri) override; + NS_IMETHOD GetTargets(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsISimpleEnumerator** targets) override; + NS_IMETHOD GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) override; + + NS_IMETHOD HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) override; +protected: + virtual nsresult GetFolderDisplayName(nsIMsgFolder *folder, + nsString& folderName) override; + virtual void EnsureFolders(); + virtual bool WantsThisFolder(nsIMsgFolder *folder); + bool ResourceIsOurRoot(nsIRDFResource *resource); + virtual nsresult OnItemAddedOrRemoved(nsIMsgFolder *parentItem, nsISupports *item, + bool added) override; + + nsCOMArray <nsIMsgFolder> m_folders; + nsCOMPtr<nsIRDFResource> m_rootResource; // the resource for our root + nsCString m_dsName; + bool m_builtFolders; +}; + + +class nsMsgUnreadFoldersDataSource : public nsMsgFlatFolderDataSource +{ +public: + nsMsgUnreadFoldersDataSource() {m_dsName = "mailnewsunreadfolders";} + virtual ~nsMsgUnreadFoldersDataSource() {} + virtual nsresult NotifyPropertyChanged(nsIRDFResource *resource, + nsIRDFResource *propertyResource, nsIRDFNode *newNode, + nsIRDFNode *oldNode = nullptr) override; +protected: + virtual bool WantsThisFolder(nsIMsgFolder *folder) override; +}; + +class nsMsgFavoriteFoldersDataSource : public nsMsgFlatFolderDataSource +{ +public: + nsMsgFavoriteFoldersDataSource() {m_dsName = "mailnewsfavefolders";} + virtual ~nsMsgFavoriteFoldersDataSource() {} +protected: + virtual bool WantsThisFolder(nsIMsgFolder *folder) override; +}; + +class nsMsgRecentFoldersDataSource : public nsMsgFlatFolderDataSource +{ +public: + nsMsgRecentFoldersDataSource() {m_dsName = "mailnewsrecentfolders"; + m_cutOffDate = 0; m_maxNumFolders = 15;} + virtual ~nsMsgRecentFoldersDataSource() {} + virtual nsresult NotifyPropertyChanged(nsIRDFResource *resource, + nsIRDFResource *property, nsIRDFNode *newNode, + nsIRDFNode *oldNode) override; + NS_IMETHOD OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) override; + virtual void Cleanup() override; +protected: + virtual void EnsureFolders() override; + uint32_t m_cutOffDate; + uint32_t m_maxNumFolders; +}; diff --git a/mailnews/base/src/nsMsgFolderNotificationService.cpp b/mailnews/base/src/nsMsgFolderNotificationService.cpp new file mode 100644 index 0000000000..ebf88275ad --- /dev/null +++ b/mailnews/base/src/nsMsgFolderNotificationService.cpp @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "msgCore.h" +#include "nsMsgFolderNotificationService.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" +#include "nsIMsgHdr.h" +#include "nsIMsgImapMailFolder.h" +#include "nsIImapIncomingServer.h" + +// +// nsMsgFolderNotificationService +// +NS_IMPL_ISUPPORTS(nsMsgFolderNotificationService, nsIMsgFolderNotificationService) + +nsMsgFolderNotificationService::nsMsgFolderNotificationService() +{ +} + +nsMsgFolderNotificationService::~nsMsgFolderNotificationService() +{ + /* destructor code */ +} + +NS_IMETHODIMP nsMsgFolderNotificationService::GetHasListeners(bool *aHasListeners) +{ + NS_ENSURE_ARG_POINTER(aHasListeners); + *aHasListeners = mListeners.Length() > 0; + return NS_OK; +} + + +/* void addListener (in nsIMsgFolderListener aListener, in msgFolderListenerFlag flags); */ +NS_IMETHODIMP nsMsgFolderNotificationService::AddListener(nsIMsgFolderListener *aListener, + msgFolderListenerFlag aFlags) +{ + NS_ENSURE_ARG_POINTER(aListener); + MsgFolderListener listener(aListener, aFlags); + mListeners.AppendElementUnlessExists(listener); + return NS_OK; +} + +/* void removeListener (in nsIMsgFolderListener aListener); */ +NS_IMETHODIMP nsMsgFolderNotificationService::RemoveListener(nsIMsgFolderListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +#define NOTIFY_MSGFOLDER_LISTENERS(propertyflag_, propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<MsgFolderListener>::ForwardIterator iter(mListeners); \ + while (iter.HasMore()) { \ + const MsgFolderListener &listener = iter.GetNext(); \ + if (listener.mFlags & propertyflag_) \ + listener.mListener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +/* void notifyMsgAdded (in nsIMsgDBHdr aMsg); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgAdded(nsIMsgDBHdr *aMsg) +{ + NOTIFY_MSGFOLDER_LISTENERS(msgAdded, MsgAdded, (aMsg)); + return NS_OK; +} + +/* void notifyMsgsClassified (in */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsClassified( + nsIArray *aMsgs, bool aJunkProcessed, bool aTraitProcessed) +{ + NOTIFY_MSGFOLDER_LISTENERS(msgsClassified, MsgsClassified, + (aMsgs, aJunkProcessed, aTraitProcessed)); + return NS_OK; +} + +/* void notifyMsgsDeleted (in nsIArray aMsgs); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsDeleted(nsIArray *aMsgs) +{ + NOTIFY_MSGFOLDER_LISTENERS(msgsDeleted, MsgsDeleted, (aMsgs)); + return NS_OK; +} + +/* void notifyMsgsMoveCopyCompleted (in boolean aMove, in nsIArray aSrcMsgs, + in nsIMsgFolder aDestFolder); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsMoveCopyCompleted( + bool aMove, nsIArray *aSrcMsgs, nsIMsgFolder *aDestFolder, + nsIArray *aDestMsgs) +{ + uint32_t count = mListeners.Length(); + + // IMAP delete model means that a "move" isn't really a move, it is a copy, + // followed by storing the IMAP deleted flag on the message. + bool isReallyMove = aMove; + if (count > 0 && aMove) + { + nsresult rv; + // Assume that all the source messages are from the same server. + nsCOMPtr<nsIMsgDBHdr> message(do_QueryElementAt(aSrcMsgs, 0, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> msgFolder; + rv = message->GetFolder(getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(msgFolder)); + if (imapFolder) + { + nsCOMPtr<nsIImapIncomingServer> imapServer; + imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer)); + if (imapServer) + { + nsMsgImapDeleteModel deleteModel; + imapServer->GetDeleteModel(&deleteModel); + if (deleteModel == nsMsgImapDeleteModels::IMAPDelete) + isReallyMove = false; + } + } + } + + NOTIFY_MSGFOLDER_LISTENERS(msgsMoveCopyCompleted, MsgsMoveCopyCompleted, + (isReallyMove, aSrcMsgs, aDestFolder, aDestMsgs)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFolderNotificationService::NotifyMsgKeyChanged(nsMsgKey aOldKey, + nsIMsgDBHdr *aNewHdr) +{ + NOTIFY_MSGFOLDER_LISTENERS(msgKeyChanged, MsgKeyChanged, (aOldKey, aNewHdr)); + return NS_OK; +} + +/* void notifyFolderAdded(in nsIMsgFolder aFolder); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderAdded(nsIMsgFolder *aFolder) +{ + NOTIFY_MSGFOLDER_LISTENERS(folderAdded, FolderAdded, (aFolder)); + return NS_OK; +} + +/* void notifyFolderDeleted(in nsIMsgFolder aFolder); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderDeleted(nsIMsgFolder *aFolder) +{ + NOTIFY_MSGFOLDER_LISTENERS(folderDeleted, FolderDeleted, (aFolder)); + return NS_OK; +} + +/* void notifyFolderMoveCopyCompleted(in boolean aMove, in nsIMsgFolder aSrcFolder, in nsIMsgFolder aDestFolder); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderMoveCopyCompleted(bool aMove, nsIMsgFolder *aSrcFolder, nsIMsgFolder *aDestFolder) +{ + NOTIFY_MSGFOLDER_LISTENERS(folderMoveCopyCompleted, FolderMoveCopyCompleted, + (aMove, aSrcFolder, aDestFolder)); + return NS_OK; +} + +/* void notifyFolderRenamed (in nsIMsgFolder aOrigFolder, in nsIMsgFolder aNewFolder); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderRenamed(nsIMsgFolder *aOrigFolder, nsIMsgFolder *aNewFolder) +{ + NOTIFY_MSGFOLDER_LISTENERS(folderRenamed, FolderRenamed, (aOrigFolder, aNewFolder)); + return NS_OK; +} + +/* void notifyItemEvent (in nsISupports aItem, in string aEvent, in nsISupports aData); */ +NS_IMETHODIMP nsMsgFolderNotificationService::NotifyItemEvent(nsISupports *aItem, const nsACString &aEvent, nsISupports *aData) +{ + NOTIFY_MSGFOLDER_LISTENERS(itemEvent, ItemEvent, (aItem, aEvent, aData)); + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgFolderNotificationService.h b/mailnews/base/src/nsMsgFolderNotificationService.h new file mode 100644 index 0000000000..61fdd842b9 --- /dev/null +++ b/mailnews/base/src/nsMsgFolderNotificationService.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsMsgFolderNotificationService_h__ +#define nsMsgFolderNotificationService_h__ + +#include "nsIMsgFolderNotificationService.h" +#include "nsIMsgFolderListener.h" +#include "nsTObserverArray.h" +#include "nsCOMPtr.h" + +class nsMsgFolderNotificationService final : public nsIMsgFolderNotificationService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFOLDERNOTIFICATIONSERVICE + + nsMsgFolderNotificationService(); + +private: + ~nsMsgFolderNotificationService(); + struct MsgFolderListener + { + nsCOMPtr<nsIMsgFolderListener> mListener; + msgFolderListenerFlag mFlags; + + MsgFolderListener(nsIMsgFolderListener *aListener, msgFolderListenerFlag aFlags) + : mListener(aListener), mFlags(aFlags) {} + MsgFolderListener(const MsgFolderListener &aListener) + : mListener(aListener.mListener), mFlags(aListener.mFlags) {} + ~MsgFolderListener() {} + + int operator==(nsIMsgFolderListener* aListener) const { + return mListener == aListener; + } + int operator==(const MsgFolderListener &aListener) const { + return mListener == aListener.mListener; + } + }; + + nsTObserverArray<MsgFolderListener> mListeners; +}; + +#endif diff --git a/mailnews/base/src/nsMsgGroupThread.cpp b/mailnews/base/src/nsMsgGroupThread.cpp new file mode 100644 index 0000000000..c68a6329d8 --- /dev/null +++ b/mailnews/base/src/nsMsgGroupThread.cpp @@ -0,0 +1,856 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgGroupThread.h" +#include "nsMsgDBView.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgGroupThread, nsIMsgThread) + +nsMsgGroupThread::nsMsgGroupThread() +{ + Init(); +} +nsMsgGroupThread::nsMsgGroupThread(nsIMsgDatabase *db) +{ + m_db = db; + Init(); +} + +void nsMsgGroupThread::Init() +{ + m_threadKey = nsMsgKey_None; + m_threadRootKey = nsMsgKey_None; + m_numUnreadChildren = 0; + m_flags = 0; + m_newestMsgDate = 0; + m_dummy = false; +} + +nsMsgGroupThread::~nsMsgGroupThread() +{ +} + +NS_IMETHODIMP nsMsgGroupThread::SetThreadKey(nsMsgKey threadKey) +{ + m_threadKey = threadKey; + // by definition, the initial thread key is also the thread root key. + m_threadRootKey = threadKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::GetThreadKey(nsMsgKey *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = m_threadKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::GetFlags(uint32_t *aFlags) +{ + NS_ENSURE_ARG_POINTER(aFlags); + *aFlags = m_flags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::SetFlags(uint32_t aFlags) +{ + m_flags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::SetSubject(const nsACString& aSubject) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgGroupThread::GetSubject(nsACString& result) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgGroupThread::GetNumChildren(uint32_t *aNumChildren) +{ + NS_ENSURE_ARG_POINTER(aNumChildren); + *aNumChildren = m_keys.Length(); // - ((m_dummy) ? 1 : 0); + return NS_OK; +} + +uint32_t nsMsgGroupThread::NumRealChildren() +{ + return m_keys.Length() - ((m_dummy) ? 1 : 0); +} + +NS_IMETHODIMP nsMsgGroupThread::GetNumUnreadChildren (uint32_t *aNumUnreadChildren) +{ + NS_ENSURE_ARG_POINTER(aNumUnreadChildren); + *aNumUnreadChildren = m_numUnreadChildren; + return NS_OK; +} + +void nsMsgGroupThread::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr) +{ + nsMsgKey msgKey; + hdr->GetMessageKey(&msgKey); + m_keys.InsertElementAt(index, msgKey); +} + +void nsMsgGroupThread::SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr) +{ + nsMsgKey msgKey; + hdr->GetMessageKey(&msgKey); + m_keys[index] = msgKey; +} + +nsMsgViewIndex nsMsgGroupThread::FindMsgHdr(nsIMsgDBHdr *hdr) +{ + nsMsgKey msgKey; + hdr->GetMessageKey(&msgKey); + return (nsMsgViewIndex)m_keys.IndexOf(msgKey); +} + +NS_IMETHODIMP nsMsgGroupThread::AddChild(nsIMsgDBHdr *child, nsIMsgDBHdr *inReplyTo, bool threadInThread, + nsIDBChangeAnnouncer *announcer) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsMsgViewIndex nsMsgGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view) +{ + nsMsgKey newHdrKey; + child->GetMessageKey(&newHdrKey); + uint32_t insertIndex = 0; + // since we're sorted by date, we could do a binary search for the + // insert point. Or, we could start at the end... + if (m_keys.Length() > 0) + { + nsMsgViewSortTypeValue sortType; + nsMsgViewSortOrderValue sortOrder; + (void) view->GetSortType(&sortType); + (void) view->GetSortOrder(&sortOrder); + // historical behavior is ascending date order unless our primary sort is + // on date + nsMsgViewSortOrderValue threadSortOrder = + (sortType == nsMsgViewSortType::byDate + && sortOrder == nsMsgViewSortOrder::descending) ? + nsMsgViewSortOrder::descending : nsMsgViewSortOrder::ascending; + // new behavior is tricky and uses the secondary sort order if the secondary + // sort is on the date + nsMsgViewSortTypeValue secondarySortType; + nsMsgViewSortOrderValue secondarySortOrder; + (void) view->GetSecondarySortType(&secondarySortType); + (void) view->GetSecondarySortOrder(&secondarySortOrder); + if (secondarySortType == nsMsgViewSortType::byDate) + threadSortOrder = secondarySortOrder; + // sort by date within group. + insertIndex = GetInsertIndexFromView(view, child, threadSortOrder); + } + m_keys.InsertElementAt(insertIndex, newHdrKey); + if (!insertIndex) + m_threadRootKey = newHdrKey; + return insertIndex; +} + +nsMsgViewIndex +nsMsgGroupThread::GetInsertIndexFromView(nsMsgDBView *view, + nsIMsgDBHdr *child, + nsMsgViewSortOrderValue threadSortOrder) +{ + return view->GetInsertIndexHelper(child, m_keys, nullptr, threadSortOrder, nsMsgViewSortType::byDate); +} + +nsMsgViewIndex nsMsgGroupThread::AddChildFromGroupView(nsIMsgDBHdr *child, nsMsgDBView *view) +{ + uint32_t newHdrFlags = 0; + uint32_t msgDate; + nsMsgKey newHdrKey = 0; + + child->GetFlags(&newHdrFlags); + child->GetMessageKey(&newHdrKey); + child->GetDateInSeconds(&msgDate); + if (msgDate > m_newestMsgDate) + SetNewestMsgDate(msgDate); + + child->AndFlags(~(nsMsgMessageFlags::Watched), &newHdrFlags); + uint32_t numChildren; + + // get the num children before we add the new header. + GetNumChildren(&numChildren); + + // if this is an empty thread, set the root key to this header's key + if (numChildren == 0) + m_threadRootKey = newHdrKey; + + if (! (newHdrFlags & nsMsgMessageFlags::Read)) + ChangeUnreadChildCount(1); + + return AddMsgHdrInDateOrder(child, view); +} + +nsresult nsMsgGroupThread::ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey, + nsIDBChangeAnnouncer *announcer) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (aIndex >= m_keys.Length()) + return NS_ERROR_INVALID_ARG; + *aResult = m_keys[aIndex]; + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult) +{ + if (aIndex >= m_keys.Length()) + return NS_MSG_MESSAGE_NOT_FOUND; + return m_db->GetMsgHdrForKey(m_keys[aIndex], aResult); +} + + +NS_IMETHODIMP nsMsgGroupThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult) +{ + return GetChildHdrAt(m_keys.IndexOf(msgKey), aResult); +} + +NS_IMETHODIMP nsMsgGroupThread::RemoveChildAt(uint32_t aIndex) +{ + NS_ENSURE_TRUE(aIndex < m_keys.Length(), NS_MSG_MESSAGE_NOT_FOUND); + + m_keys.RemoveElementAt(aIndex); + return NS_OK; +} + +nsresult nsMsgGroupThread::RemoveChild(nsMsgKey msgKey) +{ + m_keys.RemoveElement(msgKey); + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) +{ + NS_ENSURE_ARG_POINTER(child); + + uint32_t flags; + nsMsgKey key; + + child->GetFlags(&flags); + child->GetMessageKey(&key); + + // if this was the newest msg, clear the newest msg date so we'll recalc. + uint32_t date; + child->GetDateInSeconds(&date); + if (date == m_newestMsgDate) + SetNewestMsgDate(0); + + if (!(flags & nsMsgMessageFlags::Read)) + ChangeUnreadChildCount(-1); + nsMsgViewIndex threadIndex = FindMsgHdr(child); + bool wasFirstChild = threadIndex == 0; + nsresult rv = RemoveChildAt(threadIndex); + // if we're deleting the root of a dummy thread, need to update the threadKey + // and the dummy header at position 0 + if (m_dummy && wasFirstChild && m_keys.Length() > 1) + { + nsIMsgDBHdr *newRootChild; + rv = GetChildHdrAt(1, &newRootChild); + NS_ENSURE_SUCCESS(rv, rv); + SetMsgHdrAt(0, newRootChild); + } + + return rv; +} + +nsresult nsMsgGroupThread::ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer) +{ + nsresult rv = NS_OK; + + uint32_t numChildren; + uint32_t childIndex = 0; + + GetNumChildren(&numChildren); + + nsCOMPtr <nsIMsgDBHdr> curHdr; + if (numChildren > 0) + { + for (childIndex = 0; childIndex < numChildren; childIndex++) + { + rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr)); + if (NS_SUCCEEDED(rv) && curHdr) + { + nsMsgKey threadParent; + + curHdr->GetThreadParent(&threadParent); + if (threadParent == oldParent) + { + nsMsgKey curKey; + + curHdr->SetThreadParent(newParent); + curHdr->GetMessageKey(&curKey); + if (announcer) + announcer->NotifyParentChangedAll(curKey, oldParent, newParent, nullptr); + // if the old parent was the root of the thread, then only the first child gets + // promoted to root, and other children become children of the new root. + if (newParent == nsMsgKey_None) + { + m_threadRootKey = curKey; + newParent = curKey; + } + } + } + } + } + return rv; +} + +NS_IMETHODIMP nsMsgGroupThread::MarkChildRead(bool bRead) +{ + ChangeUnreadChildCount(bRead ? -1 : 1); + return NS_OK; +} + +// this could be moved into utils, because I think it's the same as the db impl. +class nsMsgGroupThreadEnumerator : public nsISimpleEnumerator { +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + + // nsMsgGroupThreadEnumerator methods: + typedef nsresult (*nsMsgGroupThreadEnumeratorFilter)(nsIMsgDBHdr* hdr, void* closure); + + nsMsgGroupThreadEnumerator(nsMsgGroupThread *thread, nsMsgKey startKey, + nsMsgGroupThreadEnumeratorFilter filter, void* closure); + int32_t MsgKeyFirstChildIndex(nsMsgKey inMsgKey); + +protected: + virtual ~nsMsgGroupThreadEnumerator(); + + nsresult Prefetch(); + + nsCOMPtr <nsIMsgDBHdr> mResultHdr; + nsMsgGroupThread* mThread; + nsMsgKey mThreadParentKey; + nsMsgKey mFirstMsgKey; + int32_t mChildIndex; + bool mDone; + bool mNeedToPrefetch; + nsMsgGroupThreadEnumeratorFilter mFilter; + void* mClosure; + bool mFoundChildren; +}; + +nsMsgGroupThreadEnumerator::nsMsgGroupThreadEnumerator(nsMsgGroupThread *thread, nsMsgKey startKey, + nsMsgGroupThreadEnumeratorFilter filter, void* closure) + : mDone(false), + mFilter(filter), mClosure(closure), mFoundChildren(false) +{ + mThreadParentKey = startKey; + mChildIndex = 0; + mThread = thread; + mNeedToPrefetch = true; + mFirstMsgKey = nsMsgKey_None; + + nsresult rv = mThread->GetRootHdr(nullptr, getter_AddRefs(mResultHdr)); + + if (NS_SUCCEEDED(rv) && mResultHdr) + mResultHdr->GetMessageKey(&mFirstMsgKey); + + uint32_t numChildren; + mThread->GetNumChildren(&numChildren); + + if (mThreadParentKey != nsMsgKey_None) + { + nsMsgKey msgKey = nsMsgKey_None; + uint32_t childIndex = 0; + + + for (childIndex = 0; childIndex < numChildren; childIndex++) + { + rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(mResultHdr)); + if (NS_SUCCEEDED(rv) && mResultHdr) + { + mResultHdr->GetMessageKey(&msgKey); + + if (msgKey == startKey) + { + mChildIndex = MsgKeyFirstChildIndex(msgKey); + mDone = (mChildIndex < 0); + break; + } + + if (mDone) + break; + + } + else + NS_ASSERTION(false, "couldn't get child from thread"); + } + } + +#ifdef DEBUG_bienvenu1 + nsCOMPtr <nsIMsgDBHdr> child; + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + nsMsgKey threadParent; + nsMsgKey msgKey; + // we're only doing one level of threading, so check if caller is + // asking for children of the first message in the thread or not. + // if not, we will tell him there are no children. + child->GetMessageKey(&msgKey); + child->GetThreadParent(&threadParent); + + printf("index = %ld key = %ld parent = %lx\n", childIndex, msgKey, threadParent); + } + } +#endif + NS_ADDREF(thread); +} + +nsMsgGroupThreadEnumerator::~nsMsgGroupThreadEnumerator() +{ + NS_RELEASE(mThread); +} + +NS_IMPL_ISUPPORTS(nsMsgGroupThreadEnumerator, nsISimpleEnumerator) + + +int32_t nsMsgGroupThreadEnumerator::MsgKeyFirstChildIndex(nsMsgKey inMsgKey) +{ + // if (msgKey != mThreadParentKey) + // mDone = true; + // look through rest of thread looking for a child of this message. + // If the inMsgKey is the first message in the thread, then all children + // without parents are considered to be children of inMsgKey. + // Otherwise, only true children qualify. + uint32_t numChildren; + nsCOMPtr <nsIMsgDBHdr> curHdr; + int32_t firstChildIndex = -1; + + mThread->GetNumChildren(&numChildren); + + // if this is the first message in the thread, just check if there's more than + // one message in the thread. + // if (inMsgKey == mThread->m_threadRootKey) + // return (numChildren > 1) ? 1 : -1; + + for (uint32_t curChildIndex = 0; curChildIndex < numChildren; curChildIndex++) + { + nsresult rv = mThread->GetChildHdrAt(curChildIndex, getter_AddRefs(curHdr)); + if (NS_SUCCEEDED(rv) && curHdr) + { + nsMsgKey parentKey; + + curHdr->GetThreadParent(&parentKey); + if (parentKey == inMsgKey) + { + firstChildIndex = curChildIndex; + break; + } + } + } +#ifdef DEBUG_bienvenu1 + printf("first child index of %ld = %ld\n", inMsgKey, firstChildIndex); +#endif + return firstChildIndex; +} + +NS_IMETHODIMP nsMsgGroupThreadEnumerator::GetNext(nsISupports **aItem) +{ + if (!aItem) + return NS_ERROR_NULL_POINTER; + nsresult rv = NS_OK; + + if (mNeedToPrefetch) + rv = Prefetch(); + + if (NS_SUCCEEDED(rv) && mResultHdr) + { + *aItem = mResultHdr; + NS_ADDREF(*aItem); + mNeedToPrefetch = true; + } + return rv; +} + +nsresult nsMsgGroupThreadEnumerator::Prefetch() +{ + nsresult rv=NS_OK; // XXX or should this default to an error? + mResultHdr = nullptr; + if (mThreadParentKey == nsMsgKey_None) + { + rv = mThread->GetRootHdr(&mChildIndex, getter_AddRefs(mResultHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv) && mResultHdr, "better be able to get root hdr"); + mChildIndex = 0; // since root can be anywhere, set mChildIndex to 0. + } + else if (!mDone) + { + uint32_t numChildren; + mThread->GetNumChildren(&numChildren); + + while ((uint32_t)mChildIndex < numChildren) + { + rv = mThread->GetChildHdrAt(mChildIndex++, getter_AddRefs(mResultHdr)); + if (NS_SUCCEEDED(rv) && mResultHdr) + { + nsMsgKey parentKey; + nsMsgKey curKey; + + if (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure))) { + mResultHdr = nullptr; + continue; + } + + mResultHdr->GetThreadParent(&parentKey); + mResultHdr->GetMessageKey(&curKey); + // if the parent is the same as the msg we're enumerating over, + // or the parentKey isn't set, and we're iterating over the top + // level message in the thread, then leave mResultHdr set to cur msg. + if (parentKey == mThreadParentKey || + (parentKey == nsMsgKey_None + && mThreadParentKey == mFirstMsgKey && curKey != mThreadParentKey)) + break; + mResultHdr = nullptr; + } + else + NS_ASSERTION(false, "better be able to get child"); + } + if (!mResultHdr && mThreadParentKey == mFirstMsgKey && !mFoundChildren && numChildren > 1) + { +// mThread->ReparentMsgsWithInvalidParent(numChildren, mThreadParentKey); + } + } + if (!mResultHdr) + { + mDone = true; + return NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) + { + mDone = true; + return rv; + } + else + mNeedToPrefetch = false; + mFoundChildren = true; + +#ifdef DEBUG_bienvenu1 + nsMsgKey debugMsgKey; + mResultHdr->GetMessageKey(&debugMsgKey); + printf("next for %ld = %ld\n", mThreadParentKey, debugMsgKey); +#endif + + return rv; +} + +NS_IMETHODIMP nsMsgGroupThreadEnumerator::HasMoreElements(bool *aResult) +{ + if (!aResult) + return NS_ERROR_NULL_POINTER; + if (mNeedToPrefetch) + Prefetch(); + *aResult = !mDone; + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupThread::EnumerateMessages(nsMsgKey parentKey, nsISimpleEnumerator* *result) +{ + nsMsgGroupThreadEnumerator* e = new nsMsgGroupThreadEnumerator(this, parentKey, nullptr, nullptr); + if (e == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(e); + *result = e; + + return NS_OK; +} +#if 0 +nsresult nsMsgGroupThread::ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey) +{ + nsresult ret = NS_OK; + // run through looking for messages that don't have a correct parent, + // i.e., a parent that's in the thread! + for (int32_t childIndex = 0; childIndex < (int32_t) numChildren; childIndex++) + { + nsCOMPtr <nsIMsgDBHdr> curChild; + ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild)); + if (NS_SUCCEEDED(ret) && curChild) + { + nsMsgKey parentKey; + nsCOMPtr <nsIMsgDBHdr> parent; + + curChild->GetThreadParent(&parentKey); + + if (parentKey != nsMsgKey_None) + { + GetChild(parentKey, getter_AddRefs(parent)); + if (!parent) + curChild->SetThreadParent(threadParentKey); + } + } + } + return ret; +} +#endif +NS_IMETHODIMP nsMsgGroupThread::GetRootHdr(int32_t *resultIndex, nsIMsgDBHdr **result) +{ + if (!result) + return NS_ERROR_NULL_POINTER; + + *result = nullptr; + + if (m_threadRootKey != nsMsgKey_None) + { + nsresult ret = GetChildHdrForKey(m_threadRootKey, result, resultIndex); + if (NS_SUCCEEDED(ret) && *result) + return ret; + else + { + printf("need to reset thread root key\n"); + uint32_t numChildren; + nsMsgKey threadParentKey = nsMsgKey_None; + GetNumChildren(&numChildren); + + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + nsCOMPtr <nsIMsgDBHdr> curChild; + ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild)); + if (NS_SUCCEEDED(ret) && curChild) + { + nsMsgKey parentKey; + + curChild->GetThreadParent(&parentKey); + if (parentKey == nsMsgKey_None) + { + NS_ASSERTION(!(*result), "two top level msgs, not good"); + curChild->GetMessageKey(&threadParentKey); + m_threadRootKey = threadParentKey; + if (resultIndex) + *resultIndex = childIndex; + *result = curChild; + NS_ADDREF(*result); +// ReparentMsgsWithInvalidParent(numChildren, threadParentKey); + // return NS_OK; + } + } + } + if (*result) + { + return NS_OK; + } + } + // if we can't get the thread root key, we'll just get the first hdr. + // there's a bug where sometimes we weren't resetting the thread root key + // when removing the thread root key. + } + if (resultIndex) + *resultIndex = 0; + return GetChildHdrAt(0, result); +} + +nsresult nsMsgGroupThread::ChangeUnreadChildCount(int32_t delta) +{ + m_numUnreadChildren += delta; + return NS_OK; +} + +nsresult nsMsgGroupThread::GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex) +{ + uint32_t numChildren; + uint32_t childIndex = 0; + nsresult rv = NS_OK; // XXX or should this default to an error? + + if (!result) + return NS_ERROR_NULL_POINTER; + + GetNumChildren(&numChildren); + + for (childIndex = 0; childIndex < numChildren; childIndex++) + { + rv = GetChildHdrAt(childIndex, result); + if (NS_SUCCEEDED(rv) && *result) + { + nsMsgKey msgKey; + // we're only doing one level of threading, so check if caller is + // asking for children of the first message in the thread or not. + // if not, we will tell him there are no children. + (*result)->GetMessageKey(&msgKey); + + if (msgKey == desiredKey) + break; + NS_RELEASE(*result); + } + } + if (resultIndex) + *resultIndex = (int32_t) childIndex; + + return rv; +} + +NS_IMETHODIMP nsMsgGroupThread::GetFirstUnreadChild(nsIMsgDBHdr **result) +{ + NS_ENSURE_ARG(result); + uint32_t numChildren; + nsresult rv = NS_OK; + + GetNumChildren(&numChildren); + + if ((int32_t) numChildren < 0) + numChildren = 0; + + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + nsCOMPtr <nsIMsgDBHdr> child; + rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + nsMsgKey msgKey; + child->GetMessageKey(&msgKey); + + bool isRead; + rv = m_db->IsRead(msgKey, &isRead); + if (NS_SUCCEEDED(rv) && !isRead) + { + *result = child; + NS_ADDREF(*result); + break; + } + } + } + + return rv; +} + +NS_IMETHODIMP nsMsgGroupThread::GetNewestMsgDate(uint32_t *aResult) +{ + // if this hasn't been set, figure it out by enumerating the msgs in the thread. + if (!m_newestMsgDate) + { + uint32_t numChildren; + nsresult rv = NS_OK; + + GetNumChildren(&numChildren); + + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + nsCOMPtr <nsIMsgDBHdr> child; + rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + uint32_t msgDate; + child->GetDateInSeconds(&msgDate); + if (msgDate > m_newestMsgDate) + m_newestMsgDate = msgDate; + } + } + + } + *aResult = m_newestMsgDate; + return NS_OK; +} + + +NS_IMETHODIMP nsMsgGroupThread::SetNewestMsgDate(uint32_t aNewestMsgDate) +{ + m_newestMsgDate = aNewestMsgDate; + return NS_OK; +} + +nsMsgXFGroupThread::nsMsgXFGroupThread() +{ +} + +nsMsgXFGroupThread::~nsMsgXFGroupThread() +{ +} + +NS_IMETHODIMP nsMsgXFGroupThread::GetNumChildren(uint32_t *aNumChildren) +{ + NS_ENSURE_ARG_POINTER(aNumChildren); + *aNumChildren = m_folders.Length(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFGroupThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult) +{ + if (aIndex >= m_folders.Length()) + return NS_MSG_MESSAGE_NOT_FOUND; + return m_folders.ObjectAt(aIndex)->GetMessageHeader(m_keys[aIndex], aResult); +} + +NS_IMETHODIMP nsMsgXFGroupThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgXFGroupThread::RemoveChildAt(uint32_t aIndex) +{ + NS_ENSURE_TRUE(aIndex < m_folders.Length(), NS_MSG_MESSAGE_NOT_FOUND); + + nsresult rv = nsMsgGroupThread::RemoveChildAt(aIndex); + NS_ENSURE_SUCCESS(rv, rv); + m_folders.RemoveElementAt(aIndex); + return NS_OK; +} + +void nsMsgXFGroupThread::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr) +{ + nsCOMPtr<nsIMsgFolder> folder; + hdr->GetFolder(getter_AddRefs(folder)); + m_folders.InsertObjectAt(folder, index); + nsMsgGroupThread::InsertMsgHdrAt(index, hdr); +} + +void nsMsgXFGroupThread::SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr) +{ + nsCOMPtr<nsIMsgFolder> folder; + hdr->GetFolder(getter_AddRefs(folder)); + m_folders.ReplaceObjectAt(folder, index); + nsMsgGroupThread::SetMsgHdrAt(index, hdr); +} + +nsMsgViewIndex nsMsgXFGroupThread::FindMsgHdr(nsIMsgDBHdr *hdr) +{ + nsMsgKey msgKey; + hdr->GetMessageKey(&msgKey); + nsCOMPtr<nsIMsgFolder> folder; + hdr->GetFolder(getter_AddRefs(folder)); + size_t index = 0; + while (true) { + index = m_keys.IndexOf(msgKey, index); + if (index == m_keys.NoIndex || m_folders[index] == folder) + break; + index++; + } + return (nsMsgViewIndex)index; +} + +nsMsgViewIndex nsMsgXFGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view) +{ + nsMsgViewIndex insertIndex = nsMsgGroupThread::AddMsgHdrInDateOrder(child, view); + nsCOMPtr<nsIMsgFolder> folder; + child->GetFolder(getter_AddRefs(folder)); + m_folders.InsertObjectAt(folder, insertIndex); + return insertIndex; +} +nsMsgViewIndex +nsMsgXFGroupThread::GetInsertIndexFromView(nsMsgDBView *view, + nsIMsgDBHdr *child, + nsMsgViewSortOrderValue threadSortOrder) +{ + return view->GetInsertIndexHelper(child, m_keys, &m_folders, threadSortOrder, nsMsgViewSortType::byDate); +} + diff --git a/mailnews/base/src/nsMsgGroupThread.h b/mailnews/base/src/nsMsgGroupThread.h new file mode 100644 index 0000000000..3d1cb4fb40 --- /dev/null +++ b/mailnews/base/src/nsMsgGroupThread.h @@ -0,0 +1,87 @@ +/* -*- 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 "mozilla/Attributes.h" +#include "msgCore.h" +#include "nsCOMArray.h" +#include "nsIMsgThread.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsMsgDBView.h" + +class nsMsgGroupView; + +class nsMsgGroupThread : public nsIMsgThread +{ +public: + friend class nsMsgGroupView; + + nsMsgGroupThread(); + nsMsgGroupThread(nsIMsgDatabase *db); + + NS_DECL_NSIMSGTHREAD + NS_DECL_ISUPPORTS + +protected: + virtual ~nsMsgGroupThread(); + + void Init(); + nsMsgViewIndex AddChildFromGroupView(nsIMsgDBHdr *child, nsMsgDBView *view); + nsresult RemoveChild(nsMsgKey msgKey); + nsresult RerootThread(nsIMsgDBHdr *newParentOfOldRoot, nsIMsgDBHdr *oldRoot, nsIDBChangeAnnouncer *announcer); + + virtual nsMsgViewIndex AddMsgHdrInDateOrder(nsIMsgDBHdr *child, nsMsgDBView *view); + virtual nsMsgViewIndex GetInsertIndexFromView(nsMsgDBView *view, + nsIMsgDBHdr *child, + nsMsgViewSortOrderValue threadSortOrder); + nsresult ReparentNonReferenceChildrenOf(nsIMsgDBHdr *topLevelHdr, nsMsgKey newParentKey, + nsIDBChangeAnnouncer *announcer); + + nsresult ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeAnnouncer *announcer); + nsresult ChangeUnreadChildCount(int32_t delta); + nsresult GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr **result, int32_t *resultIndex); + uint32_t NumRealChildren(); + virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr); + virtual void SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr); + virtual nsMsgViewIndex FindMsgHdr(nsIMsgDBHdr *hdr); + + nsMsgKey m_threadKey; + uint32_t m_numUnreadChildren; + uint32_t m_flags; + nsMsgKey m_threadRootKey; + uint32_t m_newestMsgDate; + nsTArray<nsMsgKey> m_keys; + bool m_dummy; // top level msg is a dummy, e.g., grouped by age. + nsCOMPtr <nsIMsgDatabase> m_db; // should we make a weak ref or just a ptr? +}; + +class nsMsgXFGroupThread : public nsMsgGroupThread +{ +public: + nsMsgXFGroupThread(); + + NS_IMETHOD GetNumChildren(uint32_t *aNumChildren) override; + NS_IMETHOD GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult) override; + NS_IMETHOD GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult) override; + NS_IMETHOD RemoveChildAt(uint32_t aIndex) override; +protected: + virtual ~nsMsgXFGroupThread(); + + virtual void InsertMsgHdrAt(nsMsgViewIndex index, + nsIMsgDBHdr *hdr) override; + virtual void SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr) override; + virtual nsMsgViewIndex FindMsgHdr(nsIMsgDBHdr *hdr) override; + virtual nsMsgViewIndex AddMsgHdrInDateOrder(nsIMsgDBHdr *child, + nsMsgDBView *view) override; + virtual nsMsgViewIndex GetInsertIndexFromView(nsMsgDBView *view, + nsIMsgDBHdr *child, + nsMsgViewSortOrderValue threadSortOrder + ) override; + + nsCOMArray<nsIMsgFolder> m_folders; +}; + diff --git a/mailnews/base/src/nsMsgGroupView.cpp b/mailnews/base/src/nsMsgGroupView.cpp new file mode 100644 index 0000000000..f3d9a27041 --- /dev/null +++ b/mailnews/base/src/nsMsgGroupView.cpp @@ -0,0 +1,1024 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgUtils.h" +#include "nsMsgGroupView.h" +#include "nsIMsgHdr.h" +#include "nsIMsgThread.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgSearchSession.h" +#include "nsMsgGroupThread.h" +#include "nsITreeColumns.h" +#include "nsMsgMessageFlags.h" +#include <plhash.h> +#include "mozilla/Attributes.h" + +#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25 // Allocate this more to avoid reallocation on new mail. +#define MSGHDR_CACHE_MAX_SIZE 8192 // Max msghdr cache entries. +#define MSGHDR_CACHE_DEFAULT_SIZE 100 + +nsMsgGroupView::nsMsgGroupView() +{ + m_dayChanged = false; + m_lastCurExplodedTime.tm_mday = 0; +} + +nsMsgGroupView::~nsMsgGroupView() +{ +} + +NS_IMETHODIMP nsMsgGroupView::Open(nsIMsgFolder *aFolder, nsMsgViewSortTypeValue aSortType, nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, int32_t *aCount) +{ + nsresult rv = nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + PersistFolderInfo(getter_AddRefs(dbFolderInfo)); + + nsCOMPtr <nsISimpleEnumerator> headers; + rv = m_db->EnumerateMessages(getter_AddRefs(headers)); + NS_ENSURE_SUCCESS(rv, rv); + + return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount); +} + +void nsMsgGroupView::InternalClose() +{ + m_groupsTable.Clear(); + // nothing to do if we're not grouped. + if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + return; + + bool rcvDate = false; + + if (m_sortType == nsMsgViewSortType::byReceived) + rcvDate = true; + if (m_db && + ((m_sortType == nsMsgViewSortType::byDate) || + (m_sortType == nsMsgViewSortType::byReceived))) + { + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if (dbFolderInfo) + { + uint32_t expandFlags = 0; + uint32_t num = GetSize(); + + for (uint32_t i = 0; i < num; i++) + { + if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD && ! (m_flags[i] & nsMsgMessageFlags::Elided)) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr)); + if (msgHdr) + { + uint32_t ageBucket; + nsresult rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate); + if (NS_SUCCEEDED(rv)) + expandFlags |= 1 << ageBucket; + } + } + } + dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags); + } + } +} + +NS_IMETHODIMP nsMsgGroupView::Close() +{ + InternalClose(); + return nsMsgDBView::Close(); +} + +// Set rcvDate to true to get the Received: date instead of the Date: date. +nsresult nsMsgGroupView::GetAgeBucketValue(nsIMsgDBHdr *aMsgHdr, uint32_t * aAgeBucket, bool rcvDate) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_ENSURE_ARG_POINTER(aAgeBucket); + + PRTime dateOfMsg; + nsresult rv; + if (!rcvDate) + rv = aMsgHdr->GetDate(&dateOfMsg); + else + { + uint32_t rcvDateSecs; + rv = aMsgHdr->GetUint32Property("dateReceived", &rcvDateSecs); + Seconds2PRTime(rcvDateSecs, &dateOfMsg); + } + NS_ENSURE_SUCCESS(rv, rv); + + PRTime currentTime = PR_Now(); + PRExplodedTime currentExplodedTime; + PR_ExplodeTime(currentTime, PR_LocalTimeParameters, ¤tExplodedTime); + PRExplodedTime explodedMsgTime; + PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime); + + if (m_lastCurExplodedTime.tm_mday && + m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday) + m_dayChanged = true; // this will cause us to rebuild the view. + + m_lastCurExplodedTime = currentExplodedTime; + if (currentExplodedTime.tm_year == explodedMsgTime.tm_year && + currentExplodedTime.tm_month == explodedMsgTime.tm_month && + currentExplodedTime.tm_mday == explodedMsgTime.tm_mday) + { + // same day... + *aAgeBucket = 1; + } + // figure out how many days ago this msg arrived + else if (currentTime > dateOfMsg) + { + // setting the time variables to local time + int64_t GMTLocalTimeShift = currentExplodedTime.tm_params.tp_gmt_offset + + currentExplodedTime.tm_params.tp_dst_offset; + GMTLocalTimeShift *= PR_USEC_PER_SEC; + currentTime += GMTLocalTimeShift; + dateOfMsg += GMTLocalTimeShift; + + // the most recent midnight, counting from current time + int64_t mostRecentMidnight = currentTime - currentTime % PR_USEC_PER_DAY; + int64_t yesterday = mostRecentMidnight - PR_USEC_PER_DAY; + // most recent midnight minus 6 days + int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6); + + // was the message sent yesterday? + if (dateOfMsg >= yesterday) // yes .... + *aAgeBucket = 2; + else if (dateOfMsg >= mostRecentWeek) + *aAgeBucket = 3; + else + { + int64_t lastTwoWeeks = mostRecentMidnight - PR_USEC_PER_DAY * 13; + *aAgeBucket = (dateOfMsg >= lastTwoWeeks) ? 4 : 5; + } + } + else + { + // All that remains is a future date. + *aAgeBucket = 6; + } + return NS_OK; +} + +nsresult nsMsgGroupView::HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey) +{ + nsCString cStringKey; + aHashKey.Truncate(); + nsresult rv = NS_OK; + bool rcvDate = false; + + switch (m_sortType) + { + case nsMsgViewSortType::bySubject: + (void) msgHdr->GetSubject(getter_Copies(cStringKey)); + CopyASCIItoUTF16(cStringKey, aHashKey); + break; + case nsMsgViewSortType::byAuthor: + rv = nsMsgDBView::FetchAuthor(msgHdr, aHashKey); + break; + case nsMsgViewSortType::byRecipient: + (void) msgHdr->GetRecipients(getter_Copies(cStringKey)); + CopyASCIItoUTF16(cStringKey, aHashKey); + break; + case nsMsgViewSortType::byAccount: + case nsMsgViewSortType::byTags: + { + nsCOMPtr <nsIMsgDatabase> dbToUse = m_db; + + if (!dbToUse) // probably search view + GetDBForViewIndex(0, getter_AddRefs(dbToUse)); + + rv = (m_sortType == nsMsgViewSortType::byAccount) + ? FetchAccount(msgHdr, aHashKey) + : FetchTags(msgHdr, aHashKey); + } + break; + case nsMsgViewSortType::byAttachments: + { + uint32_t flags; + msgHdr->GetFlags(&flags); + aHashKey.Assign(flags & nsMsgMessageFlags::Attachment ? '1' : '0'); + break; + } + case nsMsgViewSortType::byFlagged: + { + uint32_t flags; + msgHdr->GetFlags(&flags); + aHashKey.Assign(flags & nsMsgMessageFlags::Marked ? '1' : '0'); + break; + } + case nsMsgViewSortType::byPriority: + { + nsMsgPriorityValue priority; + msgHdr->GetPriority(&priority); + aHashKey.AppendInt(priority); + } + break; + case nsMsgViewSortType::byStatus: + { + uint32_t status = 0; + GetStatusSortValue(msgHdr, &status); + aHashKey.AppendInt(status); + } + break; + case nsMsgViewSortType::byReceived: + rcvDate = true; + MOZ_FALLTHROUGH; + case nsMsgViewSortType::byDate: + { + uint32_t ageBucket; + rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate); + if (NS_SUCCEEDED(rv)) + aHashKey.AppendInt(ageBucket); + break; + } + case nsMsgViewSortType::byCustom: + { + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + if (colHandler) + { + bool isString; + colHandler->IsString(&isString); + if (isString) + rv = colHandler->GetSortStringForRow(msgHdr, aHashKey); + else + { + uint32_t intKey; + rv = colHandler->GetSortLongForRow(msgHdr, &intKey); + aHashKey.AppendInt(intKey); + } + } + break; + } + case nsMsgViewSortType::byCorrespondent: + if (IsOutgoingMsg(msgHdr)) + rv = FetchRecipients(msgHdr, aHashKey); + else + rv = FetchAuthor(msgHdr, aHashKey); + break; + default: + NS_ASSERTION(false, "no hash key for this type"); + rv = NS_ERROR_FAILURE; + } + return rv; +} + +nsMsgGroupThread *nsMsgGroupView::CreateGroupThread(nsIMsgDatabase *db) +{ + return new nsMsgGroupThread(db); +} + +nsMsgGroupThread *nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr *msgHdr, bool *pNewThread) +{ + nsMsgKey msgKey; + uint32_t msgFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + nsString hashKey; + nsresult rv = HashHdr(msgHdr, hashKey); + if (NS_FAILED(rv)) + return nullptr; + +// if (m_sortType == nsMsgViewSortType::byDate) +// msgKey = ((nsPRUint32Key *) hashKey)->GetValue(); + nsCOMPtr<nsIMsgThread> msgThread; + m_groupsTable.Get(hashKey, getter_AddRefs(msgThread)); + bool newThread = !msgThread; + *pNewThread = newThread; + nsMsgViewIndex viewIndexOfThread; // index of first message in thread in view + nsMsgViewIndex threadInsertIndex; // index of newly added header in thread + + nsMsgGroupThread *foundThread = static_cast<nsMsgGroupThread *>(msgThread.get()); + if (foundThread) + { + // find the view index of the root node of the thread in the view + viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread, + true); + if (viewIndexOfThread == nsMsgViewIndex_None) + { + // Something is wrong with the group table. Remove the old group and + // insert a new one. + m_groupsTable.Remove(hashKey); + foundThread = nullptr; + *pNewThread = newThread = true; + } + } + // If the thread does not already exist, create one + if (!foundThread) + { + foundThread = CreateGroupThread(m_db); + msgThread = do_QueryInterface(foundThread); + m_groupsTable.Put(hashKey, msgThread); + if (GroupViewUsesDummyRow()) + { + foundThread->m_dummy = true; + msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN; + } + + viewIndexOfThread = GetInsertIndex(msgHdr); + if (viewIndexOfThread == nsMsgViewIndex_None) + viewIndexOfThread = m_keys.Length(); + + // add the thread root node to the view + InsertMsgHdrAt(viewIndexOfThread, msgHdr, msgKey, + msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided, 0); + + // For dummy rows, Have the header serve as the dummy node (it will be added + // again for its actual content later.) + if (GroupViewUsesDummyRow()) + foundThread->InsertMsgHdrAt(0, msgHdr); + + // Calculate the (integer thread key); this really only needs to be done for + // the byDate case where the expanded state of the groups can be easily + // persisted and restored because of the bounded, consecutive value space + // occupied. We calculate an integer value in all cases mainly because + // it's the sanest choice available... + // (The thread key needs to be an integer, so parse hash keys that are + // stringified integers to real integers, and hash actual strings into + // integers.) + if ((m_sortType == nsMsgViewSortType::byAttachments) || + (m_sortType == nsMsgViewSortType::byFlagged) || + (m_sortType == nsMsgViewSortType::byPriority) || + (m_sortType == nsMsgViewSortType::byStatus) || + (m_sortType == nsMsgViewSortType::byReceived) || + (m_sortType == nsMsgViewSortType::byDate)) + foundThread->m_threadKey = + atoi(NS_LossyConvertUTF16toASCII(hashKey).get()); + else + foundThread->m_threadKey = (nsMsgKey) + PL_HashString(NS_LossyConvertUTF16toASCII(hashKey).get()); + } + // Add the message to the thread as an actual content-bearing header. + // (If we use dummy rows, it was already added to the thread during creation.) + threadInsertIndex = foundThread->AddChildFromGroupView(msgHdr, this); + // check if new hdr became thread root + if (!newThread && threadInsertIndex == 0) + { + // update the root node's header (in the view) to be the same as the root + // node in the thread. + SetMsgHdrAt(msgHdr, viewIndexOfThread, msgKey, + (msgFlags & ~(nsMsgMessageFlags::Elided)) | + // maintain elided flag and dummy flag + (m_flags[viewIndexOfThread] & (nsMsgMessageFlags::Elided + | MSG_VIEW_FLAG_DUMMY)) + // ensure thread and has-children flags are set + | MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN, 0); + // update the content-bearing copy in the thread to match. (the root and + // first nodes in the thread should always be the same header.) + // note: the guy who used to be the root will still exist. If our list of + // nodes was [A A], a new node B is introduced which sorts to be the first + // node, giving us [B A A], our copy makes that [B B A], and things are + // right in the world (since we want the first two headers to be the same + // since one is our dummy and one is real.) + if (GroupViewUsesDummyRow()) + foundThread->SetMsgHdrAt(1, msgHdr); // replace the old duplicate dummy header. + // we do not update the content-bearing copy in the view to match; we leave + // that up to OnNewHeader, which is the piece of code who gets to care + // about whether the thread's children are shown or not (elided) + } + + return foundThread; +} + +NS_IMETHODIMP nsMsgGroupView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) +{ + nsresult rv = NS_OK; + + m_groupsTable.Clear(); + if (aSortType == nsMsgViewSortType::byThread || aSortType == nsMsgViewSortType::byId + || aSortType == nsMsgViewSortType::byNone || aSortType == nsMsgViewSortType::bySize) + return NS_ERROR_INVALID_ARG; + + m_sortType = aSortType; + m_sortOrder = aSortOrder; + m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort; + SaveSortInfo(m_sortType, m_sortOrder); + + if (m_sortType == nsMsgViewSortType::byCustom) + { + // If the desired sort is a custom column and there is no handler found, + // it hasn't been registered yet; after the custom column observer is + // notified with MsgCreateDBView and registers the handler, it will come + // back and build the view. + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + if (!colHandler) + return rv; + } + + bool hasMore; + nsCOMPtr <nsISupports> supports; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) + { + rv = aHeaders->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + bool notUsed; + msgHdr = do_QueryInterface(supports); + AddHdrToThread(msgHdr, ¬Used); + } + } + uint32_t expandFlags = 0; + bool expandAll = m_viewFlags & nsMsgViewFlagsType::kExpandAll; + uint32_t viewFlag = (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0; + if (viewFlag && m_db) + { + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + NS_ENSURE_SUCCESS(rv, rv); + if (dbFolderInfo) + dbFolderInfo->GetUint32Property("dateGroupFlags", 0, &expandFlags); + } + // go through the view updating the flags for threads with more than one message... + // and if grouped by date, expanding threads that were expanded before. + for (uint32_t viewIndex = 0; viewIndex < m_keys.Length(); viewIndex++) + { + nsCOMPtr <nsIMsgThread> thread; + GetThreadContainingIndex(viewIndex, getter_AddRefs(thread)); + if (thread) + { + uint32_t numChildren; + thread->GetNumChildren(&numChildren); + if (numChildren > 1 || viewFlag) + OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN); + if (expandAll || expandFlags) + { + nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>((nsIMsgThread *) thread); + if (expandAll || expandFlags & (1 << groupThread->m_threadKey)) + { + uint32_t numExpanded; + ExpandByIndex(viewIndex, &numExpanded); + viewIndex += numExpanded; + } + } + } + } + *aCount = m_keys.Length(); + return rv; +} + +// we wouldn't need this if we never instantiated this directly, +// but instead used nsMsgThreadedDBView with the grouping flag set. +// Or, we could get rid of the nsMsgThreadedDBView impl of this method. +NS_IMETHODIMP nsMsgGroupView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowAllThreads; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgGroupView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) +{ + nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + nsMsgGroupView* newMsgDBView = (nsMsgGroupView *) aNewMsgDBView; + + // If grouped, we need to clone the group thread hash table. + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) { + for (auto iter = m_groupsTable.Iter(); !iter.Done(); iter.Next()) { + newMsgDBView->m_groupsTable.Put(iter.Key(), iter.UserData()); + } + } + return NS_OK; +} + +// E.g., if the day has changed, we need to close and re-open the view. +// Or, if we're switching between grouping and threading in a cross-folder +// saved search. In that case, we needed to build an enumerator based on the +// old view type, and internally close the view based on its old type, but +// rebuild the new view based on the new view type. So we pass the new +// view flags to OpenWithHdrs. +nsresult nsMsgGroupView::RebuildView(nsMsgViewFlagsTypeValue newFlags) +{ + nsCOMPtr <nsISimpleEnumerator> headers; + if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers)))) + { + int32_t count; + m_dayChanged = false; + AutoTArray<nsMsgKey, 1> preservedSelection; + nsMsgKey curSelectedKey; + SaveAndClearSelection(&curSelectedKey, preservedSelection); + InternalClose(); + int32_t oldSize = GetSize(); + // this is important, because the tree will ask us for our + // row count, which get determine from the number of keys. + m_keys.Clear(); + // be consistent + m_flags.Clear(); + m_levels.Clear(); + + // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount() + if (mTree) + mTree->RowCountChanged(0, -oldSize); + SetSuppressChangeNotifications(true); + nsresult rv = OpenWithHdrs(headers, m_sortType, m_sortOrder, newFlags, &count); + SetSuppressChangeNotifications(false); + if (mTree) + mTree->RowCountChanged(0, GetSize()); + + NS_ENSURE_SUCCESS(rv,rv); + + // now, restore our desired selection + AutoTArray<nsMsgKey, 1> keyArray; + keyArray.AppendElement(curSelectedKey); + + return RestoreSelection(curSelectedKey, keyArray); + } + return NS_OK; +} + +nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) +{ + if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed); + + // check if we're adding a header, and the current day has changed. If it has, we're just going to + // close and re-open the view so things will be correctly categorized. + if (m_dayChanged) + return RebuildView(m_viewFlags); + + bool newThread; + nsMsgGroupThread *thread = AddHdrToThread(newHdr, &newThread); + if (thread) + { + // find the view index of (the root node of) the thread + nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr); + // may need to fix thread counts + if (threadIndex != nsMsgViewIndex_None) + { + if (newThread) + { + // AddHdrToThread creates the header elided, so we need to un-elide it + // if we want it expanded. + if(m_viewFlags & nsMsgViewFlagsType::kExpandAll) + m_flags[threadIndex] &= ~nsMsgMessageFlags::Elided; + } + else + { + m_flags[threadIndex] |= MSG_VIEW_FLAG_HASCHILDREN + | MSG_VIEW_FLAG_ISTHREAD; + } + + int32_t numRowsToInvalidate = 1; + // if the thread is expanded (not elided), we should add the header to + // the view. + if (! (m_flags[threadIndex] & nsMsgMessageFlags::Elided)) + { + uint32_t msgIndexInThread = thread->FindMsgHdr(newHdr); + bool insertedAtThreadRoot = !msgIndexInThread; + // Add any new display node and potentially fix-up changes in the root. + // (If this is a new thread and we are not using a dummy row, the only + // node to display is the root node which has already been added by + // AddHdrToThread. And since there is just the one, no change in root + // could have occurred, so we have nothing to do.) + if (!newThread || GroupViewUsesDummyRow()) + { + // we never want to insert/update the root node, because + // AddHdrToThread has already done that for us (in all cases). + if (insertedAtThreadRoot) + msgIndexInThread++; + // If this header is the new parent of the thread... AND + // If we are not using a dummy row, this means we need to append our + // old node as the first child of the new root. + // (If we are using a dummy row, the old node's "content" node already + // exists (at position threadIndex + 1) and we need to insert the + // "content" copy of the new root node there, pushing our old + // "content" node down.) + // Example mini-diagrams, wrapping the to-add thing with () + // No dummy row; we had: [A], now we have [B], we want [B (A)]. + // Dummy row; we had: [A A], now we have [B A], we want [B (B) A]. + // (Coming into this we're adding 'B') + if (!newThread && insertedAtThreadRoot && !GroupViewUsesDummyRow()) + { + // grab a copy of the old root node ('A') from the thread so we can + // insert it. (offset msgIndexInThread=1 is the right thing; we are + // non-dummy.) + thread->GetChildHdrAt(msgIndexInThread, &newHdr); + } // nothing to do for dummy case, we're already inserting 'B'. + nsMsgKey msgKey; + uint32_t msgFlags; + newHdr->GetMessageKey(&msgKey); + newHdr->GetFlags(&msgFlags); + InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey, + msgFlags, 1); + } + // the call to NoteChange() has to happen after we add the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + // (msgIndexInThread states. new thread: 0, old thread at root: 1) + if (newThread && GroupViewUsesDummyRow()) + NoteChange(threadIndex, 2, nsMsgViewNotificationCode::insertOrDelete); + else + NoteChange(threadIndex + msgIndexInThread, 1, + nsMsgViewNotificationCode::insertOrDelete); + numRowsToInvalidate = msgIndexInThread; + } + // we still need the addition notification for new threads when elided + else if (newThread) + { + NoteChange(threadIndex, 1, + nsMsgViewNotificationCode::insertOrDelete); + } + NoteChange(threadIndex, numRowsToInvalidate, nsMsgViewNotificationCode::changed); + } + } + // if thread is expanded, we need to add hdr to view... + return NS_OK; +} + +NS_IMETHODIMP nsMsgGroupView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, + aInstigator); + + nsCOMPtr <nsIMsgThread> thread; + + // check if we're adding a header, and the current day has changed. If it has, we're just going to + // close and re-open the view so things will be correctly categorized. + if (m_dayChanged) + return RebuildView(m_viewFlags); + + nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t deltaFlags = (aOldFlags ^ aNewFlags); + if (deltaFlags & nsMsgMessageFlags::Read) + thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read); + + return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator); +} + +NS_IMETHODIMP nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, + nsIDBChangeListener *aInstigator) +{ + if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator); + + // check if we're adding a header, and the current day has changed. If it has, we're just going to + // close and re-open the view so things will be correctly categorized. + if (m_dayChanged) + return RebuildView(m_viewFlags); + + nsCOMPtr <nsIMsgThread> thread; + nsMsgKey keyDeleted; + aHdrDeleted->GetMessageKey(&keyDeleted); + + nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread)); + NS_ENSURE_SUCCESS(rv, rv); + nsMsgViewIndex viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread( + thread, true); // yes to dummy node + thread->RemoveChildHdr(aHdrDeleted, nullptr); + + nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>((nsIMsgThread *) thread); + + bool rootDeleted = viewIndexOfThread != nsMsgKey_None && + m_keys[viewIndexOfThread] == keyDeleted; + rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator); + if (groupThread->m_dummy) + { + if (!groupThread->NumRealChildren()) + { + thread->RemoveChildAt(0); // get rid of dummy + if (viewIndexOfThread != nsMsgKey_None) + { + RemoveByIndex(viewIndexOfThread); + if (m_deletingRows) + mIndicesToNoteChange.AppendElement(viewIndexOfThread); + } + } + else if (rootDeleted) + { + // reflect new thread root into view.dummy row. + nsCOMPtr<nsIMsgDBHdr> hdr; + thread->GetChildHdrAt(0, getter_AddRefs(hdr)); + if (hdr) + { + nsMsgKey msgKey; + hdr->GetMessageKey(&msgKey); + SetMsgHdrAt(hdr, viewIndexOfThread, msgKey, m_flags[viewIndexOfThread], 0); + } + } + } + if (!groupThread->m_keys.Length()) + { + nsString hashKey; + rv = HashHdr(aHdrDeleted, hashKey); + if (NS_SUCCEEDED(rv)) + m_groupsTable.Remove(hashKey); + } + return rv; +} + +NS_IMETHODIMP nsMsgGroupView::GetRowProperties(int32_t aRow, nsAString& aProperties) +{ + if (!IsValidIndex(aRow)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) + { + aProperties.AssignLiteral("dummy"); + return NS_OK; + } + + return nsMsgDBView::GetRowProperties(aRow, aProperties); +} + +NS_IMETHODIMP nsMsgGroupView::GetCellProperties(int32_t aRow, + nsITreeColumn *aCol, + nsAString& aProperties) +{ + if (!IsValidIndex(aRow)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) + { + aProperties.AssignLiteral("dummy read"); + + if (!(m_flags[aRow] & nsMsgMessageFlags::Elided)) + return NS_OK; + + // Set unread property if a collapsed group thread has unread. + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + nsString hashKey; + rv = HashHdr(msgHdr, hashKey); + if (NS_FAILED(rv)) + return NS_OK; + + nsCOMPtr<nsIMsgThread> msgThread; + m_groupsTable.Get(hashKey, getter_AddRefs(msgThread)); + nsMsgGroupThread *groupThread = static_cast<nsMsgGroupThread *>(msgThread.get()); + if (!groupThread) + return NS_OK; + + uint32_t numUnrMsg = 0; + groupThread->GetNumUnreadChildren(&numUnrMsg); + if (numUnrMsg > 0) + aProperties.AppendLiteral(" hasUnread"); + + return NS_OK; + } + + return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties); +} + +NS_IMETHODIMP nsMsgGroupView::CellTextForColumn(int32_t aRow, + const char16_t *aColumnName, + nsAString &aValue) { + if (!IsValidIndex(aRow)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY && aColumnName[0] != 'u') + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + nsString hashKey; + rv = HashHdr(msgHdr, hashKey); + if (NS_FAILED(rv)) + return NS_OK; + nsCOMPtr<nsIMsgThread> msgThread; + m_groupsTable.Get(hashKey, getter_AddRefs(msgThread)); + nsMsgGroupThread * groupThread = static_cast<nsMsgGroupThread *>(msgThread.get()); + if (aColumnName[0] == 's' && aColumnName[1] == 'u' ) + { + uint32_t flags; + bool rcvDate = false; + msgHdr->GetFlags(&flags); + aValue.Truncate(); + nsString tmp_str; + switch (m_sortType) + { + case nsMsgViewSortType::byReceived: + rcvDate = true; + MOZ_FALLTHROUGH; + case nsMsgViewSortType::byDate: + { + uint32_t ageBucket = 0; + GetAgeBucketValue(msgHdr, &ageBucket, rcvDate); + switch (ageBucket) + { + case 1: + if (m_kTodayString.IsEmpty()) + m_kTodayString.Adopt(GetString(u"today")); + aValue.Assign(m_kTodayString); + break; + case 2: + if (m_kYesterdayString.IsEmpty()) + m_kYesterdayString.Adopt(GetString(u"yesterday")); + aValue.Assign(m_kYesterdayString); + break; + case 3: + if (m_kLastWeekString.IsEmpty()) + m_kLastWeekString.Adopt(GetString(u"last7Days")); + aValue.Assign(m_kLastWeekString); + break; + case 4: + if (m_kTwoWeeksAgoString.IsEmpty()) + m_kTwoWeeksAgoString.Adopt(GetString(u"last14Days")); + aValue.Assign(m_kTwoWeeksAgoString); + break; + case 5: + if (m_kOldMailString.IsEmpty()) + m_kOldMailString.Adopt(GetString(u"older")); + aValue.Assign(m_kOldMailString); + break; + default: + // Future date, error/spoofed. + if (m_kFutureDateString.IsEmpty()) + m_kFutureDateString.Adopt(GetString(u"futureDate")); + aValue.Assign(m_kFutureDateString); + break; + } + break; + } + case nsMsgViewSortType::bySubject: + FetchSubject(msgHdr, m_flags[aRow], aValue); + break; + case nsMsgViewSortType::byAuthor: + FetchAuthor(msgHdr, aValue); + break; + case nsMsgViewSortType::byStatus: + rv = FetchStatus(m_flags[aRow], aValue); + if (aValue.IsEmpty()) { + tmp_str.Adopt(GetString(u"messagesWithNoStatus")); + aValue.Assign(tmp_str); + } + break; + case nsMsgViewSortType::byTags: + rv = FetchTags(msgHdr, aValue); + if (aValue.IsEmpty()) { + tmp_str.Adopt(GetString(u"untaggedMessages")); + aValue.Assign(tmp_str); + } + break; + case nsMsgViewSortType::byPriority: + FetchPriority(msgHdr, aValue); + if (aValue.IsEmpty()) { + tmp_str.Adopt(GetString(u"noPriority")); + aValue.Assign(tmp_str); + } + break; + case nsMsgViewSortType::byAccount: + FetchAccount(msgHdr, aValue); + break; + case nsMsgViewSortType::byRecipient: + FetchRecipients(msgHdr, aValue); + break; + case nsMsgViewSortType::byAttachments: + tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Attachment ? + u"attachments" : u"noAttachments")); + aValue.Assign(tmp_str); + break; + case nsMsgViewSortType::byFlagged: + tmp_str.Adopt(GetString(flags & nsMsgMessageFlags::Marked ? + u"groupFlagged" : u"notFlagged")); + aValue.Assign(tmp_str); + break; + // byLocation is a special case; we don't want to have duplicate + // all this logic in nsMsgSearchDBView, and its hash key is what we + // want anyways, so just copy it across. + case nsMsgViewSortType::byLocation: + case nsMsgViewSortType::byCorrespondent: + aValue = hashKey; + break; + case nsMsgViewSortType::byCustom: + { + nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler(); + if (colHandler) + { + bool isString; + colHandler->IsString(&isString); + if (isString) + rv = colHandler->GetSortStringForRow(msgHdr.get(), aValue); + else + { + uint32_t intKey; + rv = colHandler->GetSortLongForRow(msgHdr.get(), &intKey); + aValue.AppendInt(intKey); + } + } + if (aValue.IsEmpty()) + aValue.AssignLiteral("*"); + break; + } + + default: + NS_ASSERTION(false, "we don't sort by group for this type"); + break; + } + + if (groupThread) + { + // Get number of messages in group + nsAutoString formattedCountMsg; + uint32_t numMsg = groupThread->NumRealChildren(); + formattedCountMsg.AppendInt(numMsg); + + // Get number of unread messages + nsAutoString formattedCountUnrMsg; + uint32_t numUnrMsg = 0; + groupThread->GetNumUnreadChildren(&numUnrMsg); + formattedCountUnrMsg.AppendInt(numUnrMsg); + + // Add text to header + aValue.Append(NS_LITERAL_STRING(" (")); + if (numUnrMsg) + { + aValue.Append(formattedCountUnrMsg); + aValue.Append(NS_LITERAL_STRING("/")); + } + + aValue.Append(formattedCountMsg); + aValue.Append(NS_LITERAL_STRING(")")); + } + } + else if (aColumnName[0] == 't' && aColumnName[1] == 'o') + { + nsAutoString formattedCountString; + uint32_t numChildren = (groupThread) ? groupThread->NumRealChildren() : 0; + formattedCountString.AppendInt(numChildren); + aValue.Assign(formattedCountString); + } + return NS_OK; + } + return nsMsgDBView::CellTextForColumn(aRow, aColumnName, aValue); +} + +NS_IMETHODIMP nsMsgGroupView::LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) +{ + if (!IsValidIndex(aViewIndex)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_flags[aViewIndex] & MSG_VIEW_FLAG_DUMMY) + { + // if we used to have one item selected, and now we have more than one, we should clear the message pane. + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + nsCOMPtr <nsIMsgWindowCommands> windowCommands; + if (msgWindow && NS_SUCCEEDED(msgWindow->GetWindowCommands(getter_AddRefs(windowCommands))) && windowCommands) + windowCommands->ClearMsgPane(); + // since we are selecting a dummy row, we should also clear out m_currentlyDisplayedMsgUri + m_currentlyDisplayedMsgUri.Truncate(); + return NS_OK; + } + else + return nsMsgDBView::LoadMessageByViewIndex(aViewIndex); +} + +NS_IMETHODIMP nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) +{ + if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread); + + nsString hashKey; + nsresult rv = HashHdr(msgHdr, hashKey); + *pThread = nullptr; + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgThread> thread; + m_groupsTable.Get(hashKey, getter_AddRefs(thread)); + thread.swap(*pThread); + } + return (*pThread) ? NS_OK : NS_ERROR_FAILURE; +} + +int32_t nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr *msgHdr, + nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex) +{ + if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + return nsMsgDBView::FindLevelInThread(msgHdr, startOfThread, viewIndex); + return (startOfThread == viewIndex) ? 0 : 1; +} + +bool nsMsgGroupView::GroupViewUsesDummyRow() +{ + // Return true to always use a header row as root grouped parent row. + return true; +} + +NS_IMETHODIMP nsMsgGroupView::AddColumnHandler(const nsAString& column, + nsIMsgCustomColumnHandler* handler) +{ + nsMsgDBView::AddColumnHandler(column, handler); + + // If the sortType is byCustom and the desired custom column is the one just + // registered, build the view. + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort && + m_sortType == nsMsgViewSortType::byCustom) + { + nsAutoString curCustomColumn; + GetCurCustomColumn(curCustomColumn); + if (curCustomColumn == column) + RebuildView(m_viewFlags); + } + + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgGroupView.h b/mailnews/base/src/nsMsgGroupView.h new file mode 100644 index 0000000000..6a2d526da0 --- /dev/null +++ b/mailnews/base/src/nsMsgGroupView.h @@ -0,0 +1,74 @@ +/* -*- 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/. */ + +#ifndef _nsMsgGroupView_H_ +#define _nsMsgGroupView_H_ + +#include "mozilla/Attributes.h" +#include "nsMsgDBView.h" +#include "nsInterfaceHashtable.h" + +class nsIMsgThread; +class nsMsgGroupThread; + +// Please note that if you override a method of nsMsgDBView, +// you will most likely want to check the m_viewFlags to see if +// we're grouping, and if not, call the base class implementation. +class nsMsgGroupView : public nsMsgDBView +{ +public: + nsMsgGroupView(); + virtual ~nsMsgGroupView(); + + NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, + nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, + int32_t *pCount) override; + NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) override; + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater); + NS_IMETHOD Close() override; + NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, + nsIDBChangeListener *aInstigator) override; + NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) override; + + NS_IMETHOD LoadMessageByViewIndex(nsMsgViewIndex aViewIndex) override; + NS_IMETHOD GetCellProperties(int32_t aRow, nsITreeColumn *aCol, nsAString& aProperties) override; + NS_IMETHOD GetRowProperties(int32_t aRow, nsAString& aProperties) override; + NS_IMETHOD CellTextForColumn(int32_t aRow, const char16_t *aColumnName, + nsAString &aValue) override; + NS_IMETHOD GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) override; + NS_IMETHOD AddColumnHandler(const nsAString& column, nsIMsgCustomColumnHandler* handler) override; + +protected: + virtual void InternalClose(); + nsMsgGroupThread *AddHdrToThread(nsIMsgDBHdr *msgHdr, bool *pNewThread); + virtual nsresult HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey); + nsresult GetAgeBucketValue(nsIMsgDBHdr *aMsgHdr, uint32_t * aAgeBucket, bool rcvDate = false); // helper function to get the age bucket for a hdr, useful when grouped by date + nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool /*ensureListed*/) override; + virtual int32_t FindLevelInThread(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startOfThread, nsMsgViewIndex viewIndex) override; + + // Returns true if we are grouped by a sort attribute that uses a dummy row. + bool GroupViewUsesDummyRow(); + nsresult RebuildView(nsMsgViewFlagsTypeValue viewFlags); + virtual nsMsgGroupThread *CreateGroupThread(nsIMsgDatabase *db); + + nsInterfaceHashtable <nsStringHashKey, nsIMsgThread> m_groupsTable; + PRExplodedTime m_lastCurExplodedTime; + bool m_dayChanged; + +private: + nsString m_kTodayString; + nsString m_kYesterdayString; + nsString m_kLastWeekString; + nsString m_kTwoWeeksAgoString; + nsString m_kOldMailString; + nsString m_kFutureDateString; +}; + +#endif diff --git a/mailnews/base/src/nsMsgMailSession.cpp b/mailnews/base/src/nsMsgMailSession.cpp new file mode 100644 index 0000000000..f9e396988c --- /dev/null +++ b/mailnews/base/src/nsMsgMailSession.cpp @@ -0,0 +1,761 @@ +/* -*- 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 "msgCore.h" // for pre-compiled headers +#include "nsMsgBaseCID.h" +#include "nsMsgMailSession.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsIMsgAccountManager.h" +#include "nsIChromeRegistry.h" +#include "nsIDirectoryService.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIObserverService.h" +#include "nsIAppStartup.h" +#include "nsToolkitCompsCID.h" +#include "nsISupportsPrimitives.h" +#include "nsIAppShellService.h" +#include "nsAppShellCID.h" +#include "nsIWindowMediator.h" +#include "nsIWindowWatcher.h" +#include "nsIMsgMailNewsUrl.h" +#include "prcmon.h" +#include "nsThreadUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIProperties.h" +#include "mozilla/Services.h" + +NS_IMPL_ISUPPORTS(nsMsgMailSession, nsIMsgMailSession, nsIFolderListener) + +nsMsgMailSession::nsMsgMailSession() +{ +} + + +nsMsgMailSession::~nsMsgMailSession() +{ + Shutdown(); +} + +nsresult nsMsgMailSession::Init() +{ + // Ensures the shutdown service is initialised + nsresult rv; + nsCOMPtr<nsIMsgShutdownService> shutdownService = + do_GetService(NS_MSGSHUTDOWNSERVICE_CONTRACTID, &rv); + return rv; +} + +nsresult nsMsgMailSession::Shutdown() +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::AddFolderListener(nsIFolderListener *aListener, + uint32_t aNotifyFlags) +{ + NS_ENSURE_ARG_POINTER(aListener); + + // we don't care about the notification flags for equivalence purposes + size_t index = mListeners.IndexOf(aListener); + NS_ASSERTION(index == size_t(-1), "tried to add duplicate listener"); + if (index == size_t(-1)) + { + folderListener newListener(aListener, aNotifyFlags); + mListeners.AppendElement(newListener); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::RemoveFolderListener(nsIFolderListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +#define NOTIFY_FOLDER_LISTENERS(propertyflag_, propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<folderListener>::ForwardIterator iter(mListeners); \ + while (iter.HasMore()) { \ + const folderListener &fL = iter.GetNext(); \ + if (fL.mNotifyFlags & nsIFolderListener::propertyflag_) \ + fL.mListener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +NS_IMETHODIMP +nsMsgMailSession::OnItemPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + const char* aOldValue, + const char* aNewValue) +{ + NOTIFY_FOLDER_LISTENERS(propertyChanged, OnItemPropertyChanged, + (aItem, aProperty, aOldValue, aNewValue)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::OnItemUnicharPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + const char16_t* aOldValue, + const char16_t* aNewValue) +{ + NOTIFY_FOLDER_LISTENERS(unicharPropertyChanged, OnItemUnicharPropertyChanged, + (aItem, aProperty, aOldValue, aNewValue)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::OnItemIntPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + int64_t aOldValue, + int64_t aNewValue) +{ + NOTIFY_FOLDER_LISTENERS(intPropertyChanged, OnItemIntPropertyChanged, + (aItem, aProperty, aOldValue, aNewValue)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + bool aOldValue, + bool aNewValue) +{ + NOTIFY_FOLDER_LISTENERS(boolPropertyChanged, OnItemBoolPropertyChanged, + (aItem, aProperty, aOldValue, aNewValue)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::OnItemPropertyFlagChanged(nsIMsgDBHdr *aItem, + nsIAtom *aProperty, + uint32_t aOldValue, + uint32_t aNewValue) +{ + NOTIFY_FOLDER_LISTENERS(propertyFlagChanged, OnItemPropertyFlagChanged, + (aItem, aProperty, aOldValue, aNewValue)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::OnItemAdded(nsIMsgFolder *aParentItem, + nsISupports *aItem) +{ + NOTIFY_FOLDER_LISTENERS(added, OnItemAdded, (aParentItem, aItem)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::OnItemRemoved(nsIMsgFolder *aParentItem, + nsISupports *aItem) +{ + NOTIFY_FOLDER_LISTENERS(removed, OnItemRemoved, (aParentItem, aItem)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::OnItemEvent(nsIMsgFolder *aFolder, + nsIAtom *aEvent) +{ + NOTIFY_FOLDER_LISTENERS(event, OnItemEvent, (aFolder, aEvent)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::AddUserFeedbackListener(nsIMsgUserFeedbackListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + size_t index = mFeedbackListeners.IndexOf(aListener); + NS_ASSERTION(index == size_t(-1), "tried to add duplicate listener"); + if (index == size_t(-1)) + mFeedbackListeners.AppendElement(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::RemoveUserFeedbackListener(nsIMsgUserFeedbackListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + mFeedbackListeners.RemoveElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::AlertUser(const nsAString &aMessage, nsIMsgMailNewsUrl *aUrl) +{ + bool listenersNotified = false; + nsTObserverArray<nsCOMPtr<nsIMsgUserFeedbackListener> >::ForwardIterator iter(mFeedbackListeners); + nsCOMPtr<nsIMsgUserFeedbackListener> listener; + + while (iter.HasMore()) + { + bool notified = false; + listener = iter.GetNext(); + listener->OnAlert(aMessage, aUrl, ¬ified); + listenersNotified = listenersNotified || notified; + } + + // If the listeners notified the user, then we don't need to. Also exit if + // aUrl is null because we won't have a nsIMsgWindow in that case. + if (listenersNotified || !aUrl) + return NS_OK; + + // If the url hasn't got a message window, then the error was a generated as a + // result of background activity (e.g. autosync, biff, etc), and hence we + // shouldn't prompt either. + nsCOMPtr<nsIMsgWindow> msgWindow; + aUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + + if (!msgWindow) + return NS_OK; + + nsCOMPtr<nsIPrompt> dialog; + msgWindow->GetPromptDialog(getter_AddRefs(dialog)); + + if (!dialog) // if we didn't get one, use the default.... + { + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + wwatch->GetNewPrompter(0, getter_AddRefs(dialog)); + } + + if (dialog) + return dialog->Alert(nullptr, PromiseFlatString(aMessage).get()); + + return NS_OK; +} + +nsresult nsMsgMailSession::GetTopmostMsgWindow(nsIMsgWindow **aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(aMsgWindow); + + *aMsgWindow = nullptr; + + uint32_t count = mWindows.Count(); + + if (count == 1) + { + NS_ADDREF(*aMsgWindow = mWindows[0]); + return (*aMsgWindow) ? NS_OK : NS_ERROR_FAILURE; + } + else if (count > 1) + { + // If multiple message windows then we have lots more work. + nsresult rv; + + // The msgWindows array does not hold z-order info. Use mediator to get + // the top most window then match that with the msgWindows array. + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> windowEnum; + +#if defined (XP_UNIX) + // The window managers under Unix/X11 do not support ZOrder information, + // so we have to use the normal enumeration call here. + rv = windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnum)); +#else + rv = windowMediator->GetZOrderDOMWindowEnumerator(nullptr, true, + getter_AddRefs(windowEnum)); +#endif + + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> windowSupports; + nsCOMPtr<nsPIDOMWindowOuter> topMostWindow; + nsCOMPtr<nsIDOMDocument> domDocument; + nsCOMPtr<nsIDOMElement> domElement; + nsAutoString windowType; + bool more; + + // loop to get the top most with attibute "mail:3pane" or "mail:messageWindow" + windowEnum->HasMoreElements(&more); + while (more) + { + rv = windowEnum->GetNext(getter_AddRefs(windowSupports)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(windowSupports, NS_ERROR_FAILURE); + + topMostWindow = do_QueryInterface(windowSupports, &rv); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(topMostWindow, NS_ERROR_FAILURE); + + domDocument = do_QueryInterface(topMostWindow->GetDoc()); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(domDocument, NS_ERROR_FAILURE); + + rv = domDocument->GetDocumentElement(getter_AddRefs(domElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(domElement, NS_ERROR_FAILURE); + + rv = domElement->GetAttribute(NS_LITERAL_STRING("windowtype"), windowType); + NS_ENSURE_SUCCESS(rv, rv); + + if (windowType.EqualsLiteral("mail:3pane") || + windowType.EqualsLiteral("mail:messageWindow")) + break; + + windowEnum->HasMoreElements(&more); + } + + // identified the top most window + if (more) + { + // use this for the match + nsIDocShell *topDocShell = topMostWindow->GetDocShell(); + + // loop for the msgWindow array to find the match + nsCOMPtr<nsIDocShell> docShell; + + while (count) + { + nsIMsgWindow *msgWindow = mWindows[--count]; + + rv = msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + + if (topDocShell == docShell) + { + NS_IF_ADDREF(*aMsgWindow = msgWindow); + break; + } + } + } + } + + return (*aMsgWindow) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgMailSession::AddMsgWindow(nsIMsgWindow *msgWindow) +{ + NS_ENSURE_ARG_POINTER(msgWindow); + + mWindows.AppendObject(msgWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::RemoveMsgWindow(nsIMsgWindow *msgWindow) +{ + mWindows.RemoveObject(msgWindow); + // Mac keeps a hidden window open so the app doesn't shut down when + // the last window is closed. So don't shutdown the account manager in that + // case. Similarly, for suite, we don't want to disable mailnews when the + // last mail window is closed. +#if !defined(XP_MACOSX) && !defined(MOZ_SUITE) + if (!mWindows.Count()) + { + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + accountManager->CleanupOnExit(); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP nsMsgMailSession::IsFolderOpenInWindow(nsIMsgFolder *folder, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + + uint32_t count = mWindows.Count(); + + for(uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgFolder> openFolder; + mWindows[i]->GetOpenFolder(getter_AddRefs(openFolder)); + if (folder == openFolder.get()) + { + *aResult = true; + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgMailSession::ConvertMsgURIToMsgURL(const char *aURI, nsIMsgWindow *aMsgWindow, char **aURL) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aURL); + + // convert the rdf msg uri into a url that represents the message... + nsCOMPtr <nsIMsgMessageService> msgService; + nsresult rv = GetMessageServiceFromURI(nsDependentCString(aURI), getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NULL_POINTER); + + nsCOMPtr<nsIURI> tURI; + rv = msgService->GetUrlForUri(aURI, getter_AddRefs(tURI), aMsgWindow); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NULL_POINTER); + + nsAutoCString urlString; + if (NS_SUCCEEDED(tURI->GetSpec(urlString))) + { + *aURL = ToNewCString(urlString); + NS_ENSURE_ARG_POINTER(aURL); + } + return rv; +} + +//---------------------------------------------------------------------------------------- +// GetSelectedLocaleDataDir - If a locale is selected, appends the selected locale to the +// defaults data dir and returns that new defaults data dir +//---------------------------------------------------------------------------------------- +nsresult +nsMsgMailSession::GetSelectedLocaleDataDir(nsIFile *defaultsDir) +{ + NS_ENSURE_ARG_POINTER(defaultsDir); + + bool baseDirExists = false; + nsresult rv = defaultsDir->Exists(&baseDirExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (baseDirExists) { + nsCOMPtr<nsIXULChromeRegistry> packageRegistry = + mozilla::services::GetXULChromeRegistryService(); + if (packageRegistry) { + nsAutoCString localeName; + rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global-region"), false, localeName); + + if (NS_SUCCEEDED(rv) && !localeName.IsEmpty()) { + bool localeDirExists = false; + nsCOMPtr<nsIFile> localeDataDir; + + rv = defaultsDir->Clone(getter_AddRefs(localeDataDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = localeDataDir->AppendNative(localeName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = localeDataDir->Exists(&localeDirExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (localeDirExists) { + // use locale provider instead + rv = defaultsDir->AppendNative(localeName); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + } + return NS_OK; +} + +//---------------------------------------------------------------------------------------- +// GetDataFilesDir - Gets the application's default folder and then appends the +// subdirectory named passed in as param dirName. If there is a selected +// locale, will append that to the dir path before returning the value +//---------------------------------------------------------------------------------------- +NS_IMETHODIMP +nsMsgMailSession::GetDataFilesDir(const char* dirName, nsIFile **dataFilesDir) +{ + + NS_ENSURE_ARG_POINTER(dirName); + NS_ENSURE_ARG_POINTER(dataFilesDir); + + nsresult rv; + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> defaultsDir; + rv = directoryService->Get(NS_APP_DEFAULTS_50_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(defaultsDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = defaultsDir->AppendNative(nsDependentCString(dirName)); + if (NS_SUCCEEDED(rv)) + rv = GetSelectedLocaleDataDir(defaultsDir); + + NS_IF_ADDREF(*dataFilesDir = defaultsDir); + + return rv; +} + +/********************************************************************************/ + +NS_IMPL_ISUPPORTS(nsMsgShutdownService, nsIMsgShutdownService, nsIUrlListener, nsIObserver) + +nsMsgShutdownService::nsMsgShutdownService() +: mQuitMode(nsIAppStartup::eAttemptQuit), + mProcessedShutdown(false), + mQuitForced(false), + mReadyToQuit(false) +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + { + observerService->AddObserver(this, "quit-application-requested", false); + observerService->AddObserver(this, "quit-application-granted", false); + observerService->AddObserver(this, "quit-application", false); + } +} + +nsMsgShutdownService::~nsMsgShutdownService() +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + { + observerService->RemoveObserver(this, "quit-application-requested"); + observerService->RemoveObserver(this, "quit-application-granted"); + observerService->RemoveObserver(this, "quit-application"); + } +} + +nsresult nsMsgShutdownService::ProcessNextTask() +{ + bool shutdownTasksDone = true; + + uint32_t count = mShutdownTasks.Length(); + if (mTaskIndex < count) + { + shutdownTasksDone = false; + + nsCOMPtr<nsIMsgShutdownTask> curTask = mShutdownTasks[mTaskIndex]; + nsString taskName; + curTask->GetCurrentTaskName(taskName); + SetStatusText(taskName); + + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID); + NS_ENSURE_TRUE(mailSession, NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgWindow> topMsgWindow; + mailSession->GetTopmostMsgWindow(getter_AddRefs(topMsgWindow)); + + bool taskIsRunning = true; + nsresult rv = curTask->DoShutdownTask(this, topMsgWindow, &taskIsRunning); + if (NS_FAILED(rv) || !taskIsRunning) + { + // We have failed, let's go on to the next task. + mTaskIndex++; + mMsgProgress->OnProgressChange(nullptr, nullptr, 0, 0, (int32_t)mTaskIndex, count); + ProcessNextTask(); + } + } + + if (shutdownTasksDone) + { + if (mMsgProgress) + mMsgProgress->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + AttemptShutdown(); + } + + return NS_OK; +} + +void nsMsgShutdownService::AttemptShutdown() +{ + if (mQuitForced) + { + PR_CEnterMonitor(this); + mReadyToQuit = true; + PR_CNotifyAll(this); + PR_CExitMonitor(this); + } + else + { + nsCOMPtr<nsIAppStartup> appStartup = + do_GetService(NS_APPSTARTUP_CONTRACTID); + NS_ENSURE_TRUE_VOID(appStartup); + NS_ENSURE_SUCCESS_VOID(appStartup->Quit(mQuitMode)); + } +} + +NS_IMETHODIMP nsMsgShutdownService::SetShutdownListener(nsIWebProgressListener *inListener) +{ + NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE); + mMsgProgress->RegisterListener(inListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgShutdownService::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + // Due to bug 459376 we don't always get quit-application-requested and + // quit-application-granted. quit-application-requested is preferred, but if + // we don't then we have to hook onto quit-application, but we don't want + // to do the checking twice so we set some flags to prevent that. + if (!strcmp(aTopic, "quit-application-granted")) + { + // Quit application has been requested and granted, therefore we will shut + // down. + mProcessedShutdown = true; + return NS_OK; + } + + // If we've already processed a shutdown notification, no need to do it again. + if (!strcmp(aTopic, "quit-application")) + { + if (mProcessedShutdown) + return NS_OK; + else + mQuitForced = true; + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_STATE(observerService); + + nsCOMPtr<nsISimpleEnumerator> listenerEnum; + nsresult rv = observerService->EnumerateObservers("msg-shutdown", getter_AddRefs(listenerEnum)); + if (NS_SUCCEEDED(rv) && listenerEnum) + { + bool hasMore; + listenerEnum->HasMoreElements(&hasMore); + if (!hasMore) + return NS_OK; + + while (hasMore) + { + nsCOMPtr<nsISupports> curObject; + listenerEnum->GetNext(getter_AddRefs(curObject)); + + nsCOMPtr<nsIMsgShutdownTask> curTask = do_QueryInterface(curObject); + if (curTask) + { + bool shouldRunTask; + curTask->GetNeedsToRunTask(&shouldRunTask); + if (shouldRunTask) + mShutdownTasks.AppendObject(curTask); + } + + listenerEnum->HasMoreElements(&hasMore); + } + + if (mShutdownTasks.Count() < 1) + return NS_ERROR_FAILURE; + + mTaskIndex = 0; + + mMsgProgress = do_CreateInstance(NS_MSGPROGRESS_CONTRACTID); + NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID); + NS_ENSURE_TRUE(mailSession, NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgWindow> topMsgWindow; + mailSession->GetTopmostMsgWindow(getter_AddRefs(topMsgWindow)); + + nsCOMPtr<mozIDOMWindowProxy> internalDomWin; + if (topMsgWindow) + topMsgWindow->GetDomWindow(getter_AddRefs(internalDomWin)); + + if (!internalDomWin) + { + // First see if there is a window open. + nsCOMPtr<nsIWindowMediator> winMed = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); + winMed->GetMostRecentWindow(nullptr, getter_AddRefs(internalDomWin)); + + //If not use the hidden window. + if (!internalDomWin) + { + nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + appShell->GetHiddenDOMWindow(getter_AddRefs(internalDomWin)); + NS_ENSURE_TRUE(internalDomWin, NS_ERROR_FAILURE); // bail if we don't get a window. + } + } + + if (!mQuitForced) + { + nsCOMPtr<nsISupportsPRBool> stopShutdown = do_QueryInterface(aSubject); + stopShutdown->SetData(true); + + // If the attempted quit was a restart, be sure to restart the app once + // the tasks have been run. This is usually the case when addons or + // updates are going to be installed. + if (aData && nsDependentString(aData).EqualsLiteral("restart")) + mQuitMode |= nsIAppStartup::eRestart; + } + + mMsgProgress->OpenProgressDialog(internalDomWin, topMsgWindow, + "chrome://messenger/content/shutdownWindow.xul", + false, nullptr); + + if (mQuitForced) + { + nsCOMPtr<nsIThread> thread(do_GetCurrentThread()); + + mReadyToQuit = false; + while (!mReadyToQuit) + { + PR_CEnterMonitor(this); + // Waiting for 50 milliseconds + PR_CWait(this, PR_MicrosecondsToInterval(50000UL)); + PR_CExitMonitor(this); + NS_ProcessPendingEvents(thread); + } + } + } + + return NS_OK; +} + +// nsIUrlListener +NS_IMETHODIMP nsMsgShutdownService::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgShutdownService::OnStopRunningUrl(nsIURI *url, nsresult aExitCode) +{ + mTaskIndex++; + + if (mMsgProgress) + { + int32_t numTasks = mShutdownTasks.Count(); + mMsgProgress->OnProgressChange(nullptr, nullptr, 0, 0, (int32_t)mTaskIndex, numTasks); + } + + ProcessNextTask(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgShutdownService::GetNumTasks(int32_t *inNumTasks) +{ + *inNumTasks = mShutdownTasks.Count(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgShutdownService::StartShutdownTasks() +{ + ProcessNextTask(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgShutdownService::CancelShutdownTasks() +{ + AttemptShutdown(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgShutdownService::SetStatusText(const nsAString & inStatusString) +{ + nsString statusString(inStatusString); + if (mMsgProgress) + mMsgProgress->OnStatusChange(nullptr, nullptr, NS_OK, nsString(statusString).get()); + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgMailSession.h b/mailnews/base/src/nsMsgMailSession.h new file mode 100644 index 0000000000..6e4f57411b --- /dev/null +++ b/mailnews/base/src/nsMsgMailSession.h @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +#ifndef nsMsgMailSession_h___ +#define nsMsgMailSession_h___ + +#include "nsIMsgMailSession.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIMsgWindow.h" +#include "nsCOMArray.h" +#include "nsIMsgShutdown.h" +#include "nsIObserver.h" +#include "nsIMutableArray.h" +#include "nsIMsgProgress.h" +#include "nsTArray.h" +#include "nsTObserverArray.h" +#include "nsIMsgUserFeedbackListener.h" +#include "nsIUrlListener.h" + +/////////////////////////////////////////////////////////////////////////////////// +// The mail session is a replacement for the old 4.x MSG_Master object. It contains +// mail session generic information such as the user's current mail identity, .... +// I'm starting this off as an empty interface and as people feel they need to +// add more information to it, they can. I think this is a better approach than +// trying to port over the old MSG_Master in its entirety as that had a lot of +// cruft in it.... +////////////////////////////////////////////////////////////////////////////////// + +class nsMsgMailSession : public nsIMsgMailSession, + public nsIFolderListener +{ +public: + nsMsgMailSession(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGMAILSESSION + NS_DECL_NSIFOLDERLISTENER + + nsresult Init(); + nsresult GetSelectedLocaleDataDir(nsIFile *defaultsDir); + +protected: + virtual ~nsMsgMailSession(); + + struct folderListener { + nsCOMPtr<nsIFolderListener> mListener; + uint32_t mNotifyFlags; + + folderListener(nsIFolderListener *aListener, uint32_t aNotifyFlags) + : mListener(aListener), mNotifyFlags(aNotifyFlags) {} + folderListener(const folderListener &aListener) + : mListener(aListener.mListener), mNotifyFlags(aListener.mNotifyFlags) {} + ~folderListener() {} + + int operator==(nsIFolderListener* aListener) const { + return mListener == aListener; + } + int operator==(const folderListener &aListener) const { + return mListener == aListener.mListener && + mNotifyFlags == aListener.mNotifyFlags; + } + }; + + nsTObserverArray<folderListener> mListeners; + nsTObserverArray<nsCOMPtr<nsIMsgUserFeedbackListener> > mFeedbackListeners; + + nsCOMArray<nsIMsgWindow> mWindows; + // stick this here temporarily + nsCOMPtr <nsIMsgWindow> m_temporaryMsgWindow; +}; + +/********************************************************************************/ + +class nsMsgShutdownService : public nsIMsgShutdownService, + public nsIUrlListener, + public nsIObserver +{ +public: + nsMsgShutdownService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSHUTDOWNSERVICE + NS_DECL_NSIURLLISTENER + NS_DECL_NSIOBSERVER + +protected: + nsresult ProcessNextTask(); + void AttemptShutdown(); + +private: + virtual ~nsMsgShutdownService(); + + nsCOMArray<nsIMsgShutdownTask> mShutdownTasks; + nsCOMPtr<nsIMsgProgress> mMsgProgress; + uint32_t mTaskIndex; + uint32_t mQuitMode; + bool mProcessedShutdown; + bool mQuitForced; + bool mReadyToQuit; +}; + +#endif /* nsMsgMailSession_h__ */ diff --git a/mailnews/base/src/nsMsgOfflineManager.cpp b/mailnews/base/src/nsMsgOfflineManager.cpp new file mode 100644 index 0000000000..be1fac4cb6 --- /dev/null +++ b/mailnews/base/src/nsMsgOfflineManager.cpp @@ -0,0 +1,399 @@ +/* -*- 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/. */ + +/* + * The offline manager service - manages going online and offline, and synchronization + */ +#include "msgCore.h" +#include "netCore.h" +#include "nsMsgOfflineManager.h" +#include "nsIServiceManager.h" +#include "nsMsgBaseCID.h" +#include "nsIImapService.h" +#include "nsMsgImapCID.h" +#include "nsIMsgSendLater.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgCompCID.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsMsgNewsCID.h" +#include "nsINntpService.h" +#include "nsIMsgStatusFeedback.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Services.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" + +static NS_DEFINE_CID(kMsgSendLaterCID, NS_MSGSENDLATER_CID); + +NS_IMPL_ISUPPORTS(nsMsgOfflineManager, + nsIMsgOfflineManager, + nsIMsgSendLaterListener, + nsIObserver, + nsISupportsWeakReference, + nsIUrlListener) + +nsMsgOfflineManager::nsMsgOfflineManager() : + m_inProgress (false), + m_sendUnsentMessages(false), + m_downloadNews(false), + m_downloadMail(false), + m_playbackOfflineImapOps(false), + m_goOfflineWhenDone(false), + m_curState(eNoState), + m_curOperation(eNoOp) +{ +} + +nsMsgOfflineManager::~nsMsgOfflineManager() +{ +} + +/* attribute nsIMsgWindow window; */ +NS_IMETHODIMP nsMsgOfflineManager::GetWindow(nsIMsgWindow * *aWindow) +{ + NS_ENSURE_ARG(aWindow); + *aWindow = m_window; + NS_IF_ADDREF(*aWindow); + return NS_OK; +} +NS_IMETHODIMP nsMsgOfflineManager::SetWindow(nsIMsgWindow * aWindow) +{ + m_window = aWindow; + if (m_window) + m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback)); + else + m_statusFeedback = nullptr; + return NS_OK; +} + +/* attribute boolean inProgress; */ +NS_IMETHODIMP nsMsgOfflineManager::GetInProgress(bool *aInProgress) +{ + NS_ENSURE_ARG(aInProgress); + *aInProgress = m_inProgress; + return NS_OK; +} + +NS_IMETHODIMP nsMsgOfflineManager::SetInProgress(bool aInProgress) +{ + m_inProgress = aInProgress; + return NS_OK; +} + +nsresult nsMsgOfflineManager::StopRunning(nsresult exitStatus) +{ + m_inProgress = false; + return exitStatus; +} + +nsresult nsMsgOfflineManager::AdvanceToNextState(nsresult exitStatus) +{ + // NS_BINDING_ABORTED is used for the user pressing stop, which + // should cause us to abort the offline process. Other errors + // should allow us to continue. + if (exitStatus == NS_BINDING_ABORTED) + { + return StopRunning(exitStatus); + } + if (m_curOperation == eGoingOnline) + { + switch (m_curState) + { + case eNoState: + + m_curState = eSendingUnsent; + if (m_sendUnsentMessages) + { + SendUnsentMessages(); + } + else + AdvanceToNextState(NS_OK); + break; + case eSendingUnsent: + + m_curState = eSynchronizingOfflineImapChanges; + if (m_playbackOfflineImapOps) + return SynchronizeOfflineImapChanges(); + else + AdvanceToNextState(NS_OK); // recurse to next state. + break; + case eSynchronizingOfflineImapChanges: + m_curState = eDone; + return StopRunning(exitStatus); + default: + NS_ASSERTION(false, "unhandled current state when going online"); + } + } + else if (m_curOperation == eDownloadingForOffline) + { + switch (m_curState) + { + case eNoState: + m_curState = eDownloadingNews; + if (m_downloadNews) + DownloadOfflineNewsgroups(); + else + AdvanceToNextState(NS_OK); + break; + case eSendingUnsent: + if (m_goOfflineWhenDone) + { + SetOnlineState(false); + } + break; + case eDownloadingNews: + m_curState = eDownloadingMail; + if (m_downloadMail) + DownloadMail(); + else + AdvanceToNextState(NS_OK); + break; + case eDownloadingMail: + m_curState = eSendingUnsent; + if (m_sendUnsentMessages) + SendUnsentMessages(); + else + AdvanceToNextState(NS_OK); + break; + default: + NS_ASSERTION(false, "unhandled current state when downloading for offline"); + } + + } + return NS_OK; +} + +nsresult nsMsgOfflineManager::SynchronizeOfflineImapChanges() +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return imapService->PlaybackAllOfflineOperations(m_window, this, getter_AddRefs(mOfflineImapSync)); +} + +nsresult nsMsgOfflineManager::SendUnsentMessages() +{ + nsresult rv; + nsCOMPtr<nsIMsgSendLater> pMsgSendLater(do_GetService(kMsgSendLaterCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // now we have to iterate over the identities, finding the *unique* unsent messages folder + // for each one, determine if they have unsent messages, and if so, add them to the list + // of identities to send unsent messages from. + // However, I think there's only ever one unsent messages folder at the moment, + // so I think we'll go with that for now. + nsCOMPtr<nsIArray> identities; + + if (NS_SUCCEEDED(rv) && accountManager) + { + rv = accountManager->GetAllIdentities(getter_AddRefs(identities)); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr <nsIMsgIdentity> identityToUse; + uint32_t numIndentities; + identities->GetLength(&numIndentities); + for (uint32_t i = 0; i < numIndentities; i++) + { + nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, i, &rv)); + if (NS_SUCCEEDED(rv) && thisIdentity) + { + nsCOMPtr <nsIMsgFolder> outboxFolder; + pMsgSendLater->GetUnsentMessagesFolder(thisIdentity, getter_AddRefs(outboxFolder)); + if (outboxFolder) + { + int32_t numMessages; + outboxFolder->GetTotalMessages(false, &numMessages); + if (numMessages > 0) + { + identityToUse = thisIdentity; + break; + } + } + } + } + if (identityToUse) + { +#ifdef MOZ_SUITE + if (m_statusFeedback) + pMsgSendLater->SetStatusFeedback(m_statusFeedback); +#endif + + pMsgSendLater->AddListener(this); + rv = pMsgSendLater->SendUnsentMessages(identityToUse); + ShowStatus("sendingUnsent"); + // if we succeeded, return - we'll run the next operation when the + // send finishes. Otherwise, advance to the next state. + if (NS_SUCCEEDED(rv)) + return rv; + } + return AdvanceToNextState(rv); + +} + +#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties" + +nsresult nsMsgOfflineManager::ShowStatus(const char *statusMsgName) +{ + if (!mStringBundle) + { + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + sBundleService->CreateBundle(MESSENGER_STRING_URL, getter_AddRefs(mStringBundle)); + return NS_OK; + } + + nsString statusString; + nsresult res = mStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(statusMsgName).get(), + getter_Copies(statusString)); + + if (NS_SUCCEEDED(res) && m_statusFeedback) + m_statusFeedback->ShowStatusString(statusString); + + return res; +} + +nsresult nsMsgOfflineManager::DownloadOfflineNewsgroups() +{ + nsresult rv; + ShowStatus("downloadingNewsgroups"); + nsCOMPtr<nsINntpService> nntpService(do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && nntpService) + rv = nntpService->DownloadNewsgroupsForOffline(m_window, this); + + if (NS_FAILED(rv)) + return AdvanceToNextState(rv); + return rv; +} + +nsresult nsMsgOfflineManager::DownloadMail() +{ + nsresult rv = NS_OK; + ShowStatus("downloadingMail"); + nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return imapService->DownloadAllOffineImapFolders(m_window, this); + // ### we should do get new mail on pop servers, and download imap messages for offline use. +} + +/* void goOnline (in boolean sendUnsentMessages, in boolean playbackOfflineImapOperations, in nsIMsgWindow aMsgWindow); */ +NS_IMETHODIMP nsMsgOfflineManager::GoOnline(bool sendUnsentMessages, bool playbackOfflineImapOperations, nsIMsgWindow *aMsgWindow) +{ + m_sendUnsentMessages = sendUnsentMessages; + m_playbackOfflineImapOps = playbackOfflineImapOperations; + m_curOperation = eGoingOnline; + m_curState = eNoState; + SetWindow(aMsgWindow); + SetOnlineState(true); + if (!m_sendUnsentMessages && !playbackOfflineImapOperations) + return NS_OK; + else + AdvanceToNextState(NS_OK); + return NS_OK; +} + +/* void synchronizeForOffline (in boolean downloadNews, in boolean downloadMail, in boolean sendUnsentMessages, in boolean goOfflineWhenDone, in nsIMsgWindow aMsgWindow); */ +NS_IMETHODIMP nsMsgOfflineManager::SynchronizeForOffline(bool downloadNews, bool downloadMail, bool sendUnsentMessages, bool goOfflineWhenDone, nsIMsgWindow *aMsgWindow) +{ + m_curOperation = eDownloadingForOffline; + m_downloadNews = downloadNews; + m_downloadMail = downloadMail; + m_sendUnsentMessages = sendUnsentMessages; + SetWindow(aMsgWindow); + m_goOfflineWhenDone = goOfflineWhenDone; + m_curState = eNoState; + if (!downloadNews && !downloadMail && !sendUnsentMessages) + { + if (goOfflineWhenDone) + return SetOnlineState(false); + } + else + return AdvanceToNextState(NS_OK); + return NS_OK; +} + +nsresult nsMsgOfflineManager::SetOnlineState(bool online) +{ + nsCOMPtr<nsIIOService> netService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED); + return netService->SetOffline(!online); +} + + // nsIUrlListener methods + +NS_IMETHODIMP +nsMsgOfflineManager::OnStartRunningUrl(nsIURI * aUrl) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgOfflineManager::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) +{ + mOfflineImapSync = nullptr; + + AdvanceToNextState(aExitCode); + return NS_OK; +} + +NS_IMETHODIMP nsMsgOfflineManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + return NS_OK; +} + +// nsIMsgSendLaterListener implementation +NS_IMETHODIMP +nsMsgOfflineManager::OnStartSending(uint32_t aTotalMessageCount) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgOfflineManager::OnMessageStartSending(uint32_t aCurrentMessage, + uint32_t aTotalMessageCount, + nsIMsgDBHdr *aMessageHeader, + nsIMsgIdentity *aIdentity) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgOfflineManager::OnMessageSendProgress(uint32_t aCurrentMessage, + uint32_t aTotalMessageCount, + uint32_t aMessageSendPercent, + uint32_t aMessageCopyPercent) +{ + if (m_statusFeedback && aTotalMessageCount) + return m_statusFeedback->ShowProgress((100 * aCurrentMessage) / + aTotalMessageCount); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgOfflineManager::OnMessageSendError(uint32_t aCurrentMessage, + nsIMsgDBHdr *aMessageHeader, + nsresult aStatus, + const char16_t *aMsg) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgOfflineManager::OnStopSending(nsresult aStatus, + const char16_t *aMsg, uint32_t aTotalTried, + uint32_t aSuccessful) +{ +#ifdef NS_DEBUG + if (NS_SUCCEEDED(aStatus)) + printf("SendLaterListener::OnStopSending: Tried to send %d messages. %d successful.\n", + aTotalTried, aSuccessful); +#endif + return AdvanceToNextState(aStatus); +} diff --git a/mailnews/base/src/nsMsgOfflineManager.h b/mailnews/base/src/nsMsgOfflineManager.h new file mode 100644 index 0000000000..07e6b347af --- /dev/null +++ b/mailnews/base/src/nsMsgOfflineManager.h @@ -0,0 +1,85 @@ +/* -*- 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/. */ + +#ifndef nsMsgOfflineManager_h__ +#define nsMsgOfflineManager_h__ + +#include "nscore.h" +#include "nsIMsgOfflineManager.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsIUrlListener.h" +#include "nsIMsgWindow.h" +#include "nsIMsgSendLaterListener.h" +#include "nsIStringBundle.h" + +class nsMsgOfflineManager + : public nsIMsgOfflineManager, + public nsIObserver, + public nsSupportsWeakReference, + public nsIMsgSendLaterListener, + public nsIUrlListener +{ +public: + + nsMsgOfflineManager(); + + NS_DECL_THREADSAFE_ISUPPORTS + + /* nsIMsgOfflineManager methods */ + + NS_DECL_NSIMSGOFFLINEMANAGER + NS_DECL_NSIOBSERVER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGSENDLATERLISTENER + + typedef enum + { + eStarting = 0, + eSynchronizingOfflineImapChanges = 1, + eDownloadingNews = 2, + eDownloadingMail = 3, + eSendingUnsent = 4, + eDone = 5, + eNoState = 6 // we're not doing anything + } offlineManagerState; + + typedef enum + { + eGoingOnline = 0, + eDownloadingForOffline = 1, + eNoOp = 2 // no operation in progress + } offlineManagerOperation; + +private: + virtual ~nsMsgOfflineManager(); + + nsresult AdvanceToNextState(nsresult exitStatus); + nsresult SynchronizeOfflineImapChanges(); + nsresult StopRunning(nsresult exitStatus); + nsresult SendUnsentMessages(); + nsresult DownloadOfflineNewsgroups(); + nsresult DownloadMail(); + + nsresult SetOnlineState(bool online); + nsresult ShowStatus(const char *statusMsgName); + + bool m_inProgress; + bool m_sendUnsentMessages; + bool m_downloadNews; + bool m_downloadMail; + bool m_playbackOfflineImapOps; + bool m_goOfflineWhenDone; + offlineManagerState m_curState; + offlineManagerOperation m_curOperation; + nsCOMPtr <nsIMsgWindow> m_window; + nsCOMPtr <nsIMsgStatusFeedback> m_statusFeedback; + nsCOMPtr<nsIStringBundle> mStringBundle; + nsCOMPtr<nsISupports> mOfflineImapSync; + +}; + +#endif diff --git a/mailnews/base/src/nsMsgPrintEngine.cpp b/mailnews/base/src/nsMsgPrintEngine.cpp new file mode 100644 index 0000000000..d2f8157edd --- /dev/null +++ b/mailnews/base/src/nsMsgPrintEngine.cpp @@ -0,0 +1,741 @@ +/* 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/. */ + +/** + * nsMsgPrintEngine.cpp provides a DocShell container for use in printing. + */ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIComponentManager.h" + +#include "nsISupports.h" + +#include "nsIURI.h" + +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsIWebProgress.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsMsgPrintEngine.h" +#include "nsIDocumentLoader.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsThreadUtils.h" +#include "nsAutoPtr.h" +#include "mozilla/Services.h" + +// Interfaces Needed +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocShellTreeItem.h" +#include "nsIWebNavigation.h" +#include "nsIChannel.h" +#include "nsIContentViewerFile.h" +#include "nsServiceManagerUtils.h" + +static const char* kPrintingPromptService = "@mozilla.org/embedcomp/printingprompt-service;1"; + +///////////////////////////////////////////////////////////////////////// +// nsMsgPrintEngine implementation +///////////////////////////////////////////////////////////////////////// + +nsMsgPrintEngine::nsMsgPrintEngine() : + mIsDoingPrintPreview(false), + mMsgInx(nsIMsgPrintEngine::MNAB_START) +{ + mCurrentlyPrintingURI = -1; +} + + +nsMsgPrintEngine::~nsMsgPrintEngine() +{ +} + +// Implement AddRef and Release +NS_IMPL_ISUPPORTS(nsMsgPrintEngine, + nsIMsgPrintEngine, + nsIWebProgressListener, + nsIObserver, + nsISupportsWeakReference) + +// nsIWebProgressListener implementation +NS_IMETHODIMP +nsMsgPrintEngine::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest *aRequest, + uint32_t progressStateFlags, + nsresult aStatus) +{ + nsresult rv = NS_OK; + + // top-level document load data + if (progressStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) { + if (progressStateFlags & nsIWebProgressListener::STATE_START) { + // Tell the user we are loading... + nsString msg; + GetString(u"LoadingMessageToPrint", msg); + SetStatusMessage(msg); + } + + if (progressStateFlags & nsIWebProgressListener::STATE_STOP) { + nsCOMPtr<nsIDocumentLoader> docLoader(do_QueryInterface(aWebProgress)); + if (docLoader) + { + // Check to see if the document DOMWin that is finished loading is the same + // one as the mail msg that we started to load. + // We only want to print when the entire msg and all of its attachments + // have finished loading. + // The mail msg doc is the last one to receive the STATE_STOP notification + nsCOMPtr<nsISupports> container; + docLoader->GetContainer(getter_AddRefs(container)); + nsCOMPtr<mozIDOMWindowProxy> domWindow(do_GetInterface(container)); + if (domWindow.get() != mMsgDOMWin.get()) { + return NS_OK; + } + } + nsCOMPtr<nsIWebProgressListener> wpl(do_QueryInterface(mPrintPromptService)); + if (wpl) { + wpl->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP|nsIWebProgressListener::STATE_IS_DOCUMENT, NS_OK); + mPrintProgressListener = nullptr; + mPrintProgress = nullptr; + mPrintProgressParams = nullptr; + } + + bool isPrintingCancelled = false; + if (mPrintSettings) + { + mPrintSettings->GetIsCancelled(&isPrintingCancelled); + } + if (!isPrintingCancelled) { + // if aWebProgress is a documentloader than the notification from + // loading the documents. If it is NULL (or not a DocLoader) then it + // it coming from Printing + if (docLoader) { + // Now, fire off the print operation! + rv = NS_ERROR_FAILURE; + + // Tell the user the message is loaded... + nsString msg; + GetString(u"MessageLoaded", msg); + SetStatusMessage(msg); + + NS_ASSERTION(mDocShell,"can't print, there is no docshell"); + if ( (!mDocShell) || (!aRequest) ) + { + return StartNextPrintOperation(); + } + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(aRequest); + if (!aChannel) return NS_ERROR_FAILURE; + + // Make sure this isn't just "about:blank" finishing.... + nsCOMPtr<nsIURI> originalURI = nullptr; + if (NS_SUCCEEDED(aChannel->GetOriginalURI(getter_AddRefs(originalURI))) && originalURI) + { + nsAutoCString spec; + + if (NS_SUCCEEDED(originalURI->GetSpec(spec))) + { + if (spec.Equals("about:blank")) + { + return StartNextPrintOperation(); + } + } + } + + // If something bad happens here (menaing we can fire the PLEvent, highly unlikely) + // we will still ask the msg to print, but if the user "cancels" out of the + // print dialog the hidden print window will not be "closed" + if (!FirePrintEvent()) + { + PrintMsgWindow(); + } + } else { + FireStartNextEvent(); + rv = NS_OK; + } + } + else + { + if (mWindow) { + nsPIDOMWindowOuter::From(mWindow)->Close(); + } + } + } + } + + return rv; +} + +NS_IMETHODIMP +nsMsgPrintEngine::OnProgressChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgPrintEngine::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI *location, + uint32_t aFlags) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgPrintEngine::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgPrintEngine::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgPrintEngine::SetWindow(mozIDOMWindowProxy *aWin) +{ + if (!aWin) + { + // It isn't an error to pass in null for aWin, in fact it means we are shutting + // down and we should start cleaning things up... + return NS_OK; + } + + mWindow = aWin; + + NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(mWindow); + + window->GetDocShell()->SetAppType(nsIDocShell::APP_TYPE_MAIL); + + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = + do_QueryInterface(window->GetDocShell()); + NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShellTreeItem> rootAsItem; + docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem)); + + nsCOMPtr<nsIDocShellTreeItem> childItem; + rootAsItem->FindChildWithName(NS_LITERAL_STRING("content"), true, + false, nullptr, nullptr, + getter_AddRefs(childItem)); + + mDocShell = do_QueryInterface(childItem); + + if(mDocShell) + SetupObserver(); + + return NS_OK; +} + +/* void setParentWindow (in mozIDOMWindowProxy ptr); */ +NS_IMETHODIMP nsMsgPrintEngine::SetParentWindow(mozIDOMWindowProxy *ptr) +{ + mParentWindow = ptr; + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgPrintEngine::ShowWindow(bool aShow) +{ + nsresult rv; + + NS_ENSURE_TRUE(mWindow, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(mWindow); + nsCOMPtr <nsIDocShellTreeItem> treeItem = + do_QueryInterface(window->GetDocShell(), &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIDocShellTreeOwner> treeOwner; + rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + NS_ENSURE_SUCCESS(rv,rv); + + if (treeOwner) { + // disable (enable) the window + nsCOMPtr<nsIBaseWindow> baseWindow; + baseWindow = do_QueryInterface(treeOwner, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = baseWindow->SetEnabled(aShow); + NS_ENSURE_SUCCESS(rv,rv); + + // hide or show the window + baseWindow->SetVisibility(aShow); + } + return rv; +} + +NS_IMETHODIMP +nsMsgPrintEngine::AddPrintURI(const char16_t *aMsgURI) +{ + NS_ENSURE_ARG_POINTER(aMsgURI); + + mURIArray.AppendElement(nsDependentString(aMsgURI)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgPrintEngine::SetPrintURICount(int32_t aCount) +{ + mURICount = aCount; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgPrintEngine::StartPrintOperation(nsIPrintSettings* aPS) +{ + NS_ENSURE_ARG_POINTER(aPS); + mPrintSettings = aPS; + + // Load the about:blank on the tail end... + nsresult rv = AddPrintURI(u"about:blank"); + if (NS_FAILED(rv)) return rv; + return StartNextPrintOperation(); +} + +//---------------------------------------------------------------------- +// Set up to use the "pluggable" Print Progress Dialog +nsresult +nsMsgPrintEngine::ShowProgressDialog(bool aIsForPrinting, bool& aDoNotify) +{ + nsresult rv; + + // default to not notifying, that if something here goes wrong + // or we aren't going to show the progress dialog we can straight into + // reflowing the doc for printing. + aDoNotify = false; + + // Assume we can't do progress and then see if we can + bool showProgressDialog = false; + + // if it is already being shown then don't bother to find out if it should be + // so skip this and leave mShowProgressDialog set to FALSE + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + prefBranch->GetBoolPref("print.show_print_progress", &showProgressDialog); + } + + // Turning off the showing of Print Progress in Prefs overrides + // whether the calling PS desire to have it on or off, so only check PS if + // prefs says it's ok to be on. + if (showProgressDialog) + { + mPrintSettings->GetShowPrintProgress(&showProgressDialog); + } + + // Now open the service to get the progress dialog + // If we don't get a service, that's ok, then just don't show progress + if (showProgressDialog) { + if (!mPrintPromptService) + { + mPrintPromptService = do_GetService(kPrintingPromptService); + } + if (mPrintPromptService) + { + nsCOMPtr<mozIDOMWindowProxy> domWin(do_QueryInterface(mParentWindow)); + if (!domWin) + { + domWin = mWindow; + } + + rv = mPrintPromptService->ShowProgress(domWin, mWebBrowserPrint, mPrintSettings, this, aIsForPrinting, + getter_AddRefs(mPrintProgressListener), + getter_AddRefs(mPrintProgressParams), + &aDoNotify); + if (NS_SUCCEEDED(rv)) { + + showProgressDialog = mPrintProgressListener != nullptr && mPrintProgressParams != nullptr; + + if (showProgressDialog) + { + nsIWebProgressListener* wpl = static_cast<nsIWebProgressListener*>(mPrintProgressListener.get()); + NS_ASSERTION(wpl, "nsIWebProgressListener is NULL!"); + NS_ADDREF(wpl); + nsString msg; + if (mIsDoingPrintPreview) { + GetString(u"LoadingMailMsgForPrintPreview", msg); + } else { + GetString(u"LoadingMailMsgForPrint", msg); + } + if (!msg.IsEmpty()) + mPrintProgressParams->SetDocTitle(msg.get()); + } + } + } + } + return rv; +} + + +NS_IMETHODIMP +nsMsgPrintEngine::StartNextPrintOperation() +{ + nsresult rv; + + // Only do this the first time through... + if (mCurrentlyPrintingURI == -1) + InitializeDisplayCharset(); + + mCurrentlyPrintingURI++; + + // First, check if we are at the end of this stuff! + if (mCurrentlyPrintingURI >= (int32_t)mURIArray.Length()) + { + // This is the end...dum, dum, dum....my only friend...the end + NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); + nsPIDOMWindowOuter::From(mWindow)->Close(); + + // Tell the user we are done... + nsString msg; + GetString(u"PrintingComplete", msg); + SetStatusMessage(msg); + return NS_OK; + } + + if (!mDocShell) + return StartNextPrintOperation(); + + const nsString &uri = mURIArray[mCurrentlyPrintingURI]; + rv = FireThatLoadOperationStartup(uri); + if (NS_FAILED(rv)) + return StartNextPrintOperation(); + else + return rv; +} + +NS_IMETHODIMP +nsMsgPrintEngine::SetStatusFeedback(nsIMsgStatusFeedback *aFeedback) +{ + mFeedback = aFeedback; + return NS_OK; +} + +#define DATA_URL_PREFIX "data:" +#define ADDBOOK_URL_PREFIX "addbook:" + +nsresult +nsMsgPrintEngine::FireThatLoadOperationStartup(const nsString& uri) +{ + if (!uri.IsEmpty()) + mLoadURI = uri; + else + mLoadURI.Truncate(); + + bool notify = false; + nsresult rv = NS_ERROR_FAILURE; + // Don't show dialog if we are out of URLs + //if ( mCurrentlyPrintingURI < mURIArray.Length() && !mIsDoingPrintPreview) + if ( mCurrentlyPrintingURI < (int32_t)mURIArray.Length()) + rv = ShowProgressDialog(!mIsDoingPrintPreview, notify); + if (NS_FAILED(rv) || !notify) + return FireThatLoadOperation(uri); + return NS_OK; +} + +nsresult +nsMsgPrintEngine::FireThatLoadOperation(const nsString& uri) +{ + nsresult rv = NS_ERROR_FAILURE; + + nsCString uriCStr; + LossyCopyUTF16toASCII(uri, uriCStr); + + nsCOMPtr <nsIMsgMessageService> messageService; + // if this is a data: url, skip it, because + // we've already got something we can print + // and we know it is not a message. + // + // if this an about:blank url, skip it, because + // ... + // + // if this is an addbook: url, skip it, because + // we know that isn't a message. + // + // if this is a message part (or .eml file on disk) + // skip it, because we don't want to print the parent message + // we want to print the part. + // example: imap://sspitzer@nsmail-1:143/fetch%3EUID%3E/INBOX%3E180958?part=1.1.2&type=application/x-message-display&filename=test" + if (!StringBeginsWith(uriCStr, NS_LITERAL_CSTRING(DATA_URL_PREFIX)) && + !StringBeginsWith(uriCStr, NS_LITERAL_CSTRING(ADDBOOK_URL_PREFIX)) && + !uriCStr.EqualsLiteral("about:blank") && + uriCStr.Find(NS_LITERAL_CSTRING("type=application/x-message-display")) == -1) { + rv = GetMessageServiceFromURI(uriCStr, getter_AddRefs(messageService)); + } + + if (NS_SUCCEEDED(rv) && messageService) { + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->DisplayMessageForPrinting(uriCStr.get(), mDocShell, nullptr, nullptr, + getter_AddRefs(dummyNull)); + } + //If it's not something we know about, then just load try loading it directly. + else + { + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + if (webNav) + rv = webNav->LoadURI(uri.get(), // URI string + nsIWebNavigation::LOAD_FLAGS_NONE, // Load flags + nullptr, // Referring URI + nullptr, // Post data + nullptr); // Extra headers + } + return rv; +} + +void +nsMsgPrintEngine::InitializeDisplayCharset() +{ + // libmime always converts to UTF-8 (both HTML and XML) + if (mDocShell) + { + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) + { + cv->SetForceCharacterSet(NS_LITERAL_CSTRING("UTF-8")); + } + } +} + +void +nsMsgPrintEngine::SetupObserver() +{ + if (!mDocShell) + return; + + if (mDocShell) + { + nsCOMPtr<nsIWebProgress> progress(do_GetInterface(mDocShell)); + NS_ASSERTION(progress, "we were expecting a nsIWebProgress"); + if (progress) + { + (void) progress->AddProgressListener((nsIWebProgressListener *)this, + nsIWebProgress::NOTIFY_STATE_DOCUMENT); + } + + // Cache a pointer to the mail message's DOMWindow + // so later we know when we can print when the + // document "loaded" msgs com thru via the Progress listener + mMsgDOMWin = do_GetInterface(mDocShell); + } +} + +nsresult +nsMsgPrintEngine::SetStatusMessage(const nsString& aMsgString) +{ + if ( (!mFeedback) || (aMsgString.IsEmpty()) ) + return NS_OK; + + mFeedback->ShowStatusString(aMsgString); + return NS_OK; +} + +#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties" + +void +nsMsgPrintEngine::GetString(const char16_t *aStringName, nsString& outStr) +{ + outStr.Truncate(); + + if (!mStringBundle) + { + static const char propertyURL[] = MESSENGER_STRING_URL; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) + sBundleService->CreateBundle(propertyURL, getter_AddRefs(mStringBundle)); + } + + if (mStringBundle) + mStringBundle->GetStringFromName(aStringName, getter_Copies(outStr)); + return; +} + +//----------------------------------------------------------- +void +nsMsgPrintEngine::PrintMsgWindow() +{ + const char* kMsgKeys[] = {"PrintingMessage", "PrintPreviewMessage", + "PrintingContact", "PrintPreviewContact", + "PrintingAddrBook", "PrintPreviewAddrBook"}; + + mDocShell->GetContentViewer(getter_AddRefs(mContentViewer)); + if (mContentViewer) + { + mWebBrowserPrint = do_QueryInterface(mContentViewer); + if (mWebBrowserPrint) + { + if (!mPrintSettings) + { + mWebBrowserPrint->GetGlobalPrintSettings(getter_AddRefs(mPrintSettings)); + } + + // fix for bug #118887 and bug #176016 + // don't show the actual url when printing mail messages or addressbook cards. + // for mail, it can review the salt. for addrbook, it's a data:// url, which + // means nothing to the end user. + // needs to be " " and not "" or nullptr, otherwise, we'll still print the url + mPrintSettings->SetDocURL(u" "); + + nsresult rv = NS_ERROR_FAILURE; + if (mIsDoingPrintPreview) + { + if (mStartupPPObs) { + rv = mStartupPPObs->Observe(nullptr, nullptr, nullptr); + } + } + else + { + mPrintSettings->SetPrintSilent(mCurrentlyPrintingURI != 0); + rv = mWebBrowserPrint->Print(mPrintSettings, (nsIWebProgressListener *)this); + } + + if (NS_FAILED(rv)) + { + mWebBrowserPrint = nullptr; + mContentViewer = nullptr; + bool isPrintingCancelled = false; + if (mPrintSettings) + { + mPrintSettings->GetIsCancelled(&isPrintingCancelled); + } + if (!isPrintingCancelled) + { + StartNextPrintOperation(); + } + else + { + if (mWindow) { + nsPIDOMWindowOuter::From(mWindow)->Close(); + } + } + } + else + { + // Tell the user we started printing... + nsString msg; + GetString(NS_ConvertASCIItoUTF16(kMsgKeys[mMsgInx]).get(), msg); + SetStatusMessage(msg); + } + } + } +} + +//--------------------------------------------------------------- +//-- Event Notification +//--------------------------------------------------------------- + +//--------------------------------------------------------------- +class nsPrintMsgWindowEvent : public mozilla::Runnable +{ +public: + nsPrintMsgWindowEvent(nsMsgPrintEngine *mpe) + : mMsgPrintEngine(mpe) + {} + + NS_IMETHOD Run() + { + if (mMsgPrintEngine) + mMsgPrintEngine->PrintMsgWindow(); + return NS_OK; + } + +private: + RefPtr<nsMsgPrintEngine> mMsgPrintEngine; +}; + +//----------------------------------------------------------- +class nsStartNextPrintOpEvent : public mozilla::Runnable +{ +public: + nsStartNextPrintOpEvent(nsMsgPrintEngine *mpe) + : mMsgPrintEngine(mpe) + {} + + NS_IMETHOD Run() + { + if (mMsgPrintEngine) + mMsgPrintEngine->StartNextPrintOperation(); + return NS_OK; + } + +private: + RefPtr<nsMsgPrintEngine> mMsgPrintEngine; +}; + +//----------------------------------------------------------- +bool +nsMsgPrintEngine::FirePrintEvent() +{ + nsCOMPtr<nsIRunnable> event = new nsPrintMsgWindowEvent(this); + return NS_SUCCEEDED(NS_DispatchToCurrentThread(event)); +} + +//----------------------------------------------------------- +nsresult +nsMsgPrintEngine::FireStartNextEvent() +{ + nsCOMPtr<nsIRunnable> event = new nsStartNextPrintOpEvent(this); + return NS_DispatchToCurrentThread(event); +} + +/* void setStartupPPObserver (in nsIObserver startupPPObs); */ +NS_IMETHODIMP nsMsgPrintEngine::SetStartupPPObserver(nsIObserver *startupPPObs) +{ + mStartupPPObs = startupPPObs; + return NS_OK; +} + +/* attribute boolean doPrintPreview; */ +NS_IMETHODIMP nsMsgPrintEngine::GetDoPrintPreview(bool *aDoPrintPreview) +{ + NS_ENSURE_ARG_POINTER(aDoPrintPreview); + *aDoPrintPreview = mIsDoingPrintPreview; + return NS_OK; +} +NS_IMETHODIMP nsMsgPrintEngine::SetDoPrintPreview(bool aDoPrintPreview) +{ + mIsDoingPrintPreview = aDoPrintPreview; + return NS_OK; +} + +/* void setMsgType (in long aMsgType); */ +NS_IMETHODIMP nsMsgPrintEngine::SetMsgType(int32_t aMsgType) +{ + if (mMsgInx >= nsIMsgPrintEngine::MNAB_START && mMsgInx < nsIMsgPrintEngine::MNAB_END) + { + mMsgInx = aMsgType; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +/*=============== nsIObserver Interface ======================*/ +NS_IMETHODIMP nsMsgPrintEngine::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + return FireThatLoadOperation(mLoadURI); +} diff --git a/mailnews/base/src/nsMsgPrintEngine.h b/mailnews/base/src/nsMsgPrintEngine.h new file mode 100644 index 0000000000..4f8a9cad05 --- /dev/null +++ b/mailnews/base/src/nsMsgPrintEngine.h @@ -0,0 +1,91 @@ +/* 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/. */ + +// nsMsgPrintEngine.h: declaration of nsMsgPrintEngine class +// implementing mozISimpleContainer, +// which provides a DocShell container for use in simple programs +// using the layout engine + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIDocShell.h" +#include "nsIDocShell.h" +#include "nsIMsgPrintEngine.h" +#include "nsIStreamListener.h" +#include "nsIWebProgressListener.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIStringBundle.h" +#include "nsIWebBrowserPrint.h" +#include "nsIWebProgressListener.h" +#include "nsWeakReference.h" +#include "nsIPrintSettings.h" +#include "nsIObserver.h" + +// Progress Dialog +#include "nsIPrintProgress.h" +#include "nsIPrintProgressParams.h" +#include "nsIPrintingPromptService.h" + +class nsMsgPrintEngine : public nsIMsgPrintEngine, + public nsIWebProgressListener, + public nsIObserver, + public nsSupportsWeakReference { + +public: + nsMsgPrintEngine(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIMsgPrintEngine interface + NS_DECL_NSIMSGPRINTENGINE + + // For nsIWebProgressListener + NS_DECL_NSIWEBPROGRESSLISTENER + + // For nsIObserver + NS_DECL_NSIOBSERVER + + void PrintMsgWindow(); + NS_IMETHOD StartNextPrintOperation(); + +protected: + virtual ~nsMsgPrintEngine(); + + bool FirePrintEvent(); + nsresult FireStartNextEvent(); + nsresult FireThatLoadOperationStartup(const nsString& uri); + nsresult FireThatLoadOperation(const nsString& uri); + void InitializeDisplayCharset(); + void SetupObserver(); + nsresult SetStatusMessage(const nsString& aMsgString); + void GetString(const char16_t *aStringName, nsString& aOutString); + nsresult ShowProgressDialog(bool aIsForPrinting, bool& aDoNotify); + + nsCOMPtr<nsIDocShell> mDocShell; + nsCOMPtr<mozIDOMWindowProxy> mWindow; + nsCOMPtr<mozIDOMWindowProxy> mParentWindow; + int32_t mURICount; + nsTArray<nsString> mURIArray; + int32_t mCurrentlyPrintingURI; + + nsCOMPtr<nsIContentViewer> mContentViewer; + nsCOMPtr<nsIStringBundle> mStringBundle; // String bundles... + nsCOMPtr<nsIMsgStatusFeedback> mFeedback; // Tell the user something why don't ya' + nsCOMPtr<nsIWebBrowserPrint> mWebBrowserPrint; + nsCOMPtr<nsIPrintSettings> mPrintSettings; + nsCOMPtr<mozIDOMWindowProxy> mMsgDOMWin; + bool mIsDoingPrintPreview; + nsCOMPtr<nsIObserver> mStartupPPObs; + int32_t mMsgInx; + + // Progress Dialog + + nsCOMPtr<nsIPrintingPromptService> mPrintPromptService; + nsCOMPtr<nsIWebProgressListener> mPrintProgressListener; + nsCOMPtr<nsIPrintProgress> mPrintProgress; + nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams; + nsString mLoadURI; +}; diff --git a/mailnews/base/src/nsMsgProgress.cpp b/mailnews/base/src/nsMsgProgress.cpp new file mode 100644 index 0000000000..02f26edbf1 --- /dev/null +++ b/mailnews/base/src/nsMsgProgress.cpp @@ -0,0 +1,262 @@ +/* -*- 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 "nsMsgProgress.h" + +#include "nsIBaseWindow.h" +#include "nsXPCOM.h" +#include "nsIMutableArray.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsError.h" +#include "nsIWindowWatcher.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +NS_IMPL_ISUPPORTS(nsMsgProgress, nsIMsgStatusFeedback, nsIMsgProgress, + nsIWebProgressListener, nsIProgressEventSink, nsISupportsWeakReference) + + +nsMsgProgress::nsMsgProgress() +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; +} + +nsMsgProgress::~nsMsgProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsMsgProgress::OpenProgressDialog(mozIDOMWindowProxy *parentDOMWindow, + nsIMsgWindow *aMsgWindow, + const char *dialogURL, + bool inDisplayModal, + nsISupports *parameters) +{ + nsresult rv; + + if (aMsgWindow) + { + SetMsgWindow(aMsgWindow); + aMsgWindow->SetStatusFeedback(this); + } + + NS_ENSURE_ARG_POINTER(dialogURL); + NS_ENSURE_ARG_POINTER(parentDOMWindow); + nsCOMPtr<nsPIDOMWindowOuter> parent = nsPIDOMWindowOuter::From(parentDOMWindow); + parent = parent->GetOuterWindow(); + NS_ENSURE_ARG_POINTER(parent); + + // Set up window.arguments[0]... + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsInterfacePointer> ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + ifptr->SetData(static_cast<nsIMsgProgress*>(this)); + ifptr->SetDataIID(&NS_GET_IID(nsIMsgProgress)); + + array->AppendElement(ifptr, false); + array->AppendElement(parameters, false); + + // Open the dialog. + nsCOMPtr<nsPIDOMWindowOuter> newWindow; + + nsString chromeOptions(NS_LITERAL_STRING("chrome,dependent,centerscreen")); + if (inDisplayModal) + chromeOptions.AppendLiteral(",modal"); + + return parent->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL), + NS_LITERAL_STRING("_blank"), + chromeOptions, + array, getter_AddRefs(newWindow)); +} + +/* void closeProgressDialog (in boolean forceClose); */ +NS_IMETHODIMP nsMsgProgress::CloseProgressDialog(bool forceClose) +{ + m_closeProgress = true; + return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, forceClose ? NS_ERROR_FAILURE : NS_OK); +} + +/* attribute boolean processCanceledByUser; */ +NS_IMETHODIMP nsMsgProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsMsgProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_BINDING_ABORTED); + return NS_OK; +} + +/* void RegisterListener (in nsIWebProgressListener listener); */ +NS_IMETHODIMP nsMsgProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + NS_ENSURE_ARG(this != listener); //Check for self-reference (see bug 271700) + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +/* void UnregisterListener (in nsIWebProgressListener listener); */ +NS_IMETHODIMP nsMsgProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + return NS_OK; +} + +/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */ +NS_IMETHODIMP nsMsgProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + nsCOMPtr<nsIMsgWindow> msgWindow (do_QueryReferent(m_msgWindow)); + if (aStateFlags == nsIWebProgressListener::STATE_STOP && msgWindow && NS_FAILED(aStatus)) + { + msgWindow->StopUrls(); + msgWindow->SetStatusFeedback(nullptr); + } + + for (int32_t i = m_listenerList.Count() - 1; i >= 0; i --) + m_listenerList[i]->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + + return NS_OK; +} + +/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */ +NS_IMETHODIMP nsMsgProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + for (int32_t i = m_listenerList.Count() - 1; i >= 0; i --) + m_listenerList[i]->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + return NS_OK; +} + +/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */ +NS_IMETHODIMP nsMsgProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */ +NS_IMETHODIMP nsMsgProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + for (int32_t i = m_listenerList.Count() - 1; i >= 0; i --) + m_listenerList[i]->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + return NS_OK; +} + +/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */ +NS_IMETHODIMP nsMsgProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsMsgProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProgress::ShowStatusString(const nsAString& aStatus) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, PromiseFlatString(aStatus).get()); +} + +NS_IMETHODIMP nsMsgProgress::SetStatusString(const nsAString& aStatus) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, PromiseFlatString(aStatus).get()); +} + +/* void startMeteors (); */ +NS_IMETHODIMP nsMsgProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void stopMeteors (); */ +NS_IMETHODIMP nsMsgProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void showProgress (in long percent); */ +NS_IMETHODIMP nsMsgProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgProgress::SetWrappedStatusFeedback(nsIMsgStatusFeedback * aJSStatusFeedback) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgProgress::SetMsgWindow(nsIMsgWindow *aMsgWindow) +{ + m_msgWindow = do_GetWeakReference(aMsgWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgProgress::GetMsgWindow(nsIMsgWindow **aMsgWindow) +{ + NS_ENSURE_ARG_POINTER(aMsgWindow); + + if (m_msgWindow) + CallQueryReferent(m_msgWindow.get(), aMsgWindow); + else + *aMsgWindow = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgProgress::OnProgress(nsIRequest *request, nsISupports* ctxt, + int64_t aProgress, int64_t aProgressMax) +{ + // XXX: What should the nsIWebProgress be? + // XXX: This truncates 64-bit to 32-bit + return OnProgressChange(nullptr, request, int32_t(aProgress), int32_t(aProgressMax), + int32_t(aProgress) /* current total progress */, int32_t(aProgressMax) /* max total progress */); +} + +NS_IMETHODIMP nsMsgProgress::OnStatus(nsIRequest *request, nsISupports* ctxt, + nsresult aStatus, const char16_t* aStatusArg) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, NS_ERROR_UNEXPECTED); + nsString str; + rv = sbs->FormatStatusMessage(aStatus, aStatusArg, getter_Copies(str)); + NS_ENSURE_SUCCESS(rv, rv); + return ShowStatusString(str); +} diff --git a/mailnews/base/src/nsMsgProgress.h b/mailnews/base/src/nsMsgProgress.h new file mode 100644 index 0000000000..4450c6ccee --- /dev/null +++ b/mailnews/base/src/nsMsgProgress.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef nsMsgProgress_h_ +#define nsMsgProgress_h_ + +#include "nsIMsgProgress.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIDOMWindow.h" +#include "nsIMsgStatusFeedback.h" +#include "nsStringGlue.h" +#include "nsIMsgWindow.h" +#include "nsIProgressEventSink.h" +#include "nsIStringBundle.h" +#include "nsWeakReference.h" + +class nsMsgProgress : public nsIMsgProgress, + public nsIMsgStatusFeedback, + public nsIProgressEventSink, + public nsSupportsWeakReference +{ +public: + nsMsgProgress(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIMSGSTATUSFEEDBACK + NS_DECL_NSIPROGRESSEVENTSINK + +private: + virtual ~nsMsgProgress(); + nsresult ReleaseListeners(void); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + nsWeakPtr m_msgWindow; + nsCOMArray<nsIWebProgressListener> m_listenerList; +}; + +#endif // nsMsgProgress_h_ diff --git a/mailnews/base/src/nsMsgPurgeService.cpp b/mailnews/base/src/nsMsgPurgeService.cpp new file mode 100644 index 0000000000..98d9e7cce5 --- /dev/null +++ b/mailnews/base/src/nsMsgPurgeService.cpp @@ -0,0 +1,486 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsMsgPurgeService.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsMsgUtils.h" +#include "nsMsgSearchCore.h" +#include "msgCore.h" +#include "nsISpamSettings.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsIMsgProtocolInfo.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "mozilla/Logging.h" +#include "nsMsgFolderFlags.h" +#include <stdlib.h> +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsArrayUtils.h" + +static PRLogModuleInfo *MsgPurgeLogModule = nullptr; + +NS_IMPL_ISUPPORTS(nsMsgPurgeService, nsIMsgPurgeService, nsIMsgSearchNotify) + +void OnPurgeTimer(nsITimer *timer, void *aPurgeService) +{ + nsMsgPurgeService *purgeService = (nsMsgPurgeService*)aPurgeService; + purgeService->PerformPurge(); +} + +nsMsgPurgeService::nsMsgPurgeService() +{ + mHaveShutdown = false; + mMinDelayBetweenPurges = 480; // never purge a folder more than once every 8 hours (60 min/hour * 8 hours) + mPurgeTimerInterval = 5; // fire the purge timer every 5 minutes, starting 5 minutes after the service is created (when we load accounts) +} + +nsMsgPurgeService::~nsMsgPurgeService() +{ + if (mPurgeTimer) + mPurgeTimer->Cancel(); + + if(!mHaveShutdown) + Shutdown(); +} + +NS_IMETHODIMP nsMsgPurgeService::Init() +{ + nsresult rv; + + if (!MsgPurgeLogModule) + MsgPurgeLogModule = PR_NewLogModule("MsgPurge"); + + // these prefs are here to help QA test this feature + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + int32_t min_delay; + rv = prefBranch->GetIntPref("mail.purge.min_delay", &min_delay); + if (NS_SUCCEEDED(rv) && min_delay) + mMinDelayBetweenPurges = min_delay; + + int32_t purge_timer_interval; + rv = prefBranch->GetIntPref("mail.purge.timer_interval", &purge_timer_interval); + if (NS_SUCCEEDED(rv) && purge_timer_interval) + mPurgeTimerInterval = purge_timer_interval; + } + + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("mail.purge.min_delay=%d minutes",mMinDelayBetweenPurges)); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("mail.purge.timer_interval=%d minutes",mPurgeTimerInterval)); + + // don't start purging right away. + // because the accounts aren't loaded and because the user might be trying to sign in + // or startup, etc. + SetupNextPurge(); + + mHaveShutdown = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgPurgeService::Shutdown() +{ + if (mPurgeTimer) + { + mPurgeTimer->Cancel(); + mPurgeTimer = nullptr; + } + + mHaveShutdown = true; + return NS_OK; +} + +nsresult nsMsgPurgeService::SetupNextPurge() +{ + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("setting to check again in %d minutes",mPurgeTimerInterval)); + + // Convert mPurgeTimerInterval into milliseconds + uint32_t timeInMSUint32 = mPurgeTimerInterval * 60000; + + // Can't currently reset a timer when it's in the process of + // calling Notify. So, just release the timer here and create a new one. + if(mPurgeTimer) + mPurgeTimer->Cancel(); + + mPurgeTimer = do_CreateInstance("@mozilla.org/timer;1"); + mPurgeTimer->InitWithFuncCallback(OnPurgeTimer, (void*)this, timeInMSUint32, + nsITimer::TYPE_ONE_SHOT); + + return NS_OK; +} + +// This is the function that looks for the first folder to purge. It also +// applies retention settings to any folder that hasn't had retention settings +// applied in mMinDelayBetweenPurges minutes (default, 8 hours). +// However, if we've spent more than .5 seconds in this loop, don't +// apply any more retention settings because it might lock up the UI. +// This might starve folders later on in the hierarchy, since we always +// start at the top, but since we also apply retention settings when you +// open a folder, or when you compact all folders, I think this will do +// for now, until we have a cleanup on shutdown architecture. +nsresult nsMsgPurgeService::PerformPurge() +{ + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("performing purge")); + + nsresult rv; + + nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + bool keepApplyingRetentionSettings = true; + + nsCOMPtr<nsIArray> allServers; + rv = accountManager->GetAllServers(getter_AddRefs(allServers)); + if (NS_SUCCEEDED(rv) && allServers) + { + uint32_t numServers; + rv = allServers->GetLength(&numServers); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("%d servers", numServers)); + nsCOMPtr<nsIMsgFolder> folderToPurge; + PRIntervalTime startTime = PR_IntervalNow(); + int32_t purgeIntervalToUse = 0; + PRTime oldestPurgeTime = 0; // we're going to pick the least-recently purged folder + + // apply retention settings to folders that haven't had retention settings + // applied in mMinDelayBetweenPurges minutes (default 8 hours) + // Because we get last purge time from the folder cache, + // this code won't open db's for folders until it decides it needs + // to apply retention settings, and since nsIMsgFolder::ApplyRetentionSettings + // will close any db's it opens, this code won't leave db's open. + for (uint32_t serverIndex=0; serverIndex < numServers; serverIndex++) + { + nsCOMPtr <nsIMsgIncomingServer> server = + do_QueryElementAt(allServers, serverIndex, &rv); + if (NS_SUCCEEDED(rv) && server) + { + if (keepApplyingRetentionSettings) + { + nsCOMPtr <nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> childFolders; + rv = rootFolder->GetDescendants(getter_AddRefs(childFolders)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t cnt = 0; + childFolders->GetLength(&cnt); + + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIUrlListener> urlListener; + nsCOMPtr<nsIMsgFolder> childFolder; + + for (uint32_t index = 0; index < cnt; index++) + { + childFolder = do_QueryElementAt(childFolders, index); + if (childFolder) + { + uint32_t folderFlags; + (void) childFolder->GetFlags(&folderFlags); + if (folderFlags & nsMsgFolderFlags::Virtual) + continue; + PRTime curFolderLastPurgeTime = 0; + nsCString curFolderLastPurgeTimeString, curFolderUri; + rv = childFolder->GetStringProperty("LastPurgeTime", curFolderLastPurgeTimeString); + if (NS_FAILED(rv)) + continue; // it is ok to fail, go on to next folder + + if (!curFolderLastPurgeTimeString.IsEmpty()) + { + PRTime theTime; + PR_ParseTimeString(curFolderLastPurgeTimeString.get(), false, &theTime); + curFolderLastPurgeTime = theTime; + } + + childFolder->GetURI(curFolderUri); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("%s curFolderLastPurgeTime=%s (if blank, then never)", curFolderUri.get(), curFolderLastPurgeTimeString.get())); + + // check if this folder is due to purge + // has to have been purged at least mMinDelayBetweenPurges minutes ago + // we don't want to purge the folders all the time - once a day is good enough + int64_t minDelayBetweenPurges(mMinDelayBetweenPurges); + int64_t microSecondsPerMinute(60000000); + PRTime nextPurgeTime = curFolderLastPurgeTime + (minDelayBetweenPurges * microSecondsPerMinute); + if (nextPurgeTime < PR_Now()) + { + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("purging %s", curFolderUri.get())); + childFolder->ApplyRetentionSettings(); + } + PRIntervalTime elapsedTime = PR_IntervalNow() - startTime; + // check if more than 500 milliseconds have elapsed in this purge process + if (PR_IntervalToMilliseconds(elapsedTime) > 500) + { + keepApplyingRetentionSettings = false; + break; + } + } + } + } + nsCString type; + nsresult rv = server->GetType(type); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString realHostName; + server->GetRealHostName(realHostName); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] %s (%s)", serverIndex, realHostName.get(), type.get())); + + nsCOMPtr <nsISpamSettings> spamSettings; + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t spamLevel; + spamSettings->GetLevel(&spamLevel); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] spamLevel=%d (if 0, don't purge)", serverIndex, spamLevel)); + if (!spamLevel) + continue; + + // check if we are set up to purge for this server + // if not, skip it. + bool purgeSpam; + spamSettings->GetPurge(&purgeSpam); + + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] purgeSpam=%s (if false, don't purge)", serverIndex, purgeSpam ? "true" : "false")); + if (!purgeSpam) + continue; + + // check if the spam folder uri is set for this server + // if not skip it. + nsCString junkFolderURI; + rv = spamSettings->GetSpamFolderURI(getter_Copies(junkFolderURI)); + NS_ENSURE_SUCCESS(rv,rv); + + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] junkFolderURI=%s (if empty, don't purge)", serverIndex, junkFolderURI.get())); + if (junkFolderURI.IsEmpty()) + continue; + + // if the junk folder doesn't exist + // because the folder pane isn't built yet, for example + // skip this account + nsCOMPtr<nsIMsgFolder> junkFolder; + GetExistingFolder(junkFolderURI, getter_AddRefs(junkFolder)); + + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] %s exists? %s (if doesn't exist, don't purge)", serverIndex, junkFolderURI.get(), junkFolder ? "true" : "false")); + if (!junkFolder) + continue; + + PRTime curJunkFolderLastPurgeTime = 0; + nsCString curJunkFolderLastPurgeTimeString; + rv = junkFolder->GetStringProperty("curJunkFolderLastPurgeTime", curJunkFolderLastPurgeTimeString); + if (NS_FAILED(rv)) + continue; // it is ok to fail, junk folder may not exist + + if (!curJunkFolderLastPurgeTimeString.IsEmpty()) + { + PRTime theTime; + PR_ParseTimeString(curJunkFolderLastPurgeTimeString.get(), false, &theTime); + curJunkFolderLastPurgeTime = theTime; + } + + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] %s curJunkFolderLastPurgeTime=%s (if blank, then never)", serverIndex, junkFolderURI.get(), curJunkFolderLastPurgeTimeString.get())); + + // check if this account is due to purge + // has to have been purged at least mMinDelayBetweenPurges minutes ago + // we don't want to purge the folders all the time + PRTime nextPurgeTime = curJunkFolderLastPurgeTime + mMinDelayBetweenPurges * 60000000 /* convert mMinDelayBetweenPurges to into microseconds */; + if (nextPurgeTime < PR_Now()) + { + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] last purge greater than min delay", serverIndex)); + + nsCOMPtr <nsIMsgIncomingServer> junkFolderServer; + rv = junkFolder->GetServer(getter_AddRefs(junkFolderServer)); + NS_ENSURE_SUCCESS(rv,rv); + + bool serverBusy = false; + bool serverRequiresPassword = true; + bool passwordPromptRequired; + bool canSearchMessages = false; + junkFolderServer->GetPasswordPromptRequired(&passwordPromptRequired); + junkFolderServer->GetServerBusy(&serverBusy); + junkFolderServer->GetServerRequiresPasswordForBiff(&serverRequiresPassword); + junkFolderServer->GetCanSearchMessages(&canSearchMessages); + // Make sure we're logged on before doing the search (assuming we need to be) + // and make sure the server isn't already in the middle of downloading new messages + // and make sure a search isn't already going on + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (search in progress? %s)", serverIndex, mSearchSession ? "true" : "false")); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (server busy? %s)", serverIndex, serverBusy ? "true" : "false")); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (serverRequiresPassword? %s)", serverIndex, serverRequiresPassword ? "true" : "false")); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] (passwordPromptRequired? %s)", serverIndex, passwordPromptRequired ? "true" : "false")); + if (canSearchMessages && !mSearchSession && !serverBusy && (!serverRequiresPassword || !passwordPromptRequired)) + { + int32_t purgeInterval; + spamSettings->GetPurgeInterval(&purgeInterval); + + if ((oldestPurgeTime == 0) || (curJunkFolderLastPurgeTime < oldestPurgeTime)) + { + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] purging! searching for messages older than %d days", serverIndex, purgeInterval)); + oldestPurgeTime = curJunkFolderLastPurgeTime; + purgeIntervalToUse = purgeInterval; + folderToPurge = junkFolder; + // if we've never purged this folder, do it... + if (curJunkFolderLastPurgeTime == 0) + break; + } + } + else { + NS_ASSERTION(canSearchMessages, "unexpected, you should be able to search"); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] not a good time for this server, try again later", serverIndex)); + } + } + else { + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("[%d] last purge too recent", serverIndex)); + } + } + } + if (folderToPurge && purgeIntervalToUse != 0) + rv = SearchFolderToPurge(folderToPurge, purgeIntervalToUse); + } + + // set up timer to check accounts again + SetupNextPurge(); + return rv; +} + +nsresult nsMsgPurgeService::SearchFolderToPurge(nsIMsgFolder *folder, int32_t purgeInterval) +{ + nsresult rv; + mSearchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mSearchSession->RegisterListener(this, + nsIMsgSearchSession::allNotifications); + + // update the time we attempted to purge this folder + char dateBuf[100]; + dateBuf[0] = '\0'; + PRExplodedTime exploded; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y", &exploded); + folder->SetStringProperty("curJunkFolderLastPurgeTime", nsDependentCString(dateBuf)); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("curJunkFolderLastPurgeTime is now %s", dateBuf)); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); //we need to get the folder's server scope because imap can have local junk folder + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgSearchScopeValue searchScope; + server->GetSearchScope(&searchScope); + + mSearchSession->AddScopeTerm(searchScope, folder); + + // look for messages older than the cutoff + // you can't also search by junk status, see + // nsMsgPurgeService::OnSearchHit() + nsCOMPtr <nsIMsgSearchTerm> searchTerm; + mSearchSession->CreateTerm(getter_AddRefs(searchTerm)); + if (searchTerm) + { + searchTerm->SetAttrib(nsMsgSearchAttrib::AgeInDays); + searchTerm->SetOp(nsMsgSearchOp::IsGreaterThan); + nsCOMPtr<nsIMsgSearchValue> searchValue; + searchTerm->GetValue(getter_AddRefs(searchValue)); + if (searchValue) + { + searchValue->SetAttrib(nsMsgSearchAttrib::AgeInDays); + searchValue->SetAge((uint32_t) purgeInterval); + searchTerm->SetValue(searchValue); + } + searchTerm->SetBooleanAnd(false); + mSearchSession->AppendTerm(searchTerm); + } + + // we are about to search + // create mHdrsToDelete array (if not previously created) + if (!mHdrsToDelete) + { + mHdrsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + uint32_t count; + mHdrsToDelete->GetLength(&count); + NS_ASSERTION(count == 0, "mHdrsToDelete is not empty"); + if (count > 0) + mHdrsToDelete->Clear(); // this shouldn't happen + } + + mSearchFolder = folder; + return mSearchSession->Search(nullptr); +} + +NS_IMETHODIMP nsMsgPurgeService::OnNewSearch() +{ + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("on new search")); + return NS_OK; +} + +NS_IMETHODIMP nsMsgPurgeService::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *aFolder) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + + nsCString messageId; + nsCString author; + nsCString subject; + + aMsgHdr->GetMessageId(getter_Copies(messageId)); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("messageId=%s", messageId.get())); + aMsgHdr->GetSubject(getter_Copies(subject)); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("subject=%s",subject.get())); + aMsgHdr->GetAuthor(getter_Copies(author)); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("author=%s",author.get())); + + // double check that the message is junk before adding to + // the list of messages to delete + // + // note, we can't just search for messages that are junk + // because not all imap server support keywords + // (which we use for the junk score) + // so the junk status would be in the message db. + // + // see bug #194090 + nsCString junkScoreStr; + nsresult rv = aMsgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + NS_ENSURE_SUCCESS(rv,rv); + + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("junkScore=%s (if empty or != nsIJunkMailPlugin::IS_SPAM_SCORE, don't add to list delete)", junkScoreStr.get())); + + // if "junkscore" is not set, don't delete the message + if (junkScoreStr.IsEmpty()) + return NS_OK; + + if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_SPAM_SCORE) { + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("added message to delete")); + return mHdrsToDelete->AppendElement(aMsgHdr, false); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgPurgeService::OnSearchDone(nsresult status) +{ + if (NS_SUCCEEDED(status)) + { + uint32_t count = 0; + if (mHdrsToDelete) + mHdrsToDelete->GetLength(&count); + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("%d messages to delete", count)); + + if (count > 0) { + MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("delete messages")); + if (mSearchFolder) + mSearchFolder->DeleteMessages(mHdrsToDelete, nullptr, false /*delete storage*/, false /*isMove*/, nullptr, false /*allowUndo*/); + } + } + if (mHdrsToDelete) + mHdrsToDelete->Clear(); + if (mSearchSession) + mSearchSession->UnregisterListener(this); + // don't cache the session + // just create another search session next time we search, rather than clearing scopes, terms etc. + // we also use mSearchSession to determine if the purge service is "busy" + mSearchSession = nullptr; + mSearchFolder = nullptr; + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgPurgeService.h b/mailnews/base/src/nsMsgPurgeService.h new file mode 100644 index 0000000000..d9757d65a1 --- /dev/null +++ b/mailnews/base/src/nsMsgPurgeService.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef NSMSGPURGESERVICE_H +#define NSMSGPURGESERVICE_H + +#include "msgCore.h" +#include "nsIMsgPurgeService.h" +#include "nsIMsgSearchSession.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsIMsgSearchNotify.h" +#include "nsIMsgFolder.h" +#include "nsIMsgFolderCache.h" +#include "nsIMsgFolderCacheElement.h" +#include "nsIMutableArray.h" + +class nsMsgPurgeService + : public nsIMsgPurgeService, + public nsIMsgSearchNotify +{ +public: + nsMsgPurgeService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPURGESERVICE + NS_DECL_NSIMSGSEARCHNOTIFY + + nsresult PerformPurge(); + +protected: + virtual ~nsMsgPurgeService(); + int32_t FindServer(nsIMsgIncomingServer *server); + nsresult SetupNextPurge(); + nsresult PurgeSurver(nsIMsgIncomingServer *server); + nsresult SearchFolderToPurge(nsIMsgFolder *folder, int32_t purgeInterval); + +protected: + nsCOMPtr<nsITimer> mPurgeTimer; + nsCOMPtr<nsIMsgSearchSession> mSearchSession; + nsCOMPtr<nsIMsgFolder> mSearchFolder; + nsCOMPtr<nsIMutableArray> mHdrsToDelete; + bool mHaveShutdown; + +private: + int32_t mMinDelayBetweenPurges; // in minutes, how long must pass between two consecutive purges on the same junk folder? + int32_t mPurgeTimerInterval; // in minutes, how often to check if we need to purge one of the junk folders? +}; + + + +#endif + diff --git a/mailnews/base/src/nsMsgQuickSearchDBView.cpp b/mailnews/base/src/nsMsgQuickSearchDBView.cpp new file mode 100644 index 0000000000..3f8c53d2db --- /dev/null +++ b/mailnews/base/src/nsMsgQuickSearchDBView.cpp @@ -0,0 +1,882 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgQuickSearchDBView.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgHdr.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgImapMailFolder.h" +#include "nsImapCore.h" +#include "nsIMsgHdr.h" +#include "nsIDBFolderInfo.h" +#include "nsArrayEnumerator.h" +#include "nsMsgMessageFlags.h" +#include "nsIMutableArray.h" +#include "nsMsgUtils.h" + +nsMsgQuickSearchDBView::nsMsgQuickSearchDBView() +{ + m_usingCachedHits = false; + m_cacheEmpty = true; +} + +nsMsgQuickSearchDBView::~nsMsgQuickSearchDBView() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgQuickSearchDBView, nsMsgDBView, nsIMsgDBView, nsIMsgSearchNotify) + +NS_IMETHODIMP nsMsgQuickSearchDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) +{ + nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_db) + return NS_ERROR_NULL_POINTER; + if (pCount) + *pCount = 0; + m_viewFolder = nullptr; + return InitThreadedView(pCount); +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::CloneDBView(nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCmdUpdater, + nsIMsgDBView **_retval) +{ + nsMsgQuickSearchDBView* newMsgDBView = new nsMsgQuickSearchDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, + nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCmdUpdater) +{ + nsMsgThreadedDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + nsMsgQuickSearchDBView* newMsgDBView = (nsMsgQuickSearchDBView *) aNewMsgDBView; + + // now copy all of our private member data + newMsgDBView->m_origKeys = m_origKeys; + return NS_OK; +} + +nsresult nsMsgQuickSearchDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) +{ + for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + (void) GetMsgHdrForViewIndex(indices[i],getter_AddRefs(msgHdr)); + if (msgHdr) + RememberDeletedMsgHdr(msgHdr); + } + + return nsMsgDBView::DeleteMessages(window, indices, numIndices, deleteStorage); +} + +NS_IMETHODIMP nsMsgQuickSearchDBView::DoCommand(nsMsgViewCommandTypeValue aCommand) +{ + if (aCommand == nsMsgViewCommandType::markAllRead) + { + nsresult rv = NS_OK; + m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, true /*dbBatching*/); + + for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < GetSize(); i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + m_db->GetMsgHdrForKey(m_keys[i],getter_AddRefs(msgHdr)); + rv = m_db->MarkHdrRead(msgHdr, true, nullptr); + } + + m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, true /*dbBatching*/); + + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder); + if (NS_SUCCEEDED(rv) && imapFolder) + rv = imapFolder->StoreImapFlags(kImapMsgSeenFlag, true, m_keys.Elements(), + m_keys.Length(), nullptr); + + m_db->SetSummaryValid(true); + return rv; + } + else + return nsMsgDBView::DoCommand(aCommand); +} + +NS_IMETHODIMP nsMsgQuickSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowQuickSearchResults; + return NS_OK; +} + +nsresult nsMsgQuickSearchDBView::AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex) +{ + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + // protect against duplication. + if (m_origKeys.BinaryIndexOf(msgKey) == m_origKeys.NoIndex) + { + nsMsgViewIndex insertIndex = GetInsertIndexHelper(msgHdr, m_origKeys, nullptr, + nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId); + m_origKeys.InsertElementAt(insertIndex, msgKey); + } + if (m_viewFlags & (nsMsgViewFlagsType::kGroupBySort| + nsMsgViewFlagsType::kThreadedDisplay)) + { + nsMsgKey parentKey; + msgHdr->GetThreadParent(&parentKey); + return nsMsgThreadedDBView::OnNewHeader(msgHdr, parentKey, true); + } + else + return nsMsgDBView::AddHdr(msgHdr, resultIndex); +} + +nsresult nsMsgQuickSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) +{ + if (newHdr) + { + bool match=false; + nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession); + if (searchSession) + searchSession->MatchHdr(newHdr, m_db, &match); + if (match) + { + // put the new header in m_origKeys, so that expanding a thread will + // show the newly added header. + nsMsgKey newKey; + (void) newHdr->GetMessageKey(&newKey); + nsMsgViewIndex insertIndex = GetInsertIndexHelper(newHdr, m_origKeys, nullptr, + nsMsgViewSortOrder::ascending, nsMsgViewSortType::byId); + m_origKeys.InsertElementAt(insertIndex, newKey); + nsMsgThreadedDBView::OnNewHeader(newHdr, aParentKey, ensureListed); // do not add a new message if there isn't a match. + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgQuickSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + nsresult rv = nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator); + + if (m_viewFolder && + (m_viewFolder != m_folder) && + (aOldFlags & nsMsgMessageFlags::Read) != (aNewFlags & nsMsgMessageFlags::Read)) + { + // if we're displaying a single folder virtual folder for an imap folder, + // the search criteria might be on message body, and we might not have the + // message body offline, in which case we can't tell if the message + // matched or not. But if the unread flag changed, we need to update the + // unread counts. Normally, VirtualFolderChangeListener::OnHdrFlagsChanged will + // handle this, but it won't work for body criteria when we don't have the + // body offline. + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_viewFolder); + if (imapFolder) + { + nsMsgViewIndex hdrIndex = FindHdr(aHdrChanged); + if (hdrIndex != nsMsgViewIndex_None) + { + nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession); + if (searchSession) + { + bool oldMatch, newMatch; + rv = searchSession->MatchHdr(aHdrChanged, m_db, &newMatch); + aHdrChanged->SetFlags(aOldFlags); + rv = searchSession->MatchHdr(aHdrChanged, m_db, &oldMatch); + aHdrChanged->SetFlags(aNewFlags); + // if it doesn't match the criteria, VirtualFolderChangeListener::OnHdrFlagsChanged + // won't tweak the read/unread counts. So do it here: + if (!oldMatch && !newMatch) + { + nsCOMPtr <nsIMsgDatabase> virtDatabase; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + + rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + dbFolderInfo->ChangeNumUnreadMessages((aOldFlags & nsMsgMessageFlags::Read) ? 1 : -1); + m_viewFolder->UpdateSummaryTotals(true); // force update from db. + virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit); + } + } + } + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrChanged, bool aPreChange, + uint32_t *aStatus, nsIDBChangeListener *aInstigator) +{ + // If the junk mail plugin just activated on a message, then + // we'll allow filters to remove from view. + // Otherwise, just update the view line. + // + // Note this will not add newly matched headers to the view. This is + // probably a bug that needs fixing. + + NS_ENSURE_ARG_POINTER(aStatus); + NS_ENSURE_ARG_POINTER(aHdrChanged); + + nsMsgViewIndex index = FindHdr(aHdrChanged); + if (index == nsMsgViewIndex_None) // message does not appear in view + return NS_OK; + + nsCString originStr; + (void) aHdrChanged->GetStringProperty("junkscoreorigin", getter_Copies(originStr)); + // check for "plugin" with only first character for performance + bool plugin = (originStr.get()[0] == 'p'); + + if (aPreChange) + { + // first call, done prior to the change + *aStatus = plugin; + return NS_OK; + } + + // second call, done after the change + bool wasPlugin = *aStatus; + + bool match = true; + nsCOMPtr<nsIMsgSearchSession> searchSession(do_QueryReferent(m_searchSession)); + if (searchSession) + searchSession->MatchHdr(aHdrChanged, m_db, &match); + + if (!match && plugin && !wasPlugin) + RemoveByIndex(index); // remove hdr from view + else + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::GetSearchSession(nsIMsgSearchSession* *aSession) +{ + NS_ASSERTION(false, "GetSearchSession method is not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsMsgQuickSearchDBView::SetSearchSession(nsIMsgSearchSession *aSession) +{ + m_searchSession = do_GetWeakReference(aSession); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder) +{ + NS_ENSURE_ARG(aMsgHdr); + if (!m_db) + return NS_ERROR_NULL_POINTER; + // remember search hit and when search is done, reconcile cache + // with new hits; + m_hdrHits.AppendObject(aMsgHdr); + nsMsgKey key; + aMsgHdr->GetMessageKey(&key); + // Is FindKey going to be expensive here? A lot of hits could make + // it a little bit slow to search through the view for every hit. + if (m_cacheEmpty || FindKey(key, false) == nsMsgViewIndex_None) + return AddHdr(aMsgHdr); + else + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::OnSearchDone(nsresult status) +{ + // We're a single-folder virtual folder if viewFolder != folder, and that is + // the only case in which we want to be messing about with a results cache + // or unread counts. + if (m_db && m_viewFolder && m_viewFolder != m_folder) + { + nsTArray<nsMsgKey> keyArray; + nsCString searchUri; + m_viewFolder->GetURI(searchUri); + uint32_t count = m_hdrHits.Count(); + // Build up message keys. The hits are in descending order but the cache + // expects them to be in ascending key order. + for (uint32_t i = count; i > 0; i--) + { + nsMsgKey key; + m_hdrHits[i-1]->GetMessageKey(&key); + keyArray.AppendElement(key); + } + nsMsgKey *staleHits; + uint32_t numBadHits; + if (m_db) + { + nsresult rv = m_db->RefreshCache(searchUri.get(), m_hdrHits.Count(), + keyArray.Elements(), &numBadHits, &staleHits); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDBHdr> hdrDeleted; + + for (uint32_t i = 0; i < numBadHits; i++) + { + m_db->GetMsgHdrForKey(staleHits[i], getter_AddRefs(hdrDeleted)); + if (hdrDeleted) + OnHdrDeleted(hdrDeleted, nsMsgKey_None, 0, this); + } + delete [] staleHits; + } + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numUnread = 0; + size_t numTotal = m_origKeys.Length(); + + for (size_t i = 0; i < m_origKeys.Length(); i++) + { + bool isRead; + m_db->IsRead(m_origKeys[i], &isRead); + if (!isRead) + numUnread++; + } + dbFolderInfo->SetNumUnreadMessages(numUnread); + dbFolderInfo->SetNumMessages(numTotal); + m_viewFolder->UpdateSummaryTotals(true); // force update from db. + virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit); + } + if (m_sortType != nsMsgViewSortType::byThread)//we do not find levels for the results. + { + m_sortValid = false; //sort the results + Sort(m_sortType, m_sortOrder); + } + if (m_viewFolder && (m_viewFolder != m_folder)) + SetMRUTimeForFolder(m_viewFolder); + + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgQuickSearchDBView::OnNewSearch() +{ + int32_t oldSize = GetSize(); + + m_keys.Clear(); + m_levels.Clear(); + m_flags.Clear(); + m_hdrHits.Clear(); + // this needs to happen after we remove all the keys, since RowCountChanged() will call our GetRowCount() + if (mTree) + mTree->RowCountChanged(0, -oldSize); + uint32_t folderFlags = 0; + if (m_viewFolder) + m_viewFolder->GetFlags(&folderFlags); + // check if it's a virtual folder - if so, we should get the cached hits + // from the db, and set a flag saying that we're using cached values. + if (folderFlags & nsMsgFolderFlags::Virtual) + { + nsCOMPtr<nsISimpleEnumerator> cachedHits; + nsCString searchUri; + m_viewFolder->GetURI(searchUri); + m_db->GetCachedHits(searchUri.get(), getter_AddRefs(cachedHits)); + if (cachedHits) + { + bool hasMore; + + m_usingCachedHits = true; + cachedHits->HasMoreElements(&hasMore); + m_cacheEmpty = !hasMore; + if (mTree) + mTree->BeginUpdateBatch(); + while (hasMore) + { + nsCOMPtr <nsISupports> supports; + nsresult rv = cachedHits->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports); + if (pHeader && NS_SUCCEEDED(rv)) + AddHdr(pHeader); + else + break; + cachedHits->HasMoreElements(&hasMore); + } + if (mTree) + mTree->EndUpdateBatch(); + } + } + return NS_OK; +} + +nsresult nsMsgQuickSearchDBView::GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result) +{ + uint32_t numChildren; + nsresult rv = NS_OK; + uint8_t minLevel = 0xff; + threadHdr->GetNumChildren(&numChildren); + nsMsgKey threadRootKey; + nsCOMPtr<nsIMsgDBHdr> rootParent; + int32_t rootIndex; + threadHdr->GetRootHdr(&rootIndex, getter_AddRefs(rootParent)); + if (rootParent) + rootParent->GetMessageKey(&threadRootKey); + else + threadHdr->GetThreadKey(&threadRootKey); + + nsCOMPtr <nsIMsgDBHdr> retHdr; + + // iterate over thread, finding mgsHdr in view with the lowest level. + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + nsCOMPtr <nsIMsgDBHdr> child; + rv = threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + nsMsgKey msgKey; + child->GetMessageKey(&msgKey); + + // this works because we've already sorted m_keys by id. + nsMsgViewIndex keyIndex = m_origKeys.BinaryIndexOf(msgKey); + if (keyIndex != nsMsgViewIndex_None) + { + // this is the root, so it's the best we're going to do. + if (msgKey == threadRootKey) + { + retHdr = child; + break; + } + uint8_t level = 0; + nsMsgKey parentId; + child->GetThreadParent(&parentId); + nsCOMPtr <nsIMsgDBHdr> parent; + // count number of ancestors - that's our level + while (parentId != nsMsgKey_None) + { + rv = m_db->GetMsgHdrForKey(parentId, getter_AddRefs(parent)); + if (parent) + { + nsMsgKey saveParentId = parentId; + parent->GetThreadParent(&parentId); + // message is it's own parent - bad, let's break out of here. + // Or we've got some circular ancestry. + if (parentId == saveParentId || level > numChildren) + break; + level++; + } + else // if we can't find the parent, don't loop forever. + break; + } + if (level < minLevel) + { + minLevel = level; + retHdr = child; + } + } + } + } + NS_IF_ADDREF(*result = retHdr); + return NS_OK; +} + +nsresult nsMsgQuickSearchDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) +{ + // don't need to sort by threads for group view. + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + return NS_OK; + // iterate over the messages in the view, getting the thread id's + // sort m_keys so we can quickly find if a key is in the view. + m_keys.Sort(); + // array of the threads' root hdr keys. + nsTArray<nsMsgKey> threadRootIds; + nsCOMPtr <nsIMsgDBHdr> rootHdr; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgThread> threadHdr; + for (uint32_t i = 0; i < m_keys.Length(); i++) + { + GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr)); + m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr)); + if (threadHdr) + { + nsMsgKey rootKey; + threadHdr->GetChildKeyAt(0, &rootKey); + nsMsgViewIndex threadRootIndex = threadRootIds.BinaryIndexOf(rootKey); + // if we already have that id in top level threads, ignore this msg. + if (threadRootIndex != nsMsgViewIndex_None) + continue; + // it would be nice if GetInsertIndexHelper always found the hdr, but it doesn't. + threadHdr->GetChildHdrAt(0, getter_AddRefs(rootHdr)); + if (!rootHdr) + continue; + threadRootIndex = GetInsertIndexHelper(rootHdr, threadRootIds, nullptr, + nsMsgViewSortOrder::ascending, + nsMsgViewSortType::byId); + threadRootIds.InsertElementAt(threadRootIndex, rootKey); + } + } + + m_sortType = nsMsgViewSortType::byNone; // sort from scratch + // need to sort the top level threads now by sort order, if it's not by id + // and ascending (which is the order per above). + if (!(sortType == nsMsgViewSortType::byId && + sortOrder == nsMsgViewSortOrder::ascending)) + { + m_keys.SwapElements(threadRootIds); + nsMsgDBView::Sort(sortType, sortOrder); + threadRootIds.SwapElements(m_keys); + } + m_keys.Clear(); + m_levels.Clear(); + m_flags.Clear(); + // now we've build up the list of thread ids - need to build the view + // from that. So for each thread id, we need to list the messages in the thread. + uint32_t numThreads = threadRootIds.Length(); + for (uint32_t threadIndex = 0; threadIndex < numThreads; threadIndex++) + { + m_db->GetMsgHdrForKey(threadRootIds[threadIndex], getter_AddRefs(rootHdr)); + if (rootHdr) + { + nsCOMPtr <nsIMsgDBHdr> displayRootHdr; + m_db->GetThreadContainingMsgHdr(rootHdr, getter_AddRefs(threadHdr)); + if (threadHdr) + { + nsMsgKey rootKey; + uint32_t rootFlags; + GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(displayRootHdr)); + if (!displayRootHdr) + continue; + displayRootHdr->GetMessageKey(&rootKey); + displayRootHdr->GetFlags(&rootFlags); + rootFlags |= MSG_VIEW_FLAG_ISTHREAD; + m_keys.AppendElement(rootKey); + m_flags.AppendElement(rootFlags); + m_levels.AppendElement(0); + + nsMsgViewIndex startOfThreadViewIndex = m_keys.Length(); + nsMsgViewIndex rootIndex = startOfThreadViewIndex - 1; + uint32_t numListed = 0; + ListIdsInThreadOrder(threadHdr, rootKey, 1, &startOfThreadViewIndex, &numListed); + if (numListed > 0) + m_flags[rootIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN; + } + } + } + + // The thread state is left expanded (despite viewFlags) so at least reflect + // the correct state. + m_viewFlags |= nsMsgViewFlagsType::kExpandAll; + + return NS_OK; +} + +nsresult +nsMsgQuickSearchDBView::ListCollapsedChildren(nsMsgViewIndex viewIndex, + nsIMutableArray *messageArray) +{ + nsCOMPtr<nsIMsgThread> threadHdr; + nsresult rv = GetThreadContainingIndex(viewIndex, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + nsCOMPtr<nsIMsgDBHdr> rootHdr; + nsMsgKey rootKey; + GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(rootHdr)); + rootHdr->GetMessageKey(&rootKey); + // group threads can have the root key twice, one for the dummy row. + bool rootKeySkipped = false; + for (uint32_t i = 0; i < numChildren; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) + { + // if this hdr is in the original view, add it to new view. + if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) + messageArray->AppendElement(msgHdr, false); + } + else + { + rootKeySkipped = true; + } + } + } + return NS_OK; +} + +nsresult nsMsgQuickSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) +{ + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && ! (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) + { + nsMsgKey parentKey = m_keys[startOfThreadViewIndex++]; + return ListIdsInThreadOrder(threadHdr, parentKey, 1, &startOfThreadViewIndex, pNumListed); + } + + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + uint32_t i; + uint32_t viewIndex = startOfThreadViewIndex + 1; + nsCOMPtr<nsIMsgDBHdr> rootHdr; + nsMsgKey rootKey; + uint32_t rootFlags = m_flags[startOfThreadViewIndex]; + *pNumListed = 0; + GetMsgHdrForViewIndex(startOfThreadViewIndex, getter_AddRefs(rootHdr)); + rootHdr->GetMessageKey(&rootKey); + // group threads can have the root key twice, one for the dummy row. + bool rootKeySkipped = false; + for (i = 0; i < numChildren; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + if (msgHdr != nullptr) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) + { + nsMsgViewIndex threadRootIndex = m_origKeys.BinaryIndexOf(msgKey); + // if this hdr is in the original view, add it to new view. + if (threadRootIndex != nsMsgViewIndex_None) + { + uint32_t childFlags; + msgHdr->GetFlags(&childFlags); + InsertMsgHdrAt(viewIndex, msgHdr, msgKey, childFlags, + FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex)); + if (! (rootFlags & MSG_VIEW_FLAG_HASCHILDREN)) + m_flags[startOfThreadViewIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN; + + viewIndex++; + (*pNumListed)++; + } + } + else + { + rootKeySkipped = true; + } + } + } + return NS_OK; +} + +nsresult +nsMsgQuickSearchDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr, + nsMsgKey parentKey, uint32_t level, + uint32_t callLevel, + nsMsgKey keyToSkip, + nsMsgViewIndex *viewIndex, + uint32_t *pNumListed) +{ + nsCOMPtr <nsISimpleEnumerator> msgEnumerator; + nsresult rv = threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + // We use the numChildren as a sanity check on the thread structure. + uint32_t numChildren; + (void) threadHdr->GetNumChildren(&numChildren); + bool hasMore; + nsCOMPtr <nsISupports> supports; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) && + hasMore) + { + rv = msgEnumerator->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + msgHdr = do_QueryInterface(supports); + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + if (msgKey == keyToSkip) + continue; + + // If we discover depths of more than numChildren, it means we have + // some sort of circular thread relationship and we bail out of the + // while loop before overflowing the stack with recursive calls. + // Technically, this is an error, but forcing a database rebuild + // is too destructive so we just return. + if (*pNumListed > numChildren || callLevel > numChildren) + { + NS_ERROR("loop in message threading while listing children"); + return NS_OK; + } + + int32_t childLevel = level; + if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) + { + uint32_t msgFlags; + msgHdr->GetFlags(&msgFlags); + InsertMsgHdrAt(*viewIndex, msgHdr, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level); + (*pNumListed)++; + (*viewIndex)++; + childLevel++; + } + rv = ListIdsInThreadOrder(threadHdr, msgKey, childLevel, callLevel + 1, + keyToSkip, viewIndex, pNumListed); + } + } + return rv; +} + +nsresult +nsMsgQuickSearchDBView::ListIdsInThreadOrder(nsIMsgThread *threadHdr, + nsMsgKey parentKey, uint32_t level, + nsMsgViewIndex *viewIndex, + uint32_t *pNumListed) +{ + nsresult rv = ListIdsInThreadOrder(threadHdr, parentKey, level, level, + nsMsgKey_None, viewIndex, pNumListed); + // Because a quick search view might not have the actual thread root + // as its root, and thus might have a message that potentially has siblings + // as its root, and the enumerator will miss the siblings, we might need to + // make a pass looking for the siblings of the non-root root. We'll put + // those after the potential children of the root. So we will list the children + // of the faux root's parent, ignoring the faux root. + if (level == 1) + { + nsCOMPtr<nsIMsgDBHdr> root; + nsCOMPtr<nsIMsgDBHdr> rootParent; + nsMsgKey rootKey; + int32_t rootIndex; + threadHdr->GetRootHdr(&rootIndex, getter_AddRefs(rootParent)); + if (rootParent) + { + rootParent->GetMessageKey(&rootKey); + if (rootKey != parentKey) + rv = ListIdsInThreadOrder(threadHdr, rootKey, level, level, parentKey, + viewIndex, pNumListed); + } + } + return rv; +} + +nsresult nsMsgQuickSearchDBView::ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta) +{ + *expansionDelta = 0; + if (index >= ((nsMsgViewIndex) m_keys.Length())) + return NS_MSG_MESSAGE_NOT_FOUND; + + char flags = m_flags[index]; + + if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + return NS_OK; + + nsCOMPtr<nsIMsgThread> threadHdr; + nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + nsCOMPtr<nsIMsgDBHdr> rootHdr; + nsMsgKey rootKey; + GetMsgHdrForViewIndex(index, getter_AddRefs(rootHdr)); + rootHdr->GetMessageKey(&rootKey); + // group threads can have the root key twice, one for the dummy row. + bool rootKeySkipped = false; + for (uint32_t i = 0; i < numChildren; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) + { + // if this hdr is in the original view, add it to new view. + if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) + (*expansionDelta)++; + } + else + { + rootKeySkipped = true; + } + } + } + if (! (flags & nsMsgMessageFlags::Elided)) + *expansionDelta = - (*expansionDelta); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, + nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, + nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) +{ + if (aViewFlags & nsMsgViewFlagsType::kGroupBySort) + return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder, + aViewFlags, aCount); + + m_sortType = aSortType; + m_sortOrder = aSortOrder; + m_viewFlags = aViewFlags; + + bool hasMore; + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) + { + rv = aHeaders->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + msgHdr = do_QueryInterface(supports); + AddHdr(msgHdr); + } + else + break; + } + *aCount = m_keys.Length(); + return rv; +} + +NS_IMETHODIMP nsMsgQuickSearchDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) +{ + nsresult rv = NS_OK; + // if the grouping has changed, rebuild the view + if ((m_viewFlags & nsMsgViewFlagsType::kGroupBySort) ^ + (aViewFlags & nsMsgViewFlagsType::kGroupBySort)) + rv = RebuildView(aViewFlags); + nsMsgDBView::SetViewFlags(aViewFlags); + + return rv; +} + +nsresult +nsMsgQuickSearchDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator) +{ + return GetViewEnumerator(enumerator); +} + +NS_IMETHODIMP +nsMsgQuickSearchDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, + nsMsgKey aParentKey, + int32_t aFlags, + nsIDBChangeListener *aInstigator) +{ + NS_ENSURE_ARG_POINTER(aHdrDeleted); + nsMsgKey msgKey; + aHdrDeleted->GetMessageKey(&msgKey); + size_t keyIndex = m_origKeys.BinaryIndexOf(msgKey); + if (keyIndex != m_origKeys.NoIndex) + m_origKeys.RemoveElementAt(keyIndex); + return nsMsgThreadedDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, + aInstigator); +} + +NS_IMETHODIMP nsMsgQuickSearchDBView::GetNumMsgsInView(int32_t *aNumMsgs) +{ + NS_ENSURE_ARG_POINTER(aNumMsgs); + *aNumMsgs = m_origKeys.Length(); + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgQuickSearchDBView.h b/mailnews/base/src/nsMsgQuickSearchDBView.h new file mode 100644 index 0000000000..1db04a6279 --- /dev/null +++ b/mailnews/base/src/nsMsgQuickSearchDBView.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _nsMsgQuickSearchDBView_H_ +#define _nsMsgQuickSearchDBView_H_ + +#include "mozilla/Attributes.h" +#include "nsMsgThreadedDBView.h" +#include "nsIMsgSearchNotify.h" +#include "nsIMsgSearchSession.h" +#include "nsCOMArray.h" +#include "nsIMsgHdr.h" + + +class nsMsgQuickSearchDBView : public nsMsgThreadedDBView, public nsIMsgSearchNotify +{ +public: + nsMsgQuickSearchDBView(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMSGSEARCHNOTIFY + + virtual const char * GetViewName(void) override {return "QuickSearchView"; } + NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, + nsMsgViewSortOrderValue sortOrder, + nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override; + NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, + nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, + nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) override; + NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCommandUpdater, + nsIMsgDBView **_retval) override; + NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, + nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCmdUpdater) override; + NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue aCommand) override; + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) override; + NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession) override; + NS_IMETHOD GetSearchSession(nsIMsgSearchSession* *aSearchSession) override; + NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) override; + NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus, + nsIDBChangeListener * aInstigator) override; + NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, + int32_t aFlags, nsIDBChangeListener *aInstigator) override; + NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override; + +protected: + virtual ~nsMsgQuickSearchDBView(); + nsWeakPtr m_searchSession; + nsTArray<nsMsgKey> m_origKeys; + bool m_usingCachedHits; + bool m_cacheEmpty; + nsCOMArray <nsIMsgDBHdr> m_hdrHits; + virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex *resultIndex = nullptr) override; + virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) override; + virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) override; + virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) override; + virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread *threadHdr, nsIMsgDBHdr **result) override; + virtual nsresult ExpansionDelta(nsMsgViewIndex index, int32_t *expansionDelta) override; + virtual nsresult ListCollapsedChildren(nsMsgViewIndex viewIndex, + nsIMutableArray *messageArray) override; + virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, nsMsgViewIndex startOfThreadViewIndex, uint32_t *pNumListed) override; + virtual nsresult ListIdsInThreadOrder(nsIMsgThread *threadHdr, + nsMsgKey parentKey, uint32_t level, + nsMsgViewIndex *viewIndex, + uint32_t *pNumListed) override; + virtual nsresult ListIdsInThreadOrder(nsIMsgThread *threadHdr, + nsMsgKey parentKey, uint32_t level, + uint32_t callLevel, + nsMsgKey keyToSkip, + nsMsgViewIndex *viewIndex, + uint32_t *pNumListed); + virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator) override; + void SavePreSearchInfo(); + void ClearPreSearchInfo(); + +}; + +#endif diff --git a/mailnews/base/src/nsMsgRDFDataSource.cpp b/mailnews/base/src/nsMsgRDFDataSource.cpp new file mode 100644 index 0000000000..5cdbf5cc63 --- /dev/null +++ b/mailnews/base/src/nsMsgRDFDataSource.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsMsgRDFDataSource.h" +#include "nsRDFCID.h" +#include "rdf.h" +#include "plstr.h" +#include "nsMsgRDFUtils.h" +#include "nsEnumeratorUtils.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +nsMsgRDFDataSource::nsMsgRDFDataSource(): + m_shuttingDown(false), + mInitialized(false) +{ +} + +nsMsgRDFDataSource::~nsMsgRDFDataSource() +{ + // final shutdown happens here + NS_ASSERTION(!mInitialized, "Object going away without cleanup, possibly dangerous!"); + if (mInitialized) Cleanup(); +} + +/* initialization happens here - object is constructed, + but possibly partially shut down +*/ +nsresult +nsMsgRDFDataSource::Init() +{ + NS_ENSURE_TRUE(!mInitialized, NS_ERROR_ALREADY_INITIALIZED); + + nsresult rv; + /* Add an observer to XPCOM shutdown */ + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(obs, NS_ERROR_UNEXPECTED); + rv = obs->AddObserver(static_cast<nsIObserver*>(this), NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + NS_ENSURE_SUCCESS(rv, rv); + + getRDFService(); + + mInitialized=true; + return rv; +} + +void nsMsgRDFDataSource::Cleanup() +{ + mRDFService = nullptr; + + // release the window + mWindow = nullptr; + + mInitialized = false; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsMsgRDFDataSource) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsMsgRDFDataSource) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mRDFService) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsMsgRDFDataSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRDFService) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMsgRDFDataSource) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMsgRDFDataSource) + +NS_INTERFACE_MAP_BEGIN(nsMsgRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIMsgRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsMsgRDFDataSource) +NS_INTERFACE_MAP_END + +/* readonly attribute string URI; */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetURI(char * *aURI) +{ + NS_NOTREACHED("should be implemented by a subclass"); + return NS_ERROR_UNEXPECTED; +} + + +/* nsIRDFResource GetSource (in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsIRDFResource **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* nsISimpleEnumerator GetSources (in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* nsIRDFNode GetTarget (in nsIRDFResource aSource, in nsIRDFResource aProperty, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsIRDFNode **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* nsISimpleEnumerator GetTargets (in nsIRDFResource aSource, in nsIRDFResource aProperty, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsISimpleEnumerator **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* void Assert (in nsIRDFResource aSource, in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgRDFDataSource::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue) +{ + return NS_RDF_NO_VALUE; +} + + +/* void Unassert (in nsIRDFResource aSource, in nsIRDFResource aProperty, in nsIRDFNode aTarget); */ +NS_IMETHODIMP +nsMsgRDFDataSource::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget) +{ + return NS_RDF_NO_VALUE; +} + + +NS_IMETHODIMP +nsMsgRDFDataSource::Change(nsIRDFResource *aSource, + nsIRDFResource *aProperty, + nsIRDFNode *aOldTarget, + nsIRDFNode *aNewTarget) +{ + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +nsMsgRDFDataSource::Move(nsIRDFResource *aOldSource, + nsIRDFResource *aNewSource, + nsIRDFResource *aProperty, + nsIRDFNode *aTarget) +{ + return NS_RDF_NO_VALUE; +} + + +/* boolean HasAssertion (in nsIRDFResource aSource, in nsIRDFResource aProperty, in nsIRDFNode aTarget, in boolean aTruthValue); */ +NS_IMETHODIMP +nsMsgRDFDataSource::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, bool *_retval) +{ + *_retval = false; + return NS_OK; +} + + +/* void AddObserver (in nsIRDFObserver aObserver); */ +NS_IMETHODIMP +nsMsgRDFDataSource::AddObserver(nsIRDFObserver *aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + if (!mInitialized) + Init(); + mObservers.AppendObject(aObserver); + return NS_OK; +} + +/* void RemoveObserver (in nsIRDFObserver aObserver); */ +NS_IMETHODIMP +nsMsgRDFDataSource::RemoveObserver(nsIRDFObserver *aObserver) +{ + NS_ENSURE_ARG_POINTER(aObserver); + mObservers.RemoveObject(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRDFDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result) +{ + *result = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRDFDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result) +{ + *result = false; + return NS_OK; +} + +/* nsISimpleEnumerator ArcLabelsIn (in nsIRDFNode aNode); */ +NS_IMETHODIMP +nsMsgRDFDataSource::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval) +{ + return NS_NewEmptyEnumerator(_retval); +} + + +/* nsISimpleEnumerator ArcLabelsOut (in nsIRDFResource aSource); */ +NS_IMETHODIMP +nsMsgRDFDataSource::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* nsISimpleEnumerator GetAllResources (); */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetAllResources(nsISimpleEnumerator **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* nsISimpleEnumerator GetAllCmds (in nsIRDFResource aSource); */ +NS_IMETHODIMP +nsMsgRDFDataSource::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* boolean IsCommandEnabled (in nsISupports aSources, in nsIRDFResource aCommand, in nsISupports aArguments); */ +NS_IMETHODIMP +nsMsgRDFDataSource::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments, bool *_retval) +{ + return NS_RDF_NO_VALUE; +} + + +/* void DoCommand (in nsISupports aSources, in nsIRDFResource aCommand, in nsISupports aArguments); */ +NS_IMETHODIMP +nsMsgRDFDataSource::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments) +{ + return NS_RDF_NO_VALUE; +} + +/* void BeginUpdateBatch (); */ +NS_IMETHODIMP +nsMsgRDFDataSource::BeginUpdateBatch() +{ + return NS_OK; +} + +/* void EndUpdateBatch (); */ +NS_IMETHODIMP +nsMsgRDFDataSource::EndUpdateBatch() +{ + return NS_OK; +} + + +/* XPCOM Shutdown observer */ +NS_IMETHODIMP +nsMsgRDFDataSource::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData ) +{ + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + m_shuttingDown = true; + Cleanup(); + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgRDFDataSource::GetWindow(nsIMsgWindow * *aWindow) +{ + if(!aWindow) + return NS_ERROR_NULL_POINTER; + + *aWindow = mWindow; + NS_IF_ADDREF(*aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgRDFDataSource::SetWindow(nsIMsgWindow * aWindow) +{ + mWindow = aWindow; + return NS_OK; +} + + +nsIRDFService * +nsMsgRDFDataSource::getRDFService() +{ + if (!mRDFService && !m_shuttingDown) { + nsresult rv; + mRDFService = do_GetService(kRDFServiceCID, &rv); + if (NS_FAILED(rv)) return nullptr; + } + + return mRDFService; +} + +nsresult nsMsgRDFDataSource::NotifyPropertyChanged(nsIRDFResource *resource, + nsIRDFResource *propertyResource, + nsIRDFNode *newNode, + nsIRDFNode *oldNode /* = nullptr */) +{ + + NotifyObservers(resource, propertyResource, newNode, oldNode, false, true); + return NS_OK; + +} + +nsresult nsMsgRDFDataSource::NotifyObservers(nsIRDFResource *subject, + nsIRDFResource *property, + nsIRDFNode *newObject, + nsIRDFNode *oldObject, + bool assert, bool change) +{ + NS_ASSERTION(!(change && assert), + "Can't change and assert at the same time!\n"); + nsMsgRDFNotification note = { this, subject, property, newObject, oldObject }; + if(change) + mObservers.EnumerateForwards(changeEnumFunc, ¬e); + else if (assert) + mObservers.EnumerateForwards(assertEnumFunc, ¬e); + else + mObservers.EnumerateForwards(unassertEnumFunc, ¬e); + return NS_OK; +} + +bool +nsMsgRDFDataSource::assertEnumFunc(nsIRDFObserver *aObserver, void *aData) +{ + nsMsgRDFNotification *note = (nsMsgRDFNotification *)aData; + aObserver->OnAssert(note->datasource, + note->subject, + note->property, + note->newObject); + return true; +} + +bool +nsMsgRDFDataSource::unassertEnumFunc(nsIRDFObserver *aObserver, void *aData) +{ + nsMsgRDFNotification* note = (nsMsgRDFNotification *)aData; + aObserver->OnUnassert(note->datasource, + note->subject, + note->property, + note->newObject); + return true; +} + +bool +nsMsgRDFDataSource::changeEnumFunc(nsIRDFObserver *aObserver, void *aData) +{ + nsMsgRDFNotification* note = (nsMsgRDFNotification *)aData; + aObserver->OnChange(note->datasource, + note->subject, + note->property, + note->oldObject, note->newObject); + return true; +} diff --git a/mailnews/base/src/nsMsgRDFDataSource.h b/mailnews/base/src/nsMsgRDFDataSource.h new file mode 100644 index 0000000000..636ebb60a6 --- /dev/null +++ b/mailnews/base/src/nsMsgRDFDataSource.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + + +#ifndef __nsMsgRDFDataSource_h +#define __nsMsgRDFDataSource_h + +#include "nsCOMPtr.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFService.h" +#include "nsIServiceManager.h" +#include "nsCOMArray.h" +#include "nsIObserver.h" +#include "nsITransactionManager.h" +#include "nsIMsgWindow.h" +#include "nsIMsgRDFDataSource.h" +#include "nsWeakReference.h" +#include "nsCycleCollectionParticipant.h" + +class nsMsgRDFDataSource : public nsIRDFDataSource, + public nsIObserver, + public nsSupportsWeakReference, + public nsIMsgRDFDataSource +{ + public: + nsMsgRDFDataSource(); + virtual nsresult Init(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsMsgRDFDataSource, + nsIRDFDataSource) + NS_DECL_NSIMSGRDFDATASOURCE + NS_DECL_NSIRDFDATASOURCE + NS_DECL_NSIOBSERVER + + // called to reset the datasource to an empty state + // if you need to release yourself as an observer/listener, do it here + virtual void Cleanup(); + + protected: + virtual ~nsMsgRDFDataSource(); + nsIRDFService *getRDFService(); + static bool assertEnumFunc(nsIRDFObserver *aObserver, void *aData); + static bool unassertEnumFunc(nsIRDFObserver *aObserver, void *aData); + static bool changeEnumFunc(nsIRDFObserver *aObserver, void *aData); + nsresult NotifyObservers(nsIRDFResource *subject, nsIRDFResource *property, + nsIRDFNode *newObject, nsIRDFNode *oldObject, + bool assert, bool change); + + virtual nsresult NotifyPropertyChanged(nsIRDFResource *resource, + nsIRDFResource *propertyResource, nsIRDFNode *newNode, + nsIRDFNode *oldNode = nullptr); + + nsCOMPtr<nsIMsgWindow> mWindow; + + bool m_shuttingDown; + bool mInitialized; + + private: + nsCOMPtr<nsIRDFService> mRDFService; + nsCOMArray<nsIRDFObserver> mObservers; +}; + +#endif diff --git a/mailnews/base/src/nsMsgRDFUtils.cpp b/mailnews/base/src/nsMsgRDFUtils.cpp new file mode 100644 index 0000000000..3202c73c92 --- /dev/null +++ b/mailnews/base/src/nsMsgRDFUtils.cpp @@ -0,0 +1,82 @@ +/* -*- 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 "nsMsgRDFUtils.h" +#include "nsIServiceManager.h" +#include "prprf.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" + +nsresult createNode(const char16_t *str, nsIRDFNode **node, nsIRDFService *rdfService) +{ + nsresult rv; + nsCOMPtr<nsIRDFLiteral> value; + + NS_ASSERTION(rdfService, "rdfService is null"); + if (!rdfService) return NS_OK; + + if (str) { + rv = rdfService->GetLiteral(str, getter_AddRefs(value)); + } + else { + rv = rdfService->GetLiteral(EmptyString().get(), getter_AddRefs(value)); + } + + if (NS_SUCCEEDED(rv)) { + *node = value; + NS_IF_ADDREF(*node); + } + return rv; +} + +nsresult createIntNode(int32_t value, nsIRDFNode **node, nsIRDFService *rdfService) +{ + *node = nullptr; + nsresult rv; + if (!rdfService) return NS_ERROR_NULL_POINTER; + nsCOMPtr<nsIRDFInt> num; + rv = rdfService->GetIntLiteral(value, getter_AddRefs(num)); + if(NS_SUCCEEDED(rv)) { + *node = num; + NS_IF_ADDREF(*node); + } + return rv; +} + +nsresult createBlobNode(uint8_t *value, uint32_t &length, nsIRDFNode **node, nsIRDFService *rdfService) +{ + NS_ENSURE_ARG_POINTER(node); + NS_ENSURE_ARG_POINTER(rdfService); + + *node = nullptr; + nsCOMPtr<nsIRDFBlob> blob; + nsresult rv = rdfService->GetBlobLiteral(value, length, getter_AddRefs(blob)); + NS_ENSURE_SUCCESS(rv,rv); + NS_IF_ADDREF(*node = blob); + return rv; +} + +nsresult GetTargetHasAssertion(nsIRDFDataSource *dataSource, nsIRDFResource* folderResource, + nsIRDFResource *property,bool tv, nsIRDFNode *target,bool* hasAssertion) +{ + NS_ENSURE_ARG_POINTER(hasAssertion); + + nsCOMPtr<nsIRDFNode> currentTarget; + + nsresult rv = dataSource->GetTarget(folderResource, property,tv, getter_AddRefs(currentTarget)); + if(NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIRDFLiteral> value1(do_QueryInterface(target)); + nsCOMPtr<nsIRDFLiteral> value2(do_QueryInterface(currentTarget)); + if(value1 && value2) + //If the two values are equal then it has this assertion + *hasAssertion = (value1 == value2); + } + else + rv = NS_NOINTERFACE; + + return rv; + +} + diff --git a/mailnews/base/src/nsMsgRDFUtils.h b/mailnews/base/src/nsMsgRDFUtils.h new file mode 100644 index 0000000000..3694ccc3b4 --- /dev/null +++ b/mailnews/base/src/nsMsgRDFUtils.h @@ -0,0 +1,104 @@ +/* -*- 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/. */ + +//This file holds some useful utility functions and declarations used by our datasources. + +#include "rdf.h" +#include "nsIRDFResource.h" +#include "nsIRDFNode.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFService.h" +#include "nsStringGlue.h" + +// this is used for notification of observers using nsVoidArray +typedef struct _nsMsgRDFNotification { + nsIRDFDataSource *datasource; + nsIRDFResource *subject; + nsIRDFResource *property; + nsIRDFNode *newObject; + nsIRDFNode *oldObject; +} nsMsgRDFNotification; + +//Some property declarations + +#define NC_RDF_CHILD NC_NAMESPACE_URI "child" +#define NC_RDF_NAME NC_NAMESPACE_URI "Name" +#define NC_RDF_OPEN NC_NAMESPACE_URI "open" +#define NC_RDF_FOLDERTREENAME NC_NAMESPACE_URI "FolderTreeName" +#define NC_RDF_FOLDERTREESIMPLENAME NC_NAMESPACE_URI "FolderTreeSimpleName" +#define NC_RDF_FOLDER NC_NAMESPACE_URI "Folder" +#define NC_RDF_SPECIALFOLDER NC_NAMESPACE_URI "SpecialFolder" +#define NC_RDF_SERVERTYPE NC_NAMESPACE_URI "ServerType" +#define NC_RDF_CANCREATEFOLDERSONSERVER NC_NAMESPACE_URI "CanCreateFoldersOnServer" +#define NC_RDF_CANFILEMESSAGESONSERVER NC_NAMESPACE_URI "CanFileMessagesOnServer" +#define NC_RDF_ISSERVER NC_NAMESPACE_URI "IsServer" +#define NC_RDF_ISSECURE NC_NAMESPACE_URI "IsSecure" +#define NC_RDF_CANSUBSCRIBE NC_NAMESPACE_URI "CanSubscribe" +#define NC_RDF_SUPPORTSOFFLINE NC_NAMESPACE_URI "SupportsOffline" +#define NC_RDF_CANFILEMESSAGES NC_NAMESPACE_URI "CanFileMessages" +#define NC_RDF_CANCREATESUBFOLDERS NC_NAMESPACE_URI "CanCreateSubfolders" +#define NC_RDF_CANRENAME NC_NAMESPACE_URI "CanRename" +#define NC_RDF_CANCOMPACT NC_NAMESPACE_URI "CanCompact" +#define NC_RDF_TOTALMESSAGES NC_NAMESPACE_URI "TotalMessages" +#define NC_RDF_TOTALUNREADMESSAGES NC_NAMESPACE_URI "TotalUnreadMessages" +#define NC_RDF_FOLDERSIZE NC_NAMESPACE_URI "FolderSize" +#define NC_RDF_CHARSET NC_NAMESPACE_URI "Charset" +#define NC_RDF_BIFFSTATE NC_NAMESPACE_URI "BiffState" +#define NC_RDF_HASUNREADMESSAGES NC_NAMESPACE_URI "HasUnreadMessages" +#define NC_RDF_SUBFOLDERSHAVEUNREADMESSAGES NC_NAMESPACE_URI "SubfoldersHaveUnreadMessages" +#define NC_RDF_NOSELECT NC_NAMESPACE_URI "NoSelect" +#define NC_RDF_VIRTUALFOLDER NC_NAMESPACE_URI "Virtual" +#define NC_RDF_INVFEDITSEARCHSCOPE NC_NAMESPACE_URI "InVFEditSearchScope" +#define NC_RDF_IMAPSHARED NC_NAMESPACE_URI "ImapShared" +#define NC_RDF_NEWMESSAGES NC_NAMESPACE_URI "NewMessages" +#define NC_RDF_SYNCHRONIZE NC_NAMESPACE_URI "Synchronize" +#define NC_RDF_SYNCDISABLED NC_NAMESPACE_URI "SyncDisabled" +#define NC_RDF_KEY NC_NAMESPACE_URI "Key" +#define NC_RDF_CANSEARCHMESSAGES NC_NAMESPACE_URI "CanSearchMessages" +#define NC_RDF_ISDEFERRED NC_NAMESPACE_URI "IsDeferred" + +//Sort Properties +#define NC_RDF_SUBJECT_COLLATION_SORT NC_NAMESPACE_URI "Subject?collation=true" +#define NC_RDF_SENDER_COLLATION_SORT NC_NAMESPACE_URI "Sender?collation=true" +#define NC_RDF_RECIPIENT_COLLATION_SORT NC_NAMESPACE_URI "Recipient?collation=true" +#define NC_RDF_ORDERRECEIVED_SORT NC_NAMESPACE_URI "OrderReceived?sort=true" +#define NC_RDF_PRIORITY_SORT NC_NAMESPACE_URI "Priority?sort=true" +#define NC_RDF_DATE_SORT NC_NAMESPACE_URI "Date?sort=true" +#define NC_RDF_SIZE_SORT NC_NAMESPACE_URI "Size?sort=true" +#define NC_RDF_ISUNREAD_SORT NC_NAMESPACE_URI "IsUnread?sort=true" +#define NC_RDF_FLAGGED_SORT NC_NAMESPACE_URI "Flagged?sort=true" + +#define NC_RDF_NAME_SORT NC_NAMESPACE_URI "Name?sort=true" +#define NC_RDF_FOLDERTREENAME_SORT NC_NAMESPACE_URI "FolderTreeName?sort=true" + +//Folder Commands +#define NC_RDF_DELETE NC_NAMESPACE_URI "Delete" +#define NC_RDF_REALLY_DELETE NC_NAMESPACE_URI "ReallyDelete" +#define NC_RDF_NEWFOLDER NC_NAMESPACE_URI "NewFolder" +#define NC_RDF_GETNEWMESSAGES NC_NAMESPACE_URI "GetNewMessages" +#define NC_RDF_COPY NC_NAMESPACE_URI "Copy" +#define NC_RDF_MOVE NC_NAMESPACE_URI "Move" +#define NC_RDF_COPYFOLDER NC_NAMESPACE_URI "CopyFolder" +#define NC_RDF_MOVEFOLDER NC_NAMESPACE_URI "MoveFolder" +#define NC_RDF_MARKALLMESSAGESREAD NC_NAMESPACE_URI "MarkAllMessagesRead" +#define NC_RDF_COMPACT NC_NAMESPACE_URI "Compact" +#define NC_RDF_COMPACTALL NC_NAMESPACE_URI "CompactAll" +#define NC_RDF_RENAME NC_NAMESPACE_URI "Rename" +#define NC_RDF_EMPTYTRASH NC_NAMESPACE_URI "EmptyTrash" + + +nsresult createNode(const char16_t *str, nsIRDFNode **, nsIRDFService *rdfService); + +//Given an int32_t creates an nsIRDFNode that is really an int literal. +nsresult createIntNode(int32_t value, nsIRDFNode **node, nsIRDFService *rdfService); + +//Given an nsIRDFBlob creates an nsIRDFNode that is really an blob literal. +nsresult createBlobNode(uint8_t *value, uint32_t &length, nsIRDFNode **node, nsIRDFService *rdfService); + +//s Assertion for a datasource that will just call GetTarget on property. When all of our +//datasource derive from our datasource baseclass, this should be moved there and the first +//parameter will no longer be needed. +nsresult GetTargetHasAssertion(nsIRDFDataSource *dataSource, nsIRDFResource* folderResource, + nsIRDFResource *property,bool tv, nsIRDFNode *target,bool* hasAssertion); diff --git a/mailnews/base/src/nsMsgSearchDBView.cpp b/mailnews/base/src/nsMsgSearchDBView.cpp new file mode 100644 index 0000000000..02cdb6ff6a --- /dev/null +++ b/mailnews/base/src/nsMsgSearchDBView.cpp @@ -0,0 +1,1433 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgSearchDBView.h" +#include "nsIMsgHdr.h" +#include "nsIMsgThread.h" +#include "nsQuickSort.h" +#include "nsIDBFolderInfo.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgCopyService.h" +#include "nsICopyMsgStreamListener.h" +#include "nsMsgUtils.h" +#include "nsITreeColumns.h" +#include "nsIMsgMessageService.h" +#include "nsArrayUtils.h" +#include "nsIMutableArray.h" +#include "nsMsgGroupThread.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgSearchSession.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +static bool gReferenceOnlyThreading; + +nsMsgSearchDBView::nsMsgSearchDBView() +{ + // don't try to display messages for the search pane. + mSuppressMsgDisplay = true; + m_totalMessagesInView = 0; + m_nextThreadId = 1; +} + +nsMsgSearchDBView::~nsMsgSearchDBView() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView, + nsIMsgCopyServiceListener, nsIMsgSearchNotify) + +NS_IMETHODIMP nsMsgSearchDBView::Open(nsIMsgFolder *folder, + nsMsgViewSortTypeValue sortType, + nsMsgViewSortOrderValue sortOrder, + nsMsgViewFlagsTypeValue viewFlags, + int32_t *pCount) +{ + // dbViewWrapper.js likes to create search views with a sort order + // of byNone, in order to have the order be the order the search results + // are returned. But this doesn't work with threaded view, so make the + // sort order be byDate if we're threaded. + + if (viewFlags & nsMsgViewFlagsType::kThreadedDisplay && + sortType == nsMsgViewSortType::byNone) + sortType = nsMsgViewSortType::byDate; + + nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, + viewFlags, pCount); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + prefBranch->GetBoolPref("mail.strict_threading", &gReferenceOnlyThreading); + + // our sort is automatically valid because we have no contents at this point! + m_sortValid = true; + + if (pCount) + *pCount = 0; + m_folder = nullptr; + return rv; +} + +NS_IMETHODIMP +nsMsgSearchDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) +{ + nsMsgSearchDBView* newMsgDBView = new nsMsgSearchDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) +{ + nsMsgGroupView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + nsMsgSearchDBView* newMsgDBView = (nsMsgSearchDBView *) aNewMsgDBView; + + // now copy all of our private member data + newMsgDBView->mDestFolder = mDestFolder; + newMsgDBView->mCommand = mCommand; + newMsgDBView->mTotalIndices = mTotalIndices; + newMsgDBView->mCurIndex = mCurIndex; + newMsgDBView->m_folders.InsertObjectsAt(m_folders, 0); + newMsgDBView->m_curCustomColumn = m_curCustomColumn; + newMsgDBView->m_hdrsForEachFolder.InsertObjectsAt(m_hdrsForEachFolder, 0); + newMsgDBView->m_uniqueFoldersSelected.InsertObjectsAt(m_uniqueFoldersSelected, 0); + + int32_t count = m_dbToUseList.Count(); + for(int32_t i = 0; i < count; i++) + { + newMsgDBView->m_dbToUseList.AppendObject(m_dbToUseList[i]); + // register the new view with the database so it gets notifications + m_dbToUseList[i]->AddListener(newMsgDBView); + } + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + // We need to clone the thread and msg hdr hash tables. + for (auto iter = m_threadsTable.Iter(); !iter.Done(); iter.Next()) { + newMsgDBView->m_threadsTable.Put(iter.Key(), iter.UserData()); + } + for (auto iter = m_hdrsTable.Iter(); !iter.Done(); iter.Next()) { + newMsgDBView->m_hdrsTable.Put(iter.Key(), iter.UserData()); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchDBView::Close() +{ + int32_t count = m_dbToUseList.Count(); + + for(int32_t i = 0; i < count; i++) + m_dbToUseList[i]->RemoveListener(this); + + m_dbToUseList.Clear(); + + return nsMsgGroupView::Close(); +} + +void nsMsgSearchDBView::InternalClose() +{ + m_threadsTable.Clear(); + m_hdrsTable.Clear(); + nsMsgGroupView::InternalClose(); + m_folders.Clear(); +} + +NS_IMETHODIMP nsMsgSearchDBView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue) +{ + NS_ENSURE_TRUE(IsValidIndex(aRow), NS_MSG_INVALID_DBVIEW_INDEX); + NS_ENSURE_ARG_POINTER(aCol); + + const char16_t* colID; + aCol->GetIdConst(&colID); + // the only thing we contribute is location; dummy rows have no location, so + // bail in that case. otherwise, check if we are dealing with 'location'. + // location, need to check for "lo" not just "l" to avoid "label" column + if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) && + colID[0] == 'l' && colID[1] == 'o') + return FetchLocation(aRow, aValue); + else + return nsMsgGroupView::GetCellText(aRow, aCol, aValue); +} + +nsresult nsMsgSearchDBView::HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey) +{ + if (m_sortType == nsMsgViewSortType::byLocation) + { + aHashKey.Truncate(); + nsCOMPtr<nsIMsgFolder> folder; + msgHdr->GetFolder(getter_AddRefs(folder)); + return folder->GetPrettiestName(aHashKey); + } + return nsMsgGroupView::HashHdr(msgHdr, aHashKey); +} + +nsresult nsMsgSearchDBView::FetchLocation(int32_t aRow, nsAString& aLocationString) +{ + nsCOMPtr <nsIMsgFolder> folder; + nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv,rv); + return folder->GetPrettiestName(aLocationString); +} + +nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, + bool /*ensureListed*/) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, + int32_t aFlags, nsIDBChangeListener *aInstigator) +{ + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + return nsMsgGroupView::OnHdrDeleted(aHdrDeleted, aParentKey, + aFlags, aInstigator); + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + nsMsgViewIndex deletedIndex = FindHdr(aHdrDeleted); + uint32_t savedFlags = 0; + if (deletedIndex != nsMsgViewIndex_None) + { + savedFlags = m_flags[deletedIndex]; + RemoveByIndex(deletedIndex); + } + + nsCOMPtr<nsIMsgThread> thread; + GetXFThreadFromMsgHdr(aHdrDeleted, getter_AddRefs(thread)); + if (thread) + { + nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get()); + viewThread->RemoveChildHdr(aHdrDeleted, nullptr); + if (deletedIndex == nsMsgViewIndex_None && viewThread->MsgCount() == 1) + { + // remove the last child of a collapsed thread. Need to find the root, + // and remove the thread flags on it. + nsCOMPtr<nsIMsgDBHdr> rootHdr; + thread->GetRootHdr(nullptr, getter_AddRefs(rootHdr)); + if (rootHdr) + { + nsMsgViewIndex threadIndex = GetThreadRootIndex(rootHdr); + if (threadIndex != nsMsgViewIndex_None) + AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | + nsMsgMessageFlags::Elided | + MSG_VIEW_FLAG_HASCHILDREN)); + } + } + else if (savedFlags & MSG_VIEW_FLAG_HASCHILDREN) +{ + if (savedFlags & nsMsgMessageFlags::Elided) + { + nsCOMPtr<nsIMsgDBHdr> rootHdr; + nsresult rv = thread->GetRootHdr(nullptr, getter_AddRefs(rootHdr)); + NS_ENSURE_SUCCESS(rv, rv); + nsMsgKey msgKey; + uint32_t msgFlags; + rootHdr->GetMessageKey(&msgKey); + rootHdr->GetFlags(&msgFlags); + // promote the new thread root + if (viewThread->MsgCount() > 1) + msgFlags |= MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | + MSG_VIEW_FLAG_HASCHILDREN; + InsertMsgHdrAt(deletedIndex, rootHdr, msgKey, msgFlags, 0); + if (!m_deletingRows) + NoteChange(deletedIndex, 1, nsMsgViewNotificationCode::insertOrDelete); + } + else if (viewThread->MsgCount() > 1) + { + OrExtraFlag(deletedIndex, MSG_VIEW_FLAG_ISTHREAD | + MSG_VIEW_FLAG_HASCHILDREN); + } + } + } + } + else + { + return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, + aFlags, aInstigator); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) +{ + // defer to base class if we're grouped or not threaded at all + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort || + !(m_viewFlags && nsMsgViewFlagsType::kThreadedDisplay)) + return nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, + aNewFlags, aInstigator); + + nsCOMPtr <nsIMsgThread> thread; + bool foundMessageId; + // check if the hdr that changed is in a xf thread, and if the read flag + // changed, update the thread unread count. GetXFThreadFromMsgHdr returns + // the thread the header does or would belong to, so we need to also + // check that the header is actually in the thread. + GetXFThreadFromMsgHdr(aHdrChanged, getter_AddRefs(thread), &foundMessageId); + if (foundMessageId) + { + nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get()); + if (viewThread->HdrIndex(aHdrChanged) != -1) + { + uint32_t deltaFlags = (aOldFlags ^ aNewFlags); + if (deltaFlags & nsMsgMessageFlags::Read) + thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read); + } + } + return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, + aNewFlags, aInstigator); +} + +void nsMsgSearchDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr, + nsMsgKey msgKey, uint32_t flags, uint32_t level) +{ + if ((int32_t) index < 0) + { + NS_ERROR("invalid insert index"); + index = 0; + level = 0; + } + else if (index > m_keys.Length()) + { + NS_ERROR("inserting past end of array"); + index = m_keys.Length(); + } + m_keys.InsertElementAt(index, msgKey); + m_flags.InsertElementAt(index, flags); + m_levels.InsertElementAt(index, level); + nsCOMPtr<nsIMsgFolder> folder; + hdr->GetFolder(getter_AddRefs(folder)); + m_folders.InsertObjectAt(folder, index); +} + +void nsMsgSearchDBView::SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, + nsMsgKey msgKey, uint32_t flags, uint32_t level) +{ + m_keys[index] = msgKey; + m_flags[index] = flags; + m_levels[index] = level; + nsCOMPtr<nsIMsgFolder> folder; + hdr->GetFolder(getter_AddRefs(folder)); + m_folders.ReplaceObjectAt(folder, index); +} + +bool nsMsgSearchDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows) +{ + for (int32_t i = 0; i < numRows; i++) + if (!m_folders.InsertObjectAt(nullptr, viewIndex + i)) + return false; + return nsMsgDBView::InsertEmptyRows(viewIndex, numRows); +} + +void nsMsgSearchDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) +{ + nsMsgDBView::RemoveRows(viewIndex, numRows); + for (int32_t i = 0; i < numRows; i++) + m_folders.RemoveObjectAt(viewIndex); +} + +nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, + nsIMsgDBHdr **msgHdr) +{ + nsresult rv = NS_MSG_INVALID_DBVIEW_INDEX; + if (index == nsMsgViewIndex_None || index >= (uint32_t) m_folders.Count()) + return rv; + nsIMsgFolder *folder = m_folders[index]; + if (folder) + { + nsCOMPtr <nsIMsgDatabase> db; + rv = folder->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + if (db) + rv = db->GetMsgHdrForKey(m_keys[index], msgHdr); + } + return rv; +} + +NS_IMETHODIMP nsMsgSearchDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **aFolder) +{ + NS_ENSURE_ARG_POINTER(aFolder); + + if (index == nsMsgViewIndex_None || index >= (uint32_t) m_folders.Count()) + return NS_MSG_INVALID_DBVIEW_INDEX; + NS_IF_ADDREF(*aFolder = m_folders[index]); + return *aFolder ? NS_OK : NS_ERROR_NULL_POINTER; +} + +nsresult nsMsgSearchDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db) +{ + nsCOMPtr <nsIMsgFolder> aFolder; + nsresult rv = GetFolderForViewIndex(index, getter_AddRefs(aFolder)); + NS_ENSURE_SUCCESS(rv, rv); + return aFolder->GetMsgDatabase(db); +} + +nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder) +{ + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + return nsMsgGroupView::OnNewHeader(msgHdr, nsMsgKey_None, true); + nsMsgKey msgKey; + uint32_t msgFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + nsCOMPtr<nsIMsgThread> thread; + nsCOMPtr<nsIMsgDBHdr> threadRoot; + // if we find an xf thread in the hash table corresponding to the new msg's + // message id, a previous header must be a reference child of the new + // message, which means we need to reparent later. + bool msgIsReferredTo; + GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread), &msgIsReferredTo); + bool newThread = !thread; + nsMsgXFViewThread *viewThread; + if (!thread) + { + viewThread = new nsMsgXFViewThread(this, m_nextThreadId++); + if (!viewThread) + return NS_ERROR_OUT_OF_MEMORY; + thread = do_QueryInterface(viewThread); + } + else + { + viewThread = static_cast<nsMsgXFViewThread*>(thread.get()); + thread->GetChildHdrAt(0, getter_AddRefs(threadRoot)); + } + + AddMsgToHashTables(msgHdr, thread); + nsCOMPtr<nsIMsgDBHdr> parent; + uint32_t posInThread; + // We need to move threads in order to keep ourselves sorted + // correctly. We want the index of the original thread...we can do this by + // getting the root header before we add the new header, and finding that. + if (newThread || !viewThread->MsgCount()) + { + viewThread->AddHdr(msgHdr, false, posInThread, + getter_AddRefs(parent)); + nsMsgViewIndex insertIndex = GetIndexForThread(msgHdr); + NS_ASSERTION(insertIndex == m_levels.Length() || !m_levels[insertIndex], + "inserting into middle of thread"); + if (insertIndex == nsMsgViewIndex_None) + return NS_ERROR_FAILURE; + if (!(m_viewFlags & nsMsgViewFlagsType::kExpandAll)) + msgFlags |= nsMsgMessageFlags::Elided; + InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0); + NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete); + } + else + { + // get the thread root index before we add the header, because adding + // the header can change the sort position. + nsMsgViewIndex threadIndex = GetThreadRootIndex(threadRoot); + viewThread->AddHdr(msgHdr, msgIsReferredTo, posInThread, + getter_AddRefs(parent)); + if (threadIndex == nsMsgViewIndex_None) + { + NS_ERROR("couldn't find thread index for newly inserted header"); + return NS_OK; // not really OK, but not failure exactly. + } + NS_ASSERTION(!m_levels[threadIndex], "threadRoot incorrect, or level incorrect"); + + bool moveThread = false; + if (m_sortType == nsMsgViewSortType::byDate) + { + uint32_t newestMsgInThread = 0, msgDate = 0; + viewThread->GetNewestMsgDate(&newestMsgInThread); + msgHdr->GetDateInSeconds(&msgDate); + moveThread = (msgDate == newestMsgInThread); + } + OrExtraFlag(threadIndex, MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD); + if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided)) + { + if (parent) + { + // since we know posInThread, we just want to insert the new hdr + // at threadIndex + posInThread, and then rebuild the view until we + // get to a sibling of the new hdr. + uint8_t newMsgLevel = viewThread->ChildLevelAt(posInThread); + InsertMsgHdrAt(threadIndex + posInThread, msgHdr, msgKey, msgFlags, + newMsgLevel); + + NoteChange(threadIndex + posInThread, 1, nsMsgViewNotificationCode::insertOrDelete); + for (nsMsgViewIndex viewIndex = threadIndex + ++posInThread; + posInThread < viewThread->MsgCount() && + viewThread->ChildLevelAt(posInThread) > newMsgLevel; viewIndex++) + { + m_levels[viewIndex] = viewThread->ChildLevelAt(posInThread++); + } + + } + else // The new header is the root, so we need to adjust + // all the children. + { + InsertMsgHdrAt(threadIndex, msgHdr, msgKey, msgFlags, 0); + + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete); + nsMsgViewIndex i; + for (i = threadIndex + 1; + i < m_keys.Length() && (i == threadIndex + 1 || m_levels[i]); i++) + m_levels[i] = m_levels[i] + 1; + // turn off thread flags on old root. + AndExtraFlag(threadIndex + 1, ~(MSG_VIEW_FLAG_ISTHREAD | + nsMsgMessageFlags::Elided | + MSG_VIEW_FLAG_HASCHILDREN)); + + NoteChange(threadIndex + 1, i - threadIndex + 1, + nsMsgViewNotificationCode::changed); + } + } + else if (!parent) + { + // new parent came into collapsed thread + nsCOMPtr<nsIMsgFolder> msgFolder; + msgHdr->GetFolder(getter_AddRefs(msgFolder)); + m_keys[threadIndex] = msgKey; + m_folders.ReplaceObjectAt(msgFolder, threadIndex); + m_flags[threadIndex] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | + nsMsgMessageFlags::Elided | + MSG_VIEW_FLAG_HASCHILDREN; + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + + } + if (moveThread) + MoveThreadAt(threadIndex); + } + } + else + { + m_folders.AppendObject(folder); + // nsMsgKey_None means it's not a valid hdr. + if (msgKey != nsMsgKey_None) + { + msgHdr->GetFlags(&msgFlags); + m_keys.AppendElement(msgKey); + m_levels.AppendElement(0); + m_flags.AppendElement(msgFlags); + NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete); + } + } + return NS_OK; + } + +// This method removes the thread at threadIndex from the view +// and puts it back in its new position, determined by the sort order. +// And, if the selection is affected, save and restore the selection. +void nsMsgSearchDBView::MoveThreadAt(nsMsgViewIndex threadIndex) +{ + bool updatesSuppressed = mSuppressChangeNotification; + // Turn off tree notifications so that we don't reload the current message. + if (!updatesSuppressed) + SetSuppressChangeNotifications(true); + + nsCOMPtr<nsIMsgDBHdr> threadHdr; + GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr)); + + uint32_t saveFlags = m_flags[threadIndex]; + bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided); + int32_t childCount = 0; + nsMsgKey preservedKey; + AutoTArray<nsMsgKey, 1> preservedSelection; + int32_t selectionCount; + int32_t currentIndex; + bool hasSelection = mTree && mTreeSelection && + ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(¤tIndex)) && + currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) || + (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) && + selectionCount > 0)); + + + if (hasSelection) + SaveAndClearSelection(&preservedKey, preservedSelection); + + if (threadIsExpanded) + { + ExpansionDelta(threadIndex, &childCount); + childCount = -childCount; + } + nsTArray<nsMsgKey> threadKeys; + nsTArray<uint32_t> threadFlags; + nsTArray<uint8_t> threadLevels; + nsCOMArray<nsIMsgFolder> threadFolders; + + if (threadIsExpanded) + { + threadKeys.SetCapacity(childCount); + threadFlags.SetCapacity(childCount); + threadLevels.SetCapacity(childCount); + threadFolders.SetCapacity(childCount); + for (nsMsgViewIndex index = threadIndex + 1; + index < (nsMsgViewIndex) GetSize() && m_levels[index]; index++) + { + threadKeys.AppendElement(m_keys[index]); + threadFlags.AppendElement(m_flags[index]); + threadLevels.AppendElement(m_levels[index]); + threadFolders.AppendObject(m_folders[index]); + } + uint32_t collapseCount; + CollapseByIndex(threadIndex, &collapseCount); + } + nsMsgDBView::RemoveByIndex(threadIndex); + m_folders.RemoveObjectAt(threadIndex); + nsMsgViewIndex newIndex = GetIndexForThread(threadHdr); + NS_ASSERTION(newIndex == m_levels.Length() || !m_levels[newIndex], + "inserting into middle of thread"); + if (newIndex == nsMsgViewIndex_None) + newIndex = 0; + nsMsgKey msgKey; + uint32_t msgFlags; + threadHdr->GetMessageKey(&msgKey); + threadHdr->GetFlags(&msgFlags); + InsertMsgHdrAt(newIndex, threadHdr, msgKey, msgFlags, 0); + + if (threadIsExpanded) + { + m_keys.InsertElementsAt(newIndex + 1, threadKeys); + m_flags.InsertElementsAt(newIndex + 1, threadFlags); + m_levels.InsertElementsAt(newIndex + 1, threadLevels); + m_folders.InsertObjectsAt(threadFolders, newIndex + 1); + } + m_flags[newIndex] = saveFlags; + // unfreeze selection. + if (hasSelection) + RestoreSelection(preservedKey, preservedSelection); + + if (!updatesSuppressed) + SetSuppressChangeNotifications(false); + nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex; + nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex; + NoteChange(lowIndex, highIndex - lowIndex + childCount + 1, + nsMsgViewNotificationCode::changed); +} + +nsresult +nsMsgSearchDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator) +{ + // We do not have an m_db, so the default behavior (in nsMsgDBView) is not + // what we want (it will crash). We just want someone to enumerate the + // headers that we already have. Conveniently, nsMsgDBView already knows + // how to do this with its view enumerator, so we just use that. + return nsMsgDBView::GetViewEnumerator(enumerator); +} + +nsresult nsMsgSearchDBView::InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder) +{ + nsMsgViewIndex insertIndex = nsMsgViewIndex_None; + // Threaded view always needs to go through AddHdrFromFolder since + // it handles the xf view thread object creation. + if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + insertIndex = GetInsertIndex(msgHdr); + + if (insertIndex == nsMsgViewIndex_None) + return AddHdrFromFolder(msgHdr, folder); + + nsMsgKey msgKey; + uint32_t msgFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0); + + // the call to NoteChange() has to happen after we add the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder) +{ + NS_ENSURE_ARG(aMsgHdr); + NS_ENSURE_ARG(folder); + + if (m_folders.IndexOf(folder) < 0 ) //do this just for new folder + { + nsCOMPtr<nsIMsgDatabase> dbToUse; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse)); + if (dbToUse) + { + dbToUse->AddListener(this); + m_dbToUseList.AppendObject(dbToUse); + } + } + m_totalMessagesInView++; + if (m_sortValid) + return InsertHdrFromFolder(aMsgHdr, folder); + else + return AddHdrFromFolder(aMsgHdr, folder); +} + +NS_IMETHODIMP +nsMsgSearchDBView::OnSearchDone(nsresult status) +{ + //we want to set imap delete model once the search is over because setting next + //message after deletion will happen before deleting the message and search scope + //can change with every search. + mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; //set to default in case it is non-imap folder + nsIMsgFolder *curFolder = m_folders.SafeObjectAt(0); + if (curFolder) + GetImapDeleteModel(curFolder); + return NS_OK; +} + +// for now also acts as a way of resetting the search datasource +NS_IMETHODIMP +nsMsgSearchDBView::OnNewSearch() +{ + int32_t oldSize = GetSize(); + + int32_t count = m_dbToUseList.Count(); + for(int32_t j = 0; j < count; j++) + m_dbToUseList[j]->RemoveListener(this); + + m_dbToUseList.Clear(); + m_folders.Clear(); + m_keys.Clear(); + m_levels.Clear(); + m_flags.Clear(); + m_totalMessagesInView = 0; + + // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount() + if (mTree) + mTree->RowCountChanged(0, -oldSize); + +// mSearchResults->Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowSearch; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::SetSearchSession(nsIMsgSearchSession *aSession) +{ + m_searchSession = do_GetWeakReference(aSession); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) +{ + nsIMsgDatabase *db = static_cast<nsIMsgDatabase *>(instigator); + if (db) + { + db->RemoveListener(this); + m_dbToUseList.RemoveObject(db); + } + return NS_OK; +} + +nsCOMArray<nsIMsgFolder>* nsMsgSearchDBView::GetFolders() +{ + return &m_folders; +} + +NS_IMETHODIMP +nsMsgSearchDBView::GetCommandStatus(nsMsgViewCommandTypeValue command, bool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p) +{ + if (command != nsMsgViewCommandType::runJunkControls) + return nsMsgDBView::GetCommandStatus(command, selectable_p, selected_p); + + *selectable_p = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder) +{ + mCommand = command; + mDestFolder = destFolder; + return nsMsgDBView::DoCommandWithFolder(command, destFolder); +} + +NS_IMETHODIMP nsMsgSearchDBView::DoCommand(nsMsgViewCommandTypeValue command) +{ + mCommand = command; + if (command == nsMsgViewCommandType::deleteMsg || + command == nsMsgViewCommandType::deleteNoTrash || + command == nsMsgViewCommandType::selectAll || + command == nsMsgViewCommandType::selectThread || + command == nsMsgViewCommandType::selectFlagged || + command == nsMsgViewCommandType::expandAll || + command == nsMsgViewCommandType::collapseAll) + return nsMsgDBView::DoCommand(command); + nsresult rv = NS_OK; + nsMsgViewIndexArray selection; + GetSelectedIndices(selection); + + nsMsgViewIndex *indices = selection.Elements(); + int32_t numIndices = selection.Length(); + + // we need to break apart the selection by folders, and then call + // ApplyCommandToIndices with the command and the indices in the + // selection that are from that folder. + + mozilla::UniquePtr<nsTArray<uint32_t>[]> indexArrays; + int32_t numArrays; + rv = PartitionSelectionByFolder(indices, numIndices, indexArrays, &numArrays); + NS_ENSURE_SUCCESS(rv, rv); + for (int32_t folderIndex = 0; folderIndex < numArrays; folderIndex++) + { + rv = ApplyCommandToIndices(command, (indexArrays.get())[folderIndex].Elements(), indexArrays[folderIndex].Length()); + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +// This method removes the specified line from the view, and adjusts the +// various flags and levels of affected messages. +nsresult nsMsgSearchDBView::RemoveByIndex(nsMsgViewIndex index) +{ + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIMsgThread> thread; + nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread)); + if (thread) + { + nsMsgXFViewThread *viewThread = static_cast<nsMsgXFViewThread*>(thread.get()); + if (viewThread->MsgCount() == 2) + { + // if we removed the next to last message in the thread, + // we need to adjust the flags on the first message in the thread. + nsMsgViewIndex threadIndex = m_levels[index] ? index -1 : index; + if (threadIndex != nsMsgViewIndex_None) + { + AndExtraFlag(threadIndex, ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided | + MSG_VIEW_FLAG_HASCHILDREN)); + m_levels[threadIndex] = 0; + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + } + } + // Bump up the level of all the descendents of the message + // that was removed, if the thread was expanded. + uint8_t removedLevel = m_levels[index]; + nsMsgViewIndex i = index + 1; + if (i < m_levels.Length() && m_levels[i] > removedLevel) + { + // promote the child of the removed message. + uint8_t promotedLevel = m_levels[i]; + m_levels[i] = promotedLevel - 1; + i++; + // now promote all the children of the promoted message. + for (; i < m_levels.Length() && + m_levels[i] > promotedLevel; i++) + m_levels[i] = m_levels[i] - 1; + } + } + } + m_folders.RemoveObjectAt(index); + return nsMsgDBView::RemoveByIndex(index); +} + +nsresult nsMsgSearchDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) +{ + nsresult rv = GetFoldersAndHdrsForSelection(indices, numIndices); + NS_ENSURE_SUCCESS(rv, rv); + if (mDeleteModel != nsMsgImapDeleteModels::MoveToTrash) + deleteStorage = true; + if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete) + m_deletingRows = true; + + // remember the deleted messages in case the user undoes the delete, + // and we want to restore the hdr to the view, even if it no + // longer matches the search criteria. + for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + (void) GetMsgHdrForViewIndex(indices[i], getter_AddRefs(msgHdr)); + if (msgHdr) + RememberDeletedMsgHdr(msgHdr); + // if we are deleting rows, save off the view indices + if (m_deletingRows) + mIndicesToNoteChange.AppendElement(indices[i]); + + } + rv = deleteStorage ? ProcessRequestsInAllFolders(window) + : ProcessRequestsInOneFolder(window); + if (NS_FAILED(rv)) + m_deletingRows = false; + return rv; +} + +nsresult +nsMsgSearchDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder) +{ + GetFoldersAndHdrsForSelection(indices, numIndices); + return ProcessRequestsInOneFolder(window); +} + +nsresult +nsMsgSearchDBView::PartitionSelectionByFolder(nsMsgViewIndex *indices, + int32_t numIndices, + mozilla::UniquePtr<nsTArray<uint32_t>[]> &indexArrays, + int32_t *numArrays) +{ + nsMsgViewIndex i; + int32_t folderIndex; + nsCOMArray<nsIMsgFolder> uniqueFoldersSelected; + nsTArray<uint32_t> numIndicesSelected; + mCurIndex = 0; + + //Build unique folder list based on headers selected by the user + for (i = 0; i < (nsMsgViewIndex) numIndices; i++) + { + nsIMsgFolder *curFolder = m_folders[indices[i]]; + folderIndex = uniqueFoldersSelected.IndexOf(curFolder); + if (folderIndex < 0) + { + uniqueFoldersSelected.AppendObject(curFolder); + numIndicesSelected.AppendElement(1); + } + else + { + numIndicesSelected[folderIndex]++; + } + } + + int32_t numFolders = uniqueFoldersSelected.Count(); + indexArrays = mozilla::MakeUnique<nsTArray<uint32_t>[]>(numFolders); + *numArrays = numFolders; + NS_ENSURE_TRUE(indexArrays, NS_ERROR_OUT_OF_MEMORY); + for (folderIndex = 0; folderIndex < numFolders; folderIndex++) + { + (indexArrays.get())[folderIndex].SetCapacity(numIndicesSelected[folderIndex]); + } + for (i = 0; i < (nsMsgViewIndex) numIndices; i++) + { + nsIMsgFolder *curFolder = m_folders[indices[i]]; + int32_t folderIndex = uniqueFoldersSelected.IndexOf(curFolder); + (indexArrays.get())[folderIndex].AppendElement(indices[i]); + } + return NS_OK; +} + +nsresult +nsMsgSearchDBView::GetFoldersAndHdrsForSelection(nsMsgViewIndex *indices, int32_t numIndices) +{ + nsresult rv = NS_OK; + mCurIndex = 0; + m_uniqueFoldersSelected.Clear(); + m_hdrsForEachFolder.Clear(); + + nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetHeadersFromSelection(indices, numIndices, messages); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numMsgs; + messages->GetLength(&numMsgs); + + uint32_t i; + // Build unique folder list based on headers selected by the user + for (i = 0; i < numMsgs; i++) + { + nsCOMPtr<nsIMsgDBHdr> hdr = do_QueryElementAt(messages, i, &rv); + if (hdr) + { + nsCOMPtr<nsIMsgFolder> curFolder; + hdr->GetFolder(getter_AddRefs(curFolder)); + if (m_uniqueFoldersSelected.IndexOf(curFolder) < 0) + m_uniqueFoldersSelected.AppendObject(curFolder); + } + } + + // Group the headers selected by each folder + uint32_t numFolders = m_uniqueFoldersSelected.Count(); + for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) + { + nsIMsgFolder *curFolder = m_uniqueFoldersSelected[folderIndex]; + nsCOMPtr<nsIMutableArray> msgHdrsForOneFolder(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + for (i = 0; i < numMsgs; i++) + { + nsCOMPtr<nsIMsgDBHdr> hdr = do_QueryElementAt(messages, i, &rv); + if (hdr) + { + nsCOMPtr<nsIMsgFolder> msgFolder; + hdr->GetFolder(getter_AddRefs(msgFolder)); + if (NS_SUCCEEDED(rv) && msgFolder && msgFolder == curFolder) + { + nsCOMPtr<nsISupports> hdrSupports = do_QueryInterface(hdr); + msgHdrsForOneFolder->AppendElement(hdrSupports, false); + } + } + } + m_hdrsForEachFolder.AppendElement(msgHdrsForOneFolder); + } + return rv; +} + +nsresult +nsMsgSearchDBView::ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, + int32_t numIndices, nsIMsgFolder *destFolder) +{ + mCommand = command; + mDestFolder = destFolder; + return nsMsgDBView::ApplyCommandToIndicesWithFolder(command, indices, numIndices, destFolder); +} + +// nsIMsgCopyServiceListener methods + +NS_IMETHODIMP +nsMsgSearchDBView::OnStartCopy() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + return NS_OK; +} + +// believe it or not, these next two are msgcopyservice listener methods! +NS_IMETHODIMP +nsMsgSearchDBView::SetMessageKey(nsMsgKey aMessageKey) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::GetMessageId(nsACString& messageId) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchDBView::OnStopCopy(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) + { + mCurIndex++; + if ((int32_t) mCurIndex < m_uniqueFoldersSelected.Count()) + { + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak)); + ProcessRequestsInOneFolder(msgWindow); + } + } + return NS_OK; +} + +// end nsIMsgCopyServiceListener methods + +nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow *window) +{ + nsresult rv = NS_OK; + + // Folder operations like copy/move are not implemented for .eml files. + if (m_uniqueFoldersSelected.Count() == 0) + return NS_ERROR_NOT_IMPLEMENTED; + + nsIMsgFolder *curFolder = m_uniqueFoldersSelected[mCurIndex]; + NS_ASSERTION(curFolder, "curFolder is null"); + nsCOMPtr<nsIMutableArray> messageArray = m_hdrsForEachFolder[mCurIndex]; + NS_ASSERTION(messageArray, "messageArray is null"); + + // called for delete with trash, copy and move + if (mCommand == nsMsgViewCommandType::deleteMsg) + curFolder->DeleteMessages(messageArray, window, false /* delete storage */, false /* is move*/, this, true /*allowUndo*/); + else + { + NS_ASSERTION(!(curFolder == mDestFolder), "The source folder and the destination folder are the same"); + if (NS_SUCCEEDED(rv) && curFolder != mDestFolder) + { + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + if (mCommand == nsMsgViewCommandType::moveMessages) + copyService->CopyMessages(curFolder, messageArray, mDestFolder, true /* isMove */, this, window, true /*allowUndo*/); + else if (mCommand == nsMsgViewCommandType::copyMessages) + copyService->CopyMessages(curFolder, messageArray, mDestFolder, false /* isMove */, this, window, true /*allowUndo*/); + } + } + } + return rv; +} + +nsresult nsMsgSearchDBView::ProcessRequestsInAllFolders(nsIMsgWindow *window) +{ + uint32_t numFolders = m_uniqueFoldersSelected.Count(); + for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) + { + nsIMsgFolder *curFolder = m_uniqueFoldersSelected[folderIndex]; + NS_ASSERTION (curFolder, "curFolder is null"); + + nsCOMPtr<nsIMutableArray> messageArray = m_hdrsForEachFolder[folderIndex]; + NS_ASSERTION(messageArray, "messageArray is null"); + + curFolder->DeleteMessages(messageArray, window, true /* delete storage */, false /* is move*/, nullptr/*copyServListener*/, false /*allowUndo*/ ); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) +{ + if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered()) + return NS_OK; + + int32_t rowCountBeforeSort = GetSize(); + + if (!rowCountBeforeSort) + return NS_OK; + + if (m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay | + nsMsgViewFlagsType::kGroupBySort)) + { + // ### This forgets which threads were expanded, and is sub-optimal + // since it rebuilds the thread objects. + m_sortType = sortType; + m_sortOrder = sortOrder; + return RebuildView(m_viewFlags); + } + + nsMsgKey preservedKey; + AutoTArray<nsMsgKey, 1> preservedSelection; + SaveAndClearSelection(&preservedKey, preservedSelection); + + nsresult rv = nsMsgDBView::Sort(sortType,sortOrder); + // the sort may have changed the number of rows + // before we restore the selection, tell the tree + // do this before we call restore selection + // this is safe when there is no selection. + rv = AdjustRowCount(rowCountBeforeSort, GetSize()); + + RestoreSelection(preservedKey, preservedSelection); + if (mTree) mTree->Invalidate(); + + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +// if nothing selected, return an NS_ERROR +NS_IMETHODIMP +nsMsgSearchDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr) +{ + NS_ENSURE_ARG_POINTER(hdr); + int32_t index; + + if (!mTreeSelection) + { + // We're in standalone mode, so use the message view index to get the header + // We can't use the key here because we don't have an m_db + index = m_currentlyDisplayedViewIndex; + } + else + { + nsresult rv = mTreeSelection->GetCurrentIndex(&index); + NS_ENSURE_SUCCESS(rv, rv); + } + + return GetMsgHdrForViewIndex(index, hdr); +} + +NS_IMETHODIMP +nsMsgSearchDBView::OpenWithHdrs(nsISimpleEnumerator *aHeaders, + nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, + nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) +{ + if (aViewFlags & nsMsgViewFlagsType::kGroupBySort) + return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder, + aViewFlags, aCount); + + m_sortType = aSortType; + m_sortOrder = aSortOrder; + m_viewFlags = aViewFlags; + SaveSortInfo(m_sortType, m_sortOrder); + + bool hasMore; + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) + { + rv = aHeaders->GetNext(getter_AddRefs(supports)); + if (NS_SUCCEEDED(rv) && supports) + { + msgHdr = do_QueryInterface(supports); + msgHdr->GetFolder(getter_AddRefs(folder)); + AddHdrFromFolder(msgHdr, folder); + } + } + *aCount = m_keys.Length(); + return rv; +} + +nsresult +nsMsgSearchDBView::GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder) +{ + nsCOMPtr <nsIMsgMessageService> msgMessageService; + nsresult rv = GetMessageServiceFromURI(nsDependentCString(aMsgURI), getter_AddRefs(msgMessageService)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + + return msgHdr->GetFolder(aFolder); +} + +nsMsgViewIndex nsMsgSearchDBView::FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex, + bool allowDummy) +{ + nsCOMPtr<nsIMsgDBHdr> curHdr; + uint32_t index; + // it would be nice to take advantage of sorted views when possible. + for (index = startIndex; index < GetSize(); index++) + { + GetMsgHdrForViewIndex(index, getter_AddRefs(curHdr)); + if (curHdr == msgHdr && + (allowDummy || + !(m_flags[index] & MSG_VIEW_FLAG_DUMMY) || + (m_flags[index] & nsMsgMessageFlags::Elided))) + break; + } + return index < GetSize() ? index : nsMsgViewIndex_None; +} + +// This method looks for the XF thread that corresponds to this message hdr, +// first by looking up the message id, then references, and finally, if subject +// threading is turned on, the subject. +nsresult nsMsgSearchDBView::GetXFThreadFromMsgHdr(nsIMsgDBHdr *msgHdr, + nsIMsgThread **pThread, + bool *foundByMessageId) +{ + NS_ENSURE_ARG_POINTER(pThread); + + nsAutoCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + *pThread = nullptr; + m_threadsTable.Get(messageId, pThread); + // The caller may want to know if we found the thread by the msgHdr's + // messageId + if (foundByMessageId) + *foundByMessageId = *pThread != nullptr; + if (!*pThread) + { + uint16_t numReferences = 0; + msgHdr->GetNumReferences(&numReferences); + for (int32_t i = numReferences - 1; i >= 0 && !*pThread; i--) + { + nsAutoCString reference; + + msgHdr->GetStringReference(i, reference); + if (reference.IsEmpty()) + break; + + m_threadsTable.Get(reference, pThread); + } + } + // if we're threading by subject, and we couldn't find the thread by ref, + // just treat subject as an other ref. + if (!*pThread && !gReferenceOnlyThreading) + { + nsCString subject; + msgHdr->GetSubject(getter_Copies(subject)); + // this is the raw rfc822 subject header, so this is OK + m_threadsTable.Get(subject, pThread); + } + return (*pThread) ? NS_OK : NS_ERROR_FAILURE; +} + +bool nsMsgSearchDBView::GetMsgHdrFromHash(nsCString &reference, nsIMsgDBHdr **hdr) +{ + return m_hdrsTable.Get(reference, hdr); +} + +bool nsMsgSearchDBView::GetThreadFromHash(nsCString &reference, + nsIMsgThread **thread) +{ + return m_threadsTable.Get(reference, thread); +} + +nsresult nsMsgSearchDBView::AddRefToHash(nsCString &reference, + nsIMsgThread *thread) +{ + // Check if this reference is already is associated with a thread; + // If so, don't overwrite that association. + nsCOMPtr<nsIMsgThread> oldThread; + m_threadsTable.Get(reference, getter_AddRefs(oldThread)); + if (oldThread) + return NS_OK; + + m_threadsTable.Put(reference, thread); + return NS_OK; +} + +nsresult nsMsgSearchDBView::AddMsgToHashTables(nsIMsgDBHdr *msgHdr, + nsIMsgThread *thread) +{ + NS_ENSURE_ARG_POINTER(msgHdr); + + uint16_t numReferences = 0; + nsresult rv; + + msgHdr->GetNumReferences(&numReferences); + for (int32_t i = 0; i < numReferences; i++) + { + nsAutoCString reference; + + msgHdr->GetStringReference(i, reference); + if (reference.IsEmpty()) + break; + + rv = AddRefToHash(reference, thread); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + m_hdrsTable.Put(messageId, msgHdr); + if (!gReferenceOnlyThreading) + { + nsCString subject; + msgHdr->GetSubject(getter_Copies(subject)); + // if we're threading by subject, just treat subject as an other ref. + AddRefToHash(subject, thread); + } + return AddRefToHash(messageId, thread); +} + +nsresult nsMsgSearchDBView::RemoveRefFromHash(nsCString &reference) +{ + m_threadsTable.Remove(reference); + return NS_OK; +} + +nsresult nsMsgSearchDBView::RemoveMsgFromHashTables(nsIMsgDBHdr *msgHdr) +{ + NS_ENSURE_ARG_POINTER(msgHdr); + + uint16_t numReferences = 0; + nsresult rv = NS_OK; + + msgHdr->GetNumReferences(&numReferences); + + for (int32_t i = 0; i < numReferences; i++) + { + nsAutoCString reference; + msgHdr->GetStringReference(i, reference); + if (reference.IsEmpty()) + break; + + rv = RemoveRefFromHash(reference); + if (NS_FAILED(rv)) + break; + } + nsCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + m_hdrsTable.Remove(messageId); + RemoveRefFromHash(messageId); + if (!gReferenceOnlyThreading) + { + nsCString subject; + msgHdr->GetSubject(getter_Copies(subject)); + // if we're threading by subject, just treat subject as an other ref. + RemoveRefFromHash(subject); + } + return rv; +} + +nsMsgGroupThread *nsMsgSearchDBView::CreateGroupThread(nsIMsgDatabase * /* db */) +{ + return new nsMsgXFGroupThread(); +} + +NS_IMETHODIMP nsMsgSearchDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, + nsIMsgThread **pThread) +{ + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + return nsMsgGroupView::GetThreadContainingMsgHdr(msgHdr, pThread); + else if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + return GetXFThreadFromMsgHdr(msgHdr, pThread); + + // if not threaded, use the real thread. + nsCOMPtr<nsIMsgDatabase> msgDB; + nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + return msgDB->GetThreadContainingMsgHdr(msgHdr, pThread); +} + +nsresult +nsMsgSearchDBView::ListIdsInThread(nsIMsgThread *threadHdr, + nsMsgViewIndex startOfThreadViewIndex, + uint32_t *pNumListed) +{ + NS_ENSURE_ARG_POINTER(threadHdr); + NS_ENSURE_ARG_POINTER(pNumListed); + + // these children ids should be in thread order. + uint32_t i; + nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1; + *pNumListed = 0; + + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + NS_ASSERTION(numChildren, "Empty thread in view/db"); + if (!numChildren) + return NS_OK; + + numChildren--; // account for the existing thread root + if (!InsertEmptyRows(viewIndex, numChildren)) + return NS_ERROR_OUT_OF_MEMORY; + + bool threadedView = m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay && + !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort); + nsMsgXFViewThread *viewThread; + if (threadedView) + viewThread = static_cast<nsMsgXFViewThread*>(threadHdr); + + for (i = 1; i <= numChildren; i++) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr)); + + if (msgHdr) + { + nsMsgKey msgKey; + uint32_t msgFlags; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + uint8_t level = (threadedView) ? viewThread->ChildLevelAt(i) : 1; + SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, + level); + (*pNumListed)++; + viewIndex++; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchDBView::GetNumMsgsInView(int32_t *aNumMsgs) +{ + NS_ENSURE_ARG_POINTER(aNumMsgs); + *aNumMsgs = m_totalMessagesInView; + return NS_OK; +} + diff --git a/mailnews/base/src/nsMsgSearchDBView.h b/mailnews/base/src/nsMsgSearchDBView.h new file mode 100644 index 0000000000..ff89f1d759 --- /dev/null +++ b/mailnews/base/src/nsMsgSearchDBView.h @@ -0,0 +1,146 @@ +/* -*- 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/. */ + +#ifndef _nsMsgSearchDBViews_H_ +#define _nsMsgSearchDBViews_H_ + +#include "mozilla/Attributes.h" +#include "nsMsgGroupView.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIMsgSearchNotify.h" +#include "nsMsgXFViewThread.h" +#include "nsCOMArray.h" +#include "mozilla/UniquePtr.h" + +class nsMsgSearchDBView : public nsMsgGroupView, public nsIMsgCopyServiceListener, public nsIMsgSearchNotify +{ +public: + nsMsgSearchDBView(); + + // these are tied together pretty intimately + friend class nsMsgXFViewThread; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIMSGSEARCHNOTIFY + NS_DECL_NSIMSGCOPYSERVICELISTENER + + NS_IMETHOD SetSearchSession(nsIMsgSearchSession *aSearchSession) override; + + virtual const char *GetViewName(void) override { return "SearchView"; } + NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, + nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override; + NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) override; + NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) override; + NS_IMETHOD Close() override; + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType, + nsMsgViewSortOrderValue sortOrder) override; + NS_IMETHOD GetCommandStatus(nsMsgViewCommandTypeValue command, + bool *selectable_p, + nsMsgViewCommandCheckStateValue *selected_p) override; + NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command) override; + NS_IMETHOD DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder) override; + NS_IMETHOD GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr) override; + NS_IMETHOD OpenWithHdrs(nsISimpleEnumerator *aHeaders, + nsMsgViewSortTypeValue aSortType, + nsMsgViewSortOrderValue aSortOrder, + nsMsgViewFlagsTypeValue aViewFlags, + int32_t *aCount) override; + NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr *aHdrDeleted, nsMsgKey aParentKey, + int32_t aFlags, nsIDBChangeListener *aInstigator) override; + NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr *aHdrChanged, uint32_t aOldFlags, + uint32_t aNewFlags, nsIDBChangeListener *aInstigator) override; + NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override; + // override to get location + NS_IMETHOD GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& aValue) override; + virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr) override; + virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, bool ensureListed) override; + NS_IMETHOD GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **folder) override; + + NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) override; + + virtual nsCOMArray<nsIMsgFolder>* GetFolders() override; + virtual nsresult GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder) override; + + NS_IMETHOD GetThreadContainingMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread) override; + +protected: + virtual ~nsMsgSearchDBView(); + virtual void InternalClose() override; + virtual nsresult HashHdr(nsIMsgDBHdr *msgHdr, nsString& aHashKey) override; + virtual nsresult ListIdsInThread(nsIMsgThread *threadHdr, + nsMsgViewIndex startOfThreadViewIndex, + uint32_t *pNumListed) override; + nsresult FetchLocation(int32_t aRow, nsAString& aLocationString); + virtual nsresult AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder); + virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db) override; + virtual nsresult RemoveByIndex(nsMsgViewIndex index) override; + virtual nsresult CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool isMove, nsIMsgFolder *destFolder) override; + virtual nsresult DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, int32_t numIndices, bool deleteStorage) override; + virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr *hdr, + nsMsgKey msgKey, uint32_t flags, uint32_t level) override; + virtual void SetMsgHdrAt(nsIMsgDBHdr *hdr, nsMsgViewIndex index, + nsMsgKey msgKey, uint32_t flags, uint32_t level) override; + virtual bool InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows) override; + virtual void RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) override; + virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr *msgHdr, nsMsgViewIndex startIndex = 0, + bool allowDummy=false) override; + nsresult GetFoldersAndHdrsForSelection(nsMsgViewIndex *indices, int32_t numIndices); + nsresult GroupSearchResultsByFolder(); + nsresult PartitionSelectionByFolder(nsMsgViewIndex *indices, + int32_t numIndices, + mozilla::UniquePtr<nsTArray<uint32_t>[]> &indexArrays, + int32_t *numArrays); + + virtual nsresult ApplyCommandToIndicesWithFolder(nsMsgViewCommandTypeValue command, nsMsgViewIndex* indices, + int32_t numIndices, nsIMsgFolder *destFolder) override; + void MoveThreadAt(nsMsgViewIndex threadIndex); + + virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator) override; + virtual nsresult InsertHdrFromFolder(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder); + + nsCOMArray<nsIMsgFolder> m_folders; + nsCOMArray<nsIMutableArray> m_hdrsForEachFolder; + nsCOMArray<nsIMsgFolder> m_uniqueFoldersSelected; + uint32_t mCurIndex; + + nsMsgViewIndex* mIndicesForChainedDeleteAndFile; + int32_t mTotalIndices; + nsCOMArray<nsIMsgDatabase> m_dbToUseList; + nsMsgViewCommandTypeValue mCommand; + nsCOMPtr <nsIMsgFolder> mDestFolder; + nsWeakPtr m_searchSession; + + nsresult ProcessRequestsInOneFolder(nsIMsgWindow *window); + nsresult ProcessRequestsInAllFolders(nsIMsgWindow *window); + // these are for doing threading of the search hits + + // used for assigning thread id's to xfview threads. + nsMsgKey m_nextThreadId; + // this maps message-ids and reference message ids to + // the corresponding nsMsgXFViewThread object. If we're + // doing subject threading, we would throw subjects + // into the same table. + nsInterfaceHashtable <nsCStringHashKey, nsIMsgThread> m_threadsTable; + + // map message-ids to msg hdrs in the view, used for threading. + nsInterfaceHashtable <nsCStringHashKey, nsIMsgDBHdr> m_hdrsTable; + uint32_t m_totalMessagesInView; + + virtual nsMsgGroupThread *CreateGroupThread(nsIMsgDatabase *db) override; + nsresult GetXFThreadFromMsgHdr(nsIMsgDBHdr *msgHdr, nsIMsgThread **pThread, + bool *foundByMessageId = nullptr); + bool GetThreadFromHash(nsCString &reference, nsIMsgThread **thread); + bool GetMsgHdrFromHash(nsCString &reference, nsIMsgDBHdr **hdr); + nsresult AddRefToHash(nsCString &reference, nsIMsgThread *thread); + nsresult AddMsgToHashTables(nsIMsgDBHdr *msgHdr, nsIMsgThread *thread); + nsresult RemoveRefFromHash(nsCString &reference); + nsresult RemoveMsgFromHashTables(nsIMsgDBHdr *msgHdr); + nsresult InitRefHash(); +}; + +#endif diff --git a/mailnews/base/src/nsMsgServiceProvider.cpp b/mailnews/base/src/nsMsgServiceProvider.cpp new file mode 100644 index 0000000000..224ae799df --- /dev/null +++ b/mailnews/base/src/nsMsgServiceProvider.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsMsgServiceProvider.h" +#include "nsIServiceManager.h" + +#include "nsRDFCID.h" +#include "nsIRDFService.h" +#include "nsIRDFRemoteDataSource.h" + +#include "nsIFileChannel.h" +#include "nsNetUtil.h" +#include "nsMsgBaseCID.h" + +#include "nsMailDirServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsISimpleEnumerator.h" +#include "nsIDirectoryEnumerator.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); +static NS_DEFINE_CID(kRDFCompositeDataSourceCID, NS_RDFCOMPOSITEDATASOURCE_CID); +static NS_DEFINE_CID(kRDFXMLDataSourceCID, NS_RDFXMLDATASOURCE_CID); + +nsMsgServiceProviderService::nsMsgServiceProviderService() +{} + +nsMsgServiceProviderService::~nsMsgServiceProviderService() +{} + +NS_IMPL_ISUPPORTS(nsMsgServiceProviderService, nsIRDFDataSource) + +nsresult +nsMsgServiceProviderService::Init() +{ + nsresult rv; + nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mInnerDataSource = do_CreateInstance(kRDFCompositeDataSourceCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + LoadISPFiles(); + return NS_OK; +} + +/** + * Looks for ISP configuration files in <.exe>\isp and any sub directories called isp + * located in the user's extensions directory. + */ +void nsMsgServiceProviderService::LoadISPFiles() +{ + nsresult rv; + nsCOMPtr<nsIProperties> dirSvc = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return; + + // Walk through the list of isp directories + nsCOMPtr<nsISimpleEnumerator> ispDirectories; + rv = dirSvc->Get(ISP_DIRECTORY_LIST, + NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(ispDirectories)); + if (NS_FAILED(rv)) + return; + + bool hasMore; + nsCOMPtr<nsIFile> ispDirectory; + while (NS_SUCCEEDED(ispDirectories->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> elem; + ispDirectories->GetNext(getter_AddRefs(elem)); + + ispDirectory = do_QueryInterface(elem); + if (ispDirectory) + LoadISPFilesFromDir(ispDirectory); + } +} + +void nsMsgServiceProviderService::LoadISPFilesFromDir(nsIFile* aDir) +{ + nsresult rv; + + bool check = false; + rv = aDir->Exists(&check); + if (NS_FAILED(rv) || !check) + return; + + rv = aDir->IsDirectory(&check); + if (NS_FAILED(rv) || !check) + return; + + nsCOMPtr<nsISimpleEnumerator> e; + rv = aDir->GetDirectoryEntries(getter_AddRefs(e)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIDirectoryEnumerator> files(do_QueryInterface(e)); + if (!files) + return; + + // we only care about the .rdf files in this directory + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(file))) && file) { + nsAutoString leafName; + file->GetLeafName(leafName); + if (!StringEndsWith(leafName, NS_LITERAL_STRING(".rdf"))) + continue; + + nsAutoCString urlSpec; + rv = NS_GetURLSpecFromFile(file, urlSpec); + if (NS_SUCCEEDED(rv)) + LoadDataSource(urlSpec.get()); + } +} + +nsresult +nsMsgServiceProviderService::LoadDataSource(const char *aURI) +{ + nsresult rv; + + nsCOMPtr<nsIRDFDataSource> ds = + do_CreateInstance(kRDFXMLDataSourceCID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFRemoteDataSource> remote = + do_QueryInterface(ds, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = remote->Init(aURI); + NS_ENSURE_SUCCESS(rv, rv); + // for now load synchronously (async seems to be busted) + rv = remote->Refresh(true); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed refresh?\n"); + + rv = mInnerDataSource->AddDataSource(ds); + + return rv; +} diff --git a/mailnews/base/src/nsMsgServiceProvider.h b/mailnews/base/src/nsMsgServiceProvider.h new file mode 100644 index 0000000000..6697ebf831 --- /dev/null +++ b/mailnews/base/src/nsMsgServiceProvider.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef __nsMsgServiceProvider_h +#define __nsMsgServiceProvider_h + +#include "nsIRDFDataSource.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIRDFCompositeDataSource.h" +#include "nsCOMPtr.h" + +class nsMsgServiceProviderService : public nsIRDFDataSource +{ + + public: + nsMsgServiceProviderService(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_FORWARD_NSIRDFDATASOURCE(mInnerDataSource->) + + private: + virtual ~nsMsgServiceProviderService(); + + nsCOMPtr<nsIRDFCompositeDataSource> mInnerDataSource; + nsresult LoadDataSource(const char *aURL); + + void LoadISPFilesFromDir(nsIFile* aDir); + void LoadISPFiles(); +}; +#endif diff --git a/mailnews/base/src/nsMsgSpecialViews.cpp b/mailnews/base/src/nsMsgSpecialViews.cpp new file mode 100644 index 0000000000..3943a5ca27 --- /dev/null +++ b/mailnews/base/src/nsMsgSpecialViews.cpp @@ -0,0 +1,179 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgSpecialViews.h" +#include "nsIMsgThread.h" +#include "nsMsgMessageFlags.h" + +nsMsgThreadsWithUnreadDBView::nsMsgThreadsWithUnreadDBView() +: m_totalUnwantedMessagesInView(0) +{ + +} + +nsMsgThreadsWithUnreadDBView::~nsMsgThreadsWithUnreadDBView() +{ +} + +NS_IMETHODIMP nsMsgThreadsWithUnreadDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowThreadsWithUnread; + return NS_OK; +} + +bool nsMsgThreadsWithUnreadDBView::WantsThisThread(nsIMsgThread *threadHdr) +{ + if (threadHdr) + { + uint32_t numNewChildren; + + threadHdr->GetNumUnreadChildren(&numNewChildren); + if (numNewChildren > 0) + return true; + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + m_totalUnwantedMessagesInView += numChildren; + } + return false; +} + +nsresult nsMsgThreadsWithUnreadDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) +{ + nsresult rv = NS_OK; + + nsCOMPtr <nsIMsgDBHdr> parentHdr; + uint32_t msgFlags; + msgHdr->GetFlags(&msgFlags); + GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(parentHdr)); + if (parentHdr && (ensureListed || !(msgFlags & nsMsgMessageFlags::Read))) + { + nsMsgKey key; + uint32_t numMsgsInThread; + rv = AddHdr(parentHdr); + threadHdr->GetNumChildren(&numMsgsInThread); + if (numMsgsInThread > 1) + { + parentHdr->GetMessageKey(&key); + nsMsgViewIndex viewIndex = FindViewIndex(key); + if (viewIndex != nsMsgViewIndex_None) + OrExtraFlag(viewIndex, nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN); + } + m_totalUnwantedMessagesInView -= numMsgsInThread; + } + else + m_totalUnwantedMessagesInView++; + return rv; +} + +NS_IMETHODIMP +nsMsgThreadsWithUnreadDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) +{ + nsMsgThreadsWithUnreadDBView* newMsgDBView = new nsMsgThreadsWithUnreadDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} + +NS_IMETHODIMP nsMsgThreadsWithUnreadDBView::GetNumMsgsInView(int32_t *aNumMsgs) +{ + nsresult rv = nsMsgDBView::GetNumMsgsInView(aNumMsgs); + NS_ENSURE_SUCCESS(rv, rv); + *aNumMsgs = *aNumMsgs - m_totalUnwantedMessagesInView; + return rv; +} + +nsMsgWatchedThreadsWithUnreadDBView::nsMsgWatchedThreadsWithUnreadDBView() +: m_totalUnwantedMessagesInView(0) +{ +} + +NS_IMETHODIMP nsMsgWatchedThreadsWithUnreadDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowWatchedThreadsWithUnread; + return NS_OK; +} + +bool nsMsgWatchedThreadsWithUnreadDBView::WantsThisThread(nsIMsgThread *threadHdr) +{ + if (threadHdr) + { + uint32_t numNewChildren; + uint32_t threadFlags; + + threadHdr->GetNumUnreadChildren(&numNewChildren); + threadHdr->GetFlags(&threadFlags); + if (numNewChildren > 0 && (threadFlags & nsMsgMessageFlags::Watched) != 0) + return true; + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + m_totalUnwantedMessagesInView += numChildren; + } + return false; +} + +nsresult nsMsgWatchedThreadsWithUnreadDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) +{ + nsresult rv = NS_OK; + uint32_t threadFlags; + uint32_t msgFlags; + msgHdr->GetFlags(&msgFlags); + threadHdr->GetFlags(&threadFlags); + if (threadFlags & nsMsgMessageFlags::Watched) + { + nsCOMPtr <nsIMsgDBHdr> parentHdr; + GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(parentHdr)); + if (parentHdr && (ensureListed || !(msgFlags & nsMsgMessageFlags::Read))) + { + uint32_t numChildren; + threadHdr->GetNumChildren(&numChildren); + rv = AddHdr(parentHdr); + if (numChildren > 1) + { + nsMsgKey key; + parentHdr->GetMessageKey(&key); + nsMsgViewIndex viewIndex = FindViewIndex(key); + if (viewIndex != nsMsgViewIndex_None) + OrExtraFlag(viewIndex, nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Watched); + } + m_totalUnwantedMessagesInView -= numChildren; + return rv; + } + } + m_totalUnwantedMessagesInView++; + return rv; +} + +NS_IMETHODIMP +nsMsgWatchedThreadsWithUnreadDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) +{ + nsMsgWatchedThreadsWithUnreadDBView* newMsgDBView = new nsMsgWatchedThreadsWithUnreadDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgWatchedThreadsWithUnreadDBView::GetNumMsgsInView(int32_t *aNumMsgs) +{ + nsresult rv = nsMsgDBView::GetNumMsgsInView(aNumMsgs); + NS_ENSURE_SUCCESS(rv, rv); + *aNumMsgs = *aNumMsgs - m_totalUnwantedMessagesInView; + return rv; +} diff --git a/mailnews/base/src/nsMsgSpecialViews.h b/mailnews/base/src/nsMsgSpecialViews.h new file mode 100644 index 0000000000..8f3757a351 --- /dev/null +++ b/mailnews/base/src/nsMsgSpecialViews.h @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +#ifndef _nsMsgSpecialViews_H_ +#define _nsMsgSpecialViews_H_ + +#include "mozilla/Attributes.h" +#include "nsMsgThreadedDBView.h" + +class nsMsgThreadsWithUnreadDBView : public nsMsgThreadedDBView +{ +public: + nsMsgThreadsWithUnreadDBView(); + virtual ~nsMsgThreadsWithUnreadDBView(); + virtual const char * GetViewName(void) override {return "ThreadsWithUnreadView"; } + NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCommandUpdater, nsIMsgDBView **_retval) override; + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override; + +virtual bool WantsThisThread(nsIMsgThread *threadHdr) override; +protected: + virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) override; + uint32_t m_totalUnwantedMessagesInView; +}; + +class nsMsgWatchedThreadsWithUnreadDBView : public nsMsgThreadedDBView +{ +public: + nsMsgWatchedThreadsWithUnreadDBView (); + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCommandUpdater, + nsIMsgDBView **_retval) override; + NS_IMETHOD GetNumMsgsInView(int32_t *aNumMsgs) override; + virtual const char *GetViewName(void) override { return "WatchedThreadsWithUnreadView"; } + virtual bool WantsThisThread(nsIMsgThread *threadHdr) override; +protected: + virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) override; + uint32_t m_totalUnwantedMessagesInView; + +}; +#ifdef DOING_CACHELESS_VIEW +// This view will initially be used for cacheless IMAP. +class nsMsgCachelessView : public nsMsgDBView +{ +public: + nsMsgCachelessView(); + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType); + virtual ~nsMsgCachelessView(); + virtual const char * GetViewName(void) {return "nsMsgCachelessView"; } + NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue viewType, int32_t *count); + nsresult SetViewSize(int32_t setSize); // Override + virtual nsresult AddNewMessages() ; + virtual nsresult AddHdr(nsIMsgDBHdr *msgHdr); + // for news, xover line, potentially, for IMAP, imap line... + virtual nsresult AddHdrFromServerLine(char *line, nsMsgKey *msgId) ; + virtual void SetInitialSortState(void); + virtual nsresult Init(uint32_t *pCount); +protected: + void ClearPendingIds(); + + nsIMsgFolder *m_folder; + nsMsgViewIndex m_curStartSeq; + nsMsgViewIndex m_curEndSeq; + bool m_sizeInitialized; +}; + +#endif /* DOING_CACHELESS_VIEW */ +#endif diff --git a/mailnews/base/src/nsMsgStatusFeedback.cpp b/mailnews/base/src/nsMsgStatusFeedback.cpp new file mode 100644 index 0000000000..7843886edd --- /dev/null +++ b/mailnews/base/src/nsMsgStatusFeedback.cpp @@ -0,0 +1,303 @@ +/* -*- 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 "msgCore.h" + +#include "nsIWebProgress.h" +#include "nsIXULBrowserWindow.h" +#include "nsMsgStatusFeedback.h" +#include "nsIDocument.h" +#include "nsIDOMElement.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIChannel.h" +#include "prinrval.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgWindow.h" +#include "nsMsgUtils.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Services.h" +#include "nsMsgUtils.h" + +#define MSGFEEDBACK_TIMER_INTERVAL 500 + +nsMsgStatusFeedback::nsMsgStatusFeedback() : + m_lastPercent(0), + m_lastProgressTime(0) +{ + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + + if (bundleService) + bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(mBundle)); + + m_msgLoadedAtom = MsgGetAtom("msgLoaded"); +} + +nsMsgStatusFeedback::~nsMsgStatusFeedback() +{ + mBundle = nullptr; +} + +NS_IMPL_ISUPPORTS(nsMsgStatusFeedback, nsIMsgStatusFeedback, + nsIProgressEventSink, nsIWebProgressListener, nsISupportsWeakReference) + +////////////////////////////////////////////////////////////////////////////////// +// nsMsgStatusFeedback::nsIWebProgressListener +////////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsMsgStatusFeedback::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + int32_t percentage = 0; + if (aMaxTotalProgress > 0) + { + percentage = (aCurTotalProgress * 100) / aMaxTotalProgress; + if (percentage) + ShowProgress(percentage); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgStatusFeedback::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aProgressStateFlags, + nsresult aStatus) +{ + nsresult rv; + + NS_ENSURE_TRUE(mBundle, NS_ERROR_NULL_POINTER); + if (aProgressStateFlags & STATE_IS_NETWORK) + { + if (aProgressStateFlags & STATE_START) + { + m_lastPercent = 0; + StartMeteors(); + nsString loadingDocument; + rv = mBundle->GetStringFromName(u"documentLoading", + getter_Copies(loadingDocument)); + if (NS_SUCCEEDED(rv)) + ShowStatusString(loadingDocument); + } + else if (aProgressStateFlags & STATE_STOP) + { + // if we are loading message for display purposes, this STATE_STOP notification is + // the only notification we get when layout is actually done rendering the message. We need + // to fire the appropriate msgHdrSink notification in this particular case. + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (channel) + { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl (do_QueryInterface(uri)); + if (mailnewsUrl) + { + // get the url type + bool messageDisplayUrl; + mailnewsUrl->IsUrlType(nsIMsgMailNewsUrl::eDisplay, &messageDisplayUrl); + + if (messageDisplayUrl) + { + // get the header sink + nsCOMPtr<nsIMsgWindow> msgWindow; + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr<nsIMsgHeaderSink> hdrSink; + msgWindow->GetMsgHeaderSink(getter_AddRefs(hdrSink)); + if (hdrSink) + hdrSink->OnEndMsgDownload(mailnewsUrl); + } + // get the folder and notify that the msg has been loaded. We're + // using NotifyPropertyFlagChanged. To be completely consistent, + // we'd send a similar notification that the old message was + // unloaded. + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgFolder> msgFolder; + mailnewsUrl->GetFolder(getter_AddRefs(msgFolder)); + nsCOMPtr <nsIMsgMessageUrl> msgUrl = do_QueryInterface(mailnewsUrl); + if (msgUrl) + { + // not sending this notification is not a fatal error... + (void) msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); + if (msgFolder && msgHdr) + msgFolder->NotifyPropertyFlagChanged(msgHdr, m_msgLoadedAtom, 0, 1); + } + } + } + } + StopMeteors(); + nsString documentDone; + rv = mBundle->GetStringFromName(u"documentDone", + getter_Copies(documentDone)); + if (NS_SUCCEEDED(rv)) + ShowStatusString(documentDone); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgStatusFeedback::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgStatusFeedback::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgStatusFeedback::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t state) +{ + return NS_OK; +} + + +NS_IMETHODIMP +nsMsgStatusFeedback::ShowStatusString(const nsAString& aStatus) +{ + nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak)); + if (jsStatusFeedback) + jsStatusFeedback->ShowStatusString(aStatus); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgStatusFeedback::SetStatusString(const nsAString& aStatus) +{ + nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak)); + if (jsStatusFeedback) + jsStatusFeedback->SetStatusString(aStatus); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgStatusFeedback::ShowProgress(int32_t aPercentage) +{ + // if the percentage hasn't changed...OR if we are going from 0 to 100% in one step + // then don't bother....just fall out.... + if (aPercentage == m_lastPercent || (m_lastPercent == 0 && aPercentage >= 100)) + return NS_OK; + + m_lastPercent = aPercentage; + + int64_t nowMS = 0; + if (aPercentage < 100) // always need to do 100% + { + nowMS = PR_IntervalToMilliseconds(PR_IntervalNow()); + if (nowMS < m_lastProgressTime + 250) + return NS_OK; + } + + m_lastProgressTime = nowMS; + nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak)); + if (jsStatusFeedback) + jsStatusFeedback->ShowProgress(aPercentage); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgStatusFeedback::StartMeteors() +{ + nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak)); + if (jsStatusFeedback) + jsStatusFeedback->StartMeteors(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgStatusFeedback::StopMeteors() +{ + nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(do_QueryReferent(mJSStatusFeedbackWeak)); + if (jsStatusFeedback) + jsStatusFeedback->StopMeteors(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgStatusFeedback::SetWrappedStatusFeedback(nsIMsgStatusFeedback * aJSStatusFeedback) +{ + NS_ENSURE_ARG_POINTER(aJSStatusFeedback); + mJSStatusFeedbackWeak = do_GetWeakReference(aJSStatusFeedback); + return NS_OK; +} + +NS_IMETHODIMP nsMsgStatusFeedback::OnProgress(nsIRequest *request, nsISupports* ctxt, + int64_t aProgress, int64_t aProgressMax) +{ + // XXX: What should the nsIWebProgress be? + // XXX: this truncates 64-bit to 32-bit + return OnProgressChange(nullptr, request, int32_t(aProgress), int32_t(aProgressMax), + int32_t(aProgress) /* current total progress */, int32_t(aProgressMax) /* max total progress */); +} + +NS_IMETHODIMP nsMsgStatusFeedback::OnStatus(nsIRequest *request, nsISupports* ctxt, + nsresult aStatus, const char16_t* aStatusArg) +{ + nsresult rv; + nsCOMPtr<nsIURI> uri; + nsString accountName; + // fetching account name from nsIRequest + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(uri)); + if (url) + { + nsCOMPtr<nsIMsgIncomingServer> server; + url->GetServer(getter_AddRefs(server)); + if (server) + server->GetPrettyName(accountName); + } + + // forming the status message + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, NS_ERROR_UNEXPECTED); + nsString str; + rv = sbs->FormatStatusMessage(aStatus, aStatusArg, getter_Copies(str)); + NS_ENSURE_SUCCESS(rv, rv); + + // prefixing the account name to the status message if status message isn't blank + // and doesn't already contain the account name. + nsString statusMessage; + if (!str.IsEmpty() && str.Find(accountName) == kNotFound) + { + nsCOMPtr<nsIStringBundle> bundle; + rv = sbs->CreateBundle(MSGS_URL, getter_AddRefs(bundle)); + const char16_t *params[] = { accountName.get(), + str.get() }; + rv = bundle->FormatStringFromName( + u"statusMessage", + params, 2, getter_Copies(statusMessage)); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + statusMessage.Assign(str); + } + return ShowStatusString(statusMessage); +} diff --git a/mailnews/base/src/nsMsgStatusFeedback.h b/mailnews/base/src/nsMsgStatusFeedback.h new file mode 100644 index 0000000000..b7565377a8 --- /dev/null +++ b/mailnews/base/src/nsMsgStatusFeedback.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef _nsMsgStatusFeedback_h +#define _nsMsgStatusFeedback_h + +#include "nsIWebProgressListener.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIProgressEventSink.h" +#include "nsIStringBundle.h" +#include "nsWeakReference.h" +#include "nsIAtom.h" + +class nsMsgStatusFeedback : public nsIMsgStatusFeedback, + public nsIProgressEventSink, + public nsIWebProgressListener, + public nsSupportsWeakReference +{ +public: + nsMsgStatusFeedback(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGSTATUSFEEDBACK + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPROGRESSEVENTSINK + +protected: + virtual ~nsMsgStatusFeedback(); + + bool m_meteorsSpinning; + int32_t m_lastPercent; + int64_t m_lastProgressTime; + + void BeginObserving(); + void EndObserving(); + + // the JS status feedback implementation object...eventually this object + // will replace this very C++ class you are looking at. + nsWeakPtr mJSStatusFeedbackWeak; + + nsCOMPtr<nsIStringBundle> mBundle; + nsCOMPtr <nsIAtom> m_msgLoadedAtom; +}; + +#endif // _nsMsgStatusFeedback_h diff --git a/mailnews/base/src/nsMsgTagService.cpp b/mailnews/base/src/nsMsgTagService.cpp new file mode 100644 index 0000000000..75480c21a9 --- /dev/null +++ b/mailnews/base/src/nsMsgTagService.cpp @@ -0,0 +1,560 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgTagService.h" +#include "nsMsgBaseCID.h" +#include "nsIPrefService.h" +#include "nsISupportsPrimitives.h" +#include "nsMsgI18N.h" +#include "nsIPrefLocalizedString.h" +#include "nsMsgDBView.h" // for labels migration +#include "nsQuickSort.h" +#include "nsMsgUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" + +#define STRLEN(s) (sizeof(s) - 1) + +#define TAG_PREF_VERSION "version" +#define TAG_PREF_SUFFIX_TAG ".tag" +#define TAG_PREF_SUFFIX_COLOR ".color" +#define TAG_PREF_SUFFIX_ORDINAL ".ordinal" +#define TAG_CMP_LESSER -1 +#define TAG_CMP_EQUAL 0 +#define TAG_CMP_GREATER 1 + +static bool gMigratingKeys = false; + +// comparison functions for nsQuickSort +static int +CompareMsgTagKeys(const void* aTagPref1, const void* aTagPref2, void* aData) +{ + return strcmp(*static_cast<const char* const*>(aTagPref1), + *static_cast<const char* const*>(aTagPref2)); +} + +static int +CompareMsgTags(const void* aTagPref1, const void* aTagPref2, void* aData) +{ + // Sort nsMsgTag objects by ascending order, using their ordinal or key. + // The "smallest" value will be first in the sorted array, + // thus being the most important element. + nsMsgTag *element1 = *(nsMsgTag**) aTagPref1; + nsMsgTag *element2 = *(nsMsgTag**) aTagPref2; + + // if we have only one element, it wins + if (!element1 && !element2) + return TAG_CMP_EQUAL; + if (!element2) + return TAG_CMP_LESSER; + if (!element1) + return TAG_CMP_GREATER; + + // only use the key if the ordinal is not defined or empty + nsAutoCString value1, value2; + element1->GetOrdinal(value1); + if (value1.IsEmpty()) + element1->GetKey(value1); + element2->GetOrdinal(value2); + if (value2.IsEmpty()) + element2->GetKey(value2); + + return strcmp(value1.get(), value2.get()); +} + + +// +// nsMsgTag +// +NS_IMPL_ISUPPORTS(nsMsgTag, nsIMsgTag) + +nsMsgTag::nsMsgTag(const nsACString &aKey, + const nsAString &aTag, + const nsACString &aColor, + const nsACString &aOrdinal) +: mTag(aTag), + mKey(aKey), + mColor(aColor), + mOrdinal(aOrdinal) +{ +} + +nsMsgTag::~nsMsgTag() +{ +} + +/* readonly attribute ACString key; */ +NS_IMETHODIMP nsMsgTag::GetKey(nsACString & aKey) +{ + aKey = mKey; + return NS_OK; +} + +/* readonly attribute AString tag; */ +NS_IMETHODIMP nsMsgTag::GetTag(nsAString & aTag) +{ + aTag = mTag; + return NS_OK; +} + +/* readonly attribute ACString color; */ +NS_IMETHODIMP nsMsgTag::GetColor(nsACString & aColor) +{ + aColor = mColor; + return NS_OK; +} + +/* readonly attribute ACString ordinal; */ +NS_IMETHODIMP nsMsgTag::GetOrdinal(nsACString & aOrdinal) +{ + aOrdinal = mOrdinal; + return NS_OK; +} + + +// +// nsMsgTagService +// +NS_IMPL_ISUPPORTS(nsMsgTagService, nsIMsgTagService) + +nsMsgTagService::nsMsgTagService() +{ + m_tagPrefBranch = nullptr; + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefService) + prefService->GetBranch("mailnews.tags.", getter_AddRefs(m_tagPrefBranch)); + // need to figure out how to migrate the tags only once. + MigrateLabelsToTags(); + RefreshKeyCache(); +} + +nsMsgTagService::~nsMsgTagService() +{ + /* destructor code */ +} + +/* wstring getTagForKey (in string key); */ +NS_IMETHODIMP nsMsgTagService::GetTagForKey(const nsACString &key, nsAString &_retval) +{ + nsAutoCString prefName(key); + if (!gMigratingKeys) + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG); + return GetUnicharPref(prefName.get(), _retval); +} + +/* void setTagForKey (in string key); */ +NS_IMETHODIMP nsMsgTagService::SetTagForKey(const nsACString &key, const nsAString &tag ) +{ + nsAutoCString prefName(key); + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG); + return SetUnicharPref(prefName.get(), tag); +} + +/* void getKeyForTag (in wstring tag); */ +NS_IMETHODIMP nsMsgTagService::GetKeyForTag(const nsAString &aTag, nsACString &aKey) +{ + uint32_t count; + char **prefList; + nsresult rv = m_tagPrefBranch->GetChildList("", &count, &prefList); + NS_ENSURE_SUCCESS(rv, rv); + // traverse the list, and look for a pref with the desired tag value. + for (uint32_t i = count; i--;) + { + // We are returned the tag prefs in the form "<key>.<tag_data_type>", but + // since we only want the tags, just check that the string ends with "tag". + nsDependentCString prefName(prefList[i]); + if (StringEndsWith(prefName, NS_LITERAL_CSTRING(TAG_PREF_SUFFIX_TAG))) + { + nsAutoString curTag; + GetUnicharPref(prefList[i], curTag); + if (aTag.Equals(curTag)) + { + aKey = Substring(prefName, 0, prefName.Length() - STRLEN(TAG_PREF_SUFFIX_TAG)); + break; + } + } + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, prefList); + ToLowerCase(aKey); + return NS_OK; +} + +/* ACString getTopKey (in ACString keylist); */ +NS_IMETHODIMP nsMsgTagService::GetTopKey(const nsACString & keyList, nsACString & _retval) +{ + _retval.Truncate(); + // find the most important key + nsTArray<nsCString> keyArray; + ParseString(keyList, ' ', keyArray); + uint32_t keyCount = keyArray.Length(); + nsCString *topKey = nullptr, *key, topOrdinal, ordinal; + for (uint32_t i = 0; i < keyCount; ++i) + { + key = &keyArray[i]; + if (key->IsEmpty()) + continue; + + // ignore unknown keywords + nsAutoString tagValue; + nsresult rv = GetTagForKey(*key, tagValue); + if (NS_FAILED(rv) || tagValue.IsEmpty()) + continue; + + // new top key, judged by ordinal order? + rv = GetOrdinalForKey(*key, ordinal); + if (NS_FAILED(rv) || ordinal.IsEmpty()) + ordinal = *key; + if ((ordinal < topOrdinal) || topOrdinal.IsEmpty()) + { + topOrdinal = ordinal; + topKey = key; // copy actual result key only once - later + } + } + // return the most important key - if any + if (topKey) + _retval = *topKey; + return NS_OK; +} + +/* void addTagForKey (in string key, in wstring tag, in string color, in string ordinal); */ +NS_IMETHODIMP nsMsgTagService::AddTagForKey(const nsACString &key, + const nsAString &tag, + const nsACString &color, + const nsACString &ordinal) +{ + nsAutoCString prefName(key); + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG); + nsresult rv = SetUnicharPref(prefName.get(), tag); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetColorForKey(key, color); + NS_ENSURE_SUCCESS(rv, rv); + rv = RefreshKeyCache(); + NS_ENSURE_SUCCESS(rv, rv); + return SetOrdinalForKey(key, ordinal); +} + +/* void addTag (in wstring tag, in long color); */ +NS_IMETHODIMP nsMsgTagService::AddTag(const nsAString &tag, + const nsACString &color, + const nsACString &ordinal) +{ + // figure out key from tag. Apply transformation stripping out + // illegal characters like <SP> and then convert to imap mod utf7. + // Then, check if we have a tag with that key yet, and if so, + // make it unique by appending A, AA, etc. + // Should we use an iterator? + nsAutoString transformedTag(tag); + MsgReplaceChar(transformedTag, " ()/{%*<>\\\"", '_'); + nsAutoCString key; + CopyUTF16toMUTF7(transformedTag, key); + // We have an imap server that converts keys to upper case so we're going + // to normalize all keys to lower case (upper case looks ugly in prefs.js) + ToLowerCase(key); + nsAutoCString prefName(key); + while (true) + { + nsAutoString tagValue; + nsresult rv = GetTagForKey(prefName, tagValue); + if (NS_FAILED(rv) || tagValue.IsEmpty() || tagValue.Equals(tag)) + return AddTagForKey(prefName, tag, color, ordinal); + prefName.Append('A'); + } + NS_ASSERTION(false, "can't get here"); + return NS_ERROR_FAILURE; +} + +/* long getColorForKey (in string key); */ +NS_IMETHODIMP nsMsgTagService::GetColorForKey(const nsACString &key, nsACString &_retval) +{ + nsAutoCString prefName(key); + if (!gMigratingKeys) + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR); + nsCString color; + nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(color)); + if (NS_SUCCEEDED(rv)) + _retval = color; + return NS_OK; +} + +/* void setColorForKey (in ACString key, in ACString color); */ +NS_IMETHODIMP nsMsgTagService::SetColorForKey(const nsACString & key, const nsACString & color) +{ + nsAutoCString prefName(key); + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR); + if (color.IsEmpty()) + { + m_tagPrefBranch->ClearUserPref(prefName.get()); + return NS_OK; + } + return m_tagPrefBranch->SetCharPref(prefName.get(), nsCString(color).get()); +} + +/* ACString getOrdinalForKey (in ACString key); */ +NS_IMETHODIMP nsMsgTagService::GetOrdinalForKey(const nsACString & key, nsACString & _retval) +{ + nsAutoCString prefName(key); + if (!gMigratingKeys) + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL); + nsCString ordinal; + nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), getter_Copies(ordinal)); + _retval = ordinal; + return rv; +} + +/* void setOrdinalForKey (in ACString key, in ACString ordinal); */ +NS_IMETHODIMP nsMsgTagService::SetOrdinalForKey(const nsACString & key, const nsACString & ordinal) +{ + nsAutoCString prefName(key); + ToLowerCase(prefName); + prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL); + if (ordinal.IsEmpty()) + { + m_tagPrefBranch->ClearUserPref(prefName.get()); + return NS_OK; + } + return m_tagPrefBranch->SetCharPref(prefName.get(), nsCString(ordinal).get()); +} + +/* void deleteTag (in wstring tag); */ +NS_IMETHODIMP nsMsgTagService::DeleteKey(const nsACString &key) +{ + // clear the associated prefs + nsAutoCString prefName(key); + if (!gMigratingKeys) + ToLowerCase(prefName); + nsresult rv = m_tagPrefBranch->DeleteBranch(prefName.get()); + NS_ENSURE_SUCCESS(rv, rv); + return RefreshKeyCache(); +} + +/* void getAllTags (out unsigned long count, [array, size_is (count), retval] out nsIMsgTag tagArray); */ +NS_IMETHODIMP nsMsgTagService::GetAllTags(uint32_t *aCount, nsIMsgTag ***aTagArray) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aTagArray); + + // preset harmless default values + *aCount = 0; + *aTagArray = nullptr; + + // get the actual tag definitions + nsresult rv; + uint32_t prefCount; + char **prefList; + rv = m_tagPrefBranch->GetChildList("", &prefCount, &prefList); + NS_ENSURE_SUCCESS(rv, rv); + // sort them by key for ease of processing + NS_QuickSort(prefList, prefCount, sizeof(char*), CompareMsgTagKeys, nullptr); + + // build an array of nsIMsgTag elements from the orderered list + // it's at max the same size as the preflist, but usually only about half + nsIMsgTag** tagArray = (nsIMsgTag**) NS_Alloc(sizeof(nsIMsgTag*) * prefCount); + + if (!tagArray) + { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); + return NS_ERROR_OUT_OF_MEMORY; + } + uint32_t currentTagIndex = 0; + nsMsgTag *newMsgTag; + nsString tag; + nsCString lastKey, color, ordinal; + for (uint32_t i = prefCount; i--;) + { + // extract just the key from <key>.<info=tag|color|ordinal> + char *info = strrchr(prefList[i], '.'); + if (info) + { + nsAutoCString key(Substring(prefList[i], info)); + if (key != lastKey) + { + if (!key.IsEmpty()) + { + // .tag MUST exist (but may be empty) + rv = GetTagForKey(key, tag); + if (NS_SUCCEEDED(rv)) + { + // .color MAY exist + color.Truncate(); + GetColorForKey(key, color); + // .ordinal MAY exist + rv = GetOrdinalForKey(key, ordinal); + if (NS_FAILED(rv)) + ordinal.Truncate(); + // store the tag info in our array + newMsgTag = new nsMsgTag(key, tag, color, ordinal); + if (!newMsgTag) + { + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(currentTagIndex, tagArray); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(tagArray[currentTagIndex++] = newMsgTag); + } + } + lastKey = key; + } + } + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefList); + + // sort the non-null entries by ordinal + NS_QuickSort(tagArray, currentTagIndex, sizeof(nsMsgTag*), CompareMsgTags, + nullptr); + + // All done, now return the values (the idl's size_is(count) parameter + // ensures that the array is cut accordingly). + *aCount = currentTagIndex; + *aTagArray = tagArray; + + return NS_OK; +} + +nsresult nsMsgTagService::SetUnicharPref(const char *prefName, + const nsAString &val) +{ + nsresult rv = NS_OK; + if (!val.IsEmpty()) + { + nsCOMPtr<nsISupportsString> supportsString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (supportsString) + { + supportsString->SetData(val); + rv = m_tagPrefBranch->SetComplexValue(prefName, + NS_GET_IID(nsISupportsString), + supportsString); + } + } + else + { + m_tagPrefBranch->ClearUserPref(prefName); + } + return rv; +} + +nsresult nsMsgTagService::GetUnicharPref(const char *prefName, + nsAString &prefValue) +{ + nsresult rv; + nsCOMPtr<nsISupportsString> supportsString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (supportsString) + { + rv = m_tagPrefBranch->GetComplexValue(prefName, + NS_GET_IID(nsISupportsString), + getter_AddRefs(supportsString)); + if (supportsString) + rv = supportsString->GetData(prefValue); + else + prefValue.Truncate(); + } + return rv; +} + + +nsresult nsMsgTagService::MigrateLabelsToTags() +{ + nsCString prefString; + + int32_t prefVersion = 0; + nsresult rv = m_tagPrefBranch->GetIntPref(TAG_PREF_VERSION, &prefVersion); + if (NS_SUCCEEDED(rv) && prefVersion > 1) + return rv; + else if (prefVersion == 1) + { + gMigratingKeys = true; + // need to convert the keys to lower case + nsIMsgTag **tagArray; + uint32_t numTags; + GetAllTags(&numTags, &tagArray); + for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++) + { + nsAutoCString key, color, ordinal; + nsAutoString tagStr; + nsIMsgTag *tag = tagArray[tagIndex]; + tag->GetKey(key); + tag->GetTag(tagStr); + tag->GetOrdinal(ordinal); + tag->GetColor(color); + DeleteKey(key); + ToLowerCase(key); + AddTagForKey(key, tagStr, color, ordinal); + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numTags, tagArray); + gMigratingKeys = false; + } + else + { + nsCOMPtr<nsIPrefBranch> prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID)); + nsCOMPtr<nsIPrefLocalizedString> pls; + nsString ucsval; + nsAutoCString labelKey("$label1"); + for(int32_t i = 0; i < PREF_LABELS_MAX; ) + { + prefString.Assign(PREF_LABELS_DESCRIPTION); + prefString.AppendInt(i + 1); + rv = prefRoot->GetComplexValue(prefString.get(), + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(pls)); + NS_ENSURE_SUCCESS(rv, rv); + pls->ToString(getter_Copies(ucsval)); + + prefString.Assign(PREF_LABELS_COLOR); + prefString.AppendInt(i + 1); + nsCString csval; + rv = prefRoot->GetCharPref(prefString.get(), getter_Copies(csval)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddTagForKey(labelKey, ucsval, csval, EmptyCString()); + NS_ENSURE_SUCCESS(rv, rv); + labelKey.SetCharAt(++i + '1', 6); + } + } + m_tagPrefBranch->SetIntPref(TAG_PREF_VERSION, 2); + return rv; +} + +NS_IMETHODIMP nsMsgTagService::IsValidKey(const nsACString &aKey, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = m_keys.Contains(aKey); + return NS_OK; +} + +// refresh the local tag key array m_keys from preferences +nsresult nsMsgTagService::RefreshKeyCache() +{ + nsIMsgTag **tagArray; + uint32_t numTags; + nsresult rv = GetAllTags(&numTags, &tagArray); + NS_ENSURE_SUCCESS(rv, rv); + m_keys.Clear(); + + for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++) + { + nsIMsgTag *tag = tagArray[tagIndex]; + if (!tag) { + rv = NS_ERROR_FAILURE; + break; + } + nsAutoCString key; + tag->GetKey(key); + if (!m_keys.InsertElementAt(tagIndex, key)) { + rv = NS_ERROR_FAILURE; + break; + } + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numTags, tagArray); + return rv; +} diff --git a/mailnews/base/src/nsMsgTagService.h b/mailnews/base/src/nsMsgTagService.h new file mode 100644 index 0000000000..d31a814784 --- /dev/null +++ b/mailnews/base/src/nsMsgTagService.h @@ -0,0 +1,57 @@ +/* -*- 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/. */ + +#ifndef nsMsgTagService_h__ +#define nsMsgTagService_h__ + +#include "nsIMsgTagService.h" +#include "nsIPrefBranch.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class nsMsgTag final : public nsIMsgTag +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGTAG + + nsMsgTag(const nsACString &aKey, + const nsAString &aTag, + const nsACString &aColor, + const nsACString &aOrdinal); + +protected: + ~nsMsgTag(); + + nsString mTag; + nsCString mKey, mColor, mOrdinal; +}; + + +class nsMsgTagService final : public nsIMsgTagService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGTAGSERVICE + + nsMsgTagService(); + +private: + ~nsMsgTagService(); + +protected: + nsresult SetUnicharPref(const char *prefName, + const nsAString &prefValue); + nsresult GetUnicharPref(const char *prefName, + nsAString &prefValue); + nsresult MigrateLabelsToTags(); + nsresult RefreshKeyCache(); + + nsCOMPtr<nsIPrefBranch> m_tagPrefBranch; + nsTArray<nsCString> m_keys; +}; + +#endif diff --git a/mailnews/base/src/nsMsgThreadedDBView.cpp b/mailnews/base/src/nsMsgThreadedDBView.cpp new file mode 100644 index 0000000000..0a2a18fbc7 --- /dev/null +++ b/mailnews/base/src/nsMsgThreadedDBView.cpp @@ -0,0 +1,983 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgThreadedDBView.h" +#include "nsIMsgHdr.h" +#include "nsIMsgThread.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgSearchSession.h" +#include "nsMsgMessageFlags.h" + +#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25 // Allocate this more to avoid reallocation on new mail. +#define MSGHDR_CACHE_MAX_SIZE 8192 // Max msghdr cache entries. +#define MSGHDR_CACHE_DEFAULT_SIZE 100 + +nsMsgThreadedDBView::nsMsgThreadedDBView() +{ + /* member initializers and constructor code */ + m_havePrevView = false; +} + +nsMsgThreadedDBView::~nsMsgThreadedDBView() +{ + /* destructor code */ +} + +NS_IMETHODIMP nsMsgThreadedDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) +{ + nsresult rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_db) + return NS_ERROR_NULL_POINTER; + // Preset msg hdr cache size for performance reason. + int32_t totalMessages, unreadMessages; + nsCOMPtr <nsIDBFolderInfo> dbFolderInfo; + PersistFolderInfo(getter_AddRefs(dbFolderInfo)); + NS_ENSURE_SUCCESS(rv, rv); + // save off sort type and order, view type and flags + dbFolderInfo->GetNumUnreadMessages(&unreadMessages); + dbFolderInfo->GetNumMessages(&totalMessages); + if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) + { + // Set unread msg size + extra entries to avoid reallocation on new mail. + totalMessages = (uint32_t)unreadMessages+MSGHDR_CACHE_LOOK_AHEAD_SIZE; + } + else + { + if (totalMessages > MSGHDR_CACHE_MAX_SIZE) + totalMessages = MSGHDR_CACHE_MAX_SIZE; // use max default + else if (totalMessages > 0) + totalMessages += MSGHDR_CACHE_LOOK_AHEAD_SIZE;// allocate extra entries to avoid reallocation on new mail. + } + // if total messages is 0, then we probably don't have any idea how many headers are in the db + // so we have no business setting the cache size. + if (totalMessages > 0) + m_db->SetMsgHdrCacheSize((uint32_t)totalMessages); + + if (pCount) + *pCount = 0; + rv = InitThreadedView(pCount); + + // this is a hack, but we're trying to find a way to correct + // incorrect total and unread msg counts w/o paying a big + // performance penalty. So, if we're not threaded, just add + // up the total and unread messages in the view and see if that + // matches what the db totals say. Except ignored threads are + // going to throw us off...hmm. Unless we just look at the + // unread counts which is what mostly tweaks people anyway... + int32_t unreadMsgsInView = 0; + if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + for (uint32_t i = m_flags.Length(); i > 0; ) + { + if (!(m_flags[--i] & nsMsgMessageFlags::Read)) + ++unreadMsgsInView; + } + + if (unreadMessages != unreadMsgsInView) + m_db->SyncCounts(); + } + m_db->SetMsgHdrCacheSize(MSGHDR_CACHE_DEFAULT_SIZE); + + return rv; +} + +NS_IMETHODIMP nsMsgThreadedDBView::Close() +{ + return nsMsgDBView::Close(); +} + +nsresult nsMsgThreadedDBView::InitThreadedView(int32_t *pCount) +{ + nsresult rv; + + m_keys.Clear(); + m_flags.Clear(); + m_levels.Clear(); + m_prevKeys.Clear(); + m_prevFlags.Clear(); + m_prevLevels.Clear(); + m_havePrevView = false; + nsresult getSortrv = NS_OK; // ### TODO m_db->GetSortInfo(&sortType, &sortOrder); + + // list all the ids into m_keys. + nsMsgKey startMsg = 0; + do + { + const int32_t kIdChunkSize = 400; + int32_t numListed = 0; + nsMsgKey idArray[kIdChunkSize]; + int32_t flagArray[kIdChunkSize]; + char levelArray[kIdChunkSize]; + + rv = ListThreadIds(&startMsg, (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) != 0, idArray, flagArray, + levelArray, kIdChunkSize, &numListed, nullptr); + if (NS_SUCCEEDED(rv)) + { + int32_t numAdded = AddKeys(idArray, flagArray, levelArray, m_sortType, numListed); + if (pCount) + *pCount += numAdded; + } + + } while (NS_SUCCEEDED(rv) && startMsg != nsMsgKey_None); + + if (NS_SUCCEEDED(getSortrv)) + { + rv = InitSort(m_sortType, m_sortOrder); + SaveSortInfo(m_sortType, m_sortOrder); + + } + return rv; +} + +nsresult nsMsgThreadedDBView::SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) +{ + NS_PRECONDITION(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay, "trying to sort unthreaded threads"); + + uint32_t numThreads = 0; + // the idea here is that copy the current view, then build up an m_keys and m_flags array of just the top level + // messages in the view, and then call nsMsgDBView::Sort(sortType, sortOrder). + // Then, we expand the threads in the result array that were expanded in the original view (perhaps by copying + // from the original view, but more likely just be calling expand). + for (uint32_t i = 0; i < m_keys.Length(); i++) + { + if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD) + { + if (numThreads < i) + { + m_keys[numThreads] = m_keys[i]; + m_flags[numThreads] = m_flags[i]; + } + m_levels[numThreads] = 0; + numThreads++; + } + } + m_keys.SetLength(numThreads); + m_flags.SetLength(numThreads); + m_levels.SetLength(numThreads); + //m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay; + m_sortType = nsMsgViewSortType::byNone; // sort from scratch + nsMsgDBView::Sort(sortType, sortOrder); + m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay; + SetSuppressChangeNotifications(true); + // Loop through the original array, for each thread that's expanded, find it in the new array + // and expand the thread. We have to update MSG_VIEW_FLAG_HAS_CHILDREN because + // we may be going from a flat sort, which doesn't maintain that flag, + // to a threaded sort, which requires that flag. + for (uint32_t j = 0; j < m_keys.Length(); j++) + { + uint32_t flags = m_flags[j]; + if ((flags & (MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided)) == MSG_VIEW_FLAG_HASCHILDREN) + { + uint32_t numExpanded; + m_flags[j] = flags | nsMsgMessageFlags::Elided; + ExpandByIndex(j, &numExpanded); + j += numExpanded; + if (numExpanded > 0) + m_flags[j - numExpanded] = flags | MSG_VIEW_FLAG_HASCHILDREN; + } + else if (flags & MSG_VIEW_FLAG_ISTHREAD && ! (flags & MSG_VIEW_FLAG_HASCHILDREN)) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsCOMPtr <nsIMsgThread> pThread; + m_db->GetMsgHdrForKey(m_keys[j], getter_AddRefs(msgHdr)); + if (msgHdr) + { + m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread)); + if (pThread) + { + uint32_t numChildren; + pThread->GetNumChildren(&numChildren); + if (numChildren > 1) + m_flags[j] = flags | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided; + } + } + } + } + SetSuppressChangeNotifications(false); + + return NS_OK; +} + +int32_t nsMsgThreadedDBView::AddKeys(nsMsgKey *pKeys, int32_t *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, int32_t numKeysToAdd) + +{ + int32_t numAdded = 0; + // Allocate enough space first to avoid memory allocation/deallocation. + m_keys.SetCapacity(m_keys.Length() + numKeysToAdd); + m_flags.SetCapacity(m_flags.Length() + numKeysToAdd); + m_levels.SetCapacity(m_levels.Length() + numKeysToAdd); + for (int32_t i = 0; i < numKeysToAdd; i++) + { + int32_t threadFlag = pFlags[i]; + int32_t flag = threadFlag; + + // skip ignored threads. + if ((threadFlag & nsMsgMessageFlags::Ignored) && !(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) + continue; + + // skip ignored subthreads + nsCOMPtr <nsIMsgDBHdr> msgHdr; + m_db->GetMsgHdrForKey(pKeys[i], getter_AddRefs(msgHdr)); + if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) + { + bool killed; + msgHdr->GetIsKilled(&killed); + if (killed) + continue; + } + + // by default, make threads collapsed, unless we're in only viewing new msgs + + if (flag & MSG_VIEW_FLAG_HASCHILDREN) + flag |= nsMsgMessageFlags::Elided; + // should this be persistent? Doesn't seem to need to be. + flag |= MSG_VIEW_FLAG_ISTHREAD; + m_keys.AppendElement(pKeys[i]); + m_flags.AppendElement(flag); + m_levels.AppendElement(pLevels[i]); + numAdded++; + // we expand as we build the view, which allows us to insert at the end of the key array, + // instead of the middle, and is much faster. + if ((!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) || m_viewFlags & nsMsgViewFlagsType::kExpandAll) && flag & nsMsgMessageFlags::Elided) + ExpandByIndex(m_keys.Length() - 1, NULL); + } + return numAdded; +} + +NS_IMETHODIMP nsMsgThreadedDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) +{ + nsresult rv; + + int32_t rowCountBeforeSort = GetSize(); + + if (!rowCountBeforeSort) + { + // still need to setup our flags even when no articles - bug 98183. + m_sortType = sortType; + if (sortType == nsMsgViewSortType::byThread && ! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + SetViewFlags(m_viewFlags | nsMsgViewFlagsType::kThreadedDisplay); + SaveSortInfo(sortType, sortOrder); + return NS_OK; + } + + if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered()) + return NS_OK; + + // sort threads by sort order + bool sortThreads = m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort); + + // if sort type is by thread, and we're already threaded, change sort type to byId + if (sortType == nsMsgViewSortType::byThread && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0) + sortType = nsMsgViewSortType::byId; + + nsMsgKey preservedKey; + AutoTArray<nsMsgKey, 1> preservedSelection; + SaveAndClearSelection(&preservedKey, preservedSelection); + // if the client wants us to forget our cached id arrays, they + // should build a new view. If this isn't good enough, we + // need a method to do that. + if (sortType != m_sortType || !m_sortValid || sortThreads) + { + SaveSortInfo(sortType, sortOrder); + if (sortType == nsMsgViewSortType::byThread) + { + m_sortType = sortType; + m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay; + m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort; + if ( m_havePrevView) + { + // restore saved id array and flags array + m_keys = m_prevKeys; + m_flags = m_prevFlags; + m_levels = m_prevLevels; + m_sortValid = true; + + // the sort may have changed the number of rows + // before we restore the selection, tell the tree + // do this before we call restore selection + // this is safe when there is no selection. + rv = AdjustRowCount(rowCountBeforeSort, GetSize()); + + RestoreSelection(preservedKey, preservedSelection); + if (mTree) mTree->Invalidate(); + return NS_OK; + } + else + { + // set sort info in anticipation of what Init will do. + InitThreadedView(nullptr); // build up thread list. + if (sortOrder != nsMsgViewSortOrder::ascending) + Sort(sortType, sortOrder); + + // the sort may have changed the number of rows + // before we update the selection, tell the tree + // do this before we call restore selection + // this is safe when there is no selection. + rv = AdjustRowCount(rowCountBeforeSort, GetSize()); + + RestoreSelection(preservedKey, preservedSelection); + if (mTree) mTree->Invalidate(); + return NS_OK; + } + } + else if (sortType != nsMsgViewSortType::byThread && (m_sortType == nsMsgViewSortType::byThread || sortThreads)/* && !m_havePrevView*/) + { + if (sortThreads) + { + SortThreads(sortType, sortOrder); + sortType = nsMsgViewSortType::byThread; // hack so base class won't do anything + } + else + { + // going from SortByThread to non-thread sort - must build new key, level,and flags arrays + m_prevKeys = m_keys; + m_prevFlags = m_flags; + m_prevLevels = m_levels; + // do this before we sort, so that we'll use the cheap method + // of expanding. + m_viewFlags &= ~(nsMsgViewFlagsType::kThreadedDisplay | nsMsgViewFlagsType::kGroupBySort); + ExpandAll(); + // m_idArray.RemoveAll(); + // m_flags.Clear(); + m_havePrevView = true; + } + } + } + else if (m_sortOrder != sortOrder)// check for toggling the sort + { + nsMsgDBView::Sort(sortType, sortOrder); + } + if (!sortThreads) + { + // call the base class in case we're not sorting by thread + rv = nsMsgDBView::Sort(sortType, sortOrder); + SaveSortInfo(sortType, sortOrder); + } + // the sort may have changed the number of rows + // before we restore the selection, tell the tree + // do this before we call restore selection + // this is safe when there is no selection. + rv = AdjustRowCount(rowCountBeforeSort, GetSize()); + + RestoreSelection(preservedKey, preservedSelection); + if (mTree) mTree->Invalidate(); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +// list the ids of the top-level thread ids starting at id == startMsg. This actually returns +// the ids of the first message in each thread. +nsresult nsMsgThreadedDBView::ListThreadIds(nsMsgKey *startMsg, bool unreadOnly, nsMsgKey *pOutput, int32_t *pFlags, char *pLevels, + int32_t numToList, int32_t *pNumListed, int32_t *pTotalHeaders) +{ + nsresult rv = NS_OK; + // N.B..don't ret before assigning numListed to *pNumListed + int32_t numListed = 0; + + if (*startMsg > 0) + { + NS_ASSERTION(m_threadEnumerator != nullptr, "where's our iterator?"); // for now, we'll just have to rely on the caller leaving + // the iterator in the right place. + } + else + { + NS_ASSERTION(m_db, "no db"); + if (!m_db) return NS_ERROR_UNEXPECTED; + rv = m_db->EnumerateThreads(getter_AddRefs(m_threadEnumerator)); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool hasMore = false; + + nsCOMPtr <nsIMsgThread> threadHdr ; + int32_t threadsRemoved = 0; + while (numListed < numToList && + NS_SUCCEEDED(rv = m_threadEnumerator->HasMoreElements(&hasMore)) && + hasMore) + { + nsCOMPtr <nsISupports> supports; + rv = m_threadEnumerator->GetNext(getter_AddRefs(supports)); + if (NS_FAILED(rv)) + { + threadHdr = nullptr; + break; + } + threadHdr = do_QueryInterface(supports); + if (!threadHdr) + break; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + uint32_t numChildren; + if (unreadOnly) + threadHdr->GetNumUnreadChildren(&numChildren); + else + threadHdr->GetNumChildren(&numChildren); + uint32_t threadFlags; + threadHdr->GetFlags(&threadFlags); + if (numChildren != 0) // not empty thread + { + int32_t unusedRootIndex; + if (pTotalHeaders) + *pTotalHeaders += numChildren; + if (unreadOnly) + rv = threadHdr->GetFirstUnreadChild(getter_AddRefs(msgHdr)); + else + rv = threadHdr->GetRootHdr(&unusedRootIndex, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv) && msgHdr != nullptr && WantsThisThread(threadHdr)) + { + uint32_t msgFlags; + uint32_t newMsgFlags; + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + msgHdr->GetFlags(&msgFlags); + // turn off high byte of msg flags - used for view flags. + msgFlags &= ~MSG_VIEW_FLAGS; + pOutput[numListed] = msgKey; + pLevels[numListed] = 0; + // turn off these flags on msg hdr - they belong in thread + msgHdr->AndFlags(~(nsMsgMessageFlags::Watched), &newMsgFlags); + AdjustReadFlag(msgHdr, &msgFlags); + // try adding in MSG_VIEW_FLAG_ISTHREAD flag for unreadonly view. + pFlags[numListed] = msgFlags | MSG_VIEW_FLAG_ISTHREAD | threadFlags; + if (numChildren > 1) + pFlags[numListed] |= MSG_VIEW_FLAG_HASCHILDREN; + + numListed++; + } + else + NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "couldn't get header for some reason"); + } + else if (threadsRemoved < 10 && !(threadFlags & (nsMsgMessageFlags::Watched | nsMsgMessageFlags::Ignored))) + { + // ### remove thread. + threadsRemoved++; // don't want to remove all empty threads first time + // around as it will choke preformance for upgrade. +#ifdef DEBUG_bienvenu + printf("removing empty non-ignored non-watched thread\n"); +#endif + } + } + + if (hasMore && threadHdr) + { + threadHdr->GetThreadKey(startMsg); + } + else + { + *startMsg = nsMsgKey_None; + nsCOMPtr <nsIDBChangeListener> dbListener = do_QueryInterface(m_threadEnumerator); + // this is needed to make the thread enumerator release its reference to the db. + if (dbListener) + dbListener->OnAnnouncerGoingAway(nullptr); + m_threadEnumerator = nullptr; + } + *pNumListed = numListed; + return rv; +} + +void nsMsgThreadedDBView::OnExtraFlagChanged(nsMsgViewIndex index, uint32_t extraFlag) +{ + if (IsValidIndex(index)) + { + if (m_havePrevView) + { + nsMsgKey keyChanged = m_keys[index]; + nsMsgViewIndex prevViewIndex = m_prevKeys.IndexOf(keyChanged); + if (prevViewIndex != nsMsgViewIndex_None) + { + uint32_t prevFlag = m_prevFlags[prevViewIndex]; + // don't want to change the elided bit, or has children or is thread + if (prevFlag & nsMsgMessageFlags::Elided) + extraFlag |= nsMsgMessageFlags::Elided; + else + extraFlag &= ~nsMsgMessageFlags::Elided; + if (prevFlag & MSG_VIEW_FLAG_ISTHREAD) + extraFlag |= MSG_VIEW_FLAG_ISTHREAD; + else + extraFlag &= ~MSG_VIEW_FLAG_ISTHREAD; + if (prevFlag & MSG_VIEW_FLAG_HASCHILDREN) + extraFlag |= MSG_VIEW_FLAG_HASCHILDREN; + else + extraFlag &= ~MSG_VIEW_FLAG_HASCHILDREN; + m_prevFlags[prevViewIndex] = extraFlag; // will this be right? + } + } + } + // we don't really know what's changed, but to be on the safe side, set the sort invalid + // so that reverse sort will pick it up. + if (m_sortType == nsMsgViewSortType::byStatus || m_sortType == nsMsgViewSortType::byFlagged || + m_sortType == nsMsgViewSortType::byUnread || m_sortType == nsMsgViewSortType::byPriority) + m_sortValid = false; +} + +void nsMsgThreadedDBView::OnHeaderAddedOrDeleted() +{ + ClearPrevIdArray(); +} + +void nsMsgThreadedDBView::ClearPrevIdArray() +{ + m_prevKeys.Clear(); + m_prevLevels.Clear(); + m_prevFlags.Clear(); + m_havePrevView = false; +} + +nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) +{ + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + return NS_OK; // nothing to do. + + if (sortType == nsMsgViewSortType::byThread) + { + nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder); // sort top level threads by id. + m_sortType = nsMsgViewSortType::byThread; + m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay; + m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort; + SetViewFlags(m_viewFlags); // persist the view flags. + // m_db->SetSortInfo(m_sortType, sortOrder); + } +// else +// m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay; + + // by default, the unread only view should have all threads expanded. + if ((m_viewFlags & (nsMsgViewFlagsType::kUnreadOnly|nsMsgViewFlagsType::kExpandAll)) + && (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + ExpandAll(); + if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + ExpandAll(); // for now, expand all and do a flat sort. + + Sort(sortType, sortOrder); + if (sortType != nsMsgViewSortType::byThread) // forget prev view, since it has everything expanded. + ClearPrevIdArray(); + return NS_OK; +} + +nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) +{ + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + return nsMsgGroupView::OnNewHeader(newHdr, aParentKey, ensureListed); + + NS_ENSURE_TRUE(newHdr, NS_MSG_MESSAGE_NOT_FOUND); + + nsMsgKey newKey; + newHdr->GetMessageKey(&newKey); + + // views can override this behaviour, which is to append to view. + // This is the mail behaviour, but threaded views want + // to insert in order... + uint32_t msgFlags; + newHdr->GetFlags(&msgFlags); + if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && !ensureListed && + (msgFlags & nsMsgMessageFlags::Read)) + return NS_OK; + // Currently, we only add the header in a threaded view if it's a thread. + // We used to check if this was the first header in the thread, but that's + // a bit harder in the unreadOnly view. But we'll catch it below. + + // if not threaded display just add it to the view. + if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + return AddHdr(newHdr); + + // need to find the thread we added this to so we can change the hasnew flag + // added message to existing thread, but not to view + // Fix flags on thread header. + int32_t threadCount; + uint32_t threadFlags; + bool moveThread = false; + nsMsgViewIndex threadIndex = ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags); + bool threadRootIsDisplayed = false; + + nsCOMPtr <nsIMsgThread> threadHdr; + m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr)); + if (threadHdr && m_sortType == nsMsgViewSortType::byDate) + { + uint32_t newestMsgInThread = 0, msgDate = 0; + threadHdr->GetNewestMsgDate(&newestMsgInThread); + newHdr->GetDateInSeconds(&msgDate); + moveThread = (msgDate == newestMsgInThread); + } + + if (threadIndex != nsMsgViewIndex_None) + { + threadRootIsDisplayed = (m_currentlyDisplayedViewIndex == threadIndex); + uint32_t flags = m_flags[threadIndex]; + if (!(flags & MSG_VIEW_FLAG_HASCHILDREN)) + { + flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD; + if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)) + flags |= nsMsgMessageFlags::Elided; + m_flags[threadIndex] = flags; + } + + if (!(flags & nsMsgMessageFlags::Elided)) + { // thread is expanded + // insert child into thread + // levels of other hdrs may have changed! + uint32_t newFlags = msgFlags; + int32_t level = 0; + nsMsgViewIndex insertIndex = threadIndex; + if (aParentKey == nsMsgKey_None) + { + newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN; + } + else + { + nsMsgViewIndex parentIndex = FindParentInThread(aParentKey, threadIndex); + level = m_levels[parentIndex] + 1; + insertIndex = GetInsertInfoForNewHdr(newHdr, parentIndex, level); + } + InsertMsgHdrAt(insertIndex, newHdr, newKey, newFlags, level); + // the call to NoteChange() has to happen after we add the key + // as NoteChange() will call RowCountChanged() which will call our GetRowCount() + NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete); + + if (aParentKey == nsMsgKey_None) + { + // this header is the new king! try collapsing the existing thread, + // removing it, installing this header as king, and expanding it. + CollapseByIndex(threadIndex, nullptr); + // call base class, so child won't get promoted. + // nsMsgDBView::RemoveByIndex(threadIndex); + ExpandByIndex(threadIndex, nullptr); + } + } + else if (aParentKey == nsMsgKey_None) + { + // if we have a collapsed thread which just got a new + // top of thread, change the keys array. + m_keys[threadIndex] = newKey; + } + + // If this message is new, the thread is collapsed, it is the + // root and it was displayed, expand it so that the user does + // not find that their message has magically turned into a summary. + if (msgFlags & nsMsgMessageFlags::New && + m_flags[threadIndex] & nsMsgMessageFlags::Elided && + threadRootIsDisplayed) + ExpandByIndex(threadIndex, nullptr); + + if (moveThread) + MoveThreadAt(threadIndex); + else + // note change, to update the parent thread's unread and total counts + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + } + else if (threadHdr) + // adding msg to thread that's not in view. + AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed); + + return NS_OK; +} + + +NS_IMETHODIMP nsMsgThreadedDBView::OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) +{ + // we need to adjust the level of the hdr whose parent changed, and invalidate that row, + // iff we're in threaded mode. +#if 0 + // This code never runs due to the if (false) and Clang complains about it + // so it is ifdefed out for now. + if (false && m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) + { + nsMsgViewIndex childIndex = FindViewIndex(aKeyChanged); + if (childIndex != nsMsgViewIndex_None) + { + nsMsgViewIndex parentIndex = FindViewIndex(newParent); + int32_t newParentLevel = (parentIndex == nsMsgViewIndex_None) ? -1 : m_levels[parentIndex]; + nsMsgViewIndex oldParentIndex = FindViewIndex(oldParent); + int32_t oldParentLevel = (oldParentIndex != nsMsgViewIndex_None || newParent == nsMsgKey_None) + ? m_levels[oldParentIndex] : -1 ; + int32_t levelChanged = m_levels[childIndex]; + int32_t parentDelta = oldParentLevel - newParentLevel; + m_levels[childIndex] = (newParent == nsMsgKey_None) ? 0 : newParentLevel + 1; + if (parentDelta > 0) + { + for (nsMsgViewIndex viewIndex = childIndex + 1; viewIndex < GetSize() && m_levels[viewIndex] > levelChanged; viewIndex++) + { + m_levels[viewIndex] = m_levels[viewIndex] - parentDelta; + NoteChange(viewIndex, 1, nsMsgViewNotificationCode::changed); + } + } + NoteChange(childIndex, 1, nsMsgViewNotificationCode::changed); + } + } +#endif + return NS_OK; +} + + +nsMsgViewIndex nsMsgThreadedDBView::GetInsertInfoForNewHdr(nsIMsgDBHdr *newHdr, nsMsgViewIndex parentIndex, int32_t targetLevel) +{ + uint32_t viewSize = GetSize(); + while (++parentIndex < viewSize) + { + // loop until we find a message at a level less than or equal to the parent level + if (m_levels[parentIndex] < targetLevel) + break; + } + return parentIndex; +} + +// This method removes the thread at threadIndex from the view +// and puts it back in its new position, determined by the sort order. +// And, if the selection is affected, save and restore the selection. +void nsMsgThreadedDBView::MoveThreadAt(nsMsgViewIndex threadIndex) +{ + // we need to check if the thread is collapsed or not... + // We want to turn off tree notifications so that we don't + // reload the current message. + // We also need to invalidate the range between where the thread was + // and where it ended up. + bool changesDisabled = mSuppressChangeNotification; + if (!changesDisabled) + SetSuppressChangeNotifications(true); + + nsCOMPtr <nsIMsgDBHdr> threadHdr; + + GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr)); + int32_t childCount = 0; + + nsMsgKey preservedKey; + AutoTArray<nsMsgKey, 1> preservedSelection; + int32_t selectionCount; + int32_t currentIndex; + bool hasSelection = mTree && mTreeSelection && + ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(¤tIndex)) && + currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) || + (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) && + selectionCount > 0)); + if (hasSelection) + SaveAndClearSelection(&preservedKey, preservedSelection); + uint32_t saveFlags = m_flags[threadIndex]; + bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided); + + if (threadIsExpanded) + { + ExpansionDelta(threadIndex, &childCount); + childCount = -childCount; + } + nsTArray<nsMsgKey> threadKeys; + nsTArray<uint32_t> threadFlags; + nsTArray<uint8_t> threadLevels; + + if (threadIsExpanded) + { + threadKeys.SetCapacity(childCount); + threadFlags.SetCapacity(childCount); + threadLevels.SetCapacity(childCount); + for (nsMsgViewIndex index = threadIndex + 1; + index < GetSize() && m_levels[index]; index++) + { + threadKeys.AppendElement(m_keys[index]); + threadFlags.AppendElement(m_flags[index]); + threadLevels.AppendElement(m_levels[index]); + } + uint32_t collapseCount; + CollapseByIndex(threadIndex, &collapseCount); + } + nsMsgDBView::RemoveByIndex(threadIndex); + nsMsgViewIndex newIndex = nsMsgViewIndex_None; + AddHdr(threadHdr, &newIndex); + + // AddHdr doesn't always set newIndex, and getting it to do so + // is going to require some refactoring. + if (newIndex == nsMsgViewIndex_None) + newIndex = FindHdr(threadHdr); + + if (threadIsExpanded) + { + m_keys.InsertElementsAt(newIndex + 1, threadKeys); + m_flags.InsertElementsAt(newIndex + 1, threadFlags); + m_levels.InsertElementsAt(newIndex + 1, threadLevels); + } + if (newIndex == nsMsgViewIndex_None) + { + NS_WARNING("newIndex=-1 in MoveThreadAt"); + newIndex = 0; + } + m_flags[newIndex] = saveFlags; + // unfreeze selection. + if (hasSelection) + RestoreSelection(preservedKey, preservedSelection); + + if (!changesDisabled) + SetSuppressChangeNotifications(false); + nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex; + nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex; + NoteChange(lowIndex, highIndex - lowIndex + childCount + 1, + nsMsgViewNotificationCode::changed); +} +nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed) +{ + nsresult rv = NS_OK; + uint32_t threadFlags; + threadHdr->GetFlags(&threadFlags); + if (!(threadFlags & nsMsgMessageFlags::Ignored)) + { + bool msgKilled; + msgHdr->GetIsKilled(&msgKilled); + if (!msgKilled) + rv = nsMsgDBView::AddHdr(msgHdr); + } + return rv; +} + +// This method just removes the specified line from the view. It does +// NOT delete it from the database. +nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index) +{ + nsresult rv = NS_OK; + int32_t flags; + + if (!IsValidIndex(index)) + return NS_MSG_INVALID_DBVIEW_INDEX; + + OnHeaderAddedOrDeleted(); + + flags = m_flags[index]; + + if (! (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + return nsMsgDBView::RemoveByIndex(index); + + nsCOMPtr<nsIMsgThread> threadHdr; + GetThreadContainingIndex(index, getter_AddRefs(threadHdr)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t numThreadChildren = 0; + // If we can't get a thread, it's already deleted and thus has 0 children. + if (threadHdr) + threadHdr->GetNumChildren(&numThreadChildren); + + // Check if we're the top level msg in the thread, and we're not collapsed. + if ((flags & MSG_VIEW_FLAG_ISTHREAD) && + !(flags & nsMsgMessageFlags::Elided) && + (flags & MSG_VIEW_FLAG_HASCHILDREN)) + { + // Fix flags on thread header - newly promoted message should have + // flags set correctly. + if (threadHdr) + { + nsMsgDBView::RemoveByIndex(index); + nsCOMPtr<nsIMsgThread> nextThreadHdr; + // Above RemoveByIndex may now make index out of bounds. + if (IsValidIndex(index) && numThreadChildren > 0) + { + // unreadOnly + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr)); + if (msgHdr != nullptr) + { + uint32_t flag = 0; + msgHdr->GetFlags(&flag); + if (numThreadChildren > 1) + flag |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN; + + m_flags[index] = flag; + m_levels[index] = 0; + } + } + } + return rv; + } + else if (!(flags & MSG_VIEW_FLAG_ISTHREAD)) + { + // We're not deleting the top level msg, but top level msg might be the + // only msg in thread now. + if (threadHdr && numThreadChildren == 1) + { + nsMsgKey msgKey; + rv = threadHdr->GetChildKeyAt(0, &msgKey); + if (NS_SUCCEEDED(rv)) + { + nsMsgViewIndex threadIndex = FindViewIndex(msgKey); + if (IsValidIndex(threadIndex)) + { + uint32_t flags = m_flags[threadIndex]; + flags &= ~(MSG_VIEW_FLAG_ISTHREAD | + nsMsgMessageFlags::Elided | + MSG_VIEW_FLAG_HASCHILDREN); + m_flags[threadIndex] = flags; + NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed); + } + } + + } + return nsMsgDBView::RemoveByIndex(index); + } + // Deleting collapsed thread header is special case. Child will be promoted, + // so just tell FE that line changed, not that it was deleted. + // Header has aleady been deleted from thread. + if (threadHdr && numThreadChildren > 0) + { + // change the id array and flags array to reflect the child header. + // If we're not deleting the header, we want the second header, + // Otherwise, the first one (which just got promoted). + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr)); + if (msgHdr != nullptr) + { + msgHdr->GetMessageKey(&m_keys[index]); + + uint32_t flag = 0; + msgHdr->GetFlags(&flag); + flag |= MSG_VIEW_FLAG_ISTHREAD; + + // if only hdr in thread (with one about to be deleted) + if (numThreadChildren == 1) + { + // adjust flags. + flag &= ~MSG_VIEW_FLAG_HASCHILDREN; + flag &= ~nsMsgMessageFlags::Elided; + // tell FE that thread header needs to be repainted. + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + } + else + { + flag |= MSG_VIEW_FLAG_HASCHILDREN; + flag |= nsMsgMessageFlags::Elided; + } + m_flags[index] = flag; + mIndicesToNoteChange.RemoveElement(index); + } + else + NS_ASSERTION(false, "couldn't find thread child"); + + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + } + else + { + // we may have deleted a whole, collapsed thread - if so, + // ensure that the current index will be noted as changed. + if (!mIndicesToNoteChange.Contains(index)) + mIndicesToNoteChange.AppendElement(index); + + rv = nsMsgDBView::RemoveByIndex(index); + } + return rv; +} + +NS_IMETHODIMP nsMsgThreadedDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowAllThreads; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgThreadedDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) +{ + nsMsgThreadedDBView* newMsgDBView = new nsMsgThreadedDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} diff --git a/mailnews/base/src/nsMsgThreadedDBView.h b/mailnews/base/src/nsMsgThreadedDBView.h new file mode 100644 index 0000000000..948260c120 --- /dev/null +++ b/mailnews/base/src/nsMsgThreadedDBView.h @@ -0,0 +1,51 @@ +/* -*- 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/. */ + +#ifndef _nsMsgThreadedDBView_H_ +#define _nsMsgThreadedDBView_H_ + +#include "mozilla/Attributes.h" +#include "nsMsgGroupView.h" + +class nsMsgThreadedDBView : public nsMsgGroupView +{ +public: + nsMsgThreadedDBView(); + virtual ~nsMsgThreadedDBView(); + + NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override; + NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCommandUpdater, nsIMsgDBView **_retval) override; + NS_IMETHOD Close() override; + int32_t AddKeys(nsMsgKey *pKeys, int32_t *pFlags, const char *pLevels, nsMsgViewSortTypeValue sortType, int32_t numKeysToAdd); + NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) override; + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD OnParentChanged (nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, nsIDBChangeListener *aInstigator) override; + +protected: + virtual const char *GetViewName(void) override { return "ThreadedDBView"; } + nsresult InitThreadedView(int32_t *pCount); + virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed) override; + virtual nsresult AddMsgToThreadNotInView(nsIMsgThread *threadHdr, nsIMsgDBHdr *msgHdr, bool ensureListed); + nsresult ListThreadIds(nsMsgKey *startMsg, bool unreadOnly, nsMsgKey *pOutput, int32_t *pFlags, char *pLevels, + int32_t numToList, int32_t *pNumListed, int32_t *pTotalHeaders); + nsresult InitSort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder); + virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder); + virtual void OnExtraFlagChanged(nsMsgViewIndex index, uint32_t extraFlag) override; + virtual void OnHeaderAddedOrDeleted() override; + void ClearPrevIdArray(); + virtual nsresult RemoveByIndex(nsMsgViewIndex index) override; + nsMsgViewIndex GetInsertInfoForNewHdr(nsIMsgDBHdr *newHdr, nsMsgViewIndex threadIndex, int32_t targetLevel); + void MoveThreadAt(nsMsgViewIndex threadIndex); + + // these are used to save off the previous view so that bopping back and forth + // between two views is quick (e.g., threaded and flat sorted by date). + bool m_havePrevView; + nsTArray<nsMsgKey> m_prevKeys; //this is used for caching non-threaded view. + nsTArray<uint32_t> m_prevFlags; + nsTArray<uint8_t> m_prevLevels; + nsCOMPtr <nsISimpleEnumerator> m_threadEnumerator; +}; + +#endif diff --git a/mailnews/base/src/nsMsgWindow.cpp b/mailnews/base/src/nsMsgWindow.cpp new file mode 100644 index 0000000000..b94cbc0e1f --- /dev/null +++ b/mailnews/base/src/nsMsgWindow.cpp @@ -0,0 +1,534 @@ +/* -*- 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 "nsMsgWindow.h" +#include "nsIURILoader.h" +#include "nsCURILoader.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDOMElement.h" +#include "mozIDOMWindow.h" +#include "nsTransactionManagerCID.h" +#include "nsIComponentManager.h" +#include "nsILoadGroup.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsPIDOMWindow.h" +#include "nsIPrompt.h" +#include "nsICharsetConverterManager.h" +#include "nsIChannel.h" +#include "nsIRequestObserver.h" +#include "netCore.h" +#include "prmem.h" +#include "plbase64.h" +#include "nsMsgI18N.h" +#include "nsIWebNavigation.h" +#include "nsMsgContentPolicy.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIAuthPrompt.h" +#include "nsMsgUtils.h" + +// used to dispatch urls to default protocol handlers +#include "nsCExternalHandlerService.h" +#include "nsIExternalProtocolService.h" + +static NS_DEFINE_CID(kTransactionManagerCID, NS_TRANSACTIONMANAGER_CID); + +NS_IMPL_ISUPPORTS(nsMsgWindow, + nsIMsgWindow, + nsIURIContentListener, + nsISupportsWeakReference, + nsIMsgWindowTest) + +nsMsgWindow::nsMsgWindow() +{ + mCharsetOverride = false; + m_stopped = false; +} + +nsMsgWindow::~nsMsgWindow() +{ + CloseWindow(); +} + +nsresult nsMsgWindow::Init() +{ + // register ourselves as a content listener with the uri dispatcher service + nsresult rv; + nsCOMPtr<nsIURILoader> dispatcher = + do_GetService(NS_URI_LOADER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dispatcher->RegisterContentListener(this); + if (NS_FAILED(rv)) + return rv; + + // create Undo/Redo Transaction Manager + mTransactionManager = do_CreateInstance(kTransactionManagerCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + return mTransactionManager->SetMaxTransactionCount(-1); +} + +NS_IMETHODIMP nsMsgWindow::GetMessageWindowDocShell(nsIDocShell ** aDocShell) +{ + *aDocShell = nullptr; + nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mMessageWindowDocShellWeak)); + if (!docShell) + { + // if we don't have a docshell, then we need to look up the message pane docshell + nsCOMPtr<nsIDocShell> rootShell(do_QueryReferent(mRootDocShellWeak)); + if (rootShell) + { + nsCOMPtr<nsIDocShellTreeItem> msgDocShellItem; + if(rootShell) + rootShell->FindChildWithName(NS_LITERAL_STRING("messagepane"), + true, false, nullptr, nullptr, + getter_AddRefs(msgDocShellItem)); + NS_ENSURE_TRUE(msgDocShellItem, NS_ERROR_FAILURE); + docShell = do_QueryInterface(msgDocShellItem); + // we don't own mMessageWindowDocShell so don't try to keep a reference to it! + mMessageWindowDocShellWeak = do_GetWeakReference(docShell); + } + } + docShell.swap(*aDocShell); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::CloseWindow() +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIURILoader> dispatcher = do_GetService(NS_URI_LOADER_CONTRACTID, &rv); + if (dispatcher) // on shut down it's possible dispatcher will be null. + rv = dispatcher->UnRegisterContentListener(this); + + mMsgWindowCommands = nullptr; + mStatusFeedback = nullptr; + + StopUrls(); + + nsCOMPtr<nsIDocShell> messagePaneDocShell(do_QueryReferent(mMessageWindowDocShellWeak)); + if (messagePaneDocShell) + { + nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(messagePaneDocShell)); + if (listener) + listener->SetParentContentListener(nullptr); + SetRootDocShell(nullptr); + mMessageWindowDocShellWeak = nullptr; + } + + // in case nsMsgWindow leaks, make sure other stuff doesn't leak. + mTransactionManager = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetStatusFeedback(nsIMsgStatusFeedback **aStatusFeedback) +{ + NS_ENSURE_ARG_POINTER(aStatusFeedback); + NS_IF_ADDREF(*aStatusFeedback = mStatusFeedback); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetStatusFeedback(nsIMsgStatusFeedback * aStatusFeedback) +{ + mStatusFeedback = aStatusFeedback; + nsCOMPtr<nsIDocShell> messageWindowDocShell; + GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell)); + + // register our status feedback object as a web progress listener + nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(messageWindowDocShell)); + if (webProgress && mStatusFeedback && messageWindowDocShell) + { + nsCOMPtr<nsIWebProgressListener> webProgressListener = do_QueryInterface(mStatusFeedback); + webProgress->AddProgressListener(webProgressListener, nsIWebProgress::NOTIFY_ALL); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetWindowCommands(nsIMsgWindowCommands * aMsgWindowCommands) +{ + mMsgWindowCommands = aMsgWindowCommands; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetWindowCommands(nsIMsgWindowCommands **aMsgWindowCommands) +{ + NS_ENSURE_ARG_POINTER(aMsgWindowCommands); + NS_IF_ADDREF(*aMsgWindowCommands = mMsgWindowCommands); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetMsgHeaderSink(nsIMsgHeaderSink * *aMsgHdrSink) +{ + NS_ENSURE_ARG_POINTER(aMsgHdrSink); + NS_IF_ADDREF(*aMsgHdrSink = mMsgHeaderSink); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetMsgHeaderSink(nsIMsgHeaderSink * aMsgHdrSink) +{ + mMsgHeaderSink = aMsgHdrSink; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetTransactionManager(nsITransactionManager * *aTransactionManager) +{ + NS_ENSURE_ARG_POINTER(aTransactionManager); + NS_IF_ADDREF(*aTransactionManager = mTransactionManager); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetTransactionManager(nsITransactionManager * aTransactionManager) +{ + mTransactionManager = aTransactionManager; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetOpenFolder(nsIMsgFolder * *aOpenFolder) +{ + NS_ENSURE_ARG_POINTER(aOpenFolder); + NS_IF_ADDREF(*aOpenFolder = mOpenFolder); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetOpenFolder(nsIMsgFolder * aOpenFolder) +{ + mOpenFolder = aOpenFolder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetRootDocShell(nsIDocShell * *aDocShell) +{ + if (mRootDocShellWeak) + CallQueryReferent(mRootDocShellWeak.get(), aDocShell); + else + *aDocShell = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetAuthPrompt(nsIAuthPrompt * *aAuthPrompt) +{ + NS_ENSURE_ARG_POINTER(aAuthPrompt); + + // testing only + if (mAuthPrompt) + { + NS_ADDREF(*aAuthPrompt = mAuthPrompt); + return NS_OK; + } + + if (!mRootDocShellWeak) + return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mRootDocShellWeak.get(), &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAuthPrompt> prompt = do_GetInterface(docShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + prompt.swap(*aAuthPrompt); + + return rv; +} + +NS_IMETHODIMP nsMsgWindow::SetAuthPrompt(nsIAuthPrompt* aAuthPrompt) +{ + mAuthPrompt = aAuthPrompt; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetRootDocShell(nsIDocShell * aDocShell) +{ + nsresult rv; + nsCOMPtr<nsIWebProgressListener> contentPolicyListener = + do_GetService(NS_MSGCONTENTPOLICY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // remove the content policy webProgressListener from the root doc shell + // we're currently holding, so we don't keep listening for loads that + // we don't care about + if (mRootDocShellWeak) { + nsCOMPtr<nsIWebProgress> oldWebProgress = + do_QueryReferent(mRootDocShellWeak, &rv); + if (NS_SUCCEEDED(rv)) { + rv = oldWebProgress->RemoveProgressListener(contentPolicyListener); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove old progress listener"); + } + } + + // Query for the doc shell and release it + mRootDocShellWeak = nullptr; + if (aDocShell) + { + mRootDocShellWeak = do_GetWeakReference(aDocShell); + + nsCOMPtr<nsIDocShell> messagePaneDocShell; + GetMessageWindowDocShell(getter_AddRefs(messagePaneDocShell)); + nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(messagePaneDocShell)); + if (listener) + listener->SetParentContentListener(this); + + // set the contentPolicy webProgressListener on the root docshell for this + // window so that it can allow JavaScript for non-message content + nsCOMPtr<nsIWebProgress> docShellProgress = + do_QueryInterface(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = docShellProgress->AddProgressListener(contentPolicyListener, + nsIWebProgress::NOTIFY_LOCATION); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetMailCharacterSet(nsACString& aMailCharacterSet) +{ + aMailCharacterSet = mMailCharacterSet; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetMailCharacterSet(const nsACString& aMailCharacterSet) +{ + mMailCharacterSet.Assign(aMailCharacterSet); + + // Convert to a canonical charset name instead of using the charset name from the message header as is. + // This is needed for charset menu item to have a check mark correctly. + nsresult rv; + nsCOMPtr <nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return ccm->GetCharsetAlias(PromiseFlatCString(aMailCharacterSet).get(), + mMailCharacterSet); +} + +NS_IMETHODIMP nsMsgWindow::GetCharsetOverride(bool *aCharsetOverride) +{ + NS_ENSURE_ARG_POINTER(aCharsetOverride); + *aCharsetOverride = mCharsetOverride; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetCharsetOverride(bool aCharsetOverride) +{ + mCharsetOverride = aCharsetOverride; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetDomWindow(mozIDOMWindowProxy **aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + if (mDomWindow) + CallQueryReferent(mDomWindow.get(), aWindow); + else + *aWindow = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetDomWindow(mozIDOMWindowProxy * aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + mDomWindow = do_GetWeakReference(aWindow); + + nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWindow); + nsIDocShell *docShell = nullptr; + if (win) + docShell = win->GetDocShell(); + + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(docShell)); + + if(docShellAsItem) + { + nsCOMPtr<nsIDocShellTreeItem> rootAsItem; + docShellAsItem->GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem)); + + nsCOMPtr<nsIDocShell> rootAsShell(do_QueryInterface(rootAsItem)); + SetRootDocShell(rootAsShell); + + // force ourselves to figure out the message pane + nsCOMPtr<nsIDocShell> messageWindowDocShell; + GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell)); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetNotificationCallbacks(nsIInterfaceRequestor * aNotificationCallbacks) +{ + mNotificationCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetNotificationCallbacks(nsIInterfaceRequestor **aNotificationCallbacks) +{ + NS_ENSURE_ARG_POINTER(aNotificationCallbacks); + NS_IF_ADDREF(*aNotificationCallbacks = mNotificationCallbacks); + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::StopUrls() +{ + m_stopped = true; + nsCOMPtr<nsIWebNavigation> webnav(do_QueryReferent(mRootDocShellWeak)); + return webnav ? webnav->Stop(nsIWebNavigation::STOP_NETWORK) : NS_ERROR_FAILURE; +} + +// nsIURIContentListener support +NS_IMETHODIMP nsMsgWindow::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::DoContent(const nsACString& aContentType, bool aIsContentPreferred, + nsIRequest *request, nsIStreamListener **aContentHandler, bool *aAbortProcess) +{ + if (!aContentType.IsEmpty()) + { + // forward the DoContent call to our docshell + nsCOMPtr<nsIDocShell> messageWindowDocShell; + GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell)); + nsCOMPtr<nsIURIContentListener> ctnListener = do_QueryInterface(messageWindowDocShell); + if (ctnListener) + { + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if (!aChannel) return NS_ERROR_FAILURE; + + // get the url for the channel...let's hope it is a mailnews url so we can set our msg hdr sink on it.. + // right now, this is the only way I can think of to force the msg hdr sink into the mime converter so it can + // get too it later... + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri)); + if (mailnewsUrl) + mailnewsUrl->SetMsgWindow(this); + } + return ctnListener->DoContent(aContentType, aIsContentPreferred, request, aContentHandler, aAbortProcess); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgWindow::IsPreferred(const char * aContentType, + char ** aDesiredContentType, + bool * aCanHandleContent) +{ + *aCanHandleContent = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::CanHandleContent(const char * aContentType, + bool aIsContentPreferred, + char ** aDesiredContentType, + bool * aCanHandleContent) + +{ + // the mail window knows nothing about the default content types + // its docshell can handle...ask the content area if it can handle + // the content type... + + nsCOMPtr<nsIDocShell> messageWindowDocShell; + GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell)); + nsCOMPtr<nsIURIContentListener> ctnListener (do_GetInterface(messageWindowDocShell)); + if (ctnListener) + return ctnListener->CanHandleContent(aContentType, aIsContentPreferred, + aDesiredContentType, aCanHandleContent); + else + *aCanHandleContent = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetParentContentListener(nsIURIContentListener** aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetParentContentListener(nsIURIContentListener* aParent) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetLoadCookie(nsISupports ** aLoadCookie) +{ + NS_ENSURE_ARG_POINTER(aLoadCookie); + *aLoadCookie = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::SetLoadCookie(nsISupports * aLoadCookie) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgWindow::GetPromptDialog(nsIPrompt **aPrompt) +{ + NS_ENSURE_ARG_POINTER(aPrompt); + + // testing only + if (mPromptDialog) + { + NS_ADDREF(*aPrompt = mPromptDialog); + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIDocShell> rootShell(do_QueryReferent(mRootDocShellWeak, &rv)); + if (rootShell) + { + nsCOMPtr<nsIPrompt> dialog; + dialog = do_GetInterface(rootShell, &rv); + dialog.swap(*aPrompt); + } + return rv; +} + +NS_IMETHODIMP nsMsgWindow::SetPromptDialog(nsIPrompt* aPromptDialog) +{ + mPromptDialog = aPromptDialog; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgWindow::DisplayHTMLInMessagePane(const nsAString& title, const nsAString& body, bool clearMsgHdr) +{ + if (clearMsgHdr && mMsgWindowCommands) + mMsgWindowCommands->ClearMsgPane(); + + nsString htmlStr; + htmlStr.Append(NS_LITERAL_STRING("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head><body>")); + htmlStr.Append(body); + htmlStr.Append(NS_LITERAL_STRING("</body></html>")); + + char *encodedHtml = PL_Base64Encode(NS_ConvertUTF16toUTF8(htmlStr).get(), 0, nullptr); + if (!encodedHtml) + return NS_ERROR_OUT_OF_MEMORY; + + nsCString dataSpec; + dataSpec = "data:text/html;base64,"; + dataSpec += encodedHtml; + + PR_FREEIF(encodedHtml); + + nsCOMPtr <nsIDocShell> docShell; + GetMessageWindowDocShell(getter_AddRefs(docShell)); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell)); + NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE); + + return webNav->LoadURI(NS_ConvertASCIItoUTF16(dataSpec).get(), + nsIWebNavigation::LOAD_FLAGS_NONE, + nullptr, nullptr, nullptr); +} + +NS_IMPL_GETSET(nsMsgWindow, Stopped, bool, m_stopped) diff --git a/mailnews/base/src/nsMsgWindow.h b/mailnews/base/src/nsMsgWindow.h new file mode 100644 index 0000000000..6dd061990f --- /dev/null +++ b/mailnews/base/src/nsMsgWindow.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _nsMsgWindow_h +#define _nsMsgWindow_h + +#include "nsIMsgWindow.h" +#include "nsIMsgStatusFeedback.h" +#include "nsITransactionManager.h" +#include "nsIMsgFolder.h" +#include "nsIDocShell.h" +#include "nsIURIContentListener.h" +#include "nsIMimeMiscStatus.h" +#include "nsWeakReference.h" +#include "nsIInterfaceRequestor.h" +#include "nsCOMPtr.h" + +class nsMsgWindow : public nsIMsgWindow, + public nsIURIContentListener, + public nsIMsgWindowTest, + public nsSupportsWeakReference +{ + +public: + + NS_DECL_THREADSAFE_ISUPPORTS + + nsMsgWindow(); + nsresult Init(); + NS_DECL_NSIMSGWINDOW + NS_DECL_NSIURICONTENTLISTENER + NS_DECL_NSIMSGWINDOWTEST + +protected: + virtual ~nsMsgWindow(); + nsCOMPtr<nsIMsgHeaderSink> mMsgHeaderSink; + nsCOMPtr<nsIMsgStatusFeedback> mStatusFeedback; + nsCOMPtr<nsITransactionManager> mTransactionManager; + nsCOMPtr<nsIMsgFolder> mOpenFolder; + nsCOMPtr<nsIMsgWindowCommands> mMsgWindowCommands; + // These are used by the backend protocol code to attach + // notification callbacks to channels, e.g., nsIBadCertListner2. + nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks; + // prompt dialog used during testing only + nsCOMPtr<nsIPrompt> mPromptDialog; + // authorization prompt used during testing only + nsCOMPtr<nsIAuthPrompt> mAuthPrompt; + + // let's not make this a strong ref - we don't own it. + nsWeakPtr mRootDocShellWeak; + nsWeakPtr mMessageWindowDocShellWeak; + nsWeakPtr mDomWindow; + + nsCString mMailCharacterSet; + bool mCharsetOverride; + bool m_stopped; +}; + +#endif diff --git a/mailnews/base/src/nsMsgXFViewThread.cpp b/mailnews/base/src/nsMsgXFViewThread.cpp new file mode 100644 index 0000000000..113aeef6ff --- /dev/null +++ b/mailnews/base/src/nsMsgXFViewThread.cpp @@ -0,0 +1,481 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgXFViewThread.h" +#include "nsMsgSearchDBView.h" +#include "nsMsgMessageFlags.h" + +NS_IMPL_ISUPPORTS(nsMsgXFViewThread, nsIMsgThread) + +nsMsgXFViewThread::nsMsgXFViewThread(nsMsgSearchDBView *view, nsMsgKey threadId) +{ + m_numUnreadChildren = 0; + m_numChildren = 0; + m_flags = 0; + m_newestMsgDate = 0; + m_view = view; + m_threadId = threadId; +} + +nsMsgXFViewThread::~nsMsgXFViewThread() +{ +} + +NS_IMETHODIMP nsMsgXFViewThread::SetThreadKey(nsMsgKey threadKey) +{ + m_threadId = threadKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetThreadKey(nsMsgKey *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = m_threadId; + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetFlags(uint32_t *aFlags) +{ + NS_ENSURE_ARG_POINTER(aFlags); + *aFlags = m_flags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::SetFlags(uint32_t aFlags) +{ + m_flags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::SetSubject(const nsACString& aSubject) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetSubject(nsACString& result) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetNumChildren(uint32_t *aNumChildren) +{ + NS_ENSURE_ARG_POINTER(aNumChildren); + *aNumChildren = m_keys.Length(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetNumUnreadChildren (uint32_t *aNumUnreadChildren) +{ + NS_ENSURE_ARG_POINTER(aNumUnreadChildren); + *aNumUnreadChildren = m_numUnreadChildren; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgXFViewThread::AddChild(nsIMsgDBHdr *aNewHdr, nsIMsgDBHdr *aInReplyTo, + bool aThreadInThread, nsIDBChangeAnnouncer *aAnnouncer) +{ + uint32_t whereInserted; + return AddHdr(aNewHdr, false, whereInserted, nullptr); +} + +// Returns the parent of the newly added header. If reparentChildren +// is true, we believe that the new header is a parent of an existing +// header, and we should find it, and reparent it. +nsresult nsMsgXFViewThread::AddHdr(nsIMsgDBHdr *newHdr, + bool reparentChildren, + uint32_t &whereInserted, + nsIMsgDBHdr **outParent) +{ + nsCOMPtr<nsIMsgFolder> newHdrFolder; + newHdr->GetFolder(getter_AddRefs(newHdrFolder)); + + uint32_t newHdrFlags = 0; + uint32_t msgDate; + nsMsgKey newHdrKey = 0; + + newHdr->GetMessageKey(&newHdrKey); + newHdr->GetDateInSeconds(&msgDate); + newHdr->GetFlags(&newHdrFlags); + if (msgDate > m_newestMsgDate) + SetNewestMsgDate(msgDate); + + if (newHdrFlags & nsMsgMessageFlags::Watched) + SetFlags(m_flags | nsMsgMessageFlags::Watched); + + ChangeChildCount(1); + if (! (newHdrFlags & nsMsgMessageFlags::Read)) + ChangeUnreadChildCount(1); + + if (m_numChildren == 1) + { + m_keys.InsertElementAt(0, newHdrKey); + m_levels.InsertElementAt(0, 0); + m_folders.InsertObjectAt(newHdrFolder, 0); + if (outParent) + *outParent = nullptr; + whereInserted = 0; + return NS_OK; + } + + // Find our parent, if any, in the thread. Starting at the newest + // reference, and working our way back, see if we've mapped that reference + // to this thread. + uint16_t numReferences; + newHdr->GetNumReferences(&numReferences); + nsCOMPtr<nsIMsgDBHdr> parent; + int32_t parentIndex = -1; + + for (int32_t i = numReferences - 1; i >= 0; i--) + { + nsAutoCString reference; + newHdr->GetStringReference(i, reference); + if (reference.IsEmpty()) + break; + + // I could look for the thread from the reference, but getting + // the header directly should be fine. If it's not, that means + // that the parent isn't in this thread, though it should be. + m_view->GetMsgHdrFromHash(reference, getter_AddRefs(parent)); + if (parent) + { + parentIndex = HdrIndex(parent); + if (parentIndex == -1) + { + NS_ERROR("how did we get in the wrong thread?"); + parent = nullptr; + } + break; + } + } + if (parent) + { + if (outParent) + NS_ADDREF(*outParent = parent); + uint32_t parentLevel = m_levels[parentIndex]; + nsMsgKey parentKey; + parent->GetMessageKey(&parentKey); + nsCOMPtr<nsIMsgFolder> parentFolder; + parent->GetFolder(getter_AddRefs(parentFolder)); + // iterate over our parents' children until we find one we're older than, + // and insert ourselves before it, or as the last child. In other words, + // insert, sorted by date. + uint32_t msgDate, childDate; + newHdr->GetDateInSeconds(&msgDate); + nsCOMPtr<nsIMsgDBHdr> child; + nsMsgViewIndex i; + nsMsgViewIndex insertIndex = m_keys.Length(); + uint32_t insertLevel = parentLevel + 1; + for (i = parentIndex; + i < m_keys.Length() && (i == (nsMsgViewIndex)parentIndex || m_levels[i] >= parentLevel); + i++) + { + GetChildHdrAt(i, getter_AddRefs(child)); + if (child) + { + if (reparentChildren && IsHdrParentOf(newHdr, child)) + { + insertIndex = i; + // bump all the children of the current child, and the child + nsMsgViewIndex j = insertIndex; + uint8_t childLevel = m_levels[insertIndex]; + do + { + m_levels[j] = m_levels[j] + 1; + j++; + } + while (j < m_keys.Length() && m_levels[j] > childLevel); + break; + } + else if (m_levels[i] == parentLevel + 1) // possible sibling + { + child->GetDateInSeconds(&childDate); + if (msgDate < childDate) + { + // if we think we need to reparent, remember this + // insert index, but keep looking for children. + insertIndex = i; + insertLevel = m_levels[i]; + // if the sibling we're inserting after has children, we need + // to go after the children. + while (insertIndex + 1 < m_keys.Length() && m_levels[insertIndex + 1] > insertLevel) + insertIndex++; + if (!reparentChildren) + break; + } + } + } + } + m_keys.InsertElementAt(insertIndex, newHdrKey); + m_levels.InsertElementAt(insertIndex, insertLevel); + m_folders.InsertObjectAt(newHdrFolder, insertIndex); + whereInserted = insertIndex; + } + else + { + if (outParent) + *outParent = nullptr; + nsCOMPtr<nsIMsgDBHdr> rootHdr; + GetChildHdrAt(0, getter_AddRefs(rootHdr)); + // If the new header is a parent of the root then it should be promoted. + if (rootHdr && IsHdrParentOf(newHdr, rootHdr)) + { + m_keys.InsertElementAt(0, newHdrKey); + m_levels.InsertElementAt(0, 0); + m_folders.InsertObjectAt(newHdrFolder, 0); + whereInserted = 0; + // Adjust level of old root hdr and its children + for (nsMsgViewIndex i = 1; i < m_keys.Length(); i++) + m_levels[i] = m_levels[1] + 1; + } + else + { + m_keys.AppendElement(newHdrKey); + m_levels.AppendElement(1); + m_folders.AppendObject(newHdrFolder); + if (outParent) + NS_IF_ADDREF(*outParent = rootHdr); + whereInserted = m_keys.Length() -1; + } + } + + // ### TODO handle the case where the root header starts + // with Re, and the new one doesn't, and is earlier. In that + // case, we want to promote the new header to root. + +// PRTime newHdrDate; +// newHdr->GetDate(&newHdrDate); + +// if (numChildren > 0 && !(newHdrFlags & nsMsgMessageFlags::HasRe)) +// { +// PRTime topLevelHdrDate; + +// nsCOMPtr<nsIMsgDBHdr> topLevelHdr; +// rv = GetRootHdr(nullptr, getter_AddRefs(topLevelHdr)); +// if (NS_SUCCEEDED(rv) && topLevelHdr) +// { +// topLevelHdr->GetDate(&topLevelHdrDate); +// if (newHdrDate < topLevelHdrDate) + +// } +// } + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr **aResult) +{ + if (aIndex >= m_keys.Length()) + return NS_MSG_MESSAGE_NOT_FOUND; + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = m_folders[aIndex]->GetMsgDatabase(getter_AddRefs(db)); + NS_ENSURE_SUCCESS(rv, rv); + return db->GetMsgHdrForKey(m_keys[aIndex], aResult); +} + +NS_IMETHODIMP nsMsgXFViewThread::RemoveChildAt(uint32_t aIndex) +{ + m_keys.RemoveElementAt(aIndex); + m_levels.RemoveElementAt(aIndex); + m_folders.RemoveObjectAt(aIndex); + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::RemoveChildHdr(nsIMsgDBHdr *child, nsIDBChangeAnnouncer *announcer) +{ + NS_ENSURE_ARG_POINTER(child); + nsMsgKey msgKey; + uint32_t msgFlags; + child->GetMessageKey(&msgKey); + child->GetFlags(&msgFlags); + nsCOMPtr<nsIMsgFolder> msgFolder; + child->GetFolder(getter_AddRefs(msgFolder)); + // if this was the newest msg, clear the newest msg date so we'll recalc. + uint32_t date; + child->GetDateInSeconds(&date); + if (date == m_newestMsgDate) + SetNewestMsgDate(0); + + for (uint32_t childIndex = 0; childIndex < m_keys.Length(); childIndex++) + { + if (m_keys[childIndex] == msgKey && m_folders[childIndex] == msgFolder) + { + uint8_t levelRemoved = m_keys[childIndex]; + // Adjust the levels of all the children of this header + nsMsgViewIndex i; + for (i = childIndex + 1; + i < m_keys.Length() && m_levels[i] > levelRemoved; i++) + m_levels[i] = m_levels[i] - 1; + + m_view->NoteChange(childIndex + 1, i - childIndex + 1, + nsMsgViewNotificationCode::changed); + m_keys.RemoveElementAt(childIndex); + m_levels.RemoveElementAt(childIndex); + m_folders.RemoveObjectAt(childIndex); + if (!(msgFlags & nsMsgMessageFlags::Read)) + ChangeUnreadChildCount(-1); + ChangeChildCount(-1); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetRootHdr(int32_t *aResultIndex, nsIMsgDBHdr **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (aResultIndex) + *aResultIndex = 0; + return GetChildHdrAt(0, aResult); +} + +NS_IMETHODIMP nsMsgXFViewThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey *aResult) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr **aResult) +{ + NS_ASSERTION(false, "shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + + +int32_t nsMsgXFViewThread::HdrIndex(nsIMsgDBHdr *hdr) +{ + nsMsgKey msgKey; + nsCOMPtr<nsIMsgFolder> folder; + hdr->GetMessageKey(&msgKey); + hdr->GetFolder(getter_AddRefs(folder)); + for (uint32_t i = 0; i < m_keys.Length(); i++) + { + if (m_keys[i] == msgKey && m_folders[i] == folder) + return i; + } + return -1; +} + +void nsMsgXFViewThread::ChangeUnreadChildCount(int32_t delta) +{ + m_numUnreadChildren += delta; +} + +void nsMsgXFViewThread::ChangeChildCount(int32_t delta) +{ + m_numChildren += delta; +} + +bool nsMsgXFViewThread::IsHdrParentOf(nsIMsgDBHdr *possibleParent, + nsIMsgDBHdr *possibleChild) +{ + uint16_t referenceToCheck = 0; + possibleChild->GetNumReferences(&referenceToCheck); + nsAutoCString reference; + + nsCString messageId; + possibleParent->GetMessageId(getter_Copies(messageId)); + + while (referenceToCheck > 0) + { + possibleChild->GetStringReference(referenceToCheck - 1, reference); + + if (reference.Equals(messageId)) + return true; + // if reference didn't match, check if this ref is for a non-existent + // header. If it is, continue looking at ancestors. + nsCOMPtr<nsIMsgDBHdr> refHdr; + m_view->GetMsgHdrFromHash(reference, getter_AddRefs(refHdr)); + if (refHdr) + break; + referenceToCheck--; + } + return false; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetNewestMsgDate(uint32_t *aResult) +{ + // if this hasn't been set, figure it out by enumerating the msgs in the thread. + if (!m_newestMsgDate) + { + uint32_t numChildren; + nsresult rv = NS_OK; + + GetNumChildren(&numChildren); + + if ((int32_t) numChildren < 0) + numChildren = 0; + + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + nsCOMPtr<nsIMsgDBHdr> child; + rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + uint32_t msgDate; + child->GetDateInSeconds(&msgDate); + if (msgDate > m_newestMsgDate) + m_newestMsgDate = msgDate; + } + } + } + *aResult = m_newestMsgDate; + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::SetNewestMsgDate(uint32_t aNewestMsgDate) +{ + m_newestMsgDate = aNewestMsgDate; + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::MarkChildRead(bool aRead) +{ + ChangeUnreadChildCount(aRead ? -1 : 1); + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFViewThread::GetFirstUnreadChild(nsIMsgDBHdr **aResult) +{ + NS_ENSURE_ARG(aResult); + uint32_t numChildren; + nsresult rv = NS_OK; + + GetNumChildren(&numChildren); + + if ((int32_t) numChildren < 0) + numChildren = 0; + + for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) + { + nsCOMPtr<nsIMsgDBHdr> child; + rv = GetChildHdrAt(childIndex, getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + nsMsgKey msgKey; + child->GetMessageKey(&msgKey); + + bool isRead; + nsCOMPtr<nsIMsgDatabase> db; + nsresult rv = m_folders[childIndex]->GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv)) + rv = db->IsRead(msgKey, &isRead); + if (NS_SUCCEEDED(rv) && !isRead) + { + NS_ADDREF(*aResult = child); + break; + } + } + } + return rv; +} +NS_IMETHODIMP nsMsgXFViewThread::EnumerateMessages(nsMsgKey aParentKey, + nsISimpleEnumerator **aResult) +{ + NS_ERROR("shouldn't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mailnews/base/src/nsMsgXFViewThread.h b/mailnews/base/src/nsMsgXFViewThread.h new file mode 100644 index 0000000000..5c404afa84 --- /dev/null +++ b/mailnews/base/src/nsMsgXFViewThread.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ +#ifndef nsMsgXFViewThread_h__ +#define nsMsgXFViewThread_h__ + +#include "msgCore.h" +#include "nsCOMArray.h" +#include "nsIMsgThread.h" +#include "MailNewsTypes.h" +#include "nsTArray.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsMsgDBView.h" + +class nsMsgSearchDBView; + +class nsMsgXFViewThread : public nsIMsgThread +{ +public: + + nsMsgXFViewThread(nsMsgSearchDBView *view, nsMsgKey threadId); + + NS_DECL_NSIMSGTHREAD + NS_DECL_ISUPPORTS + + bool IsHdrParentOf(nsIMsgDBHdr *possibleParent, + nsIMsgDBHdr *possibleChild); + + void ChangeUnreadChildCount(int32_t delta); + void ChangeChildCount(int32_t delta); + + nsresult AddHdr(nsIMsgDBHdr *newHdr, bool reparentChildren, + uint32_t &whereInserted, nsIMsgDBHdr **outParent); + int32_t HdrIndex(nsIMsgDBHdr *hdr); + uint32_t ChildLevelAt(uint32_t msgIndex) {return m_levels[msgIndex];} + uint32_t MsgCount() {return m_numChildren;}; + +protected: + virtual ~nsMsgXFViewThread(); + + nsMsgSearchDBView *m_view; + uint32_t m_numUnreadChildren; + uint32_t m_numChildren; + uint32_t m_flags; + uint32_t m_newestMsgDate; + nsMsgKey m_threadId; + nsTArray<nsMsgKey> m_keys; + nsCOMArray<nsIMsgFolder> m_folders; + nsTArray<uint8_t> m_levels; +}; + +#endif diff --git a/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp new file mode 100644 index 0000000000..cdc4f0b5a7 --- /dev/null +++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp @@ -0,0 +1,514 @@ +/* -*- 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 "msgCore.h" +#include "nsMsgXFVirtualFolderDBView.h" +#include "nsIMsgHdr.h" +#include "nsIMsgThread.h" +#include "nsQuickSort.h" +#include "nsIDBFolderInfo.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgCopyService.h" +#include "nsICopyMsgStreamListener.h" +#include "nsMsgUtils.h" +#include "nsITreeColumns.h" +#include "nsIMsgSearchSession.h" +#include "nsMsgDBCID.h" +#include "nsMsgMessageFlags.h" +#include "nsServiceManagerUtils.h" + +nsMsgXFVirtualFolderDBView::nsMsgXFVirtualFolderDBView() +{ + mSuppressMsgDisplay = false; + m_doingSearch = false; + m_doingQuickSearch = false; + m_totalMessagesInView = 0; +} + +nsMsgXFVirtualFolderDBView::~nsMsgXFVirtualFolderDBView() +{ +} + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder *folder, + nsMsgViewSortTypeValue sortType, + nsMsgViewSortOrderValue sortOrder, + nsMsgViewFlagsTypeValue viewFlags, + int32_t *pCount) +{ + m_viewFolder = folder; + return nsMsgSearchDBView::Open(folder, sortType, sortOrder, viewFlags, pCount); +} + +void nsMsgXFVirtualFolderDBView::RemovePendingDBListeners() +{ + nsresult rv; + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + // UnregisterPendingListener will return an error when there are no more instances + // of this object registered as pending listeners. + while (NS_SUCCEEDED(rv)) + rv = msgDBService->UnregisterPendingListener(this); +} + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::Close() +{ + RemovePendingDBListeners(); + return nsMsgSearchDBView::Close(); +} + +NS_IMETHODIMP +nsMsgXFVirtualFolderDBView::CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) +{ + nsMsgXFVirtualFolderDBView* newMsgDBView = new nsMsgXFVirtualFolderDBView(); + + if (!newMsgDBView) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = newMsgDBView); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgXFVirtualFolderDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) +{ + nsMsgSearchDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); + + nsMsgXFVirtualFolderDBView* newMsgDBView = (nsMsgXFVirtualFolderDBView *) aNewMsgDBView; + + newMsgDBView->m_viewFolder = m_viewFolder; + newMsgDBView->m_searchSession = m_searchSession; + + int32_t scopeCount; + nsresult rv; + nsCOMPtr <nsIMsgSearchSession> searchSession = + do_QueryReferent(m_searchSession, &rv); + // It's OK not to have a search session. + NS_ENSURE_SUCCESS(rv, NS_OK); + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + searchSession->CountSearchScopes(&scopeCount); + for (int32_t i = 0; i < scopeCount; i++) + { + nsMsgSearchScopeValue scopeId; + nsCOMPtr<nsIMsgFolder> searchFolder; + searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder)); + if (searchFolder) + msgDBService->RegisterPendingListener(searchFolder, newMsgDBView); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue *aViewType) +{ + NS_ENSURE_ARG_POINTER(aViewType); + *aViewType = nsMsgViewType::eShowVirtualFolderResults; + return NS_OK; +} + +nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool /*ensureListed*/) +{ + if (newHdr) + { + bool match = false; + nsCOMPtr <nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession); + if (searchSession) + searchSession->MatchHdr(newHdr, m_db, &match); + if (!match) + match = WasHdrRecentlyDeleted(newHdr); + if (match) + { + nsCOMPtr <nsIMsgFolder> folder; + newHdr->GetFolder(getter_AddRefs(folder)); + bool saveDoingSearch = m_doingSearch; + m_doingSearch = false; + OnSearchHit(newHdr, folder); + m_doingSearch = saveDoingSearch; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::OnHdrPropertyChanged(nsIMsgDBHdr *aHdrChanged, + bool aPreChange, uint32_t *aStatus, nsIDBChangeListener *aInstigator) +{ + // If the junk mail plugin just activated on a message, then + // we'll allow filters to remove from view. + // Otherwise, just update the view line. + // + // Note this will not add newly matched headers to the view. This is + // probably a bug that needs fixing. + + NS_ENSURE_ARG_POINTER(aStatus); + NS_ENSURE_ARG_POINTER(aHdrChanged); + + nsMsgViewIndex index = FindHdr(aHdrChanged); + if (index == nsMsgViewIndex_None) // message does not appear in view + return NS_OK; + + nsCString originStr; + (void) aHdrChanged->GetStringProperty("junkscoreorigin", getter_Copies(originStr)); + // check for "plugin" with only first character for performance + bool plugin = (originStr.get()[0] == 'p'); + + if (aPreChange) + { + // first call, done prior to the change + *aStatus = plugin; + return NS_OK; + } + + // second call, done after the change + bool wasPlugin = *aStatus; + + bool match = true; + nsCOMPtr<nsIMsgSearchSession> searchSession(do_QueryReferent(m_searchSession)); + if (searchSession) + searchSession->MatchHdr(aHdrChanged, m_db, &match); + + if (!match && plugin && !wasPlugin) + RemoveByIndex(index); // remove hdr from view + else + NoteChange(index, 1, nsMsgViewNotificationCode::changed); + + return NS_OK; +} + +void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, uint32_t numNewHits) +{ + nsCOMPtr <nsIMsgDatabase> db; + nsresult rv = folder->GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + { + nsCString searchUri; + m_viewFolder->GetURI(searchUri); + uint32_t numBadHits; + nsMsgKey *badHits; + rv = db->RefreshCache(searchUri.get(), numNewHits, newHits, + &numBadHits, &badHits); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgDBHdr> badHdr; + for (uint32_t badHitIndex = 0; badHitIndex < numBadHits; badHitIndex++) + { + // ### of course, this isn't quite right, since we should be + // using FindHdr, and we shouldn't be expanding the threads. + db->GetMsgHdrForKey(badHits[badHitIndex], getter_AddRefs(badHdr)); + // let nsMsgSearchDBView decide what to do about this header + // getting removed. + if (badHdr) + OnHdrDeleted(badHdr, nsMsgKey_None, 0, this); + } + delete [] badHits; + } + } +} + +void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder) +{ + // Handle the most recent folder with hits, if any. + if (m_curFolderGettingHits) + { + uint32_t count = m_hdrHits.Count(); + nsTArray<nsMsgKey> newHits; + newHits.SetLength(count); + for (uint32_t i = 0; i < count; i++) + m_hdrHits[i]->GetMessageKey(&newHits[i]); + + newHits.Sort(); + UpdateCacheAndViewForFolder(m_curFolderGettingHits, newHits.Elements(), newHits.Length()); + m_foldersSearchingOver.RemoveObject(m_curFolderGettingHits); + } + + while (m_foldersSearchingOver.Count() > 0) + { + // this new folder has cached hits. + if (m_foldersSearchingOver[0] == curSearchFolder) + { + m_curFolderHasCachedHits = true; + m_foldersSearchingOver.RemoveObjectAt(0); + break; + } + else + { + // this must be a folder that had no hits with the current search. + // So all cached hits, if any, need to be removed. + UpdateCacheAndViewForFolder(m_foldersSearchingOver[0], nullptr, 0); + m_foldersSearchingOver.RemoveObjectAt(0); + } + } +} +NS_IMETHODIMP +nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *aFolder) +{ + NS_ENSURE_ARG(aMsgHdr); + NS_ENSURE_ARG(aFolder); + + nsCOMPtr<nsIMsgDatabase> dbToUse; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + aFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse)); + + if (m_curFolderGettingHits != aFolder && m_doingSearch && !m_doingQuickSearch) + { + m_curFolderHasCachedHits = false; + // since we've gotten a hit for a new folder, the searches for + // any previous folders are done, so deal with stale cached hits + // for those folders now. + UpdateCacheAndViewForPrevSearchedFolders(aFolder); + m_curFolderGettingHits = aFolder; + m_hdrHits.Clear(); + m_curFolderStartKeyIndex = m_keys.Length(); + } + bool hdrInCache = false; + nsCString searchUri; + if (!m_doingQuickSearch) + { + m_viewFolder->GetURI(searchUri); + dbToUse->HdrIsInCache(searchUri.get(), aMsgHdr, &hdrInCache); + } + if (!m_doingSearch || !m_curFolderHasCachedHits || !hdrInCache) + { + if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) + nsMsgGroupView::OnNewHeader(aMsgHdr, nsMsgKey_None, true); + else if (m_sortValid) + InsertHdrFromFolder(aMsgHdr, aFolder); + else + AddHdrFromFolder(aMsgHdr, aFolder); + } + m_hdrHits.AppendObject(aMsgHdr); + m_totalMessagesInView++; + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status) +{ + NS_ENSURE_TRUE(m_viewFolder, NS_ERROR_NOT_INITIALIZED); + + // handle any non verified hits we haven't handled yet. + if (NS_SUCCEEDED(status) && !m_doingQuickSearch && status != NS_MSG_SEARCH_INTERRUPTED) + UpdateCacheAndViewForPrevSearchedFolders(nullptr); + + m_doingSearch = false; + //we want to set imap delete model once the search is over because setting next + //message after deletion will happen before deleting the message and search scope + //can change with every search. + mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; //set to default in case it is non-imap folder + nsIMsgFolder *curFolder = m_folders.SafeObjectAt(0); + if (curFolder) + GetImapDeleteModel(curFolder); + + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + // count up the number of unread and total messages from the view, and set those in the + // folder - easier than trying to keep the count up to date in the face of + // search hits coming in while the user is reading/deleting messages. + uint32_t numUnread = 0; + for (uint32_t i = 0; i < m_flags.Length(); i++) + if (m_flags[i] & nsMsgMessageFlags::Elided) + { + nsCOMPtr<nsIMsgThread> thread; + GetThreadContainingIndex(i, getter_AddRefs(thread)); + if (thread) + { + uint32_t unreadInThread; + thread->GetNumUnreadChildren(&unreadInThread); + numUnread += unreadInThread; + } + } + else + { + if (!(m_flags[i] & nsMsgMessageFlags::Read)) + numUnread++; + } + dbFolderInfo->SetNumUnreadMessages(numUnread); + dbFolderInfo->SetNumMessages(m_totalMessagesInView); + m_viewFolder->UpdateSummaryTotals(true); // force update from db. + virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit); + if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread && + !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + m_sortValid = false; //sort the results + Sort(m_sortType, m_sortOrder); + } + m_foldersSearchingOver.Clear(); + m_curFolderGettingHits = nullptr; + return rv; +} + + +NS_IMETHODIMP +nsMsgXFVirtualFolderDBView::OnNewSearch() +{ + int32_t oldSize = GetSize(); + + RemovePendingDBListeners(); + m_doingSearch = true; + m_totalMessagesInView = 0; + m_folders.Clear(); + m_keys.Clear(); + m_levels.Clear(); + m_flags.Clear(); + + // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount() + if (mTree) + mTree->RowCountChanged(0, -oldSize); + + // to use the search results cache, we'll need to iterate over the scopes in the + // search session, calling getNthSearchScope for i = 0; i < searchSession.countSearchScopes; i++ + // and for each folder, then open the db and pull out the cached hits, add them to the view. + // For each hit in a new folder, we'll then clean up the stale hits from the previous folder(s). + + int32_t scopeCount; + nsCOMPtr<nsIMsgSearchSession> searchSession = do_QueryReferent(m_searchSession); + NS_ENSURE_TRUE(searchSession, NS_OK); // just ignore + nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID); + searchSession->CountSearchScopes(&scopeCount); + + // Figure out how many search terms the virtual folder has. + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString terms; + dbFolderInfo->GetCharProperty("searchStr", terms); + nsCOMPtr<nsISupportsArray> searchTerms; + rv = searchSession->GetSearchTerms(getter_AddRefs(searchTerms)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString curSearchAsString; + + rv = MsgTermListToString(searchTerms, curSearchAsString); + // Trim off the initial AND/OR, which is irrelevant and inconsistent between + // what searchSpec.js generates, and what's in virtualFolders.dat. + curSearchAsString.Cut(0, StringBeginsWith(curSearchAsString, NS_LITERAL_CSTRING("AND")) ? 3 : 2); + terms.Cut(0, StringBeginsWith(terms, NS_LITERAL_CSTRING("AND")) ? 3 : 2); + + NS_ENSURE_SUCCESS(rv, rv); + // If the search session search string doesn't match the vf search str, then we're doing + // quick search, which means we don't want to invalidate cached results, or + // used cached results. + m_doingQuickSearch = !curSearchAsString.Equals(terms); + + if (mTree && !m_doingQuickSearch) + mTree->BeginUpdateBatch(); + + for (int32_t i = 0; i < scopeCount; i++) + { + nsMsgSearchScopeValue scopeId; + nsCOMPtr<nsIMsgFolder> searchFolder; + searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder)); + if (searchFolder) + { + nsCOMPtr<nsISimpleEnumerator> cachedHits; + nsCOMPtr<nsIMsgDatabase> searchDB; + nsCString searchUri; + m_viewFolder->GetURI(searchUri); + nsresult rv = searchFolder->GetMsgDatabase(getter_AddRefs(searchDB)); + if (NS_SUCCEEDED(rv) && searchDB) + { + if (msgDBService) + msgDBService->RegisterPendingListener(searchFolder, this); + + m_foldersSearchingOver.AppendObject(searchFolder); + if (m_doingQuickSearch) // ignore cached hits in quick search case. + continue; + searchDB->GetCachedHits(searchUri.get(), getter_AddRefs(cachedHits)); + bool hasMore; + if (cachedHits) + { + cachedHits->HasMoreElements(&hasMore); + if (hasMore) + { + mozilla::DebugOnly<nsMsgKey> prevKey = nsMsgKey_None; + while (hasMore) + { + nsCOMPtr <nsISupports> supports; + nsresult rv = cachedHits->GetNext(getter_AddRefs(supports)); + NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); + nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports); + if (pHeader && NS_SUCCEEDED(rv)) + { + nsMsgKey msgKey; + pHeader->GetMessageKey(&msgKey); + NS_ASSERTION(prevKey == nsMsgKey_None || msgKey > prevKey, + "cached Hits not sorted"); +#ifdef DEBUG + prevKey = msgKey; +#endif + AddHdrFromFolder(pHeader, searchFolder); + } + else + break; + cachedHits->HasMoreElements(&hasMore); + } + } + } + } + } + } + if (mTree && !m_doingQuickSearch) + mTree->EndUpdateBatch(); + + m_curFolderStartKeyIndex = 0; + m_curFolderGettingHits = nullptr; + m_curFolderHasCachedHits = false; + + // if we have cached hits, sort them. + if (GetSize() > 0) + { + // currently, we keep threaded views sorted while we build them. + if (m_sortType != nsMsgViewSortType::byThread && + !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) + { + m_sortValid = false; //sort the results + Sort(m_sortType, m_sortOrder); + } + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::DoCommand(nsMsgViewCommandTypeValue command) +{ + return nsMsgSearchDBView::DoCommand(command); +} + + + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::GetMsgFolder(nsIMsgFolder **aMsgFolder) +{ + NS_ENSURE_ARG_POINTER(aMsgFolder); + NS_IF_ADDREF(*aMsgFolder = m_viewFolder); + return NS_OK; +} + +NS_IMETHODIMP nsMsgXFVirtualFolderDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) +{ + nsresult rv = NS_OK; + // if the grouping/threading has changed, rebuild the view + if ((m_viewFlags & (nsMsgViewFlagsType::kGroupBySort | + nsMsgViewFlagsType::kThreadedDisplay)) != + (aViewFlags & (nsMsgViewFlagsType::kGroupBySort | + nsMsgViewFlagsType::kThreadedDisplay))) + rv = RebuildView(aViewFlags); + nsMsgDBView::SetViewFlags(aViewFlags); + return rv; +} + + +nsresult +nsMsgXFVirtualFolderDBView::GetMessageEnumerator(nsISimpleEnumerator **enumerator) +{ + return GetViewEnumerator(enumerator); +} diff --git a/mailnews/base/src/nsMsgXFVirtualFolderDBView.h b/mailnews/base/src/nsMsgXFVirtualFolderDBView.h new file mode 100644 index 0000000000..77a2623d42 --- /dev/null +++ b/mailnews/base/src/nsMsgXFVirtualFolderDBView.h @@ -0,0 +1,61 @@ +/* -*- 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/. */ + +#ifndef _nsMsgXFVirtualFolderDBView_H_ +#define _nsMsgXFVirtualFolderDBView_H_ + +#include "mozilla/Attributes.h" +#include "nsMsgSearchDBView.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIMsgSearchNotify.h" +#include "nsCOMArray.h" + +class nsMsgGroupThread; + +class nsMsgXFVirtualFolderDBView : public nsMsgSearchDBView +{ +public: + nsMsgXFVirtualFolderDBView(); + virtual ~nsMsgXFVirtualFolderDBView(); + + // we override all the methods, currently. Might change... + NS_DECL_NSIMSGSEARCHNOTIFY + + virtual const char * GetViewName(void) override {return "XFVirtualFolderView"; } + NS_IMETHOD Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, + nsMsgViewFlagsTypeValue viewFlags, int32_t *pCount) override; + NS_IMETHOD CloneDBView(nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, + nsIMsgDBViewCommandUpdater *aCmdUpdater, nsIMsgDBView **_retval) override; + NS_IMETHOD CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, + nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) override; + NS_IMETHOD Close() override; + NS_IMETHOD GetViewType(nsMsgViewTypeValue *aViewType) override; + NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command) override; + NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) override; + NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr *aHdrToChange, bool aPreChange, uint32_t *aStatus, + nsIDBChangeListener * aInstigator) override; + NS_IMETHOD GetMsgFolder(nsIMsgFolder **aMsgFolder) override; + + virtual nsresult OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey parentKey, bool ensureListed) override; + void UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder *curSearchFolder); + void UpdateCacheAndViewForFolder(nsIMsgFolder *folder, nsMsgKey *newHits, uint32_t numNewHits); + void RemovePendingDBListeners(); + +protected: + + virtual nsresult GetMessageEnumerator(nsISimpleEnumerator **enumerator) override; + + uint32_t m_cachedFolderArrayIndex; // array index of next folder with cached hits to deal with. + nsCOMArray<nsIMsgFolder> m_foldersSearchingOver; + nsCOMArray<nsIMsgDBHdr> m_hdrHits; + nsCOMPtr <nsIMsgFolder> m_curFolderGettingHits; + uint32_t m_curFolderStartKeyIndex; // keeps track of the index of the first hit from the cur folder + bool m_curFolderHasCachedHits; + bool m_doingSearch; + // Are we doing a quick search on top of the virtual folder search? + bool m_doingQuickSearch; +}; + +#endif diff --git a/mailnews/base/src/nsSpamSettings.cpp b/mailnews/base/src/nsSpamSettings.cpp new file mode 100644 index 0000000000..e8318b75af --- /dev/null +++ b/mailnews/base/src/nsSpamSettings.cpp @@ -0,0 +1,892 @@ +/* -*- 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 "nsSpamSettings.h" +#include "nsIFile.h" +#include "plstr.h" +#include "prmem.h" +#include "nsIMsgHdr.h" +#include "nsNetUtil.h" +#include "nsIMsgFolder.h" +#include "nsMsgUtils.h" +#include "nsMsgFolderFlags.h" +#include "nsImapCore.h" +#include "nsIImapIncomingServer.h" +#include "nsIRDFService.h" +#include "nsIRDFResource.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIStringBundle.h" +#include "nsDateTimeFormatCID.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" +#include "nsMailDirServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsISimpleEnumerator.h" +#include "nsIDirectoryEnumerator.h" +#include "nsAbBaseCID.h" +#include "nsIAbManager.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" + +using namespace mozilla::mailnews; + +nsSpamSettings::nsSpamSettings() +{ + mLevel = 0; + mMoveOnSpam = false; + mMoveTargetMode = nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT; + mPurge = false; + mPurgeInterval = 14; // 14 days + + mServerFilterTrustFlags = 0; + + mUseWhiteList = false; + mUseServerFilter = false; + + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mLogFile)); + if (NS_SUCCEEDED(rv)) + mLogFile->Append(NS_LITERAL_STRING("junklog.html")); +} + +nsSpamSettings::~nsSpamSettings() +{ +} + +NS_IMPL_ISUPPORTS(nsSpamSettings, nsISpamSettings, nsIUrlListener) + +NS_IMETHODIMP +nsSpamSettings::GetLevel(int32_t *aLevel) +{ + NS_ENSURE_ARG_POINTER(aLevel); + *aLevel = mLevel; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::SetLevel(int32_t aLevel) +{ + NS_ASSERTION((aLevel >= 0 && aLevel <= 100), "bad level"); + mLevel = aLevel; + return NS_OK; +} + +NS_IMETHODIMP +nsSpamSettings::GetMoveTargetMode(int32_t *aMoveTargetMode) +{ + NS_ENSURE_ARG_POINTER(aMoveTargetMode); + *aMoveTargetMode = mMoveTargetMode; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::SetMoveTargetMode(int32_t aMoveTargetMode) +{ + NS_ASSERTION((aMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_FOLDER || aMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT), "bad move target mode"); + mMoveTargetMode = aMoveTargetMode; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::GetManualMark(bool *aManualMark) +{ + NS_ENSURE_ARG_POINTER(aManualMark); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefBranch->GetBoolPref("mail.spam.manualMark", aManualMark); +} + +NS_IMETHODIMP nsSpamSettings::GetManualMarkMode(int32_t *aManualMarkMode) +{ + NS_ENSURE_ARG_POINTER(aManualMarkMode); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefBranch->GetIntPref("mail.spam.manualMarkMode", aManualMarkMode); +} + +NS_IMETHODIMP nsSpamSettings::GetLoggingEnabled(bool *aLoggingEnabled) +{ + NS_ENSURE_ARG_POINTER(aLoggingEnabled); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefBranch->GetBoolPref("mail.spam.logging.enabled", aLoggingEnabled); +} + +NS_IMETHODIMP nsSpamSettings::GetMarkAsReadOnSpam(bool *aMarkAsReadOnSpam) +{ + NS_ENSURE_ARG_POINTER(aMarkAsReadOnSpam); + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return prefBranch->GetBoolPref("mail.spam.markAsReadOnSpam", aMarkAsReadOnSpam); +} + +NS_IMPL_GETSET(nsSpamSettings, MoveOnSpam, bool, mMoveOnSpam) +NS_IMPL_GETSET(nsSpamSettings, Purge, bool, mPurge) +NS_IMPL_GETSET(nsSpamSettings, UseWhiteList, bool, mUseWhiteList) +NS_IMPL_GETSET(nsSpamSettings, UseServerFilter, bool, mUseServerFilter) + +NS_IMETHODIMP nsSpamSettings::GetWhiteListAbURI(char * *aWhiteListAbURI) +{ + NS_ENSURE_ARG_POINTER(aWhiteListAbURI); + *aWhiteListAbURI = ToNewCString(mWhiteListAbURI); + return NS_OK; +} +NS_IMETHODIMP nsSpamSettings::SetWhiteListAbURI(const char * aWhiteListAbURI) +{ + mWhiteListAbURI = aWhiteListAbURI; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::GetActionTargetAccount(char * *aActionTargetAccount) +{ + NS_ENSURE_ARG_POINTER(aActionTargetAccount); + *aActionTargetAccount = ToNewCString(mActionTargetAccount); + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::SetActionTargetAccount(const char * aActionTargetAccount) +{ + mActionTargetAccount = aActionTargetAccount; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::GetActionTargetFolder(char * *aActionTargetFolder) +{ + NS_ENSURE_ARG_POINTER(aActionTargetFolder); + *aActionTargetFolder = ToNewCString(mActionTargetFolder); + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::SetActionTargetFolder(const char * aActionTargetFolder) +{ + mActionTargetFolder = aActionTargetFolder; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::GetPurgeInterval(int32_t *aPurgeInterval) +{ + NS_ENSURE_ARG_POINTER(aPurgeInterval); + *aPurgeInterval = mPurgeInterval; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::SetPurgeInterval(int32_t aPurgeInterval) +{ + NS_ASSERTION(aPurgeInterval >= 0, "bad purge interval"); + mPurgeInterval = aPurgeInterval; + return NS_OK; +} + +NS_IMETHODIMP +nsSpamSettings::SetLogStream(nsIOutputStream *aLogStream) +{ + // if there is a log stream already, close it + if (mLogStream) { + // will flush + nsresult rv = mLogStream->Close(); + NS_ENSURE_SUCCESS(rv,rv); + } + + mLogStream = aLogStream; + return NS_OK; +} + +#define LOG_HEADER "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style type=\"text/css\">body{font-family:Consolas,\"Lucida Console\",Monaco,\"Courier New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n" +#define LOG_HEADER_LEN (strlen(LOG_HEADER)) + +NS_IMETHODIMP +nsSpamSettings::GetLogStream(nsIOutputStream **aLogStream) +{ + NS_ENSURE_ARG_POINTER(aLogStream); + + nsresult rv; + + if (!mLogStream) { + // append to the end of the log file + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mLogStream), + mLogFile, + PR_CREATE_FILE | PR_WRONLY | PR_APPEND, + 0600); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t fileSize; + rv = mLogFile->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + // write the header at the start + if (fileSize == 0) + { + uint32_t writeCount; + + rv = mLogStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(writeCount == LOG_HEADER_LEN, "failed to write out log header"); + } + } + + NS_ADDREF(*aLogStream = mLogStream); + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::Initialize(nsIMsgIncomingServer *aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + nsresult rv; + int32_t spamLevel; + rv = aServer->GetIntValue("spamLevel", &spamLevel); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLevel(spamLevel); + NS_ENSURE_SUCCESS(rv, rv); + + bool moveOnSpam; + rv = aServer->GetBoolValue("moveOnSpam", &moveOnSpam); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetMoveOnSpam(moveOnSpam); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t moveTargetMode; + rv = aServer->GetIntValue("moveTargetMode", &moveTargetMode); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetMoveTargetMode(moveTargetMode); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spamActionTargetAccount; + rv = aServer->GetCharValue("spamActionTargetAccount", spamActionTargetAccount); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetActionTargetAccount(spamActionTargetAccount.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spamActionTargetFolder; + rv = aServer->GetCharValue("spamActionTargetFolder", spamActionTargetFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetActionTargetFolder(spamActionTargetFolder.get()); + NS_ENSURE_SUCCESS(rv, rv); + + bool useWhiteList; + rv = aServer->GetBoolValue("useWhiteList", &useWhiteList); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetUseWhiteList(useWhiteList); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString whiteListAbURI; + rv = aServer->GetCharValue("whiteListAbURI", whiteListAbURI); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetWhiteListAbURI(whiteListAbURI.get()); + NS_ENSURE_SUCCESS(rv, rv); + + bool purgeSpam; + rv = aServer->GetBoolValue("purgeSpam", &purgeSpam); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPurge(purgeSpam); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t purgeSpamInterval; + rv = aServer->GetIntValue("purgeSpamInterval", &purgeSpamInterval); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetPurgeInterval(purgeSpamInterval); + NS_ENSURE_SUCCESS(rv, rv); + + bool useServerFilter; + rv = aServer->GetBoolValue("useServerFilter", &useServerFilter); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetUseServerFilter(useServerFilter); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString serverFilterName; + rv = aServer->GetCharValue("serverFilterName", serverFilterName); + if (NS_SUCCEEDED(rv)) + SetServerFilterName(serverFilterName); + int32_t serverFilterTrustFlags = 0; + rv = aServer->GetIntValue("serverFilterTrustFlags", &serverFilterTrustFlags); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetServerFilterTrustFlags(serverFilterTrustFlags); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (prefBranch) + prefBranch->GetCharPref("mail.trusteddomains", + getter_Copies(mTrustedMailDomains)); + + mWhiteListDirArray.Clear(); + if (!mWhiteListAbURI.IsEmpty()) + { + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<nsCString> whiteListArray; + ParseString(mWhiteListAbURI, ' ', whiteListArray); + + for (uint32_t index = 0; index < whiteListArray.Length(); index++) + { + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(whiteListArray[index], + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + if (directory) + mWhiteListDirArray.AppendObject(directory); + } + } + + // the next two preferences affect whether we try to whitelist our own + // address or domain. Spammers send emails with spoofed from address matching + // either the email address of the recipient, or the recipient's domain, + // hoping to get whitelisted. + // + // The terms to describe this get wrapped up in chains of negatives. A full + // definition of the boolean inhibitWhiteListingIdentityUser is "Suppress address + // book whitelisting if the sender matches an identity's email address" + + rv = aServer->GetBoolValue("inhibitWhiteListingIdentityUser", + &mInhibitWhiteListingIdentityUser); + NS_ENSURE_SUCCESS(rv, rv); + rv = aServer->GetBoolValue("inhibitWhiteListingIdentityDomain", + &mInhibitWhiteListingIdentityDomain); + NS_ENSURE_SUCCESS(rv, rv); + + // collect lists of identity users if needed + if (mInhibitWhiteListingIdentityDomain || mInhibitWhiteListingIdentityUser) + { + nsCOMPtr<nsIMsgAccountManager> + accountManager(do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString accountKey; + if (account) + account->GetKey(accountKey); + + // Loop through all accounts, adding emails from this account, as well as + // from any accounts that defer to this account. + mEmails.Clear(); + nsCOMPtr<nsIArray> accounts; + rv = accountManager->GetAccounts(getter_AddRefs(accounts)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t accountCount = 0; + if (account && accounts) // no sense scanning accounts if we've nothing to match + accounts->GetLength(&accountCount); + + for (uint32_t i = 0; i < accountCount; i++) + { + nsCOMPtr<nsIMsgAccount> loopAccount(do_QueryElementAt(accounts, i)); + if (!loopAccount) + continue; + nsAutoCString loopAccountKey; + loopAccount->GetKey(loopAccountKey); + nsCOMPtr<nsIMsgIncomingServer> loopServer; + loopAccount->GetIncomingServer(getter_AddRefs(loopServer)); + nsAutoCString deferredToAccountKey; + if (loopServer) + loopServer->GetCharValue("deferred_to_account", deferredToAccountKey); + + // Add the emails for any account that defers to this one, or for the + // account itself. + if (accountKey.Equals(deferredToAccountKey) || accountKey.Equals(loopAccountKey)) + { + nsCOMPtr<nsIArray> identities; + loopAccount->GetIdentities(getter_AddRefs(identities)); + if (!identities) + continue; + uint32_t identityCount = 0; + identities->GetLength(&identityCount); + for (uint32_t j = 0; j < identityCount; ++j) + { + nsCOMPtr<nsIMsgIdentity> identity(do_QueryElementAt(identities, j, &rv)); + if (NS_FAILED(rv) || !identity) + continue; + nsAutoCString email; + identity->GetEmail(email); + if (!email.IsEmpty()) + mEmails.AppendElement(email); + } + } + } + } + + return UpdateJunkFolderState(); +} + +nsresult nsSpamSettings::UpdateJunkFolderState() +{ + nsresult rv; + + // if the spam folder uri changed on us, we need to unset the junk flag + // on the old spam folder + nsCString newJunkFolderURI; + rv = GetSpamFolderURI(getter_Copies(newJunkFolderURI)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mCurrentJunkFolderURI.IsEmpty() && !mCurrentJunkFolderURI.Equals(newJunkFolderURI)) + { + nsCOMPtr<nsIMsgFolder> oldJunkFolder; + rv = GetExistingFolder(mCurrentJunkFolderURI, getter_AddRefs(oldJunkFolder)); + if (NS_SUCCEEDED(rv) && oldJunkFolder) + { + // remove the nsMsgFolderFlags::Junk on the old junk folder + // XXX TODO + // JUNK MAIL RELATED + // (in ClearFlag?) we need to make sure that this folder + // is not the junk folder for another account + // the same goes for set flag. have fun with all that. + oldJunkFolder->ClearFlag(nsMsgFolderFlags::Junk); + } + } + + mCurrentJunkFolderURI = newJunkFolderURI; + + // only try to create the junk folder if we are moving junk + // and we have a non-empty uri + if (mMoveOnSpam && !mCurrentJunkFolderURI.IsEmpty()) { + // as the url listener, the spam settings will set the nsMsgFolderFlags::Junk folder flag + // on the junk mail folder, after it is created + rv = GetOrCreateFolder(mCurrentJunkFolderURI, this); + } + + return rv; +} + +NS_IMETHODIMP nsSpamSettings::Clone(nsISpamSettings *aSpamSettings) +{ + NS_ENSURE_ARG_POINTER(aSpamSettings); + + nsresult rv = aSpamSettings->GetUseWhiteList(&mUseWhiteList); + NS_ENSURE_SUCCESS(rv,rv); + + (void)aSpamSettings->GetMoveOnSpam(&mMoveOnSpam); + (void)aSpamSettings->GetPurge(&mPurge); + (void)aSpamSettings->GetUseServerFilter(&mUseServerFilter); + + rv = aSpamSettings->GetPurgeInterval(&mPurgeInterval); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aSpamSettings->GetLevel(&mLevel); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aSpamSettings->GetMoveTargetMode(&mMoveTargetMode); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString actionTargetAccount; + rv = aSpamSettings->GetActionTargetAccount(getter_Copies(actionTargetAccount)); + NS_ENSURE_SUCCESS(rv,rv); + mActionTargetAccount = actionTargetAccount; + + nsCString actionTargetFolder; + rv = aSpamSettings->GetActionTargetFolder(getter_Copies(actionTargetFolder)); + NS_ENSURE_SUCCESS(rv,rv); + mActionTargetFolder = actionTargetFolder; + + nsCString whiteListAbURI; + rv = aSpamSettings->GetWhiteListAbURI(getter_Copies(whiteListAbURI)); + NS_ENSURE_SUCCESS(rv,rv); + mWhiteListAbURI = whiteListAbURI; + + aSpamSettings->GetServerFilterName(mServerFilterName); + aSpamSettings->GetServerFilterTrustFlags(&mServerFilterTrustFlags); + + return rv; +} + +NS_IMETHODIMP nsSpamSettings::GetSpamFolderURI(char **aSpamFolderURI) +{ + NS_ENSURE_ARG_POINTER(aSpamFolderURI); + + if (mMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_FOLDER) + return GetActionTargetFolder(aSpamFolderURI); + + // if the mode is nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT + // the spam folder URI = account uri + "/Junk" + nsCString folderURI; + nsresult rv = GetActionTargetAccount(getter_Copies(folderURI)); + NS_ENSURE_SUCCESS(rv,rv); + + // we might be trying to get the old spam folder uri + // in order to clear the flag + // if we didn't have one, bail out. + if (folderURI.IsEmpty()) + return NS_OK; + + nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIRDFResource> folderResource; + rv = rdf->GetResource(folderURI, getter_AddRefs(folderResource)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(folderResource); + if (!folder) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr <nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + + // see nsMsgFolder::SetPrettyName() for where the pretty name is set. + + // Check for an existing junk folder - this will do a case-insensitive + // search by URI - if we find a junk folder, use its URI. + nsCOMPtr<nsIMsgFolder> junkFolder; + folderURI.Append("/Junk"); + if (NS_SUCCEEDED(server->GetMsgFolderFromURI(nullptr, folderURI, + getter_AddRefs(junkFolder))) && + junkFolder) + junkFolder->GetURI(folderURI); + + // XXX todo + // better not to make base depend in imap + // but doing it here, like in nsMsgCopy.cpp + // one day, we'll fix this (and nsMsgCopy.cpp) to use GetMsgFolderFromURI() + nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server); + if (imapServer) { + // Make sure an specific IMAP folder has correct personal namespace + // see bug #197043 + nsCString folderUriWithNamespace; + (void)imapServer->GetUriWithNamespacePrefixIfNecessary(kPersonalNamespace, folderURI, + folderUriWithNamespace); + if (!folderUriWithNamespace.IsEmpty()) + folderURI = folderUriWithNamespace; + } + + *aSpamFolderURI = ToNewCString(folderURI); + if (!*aSpamFolderURI) + return NS_ERROR_OUT_OF_MEMORY; + else + return rv; +} + +NS_IMETHODIMP nsSpamSettings::GetServerFilterName(nsACString &aFilterName) +{ + aFilterName = mServerFilterName; + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::SetServerFilterName(const nsACString &aFilterName) +{ + mServerFilterName = aFilterName; + mServerFilterFile = nullptr; // clear out our stored location value + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::GetServerFilterFile(nsIFile ** aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + if (!mServerFilterFile) + { + nsresult rv; + nsAutoCString serverFilterFileName; + GetServerFilterName(serverFilterFileName); + serverFilterFileName.Append(".sfd"); + + nsCOMPtr<nsIProperties> dirSvc = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Walk through the list of isp directories + nsCOMPtr<nsISimpleEnumerator> ispDirectories; + rv = dirSvc->Get(ISP_DIRECTORY_LIST, NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(ispDirectories)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(ispDirectories->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> elem; + ispDirectories->GetNext(getter_AddRefs(elem)); + file = do_QueryInterface(elem); + + if (file) + { + // append our desired leaf name then test to see if the file exists. If it does, we've found + // mServerFilterFile. + file->AppendNative(serverFilterFileName); + bool exists; + if (NS_SUCCEEDED(file->Exists(&exists)) && exists) + { + file.swap(mServerFilterFile); + break; + } + } // if file + } // until we find the location of mServerFilterName + } // if we haven't already stored mServerFilterFile + + NS_IF_ADDREF(*aFile = mServerFilterFile); + return NS_OK; +} + + +NS_IMPL_GETSET(nsSpamSettings, ServerFilterTrustFlags, int32_t, mServerFilterTrustFlags) + +#define LOG_ENTRY_START_TAG "<p>\n" +#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG)) +#define LOG_ENTRY_END_TAG "</p>\n" +#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG)) +// Does this need to be localizable? +#define LOG_ENTRY_TIMESTAMP "[$S] " + +NS_IMETHODIMP nsSpamSettings::LogJunkHit(nsIMsgDBHdr *aMsgHdr, bool aMoveMessage) +{ + bool loggingEnabled; + nsresult rv = GetLoggingEnabled(&loggingEnabled); + NS_ENSURE_SUCCESS(rv,rv); + + if (!loggingEnabled) + return NS_OK; + + PRTime date; + + nsString authorValue; + nsString subjectValue; + nsString dateValue; + + (void)aMsgHdr->GetDate(&date); + PRExplodedTime exploded; + PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded); + + if (!mDateFormatter) + { + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!mDateFormatter) + { + return NS_ERROR_FAILURE; + } + } + mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort, + kTimeFormatSeconds, &exploded, + dateValue); + + (void)aMsgHdr->GetMime2DecodedAuthor(authorValue); + (void)aMsgHdr->GetMime2DecodedSubject(subjectValue); + + nsCString buffer; + // this is big enough to hold a log entry. + // do this so we avoid growing and copying as we append to the log. +#ifdef MOZILLA_INTERNAL_API + buffer.SetCapacity(512); +#endif + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + const char16_t *junkLogDetectFormatStrings[3] = { authorValue.get(), subjectValue.get(), dateValue.get() }; + nsString junkLogDetectStr; + rv = bundle->FormatStringFromName( + u"junkLogDetectStr", + junkLogDetectFormatStrings, 3, + getter_Copies(junkLogDetectStr)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(junkLogDetectStr); + buffer += "\n"; + + if (aMoveMessage) { + nsCString msgId; + aMsgHdr->GetMessageId(getter_Copies(msgId)); + + nsCString junkFolderURI; + rv = GetSpamFolderURI(getter_Copies(junkFolderURI)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertASCIItoUTF16 msgIdValue(msgId); + NS_ConvertASCIItoUTF16 junkFolderURIValue(junkFolderURI); + + const char16_t *logMoveFormatStrings[2] = { msgIdValue.get(), junkFolderURIValue.get() }; + nsString logMoveStr; + rv = bundle->FormatStringFromName( + u"logMoveStr", + logMoveFormatStrings, 2, + getter_Copies(logMoveStr)); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += NS_ConvertUTF16toUTF8(logMoveStr); + buffer += "\n"; + } + + return LogJunkString(buffer.get()); +} + +NS_IMETHODIMP nsSpamSettings::LogJunkString(const char *string) +{ + bool loggingEnabled; + nsresult rv = GetLoggingEnabled(&loggingEnabled); + NS_ENSURE_SUCCESS(rv,rv); + + if (!loggingEnabled) + return NS_OK; + + nsString dateValue; + PRExplodedTime exploded; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + + if (!mDateFormatter) + { + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!mDateFormatter) + { + return NS_ERROR_FAILURE; + } + } + mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort, + kTimeFormatSeconds, &exploded, + dateValue); + + nsCString timestampString(LOG_ENTRY_TIMESTAMP); + MsgReplaceSubstring(timestampString, "$S", NS_ConvertUTF16toUTF8(dateValue).get()); + + nsCOMPtr <nsIOutputStream> logStream; + rv = GetLogStream(getter_AddRefs(logStream)); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t writeCount; + + rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN, "failed to write out start log tag"); + + rv = logStream->Write(timestampString.get(), timestampString.Length(), &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == timestampString.Length(), "failed to write out timestamp"); + + // HTML-escape the log for security reasons. + // We don't want someone to send us a message with a subject with + // HTML tags, especially <script>. + char *escapedBuffer = MsgEscapeHTML(string); + if (!escapedBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t escapedBufferLen = strlen(escapedBuffer); + rv = logStream->Write(escapedBuffer, escapedBufferLen, &writeCount); + PR_Free(escapedBuffer); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit"); + + rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN, "failed to write out end log tag"); + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::OnStartRunningUrl(nsIURI* aURL) +{ + // do nothing + // all the action happens in OnStopRunningUrl() + return NS_OK; +} + +NS_IMETHODIMP nsSpamSettings::OnStopRunningUrl(nsIURI* aURL, nsresult exitCode) +{ + nsCString junkFolderURI; + nsresult rv = GetSpamFolderURI(getter_Copies(junkFolderURI)); + NS_ENSURE_SUCCESS(rv,rv); + + if (junkFolderURI.IsEmpty()) + return NS_ERROR_UNEXPECTED; + + // when we get here, the folder should exist. + nsCOMPtr <nsIMsgFolder> junkFolder; + rv = GetExistingFolder(junkFolderURI, getter_AddRefs(junkFolder)); + NS_ENSURE_SUCCESS(rv,rv); + if (!junkFolder) + return NS_ERROR_UNEXPECTED; + + rv = junkFolder->SetFlag(nsMsgFolderFlags::Junk); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP nsSpamSettings::CheckWhiteList(nsIMsgDBHdr *aMsgHdr, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; // default in case of error or no whitelisting + + if (!mUseWhiteList || (!mWhiteListDirArray.Count() && + mTrustedMailDomains.IsEmpty())) + return NS_OK; + + // do per-message processing + + nsCString author; + aMsgHdr->GetAuthor(getter_Copies(author)); + + nsAutoCString authorEmailAddress; + ExtractEmail(EncodedHeader(author), authorEmailAddress); + + if (authorEmailAddress.IsEmpty()) + return NS_OK; + + // should we skip whitelisting for the identity email? + if (mInhibitWhiteListingIdentityUser) + { + for (uint32_t i = 0; i < mEmails.Length(); ++i) + { + if (mEmails[i].Equals(authorEmailAddress, nsCaseInsensitiveCStringComparator())) + return NS_OK; + } + } + + if (!mTrustedMailDomains.IsEmpty() || mInhibitWhiteListingIdentityDomain) + { + nsAutoCString domain; + int32_t atPos = authorEmailAddress.FindChar('@'); + if (atPos >= 0) + domain = Substring(authorEmailAddress, atPos + 1); + if (!domain.IsEmpty()) + { + if (!mTrustedMailDomains.IsEmpty() && + MsgHostDomainIsTrusted(domain, mTrustedMailDomains)) + { + *aResult = true; + return NS_OK; + } + + if (mInhibitWhiteListingIdentityDomain) + { + for (uint32_t i = 0; i < mEmails.Length(); ++i) + { + nsAutoCString identityDomain; + int32_t atPos = mEmails[i].FindChar('@'); + if (atPos >= 0) + { + identityDomain = Substring(mEmails[i], atPos + 1); + if (identityDomain.Equals(domain, nsCaseInsensitiveCStringComparator())) + return NS_OK; // don't whitelist + } + } + } + } + } + + if (mWhiteListDirArray.Count()) + { + nsCOMPtr<nsIAbCard> cardForAddress; + for (int32_t index = 0; + index < mWhiteListDirArray.Count() && !cardForAddress; + index++) + { + mWhiteListDirArray[index]->CardForEmailAddress(authorEmailAddress, + getter_AddRefs(cardForAddress)); + } + if (cardForAddress) + { + *aResult = true; + return NS_OK; + } + } + return NS_OK; // default return is false +} diff --git a/mailnews/base/src/nsSpamSettings.h b/mailnews/base/src/nsSpamSettings.h new file mode 100644 index 0000000000..9432a1835c --- /dev/null +++ b/mailnews/base/src/nsSpamSettings.h @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +#ifndef nsSpamSettings_h__ +#define nsSpamSettings_h__ + +#include "nsCOMPtr.h" +#include "nsISpamSettings.h" +#include "nsStringGlue.h" +#include "nsIOutputStream.h" +#include "nsIMsgIncomingServer.h" +#include "nsIUrlListener.h" +#include "nsIDateTimeFormat.h" +#include "nsCOMArray.h" +#include "nsIAbDirectory.h" +#include "nsTArray.h" + +class nsSpamSettings : public nsISpamSettings, public nsIUrlListener +{ +public: + nsSpamSettings(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISPAMSETTINGS + NS_DECL_NSIURLLISTENER + +private: + virtual ~nsSpamSettings(); + + nsCOMPtr <nsIOutputStream> mLogStream; + nsCOMPtr<nsIFile> mLogFile; + + int32_t mLevel; + int32_t mPurgeInterval; + int32_t mMoveTargetMode; + + bool mPurge; + bool mUseWhiteList; + bool mMoveOnSpam; + bool mUseServerFilter; + + nsCString mActionTargetAccount; + nsCString mActionTargetFolder; + nsCString mWhiteListAbURI; + nsCString mCurrentJunkFolderURI; // used to detect changes to the spam folder in ::initialize + + nsCString mServerFilterName; + nsCOMPtr<nsIFile> mServerFilterFile; + int32_t mServerFilterTrustFlags; + + nsCOMPtr<nsIDateTimeFormat> mDateFormatter; + + // array of address directories to use in junk whitelisting + nsCOMArray<nsIAbDirectory> mWhiteListDirArray; + // mail domains to use in junk whitelisting + nsCString mTrustedMailDomains; + // should we inhibit whitelisting address of identity? + bool mInhibitWhiteListingIdentityUser; + // should we inhibit whitelisting domain of identity? + bool mInhibitWhiteListingIdentityDomain; + // email addresses associated with this server + nsTArray<nsCString> mEmails; + + // helper routine used by Initialize which unsets the junk flag on the previous junk folder + // for this account, and sets it on the new junk folder. + nsresult UpdateJunkFolderState(); +}; + +#endif /* nsSpamSettings_h__ */ diff --git a/mailnews/base/src/nsStatusBarBiffManager.cpp b/mailnews/base/src/nsStatusBarBiffManager.cpp new file mode 100644 index 0000000000..49d9bfb521 --- /dev/null +++ b/mailnews/base/src/nsStatusBarBiffManager.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 4; 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 "nsStatusBarBiffManager.h" +#include "nsMsgBiffManager.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsIObserverService.h" +#include "nsIWindowMediator.h" +#include "nsIMsgMailSession.h" +#include "MailNewsTypes.h" +#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later... +#include "nsIFileChannel.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsIFileURL.h" +#include "nsIFile.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +// QueryInterface, AddRef, and Release +// +NS_IMPL_ISUPPORTS(nsStatusBarBiffManager, nsIStatusBarBiffManager, nsIFolderListener, nsIObserver) + +nsIAtom * nsStatusBarBiffManager::kBiffStateAtom = nullptr; + +nsStatusBarBiffManager::nsStatusBarBiffManager() +: mInitialized(false), mCurrentBiffState(nsIMsgFolder::nsMsgBiffState_Unknown) +{ +} + +nsStatusBarBiffManager::~nsStatusBarBiffManager() +{ + NS_IF_RELEASE(kBiffStateAtom); +} + +#define NEW_MAIL_PREF_BRANCH "mail.biff." +#define CHAT_PREF_BRANCH "mail.chat." +#define FEED_PREF_BRANCH "mail.feed." +#define PREF_PLAY_SOUND "play_sound" +#define PREF_SOUND_URL "play_sound.url" +#define PREF_SOUND_TYPE "play_sound.type" +#define SYSTEM_SOUND_TYPE 0 +#define CUSTOM_SOUND_TYPE 1 +#define PREF_CHAT_ENABLED "mail.chat.enabled" +#define PLAY_CHAT_NOTIFICATION_SOUND "play-chat-notification-sound" + +nsresult nsStatusBarBiffManager::Init() +{ + if (mInitialized) + return NS_ERROR_ALREADY_INITIALIZED; + + nsresult rv; + + kBiffStateAtom = MsgNewAtom("BiffState").take(); + + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + if(NS_SUCCEEDED(rv)) + mailSession->AddFolderListener(this, nsIFolderListener::intPropertyChanged); + + nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool chatEnabled = false; + if (NS_SUCCEEDED(rv)) + rv = pref->GetBoolPref(PREF_CHAT_ENABLED, &chatEnabled); + if (NS_SUCCEEDED(rv) && chatEnabled) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + observerService->AddObserver(this, PLAY_CHAT_NOTIFICATION_SOUND, false); + } + + mInitialized = true; + return NS_OK; +} + +nsresult nsStatusBarBiffManager::PlayBiffSound(const char *aPrefBranch) +{ + nsresult rv; + nsCOMPtr<nsIPrefService> prefSvc = (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIPrefBranch> pref; + rv = prefSvc->GetBranch(aPrefBranch, getter_AddRefs(pref)); + NS_ENSURE_SUCCESS(rv, rv); + + bool playSound; + if (mServerType.EqualsLiteral("rss")) { + nsCOMPtr<nsIPrefBranch> prefFeed; + rv = prefSvc->GetBranch(FEED_PREF_BRANCH, getter_AddRefs(prefFeed)); + NS_ENSURE_SUCCESS(rv, rv); + rv = prefFeed->GetBoolPref(PREF_PLAY_SOUND, &playSound); + } + else { + rv = pref->GetBoolPref(PREF_PLAY_SOUND, &playSound); + } + NS_ENSURE_SUCCESS(rv, rv); + + if (!playSound) + return NS_OK; + + // lazily create the sound instance + if (!mSound) + mSound = do_CreateInstance("@mozilla.org/sound;1"); + + int32_t soundType = SYSTEM_SOUND_TYPE; + rv = pref->GetIntPref(PREF_SOUND_TYPE, &soundType); + NS_ENSURE_SUCCESS(rv, rv); + + bool customSoundPlayed = false; + + if (soundType == CUSTOM_SOUND_TYPE) { + nsCString soundURLSpec; + rv = pref->GetCharPref(PREF_SOUND_URL, getter_Copies(soundURLSpec)); + + if (NS_SUCCEEDED(rv) && !soundURLSpec.IsEmpty()) { + if (!strncmp(soundURLSpec.get(), "file://", 7)) { + nsCOMPtr<nsIURI> fileURI; + rv = NS_NewURI(getter_AddRefs(fileURI), soundURLSpec); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIFileURL> soundURL = do_QueryInterface(fileURI,&rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIFile> soundFile; + rv = soundURL->GetFile(getter_AddRefs(soundFile)); + if (NS_SUCCEEDED(rv)) { + bool soundFileExists = false; + rv = soundFile->Exists(&soundFileExists); + if (NS_SUCCEEDED(rv) && soundFileExists) { + rv = mSound->Play(soundURL); + if (NS_SUCCEEDED(rv)) + customSoundPlayed = true; + } + } + } + } + else { + // todo, see if we can create a nsIFile using the string as a native path. + // if that fails, try playing a system sound + NS_ConvertUTF8toUTF16 utf16SoundURLSpec(soundURLSpec); + rv = mSound->PlaySystemSound(utf16SoundURLSpec); + if (NS_SUCCEEDED(rv)) + customSoundPlayed = true; + } + } + } +#ifndef XP_MACOSX + // if nothing played, play the default system sound + if (!customSoundPlayed) { + rv = mSound->PlayEventSound(nsISound::EVENT_NEW_MAIL_RECEIVED); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + return rv; +} + +// nsIFolderListener methods.... +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemAdded(nsIMsgFolder *parentItem, nsISupports *item) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemRemoved(nsIMsgFolder *parentItem, nsISupports *item) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char *oldValue, const char *newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemIntPropertyChanged(nsIMsgFolder *item, nsIAtom *property, int64_t oldValue, int64_t newValue) +{ + if (kBiffStateAtom == property && mCurrentBiffState != newValue) { + // if we got new mail, attempt to play a sound. + // if we fail along the way, don't return. + // we still need to update the UI. + if (newValue == nsIMsgFolder::nsMsgBiffState_NewMail) { + // Get the folder's server type. + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = item->GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + server->GetType(mServerType); + + // if we fail to play the biff sound, keep going. + (void)PlayBiffSound(NEW_MAIL_PREF_BRANCH); + } + mCurrentBiffState = newValue; + + // don't care if notification fails + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) + observerService->NotifyObservers(static_cast<nsIStatusBarBiffManager*>(this), "mail:biff-state-changed", nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemBoolPropertyChanged(nsIMsgFolder *item, nsIAtom *property, bool oldValue, bool newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemUnicharPropertyChanged(nsIMsgFolder *item, nsIAtom *property, const char16_t *oldValue, const char16_t *newValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStatusBarBiffManager::OnItemEvent(nsIMsgFolder *item, nsIAtom *event) +{ + return NS_OK; +} + +// nsIObserver implementation +NS_IMETHODIMP +nsStatusBarBiffManager::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + return PlayBiffSound(CHAT_PREF_BRANCH); +} + +// nsIStatusBarBiffManager method.... +NS_IMETHODIMP +nsStatusBarBiffManager::GetBiffState(int32_t *aBiffState) +{ + NS_ENSURE_ARG_POINTER(aBiffState); + *aBiffState = mCurrentBiffState; + return NS_OK; +} + diff --git a/mailnews/base/src/nsStatusBarBiffManager.h b/mailnews/base/src/nsStatusBarBiffManager.h new file mode 100644 index 0000000000..cb85742e30 --- /dev/null +++ b/mailnews/base/src/nsStatusBarBiffManager.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsStatusBarBiffManager_h__ +#define nsStatusBarBiffManager_h__ + +#include "nsIStatusBarBiffManager.h" + +#include "msgCore.h" +#include "nsCOMPtr.h" +#include "nsISound.h" +#include "nsIObserver.h" + +class nsStatusBarBiffManager : public nsIStatusBarBiffManager, + public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSISTATUSBARBIFFMANAGER + NS_DECL_NSIOBSERVER + + nsStatusBarBiffManager(); + nsresult Init(); + +private: + virtual ~nsStatusBarBiffManager(); + + bool mInitialized; + int32_t mCurrentBiffState; + nsCString mServerType; + nsCOMPtr<nsISound> mSound; + nsresult PlayBiffSound(const char *aPrefBranch); + +protected: + static nsIAtom* kBiffStateAtom; +}; + + + +#endif // nsStatusBarBiffManager_h__ + diff --git a/mailnews/base/src/nsSubscribableServer.cpp b/mailnews/base/src/nsSubscribableServer.cpp new file mode 100644 index 0000000000..80f9c9b64d --- /dev/null +++ b/mailnews/base/src/nsSubscribableServer.cpp @@ -0,0 +1,805 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsSubscribableServer.h" +#include "nsIMsgIncomingServer.h" +#include "prmem.h" +#include "rdf.h" +#include "nsRDFCID.h" +#include "nsIServiceManager.h" +#include "nsMsgI18N.h" +#include "nsMsgUtils.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsServiceManagerUtils.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +nsSubscribableServer::nsSubscribableServer(void) +{ + mDelimiter = '.'; + mShowFullName = true; + mTreeRoot = nullptr; + mStopped = false; +} + +nsresult +nsSubscribableServer::Init() +{ + nsresult rv; + + rv = EnsureRDFService(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), + getter_AddRefs(kNC_Child)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribed"), + getter_AddRefs(kNC_Subscribed)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetLiteral(u"true", getter_AddRefs(kTrueLiteral)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetLiteral(u"false", getter_AddRefs(kFalseLiteral)); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsSubscribableServer::~nsSubscribableServer(void) +{ + mozilla::DebugOnly<nsresult> rv = FreeSubtree(mTreeRoot); + NS_ASSERTION(NS_SUCCEEDED(rv),"failed to free tree"); +} + +NS_IMPL_ISUPPORTS(nsSubscribableServer, nsISubscribableServer) + +NS_IMETHODIMP +nsSubscribableServer::SetIncomingServer(nsIMsgIncomingServer *aServer) +{ + if (!aServer) { + mIncomingServerUri.AssignLiteral(""); + return NS_OK; + } + + // We intentionally do not store a pointer to the aServer here + // as it would create reference loops, because nsIImapIncomingServer + // and nsINntpIncomingServer keep a reference to an internal + // nsISubscribableServer object. + // We only need the URI of the server anyway. + return aServer->GetServerURI(mIncomingServerUri); +} + +NS_IMETHODIMP +nsSubscribableServer::GetDelimiter(char *aDelimiter) +{ + if (!aDelimiter) return NS_ERROR_NULL_POINTER; + *aDelimiter = mDelimiter; + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::SetDelimiter(char aDelimiter) +{ + mDelimiter = aDelimiter; + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::SetAsSubscribed(const nsACString &path) +{ + nsresult rv = NS_OK; + + SubscribeTreeNode *node = nullptr; + rv = FindAndCreateNode(path, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + node->isSubscribable = true; + node->isSubscribed = true; + + rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed); + NS_ENSURE_SUCCESS(rv,rv); + + return rv; +} + +NS_IMETHODIMP +nsSubscribableServer::AddTo(const nsACString& aName, bool aAddAsSubscribed, + bool aSubscribable, bool aChangeIfExists) +{ + nsresult rv = NS_OK; + + if (mStopped) { +#ifdef DEBUG_seth + printf("stopped!\n"); +#endif + return NS_ERROR_FAILURE; + } + + SubscribeTreeNode *node = nullptr; + + // todo, shouldn't we pass in aAddAsSubscribed, for the + // default value if we create it? + rv = FindAndCreateNode(aName, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + if (aChangeIfExists) { + node->isSubscribed = aAddAsSubscribed; + rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed); + NS_ENSURE_SUCCESS(rv,rv); + } + + node->isSubscribable = aSubscribable; + return rv; +} + +NS_IMETHODIMP +nsSubscribableServer::SetState(const nsACString &aPath, bool aState, + bool *aStateChanged) +{ + nsresult rv = NS_OK; + NS_ASSERTION(!aPath.IsEmpty() && aStateChanged, "no path or stateChanged"); + if (aPath.IsEmpty() || !aStateChanged) return NS_ERROR_NULL_POINTER; + + NS_ASSERTION(MsgIsUTF8(aPath), "aPath is not in UTF-8"); + + *aStateChanged = false; + + SubscribeTreeNode *node = nullptr; + rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + NS_ASSERTION(node->isSubscribable, "fix this"); + if (!node->isSubscribable) { + return NS_OK; + } + + if (node->isSubscribed == aState) { + return NS_OK; + } + else { + node->isSubscribed = aState; + *aStateChanged = true; + rv = NotifyChange(node, kNC_Subscribed, node->isSubscribed); + NS_ENSURE_SUCCESS(rv,rv); + } + + return rv; +} + +void +nsSubscribableServer::BuildURIFromNode(SubscribeTreeNode *node, nsACString &uri) +{ + if (node->parent) { + BuildURIFromNode(node->parent, uri); + if (node->parent == mTreeRoot) { + uri += "/"; + } + else { + uri += mDelimiter; + } + } + + uri += node->name; + return; +} + +nsresult +nsSubscribableServer::NotifyAssert(SubscribeTreeNode *subjectNode, nsIRDFResource *property, SubscribeTreeNode *objectNode) +{ + nsresult rv; + + bool hasObservers = true; + rv = EnsureSubscribeDS(); + NS_ENSURE_SUCCESS(rv,rv); + rv = mSubscribeDS->GetHasObservers(&hasObservers); + NS_ENSURE_SUCCESS(rv,rv); + // no need to do all this work, there are no observers + if (!hasObservers) { + return NS_OK; + } + + nsAutoCString subjectUri; + BuildURIFromNode(subjectNode, subjectUri); + + // we could optimize this, since we know that objectUri == subjectUri + mDelimiter + object->name + // is it worth it? + nsAutoCString objectUri; + BuildURIFromNode(objectNode, objectUri); + + nsCOMPtr <nsIRDFResource> subject; + nsCOMPtr <nsIRDFResource> object; + + rv = EnsureRDFService(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(subjectUri, getter_AddRefs(subject)); + NS_ENSURE_SUCCESS(rv,rv); + rv = mRDFService->GetResource(objectUri, getter_AddRefs(object)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = Notify(subject, property, object, true, false); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsresult +nsSubscribableServer::EnsureRDFService() +{ + nsresult rv; + + if (!mRDFService) { + mRDFService = do_GetService(kRDFServiceCID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv) && mRDFService, "failed to get rdf service"); + NS_ENSURE_SUCCESS(rv,rv); + if (!mRDFService) return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsSubscribableServer::NotifyChange(SubscribeTreeNode *subjectNode, nsIRDFResource *property, bool value) +{ + nsresult rv; + nsCOMPtr <nsIRDFResource> subject; + + bool hasObservers = true; + rv = EnsureSubscribeDS(); + NS_ENSURE_SUCCESS(rv,rv); + rv = mSubscribeDS->GetHasObservers(&hasObservers); + NS_ENSURE_SUCCESS(rv,rv); + // no need to do all this work, there are no observers + if (!hasObservers) { + return NS_OK; + } + + nsAutoCString subjectUri; + BuildURIFromNode(subjectNode, subjectUri); + + rv = EnsureRDFService(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(subjectUri, getter_AddRefs(subject)); + NS_ENSURE_SUCCESS(rv,rv); + + if (value) { + rv = Notify(subject,property,kTrueLiteral,false,true); + } + else { + rv = Notify(subject,property,kFalseLiteral,false,true); + } + + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsresult +nsSubscribableServer::EnsureSubscribeDS() +{ + nsresult rv = NS_OK; + + if (!mSubscribeDS) { + nsCOMPtr<nsIRDFDataSource> ds; + + rv = EnsureRDFService(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetDataSource("rdf:subscribe", getter_AddRefs(ds)); + NS_ENSURE_SUCCESS(rv,rv); + if (!ds) return NS_ERROR_FAILURE; + + mSubscribeDS = do_QueryInterface(ds, &rv); + NS_ENSURE_SUCCESS(rv,rv); + if (!mSubscribeDS) return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +nsSubscribableServer::Notify(nsIRDFResource *subject, nsIRDFResource *property, nsIRDFNode *object, bool isAssert, bool isChange) +{ + nsresult rv = NS_OK; + + rv = EnsureSubscribeDS(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mSubscribeDS->NotifyObservers(subject, property, object, isAssert, isChange); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP +nsSubscribableServer::SetSubscribeListener(nsISubscribeListener *aListener) +{ + mSubscribeListener = aListener; + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::GetSubscribeListener(nsISubscribeListener **aListener) +{ + if (!aListener) return NS_ERROR_NULL_POINTER; + if (mSubscribeListener) { + *aListener = mSubscribeListener; + NS_ADDREF(*aListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::SubscribeCleanup() +{ + NS_ASSERTION(false,"override this."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSubscribableServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer, const char *uri) +{ + mStopped = false; + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer, bool aGetOnlyNew /*ignored*/) +{ + nsresult rv = NS_OK; + + mStopped = false; + + rv = FreeSubtree(mTreeRoot); + mTreeRoot = nullptr; + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::StopPopulating(nsIMsgWindow *aMsgWindow) +{ + mStopped = true; + return NS_OK; +} + + +NS_IMETHODIMP +nsSubscribableServer::UpdateSubscribed() +{ + NS_ASSERTION(false,"override this."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSubscribableServer::Subscribe(const char16_t *aName) +{ + NS_ASSERTION(false,"override this."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSubscribableServer::Unsubscribe(const char16_t *aName) +{ + NS_ASSERTION(false,"override this."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSubscribableServer::SetShowFullName(bool showFullName) +{ + mShowFullName = showFullName; + return NS_OK; +} + +nsresult +nsSubscribableServer::FreeSubtree(SubscribeTreeNode *node) +{ + nsresult rv = NS_OK; + + if (node) { + // recursively free the children + if (node->firstChild) { + // will free node->firstChild + rv = FreeSubtree(node->firstChild); + NS_ENSURE_SUCCESS(rv,rv); + node->firstChild = nullptr; + } + + // recursively free the siblings + if (node->nextSibling) { + // will free node->nextSibling + rv = FreeSubtree(node->nextSibling); + NS_ENSURE_SUCCESS(rv,rv); + node->nextSibling = nullptr; + } + +#ifdef HAVE_SUBSCRIBE_DESCRIPTION + NS_ASSERTION(node->description == nullptr, "you need to free the description"); +#endif + NS_Free(node->name); +#if 0 + node->name = nullptr; + node->parent = nullptr; + node->lastChild = nullptr; + node->cachedChild = nullptr; +#endif + + PR_Free(node); + } + + return NS_OK; +} + +nsresult +nsSubscribableServer::CreateNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **result) +{ + NS_ASSERTION(result && name, "result or name is null"); + if (!result || !name) return NS_ERROR_NULL_POINTER; + + *result = (SubscribeTreeNode *) PR_Malloc(sizeof(SubscribeTreeNode)); + if (!*result) return NS_ERROR_OUT_OF_MEMORY; + + (*result)->name = strdup(name); + if (!(*result)->name) return NS_ERROR_OUT_OF_MEMORY; + + (*result)->parent = parent; + (*result)->prevSibling = nullptr; + (*result)->nextSibling = nullptr; + (*result)->firstChild = nullptr; + (*result)->lastChild = nullptr; + (*result)->isSubscribed = false; + (*result)->isSubscribable = false; +#ifdef HAVE_SUBSCRIBE_DESCRIPTION + (*result)->description = nullptr; +#endif +#ifdef HAVE_SUBSCRIBE_MESSAGES + (*result)->messages = 0; +#endif + (*result)->cachedChild = nullptr; + + if (parent) { + parent->cachedChild = *result; + } + + return NS_OK; +} + +nsresult +nsSubscribableServer::AddChildNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **child) +{ + nsresult rv = NS_OK; + NS_ASSERTION(parent && child && name, "parent, child or name is null"); + if (!parent || !child || !name) return NS_ERROR_NULL_POINTER; + + if (!parent->firstChild) { + // CreateNode will set the parent->cachedChild + rv = CreateNode(parent, name, child); + NS_ENSURE_SUCCESS(rv,rv); + + parent->firstChild = *child; + parent->lastChild = *child; + + rv = NotifyAssert(parent, kNC_Child, *child); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_OK; + } + else { + if (parent->cachedChild) { + if (strcmp(parent->cachedChild->name,name) == 0) { + *child = parent->cachedChild; + return NS_OK; + } + } + + SubscribeTreeNode *current = parent->firstChild; + + /* + * insert in reverse alphabetical order + * this will reduce the # of strcmps + * since this is faster assuming: + * 1) the hostinfo.dat feeds us the groups in alphabetical order + * since we control the hostinfo.dat file, we can guarantee this. + * 2) the server gives us the groups in alphabetical order + * we can't guarantee this, but it seems to be a common thing + * + * because we have firstChild, lastChild, nextSibling, prevSibling + * we can efficiently reverse the order when dumping to hostinfo.dat + * or to GetTargets() + */ + int32_t compare = strcmp(current->name, name); + + while (current && (compare != 0)) { + if (compare < 0) { + // CreateNode will set the parent->cachedChild + rv = CreateNode(parent, name, child); + NS_ENSURE_SUCCESS(rv,rv); + + (*child)->nextSibling = current; + (*child)->prevSibling = current->prevSibling; + current->prevSibling = (*child); + if (!(*child)->prevSibling) { + parent->firstChild = (*child); + } + else { + (*child)->prevSibling->nextSibling = (*child); + } + + rv = NotifyAssert(parent, kNC_Child, *child); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; + } + current = current->nextSibling; + if (current) { + NS_ASSERTION(current->name, "no name!"); + compare = strcmp(current->name,name); + } + else { + compare = -1; // anything but 0, since that would be a match + } + } + + if (compare == 0) { + // already exists; + *child = current; + + // set the cachedChild + parent->cachedChild = *child; + return NS_OK; + } + + // CreateNode will set the parent->cachedChild + rv = CreateNode(parent, name, child); + NS_ENSURE_SUCCESS(rv,rv); + + (*child)->prevSibling = parent->lastChild; + (*child)->nextSibling = nullptr; + parent->lastChild->nextSibling = *child; + parent->lastChild = *child; + + rv = NotifyAssert(parent, kNC_Child, *child); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; + } + return NS_OK; +} + +nsresult +nsSubscribableServer::FindAndCreateNode(const nsACString &aPath, + SubscribeTreeNode **aResult) +{ + nsresult rv = NS_OK; + NS_ASSERTION(aResult, "no result"); + if (!aResult) return NS_ERROR_NULL_POINTER; + + if (!mTreeRoot) { + // the root has no parent, and its name is server uri + rv = CreateNode(nullptr, mIncomingServerUri.get(), &mTreeRoot); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (aPath.IsEmpty()) { + *aResult = mTreeRoot; + return NS_OK; + } + + char *token = nullptr; + nsCString pathStr(aPath); + char *rest = pathStr.BeginWriting(); + + // todo do this only once + char delimstr[2]; + delimstr[0] = mDelimiter; + delimstr[1] = '\0'; + + *aResult = nullptr; + + SubscribeTreeNode *parent = mTreeRoot; + SubscribeTreeNode *child = nullptr; + + token = NS_strtok(delimstr, &rest); + // special case paths that start with the hierarchy delimiter. + // We want to include that delimiter in the first token name. + if (token && pathStr[0] == mDelimiter) + --token; + while (token && *token) { + rv = AddChildNode(parent, token, &child); + if (NS_FAILED(rv)) + return rv; + token = NS_strtok(delimstr, &rest); + parent = child; + } + + // the last child we add is the result + *aResult = child; + return rv; +} + +NS_IMETHODIMP +nsSubscribableServer::HasChildren(const nsACString &aPath, bool *aHasChildren) +{ + nsresult rv = NS_OK; + NS_ASSERTION(aHasChildren, "no hasChildren"); + if (!aHasChildren) return NS_ERROR_NULL_POINTER; + + *aHasChildren = false; + + SubscribeTreeNode *node = nullptr; + rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + *aHasChildren = (node->firstChild != nullptr); + return NS_OK; +} + + +NS_IMETHODIMP +nsSubscribableServer::IsSubscribed(const nsACString &aPath, + bool *aIsSubscribed) +{ + NS_ENSURE_ARG_POINTER(aIsSubscribed); + + *aIsSubscribed = false; + + SubscribeTreeNode *node = nullptr; + nsresult rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + *aIsSubscribed = node->isSubscribed; + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::IsSubscribable(const nsACString &aPath, + bool *aIsSubscribable) +{ + NS_ENSURE_ARG_POINTER(aIsSubscribable); + + *aIsSubscribable = false; + + SubscribeTreeNode *node = nullptr; + nsresult rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + *aIsSubscribable = node->isSubscribable; + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::GetLeafName(const nsACString &aPath, nsAString &aLeafName) +{ + SubscribeTreeNode *node = nullptr; + nsresult rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + // XXX TODO FIXME + // I'm assuming that mShowFullName is true for NNTP, false for IMAP. + // for imap, the node name is in modified UTF7 + // for news, the path is escaped UTF8 + // + // when we switch to using the tree, this hack will go away. + if (mShowFullName) { + return NS_MsgDecodeUnescapeURLPath(aPath, aLeafName); + } + + return CopyMUTF7toUTF16(nsDependentCString(node->name), aLeafName); +} + +NS_IMETHODIMP +nsSubscribableServer::GetFirstChildURI(const nsACString &aPath, + nsACString &aResult) +{ + aResult.Truncate(); + + SubscribeTreeNode *node = nullptr; + nsresult rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) return NS_ERROR_FAILURE; + + // no children + if (!node->firstChild) return NS_ERROR_FAILURE; + + BuildURIFromNode(node->firstChild, aResult); + + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribableServer::GetChildren(const nsACString &aPath, + nsISimpleEnumerator **aResult) +{ + SubscribeTreeNode *node = nullptr; + nsresult rv = FindAndCreateNode(aPath, &node); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(node,"didn't find the node"); + if (!node) + return NS_ERROR_FAILURE; + + nsAutoCString uriPrefix; + NS_ASSERTION(mTreeRoot, "no tree root!"); + if (!mTreeRoot) + return NS_ERROR_UNEXPECTED; + + uriPrefix = mTreeRoot->name; // the root's name is the server uri + uriPrefix += "/"; + if (!aPath.IsEmpty()) { + uriPrefix += aPath; + uriPrefix += mDelimiter; + } + + // we inserted them in reverse alphabetical order. + // so pull them out in reverse to get the right order + // in the subscribe dialog + SubscribeTreeNode *current = node->lastChild; + // return failure if there are no children. + if (!current) + return NS_ERROR_FAILURE; + + nsCOMArray<nsIRDFResource> result; + + while (current) { + nsAutoCString uri; + uri = uriPrefix; + NS_ASSERTION(current->name, "no name"); + if (!current->name) + return NS_ERROR_FAILURE; + + uri += current->name; + + nsCOMPtr <nsIRDFResource> res; + rv = EnsureRDFService(); + NS_ENSURE_SUCCESS(rv,rv); + + // todo, is this creating nsMsgFolders? + mRDFService->GetResource(uri, getter_AddRefs(res)); + result.AppendObject(res); + + current = current->prevSibling; + } + + return NS_NewArrayEnumerator(aResult, result); +} + +NS_IMETHODIMP +nsSubscribableServer::CommitSubscribeChanges() +{ + NS_ASSERTION(false,"override this."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSubscribableServer::SetSearchValue(const nsAString &aSearchValue) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSubscribableServer::GetSupportsSubscribeSearch(bool *retVal) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mailnews/base/src/nsSubscribableServer.h b/mailnews/base/src/nsSubscribableServer.h new file mode 100644 index 0000000000..cbbac78e5b --- /dev/null +++ b/mailnews/base/src/nsSubscribableServer.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsSubscribableServer_h__ +#define nsSubscribableServer_h__ + +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsISubscribableServer.h" +#include "nsIRDFService.h" +#include "nsSubscribeDataSource.h" +#include "nsIRDFResource.h" + +typedef struct _subscribeTreeNode { + char *name; + bool isSubscribed; + struct _subscribeTreeNode *prevSibling; + struct _subscribeTreeNode *nextSibling; + struct _subscribeTreeNode *firstChild; + struct _subscribeTreeNode *lastChild; + struct _subscribeTreeNode *parent; + struct _subscribeTreeNode *cachedChild; +#ifdef HAVE_SUBSCRIBE_DESCRIPTION + char16_t *description; +#endif +#ifdef HAVE_SUBSCRIBE_MESSAGES + uint32_t messages; +#endif + bool isSubscribable; +} SubscribeTreeNode; + +#if defined(DEBUG_sspitzer) || defined(DEBUG_seth) +#define DEBUG_SUBSCRIBE 1 +#endif + +class nsSubscribableServer : public nsISubscribableServer +{ + public: + nsSubscribableServer(); + + nsresult Init(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUBSCRIBABLESERVER + +private: + virtual ~nsSubscribableServer(); + + nsresult ConvertNameToUnichar(const char *inStr, char16_t **outStr); + nsCOMPtr <nsISubscribeListener> mSubscribeListener; + nsCString mIncomingServerUri; + nsCOMPtr <nsISubscribeDataSource> mSubscribeDS; + char mDelimiter; + bool mShowFullName; + bool mStopped; + + nsCOMPtr <nsIRDFResource> kNC_Child; + nsCOMPtr <nsIRDFResource> kNC_Subscribed; + nsCOMPtr <nsIRDFLiteral> kTrueLiteral; + nsCOMPtr <nsIRDFLiteral> kFalseLiteral; + + nsCOMPtr <nsIRDFService> mRDFService; + + SubscribeTreeNode *mTreeRoot; + nsresult FreeSubtree(SubscribeTreeNode *node); + nsresult CreateNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **result); + nsresult AddChildNode(SubscribeTreeNode *parent, const char *name, SubscribeTreeNode **child); + nsresult FindAndCreateNode(const nsACString &aPath, + SubscribeTreeNode **aResult); + nsresult NotifyAssert(SubscribeTreeNode *subjectNode, nsIRDFResource *property, SubscribeTreeNode *objectNode); + nsresult NotifyChange(SubscribeTreeNode *subjectNode, nsIRDFResource *property, bool value); + nsresult Notify(nsIRDFResource *subject, nsIRDFResource *property, nsIRDFNode *object, bool isAssert, bool isChange); + void BuildURIFromNode(SubscribeTreeNode *node, nsACString &uri); + nsresult EnsureSubscribeDS(); + nsresult EnsureRDFService(); +}; + +#endif // nsSubscribableServer_h__ diff --git a/mailnews/base/src/nsSubscribeDataSource.cpp b/mailnews/base/src/nsSubscribeDataSource.cpp new file mode 100644 index 0000000000..fe13cffca2 --- /dev/null +++ b/mailnews/base/src/nsSubscribeDataSource.cpp @@ -0,0 +1,673 @@ +/* -*- 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 "nsSubscribeDataSource.h" + +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsIComponentManager.h" +#include "rdf.h" +#include "nsIServiceManager.h" +#include "nsEnumeratorUtils.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsIMsgFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +nsSubscribeDataSource::nsSubscribeDataSource() +{ +} + +nsSubscribeDataSource::~nsSubscribeDataSource() +{ +} + +NS_IMPL_ISUPPORTS(nsSubscribeDataSource, nsIRDFDataSource, nsISubscribeDataSource) + +nsresult +nsSubscribeDataSource::Init() +{ + nsresult rv; + + mRDFService = do_GetService(kRDFServiceCID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv) && mRDFService, "failed to get rdf service"); + NS_ENSURE_SUCCESS(rv,rv); + if (!mRDFService) return NS_ERROR_FAILURE; + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), + getter_AddRefs(kNC_Child)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"), + getter_AddRefs(kNC_Name)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "LeafName"), + getter_AddRefs(kNC_LeafName)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribed"), + getter_AddRefs(kNC_Subscribed)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Subscribable"), + getter_AddRefs(kNC_Subscribable)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "ServerType"), + getter_AddRefs(kNC_ServerType)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetLiteral(u"true", getter_AddRefs(kTrueLiteral)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mRDFService->GetLiteral(u"false", getter_AddRefs(kFalseLiteral)); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetURI(char * *aURI) +{ + if ((*aURI = strdup("rdf:subscribe")) == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + else + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetSource(nsIRDFResource *property, nsIRDFNode *target, bool tv, nsIRDFResource **source) +{ + NS_PRECONDITION(property != nullptr, "null ptr"); + if (! property) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(target != nullptr, "null ptr"); + if (! target) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + *source = nullptr; + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetTarget(nsIRDFResource *source, + nsIRDFResource *property, + bool tv, + nsIRDFNode **target /* out */) +{ + nsresult rv = NS_RDF_NO_VALUE; + + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(property != nullptr, "null ptr"); + if (! property) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(target != nullptr, "null ptr"); + if (! target) + return NS_ERROR_NULL_POINTER; + + *target = nullptr; + + // we only have positive assertions in the subscribe data source. + if (! tv) return NS_RDF_NO_VALUE; + + nsCOMPtr<nsISubscribableServer> server; + nsCString relativePath; + rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath)); + if (NS_FAILED(rv) || !server) + return NS_RDF_NO_VALUE; + + if (property == kNC_Name.get()) { + nsCOMPtr<nsIRDFLiteral> name; + rv = mRDFService->GetLiteral(NS_ConvertUTF8toUTF16(relativePath).get(), + getter_AddRefs(name)); + NS_ENSURE_SUCCESS(rv,rv); + + if (!name) rv = NS_RDF_NO_VALUE; + if (rv == NS_RDF_NO_VALUE) return(rv); + return name->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target); + } + else if (property == kNC_Child.get()) { + nsCString childUri; + rv = server->GetFirstChildURI(relativePath, childUri); + if (NS_FAILED(rv)) return NS_RDF_NO_VALUE; + if (childUri.IsEmpty()) return NS_RDF_NO_VALUE; + + nsCOMPtr <nsIRDFResource> childResource; + rv = mRDFService->GetResource(childUri, getter_AddRefs(childResource)); + NS_ENSURE_SUCCESS(rv,rv); + + return childResource->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target); + } + else if (property == kNC_Subscribed.get()) { + bool isSubscribed; + rv = server->IsSubscribed(relativePath, &isSubscribed); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*target = (isSubscribed ? kTrueLiteral : kFalseLiteral)); + return NS_OK; + } + else if (property == kNC_Subscribable.get()) { + bool isSubscribable; + rv = server->IsSubscribable(relativePath, &isSubscribable); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*target = (isSubscribable ? kTrueLiteral : kFalseLiteral)); + return NS_OK; + } + else if (property == kNC_ServerType.get()) { + nsCString serverTypeStr; + rv = GetServerType(server, serverTypeStr); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFLiteral> serverType; + rv = mRDFService->GetLiteral(NS_ConvertASCIItoUTF16(serverTypeStr).get(), + getter_AddRefs(serverType)); + NS_ENSURE_SUCCESS(rv,rv); + + if (!serverType) + rv = NS_RDF_NO_VALUE; + if (rv == NS_RDF_NO_VALUE) + return rv; + return serverType->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target); + } + else if (property == kNC_LeafName.get()) { + nsString leafNameStr; + rv = server->GetLeafName(relativePath, leafNameStr); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFLiteral> leafName; + rv = mRDFService->GetLiteral(leafNameStr.get(), getter_AddRefs(leafName)); + NS_ENSURE_SUCCESS(rv,rv); + + if (!leafName) + rv = NS_RDF_NO_VALUE; + if (rv == NS_RDF_NO_VALUE) + return rv; + return leafName->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target); + } + else { + // do nothing + } + + return(NS_RDF_NO_VALUE); +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetTargets(nsIRDFResource *source, + nsIRDFResource *property, + bool tv, + nsISimpleEnumerator **targets /* out */) +{ + nsresult rv = NS_OK; + + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(property != nullptr, "null ptr"); + if (! property) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(targets != nullptr, "null ptr"); + if (! targets) + return NS_ERROR_NULL_POINTER; + + *targets = nullptr; + + // we only have positive assertions in the subscribe data source. + if (!tv) return NS_RDF_NO_VALUE; + + nsCOMPtr<nsISubscribableServer> server; + nsCString relativePath; // UTF-8 + + rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath)); + if (NS_FAILED(rv) || !server) { + return NS_NewEmptyEnumerator(targets); + } + + if (property == kNC_Child.get()) { + rv = server->GetChildren(relativePath, targets); + if (NS_FAILED(rv)) { + return NS_NewEmptyEnumerator(targets); + } + return rv; + } + else if (property == kNC_LeafName.get()) { + nsString leafNameStr; + rv = server->GetLeafName(relativePath, leafNameStr); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFLiteral> leafName; + rv = mRDFService->GetLiteral(leafNameStr.get(), getter_AddRefs(leafName)); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_NewSingletonEnumerator(targets, leafName); + } + else if (property == kNC_Subscribed.get()) { + bool isSubscribed; + rv = server->IsSubscribed(relativePath, &isSubscribed); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_NewSingletonEnumerator(targets, + isSubscribed ? kTrueLiteral : kFalseLiteral); + } + else if (property == kNC_Subscribable.get()) { + bool isSubscribable; + rv = server->IsSubscribable(relativePath, &isSubscribable); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_NewSingletonEnumerator(targets, + isSubscribable ? kTrueLiteral : kFalseLiteral); + } + else if (property == kNC_Name.get()) { + nsCOMPtr<nsIRDFLiteral> name; + rv = mRDFService->GetLiteral(NS_ConvertUTF8toUTF16(relativePath).get(), + getter_AddRefs(name)); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_NewSingletonEnumerator(targets, name); + } + else if (property == kNC_ServerType.get()) { + nsCString serverTypeStr; + rv = GetServerType(server, serverTypeStr); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIRDFLiteral> serverType; + rv = mRDFService->GetLiteral(NS_ConvertASCIItoUTF16(serverTypeStr).get(), + getter_AddRefs(serverType)); + NS_ENSURE_SUCCESS(rv,rv); + + return NS_NewSingletonEnumerator(targets, serverType); + } + else { + // do nothing + } + + return NS_NewEmptyEnumerator(targets); +} + +NS_IMETHODIMP +nsSubscribeDataSource::Assert(nsIRDFResource *source, + nsIRDFResource *property, + nsIRDFNode *target, + bool tv) +{ + return NS_RDF_ASSERTION_REJECTED; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::Unassert(nsIRDFResource *source, + nsIRDFResource *property, + nsIRDFNode *target) +{ + return NS_RDF_ASSERTION_REJECTED; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + return NS_RDF_ASSERTION_REJECTED; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + return NS_RDF_ASSERTION_REJECTED; +} + +nsresult +nsSubscribeDataSource::GetServerType(nsISubscribableServer *server, nsACString& serverType) +{ + NS_ENSURE_ARG_POINTER(server); + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> incomingServer(do_QueryInterface(server, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + return incomingServer->GetType(serverType); +} + +nsresult +nsSubscribeDataSource::GetServerAndRelativePathFromResource(nsIRDFResource *source, nsISubscribableServer **server, char **relativePath) +{ + nsresult rv = NS_OK; + + const char *sourceURI = nullptr; + rv = source->GetValueConst(&sourceURI); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(source, &rv)); + // we expect this to fail sometimes, so don't assert + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + rv = folder->GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = incomingServer->QueryInterface(NS_GET_IID(nsISubscribableServer), (void**)server); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString serverURI; + rv = incomingServer->GetServerURI(serverURI); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t serverURILen = serverURI.Length(); + if (serverURILen == strlen(sourceURI)) + *relativePath = nullptr; + else { + // XXX : perhaps, have to unescape before returning + *relativePath = strdup(sourceURI + serverURILen + 1); + if (!*relativePath) + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribeDataSource::HasAssertion(nsIRDFResource *source, + nsIRDFResource *property, + nsIRDFNode *target, + bool tv, + bool *hasAssertion /* out */) +{ + nsresult rv = NS_OK; + + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(property != nullptr, "null ptr"); + if (! property) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(target != nullptr, "null ptr"); + if (! target) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(hasAssertion != nullptr, "null ptr"); + if (! hasAssertion) + return NS_ERROR_NULL_POINTER; + + *hasAssertion = false; + + // we only have positive assertions in the subscribe data source. + if (!tv) return NS_OK; + + if (property == kNC_Child.get()) { + nsCOMPtr<nsISubscribableServer> server; + nsCString relativePath; + + rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath)); + if (NS_FAILED(rv) || !server) { + *hasAssertion = false; + return NS_OK; + } + + // not everything has children + rv = server->HasChildren(relativePath, hasAssertion); + NS_ENSURE_SUCCESS(rv,rv); + } + else if (property == kNC_Name.get()) { + // everything has a name + *hasAssertion = true; + } + else if (property == kNC_LeafName.get()) { + // everything has a leaf name + *hasAssertion = true; + } + else if (property == kNC_Subscribed.get()) { + // everything is subscribed or not + *hasAssertion = true; + } + else if (property == kNC_Subscribable.get()) { + // everything is subscribable or not + *hasAssertion = true; + } + else if (property == kNC_ServerType.get()) { + // everything has a server type + *hasAssertion = true; + } + else { + // do nothing + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsSubscribeDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSubscribeDataSource::HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc, bool *result) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsISubscribableServer> server; + nsCString relativePath; + + if (aArc == kNC_Child.get()) { + rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath)); + if (NS_FAILED(rv) || !server) { + *result = false; + return NS_OK; + } + + bool hasChildren = false; + rv = server->HasChildren(relativePath, &hasChildren); + NS_ENSURE_SUCCESS(rv,rv); + *result = hasChildren; + return NS_OK; + } + else if ((aArc == kNC_Subscribed.get()) || + (aArc == kNC_Subscribable.get()) || + (aArc == kNC_LeafName.get()) || + (aArc == kNC_ServerType.get()) || + (aArc == kNC_Name.get())) { + *result = true; + return NS_OK; + } + + *result = false; + return NS_OK; +} + + +NS_IMETHODIMP +nsSubscribeDataSource::ArcLabelsIn(nsIRDFNode *node, + nsISimpleEnumerator ** labels /* out */) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::ArcLabelsOut(nsIRDFResource *source, + nsISimpleEnumerator **labels /* out */) +{ + nsresult rv = NS_OK; + + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(labels != nullptr, "null ptr"); + if (! labels) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsISubscribableServer> server; + nsCString relativePath; + + rv = GetServerAndRelativePathFromResource(source, getter_AddRefs(server), getter_Copies(relativePath)); + if (NS_FAILED(rv) || !server) { + return NS_NewEmptyEnumerator(labels); + } + + bool hasChildren = false; + rv = server->HasChildren(relativePath, &hasChildren); + NS_ENSURE_SUCCESS(rv,rv); + + // Initialise with the number of items below, to save reallocating on each + // addition. + nsCOMArray<nsIRDFResource> array(hasChildren ? 6 : 5); + + array.AppendObject(kNC_Subscribed); + array.AppendObject(kNC_Subscribable); + array.AppendObject(kNC_Name); + array.AppendObject(kNC_ServerType); + array.AppendObject(kNC_LeafName); + + if (hasChildren) { + array.AppendObject(kNC_Child); + } + + return NS_NewArrayEnumerator(labels, array); +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetAllResources(nsISimpleEnumerator** aCursor) +{ + NS_NOTYETIMPLEMENTED("sorry!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::AddObserver(nsIRDFObserver *n) +{ + NS_ENSURE_ARG_POINTER(n); + mObservers.AppendElement(n); + return NS_OK; +} + + +NS_IMETHODIMP +nsSubscribeDataSource::RemoveObserver(nsIRDFObserver *n) +{ + NS_ENSURE_ARG_POINTER(n); + mObservers.RemoveElement(n); + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetHasObservers(bool *hasObservers) +{ + NS_ENSURE_ARG_POINTER(hasObservers); + *hasObservers = !mObservers.IsEmpty(); + return NS_OK; +} + +NS_IMETHODIMP +nsSubscribeDataSource::GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/*<nsIRDFResource>*/** commands) +{ + return(NS_NewEmptyEnumerator(commands)); +} + +NS_IMETHODIMP +nsSubscribeDataSource::IsCommandEnabled(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources, + nsIRDFResource* aCommand, + nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments, + bool* aResult) +{ + return(NS_ERROR_NOT_IMPLEMENTED); +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::DoCommand(nsISupports/*nsISupportsArray<nsIRDFResource>*/* aSources, + nsIRDFResource* aCommand, + nsISupports/*nsISupportsArray<nsIRDFResource>*/* aArguments) +{ + return(NS_ERROR_NOT_IMPLEMENTED); +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::BeginUpdateBatch() +{ + return NS_OK; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::EndUpdateBatch() +{ + return NS_OK; +} + + + +NS_IMETHODIMP +nsSubscribeDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval) +{ + NS_ASSERTION(false, "Not implemented"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +#define NOTIFY_SUBSCRIBE_LISTENERS(propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + { \ + nsTObserverArray<nsCOMPtr<nsIRDFObserver> >::ForwardIterator iter(mObservers); \ + while (iter.HasMore()) \ + { \ + iter.GetNext()->propertyfunc_ params_; \ + } \ + } \ + PR_END_MACRO + +NS_IMETHODIMP +nsSubscribeDataSource::NotifyObservers(nsIRDFResource *subject, + nsIRDFResource *property, + nsIRDFNode *object, + bool assert, bool change) +{ + NS_ASSERTION(!(change && assert), + "Can't change and assert at the same time!\n"); + + if (change) + NOTIFY_SUBSCRIBE_LISTENERS(OnChange, (this, subject, property, nullptr, object)); + else if (assert) + NOTIFY_SUBSCRIBE_LISTENERS(OnAssert, (this, subject, property, object)); + else + NOTIFY_SUBSCRIBE_LISTENERS(OnUnassert, (this, subject, property, object)); + return NS_OK; +} diff --git a/mailnews/base/src/nsSubscribeDataSource.h b/mailnews/base/src/nsSubscribeDataSource.h new file mode 100644 index 0000000000..bfe72c4c46 --- /dev/null +++ b/mailnews/base/src/nsSubscribeDataSource.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef nsSubscribeDataSource_h__ +#define nsSubscribeDataSource_h__ + +#include "nsIRDFService.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFResource.h" +#include "nsIRDFLiteral.h" +#include "nsCOMPtr.h" +#include "nsISubscribableServer.h" +#include "nsTObserverArray.h" + +/** + * The subscribe data source. + */ +class nsSubscribeDataSource : public nsIRDFDataSource, public nsISubscribeDataSource +{ + +public: + nsSubscribeDataSource(); + + nsresult Init(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRDFDATASOURCE + NS_DECL_NSISUBSCRIBEDATASOURCE + +private: + virtual ~nsSubscribeDataSource(); + nsCOMPtr <nsIRDFResource> kNC_Child; + nsCOMPtr <nsIRDFResource> kNC_Name; + nsCOMPtr <nsIRDFResource> kNC_LeafName; + nsCOMPtr <nsIRDFResource> kNC_Subscribed; + nsCOMPtr <nsIRDFResource> kNC_Subscribable; + nsCOMPtr <nsIRDFResource> kNC_ServerType; + nsCOMPtr <nsIRDFLiteral> kTrueLiteral; + nsCOMPtr <nsIRDFLiteral> kFalseLiteral; + + nsCOMPtr <nsIRDFService> mRDFService; + nsTObserverArray<nsCOMPtr<nsIRDFObserver> > mObservers; + + nsresult GetServerAndRelativePathFromResource(nsIRDFResource *source, nsISubscribableServer **server, char **relativePath); + nsresult GetServerType(nsISubscribableServer *server, nsACString& serverType); +}; + +#endif /* nsSubscribedDataSource_h__ */ diff --git a/mailnews/base/src/virtualFolderWrapper.js b/mailnews/base/src/virtualFolderWrapper.js new file mode 100644 index 0000000000..efb4ed68af --- /dev/null +++ b/mailnews/base/src/virtualFolderWrapper.js @@ -0,0 +1,255 @@ +/* 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/. */ + +/* + * Wrap everything about virtual folders. + */ + +this.EXPORTED_SYMBOLS = ['VirtualFolderHelper']; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; + +Cu.import("resource:///modules/mailServices.js"); +Cu.import("resource:///modules/iteratorUtils.jsm"); + +var VirtualFolderHelper = { + /** + * Create a new virtual folder (an actual nsIMsgFolder that did not previously + * exist), wrapping it in a VirtualFolderWrapper, and returning that wrapper. + * + * If the call to addSubfolder fails (and therefore throws), we will NOT catch + * it. + * + * @param aFolderName The name of the new folder to create. + * @param aParentFolder The folder in which to create the search folder. + * @param aSearchFolders A list of nsIMsgFolders that you want to use as the + * sources for the virtual folder OR a string that is the already '|' + * delimited list of folder URIs to use. + * @param aSearchTerms The search terms to use for the virtual folder. This + * should be a JS list/nsIMutableArray/nsISupportsArray of + * nsIMsgSearchTermbs. + * @param aOnlineSearch Should the search attempt to use the server's search + * capabilities when possible and appropriate? + * + * @return The VirtualFolderWrapper wrapping the newly created folder. You + * would probably only want this for its virtualFolder attribute which has + * the nsIMsgFolder we created. Be careful about accessing any of the + * other attributes, as they will bring its message database back to life. + */ + createNewVirtualFolder: function (aFolderName, aParentFolder, aSearchFolders, + aSearchTerms, aOnlineSearch) { + let msgFolder = aParentFolder.addSubfolder(aFolderName); + msgFolder.prettyName = aFolderName; + msgFolder.setFlag(Ci.nsMsgFolderFlags.Virtual); + + let wrappedVirt = new VirtualFolderWrapper(msgFolder); + wrappedVirt.searchTerms = aSearchTerms; + wrappedVirt.searchFolders = aSearchFolders; + wrappedVirt.onlineSearch = aOnlineSearch; + + let msgDatabase = msgFolder.msgDatabase; + msgDatabase.summaryValid = true; + msgDatabase.Close(true); + + aParentFolder.NotifyItemAdded(msgFolder); + MailServices.accounts.saveVirtualFolders(); + + return wrappedVirt; + }, + + /** + * Given an existing nsIMsgFolder that is a virtual folder, wrap it into a + * VirtualFolderWrapper. + */ + wrapVirtualFolder: function (aMsgFolder) { + return new VirtualFolderWrapper(aMsgFolder); + }, +}; + +/** + * Abstracts dealing with the properties of a virtual folder that differentiate + * it from a non-virtual folder. A virtual folder is an odd duck. When + * holding an nsIMsgFolder that is a virtual folder, it is distinguished by + * the virtual flag and a number of properties that tell us the string + * representation of its search, the folders it searches over, and whether we + * use online searching or not. + * Virtual folders and their defining attributes are loaded from + * virtualFolders.dat (in the profile directory) by the account manager at + * startup, (re-)creating them if need be. It also saves them back to the + * file at shutdown. The most important thing the account manager does is to + * create VirtualFolderChangeListener instances that are registered with the + * message database service. This means that if one of the databases for the + * folders that the virtual folder includes is opened for some reason (for + * example, new messages are added to the folder because of a filter or they + * are delivered there), the virtual folder gets a chance to know about this + * and update the virtual folder's "cache" of information, such as the message + * counts or the presence of the message in the folder. + * The odd part is that a lot of the virtual folder logic also happens as a + * result of the nsMsgDBView subclasses being told the search query and the + * underlying folders. This makes for an odd collaboration of UI and backend + * logic. + * + * Justification for this class: Virtual folders aren't all that complex, but + * they are complex enough that we don't want to have the same code duplicated + * all over the place. We also don't want to have a loose assembly of global + * functions for working with them. So here we are. + * + * Important! Accessing any of our attributes results in the message database + * being loaded so that we can access the dBFolderInfo associated with the + * database. The message database is not automatically forgotten by the + * folder, which can lead to an (effective) memory leak. Please make sure + * that you are playing your part in not leaking memory by only using the + * wrapper when you have a serious need to access the database, and by + * forcing the folder to forget about the database when you are done by + * setting the database to null (unless you know with confidence someone else + * definitely wants the database around and will clean it up.) + */ +function VirtualFolderWrapper(aVirtualFolder) { + this.virtualFolder = aVirtualFolder; +}; +VirtualFolderWrapper.prototype = { + /** + * @return the list of nsIMsgFolders that this virtual folder is a + * search over. + */ + get searchFolders() { + let rdfService = Cc['@mozilla.org/rdf/rdf-service;1'] + .getService(Ci.nsIRDFService); + let virtualFolderUris = + this.dbFolderInfo.getCharProperty("searchFolderUri").split("|"); + let folders = []; + for (let folderURI of virtualFolderUris) { + if (folderURI) + folders.push(rdfService.GetResource(folderURI) + .QueryInterface(Ci.nsIMsgFolder)); + } + return folders; + }, + /** + * Set the search folders that back this virtual folder. + * + * @param aFolders Either a "|"-delimited string of folder URIs or a list of + * nsIMsgFolders that fixIterator can traverse (JS array/nsIMutableArray/ + * nsISupportsArray). + */ + set searchFolders(aFolders) { + if (typeof(aFolders) == "string") { + this.dbFolderInfo.setCharProperty("searchFolderUri", aFolders); + } + else { + let uris = Array.from(fixIterator(aFolders, Ci.nsIMsgFolder)) + .map(folder => folder.URI); + this.dbFolderInfo.setCharProperty("searchFolderUri", uris.join("|")); + } + }, + + /** + * @return a "|"-delimited string containing the URIs of the folders that back + * this virtual folder. + */ + get searchFolderURIs() { + return this.dbFolderInfo.getCharProperty("searchFolderUri"); + }, + + /** + * @return the list of search terms that define this virtual folder. + */ + get searchTerms() { + return this.searchTermsSession.searchTerms; + }, + /** + * @return a newly created filter with the search terms loaded into it that + * define this virtual folder. The filter is apparently useful as an + * nsIMsgSearchSession stand-in to some code. + */ + get searchTermsSession() { + // Temporary means it doesn't get exposed to the UI and doesn't get saved to + // disk. Which is good, because this is just a trick to parse the string + // into search terms. + let filterList = MailServices.filters.getTempFilterList(this.virtualFolder); + let tempFilter = filterList.createFilter("temp"); + filterList.parseCondition(tempFilter, this.searchString); + return tempFilter; + }, + + /** + * Set the search string for this virtual folder to the stringified version of + * the provided list of nsIMsgSearchTerm search terms. If you already have + * a strinigified version of the search constraint, just set |searchString| + * directly. + * + * @param aTerms Some collection that fixIterator can traverse. A JS list or + * XPCOM array (nsIMutableArray or nsISupportsArray) should work. + */ + set searchTerms(aTerms) { + let condition = ""; + for (let term in fixIterator(aTerms, Ci.nsIMsgSearchTerm)) { + if (condition.length) + condition += " "; + if (term.matchAll) { + condition = "ALL"; + break; + } + condition += (term.booleanAnd) ? "AND (" : "OR ("; + condition += term.termAsString + ")"; + } + this.searchString = condition; + }, + + /** + * @return the set of search terms that define this virtual folder as a + * string. You may prefer to use |searchTerms| which converts them + * into a list of nsIMsgSearchTerms instead. + */ + get searchString() { + return this.dbFolderInfo.getCharProperty("searchStr"); + }, + /** + * Set the search that defines this virtual folder from a string. If you have + * a list of nsIMsgSearchTerms, you should use |searchTerms| instead. + */ + set searchString(aSearchString) { + this.dbFolderInfo.setCharProperty("searchStr", aSearchString); + }, + + /** + * @return whether the virtual folder is configured for online search. + */ + get onlineSearch() { + return this.dbFolderInfo.getBooleanProperty("searchOnline", false); + }, + /** + * Set whether the virtual folder is configured for online search. + */ + set onlineSearch(aOnlineSearch) { + this.dbFolderInfo.setBooleanProperty("searchOnline", aOnlineSearch); + }, + + /** + * @return the dBFolderInfo associated with the virtual folder directly. May + * be null. Will cause the message database to be opened, which may have + * memory bloat/leak ramifications, so make sure the folder's database was + * already going to be opened anyways or that you call + * |cleanUpMessageDatabase|. + */ + get dbFolderInfo() { + let msgDatabase = this.virtualFolder.msgDatabase; + return (msgDatabase && msgDatabase.dBFolderInfo); + }, + + /** + * Avoid memory bloat by making the virtual folder forget about its database. + * If the database is actually in use (read: someone is keeping it alive by + * having references to it from places other than the nsIMsgFolder), the + * folder will be able to re-establish the reference for minimal cost. + */ + cleanUpMessageDatabase: + function VirtualFolderWrapper_cleanUpMessageDatabase() { + this.virtualFolder.msgDatabase.Close(true); + this.virtualFolder.msgDatabase = null; + } +}; |