Bug 455812 nsAutoSyncManager should expose a scriptable XPCOM interface to allow monitor background download operations. r=bienvenu,sr=dmose
authorEmre Birol <bugmil.ebirol@gmail.com>
Wed, 24 Sep 2008 16:39:33 +0100
changeset 421 536df74e4ed3a430d1982c0a54da4751965cd522
parent 420 d3427aaaa3f8a2e43c5f43b6fabd5de9b48de970
child 422 3a7803661daa648f43f5f5777631bcd7649cefd3
push idunknown
push userunknown
push dateunknown
reviewersbienvenu, dmose
bugs455812
Bug 455812 nsAutoSyncManager should expose a scriptable XPCOM interface to allow monitor background download operations. r=bienvenu,sr=dmose
mail/base/content/mailWindow.js
mail/locales/en-US/chrome/messenger/messenger.properties
mailnews/imap/public/nsIAutoSyncManager.idl
mailnews/imap/src/nsAutoSyncManager.cpp
mailnews/imap/src/nsAutoSyncManager.h
--- a/mail/base/content/mailWindow.js
+++ b/mail/base/content/mailWindow.js
@@ -59,20 +59,166 @@ var gContextMenu;
 var accountManagerDataSource;
 var folderDataSource;
 var unreadFolderDataSource;
 var favoriteFoldersDataSource;
 var recentFoldersDataSource;
 
 var gAccountCentralLoaded = true;
 
+var gAutoSyncManager;
+const nsIAutoSyncMgrListener = Components.interfaces.nsIAutoSyncMgrListener;
+
+var gAutoSyncMonitor = {
+  logEnabled : false,
+  msgWindow : null,
+  inQFolderList : new Array(),
+  runnning : false,
+  
+  onStateChanged : function(running)
+    {
+      this.runnning = running;
+      this.log("***Auto_Sync OnStatusChanged: " + (running ? "running" : "sleeping") + "\n");
+      if (!this.running)
+        this.clearStatusString();
+    },
+  onFolderAddedIntoQ : function(queue, folder)
+    {
+      if (folder instanceof Components.interfaces.nsIMsgFolder) 
+      {
+        if (queue == nsIAutoSyncMgrListener.PriorityQueue)
+        {
+          this.inQFolderList.push(folder);
+          this.log("***Auto_Sync OnFolderAddedIntoQ [" + this.inQFolderList.length + "] " + 
+                          folder.prettiestName + " of " + folder.server.prettyName + "\n");
+        }
+      }
+    },
+  onFolderRemovedFromQ : function(queue, folder)
+    {
+      if (folder instanceof Components.interfaces.nsIMsgFolder) 
+      {        
+        if (queue == nsIAutoSyncMgrListener.PriorityQueue)
+        { 
+          var i = this.inQFolderList.indexOf(folder);
+          if (i > -1)
+            this.inQFolderList.splice(i,1);
+         
+          this.log("***Auto_Sync OnFolderRemovedFromQ [" + this.inQFolderList.length + "] " + 
+                          folder.prettiestName + " of " + folder.server.prettyName + "\n");
+        
+          if (this.inQFolderList.length > 0)
+            this.showStatusString();
+          else
+            this.clearStatusString();
+        }
+      }
+    },
+  onDownloadStarted : function(folder, numOfMessages, totalPending)
+    {
+      if (folder instanceof Components.interfaces.nsIMsgFolder) 
+      {        
+        this.log("***Auto_Sync OnDownloadStarted (" + numOfMessages + "/" + totalPending + "): " + 
+                                folder.prettiestName + " of " + folder.server.prettyName + "\n");
+                
+        this.showStatusString();
+      }
+    },
+  onDownloadCompleted : function(folder)
+    {
+      if (folder instanceof Components.interfaces.nsIMsgFolder) 
+      {         
+        this.log("***Auto_Sync OnDownloadCompleted: " + folder.prettiestName + " of " + 
+                                                                  folder.server.prettyName + "\n");
+        if (this.runnning)
+          this.showStatusString();     
+      }
+    },
+  onDownloadError : function(folder)
+    {
+      if (folder instanceof Components.interfaces.nsIMsgFolder) 
+      {         
+        this.log("***Auto_Sync OnDownloadError: " + folder.prettiestName + " of " + 
+                                                                    folder.server.prettyName + "\n");
+      }
+    },
+  onDiscoveryQProcessed : function (folder, numOfHdrsProcessed, leftToProcess)
+    {
+      this.log("***Auto_Sync onDiscoveryQProcessed: Processed " + numOfHdrsProcessed + "/" + 
+                            (leftToProcess+numOfHdrsProcessed) + " of " + folder.prettiestName + "\n");
+    },
+  onAutoSyncInitiated : function (folder)
+    {
+      this.log("***Auto_Sync onAutoSyncInitiated: " + folder.prettiestName + " of " +
+                                                  folder.server.prettyName + " has been updated.\n"); 
+    },
+  getFolderListString : function()
+    {
+      var folderList;
+      if (this.inQFolderList.length > 0)
+        folderList = this.inQFolderList[0].prettiestName;
+          
+      for (var i = 1; i < this.inQFolderList.length; i++)
+        folderList = folderList + ", " + this.inQFolderList[i].prettiestName;
+        
+      return folderList;
+    },
+  getAccountListString : function()
+    {
+      var accountList;
+      if (this.inQFolderList.length > 0)
+        accountList = this.inQFolderList[0].server.prettyName;
+          
+      for (var i = 1; i < this.inQFolderList.length; i++)
+      {
+        // no do repeat already existing account names
+        if (accountList.search(this.inQFolderList[i].server.prettyName) == -1)
+          accountList = accountList + ", " + this.inQFolderList[i].server.prettyName;
+      }
+      return accountList;
+    },
+  showStatusString : function()
+    {
+      if (this.msgWindow && this.msgWindow.statusFeedback)
+      {
+        this.msgWindow.statusFeedback.showStatusString(
+                this.formatStatusString(this.getFolderListString(), this.getAccountListString()));
+      }
+    },
+  clearStatusString : function()
+    {
+      if (this.msgWindow && this.msgWindow.statusFeedback)
+        this.msgWindow.statusFeedback.showStatusString("");
+    },
+  formatStatusString : function(folderList, accountList)
+    {
+      if (!gMessengerBundle)
+        gMessengerBundle = document.getElementById("bundle_messenger");
+          
+      return gMessengerBundle.getFormattedString("autosyncProgress", 
+                                                  [folderList, accountList]);
+    },
+  log : function(text)
+    {
+      if (this.logEnabled)
+        dump(text);
+    } 
+};
+
 function OnMailWindowUnload()
 {
   MailOfflineMgr.uninit();
   ClearPendingReadTimer();
+  
+  try {
+    gAutoSyncManager.removeListener(gAutoSyncMonitor);
+  }
+  catch(ex) {
+    dump("error while removing auto-sync listener: " + ex);
+  }
 
   var searchSession = GetSearchSession();
   if (searchSession)
   {
     removeGlobalListeners();
     if (gPreQuickSearchView)     //close the cached pre quick search view
       gPreQuickSearchView.close();
   }
@@ -146,16 +292,21 @@ function CreateMailWindowGlobals()
   folderDataSource = Components.classes[prefix + "mailnewsfolders"]
                                .getService();
   unreadFolderDataSource = Components.classes[prefix + "mailnewsunreadfolders"]
                                      .getService();
   favoriteFoldersDataSource = Components.classes[prefix + "mailnewsfavefolders"]
                                         .getService();
   recentFoldersDataSource = Components.classes[prefix + "mailnewsrecentfolders"]
                                       .getService();
+                                      
+  gAutoSyncManager = Components.classes["@mozilla.org/imap/autosyncmgr;1"]
+                                       .getService(Components.interfaces.nsIAutoSyncManager);
+  gAutoSyncMonitor.msgWindow = msgWindow;
+  gAutoSyncManager.addListener(gAutoSyncMonitor);
 }
 
 function InitMsgWindow()
 {
   msgWindow.windowCommands = new nsMsgWindowCommands();
   // set the domWindow before setting the status feedback and header sink objects
   msgWindow.domWindow = window;
   msgWindow.statusFeedback = statusFeedback;
@@ -536,8 +687,10 @@ function OpenInboxForServer(server)
 
 function GetSearchSession()
 {
   if (("gSearchSession" in top) && gSearchSession)
     return gSearchSession;
   else
     return null;
 }
+
+
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -152,16 +152,21 @@ sizeColumnTooltip=Click to sort by size
 sizeColumnHeader=Size
 linesColumnTooltip=Click to sort by lines
 linesColumnHeader=Lines
 
 # status feedback stuff
 documentDone=
 documentLoading=Loading Message…
 
+# LOCALIZATION NOTE (autosyncProgress): Do not translate the word "%1$S" or "%2$S" below.
+# Place the word %1$S in your translation where the name of the comma separated folders should appear.
+# Place the word %2$S in your translation where the name of the comma separated accounts should appear.
+autosyncProgress=Synchronizing messages in %1$S from %2$S…
+
 unreadMsgStatus=Unread: %S
 selectedMsgStatus=Selected: %S
 totalMsgStatus=Total: %S
 
 # localized folder names
 
 localFolders=Local Folders
 
--- a/mailnews/imap/public/nsIAutoSyncManager.idl
+++ b/mailnews/imap/public/nsIAutoSyncManager.idl
@@ -35,43 +35,129 @@
  * ***** END LICENSE BLOCK ***** */
  
 #include "nsISupports.idl"
 
 interface nsIAutoSyncMsgStrategy;
 interface nsIAutoSyncFolderStrategy;
 interface nsIMsgDBHdr;
 interface nsIAutoSyncState;
+interface nsIAutoSyncMgrListener;
+interface nsIMsgFolder;
+interface nsIMsgAccount;
+
+[scriptable, uuid(41ec36a7-1a53-4ca3-b698-dca6452a8761)] 
+interface nsIAutoSyncMgrListener : nsISupports {
+
+  /**
+   * Queue types
+   */
+  const long PriorityQueue = 1;
+  const long UpdateQueue = 2;
+  const long DiscoveryQueue = 3;
+
+  /**
+   * It is called on the listener when a new folder is added into
+   * the queue
+   *
+   * @param aQType type of the queue
+   * @param aFolder folder that is added into the queue
+   */
+  void onFolderAddedIntoQ(in long aQType, in nsIMsgFolder aFolder);
+  
+  /**
+   * It is called on the listener when a folder is removed from
+   * the queue
+   *
+   * @param aQType type of the queue
+   * @param aFolder folder that is removed from the queue
+   */
+  void onFolderRemovedFromQ(in long aQType, in nsIMsgFolder aFolder);
+  
+  /**
+   * It is called on the listener when a message download is successfully started
+   *
+   * @param aFolder folder in which the download is started 
+   * @param aNumberOfMessages number of the messages that will be downloaded 
+   * @param aTotalPending  total number of messages waiting to be downloaded 
+   */
+  void onDownloadStarted(in nsIMsgFolder aFolder, in unsigned long aNumberOfMessages, 
+                         in unsigned long aTotalPending);
+  /**
+   * It is called on the listener when a message download on the given folder 
+   * is completed
+   */
+  void onDownloadCompleted(in nsIMsgFolder aFolder);
+  
+  /**
+   * It is called on the listener when an error occurs during the message download
+   */
+  void onDownloadError(in nsIMsgFolder aFolder);
+  
+  /*
+   * Auto-Sync manager is running or waiting for idle 
+   */
+  void onStateChanged(in boolean aRunning);
+  
+  /**
+   * It is called on the listener after the auto-sync manager starts to process 
+   * existing headers of the given folder to find missing message bodies
+   * (mostly for debugging purposes)
+   */
+  void onDiscoveryQProcessed(in nsIMsgFolder aFolder, in unsigned long aNumberOfHdrsProcessed, 
+                             in unsigned long aLeftToProcess);
+  
+  /**
+   * It is called on the listener after the auto-sync manager updates the given folder 
+   * (mostly for debugging purposes)
+   */
+  void onAutoSyncInitiated(in nsIMsgFolder aFolder);
+};
+
 
 [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;
+  
+  /**
+   * Adds a listener to notify about auto-sync events
+   */
+  void addListener(in nsIAutoSyncMgrListener aListener);
+  
+  /**
+   * Removes the listener from notification list
+   */
+  void removeListener(in nsIAutoSyncMgrListener aListener);
     
+  /**
+   * Tests the given message to make sure that whether 
+   * it fits the download criteria or not
+   */
   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);
   
@@ -83,16 +169,34 @@ interface nsIAutoSyncManager : nsISuppor
   
   /**
    * Called by the nsAutoSyncState object when the download
    * completed. Given interface is already addref'd.
    */
   void onDownloadCompleted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aExitCode);
   
   /**
+   * Number of elements in the discovery queue.
+   * @see nsAutoSyncManager.h for details
+   */
+  readonly attribute unsigned long discoveryQLength;
+  
+  /**
+   * Number of elements in the update queue.
+   * @see nsAutoSyncManager.h for details
+   */
+  readonly attribute unsigned long updateQLength;
+  
+  /**
+   * Number of elements in the download queue (a.k.a priority queue).
+   * @see nsAutoSyncManager.h for details
+   */
+  readonly attribute unsigned long downloadQLength;
+
+  /**
    * Active download model; Chained (serial), or Parallel
    */
   attribute long downloadModel;
 };
 
 %{C++
 #define NS_AUTOSYNCMANAGER_CID  \
 { /* C358C568-47B2-42b2-8146-3C0F8D1FAD6E */  \
--- a/mailnews/imap/src/nsAutoSyncManager.cpp
+++ b/mailnews/imap/src/nsAutoSyncManager.cpp
@@ -29,38 +29,28 @@
  * 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()
 {
@@ -170,16 +160,28 @@ NS_IMETHODIMP nsDefaultAutoSyncFolderStr
 NS_IMETHODIMP 
 nsDefaultAutoSyncFolderStrategy::IsExcluded(nsIMsgFolder *aFolder, PRBool *aDecision)
 {
   NS_ENSURE_ARG_POINTER(aDecision);
   *aDecision = PR_FALSE;
   return NS_OK;
 }
 
+#define NOTIFY_LISTENERS_STATIC(obj_, propertyfunc_, params_) \
+  PR_BEGIN_MACRO \
+  nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener> >::ForwardIterator iter(obj_->mListeners); \
+  nsCOMPtr<nsIAutoSyncMgrListener> listener; \
+  while (iter.HasMore()) { \
+    listener = iter.GetNext(); \
+    listener->propertyfunc_ params_; \
+  } \
+  PR_END_MACRO
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+  NOTIFY_LISTENERS_STATIC(this, propertyfunc_, params_)
 
 nsAutoSyncManager::nsAutoSyncManager()
 {
   mGroupSize = kDefaultGroupSize;
   mStartupTime = PR_Now();
   mDownloadModel = dmChained;
   mUpdateState = completed;
   
@@ -245,23 +247,27 @@ void nsAutoSyncManager::TimerCallback(ns
   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
-      
+      nsCOMPtr<nsIMsgFolder> folder;
+      autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+      if (folder)
+        NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnDiscoveryQProcessed, (folder, kNumberOfHeadersToProcess, leftToProcess));
+            
       if (NS_SUCCEEDED(rv) && 0 == leftToProcess)
+      {
         autoSyncMgr->mDiscoveryQ.RemoveObjectAt(0);
+        if (folder)
+          NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+      }
     }
   }
   
   if (autoSyncMgr->mUpdateQ.Count() > 0)
   {
     if (autoSyncMgr->mUpdateState == completed)
     {
       nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mUpdateQ[0]);
@@ -274,26 +280,37 @@ void nsAutoSyncManager::TimerCallback(ns
           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;
+              NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnAutoSyncInitiated, (folder));
+            }
           }
         }
       } 
     }
     // 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); 
+    {
+      nsCOMPtr<nsIMsgFolder> folder;
+      autoSyncMgr->mUpdateQ[0]->GetOwnerFolder(getter_AddRefs(folder));
+      
+      autoSyncMgr->mUpdateQ.RemoveObjectAt(0);
+      
+      if (folder)
+        NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::UpdateQueue, folder));
+    }
       
   }//endif
 
 }
 
 /**
  * Populates aChainedQ with the auto-sync state objects that are not owned by 
  * the same imap server. 
@@ -335,20 +352,16 @@ void nsAutoSyncManager::ChainFoldersInQ(
     }//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)
@@ -452,35 +465,35 @@ NS_IMETHODIMP nsAutoSyncManager::OnStopR
 }
 
 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);
+    NOTIFY_LISTENERS(OnStateChanged, (PR_FALSE));
     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
-  
+    
+  // notify listeners that auto-sync is running
+  NOTIFY_LISTENERS(OnStateChanged, (PR_TRUE));
+    
   nsCOMArray<nsIAutoSyncState> chainedQ;
   nsCOMArray<nsIAutoSyncState> *queue = &mPriorityQ;
   if (mDownloadModel == dmChained) 
   {
     ChainFoldersInQ(mPriorityQ, chainedQ);
     queue = &chainedQ;
   }
   
@@ -570,90 +583,76 @@ nsresult nsAutoSyncManager::AutoUpdateFo
         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
+        if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle == state)
         {
           // 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);
+              if (folder)
+                NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::UpdateQueue, folder));
+            }
           }
         }
   
         // 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);
+            if (folder)
+              NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+          }
         }
       }//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();
@@ -679,17 +678,20 @@ void nsAutoSyncManager::ScheduleFolderFo
       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
+          NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::PriorityQueue, folder));
+        }
       }
     }
     else 
     {
       // find the right spot for the given folder      
       PRUint32 qidx = mPriorityQ.Count();
       while (qidx > 0) 
       {
@@ -713,17 +715,18 @@ void nsAutoSyncManager::ScheduleFolderFo
         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);
-        
+
+        NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::PriorityQueue, folderB));
         break;
       }//endwhile
     }
   }//endif
 }
 
 nsresult nsAutoSyncManager::DownloadMessagesForOffline(nsIAutoSyncState *aAutoSyncStateObj)
 {  
@@ -738,28 +741,22 @@ nsresult nsAutoSyncManager::DownloadMess
   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
-
+    nsCOMPtr<nsIMsgFolder> folder;
+    aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+    if (NS_SUCCEEDED(rv) && folder)
+      NOTIFY_LISTENERS(OnDownloadStarted, (folder, length, count));
   }
   
   return rv;
 }
 
 /**
  * Assuming that the download operation on the given folder has been failed at least once, 
  * execute these steps:
@@ -773,16 +770,21 @@ nsresult nsAutoSyncManager::HandleDownlo
 {
   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);
   
+  nsCOMPtr<nsIMsgFolder> folder;
+  aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+  if (folder)
+    NOTIFY_LISTENERS(OnDownloadError, (folder));
+  
   // 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)) )
@@ -908,21 +910,16 @@ nsAutoSyncManager::DoesMsgFitDownloadCri
 }
 
 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));
@@ -946,17 +943,16 @@ NS_IMETHODIMP nsAutoSyncManager::OnDownl
       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
@@ -986,16 +982,21 @@ nsAutoSyncManager::OnDownloadCompleted(n
       if (NS_FAILED(rv))
         rv = HandleDownloadErrorFor(autoSyncStateObj);
     }
     return rv;
   }
       
   // download is successful, reset the retry counter of the folder
   autoSyncStateObj->ResetRetryCounter();
+  
+  nsCOMPtr<nsIMsgFolder> folder;
+  aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+  if (folder)
+    NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
       
   PRInt32 count;
   rv = autoSyncStateObj->GetPendingMessageCount(&count);
   NS_ENSURE_SUCCESS(rv, rv);
   
   nsIAutoSyncState *nextFolderToDownload = nsnull;
   if (count > 0)
   {
@@ -1018,22 +1019,24 @@ nsAutoSyncManager::OnDownloadCompleted(n
       
       // 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);
+    
+    nsCOMPtr<nsIMsgFolder> folder;
+    nsresult rv = autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+    
     mPriorityQ.RemoveObject(autoSyncStateObj);
+    if (NS_SUCCEEDED(rv))
+      NOTIFY_LISTENERS(OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::PriorityQueue, folder));
 
     //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
@@ -1054,45 +1057,57 @@ NS_IMETHODIMP nsAutoSyncManager::GetDown
   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)
+NS_IMETHODIMP nsAutoSyncManager::AddListener(nsIAutoSyncMgrListener *aListener)
+{
+  NS_ENSURE_ARG_POINTER(aListener);
+  mListeners.AppendElementUnlessExists(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::RemoveListener(nsIAutoSyncMgrListener *aListener)
 {
-  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());
-    }
-  }
+  NS_ENSURE_ARG_POINTER(aListener);
+  mListeners.RemoveElement(aListener);
+  return NS_OK;
+}
+
+/* readonly attribute unsigned long discoveryQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDiscoveryQLength(PRUint32 *aDiscoveryQLength)
+{
+  NS_ENSURE_ARG_POINTER(aDiscoveryQLength);
+  *aDiscoveryQLength = mDiscoveryQ.Count();
+  return NS_OK;
 }
 
-nsCString nsAutoSyncManager::DebugGetFolderName(nsIAutoSyncState *autoSyncStObj)
-{           
-  nsCOMPtr<nsIMsgFolder> folder;
-  if (NS_SUCCEEDED(autoSyncStObj->GetOwnerFolder(getter_AddRefs(folder))))
-    return DebugGetFolderName(folder);  
-  return nsCString();
+/* readonly attribute unsigned long uploadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetUpdateQLength(PRUint32 *aUpdateQLength)
+{
+  NS_ENSURE_ARG_POINTER(aUpdateQLength);
+  *aUpdateQLength = mUpdateQ.Count();
+  return NS_OK;
 }
 
-nsCString nsAutoSyncManager::DebugGetFolderName(nsIMsgFolder *folder)
-{           
-  nsCString folderName;
-  if (folder)
-    folder->GetURI(folderName);
+/* readonly attribute unsigned long downloadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadQLength(PRUint32 *aDownloadQLength)
+{
+  NS_ENSURE_ARG_POINTER(aDownloadQLength);
+  *aDownloadQLength = mPriorityQ.Count();
+  return NS_OK;
+}
 
-  return folderName;
+void nsAutoSyncManager::SetIdleState(IdleState st) 
+{ 
+  mIdleState = st;
 }
-#endif
+    
+nsAutoSyncManager::IdleState nsAutoSyncManager::GetIdleState() const 
+{ 
+  return mIdleState; 
+}
 
 NS_IMPL_ISUPPORTS3(nsAutoSyncManager, nsIObserver, nsIUrlListener, nsIAutoSyncManager)
--- a/mailnews/imap/src/nsAutoSyncManager.h
+++ b/mailnews/imap/src/nsAutoSyncManager.h
@@ -38,16 +38,17 @@
 #define nsAutoSyncManager_h__
 
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "nsCOMArray.h"
 #include "nsIObserver.h"
 #include "nsIUrlListener.h"
 #include "nsITimer.h"
+#include "nsTObserverArray.h"
 #include "nsIAutoSyncManager.h"
 #include "nsIAutoSyncMsgStrategy.h"
 #include "nsIAutoSyncFolderStrategy.h"
 
 class nsImapMailFolder;
 class nsIMsgDBHdr;
 class nsIIdleService;
 class nsIMsgFolder;
@@ -110,18 +111,18 @@ class nsAutoSyncManager : public nsIObse
     NS_DECL_NSIURLLISTENER
     NS_DECL_NSIAUTOSYNCMANAGER
 
     nsAutoSyncManager();
     
   private:
     ~nsAutoSyncManager();
 
-    void SetIdleState(IdleState st) { mIdleState = st; }
-    IdleState GetIdleState() const { return mIdleState; }
+    void SetIdleState(IdleState st);    
+    IdleState GetIdleState() const;
     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, 
@@ -143,22 +144,16 @@ class nsAutoSyncManager : public nsIObse
     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
@@ -168,24 +163,25 @@ class nsAutoSyncManager : public nsIObse
    
   private:
     PRUint32 mGroupSize;
     IdleState mIdleState;
     PRTime mStartupTime;
     PRInt32 mDownloadModel;
     nsCOMPtr<nsIIdleService> mIdleService;
     nsCOMPtr<nsITimer> mTimer;
+    nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener> > mListeners;
 };
 
 #endif
 
 /*
- How queues interact in general:
+How queues inter-relate:
 
-sAutoSyncState has an internal priority queue to store messages waiting to be
+nsAutoSyncState 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