download imap messages for offline use onidle, patch by emre, r/sr=bienvenu, 436616
authorDavid Bienvenu <bienvenu@nventure.com>
Wed, 17 Sep 2008 16:56:34 -0700
changeset 367 4df6a0bfc947a3eb1d7f45b6bd99078737f3ee23
parent 366 2badeaeb4bdf11c7ec07a71111d8b3736e0a876b
child 368 f8ca45bb7b11f4632edd40a4724e52fe3c0c2b19
push idunknown
push userunknown
push dateunknown
bugs436616
download imap messages for offline use onidle, patch by emre, r/sr=bienvenu, 436616
mailnews/imap/build/nsImapFactory.cpp
mailnews/imap/public/Makefile.in
mailnews/imap/public/nsIAutoSyncFolderStrategy.idl
mailnews/imap/public/nsIAutoSyncManager.idl
mailnews/imap/public/nsIAutoSyncMsgStrategy.idl
mailnews/imap/public/nsIAutoSyncState.idl
mailnews/imap/public/nsIMsgImapMailFolder.idl
mailnews/imap/src/Makefile.in
mailnews/imap/src/nsAutoSyncManager.cpp
mailnews/imap/src/nsAutoSyncManager.h
mailnews/imap/src/nsAutoSyncState.cpp
mailnews/imap/src/nsAutoSyncState.h
mailnews/imap/src/nsImapMailFolder.cpp
mailnews/imap/src/nsImapMailFolder.h
mailnews/imap/src/nsImapService.cpp
mailnews/mailnews.js
--- a/mailnews/imap/build/nsImapFactory.cpp
+++ b/mailnews/imap/build/nsImapFactory.cpp
@@ -44,25 +44,27 @@
 #include "nsIMAPHostSessionList.h"
 #include "nsImapIncomingServer.h"
 #include "nsImapService.h"
 #include "nsImapMailFolder.h"
 #include "nsImapUrl.h"
 #include "nsImapProtocol.h"
 #include "nsMsgImapCID.h"
 #include "nsCURILoader.h"
+#include "nsAutoSyncManager.h"
 
 // private factory declarations for each component we know how to produce
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapUrl)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapProtocol)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsIMAPHostSessionList, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapIncomingServer)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapMailFolder)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsImapMockChannel)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoSyncManager)
 
 // The list of components we register
 static const nsModuleComponentInfo gComponents[] = {
     { "IMAP URL", NS_IMAPURL_CID,
       nsnull, nsImapUrlConstructor },
 
     { "IMAP Protocol Channel", NS_IMAPPROTOCOL_CID,
       nsnull, nsImapProtocolConstructor },    
@@ -92,15 +94,20 @@ static const nsModuleComponentInfo gComp
       NS_IMAPSERVICE_CONTRACTID,
       nsImapServiceConstructor },
 
     { "IMAP Protocol Handler", NS_IMAPSERVICE_CID,
       NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "imap", nsImapServiceConstructor},
 
     { "IMAP Protocol Handler", NS_IMAPSERVICE_CID,
       NS_IMAPPROTOCOLINFO_CONTRACTID, nsImapServiceConstructor },
-  { "imap folder content handler",
-    NS_IMAPSERVICE_CID,
-    NS_CONTENT_HANDLER_CONTRACTID_PREFIX"x-application-imapfolder",
-    nsImapServiceConstructor }
+      
+    { "imap folder content handler",
+      NS_IMAPSERVICE_CID,
+      NS_CONTENT_HANDLER_CONTRACTID_PREFIX"x-application-imapfolder",
+      nsImapServiceConstructor },
+    
+    { "Auto-Sync Manager", NS_AUTOSYNCMANAGER_CID,
+      NS_AUTOSYNCMANAGER_CONTRACTID, 
+      nsAutoSyncManagerConstructor }
 };
 
 NS_IMPL_NSGETMODULE(IMAP_factory, gComponents)
--- a/mailnews/imap/public/Makefile.in
+++ b/mailnews/imap/public/Makefile.in
@@ -56,14 +56,18 @@ XPIDLSRCS	= \
 		nsIImapMessageSink.idl \
 		nsIImapProtocolSink.idl \
 		nsIImapIncomingServer.idl \
 		nsIImapFlagAndUidState.idl \
 		nsIImapMockChannel.idl \
 		nsIImapProtocol.idl \
 		nsIMailboxSpec.idl \
 		nsIImapMailFolderSink.idl \
-        	nsIMsgImapMailFolder.idl \
-    nsIImapHeaderXferInfo.idl \
+		nsIMsgImapMailFolder.idl \
+		nsIImapHeaderXferInfo.idl \
+		nsIAutoSyncState.idl \
+		nsIAutoSyncManager.idl \
+		nsIAutoSyncFolderStrategy.idl \
+		nsIAutoSyncMsgStrategy.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl
@@ -0,0 +1,55 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#include "nsISupports.idl"
+#include "nsIAutoSyncMsgStrategy.idl"
+
+interface nsIMsgAccount;
+
+[scriptable, uuid(d3bf91cc-37bb-4752-9994-1a8473e46a90)] 
+interface nsIAutoSyncFolderStrategy : nsISupports {
+
+  /**
+   * Returns a relative-priority for the second folder by comparing with the first one.
+   */
+  nsAutoSyncStrategyDecisionType sort(in nsIMsgFolder aFolder1, in nsIMsgFolder aFolder2);
+  
+  /**
+   * Tests whether the given folder should be excluded or not.
+   */
+  boolean isExcluded(in nsIMsgFolder aFolder);
+
+};
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncManager.idl
@@ -0,0 +1,106 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#include "nsISupports.idl"
+
+interface nsIAutoSyncMsgStrategy;
+interface nsIAutoSyncFolderStrategy;
+interface nsIMsgDBHdr;
+interface nsIAutoSyncState;
+
+[scriptable, uuid(C358C568-47B2-42b2-8146-3C0F8D1FAD6E)] 
+interface nsIAutoSyncManager : nsISupports {
+
+  /** 
+   * Download models
+   */
+  const long dmParallel = 0;
+  const long dmChained = 1;
+
+  /**
+   * Cumulative message size per download
+   */
+  attribute unsigned long groupSize;
+  
+  /** 
+   * Active strategy function to prioritize
+   * messages in the download queue
+   */
+  attribute nsIAutoSyncMsgStrategy msgStrategy;
+  
+  /** 
+   * Active strategy function to prioritize
+   * folders in the download queue
+   */
+  attribute nsIAutoSyncFolderStrategy folderStrategy;
+    
+  boolean doesMsgFitDownloadCriteria(in nsIMsgDBHdr aMsgHdr);
+  
+  /**
+   * Called by the nsAutoSyncState object when the download
+   * queue is changed. Given interface is already addref'd.
+   */
+  void onDownloadQChanged(in nsIAutoSyncState aAutoSyncStateObj);
+  
+  /**
+   * Called by the nsAutoSyncState object when the download
+   * is started. Given interface is already addref'd.
+   */
+  void onDownloadStarted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aStartCode);
+  
+  /**
+   * Called by the nsAutoSyncState object when the download
+   * completed. Given interface is already addref'd.
+   */
+  void onDownloadCompleted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aExitCode);
+  
+  /**
+   * Active download model; Chained (serial), or Parallel
+   */
+  attribute long downloadModel;
+};
+
+%{C++
+#define NS_AUTOSYNCMANAGER_CID  \
+{ /* C358C568-47B2-42b2-8146-3C0F8D1FAD6E */  \
+  0xc358c568, 0x47b2, 0x42b2,                 \
+  { 0x81, 0x46, 0x3c, 0xf, 0x8d, 0x1f, 0xad, 0x6e }}
+#define NS_AUTOSYNCMANAGER_CLASSNAME \
+  "Auto-Sync Manager"
+#define NS_AUTOSYNCMANAGER_CONTRACTID \
+  "@mozilla.org/imap/autosyncmgr;1"
+%}
+
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl
@@ -0,0 +1,67 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+
+typedef long nsAutoSyncStrategyDecisionType;
+
+[scriptable,uuid(0365bec5-3753-43c2-b13e-441747815f37)]
+interface nsAutoSyncStrategyDecisions
+{
+  /// same priority
+  const nsAutoSyncStrategyDecisionType Same         = 0x00000001;
+  /// higher priority
+  const nsAutoSyncStrategyDecisionType Higher       = 0x00000002;
+  /// lower priority
+  const nsAutoSyncStrategyDecisionType Lower        = 0x00000004;
+};
+
+[scriptable, uuid(9cb4baff-3112-4cf8-8463-f81b0aa78f93)] 
+interface nsIAutoSyncMsgStrategy : nsISupports {
+
+  /**
+   * Returns a relative-priority for the second message by comparing with the first message.
+   */
+  nsAutoSyncStrategyDecisionType sort(in nsIMsgFolder aFolder, in nsIMsgDBHdr aMsgHdr1, in nsIMsgDBHdr aMsgHdr2);
+  
+  /**
+   * Tests whether the given message should be excluded or not.
+   */
+  boolean isExcluded(in nsIMsgFolder aFolder, in nsIMsgDBHdr aMsgHdr);
+};
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/public/nsIAutoSyncState.idl
@@ -0,0 +1,118 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIArray;
+interface nsIMutableArray;
+
+[scriptable, uuid(4ede03df-9d8b-4c99-b462-daefb9f70493)] 
+interface nsIAutoSyncState : nsISupports {
+
+  /**
+   * Auto-Sync states
+   */
+   
+  /** sync'd and no pending messages */
+  const long stCompletedIdle = 0;     
+  
+  const long stDownloadInProgress	= 1;
+  
+  /** ready to download the next group of messages */
+  const long stReadyToDownload = 2;             
+
+  /**
+   * Puts the download queue offset to its previous position. 
+   */
+  void rollback();
+  
+  /**
+   * Clears the download queue. Resets the offsets.
+   */
+  void resetDownloadQ();
+  
+  /**
+   * Rollbacks the offset to the previous position and
+   * changes the state to ready-to-download.
+   */
+  void tryCurrentGroupAgain(in unsigned long aRetryCount);
+  
+  void resetRetryCounter();
+  
+  /**
+   * Tests whether the given folder has the same imap server. 
+   */
+  boolean isSibling(in nsIAutoSyncState aAnotherStateObj);
+
+  void downloadMessagesForOffline(in nsIArray aMessageList);
+
+  /**
+   * Populates the given array with the keys of the messages that will 
+   * be downloaded next.
+   */
+  nsIMutableArray getNextGroupOfMessages();
+  
+  /**
+   * Iterates through the existing headers of the folder to find 
+   * the messages not downloaded yet. 
+   *
+   * @param aNumberOfHeadersToProcess  - num to process in this pass
+   *
+   * @ret   the number of headers left to process
+   */
+  unsigned long processExistingHeaders(in unsigned long aNumberOfHeadersToProcess);
+
+  /**
+   * Last time the existing headers are completely processed. 
+   */
+  [noscript]readonly attribute PRTime lastSyncTime;
+  
+  /**
+   * Last time the owner folder is updated.
+   */
+  [noscript]attribute PRTime lastUpdateTime;
+  
+  /**
+   * Download operation state. 
+   */
+  attribute long state;
+  
+  readonly attribute long pendingMessageCount;
+  
+  readonly attribute nsIMsgFolder ownerFolder;
+};
--- a/mailnews/imap/public/nsIMsgImapMailFolder.idl
+++ b/mailnews/imap/public/nsIMsgImapMailFolder.idl
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 #include "nsIMsgFolder.idl"
 
 interface nsIMsgWindow;
 interface nsIImapIncomingServer;
 interface nsIMsgParseMailMsgState;
+interface nsIAutoSyncState;
 
 // this is a simple interface which allows the imap folder to update some values
 // that the folder props js code will use to update the sharing and quota tabs in the folder props.
                  
 [scriptable, uuid(09D99F2C-3E23-4f8c-A536-5C277BAA9585)] 
 interface nsIMsgImapFolderProps : nsISupports {
 
     void setFolderType(in AString folderType);
@@ -109,29 +110,32 @@ interface nsIMsgImapMailFolder : nsISupp
   nsIURI fetchCustomMsgAttribute(in ACString msgAttribute, in string uids, in nsIMsgWindow aWindow);
   nsIURI storeCustomKeywords(in nsIMsgWindow aMsgWindow,
                       in ACString aFlagsToAdd,
                       in ACString aFlagsToSubtract,
                       [array, size_is (aNumKeys)] in nsMsgKey aKeysToStore,
                       in unsigned long aNumKeys);
 
   void notifyIfNewMail();
+  
+  void initiateAutoSync(in nsIUrlListener aUrlListener);
 
   attribute boolean verifiedAsOnlineFolder;
   attribute boolean explicitlyVerify;
   attribute wchar hierarchyDelimiter;
   attribute long boxFlags;
   attribute ACString onlineName;
   attribute boolean isNamespace;
   readonly attribute boolean canOpenFolder;
   attribute ACString adminUrl;
   readonly attribute boolean hasAdminUrl;
   attribute boolean performingBiff;
   readonly attribute nsIMsgParseMailMsgState hdrParser;
   readonly attribute nsIImapIncomingServer imapIncomingServer;
+  readonly attribute nsIAutoSyncState autoSyncStateObj;
 
   /**
    * Quota
    * |valid| indicates whether the server has provided quota information on
    * this folder. This can be false
    * - if the server does not supports quotas,
    * - if there are no storage quotas on this folder, or
    * - if the folder has never been opened.
@@ -150,11 +154,10 @@ interface nsIMsgImapMailFolder : nsISupp
   void changePendingTotal(in long aDelta);
   /**
    * Change the number of "pending" unread messages in a folder,
    * unread messages we know about, but don't have the headers for yet
    *
    * @param aDelta amount to change the unread count by.
    */
   void changePendingUnread(in long aDelta);
-
-
+  
 };
--- a/mailnews/imap/src/Makefile.in
+++ b/mailnews/imap/src/Makefile.in
@@ -49,16 +49,17 @@ MOZILLA_INTERNAL_API = 1
 
 REQUIRES	= xpcom \
 		  string \
 		  rdf \
 		  msgbase \
 		  msgbaseutil \
 		  rdfutil \
 		  necko \
+		  widget \
 		  appshell \
 		  msgdb \
 		  dom \
 		  uconv \
 		  unicharutil \
 		  mime \
 		  caps \
 		  pref \
@@ -92,16 +93,18 @@ CPPSRCS		= \
 		nsImapSearchResults.cpp \
 		nsImapServerResponseParser.cpp \
 		nsImapService.cpp \
 		nsImapUrl.cpp \
 		nsImapFlagAndUidState.cpp \
 		nsImapUndoTxn.cpp \
 		nsImapStringBundle.cpp \
 		nsImapOfflineSync.cpp \
+		nsAutoSyncState.cpp \
+		nsAutoSyncManager.cpp \
 		$(NULL)
 
 include $(topsrcdir)/config/config.mk
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncManager.cpp
@@ -0,0 +1,1098 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#ifdef DEBUG_David_Bienvenu
+#define DEBUG_me
+#endif
+
+#include "nsAutoSyncManager.h"
+#include "nsAutoSyncState.h"
+#include "nsIIdleService.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgImapCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapIncomingServer.h"
+#include "nsMsgUtils.h"
+
+#ifdef DEBUG_me
+#define DEBUG_AutoSyncManager_L0
+#define DEBUG_AutoSyncManager_L1
+//#define DEBUG_AutoSyncManager_L2
+#endif
+
+NS_IMPL_ISUPPORTS1(nsDefaultAutoSyncMsgStrategy, nsIAutoSyncMsgStrategy)
+
+nsDefaultAutoSyncMsgStrategy::nsDefaultAutoSyncMsgStrategy()
+{
+}
+
+nsDefaultAutoSyncMsgStrategy::~nsDefaultAutoSyncMsgStrategy()
+{
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::Sort(nsIMsgFolder *aFolder, 
+  nsIMsgDBHdr *aMsgHdr1, nsIMsgDBHdr *aMsgHdr2, nsAutoSyncStrategyDecisionType *aDecision)
+{
+  NS_ENSURE_ARG_POINTER(aDecision);
+
+  PRUint32 msgSize1 = 0, msgSize2 = 0;
+  PRTime msgDate1 = 0, msgDate2 = 0;
+  
+  if (!aMsgHdr1 || !aMsgHdr2)
+  {
+    *aDecision = nsAutoSyncStrategyDecisions::Same;
+    return NS_OK;
+  }
+  
+  aMsgHdr1->GetMessageSize(&msgSize1);
+  aMsgHdr1->GetDate(&msgDate1);
+  
+  aMsgHdr2->GetMessageSize(&msgSize2);
+  aMsgHdr2->GetDate(&msgDate2);
+  
+  //Special case: if message size is larger than a 
+  // certain size, then place it to the bottom of the q
+  if (msgSize2 > kFirstPassMessageSize && msgSize1 > kFirstPassMessageSize)
+    *aDecision = msgSize2 > msgSize1 ? 
+        nsAutoSyncStrategyDecisions::Lower : nsAutoSyncStrategyDecisions::Higher;
+  else if (msgSize2 > kFirstPassMessageSize)
+    *aDecision = nsAutoSyncStrategyDecisions::Lower;
+  else if (msgSize1 > kFirstPassMessageSize)
+    *aDecision = nsAutoSyncStrategyDecisions::Higher;
+  else
+  {
+    // Most recent and smallest first
+    if (msgDate1 < msgDate2)
+      *aDecision = nsAutoSyncStrategyDecisions::Higher;
+    else if (msgDate1 > msgDate2)
+      *aDecision = nsAutoSyncStrategyDecisions::Lower;
+    else 
+    {
+      if (msgSize1 > msgSize2)
+        *aDecision = nsAutoSyncStrategyDecisions::Higher;
+      else if (msgSize1 < msgSize2)
+        *aDecision = nsAutoSyncStrategyDecisions::Lower;
+      else
+        *aDecision = nsAutoSyncStrategyDecisions::Same;
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::IsExcluded(nsIMsgFolder *aFolder, 
+  nsIMsgDBHdr *aMsgHdr, PRBool *aDecision)
+{
+  NS_ENSURE_ARG_POINTER(aDecision);
+  *aDecision = PR_FALSE;  
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(nsDefaultAutoSyncFolderStrategy, nsIAutoSyncFolderStrategy)
+
+nsDefaultAutoSyncFolderStrategy::nsDefaultAutoSyncFolderStrategy()
+{
+}
+
+nsDefaultAutoSyncFolderStrategy::~nsDefaultAutoSyncFolderStrategy()
+{
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncFolderStrategy::Sort(nsIMsgFolder *aFolderA, 
+  nsIMsgFolder *aFolderB, nsAutoSyncStrategyDecisionType *aDecision)
+{
+  NS_ENSURE_ARG_POINTER(aDecision);
+
+  if (!aFolderA || !aFolderB)
+  {
+    *aDecision = nsAutoSyncStrategyDecisions::Same;
+    return NS_OK;
+  }
+  
+  PRBool isInbox1, isInbox2, isDrafts1, isDrafts2, isTrash1, isTrash2;
+  aFolderA->GetFlag(nsMsgFolderFlags::Inbox, &isInbox1);
+  aFolderB->GetFlag(nsMsgFolderFlags::Inbox, &isInbox2);
+  //
+  aFolderA->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts1);
+  aFolderB->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts2);
+  //
+  aFolderA->GetFlag(nsMsgFolderFlags::Trash, &isTrash1);
+  aFolderB->GetFlag(nsMsgFolderFlags::Trash, &isTrash2);
+  
+  //Follow this order;
+  // INBOX > DRAFTS > SUBFOLDERS > TRASH
+  
+  if (isInbox2 || (isDrafts2 && !isInbox1) || isTrash1)
+    *aDecision = nsAutoSyncStrategyDecisions::Higher;
+  else if (isInbox1 || (isDrafts1 && !isDrafts2) || isTrash2)
+    *aDecision = nsAutoSyncStrategyDecisions::Lower;
+  else
+    *aDecision = nsAutoSyncStrategyDecisions::Same;
+    
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsDefaultAutoSyncFolderStrategy::IsExcluded(nsIMsgFolder *aFolder, PRBool *aDecision)
+{
+  NS_ENSURE_ARG_POINTER(aDecision);
+  *aDecision = PR_FALSE;
+  return NS_OK;
+}
+
+
+nsAutoSyncManager::nsAutoSyncManager()
+{
+  mGroupSize = kDefaultGroupSize;
+  mStartupTime = PR_Now();
+  mDownloadModel = dmChained;
+  mUpdateState = completed;
+  
+  nsresult rv;    
+  mIdleService = do_GetService("@mozilla.org/widget/idleservice;1", &rv);
+  if (mIdleService)
+    mIdleService->AddIdleObserver(this, kIdleTimeInSec);
+ 
+  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to get subscribed to the idle service");
+}
+
+nsAutoSyncManager::~nsAutoSyncManager()
+{
+  if (mTimer)
+    mTimer->Cancel();
+  
+  if (mIdleService)
+    mIdleService->RemoveIdleObserver(this, kIdleTimeInSec);
+}
+
+void nsAutoSyncManager::InitTimer()
+{
+  if (!mTimer)
+  {
+    nsresult rv;
+    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+    NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create timer in nsAutoSyncManager");
+    
+    mTimer->InitWithFuncCallback(TimerCallback, (void *) this, 
+                                 kTimerIntervalInMs, nsITimer::TYPE_REPEATING_SLACK);
+  }
+}
+
+void nsAutoSyncManager::StopTimer()
+{
+  if (mTimer)
+  {
+    mTimer->Cancel();
+    mTimer = nsnull;
+  }
+}
+
+void nsAutoSyncManager::StartTimerIfNeeded()
+{
+  if ((mUpdateQ.Count() > 0 || mDiscoveryQ.Count() > 0) && !mTimer)
+    InitTimer();
+}
+
+void nsAutoSyncManager::TimerCallback(nsITimer *aTimer, void *aClosure)
+{
+  if (!aClosure)
+    return;
+  
+  nsAutoSyncManager *autoSyncMgr = static_cast<nsAutoSyncManager*>(aClosure);
+  if (autoSyncMgr->GetIdleState() == back ||
+    (autoSyncMgr->mDiscoveryQ.Count() <= 0 && autoSyncMgr->mUpdateQ.Count() <= 0))
+  {
+    // Idle will create a new timer automatically if discovery Q or update Q is not empty
+    autoSyncMgr->StopTimer();
+  }
+
+  // process folders within the discovery queue 
+  if (autoSyncMgr->mDiscoveryQ.Count() > 0)
+  {
+    nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mDiscoveryQ[0]);
+    if (autoSyncStateObj)
+    {
+      PRUint32 leftToProcess;
+      nsresult rv = autoSyncStateObj->ProcessExistingHeaders(kNumberOfHeadersToProcess, &leftToProcess);
+      
+      #if  defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L2)
+      printf("Existing headers are processed for folder %s. There are %d more headers to be processed\n", 
+             autoSyncMgr->DebugGetFolderName(autoSyncStateObj).get(), leftToProcess);
+      #endif
+      
+      if (NS_SUCCEEDED(rv) && 0 == leftToProcess)
+        autoSyncMgr->mDiscoveryQ.RemoveObjectAt(0);
+    }
+  }
+  
+  if (autoSyncMgr->mUpdateQ.Count() > 0)
+  {
+    if (autoSyncMgr->mUpdateState == completed)
+    {
+      nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mUpdateQ[0]);
+      if (autoSyncStateObj)
+      {
+        PRInt32 state;
+        nsresult rv = autoSyncStateObj->GetState(&state);
+        if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle == state)
+        {
+          nsCOMPtr<nsIMsgFolder> folder; 
+          autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+          if (folder)
+          {
+            nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+            NS_ENSURE_SUCCESS(rv,);
+            rv = imapFolder->InitiateAutoSync(autoSyncMgr);
+            if (NS_SUCCEEDED(rv))
+              autoSyncMgr->mUpdateState = initiated;
+          }
+        }
+      } 
+    }
+    // if initiation is not successful for some reason, or 
+    // if there is an on going download for this folder, 
+    // remove it from q and continue with the next one  
+    if (autoSyncMgr->mUpdateState != initiated)
+      autoSyncMgr->mUpdateQ.RemoveObjectAt(0); 
+      
+  }//endif
+
+}
+
+/**
+ * Populates aChainedQ with the auto-sync state objects that are not owned by 
+ * the same imap server. 
+ * Assumes that aChainedQ initially empty.
+ */
+void nsAutoSyncManager::ChainFoldersInQ(const nsCOMArray<nsIAutoSyncState> &aQueue,
+      nsCOMArray<nsIAutoSyncState> &aChainedQ)
+{
+  if (aQueue.Count() > 0)
+    aChainedQ.AppendObject(aQueue[0]);
+  
+  PRInt32 pqElemCount = aQueue.Count();
+  for (PRInt32 pqidx = 1; pqidx < pqElemCount; pqidx++)
+  {
+    PRBool chained = PR_FALSE;
+    PRInt32 needToBeReplacedWith = -1;
+    PRInt32 elemCount = aChainedQ.Count();
+    for (PRInt32 idx = 0; idx < elemCount; idx++)
+    {
+      PRBool isSibling;
+      nsresult rv = aChainedQ[idx]->IsSibling(aQueue[pqidx], &isSibling);
+      
+      if (NS_SUCCEEDED(rv) && isSibling)
+      {
+        // this prevent us to overwrite a lower priority sibling in
+        // download-in-progress state with a higher priority one. 
+        // we have to wait until its download is completed before 
+        // switching to new one. 
+        PRInt32 state;
+        aQueue[pqidx]->GetState(&state);
+        if (aQueue[pqidx] != aChainedQ[idx] && 
+            state == nsAutoSyncState::stDownloadInProgress)
+          needToBeReplacedWith = idx;
+        else
+          chained = PR_TRUE;
+          
+        break;
+      }
+    }//endfor
+    
+    if (needToBeReplacedWith > -1)
+      aChainedQ.ReplaceObjectAt(aQueue[pqidx], needToBeReplacedWith);
+    else if (!chained)
+      aChainedQ.AppendObject(aQueue[pqidx]);
+      
+  }//endfor
+  
+  #if  defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L2)
+  DebugDumpQ("Chained", aChainedQ);
+  #endif
+}
+
+/**
+ * Searches the given queue for another folder owned by the same imap server.
+ */
+nsIAutoSyncState* 
+nsAutoSyncManager::SearchQForSibling(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 aStartIdx, PRInt32 *aIndex)
+{
+  if (aIndex)
+    *aIndex = -1;
+  
+  if (aAutoSyncStateObj)
+  {
+    PRBool isSibling;
+    PRInt32 elemCount = aQueue.Count();
+    for (PRInt32 idx = aStartIdx; idx < elemCount; idx++)
+    {
+      nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+      
+      if (NS_SUCCEEDED(rv) && isSibling && aAutoSyncStateObj != aQueue[idx])
+      {
+        if (aIndex) 
+          *aIndex = idx;
+        
+        return aQueue[idx];
+      }
+    }
+  }
+  return nsnull;  
+}
+
+/**
+ * Searches for the next folder owned by the same imap server in the given queue,
+ * starting from the index of the given folder.
+ */
+nsIAutoSyncState* 
+nsAutoSyncManager::GetNextSibling(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 *aIndex)
+{ 
+
+  if (aIndex)
+    *aIndex = -1;
+  
+  if (aAutoSyncStateObj)
+  {
+    PRBool located = PR_FALSE;
+    PRBool isSibling;
+    PRInt32 elemCount = aQueue.Count();
+    for (PRInt32 idx = 0; idx < elemCount; idx++)
+    {
+      if (!located)
+      {
+        located = (aAutoSyncStateObj == aQueue[idx]);
+        continue;
+      }
+      
+      nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+      if (NS_SUCCEEDED(rv) && isSibling)
+      {
+        if (aIndex) 
+          *aIndex = idx;
+        
+        return aQueue[idx];
+      }
+    }
+  }
+  return nsnull;  
+}
+
+/** 
+ * Checks whether there is another folder in the given q that is owned 
+ * by the same imap server or not. 
+ */
+PRBool nsAutoSyncManager::DoesQContainAnySiblingOf(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 *aIndex)
+{
+  return (nsnull != SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex));
+}
+
+/**
+ * Searches the given queue for the highest priority folder owned by the
+ * same imap server.
+ */
+nsIAutoSyncState* 
+nsAutoSyncManager::GetHighestPrioSibling(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                                      nsIAutoSyncState *aAutoSyncStateObj, PRInt32 *aIndex)
+{
+  return SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex);
+}
+
+// to chain update folder actions
+NS_IMETHODIMP nsAutoSyncManager::OnStartRunningUrl(nsIURI* aUrl)
+{
+  return NS_OK;
+}
+
+
+NS_IMETHODIMP nsAutoSyncManager::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode)
+{
+  mUpdateState = completed;
+  if (mUpdateQ.Count() > 0)
+    mUpdateQ.RemoveObjectAt(0);
+
+  return aExitCode;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Observe(nsISupports*, const char *aTopic, const PRUnichar *aSomeData)
+{
+  // Check topic here, idle or back
+  if (PL_strcmp(aTopic, "idle") != 0)
+  {
+    SetIdleState(back);
+    return NS_OK;
+  }
+  else
+  {
+    SetIdleState(idle);
+    if (WeAreOffline())
+      return NS_OK;
+    StartTimerIfNeeded();
+  }
+  
+  // TODO: Any better way to do it?  
+  // to ignore idle events sent during the startup
+  if ((mStartupTime + (10UL * PR_USEC_PER_SEC)) > PR_Now())
+    return NS_OK;
+  
+  #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L2)
+  DebugDumpQ("Priority", mPriorityQ);
+  #endif
+  
+  nsCOMArray<nsIAutoSyncState> chainedQ;
+  nsCOMArray<nsIAutoSyncState> *queue = &mPriorityQ;
+  if (mDownloadModel == dmChained) 
+  {
+    ChainFoldersInQ(mPriorityQ, chainedQ);
+    queue = &chainedQ;
+  }
+  
+  // process folders in the priority queue 
+  PRInt32 elemCount = queue->Count();
+  for (PRInt32 idx = 0; idx < elemCount; idx++)
+  {
+    nsCOMPtr<nsIAutoSyncState> autoSyncStateObj((*queue)[idx]);
+    if (!autoSyncStateObj)
+      continue;
+    
+    PRInt32 state;
+    autoSyncStateObj->GetState(&state);
+    
+    //TODO: Test cached-connection availability in parallel mode
+    // and do not exceed (cached-connection count - 1)
+    
+    if (state != nsAutoSyncState::stReadyToDownload)
+      continue;
+    else
+    {
+      if (NS_FAILED(DownloadMessagesForOffline(autoSyncStateObj)))
+        HandleDownloadErrorFor(autoSyncStateObj);
+    }
+  }//endfor
+  
+  return AutoUpdateFolders();
+}
+
+/**
+ * Updates offline imap folders that are not synchronized recently.
+ */
+nsresult nsAutoSyncManager::AutoUpdateFolders()
+{
+  nsresult rv;
+
+  // iterate through each imap account and update offline folders automatically
+  
+  nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  nsCOMPtr<nsISupportsArray> accounts;
+  rv = accountManager->GetAccounts(getter_AddRefs(accounts));
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  PRUint32 accountCount;
+  accounts->Count(&accountCount);
+  
+  for (PRUint32 i = 0; i < accountCount; ++i) 
+  {
+    nsCOMPtr<nsIMsgAccount> account(do_QueryElementAt(accounts, i, &rv));
+    if (!account)
+      continue;
+    
+    nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+    rv = account->GetIncomingServer(getter_AddRefs(incomingServer));
+    if (!incomingServer)
+      continue;
+    
+    nsCString type;
+    rv = incomingServer->GetType(type);
+    
+    if (!type.EqualsLiteral("imap"))
+      continue;
+    
+    nsCOMPtr<nsIMsgFolder> rootFolder;
+    nsCOMPtr<nsISupportsArray> allDescendents;
+    
+    rv = incomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+    if (rootFolder)
+    {
+      allDescendents = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv);
+      if (NS_FAILED(rv))
+        continue;
+        
+      rv = rootFolder->ListDescendents(allDescendents);
+      if (!allDescendents)
+        continue;
+      
+      PRUint32 cnt = 0;
+      rv = allDescendents->Count(&cnt);
+      if (NS_FAILED(rv))
+        continue;
+      
+      for (PRUint32 i = 0; i < cnt; i++)
+      {
+        nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(allDescendents, i, &rv));
+        if (NS_FAILED(rv))
+          continue;
+        
+        PRBool isFolderOffline = PR_FALSE;
+        rv = folder->GetFlag(nsMsgFolderFlags::Offline, &isFolderOffline);
+        // skip this folder if not offline
+        if (NS_FAILED(rv) || !isFolderOffline)
+        {
+          #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L1)
+          printf("*** Skipping folder %s [Offline: no]\n", DebugGetFolderName(folder).get());
+          #endif
+          continue; 
+        }
+        
+        nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+        if (NS_FAILED(rv))
+          continue;
+          
+        nsCOMPtr<nsIImapIncomingServer> imapServer;
+        rv = imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
+        if (imapServer)
+        {
+          PRBool autoSyncOfflineStores = PR_FALSE;
+          rv = imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+          
+          // skip if AutoSyncOfflineStores pref is not set for this folder
+          if (NS_FAILED(rv) || !autoSyncOfflineStores)
+          {
+            #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L1)
+            printf("*** Skipping folder %s [AutoSyncOffline pref: off]\n", DebugGetFolderName(folder).get());
+            #endif
+            continue;
+          }
+        }
+        
+        nsCOMPtr<nsIAutoSyncState> autoSyncState;
+        rv = imapFolder->GetAutoSyncStateObj(getter_AddRefs(autoSyncState));
+        NS_ASSERTION(autoSyncState, "*** nsAutoSyncState shouldn't be NULL, check owner folder");
+        
+        // shouldn't happen but lets be defensive here
+        if (!autoSyncState)
+          continue;
+        
+        PRInt32 state;
+        rv = autoSyncState->GetState(&state);
+        
+        if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle != state)
+        {
+          #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L1)
+          printf("*** Skipping folder %s [Offline: yes][Status: %s]\n", DebugGetFolderName(folder).get(),
+                 nsAutoSyncState::stCompletedIdle != state ? 
+                 (nsAutoSyncState::stReadyToDownload == state ? "InQ" : "Downloading") : "InSync/Idle" );
+          #endif            
+        }
+        else
+        {
+          // ensure that we wait for at least nsMsgIncomingServer::BiffMinutes between
+          // each update of the same folder
+          PRTime lastUpdateTime;
+          rv = autoSyncState->GetLastUpdateTime(&lastUpdateTime);
+          PRTime span = GetUpdateIntervalFor(autoSyncState) * (PR_USEC_PER_SEC * 60UL);
+          if ( NS_SUCCEEDED(rv) && ((lastUpdateTime + span) < PR_Now()) )
+          {
+            #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L1)
+            printf("*** Initiating Auto-Sync on folder %s\n", DebugGetFolderName(folder).get());
+            #endif
+          
+            if (mUpdateQ.IndexOf(autoSyncState) == -1)
+              mUpdateQ.AppendObject(autoSyncState);
+          }
+        }
+  
+        // check last sync time
+        PRTime lastSyncTime;
+        rv = autoSyncState->GetLastSyncTime(&lastSyncTime);
+        if ( NS_SUCCEEDED(rv) && ((lastSyncTime + kAutoSyncFreq) < PR_Now()) )
+        {
+          // add this folder into discovery queue to process existing headers
+          // and discover messages not downloaded yet
+          if (mDiscoveryQ.IndexOf(autoSyncState) == -1)
+            mDiscoveryQ.AppendObject(autoSyncState);
+        }
+      }//endfor
+    }//endif
+  }//endfor
+  
+  // lazily create the timer if there is something to process in the queue
+  // when timer is done, it will self destruct
+  StartTimerIfNeeded();
+  
+  return rv;
+}
+
+/**
+ * Places the given folder into the priority queue based on active
+ * strategy function.
+ */
+void nsAutoSyncManager::ScheduleFolderForOfflineDownload(nsIAutoSyncState *aAutoSyncStateObj)
+{
+  if (aAutoSyncStateObj &&  (mPriorityQ.IndexOf(aAutoSyncStateObj) == -1))
+  {
+    nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+    GetFolderStrategy(getter_AddRefs(folStrategy));
+        
+    if (mPriorityQ.Count() <= 0)
+    {
+      // make sure that we don't insert a folder excluded by the given strategy
+      nsCOMPtr<nsIMsgFolder> folder;
+      aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+      if (folder)
+      {
+        PRBool excluded = PR_FALSE;
+        if (folStrategy)
+          folStrategy->IsExcluded(folder, &excluded);
+        
+        if (!excluded)
+          mPriorityQ.AppendObject(aAutoSyncStateObj); // insert into the first spot
+      }
+    }
+    else 
+    {
+      // find the right spot for the given folder      
+      PRUint32 qidx = mPriorityQ.Count();
+      while (qidx > 0) 
+      {
+        --qidx;
+        
+        nsCOMPtr<nsIMsgFolder> folderA, folderB;
+        mPriorityQ[qidx]->GetOwnerFolder(getter_AddRefs(folderA));
+        aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+        
+        PRBool excluded = PR_FALSE;
+        if (folderB && folStrategy)
+          folStrategy->IsExcluded(folderB, &excluded);
+          
+        if (excluded)
+          break;
+        
+        nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+        if (folderA && folderB && folStrategy)
+          folStrategy->Sort(folderA, folderB, &decision);
+                  
+        if (decision == nsAutoSyncStrategyDecisions::Higher && 0 == qidx)
+          mPriorityQ.InsertObjectAt(aAutoSyncStateObj, 0);
+        else if (decision == nsAutoSyncStrategyDecisions::Higher)
+          continue;
+        else if (decision == nsAutoSyncStrategyDecisions::Lower)
+          mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx+1);
+        else //  decision == nsAutoSyncStrategyDecisions::Same
+          mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx);
+        
+        break;
+      }//endwhile
+    }
+  }//endif
+}
+
+nsresult nsAutoSyncManager::DownloadMessagesForOffline(nsIAutoSyncState *aAutoSyncStateObj)
+{  
+  if (!aAutoSyncStateObj)
+    return NS_ERROR_INVALID_ARG;
+  
+  PRInt32 count;
+  nsresult rv = aAutoSyncStateObj->GetPendingMessageCount(&count);
+  if ( NS_FAILED(rv) || (NS_SUCCEEDED(rv) && 0 == count) )
+    return NS_ERROR_FAILURE; 
+ 
+  nsCOMPtr<nsIMutableArray> messagesToDownload;
+  rv = aAutoSyncStateObj->GetNextGroupOfMessages(getter_AddRefs(messagesToDownload));
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  PRUint32 length;
+  rv = messagesToDownload->GetLength(&length);
+  if (NS_SUCCEEDED(rv) && length > 0)
+  {
+    #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L0)
+    printf("Downloading %u messages for folder %s\n", length, 
+                                        DebugGetFolderName(aAutoSyncStateObj).get());
+    #endif 
+    rv = aAutoSyncStateObj->DownloadMessagesForOffline(messagesToDownload);
+    
+    #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L0)
+    if (NS_FAILED(rv))
+      printf("Last download operation for folder [%s] failed\n", 
+                                        DebugGetFolderName(aAutoSyncStateObj).get());
+    #endif
+
+  }
+  
+  return rv;
+}
+
+/**
+ * Assuming that the download operation on the given folder has been failed at least once, 
+ * execute these steps:
+ *  - put the auto-sync state into ready-to-download mode
+ *  - rollback the message offset so we can try the same group again (unless the retry
+ *     count is reached to the given limit)
+ *  - if parallel model is active, wait to be resumed by the next idle
+ *  - if chained model is active, searche the priority queue to find a sibling to continue with.
+ */
+nsresult nsAutoSyncManager::HandleDownloadErrorFor(nsIAutoSyncState *aAutoSyncStateObj)
+{
+  if (!aAutoSyncStateObj)
+    return NS_ERROR_INVALID_ARG;
+  
+  // force the auto-sync state to try downloading the same group at least
+  // kGroupRetryCount times before it moves to the next one
+  aAutoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+  
+  // if parallel model, don't do anything else
+  
+  if (mDownloadModel == dmChained)
+  {
+    // switch to the next folder in the chain and continue downloading
+    nsIAutoSyncState *autoSyncStateObj = aAutoSyncStateObj;
+    nsIAutoSyncState *nextAutoSyncStateObj = nsnull;
+    while ( (nextAutoSyncStateObj = GetNextSibling(mPriorityQ, autoSyncStateObj)) )
+    {
+      autoSyncStateObj = nextAutoSyncStateObj;
+      if (NS_SUCCEEDED(DownloadMessagesForOffline(autoSyncStateObj)))
+        break;
+      else
+        autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+    }
+  }
+  
+  return NS_OK;
+}
+
+PRUint32 nsAutoSyncManager::GetUpdateIntervalFor(nsIAutoSyncState *aAutoSyncStateObj)
+{
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsresult rv = aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+  if (NS_FAILED(rv))
+    return kDefaultUpdateInterval;
+  
+  nsCOMPtr<nsIMsgIncomingServer> server;
+  rv = folder->GetServer(getter_AddRefs(server));
+  if (NS_FAILED(rv))
+    return kDefaultUpdateInterval;
+
+  if (server)
+  {
+    PRInt32 interval;
+    rv = server->GetBiffMinutes(&interval);
+    
+    if (NS_SUCCEEDED(rv))
+      return (PRUint32)interval;
+  }
+
+  return kDefaultUpdateInterval;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetGroupSize(PRUint32 *aGroupSize)
+{
+  NS_ENSURE_ARG_POINTER(aGroupSize);
+  *aGroupSize = mGroupSize;
+  return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetGroupSize(PRUint32 aGroupSize)
+{
+  mGroupSize = aGroupSize;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetMsgStrategy(nsIAutoSyncMsgStrategy * *aMsgStrategy)
+{
+  NS_ENSURE_ARG_POINTER(aMsgStrategy);
+  
+  // lazily create if it is not done already
+  if (!mMsgStrategyImpl)
+  {
+    mMsgStrategyImpl = new nsDefaultAutoSyncMsgStrategy;
+    if (!mMsgStrategyImpl)
+      return NS_ERROR_OUT_OF_MEMORY;
+  }
+  
+  NS_IF_ADDREF(*aMsgStrategy = mMsgStrategyImpl);
+  return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetMsgStrategy(nsIAutoSyncMsgStrategy * aMsgStrategy)
+{
+  mMsgStrategyImpl = aMsgStrategy;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetFolderStrategy(nsIAutoSyncFolderStrategy * *aFolderStrategy)
+{
+  NS_ENSURE_ARG_POINTER(aFolderStrategy);
+  
+  // lazily create if it is not done already
+  if (!mFolderStrategyImpl)
+  {
+    mFolderStrategyImpl = new nsDefaultAutoSyncFolderStrategy;
+    if (!mFolderStrategyImpl)
+      return NS_ERROR_OUT_OF_MEMORY;
+  }
+    
+  NS_IF_ADDREF(*aFolderStrategy = mFolderStrategyImpl);
+  return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetFolderStrategy(nsIAutoSyncFolderStrategy * aFolderStrategy)
+{
+  mFolderStrategyImpl = aFolderStrategy;
+  return NS_OK;
+}
+
+NS_IMETHODIMP 
+nsAutoSyncManager::DoesMsgFitDownloadCriteria(nsIMsgDBHdr *aMsgHdr, PRBool *aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  
+  PRUint32 msgFlags = 0;
+  aMsgHdr->GetFlags(&msgFlags);
+  
+  // check whether this message is marked imap deleted or not 
+  *aResult = !(msgFlags & MSG_FLAG_IMAP_DELETED);
+  if (!(*aResult))
+    return NS_OK;
+    
+  PRBool shouldStoreMsgOffline = PR_TRUE;
+  nsCOMPtr<nsIMsgFolder> folder;
+  aMsgHdr->GetFolder(getter_AddRefs(folder));
+  if (folder)
+  {
+    nsMsgKey msgKey;
+    nsresult rv = aMsgHdr->GetMessageKey(&msgKey);
+    // a cheap way to get the size limit for this folder and make
+    // sure that we don't have this message offline already
+    if (NS_SUCCEEDED(rv))
+      folder->ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+  }
+        
+  *aResult &= shouldStoreMsgOffline;
+  
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::OnDownloadQChanged(nsIAutoSyncState *aAutoSyncStateObj)
+{  
+  nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+  if (!autoSyncStateObj)
+    return NS_ERROR_INVALID_ARG;
+  
+  #if  defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L2)
+  printf("OnDownloadQChanged() is called with folder %s\n", 
+                                        DebugGetFolderName(autoSyncStateObj).get());
+  #endif
+
+  // we want to start downloading immediately
+  
+  // unless the folder is excluded
+  PRBool excluded = PR_FALSE;
+  nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+  nsCOMPtr<nsIMsgFolder> folder;
+  
+  GetFolderStrategy(getter_AddRefs(folStrategy));
+  autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+        
+  if (folder && folStrategy)
+    folStrategy->IsExcluded(folder, &excluded);
+  
+  // and if the folder is in completed state
+  PRInt32 state;
+  nsresult rv = autoSyncStateObj->GetState(&state);
+  if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle == state && !excluded)
+  {
+    // add this folder into the priority queue - if state == stCompletedIdle shouldn't be
+    // in the priority queue
+    autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+    ScheduleFolderForOfflineDownload(autoSyncStateObj);
+    
+    if (mDownloadModel == dmParallel || !DoesQContainAnySiblingOf(mPriorityQ, autoSyncStateObj))
+    {
+      rv = DownloadMessagesForOffline(autoSyncStateObj);
+      if (NS_FAILED(rv))
+        HandleDownloadErrorFor(autoSyncStateObj);
+    }
+  }
+  return rv;
+}
+
+
+NS_IMETHODIMP 
+nsAutoSyncManager::OnDownloadStarted(nsIAutoSyncState *aAutoSyncStateObj, nsresult aStartCode)
+{
+  nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+  if (!autoSyncStateObj)
+    return NS_ERROR_INVALID_ARG;
+
+  // resume downloads during next idle time
+  if (NS_FAILED(aStartCode))
+    autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);  
+  
+  return aStartCode;
+}
+
+NS_IMETHODIMP 
+nsAutoSyncManager::OnDownloadCompleted(nsIAutoSyncState *aAutoSyncStateObj, nsresult aExitCode)
+{
+  nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+  if (!autoSyncStateObj)
+    return NS_ERROR_INVALID_ARG;    
+
+  nsresult rv = aExitCode;
+
+  if (NS_FAILED(aExitCode))
+  {
+    // retry the same group kGroupRetryCount times
+    // try again if TB still idle, otherwise wait for the next idle time
+    autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+    if (GetIdleState() == idle)
+    {
+      rv = DownloadMessagesForOffline(autoSyncStateObj);
+      if (NS_FAILED(rv))
+        rv = HandleDownloadErrorFor(autoSyncStateObj);
+    }
+    return rv;
+  }
+      
+  // download is successful, reset the retry counter of the folder
+  autoSyncStateObj->ResetRetryCounter();
+      
+  PRInt32 count;
+  rv = autoSyncStateObj->GetPendingMessageCount(&count);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsIAutoSyncState *nextFolderToDownload = nsnull;
+  if (count > 0)
+  {
+    autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+    
+    // in parallel model, we continue downloading the same folder as long as it has
+    // more pending messages
+    nextFolderToDownload = autoSyncStateObj;
+    
+    // in chained model, ensure that we are always downloading the highest priority 
+    // folder first 
+    if (mDownloadModel == dmChained)
+    {
+      // switch to higher priority folder and continue to download, 
+      // if any added recently
+      PRInt32 myIndex = mPriorityQ.IndexOf(autoSyncStateObj);
+      
+      PRInt32 siblingIndex;
+      nsIAutoSyncState *sibling = GetHighestPrioSibling(mPriorityQ, autoSyncStateObj, &siblingIndex);
+      
+      // lesser index = higher priority
+      if (sibling && myIndex > -1 && siblingIndex < myIndex) 
+        nextFolderToDownload = sibling;
+    }
+  }
+  else 
+  {
+    #if defined(DEBUG_me) && defined(DEBUG_AutoSyncManager_L0)              
+    printf("*** Folder %s is syncd\n", DebugGetFolderName(autoSyncStateObj).get());
+    #endif
+
+    autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+    mPriorityQ.RemoveObject(autoSyncStateObj);
+
+    //find the next folder owned by the same server in the queue and continue downloading
+    if (mDownloadModel == dmChained)
+      nextFolderToDownload = GetHighestPrioSibling(mPriorityQ, autoSyncStateObj);
+      
+  }//endif
+  
+  // continue downloading if TB is still in idle state
+  if (nextFolderToDownload && GetIdleState() == idle)
+  {
+    rv = DownloadMessagesForOffline(nextFolderToDownload);
+    if (NS_FAILED(rv))
+      rv = HandleDownloadErrorFor(nextFolderToDownload);
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadModel(PRInt32 *aDownloadModel)
+{
+  NS_ENSURE_ARG_POINTER(aDownloadModel);
+  *aDownloadModel = mDownloadModel;
+  return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetDownloadModel(PRInt32 aDownloadModel)
+{
+  mDownloadModel = aDownloadModel;
+  return NS_OK;
+}
+
+#ifdef DEBUG_me
+void nsAutoSyncManager::DebugDumpQ(char* text, const nsCOMArray<nsIAutoSyncState> &q)
+{
+  PRUint32 count = q.Count();
+  printf("%s Q content (%d):\n", text, count);
+  while (count > 0) 
+  {
+    count--;
+    nsCOMPtr<nsIMsgFolder> folder;
+    if (NS_SUCCEEDED(q[count]->GetOwnerFolder(getter_AddRefs(folder))))
+    {
+      nsCString folderName;
+      folder->GetURI(folderName);
+      printf("Elem #%d, Folder: %s\n", count+1, folderName.get());
+    }
+  }
+}
+
+nsCString nsAutoSyncManager::DebugGetFolderName(nsIAutoSyncState *autoSyncStObj)
+{           
+  nsCOMPtr<nsIMsgFolder> folder;
+  if (NS_SUCCEEDED(autoSyncStObj->GetOwnerFolder(getter_AddRefs(folder))))
+    return DebugGetFolderName(folder);  
+  return nsCString();
+}
+
+nsCString nsAutoSyncManager::DebugGetFolderName(nsIMsgFolder *folder)
+{           
+  nsCString folderName;
+  if (folder)
+    folder->GetURI(folderName);
+
+  return folderName;
+}
+#endif
+
+NS_IMPL_ISUPPORTS3(nsAutoSyncManager, nsIObserver, nsIUrlListener, nsIAutoSyncManager)
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncManager.h
@@ -0,0 +1,228 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsAutoSyncManager_h__
+#define nsAutoSyncManager_h__
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "nsIObserver.h"
+#include "nsIUrlListener.h"
+#include "nsITimer.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIAutoSyncMsgStrategy.h"
+#include "nsIAutoSyncFolderStrategy.h"
+
+class nsImapMailFolder;
+class nsIMsgDBHdr;
+class nsIIdleService;
+class nsIMsgFolder;
+
+/**
+ * Default strategy implementation to prioritize messages in the download queue.   
+ */
+class nsDefaultAutoSyncMsgStrategy : public nsIAutoSyncMsgStrategy
+{
+  enum { kFirstPassMessageSize = 10U*1024U*1024U }; // 10MB
+  
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIAUTOSYNCMSGSTRATEGY
+
+    nsDefaultAutoSyncMsgStrategy();
+
+  private:
+    ~nsDefaultAutoSyncMsgStrategy();
+};
+
+/**
+ * Default strategy implementation to prioritize folders in the download queue.  
+ */
+class nsDefaultAutoSyncFolderStrategy : public nsIAutoSyncFolderStrategy
+{
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIAUTOSYNCFOLDERSTRATEGY
+
+    nsDefaultAutoSyncFolderStrategy();
+
+  private:
+    ~nsDefaultAutoSyncFolderStrategy();
+};
+
+// see the end of the page for auto-sync internals
+
+/**
+ * Manages background message download operations for offline imap folders. 
+ */
+class nsAutoSyncManager : public nsIObserver, 
+                          public nsIUrlListener,
+                          public nsIAutoSyncManager
+{
+  static const PRTime kAutoSyncFreq = 60UL * (PR_USEC_PER_SEC * 60UL);  // 1hr
+  static const PRUint32 kDefaultUpdateInterval = 10UL;                  // 10min
+  static const PRInt32 kTimerIntervalInMs = 400;
+  static const PRUint32 kNumberOfHeadersToProcess = 250U;
+  static const PRUint32 kDefaultGroupSize = 50U*1024U /* 50K */;
+  static const PRInt32 kIdleTimeInSec = 10;
+  static const PRUint32 kGroupRetryCount = 3;
+  
+  enum IdleState { idle, back };
+  enum UpdateState { initiated, completed };
+      
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+    NS_DECL_NSIURLLISTENER
+    NS_DECL_NSIAUTOSYNCMANAGER
+
+    nsAutoSyncManager();
+    
+  private:
+    ~nsAutoSyncManager();
+
+    void SetIdleState(IdleState st) { mIdleState = st; }
+    IdleState GetIdleState() const { return mIdleState; }
+    nsresult AutoUpdateFolders(); 
+    void ScheduleFolderForOfflineDownload(nsIAutoSyncState *aAutoSyncStateObj);
+    nsresult DownloadMessagesForOffline(nsIAutoSyncState *aAutoSyncStateObj);
+    nsresult HandleDownloadErrorFor(nsIAutoSyncState *aAutoSyncStateObj);
+    
+    // Helper methods for priority Q operations
+    static
+    void ChainFoldersInQ(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                          nsCOMArray<nsIAutoSyncState> &aChainedQ);
+    static
+    nsIAutoSyncState* SearchQForSibling(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 aStartIdx, PRInt32 *aIndex = nsnull);
+    static
+    PRBool DoesQContainAnySiblingOf(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 *aIndex = nsnull);
+    static 
+    nsIAutoSyncState* GetNextSibling(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 *aIndex = nsnull);
+    static 
+    nsIAutoSyncState* GetHighestPrioSibling(const nsCOMArray<nsIAutoSyncState> &aQueue, 
+                          nsIAutoSyncState *aAutoSyncStateObj, PRInt32 *aIndex = nsnull);
+    
+    /// timer to process existing keys and updates
+    void InitTimer();
+    static void TimerCallback(nsITimer *aTimer, void *aClosure);
+    void StopTimer();
+    void StartTimerIfNeeded();
+    
+    /// pref helpers
+    PRUint32 GetUpdateIntervalFor(nsIAutoSyncState *aAutoSyncStateObj);
+    
+    #ifdef DEBUG_me
+    void DebugDumpQ(char *s, const nsCOMArray<nsIAutoSyncState> &aQueue);
+    nsCString DebugGetFolderName(nsIAutoSyncState *aAutoSyncStateObj);
+    nsCString DebugGetFolderName(nsIMsgFolder *aFolder);
+    #endif
+
+  protected:
+    nsCOMPtr<nsIAutoSyncMsgStrategy> mMsgStrategyImpl;
+    nsCOMPtr<nsIAutoSyncFolderStrategy> mFolderStrategyImpl;
+    // contains the folders that will be downloaded on background
+    nsCOMArray<nsIAutoSyncState> mPriorityQ;
+    // contains the folders that will be examined for existing headers
+    nsCOMArray<nsIAutoSyncState> mDiscoveryQ;
+    // contains the folders that will be updated in order
+    // (see nsImapMailFolder::UpdateFolder for update operation)
+    nsCOMArray<nsIAutoSyncState> mUpdateQ;
+    UpdateState mUpdateState;
+   
+  private:
+    PRUint32 mGroupSize;
+    IdleState mIdleState;
+    PRTime mStartupTime;
+    PRInt32 mDownloadModel;
+    nsCOMPtr<nsIIdleService> mIdleService;
+    nsCOMPtr<nsITimer> mTimer;
+};
+
+#endif
+
+/*
+ How queues interact in general:
+
+sAutoSyncState has an internal priority queue to store messages waiting to be
+downloaded. nsAutoSyncMsgStrategy object determines the order in this queue,
+nsAutoSyncManager uses this queue to manage downloads. Two events cause a
+change in this queue: 
+
+1) nsImapMailFolder::HeaderFetchCompleted: is triggered when TB notices that
+there are pending messages on the server -- via IDLE command from the server, 
+via explicit select from the user, or via automatic Update during idle time. If 
+it turns out that there are pending messages on the server, it adds them into 
+nsAutoSyncState's download queue.
+
+2) nsAutoSyncState::ProcessExistingHeaders: is triggered for every imap folder 
+every hour or so. nsAutoSyncManager uses an internal queue called Discovery 
+queue to keep track of this task. The purpose of ProcessExistingHeaders() 
+method is to check existing headers of a given folder in batches and discover 
+the messages without bodies, in asynchronous fashion. This process is 
+sequential, one and only one folder at any given time, very similar to 
+indexing. Again, if it turns out that the folder in hand has messages w/o 
+bodies, ProcessExistingHeaders adds them into nsAutoSyncState's download queue.
+
+Any change in nsAutoSyncState's download queue, notifies nsAutoSyncManager and 
+nsAutoSyncManager puts the requesting  nsAutoSyncState into its internal 
+priority queue (called mPriorityQ) -- if the folder is not already there. 
+nsAutoSyncFolderStrategy object determines the order in this queue. This queue 
+is processed in two modes: chained and parallel. 
+
+i) Chained: One folder per imap server any given time. Folders owned by 
+different imap servers are simultaneous.
+
+ii) Parallel: All folders at the same time, using all cached-connections - 
+a.k.a 'Folders gone wild' mode.
+
+The order the folders are added into the mPriorityQ doesn't matter since every
+time a batch completed for an imap server, nsAutoSyncManager adjusts the order.
+So, lets say that updating a sub-folder starts downloading message immediately,
+when an higher priority folder is added into the queue, nsAutoSyncManager
+switches to this higher priority folder instead of processing the next group of
+messages of the lower priority one. Setting group size too high might delay
+this switch at worst. 
+
+And finally, Update queue helps nsAutoSyncManager to keep track of folders 
+waiting to be updated. With the latest change, we update one and only one
+folder at any given time. Frequency of updating is 5 min. We add folders into
+the update queue during idle time, if they are not in mPriorityQ already.
+
+*/
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncState.cpp
@@ -0,0 +1,685 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+ 
+#ifdef DEBUG_David_Bienvenu
+#define DEBUG_me
+#endif
+
+#include "nsAutoSyncState.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgImapCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIAutoSyncMsgStrategy.h"
+
+#ifdef DEBUG_me
+#define DEBUG_AutoSyncState_L0
+//#define DEBUG_AutoSyncState_L1
+//#define DEBUG_AutoSyncState_L2
+#endif
+
+
+MsgStrategyComparatorAdaptor::MsgStrategyComparatorAdaptor(nsIAutoSyncMsgStrategy* aStrategy, 
+  nsIMsgFolder *aFolder, nsIMsgDatabase *aDatabase) : mStrategy(aStrategy), mFolder(aFolder), 
+    mDatabase(aDatabase)
+{
+}
+
+/** @return True if the elements are equals; false otherwise. */
+PRBool MsgStrategyComparatorAdaptor::Equals(const nsMsgKey& a, const nsMsgKey& b) const
+{
+  nsCOMPtr<nsIMsgDBHdr> hdrA;
+  nsCOMPtr<nsIMsgDBHdr> hdrB;
+  
+  mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+  mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+  
+  if (hdrA && hdrB)
+  {
+    nsresult rv;
+    nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+    
+    nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(mFolder);
+    if (mStrategy)
+      rv = mStrategy->Sort(folder, hdrA, hdrB, &decision);
+    
+    if (NS_SUCCEEDED(rv))
+      return (decision == nsAutoSyncStrategyDecisions::Same);
+  }
+  
+  return PR_FALSE;
+}
+
+/** @return True if (a < b); false otherwise. */
+PRBool MsgStrategyComparatorAdaptor::LessThan(const nsMsgKey& a, const nsMsgKey& b) const
+{
+  nsCOMPtr<nsIMsgDBHdr> hdrA;
+  nsCOMPtr<nsIMsgDBHdr> hdrB;
+  
+  mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+  mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+  if (hdrA && hdrB)
+  {
+    nsresult rv;
+    nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+    
+    nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(mFolder);
+    if (mStrategy)
+      rv = mStrategy->Sort(folder, hdrA, hdrB, &decision);
+      
+    if (NS_SUCCEEDED(rv))
+        return (decision == nsAutoSyncStrategyDecisions::Lower);      
+  }
+  
+  return PR_FALSE;
+}
+
+nsAutoSyncState::nsAutoSyncState(nsImapMailFolder *aOwnerFolder, PRTime aLastSyncTime) 
+  : mSyncState(stCompletedIdle), mOffset(0U), mLastOffset(0U), mLastSyncTime(aLastSyncTime), 
+    mLastUpdateTime(0UL), mProcessPointer(0U), mIsDownloadQChanged(PR_FALSE), mRetryCounter(0U)
+{
+  mOwnerFolder = do_GetWeakReference(static_cast<nsIMsgImapMailFolder*>(aOwnerFolder));
+}
+
+nsAutoSyncState::~nsAutoSyncState()
+{
+}
+
+// TODO:XXXemre should be implemented when we start
+// doing space management
+nsresult nsAutoSyncState::ManageStorageSpace()
+{
+  return NS_OK;
+}
+
+nsresult nsAutoSyncState::PlaceIntoDownloadQ(const nsTArray<nsMsgKey> &aMsgKeyList)
+{
+  nsresult rv;
+  if (!aMsgKeyList.IsEmpty())
+  {
+    nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+        
+    nsCOMPtr<nsIMsgDatabase> database;
+    rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(database));
+    if (!database)
+      return NS_ERROR_FAILURE;
+    
+    nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv,rv);
+    
+    nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+    autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+    
+    // increase the array size
+    mDownloadQ.SetCapacity(mDownloadQ.Length() + aMsgKeyList.Length());
+    
+    // remove excluded messages
+    PRInt32 elemCount = aMsgKeyList.Length();
+    for (PRInt32 idx = 0; idx < elemCount; idx++)
+    {
+      nsCOMPtr<nsIMsgDBHdr> hdr;
+      rv = database->GetMsgHdrForKey(aMsgKeyList[idx], getter_AddRefs(hdr));
+      if(!hdr)
+        continue; // can't get message header, continue with the next one
+      
+      PRBool doesFit = PR_TRUE;
+      rv = autoSyncMgr->DoesMsgFitDownloadCriteria(hdr, &doesFit);
+      if (NS_SUCCEEDED(rv) && !mDownloadQ.Contains(aMsgKeyList[idx]) && doesFit)
+      {
+        PRBool excluded = PR_FALSE;
+        if (msgStrategy)
+        {
+          rv = msgStrategy->IsExcluded(folder, hdr, &excluded);
+          
+          if (NS_SUCCEEDED(rv) && !excluded)
+          {
+            mIsDownloadQChanged = PR_TRUE;
+            mDownloadQ.AppendElement(aMsgKeyList[idx]);
+          }
+        }
+      }
+    }//endfor
+    
+    if (mIsDownloadQChanged)
+    {
+      #if defined(DEBUG_me) && defined(DEBUG_AutoSyncState_L1)
+      DebugPrintOwnerFolderName("Download Q is created for ");
+       #ifdef DEBUG_AutoSyncState_L2
+       DebugPrintQWithSize(mDownloadQ, 0);
+       #endif   
+      #endif
+
+      rv = autoSyncMgr->OnDownloadQChanged(this);
+    }
+    
+  }
+  return rv;
+}
+
+nsresult nsAutoSyncState::SortQueueBasedOnStrategy(nsTArray<nsMsgKey> &aQueue)
+{
+  nsresult rv;
+  nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsCOMPtr<nsIMsgDatabase> database;
+  rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(database));
+  if (!database)
+    return NS_ERROR_FAILURE;
+  
+  nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+        
+  nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+  rv = autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+  NS_ENSURE_SUCCESS(rv, rv);
+      
+  MsgStrategyComparatorAdaptor strategyComp(msgStrategy, folder, database);
+  aQueue.Sort(strategyComp);
+  
+  return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetNextGroupOfMessages(nsIMutableArray **aMessagesList)
+{
+  NS_ENSURE_ARG_POINTER(aMessagesList);
+  
+  nsresult rv;
+  nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsCOMPtr<nsIMsgDatabase> database;
+  folder->GetMsgDatabase(nsnull, getter_AddRefs(database));
+      
+  nsCOMPtr<nsIMutableArray> group = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (database)
+  {
+    if (!mDownloadQ.IsEmpty())
+    {
+      // sort the download queue if new items are added since the last time
+      if (mIsDownloadQChanged)
+      {
+        if (mOffset > 0)
+        {
+          // get rid of the downloaded message keys and reset the offsets
+          mDownloadQ.RemoveElementsAt(0, mOffset);
+          mOffset = mLastOffset = 0;
+        }
+        
+        if ( NS_SUCCEEDED(SortQueueBasedOnStrategy(mDownloadQ)) )
+          mIsDownloadQChanged = PR_FALSE;
+      }
+      
+      nsresult rv;
+      nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRUint32 groupSize;
+      PRUint32 totalSize = 0;
+      PRUint32 msgCount = mDownloadQ.Length();
+      PRUint32 idx = mOffset;
+
+      // get the group size allowed per download
+      autoSyncMgr->GetGroupSize(&groupSize);
+      
+      nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+      autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+      for (; idx < msgCount; idx++)
+      {
+        nsCOMPtr<nsIMsgDBHdr> qhdr;
+        database->GetMsgHdrForKey(mDownloadQ[idx], getter_AddRefs(qhdr));
+        if(!qhdr)
+          continue; //maybe deleted, skip it!
+        
+        // ensure that we don't have this message body offline already,
+        // possible if the user explicitly selects this message prior
+        // to auto-sync kicks in
+        PRUint32 msgFlags = 0;
+        qhdr->GetFlags(&msgFlags);
+        if ( msgFlags & MSG_FLAG_OFFLINE )
+          continue;
+          
+        // this check point allows msg strategy function
+        // to do last minute decisions based on the current
+        // state of TB such as the size of the message store etc..
+        if (msgStrategy)
+        {
+          PRBool excluded = PR_FALSE;
+          if (NS_SUCCEEDED(msgStrategy->IsExcluded(folder, qhdr, &excluded)) && excluded)
+            continue;
+        }
+
+        PRUint32 msgSize;
+        qhdr->GetMessageSize(&msgSize);
+        
+        if (totalSize == 0 && msgSize >= groupSize) 
+        {
+          group->AppendElement(qhdr, PR_FALSE);
+          idx++;
+          break;
+        }
+        else if (totalSize + msgSize > groupSize)
+          break;
+        else
+          group->AppendElement(qhdr, PR_FALSE);
+         
+        totalSize += msgSize;
+      }//endfor
+      
+      mLastOffset = mOffset;
+      mOffset = idx;
+    }
+    
+    #if defined(DEBUG_me) && defined(DEBUG_AutoSyncState_L1)
+    DebugPrintOwnerFolderName("Next group of messages to be downloaded.");
+     #ifdef DEBUG_AutoSyncState_L2
+      DebugPrintQWithSize(group.get(), 0);
+     #endif
+    #endif
+  } //endif
+  
+   // return it to the caller
+  NS_IF_ADDREF(*aMessagesList = group);
+ 
+  return NS_OK;
+}
+
+/**
+ * Usually called by nsAutoSyncManager when the last sync time is expired.
+ */
+NS_IMETHODIMP nsAutoSyncState::ProcessExistingHeaders(PRUint32 aNumOfHdrsToProcess, PRUint32 *aLeftToProcess)
+{
+  NS_ENSURE_ARG_POINTER(aLeftToProcess);
+  
+  nsresult rv;
+  nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsCOMPtr<nsIMsgDatabase> database;
+  rv = folder->GetMsgDatabase(nsnull, getter_AddRefs(database));
+  if (!database)
+    return NS_ERROR_FAILURE;
+  
+  // create a queue to process existing headers for the first time
+  if (mExistingHeadersQ.IsEmpty())
+  {
+    rv = database->ListAllKeys(mExistingHeadersQ);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mProcessPointer = 0;
+  }
+  
+  // process the existing headers and find the messages not downloaded yet
+  PRUint32 lastIdx = mProcessPointer;
+  nsTArray<nsMsgKey> msgKeys;
+  PRUint32 keyCount = mExistingHeadersQ.Length();
+  for (; mProcessPointer < (lastIdx + aNumOfHdrsToProcess) && mProcessPointer < keyCount; mProcessPointer++)
+  {
+    nsCOMPtr<nsIMsgDBHdr> hdr;
+    rv = database->GetMsgHdrForKey(mExistingHeadersQ[mProcessPointer], getter_AddRefs(hdr));
+    if (hdr)
+    {
+      PRUint32 msgFlags = 0;
+      hdr->GetFlags(&msgFlags);
+      
+      if (!(msgFlags & MSG_FLAG_OFFLINE))
+        msgKeys.AppendElement(mExistingHeadersQ[mProcessPointer]);
+    }
+  }
+  
+  #if defined(DEBUG_me) && defined(DEBUG_AutoSyncState_L1)
+  nsCString folderName;
+  folder->GetURI(folderName);
+  printf("%d messages will be added into the download q of folder %s\n", msgKeys.Length(), folderName.get());
+  #endif   
+  
+  if (!msgKeys.IsEmpty())
+  {
+    rv = PlaceIntoDownloadQ(msgKeys);
+    if (NS_FAILED(rv))
+      mProcessPointer = lastIdx;
+  }
+      
+  *aLeftToProcess = keyCount - mProcessPointer;
+    
+  // cleanup if we are done processing
+  if (0 == *aLeftToProcess)
+  {
+    mLastSyncTime = PR_Now();
+    mExistingHeadersQ.Clear();
+    mProcessPointer = 0;
+  }
+  
+  return rv;
+}
+
+nsresult nsAutoSyncState::OnNewHeaderFetchCompleted(const nsTArray<nsMsgKey> &aMsgKeyList)
+{
+  return PlaceIntoDownloadQ(aMsgKeyList);
+}
+
+NS_IMETHODIMP nsAutoSyncState::OnStartRunningUrl(nsIURI* aUrl)
+{
+  nsresult rv = NS_OK;
+    
+  // if there is a problem to start the download, set rv with the
+  // corresponding error code. In that case, AutoSyncManager is going to
+  // set the autosync state to nsAutoSyncState::stReadyToDownload
+  // to resume downloading another time
+  
+  // TODO: is there a way to make sure that download started without
+  // problem through nsIURI interface?
+   
+  nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  return autoSyncMgr->OnDownloadStarted(this, rv);
+}
+
+NS_IMETHODIMP nsAutoSyncState::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode)
+{ 
+  nsresult rv;
+  nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  //XXXemre how we recover from this error?
+  rv = ownerFolder->ReleaseSemaphore(ownerFolder);
+  NS_ASSERTION(NS_SUCCEEDED(rv), "*** Cannot release folder semaphore");
+   
+  nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+  if (mailUrl)
+    rv = mailUrl->UnRegisterListener(this);
+    
+  nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = autoSyncMgr->OnDownloadCompleted(this, aExitCode);
+    
+  return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetState(PRInt32 *aState)
+{
+  NS_ENSURE_ARG_POINTER(aState);
+  *aState = mSyncState;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::SetState(PRInt32 aState)
+{
+  mSyncState = aState;
+  if (aState == stCompletedIdle)
+  {
+    ResetDownloadQ();
+    
+    //tell folder to let go of its cached msg db pointer
+    nsresult rv;
+    nsCOMPtr<nsIMsgMailSession> session =
+             do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+    if (NS_SUCCEEDED(rv) && session)
+    {
+      nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      PRBool folderOpen;
+      PRUint32 folderFlags;
+      ownerFolder->GetFlags(&folderFlags);
+      session->IsFolderOpenInWindow(ownerFolder, &folderOpen);
+      if (!folderOpen && ! (folderFlags & nsMsgFolderFlags::Inbox))
+        ownerFolder->SetMsgDatabase(nsnull);
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::TryCurrentGroupAgain(PRUint32 aRetryCount)
+{
+  SetState(stReadyToDownload);
+
+  nsresult rv;
+  if (++mRetryCounter > aRetryCount)
+  {
+    ResetRetryCounter();
+    rv = NS_ERROR_FAILURE;
+  }
+  else
+    rv = Rollback();
+    
+  return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetRetryCounter()
+{
+  mRetryCounter = 0;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetPendingMessageCount(PRInt32 *aMsgCount)
+{
+  NS_ENSURE_ARG_POINTER(aMsgCount);
+  *aMsgCount = mDownloadQ.Length() - mOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetOwnerFolder(nsIMsgFolder **aFolder)
+{
+  NS_ENSURE_ARG_POINTER(aFolder);
+  
+  nsresult rv;
+  nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  NS_IF_ADDREF(*aFolder = ownerFolder);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::Rollback()
+{
+  mOffset = mLastOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetDownloadQ()
+{
+  mOffset = mLastOffset = 0;
+  mDownloadQ.Clear();
+  mDownloadQ.Compact();
+  
+  return NS_OK;
+}
+
+/**
+ * Tests whether the given folder is owned by the same imap server
+ * or not.
+ */
+NS_IMETHODIMP nsAutoSyncState::IsSibling(nsIAutoSyncState *aAnotherStateObj, PRBool *aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  *aResult = PR_FALSE;
+
+  nsresult rv;
+  nsCOMPtr<nsIMsgFolder> folderA, folderB;
+  
+  rv = GetOwnerFolder(getter_AddRefs(folderA));
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  rv = aAnotherStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  nsCOMPtr <nsIMsgIncomingServer> serverA, serverB;
+  rv = folderA->GetServer(getter_AddRefs(serverA));
+  NS_ENSURE_SUCCESS(rv,rv);
+  rv = folderB->GetServer(getter_AddRefs(serverB));
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  PRBool isSibling;
+  rv = serverA->Equals(serverB, &isSibling);
+  
+  if (NS_SUCCEEDED(rv))
+    *aResult = isSibling;
+  
+  return rv;
+}
+
+
+NS_IMETHODIMP nsAutoSyncState::DownloadMessagesForOffline(nsIArray *aMessagesList)
+{
+  NS_ENSURE_ARG_POINTER(aMessagesList);
+  
+  PRUint32 count;
+  nsresult rv = aMessagesList->GetLength(&count);
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  nsCAutoString messageIds;
+  nsTArray<nsMsgKey> msgKeys;
+  
+  rv = nsImapMailFolder::BuildIdsAndKeyArray(aMessagesList, messageIds, msgKeys);  
+  if (NS_FAILED(rv) || messageIds.IsEmpty()) 
+    return rv;
+  
+  // acquire semaphore for offline store. If it fails, we won't download
+  nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = folder->AcquireSemaphore(folder);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  // start downloading
+  rv = imapService->DownloadMessagesForOffline(messageIds, 
+                                               folder, 
+                                               this, 
+                                               nsnull);
+  if (NS_SUCCEEDED(rv))
+    SetState(stDownloadInProgress);                                              
+  
+  return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetLastSyncTime(PRTime *aLastSyncTime)
+{
+  NS_ENSURE_ARG_POINTER(aLastSyncTime);
+  *aLastSyncTime = mLastSyncTime;
+  return NS_OK;
+}
+
+void nsAutoSyncState::SetLastSyncTimeInSec(PRInt32 aLastSyncTime)
+{
+  mLastSyncTime = ((PRTime)aLastSyncTime * PR_USEC_PER_SEC);
+}
+
+
+NS_IMETHODIMP nsAutoSyncState::GetLastUpdateTime(PRTime *aLastUpdateTime)
+{
+  NS_ENSURE_ARG_POINTER(aLastUpdateTime);
+  *aLastUpdateTime = mLastUpdateTime;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::SetLastUpdateTime(PRTime aLastUpdateTime)
+{
+  mLastUpdateTime = aLastUpdateTime;
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS2(nsAutoSyncState, nsIAutoSyncState, nsIUrlListener)
+
+
+#ifdef DEBUG_me
+void nsAutoSyncState::DebugPrintQWithSize(nsTArray<nsMsgKey>& q, PRUint32 toOffset)
+{
+  nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+  if (ownerFolder)
+  {
+    nsCOMPtr<nsIMsgDatabase> database;
+    ownerFolder->GetMsgDatabase(nsnull, getter_AddRefs(database));
+    
+    PRUint32 x = q.Length();
+    while (x > toOffset && database) 
+    {
+      x--;
+      nsCOMPtr<nsIMsgDBHdr> h;
+      database->GetMsgHdrForKey(q[x], getter_AddRefs(h));
+      PRUint32 s;
+      if (h)
+        h->GetMessageSize(&s);
+      printf("Elem #%d, size: %u bytes\n", x+1, s);
+    }
+  }
+}
+
+void nsAutoSyncState::DebugPrintQWithSize(nsIMutableArray *q, PRUint32 toOffset)
+{
+  nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+  if (ownerFolder)
+  {
+    nsCOMPtr<nsIMsgDatabase> database;
+    ownerFolder->GetMsgDatabase(nsnull, getter_AddRefs(database));
+
+    PRUint32 x;
+    q->GetLength(&x);
+    while (x > toOffset && database) 
+    {
+      x--;
+      nsCOMPtr<nsIMsgDBHdr> h;
+      q->QueryElementAt(x, NS_GET_IID(nsIMsgDBHdr),
+                        getter_AddRefs(h));
+      PRUint32 s;
+      if (h)
+        h->GetMessageSize(&s);
+      printf("Elem #%d, size: %u bytes\n", x+1, s);
+    }
+  }
+}
+
+void nsAutoSyncState::DebugPrintOwnerFolderName(char *s)
+{
+  nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+  if (ownerFolder)
+  {
+    nsCString folderName;
+    ownerFolder->GetURI(folderName);
+    printf("*** %s Folder: %s ***\n", s, folderName.get());
+  }
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncState.h
@@ -0,0 +1,126 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Emre Birol  <ebirol@gmail.com> (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef nsAutoSyncState_h__
+#define nsAutoSyncState_h__
+
+#include "MailNewsTypes.h"
+#include "nsIAutoSyncState.h"
+#include "nsIAutoSyncManager.h" 
+#include "nsIUrlListener.h"
+#include "nsWeakPtr.h"
+#include "nsTArray.h"
+
+class nsImapMailFolder;
+class nsIAutoSyncMsgStrategy;
+class nsIMsgDatabase;
+
+/**
+ * An adaptor class to make msg strategy nsTArray.Sort()
+ * compatible.
+ */
+class MsgStrategyComparatorAdaptor 
+{
+ public:
+  MsgStrategyComparatorAdaptor(nsIAutoSyncMsgStrategy* aStrategy, 
+    nsIMsgFolder *aFolder, nsIMsgDatabase *aDatabase);
+
+  /** @return True if the elements are equals; false otherwise. */
+  PRBool Equals(const nsMsgKey& a, const nsMsgKey& b) const;
+  
+  /** @return True if (a < b); false otherwise. */
+  PRBool LessThan(const nsMsgKey& a, const nsMsgKey& b) const;
+  
+ private:
+  MsgStrategyComparatorAdaptor();
+  
+ private:
+  nsIAutoSyncMsgStrategy *mStrategy;
+  nsIMsgFolder *mFolder;
+  nsIMsgDatabase *mDatabase;
+};
+
+
+/**
+ * Facilitates auto-sync capabilities for imap folders.
+ */
+class nsAutoSyncState : public nsIAutoSyncState, public nsIUrlListener
+{
+  static const PRInt32 kFirstPassMsgKeyCount = 500;
+  
+ public: 
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIAUTOSYNCSTATE
+  NS_DECL_NSIURLLISTENER
+  
+  nsAutoSyncState(nsImapMailFolder *aOwnerFolder, PRTime aLastSyncTime = 0UL);
+  
+  /// Called by owner folder when new headers are fetched form the server
+  nsresult OnNewHeaderFetchCompleted(const nsTArray<nsMsgKey> &aMsgKeyList);
+
+  /// Sets the last sync time in lower precision (seconds)
+  void SetLastSyncTimeInSec(PRInt32 aLastSyncTime);
+  
+  /// Manages storage space for auto-sync operations 
+  nsresult ManageStorageSpace();
+
+ private:
+  ~nsAutoSyncState();
+  
+  nsresult PlaceIntoDownloadQ(const nsTArray<nsMsgKey> &aMsgKeyList);
+  nsresult SortQueueBasedOnStrategy(nsTArray<nsMsgKey> &aQueue);  
+  
+  #ifdef DEBUG_me
+  void DebugPrintOwnerFolderName(char *s);
+  void DebugPrintQWithSize(nsTArray<nsMsgKey>& q, PRUint32 toOffset = 0);
+  void DebugPrintQWithSize(nsIMutableArray *q, PRUint32 toOffset = 0);
+  #endif
+  
+ private:
+  PRInt32 mSyncState;
+  nsWeakPtr mOwnerFolder;
+  PRUint32 mOffset;
+  PRUint32 mLastOffset;
+  PRTime mLastSyncTime;
+  PRTime mLastUpdateTime;
+  PRUint32 mProcessPointer;
+  PRBool mIsDownloadQChanged;
+  PRUint32 mRetryCounter;
+  nsTArray<nsMsgKey> mDownloadQ;
+  nsTArray<nsMsgKey> mExistingHeadersQ;
+};
+
+#endif
--- a/mailnews/imap/src/nsImapMailFolder.cpp
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -118,16 +118,17 @@
 #include "nsIMsgFolderNotificationService.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsIExternalProtocolService.h"
 #include "nsCExternalHandlerService.h"
 #include "prprf.h"
 #include "nsIMutableArray.h"
 #include "nsArrayUtils.h"
 #include "nsArrayEnumerator.h"
+#include "nsAutoSyncManager.h"
 
 static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
 static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
 static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);
 
 nsIAtom* nsImapMailFolder::mImapHdrDownloadedAtom=nsnull;
 
 #define FOUR_K 4096
@@ -235,17 +236,17 @@ nsImapMailFolder::nsImapMailFolder() :
   m_numStatusRecentMessages = 0;
   m_numStatusUnseenMessages = 0;
   m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
   m_folderACL = nsnull;
   m_aclFlags = 0;
   m_supportedUserFlags = 0;
   m_namespace = nsnull;
   m_numFilterClassifyRequests = 0;
-  m_pendingPlaybackReq = nsnull;  
+  m_pendingPlaybackReq = nsnull;
 }
 
 nsImapMailFolder::~nsImapMailFolder()
 {
   MOZ_COUNT_DTOR(nsImapMailFolder);
   if (m_appendMsgMonitor)
       PR_DestroyMonitor(m_appendMsgMonitor);
 
@@ -1779,26 +1780,45 @@ NS_IMETHODIMP nsImapMailFolder::ReadFrom
       && hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
     m_hierarchyDelimiter = (PRUnichar) hierarchyDelimiter;
   rv = element->GetStringProperty("onlineName", onlineName);
   if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
     m_onlineFolderName.Assign(onlineName);
 
   m_aclFlags = -1; // init to invalid value.
   element->GetInt32Property("aclFlags", (PRInt32 *) &m_aclFlags);
+  
+  PRInt32 lastSyncTimeInSec;
+  if ( NS_FAILED(element->GetInt32Property("lastSyncTimeInSec", (PRInt32 *) &lastSyncTimeInSec)) )
+    lastSyncTimeInSec = 0U;
+  
+  // make sure that auto-sync state object is created
+  InitAutoSyncState();
+  m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec);
+  
   return rv;
 }
 
 NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
 {
   nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element);
   element->SetInt32Property("boxFlags", m_boxFlags);
   element->SetInt32Property("hierDelim", (PRInt32) m_hierarchyDelimiter);
   element->SetStringProperty("onlineName", m_onlineFolderName);
   element->SetInt32Property("aclFlags", (PRInt32) m_aclFlags);
+  
+  // store folder's last sync time
+  if (m_autoSyncStateObj)
+  {
+    PRTime lastSyncTime;
+    m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
+    // store in sec
+    element->SetInt32Property("lastSyncTimeInSec", (PRInt32) (lastSyncTime / PR_USEC_PER_SEC));
+  }
+   
   return rv;
 }
 
 NS_IMETHODIMP
 nsImapMailFolder::MarkMessagesFlagged(nsIArray *messages, PRBool markFlagged)
 {
   nsresult rv;
   // tell the folder to do it, which will mark them read in the db.
@@ -1889,17 +1909,17 @@ nsImapMailFolder::GetDBFolderInfoAndDB(n
       m_onlineFolderName.Assign(onlineCName);
       CopyASCIItoUTF16(onlineCName, autoOnlineName);
     }
     (*folderInfo)->SetProperty("onlineName", autoOnlineName);
   }
   return rv;
 }
 
-nsresult
+/* static */ nsresult
 nsImapMailFolder::BuildIdsAndKeyArray(nsIArray* messages,
                                       nsCString& msgIds,
                                       nsTArray<nsMsgKey>& keyArray)
 {
   NS_ENSURE_ARG_POINTER(messages);
   nsresult rv;
   PRUint32 count = 0;
   PRUint32 i;
@@ -5003,44 +5023,39 @@ nsImapMailFolder::HeaderFetchCompleted(n
     nsCOMPtr<nsIImapIncomingServer> imapServer;
     GetImapIncomingServer(getter_AddRefs(imapServer));
 
     PRBool autoDownloadNewHeaders = PR_FALSE;
     PRBool autoSyncOfflineStores = PR_FALSE;
 
     if (imapServer)
       imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+    
     if (autoSyncOfflineStores || mFlags & nsMsgFolderFlags::Inbox)
     {
       if (imapServer && mFlags & nsMsgFolderFlags::Inbox && !autoSyncOfflineStores)
         imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders);
-      // this isn't quite right - we only want to download bodies for new headers
-      // but we don't know what the new headers are. We could query the inbox db
-      // for new messages, if the above filter playback actually moves the filtered
-      // messages before we get to this code.
-      if (autoDownloadNewHeaders || autoSyncOfflineStores)
-      {
-          // acquire semaphore for offline store. If it fails, we won't download for offline use.
-        if (NS_SUCCEEDED(AcquireSemaphore(static_cast<nsIMsgImapMailFolder*>(this))))
-          m_downloadingFolderForOfflineUse = PR_TRUE;
-      }
-    }
-
-    if (m_downloadingFolderForOfflineUse)
-    {
+
       nsTArray<nsMsgKey> keysToDownload;
       GetBodysToDownload(&keysToDownload);
       if (!keysToDownload.IsEmpty())
+      {
         SetNotifyDownloadedLines(PR_TRUE);
-
-      aProtocol->NotifyBodysToDownload(keysToDownload.Elements(), keysToDownload.Length());
-    }
-    else
-      aProtocol->NotifyBodysToDownload(nsnull, 0/*keysToFetch.Length() */);
-
+      
+        // create auto-sync state object lazily
+        InitAutoSyncState();
+      
+        // make enough room for new downloads
+        m_autoSyncStateObj->ManageStorageSpace();
+        
+        m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload);
+      }
+    }
+    aProtocol->NotifyBodysToDownload(nsnull, 0/*keysToFetch.Length() */);
+   
     nsCOMPtr <nsIURI> runningUri;
     aProtocol->GetRunningUrl(getter_AddRefs(runningUri));
     if (runningUri)
     {
       nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningUri);
       if (mailnewsUrl)
         mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
     }
@@ -8225,16 +8240,65 @@ NS_IMETHODIMP nsImapMailFolder::ChangePe
 
 /* void changePendingUnread (in long aDelta); */
 NS_IMETHODIMP nsImapMailFolder::ChangePendingUnread(PRInt32 aDelta)
 {
   ChangeNumPendingUnread(aDelta);
   return NS_OK;
 }
 
+NS_IMETHODIMP nsImapMailFolder::GetAutoSyncStateObj(nsIAutoSyncState **autoSyncStateObj)
+{
+  NS_ENSURE_ARG_POINTER(autoSyncStateObj);
+  
+  // create auto-sync state object lazily
+  InitAutoSyncState();
+    
+  NS_IF_ADDREF(*autoSyncStateObj = m_autoSyncStateObj);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::InitiateAutoSync(nsIUrlListener *aUrlListener)
+{
+  #ifdef DEBUG_me
+  nsCString folderName;
+  GetURI(folderName);
+  printf("Updating folder: %s\n", folderName.get());
+  #endif
+
+  // HACK: if UpdateFolder finds out that it can't open 
+  // the folder, it doesn't set the url listener and returns 
+  // no error. In this case, we return success from this call 
+  // but the caller never gets a notification on its url listener.
+  PRBool canOpenThisFolder = PR_TRUE;
+  GetCanOpenFolder(&canOpenThisFolder);
+  
+  if (!canOpenThisFolder)
+  {
+    #ifdef DEBUG_me
+    printf("Cannot update folder: %s\n", folderName.get());
+    #endif
+    return NS_ERROR_FAILURE;
+  }
+
+  // create auto-sync state object lazily
+  InitAutoSyncState();
+  
+  nsresult rv = m_autoSyncStateObj->ManageStorageSpace();
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = UpdateFolder(nsnull, aUrlListener);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  // record the last update time
+  m_autoSyncStateObj->SetLastUpdateTime(PR_Now());
+  
+  return NS_OK;
+}
+
 nsresult nsImapMailFolder::CreatePlaybackTimer()
 {
   nsresult rv = NS_OK;
   if (!m_playbackTimer)
   {
     m_playbackTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
     NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create pseudo-offline operation timer in nsImapMailFolder");
   }
@@ -8253,8 +8317,14 @@ void nsImapMailFolder::PlaybackTimerCall
     nsresult rv = offlineSync->ProcessNextOperation();
     NS_ASSERTION(NS_SUCCEEDED(rv), "pseudo-offline playback is not successful");
   }
   
   // release request struct
   request->SrcFolder->m_pendingPlaybackReq = nsnull;
   delete request;
 }
+
+void nsImapMailFolder::InitAutoSyncState()
+{
+  if (!m_autoSyncStateObj)
+    m_autoSyncStateObj = new nsAutoSyncState(this);
+}
--- a/mailnews/imap/src/nsImapMailFolder.h
+++ b/mailnews/imap/src/nsImapMailFolder.h
@@ -61,16 +61,17 @@
 #include "nsIImapServerSink.h"
 #include "nsIMsgFilterPlugin.h"
 #include "nsIEventTarget.h"
 #include "nsIThread.h"
 #include "nsDataHashtable.h"
 #include "nsIMutableArray.h"
 #include "nsITimer.h"
 #include "nsCOMArray.h"
+#include "nsAutoSyncState.h"
 
 class nsImapMoveCoalescer;
 class nsIMsgIdentity;
 
 #define COPY_BUFFER_SIZE 16384
 
 /* b64534f0-3d53-11d3-ac2a-00805f8ac968 */
 
@@ -336,16 +337,17 @@ public:
                                   nsIMsgDatabase *sourceDB,
                                   const nsACString& destFolder,
                                   nsIMsgFilter *filter,
                                   nsIMsgWindow *msgWindow);
   virtual nsresult SpamFilterClassifyMessage(const char *aURI, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin);
   virtual nsresult SpamFilterClassifyMessages(const char **aURIArray, PRUint32 aURICount, nsIMsgWindow *aMsgWindow, nsIJunkMailPlugin *aJunkMailPlugin);
 
   static nsresult  AllocateUidStringFromKeys(nsMsgKey *keys, PRUint32 numKeys, nsCString &msgIds);
+  static nsresult  BuildIdsAndKeyArray(nsIArray* messages, nsCString& msgIds, nsTArray<nsMsgKey>& keyArray);
 
   // these might end up as an nsIImapMailFolder attribute.
   nsresult SetSupportedUserFlags(PRUint32 userFlags);
   nsresult GetSupportedUserFlags(PRUint32 *userFlags);
 protected:
   // Helper methods
 
   void FindKeysToAdd(const nsTArray<nsMsgKey> &existingKeys, nsTArray<nsMsgKey>
@@ -407,17 +409,16 @@ protected:
                           PRBool selectedState,
                           PRBool acrossServers,
                           PRUint32 newMsgFlags,
                           const nsACString &newMsgKeywords,
                           nsIMsgCopyServiceListener* listener,
                           nsIMsgWindow *msgWindow,
                           PRBool allowUndo);
   nsresult OnCopyCompleted(nsISupports *srcSupport, nsresult exitCode);
-  nsresult BuildIdsAndKeyArray(nsIArray* messages, nsCString& msgIds, nsTArray<nsMsgKey>& keyArray);
   nsresult GetMoveCoalescer();
   nsresult PlaybackCoalescedOperations();
   virtual nsresult CreateBaseMessageURI(const nsACString& aURI);
   // offline-ish methods
   nsresult GetClearedOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB);
   nsresult GetOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB);
   nsresult CopyMessagesOffline(nsIMsgFolder* srcFolder,
                                 nsIArray* messages,
@@ -430,16 +431,19 @@ protected:
   void GetTrashFolderName(nsAString &aFolderName);
   PRBool ShowPreviewText();
 
   // Pseudo-Offline operation playback timer
   static 
   void PlaybackTimerCallback(nsITimer *aTimer, void *aClosure);
   
   nsresult CreatePlaybackTimer();
+  
+  // Allocate and initialize associated auto-sync state object 
+  void InitAutoSyncState();
 
   PRBool m_initialized;
   PRBool m_haveDiscoveredAllFolders;
   PRBool m_haveReadNameFromDB;
   nsCOMPtr<nsIMsgParseMailMsgState> m_msgParser;
   nsCOMPtr<nsIMsgFilterList> m_filterList;
   nsCOMPtr<nsIMsgFilterPlugin> m_filterPlugin;  // XXX should be a list
   // used with filter plugins to know when we've finished classifying and can playback moves
@@ -485,17 +489,20 @@ protected:
   PRUint32     m_aclFlags;
   PRUint32     m_supportedUserFlags;
 
   static nsIAtom* mImapHdrDownloadedAtom;
 
   // offline imap support
   PRBool m_downloadMessageForOfflineUse;
   PRBool m_downloadingFolderForOfflineUse;
-
+  
+  // auto-sync (preemptive download) support
+  nsRefPtr<nsAutoSyncState> m_autoSyncStateObj;
+  
   // Quota support
   nsCString m_folderQuotaRoot;
   PRUint32 m_folderQuotaUsedKB;
   PRUint32 m_folderQuotaMaxKB;
   
   // Pseudo-Offline Playback support
   nsPlaybackRequest *m_pendingPlaybackReq;
   nsCOMPtr<nsITimer> m_playbackTimer;
--- a/mailnews/imap/src/nsImapService.cpp
+++ b/mailnews/imap/src/nsImapService.cpp
@@ -94,16 +94,17 @@
 #include "nsIDOMWindowInternal.h"
 #include "nsIMessengerWindowService.h"
 #include "nsIWindowMediator.h"
 #include "nsIPrompt.h"
 #include "nsIWindowWatcher.h"
 #include "nsImapProtocol.h"
 #include "nsIMsgMailSession.h"
 #include "nsIStreamConverterService.h"
+#include "nsIAutoSyncManager.h"
 #include "nsThreadUtils.h"
 #include "nsNetUtil.h"
 #include "nsInt64.h"
 
 #define PREF_MAIL_ROOT_IMAP "mail.root.imap"            // old - for backward compatibility only
 #define PREF_MAIL_ROOT_IMAP_REL "mail.root.imap-rel"
 
 static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
@@ -135,16 +136,26 @@ nsImapService::nsImapService()
   {
     nsresult rv;
     nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); 
     if (NS_SUCCEEDED(rv) && prefBranch) 
     {
       prefBranch->GetBoolPref("mail.imap.mime_parts_on_demand", &gMIMEOnDemand);
       prefBranch->GetIntPref("mail.imap.mime_parts_on_demand_threshold", &gMIMEOnDemandThreshold);
     }
+    
+    // initialize auto-sync service
+    nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+    if (NS_SUCCEEDED(rv) && autoSyncMgr) 
+    {
+      // auto-sync manager initialization goes here
+      // assign new strategy objects here... 
+    }
+    NS_ASSERTION(autoSyncMgr != nsnull, "*** Cannot initialize nsAutoSyncManager service.");
+      
     gInitialized = PR_TRUE;
   }
 }
 
 nsImapService::~nsImapService()
 {
 }
 
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -504,16 +504,20 @@ pref("mail.server.default.spamActionTarg
 pref("mail.server.default.useWhiteList", true);
 pref("mail.server.default.whiteListAbURI", "moz-abmdbdirectory://abook.mab"); // the Personal addressbook.
 pref("mail.server.default.useServerFilter", false);
 pref("mail.server.default.serverFilterName", "SpamAssassin");
 pref("mail.server.default.serverFilterTrustFlags", 1); // 1 == trust positives, 2 == trust negatives, 3 == trust both
 pref("mail.server.default.purgeSpam", false);
 pref("mail.server.default.purgeSpamInterval", 14); // 14 days
 
+// to activate auto-sync feature (preemptive message download for imap) by default
+pref("mail.server.default.autosync_offline_stores",true);
+pref("mail.server.default.offline_download",true);
+
 // the probablilty threshold over which messages are classified as junk
 // this number is divided by 100 before it is used. The classifier can be fine tuned
 // by changing this pref. Typical values are .99, .95, .90, .5, etc.
 pref("mail.adaptivefilters.junk_threshold", 90);
 pref("mail.spam.version", 0); // used to determine when to migrate global spam settings
 pref("mail.spam.logging.enabled", false);
 pref("mail.spam.manualMark", false);
 pref("mail.spam.markAsReadOnSpam", false);