Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; 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 "nsMailDatabase.h"
#include "nsDBFolderInfo.h"
#include "nsMsgLocalFolderHdrs.h"
#include "nsNetUtil.h"
#include "nsMsgOfflineImapOperation.h"
#include "nsMsgFolderFlags.h"
#include "mozilla/Logging.h"
#include "prprf.h"
#include "nsMsgUtils.h"
#include "nsIMsgPluggableStore.h"
#include "nsSimpleEnumerator.h"
using namespace mozilla;
extern LazyLogModule IMAPOffline; // defined in nsMsgOfflineImapOperation.cpp
// scope for all offine ops table
const char* kOfflineOpsScope = "ns:msg:db:row:scope:ops:all";
const char* kOfflineOpsTableKind = "ns:msg:db:table:kind:ops";
struct mdbOid gAllOfflineOpsTableOID;
nsMailDatabase::nsMailDatabase() : m_reparse(false) {
m_mdbAllOfflineOpsTable = nullptr;
m_offlineOpsRowScopeToken = 0;
m_offlineOpsTableKindToken = 0;
}
nsMailDatabase::~nsMailDatabase() {}
// caller passes in upgrading==true if they want back a db even if the db is out
// of date. If so, they'll extract out the interesting info from the db, close
// it, delete it, and then try to open the db again, prior to reparsing.
nsresult nsMailDatabase::Open(nsMsgDBService* aDBService, nsIFile* aSummaryFile,
bool aCreate, bool aUpgrading) {
#ifdef DEBUG
nsString leafName;
aSummaryFile->GetLeafName(leafName);
if (!StringEndsWith(leafName, NS_LITERAL_STRING_FROM_CSTRING(SUMMARY_SUFFIX),
nsCaseInsensitiveStringComparator))
NS_ERROR("non summary file passed into open");
#endif
return nsMsgDatabase::Open(aDBService, aSummaryFile, aCreate, aUpgrading);
}
NS_IMETHODIMP nsMailDatabase::ForceClosed() {
m_mdbAllOfflineOpsTable = nullptr;
return nsMsgDatabase::ForceClosed();
}
// get this on demand so that only db's that have offline ops will
// create the table.
nsresult nsMailDatabase::GetAllOfflineOpsTable() {
nsresult rv = NS_OK;
if (!m_mdbAllOfflineOpsTable)
rv = GetTableCreateIfMissing(kOfflineOpsScope, kOfflineOpsTableKind,
getter_AddRefs(m_mdbAllOfflineOpsTable),
m_offlineOpsRowScopeToken,
m_offlineOpsTableKindToken);
return rv;
}
NS_IMETHODIMP nsMailDatabase::DeleteMessages(
nsTArray<nsMsgKey> const& nsMsgKeys, nsIDBChangeListener* instigator) {
nsresult rv;
if (m_folder) {
bool isLocked;
m_folder->GetLocked(&isLocked);
if (isLocked) {
NS_ASSERTION(false, "Some other operation is in progress");
return NS_MSG_FOLDER_BUSY;
}
}
rv = nsMsgDatabase::DeleteMessages(nsMsgKeys, instigator);
SetSummaryValid(true);
return rv;
}
NS_IMETHODIMP nsMailDatabase::GetSummaryValid(bool* aResult) {
uint32_t version;
m_dbFolderInfo->GetVersion(&version);
if (GetCurVersion() != version) {
*aResult = false;
return NS_OK;
}
if (!m_folder) {
// If the folder is not set, we just return without checking the validity
// of the summary file. For now, this is an expected condition when the
// message database is being opened from a URL in
// nsMailboxUrl::GetMsgHdrForKey() which calls
// nsMsgDBService::OpenMailDBFromFile() without a folder.
// Returning an error here would lead to the deletion of the MSF in the
// caller nsMsgDatabase::CheckForErrors().
*aResult = true;
return NS_OK;
}
// If this is a virtual folder, there is no storage.
bool isVirtual = false;
m_folder->GetFlag(nsMsgFolderFlags::Virtual, &isVirtual);
if (isVirtual) {
*aResult = true;
return NS_OK;
}
nsCOMPtr<nsIMsgPluggableStore> msgStore;
nsresult rv = m_folder->GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
return msgStore->IsSummaryFileValid(m_folder, this, aResult);
}
NS_IMETHODIMP nsMailDatabase::SetSummaryValid(bool aValid) {
nsMsgDatabase::SetSummaryValid(aValid);
if (!m_folder) return NS_ERROR_NULL_POINTER;
// If this is a virtual folder, there is no storage.
bool flag;
m_folder->GetFlag(nsMsgFolderFlags::Virtual, &flag);
if (flag) return NS_OK;
nsCOMPtr<nsIMsgPluggableStore> msgStore;
nsresult rv = m_folder->GetMsgStore(getter_AddRefs(msgStore));
NS_ENSURE_SUCCESS(rv, rv);
return msgStore->SetSummaryFileValid(m_folder, this, aValid);
}
NS_IMETHODIMP nsMailDatabase::RemoveOfflineOp(nsIMsgOfflineImapOperation* op) {
nsresult rv = GetAllOfflineOpsTable();
NS_ENSURE_SUCCESS(rv, rv);
if (!op || !m_mdbAllOfflineOpsTable) return NS_ERROR_NULL_POINTER;
nsMsgOfflineImapOperation* offlineOp =
static_cast<nsMsgOfflineImapOperation*>(
op); // closed system, so this is ok
nsIMdbRow* row = offlineOp->GetMDBRow();
rv = m_mdbAllOfflineOpsTable->CutRow(GetEnv(), row);
row->CutAllColumns(GetEnv());
return rv;
}
NS_IMETHODIMP nsMailDatabase::GetOfflineOpForKey(
nsMsgKey msgKey, bool create, nsIMsgOfflineImapOperation** offlineOp) {
mdb_bool hasOid;
mdbOid rowObjectId;
nsresult err;
nsresult rv = GetAllOfflineOpsTable();
NS_ENSURE_SUCCESS(rv, rv);
if (!offlineOp || !m_mdbAllOfflineOpsTable) return NS_ERROR_NULL_POINTER;
*offlineOp = NULL;
rowObjectId.mOid_Id = msgKey;
rowObjectId.mOid_Scope = m_offlineOpsRowScopeToken;
err = m_mdbAllOfflineOpsTable->HasOid(GetEnv(), &rowObjectId, &hasOid);
if (NS_SUCCEEDED(err) && m_mdbStore && (hasOid || create)) {
nsCOMPtr<nsIMdbRow> offlineOpRow;
err = m_mdbStore->GetRow(GetEnv(), &rowObjectId,
getter_AddRefs(offlineOpRow));
if (create) {
if (!offlineOpRow) {
err = m_mdbStore->NewRowWithOid(GetEnv(), &rowObjectId,
getter_AddRefs(offlineOpRow));
NS_ENSURE_SUCCESS(err, err);
}
if (offlineOpRow && !hasOid)
m_mdbAllOfflineOpsTable->AddRow(GetEnv(), offlineOpRow);
}
if (NS_SUCCEEDED(err) && offlineOpRow) {
NS_IF_ADDREF(*offlineOp =
new nsMsgOfflineImapOperation(this, offlineOpRow));
(*offlineOp)->SetMessageKey(msgKey);
}
if (!hasOid && m_dbFolderInfo) {
// set initial value for flags so we don't lose them.
nsCOMPtr<nsIMsgDBHdr> msgHdr;
GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
if (msgHdr) {
uint32_t flags;
msgHdr->GetFlags(&flags);
(*offlineOp)->SetNewFlags(flags);
}
int32_t newFlags;
m_dbFolderInfo->OrFlags(nsMsgFolderFlags::OfflineEvents, &newFlags);
}
}
return err;
}
NS_IMETHODIMP nsMailDatabase::ListAllOfflineOpIds(
nsTArray<nsMsgKey>& offlineOpIds) {
nsresult rv = GetAllOfflineOpsTable();
NS_ENSURE_SUCCESS(rv, rv);
nsIMdbTableRowCursor* rowCursor;
if (m_mdbAllOfflineOpsTable) {
nsresult err =
m_mdbAllOfflineOpsTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
while (NS_SUCCEEDED(err) && rowCursor) {
mdbOid outOid;
mdb_pos outPos;
err = rowCursor->NextRowOid(GetEnv(), &outOid, &outPos);
// is this right? Mork is returning a 0 id, but that should valid.
if (outPos < 0 || outOid.mOid_Id == (mdb_id)-1) break;
if (NS_SUCCEEDED(err)) {
offlineOpIds.AppendElement(outOid.mOid_Id);
if (MOZ_LOG_TEST(IMAPOffline, LogLevel::Info)) {
nsCOMPtr<nsIMsgOfflineImapOperation> offlineOp;
GetOfflineOpForKey(outOid.mOid_Id, false, getter_AddRefs(offlineOp));
if (offlineOp) {
nsMsgOfflineImapOperation* logOp =
static_cast<nsMsgOfflineImapOperation*>(
static_cast<nsIMsgOfflineImapOperation*>(offlineOp.get()));
if (logOp) logOp->Log();
}
}
}
}
// TODO: would it cause a problem to replace this with "rv = err;" ?
rv = (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
rowCursor->Release();
}
offlineOpIds.Sort();
return rv;
}
NS_IMETHODIMP nsMailDatabase::ListAllOfflineDeletes(
nsTArray<nsMsgKey>& offlineDeletes) {
nsresult rv = GetAllOfflineOpsTable();
NS_ENSURE_SUCCESS(rv, rv);
nsIMdbTableRowCursor* rowCursor;
if (m_mdbAllOfflineOpsTable) {
nsresult err =
m_mdbAllOfflineOpsTable->GetTableRowCursor(GetEnv(), -1, &rowCursor);
while (NS_SUCCEEDED(err) && rowCursor) {
mdbOid outOid;
mdb_pos outPos;
nsIMdbRow* offlineOpRow;
err = rowCursor->NextRow(GetEnv(), &offlineOpRow, &outPos);
// is this right? Mork is returning a 0 id, but that should valid.
if (outPos < 0 || offlineOpRow == nullptr) break;
if (NS_SUCCEEDED(err)) {
offlineOpRow->GetOid(GetEnv(), &outOid);
RefPtr<nsIMsgOfflineImapOperation> offlineOp =
new nsMsgOfflineImapOperation(this, offlineOpRow);
imapMessageFlagsType newFlags;
nsOfflineImapOperationType opType;
offlineOp->GetOperation(&opType);
offlineOp->GetNewFlags(&newFlags);
if (opType & nsIMsgOfflineImapOperation::kMsgMoved ||
((opType & nsIMsgOfflineImapOperation::kFlagsChanged) &&
(newFlags & nsIMsgOfflineImapOperation::kMsgMarkedDeleted)))
offlineDeletes.AppendElement(outOid.mOid_Id);
offlineOpRow->Release();
}
}
// TODO: would it cause a problem to replace this with "rv = err;" ?
rv = (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE;
rowCursor->Release();
}
return rv;
}
// This is used to remember that the db is out of sync with the mail folder
// and needs to be regenerated.
void nsMailDatabase::SetReparse(bool reparse) { m_reparse = reparse; }
class nsMsgOfflineOpEnumerator : public nsSimpleEnumerator {
public:
const nsID& DefaultInterface() override {
return NS_GET_IID(nsIMsgOfflineImapOperation);
}
// nsISimpleEnumerator methods:
NS_DECL_NSISIMPLEENUMERATOR
explicit nsMsgOfflineOpEnumerator(nsMailDatabase* db);
protected:
~nsMsgOfflineOpEnumerator() override;
nsresult GetRowCursor();
nsresult PrefetchNext();
nsMailDatabase* mDB;
nsIMdbTableRowCursor* mRowCursor;
nsCOMPtr<nsIMsgOfflineImapOperation> mResultOp;
bool mDone;
bool mNextPrefetched;
};
nsMsgOfflineOpEnumerator::nsMsgOfflineOpEnumerator(nsMailDatabase* db)
: mDB(db), mRowCursor(nullptr), mDone(false) {
NS_ADDREF(mDB);
mNextPrefetched = false;
}
nsMsgOfflineOpEnumerator::~nsMsgOfflineOpEnumerator() {
NS_IF_RELEASE(mRowCursor);
NS_RELEASE(mDB);
}
nsresult nsMsgOfflineOpEnumerator::GetRowCursor() {
nsresult rv = NS_OK;
mDone = false;
if (!mDB || !mDB->m_mdbAllOfflineOpsTable) return NS_ERROR_NULL_POINTER;
rv = mDB->m_mdbAllOfflineOpsTable->GetTableRowCursor(mDB->GetEnv(), -1,
&mRowCursor);
return rv;
}
NS_IMETHODIMP nsMsgOfflineOpEnumerator::GetNext(nsISupports** aItem) {
NS_ENSURE_ARG_POINTER(aItem);
nsresult rv = NS_OK;
if (!mNextPrefetched) rv = PrefetchNext();
if (NS_SUCCEEDED(rv)) {
if (mResultOp) {
NS_ADDREF(*aItem = mResultOp);
mNextPrefetched = false;
}
}
return rv;
}
nsresult nsMsgOfflineOpEnumerator::PrefetchNext() {
nsresult rv = NS_OK;
nsIMdbRow* offlineOpRow;
mdb_pos rowPos;
if (!mRowCursor) {
rv = GetRowCursor();
if (NS_FAILED(rv)) return rv;
}
rv = mRowCursor->NextRow(mDB->GetEnv(), &offlineOpRow, &rowPos);
if (!offlineOpRow) {
mDone = true;
return NS_ERROR_FAILURE;
}
if (NS_FAILED(rv)) {
mDone = true;
return rv;
}
nsIMsgOfflineImapOperation* op =
new nsMsgOfflineImapOperation(mDB, offlineOpRow);
mResultOp = op;
if (!op) return NS_ERROR_OUT_OF_MEMORY;
if (mResultOp) {
mNextPrefetched = true;
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP nsMsgOfflineOpEnumerator::HasMoreElements(bool* aResult) {
NS_ENSURE_ARG_POINTER(aResult);
if (!mNextPrefetched) PrefetchNext();
*aResult = !mDone;
return NS_OK;
}