/* -*- 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 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 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 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 folderCacheEl; m_cacheElements.Get(key, getter_AddRefs(folderCacheEl)); if (!folderCacheEl) return NS_ERROR_FAILURE; nsMsgFolderCacheElement *element = static_cast(static_cast(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 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; }