/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * 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 Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "msg.h" #include "xp.h" #include "maildb.h" #include "dberror.h" #include "msgdbvw.h" #include "mailhdr.h" #include "thrhead.h" #include "grpinfo.h" #include "imap.h" #include "msgmast.h" #include "msgprefs.h" #include "msgimap.h" #include "imapoff.h" #include "prefapi.h" #include "msgdbapi.h" extern "C" { extern int MK_MSG_CANT_OPEN; } MailDB::MailDB() { m_folderName = NULL; m_reparse = FALSE; m_folderFile = NULL; m_master = NULL; } MailDB::~MailDB() { FREEIF(m_folderName); } MsgERR MailDB::OnNewPath (const char *newPath) { FREEIF(m_folderName); m_folderName = XP_STRDUP (newPath); return eSUCCESS; } // static method. This routine works in very different ways depending on the passed flags. // If create is true, we will create a database if none was there. // If upgrading is TRUE, the caller is upgrading and does not care that the database is // gone, or out of sync with the mail folder. The caller will upgrade in the background // and just wants a db open to upgrade into. MsgERR MailDB::Open(const char * folderName, XP_Bool create, MailDB** pMessageDB, XP_Bool upgrading /*=FALSE*/) { MailDB *mailDB; int statResult; XP_StatStruct st; DBFolderInfo *folderInfo = NULL; XP_Bool newFile = FALSE; char *dbName; *pMessageDB = NULL; if (m_cacheEnabled) { dbName = WH_FileName(folderName, xpMailFolderSummary); if (!dbName) return eOUT_OF_MEMORY; mailDB = (MailDB *) FindInCache(dbName); XP_FREE(dbName); if (mailDB) { *pMessageDB = mailDB; // make this behave like the non-cache case. Is global dB set? DMB TODO ++(mailDB->m_useCount); return(eSUCCESS); } } // if the old summary doesn't exist, we're creating a new one. if (XP_Stat (folderName, &st, xpMailFolderSummary) && create) newFile = TRUE; mailDB = new MailDB; if (!mailDB) return(eOUT_OF_MEMORY); mailDB->m_folderName = XP_STRDUP(folderName); dbName = WH_FileName(folderName, xpMailFolderSummary); if (!dbName) return eOUT_OF_MEMORY; // stat file before we open the db, because if we've latered // any messages, handling latered will change time stamp on // folder file. statResult = XP_Stat (folderName, &st, xpMailFolder); MsgERR err = mailDB->MessageDBOpen(dbName, create); XP_FREE(dbName); if (err == eSUCCESS) { folderInfo = mailDB->GetDBFolderInfo(); if (folderInfo == NULL) { err = eOldSummaryFile; } else { // if opening existing file, make sure summary file is up to date. // if caller is upgrading, don't return eOldSummaryFile so the caller // can pull out the transfer info for the new db. if (!newFile && !statResult && !upgrading) { if (folderInfo->m_folderSize != st.st_size || folderInfo->m_folderDate != st.st_mtime || folderInfo->GetNumNewMessages() < 0) err = eOldSummaryFile; } // compare current version of db versus filed out version info. if (mailDB->GetCurVersion() != folderInfo->GetDiskVersion()) err = eOldSummaryFile; } if (err != eSUCCESS) { mailDB->Close(); mailDB = NULL; } } if (err != eSUCCESS || newFile) { // if we couldn't open file, or we have a blank one, and we're supposed // to upgrade, updgrade it. if (newFile && !upgrading) // caller is upgrading, and we have empty summary file, { // leave db around and open so caller can upgrade it. err = eNoSummaryFile; } else if (err != eSUCCESS) { *pMessageDB = NULL; delete mailDB; } } if (err == eSUCCESS || err == eNoSummaryFile) { *pMessageDB = mailDB; if (m_cacheEnabled) GetDBCache()->Add(mailDB); if (err == eSUCCESS) mailDB->HandleLatered(); } return(err); } // This routine opens an invalid db, pulls out the information worth saving, (like sort order), // blows away the invalid db, creates a new empty one, and restores the information worth saving. /* static */ MsgERR MailDB::CloneInvalidDBInfoIntoNewDB(const char * pathname, MailDB** pMailDB) { XP_StatStruct folderst; MailDB *mailDB; TDBFolderInfoTransfer *originalInfo = NULL; int status = MailDB::Open(pathname, TRUE, &mailDB, TRUE); if (status == eSUCCESS && mailDB != NULL) { originalInfo = new TDBFolderInfoTransfer(*mailDB->GetDBFolderInfo()); mailDB->Close(); } XP_Trace("blowing away old summary file\n"); if (!XP_Stat(pathname, &folderst, xpMailFolderSummary) && XP_FileRemove(pathname, xpMailFolderSummary) != 0) { status = MK_MSG_CANT_OPEN; } else { status = MailDB::Open(pathname, TRUE, &mailDB, TRUE); if (originalInfo) { if (status == eSUCCESS && mailDB != NULL) originalInfo->TransferFolderInfo(*mailDB->GetDBFolderInfo()); delete originalInfo; } } *pMailDB = mailDB; return status; } MSG_FolderInfo *MailDB::GetFolderInfo() { if (!m_folderInfo) { m_folderInfo = m_master->FindMailFolder(m_folderName, FALSE); } return m_folderInfo; } // caller needs to unrefer when finished. MailMessageHdr *MailDB::GetMailHdrForKey(MessageKey messageKey) { MailMessageHdr *headerObject = NULL; MSG_HeaderHandle headerHandle = MSG_DBHandle_GetHandleForKey(m_dbHandle, messageKey); if (headerHandle) headerObject = new MailMessageHdr(headerHandle); return headerObject; } MsgERR MailDB::DeleteMessages(IDArray &messageKeys, ChangeListener *instigator) { m_folderFile = XP_FileOpen(m_folderName, xpMailFolder, XP_FILE_UPDATE_BIN); MsgERR err = MessageDB::DeleteMessages(messageKeys, instigator); if (m_folderFile) XP_FileClose(m_folderFile); m_folderFile = NULL; SetFolderInfoValid(m_folderName, 0, 0); return err; } // Helper routine - lowest level of flag setting void MailDB::SetHdrFlag(DBMessageHdr *msgHdr, XP_Bool bSet, MsgFlags flag) { XP_File fid = NULL; MessageDB::SetHdrFlag(msgHdr, bSet, flag); // update summary flag if (msgHdr->GetFlags() & kDirty) { // ### Hack - we know it's a mail message hdr 'cuz it's in a maildb // ImapMailDB::UpdateFolderFlag does nothing UpdateFolderFlag((MailMessageHdr *) msgHdr, bSet, flag, &fid); if (fid != NULL) { XP_FileClose(fid); SetFolderInfoValid(m_folderName, 0, 0); } } } // We let the caller close the file in case he's updating a lot of flags // and we don't want to open and close the file every time through. // As an experiment, try caching the fid in the db as m_folderFile. // If this is set, use it but don't return *pFid. void MailDB::UpdateFolderFlag(MailMessageHdr *mailHdr, XP_Bool /*bSet*/, MsgFlags /*flag*/, XP_File *pFid) { static char buf[30]; XP_File fid = (m_folderFile) ? m_folderFile : *pFid; if (mailHdr->GetFlags() & kDirty) { if (mailHdr->GetStatusOffset() > 0) { if (fid == NULL) { fid = XP_FileOpen(m_folderName, xpMailFolder, XP_FILE_UPDATE_BIN); } if (fid) { uint32 position = mailHdr->GetStatusOffset() + mailHdr->GetMessageOffset(); XP_ASSERT(mailHdr->GetStatusOffset() < 10000); XP_FileSeek(fid, position, SEEK_SET); buf[0] = '\0'; if (XP_FileReadLine(buf, sizeof(buf), fid)) { if (strncmp(buf, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) == 0 && strncmp(buf + X_MOZILLA_STATUS_LEN, ": ", 2) == 0 && strlen(buf) > X_MOZILLA_STATUS_LEN + 6) { XP_FileSeek(fid, position, SEEK_SET); // We are filing out old Cheddar flags here XP_FilePrintf(fid, X_MOZILLA_STATUS_FORMAT, (mailHdr->GetMozillaStatusFlags() & ~MSG_FLAG_RUNTIME_ONLY)); // time to upate x-mozilla-status2 position = XP_FileTell(fid); XP_FileSeek(fid, position + LINEBREAK_LEN, SEEK_SET); if (XP_FileReadLine(buf, sizeof(buf), fid)) { if (strncmp(buf, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN) == 0 && strncmp(buf + X_MOZILLA_STATUS2_LEN, ": ", 2) == 0 && strlen(buf) > X_MOZILLA_STATUS2_LEN + 10) { uint32 dbFlags = mailHdr->GetFlags(); MessageDB::ConvertDBFlagsToPublicFlags(&dbFlags); dbFlags &= (MSG_FLAG_MDN_REPORT_NEEDED | MSG_FLAG_MDN_REPORT_SENT | MSG_FLAG_TEMPLATE); XP_FileSeek(fid, position + LINEBREAK_LEN, SEEK_SET); XP_FilePrintf(fid, X_MOZILLA_STATUS2_FORMAT, dbFlags); } } } else { PRINTF(("Didn't find %s where expected at position %ld\n" "instead, found %s.\n", X_MOZILLA_STATUS, (long) position, buf)); SetReparse(TRUE); } } else { PRINTF(("Couldn't read old status line at all at position %ld\n", (long) position)); SetReparse(TRUE); } } else { PRINTF(("Couldn't open mail folder for update%s!\n", m_folderName)); XP_ASSERT(FALSE); } } if (!m_folderFile) *pFid = fid; mailHdr->AndFlags(~kDirty); } } // Use this function if you have the correct counts, but just want to protect // from a crash. In theory, this Commit should be quick, since there's only // one or two dirty objects. MsgERR MailDB::SetSummaryValid(XP_Bool valid /*= TRUE*/) { XP_StatStruct folderst; if (XP_Stat((char*) m_folderName, &folderst, xpMailFolder)) return eDBExistsNot; if (valid) { m_dbFolderInfo->m_folderSize = folderst.st_size; m_dbFolderInfo->m_folderDate = folderst.st_mtime; } else { m_dbFolderInfo->m_folderDate = 0; // that ought to do the trick. } // m_dbFolderInfo->setDirty(); DMB TODO Commit(); return eSUCCESS; } MsgERR MailDB::GetIdsWithNoBodies (IDArray &bodylessIds) { DBMessageHdr *currentHeader; ListContext *listContext = NULL; MsgERR dbErr = ListFirst (&listContext, ¤tHeader); int32 maxMsgSize = 0x7fffffff; XP_Bool respectMsgSizeLimit = FALSE; PREF_GetBoolPref("mail.limit_message_size", &respectMsgSizeLimit); if (respectMsgSizeLimit && PREF_OK == PREF_GetIntPref("mail.max_size", &maxMsgSize)) maxMsgSize *= 1024; while (dbErr == eSUCCESS) { if (0 == ((MailMessageHdr *)currentHeader)->GetOfflineMessageLength(GetDB()) && currentHeader->GetByteLength() < (uint32) maxMsgSize) bodylessIds.Add(currentHeader->GetMessageKey()); delete currentHeader; dbErr = ListNext(listContext, ¤tHeader); } if (dbErr == eDBEndOfList) { dbErr = eSUCCESS; ListDone(listContext); } return dbErr; } /* static */ MsgERR MailDB::SetFolderInfoValid(const char* pathname, int num, int numunread) { XP_StatStruct folderst; MsgERR err; if (XP_Stat((char*) pathname, &folderst, xpMailFolder)) return eDBExistsNot; char *dbName = WH_FileName(pathname, xpMailFolderSummary); if (!dbName) return eOUT_OF_MEMORY; MessageDB *pMessageDB = MessageDB::FindInCache(dbName); if (pMessageDB == NULL) { pMessageDB = new MailDB; // ### this does later stuff (marks latered messages unread), which may be a problem err = pMessageDB->MessageDBOpen(dbName, FALSE); if (err != eSUCCESS) { delete pMessageDB; pMessageDB = NULL; } } else pMessageDB->AddUseCount(); XP_FREE(dbName); if (pMessageDB == NULL) { XP_Trace("Exception opening summary file\n"); return eOldSummaryFile; } pMessageDB->m_dbFolderInfo->m_folderSize = folderst.st_size; pMessageDB->m_dbFolderInfo->m_folderDate = folderst.st_mtime; pMessageDB->m_dbFolderInfo->ChangeNumVisibleMessages(num); pMessageDB->m_dbFolderInfo->ChangeNumNewMessages(numunread); pMessageDB->m_dbFolderInfo->ChangeNumMessages(num); pMessageDB->Close(); return eSUCCESS; } // This is used to remember that the db is out of sync with the mail folder // and needs to be regenerated. void MailDB::SetReparse(XP_Bool reparse) { m_reparse = reparse; } ImapMailDB::ImapMailDB() : MailDB() { } ImapMailDB::~ImapMailDB() {} MsgERR ImapMailDB::Open(const char * folderName, XP_Bool create, MailDB** pMessageDB, MSG_Master* mailMaster, XP_Bool *dbWasCreated) { ImapMailDB *mailDB=NULL; XP_StatStruct st; DBFolderInfo *folderInfo = NULL; XP_Bool newFile = FALSE; char* dbName; MsgERR err=eUNKNOWN; XP_Bool dbFoundInCache=FALSE; XP_ASSERT(mailMaster); *dbWasCreated = FALSE; // code below can't call XP_File routines, or dbName will be crunched. if (m_cacheEnabled) { dbName = WH_FileName(folderName, xpMailFolderSummary); if (!dbName) return eOUT_OF_MEMORY; mailDB = (ImapMailDB *) FindInCache(dbName); XP_FREE(dbName); if (mailDB) { *pMessageDB = mailDB; ++(mailDB->m_useCount); // make this behave like the non-cache case. Is global DB set? DMB TODO err = eSUCCESS; dbFoundInCache = TRUE; } } if (!dbFoundInCache) { // if the old summary doesn't exist, we're creating a new one. if (XP_Stat (folderName, &st, xpMailFolderSummary) && create) { newFile = TRUE; *dbWasCreated = TRUE; } mailDB = new ImapMailDB; if (!mailDB) { *pMessageDB = NULL; return(eOUT_OF_MEMORY); } mailDB->m_folderName = XP_STRDUP(folderName); dbName = WH_FileName(folderName, xpMailFolderSummary); if (!dbName) { err = eOUT_OF_MEMORY; } else { err = mailDB->MessageDBOpen(dbName, create); XP_FREE(dbName); } if (err == eSUCCESS) { folderInfo = mailDB->GetDBFolderInfo(); // if opening existing file, make sure summary file is up to date. if (!newFile && !XP_Stat (folderName, &st, xpMailFolder)) { if (folderInfo->m_folderSize != st.st_size) err = eOldSummaryFile; } if (mailDB->GetCurVersion() != folderInfo->GetDiskVersion()) err = eOldSummaryFile; if (err != eSUCCESS) { mailDB->Close(); mailDB = NULL; } } if (err == eSUCCESS) { *pMessageDB = mailDB; if (m_cacheEnabled) GetDBCache()->Add(mailDB); } } if (mailDB) { mailDB->SetMaster(mailMaster); if (mailMaster) { // this causes an infinite recursion, so don't do it. // MSG_FolderInfoMail *folderInfo = mailMaster->FindImapMailFolder(folderName); // mailDB->SetFolderInfo(folderInfo); } } return(err); } void ImapMailDB::UpdateFolderFlag(MailMessageHdr *, XP_Bool , MsgFlags , XP_File *fid) { fid = NULL; } MSG_FolderInfo *ImapMailDB::GetFolderInfo() { if (!m_folderInfo) { XPStringObj mailBoxName; m_dbFolderInfo->GetMailboxName(mailBoxName); m_folderInfo = m_master->FindImapMailFolderFromPath(m_folderName); } return m_folderInfo; } // caller needs to unrefer when finished. DBOfflineImapOperation *MailDB::GetOfflineOpForKey(MessageKey messageKey, XP_Bool create) { DBOfflineImapOperation *offlineObject = NULL; MSG_OfflineIMAPOperationHandle offlineOpHandle = MSG_DBHandle_GetOfflineOp(m_dbHandle, messageKey); if (offlineOpHandle) { offlineObject = new DBOfflineImapOperation; if (offlineObject) { offlineObject->SetHandle(offlineOpHandle); offlineObject->SetDBHandle(m_dbHandle); } } if (!offlineObject && create) { offlineObject = new DBOfflineImapOperation; if (offlineObject) { offlineObject->SetHandle(GetOfflineIMAPOperation()); offlineObject->SetMessageKey(messageKey); offlineObject->SetDBHandle(m_dbHandle); // if there is a corresponding msg, use its flags MailMessageHdr *msgHdr = GetMailHdrForKey(messageKey); if (msgHdr) { imapMessageFlagsType imapFlags = kNoImapMsgFlag; if (msgHdr->GetFlags() & MSG_FLAG_READ) imapFlags |= kImapMsgSeenFlag; if (msgHdr->GetFlags() & MSG_FLAG_REPLIED) imapFlags |= kImapMsgAnsweredFlag; if (msgHdr->GetFlags() & MSG_FLAG_MARKED) imapFlags |= kImapMsgFlaggedFlag; if (msgHdr->GetFlags() & MSG_FLAG_FORWARDED) imapFlags |= kImapMsgForwardedFlag; offlineObject->SetInitialImapFlags(imapFlags); delete msgHdr; } AddOfflineOp(offlineObject); } } return offlineObject; } MsgERR MailDB::SetSourceMailbox(DBOfflineImapOperation *op, const char *mailbox, MessageKey key) { MsgERR err = MSG_OfflineIMAPOperationHandle_SetSourceMailbox(op->GetHandle(), m_dbHandle, mailbox, key); if (err == eCorruptDB) SetSummaryValid(FALSE); return err; } MsgERR MailDB::ListAllOfflineOpIds(IDArray &outputIds) { MessageKey *resultKeys; int32 numKeys; MsgERR err = MSG_DBHandle_ListAllOflineOperationKeys(m_dbHandle, &resultKeys, &numKeys); outputIds.SetArray(resultKeys, numKeys, numKeys); return err; } // List the ids of messages that are going to be deleted via offline playback, // because we don't need to refetch those headers. int MailDB::ListAllOfflineDeletes(IDArray &outputIds) { MessageKey *deletedKeys; int32 numKeys; MsgERR err = MSG_DBHandle_ListAllOfflineDeletedMessageKeys(m_dbHandle, &deletedKeys, &numKeys); outputIds.Add(deletedKeys, numKeys); XP_FREE(deletedKeys); return numKeys; } MsgERR MailDB::AddOfflineOp(DBOfflineImapOperation *op) { MSG_DBHandle_AddOfflineOperation(m_dbHandle, op->GetHandle()); MSG_FolderInfo *folderInfo = GetFolderInfo(); XP_ASSERT(folderInfo); // it would be bad if we couldn't get the folder info. if (folderInfo) { folderInfo->SetFolderPrefFlags(folderInfo->GetFolderPrefFlags() | MSG_FOLDER_PREF_OFFLINEEVENTS); if (folderInfo->GetType() == FOLDER_IMAPMAIL) { MSG_IMAPFolderInfoMail *imapFolder = (MSG_IMAPFolderInfoMail *) folderInfo; imapFolder->SetHasOfflineEvents(TRUE); // where should we clear this flag? At the end of the process events url? } } return eSUCCESS; } MsgERR MailDB::DeleteOfflineOp(MessageKey opKey) { DBOfflineImapOperation *doomedOp = GetOfflineOpForKey(opKey, FALSE); if (doomedOp) { MSG_DBHandle_RemoveOfflineOperation(m_dbHandle, doomedOp->GetHandle()); delete doomedOp; } return eSUCCESS; } MsgERR ImapMailDB::SetSummaryValid(XP_Bool valid /* = TRUE */) { return MessageDB::SetSummaryValid(valid); }