summaryrefslogtreecommitdiff
path: root/mailnews/base/src
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/src')
-rw-r--r--mailnews/base/src/MailNewsDLF.cpp101
-rw-r--r--mailnews/base/src/MailNewsDLF.h38
-rw-r--r--mailnews/base/src/MailnewsLoadContextInfo.cpp55
-rw-r--r--mailnews/base/src/MailnewsLoadContextInfo.h32
-rw-r--r--mailnews/base/src/folderLookupService.js99
-rw-r--r--mailnews/base/src/moz.build82
-rw-r--r--mailnews/base/src/msgAsyncPrompter.js126
-rw-r--r--mailnews/base/src/msgBase.manifest12
-rw-r--r--mailnews/base/src/msgOAuth2Module.js150
-rw-r--r--mailnews/base/src/newMailNotificationService.js377
-rw-r--r--mailnews/base/src/nsCidProtocolHandler.cpp73
-rw-r--r--mailnews/base/src/nsCidProtocolHandler.h24
-rw-r--r--mailnews/base/src/nsCopyMessageStreamListener.cpp145
-rw-r--r--mailnews/base/src/nsCopyMessageStreamListener.h37
-rw-r--r--mailnews/base/src/nsMailDirProvider.cpp206
-rw-r--r--mailnews/base/src/nsMailDirProvider.h43
-rw-r--r--mailnews/base/src/nsMailDirServiceDefs.h31
-rw-r--r--mailnews/base/src/nsMailNewsCommandLineHandler.js170
-rw-r--r--mailnews/base/src/nsMessenger.cpp3072
-rw-r--r--mailnews/base/src/nsMessenger.h102
-rw-r--r--mailnews/base/src/nsMessengerBootstrap.cpp83
-rw-r--r--mailnews/base/src/nsMessengerBootstrap.h31
-rw-r--r--mailnews/base/src/nsMessengerContentHandler.cpp81
-rw-r--r--mailnews/base/src/nsMessengerContentHandler.h20
-rw-r--r--mailnews/base/src/nsMessengerOSXIntegration.h63
-rw-r--r--mailnews/base/src/nsMessengerOSXIntegration.mm730
-rw-r--r--mailnews/base/src/nsMessengerUnixIntegration.cpp762
-rw-r--r--mailnews/base/src/nsMessengerUnixIntegration.h63
-rw-r--r--mailnews/base/src/nsMessengerWinIntegration.cpp1191
-rw-r--r--mailnews/base/src/nsMessengerWinIntegration.h122
-rw-r--r--mailnews/base/src/nsMsgAccount.cpp435
-rw-r--r--mailnews/base/src/nsMsgAccount.h38
-rw-r--r--mailnews/base/src/nsMsgAccountManager.cpp3708
-rw-r--r--mailnews/base/src/nsMsgAccountManager.h216
-rw-r--r--mailnews/base/src/nsMsgAccountManagerDS.cpp1183
-rw-r--r--mailnews/base/src/nsMsgAccountManagerDS.h142
-rw-r--r--mailnews/base/src/nsMsgBiffManager.cpp373
-rw-r--r--mailnews/base/src/nsMsgBiffManager.h55
-rw-r--r--mailnews/base/src/nsMsgContentPolicy.cpp1076
-rw-r--r--mailnews/base/src/nsMsgContentPolicy.h93
-rw-r--r--mailnews/base/src/nsMsgCopyService.cpp708
-rw-r--r--mailnews/base/src/nsMsgCopyService.h94
-rw-r--r--mailnews/base/src/nsMsgDBView.cpp8066
-rw-r--r--mailnews/base/src/nsMsgDBView.h513
-rw-r--r--mailnews/base/src/nsMsgFolderCache.cpp376
-rw-r--r--mailnews/base/src/nsMsgFolderCache.h50
-rw-r--r--mailnews/base/src/nsMsgFolderCacheElement.cpp163
-rw-r--r--mailnews/base/src/nsMsgFolderCacheElement.h35
-rw-r--r--mailnews/base/src/nsMsgFolderCompactor.cpp1348
-rw-r--r--mailnews/base/src/nsMsgFolderCompactor.h115
-rw-r--r--mailnews/base/src/nsMsgFolderDataSource.cpp2475
-rw-r--r--mailnews/base/src/nsMsgFolderDataSource.h356
-rw-r--r--mailnews/base/src/nsMsgFolderNotificationService.cpp172
-rw-r--r--mailnews/base/src/nsMsgFolderNotificationService.h46
-rw-r--r--mailnews/base/src/nsMsgGroupThread.cpp856
-rw-r--r--mailnews/base/src/nsMsgGroupThread.h87
-rw-r--r--mailnews/base/src/nsMsgGroupView.cpp1024
-rw-r--r--mailnews/base/src/nsMsgGroupView.h74
-rw-r--r--mailnews/base/src/nsMsgMailSession.cpp761
-rw-r--r--mailnews/base/src/nsMsgMailSession.h106
-rw-r--r--mailnews/base/src/nsMsgOfflineManager.cpp399
-rw-r--r--mailnews/base/src/nsMsgOfflineManager.h85
-rw-r--r--mailnews/base/src/nsMsgPrintEngine.cpp741
-rw-r--r--mailnews/base/src/nsMsgPrintEngine.h91
-rw-r--r--mailnews/base/src/nsMsgProgress.cpp262
-rw-r--r--mailnews/base/src/nsMsgProgress.h47
-rw-r--r--mailnews/base/src/nsMsgPurgeService.cpp486
-rw-r--r--mailnews/base/src/nsMsgPurgeService.h55
-rw-r--r--mailnews/base/src/nsMsgQuickSearchDBView.cpp882
-rw-r--r--mailnews/base/src/nsMsgQuickSearchDBView.h87
-rw-r--r--mailnews/base/src/nsMsgRDFDataSource.cpp371
-rw-r--r--mailnews/base/src/nsMsgRDFDataSource.h66
-rw-r--r--mailnews/base/src/nsMsgRDFUtils.cpp82
-rw-r--r--mailnews/base/src/nsMsgRDFUtils.h104
-rw-r--r--mailnews/base/src/nsMsgSearchDBView.cpp1433
-rw-r--r--mailnews/base/src/nsMsgSearchDBView.h146
-rw-r--r--mailnews/base/src/nsMsgServiceProvider.cpp139
-rw-r--r--mailnews/base/src/nsMsgServiceProvider.h34
-rw-r--r--mailnews/base/src/nsMsgSpecialViews.cpp179
-rw-r--r--mailnews/base/src/nsMsgSpecialViews.h71
-rw-r--r--mailnews/base/src/nsMsgStatusFeedback.cpp303
-rw-r--r--mailnews/base/src/nsMsgStatusFeedback.h50
-rw-r--r--mailnews/base/src/nsMsgTagService.cpp560
-rw-r--r--mailnews/base/src/nsMsgTagService.h57
-rw-r--r--mailnews/base/src/nsMsgThreadedDBView.cpp983
-rw-r--r--mailnews/base/src/nsMsgThreadedDBView.h51
-rw-r--r--mailnews/base/src/nsMsgWindow.cpp534
-rw-r--r--mailnews/base/src/nsMsgWindow.h61
-rw-r--r--mailnews/base/src/nsMsgXFViewThread.cpp481
-rw-r--r--mailnews/base/src/nsMsgXFViewThread.h54
-rw-r--r--mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp514
-rw-r--r--mailnews/base/src/nsMsgXFVirtualFolderDBView.h61
-rw-r--r--mailnews/base/src/nsSpamSettings.cpp892
-rw-r--r--mailnews/base/src/nsSpamSettings.h71
-rw-r--r--mailnews/base/src/nsStatusBarBiffManager.cpp251
-rw-r--r--mailnews/base/src/nsStatusBarBiffManager.h44
-rw-r--r--mailnews/base/src/nsSubscribableServer.cpp805
-rw-r--r--mailnews/base/src/nsSubscribableServer.h80
-rw-r--r--mailnews/base/src/nsSubscribeDataSource.cpp673
-rw-r--r--mailnews/base/src/nsSubscribeDataSource.h50
-rw-r--r--mailnews/base/src/virtualFolderWrapper.js255
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, &copyImmediately);
+ 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", &currentDisplayNameVersion);
+
+ // 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", &currentDisplayNameVersion);
+ 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", &currentDisplayNameVersion);
+ 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(&currentIndex)) &&
+ 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(&currentIndex);
+ 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, &currentExplodedTime);
+ 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, &notUsed);
+ }
+ }
+ 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, &notified);
+ 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, &note);
+ else if (assert)
+ mObservers.EnumerateForwards(assertEnumFunc, &note);
+ else
+ mObservers.EnumerateForwards(unassertEnumFunc, &note);
+ 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(&currentIndex)) &&
+ 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(&currentIndex)) &&
+ 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;
+ }
+};