diff options
Diffstat (limited to 'mailnews/base/src/nsMsgFolderCache.cpp')
-rw-r--r-- | mailnews/base/src/nsMsgFolderCache.cpp | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/mailnews/base/src/nsMsgFolderCache.cpp b/mailnews/base/src/nsMsgFolderCache.cpp new file mode 100644 index 000000000..9510a6e3d --- /dev/null +++ b/mailnews/base/src/nsMsgFolderCache.cpp @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgFolderCacheElement.h" +#include "nsMsgFolderCache.h" +#include "nsMorkCID.h" +#include "nsIMdbFactoryFactory.h" +#include "nsMsgBaseCID.h" +#include "nsServiceManagerUtils.h" + +const char *kFoldersScope = "ns:msg:db:row:scope:folders:all"; // scope for all folders table +const char *kFoldersTableKind = "ns:msg:db:table:kind:folders"; + +nsMsgFolderCache::nsMsgFolderCache() +{ + m_mdbEnv = nullptr; + m_mdbStore = nullptr; + m_mdbAllFoldersTable = nullptr; +} + +// should this, could this be an nsCOMPtr ? +static nsIMdbFactory *gMDBFactory = nullptr; + +nsMsgFolderCache::~nsMsgFolderCache() +{ + m_cacheElements.Clear(); // make sure the folder cache elements are released before we release our m_mdb objects... + if (m_mdbAllFoldersTable) + m_mdbAllFoldersTable->Release(); + if (m_mdbStore) + m_mdbStore->Release(); + NS_IF_RELEASE(gMDBFactory); + if (m_mdbEnv) + m_mdbEnv->Release(); +} + + +NS_IMPL_ISUPPORTS(nsMsgFolderCache, nsIMsgFolderCache) + +void nsMsgFolderCache::GetMDBFactory(nsIMdbFactory ** aMdbFactory) +{ + if (!mMdbFactory) + { + nsresult rv; + nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mdbFactoryService) + rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory)); + } + NS_IF_ADDREF(*aMdbFactory = mMdbFactory); +} + +// initialize the various tokens and tables in our db's env +nsresult nsMsgFolderCache::InitMDBInfo() +{ + nsresult err = NS_OK; + if (GetStore()) + { + err = GetStore()->StringToToken(GetEnv(), kFoldersScope, &m_folderRowScopeToken); + if (NS_SUCCEEDED(err)) + { + err = GetStore()->StringToToken(GetEnv(), kFoldersTableKind, &m_folderTableKindToken); + if (NS_SUCCEEDED(err)) + { + // The table of all message hdrs will have table id 1. + m_allFoldersTableOID.mOid_Scope = m_folderRowScopeToken; + m_allFoldersTableOID.mOid_Id = 1; + } + } + } + return err; +} + +// set up empty tables, dbFolderInfo, etc. +nsresult nsMsgFolderCache::InitNewDB() +{ + nsresult err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) + { + nsIMdbStore *store = GetStore(); + // create the unique table for the dbFolderInfo. + // TODO: this error assignment is suspicious and never checked. + (void) store->NewTable(GetEnv(), m_folderRowScopeToken, m_folderTableKindToken, + false, nullptr, &m_mdbAllFoldersTable); + } + return err; +} + +nsresult nsMsgFolderCache::InitExistingDB() +{ + nsresult err = InitMDBInfo(); + if (NS_FAILED(err)) + return err; + + err = GetStore()->GetTable(GetEnv(), &m_allFoldersTableOID, &m_mdbAllFoldersTable); + if (NS_SUCCEEDED(err) && m_mdbAllFoldersTable) + { + nsIMdbTableRowCursor* rowCursor = nullptr; + err = m_mdbAllFoldersTable->GetTableRowCursor(GetEnv(), -1, &rowCursor); + if (NS_SUCCEEDED(err) && rowCursor) + { + // iterate over the table rows and create nsMsgFolderCacheElements for each. + while (true) + { + nsresult rv; + nsIMdbRow* hdrRow; + mdb_pos rowPos; + + rv = rowCursor->NextRow(GetEnv(), &hdrRow, &rowPos); + if (NS_FAILED(rv) || !hdrRow) + break; + + rv = AddCacheElement(EmptyCString(), hdrRow, nullptr); + hdrRow->Release(); + if (NS_FAILED(rv)) + return rv; + } + rowCursor->Release(); + } + } + else + err = NS_ERROR_FAILURE; + + return err; +} + +nsresult nsMsgFolderCache::OpenMDB(const nsACString& dbName, bool exists) +{ + nsresult ret=NS_OK; + nsCOMPtr<nsIMdbFactory> mdbFactory; + GetMDBFactory(getter_AddRefs(mdbFactory)); + if (mdbFactory) + { + ret = mdbFactory->MakeEnv(nullptr, &m_mdbEnv); + if (NS_SUCCEEDED(ret)) + { + nsIMdbThumb *thumb = nullptr; + nsIMdbHeap* dbHeap = nullptr; + + if (m_mdbEnv) + m_mdbEnv->SetAutoClear(true); + if (exists) + { + mdbOpenPolicy inOpenPolicy; + mdb_bool canOpen; + mdbYarn outFormatVersion; + + nsIMdbFile* oldFile = nullptr; + ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, nsCString(dbName).get(), + mdbBool_kFalse, // not readonly, we want modifiable + &oldFile); + if ( oldFile ) + { + if (NS_SUCCEEDED(ret)) + { + ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // file to investigate + &canOpen, &outFormatVersion); + if (NS_SUCCEEDED(ret) && canOpen) + { + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->OpenFileStore(m_mdbEnv, NULL, oldFile, &inOpenPolicy, + &thumb); + } + else + ret = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + NS_RELEASE(oldFile); // always release our file ref, store has own + } + } + if (NS_SUCCEEDED(ret) && thumb) + { + mdb_count outTotal; // total somethings to do in operation + mdb_count outCurrent; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken; // is operation irreparably dead and broken? + do + { + ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken); + if (NS_FAILED(ret)) + { + outDone = true; + break; + } + } + while (NS_SUCCEEDED(ret) && !outBroken && !outDone); + // m_mdbEnv->ClearErrors(); // ### temporary... + if (NS_SUCCEEDED(ret) && outDone) + { + ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore); + if (NS_SUCCEEDED(ret) && m_mdbStore) + ret = InitExistingDB(); + } +#ifdef DEBUG_bienvenu1 + DumpContents(); +#endif + } + else // ### need error code saying why open file store failed + { + nsIMdbFile* newFile = 0; + ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, nsCString(dbName).get(), &newFile); + if ( newFile ) + { + if (NS_SUCCEEDED(ret)) + { + mdbOpenPolicy inOpenPolicy; + + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap, + newFile, &inOpenPolicy, &m_mdbStore); + if (NS_SUCCEEDED(ret)) + ret = InitNewDB(); + } + NS_RELEASE(newFile); // always release our file ref, store has own + } + + } + NS_IF_RELEASE(thumb); + } + } + return ret; +} + +NS_IMETHODIMP nsMsgFolderCache::Init(nsIFile *aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + + bool exists; + aFile->Exists(&exists); + + nsAutoCString dbPath; + aFile->GetNativePath(dbPath); + // ### evil cast until MDB supports file streams. + nsresult rv = OpenMDB(dbPath, exists); + // if this fails and panacea.dat exists, try blowing away the db and recreating it + if (NS_FAILED(rv) && exists) + { + if (m_mdbStore) + m_mdbStore->Release(); + aFile->Remove(false); + rv = OpenMDB(dbPath, false); + } + return rv; +} + +NS_IMETHODIMP nsMsgFolderCache::GetCacheElement(const nsACString& pathKey, bool createIfMissing, + nsIMsgFolderCacheElement **result) +{ + NS_ENSURE_ARG_POINTER(result); + NS_ENSURE_TRUE(!pathKey.IsEmpty(), NS_ERROR_FAILURE); + + nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl; + m_cacheElements.Get(pathKey, getter_AddRefs(folderCacheEl)); + folderCacheEl.swap(*result); + + if (*result) + return NS_OK; + else if (createIfMissing) + { + nsIMdbRow* hdrRow; + + if (GetStore()) + { + nsresult err = GetStore()->NewRow(GetEnv(), m_folderRowScopeToken, // row scope for row ids + &hdrRow); + if (NS_SUCCEEDED(err) && hdrRow) + { + m_mdbAllFoldersTable->AddRow(GetEnv(), hdrRow); + nsresult ret = AddCacheElement(pathKey, hdrRow, result); + if (*result) + (*result)->SetStringProperty("key", pathKey); + hdrRow->Release(); + return ret; + } + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgFolderCache::RemoveElement(const nsACString& key) +{ + nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl; + m_cacheElements.Get(key, getter_AddRefs(folderCacheEl)); + if (!folderCacheEl) + return NS_ERROR_FAILURE; + nsMsgFolderCacheElement *element = static_cast<nsMsgFolderCacheElement *>(static_cast<nsISupports *>(folderCacheEl.get())); // why the double cast?? + m_mdbAllFoldersTable->CutRow(GetEnv(), element->m_mdbRow); + m_cacheElements.Remove(key); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCache::Clear() +{ + m_cacheElements.Clear(); + if (m_mdbAllFoldersTable) + m_mdbAllFoldersTable->CutAllRows(GetEnv()); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFolderCache::Close() +{ + return Commit(true); +} + +NS_IMETHODIMP nsMsgFolderCache::Commit(bool compress) +{ + nsresult ret = NS_OK; + nsIMdbThumb *commitThumb = nullptr; + if (m_mdbStore) + { + if (compress) + ret = m_mdbStore->CompressCommit(GetEnv(), &commitThumb); + else + ret = m_mdbStore->LargeCommit(GetEnv(), &commitThumb); + } + + if (commitThumb) + { + mdb_count outTotal = 0; // total somethings to do in operation + mdb_count outCurrent = 0; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken = false; // is operation irreparably dead and broken? + while (!outDone && !outBroken && NS_SUCCEEDED(ret)) + ret = commitThumb->DoMore(GetEnv(), &outTotal, &outCurrent, &outDone, &outBroken); + NS_IF_RELEASE(commitThumb); + } + // ### do something with error, but clear it now because mork errors out on commits. + if (GetEnv()) + GetEnv()->ClearErrors(); + return ret; +} + +nsresult nsMsgFolderCache::AddCacheElement(const nsACString& key, nsIMdbRow *row, nsIMsgFolderCacheElement **result) +{ + nsMsgFolderCacheElement *cacheElement = new nsMsgFolderCacheElement; + NS_ENSURE_TRUE(cacheElement, NS_ERROR_OUT_OF_MEMORY); + nsCOMPtr<nsIMsgFolderCacheElement> folderCacheEl(do_QueryInterface(cacheElement)); + + cacheElement->SetMDBRow(row); + cacheElement->SetOwningCache(this); + nsCString hashStrKey(key); + // if caller didn't pass in key, try to get it from row. + if (key.IsEmpty()) + folderCacheEl->GetStringProperty("key", hashStrKey); + folderCacheEl->SetKey(hashStrKey); + m_cacheElements.Put(hashStrKey, folderCacheEl); + if (result) + folderCacheEl.swap(*result); + return NS_OK; +} + +nsresult nsMsgFolderCache::RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, nsACString& resultStr) +{ + nsresult err = NS_OK; + nsIMdbCell *hdrCell; + if (hdrRow) // ### probably should be an error if hdrRow is NULL... + { + err = hdrRow->GetCell(GetEnv(), columnToken, &hdrCell); + if (NS_SUCCEEDED(err) && hdrCell) + { + struct mdbYarn yarn; + hdrCell->AliasYarn(GetEnv(), &yarn); + resultStr.Assign((const char *)yarn.mYarn_Buf, yarn.mYarn_Fill); + resultStr.SetLength(yarn.mYarn_Fill); // ensure the string is null terminated. + hdrCell->Release(); // always release ref + } + } + return err; +} |