Back to index

lightning-sunbird  0.9+nobinonly
nsNNTPNewsgroupList.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *   Henrik Gemal <mozilla@gemal.dk>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 /*
00041  * formerly listngst.cpp
00042  * This class should ultimately be part of a news group listing
00043  * state machine - either by inheritance or delegation.
00044  * Currently, a folder pane owns one and libnet news group listing
00045  * related messages get passed to this object.
00046  */
00047 
00048 #include "msgCore.h"    // precompiled header...
00049 #include "MailNewsTypes.h"
00050 #include "nsCOMPtr.h"
00051 #include "nsIDBFolderInfo.h"
00052 #include "nsINewsDatabase.h"
00053 #include "nsIMsgStatusFeedback.h"
00054 #include "nsCOMPtr.h"
00055 #include "nsIDOMWindowInternal.h"
00056 
00057 #include "nsXPIDLString.h"
00058 #include "nsIMsgAccountManager.h"
00059 #include "nsIMsgIncomingServer.h"
00060 #include "nsINntpIncomingServer.h"
00061 #include "nsMsgBaseCID.h"
00062 
00063 #include "nsNNTPNewsgroupList.h"
00064 
00065 #include "nsINNTPArticleList.h"
00066 #include "nsMsgKeySet.h"
00067 
00068 #include "nntpCore.h"
00069 #include "nsIStringBundle.h"
00070 
00071 #include "plstr.h"
00072 #include "prmem.h"
00073 #include "prprf.h"
00074 
00075 #include "nsCRT.h"
00076 #include "nsMsgUtils.h"
00077 
00078 #include "nsMsgDatabase.h"
00079 
00080 #include "nsIDBFolderInfo.h"
00081 
00082 #include "nsNewsUtils.h"
00083 
00084 #include "nsMsgDBCID.h"
00085 
00086 #include "nsINewsDownloadDialogArgs.h"
00087 
00088 #include "nsXPCOM.h"
00089 #include "nsISupportsPrimitives.h"
00090 #include "nsIInterfaceRequestor.h"
00091 #include "nsIInterfaceRequestorUtils.h"
00092 #include "nsIMsgWindow.h"
00093 #include "nsIDocShell.h"
00094 
00095 // update status on header download once per second
00096 #define MIN_STATUS_UPDATE_INTERVAL PR_USEC_PER_SEC
00097 
00098 
00099 nsNNTPNewsgroupList::nsNNTPNewsgroupList()
00100   : m_finishingXover(PR_FALSE),
00101     m_getOldMessages(PR_FALSE),
00102     m_promptedAlready(PR_FALSE),
00103     m_downloadAll(PR_FALSE),
00104     m_maxArticles(0),
00105     m_lastPercent(-1),
00106     m_lastProcessedNumber(0),
00107     m_firstMsgNumber(0),
00108     m_lastMsgNumber(0),
00109     m_firstMsgToDownload(0),
00110     m_lastMsgToDownload(0),
00111     m_set(nsnull)
00112 {
00113     memset(&m_knownArts, 0, sizeof(m_knownArts));
00114     m_lastStatusUpdate = LL_Zero();
00115 }
00116 
00117 nsNNTPNewsgroupList::~nsNNTPNewsgroupList()
00118 {
00119   CleanUp();
00120 }
00121 
00122 NS_IMPL_ISUPPORTS2(nsNNTPNewsgroupList, nsINNTPNewsgroupList, nsIMsgFilterHitNotify)
00123 
00124 nsresult
00125 nsNNTPNewsgroupList::Initialize(nsINntpUrl *runningURL, nsIMsgNewsFolder *newsFolder)
00126 {
00127   m_newsFolder = newsFolder;
00128   m_runningURL = runningURL;
00129   m_knownArts.set = nsMsgKeySet::Create();
00130 
00131   return NS_OK;
00132 }
00133 
00134 nsresult
00135 nsNNTPNewsgroupList::CleanUp() 
00136 {
00137   // here we make sure that there aren't missing articles in the unread set
00138   // So if an article is the unread set, and the known arts set, but isn't in the
00139   // db, then we should mark it read in the unread set.
00140   if (m_newsDB)
00141   {
00142     if (m_knownArts.set)
00143     {
00144       nsCOMPtr <nsIDBFolderInfo> folderInfo;
00145       m_newsDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
00146       PRInt32 firstKnown = m_knownArts.set->GetFirstMember();
00147       PRInt32 lastKnown =  m_knownArts.set->GetLastMember();
00148       if (folderInfo)
00149       {
00150         PRUint32 lastMissingCheck;
00151         folderInfo->GetUint32Property("lastMissingCheck", 0, &lastMissingCheck);
00152         if (lastMissingCheck)
00153           firstKnown = lastMissingCheck + 1;
00154       }
00155       PRBool done = firstKnown > lastKnown; // just in case...
00156       PRBool foundMissingArticle = PR_FALSE;
00157       while (!done)
00158       {
00159         PRInt32 firstUnreadStart, firstUnreadEnd;
00160         m_set->FirstMissingRange(firstKnown, lastKnown, &firstUnreadStart, &firstUnreadEnd);
00161         if (firstUnreadStart)
00162         {
00163           while (firstUnreadStart <= firstUnreadEnd)
00164           {
00165             PRBool containsKey;
00166             m_newsDB->ContainsKey(firstUnreadStart, &containsKey);
00167             if (!containsKey)
00168             {
00169               m_set->Add(firstUnreadStart);
00170               foundMissingArticle = PR_TRUE;
00171             }
00172             firstUnreadStart++;
00173           }
00174           firstKnown = firstUnreadStart;
00175         }
00176         else
00177           break;
00178 
00179       }
00180       if (folderInfo)
00181         folderInfo->SetUint32Property("lastMissingCheck", lastKnown);
00182       
00183       if (foundMissingArticle)
00184       {
00185         nsresult rv;
00186         nsCOMPtr<nsINewsDatabase> db(do_QueryInterface(m_newsDB, &rv));
00187         NS_ENSURE_SUCCESS(rv,rv);
00188         db->SetReadSet(m_set);
00189       }
00190     }
00191     m_newsDB->Commit(nsMsgDBCommitType::kSessionCommit);
00192     m_newsDB->Close(PR_TRUE);
00193     m_newsDB = nsnull;
00194   }
00195 
00196   if (m_knownArts.set)
00197   {
00198     delete m_knownArts.set;
00199     m_knownArts.set = nsnull;
00200   }
00201   if (m_newsFolder)
00202     m_newsFolder->NotifyFinishedDownloadinghdrs();
00203   
00204   m_newsFolder = nsnull;
00205   m_runningURL = nsnull;
00206     
00207   return NS_OK;
00208 }
00209 
00210 #ifdef HAVE_CHANGELISTENER
00211 void nsNNTPNewsgroupList::OnAnnouncerGoingAway (ChangeAnnouncer *instigator)
00212 {
00213 }
00214 #endif
00215 
00216 static nsresult 
00217 openWindow(nsIMsgWindow *aMsgWindow, const char *chromeURL,
00218            nsINewsDownloadDialogArgs *param) 
00219 {
00220     nsresult rv;
00221 
00222     NS_ENSURE_ARG_POINTER(aMsgWindow);
00223 
00224        nsCOMPtr<nsIDocShell> docShell;
00225        rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
00226     if (NS_FAILED(rv))
00227         return rv;
00228 
00229        nsCOMPtr<nsIDOMWindowInternal> parentWindow(do_GetInterface(docShell));
00230        NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE);
00231 
00232     nsCOMPtr<nsISupportsInterfacePointer> ifptr =
00233         do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
00234     NS_ENSURE_SUCCESS(rv, rv);
00235 
00236     ifptr->SetData(param);
00237     ifptr->SetDataIID(&NS_GET_IID(nsINewsDownloadDialogArgs));
00238 
00239     nsCOMPtr<nsIDOMWindow> dialogWindow;
00240     rv = parentWindow->OpenDialog(NS_ConvertASCIItoUCS2(chromeURL),
00241                                   NS_LITERAL_STRING("_blank"),
00242                                   NS_LITERAL_STRING("centerscreen,chrome,modal,titlebar"),
00243                                   ifptr, getter_AddRefs(dialogWindow));
00244 
00245     return rv;
00246 }       
00247 
00248 nsresult
00249 nsNNTPNewsgroupList::GetRangeOfArtsToDownload(nsIMsgWindow *aMsgWindow,
00250                                               PRInt32 first_possible,
00251                                               PRInt32 last_possible,
00252                                               PRInt32 maxextra,
00253                                               PRInt32 *first,
00254                                               PRInt32 *last,
00255                                               PRInt32 *status)
00256 {
00257        nsresult rv = NS_OK;
00258 
00259     NS_ENSURE_ARG_POINTER(first);
00260     NS_ENSURE_ARG_POINTER(last);
00261     NS_ENSURE_ARG_POINTER(status);
00262     
00263        *first = 0;
00264        *last = 0;
00265 
00266     nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
00267     NS_ENSURE_SUCCESS(rv,rv);
00268 
00269     m_msgWindow = aMsgWindow;
00270 
00271        if (!m_newsDB) {
00272       rv = folder->GetMsgDatabase(nsnull /* use m_msgWindow? */, getter_AddRefs(m_newsDB));
00273        }
00274        
00275     nsCOMPtr<nsINewsDatabase> db(do_QueryInterface(m_newsDB, &rv));
00276     NS_ENSURE_SUCCESS(rv,rv);
00277             
00278        rv = db->GetReadSet(&m_set);
00279     if (NS_FAILED(rv) || !m_set) {
00280        return rv;
00281     }
00282             
00283        m_set->SetLastMember(last_possible);      // make sure highwater mark is valid.
00284 
00285     nsCOMPtr <nsIDBFolderInfo> newsGroupInfo;
00286        rv = m_newsDB->GetDBFolderInfo(getter_AddRefs(newsGroupInfo));
00287        if (NS_SUCCEEDED(rv) && newsGroupInfo) {
00288       nsXPIDLCString knownArtsString;
00289       nsMsgKey mark;
00290       newsGroupInfo->GetKnownArtsSet(getter_Copies(knownArtsString));
00291       
00292       rv = newsGroupInfo->GetHighWater(&mark);
00293       NS_ENSURE_SUCCESS(rv,rv);
00294 
00295       if (last_possible < ((PRInt32)mark))
00296         newsGroupInfo->SetHighWater(last_possible, PR_TRUE);
00297       if (m_knownArts.set) {
00298         delete m_knownArts.set;
00299       }
00300       m_knownArts.set = nsMsgKeySet::Create(knownArtsString.get());
00301     }
00302     else
00303     {  
00304       if (m_knownArts.set) {
00305         delete m_knownArts.set;
00306       }
00307       m_knownArts.set = nsMsgKeySet::Create();
00308       nsMsgKey low, high;
00309       rv = m_newsDB->GetLowWaterArticleNum(&low);
00310       NS_ENSURE_SUCCESS(rv,rv);
00311       rv = m_newsDB->GetHighWaterArticleNum(&high);
00312       NS_ENSURE_SUCCESS(rv,rv);
00313       
00314       m_knownArts.set->AddRange(low,high);
00315     }
00316     
00317     if (m_knownArts.set->IsMember(last_possible)) {
00318       nsXPIDLString statusString;
00319       nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
00320       NS_ENSURE_SUCCESS(rv, rv);
00321       
00322       nsCOMPtr<nsIStringBundle> bundle;
00323       rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
00324       NS_ENSURE_SUCCESS(rv, rv);
00325       
00326       rv = bundle->GetStringFromName(NS_LITERAL_STRING("noNewMessages").get(), getter_Copies(statusString));
00327       NS_ENSURE_SUCCESS(rv, rv);
00328 
00329       SetProgressStatus(statusString);
00330     }
00331     
00332     if (maxextra <= 0 || last_possible < first_possible || last_possible < 1) 
00333     {
00334       *status=0;
00335       return NS_OK;
00336     }
00337 
00338     m_knownArts.first_possible = first_possible;
00339     m_knownArts.last_possible = last_possible;
00340 
00341     nsCOMPtr <nsIMsgIncomingServer> server;
00342     rv = folder->GetServer(getter_AddRefs(server));
00343     NS_ENSURE_SUCCESS(rv,rv);
00344               
00345     nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(server, &rv);
00346     NS_ENSURE_SUCCESS(rv,rv);
00347 
00348        /* Determine if we only want to get just new articles or more messages.
00349        If there are new articles at the end we haven't seen, we always want to get those first.  
00350        Otherwise, we get the newest articles we haven't gotten, if we're getting more. 
00351        My thought for now is that opening a newsgroup should only try to get new articles.
00352        Selecting "More Messages" will first try to get unseen messages, then old messages. */
00353 
00354        if (m_getOldMessages || !m_knownArts.set->IsMember(last_possible)) 
00355        {
00356               PRBool notifyMaxExceededOn = PR_TRUE;
00357               rv = nntpServer->GetNotifyOn(&notifyMaxExceededOn);
00358               if (NS_FAILED(rv)) notifyMaxExceededOn = PR_TRUE;
00359 
00360               // if the preference to notify when downloading more than x headers is not on,
00361               // and we're downloading new headers, set maxextra to a very large number.
00362               if (!m_getOldMessages && !notifyMaxExceededOn)
00363                      maxextra = 0x7FFFFFFFL;
00364         int result =
00365             m_knownArts.set->LastMissingRange(first_possible, last_possible,
00366                                               first, last);
00367               if (result < 0) {
00368             *status=result;
00369                      return NS_ERROR_NOT_INITIALIZED;
00370         }
00371               if (*first > 0 && *last - *first >= maxextra) 
00372               {
00373                      if (!m_getOldMessages && !m_promptedAlready && notifyMaxExceededOn)
00374                      {
00375                             m_downloadAll = PR_FALSE;   
00376                      
00377                 nsCOMPtr<nsINewsDownloadDialogArgs> args = do_CreateInstance("@mozilla.org/messenger/newsdownloaddialogargs;1", &rv);
00378                 if (NS_FAILED(rv)) return rv;
00379                 NS_ENSURE_SUCCESS(rv,rv);
00380 
00381                 rv = args->SetArticleCount(*last - *first + 1);
00382                 NS_ENSURE_SUCCESS(rv,rv);
00383         
00384                 nsXPIDLString groupName;
00385                 rv = m_newsFolder->GetUnicodeName(groupName);
00386                 NS_ENSURE_SUCCESS(rv,rv);
00387 
00388                 rv = args->SetGroupName(groupName);
00389                 NS_ENSURE_SUCCESS(rv,rv);
00390 
00391                 // get the server key
00392                 nsXPIDLCString serverKey;
00393                 rv = server->GetKey(getter_Copies(serverKey));
00394                 NS_ENSURE_SUCCESS(rv,rv);
00395 
00396                 rv = args->SetServerKey((const char *)serverKey);
00397                 NS_ENSURE_SUCCESS(rv,rv);
00398 
00399                 // we many not have a msgWindow if we are running an autosubscribe url from the browser
00400                 // and there isn't a 3 pane open.
00401                 //
00402                 // if we don't have one, bad things will happen when we fail to open up the "download headers dialog"
00403                 // (we will subscribe to the newsgroup, but it will appear like there are no messages!)
00404                 //
00405                 // for now, act like the "download headers dialog" came up, and the user hit cancel.  (very safe)
00406                 //
00407                 // TODO, figure out why we aren't opening and using a 3 pane when the autosubscribe url is run.
00408                 // perhaps we can find an available 3 pane, and use it.
00409 
00410                 PRBool download = PR_FALSE;  
00411 
00412                 if (aMsgWindow) {
00413                               rv = openWindow(aMsgWindow, DOWNLOAD_HEADERS_URL, args);
00414                   NS_ENSURE_SUCCESS(rv,rv);
00415 
00416                   rv = args->GetHitOK(&download);
00417                   NS_ENSURE_SUCCESS(rv,rv);
00418                 }
00419 
00420                             if (download) {
00421                     rv = args->GetDownloadAll(&m_downloadAll);
00422                     NS_ENSURE_SUCCESS(rv,rv);
00423 
00424                                    m_maxArticles = 0;
00425 
00426                     rv = nntpServer->GetMaxArticles(&m_maxArticles); 
00427                     NS_ENSURE_SUCCESS(rv,rv);
00428                     
00429                                    maxextra = m_maxArticles;
00430                                    if (!m_downloadAll)
00431                                    {
00432                                           PRBool markOldRead = PR_FALSE;
00433 
00434                                           rv = nntpServer->GetMarkOldRead(&markOldRead);
00435                         if (NS_FAILED(rv)) markOldRead = PR_FALSE;
00436 
00437                                           if (markOldRead && m_set)
00438                                                  m_set->AddRange(*first, *last - maxextra); 
00439                                           *first = *last - maxextra + 1;
00440                                    }
00441                             }
00442                             else
00443                                    *first = *last = 0;
00444                             m_promptedAlready = PR_TRUE;
00445                      }
00446                      else if (m_promptedAlready && !m_downloadAll)
00447                             *first = *last - m_maxArticles + 1;
00448                      else if (!m_downloadAll)
00449                             *first = *last - maxextra + 1;
00450               }
00451        }
00452 
00453        m_firstMsgToDownload = *first;
00454        m_lastMsgToDownload = *last;
00455     *status=0;
00456        return NS_OK;
00457 }
00458 
00459 nsresult
00460 nsNNTPNewsgroupList::AddToKnownArticles(PRInt32 first, PRInt32 last)
00461 {
00462   int         status;
00463   
00464   if (!m_knownArts.set) 
00465   {
00466     m_knownArts.set = nsMsgKeySet::Create();
00467     
00468     if (!m_knownArts.set) {
00469       return NS_ERROR_OUT_OF_MEMORY;
00470     }
00471     
00472   }
00473   
00474   status = m_knownArts.set->AddRange(first, last);
00475   
00476   if (m_newsDB) {
00477     nsresult rv = NS_OK;
00478     nsCOMPtr <nsIDBFolderInfo> newsGroupInfo;
00479     rv = m_newsDB->GetDBFolderInfo(getter_AddRefs(newsGroupInfo));
00480     if (NS_SUCCEEDED(rv) && newsGroupInfo) {
00481       nsXPIDLCString output;
00482       status = m_knownArts.set->Output(getter_Copies(output));
00483       if (output) {
00484         newsGroupInfo->SetKnownArtsSet(output);
00485       }
00486     }
00487   }
00488   
00489   return status;
00490 }
00491 
00492 
00493 
00494 
00495 nsresult
00496 nsNNTPNewsgroupList::InitXOVER(PRInt32 first_msg, PRInt32 last_msg)
00497 {
00498        int           status = 0;
00499 
00500        // Tell the FE to show the GetNewMessages progress dialog
00501 #ifdef HAVE_PANES
00502        FE_PaneChanged (m_pane, PR_FALSE, MSG_PanePastPasswordCheck, 0);
00503 #endif
00504        /* Consistency checks, not that I know what to do if it fails (it will
00505         probably handle it OK...) */
00506        NS_ASSERTION(first_msg <= last_msg, "first > last");
00507 
00508        /* If any XOVER lines from the last time failed to come in, mark those
00509           messages as read. */
00510        if (m_lastProcessedNumber < m_lastMsgNumber) 
00511        {
00512               m_set->AddRange(m_lastProcessedNumber + 1, m_lastMsgNumber);
00513        }
00514        m_firstMsgNumber = first_msg;
00515        m_lastMsgNumber = last_msg;
00516        m_lastProcessedNumber = first_msg > 1 ? first_msg - 1 : 1;
00517 
00518        return status;
00519 }
00520 
00521 // from RFC 822, don't translate 
00522 #define FROM_HEADER "From: "
00523 #define SUBECT_HEADER "Subject: "
00524 #define DATE_HEADER "Date: "
00525 
00526 nsresult
00527 nsNNTPNewsgroupList::ParseLine(char *line, PRUint32 * message_number) 
00528 {
00529   nsresult rv = NS_OK;
00530   nsCOMPtr <nsIMsgDBHdr> newMsgHdr;
00531   char *dateStr = nsnull;  // keep track of date str, for filters
00532   char *authorStr = nsnull; // keep track of author str, for filters
00533 
00534   if (!line || !message_number) {
00535     return NS_ERROR_NULL_POINTER;
00536   }
00537   
00538   char *next = line;
00539   
00540 #define GET_TOKEN()                                                   \
00541   line = next;                                                               \
00542   next = (line ? PL_strchr (line, '\t') : 0);    \
00543   if (next) *next++ = 0
00544   
00545   GET_TOKEN (); /* message number */
00546   *message_number = atol(line);
00547   
00548   if (atol(line) == 0)                                  /* bogus xover data */
00549     return NS_ERROR_UNEXPECTED;
00550   
00551   m_newsDB->CreateNewHdr(*message_number, getter_AddRefs(newMsgHdr));      
00552   
00553   NS_ASSERTION(newMsgHdr, "CreateNewHdr didn't fail, but it returned a null newMsgHdr");
00554   if (!newMsgHdr) 
00555     return NS_ERROR_NULL_POINTER;
00556   
00557   GET_TOKEN (); /* subject */
00558   if (line) {
00559     const char *subject = line;  /* #### const evilness */
00560     PRUint32 subjectLen = strlen(line);
00561     
00562     PRUint32 flags = 0;
00563     // ### should call IsHeaderRead here...
00564     /* strip "Re: " */
00565     nsXPIDLCString modifiedSubject;
00566     if (NS_MsgStripRE(&subject, &subjectLen, getter_Copies(modifiedSubject)))
00567       (void) newMsgHdr->OrFlags(MSG_FLAG_HAS_RE, &flags); // this will make sure read flags agree with newsrc
00568     
00569     if (! (flags & MSG_FLAG_READ))
00570       rv = newMsgHdr->OrFlags(MSG_FLAG_NEW, &flags);
00571     
00572     rv = newMsgHdr->SetSubject(modifiedSubject.IsEmpty() ? subject : modifiedSubject.get());
00573     if (NS_FAILED(rv)) 
00574       return rv;
00575   }
00576   
00577   GET_TOKEN ();                                                                            /* author */
00578   if (line) {
00579     authorStr = line;
00580     rv = newMsgHdr->SetAuthor(line);
00581     if (NS_FAILED(rv)) 
00582       return rv;
00583   }
00584   
00585   GET_TOKEN ();      
00586   if (line) {
00587     dateStr = line;
00588     PRTime date;
00589     PRStatus status = PR_ParseTimeString (line, PR_FALSE, &date);
00590     if (PR_SUCCESS == status) {      
00591       rv = newMsgHdr->SetDate(date);                                  /* date */
00592       if (NS_FAILED(rv)) 
00593         return rv;
00594     }
00595   }
00596   
00597   GET_TOKEN ();                                                                            /* message id */
00598   if (line) {
00599     char *strippedId = line;
00600     
00601     if (strippedId[0] == '<')
00602       strippedId++;
00603     
00604     char * lastChar = strippedId + PL_strlen(strippedId) -1;
00605     
00606     if (*lastChar == '>')
00607       *lastChar = '\0';
00608     
00609     rv = newMsgHdr->SetMessageId(strippedId);
00610     if (NS_FAILED(rv)) 
00611       return rv;           
00612   }
00613   
00614   GET_TOKEN ();                                                                            /* references */
00615   if (line) {
00616     rv = newMsgHdr->SetReferences(line);
00617     if (NS_FAILED(rv)) 
00618       return rv;           
00619   }
00620   
00621   GET_TOKEN ();                                                                            /* bytes */
00622   if (line) {
00623     PRUint32 msgSize = 0;
00624     msgSize = (line) ? atol (line) : 0;
00625     
00626     rv = newMsgHdr->SetMessageSize(msgSize);
00627     if (NS_FAILED(rv)) return rv;           
00628   }
00629   
00630   GET_TOKEN ();                                                                            /* lines */
00631   if (line) {
00632     PRUint32 numLines = 0;
00633     numLines = line ? atol (line) : 0;
00634     rv = newMsgHdr->SetLineCount(numLines);
00635     if (NS_FAILED(rv)) return rv;           
00636   }
00637   
00638   GET_TOKEN (); /* xref */
00639   
00640   // apply filters
00641   // XXX TODO
00642   // do spam classification for news
00643 
00644   nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
00645   NS_ENSURE_SUCCESS(rv,rv);
00646   
00647   if (!m_filterList) 
00648   {
00649     rv = folder->GetFilterList(m_msgWindow, getter_AddRefs(m_filterList));
00650     NS_ENSURE_SUCCESS(rv,rv);
00651   }
00652   
00653   if (!m_serverFilterList)
00654   {
00655     nsCOMPtr<nsIMsgIncomingServer> server;
00656     rv = folder->GetServer(getter_AddRefs(server));
00657     NS_ENSURE_SUCCESS(rv,rv);
00658 
00659     rv = server->GetFilterList(m_msgWindow, getter_AddRefs(m_serverFilterList));
00660     NS_ENSURE_SUCCESS(rv,rv);
00661   }
00662 
00663   // flag for kill
00664   // if the action is Delete, and we get a hit (see ApplyFilterHit())
00665   // we set this to PR_FALSE.  if false, we won't add it to the db.
00666   m_addHdrToDB = PR_TRUE;
00667 
00668   PRUint32 filterCount = 0;
00669   if (m_filterList) {
00670        rv = m_filterList->GetFilterCount(&filterCount);
00671     NS_ENSURE_SUCCESS(rv,rv);
00672   }
00673 
00674   PRUint32 serverFilterCount = 0;
00675   if (m_serverFilterList) {
00676        rv = m_serverFilterList->GetFilterCount(&serverFilterCount);
00677     NS_ENSURE_SUCCESS(rv,rv);
00678   }
00679 
00680   // only do this if we have filters
00681   if (filterCount || serverFilterCount) 
00682   {
00683     // build up a "headers" for filter code
00684     nsXPIDLCString subject;
00685     rv = newMsgHdr->GetSubject(getter_Copies(subject));
00686     NS_ENSURE_SUCCESS(rv,rv);
00687     
00688     PRUint32 headersSize = 0;
00689  
00690     // +1 to separate headers with a null byte 
00691     if (authorStr)
00692       headersSize += strlen(FROM_HEADER) + strlen(authorStr) + 1;
00693     
00694     if (!(subject.IsEmpty()))
00695       headersSize += strlen(SUBECT_HEADER) + subject.Length() + 1;
00696 
00697     if (dateStr)
00698      headersSize += strlen(DATE_HEADER) + strlen(dateStr) + 1;
00699     
00700     if (headersSize) {
00701       char *headers = (char *)PR_Malloc(headersSize);
00702       char *headerPos = headers;
00703       if (!headers)
00704         return NS_ERROR_OUT_OF_MEMORY;
00705     
00706       if (authorStr) {
00707         PL_strcpy(headerPos, FROM_HEADER);
00708         headerPos += strlen(FROM_HEADER);
00709     
00710         PL_strcpy(headerPos, authorStr);
00711         headerPos += strlen(authorStr);
00712     
00713         *headerPos = '\0';
00714         headerPos++;
00715       }
00716 
00717       if (!(subject.IsEmpty())) {
00718         PL_strcpy(headerPos, SUBECT_HEADER);
00719         headerPos += strlen(SUBECT_HEADER);
00720         
00721         PL_strcpy(headerPos, subject.get());
00722         headerPos += subject.Length();
00723         
00724         *headerPos = '\0';
00725         headerPos++;
00726       }
00727 
00728       if (dateStr) {        
00729         PL_strcpy(headerPos, DATE_HEADER);
00730         headerPos += strlen(DATE_HEADER);
00731         
00732         PL_strcpy(headerPos, dateStr);
00733         headerPos += strlen(dateStr);
00734         
00735         *headerPos = '\0';
00736         headerPos++;
00737       }
00738 
00739       // on a filter hit (see ApplyFilterHit()), we'll be modifying the header 
00740       // so keep track of the header
00741       m_newMsgHdr = newMsgHdr;
00742       
00743       // the per-newsgroup filters should probably go first. It doesn't matter
00744       // right now since nothing stops filter execution for newsgroups, but if something
00745       // does, like adding a "stop execution" action, then users should be able to
00746       // override the global filters in the per-newsgroup filters.
00747       if (filterCount)
00748       {
00749         rv = m_filterList->ApplyFiltersToHdr(nsMsgFilterType::NewsRule, newMsgHdr, folder, m_newsDB, 
00750           headers, headersSize, this, m_msgWindow, nsnull);
00751       }
00752       if (serverFilterCount)
00753       {
00754         rv = m_serverFilterList->ApplyFiltersToHdr(nsMsgFilterType::NewsRule, newMsgHdr, folder, m_newsDB, 
00755           headers, headersSize, this, m_msgWindow, nsnull);
00756       }
00757 
00758       PR_Free ((void*) headers);
00759       NS_ENSURE_SUCCESS(rv,rv);
00760     }
00761   }
00762   
00763   // if we deleted it, don't add it
00764   if (m_addHdrToDB) {
00765     rv = m_newsDB->AddNewHdrToDB(newMsgHdr, PR_TRUE);
00766     NS_ENSURE_SUCCESS(rv,rv);
00767   }
00768   
00769   return NS_OK;
00770 }
00771 
00772 NS_IMETHODIMP nsNNTPNewsgroupList::ApplyFilterHit(nsIMsgFilter *aFilter, nsIMsgWindow *aMsgWindow, PRBool *aApplyMore)
00773 {
00774   NS_ENSURE_ARG_POINTER(aFilter);
00775   NS_ENSURE_ARG_POINTER(aApplyMore);
00776   NS_ENSURE_TRUE(m_newMsgHdr, NS_ERROR_UNEXPECTED);
00777   NS_ENSURE_TRUE(m_newsDB, NS_ERROR_UNEXPECTED);
00778    
00779   // you can't move news messages, so applyMore is always true
00780   *aApplyMore = PR_TRUE;
00781   
00782   nsCOMPtr<nsISupportsArray> filterActionList;
00783   nsresult rv = NS_NewISupportsArray(getter_AddRefs(filterActionList));
00784   NS_ENSURE_SUCCESS(rv, rv);
00785   rv = aFilter->GetSortedActionList(filterActionList);
00786   NS_ENSURE_SUCCESS(rv, rv);
00787 
00788   PRUint32 numActions;
00789   rv = filterActionList->Count(&numActions);
00790   NS_ENSURE_SUCCESS(rv, rv);
00791 
00792   PRBool loggingEnabled = PR_FALSE;
00793   nsCOMPtr<nsIMsgFilterList> currentFilterList;
00794   rv = aFilter->GetFilterList(getter_AddRefs(currentFilterList));
00795   if (NS_SUCCEEDED(rv) && currentFilterList && numActions)
00796     currentFilterList->GetLoggingEnabled(&loggingEnabled);
00797 
00798   for (PRUint32 actionIndex = 0; actionIndex < numActions; actionIndex++)
00799   {
00800     nsCOMPtr<nsIMsgRuleAction> filterAction;
00801     filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction), getter_AddRefs(filterAction));
00802     if (!filterAction)
00803       continue;
00804     
00805     nsMsgRuleActionType actionType;
00806     if (NS_SUCCEEDED(filterAction->GetType(&actionType)))
00807     {  
00808       switch (actionType)
00809       {
00810       case nsMsgFilterAction::Delete:
00811         m_addHdrToDB = PR_FALSE;
00812         break;
00813       case nsMsgFilterAction::MarkRead:
00814         m_newsDB->MarkHdrRead(m_newMsgHdr, PR_TRUE, nsnull);
00815         break;
00816       case nsMsgFilterAction::KillThread:
00817         {
00818           PRUint32 newFlags;
00819           // The db will check for this flag when a hdr gets added to the db, and set the flag appropriately on the thread object
00820           m_newMsgHdr->OrFlags(MSG_FLAG_IGNORED, &newFlags);
00821         }
00822         break;
00823       case nsMsgFilterAction::WatchThread:
00824         {
00825           PRUint32 newFlags;
00826           m_newMsgHdr->OrFlags(MSG_FLAG_WATCHED, &newFlags);
00827         }
00828         break;
00829       case nsMsgFilterAction::MarkFlagged:
00830         m_newMsgHdr->MarkFlagged(PR_TRUE);
00831         break;
00832       case nsMsgFilterAction::ChangePriority:
00833         {
00834           nsMsgPriorityValue filterPriority;
00835           filterAction->GetPriority(&filterPriority);
00836           m_newMsgHdr->SetPriority(filterPriority);
00837         }
00838         break;
00839       case nsMsgFilterAction::AddTag:
00840       {
00841         nsXPIDLCString keyword;
00842         filterAction->GetStrValue(getter_Copies(keyword));
00843         nsCOMPtr<nsISupportsArray> messageArray;
00844         NS_NewISupportsArray(getter_AddRefs(messageArray));
00845         messageArray->AppendElement(m_newMsgHdr);
00846         nsCOMPtr <nsIMsgFolder> folder = do_QueryInterface(m_newsFolder, &rv);
00847         if (folder)
00848           folder->AddKeywordsToMessages(messageArray, keyword.get());
00849         break;
00850       }
00851       case nsMsgFilterAction::Label:
00852         {
00853           nsMsgLabelValue filterLabel;
00854           filterAction->GetLabel(&filterLabel);
00855           nsMsgKey msgKey;
00856           m_newMsgHdr->GetMessageKey(&msgKey);
00857           m_newsDB->SetLabel(msgKey, filterLabel);
00858         }
00859         break;
00860       default:
00861         NS_ASSERTION(0, "unexpected action");
00862         break;
00863       }
00864       
00865       if (loggingEnabled)
00866         (void) aFilter->LogRuleHit(filterAction, m_newMsgHdr);
00867     }
00868   }
00869   return NS_OK;
00870 }
00871 
00872 nsresult
00873 nsNNTPNewsgroupList::ProcessXOVERLINE(const char *line, PRUint32 *status)
00874 {
00875   PRUint32 message_number=0;
00876   //  PRInt32 lines;
00877   PRBool read_p = PR_FALSE;
00878   nsresult rv = NS_OK;
00879 
00880   NS_ASSERTION(line, "null ptr");
00881   if (!line)
00882     return NS_ERROR_NULL_POINTER;
00883 
00884   if (m_newsDB)
00885   {
00886     char *xoverline = PL_strdup(line);
00887     if (!xoverline) 
00888       return NS_ERROR_OUT_OF_MEMORY;
00889     rv = ParseLine(xoverline, &message_number);
00890     PL_strfree(xoverline);
00891     xoverline = nsnull;
00892     if (NS_FAILED(rv))
00893       return rv;
00894   }
00895   else
00896     return NS_ERROR_NOT_INITIALIZED;
00897 
00898   NS_ASSERTION(message_number > m_lastProcessedNumber ||
00899                message_number == 1, "bad message_number");
00900   if (m_set && message_number > m_lastProcessedNumber + 1)
00901   {
00902   /* There are some articles that XOVER skipped; they must no longer
00903      exist.  Mark them as read in the newsrc, so we don't include them
00904      next time in our estimated number of unread messages. */
00905     if (m_set->AddRange(m_lastProcessedNumber + 1, message_number - 1)) 
00906     {
00907     /* This isn't really an important enough change to warrant causing
00908        the newsrc file to be saved; we haven't gathered any information
00909        that won't also be gathered for free next time.  */
00910     }
00911   }
00912 
00913   m_lastProcessedNumber = message_number;
00914   if (m_knownArts.set) 
00915   {
00916     int result = m_knownArts.set->Add(message_number);
00917     if (result < 0) {
00918       if (status) 
00919         *status = result;
00920       return NS_ERROR_NOT_INITIALIZED;
00921     }
00922   }
00923 
00924   if (message_number > m_lastMsgNumber)
00925     m_lastMsgNumber = message_number;
00926   else if (message_number < m_firstMsgNumber)
00927     m_firstMsgNumber = message_number;
00928 
00929   if (m_set) {
00930     read_p = m_set->IsMember(message_number);
00931   }
00932 
00933   /* Update the progress meter with a percentage of articles retrieved */
00934   if (m_lastMsgNumber > m_firstMsgNumber)
00935   {
00936     PRInt32 totalToDownload = m_lastMsgToDownload - m_firstMsgToDownload + 1;
00937     PRInt32 lastIndex = m_lastProcessedNumber - m_firstMsgNumber + 1;
00938     PRInt32 numDownloaded = lastIndex;
00939     PRInt32 totIndex = m_lastMsgNumber - m_firstMsgNumber + 1;
00940 
00941     PRInt32  percent = (totIndex) ? (PRInt32)(100.0 * (double)numDownloaded / (double)totalToDownload) : 0;
00942 
00943     PRTime elapsedTime;
00944 
00945     LL_SUB(elapsedTime, PR_Now(), m_lastStatusUpdate);
00946 
00947     if (LL_CMP(elapsedTime, >, MIN_STATUS_UPDATE_INTERVAL) || 
00948         lastIndex == totIndex)
00949     {
00950       nsAutoString numDownloadedStr;
00951       numDownloadedStr.AppendInt(numDownloaded);
00952 
00953       nsAutoString totalToDownloadStr;
00954       totalToDownloadStr.AppendInt(totalToDownload);
00955 
00956       nsXPIDLString statusString;
00957       nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
00958       NS_ENSURE_SUCCESS(rv, rv);
00959 
00960       nsCOMPtr<nsIStringBundle> bundle;
00961       rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
00962       NS_ENSURE_SUCCESS(rv, rv);
00963 
00964       const PRUnichar *formatStrings[2] = { numDownloadedStr.get(), totalToDownloadStr.get() };
00965       rv = bundle->FormatStringFromName(NS_LITERAL_STRING("downloadingHeaders").get(), formatStrings, 2, getter_Copies(statusString));
00966       NS_ENSURE_SUCCESS(rv, rv);
00967       
00968 #ifdef DEBUG_NEWS
00969       PRInt32 elapsed;
00970       LL_L2I(elapsed, elapsedTime);
00971       printf("usecs elapsed since last update: %d\n", elapsed);
00972 #endif
00973 
00974       SetProgressStatus(statusString);
00975       m_lastStatusUpdate = PR_Now();
00976 
00977       // only update the progress meter if it has changed
00978       if (percent != m_lastPercent) {
00979         SetProgressBarPercent(percent);
00980         m_lastPercent = percent;
00981       }
00982     }
00983   }
00984   return NS_OK;
00985 }
00986 
00987 nsresult
00988 nsNNTPNewsgroupList::ResetXOVER()
00989 {
00990   m_lastMsgNumber = m_firstMsgNumber;
00991   m_lastProcessedNumber = m_lastMsgNumber;
00992   return 0;
00993 }
00994 
00995 /* When we don't have XOVER, but use HEAD, this is called instead.
00996    It reads lines until it has a whole header block, then parses the
00997    headers; then takes selected headers and creates an XOVER line
00998    from them.  This is more for simplicity and code sharing than
00999    anything else; it means we end up parsing some things twice.
01000    But if we don't have XOVER, things are going to be so horribly
01001    slow anyway that this just doesn't matter.
01002  */
01003 
01004 nsresult
01005 nsNNTPNewsgroupList::ProcessNonXOVER (const char * /*line*/)
01006 {
01007   // ### dmb write me
01008   return NS_ERROR_NOT_IMPLEMENTED;
01009 }
01010 
01011 nsresult
01012 nsNNTPNewsgroupList::FinishXOVERLINE(int status, int *newstatus)
01013 {
01014   nsresult rv;
01015   struct MSG_NewsKnown* k;
01016 
01017   /* If any XOVER lines from the last time failed to come in, mark those
01018      messages as read. */
01019 
01020   if (status >= 0 && m_lastProcessedNumber < m_lastMsgNumber) {
01021     m_set->AddRange(m_lastProcessedNumber + 1, m_lastMsgNumber);
01022   }
01023 
01024   if (m_lastProcessedNumber)
01025     AddToKnownArticles(m_firstMsgNumber, m_lastProcessedNumber);
01026 
01027   k = &m_knownArts;
01028 
01029   if (k && k->set) 
01030   {
01031     PRInt32 n = k->set->FirstNonMember();
01032     if (n < k->first_possible || n > k->last_possible) 
01033     {
01034       /* We know we've gotten all there is to know.  
01035          Take advantage of that to update our counts... */
01036       // ### dmb
01037     }
01038   }
01039 
01040   if (!m_finishingXover)
01041   {
01042     // turn on m_finishingXover - this is a horrible hack to avoid recursive 
01043     // calls which happen when the fe selects a message as a result of getting EndingUpdate,
01044     // which interrupts this url right before it was going to finish and causes FinishXOver
01045     // to get called again.
01046     m_finishingXover = PR_TRUE;
01047 
01048     // XXX is this correct?
01049     m_runningURL = nsnull;
01050 
01051     if (m_lastMsgNumber > 0) {
01052       nsAutoString firstStr;
01053       firstStr.AppendInt(m_lastProcessedNumber - m_firstMsgNumber + 1);
01054 
01055       nsAutoString lastStr;
01056       lastStr.AppendInt(m_lastMsgNumber - m_firstMsgNumber + 1);
01057 
01058       nsXPIDLString statusString;
01059       nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
01060       NS_ENSURE_SUCCESS(rv, rv);
01061 
01062       nsCOMPtr<nsIStringBundle> bundle;
01063       rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
01064       NS_ENSURE_SUCCESS(rv, rv);
01065 
01066       const PRUnichar *formatStrings[2] = { firstStr.get(), lastStr.get() };
01067       rv = bundle->FormatStringFromName(NS_LITERAL_STRING("downloadingArticles").get(), formatStrings, 2, getter_Copies(statusString));
01068       NS_ENSURE_SUCCESS(rv, rv);
01069 
01070       SetProgressStatus(statusString);
01071     }
01072   }
01073 
01074   if (newstatus) 
01075     *newstatus=0;
01076 
01077   return NS_OK;
01078 }
01079 
01080 nsresult
01081 nsNNTPNewsgroupList::ClearXOVERState()
01082 {
01083     return NS_OK;
01084 }
01085 
01086 void
01087 nsNNTPNewsgroupList::SetProgressBarPercent(PRInt32 percent)
01088 {
01089   if (!m_runningURL) 
01090     return;
01091 
01092   nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
01093   if (mailnewsUrl) {
01094     nsCOMPtr <nsIMsgStatusFeedback> feedback;
01095     mailnewsUrl->GetStatusFeedback(getter_AddRefs(feedback));
01096 
01097     if (feedback) {
01098       feedback->ShowProgress(percent);
01099     }
01100   }
01101 } 
01102 
01103 void
01104 nsNNTPNewsgroupList::SetProgressStatus(const PRUnichar *message)
01105 {
01106   if (!m_runningURL) 
01107     return;
01108 
01109   nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningURL);
01110   if (mailnewsUrl) {
01111     nsCOMPtr <nsIMsgStatusFeedback> feedback;
01112     mailnewsUrl->GetStatusFeedback(getter_AddRefs(feedback));
01113 
01114     if (feedback) {
01115       feedback->ShowStatusString(message);
01116     }
01117   }
01118 }     
01119 
01120 NS_IMETHODIMP nsNNTPNewsgroupList::SetGetOldMessages(PRBool aGetOldMessages) 
01121 {
01122        m_getOldMessages = aGetOldMessages;
01123        return NS_OK;
01124 }
01125 
01126 NS_IMETHODIMP nsNNTPNewsgroupList::GetGetOldMessages(PRBool *aGetOldMessages) 
01127 {
01128        NS_ENSURE_ARG(aGetOldMessages);
01129 
01130        *aGetOldMessages = m_getOldMessages;
01131        return NS_OK;
01132 }