/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "msgCore.h" #include "nsIMsgAccountManager.h" #include "nsMsgFolderCacheElement.h" #include "nsMsgFolderCache.h" #include "nsMorkCID.h" #include "nsIMdbFactoryFactory.h" #include "nsFileStream.h" #include "nsIFileSpec.h" #include "nsXPIDLString.h" #include "nsMsgBaseCID.h" static NS_DEFINE_CID(kMorkCID, NS_MORK_CID); static NS_DEFINE_CID(kCMsgMailSessionCID, NS_MSGMAILSESSION_CID); 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 = nsnull; m_mdbStore = nsnull; NS_INIT_REFCNT(); m_mdbAllFoldersTable = nsnull; } // should this, could this be an nsCOMPtr ? static nsIMdbFactory *gMDBFactory = nsnull; nsMsgFolderCache::~nsMsgFolderCache() { delete m_cacheElements; if (m_mdbAllFoldersTable) m_mdbAllFoldersTable->Release(); if (m_mdbStore) m_mdbStore->Release(); if (gMDBFactory) { NS_RELEASE(gMDBFactory); } gMDBFactory = nsnull; if (m_mdbEnv) { m_mdbEnv->Release(); } } NS_IMPL_ADDREF(nsMsgFolderCache) NS_IMPL_RELEASE(nsMsgFolderCache) nsresult nsMsgFolderCache::QueryInterface(const nsIID& iid, void **result) { nsresult rv = NS_NOINTERFACE; if (! result) return NS_ERROR_NULL_POINTER; void *res = nsnull; if (iid.Equals(NS_GET_IID(nsIMsgFolderCache)) || iid.Equals(NS_GET_IID(nsISupports))) res = NS_STATIC_CAST(nsIMsgFolderCache*, this); if (res) { NS_ADDREF(this); *result = res; rv = NS_OK; } return rv; } /* static */ nsIMdbFactory *nsMsgFolderCache::GetMDBFactory() { if (!gMDBFactory) { nsresult rv; nsCOMPtr factoryfactory; rv = nsComponentManager::CreateInstance(kMorkCID, nsnull, NS_GET_IID(nsIMdbFactoryFactory), (void **) getter_AddRefs(factoryfactory)); if (NS_SUCCEEDED(rv) && factoryfactory) rv = factoryfactory->GetMdbFactory(&gMDBFactory); } return gMDBFactory; } // 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 (err == NS_OK) { err = GetStore()->StringToToken(GetEnv(), kFoldersTableKind, &m_folderTableKindToken); if (err == NS_OK) { // 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 = NS_OK; err = InitMDBInfo(); if (err == NS_OK) { nsIMdbStore *store = GetStore(); // create the unique table for the dbFolderInfo. mdb_err mdberr; mdberr = (nsresult) store->NewTable(GetEnv(), m_folderRowScopeToken, m_folderTableKindToken, PR_FALSE, nsnull, &m_mdbAllFoldersTable); } return err; } nsresult nsMsgFolderCache::InitExistingDB() { nsresult err = NS_OK; err = InitMDBInfo(); if (err == NS_OK) { err = GetStore()->GetTable(GetEnv(), &m_allFoldersTableOID, &m_mdbAllFoldersTable); if (NS_SUCCEEDED(err) && m_mdbAllFoldersTable) { nsIMdbTableRowCursor* rowCursor = nsnull; err = m_mdbAllFoldersTable->GetTableRowCursor(GetEnv(), -1, &rowCursor); if (NS_SUCCEEDED(err) && rowCursor) { // iterate over the table rows and create nsMsgFolderCacheElements for each. while (PR_TRUE) { nsresult rv; nsIMdbRow* hdrRow; mdb_pos rowPos; rv = rowCursor->NextRow(GetEnv(), &hdrRow, &rowPos); if (NS_FAILED(rv) || !hdrRow) break; rv = AddCacheElement(nsnull, hdrRow, nsnull); hdrRow->Release(); if (NS_FAILED(rv)) return rv; } rowCursor->Release(); } } else err = NS_ERROR_FAILURE; } return err; } nsresult nsMsgFolderCache::OpenMDB(const char *dbName, PRBool exists) { nsresult ret=NS_OK; nsIMdbFactory *myMDBFactory = GetMDBFactory(); if (myMDBFactory) { ret = myMDBFactory->MakeEnv(nsnull, &m_mdbEnv); if (NS_SUCCEEDED(ret)) { nsIMdbThumb *thumb = nsnull; char *nativeFileName = nsCRT::strdup(dbName); nsIMdbHeap* dbHeap = 0; mdb_bool dbFrozen = mdbBool_kFalse; // not readonly, we want modifiable if (!nativeFileName) return NS_ERROR_OUT_OF_MEMORY; if (m_mdbEnv) m_mdbEnv->SetAutoClear(PR_TRUE); #if defined(XP_PC) || defined(XP_MAC) // UnixToNative(nativeFileName); #endif if (exists) { mdbOpenPolicy inOpenPolicy; mdb_bool canOpen; mdbYarn outFormatVersion; nsIMdbFile* oldFile = 0; ret = myMDBFactory->OpenOldFile(m_mdbEnv, dbHeap, nativeFileName, dbFrozen, &oldFile); if ( oldFile ) { if ( ret == NS_OK ) { ret = myMDBFactory->CanOpenFilePort(m_mdbEnv, oldFile, // file to investigate &canOpen, &outFormatVersion); if (ret == 0 && canOpen) { inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; inOpenPolicy.mOpenPolicy_MinMemory = 0; inOpenPolicy.mOpenPolicy_MaxLazy = 0; ret = myMDBFactory->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 = PR_FALSE; // is operation finished? mdb_bool outBroken; // is operation irreparably dead and broken? do { ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken); if (ret != 0) {// mork isn't really doing NS erorrs yet. outDone = PR_TRUE; break; } } while (NS_SUCCEEDED(ret) && !outBroken && !outDone); // m_mdbEnv->ClearErrors(); // ### temporary... if (NS_SUCCEEDED(ret) && outDone) { ret = myMDBFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore); if (ret == NS_OK && m_mdbStore) ret = InitExistingDB(); } #ifdef DEBUG_bienvenu1 DumpContents(); #endif } else // ### need error code saying why open file store failed { nsIMdbFile* newFile = 0; ret = myMDBFactory->CreateNewFile(m_mdbEnv, dbHeap, dbName, &newFile); if ( newFile ) { if (ret == NS_OK) { mdbOpenPolicy inOpenPolicy; inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; inOpenPolicy.mOpenPolicy_MinMemory = 0; inOpenPolicy.mOpenPolicy_MaxLazy = 0; ret = myMDBFactory->CreateNewFileStore(m_mdbEnv, dbHeap, newFile, &inOpenPolicy, &m_mdbStore); if (ret == NS_OK) ret = InitNewDB(); } NS_RELEASE(newFile); // always release our file ref, store has own } } NS_IF_RELEASE(thumb); nsCRT::free(nativeFileName); } } return ret; } NS_IMETHODIMP nsMsgFolderCache::Init(nsIFileSpec *dbFileSpec) { if (!dbFileSpec) return NS_ERROR_NULL_POINTER; nsresult rv = NS_ERROR_OUT_OF_MEMORY; m_cacheElements = new nsSupportsHashtable; if (m_cacheElements) { rv = dbFileSpec->GetFileSpec(&m_dbFileSpec); if (NS_SUCCEEDED(rv)) { PRBool exists = m_dbFileSpec.Exists(); // ### evil cast until MDB supports file streams. rv = OpenMDB((const char *) m_dbFileSpec, 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(); m_dbFileSpec.Delete(PR_FALSE); rv = OpenMDB((const char *) m_dbFileSpec, PR_FALSE); } } } return rv; } NS_IMETHODIMP nsMsgFolderCache::GetCacheElement(const char *pathKey, PRBool createIfMissing, nsIMsgFolderCacheElement **result) { if (!result || !pathKey) return NS_ERROR_NULL_POINTER; if (strlen(pathKey) == 0) { return NS_ERROR_FAILURE; } nsCStringKey hashKey(pathKey); *result = (nsIMsgFolderCacheElement *) m_cacheElements->Get(&hashKey); // nsHashTable already does an address on *result if (*result) { return NS_OK; } else if (createIfMissing) { nsIMdbRow* hdrRow; if (GetStore()) { mdb_err 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::Close() { return Commit(PR_TRUE); } NS_IMETHODIMP nsMsgFolderCache::Commit(PRBool compress) { nsresult ret = NS_OK; nsIMdbThumb *commitThumb = nsnull; 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 = PR_FALSE; // is operation finished? mdb_bool outBroken = PR_FALSE; // is operation irreparably dead and broken? while (!outDone && !outBroken && ret == NS_OK) { 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 char *key, nsIMdbRow *row, nsIMsgFolderCacheElement **result) { nsMsgFolderCacheElement *cacheElement = new nsMsgFolderCacheElement; if (cacheElement) { cacheElement->SetMDBRow(row); cacheElement->SetOwningCache(this); nsCAutoString hashStrKey(key); // if caller didn't pass in key, try to get it from row. if (!key) { char *existingKey = nsnull; cacheElement->GetStringProperty("key", &existingKey); cacheElement->SetKey(existingKey); hashStrKey = existingKey; PR_Free(existingKey); } else cacheElement->SetKey((char *) key); nsCOMPtr supports(do_QueryInterface(cacheElement)); if(supports) { nsCStringKey hashKey(hashStrKey); m_cacheElements->Put(&hashKey, supports); } if (result) { *result = cacheElement; NS_ADDREF(*result); } return NS_OK; } else return NS_ERROR_OUT_OF_MEMORY; } nsresult nsMsgFolderCache::RowCellColumnToCharPtr(nsIMdbRow *hdrRow, mdb_token columnToken, char **resultPtr) { nsresult err = NS_OK; nsIMdbCell *hdrCell; if (hdrRow) // ### probably should be an error if hdrRow is NULL... { err = hdrRow->GetCell(GetEnv(), columnToken, &hdrCell); if (err == NS_OK && hdrCell) { struct mdbYarn yarn; hdrCell->AliasYarn(GetEnv(), &yarn); char *result = (char *) PR_Malloc(yarn.mYarn_Fill + 1); if (result) { memcpy(result, yarn.mYarn_Buf, yarn.mYarn_Fill); result[yarn.mYarn_Fill] = '\0'; } else err = NS_ERROR_OUT_OF_MEMORY; *resultPtr = result; hdrCell->Release(); // always release ref } } return err; }