489 lines
13 KiB
C++
489 lines
13 KiB
C++
/* -*- 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 <pp@ludusdesign.com>
|
|
*
|
|
* 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 <nsIMdbFactoryFactory> 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<nsISupports> 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;
|
|
}
|