Back to index

lightning-sunbird  0.9+nobinonly
nsMsgFolderCompactor.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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) 2001
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "msgCore.h"    // precompiled header...
00039 #include "nsCOMPtr.h"
00040 #include "nsXPIDLString.h"
00041 #include "nsIMsgFolder.h"    
00042 #include "nsIMsgHdr.h"
00043 #include "nsIFileSpec.h"
00044 #include "nsIStreamListener.h"
00045 #include "nsIMsgMessageService.h"
00046 #include "nsLocalFolderSummarySpec.h"
00047 #include "nsFileStream.h"
00048 #include "nsMsgDBCID.h"
00049 #include "nsMsgUtils.h"
00050 #include "nsIRDFService.h"
00051 #include "nsIDBFolderInfo.h"
00052 #include "nsRDFCID.h"
00053 #include "nsIDocShell.h"
00054 #include "nsMsgFolderCompactor.h"
00055 #include "nsIPrompt.h"
00056 #include "nsIInterfaceRequestorUtils.h"
00057 #include "nsIMsgLocalMailFolder.h"
00058 #include "nsIMsgImapMailFolder.h"
00059 #include "nsMailHeaders.h"
00060 #include "nsMsgI18N.h"
00061 #include "prprf.h"
00062 #include "nsMsgLocalFolderHdrs.h"
00063 
00064 static NS_DEFINE_CID(kCMailDB, NS_MAILDB_CID);
00066 // nsFolderCompactState
00068 
00069 NS_IMPL_ISUPPORTS5(nsFolderCompactState, nsIMsgFolderCompactor, nsIRequestObserver, nsIStreamListener, nsICopyMessageStreamListener, nsIUrlListener)
00070 
00071 nsFolderCompactState::nsFolderCompactState()
00072 {
00073   m_fileStream = nsnull;
00074   m_size = 0;
00075   m_curIndex = -1;
00076   m_status = NS_OK;
00077   m_compactAll = PR_FALSE;
00078   m_compactOfflineAlso = PR_FALSE;
00079   m_compactingOfflineFolders = PR_FALSE;
00080   m_parsingFolder=PR_FALSE;
00081   m_folderIndex =0;
00082   m_startOfMsg = PR_TRUE;
00083   m_needStatusLine = PR_FALSE;
00084 }
00085 
00086 nsFolderCompactState::~nsFolderCompactState()
00087 {
00088   CloseOutputStream();
00089 
00090   if (NS_FAILED(m_status))
00091   {
00092     CleanupTempFilesAfterError();
00093     // if for some reason we failed remove the temp folder and database
00094   }
00095 }
00096 
00097 void nsFolderCompactState::CloseOutputStream()
00098 {
00099   if (m_fileStream)
00100   {
00101     m_fileStream->close();
00102     delete m_fileStream;
00103     m_fileStream = nsnull;
00104   }
00105 
00106 }
00107 
00108 void nsFolderCompactState::CleanupTempFilesAfterError()
00109 {
00110   CloseOutputStream();
00111   if (m_db)
00112     m_db->ForceClosed();
00113   nsLocalFolderSummarySpec summarySpec(m_fileSpec);
00114   m_fileSpec.Delete(PR_FALSE);
00115   summarySpec.Delete(PR_FALSE);
00116 }
00117 
00118 nsresult nsFolderCompactState::BuildMessageURI(const char *baseURI, PRUint32 key, nsCString& uri)
00119 {
00120   uri.Append(baseURI);
00121   uri.Append('#');
00122   uri.AppendInt(key);
00123   return NS_OK;
00124 }
00125 
00126 
00127 nsresult
00128 nsFolderCompactState::InitDB(nsIMsgDatabase *db)
00129 {
00130   nsCOMPtr<nsIMsgDatabase> mailDBFactory;
00131   nsCOMPtr<nsIFileSpec> newPathSpec;
00132 
00133   db->ListAllKeys(m_keyArray);
00134   nsresult rv = NS_NewFileSpecWithSpec(m_fileSpec, getter_AddRefs(newPathSpec));
00135 
00136   nsCOMPtr<nsIMsgDBService> msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
00137   if (msgDBService) 
00138   {
00139     nsresult folderOpen = msgDBService->OpenMailDBFromFileSpec(newPathSpec, PR_TRUE,
00140                                      PR_FALSE,
00141                                      getter_AddRefs(m_db));
00142 
00143     if(NS_FAILED(folderOpen) &&
00144        folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE || 
00145        folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING )
00146     {
00147       // if it's out of date then reopen with upgrade.
00148       rv = msgDBService->OpenMailDBFromFileSpec(newPathSpec,
00149                                PR_TRUE, PR_TRUE,
00150                                getter_AddRefs(m_db));
00151     }
00152   }
00153   return rv;
00154 }
00155 
00156 NS_IMETHODIMP nsFolderCompactState::CompactAll(nsISupportsArray *aArrayOfFoldersToCompact, nsIMsgWindow *aMsgWindow, PRBool aCompactOfflineAlso, nsISupportsArray *aOfflineFolderArray)
00157 {
00158   nsresult rv = NS_OK;
00159   m_window = aMsgWindow;
00160   if (aArrayOfFoldersToCompact)  
00161     m_folderArray =do_QueryInterface(aArrayOfFoldersToCompact, &rv);
00162   else if (aOfflineFolderArray)
00163   {
00164     m_folderArray = do_QueryInterface(aOfflineFolderArray, &rv);
00165     m_compactingOfflineFolders = PR_TRUE;
00166     aOfflineFolderArray = nsnull;
00167   }
00168   if (NS_FAILED(rv) || !m_folderArray)
00169     return rv;
00170  
00171   m_compactAll = PR_TRUE;
00172   m_compactOfflineAlso = aCompactOfflineAlso;
00173   if (m_compactOfflineAlso)
00174     m_offlineFolderArray = do_QueryInterface(aOfflineFolderArray);
00175 
00176   m_folderIndex = 0;
00177   nsCOMPtr<nsIMsgFolder> firstFolder = do_QueryElementAt(m_folderArray,
00178                                                          m_folderIndex, &rv);
00179 
00180   if (NS_SUCCEEDED(rv) && firstFolder)
00181     Compact(firstFolder, m_compactingOfflineFolders, aMsgWindow);   //start with first folder from here.
00182   
00183   return rv;
00184 }
00185 
00186 NS_IMETHODIMP
00187 nsFolderCompactState::Compact(nsIMsgFolder *folder, PRBool aOfflineStore, nsIMsgWindow *aMsgWindow)
00188 {
00189   if (!m_compactingOfflineFolders && !aOfflineStore)
00190 {
00191     nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
00192     if (imapFolder)
00193       return folder->Compact(this, aMsgWindow);
00194   }
00195    m_window = aMsgWindow;
00196    nsresult rv;
00197    nsCOMPtr<nsIMsgDatabase> db;
00198    nsCOMPtr<nsIDBFolderInfo> folderInfo;
00199    nsCOMPtr<nsIMsgDatabase> mailDBFactory;
00200    nsCOMPtr<nsIFileSpec> pathSpec;
00201    nsXPIDLCString baseMessageURI;
00202 
00203    nsCOMPtr <nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder, &rv);
00204    if (NS_SUCCEEDED(rv) && localFolder)
00205    {
00206      rv=localFolder->GetDatabaseWOReparse(getter_AddRefs(db));
00207      if (NS_FAILED(rv) || !db)
00208      {
00209        if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
00210        {
00211          m_folder =folder;  //will be used to compact
00212          m_parsingFolder = PR_TRUE;
00213          rv = localFolder->ParseFolder(m_window, this);
00214        }
00215        return rv;
00216      }
00217      else
00218      {
00219        PRBool valid;  
00220        rv = db->GetSummaryValid(&valid); 
00221        if (!valid) //we are probably parsing the folder because we selected it.
00222        {
00223          folder->NotifyCompactCompleted();
00224          if (m_compactAll)
00225            return CompactNextFolder();
00226          else
00227            return NS_OK;
00228        }
00229      }
00230    }
00231    else
00232    {
00233      rv=folder->GetMsgDatabase(nsnull, getter_AddRefs(db));
00234      NS_ENSURE_SUCCESS(rv,rv);
00235    }
00236    rv = folder->GetPath(getter_AddRefs(pathSpec));
00237    NS_ENSURE_SUCCESS(rv,rv);
00238 
00239    rv = folder->GetBaseMessageURI(getter_Copies(baseMessageURI));
00240    NS_ENSURE_SUCCESS(rv,rv);
00241     
00242    rv = Init(folder, baseMessageURI, db, pathSpec, m_window);
00243    NS_ENSURE_SUCCESS(rv,rv);
00244 
00245    PRBool isLocked;
00246    m_folder->GetLocked(&isLocked);
00247    if(!isLocked)
00248    {
00249      nsCOMPtr <nsISupports> supports = do_QueryInterface(NS_STATIC_CAST(nsIMsgFolderCompactor*, this));
00250      m_folder->AcquireSemaphore(supports);
00251      return StartCompacting();
00252    }
00253    else
00254    {
00255      m_folder->NotifyCompactCompleted();
00256      m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window);
00257      CleanupTempFilesAfterError();
00258      if (m_compactAll)
00259        return CompactNextFolder();
00260      else
00261        return NS_OK;
00262    }
00263 }
00264 
00265 nsresult nsFolderCompactState::ShowStatusMsg(const PRUnichar *aMsg)
00266 {
00267   nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
00268   if (m_window)
00269   {
00270     m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
00271     if (statusFeedback && aMsg)
00272       return statusFeedback->SetStatusString (aMsg);
00273   }
00274   return NS_OK;
00275 }
00276 
00277 nsresult
00278 nsFolderCompactState::Init(nsIMsgFolder *folder, const char *baseMsgUri, nsIMsgDatabase *db,
00279                            nsIFileSpec *pathSpec, nsIMsgWindow *aMsgWindow)
00280 {
00281   nsresult rv;
00282 
00283   m_folder = folder;
00284   m_baseMessageUri = baseMsgUri;
00285 
00286   pathSpec->GetFileSpec(&m_fileSpec);
00287 
00288   // need to make sure the temp file goes in the same real directory
00289   // as the original file, so resolve sym links.
00290   PRBool ignored;
00291   m_fileSpec.ResolveSymlink(ignored);
00292 
00293   m_fileSpec.SetLeafName("nstmp");
00294   m_fileSpec.MakeUnique();   //make sure we are not crunching existing nstmp file
00295   m_window = aMsgWindow;
00296   m_keyArray.RemoveAll();
00297   InitDB(db);
00298 
00299   m_size = m_keyArray.GetSize();
00300   m_curIndex = 0;
00301   
00302   m_fileStream = new nsOutputFileStream(m_fileSpec);
00303   if (!m_fileStream) 
00304   {
00305     m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
00306     rv = NS_ERROR_OUT_OF_MEMORY; 
00307   }
00308   else
00309   {
00310     rv = GetMessageServiceFromURI(baseMsgUri,
00311                                 getter_AddRefs(m_messageService));
00312   }
00313   if (NS_FAILED(rv))
00314   {
00315     m_status = rv;
00316     Release(); // let go of ourselves...
00317   }
00318   return rv;
00319 }
00320 
00321 void nsFolderCompactState::ShowCompactingStatusMsg()
00322 {
00323   nsXPIDLString statusString;
00324   nsresult rv = m_folder->GetStringWithFolderNameFromBundle("compactingFolder", getter_Copies(statusString));
00325   if (statusString && NS_SUCCEEDED(rv))
00326     ShowStatusMsg(statusString);
00327 }
00328 
00329 NS_IMETHODIMP nsFolderCompactState::OnStartRunningUrl(nsIURI *url)
00330 {
00331   return NS_OK;
00332 }
00333 
00334 NS_IMETHODIMP nsFolderCompactState::OnStopRunningUrl(nsIURI *url, nsresult status)
00335 {
00336   if (m_parsingFolder)
00337   {
00338     m_parsingFolder=PR_FALSE;
00339     if (NS_SUCCEEDED(status))
00340       status=Compact(m_folder, m_compactingOfflineFolders, m_window);
00341     else if (m_compactAll)
00342       CompactNextFolder();
00343   }
00344   else if (m_compactAll) // this should be the imap case only
00345     CompactNextFolder();
00346   return NS_OK;
00347 }
00348 
00349 nsresult nsFolderCompactState::StartCompacting()
00350 {
00351   nsresult rv = NS_OK;
00352   if (m_size > 0)
00353   {
00354     ShowCompactingStatusMsg();
00355     AddRef();
00356     rv = m_messageService->CopyMessages(&m_keyArray, m_folder, this, PR_FALSE, nsnull, m_window, nsnull);
00357     // m_curIndex = m_size;  // advance m_curIndex to the end - we're done
00358 
00359   }
00360   else
00361   { // no messages to copy with
00362     FinishCompact();
00363 //    Release(); // we don't "own" ourselves yet.
00364   }
00365   return rv;
00366 }
00367 
00368 nsresult
00369 nsFolderCompactState::FinishCompact()
00370 {
00371     // All okay time to finish up the compact process
00372   nsresult rv = NS_OK;
00373   nsCOMPtr<nsIFileSpec> pathSpec;
00374   nsCOMPtr<nsIDBFolderInfo> folderInfo;
00375   nsFileSpec fileSpec;
00376 
00377     // get leaf name and database name of the folder
00378   rv = m_folder->GetPath(getter_AddRefs(pathSpec));
00379   pathSpec->GetFileSpec(&fileSpec);
00380 
00381   // need to make sure we put the .msf file in the same directory
00382   // as the original mailbox, so resolve symlinks.
00383   PRBool ignored;
00384   fileSpec.ResolveSymlink(ignored);
00385 
00386   nsLocalFolderSummarySpec summarySpec(fileSpec);
00387   nsXPIDLCString leafName;
00388   nsCAutoString dbName(summarySpec.GetLeafName());
00389 
00390   pathSpec->GetLeafName(getter_Copies(leafName));
00391 
00392     // close down the temp file stream; preparing for deleting the old folder
00393     // and its database; then rename the temp folder and database
00394   m_fileStream->flush();
00395   m_fileStream->close();
00396   delete m_fileStream;
00397   m_fileStream = nsnull;
00398 
00399   // make sure the new database is valid.
00400   // Close it so we can rename the .msf file.
00401   m_db->SetSummaryValid(PR_TRUE);
00402   m_db->ForceClosed();
00403   m_db = nsnull;
00404 
00405   nsLocalFolderSummarySpec newSummarySpec(m_fileSpec);
00406 
00407   nsCOMPtr <nsIDBFolderInfo> transferInfo;
00408   m_folder->GetDBTransferInfo(getter_AddRefs(transferInfo));
00409 
00410   // close down database of the original folder and remove the folder node
00411   // and all it's message node from the tree
00412   m_folder->ForceDBClosed();
00413 
00414   PRBool folderRenameSucceeded = PR_FALSE;
00415   PRBool msfRenameSucceeded = PR_FALSE;
00416     // remove the old folder and database
00417   summarySpec.Delete(PR_FALSE);
00418   if (!summarySpec.Exists())
00419   {
00420     fileSpec.Delete(PR_FALSE);
00421     if (!fileSpec.Exists())
00422     {
00423       // rename the copied folder and database to be the original folder and
00424       // database 
00425       rv = m_fileSpec.Rename(leafName.get());
00426       NS_ASSERTION(NS_SUCCEEDED(rv), "error renaming compacted folder");
00427       if (NS_SUCCEEDED(rv))
00428       {
00429         folderRenameSucceeded = PR_TRUE;
00430         rv = newSummarySpec.Rename(dbName.get());
00431         NS_ASSERTION(NS_SUCCEEDED(rv), "error renaming compacted folder's db");
00432         msfRenameSucceeded = NS_SUCCEEDED(rv);
00433       }
00434     }
00435   }
00436   NS_ASSERTION(msfRenameSucceeded && folderRenameSucceeded, "rename failed in compact");
00437   if (!folderRenameSucceeded)
00438     m_fileSpec.Delete(PR_FALSE);
00439   if (!msfRenameSucceeded)
00440     newSummarySpec.Delete(PR_FALSE);
00441   rv = ReleaseFolderLock();
00442   NS_ASSERTION(NS_SUCCEEDED(rv),"folder lock not released successfully");
00443   if (msfRenameSucceeded && folderRenameSucceeded)
00444   {
00445     m_folder->SetDBTransferInfo(transferInfo);
00446 
00447     nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00448 
00449     m_folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(m_db));
00450 
00451     // since we're transferring info from the old db, we need to reset the expunged bytes,
00452     // and set the summary valid again.
00453     if(dbFolderInfo)
00454       dbFolderInfo->SetExpungedBytes(0);
00455   }
00456   if (m_db)
00457     m_db->Close(PR_TRUE);
00458   m_db = nsnull;
00459 
00460   m_folder->NotifyCompactCompleted();
00461 
00462   if (m_compactAll)
00463     rv = CompactNextFolder();
00464   else 
00465     ShowDoneStatus();
00466       
00467   return rv;
00468 }
00469 
00470 nsresult
00471 nsFolderCompactState::ReleaseFolderLock()
00472 {
00473   nsresult result = NS_OK;
00474   if (!m_folder) return result;
00475   PRBool haveSemaphore;
00476   nsCOMPtr <nsISupports> supports = do_QueryInterface(NS_STATIC_CAST(nsIMsgFolderCompactor*, this));
00477   result = m_folder->TestSemaphore(supports, &haveSemaphore);
00478   if(NS_SUCCEEDED(result) && haveSemaphore)
00479     result = m_folder->ReleaseSemaphore(supports);
00480   return result;
00481 }
00482 
00483 void nsFolderCompactState::ShowDoneStatus()
00484 {
00485   if (m_folder)
00486   {
00487     nsXPIDLString statusString;
00488     nsresult rv = m_folder->GetStringWithFolderNameFromBundle("doneCompacting", getter_Copies(statusString));
00489     if (statusString && NS_SUCCEEDED(rv))
00490       ShowStatusMsg(statusString);
00491   }
00492 }
00493 
00494 nsresult
00495 nsFolderCompactState::CompactNextFolder()
00496 {
00497    nsresult rv = NS_OK;
00498    m_folderIndex++;
00499    PRUint32 cnt=0;
00500    rv = m_folderArray->Count(&cnt);
00501    NS_ENSURE_SUCCESS(rv,rv);
00502    if (m_folderIndex == cnt)
00503    {
00504      if (m_compactOfflineAlso)
00505      {
00506        m_compactingOfflineFolders = PR_TRUE;
00507        nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray,
00508                                                          m_folderIndex-1, &rv);
00509        if (NS_SUCCEEDED(rv) && folder)
00510          folder->CompactAllOfflineStores(m_window, m_offlineFolderArray);
00511      }
00512      else
00513      {
00514        ShowDoneStatus();
00515        return rv;
00516      }
00517        
00518    } 
00519    nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(m_folderArray,
00520                                                      m_folderIndex, &rv);
00521 
00522    if (NS_SUCCEEDED(rv) && folder)
00523      rv = Compact(folder, m_compactingOfflineFolders, m_window);                    
00524     else
00525       ShowDoneStatus();
00526    return rv;
00527 }
00528 
00529 nsresult
00530 nsFolderCompactState::GetMessage(nsIMsgDBHdr **message)
00531 {
00532   return GetMsgDBHdrFromURI(m_messageUri.get(), message);
00533 }
00534 
00535 
00536 NS_IMETHODIMP
00537 nsFolderCompactState::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
00538 {
00539   return StartMessage();
00540 }
00541 
00542 NS_IMETHODIMP
00543 nsFolderCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
00544                                     nsresult status)
00545 {
00546   nsresult rv = status;
00547   nsCOMPtr<nsIMsgDBHdr> msgHdr;
00548   nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
00549 
00550   if (NS_FAILED(rv)) goto done;
00551   EndCopy(nsnull, status);
00552   if (m_curIndex >= m_size)
00553   {
00554     msgHdr = nsnull;
00555     newMsgHdr = nsnull;
00556     // no more to copy finish it up
00557    FinishCompact();
00558     Release(); // kill self
00559   }
00560   else
00561   {
00562     // in case we're not getting an error, we still need to pretend we did get an error,
00563     // because the compact did not successfully complete.
00564     if (NS_SUCCEEDED(status))
00565     {
00566       m_folder->NotifyCompactCompleted();
00567       CleanupTempFilesAfterError();
00568       ReleaseFolderLock();
00569       Release();
00570     }
00571   }
00572 
00573 done:
00574   if (NS_FAILED(rv)) {
00575     m_status = rv; // set the status to rv so the destructor can remove the
00576                    // temp folder and database
00577     m_folder->NotifyCompactCompleted();
00578     ReleaseFolderLock();
00579     Release(); // kill self
00580     return rv;
00581   }
00582   return rv;
00583 }
00584 
00585 void nsFolderCompactState::AdvanceToNextLine(const char *buffer, PRUint32 &bufferOffset, PRUint32 maxBufferOffset)
00586 {
00587   for (; bufferOffset < maxBufferOffset; bufferOffset++)
00588   {
00589     if (buffer[bufferOffset] == nsCRT::CR || buffer[bufferOffset] == nsCRT::LF)
00590     {
00591       bufferOffset++;
00592       if (buffer[bufferOffset- 1] == nsCRT::CR && buffer[bufferOffset] == nsCRT::LF)
00593         bufferOffset++;
00594       break;
00595     }
00596   }
00597 }
00598 
00599 NS_IMETHODIMP
00600 nsFolderCompactState::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
00601                                       nsIInputStream *inStr, 
00602                                       PRUint32 sourceOffset, PRUint32 count)
00603 {
00604   if (!m_fileStream || !inStr) 
00605     return NS_ERROR_FAILURE;
00606 
00607   nsresult rv = NS_OK;
00608   PRUint32 msgFlags;
00609   PRBool checkForKeyword = m_startOfMsg;
00610   PRBool addKeywordHdr = PR_FALSE;
00611   PRUint32 needToGrowKeywords = 0;
00612   PRUint32 statusOffset;
00613   nsXPIDLCString msgHdrKeywords;
00614 
00615   if (m_startOfMsg)
00616   {
00617     m_statusOffset = 0;
00618     m_addedHeaderSize = 0;
00619     m_messageUri.SetLength(0); // clear the previous message uri
00620     if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keyArray[m_curIndex],
00621                                 m_messageUri)))
00622     {
00623       rv = GetMessage(getter_AddRefs(m_curSrcHdr));
00624       NS_ENSURE_SUCCESS(rv, rv);
00625       if (m_curSrcHdr)
00626       {
00627         (void) m_curSrcHdr->GetFlags(&msgFlags);
00628         (void) m_curSrcHdr->GetStatusOffset(&statusOffset);
00629         
00630         if (statusOffset == 0)
00631           m_needStatusLine = PR_TRUE;
00632         // x-mozilla-status lines should be at the start of the headers, and the code
00633         // below assumes everything will fit in m_dataBuffer - if there's not
00634         // room, skip the keyword stuff.
00635         if (statusOffset > sizeof(m_dataBuffer) - 1024)
00636         {
00637           checkForKeyword = PR_FALSE;
00638           NS_ASSERTION(PR_FALSE, "status offset past end of read buffer size");
00639           
00640         }
00641       }
00642     }
00643     m_startOfMsg = PR_FALSE;
00644   }
00645   PRUint32 maxReadCount, readCount, writeCount;
00646   while (NS_SUCCEEDED(rv) && (PRInt32) count > 0)
00647   {
00648     maxReadCount = count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count;
00649     writeCount = 0;
00650     rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount);
00651     
00652     // if status offset is past the number of bytes we read, it's probably bogus,
00653     // and we shouldn't do any of the keyword stuff.
00654     if (statusOffset + X_MOZILLA_STATUS_LEN > readCount)
00655       checkForKeyword = PR_FALSE;
00656     
00657     if (NS_SUCCEEDED(rv))
00658     {
00659       if (checkForKeyword)
00660       {
00661         // make sure that status offset really points to x-mozilla-status line
00662         if  (!strncmp(m_dataBuffer + statusOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
00663         {
00664           const char *keywordHdr = PL_strnrstr(m_dataBuffer, HEADER_X_MOZILLA_KEYWORDS, readCount);
00665           if (keywordHdr)
00666             m_curSrcHdr->GetUint32Property("growKeywords", &needToGrowKeywords);
00667           else
00668             addKeywordHdr = PR_TRUE;
00669           m_curSrcHdr->GetStringProperty("keywords", getter_Copies(msgHdrKeywords));
00670         }
00671         checkForKeyword = PR_FALSE;
00672       }
00673       PRUint32 blockOffset = 0;
00674       if (m_needStatusLine)
00675       {
00676         m_needStatusLine = PR_FALSE;
00677         // we need to parse out the "From " header, write it out, then 
00678         // write out the x-mozilla-status headers, and set the 
00679         // status offset of the dest hdr for later use 
00680         // in OnEndCopy).
00681         if (!strncmp(m_dataBuffer, "From ", 5))
00682         {
00683           blockOffset = 5;
00684           // skip from line
00685           AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
00686           char statusLine[50];
00687           writeCount = m_fileStream->write(m_dataBuffer, blockOffset);
00688           m_statusOffset = blockOffset;
00689           PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
00690           m_addedHeaderSize = m_fileStream->write(statusLine, strlen(statusLine));
00691           PR_snprintf(statusLine, sizeof(statusLine), X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
00692           m_addedHeaderSize += m_fileStream->write(statusLine, strlen(statusLine));
00693         }
00694         else
00695         {
00696           NS_ASSERTION(PR_FALSE, "not an envelope");
00697           // try to mark the db as invalid so it will be reparsed.
00698           nsCOMPtr <nsIMsgDatabase> srcDB;
00699           m_folder->GetMsgDatabase(nsnull, getter_AddRefs(srcDB));
00700           if (srcDB)
00701           {
00702             srcDB->SetSummaryValid(PR_FALSE);
00703             srcDB->ForceClosed();
00704           }
00705         }
00706       }
00707 #define EXTRA_KEYWORD_HDR "                                                                                 "MSG_LINEBREAK
00708 
00709        // if status offset isn't in the first block, this code won't work. There's no good reason
00710       // for the status offset not to be at the beginning of the message anyway.
00711       if (addKeywordHdr)
00712       {
00713         // if blockOffset is set, we added x-mozilla-status headers so
00714         // file pointer is already past them.
00715         if (!blockOffset)
00716         {
00717           blockOffset = statusOffset;
00718           // skip x-mozilla-status and status2 lines.
00719           AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
00720           AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
00721           // need to rewrite the headers up to and including the x-mozilla-status2 header
00722           writeCount = m_fileStream->write(m_dataBuffer, blockOffset);
00723         }
00724         // we should write out the existing keywords from the msg hdr, if any.
00725         if (msgHdrKeywords.IsEmpty())
00726         { // no keywords, so write blank header
00727           m_addedHeaderSize += m_fileStream->write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1);
00728         }
00729         else
00730         {
00731           if (msgHdrKeywords.Length() < sizeof(X_MOZILLA_KEYWORDS) - sizeof(HEADER_X_MOZILLA_KEYWORDS) + 10 /* allow some slop */)
00732           { // keywords fit in normal blank header, so replace blanks in keyword hdr with keywords
00733             nsCAutoString keywordsHdr(X_MOZILLA_KEYWORDS);
00734             keywordsHdr.Replace(sizeof(HEADER_X_MOZILLA_KEYWORDS) + 1, msgHdrKeywords.Length(), msgHdrKeywords);
00735             m_addedHeaderSize += m_fileStream->write(keywordsHdr.get(), keywordsHdr.Length());
00736           }
00737           else
00738           { // keywords don't fit, so write out keywords on one line and an extra blank line
00739             nsCString newKeywordHeader(HEADER_X_MOZILLA_KEYWORDS ": ");
00740             newKeywordHeader.Append(msgHdrKeywords);
00741             newKeywordHeader.Append(MSG_LINEBREAK EXTRA_KEYWORD_HDR);
00742             m_addedHeaderSize += m_fileStream->write(newKeywordHeader.get(), newKeywordHeader.Length());
00743           }
00744         }
00745         addKeywordHdr = PR_FALSE;
00746       }
00747       else if (needToGrowKeywords)
00748       {
00749         blockOffset = statusOffset;
00750         if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN))
00751           AdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status hdr
00752         if (!strncmp(m_dataBuffer + blockOffset, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN))
00753           AdvanceToNextLine(m_dataBuffer, blockOffset, readCount); // skip x-mozilla-status2 hdr
00754         PRUint32 preKeywordBlockOffset = blockOffset;
00755         if (!strncmp(m_dataBuffer + blockOffset, HEADER_X_MOZILLA_KEYWORDS, sizeof(HEADER_X_MOZILLA_KEYWORDS) - 1))
00756         {
00757           do
00758           {
00759             // skip x-mozilla-keywords hdr and any existing continuation headers
00760             AdvanceToNextLine(m_dataBuffer, blockOffset, readCount);
00761           }
00762           while (m_dataBuffer[blockOffset] == ' ');
00763         }
00764         PRInt32 oldKeywordSize = blockOffset - preKeywordBlockOffset;
00765 
00766         // rewrite the headers up to and including the x-mozilla-status2 header
00767         writeCount = m_fileStream->write(m_dataBuffer, preKeywordBlockOffset);
00768         // let's just rewrite all the keywords on several lines and add a blank line,
00769         // instead of worrying about which are missing.
00770         PRBool done = PR_FALSE;
00771         nsCAutoString keywordHdr(HEADER_X_MOZILLA_KEYWORDS ": ");
00772         PRInt32 nextBlankOffset = 0;
00773         PRInt32 curHdrLineStart = 0;
00774         PRInt32 newKeywordSize = 0;
00775         while (!done)
00776         {
00777           nextBlankOffset = msgHdrKeywords.FindChar(' ', nextBlankOffset);
00778           if (nextBlankOffset == kNotFound)
00779           {
00780             nextBlankOffset = msgHdrKeywords.Length();
00781             done = PR_TRUE;
00782           }
00783           if (nextBlankOffset - curHdrLineStart > 90 || done)
00784           {
00785             keywordHdr.Append(nsDependentCSubstring(msgHdrKeywords, curHdrLineStart, msgHdrKeywords.Length() - curHdrLineStart));
00786             keywordHdr.Append(MSG_LINEBREAK);
00787             newKeywordSize += m_fileStream->write(keywordHdr.get(), keywordHdr.Length());
00788             curHdrLineStart = nextBlankOffset;
00789             keywordHdr.Assign(' ');
00790           }
00791           nextBlankOffset++;
00792         }
00793         newKeywordSize += m_fileStream->write(EXTRA_KEYWORD_HDR, sizeof(EXTRA_KEYWORD_HDR) - 1);
00794         m_addedHeaderSize += newKeywordSize - oldKeywordSize;
00795         m_curSrcHdr->SetUint32Property("growKeywords", 0);
00796         needToGrowKeywords = PR_FALSE;
00797         writeCount += blockOffset - preKeywordBlockOffset; // fudge writeCount
00798 
00799       }
00800       if (readCount <= blockOffset)
00801       {
00802         NS_ASSERTION(PR_FALSE, "bad block offset");
00803         // not sure what to do to handle this.
00804       
00805       }
00806       writeCount += m_fileStream->write(m_dataBuffer + blockOffset, readCount - blockOffset);
00807       count -= readCount;
00808       if (writeCount != readCount)
00809       {
00810         m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
00811         return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
00812       }
00813     }
00814   }
00815   return rv;
00816 }
00817 
00818 nsOfflineStoreCompactState::nsOfflineStoreCompactState()
00819 {
00820 }
00821 
00822 nsOfflineStoreCompactState::~nsOfflineStoreCompactState()
00823 {
00824 }
00825 
00826 
00827 nsresult
00828 nsOfflineStoreCompactState::InitDB(nsIMsgDatabase *db)
00829 {
00830   db->ListAllOfflineMsgs(&m_keyArray);
00831   m_db = db;
00832   return NS_OK;
00833 }
00834 
00835 NS_IMETHODIMP
00836 nsOfflineStoreCompactState::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
00837                                           nsresult status)
00838 {
00839   nsresult rv = status;
00840   nsCOMPtr<nsIURI> uri;
00841   nsCOMPtr<nsIMsgDBHdr> msgHdr;
00842   nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
00843   nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
00844   ReleaseFolderLock();
00845 
00846   if (NS_FAILED(rv)) goto done;
00847   uri = do_QueryInterface(ctxt, &rv);
00848   if (NS_FAILED(rv)) goto done;
00849   rv = GetMessage(getter_AddRefs(msgHdr));
00850   if (NS_FAILED(rv)) goto done;
00851 
00852   if (msgHdr)
00853     msgHdr->SetMessageOffset(m_startOfNewMsg);
00854 
00855   if (m_window)
00856   {
00857     m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
00858     if (statusFeedback)
00859       statusFeedback->ShowProgress (100 * m_curIndex / m_size);
00860   }
00861     // advance to next message 
00862   m_curIndex ++;
00863   if (m_curIndex >= m_size)
00864   {
00865     m_db->Commit(nsMsgDBCommitType::kLargeCommit);
00866     msgHdr = nsnull;
00867     newMsgHdr = nsnull;
00868     // no more to copy finish it up
00869    FinishCompact();
00870     Release(); // kill self
00871   }
00872   else
00873   {
00874     m_messageUri.SetLength(0); // clear the previous message uri
00875     rv = BuildMessageURI(m_baseMessageUri.get(), m_keyArray[m_curIndex],
00876                                 m_messageUri);
00877     if (NS_FAILED(rv)) goto done;
00878     rv = m_messageService->CopyMessage(m_messageUri.get(), this, PR_FALSE, nsnull,
00879                                        /* ### should get msg window! */ nsnull, nsnull);
00880    if (NS_FAILED(rv))
00881    {
00882      PRUint32 resultFlags;
00883      msgHdr->AndFlags(~MSG_FLAG_OFFLINE, &resultFlags);
00884    }
00885   // if this fails, we should clear the offline flag on the source message.
00886     
00887   }
00888 
00889 done:
00890   if (NS_FAILED(rv)) {
00891     m_status = rv; // set the status to rv so the destructor can remove the
00892                    // temp folder and database
00893     Release(); // kill self
00894     return rv;
00895   }
00896   return rv;
00897 }
00898  
00899 
00900 nsresult
00901 nsOfflineStoreCompactState::FinishCompact()
00902 {
00903     // All okay time to finish up the compact process
00904   nsresult rv = NS_OK;
00905   nsCOMPtr<nsIFileSpec> pathSpec;
00906   nsFileSpec fileSpec;
00907   PRUint32 flags;
00908 
00909     // get leaf name and database name of the folder
00910   m_folder->GetFlags(&flags);
00911   rv = m_folder->GetPath(getter_AddRefs(pathSpec));
00912   pathSpec->GetFileSpec(&fileSpec);
00913 
00914   nsXPIDLCString leafName;
00915 
00916   pathSpec->GetLeafName(getter_Copies(leafName));
00917 
00918     // close down the temp file stream; preparing for deleting the old folder
00919     // and its database; then rename the temp folder and database
00920   m_fileStream->flush();
00921   m_fileStream->close();
00922   delete m_fileStream;
00923   m_fileStream = nsnull;
00924 
00925     // make sure the new database is valid
00926   nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
00927   m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
00928   if (dbFolderInfo)
00929     dbFolderInfo->SetExpungedBytes(0);
00930   // this forces the m_folder to update mExpungedBytes from the db folder info.
00931   PRUint32 expungedBytes;
00932   m_folder->GetExpungedBytes(&expungedBytes);
00933   m_folder->UpdateSummaryTotals(PR_TRUE);
00934   m_db->SetSummaryValid(PR_TRUE);
00935 
00936     // remove the old folder 
00937   fileSpec.Delete(PR_FALSE);
00938 
00939     // rename the copied folder to be the original folder 
00940   m_fileSpec.Rename(leafName.get());
00941 
00942   PRUnichar emptyStr = 0;
00943   ShowStatusMsg(&emptyStr);
00944   if (m_compactAll)
00945     rv = CompactNextFolder();
00946   return rv;
00947 }
00948 
00949 
00950 NS_IMETHODIMP
00951 nsFolderCompactState::Init(nsIMsgFolder *srcFolder, nsICopyMessageListener *destination, nsISupports *listenerData)
00952 {
00953   return NS_OK;
00954 }
00955 
00956 NS_IMETHODIMP
00957 nsFolderCompactState::StartMessage()
00958 {
00959   nsresult rv = NS_ERROR_FAILURE;
00960   NS_ASSERTION(m_fileStream, "Fatal, null m_fileStream...\n");
00961   if (m_fileStream)
00962   {
00963     // this will force an internal flush, but not a sync. Tell should really do an internal flush,
00964     // but it doesn't, and I'm afraid to change that nsIFileStream.cpp code anymore.
00965     m_fileStream->seek(PR_SEEK_CUR, 0);
00966     // record the new message key for the message
00967     m_startOfNewMsg = m_fileStream->tell();
00968     rv = NS_OK;
00969   }
00970   return rv;
00971 }
00972 
00973 NS_IMETHODIMP
00974 nsFolderCompactState::EndMessage(nsMsgKey key)
00975 {
00976   return NS_OK;
00977 }
00978 
00979 NS_IMETHODIMP
00980 nsFolderCompactState::EndCopy(nsISupports *url, nsresult aStatus)
00981 {
00982   nsCOMPtr<nsIMsgDBHdr> msgHdr;
00983   nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
00984 
00985   if (m_curIndex >= m_size)
00986   {
00987     NS_ASSERTION(PR_FALSE, "m_curIndex out of bounds");
00988     return NS_OK;
00989   }
00990 
00991     // okay done with the current message; copying the existing message header
00992     // to the new database
00993   if (m_curSrcHdr)
00994     m_db->CopyHdrFromExistingHdr(m_startOfNewMsg, m_curSrcHdr, PR_TRUE,
00995                                getter_AddRefs(newMsgHdr));
00996   m_curSrcHdr = nsnull;
00997   if (newMsgHdr)
00998   {
00999     if ( m_statusOffset != 0)
01000       newMsgHdr->SetStatusOffset(m_statusOffset);
01001     if (m_addedHeaderSize != 0)
01002     {
01003       PRUint32 oldMsgSize;
01004       (void) newMsgHdr->GetMessageSize(&oldMsgSize);
01005       newMsgHdr->SetMessageSize(oldMsgSize + m_addedHeaderSize);
01006     }
01007   }
01008 
01009 //  m_db->Commit(nsMsgDBCommitType::kLargeCommit);  // no sense commiting until the end
01010     // advance to next message 
01011   m_curIndex ++;
01012   m_startOfMsg = PR_TRUE;
01013   nsCOMPtr <nsIMsgStatusFeedback> statusFeedback;
01014   if (m_window)
01015   {
01016     m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
01017     if (statusFeedback)
01018       statusFeedback->ShowProgress (100 * m_curIndex / m_size);
01019   }
01020   return NS_OK;
01021 }
01022 
01023 nsresult nsOfflineStoreCompactState::StartCompacting()
01024 {
01025   nsresult rv = NS_OK;
01026   if (m_size > 0 && m_curIndex == 0)
01027   {
01028     AddRef(); // we own ourselves, until we're done, anyway.
01029     ShowCompactingStatusMsg();
01030     m_messageUri.SetLength(0); // clear the previous message uri
01031     rv = BuildMessageURI(m_baseMessageUri.get(),
01032                                 m_keyArray[0],
01033                                 m_messageUri);
01034     if (NS_SUCCEEDED(rv))
01035       rv = m_messageService->CopyMessage(
01036         m_messageUri.get(), this, PR_FALSE, nsnull, m_window, nsnull);
01037 
01038   }
01039   else
01040   { // no messages to copy with
01041     ReleaseFolderLock();
01042     FinishCompact();
01043 //    Release(); // we don't "own" ourselves yet.
01044   }
01045   return rv;
01046 }
01047