Back to index

lightning-sunbird  0.9+nobinonly
nsMsgFolderCache.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; 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) 1999
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "msgCore.h"
00040 #include "nsIMsgAccountManager.h"
00041 #include "nsMsgFolderCacheElement.h"
00042 #include "nsMsgFolderCache.h"
00043 #include "nsMorkCID.h"
00044 #include "nsIMdbFactoryFactory.h"
00045 #include "nsFileStream.h"
00046 #include "nsIFileSpec.h"
00047 
00048 #include "nsXPIDLString.h"
00049 #include "nsMsgBaseCID.h"
00050 
00051 static NS_DEFINE_CID(kMorkCID, NS_MORK_CID);
00052 
00053 const char *kFoldersScope = "ns:msg:db:row:scope:folders:all"; // scope for all folders table
00054 const char *kFoldersTableKind = "ns:msg:db:table:kind:folders";
00055 
00056 
00057 nsMsgFolderCache::nsMsgFolderCache()
00058 {
00059   m_cacheElements = nsnull;
00060        m_mdbEnv = nsnull;
00061        m_mdbStore = nsnull;
00062        m_mdbAllFoldersTable = nsnull;
00063 }
00064 
00065 // should this, could this be an nsCOMPtr ?
00066 static nsIMdbFactory *gMDBFactory = nsnull;
00067 
00068 nsMsgFolderCache::~nsMsgFolderCache()
00069 {
00070        delete m_cacheElements;
00071         if (m_mdbAllFoldersTable)
00072           m_mdbAllFoldersTable->Release();
00073        if (m_mdbStore)
00074               m_mdbStore->Release();
00075        if (gMDBFactory)
00076        {
00077               NS_RELEASE(gMDBFactory);
00078        }
00079        gMDBFactory = nsnull;
00080        if (m_mdbEnv)
00081        {
00082               m_mdbEnv->Release();
00083        }
00084 }
00085 
00086 NS_IMPL_ADDREF(nsMsgFolderCache)
00087 NS_IMPL_RELEASE(nsMsgFolderCache)
00088   
00089 nsresult
00090 nsMsgFolderCache::QueryInterface(const nsIID& iid, void **result)
00091 {
00092        nsresult rv = NS_NOINTERFACE;
00093        if (! result)
00094               return NS_ERROR_NULL_POINTER;
00095 
00096        void *res = nsnull;
00097        if (iid.Equals(NS_GET_IID(nsIMsgFolderCache)) ||
00098              iid.Equals(NS_GET_IID(nsISupports)))
00099               res = NS_STATIC_CAST(nsIMsgFolderCache*, this);
00100        if (res) 
00101        {
00102               NS_ADDREF(this);
00103               *result = res;
00104               rv = NS_OK;
00105        }
00106        return rv;
00107 }
00108 
00109 /* static */ nsIMdbFactory *nsMsgFolderCache::GetMDBFactory()
00110 {
00111        if (!gMDBFactory)
00112        {
00113               nsresult rv;
00114               nsCOMPtr <nsIMdbFactoryFactory> factoryfactory = do_CreateInstance(kMorkCID, &rv);
00115               if (NS_SUCCEEDED(rv) && factoryfactory)
00116                 rv = factoryfactory->GetMdbFactory(&gMDBFactory);
00117        }
00118        return gMDBFactory;
00119 }
00120 
00121 // initialize the various tokens and tables in our db's env
00122 nsresult nsMsgFolderCache::InitMDBInfo()
00123 {
00124        nsresult err = NS_OK;
00125 
00126        if (GetStore())
00127        {
00128               err    = GetStore()->StringToToken(GetEnv(), kFoldersScope, &m_folderRowScopeToken); 
00129               if (err == NS_OK)
00130               {
00131                      err = GetStore()->StringToToken(GetEnv(), kFoldersTableKind, &m_folderTableKindToken); 
00132                      if (err == NS_OK)
00133                      {
00134                             // The table of all message hdrs will have table id 1.
00135                             m_allFoldersTableOID.mOid_Scope = m_folderRowScopeToken;
00136                             m_allFoldersTableOID.mOid_Id = 1;
00137                      }
00138               }
00139        }
00140        return err;
00141 }
00142 
00143 // set up empty tables, dbFolderInfo, etc.
00144 nsresult nsMsgFolderCache::InitNewDB()
00145 {
00146        nsresult err = NS_OK;
00147 
00148        err = InitMDBInfo();
00149        if (err == NS_OK)
00150        {
00151               nsIMdbStore *store = GetStore();
00152               // create the unique table for the dbFolderInfo.
00153               mdb_err mdberr;
00154 
00155         mdberr = (nsresult) store->NewTable(GetEnv(), m_folderRowScopeToken, 
00156                      m_folderTableKindToken, PR_FALSE, nsnull, &m_mdbAllFoldersTable);
00157 
00158        }
00159        return err;
00160 }
00161 
00162 nsresult nsMsgFolderCache::InitExistingDB()
00163 {
00164        nsresult err = NS_OK;
00165 
00166        err = InitMDBInfo();
00167        if (err == NS_OK)
00168        {
00169               err = GetStore()->GetTable(GetEnv(), &m_allFoldersTableOID, &m_mdbAllFoldersTable);
00170               if (NS_SUCCEEDED(err) && m_mdbAllFoldersTable)
00171               {
00172                      nsIMdbTableRowCursor* rowCursor = nsnull;
00173                      err = m_mdbAllFoldersTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
00174                      if (NS_SUCCEEDED(err) && rowCursor)
00175                      {
00176                             // iterate over the table rows and create nsMsgFolderCacheElements for each.
00177                             while (PR_TRUE)
00178                             {
00179                                    nsresult rv;
00180                                    nsIMdbRow* hdrRow;
00181                                    mdb_pos rowPos;
00182 
00183                                    rv = rowCursor->NextRow(GetEnv(), &hdrRow, &rowPos);
00184                                    if (NS_FAILED(rv) || !hdrRow)
00185                                           break;
00186 
00187                                    rv = AddCacheElement(nsnull, hdrRow, nsnull);
00188                                         hdrRow->Release();
00189                                    if (NS_FAILED(rv))
00190                                           return rv;
00191                             }
00192                             rowCursor->Release();
00193                      }
00194               }
00195     else
00196       err = NS_ERROR_FAILURE;
00197        }
00198        return err;
00199 }
00200 
00201 nsresult nsMsgFolderCache::OpenMDB(const char *dbName, PRBool exists)
00202 {
00203        nsresult ret=NS_OK;
00204        nsIMdbFactory *myMDBFactory = GetMDBFactory();
00205        if (myMDBFactory)
00206        {
00207               ret = myMDBFactory->MakeEnv(nsnull, &m_mdbEnv);
00208               if (NS_SUCCEEDED(ret))
00209               {
00210                      nsIMdbThumb *thumb = nsnull;
00211                      char   *nativeFileName = nsCRT::strdup(dbName);
00212                      nsIMdbHeap* dbHeap = 0;
00213                      mdb_bool dbFrozen = mdbBool_kFalse; // not readonly, we want modifiable
00214 
00215                      if (!nativeFileName)
00216                             return NS_ERROR_OUT_OF_MEMORY;
00217 
00218                      if (m_mdbEnv)
00219                             m_mdbEnv->SetAutoClear(PR_TRUE);
00220                      if (exists)
00221                      {
00222                             mdbOpenPolicy inOpenPolicy;
00223                             mdb_bool      canOpen;
00224                             mdbYarn              outFormatVersion;
00225                             
00226                             nsIMdbFile* oldFile = 0;
00227                             ret = myMDBFactory->OpenOldFile(m_mdbEnv, dbHeap, nativeFileName,
00228                                     dbFrozen, &oldFile);
00229                             if ( oldFile )
00230                             {
00231                                    if ( ret == NS_OK )
00232                                    {
00233                                           ret = myMDBFactory->CanOpenFilePort(m_mdbEnv, oldFile, // file to investigate
00234                                                  &canOpen, &outFormatVersion);
00235                                           if (ret == 0 && canOpen)
00236                                           {
00237                                                  inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
00238                                                  inOpenPolicy.mOpenPolicy_MinMemory = 0;
00239                                                  inOpenPolicy.mOpenPolicy_MaxLazy = 0;
00240 
00241                                                  ret = myMDBFactory->OpenFileStore(m_mdbEnv, NULL, oldFile, &inOpenPolicy, 
00242                                                                              &thumb); 
00243                                           }
00244                                           else
00245                                                  ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
00246                                    }
00247                                    NS_RELEASE(oldFile); // always release our file ref, store has own
00248                             }
00249                      }
00250                      if (NS_SUCCEEDED(ret) && thumb)
00251                      {
00252                             mdb_count outTotal;    // total somethings to do in operation
00253                             mdb_count outCurrent;  // subportion of total completed so far
00254                             mdb_bool outDone = PR_FALSE;      // is operation finished?
00255                             mdb_bool outBroken;     // is operation irreparably dead and broken?
00256                             do
00257                             {
00258                                    ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken);
00259                                    if (ret != 0)
00260                                    {// mork isn't really doing NS erorrs yet.
00261                                           outDone = PR_TRUE;
00262                                           break;
00263                                    }
00264                             }
00265                             while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
00266 //                          m_mdbEnv->ClearErrors(); // ### temporary...
00267                             if (NS_SUCCEEDED(ret) && outDone)
00268                             {
00269                                    ret = myMDBFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore);
00270                                    if (ret == NS_OK && m_mdbStore)
00271                                           ret = InitExistingDB();
00272                             }
00273 #ifdef DEBUG_bienvenu1
00274                             DumpContents();
00275 #endif
00276                      }
00277                      else // ### need error code saying why open file store failed
00278                      {
00279                             nsIMdbFile* newFile = 0;
00280                             ret = myMDBFactory->CreateNewFile(m_mdbEnv, dbHeap, dbName, &newFile);
00281                             if ( newFile )
00282                             {
00283                                    if (ret == NS_OK)
00284                                    {
00285                                           mdbOpenPolicy inOpenPolicy;
00286 
00287                                           inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
00288                                           inOpenPolicy.mOpenPolicy_MinMemory = 0;
00289                                           inOpenPolicy.mOpenPolicy_MaxLazy = 0;
00290 
00291                                           ret = myMDBFactory->CreateNewFileStore(m_mdbEnv, dbHeap,
00292                                                   newFile, &inOpenPolicy, &m_mdbStore);
00293                                           if (ret == NS_OK)
00294                                                  ret = InitNewDB();
00295                                    }
00296                                    NS_RELEASE(newFile); // always release our file ref, store has own
00297                             }
00298 
00299                      }
00300                      NS_IF_RELEASE(thumb);
00301                      nsCRT::free(nativeFileName);
00302               }
00303        }
00304        return ret;
00305 }
00306 
00307 NS_IMETHODIMP nsMsgFolderCache::Init(nsIFileSpec *dbFileSpec)
00308 {
00309        if (!dbFileSpec)
00310               return NS_ERROR_NULL_POINTER;
00311 
00312        nsresult rv = NS_ERROR_OUT_OF_MEMORY;
00313 
00314        m_cacheElements = new nsSupportsHashtable;
00315 
00316        if (m_cacheElements)
00317        {
00318               rv = dbFileSpec->GetFileSpec(&m_dbFileSpec);
00319 
00320               if (NS_SUCCEEDED(rv))
00321               {
00322                      PRBool exists = m_dbFileSpec.Exists();
00323                      // ### evil cast until MDB supports file streams.
00324                      rv = OpenMDB((const char *) m_dbFileSpec, exists);
00325       // if this fails and panacea.dat exists, try blowing away the db and recreating it
00326       if (NS_FAILED(rv) && exists)
00327       {
00328              if (m_mdbStore)
00329                     m_mdbStore->Release();
00330         m_dbFileSpec.Delete(PR_FALSE);
00331                      rv = OpenMDB((const char *) m_dbFileSpec, PR_FALSE);
00332       }
00333               }
00334        }
00335        return rv;
00336 }
00337 
00338 NS_IMETHODIMP nsMsgFolderCache::GetCacheElement(const char *pathKey, PRBool createIfMissing, 
00339                                                 nsIMsgFolderCacheElement **result)
00340 {
00341   
00342   if (!result || !pathKey || !m_cacheElements)
00343     return NS_ERROR_NULL_POINTER;
00344               
00345   if (!*pathKey)
00346     return NS_ERROR_FAILURE;
00347   
00348   nsCStringKey hashKey(pathKey);
00349   
00350   *result = (nsIMsgFolderCacheElement *) m_cacheElements->Get(&hashKey);
00351   
00352   // nsHashTable already does an address on *result
00353   if (*result)
00354   {
00355     return NS_OK;
00356   }
00357   else if (createIfMissing)
00358   {
00359     nsIMdbRow* hdrRow;
00360     
00361     if (GetStore())
00362     {
00363       mdb_err err = GetStore()->NewRow(GetEnv(), m_folderRowScopeToken,   // row scope for row ids
00364         &hdrRow);
00365       if (NS_SUCCEEDED(err) && hdrRow)
00366       {
00367         m_mdbAllFoldersTable->AddRow(GetEnv(), hdrRow);
00368         nsresult ret = AddCacheElement(pathKey, hdrRow, result);
00369         if (*result)
00370           (*result)->SetStringProperty("key", pathKey);
00371         hdrRow->Release();
00372         return ret;
00373       }
00374     }
00375   }
00376   return NS_ERROR_FAILURE;
00377 }
00378 
00379 NS_IMETHODIMP nsMsgFolderCache::RemoveElement(const char *key)
00380 {
00381   if (!key || !*key)
00382     return NS_ERROR_NULL_POINTER;
00383   
00384   nsCStringKey hashKey(key);
00385   
00386   nsCOMPtr <nsISupports> supports = getter_AddRefs(m_cacheElements->Get(&hashKey));
00387   if (!supports)
00388     return NS_ERROR_FAILURE;
00389   nsCOMPtr <nsIMsgFolderCacheElement> cacheElement = do_QueryInterface(supports);
00390   nsMsgFolderCacheElement *element = NS_STATIC_CAST(nsMsgFolderCacheElement *, NS_STATIC_CAST(nsISupports *, cacheElement.get())); 
00391   m_mdbAllFoldersTable->CutRow(GetEnv(), element->m_mdbRow);
00392   m_cacheElements->Remove(&hashKey);
00393   return NS_OK;
00394 }
00395 
00396 NS_IMETHODIMP nsMsgFolderCache::Clear()
00397 {
00398   if (m_cacheElements)
00399     m_cacheElements->Reset();
00400   if (m_mdbAllFoldersTable)
00401     m_mdbAllFoldersTable->CutAllRows(GetEnv());
00402   return NS_OK;
00403 }
00404 
00405 NS_IMETHODIMP nsMsgFolderCache::Close()
00406 {
00407   return Commit(PR_TRUE);
00408 }
00409 
00410 NS_IMETHODIMP nsMsgFolderCache::Commit(PRBool compress)
00411 {
00412        nsresult ret = NS_OK;
00413 
00414        nsIMdbThumb   *commitThumb = nsnull;
00415        if (m_mdbStore)
00416     if (compress)
00417                 ret = m_mdbStore->CompressCommit(GetEnv(), &commitThumb);
00418     else
00419       ret = m_mdbStore->LargeCommit(GetEnv(), &commitThumb);
00420 
00421        if (commitThumb)
00422        {
00423               mdb_count outTotal = 0;    // total somethings to do in operation
00424               mdb_count outCurrent = 0;  // subportion of total completed so far
00425               mdb_bool outDone = PR_FALSE;      // is operation finished?
00426               mdb_bool outBroken = PR_FALSE;     // is operation irreparably dead and broken?
00427               while (!outDone && !outBroken && ret == NS_OK)
00428               {
00429                      ret = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken);
00430               }
00431               NS_IF_RELEASE(commitThumb);
00432        }
00433        // ### do something with error, but clear it now because mork errors out on commits.
00434        if (GetEnv())
00435               GetEnv()->ClearErrors();
00436        return ret;
00437 }
00438 
00439 nsresult nsMsgFolderCache::AddCacheElement(const char *key, nsIMdbRow *row, nsIMsgFolderCacheElement **result)
00440 {
00441        nsMsgFolderCacheElement *cacheElement = new nsMsgFolderCacheElement;
00442 
00443        if (cacheElement)
00444        {
00445               cacheElement->SetMDBRow(row);
00446               cacheElement->SetOwningCache(this);
00447               nsCAutoString hashStrKey(key);
00448               // if caller didn't pass in key, try to get it from row.
00449               if (!key)
00450               {
00451                      char *existingKey = nsnull;
00452                      cacheElement->GetStringProperty("key", &existingKey);   
00453                      cacheElement->SetKey(existingKey);
00454                      hashStrKey = existingKey;
00455                      PR_Free(existingKey);
00456               }
00457               else
00458                      cacheElement->SetKey((char *) key);
00459               nsCOMPtr<nsISupports> supports(do_QueryInterface(cacheElement));
00460               if(supports)
00461               {
00462                      nsCStringKey hashKey(hashStrKey);
00463                      m_cacheElements->Put(&hashKey, supports);
00464               }
00465               if (result)
00466               {
00467                      *result = cacheElement;
00468                      NS_ADDREF(*result);
00469               }
00470               return NS_OK;
00471        }
00472        else
00473               return NS_ERROR_OUT_OF_MEMORY;
00474 }
00475 
00476 nsresult nsMsgFolderCache::RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, char **resultPtr)
00477 {
00478        nsresult      err = NS_OK;
00479        nsIMdbCell    *hdrCell;
00480 
00481        if (hdrRow)   // ### probably should be an error if hdrRow is NULL...
00482        {
00483               err = hdrRow->GetCell(GetEnv(), columnToken, &hdrCell);
00484               if (err == NS_OK && hdrCell)
00485               {
00486                      struct mdbYarn yarn;
00487                      hdrCell->AliasYarn(GetEnv(), &yarn);
00488                      char *result = (char *) PR_Malloc(yarn.mYarn_Fill + 1);
00489                      if (result)
00490                      {
00491                             memcpy(result, yarn.mYarn_Buf, yarn.mYarn_Fill);
00492                             result[yarn.mYarn_Fill] = '\0';
00493                      }
00494                      else
00495                             err = NS_ERROR_OUT_OF_MEMORY;
00496 
00497                      *resultPtr = result;
00498                      hdrCell->Release(); // always release ref
00499               }
00500        }
00501        return err;
00502 }