зеркало из https://github.com/mozilla/pjs.git
4740 строки
139 KiB
C++
4740 строки
139 KiB
C++
/* -*- 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.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
#include "rosetta.h"
|
|
#include "msg.h"
|
|
#include "errcode.h"
|
|
#include "csid.h" // for CS_UNKNOWN
|
|
|
|
#include "msgimap.h"
|
|
#include "maildb.h"
|
|
#include "mailhdr.h"
|
|
#include "msgprefs.h"
|
|
#include "msgfpane.h"
|
|
|
|
#include "grpinfo.h"
|
|
#include "newsdb.h"
|
|
#include "msgtpane.h"
|
|
#include "msgspane.h"
|
|
#include "msgmpane.h"
|
|
#include "msgdbvw.h"
|
|
#include "prsembst.h"
|
|
#include "xpgetstr.h"
|
|
#include "nwsartst.h"
|
|
#include "msgundmg.h"
|
|
#include "msgundac.h"
|
|
#include "maildb.h"
|
|
#include "xplocale.h"
|
|
#include "msgurlq.h"
|
|
#include "newshost.h"
|
|
#include "hosttbl.h"
|
|
#include "newsset.h"
|
|
#include "grec.h"
|
|
#include "prefapi.h"
|
|
#include "newsdb.h"
|
|
#include "idarray.h"
|
|
#include "imapoff.h"
|
|
#include "imaphost.h"
|
|
#include "msgbiff.h"
|
|
|
|
#if defined(XP_WIN16) || defined(XP_OS2)
|
|
#define MAX_FILE_LENGTH_WITHOUT_EXTENSION 8
|
|
#elif defined(XP_MAC)
|
|
#define MAX_FILE_LENGTH_WITHOUT_EXTENSION 26
|
|
#elif defined(XP_WIN32)
|
|
#define MAX_FILE_LENGTH_WITHOUT_EXTENSION 256
|
|
#else
|
|
#define MAX_FILE_LENGTH_WITHOUT_EXTENSION 32000
|
|
#endif
|
|
|
|
|
|
void PostMessageCopyUrlExitFunc (URL_Struct *URL_s, int status, MWContext *window_id);
|
|
void OfflineOpExitFunction (URL_Struct *URL_s, int status, MWContext *window_id);
|
|
|
|
/* Some prototypes for routines in mkpop3.c */
|
|
|
|
extern "C" char* ReadPopData(char *name);
|
|
extern "C" void SavePopData(char *data);
|
|
extern "C" void net_pop3_delete_if_in_server(char *data, char *uidl, XP_Bool *changed);
|
|
extern "C" void KillPopData(char* data);
|
|
|
|
|
|
extern "C"
|
|
{
|
|
extern int MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
extern int MK_OUT_OF_MEMORY;
|
|
extern int MK_MSG_CANT_COPY_TO_SAME_FOLDER;
|
|
extern int MK_MSG_CANT_COPY_TO_QUEUE_FOLDER;
|
|
extern int MK_MSG_CANT_COPY_TO_QUEUE_FOLDER_OLD;
|
|
extern int MK_MSG_CANT_COPY_TO_DRAFTS_FOLDER;
|
|
extern int MK_MSG_CANT_CREATE_FOLDER;
|
|
extern int MK_COULD_NOT_CREATE_DIRECTORY;
|
|
extern int MK_UNABLE_TO_DELETE_FILE;
|
|
extern int MK_UNABLE_TO_OPEN_FILE;
|
|
extern int MK_MSG_FOLDER_ALREADY_EXISTS;
|
|
extern int MK_MSG_FOLDER_BUSY;
|
|
extern int MK_MSG_CANT_DELETE_NEWS_DB;
|
|
extern int MK_MSG_IMAP_CONTAINER_NAME;
|
|
extern int MK_MSG_CANT_MOVE_FOLDER;
|
|
extern int MK_MSG_ONLINE_IMAP_WITH_NO_BODY;
|
|
extern int MK_MSG_LOCAL_MAIL;
|
|
extern int MK_NEWS_DISCUSSIONS_ON;
|
|
extern int MK_MSG_DELETING_MSGS_STATUS;
|
|
extern int MK_MSG_COPYING_MSGS_STATUS;
|
|
extern int MK_MSG_MOVING_MSGS_STATUS;
|
|
extern int MK_MSG_COPY_TARGET_NO_SELECT;
|
|
extern int MK_MSG_TMP_FOLDER_UNWRITABLE;
|
|
extern int MK_POP3_OUT_OF_DISK_SPACE;
|
|
}
|
|
|
|
MSG_FolderInfo::MSG_FolderInfo(const char* n, uint8 d, XPCompareFunc* comparator)
|
|
: m_subFolders(NULL)
|
|
, m_flags(0)
|
|
, m_name((n) ? XP_STRDUP(n) : 0)
|
|
{
|
|
|
|
m_depth = d;
|
|
m_numUnreadMessages = -1;
|
|
m_numTotalMessages = 0;
|
|
m_master = NULL;
|
|
m_semaphoreHolder = NULL;
|
|
m_prefFlags = 0;
|
|
m_csid = CS_UNKNOWN;
|
|
m_lastMessageLoaded = MSG_MESSAGEKEYNONE;
|
|
m_numPendingUnreadMessages = 0;
|
|
m_numPendingTotalMessages = 0;
|
|
m_subFolders = new MSG_FolderArray (comparator);
|
|
|
|
m_isCachable = TRUE;
|
|
}
|
|
|
|
MSG_FolderInfo::~MSG_FolderInfo()
|
|
{
|
|
#ifdef DEBUG
|
|
m_subFolders->VerifySort();
|
|
#endif // DEBUG
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
delete m_subFolders->GetAt(i);
|
|
|
|
delete m_subFolders;
|
|
|
|
if (m_name)
|
|
XP_FREE(m_name);
|
|
}
|
|
|
|
MsgERR MSG_FolderInfo::OnCloseFolder ()
|
|
{
|
|
return eSUCCESS;
|
|
}
|
|
|
|
MWContext *MSG_FolderInfo::GetFolderPaneContext()
|
|
{
|
|
// find the folder pane context
|
|
MSG_Pane *folderPane = m_master->FindFirstPaneOfType(MSG_FOLDERPANE);
|
|
MWContext *folderPaneContext = NULL;
|
|
if (folderPane)
|
|
folderPaneContext = folderPane->GetContext();
|
|
if (!folderPane || !folderPaneContext)
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
return NULL;
|
|
}
|
|
return folderPaneContext;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::StartAsyncCopyMessagesInto (MSG_FolderInfo *dstFolder,
|
|
MSG_Pane* sourcePane,
|
|
MessageDB *sourceDB,
|
|
IDArray *srcArray,
|
|
int32 srcCount,
|
|
MWContext *currentContext,
|
|
MSG_UrlQueue *urlQueue,
|
|
XP_Bool deleteAfterCopy,
|
|
MessageKey nextKeyToLoad)
|
|
{
|
|
// General note: If either the source or destination folder is an IMAP folder then we add the copy info struct
|
|
// to the end of the current context's chain of copy info structs then fire off an IMAP URL.
|
|
// However, local folders don't work this way! We must add the copy info struct to the URL queue where it will be fired
|
|
// at its leisure.
|
|
|
|
MessageCopyInfo *copyInfo = (MessageCopyInfo *) XP_ALLOC(sizeof(MessageCopyInfo));
|
|
|
|
if (copyInfo)
|
|
{
|
|
XP_BZERO (copyInfo, sizeof(MessageCopyInfo));
|
|
copyInfo->srcFolder = this;
|
|
copyInfo->dstFolder = dstFolder;
|
|
copyInfo->nextCopyInfo = NULL;
|
|
copyInfo->dstIMAPfolderUpdated=FALSE;
|
|
copyInfo->offlineFolderPositionOfMostRecentMessage = 0;
|
|
copyInfo->srcDB = sourceDB;
|
|
copyInfo->srcArray = srcArray;
|
|
copyInfo->srcCount = srcCount;
|
|
|
|
copyInfo->moveState.ismove = deleteAfterCopy;
|
|
copyInfo->moveState.sourcePane = sourcePane;
|
|
copyInfo->moveState.nextKeyToLoad = nextKeyToLoad;
|
|
copyInfo->moveState.urlForNextKeyLoad = NULL;
|
|
copyInfo->moveState.moveCompleted = FALSE;
|
|
copyInfo->moveState.finalDownLoadMessageSize = 0;
|
|
copyInfo->moveState.imap_connection = 0;
|
|
copyInfo->moveState.haveUploadedMessageSize = FALSE;
|
|
|
|
MsgERR openErr = eSUCCESS;
|
|
XP_Bool wasCreated;
|
|
if (dstFolder->GetType() == FOLDER_MAIL)
|
|
openErr = MailDB::Open (dstFolder->GetMailFolderInfo()->GetPathname(), FALSE, ©Info->moveState.destDB, FALSE);
|
|
else if (dstFolder->GetType() == FOLDER_IMAPMAIL && !IsNews())
|
|
openErr = ImapMailDB::Open (dstFolder->GetMailFolderInfo()->GetPathname(), FALSE, ©Info->moveState.destDB,
|
|
sourcePane->GetMaster(), &wasCreated);
|
|
|
|
|
|
if (!dstFolder->GetMailFolderInfo() || (openErr != eSUCCESS))
|
|
copyInfo->moveState.destDB = NULL;
|
|
|
|
// let the front end know that we are starting a long update
|
|
sourcePane->StartingUpdate(MSG_NotifyNone, 0, 0);
|
|
|
|
if ((this->GetType() == FOLDER_IMAPMAIL) || (dstFolder->GetType() == FOLDER_IMAPMAIL))
|
|
{
|
|
// add this copyinfo struct to the end
|
|
if (currentContext->msgCopyInfo != NULL)
|
|
{
|
|
MessageCopyInfo *endingNode = currentContext->msgCopyInfo;
|
|
while (endingNode->nextCopyInfo != NULL)
|
|
endingNode = endingNode->nextCopyInfo;
|
|
endingNode->nextCopyInfo = copyInfo;
|
|
}
|
|
else
|
|
currentContext->msgCopyInfo = copyInfo;
|
|
|
|
// BeginCopyMessages will fire an IMAP url. The IMAP
|
|
// module will call FinishCopyMessages so that the whole
|
|
// shebang is handled as one IMAP url. Previously the copy
|
|
// happened with a mailbox url and IMAP url running together
|
|
// in the same context. This worked on mac only.
|
|
MsgERR copyErr = BeginCopyingMessages(dstFolder, sourceDB, srcArray,urlQueue,srcCount,copyInfo);
|
|
if (0 != copyErr)
|
|
{
|
|
CleanupCopyMessagesInto(¤tContext->msgCopyInfo);
|
|
|
|
if (!NET_IsOffline() && ((int32) copyErr < -1) )
|
|
FE_Alert (sourcePane->GetContext(), XP_GetString(copyErr));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// okay, add this URL to our URL queue.
|
|
URL_Struct *url_struct = NET_CreateURLStruct("mailbox:copymessages", NET_DONT_RELOAD);
|
|
if (url_struct)
|
|
{
|
|
MSG_UrlQueue::AddLocalMsgCopyUrlToPane(copyInfo, url_struct, PostMessageCopyUrlExitFunc, sourcePane, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::CleanupCopyMessagesInto (MessageCopyInfo **info)
|
|
{
|
|
if (!info || !*info)
|
|
return;
|
|
|
|
MSG_Pane *sourcePane = (*info)->moveState.sourcePane;
|
|
|
|
XP_Bool searchPane = sourcePane ? sourcePane->GetPaneType() == MSG_SEARCHPANE : FALSE;
|
|
|
|
if ((*info)->moveState.destDB != NULL)
|
|
{
|
|
(*info)->moveState.destDB->Close();
|
|
(*info)->moveState.destDB = NULL;
|
|
}
|
|
if ((*info)->dstFolder->TestSemaphore(this))
|
|
(*info)->dstFolder->ReleaseSemaphore(this);
|
|
|
|
// if we were a search pane, and an error occurred, close the view on this action..
|
|
if (sourcePane != NULL && searchPane)
|
|
((MSG_SearchPane *) sourcePane)->CloseView((*info)->srcFolder);
|
|
|
|
|
|
// tell the fe that we are finished with
|
|
// out backend driven update. They can
|
|
// now do things like load the next message.
|
|
|
|
// now that an imap copy message is at most 2 urls, we can end the
|
|
// the update here. Now this is this only ending update and resource
|
|
// cleanup for message copying
|
|
sourcePane->EndingUpdate(MSG_NotifyNone, 0, 0);
|
|
|
|
// tell the FE that we're done copying so they can re-enable
|
|
// selection if they've decided to disable it during the copy
|
|
|
|
// I don't think we want to do this if we are a search pane but i haven't been able to
|
|
// justify why yet!!
|
|
|
|
if (!searchPane)
|
|
FE_PaneChanged(sourcePane, TRUE, MSG_PaneNotifyCopyFinished, 0);
|
|
|
|
// EndingUpdate may have caused an interruption of this context and cleaning up the
|
|
// url queue may have deleted this MessageCopyInfo already
|
|
if (*info)
|
|
{
|
|
MessageCopyInfo *deleteMe = *info;
|
|
*info = deleteMe->nextCopyInfo; // but nextCopyInfo == NULL. this causes the fault later on (MSCOTT)
|
|
XP_FREE(deleteMe);
|
|
}
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::HasSubFolders() const
|
|
{
|
|
return m_subFolders->GetSize() > 0;
|
|
}
|
|
|
|
int MSG_FolderInfo::GetNumSubFolders() const
|
|
{
|
|
return m_subFolders->GetSize();
|
|
}
|
|
|
|
MSG_FolderInfo* MSG_FolderInfo::GetSubFolder(int which) const
|
|
{
|
|
XP_ASSERT(which >= 0 && which < m_subFolders->GetSize());
|
|
return m_subFolders->GetAt (which);
|
|
}
|
|
|
|
void MSG_FolderInfo::RemoveSubFolder (MSG_FolderInfo *which)
|
|
{
|
|
int idx = m_subFolders->FindIndex (0, which);
|
|
if (idx != -1)
|
|
m_subFolders->RemoveAt (idx);
|
|
else
|
|
XP_ASSERT(FALSE); //someone asked to remove a folder we don't own
|
|
|
|
XP_ASSERT(m_subFolders->FindIndex(0, which) == -1);
|
|
if (m_subFolders->GetSize() == 0)
|
|
{
|
|
// Our last child was deleted, so reset our hierarchy bits and tell the panes that this
|
|
// folderInfo changed, which eventually redraws the expand/collapse widget in the folder pane
|
|
m_flags &= ~MSG_FOLDER_FLAG_DIRECTORY;
|
|
m_flags &= ~MSG_FOLDER_FLAG_ELIDED;
|
|
m_master->BroadcastFolderChanged (this);
|
|
}
|
|
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::GetAdminUrl(MWContext * /* context */, MSG_AdminURLType /* type */)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::HaveAdminUrl(MSG_AdminURLType /* type */)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::DeleteIsMoveToTrash()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::ShowDeletedMessages()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::ChangeNumPendingUnread(int32 delta)
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
MsgERR err = GetDBFolderInfoAndDB(&folderInfo, &db);
|
|
if (err == eSUCCESS)
|
|
{
|
|
if (folderInfo)
|
|
{
|
|
m_numPendingUnreadMessages += delta;
|
|
folderInfo->ChangeImapUnreadPendingMessages(delta);
|
|
}
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
}
|
|
|
|
void MSG_FolderInfo::ChangeNumPendingTotalMessages(int32 delta)
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
MsgERR err = GetDBFolderInfoAndDB(&folderInfo, &db);
|
|
if (err == eSUCCESS)
|
|
{
|
|
if (folderInfo)
|
|
{
|
|
m_numPendingTotalMessages += delta;
|
|
folderInfo->ChangeImapTotalPendingMessages(delta);
|
|
}
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::SetFolderPrefFlags(int32 flags)
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
|
|
flags |= MSG_FOLDER_PREF_CACHED;
|
|
|
|
// don't do anything if the flags aren't changing (modulo the cached bit)
|
|
if (flags != GetFolderPrefFlags())
|
|
{
|
|
MsgERR err = GetDBFolderInfoAndDB(&folderInfo, &db);
|
|
if (err == eSUCCESS)
|
|
{
|
|
if (folderInfo)
|
|
{
|
|
m_prefFlags = flags;
|
|
folderInfo->SetFlags(flags & ~MSG_FOLDER_PREF_CACHED);
|
|
}
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 MSG_FolderInfo::GetFolderPrefFlags()
|
|
{
|
|
ReadDBFolderInfo();
|
|
return m_prefFlags;
|
|
}
|
|
|
|
void MSG_FolderInfo::SetFolderCSID(int16 csid)
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
MsgERR err = GetDBFolderInfoAndDB(&folderInfo, &db);
|
|
if (err == eSUCCESS)
|
|
{
|
|
if (folderInfo)
|
|
{
|
|
m_csid = csid;
|
|
folderInfo->SetCSID(m_csid);
|
|
}
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
}
|
|
|
|
int16 MSG_FolderInfo::GetFolderCSID()
|
|
{
|
|
ReadDBFolderInfo();
|
|
return m_csid;
|
|
}
|
|
|
|
void MSG_FolderInfo::SetLastMessageLoaded(MessageKey lastMessageLoaded)
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
MsgERR err = GetDBFolderInfoAndDB(&folderInfo, &db);
|
|
if (err == eSUCCESS)
|
|
{
|
|
if (folderInfo)
|
|
{
|
|
m_lastMessageLoaded = lastMessageLoaded;
|
|
folderInfo->SetLastMessageLoaded(lastMessageLoaded);
|
|
}
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
}
|
|
|
|
MessageKey MSG_FolderInfo::GetLastMessageLoaded()
|
|
{
|
|
ReadDBFolderInfo();
|
|
return m_lastMessageLoaded;
|
|
}
|
|
|
|
void MSG_FolderInfo::RememberPassword(const char * /* password */)
|
|
{
|
|
}
|
|
|
|
char *MSG_FolderInfo::GetRememberedPassword()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::UserNeedsToAuthenticateForFolder(XP_Bool displayOnly)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
const char *MSG_FolderInfo::GetUserName()
|
|
{
|
|
return "";
|
|
}
|
|
const char *MSG_FolderInfo::GetHostName()
|
|
{
|
|
return "";
|
|
}
|
|
|
|
const char *MSG_FolderInfoMail::GetUserName()
|
|
{
|
|
return NET_GetPopUsername();
|
|
}
|
|
|
|
const char *MSG_FolderInfoMail::GetHostName()
|
|
{
|
|
return m_master->GetPrefs()->GetPopHost();
|
|
}
|
|
|
|
// We're a local folder - if we're using pop, check to see if we've got the pop password.
|
|
// If we're using imap, check if we've cached the password on the default imap host.
|
|
char *MSG_FolderInfoMail::GetRememberedPassword()
|
|
{
|
|
XP_Bool serverIsIMAP = m_master->GetPrefs()->GetMailServerIsIMAP4();
|
|
char *savedPassword = NULL;
|
|
if (serverIsIMAP)
|
|
{
|
|
MSG_IMAPHost *defaultIMAPHost = m_master->GetIMAPHostTable()->GetDefaultHost();
|
|
if (defaultIMAPHost)
|
|
{
|
|
MSG_FolderInfo *hostFolderInfo = defaultIMAPHost->GetHostFolderInfo();
|
|
MSG_FolderInfo *defaultHostIMAPInbox = NULL;
|
|
if (hostFolderInfo->GetFoldersWithFlag(MSG_FOLDER_FLAG_INBOX, &defaultHostIMAPInbox, 1) == 1
|
|
&& defaultHostIMAPInbox != NULL)
|
|
{
|
|
savedPassword = defaultHostIMAPInbox->GetRememberedPassword();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MSG_FolderInfo *offlineInbox = NULL;
|
|
if (m_flags & MSG_FOLDER_FLAG_INBOX)
|
|
{
|
|
char *retPassword = NULL;
|
|
MailDB *mailDb = NULL;
|
|
XP_Bool wasCreated=FALSE;
|
|
MailDB::Open(m_pathName, FALSE, &mailDb, FALSE);
|
|
if (mailDb)
|
|
{
|
|
XPStringObj cachedPassword;
|
|
mailDb->GetCachedPassword(cachedPassword);
|
|
retPassword = XP_STRDUP(cachedPassword);
|
|
mailDb->Close();
|
|
|
|
}
|
|
return retPassword;
|
|
}
|
|
if (m_master->GetLocalMailFolderTree()->GetFoldersWithFlag(MSG_FOLDER_FLAG_INBOX, &offlineInbox, 1) && offlineInbox)
|
|
savedPassword = offlineInbox->GetRememberedPassword();
|
|
}
|
|
return savedPassword;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoMail::UserNeedsToAuthenticateForFolder(XP_Bool /* displayOnly */)
|
|
{
|
|
XP_Bool ret = FALSE;
|
|
if (m_master->IsCachePasswordProtected() && !m_master->IsUserAuthenticated() && !m_master->AreLocalFoldersAuthenticated())
|
|
{
|
|
char *savedPassword = GetRememberedPassword();
|
|
if (savedPassword && XP_STRLEN(savedPassword))
|
|
ret = TRUE;
|
|
FREEIF(savedPassword);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
int32 MSG_FolderInfo::GetNumPendingUnread(XP_Bool deep) const
|
|
{
|
|
int32 total = m_numPendingUnreadMessages;
|
|
if (deep)
|
|
{
|
|
MSG_FolderInfo *folder;
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
folder = m_subFolders->GetAt(i);
|
|
if (folder)
|
|
total += folder->GetNumPendingUnread(deep);
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int32 MSG_FolderInfo::GetNumPendingTotalMessages(XP_Bool deep) const
|
|
{
|
|
int32 total = m_numPendingTotalMessages;
|
|
if (deep)
|
|
{
|
|
MSG_FolderInfo *folder;
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
folder = m_subFolders->GetAt(i);
|
|
total += folder->GetNumPendingTotalMessages(deep);
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
|
|
int32 MSG_FolderInfo::GetNumUnread(XP_Bool deep) const
|
|
{
|
|
int32 total = m_numUnreadMessages;
|
|
if (deep)
|
|
{
|
|
MSG_FolderInfo *folder;
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
folder = m_subFolders->GetAt(i);
|
|
if (folder)
|
|
{
|
|
int32 num = folder->GetNumUnread(deep);
|
|
if (num >= 0) // it's legal for counts to be negative if we don't know
|
|
total += num;
|
|
}
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int32 MSG_FolderInfo::GetTotalMessages(XP_Bool deep) const
|
|
{
|
|
int32 total = m_numTotalMessages;
|
|
if (deep)
|
|
{
|
|
MSG_FolderInfo *folder;
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
folder = m_subFolders->GetAt(i);
|
|
int32 num = folder->GetTotalMessages (deep);
|
|
if (num >= 0) // it's legal for counts to be negative if we don't know
|
|
total += num;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int32 MSG_FolderInfo::GetExpungedBytesCount() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
const char *MSG_FolderInfo::GetPrettyName()
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
// return pretty name if any, otherwise ugly name.
|
|
const char *MSG_FolderInfo::GetPrettiestName()
|
|
{
|
|
const char *prettyName = GetPrettyName();
|
|
return (prettyName) ? prettyName : GetName();
|
|
}
|
|
|
|
int MSG_FolderInfo::GetNumSubFoldersToDisplay() const
|
|
{
|
|
return GetNumSubFolders();
|
|
}
|
|
|
|
MSG_FolderArray *MSG_FolderInfo::GetSubFolders ()
|
|
{
|
|
return m_subFolders;
|
|
}
|
|
|
|
void MSG_FolderInfo::GetVisibleSubFolders (MSG_FolderArray &subFolders)
|
|
{
|
|
// The folder pane uses this routine to work around the fact
|
|
// that unsubscribed newsgroups are children of the news host.
|
|
// We can't count those when computing folder pane view indexes.
|
|
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
MSG_FolderInfo *f = m_subFolders->GetAt(i);
|
|
if (f && f->CanBeInFolderPane())
|
|
subFolders.Add (f);
|
|
}
|
|
}
|
|
|
|
void MSG_FolderInfo::SummaryChanged()
|
|
{
|
|
UpdateSummaryTotals();
|
|
if (m_master)
|
|
m_master->BroadcastFolderChanged(this);
|
|
}
|
|
|
|
MsgERR MSG_FolderInfo::Rename (const char *newName)
|
|
{
|
|
MsgERR status = 0;
|
|
MSG_FolderInfo *parentFolderInfo;
|
|
|
|
parentFolderInfo = m_master->GetFolderTree()->FindParentOf(this);
|
|
parentFolderInfo->m_subFolders->Remove(this);
|
|
SetName(newName);
|
|
parentFolderInfo->m_subFolders->Add(this);
|
|
if (!GetName())
|
|
status = MK_OUT_OF_MEMORY;
|
|
return status;
|
|
}
|
|
|
|
const char* MSG_FolderInfo::GetName()
|
|
{ // Name of this folder (as presented to user).
|
|
return m_name;
|
|
}
|
|
|
|
void MSG_FolderInfo::SetName(const char *name)
|
|
{
|
|
FREEIF(m_name);
|
|
m_name = XP_STRDUP(name);
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::ContainsChildNamed (const char *name)
|
|
{
|
|
return FindChildNamed(name) != NULL;
|
|
}
|
|
|
|
MSG_FolderInfo *MSG_FolderInfo::FindChildNamed (const char *name)
|
|
{
|
|
MSG_FolderInfo *folder = NULL;
|
|
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
folder = m_subFolders->GetAt(i);
|
|
// case-insensitive compare is probably LCD across OS filesystems
|
|
if (!strcasecomp(folder->GetName(), name))
|
|
return folder;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
MSG_FolderInfo *MSG_FolderInfo::FindParentOf (MSG_FolderInfo *child)
|
|
{
|
|
MSG_FolderInfo *result = NULL;
|
|
|
|
for (int i = 0; i < m_subFolders->GetSize() && result == NULL; i++)
|
|
{
|
|
if (m_subFolders->GetAt (i) == child)
|
|
result = this;
|
|
}
|
|
|
|
for (int j = 0; j < m_subFolders->GetSize() && result == NULL; j++)
|
|
{
|
|
MSG_FolderInfo *folder = m_subFolders->GetAt (j);
|
|
result = folder->FindParentOf (child);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::IsParentOf (MSG_FolderInfo *child, XP_Bool deep)
|
|
{
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
MSG_FolderInfo *folder = m_subFolders->GetAt(i);
|
|
if (folder == child || (deep && folder->IsParentOf(child, deep)))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::UpdateSummaryTotals()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
uint32 MSG_FolderInfo::GetFlags() const
|
|
{
|
|
return m_flags;
|
|
}
|
|
|
|
uint8 MSG_FolderInfo::GetDepth() const
|
|
{
|
|
return m_depth;
|
|
}
|
|
|
|
void MSG_FolderInfo::SetDepth(uint8 depth)
|
|
{
|
|
m_depth = depth;
|
|
}
|
|
|
|
MSG_FolderInfoMail *MSG_FolderInfo::GetMailFolderInfo() {return NULL;}
|
|
MSG_FolderInfoNews *MSG_FolderInfo::GetNewsFolderInfo() {return NULL;}
|
|
MSG_IMAPFolderInfoMail *MSG_FolderInfo::GetIMAPFolderInfoMail() {return NULL;}
|
|
MSG_IMAPFolderInfoContainer *MSG_FolderInfo::GetIMAPFolderInfoContainer() {return NULL;}
|
|
MSG_IMAPHost *MSG_FolderInfo::GetIMAPHost() {return NULL;}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::CanCreateChildren () { return FALSE; }
|
|
XP_Bool MSG_FolderInfo::CanBeRenamed () { return FALSE; }
|
|
|
|
void MSG_FolderInfo::MarkAllRead(MWContext *context, XP_Bool deep)
|
|
{
|
|
MessageDB *db;
|
|
DBFolderInfo *folderInfo;
|
|
if (GetDBFolderInfoAndDB(&folderInfo, &db) == eSUCCESS)
|
|
{
|
|
if (db)
|
|
{
|
|
db->MarkAllRead(context);
|
|
db->Close();
|
|
UpdateSummaryTotals();
|
|
GetMaster()->BroadcastFolderChanged(this);
|
|
}
|
|
}
|
|
if (deep)
|
|
{
|
|
for (int i = 0; i < GetNumSubFolders(); i++)
|
|
{
|
|
MSG_FolderInfo *subFolder = GetSubFolder(i);
|
|
if (subFolder)
|
|
subFolder->MarkAllRead(context, deep);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 MSG_FolderInfo::GetTotalMessagesInDB() const
|
|
{
|
|
return m_numTotalMessages; // news overrides this, since m_numTotalMessages for news is server-based
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::ToggleFlag (uint32 whichFlag)
|
|
{
|
|
XP_ASSERT(whichFlag != MSG_FOLDER_FLAG_SENTMAIL);
|
|
m_flags ^= whichFlag;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::SetFlag (uint32 whichFlag)
|
|
{
|
|
m_flags |= whichFlag;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::ClearFlag(uint32 whichFlag)
|
|
{
|
|
m_flags &= ~whichFlag;
|
|
}
|
|
|
|
void MSG_FolderInfo::SetFlagInAllFolderPanes(uint32 which)
|
|
{
|
|
SetFlag(which);
|
|
|
|
MSG_FolderPane *eachPane;
|
|
for (eachPane = (MSG_FolderPane *) m_master->FindFirstPaneOfType(MSG_FOLDERPANE); eachPane;
|
|
eachPane = (MSG_FolderPane *) m_master->FindNextPaneOfType(eachPane->GetNextPane(), MSG_FOLDERPANE))
|
|
{
|
|
eachPane->SetFlagForFolder(this, which);
|
|
}
|
|
}
|
|
|
|
void MSG_FolderInfo::GetExpansionArray (MSG_FolderArray &array)
|
|
{
|
|
// the application of flags in GetExpansionArray is subtly different
|
|
// than in GetFoldersWithFlag
|
|
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
MSG_FolderInfo *folder = m_subFolders->GetAt(i);
|
|
array.InsertAt(array.GetSize(), folder);
|
|
if (!(folder->GetFlags() & MSG_FOLDER_FLAG_ELIDED))
|
|
folder->GetExpansionArray(array);
|
|
}
|
|
}
|
|
|
|
|
|
int32 MSG_FolderInfo::GetFoldersWithFlag(uint32 f, MSG_FolderInfo** result, int32 resultsize)
|
|
{
|
|
int num = 0;
|
|
if ((f & m_flags) == f) {
|
|
if (result && num < resultsize) {
|
|
result[num] = this;
|
|
}
|
|
num++;
|
|
}
|
|
MSG_FolderInfo *folder = NULL;
|
|
for (int i=0; i < m_subFolders->GetSize(); i++) {
|
|
folder = m_subFolders->GetAt(i);
|
|
// CAREFUL! if NULL ise passed in for result then the caller
|
|
// still wants the full count! Otherwise, the result should be at most the
|
|
// number that the caller asked for.
|
|
if (!result)
|
|
num += folder->GetFoldersWithFlag(f, NULL, 0);
|
|
else if (num < resultsize)
|
|
num += folder->GetFoldersWithFlag(f, result + num, resultsize - num);
|
|
else
|
|
break;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
|
|
char * MSG_FolderInfo::EscapeMessageId (const char *messageId)
|
|
{
|
|
char *id = NULL;
|
|
|
|
/* Take off bracketing <>, and quote special characters. */
|
|
if (messageId[0] == '<') {
|
|
char *i2;
|
|
id = XP_STRDUP(messageId + 1);
|
|
if (!id)
|
|
return 0;
|
|
if (*id && id[XP_STRLEN(id) - 1] == '>')
|
|
id[XP_STRLEN(id) - 1] = '\0';
|
|
i2 = NET_Escape (id, URL_XALPHAS);
|
|
XP_FREE (id);
|
|
id = i2;
|
|
}
|
|
else
|
|
id = NET_Escape (messageId, URL_XALPHAS);
|
|
return id;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfo::AcquireSemaphore (void *semHolder)
|
|
{
|
|
XP_ASSERT(semHolder != NULL);
|
|
MsgERR err = 0;
|
|
|
|
if (m_semaphoreHolder == NULL)
|
|
m_semaphoreHolder = semHolder;
|
|
else
|
|
err = MK_MSG_FOLDER_BUSY;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::ReleaseSemaphore (void *semHolder)
|
|
{
|
|
XP_ASSERT(m_semaphoreHolder == semHolder);
|
|
|
|
if (!m_semaphoreHolder || m_semaphoreHolder == semHolder)
|
|
m_semaphoreHolder = NULL;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::RequiresCleanup()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void MSG_FolderInfo::ClearRequiresCleanup()
|
|
{
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::TestSemaphore (void *semHolder)
|
|
{
|
|
XP_ASSERT(semHolder);
|
|
|
|
if (m_semaphoreHolder == semHolder)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::CanBeInFolderPane ()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::KnowsSearchNntpExtension()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::AllowsPosting ()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfo::DisplayRecipients ()
|
|
{
|
|
if (m_flags & MSG_FOLDER_FLAG_SENTMAIL && !(m_flags & MSG_FOLDER_FLAG_INBOX))
|
|
return TRUE;
|
|
else
|
|
{
|
|
// Only mail folders can be FCC folders
|
|
if (m_flags & MSG_FOLDER_FLAG_MAIL || m_flags & MSG_FOLDER_FLAG_IMAPBOX)
|
|
{
|
|
// There's one FCC folder for sent mail, and one for sent news
|
|
MSG_FolderInfo *fccFolders[2];
|
|
int numFccFolders = m_master->GetFolderTree()->GetFoldersWithFlag (MSG_FOLDER_FLAG_SENTMAIL, fccFolders, 2);
|
|
for (int i = 0; i < numFccFolders; i++)
|
|
if (fccFolders[i]->IsParentOf (this))
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfo::ReadDBFolderInfo (XP_Bool force /* = FALSE */)
|
|
{
|
|
// Since it turns out to be pretty expensive to open and close
|
|
// the DBs all the time, if we have to open it once, get everything
|
|
// we might need while we're here
|
|
|
|
MsgERR err = eUNKNOWN;
|
|
if (force || !(m_prefFlags & MSG_FOLDER_PREF_CACHED))
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
err = GetDBFolderInfoAndDB(&folderInfo, &db);
|
|
if (err == eSUCCESS)
|
|
{
|
|
m_isCachable = TRUE;
|
|
if (folderInfo)
|
|
{
|
|
m_prefFlags = folderInfo->GetFlags();
|
|
m_prefFlags |= MSG_FOLDER_PREF_CACHED;
|
|
folderInfo->SetFlags(m_prefFlags);
|
|
|
|
m_numTotalMessages = folderInfo->GetNumMessages();
|
|
m_numUnreadMessages = folderInfo->GetNumNewMessages();
|
|
|
|
m_numPendingTotalMessages = folderInfo->GetImapTotalPendingMessages();
|
|
m_numPendingUnreadMessages = folderInfo->GetImapUnreadPendingMessages();
|
|
|
|
m_csid = folderInfo->GetCSID();
|
|
if (db && !db->HasNew() && m_numPendingUnreadMessages <= 0)
|
|
ClearFlag(MSG_FOLDER_FLAG_GOT_NEW);
|
|
}
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#define kFolderInfoCacheFormat1 "\t%08lX\t%08lX\t%ld\t%ld\t%d"
|
|
#define kFolderInfoCacheFormat2 "\t%08lX\t%08lX\t%ld\t%ld\t%ld\t%ld\t%d"
|
|
|
|
#define kFolderInfoCacheVersion 2
|
|
|
|
MsgERR MSG_FolderInfo::WriteToCache (XP_File f)
|
|
{
|
|
// This function is coupled tightly with ReadFromCache,
|
|
// and loosely with ReadDBFolderInfo
|
|
|
|
XP_FilePrintf (f, "\t%d", kFolderInfoCacheVersion);
|
|
XP_FilePrintf (f, kFolderInfoCacheFormat2, (long) m_flags, (long) m_prefFlags,
|
|
(long) m_numTotalMessages, (long) m_numPendingTotalMessages,
|
|
(long) m_numUnreadMessages, (long) m_numPendingUnreadMessages, (int) m_csid);
|
|
|
|
return eSUCCESS;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfo::ReadFromCache (char *buf)
|
|
{
|
|
// This function is coupled tightly with WriteToCache,
|
|
// and loosely with ReadDBFolderInfo
|
|
|
|
MsgERR err = eSUCCESS;
|
|
int version;
|
|
int tokensRead = sscanf (buf, "\t%d", &version);
|
|
SkipCacheTokens (&buf, tokensRead);
|
|
|
|
if ((version == 1) || (version == 2))
|
|
{
|
|
uint32 tempFlags;
|
|
int tempCsid;
|
|
if (version == 1)
|
|
sscanf (buf, kFolderInfoCacheFormat1, &tempFlags, &m_prefFlags,
|
|
&m_numTotalMessages, &m_numUnreadMessages, &tempCsid);
|
|
else
|
|
sscanf (buf, kFolderInfoCacheFormat2, &tempFlags, &m_prefFlags,
|
|
&m_numTotalMessages, &m_numPendingTotalMessages, &m_numUnreadMessages, &m_numPendingUnreadMessages, &tempCsid);
|
|
|
|
m_prefFlags |= MSG_FOLDER_PREF_CACHED;
|
|
// I'm chicken to just blast in all the flags, and the real
|
|
// reason I'm storing them is to get elided, so just get that
|
|
|
|
if (tempFlags & MSG_FOLDER_FLAG_DIRECTORY)
|
|
{
|
|
m_flags |= MSG_FOLDER_FLAG_DIRECTORY;
|
|
if (!(tempFlags & MSG_FOLDER_FLAG_ELIDED))
|
|
m_flags &= ~MSG_FOLDER_FLAG_ELIDED;
|
|
else
|
|
m_flags |= MSG_FOLDER_FLAG_ELIDED;
|
|
}
|
|
|
|
// Let's remember the IMAP folder types, too
|
|
if (tempFlags & MSG_FOLDER_FLAG_IMAP_PERSONAL)
|
|
m_flags |= MSG_FOLDER_FLAG_IMAP_PERSONAL;
|
|
if (tempFlags & MSG_FOLDER_FLAG_IMAP_PUBLIC)
|
|
m_flags |= MSG_FOLDER_FLAG_IMAP_PUBLIC;
|
|
if (tempFlags & MSG_FOLDER_FLAG_IMAP_OTHER_USER)
|
|
m_flags |= MSG_FOLDER_FLAG_IMAP_OTHER_USER;
|
|
if (tempFlags & MSG_FOLDER_FLAG_PERSONAL_SHARED)
|
|
m_flags |= MSG_FOLDER_FLAG_PERSONAL_SHARED;
|
|
|
|
// Can't scan a %d into a short on 32 bit platforms
|
|
m_csid = (int16) tempCsid;
|
|
}
|
|
else
|
|
err = eUNKNOWN;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::IsCachable()
|
|
{
|
|
// If we haven't opened the DB for this, we don't know what the real
|
|
// cache values should be, so don't write anything.
|
|
|
|
return m_isCachable;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfo::SkipCacheTokens (char **ppBuf, int numTokens)
|
|
{
|
|
for (int i = 0; i < numTokens; i++)
|
|
{
|
|
while (**ppBuf == '\t')
|
|
(*ppBuf)++;
|
|
while (**ppBuf != '\t')
|
|
(*ppBuf)++;
|
|
}
|
|
}
|
|
|
|
|
|
const char *MSG_FolderInfo::GetRelativePathName ()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*static*/ int MSG_FolderInfo::CompareFolderDepth (const void *v1, const void *v2)
|
|
{
|
|
MSG_FolderInfo *f1 = *(MSG_FolderInfo**) v1;
|
|
MSG_FolderInfo *f2 = *(MSG_FolderInfo**) v2;
|
|
|
|
// Sort shallowest folders to the top
|
|
return f1->m_depth - f2->m_depth;
|
|
}
|
|
|
|
|
|
int32 MSG_FolderInfo::GetSizeOnDisk ()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char *MSG_FolderInfo::GenerateUniqueSubfolderName(const char* /*prefix*/, MSG_FolderInfo* /*otherFolder*/)
|
|
{
|
|
/* override in MSG_FolderInfoMail */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const char* NameFromPathname(const char* pathname)
|
|
{
|
|
char* ptr = XP_STRRCHR(pathname, '/');
|
|
if (ptr)
|
|
return ptr + 1;
|
|
return pathname;
|
|
}
|
|
|
|
int32 MSG_FolderInfoMail::GetExpungedBytesCount() const
|
|
{
|
|
return m_expungedBytes;
|
|
}
|
|
|
|
void MSG_FolderInfoMail::SetExpungedBytesCount(int32 count)
|
|
{
|
|
m_expungedBytes = count;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoMail::IsMail () { return TRUE; }
|
|
XP_Bool MSG_FolderInfoMail::IsNews () { return FALSE; }
|
|
XP_Bool MSG_FolderInfoMail::CanCreateChildren () { return TRUE; }
|
|
|
|
#ifdef XP_WIN16
|
|
#define RETURN_COMPARE_LONG_VALUES(b,a) \
|
|
if (b > a)\
|
|
return 1;\
|
|
else if (b < a)\
|
|
return -1;\
|
|
else\
|
|
return 0;
|
|
#else
|
|
#define RETURN_COMPARE_LONG_VALUES(b,a) return b - a;
|
|
#endif
|
|
|
|
static inline uint32 MakeFakeSortFlags(uint32 folderFlags)
|
|
{
|
|
const uint32 kSystemMailboxMask = (0x1F00);
|
|
uint32 result = folderFlags & kSystemMailboxMask;
|
|
if (folderFlags & MSG_FOLDER_FLAG_TEMPLATES)
|
|
result = 0x3FF; // just below drafts.
|
|
return result;
|
|
}
|
|
|
|
int MSG_FolderInfoMail::CompareFolders(const void* a, const void* b)
|
|
{
|
|
MSG_FolderInfoMail *aFolder = *((MSG_FolderInfoMail**) a);
|
|
MSG_FolderInfoMail *bFolder = *((MSG_FolderInfoMail**) b);
|
|
|
|
if (aFolder->GetDepth() == 2)
|
|
{
|
|
// This bit-twiddling controls the order that system mailboxes are
|
|
// shown according to the bitflags and comments in msgcom.h. This code
|
|
// relies on the bit positions of the flags (e.g. MSG_FOLDER_FLAG_INBOX)
|
|
|
|
|
|
uint32 aFlags = MakeFakeSortFlags(aFolder->GetFlags());
|
|
uint32 bFlags = MakeFakeSortFlags(bFolder->GetFlags());
|
|
if (aFlags != 0 || bFlags != 0)
|
|
if (aFlags != bFlags)
|
|
RETURN_COMPARE_LONG_VALUES(bFlags, aFlags);
|
|
|
|
// Sort IMAP Namespaces at the end of the list
|
|
const uint32 kImapNamespaceMask = 0x300000;
|
|
uint32 nsFlagsA = aFolder->GetFlags() & kImapNamespaceMask;
|
|
uint32 nsFlagsB = bFolder->GetFlags() & kImapNamespaceMask;
|
|
if (nsFlagsA != 0 || nsFlagsB != 0)
|
|
if (nsFlagsA != nsFlagsB)
|
|
RETURN_COMPARE_LONG_VALUES(nsFlagsA, nsFlagsB);
|
|
|
|
// Else let the name break the tie (jrm had 2 sent-mail folders!!)
|
|
}
|
|
|
|
return strcasecomp(aFolder->GetName(), bFolder->GetName());
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoMail::ShouldIgnoreFile (const char *name)
|
|
{
|
|
if (name[0] == '.' || name[0] == '#' || name[XP_STRLEN(name) - 1] == '~')
|
|
return TRUE;
|
|
|
|
if (!XP_STRCASECMP (name, "rules.dat"))
|
|
return TRUE;
|
|
|
|
#if defined (XP_WIN) || defined (XP_MAC) || defined(XP_OS2)
|
|
// don't add summary files to the list of folders;
|
|
//don't add popstate files to the list either, or rules (sort.dat).
|
|
if ((XP_STRLEN(name) > 4 &&
|
|
!XP_STRCASECMP(name + XP_STRLEN(name) - 4, ".snm")) ||
|
|
!XP_STRCASECMP(name, "popstate.dat") ||
|
|
!XP_STRCASECMP(name, "sort.dat") ||
|
|
!XP_STRCASECMP(name, "mailfilt.log") ||
|
|
!XP_STRCASECMP(name, "filters.js") ||
|
|
!XP_STRCASECMP(name + XP_STRLEN(name) - 4, ".toc"))
|
|
return TRUE;
|
|
#endif
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// a factory that deals with pathname to long for us to append ".snm" to. Useful when the user imports
|
|
// a mailbox file with a long name. If there is a new name then pathname is replaced.
|
|
MSG_FolderInfoMail *MSG_FolderInfoMail::CreateMailFolderInfo(char* pathname, MSG_Master *master, uint8 depth, uint32 flags)
|
|
{
|
|
int renameStatus = 0;
|
|
char *leafName = XP_STRDUP(NameFromPathname (pathname)); // we have to dup it because we need it after pathname is deleted
|
|
|
|
char *mangledPath = NULL;
|
|
char *mangledLeaf = NULL;
|
|
|
|
const int charLimit = MAX_FILE_LENGTH_WITHOUT_EXTENSION; // set on platform specific basis
|
|
|
|
char *baseDir = XP_STRDUP(pathname);
|
|
if (baseDir)
|
|
{
|
|
char *lastSlash = XP_STRRCHR(baseDir, '/');
|
|
if (lastSlash)
|
|
*lastSlash = '\0';
|
|
}
|
|
|
|
if (leafName && (XP_STRLEN(leafName) > charLimit))
|
|
mangledLeaf = CreatePlatformLeafNameForDisk(leafName, master, baseDir);
|
|
|
|
if (leafName && mangledLeaf && XP_STRCMP(leafName, mangledLeaf))
|
|
{
|
|
// the user must have imported a mailbox with a file name to long for this platform.
|
|
mangledPath = XP_STRDUP(pathname); // this is enough room, we will shorten it
|
|
if (mangledPath)
|
|
{
|
|
char *leafPtr = XP_STRRCHR(mangledPath, '/');
|
|
if (leafPtr)
|
|
{
|
|
XP_STRCPY(++leafPtr, mangledLeaf);
|
|
|
|
// rename the mailbox file
|
|
renameStatus = XP_FileRename(pathname, xpMailFolder, mangledPath, xpMailFolder);
|
|
|
|
XP_FREE(pathname);
|
|
pathname = mangledPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
MSG_FolderInfoMail *newFolder = new MSG_FolderInfoMail (pathname, master, depth, flags);
|
|
|
|
if (newFolder && mangledPath && (0 == renameStatus))
|
|
{
|
|
// if this folder was created with a new name, save the old pretty leaf name in the db
|
|
MailDB *mailDB;
|
|
MsgERR openErr = MailDB::Open (newFolder->GetPathname(), TRUE, &mailDB, TRUE);;
|
|
|
|
DBFolderInfo *info = NULL;
|
|
if (openErr == eSUCCESS && mailDB)
|
|
{
|
|
info = mailDB->GetDBFolderInfo();
|
|
newFolder->SetName(leafName);
|
|
info->SetMailboxName(leafName);
|
|
mailDB->SetSummaryValid(FALSE);
|
|
mailDB->Close();
|
|
}
|
|
}
|
|
|
|
|
|
FREEIF(mangledLeaf);
|
|
FREEIF(leafName);
|
|
FREEIF(baseDir);
|
|
|
|
return newFolder;
|
|
}
|
|
|
|
void
|
|
MSG_FolderInfoMail::SetPrefFolderFlag()
|
|
{
|
|
const char *path = GetPathname();
|
|
|
|
if (!path)
|
|
return;
|
|
|
|
char *localMailFcc = NULL;
|
|
char *localNewsFcc = NULL;
|
|
|
|
MSG_Prefs::GetXPDirPathPref("mail.default_fcc", FALSE, &localMailFcc);
|
|
MSG_Prefs::GetXPDirPathPref("news.default_fcc", FALSE, &localNewsFcc);
|
|
|
|
const char *mailDirectory = m_master->GetPrefs()->GetFolderDirectory();
|
|
if (!localMailFcc || !*localMailFcc || XP_STRCMP(mailDirectory, localMailFcc) == 0)
|
|
{
|
|
XP_FREEIF(localMailFcc);
|
|
localMailFcc = PR_smprintf("%s/%s", mailDirectory, SENT_FOLDER_NAME);
|
|
}
|
|
|
|
if (!localNewsFcc || !*localNewsFcc || XP_STRCMP(mailDirectory, localNewsFcc) == 0)
|
|
{
|
|
XP_FREEIF(localNewsFcc);
|
|
localNewsFcc = PR_smprintf("%s/%s", mailDirectory, SENT_FOLDER_NAME);
|
|
}
|
|
|
|
if (XP_STRCMP(path, localMailFcc) == 0 ||
|
|
XP_STRCMP(path, localNewsFcc) == 0)
|
|
SetFlag(MSG_FOLDER_FLAG_SENTMAIL);
|
|
else
|
|
ClearFlag(MSG_FOLDER_FLAG_SENTMAIL);
|
|
|
|
XP_FREEIF(localMailFcc);
|
|
XP_FREEIF(localNewsFcc);
|
|
|
|
char *draftsPath = msg_MagicFolderName (m_master->GetPrefs(),
|
|
MSG_FOLDER_FLAG_DRAFTS, NULL);
|
|
|
|
// +8 skip "mailbox:" part of url
|
|
if ( draftsPath && NET_URL_Type(draftsPath) == MAILBOX_TYPE_URL &&
|
|
XP_STRCMP(draftsPath+8, mailDirectory) == 0)
|
|
{
|
|
// Default case
|
|
XP_FREEIF(draftsPath);
|
|
draftsPath = PR_smprintf("mailbox:%s/%s", mailDirectory, DRAFTS_FOLDER_NAME);
|
|
}
|
|
|
|
if ( draftsPath && ((NET_URL_Type(draftsPath) == MAILBOX_TYPE_URL &&
|
|
XP_STRCMP(path, draftsPath+8) == 0) ||
|
|
XP_STRCMP(path, draftsPath) == 0) )
|
|
SetFlag(MSG_FOLDER_FLAG_DRAFTS);
|
|
else
|
|
ClearFlag(MSG_FOLDER_FLAG_DRAFTS);
|
|
|
|
XP_FREEIF(draftsPath);
|
|
|
|
char *templatesPath = msg_MagicFolderName (m_master->GetPrefs(),
|
|
MSG_FOLDER_FLAG_TEMPLATES,
|
|
NULL);
|
|
|
|
if ( templatesPath && NET_URL_Type(templatesPath) == MAILBOX_TYPE_URL &&
|
|
XP_STRCMP(templatesPath+8, mailDirectory) == 0)
|
|
{
|
|
// Default case
|
|
XP_FREEIF(templatesPath);
|
|
templatesPath = PR_smprintf("mailbox:%s/%s", mailDirectory, TEMPLATES_FOLDER_NAME);
|
|
}
|
|
|
|
if ( templatesPath &&
|
|
((NET_URL_Type(templatesPath) == MAILBOX_TYPE_URL &&
|
|
XP_STRCMP(path, templatesPath+8) == 0) ||
|
|
XP_STRCMP(path, templatesPath) == 0) )
|
|
SetFlag(MSG_FOLDER_FLAG_TEMPLATES);
|
|
else
|
|
ClearFlag(MSG_FOLDER_FLAG_TEMPLATES);
|
|
|
|
XP_FREEIF(templatesPath);
|
|
}
|
|
|
|
MSG_FolderInfoMail*
|
|
MSG_FolderInfoMail::BuildFolderTree (const char *path, uint8 depth, MSG_FolderArray *parentArray,
|
|
MSG_Master *master, XP_Bool buildingImapTree /*= FALSE*/, MSG_IMAPHost *host /* = NULL */)
|
|
{
|
|
const char *kDirExt = ".sbd";
|
|
|
|
// newPath will be owned by the folder info, so don't delete it here
|
|
char *newPath = (char*) XP_ALLOC(XP_STRLEN(path) + XP_STRLEN(kDirExt) + 1);
|
|
if (!newPath)
|
|
return NULL;
|
|
XP_STRCPY (newPath, path);
|
|
|
|
uint32 newFlags = MSG_FOLDER_FLAG_MAIL;
|
|
const char *leafName = NameFromPathname (path);
|
|
if (buildingImapTree)
|
|
{
|
|
const char *dbLeafName = leafName;
|
|
// this allocates a string, so remember to free it at the end.
|
|
leafName = MSG_IMAPFolderInfoMail::CreateMailboxNameFromDbName(dbLeafName);
|
|
}
|
|
|
|
if (depth == 2) // Gross. "depth 2" means that this is a top
|
|
// level folder. (See, depth 0 is the root,
|
|
// and depth 1 is the container for all
|
|
// local mail, so depth 2 is top level folder.)
|
|
{
|
|
// certain folder names are magic when in the base directory
|
|
#ifdef XP_MAC
|
|
char* escapedName = NULL;
|
|
do {
|
|
escapedName = NET_Escape(INBOX_FOLDER_NAME, URL_PATH);
|
|
if (escapedName && !XP_FILENAMECMP(leafName, escapedName) &&
|
|
(!master->GetPrefs()->GetMailServerIsIMAP4() || buildingImapTree))
|
|
{
|
|
newFlags |= MSG_FOLDER_FLAG_INBOX;
|
|
break;
|
|
}
|
|
XP_FREEIF(escapedName);
|
|
escapedName = NET_Escape(TRASH_FOLDER_NAME, URL_PATH);
|
|
if (escapedName && !XP_FILENAMECMP(leafName, escapedName))
|
|
{
|
|
newFlags |= MSG_FOLDER_FLAG_TRASH;
|
|
break;
|
|
}
|
|
XP_FREEIF(escapedName);
|
|
escapedName = NET_Escape(MSG_GetQueueFolderName(), URL_PATH);
|
|
if (escapedName && !XP_FILENAMECMP(leafName, escapedName))
|
|
{
|
|
newFlags |= MSG_FOLDER_FLAG_QUEUE;
|
|
break;
|
|
}
|
|
XP_FREEIF(escapedName);
|
|
} while (0);
|
|
XP_FREEIF(escapedName);
|
|
#else
|
|
if (!XP_FILENAMECMP(leafName, INBOX_FOLDER_NAME) &&
|
|
(!master->GetPrefs()->GetMailServerIsIMAP4() || buildingImapTree))
|
|
newFlags |= MSG_FOLDER_FLAG_INBOX;
|
|
else if (!XP_FILENAMECMP(leafName, TRASH_FOLDER_NAME))
|
|
newFlags |= MSG_FOLDER_FLAG_TRASH;
|
|
else if (!XP_FILENAMECMP(leafName, MSG_GetQueueFolderName()))
|
|
newFlags |= MSG_FOLDER_FLAG_QUEUE;
|
|
#endif
|
|
}
|
|
|
|
MSG_FolderInfoMail *newFolder = NULL;
|
|
XP_Dir dir = XP_OpenDir (newPath, xpMailFolder);
|
|
if (dir)
|
|
{
|
|
// newPath specifies a filesystem directory
|
|
char *lastFour = &newPath[XP_STRLEN(newPath) - XP_STRLEN(kDirExt)];
|
|
if (XP_STRCASECMP(lastFour, kDirExt))
|
|
{
|
|
// if the path doesn't contain .sbd, we can scan it. .sbd directories were
|
|
// created with the Navigator UI, and will be picked up when their
|
|
// corresponding mail folder is found.
|
|
newFlags |= (MSG_FOLDER_FLAG_DIRECTORY | MSG_FOLDER_FLAG_ELIDED);
|
|
if (buildingImapTree)
|
|
newFolder = new MSG_IMAPFolderInfoMail (newPath, master, depth, newFlags, host);
|
|
else
|
|
newFolder = new MSG_FolderInfoMail (newPath, master, depth, newFlags);
|
|
if (newFolder)
|
|
{
|
|
newFolder->SetMaster(master);
|
|
newFolder->SetPrefFolderFlag();
|
|
}
|
|
if (newFolder && parentArray)
|
|
parentArray->Add (newFolder);
|
|
|
|
XP_DirEntryStruct *entry = NULL;
|
|
for (entry = XP_ReadDir(dir); entry; entry = XP_ReadDir(dir))
|
|
{
|
|
if (newFolder->ShouldIgnoreFile (entry->d_name))
|
|
continue;
|
|
|
|
char *subName = (char*) XP_ALLOC(XP_STRLEN(newPath) + XP_STRLEN(entry->d_name) + 5);
|
|
if (subName)
|
|
{
|
|
XP_STRCPY (subName, newPath);
|
|
if (*entry->d_name != '/')
|
|
XP_STRCAT (subName, "/");
|
|
XP_STRCAT (subName, entry->d_name);
|
|
BuildFolderTree (subName, depth + 1, newFolder->m_subFolders, master, buildingImapTree, host);
|
|
FREEIF(subName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is a .SBD directory that we created. We're going to ignore it
|
|
// for the purpose of building the tree, so delete the pathname
|
|
FREEIF(newPath);
|
|
}
|
|
XP_CloseDir (dir);
|
|
}
|
|
else
|
|
{
|
|
// newPath specifies a Berkeley mail folder or an imap db
|
|
if (buildingImapTree)
|
|
{
|
|
char *imapBoxName = MSG_IMAPFolderInfoMail::CreateMailboxNameFromDbName(newPath);
|
|
if (imapBoxName)
|
|
{
|
|
FREEIF(newPath);
|
|
newPath = imapBoxName;
|
|
newFolder = new MSG_IMAPFolderInfoMail (imapBoxName, master, depth, newFlags, host);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newFolder = CreateMailFolderInfo (newPath, master, depth, newFlags);
|
|
}
|
|
if (newFolder)
|
|
{
|
|
newFolder->SetMaster(master);
|
|
newFolder->SetPrefFolderFlag();
|
|
if (parentArray)
|
|
{
|
|
if (parentArray->FindIndex(0, newFolder) == -1)
|
|
parentArray->Add (newFolder); // This maintains the sort order (XPSortedPtrArrray).
|
|
else if (buildingImapTree) // this is probably one of those duplicate imap databases - blow it away.
|
|
{
|
|
delete newFolder;
|
|
newFolder = NULL;
|
|
char *imapBoxName = MSG_IMAPFolderInfoMail::CreateMailboxNameFromDbName(path);
|
|
XP_FileRemove(imapBoxName, xpMailFolderSummary);
|
|
XP_Trace("removing duplicate db %s\n", leafName);
|
|
FREEIF(imapBoxName);
|
|
}
|
|
}
|
|
|
|
if (newFolder)
|
|
{
|
|
newFolder->UpdateSummaryTotals();
|
|
int32 flags = newFolder->GetFolderPrefFlags(); // cache folder pref flags
|
|
|
|
// Look for a directory for this mail folder, and recurse into it.
|
|
// e.g. if the folder is "inbox", look for "inbox.sbd".
|
|
XP_Dir subDir = XP_OpenDir(newFolder->GetPathname(), xpMailSubdirectory);
|
|
if (subDir)
|
|
{
|
|
// If we knew it was a directory before getting here, we must have
|
|
// found that out from the folder cache. In that case, the elided bit
|
|
// is already what it should be, and we shouldn't change it. Otherwise
|
|
// the default setting is collapsed.
|
|
// NOTE: these flags do not affect the sort order, so we don't have to call
|
|
// QuickSort after changing them.
|
|
if (!(newFolder->m_flags & MSG_FOLDER_FLAG_DIRECTORY))
|
|
newFolder->m_flags |= MSG_FOLDER_FLAG_ELIDED;
|
|
newFolder->m_flags |= MSG_FOLDER_FLAG_DIRECTORY;
|
|
|
|
XP_DirEntryStruct *entry = NULL;
|
|
for (entry = XP_ReadDir(subDir); entry; entry = XP_ReadDir(subDir))
|
|
{
|
|
if (newFolder->ShouldIgnoreFile (entry->d_name))
|
|
continue;
|
|
|
|
char *subPath = (char*) XP_ALLOC(XP_STRLEN(newFolder->GetPathname()) + XP_STRLEN(kDirExt) + XP_STRLEN(entry->d_name) + 2);
|
|
if (subPath)
|
|
{
|
|
XP_STRCPY (subPath, newFolder->GetPathname());
|
|
XP_STRCAT (subPath, kDirExt);
|
|
if (*entry->d_name != '/')
|
|
XP_STRCAT (subPath, "/");
|
|
XP_STRCAT (subPath, entry->d_name);
|
|
BuildFolderTree (subPath, depth + 1, newFolder->m_subFolders, master, buildingImapTree, host);
|
|
FREEIF(subPath);
|
|
}
|
|
}
|
|
XP_CloseDir (subDir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (buildingImapTree && leafName)
|
|
XP_FREE((char *) leafName);
|
|
|
|
if (newFolder && !buildingImapTree && (newFolder->GetFlags() & MSG_FOLDER_FLAG_INBOX))
|
|
{
|
|
// LATER ON WE WILL ENABLE ANY FOLDER, NOT JUST INBOXES, This is a POP3 folder, Imap folders
|
|
// are added to the Biff Master in their constructor
|
|
XP_Bool getMail = FALSE, activate = FALSE;
|
|
int32 interval = 0;
|
|
|
|
PREF_GetBoolPref("mail.check_new_mail", &getMail);
|
|
PREF_GetIntPref("mail.check_time", &interval);
|
|
|
|
MSG_Biff_Master::AddBiffFolder((char*) newFolder->GetPathname(), getMail,
|
|
interval, TRUE, NULL /* no host name for POP */);
|
|
}
|
|
return newFolder;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfo::SaveMessages(IDArray * array,
|
|
const char * fileName,
|
|
MSG_Pane *pane,
|
|
MessageDB * msgDB,
|
|
int (*doneCB)(void *, int status), void *state)
|
|
{
|
|
DownloadArticlesToFolder::SaveMessages(array, fileName, pane, this, msgDB, doneCB, state);
|
|
return eSUCCESS;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfo::ShouldPerformOperationOffline()
|
|
{
|
|
#ifdef XP_UNIX
|
|
return NET_IsOffline();
|
|
#else
|
|
if (NET_IsOffline())
|
|
return TRUE;
|
|
if (GetType() == FOLDER_IMAPMAIL)
|
|
{
|
|
MSG_IMAPFolderInfoMail *imapFolder = (MSG_IMAPFolderInfoMail *) this;
|
|
return imapFolder->GetRunningIMAPUrl();
|
|
}
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
MSG_FolderInfoMail::MSG_FolderInfoMail(char* path, MSG_Master *master, uint8 depth, uint32 flags) :
|
|
MSG_FolderInfo(NameFromPathname(path), depth, CompareFolders)
|
|
{
|
|
// if we're a top level folder, we don't have unread messages
|
|
if ( depth < 2 ) {
|
|
m_numUnreadMessages = 0;
|
|
}
|
|
|
|
m_HaveReadNameFromDB = FALSE;
|
|
m_expungedBytes = 0;
|
|
m_pathName = path;
|
|
m_flags = flags;
|
|
m_parseMailboxState = NULL;
|
|
m_master = master;
|
|
m_gettingMail = FALSE;
|
|
}
|
|
|
|
|
|
const char *MSG_FolderInfoMail::GetPrettyName()
|
|
{
|
|
if (m_depth == 1) {
|
|
// Depth == 1 means we are on the mail server level
|
|
// override the name here to say "Local Mail"
|
|
if (GetType() == FOLDER_MAIL)
|
|
return (XP_GetString(MK_MSG_LOCAL_MAIL));
|
|
else return MSG_FolderInfo::GetPrettyName();
|
|
}
|
|
else return MSG_FolderInfo::GetPrettyName();
|
|
}
|
|
|
|
// this override pulls the value from the db
|
|
const char* MSG_FolderInfoMail::GetName() // Name of this folder (as presented to user).
|
|
{
|
|
if (!m_HaveReadNameFromDB)
|
|
{
|
|
if (m_depth == 1)
|
|
{
|
|
SetName(XP_GetString(MK_MSG_LOCAL_MAIL));
|
|
m_HaveReadNameFromDB = TRUE;
|
|
}
|
|
else
|
|
ReadDBFolderInfo();
|
|
}
|
|
|
|
#if defined(XP_WIN16) || defined(XP_OS2_HACK)
|
|
// (OS/2) DSR082197 - Mike thinks this is OK, but wants me to flag it in case we run into NLV problems.
|
|
// For Win16, try to make the filenames look presentable using the same
|
|
// mechanism as the Explorer. These macros are not i18n aware, but neither
|
|
// is the FAT filesystem, so no data can be lost here.
|
|
//
|
|
// For Win16, try to make the filenames look presentable, but only
|
|
// if we're using a single byte character set. It would be nice to have
|
|
// an XP API for this, but apparently we don't.
|
|
static XP_Bool guiCsidIsDoubleBye = (XP_Bool) ::GetSystemMetrics (SM_DBCSENABLED);
|
|
if (m_name && !guiCsidIsDoubleBye)
|
|
{
|
|
int i = 0;
|
|
while (m_name[i] != '\0')
|
|
{
|
|
if (i == 0)
|
|
m_name[i] = XP_TO_UPPER(m_name[i]);
|
|
else
|
|
m_name[i] = XP_TO_LOWER(m_name[i]);
|
|
i++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return m_name;
|
|
}
|
|
|
|
const char *MSG_FolderInfoMail::GenerateUniqueSubfolderName(const char *prefix, MSG_FolderInfo *otherFolder)
|
|
{
|
|
/* only try 256 times */
|
|
for (int count = 0; (count < 256); count++)
|
|
{
|
|
char *uniqueName = PR_smprintf("%s%d",prefix,count);
|
|
if ((!ContainsChildNamed(uniqueName)) &&
|
|
(otherFolder ? !(otherFolder->ContainsChildNamed(uniqueName)) : TRUE))
|
|
return (uniqueName);
|
|
else
|
|
FREEIF(uniqueName);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoMail::MessageWriteStreamIsReadyForWrite(msg_move_state * /*state*/)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
// since we are writing to file, we don't care
|
|
// ahead of time how big the message is
|
|
// or what its imap flags are
|
|
int MSG_FolderInfoMail::CreateMessageWriteStream(msg_move_state *state, uint32 /*msgSize*/, uint32 /*flags*/)
|
|
{
|
|
MsgERR returnError = 0;
|
|
XP_StatStruct destFolderStat;
|
|
|
|
if (!XP_Stat(this->GetPathname(), &destFolderStat, xpMailFolder))
|
|
state->writestate.position = destFolderStat.st_size;
|
|
|
|
state->writestate.fid = XP_FileOpen (this->GetPathname(), xpMailFolder, XP_FILE_APPEND_BIN);
|
|
if (!state->writestate.fid)
|
|
returnError = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
|
|
return returnError;
|
|
}
|
|
|
|
|
|
uint32 MSG_FolderInfoMail::MessageCopySize(DBMessageHdr *msgHeader, msg_move_state * /*state*/)
|
|
{
|
|
return msgHeader->GetByteLength();
|
|
}
|
|
|
|
static void PostMessageCopyUrlExitCompleted(URL_Struct *URL_s, int status, MWContext *window_id)
|
|
{
|
|
if ((window_id->msgCopyInfo->dstFolder->GetType() == FOLDER_IMAPMAIL) && !window_id->msgCopyInfo->dstIMAPfolderUpdated )
|
|
{
|
|
MSG_IMAPFolderInfoMail *dstImapFolder = (MSG_IMAPFolderInfoMail *) window_id->msgCopyInfo->dstFolder;
|
|
window_id->msgCopyInfo->dstIMAPfolderUpdated = TRUE;
|
|
|
|
dstImapFolder->FinishUploadFromFile(window_id->msgCopyInfo, status);
|
|
|
|
// if the destination folder is open in a thread pane then really run the update
|
|
// otherwise we rely on tricking the FE until the user opens this folder.
|
|
MSG_Pane *threadPane = window_id->imapURLPane->GetMaster()->FindThreadPaneNamed(dstImapFolder->GetPathname());
|
|
|
|
if (threadPane)
|
|
{
|
|
MSG_UrlQueue *queue = MSG_UrlQueue::FindQueue(URL_s->address,window_id);
|
|
dstImapFolder->StartUpdateOfNewlySelectedFolder(threadPane, FALSE /*pretend we're loading folder, was FALSE*/, queue,
|
|
NULL, FALSE, FALSE);
|
|
}
|
|
}
|
|
MSG_MessageCopyIsCompleted (&window_id->msgCopyInfo);
|
|
if (URL_s->msg_pane->GetGoOnlineState())
|
|
OfflineOpExitFunction( URL_s, status, window_id);
|
|
|
|
|
|
if (MSG_Pane::PaneInMasterList(window_id->imapURLPane))
|
|
{
|
|
MSG_FolderPane *folderPane = (MSG_FolderPane *) window_id->imapURLPane->GetMaster()->FindFirstPaneOfType (MSG_FOLDERPANE);
|
|
if (folderPane)
|
|
folderPane->PostProcessRemoteCopyAction();
|
|
}
|
|
else
|
|
XP_ASSERT(FALSE); // where'd our pane go? Probably a progress pane, but still.
|
|
}
|
|
|
|
void PostMessageCopyUrlExitFunc (URL_Struct *URL_s, int status,
|
|
MWContext *window_id)
|
|
{
|
|
XP_ASSERT(window_id->msgCopyInfo);
|
|
|
|
if (window_id->msgCopyInfo)
|
|
{
|
|
if (window_id->msgCopyInfo->moveState.urlForNextKeyLoad)
|
|
{
|
|
XP_ASSERT(window_id->msgCopyInfo->moveState.sourcePane);
|
|
|
|
URL_Struct *urlStruct = NET_CreateURLStruct(window_id->msgCopyInfo->moveState.urlForNextKeyLoad, NET_DONT_RELOAD);
|
|
FREEIF (window_id->msgCopyInfo->moveState.urlForNextKeyLoad);
|
|
window_id->msgCopyInfo->moveState.urlForNextKeyLoad = NULL;
|
|
|
|
if ((window_id->msgCopyInfo->srcFolder->GetType() == FOLDER_IMAPMAIL) &&
|
|
(window_id->msgCopyInfo->moveState.sourcePane->GetPaneType() == MSG_MESSAGEPANE))
|
|
{
|
|
XP_DELETE(urlStruct); // don't need it, but we need to delete urlForNextKeyLoad before we start next url
|
|
MSG_MessagePane *messagePane = (MSG_MessagePane *) window_id->msgCopyInfo->moveState.sourcePane;
|
|
// let the message pane load the message. It needs to reset it's own state for loading a new message
|
|
// just blasting the message into it never called any MSG_MessagePane functions.
|
|
messagePane->LoadMessage(window_id->msgCopyInfo->srcFolder,
|
|
window_id->msgCopyInfo->moveState.nextKeyToLoad,
|
|
PostMessageCopyUrlExitFunc,
|
|
TRUE);
|
|
}
|
|
else if (window_id->msgCopyInfo->moveState.sourcePane->GetPaneType() == MSG_MESSAGEPANE)
|
|
{
|
|
// MSG_MessagePane::OpenMessageSock will get called by mailbox url
|
|
MSG_Pane *savePane = window_id->msgCopyInfo->moveState.sourcePane;
|
|
MSG_MessageCopyIsCompleted (&window_id->msgCopyInfo);
|
|
MSG_UrlQueue::AddUrlToPane(urlStruct, NULL, savePane);
|
|
}
|
|
else
|
|
{// probably a move from a thread pane, no message load, we are done
|
|
XP_DELETE(urlStruct); // don't need it, but we need to delete urlForNextKeyLoad before we start next url
|
|
PostMessageCopyUrlExitCompleted (URL_s, status, window_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PostMessageCopyUrlExitCompleted (URL_s, status, window_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MSG_FolderInfoMail::CloseOfflineDestinationDbAfterMove(msg_move_state *state)
|
|
{
|
|
if (state->destDB)
|
|
{
|
|
state->destDB->Close();
|
|
state->destDB = NULL;
|
|
MailDB::SetFolderInfoValid(GetPathname(), 0, 0);
|
|
SummaryChanged();
|
|
}
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::CheckForLegalCopy(MSG_FolderInfo *dstFolder)
|
|
{
|
|
XP_ASSERT(dstFolder);
|
|
int err = 0;
|
|
|
|
if (!dstFolder)
|
|
err = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
else if ( (FOLDER_MAIL != dstFolder->GetType()) && (FOLDER_IMAPMAIL != dstFolder->GetType()) )
|
|
err = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
else if (this == dstFolder)
|
|
err = MK_MSG_CANT_COPY_TO_SAME_FOLDER;
|
|
else if (dstFolder->GetFlags() & (MSG_FOLDER_FLAG_QUEUE)) {
|
|
const char *q = MSG_GetQueueFolderName();
|
|
if (q) {
|
|
if (!strcasecomp(q,QUEUE_FOLDER_NAME_OLD))
|
|
err = MK_MSG_CANT_COPY_TO_QUEUE_FOLDER_OLD;
|
|
else
|
|
err = MK_MSG_CANT_COPY_TO_QUEUE_FOLDER;
|
|
}
|
|
else
|
|
err = MK_MSG_CANT_COPY_TO_QUEUE_FOLDER;
|
|
}
|
|
else if (dstFolder->GetFolderPrefFlags() & MSG_FOLDER_PREF_IMAPNOSELECT)
|
|
err = MK_MSG_COPY_TARGET_NO_SELECT;
|
|
|
|
#ifdef DISABL_COPY_MSG_TO_DRAFTS
|
|
else if (dstFolder->GetFlags() & (MSG_FOLDER_FLAG_DRAFTS))
|
|
err = MK_MSG_CANT_COPY_TO_DRAFTS_FOLDER;
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::BeginOfflineAppend(MSG_IMAPFolderInfoMail *dstFolder,
|
|
MessageDB *sourceDB,
|
|
IDArray *srcArray,
|
|
int32 srcCount,
|
|
msg_move_state *state)
|
|
{
|
|
MsgERR stopit = 0;
|
|
MailDB *sourceMailDB = sourceDB ? sourceDB->GetMailDB() : 0;
|
|
if (sourceMailDB)
|
|
{
|
|
for (int sourceKeyIndex=0; !stopit && (sourceKeyIndex < srcCount); sourceKeyIndex++)
|
|
{
|
|
if (state->destDB)
|
|
{
|
|
MessageKey fakeKey = 1;
|
|
fakeKey += state->destDB->ListHighwaterMark();
|
|
|
|
MailMessageHdr *mailHdr = sourceMailDB->GetMailHdrForKey(srcArray->GetAt(sourceKeyIndex));
|
|
if (mailHdr)
|
|
{
|
|
MailMessageHdr *newMailHdr = new MailMessageHdr;
|
|
newMailHdr->CopyFromMsgHdr(mailHdr, sourceMailDB->GetDB(), state->destDB->GetDB());
|
|
newMailHdr->SetMessageKey(fakeKey);
|
|
|
|
uint32 onlineMsgSize = dstFolder->MessageCopySize(mailHdr, state); // does its own seek
|
|
|
|
XP_FileSeek (state->infid, // seek to beginning of message
|
|
mailHdr->GetMessageOffset(),
|
|
SEEK_SET);
|
|
|
|
uint32 bytesWritten = 0;
|
|
while (bytesWritten < onlineMsgSize)
|
|
bytesWritten += dstFolder->WriteBlockToImapServer(mailHdr, state, newMailHdr, TRUE);
|
|
|
|
newMailHdr->SetMessageSize(onlineMsgSize);
|
|
newMailHdr->OrFlags(kOffline);
|
|
|
|
stopit = state->destDB->AddHdrToDB(newMailHdr, NULL, TRUE);
|
|
|
|
DBOfflineImapOperation *fakeOp = state->destDB->GetOfflineOpForKey(fakeKey, TRUE);
|
|
if (fakeOp)
|
|
{
|
|
fakeOp->SetSourceMailbox(GetPathname(), srcArray->GetAt(sourceKeyIndex));
|
|
delete fakeOp;
|
|
}
|
|
else
|
|
stopit = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
|
|
DBOfflineImapOperation *op = sourceMailDB->GetOfflineOpForKey(srcArray->GetAt(sourceKeyIndex), TRUE);
|
|
if (op)
|
|
{
|
|
const char *destinationPath = dstFolder->GetOnlineName();
|
|
|
|
op->AddMessageCopyOperation(destinationPath);
|
|
delete op;
|
|
}
|
|
else
|
|
stopit = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
delete mailHdr;
|
|
}
|
|
else
|
|
stopit = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
}
|
|
else
|
|
stopit = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
}
|
|
|
|
if (state->destDB)
|
|
{
|
|
state->destDB->Commit();
|
|
sourceMailDB->Commit();
|
|
dstFolder->SummaryChanged();
|
|
FE_Progress(state->sourcePane->GetContext(), "");
|
|
}
|
|
}
|
|
else
|
|
stopit = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
|
|
return stopit ? stopit : -1;
|
|
}
|
|
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::BeginCopyingMessages (MSG_FolderInfo *dstFolder,
|
|
MessageDB *sourceDB,
|
|
IDArray *srcArray,
|
|
MSG_UrlQueue *urlQueue,
|
|
int32 srcCount,
|
|
MessageCopyInfo *copyInfo)
|
|
{
|
|
MsgERR err = CheckForLegalCopy(dstFolder);
|
|
|
|
if (err != 0)
|
|
return err;
|
|
|
|
msg_move_state *state = ©Info->moveState;
|
|
state->destfolder = dstFolder;
|
|
state->infid = XP_FileOpen (GetPathname(), xpMailFolder, XP_FILE_READ_BIN);
|
|
if (state->infid)
|
|
{
|
|
state->midMessage = FALSE;
|
|
state->messageBytesMovedSoFar = 0;
|
|
|
|
state->size = 10240;
|
|
while (!state->buf && (state->size > 1024))
|
|
{
|
|
state->buf = new char[state->size];
|
|
if (!state->buf)
|
|
state->size /= 2;
|
|
}
|
|
if (state->buf)
|
|
err = 0;
|
|
else
|
|
err = MK_OUT_OF_MEMORY;
|
|
}
|
|
else
|
|
err = MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
|
|
|
|
if ((dstFolder->GetType() == FOLDER_IMAPMAIL) && NET_IsOffline())
|
|
{
|
|
return BeginOfflineAppend((MSG_IMAPFolderInfoMail *)dstFolder,
|
|
sourceDB,
|
|
srcArray,
|
|
srcCount,
|
|
state);
|
|
}
|
|
|
|
if ((err == 0) && (dstFolder->GetType() == FOLDER_IMAPMAIL))
|
|
{
|
|
state->sourcePane->GetContext()->msgCopyInfo->imapOnLineCopyState = kInProgress;
|
|
// make sure that the URL we are firing does not start writing bytes to
|
|
// an uninitialized socket
|
|
state->uploadToIMAPsocket = NULL;
|
|
|
|
MSG_FolderInfoMail *dstMailFolder = dstFolder->GetMailFolderInfo();
|
|
DBMessageHdr *msgHdr = sourceDB->GetDBHdrForKey(srcArray->GetAt(0));
|
|
// remember this up front so we can tell the imap connection about it when we get told about the connection.
|
|
if (dstMailFolder && msgHdr)
|
|
{
|
|
state->msgSize = dstMailFolder->MessageCopySize(msgHdr, state);
|
|
state->msg_flags = msgHdr->GetFlags();
|
|
delete msgHdr;
|
|
}
|
|
|
|
// fire off the imap url to get the server ready for the message upload
|
|
MSG_IMAPFolderInfoMail *imapDstFolder = (MSG_IMAPFolderInfoMail *) dstFolder;
|
|
char *url_string = CreateImapOffToOnlineCopyUrl(imapDstFolder->GetHostName(),
|
|
imapDstFolder->GetOnlineName(),
|
|
imapDstFolder->GetOnlineHierarchySeparator());
|
|
|
|
if (urlQueue)
|
|
urlQueue->AddUrl(url_string, PostMessageCopyUrlExitFunc);
|
|
else
|
|
{
|
|
URL_Struct *url_struct = NET_CreateURLStruct(url_string, NET_DONT_RELOAD);
|
|
|
|
// after this url is done, this exit function will fire a url to update
|
|
// the destination folder
|
|
|
|
if (url_struct)
|
|
{
|
|
url_struct->pre_exit_fn = PostMessageCopyUrlExitFunc;
|
|
state->sourcePane->GetURL(url_struct, TRUE);
|
|
}
|
|
}
|
|
FREEIF(url_string);
|
|
}
|
|
|
|
if (err == 0)
|
|
{
|
|
MSG_FolderInfoMail *dstMailFolder = dstFolder->GetMailFolderInfo();
|
|
DBMessageHdr *msgHdr = sourceDB->GetDBHdrForKey(srcArray->GetAt(0));
|
|
if (dstMailFolder && msgHdr)
|
|
{
|
|
err = dstMailFolder->CreateMessageWriteStream(state, dstMailFolder->MessageCopySize(msgHdr, state), msgHdr->GetFlags());
|
|
delete msgHdr;
|
|
}
|
|
else
|
|
err = MK_CONNECTED; /* (rb) eID_NOT_FOUND; The message has already been deleted, but the UI has not
|
|
cached up, the user pressed the delete key way too fast. Instead say we are done. */
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
uint32 MSG_FolderInfoMail::SeekToEndOfMessageStore(msg_move_state *state)
|
|
{
|
|
uint32 newMsgPosition=0;
|
|
// state->writestate.fid can be 0 if the destination file is read only
|
|
if (state->writestate.fid && !XP_FileSeek (state->writestate.fid, 0, SEEK_END))
|
|
newMsgPosition = ftell (state->writestate.fid);
|
|
|
|
return newMsgPosition;
|
|
}
|
|
|
|
uint32 MSG_FolderInfoMail::WriteMessageBuffer(msg_move_state *state, char *buffer, uint32 bytesToWrite, MailMessageHdr * /*for offline append*/)
|
|
{
|
|
XP_FileWrite (buffer, bytesToWrite, state->writestate.fid);
|
|
if (ferror(state->writestate.fid))
|
|
return MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
return 0;
|
|
}
|
|
|
|
void MSG_FolderInfoMail::CloseMessageWriteStream(msg_move_state *state)
|
|
{
|
|
if (state->writestate.fid)
|
|
{
|
|
XP_FileClose (state->writestate.fid);
|
|
state->writestate.fid = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_FolderInfoMail::SaveMessageHeader(msg_move_state *state, MailMessageHdr *hdr, uint32 messageKey, MessageDB * srcDB)
|
|
{
|
|
if (state->destDB != NULL)
|
|
{
|
|
MailMessageHdr *newHdr = new MailMessageHdr();
|
|
if (newHdr)
|
|
{
|
|
MsgERR msgErr;
|
|
MessageDBView *view = state->sourcePane->GetMsgView();
|
|
|
|
// hack for the search pane case!!! hopefully I won't have to do this too often...
|
|
if (state->sourcePane->GetPaneType() == MSG_SEARCHPANE)
|
|
{
|
|
if (srcDB == NULL)
|
|
return; // nothing we can do here..
|
|
newHdr->CopyFromMsgHdr (hdr, srcDB->GetDB(), state->destDB->GetDB());
|
|
|
|
}
|
|
else
|
|
newHdr->CopyFromMsgHdr (hdr, view->GetDB()->GetDB(), state->destDB->GetDB());
|
|
|
|
// set new byte offset, since the offset in the old file is certainly wrong
|
|
newHdr->SetMessageKey (messageKey);
|
|
// PHP impedance mismatch on error types
|
|
msgErr = state->destDB->AddHdrToDB (newHdr, NULL, TRUE); // tell db to notify
|
|
|
|
// if this was an IMAP download, then we must tweak the message size to account
|
|
// for the envelope and line termination differences
|
|
if (state->finalDownLoadMessageSize)
|
|
{
|
|
newHdr->SetByteLength(state->finalDownLoadMessageSize);
|
|
newHdr->SetMessageSize(state->finalDownLoadMessageSize);
|
|
}
|
|
|
|
// update the folder info
|
|
// mailDb->m_dbFolderInfo->m_folderSize += hdr->GetByteLength();
|
|
// mailDb->m_dbFolderInfo->setDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int MSG_FolderInfoMail::FinishCopyingMessages (MWContext *currentContext,
|
|
MSG_FolderInfo *srcFolder,
|
|
MSG_FolderInfo *dstFolder,
|
|
MessageDB *sourceDB,
|
|
IDArray *srcArray,
|
|
int32 srcCount,
|
|
msg_move_state *state)
|
|
{
|
|
int err = 0;
|
|
// int32 diskSpace = 0;
|
|
uint32 messageLength = 0;
|
|
|
|
if (!state->infid)
|
|
return MK_MSG_ERROR_WRITING_MAIL_FOLDER;
|
|
|
|
if (!dstFolder->TestSemaphore(this))
|
|
{
|
|
err = dstFolder->AcquireSemaphore(this);
|
|
if (err)
|
|
{
|
|
FE_Alert (currentContext, XP_GetString(err));
|
|
return MK_MSG_FOLDER_BUSY;
|
|
}
|
|
}
|
|
|
|
MSG_FolderInfoMail *dstMailFolder = (MSG_FolderInfoMail *) dstFolder;
|
|
//diskSpace = FE_DiskSpaceAvailable(currentContext, dstMailFolder->GetPathname()); // too slow probably
|
|
|
|
// seek any relevant files to the correct position
|
|
|
|
MailMessageHdr *mailHdr = (MailMessageHdr *) sourceDB->GetDBHdrForKey(srcArray->GetAt(state->msgIndex));
|
|
if (mailHdr)
|
|
messageLength = mailHdr->GetByteLength();
|
|
/* When checking for disk space available, take in consideration possible database
|
|
changes, therefore ask for a little more than what the message size is.
|
|
Also, due to disk sector sizes, allocation blocks, etc. The space "available" may be greater
|
|
than the actual space usable. */
|
|
//if((diskSpace +3096) <= messageLength)
|
|
// return(MK_POP3_OUT_OF_DISK_SPACE);
|
|
|
|
if (!mailHdr)
|
|
return(MK_POP3_OUT_OF_DISK_SPACE);
|
|
|
|
XP_FileSeek (state->infid,
|
|
mailHdr->GetMessageOffset() + state->messageBytesMovedSoFar,
|
|
SEEK_SET);
|
|
|
|
uint32 storePosition = dstMailFolder->SeekToEndOfMessageStore(state);
|
|
UndoManager *undoManager = NULL;
|
|
|
|
undoManager = state->sourcePane->GetUndoManager();
|
|
|
|
|
|
// write header info if this starts a new message in an offline folder
|
|
if (!state->midMessage)
|
|
{
|
|
const char *statusMsgTemplate;
|
|
char *statusMsg;
|
|
|
|
statusMsgTemplate = XP_GetString(state->ismove ? MK_MSG_MOVING_MSGS_STATUS : MK_MSG_COPYING_MSGS_STATUS);
|
|
if (statusMsgTemplate)
|
|
{
|
|
statusMsg = PR_smprintf(statusMsgTemplate, state->msgIndex + 1, srcCount, dstMailFolder->GetName());
|
|
FE_Progress (currentContext, statusMsg);
|
|
int32 percent = (int32) ((100l * (int32)(state->msgIndex + 1)) / (int32)(srcCount));
|
|
FE_SetProgressBarPercent(currentContext, percent);
|
|
FREEIF(statusMsg);
|
|
}
|
|
if (dstMailFolder->GetType() == FOLDER_MAIL)
|
|
{
|
|
dstMailFolder->SaveMessageHeader(state, mailHdr, storePosition, sourceDB); // sourceDB added by mscott
|
|
// Undo stuff... Who knows what am I doing here
|
|
if (undoManager && undoManager->GetState() == UndoIdle)
|
|
state->sourcePane->GetUndoManager()->AddMsgKey(storePosition);
|
|
// end Undo stuff
|
|
}
|
|
}
|
|
|
|
HG87963
|
|
// copy 2k or up to 10k of data or one message
|
|
ImapOnlineCopyState imapAppendState = state->sourcePane->GetContext()->msgCopyInfo->imapOnLineCopyState;
|
|
|
|
if (dstMailFolder->MessageWriteStreamIsReadyForWrite(state))
|
|
{
|
|
uint32 bytesLeft = messageLength - state->messageBytesMovedSoFar;
|
|
|
|
uint32 nRead=0;
|
|
if (dstFolder->GetType() == FOLDER_IMAPMAIL)
|
|
{
|
|
if ((imapAppendState == kReadyForAppendData) && (bytesLeft > 0))
|
|
{
|
|
((MSG_IMAPFolderInfoMail *) dstFolder)->WriteBlockToImapServer(mailHdr, state, NULL, TRUE); // write it now.
|
|
|
|
// close the imap stream so we can determine success/fail from server
|
|
if (state->messageBytesMovedSoFar >= messageLength)
|
|
dstMailFolder->CloseMessageWriteStream(state);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// do a block read, this is not line based IO
|
|
nRead = XP_FileRead (state->buf, bytesLeft > state->size ? state->size : bytesLeft, state->infid);
|
|
err = dstMailFolder->WriteMessageBuffer(state, state->buf, nRead, NULL);
|
|
if (err == 0)
|
|
state->messageBytesMovedSoFar += nRead;
|
|
else
|
|
{
|
|
// wrote a partial message and somehow we are out of space, back out our changes
|
|
// by removing the message and header
|
|
XP_FileClose (state->writestate.fid); // close before truncating
|
|
state->writestate.fid = NULL;
|
|
if (mailHdr)
|
|
{
|
|
err = XP_FileTruncate (dstMailFolder->GetPathname(), xpMailFolder, storePosition);
|
|
sourceDB->DeleteHeader(mailHdr, NULL, FALSE);
|
|
delete mailHdr;
|
|
}
|
|
if (state->buf)
|
|
{
|
|
delete [] state->buf;
|
|
state->buf = NULL;
|
|
}
|
|
return MK_POP3_OUT_OF_DISK_SPACE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
XP_Bool failedImapAppend = imapAppendState == kFailedAppend;
|
|
if (failedImapAppend)
|
|
state->msgIndex = srcCount; // do not process any more appends.
|
|
|
|
if ((state->messageBytesMovedSoFar < messageLength) || (imapAppendState == kReadyForAppendData))
|
|
state->midMessage = TRUE;
|
|
else
|
|
{
|
|
state->midMessage = FALSE;
|
|
state->messageBytesMovedSoFar = 0;
|
|
state->msgIndex++;
|
|
if ((dstFolder->GetType() == FOLDER_IMAPMAIL) && (state->msgIndex < srcCount) )
|
|
{
|
|
// create the next IMAP message stream. It will upload the size
|
|
MSG_FolderInfoMail *dstMailFolder = (MSG_FolderInfoMail *) dstFolder;
|
|
DBMessageHdr *msgHdr = sourceDB->GetDBHdrForKey(srcArray->GetAt(state->msgIndex));
|
|
err = dstMailFolder->CreateMessageWriteStream(
|
|
state,
|
|
dstMailFolder->MessageCopySize(msgHdr, state), msgHdr->GetFlags());
|
|
delete msgHdr;
|
|
}
|
|
}
|
|
|
|
// if this is the end of the copy, close the source and destination
|
|
if (state->msgIndex == srcCount)
|
|
{
|
|
MSG_ViewIndex doomedIndex = MSG_VIEWINDEXNONE;
|
|
|
|
if (dstMailFolder->GetType() == FOLDER_MAIL)
|
|
{
|
|
dstMailFolder->CloseMessageWriteStream(state);
|
|
dstMailFolder->CloseOfflineDestinationDbAfterMove(state);
|
|
}
|
|
else // let the imap libnet module know that we are done with appends
|
|
{
|
|
if (!failedImapAppend)
|
|
UpdatePendingCounts(dstMailFolder, srcArray);
|
|
IMAP_UploadAppendMessageSize(state->imap_connection, 0,0);
|
|
}
|
|
|
|
|
|
|
|
// *** Undo hook up - local folder only for now
|
|
if (!failedImapAppend && undoManager && (undoManager->GetState() == UndoIdle) &&
|
|
(dstMailFolder->GetType() == FOLDER_MAIL))
|
|
{
|
|
MoveCopyMessagesUndoAction *undoAction =
|
|
new MoveCopyMessagesUndoAction(srcFolder,
|
|
dstFolder,
|
|
state->ismove,
|
|
state->sourcePane,
|
|
srcArray->GetAt(0),
|
|
state->nextKeyToLoad);
|
|
|
|
if (undoAction)
|
|
{
|
|
uint i;
|
|
for (i=0; i < srcCount; i++)
|
|
undoAction->AddDstKey(undoManager->GetAndRemoveMsgKey());
|
|
for (i=0; i < srcCount; i++)
|
|
undoAction->AddSrcKey(srcArray->GetAt(i));
|
|
undoManager->AddUndoAction(undoAction);
|
|
}
|
|
}
|
|
// *** End Undo Hook up
|
|
|
|
MessageDBView * view = NULL;
|
|
// a search pane does not have a view, we need to work around this fact...
|
|
if (state->sourcePane && state->sourcePane->GetPaneType() == MSG_SEARCHPANE) // make sure we can cast..
|
|
view = ((MSG_SearchPane *) state->sourcePane)->GetView(srcFolder);
|
|
else
|
|
view = state->sourcePane ? state->sourcePane->GetMsgView() : 0;
|
|
|
|
// if this was a move delete the source messages
|
|
int32 i;
|
|
if (state->ismove && !failedImapAppend)
|
|
{
|
|
// Run the delete loop forwards
|
|
// so the indices will be right for DeleteMessagesByIndex
|
|
if (state->sourcePane)
|
|
{
|
|
IDArray viewIndices;
|
|
for (i = 0; i < srcCount; i++)
|
|
{
|
|
// should we stop on error?
|
|
state->msgIndex--;
|
|
if (view)
|
|
{
|
|
MessageKey key = srcArray->GetAt(i);
|
|
|
|
// Tell the FE we're deleting this message in case they want to close
|
|
// any open message windows.
|
|
if (MSG_THREADPANE == state->sourcePane->GetPaneType())
|
|
FE_PaneChanged (state->sourcePane, TRUE, MSG_PaneNotifyMessageDeleted, (uint32) key);
|
|
|
|
doomedIndex = view->FindViewIndex(key);
|
|
viewIndices.Add(doomedIndex);
|
|
}
|
|
}
|
|
if (view)
|
|
{
|
|
//const char *statusMsgTemplate;
|
|
//char *statusMsg;
|
|
|
|
//statusMsgTemplate = XP_GetString(MK_MSG_DELETING_MSGS_STATUS);
|
|
//if (statusMsgTemplate)
|
|
//{
|
|
// statusMsg = PR_smprintf(statusMsgTemplate, state->msgIndex, srcCount);
|
|
// FE_Progress (currentContext, statusMsg);
|
|
// FREEIF(statusMsg);
|
|
//}
|
|
|
|
state->sourcePane->StartingUpdate(MSG_NotifyNone, 0, 0);
|
|
|
|
if (srcFolder->GetType() != FOLDER_IMAPMAIL)
|
|
DeleteMsgsOnServer(state, sourceDB, srcArray, srcCount);
|
|
err = view->DeleteMessagesByIndex (viewIndices.GetData(), viewIndices.GetSize(), TRUE /* delete from db */);
|
|
|
|
state->sourcePane->EndingUpdate(MSG_NotifyNone, 0, 0);
|
|
}
|
|
}
|
|
srcFolder->SummaryChanged();
|
|
}
|
|
|
|
if (state->infid)
|
|
{
|
|
XP_FileClose (state->infid);
|
|
state->infid = NULL;
|
|
}
|
|
if (state->buf)
|
|
{
|
|
delete [] state->buf;
|
|
state->buf = NULL;
|
|
}
|
|
delete srcArray;
|
|
|
|
if (view)
|
|
SetUrlForNextMessageLoad(state,view,doomedIndex); // should we do this if we are a search pane?
|
|
|
|
// search as view modification..inform search pane that we are done with this view
|
|
// if (state->sourcePane && state->sourcePane->GetPaneType() == MSG_SEARCHPANE)
|
|
// ((MSG_SearchPane *) state->sourcePane)->CloseView(srcFolder);
|
|
|
|
err = MK_CONNECTED;
|
|
}
|
|
delete mailHdr;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
// A message is being deleted from a POP3 mail file, so check and see if we have the message
|
|
// being deleted in the server. If so, then we need to remove the message from the server as well.
|
|
// We have saved the UIDL of the message in the popstate.dat file and we must match this uidl, so
|
|
// read the message headers and see if we have it, then mark the message for deletion from the server.
|
|
// The next time we look at mail the message will be deleted from the server.
|
|
|
|
void MSG_FolderInfoMail::DeleteMsgsOnServer(msg_move_state *state, MessageDB *sourceDB, IDArray *srcArray, int32 srcCount)
|
|
{
|
|
char *uidl;
|
|
char *header = NULL;
|
|
uint32 offset = 0, err = 0, size = 0, len = 0, i = 0;
|
|
MailMessageHdr *hdr = NULL;
|
|
MessageKey key = 0;
|
|
XP_Bool leave_on_server = FALSE;
|
|
XP_Bool changed = FALSE;
|
|
char *popData = NULL;
|
|
|
|
PREF_GetBoolPref("mail.leave_on_server", &leave_on_server);
|
|
offset = XP_FileTell(state->infid);
|
|
header = (char*) XP_ALLOC(512);
|
|
for (i = 0; header && (i < srcCount); i++)
|
|
{
|
|
/* get uidl for this message */
|
|
uidl = NULL;
|
|
hdr = ((MailDB*) sourceDB)->GetMailHdrForKey(srcArray->GetAt(i));
|
|
if (hdr && ((hdr->GetFlags() & kPartial) || leave_on_server))
|
|
{
|
|
len = 0;
|
|
err = XP_FileSeek(state->infid, hdr->GetMessageOffset(), SEEK_SET); /* GetMessageKey */
|
|
len = hdr->GetMessageSize();
|
|
while ((len > 0) && !uidl)
|
|
{
|
|
size = len;
|
|
if (size > 512)
|
|
size = 512;
|
|
if (XP_FileReadLine(header, size, state->infid))
|
|
{
|
|
size = strlen(header);
|
|
if (!size)
|
|
len = 0;
|
|
else {
|
|
len -= size;
|
|
uidl = XP_STRSTR(header, X_UIDL);
|
|
}
|
|
} else
|
|
len = 0;
|
|
}
|
|
if (uidl)
|
|
{
|
|
if (!popData)
|
|
popData = ReadPopData((char*) MSG_GetPopHost(GetMaster()->GetPrefs()));
|
|
uidl += X_UIDL_LEN + 2; // skip UIDL: header
|
|
len = strlen(uidl);
|
|
if (len >= 2)
|
|
uidl[len - 2] = 0; // get rid of return-newline at end of string
|
|
net_pop3_delete_if_in_server(popData, uidl, &changed);
|
|
}
|
|
}
|
|
delete hdr;
|
|
}
|
|
FREEIF(header);
|
|
if (popData)
|
|
{
|
|
if (changed)
|
|
SavePopData(popData);
|
|
KillPopData(popData);
|
|
popData = NULL;
|
|
}
|
|
err = XP_FileSeek(state->infid, offset, SEEK_SET);
|
|
}
|
|
|
|
|
|
// This algorithm loads the next message url into the move state
|
|
// This function is shared by local and imap copies
|
|
void MSG_FolderInfoMail::SetUrlForNextMessageLoad(msg_move_state *state, MessageDBView *view, MSG_ViewIndex doomedIndex)
|
|
{
|
|
//if (state->nextKeyToLoad!=MSG_MESSAGEKEYNONE)
|
|
{
|
|
// in general, the next key to load is the message currently at the
|
|
// position of the last key deleted, i.e., doomedIndex.
|
|
// If that's not valid, use state->nextKeyToLoad.
|
|
MessageKey nextKeyToLoad = view->GetAt(doomedIndex);
|
|
if (nextKeyToLoad == MSG_MESSAGEKEYNONE)
|
|
nextKeyToLoad = state->nextKeyToLoad;
|
|
else
|
|
state->nextKeyToLoad = nextKeyToLoad; // used in PostMessageCopyUrlExitFunc
|
|
|
|
if (nextKeyToLoad != MSG_MESSAGEKEYNONE)
|
|
state->urlForNextKeyLoad = BuildUrl(view->GetDB(), nextKeyToLoad);
|
|
else if (state->ismove)
|
|
FE_PaneChanged(state->sourcePane, TRUE, MSG_PaneNotifyLastMessageDeleted, 0);
|
|
}
|
|
}
|
|
|
|
|
|
// returns a pointer, within the passed buffer, to the
|
|
// next CR, LF, or '\0'
|
|
char *MSG_FolderInfoMail::FindNextLineTerminatingCharacter(char *bufferToSearch)
|
|
{
|
|
char *returnValue = bufferToSearch;
|
|
while ( *returnValue && (*returnValue != CR) && (*returnValue != LF) )
|
|
returnValue++;
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
|
|
// This function ensures that each line in the buffer ends in
|
|
// CRLF, as required by RFC822 and therefore IMAP APPEND.
|
|
//
|
|
// This function assumes that there is enough room in the buffer to
|
|
// double its size. The return value is the new buffer size.
|
|
uint32 MSG_FolderInfoMail::ConvertBufferToRFC822LineTermination(char *buffer, uint32 bufferSize, XP_Bool *lastCharacterOfPrevBufferWasCR)
|
|
{
|
|
// check for a <CR><LF> stradling buffer boundaries
|
|
if (*lastCharacterOfPrevBufferWasCR && (*buffer == LF))
|
|
{
|
|
// we added a LF last time, shifting the buffer seems inefficient but this condition is very rare
|
|
XP_MEMMOVE(buffer, // destination
|
|
buffer+1, // source
|
|
--bufferSize);
|
|
}
|
|
|
|
*lastCharacterOfPrevBufferWasCR = *(buffer + bufferSize - 1) == CR;
|
|
|
|
|
|
|
|
// adjust the rest of the buffer
|
|
uint32 adjustedBufferSize = bufferSize;
|
|
*(buffer + bufferSize) = '\0'; // write a '\0' at the end so FindNextLineTerminatingCharacter will stop
|
|
|
|
char *currentLineTerminator = FindNextLineTerminatingCharacter(buffer);
|
|
|
|
if (*currentLineTerminator)
|
|
{
|
|
do {
|
|
if (XP_STRNCMP(currentLineTerminator, CRLF, 2))
|
|
{
|
|
XP_MEMMOVE(currentLineTerminator+2, // destination
|
|
currentLineTerminator+1, // source
|
|
adjustedBufferSize - (currentLineTerminator - buffer));
|
|
|
|
*currentLineTerminator++ = CR;
|
|
*currentLineTerminator++ = LF;
|
|
adjustedBufferSize++;
|
|
}
|
|
else
|
|
currentLineTerminator += 2;
|
|
currentLineTerminator = FindNextLineTerminatingCharacter(currentLineTerminator);
|
|
} while (*currentLineTerminator);
|
|
}
|
|
|
|
return adjustedBufferSize;
|
|
}
|
|
|
|
|
|
char *MSG_FolderInfoMail::CreatePlatformLeafNameForDisk(const char *userLeafName, MSG_Master *master, MSG_FolderInfoMail *parent)
|
|
{
|
|
char *leafName = NULL;
|
|
char *baseDir = NULL;
|
|
|
|
if (parent->m_depth > 1)
|
|
{
|
|
baseDir = (char *) XP_ALLOC(XP_STRLEN(parent->GetPathname()) + XP_STRLEN(".sbd") + 1);
|
|
if (baseDir)
|
|
{
|
|
XP_STRCPY(baseDir, parent->GetPathname());
|
|
XP_STRCAT(baseDir, ".sbd");
|
|
}
|
|
}
|
|
else
|
|
baseDir = XP_STRDUP(parent->GetPathname());
|
|
|
|
leafName = CreatePlatformLeafNameForDisk(userLeafName, master, baseDir);
|
|
|
|
FREEIF(baseDir);
|
|
|
|
return leafName;
|
|
}
|
|
|
|
|
|
|
|
|
|
char *MSG_FolderInfoMail::CreatePlatformLeafNameForDisk(const char *userLeafName, MSG_Master *, const char *baseDir)
|
|
{
|
|
const int charLimit = MAX_FILE_LENGTH_WITHOUT_EXTENSION; // set on platform specific basis
|
|
#if XP_MAC
|
|
const char *illegalChars = ":";
|
|
#elif defined(XP_WIN16) || defined(XP_OS2)
|
|
const char *illegalChars = "\"/\\[]:;=,|?<>*$. ";
|
|
#elif defined(XP_WIN32)
|
|
const char *illegalChars = "\"/\\[]:;=,|?<>*$";
|
|
#else // UNIX
|
|
const char *illegalChars = "";
|
|
#endif
|
|
|
|
// if we weren't passed in anything, return NULL
|
|
if (!userLeafName || !baseDir)
|
|
return NULL;
|
|
|
|
// mangledLeaf is the new leaf name.
|
|
// If userLeafName (a) contains all legal characters
|
|
// (b) is within the valid length for the given platform
|
|
// (c) does not already exist on the disk
|
|
// then we simply return XP_STRDUP(userLeafName)
|
|
// Otherwise we mangle it
|
|
char *mangledLeaf = NULL;
|
|
|
|
// leafLength is the length of mangledLeaf which we will return
|
|
// if userLeafName is greater than the maximum allowed for this
|
|
// platform, then we truncate and mangle it. Otherwise leave it alone.
|
|
int32 leafLength = MIN(XP_STRLEN(userLeafName),charLimit);
|
|
|
|
// Create the path for the summary file, to see if it already exists
|
|
// on the disk
|
|
int dirNameLength = XP_STRLEN(baseDir);
|
|
|
|
// mangledPath is the entire path to the newly mangled leaf name
|
|
int allocatedSizeForMangledPath = dirNameLength + leafLength + 2;
|
|
char *mangledPath = (char *) XP_ALLOC(allocatedSizeForMangledPath); // 2 == length of '/' and '\0'
|
|
if (!mangledPath)
|
|
return NULL;
|
|
|
|
// copy the given leaf name into the mangled path for now, truncating it
|
|
// at the maximum length.
|
|
// enough for 676 names with matching prefix
|
|
XP_STRCPY(mangledPath, baseDir);
|
|
XP_STRCAT(mangledPath, "/"); // 3rd argument is amount of space left available for cat
|
|
XP_STRNCAT_SAFE(mangledPath, userLeafName, allocatedSizeForMangledPath - XP_STRLEN(mangledPath));
|
|
// mangledPath[XP_STRLEN(mangledPath)] = '\0';
|
|
|
|
// pathLeaf points to the location of the leaf name in mangledPath
|
|
char *pathLeaf = mangledPath + dirNameLength + 1;
|
|
|
|
// after this while, if (*illegalCharacterPtr == 0), there are no illegal characters
|
|
const char *illegalCharacterPtr = pathLeaf;
|
|
while (*illegalCharacterPtr && !XP_STRCHR(illegalChars, *illegalCharacterPtr) && (*illegalCharacterPtr >= 31))
|
|
illegalCharacterPtr++;
|
|
|
|
|
|
XP_StatStruct possibleNameStat;
|
|
if (!*illegalCharacterPtr && (0 != XP_Stat(mangledPath, &possibleNameStat, xpMailFolderSummary))) {
|
|
// if there are no illegal characters
|
|
// and the file doesn't already exist, then don't do anything to the string
|
|
// Note that this might be truncated to charLength, but if so, the file still
|
|
// does not exist, so we are OK.
|
|
mangledLeaf = XP_STRDUP(pathLeaf);
|
|
XP_FREE(mangledPath);
|
|
return mangledLeaf;
|
|
}
|
|
|
|
|
|
// if we are here, then any of the following may apply:
|
|
// (a) there were illegal characters
|
|
// (b) the file already existed
|
|
|
|
// First, replace all illegal characters with '_'
|
|
for (char *possibleillegal = pathLeaf; *possibleillegal; possibleillegal++)
|
|
if (XP_STRCHR(illegalChars, *possibleillegal) || (*possibleillegal < 31) )
|
|
*possibleillegal = '_';
|
|
|
|
|
|
// Now, we have to loop until we find a filename that doesn't already
|
|
// exist on the disk
|
|
XP_Bool nameSpaceExhausted = FALSE;
|
|
|
|
if (0 == XP_Stat(mangledPath, &possibleNameStat, xpMailFolderSummary)) {
|
|
if (leafLength >= 2) pathLeaf[leafLength - 2] = 'A';
|
|
pathLeaf[leafLength - 1] = 'A'; // leafLength must be at least 1
|
|
// pathLeaf[leafLength] = 0;
|
|
}
|
|
|
|
|
|
while (!nameSpaceExhausted && (0 == XP_Stat(mangledPath, &possibleNameStat, xpMailFolderSummary)))
|
|
{
|
|
|
|
if (leafLength >= 2)
|
|
{
|
|
if (++(pathLeaf[leafLength - 1]) > 'Z')
|
|
{
|
|
pathLeaf[leafLength - 1] = 'A';
|
|
nameSpaceExhausted = (pathLeaf[leafLength - 2])++ == 'Z';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nameSpaceExhausted = (++(pathLeaf[leafLength - 1]) == 'Z');
|
|
}
|
|
}
|
|
|
|
mangledLeaf = XP_STRDUP(pathLeaf);
|
|
|
|
XP_FREE(mangledPath);
|
|
return mangledLeaf;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoMail::CreateSubfolder (const char *leafNameFromUser, MSG_FolderInfo **ppOutFolder, int32 *pOutPos)
|
|
{
|
|
MsgERR status = 0;
|
|
*ppOutFolder = NULL;
|
|
*pOutPos = 0;
|
|
XP_StatStruct stat;
|
|
|
|
|
|
// Only create a .sbd pathname if we're not in the root folder. The root folder
|
|
// e.g. c:\netscape\mail has to behave differently than subfolders.
|
|
if (m_depth > 1)
|
|
{
|
|
// Look around in our directory to get a subdirectory, creating it
|
|
// if necessary
|
|
XP_BZERO (&stat, sizeof(stat));
|
|
if (0 == XP_Stat (m_pathName, &stat, xpMailSubdirectory))
|
|
{
|
|
if (!S_ISDIR(stat.st_mode))
|
|
status = MK_COULD_NOT_CREATE_DIRECTORY; // a file .sbd already exists
|
|
}
|
|
else {
|
|
status = XP_MakeDirectory (m_pathName, xpMailSubdirectory);
|
|
if (status == -1)
|
|
status = MK_COULD_NOT_CREATE_DIRECTORY;
|
|
}
|
|
}
|
|
|
|
char *leafNameForDisk = CreatePlatformLeafNameForDisk(leafNameFromUser,m_master, this);
|
|
if (!leafNameForDisk)
|
|
status = MK_OUT_OF_MEMORY;
|
|
|
|
if (0 == status) //ok so far
|
|
{
|
|
// Now that we have a suitable parent directory created/identified,
|
|
// we can create the new mail folder inside the parent dir. Again,
|
|
|
|
char *newFolderPath = (char*) XP_ALLOC(XP_STRLEN(m_pathName) + XP_STRLEN(leafNameForDisk) + XP_STRLEN(".sbd/") + 1);
|
|
if (newFolderPath)
|
|
{
|
|
XP_STRCPY (newFolderPath, m_pathName);
|
|
if (m_depth == 1)
|
|
XP_STRCAT (newFolderPath, "/");
|
|
else
|
|
XP_STRCAT (newFolderPath, ".sbd/");
|
|
XP_STRCAT (newFolderPath, leafNameForDisk);
|
|
|
|
if (0 != XP_Stat (newFolderPath, &stat, xpMailFolder))
|
|
{
|
|
XP_File file = XP_FileOpen(newFolderPath, xpMailFolder, XP_FILE_WRITE_BIN);
|
|
if (file)
|
|
{
|
|
// Create an empty database for this mail folder, set its name from the user
|
|
MailDB *unusedDb = NULL;
|
|
MailDB::Open(newFolderPath, TRUE, &unusedDb, TRUE);
|
|
if (unusedDb)
|
|
{
|
|
unusedDb->m_dbFolderInfo->SetMailboxName(leafNameFromUser);
|
|
|
|
MSG_FolderInfoMail *newFolder = BuildFolderTree (newFolderPath, m_depth + 1, m_subFolders, m_master);
|
|
if (newFolder)
|
|
{
|
|
// so we don't show ??? in totals
|
|
newFolder->SummaryChanged();
|
|
*ppOutFolder = newFolder;
|
|
*pOutPos = m_subFolders->FindIndex (0, newFolder);
|
|
}
|
|
else
|
|
status = MK_OUT_OF_MEMORY;
|
|
unusedDb->SetFolderInfoValid(newFolderPath,0,0);
|
|
unusedDb->Close();
|
|
}
|
|
else
|
|
{
|
|
XP_FileClose(file);
|
|
file = NULL;
|
|
XP_FileRemove (newFolderPath, xpMailFolder);
|
|
status = MK_MSG_CANT_CREATE_FOLDER;
|
|
}
|
|
if (file)
|
|
{
|
|
XP_FileClose(file);
|
|
file = NULL;
|
|
}
|
|
}
|
|
else
|
|
status = MK_MSG_CANT_CREATE_FOLDER;
|
|
}
|
|
else
|
|
status = MK_MSG_FOLDER_ALREADY_EXISTS;
|
|
FREEIF(newFolderPath);
|
|
}
|
|
else
|
|
status = MK_OUT_OF_MEMORY;
|
|
}
|
|
FREEIF(leafNameForDisk);
|
|
return status;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfoMail::RemoveSubFolder (MSG_FolderInfo *which)
|
|
{
|
|
// Let the base class do list management
|
|
MSG_FolderInfo::RemoveSubFolder (which);
|
|
|
|
// Derived class is responsible for managing the subdirectory
|
|
if (0 == m_subFolders->GetSize())
|
|
XP_RemoveDirectory (m_pathName, xpMailSubdirectory);
|
|
}
|
|
|
|
void MSG_FolderInfoMail::SetGettingMail(XP_Bool flag)
|
|
{
|
|
m_gettingMail = flag;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoMail::GetGettingMail()
|
|
{
|
|
return m_gettingMail;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::Delete ()
|
|
{
|
|
MessageDB *db;
|
|
// remove the summary file
|
|
MsgERR status = CloseDatabase (m_pathName, &db);
|
|
if (0 == status)
|
|
{
|
|
if (db != NULL)
|
|
db->Close(); // decrement ref count, so it will leave cache
|
|
XP_FileRemove (m_pathName, xpMailFolderSummary);
|
|
}
|
|
|
|
if ((0 == status) && (GetType() == FOLDER_MAIL))
|
|
{
|
|
// remove the mail folder file
|
|
status = XP_FileRemove (m_pathName, xpMailFolder);
|
|
|
|
// if the delete seems to have failed, but the file doesn't
|
|
// exist, that's not really an error condition, is it now?
|
|
if (status)
|
|
{
|
|
XP_StatStruct fileStat;
|
|
if (0 == XP_Stat(m_pathName, &fileStat, xpMailFolder))
|
|
status = 0;
|
|
}
|
|
}
|
|
|
|
|
|
if (0 != status)
|
|
status = MK_UNABLE_TO_DELETE_FILE;
|
|
return status;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfo::PropagateDelete (MSG_FolderInfo **ppFolder, XP_Bool deleteStorage)
|
|
{
|
|
MsgERR status = 0;
|
|
|
|
// first, find the folder we're looking to delete
|
|
for (int i = 0; i < m_subFolders->GetSize() && *ppFolder; i++)
|
|
{
|
|
MSG_FolderInfo *child = m_subFolders->GetAt(i);
|
|
if (*ppFolder == child)
|
|
{
|
|
// maybe delete disk storage for it, and its subfolders
|
|
status = child->RecursiveDelete(deleteStorage);
|
|
|
|
if (status == 0)
|
|
{
|
|
// Send out a broadcast message that this folder is going away.
|
|
// Many important things happen on this broadcast.
|
|
m_master->BroadcastFolderDeleted (child);
|
|
|
|
RemoveSubFolder(child);
|
|
delete child;
|
|
*ppFolder = NULL; // stop looking
|
|
}
|
|
}
|
|
else if ((*ppFolder)->GetDepth() > child->GetDepth())
|
|
{
|
|
status = child->PropagateDelete (ppFolder, deleteStorage);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfo::RecursiveDelete (XP_Bool deleteStorage)
|
|
{
|
|
// If deleteStorage is TRUE, recursively deletes disk storage for this folder
|
|
// and all its subfolders.
|
|
// Regardless of deleteStorage, always unlinks them from the children lists and
|
|
// frees memory for the subfolders but NOT for _this_
|
|
|
|
MsgERR status = 0;
|
|
|
|
for (int k = 0; k < m_subFolders->GetSize() && !status; k++)
|
|
{
|
|
MSG_FolderInfo *child = m_subFolders->GetAt(k);
|
|
status = child->RecursiveDelete(deleteStorage); // recur
|
|
RemoveSubFolder(child); // unlink it from this's child list
|
|
delete child; // free memory
|
|
}
|
|
|
|
// now delete the disk storage for _this_
|
|
if (deleteStorage && (status == 0))
|
|
status = Delete();
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::CloseDatabase (const char *path, MessageDB **outDb)
|
|
{
|
|
MsgERR err = eDBNotOpen;
|
|
|
|
char* dbName = WH_FileName(path, xpMailFolderSummary);
|
|
if (!dbName) return eOUT_OF_MEMORY;
|
|
MessageDB *db = MessageDB::FindInCache(dbName);
|
|
XP_FREE(dbName);
|
|
if (db)
|
|
err = db->CloseDB();
|
|
|
|
if (outDb)
|
|
*outDb = db;
|
|
|
|
// This is not really an error condition when trying to close the DB
|
|
if (eDBNotOpen == err)
|
|
err = 0;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::ReopenDatabase (MessageDB *db, const char *newPath)
|
|
{
|
|
char* dbName = WH_FileName (newPath, xpMailFolderSummary);
|
|
if (!dbName) return eOUT_OF_MEMORY;
|
|
MsgERR err = db->OpenDB (dbName, FALSE /*create?*/);
|
|
if (eSUCCESS == err)
|
|
err = db->OnNewPath (newPath);
|
|
XP_FREE(dbName);
|
|
return err;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoMail::CanBeRenamed ()
|
|
{
|
|
// The root mail folder can't be renamed
|
|
if (m_depth < 2)
|
|
return FALSE;
|
|
|
|
// Here's a weird case necessitated because we don't have a separate
|
|
// preference for any folder name except the FCC folder (Sent). Others
|
|
// are known by name, and as such, can't be renamed. I guess.
|
|
if (m_flags & MSG_FOLDER_FLAG_TRASH ||
|
|
m_flags & MSG_FOLDER_FLAG_DRAFTS ||
|
|
m_flags & MSG_FOLDER_FLAG_QUEUE ||
|
|
m_flags & MSG_FOLDER_FLAG_INBOX ||
|
|
m_flags & MSG_FOLDER_FLAG_TEMPLATES)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::Rename (const char * newUserLeafName)
|
|
{
|
|
// change the leaf name (stored separately)
|
|
MsgERR status = MSG_FolderInfo::Rename (newUserLeafName);
|
|
if (status == 0)
|
|
{
|
|
char *baseDir = XP_STRDUP(m_pathName);
|
|
if (baseDir)
|
|
{
|
|
char *base_slash = XP_STRRCHR (baseDir, '/');
|
|
if (base_slash)
|
|
*base_slash = '\0';
|
|
}
|
|
|
|
char *leafNameForDisk = CreatePlatformLeafNameForDisk(newUserLeafName,m_master, baseDir);
|
|
if (!leafNameForDisk)
|
|
status = MK_OUT_OF_MEMORY;
|
|
|
|
if (0 == status)
|
|
{
|
|
// calculate the new path name
|
|
char *newPath = (char*) XP_ALLOC(XP_STRLEN(m_pathName) + XP_STRLEN(leafNameForDisk) + 1);
|
|
XP_STRCPY (newPath, m_pathName);
|
|
char *slash = XP_STRRCHR (newPath, '/');
|
|
if (slash)
|
|
XP_STRCPY (slash + 1, leafNameForDisk);
|
|
|
|
// rename the mail summary file, if there is one
|
|
MessageDB *db = NULL;
|
|
status = CloseDatabase (m_pathName, &db);
|
|
|
|
XP_StatStruct fileStat;
|
|
if (!XP_Stat(m_pathName, &fileStat, xpMailFolderSummary))
|
|
status = XP_FileRename(m_pathName, xpMailFolderSummary, newPath, xpMailFolderSummary);
|
|
if (0 == status)
|
|
{
|
|
if (db)
|
|
{
|
|
if (ReopenDatabase (db, newPath) == 0)
|
|
db->m_dbFolderInfo->SetMailboxName(newUserLeafName);
|
|
}
|
|
else
|
|
{
|
|
MailDB *mailDb = NULL;
|
|
MailDB::Open(newPath, TRUE, &mailDb, TRUE);
|
|
if (mailDb)
|
|
{
|
|
mailDb->m_dbFolderInfo->SetMailboxName(newUserLeafName);
|
|
mailDb->Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
// rename the mail folder file, if its local
|
|
if ((status == 0) && (GetType() == FOLDER_MAIL))
|
|
status = XP_FileRename (m_pathName, xpMailFolder, newPath, xpMailFolder);
|
|
|
|
if (status == 0)
|
|
{
|
|
// rename the subdirectory if there is one
|
|
if (m_subFolders->GetSize() > 0)
|
|
status = XP_FileRename (m_pathName, xpMailSubdirectory, newPath, xpMailSubdirectory);
|
|
|
|
// tell all our children about the new pathname
|
|
if (status == 0)
|
|
{
|
|
int startingAt = XP_STRLEN (newPath) - XP_STRLEN (leafNameForDisk) + 1; // add one for trailing '/'
|
|
status = PropagateRename (leafNameForDisk, startingAt);
|
|
}
|
|
}
|
|
}
|
|
FREEIF(baseDir);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::PropagateRename (const char *newName, int startingAt)
|
|
{
|
|
XP_ASSERT(newName);
|
|
XP_ASSERT(m_pathName);
|
|
|
|
// change our own pathname in memory to reflect the new name
|
|
MsgERR err = 0;
|
|
char *walkPath = &m_pathName[startingAt];
|
|
char *diffEnd = XP_STRCHR (walkPath, '/');
|
|
int diffLen = diffEnd ? (XP_STRLEN(diffEnd) + 1) : 0;
|
|
|
|
char *newPath = (char*) XP_ALLOC (startingAt + XP_STRLEN(newName) + diffLen + 5);
|
|
if (newPath)
|
|
{
|
|
XP_STRNCPY_SAFE (newPath, m_pathName, startingAt);
|
|
XP_STRCAT (newPath, newName);
|
|
if (diffLen > 0)
|
|
{
|
|
XP_STRCAT (newPath, ".sbd");
|
|
XP_STRNCAT_SAFE (newPath, diffEnd, diffLen);
|
|
}
|
|
XP_FREE (m_pathName);
|
|
m_pathName = newPath;
|
|
}
|
|
else
|
|
err = MK_OUT_OF_MEMORY;
|
|
|
|
// recurse down the subfolders, changing their pathname in memory
|
|
MSG_FolderInfoMail *folder = NULL;
|
|
for (int i = 0; i < m_subFolders->GetSize() && err == 0; i++)
|
|
{
|
|
folder = (MSG_FolderInfoMail*) m_subFolders->GetAt(i);
|
|
XP_ASSERT(folder->IsMail());
|
|
err = folder->PropagateRename (newName, startingAt);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::PropagateAdopt (const char *parentName, uint8 parentDepth)
|
|
{
|
|
XP_ASSERT(parentName);
|
|
MSG_FolderInfoMail *child = NULL;
|
|
MsgERR err = eSUCCESS;
|
|
|
|
// Build the new path to this folder
|
|
const char *pathNameLeaf = XP_STRRCHR (m_pathName, '/');
|
|
XP_ASSERT(pathNameLeaf);
|
|
|
|
char *newPathName = (char*) XP_ALLOC (XP_STRLEN(parentName) + XP_STRLEN(pathNameLeaf) + XP_STRLEN(".sbd/") + 1);
|
|
XP_STRCPY (newPathName, parentName);
|
|
if (parentDepth > 1)
|
|
XP_STRCAT (newPathName, ".sbd");
|
|
XP_STRCAT (newPathName, pathNameLeaf);
|
|
|
|
// Move the mail folder new parent's tree
|
|
int mailboxRenameStatus = 0;
|
|
if (GetType() == FOLDER_MAIL) // only rename local mailbox file, imap mailbox is not here - km
|
|
mailboxRenameStatus = XP_FileRename(m_pathName, xpMailFolder, newPathName, xpMailFolder);
|
|
if (0 == mailboxRenameStatus)
|
|
{
|
|
// Close the summary file and move it
|
|
MessageDB *db = NULL;
|
|
err = CloseDatabase (m_pathName, &db);
|
|
int dataBaseRenameStatus = 0;
|
|
XP_StatStruct fileStat;
|
|
if (!XP_Stat(m_pathName, &fileStat, xpMailFolderSummary))
|
|
dataBaseRenameStatus = XP_FileRename(m_pathName, xpMailFolderSummary, newPathName, xpMailFolderSummary);
|
|
if (0 == dataBaseRenameStatus)
|
|
{
|
|
if (db)
|
|
err = ReopenDatabase (db, newPathName);
|
|
|
|
// Depth at new location may be different
|
|
m_depth = parentDepth + 1;
|
|
|
|
if (m_subFolders->GetSize() > 0)
|
|
{
|
|
// Create a subdirectory in the new location
|
|
XP_MakeDirectory (newPathName, xpMailSubdirectory);
|
|
|
|
// Adopt our children into the new tree
|
|
for (int i = 0; i < m_subFolders->GetSize(); i++)
|
|
{
|
|
child = (MSG_FolderInfoMail*) m_subFolders->GetAt (i);
|
|
err = child->PropagateAdopt (newPathName, m_depth);
|
|
}
|
|
|
|
// Remove the old subdirectory since its children have been moved
|
|
XP_RemoveDirectory (m_pathName, xpMailSubdirectory);
|
|
}
|
|
|
|
// Now we're really done with the old name
|
|
XP_FREE(m_pathName);
|
|
m_pathName = newPathName;
|
|
newPathName = NULL;
|
|
}
|
|
else
|
|
err = MK_MSG_CANT_CREATE_FOLDER;
|
|
}
|
|
else
|
|
err = MK_MSG_CANT_CREATE_FOLDER;
|
|
|
|
if (newPathName)
|
|
XP_FREE(newPathName); // only for error conditions
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoMail::Adopt (MSG_FolderInfo *srcFolder, int32 *pOutPos)
|
|
{
|
|
MsgERR err = eSUCCESS;
|
|
XP_ASSERT (srcFolder->GetType() == GetType()); // we can only adopt the same type of folder
|
|
MSG_FolderInfoMail *mailFolder = (MSG_FolderInfoMail*) srcFolder;
|
|
|
|
if (srcFolder == this)
|
|
return MK_MSG_CANT_COPY_TO_SAME_FOLDER;
|
|
|
|
if (ContainsChildNamed(mailFolder->GetName()))
|
|
return MK_MSG_FOLDER_ALREADY_EXISTS;
|
|
|
|
// If we aren't already a directory, create the directory and set the flag bits
|
|
if (0 == m_subFolders->GetSize())
|
|
{
|
|
XP_Dir dir = XP_OpenDir (m_pathName, xpMailSubdirectory);
|
|
if (dir)
|
|
XP_CloseDir (dir);
|
|
else
|
|
{
|
|
XP_MakeDirectory (m_pathName, xpMailSubdirectory);
|
|
dir = XP_OpenDir (m_pathName, xpMailSubdirectory);
|
|
if (dir)
|
|
XP_CloseDir (dir);
|
|
else
|
|
err = MK_COULD_NOT_CREATE_DIRECTORY;
|
|
}
|
|
if (eSUCCESS == err)
|
|
{
|
|
m_flags |= MSG_FOLDER_FLAG_DIRECTORY;
|
|
m_flags |= MSG_FOLDER_FLAG_ELIDED;
|
|
}
|
|
}
|
|
|
|
// Recurse the tree to adopt srcFolder's children
|
|
err = mailFolder->PropagateAdopt (m_pathName, m_depth);
|
|
|
|
// Add the folder to our tree in the right sorted position
|
|
if (eSUCCESS == err)
|
|
{
|
|
XP_ASSERT(m_subFolders->FindIndex(0, srcFolder) == -1);
|
|
*pOutPos = m_subFolders->Add (srcFolder);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
MSG_FolderInfoMail::~MSG_FolderInfoMail()
|
|
{
|
|
if (m_pathName)
|
|
MSG_Biff_Master::RemoveBiffFolder(m_pathName);
|
|
FREEIF(m_pathName);
|
|
}
|
|
|
|
|
|
FolderType MSG_FolderInfoMail::GetType()
|
|
{
|
|
return FOLDER_MAIL;
|
|
}
|
|
|
|
|
|
char *MSG_FolderInfoMail::BuildUrl (MessageDB *db, MessageKey key)
|
|
{
|
|
const char *urlScheme = "mailbox:";
|
|
const char *urlSeparator = "?id=";
|
|
|
|
char *url = NULL;
|
|
if (db)
|
|
{
|
|
DBMessageHdr *msgHdr = db->GetDBHdrForKey(key);
|
|
if (msgHdr)
|
|
{
|
|
XPStringObj messageId;
|
|
msgHdr->GetMessageId(messageId, db->GetDB());
|
|
|
|
char *escapedId = EscapeMessageId (messageId);
|
|
if (escapedId) {
|
|
char *messageNumStr = PR_smprintf("&number=%ld", key);
|
|
|
|
url = (char*) XP_ALLOC (XP_STRLEN(m_pathName) +
|
|
XP_STRLEN(escapedId) + XP_STRLEN(urlScheme) +
|
|
XP_STRLEN(urlSeparator) +
|
|
XP_STRLEN(messageNumStr) + 1 /*null terminator*/);
|
|
|
|
if (url) {
|
|
XP_STRCPY (url, urlScheme);
|
|
XP_STRCAT (url, m_pathName);
|
|
XP_STRCAT (url, urlSeparator);
|
|
XP_STRCAT (url, escapedId);
|
|
XP_STRCAT(url, messageNumStr);
|
|
}
|
|
XP_FREE(escapedId);
|
|
XP_FREE(messageNumStr);
|
|
}
|
|
delete msgHdr;
|
|
}
|
|
} else {
|
|
url = PR_smprintf("%s%s", urlScheme, m_pathName);
|
|
}
|
|
return url;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoMail::ReadDBFolderInfo (XP_Bool force /* = FALSE */)
|
|
{
|
|
MsgERR err = eSUCCESS;
|
|
|
|
if (m_master->InitFolderFromCache (this))
|
|
return err;
|
|
|
|
if (force || !m_HaveReadNameFromDB)
|
|
{
|
|
DBFolderInfo *folderInfo;
|
|
MessageDB *db;
|
|
|
|
err = GetDBFolderInfoAndDB (&folderInfo, &db);
|
|
if (eSUCCESS == err)
|
|
{
|
|
// Get the number of expunged byted
|
|
m_expungedBytes = folderInfo->m_expunged_bytes;
|
|
|
|
// Get the real mailbox name. Use this flag to preserve the
|
|
// contract we have with the FE, which is that we're allowed
|
|
// to give out an unallocated pointer, and must never free it.
|
|
if (!m_HaveReadNameFromDB)
|
|
{
|
|
if (db)
|
|
{
|
|
XPStringObj userBoxName;
|
|
db->m_dbFolderInfo->GetMailboxName(userBoxName);
|
|
if (XP_STRLEN(userBoxName))
|
|
SetName(userBoxName);
|
|
// else, this db is old or this folder was created from
|
|
// a mailbox file and no corresponding db file.
|
|
}
|
|
}
|
|
|
|
// Tell the base class to go get all its stuff
|
|
err = MSG_FolderInfo::ReadDBFolderInfo (force);
|
|
|
|
if (db)
|
|
db->Close();
|
|
}
|
|
|
|
// Set this so we don't keep asking if we didn't get it.
|
|
m_HaveReadNameFromDB = TRUE;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
#define kMailFolderCacheFormat "\t%s\t%ld"
|
|
#define kMailFolderCacheVersion 1
|
|
|
|
MsgERR MSG_FolderInfoMail::WriteToCache (XP_File f)
|
|
{
|
|
XP_FilePrintf (f, "\t%d", kMailFolderCacheVersion);
|
|
XP_FilePrintf (f, kMailFolderCacheFormat, GetName(), (long) m_expungedBytes);
|
|
|
|
return MSG_FolderInfo::WriteToCache (f);
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoMail::ReadFromCache (char *buf)
|
|
{
|
|
MsgERR err = eSUCCESS;
|
|
int version;
|
|
int tokensRead = sscanf (buf, "\t%d", &version);
|
|
SkipCacheTokens (&buf, tokensRead);
|
|
|
|
if (version == 1)
|
|
{
|
|
// Use natural long tmp to prevent 64-bit OSs from scribbling 64 bits into an int32
|
|
long tmpExpungedBytes;
|
|
char *tmpName = (char*) XP_ALLOC(XP_STRLEN(buf));
|
|
if (tmpName)
|
|
{
|
|
sscanf (buf, kMailFolderCacheFormat, tmpName, &tmpExpungedBytes);
|
|
char *name = buf + 1; // skip over tab;
|
|
char *secondTab = XP_STRCHR(name, '\t');
|
|
if (secondTab)
|
|
{
|
|
*secondTab = '\0';
|
|
SetName (name); // kinda hacky. maybe scan/printf wasn't such a good idea
|
|
*secondTab = '\t';
|
|
}
|
|
SkipCacheTokens (&buf, 2);
|
|
m_expungedBytes = (int32) tmpExpungedBytes;
|
|
XP_FREE(tmpName);
|
|
}
|
|
else
|
|
err = eOUT_OF_MEMORY;
|
|
m_HaveReadNameFromDB = TRUE;
|
|
}
|
|
else
|
|
err = eUNKNOWN;
|
|
|
|
err = MSG_FolderInfo::ReadFromCache (buf);
|
|
|
|
return err;
|
|
}
|
|
|
|
const char *MSG_FolderInfoMail::GetRelativePathName ()
|
|
{
|
|
#if 0
|
|
// ###phil What we really want here is the profile directory, but
|
|
// there's no XP way to get that yet. jonm says he'll add a pref.
|
|
const char *userMailDir = m_master->GetPrefs()->GetFolderDirectory();
|
|
return m_pathName + XP_STRLEN(userMailDir);
|
|
#else
|
|
return m_pathName;
|
|
#endif
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoMail::GetDBFolderInfoAndDB(DBFolderInfo **folderInfo, MessageDB **db)
|
|
{
|
|
MailDB *mailDB;
|
|
|
|
MsgERR openErr;
|
|
XP_Bool wasCreated;
|
|
XP_ASSERT(db && folderInfo);
|
|
if (!db || !folderInfo)
|
|
return eBAD_PARAMETER;
|
|
|
|
if (GetType() == FOLDER_MAIL)
|
|
openErr = MailDB::Open (GetMailFolderInfo()->GetPathname(), FALSE, &mailDB, FALSE);
|
|
else
|
|
openErr = ImapMailDB::Open (GetMailFolderInfo()->GetPathname(), FALSE, &mailDB,
|
|
GetMaster(), &wasCreated);
|
|
|
|
*db = mailDB;
|
|
if (openErr == eSUCCESS && *db)
|
|
*folderInfo = (*db)->GetDBFolderInfo();
|
|
return openErr;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoMail::UpdateSummaryTotals()
|
|
{
|
|
MsgERR err = ReadDBFolderInfo (TRUE /*force read*/);
|
|
|
|
// If we asked, but didn't get any, stop asking
|
|
if (m_numUnreadMessages == -1)
|
|
m_numUnreadMessages = -2;
|
|
|
|
return err == eSUCCESS;
|
|
}
|
|
|
|
|
|
MSG_FolderInfoMail* MSG_FolderInfoMail::FindPathname(const char* p)
|
|
{
|
|
if (XP_FILENAMECMP(m_pathName, p) == 0)
|
|
return this;
|
|
for (int i=0 ; i < m_subFolders->GetSize() ; i++) {
|
|
MSG_FolderInfoMail* sub = (MSG_FolderInfoMail*) m_subFolders->GetAt(i);
|
|
FolderType folderType = sub->GetType();
|
|
XP_ASSERT((folderType == FOLDER_MAIL) ||
|
|
(folderType == FOLDER_IMAPMAIL)); // descends from MSG_FolderInfoMail
|
|
MSG_FolderInfoMail* result = sub->FindPathname(p);
|
|
if (result)
|
|
return result;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoMail::IsDeletable ()
|
|
{
|
|
// These are specified in the "Mail/News Windows" UI spec
|
|
if ((m_flags & MSG_FOLDER_FLAG_TRASH) && !DeleteIsMoveToTrash())
|
|
return TRUE; // allow delete of trash if we don't use trash
|
|
if (m_depth == 1)
|
|
return FALSE;
|
|
if (m_flags & MSG_FOLDER_FLAG_INBOX ||
|
|
m_flags & MSG_FOLDER_FLAG_DRAFTS ||
|
|
m_flags & MSG_FOLDER_FLAG_TRASH ||
|
|
m_flags & MSG_FOLDER_FLAG_TEMPLATES)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoMail::RequiresCleanup()
|
|
{
|
|
if (m_expungedBytes > 0)
|
|
{
|
|
int32 purgeThreshhold = m_master->GetPrefs()->GetPurgeThreshhold();
|
|
XP_Bool purgePrompt = m_master->GetPrefs()->GetPurgeThreshholdEnabled();;
|
|
return (purgePrompt && m_expungedBytes / 1000L > purgeThreshhold);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
int32 MSG_FolderInfoMail::GetSizeOnDisk ()
|
|
{
|
|
int32 ret = 0;
|
|
XP_StatStruct st;
|
|
|
|
if (!XP_Stat(GetPathname(), &st, xpMailFolder))
|
|
ret += st.st_size;
|
|
|
|
if (!XP_Stat(GetPathname(), &st, xpMailFolderSummary))
|
|
ret += st.st_size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void MSG_FolderInfoMail::UpdatePendingCounts(MSG_FolderInfo * dstFolder,
|
|
IDArray *srcArray,
|
|
XP_Bool countUnread, XP_Bool missingAreRead)
|
|
{
|
|
if (dstFolder->GetType() == FOLDER_IMAPMAIL)
|
|
{
|
|
XP_Bool wasCreated=FALSE;
|
|
|
|
dstFolder->ChangeNumPendingTotalMessages(srcArray->GetSize());
|
|
|
|
if (countUnread)
|
|
{
|
|
MailDB *affectedDB = NULL;
|
|
ImapMailDB::Open(GetPathname(), FALSE, &affectedDB, GetMaster(), &wasCreated);
|
|
if (affectedDB)
|
|
{
|
|
// count the moves that were unread
|
|
int numUnread = 0;
|
|
for (int keyIndex=0; keyIndex < srcArray->GetSize(); keyIndex++)
|
|
{
|
|
// if the key is not there, then assume what the caller tells us to.
|
|
XP_Bool isRead = missingAreRead;
|
|
affectedDB->IsRead((*srcArray)[keyIndex], &isRead);
|
|
if (!isRead)
|
|
numUnread++;
|
|
}
|
|
|
|
if (numUnread)
|
|
dstFolder->ChangeNumPendingUnread(numUnread);
|
|
affectedDB->Close();
|
|
}
|
|
}
|
|
dstFolder->SummaryChanged();
|
|
}
|
|
}
|
|
|
|
|
|
MSG_FolderInfoNews::MSG_FolderInfoNews(char *groupName, msg_NewsArtSet* set,
|
|
XP_Bool subscribed, MSG_NewsHost* host,
|
|
uint8 depth)
|
|
: MSG_FolderInfo(groupName, depth)
|
|
{
|
|
m_flags |= MSG_FOLDER_FLAG_NEWSGROUP;
|
|
m_host = host;
|
|
m_set = set;
|
|
m_subscribed = subscribed;
|
|
m_dbfilename = NULL;
|
|
m_XPDBFileName = NULL;
|
|
m_XPRuleFileName = NULL;
|
|
m_prettyName = NULL;
|
|
m_master = m_host->GetMaster();
|
|
m_listNewsGroupState = NULL;
|
|
if (m_subscribed) {
|
|
#ifdef XP_MAC
|
|
// Should be done on all platforms: macintosh-only for beta-paranoid reasons.
|
|
m_flags |= MSG_FOLDER_FLAG_SUBSCRIBED;
|
|
#endif
|
|
if (m_host->IsCategoryContainer(groupName))
|
|
m_flags |=
|
|
MSG_FOLDER_FLAG_CAT_CONTAINER | MSG_FOLDER_FLAG_CATEGORY;
|
|
if (m_host->IsProfile(groupName))
|
|
m_flags |= MSG_FOLDER_FLAG_PROFILE_GROUP;
|
|
}
|
|
|
|
m_wantNewTotals = TRUE;
|
|
m_purgeStatus = PurgeMaybeNeeded;
|
|
m_username = NULL;
|
|
m_password = NULL;
|
|
m_nntpHighwater = 0;
|
|
m_nntpTotalArticles = -1;
|
|
m_totalInDB = 0;
|
|
m_unreadInDB = 0;
|
|
m_autoSubscribed = FALSE;
|
|
m_isCachable = FALSE;
|
|
}
|
|
|
|
MSG_FolderInfoNews::~MSG_FolderInfoNews()
|
|
{
|
|
FREEIF(m_dbfilename);
|
|
FREEIF(m_XPDBFileName);
|
|
delete [] m_prettyName;
|
|
m_prettyName = NULL;
|
|
FREEIF(m_username);
|
|
FREEIF(m_password);
|
|
delete m_set;
|
|
}
|
|
|
|
|
|
char *MSG_FolderInfoNews::BuildUrl (MessageDB * db, MessageKey key)
|
|
{
|
|
char* url = NULL;
|
|
const char* groupname = GetNewsgroupName();
|
|
const char* urlbase = GetHost()->GetURLBase();
|
|
|
|
if (db == NULL) {
|
|
|
|
if (key == MSG_MESSAGEKEYNONE)
|
|
url = PR_smprintf("%s/%s", urlbase, groupname);
|
|
}
|
|
else
|
|
{
|
|
DBMessageHdr *header = db->GetDBHdrForKey (key);
|
|
|
|
if (header != NULL)
|
|
{
|
|
XPStringObj messageId;
|
|
header->GetMessageId(messageId, db->GetDB());
|
|
char *escapedId = EscapeMessageId (messageId);
|
|
if (escapedId)
|
|
{
|
|
url = PR_smprintf("%s/%s", urlbase, escapedId);
|
|
XP_FREE(escapedId);
|
|
}
|
|
delete header;
|
|
}
|
|
}
|
|
return url;
|
|
}
|
|
|
|
/* Compute a value for the "?newshost=" parameter. This gets passed in
|
|
with all of the mailto's, even the ones that don't have to do with
|
|
posting, because the user might decide later to type in some newsgroups,
|
|
and we need to know which host to send them to. Perhaps the UI should
|
|
have a way of selecting the host - say you wanted to forward a message
|
|
frome one news server to another. But it doesn't now.
|
|
The caller must free the returned pointer.
|
|
*/
|
|
char *MSG_FolderInfoNews::GetHostUrl()
|
|
{
|
|
const char *hostName;
|
|
HG26477
|
|
|
|
hostName = GetHost()->getNameAndPort();
|
|
MSG_NewsHost *newsHost = GetHost();
|
|
if (newsHost)
|
|
{
|
|
HG78365
|
|
if (hostName != NULL)
|
|
{
|
|
int32 length = XP_STRLEN(hostName) + 20;
|
|
char *host = (char *) XP_ALLOC (length);
|
|
if (!host)
|
|
return 0; /* #### MK_OUT_OF_MEMORY */
|
|
XP_STRCPY (host, hostName);
|
|
HG89732
|
|
return host;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
FolderType MSG_FolderInfoNews::GetType() {
|
|
return FOLDER_NEWSGROUP;
|
|
}
|
|
|
|
// more virtual inlines for the compiler-challenged platforms
|
|
XP_Bool MSG_FolderInfoNews::IsMail () { return FALSE; }
|
|
XP_Bool MSG_FolderInfoNews::IsNews () { return TRUE; }
|
|
XP_Bool MSG_FolderInfoNews::IsDeletable () { return TRUE; }
|
|
MSG_FolderInfoNews *MSG_FolderInfoNews::GetNewsFolderInfo() {return this;}
|
|
|
|
int32 MSG_FolderInfoNews::GetTotalMessagesInDB() const
|
|
{
|
|
return m_totalInDB; // override this, since m_numTotalMessages for news is server-based
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoNews::CreateSubfolder (const char *, MSG_FolderInfo **, int32 *)
|
|
{
|
|
//PHP maybe we can use this to create categories
|
|
return 0;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoNews::Delete ()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoNews::Rename (const char * /*newName*/)
|
|
{
|
|
return MK_UNABLE_TO_OPEN_FILE;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoNews::Adopt (MSG_FolderInfo *src, int32 *newIdx)
|
|
{
|
|
MsgERR err = MK_MSG_CANT_MOVE_FOLDER;
|
|
|
|
if (0 == m_host->ReorderGroup (src->GetNewsFolderInfo(), this, newIdx))
|
|
err = eSUCCESS;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
msg_NewsArtSet*
|
|
MSG_FolderInfoNews::GetSet()
|
|
{
|
|
return m_set;
|
|
}
|
|
|
|
XP_Bool
|
|
MSG_FolderInfoNews::IsSubscribed()
|
|
{
|
|
return m_subscribed;
|
|
}
|
|
|
|
XP_Bool
|
|
MSG_FolderInfoNews::IsCategory()
|
|
{
|
|
return m_subscribed && m_host->IsCategory(GetNewsgroupName());
|
|
// ###tw Probably should keep this info cached.
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoNews::CanBeInFolderPane ()
|
|
{
|
|
return m_subscribed;
|
|
}
|
|
|
|
const char *MSG_FolderInfoNews::GetRelativePathName ()
|
|
{
|
|
// xp db file name ought to be unique enough
|
|
return GetXPDBFileName();
|
|
}
|
|
|
|
const char*
|
|
MSG_FolderInfoNews::GetDBFileName()
|
|
{
|
|
if (!m_dbfilename) {
|
|
const char* dbFileName = GetXPDBFileName();
|
|
if (!dbFileName) return NULL;
|
|
m_dbfilename = WH_FileName(dbFileName, xpXoverCache);
|
|
}
|
|
return m_dbfilename;
|
|
}
|
|
|
|
char *
|
|
MSG_FolderInfoNews::GetUniqueFileName(const char *extension)
|
|
{
|
|
char* tmp;
|
|
const char* newsgroupName = GetNewsgroupName();
|
|
const char* dbName = m_host->GetDBDirName();
|
|
if (XP_STRLEN(newsgroupName) > MAX_FILE_LENGTH_WITHOUT_EXTENSION)
|
|
{
|
|
// Use a hex version of the unique id (forcing 8.3 format).
|
|
long uniqueID = (long)m_host->GetUniqueID(newsgroupName);
|
|
tmp = PR_smprintf("%s/08%lX.%s", dbName, uniqueID, extension);
|
|
}
|
|
else
|
|
tmp = PR_smprintf("%s/%s.%s", dbName, newsgroupName, extension);
|
|
if (!tmp)
|
|
return NULL;
|
|
return tmp;
|
|
#undef MAX_FILE_LENGTH_WITHOUT_EXTENSION
|
|
}
|
|
|
|
const char *
|
|
MSG_FolderInfoNews::GetXPDBFileName()
|
|
{
|
|
if (!m_XPDBFileName)
|
|
{
|
|
m_XPDBFileName = GetUniqueFileName("snm");
|
|
}
|
|
return m_XPDBFileName;
|
|
}
|
|
|
|
const char *
|
|
MSG_FolderInfoNews::GetXPRuleFileName()
|
|
{
|
|
if (!m_XPRuleFileName)
|
|
{
|
|
m_XPRuleFileName = GetUniqueFileName("dat");
|
|
}
|
|
return m_XPRuleFileName;
|
|
}
|
|
|
|
const char *MSG_FolderInfoNews::GetPrettyName()
|
|
{
|
|
if (!m_prettyName) {
|
|
m_prettyName = m_host->GetPrettyName(GetNewsgroupName());
|
|
}
|
|
return m_prettyName;
|
|
}
|
|
|
|
void MSG_FolderInfoNews::ClearPrettyName()
|
|
{
|
|
if (m_prettyName)
|
|
{
|
|
delete [] m_prettyName;
|
|
m_prettyName = NULL;
|
|
}
|
|
}
|
|
|
|
NewsGroupDB *MSG_FolderInfoNews::OpenNewsDB(XP_Bool deleteInvalidDB /* = FALSE */, MsgERR *pErr /* = NULL */)
|
|
{
|
|
NewsGroupDB *newsDB = NULL;
|
|
char *url = BuildUrl(NULL, MSG_MESSAGEKEYNONE);
|
|
MsgERR err = eOUT_OF_MEMORY;
|
|
|
|
if (url != NULL)
|
|
{
|
|
if ((err = NewsGroupDB::Open(url, m_host->GetMaster(), &newsDB)) != eSUCCESS)
|
|
newsDB = NULL;
|
|
if (err != eSUCCESS && deleteInvalidDB)
|
|
{
|
|
const char *dbFileName = GetXPDBFileName();
|
|
|
|
XP_Trace("blowing away old database file\n");
|
|
if (dbFileName != NULL)
|
|
{
|
|
XP_FileRemove(dbFileName, xpXoverCache);
|
|
err = NewsGroupDB::Open(url, m_host->GetMaster(), &newsDB);
|
|
}
|
|
}
|
|
HG87535
|
|
XP_FREE(url);
|
|
}
|
|
if (pErr)
|
|
*pErr = err;
|
|
return newsDB;
|
|
}
|
|
|
|
void MSG_FolderInfoNews::MarkAllRead(MWContext *context, XP_Bool deep)
|
|
{
|
|
MSG_FolderInfo::MarkAllRead(context, deep);
|
|
if (m_set && 1 <= m_nntpHighwater)
|
|
{
|
|
m_set->AddRange(1, m_nntpHighwater);
|
|
UpdateSummaryTotals();
|
|
GetMaster()->BroadcastFolderChanged(this);
|
|
}
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoNews::GetDBFolderInfoAndDB(DBFolderInfo **folderInfo, MessageDB **db)
|
|
{
|
|
MsgERR err;
|
|
|
|
XP_ASSERT(db && folderInfo);
|
|
if (!db || !folderInfo)
|
|
return eBAD_PARAMETER;
|
|
|
|
if ((*db = OpenNewsDB(FALSE, &err)) != NULL)
|
|
*folderInfo = (*db) ->GetDBFolderInfo();
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MsgERR MSG_FolderInfoNews::ReadFromCache (char *buf)
|
|
{
|
|
MsgERR err = MSG_FolderInfo::ReadFromCache (buf);
|
|
if (eSUCCESS == err)
|
|
m_isCachable = TRUE;
|
|
return err;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoNews::UpdateSummaryTotals()
|
|
{
|
|
MessageDB *newsDB;
|
|
DBFolderInfo *folderInfo;
|
|
XP_Bool gotTotals = FALSE;
|
|
|
|
MsgERR err = GetDBFolderInfoAndDB(&folderInfo, &newsDB);
|
|
if (err == eSUCCESS && newsDB)
|
|
{
|
|
if (folderInfo != NULL)
|
|
{
|
|
m_isCachable = TRUE;
|
|
m_totalInDB = m_numTotalMessages = folderInfo->GetNumMessages();
|
|
m_unreadInDB = folderInfo->GetNumNewMessages();
|
|
m_csid = folderInfo->GetCSID();
|
|
if (m_nntpHighwater)
|
|
{
|
|
m_numUnreadMessages = m_set->CountMissingInRange(1, m_nntpHighwater);
|
|
m_numTotalMessages = m_nntpTotalArticles;
|
|
// can't be more unread than on the server (there can, but it's a bit weird)
|
|
if (m_numUnreadMessages > m_numTotalMessages)
|
|
{
|
|
m_numUnreadMessages = m_numTotalMessages;
|
|
int32 deltaInDB = MIN(m_totalInDB - m_unreadInDB, m_numUnreadMessages);
|
|
// if we know there are read messages in the db, subtract that from the unread total
|
|
if (deltaInDB > 0)
|
|
m_numUnreadMessages -= deltaInDB;
|
|
}
|
|
}
|
|
else
|
|
m_numUnreadMessages = folderInfo->GetNumNewMessages();
|
|
XP_ASSERT(m_numUnreadMessages >= 0);
|
|
gotTotals = TRUE;
|
|
if (m_purgeStatus == PurgeMaybeNeeded)
|
|
m_purgeStatus = newsDB->GetNewsDB()->PurgeNeeded(GetMaster()->GetPurgeHdrInfo(), GetMaster()->GetPurgeArtInfo())
|
|
? PurgeNeeded : PurgeNotNeeded;
|
|
}
|
|
newsDB->Close();
|
|
}
|
|
if (!gotTotals) {
|
|
// Well, we tried. Set to -2 (meaning don't bother trying again),
|
|
// unless we already got a guess from somewhere else, in which case
|
|
// we'll keep that guess.
|
|
if (m_numUnreadMessages < 0) {
|
|
m_numUnreadMessages = -2; // well, we tried...
|
|
}
|
|
}
|
|
|
|
return gotTotals;
|
|
}
|
|
|
|
|
|
void
|
|
MSG_FolderInfoNews::UpdateSummaryFromNNTPInfo(int32 oldest,
|
|
int32 youngest,
|
|
int32 total)
|
|
{
|
|
/* First, mark all of the articles now known to be expired as read. */
|
|
if (oldest > 1) m_set->AddRange(1, oldest - 1);
|
|
/* Now search the newsrc line and figure out how many of these
|
|
messages are marked as unread. */
|
|
// make sure youngest is a least 1. MSNews seems to return a youngest of 0.
|
|
if (youngest == 0)
|
|
youngest = 1;
|
|
int32 unread = m_set->CountMissingInRange(oldest, youngest);
|
|
XP_ASSERT(unread >= 0);
|
|
if (unread < 0) return;
|
|
if (unread > total) {
|
|
/* This can happen when the newsrc file shows more unread than exist
|
|
in the group (total is not necessarily `end - start'.) */
|
|
unread = total;
|
|
int32 deltaInDB = m_totalInDB - m_unreadInDB;
|
|
// if we know there are read messages in the db, subtract that from the unread total
|
|
if (deltaInDB > 0)
|
|
unread -= deltaInDB;
|
|
}
|
|
m_wantNewTotals = FALSE;
|
|
m_numUnreadMessages = unread;
|
|
m_numTotalMessages = total;
|
|
m_nntpHighwater = youngest;
|
|
m_master->BroadcastFolderChanged(this);
|
|
m_nntpTotalArticles = total;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoNews::Subscribe(XP_Bool bSubscribe, MSG_Pane *invokingPane /* = NULL */)
|
|
{
|
|
if (!bSubscribe)
|
|
{
|
|
// Try to delete the database to free up disk space
|
|
if ((!m_XPDBFileName || 0 != XP_FileRemove (m_XPDBFileName, xpXoverCache)) && invokingPane)
|
|
{
|
|
const char *prompt = XP_GetString (MK_MSG_CANT_DELETE_NEWS_DB);
|
|
char *message = PR_smprintf (prompt, GetName());
|
|
if (message)
|
|
{
|
|
XP_Bool continueUnsubscribe = FE_Confirm (invokingPane->GetContext(), message);
|
|
XP_FREE(message);
|
|
if (!continueUnsubscribe)
|
|
return eUNKNOWN; //don't continue unsubscribing
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_subscribed != bSubscribe)
|
|
{
|
|
// Remember the new setting and rewrite the news rc file
|
|
m_subscribed = bSubscribe;
|
|
#ifdef XP_MAC
|
|
// Should be done on all platforms: macintosh-only for beta-paranoid reasons.
|
|
m_flags ^= MSG_FOLDER_FLAG_SUBSCRIBED;
|
|
#endif
|
|
GetHost()->MarkDirty();
|
|
}
|
|
return eSUCCESS; //continue unsubscribing
|
|
}
|
|
|
|
|
|
typedef struct tIMAPUploadInfo
|
|
{
|
|
MSG_IMAPFolderInfoMail *m_destFolder;
|
|
MSG_FolderInfoMail *m_srcFolder;
|
|
MSG_Pane *m_sourcePane;
|
|
MailDB *m_sourceDB;
|
|
IDArray *m_idArrayForTempDB;
|
|
IDArray *m_sourceIDArray;
|
|
XP_Bool m_isMove;
|
|
} IMAPUploadInfo;
|
|
|
|
void MSG_IMAPFolderInfoMail::FinishUploadFromFile(struct MessageCopyInfo*, int status)
|
|
{
|
|
if (m_uploadInfo)
|
|
{
|
|
if (m_uploadInfo->m_sourceDB)
|
|
m_uploadInfo->m_sourceDB->Close();
|
|
XP_FileRemove(m_uploadInfo->m_srcFolder->GetPathname(), xpMailFolder);
|
|
XP_FileRemove(m_uploadInfo->m_srcFolder->GetPathname(), xpMailFolderSummary);
|
|
MSG_FolderInfoMail *localTree = GetMaster()->GetLocalMailFolderTree();
|
|
localTree->RemoveSubFolder(m_uploadInfo->m_srcFolder);
|
|
// delete the original messages
|
|
if (m_uploadInfo->m_isMove && status == 0)
|
|
{
|
|
MSG_IMAPFolderInfoMail *imapFolder = (m_uploadInfo->m_sourcePane->GetFolder())
|
|
? m_uploadInfo->m_sourcePane->GetFolder()->GetIMAPFolderInfoMail() : 0;
|
|
|
|
if (imapFolder)
|
|
imapFolder->DeleteSpecifiedMessages(m_uploadInfo->m_sourcePane, *m_uploadInfo->m_sourceIDArray);
|
|
}
|
|
// unfortunately, PostMessageCopyExit hasn't been called yet - this leaks the folder info
|
|
// which isn't acceptable - need to run this after we've called CleanupCopyMessagesInto
|
|
// delete m_uploadInfo->m_srcFolder;
|
|
delete m_uploadInfo->m_sourceIDArray;
|
|
delete m_uploadInfo;
|
|
m_uploadInfo = NULL;
|
|
}
|
|
}
|
|
|
|
void MSG_IMAPFolderInfoMail::UploadMessagesFromFile(MSG_FolderInfoMail *srcFolder, MSG_Pane *sourcePane, MailDB *mailDB)
|
|
{
|
|
m_uploadInfo->m_idArrayForTempDB = new IDArray;
|
|
MWContext *sourceContext = sourcePane->GetContext();
|
|
|
|
// send a matching ending update - because of double copy nature of download+upload
|
|
sourcePane->EndingUpdate(MSG_NotifyNone, 0, 0);
|
|
|
|
MSG_FinishCopyingMessages(sourceContext);
|
|
|
|
// search as view modification..inform search pane that we are done with this view
|
|
// mscott: we have to do this here because when we copy news aticles to an IMAP folder,
|
|
// we need to tell the search pane to close its DB view when the operation is done.
|
|
// This calback happens in ::CleanupMessageCopy for everyone else except for this case!!!
|
|
// Why here? By the time we reach ::CleanupMessageCopy, the src folder is the temp file
|
|
// the news articles were copied to and not the news db view the search pane is keeping open....*sigh*
|
|
if (sourcePane && sourcePane->GetPaneType() == MSG_SEARCHPANE && sourceContext && sourceContext->msgCopyInfo)
|
|
((MSG_SearchPane *) sourcePane)->CloseView(sourceContext->msgCopyInfo->srcFolder);
|
|
|
|
// clear out the copyinfo from the news source copy
|
|
FREEIF(sourceContext->msgCopyInfo);
|
|
|
|
if (mailDB)
|
|
{
|
|
mailDB->ListAllIds(m_uploadInfo->m_idArrayForTempDB);
|
|
if (m_uploadInfo->m_idArrayForTempDB->GetSize() > 0)
|
|
{
|
|
srcFolder->StartAsyncCopyMessagesInto (this,
|
|
sourcePane,
|
|
mailDB,
|
|
m_uploadInfo->m_idArrayForTempDB,
|
|
m_uploadInfo->m_idArrayForTempDB->GetSize(),
|
|
sourcePane->GetContext(),
|
|
NULL, // do not run in url queue
|
|
FALSE,
|
|
MSG_MESSAGEKEYNONE);
|
|
}
|
|
else
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
FinishUploadFromFile(NULL, -1);
|
|
}
|
|
// we need to know when this is done so we can delete the tmp folder and folder info, and close the db.
|
|
}
|
|
}
|
|
|
|
/* static */ int MSG_IMAPFolderInfoMail::UploadMsgsInFolder(void *uploadInfo, int status)
|
|
{
|
|
IMAPUploadInfo *imapUploadInfo = (IMAPUploadInfo *) uploadInfo;
|
|
|
|
imapUploadInfo->m_destFolder->m_uploadInfo = imapUploadInfo;
|
|
if (status >= 0)
|
|
{
|
|
imapUploadInfo->m_destFolder->UploadMessagesFromFile(imapUploadInfo->m_srcFolder, imapUploadInfo->m_sourcePane, imapUploadInfo->m_sourceDB);
|
|
}
|
|
else
|
|
{
|
|
MSG_FinishCopyingMessages(imapUploadInfo->m_sourcePane->GetContext());
|
|
imapUploadInfo->m_destFolder->FinishUploadFromFile(imapUploadInfo->m_sourcePane->GetContext()->msgCopyInfo, status);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoNews::BeginCopyingMessages (MSG_FolderInfo *dstFolder,
|
|
MessageDB * /* sourceDB */,
|
|
IDArray *srcArray,
|
|
MSG_UrlQueue * /*urlQueue will use for news -> imap */,
|
|
int32 srcCount,
|
|
MessageCopyInfo *copyInfo)
|
|
{
|
|
FolderType dstFolderType = dstFolder->GetType();
|
|
NewsGroupDB *newsDB = OpenNewsDB();
|
|
|
|
msg_move_state *state = ©Info->moveState;
|
|
state->sourceNewsDB = newsDB;
|
|
|
|
if (newsDB != NULL)
|
|
{
|
|
IDArray keysToSave;
|
|
for (int32 i = 0; i < srcCount; i++)
|
|
keysToSave.Add (srcArray->GetAt(i));
|
|
|
|
if (dstFolderType == FOLDER_MAIL)
|
|
{
|
|
const char *fileName = ((MSG_FolderInfoMail*) dstFolder)->GetPathname();
|
|
|
|
return SaveMessages (&keysToSave, fileName, state->sourcePane, newsDB);
|
|
}
|
|
else if (dstFolderType == FOLDER_IMAPMAIL)
|
|
{
|
|
return DownloadToTempFileAndUpload(copyInfo, keysToSave, dstFolder, newsDB);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int MSG_FolderInfoNews::FinishCopyingMessages(MWContext * /*context*/,
|
|
MSG_FolderInfo * /* srcFolder */,
|
|
MSG_FolderInfo * dstFolder,
|
|
MessageDB * /*sourceDB*/,
|
|
IDArray * /*srcArray*/,
|
|
int32 /*srcCount*/,
|
|
msg_move_state * state)
|
|
{
|
|
if (dstFolder && dstFolder->TestSemaphore(this))
|
|
return MK_WAITING_FOR_CONNECTION;
|
|
else
|
|
{
|
|
if (state->sourceNewsDB)
|
|
{
|
|
state->sourceNewsDB->Close();
|
|
state->sourceNewsDB = NULL;
|
|
}
|
|
return MK_CONNECTED;
|
|
}
|
|
}
|
|
|
|
int MSG_FolderInfo::DownloadToTempFileAndUpload(MessageCopyInfo *copyInfo, IDArray &keysToSave, MSG_FolderInfo *dstFolder, MessageDB *sourceDB)
|
|
{
|
|
XP_FileType tmptype;
|
|
msg_move_state *state = ©Info->moveState;
|
|
MSG_FolderInfoMail *dstMailFolder = dstFolder->GetMailFolderInfo();
|
|
|
|
char *fileName = FE_GetTempFileFor(state->sourcePane->GetContext(), dstMailFolder->GetPathname(), xpMailFolder,
|
|
&tmptype);
|
|
if (!fileName)
|
|
return MK_OUT_OF_MEMORY;
|
|
|
|
// this is unfortunate, but we'd like to have the tmp folder info in the folder tree so
|
|
// that downloading the news articles will update the database. We're going to have
|
|
// to remove it when we're done. This scheme also has the disadvantage of putting
|
|
// all the messages in the same tmp file, and then uploading one by one, but it's
|
|
// the easiest way to hook the existing code. If we did it one message at a time,
|
|
// we wouldn't need the database (or would we?).
|
|
// We could pass a dest folder to saveMessages, null for the save as case.
|
|
// Then, we wouldn't need to put it in the folder tree...
|
|
|
|
MSG_FolderInfoMail *localTree = GetMaster()->GetLocalMailFolderTree();
|
|
MSG_FolderInfoMail *tmpFolderInfo = new MSG_FolderInfoMail(XP_STRDUP(fileName), m_master, 2, 0);
|
|
|
|
FREEIF(fileName);
|
|
if (!tmpFolderInfo)
|
|
return MK_OUT_OF_MEMORY;
|
|
|
|
localTree->GetSubFolders()->Add(tmpFolderInfo);
|
|
|
|
IMAPUploadInfo *uploadInfo = new IMAPUploadInfo;
|
|
|
|
if (!uploadInfo)
|
|
return MK_OUT_OF_MEMORY;
|
|
|
|
// replace the dstFolder in the copy info to be the temp file to clean up locks correctly
|
|
copyInfo->dstFolder = tmpFolderInfo;
|
|
|
|
uploadInfo->m_destFolder = dstMailFolder->GetIMAPFolderInfoMail();
|
|
uploadInfo->m_sourcePane = state->sourcePane;
|
|
uploadInfo->m_srcFolder = tmpFolderInfo;
|
|
uploadInfo->m_isMove = copyInfo->moveState.ismove;
|
|
uploadInfo->m_sourceIDArray = new IDArray;
|
|
uploadInfo->m_sourceIDArray->CopyArray(keysToSave); // copy source id array.
|
|
XP_File outFp = XP_FileOpen (tmpFolderInfo->GetPathname(), xpMailFolder, XP_FILE_WRITE_BIN);
|
|
if (outFp)
|
|
XP_FileClose(outFp);
|
|
if (copyInfo->moveState.destDB)
|
|
copyInfo->moveState.destDB->Close();
|
|
MsgERR err = MailDB::Open(tmpFolderInfo->GetPathname(), TRUE, &uploadInfo->m_sourceDB, FALSE);
|
|
if ((err == eSUCCESS || err == eNoSummaryFile) && uploadInfo->m_sourceDB)
|
|
{
|
|
copyInfo->moveState.destDB = uploadInfo->m_sourceDB;
|
|
uploadInfo->m_sourceDB->AddUseCount();
|
|
state->sourcePane->GetContext()->currentIMAPfolder = this->GetIMAPFolderInfoMail();
|
|
MSG_StartImapMessageToOfflineFolderDownload(state->sourcePane->GetContext());
|
|
// UploadMsgsInFolder needs to know the dstFolder, the source fileName, and the sourcePane, to run the context in.
|
|
return SaveMessages(&keysToSave, tmpFolderInfo->GetPathname(), state->sourcePane, sourceDB, MSG_IMAPFolderInfoMail::UploadMsgsInFolder, uploadInfo);
|
|
}
|
|
else
|
|
return MK_MSG_TMP_FOLDER_UNWRITABLE;
|
|
}
|
|
|
|
XP_Bool MSG_FolderInfoNews::RequiresCleanup()
|
|
{
|
|
return (m_purgeStatus == PurgeNeeded);
|
|
}
|
|
|
|
void MSG_FolderInfoNews::ClearRequiresCleanup()
|
|
{
|
|
m_purgeStatus = PurgeNotNeeded;
|
|
}
|
|
|
|
void MSG_FolderInfoNews::SetNewsgroupUsername(const char *username)
|
|
{
|
|
if (username)
|
|
StrAllocCopy (m_username, username);
|
|
else
|
|
FREEIF(m_username);
|
|
}
|
|
|
|
|
|
const char * MSG_FolderInfoNews::GetNewsgroupUsername(void)
|
|
{
|
|
return m_username;
|
|
}
|
|
|
|
|
|
void MSG_FolderInfoNews::SetNewsgroupPassword(const char *password)
|
|
{
|
|
if (password)
|
|
StrAllocCopy(m_password, password);
|
|
else
|
|
FREEIF(m_password);
|
|
RememberPassword(password); // save it in the db for offline use.
|
|
}
|
|
|
|
|
|
const char * MSG_FolderInfoNews::GetNewsgroupPassword(void)
|
|
{
|
|
return m_password;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoNews::KnowsSearchNntpExtension()
|
|
{
|
|
// The host must know the SEARCH extension AND the group must be in the LIST SEARCHES response
|
|
return m_host->QueryExtension("SEARCH") && m_host->QuerySearchableGroup (GetName());
|
|
}
|
|
|
|
|
|
XP_Bool MSG_FolderInfoNews::AllowsPosting ()
|
|
{
|
|
// Don't allow posting to virtual newsgroups
|
|
if (m_flags & MSG_FOLDER_FLAG_PROFILE_GROUP)
|
|
return FALSE;
|
|
|
|
// The news host can tell us in the connection banner if it doesn't
|
|
// allow posting. Honor that setting here.
|
|
if (!m_host || !m_host->GetPostingAllowed())
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int32 MSG_FolderInfoNews::GetSizeOnDisk ()
|
|
{
|
|
int32 ret = 0;
|
|
XP_StatStruct st;
|
|
|
|
if (!XP_Stat(GetXPDBFileName(), &st, xpXoverCache))
|
|
ret += st.st_size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
FolderType MSG_FolderInfoCategoryContainer::GetType()
|
|
{
|
|
return FOLDER_CATEGORYCONTAINER;
|
|
}
|
|
|
|
MSG_FolderInfoCategoryContainer::MSG_FolderInfoCategoryContainer(char *groupName, msg_NewsArtSet* set,
|
|
XP_Bool subscribed, MSG_NewsHost* host, uint8 depth)
|
|
: MSG_FolderInfoNews(groupName, set, subscribed, host, depth)
|
|
{
|
|
}
|
|
|
|
MSG_FolderInfoCategoryContainer::~MSG_FolderInfoCategoryContainer()
|
|
{
|
|
}
|
|
|
|
MSG_FolderInfoNews *
|
|
MSG_FolderInfoCategoryContainer::BuildCategoryTree(MSG_FolderInfoNews *parent,
|
|
const char *groupName,
|
|
msg_GroupRecord* group,
|
|
uint8 depth,
|
|
MSG_Master *master)
|
|
{
|
|
m_host->AssureAllDescendentsLoaded(group);
|
|
|
|
msg_GroupRecord* child;
|
|
int32 result = 0;
|
|
|
|
// slip in a new parent which represents the root category.
|
|
for (child = group->GetChildren() ; child ; child = child->GetSibling()) {
|
|
MSG_FolderInfoNews *info = NULL;
|
|
char *catName = PR_smprintf("%s.%s", groupName, child->GetPartName());
|
|
if (!catName)
|
|
break;
|
|
|
|
if (child->IsGroup()) {
|
|
info = m_host->FindGroup(catName);
|
|
// this relies on auto-subscribe of categories!
|
|
if (info && info->IsSubscribed()) {
|
|
// set the level correctly for category pane.
|
|
info->SetDepth(depth + 1);
|
|
info->SetFlag(MSG_FOLDER_FLAG_CATEGORY);
|
|
|
|
if (child->GetChildren())
|
|
info->SetFlag(MSG_FOLDER_FLAG_DIRECTORY);
|
|
|
|
if (!parent->IsParentOf(info, FALSE))
|
|
parent->GetSubFolders()->Add(info);
|
|
#ifdef DEBUG_bienvenu1
|
|
XP_Trace("Adding %s at depth %d\n", catName, depth + 1);
|
|
#endif
|
|
// parent = info;
|
|
result++;
|
|
}
|
|
}
|
|
BuildCategoryTree(info ? info : parent, catName, child,
|
|
depth + ((info) ? 1 : 0), master);
|
|
|
|
XP_FREE(catName);
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
// while it would be nice to put this in the right place, rebuilding the category
|
|
// tree is really easy.
|
|
MSG_FolderInfoNews *
|
|
MSG_FolderInfoCategoryContainer::AddToCategoryTree(MSG_FolderInfoNews* /*parent*/,
|
|
const char* /*groupName*/,
|
|
msg_GroupRecord* group,
|
|
MSG_Master *master)
|
|
{
|
|
MSG_FolderInfoNews *retFolder = NULL;
|
|
msg_GroupRecord *categoryParent = group->GetParent();
|
|
if (categoryParent)
|
|
{
|
|
char *categoryParentName = categoryParent->GetFullName();
|
|
if (categoryParentName)
|
|
{
|
|
MSG_FolderInfoNews *parent = m_host->FindGroup(categoryParentName);
|
|
if (parent)
|
|
{
|
|
// make sure we're using the root category...
|
|
parent = parent->GetNewsFolderInfo();
|
|
parent->GetSubFolders()->RemoveAll();
|
|
retFolder = BuildCategoryTree(parent, categoryParentName, categoryParent, parent->GetDepth(), master);
|
|
}
|
|
delete [] categoryParentName;
|
|
}
|
|
}
|
|
return retFolder;
|
|
}
|
|
|
|
MSG_FolderInfoCategoryContainer *MSG_FolderInfoNews::CloneIntoCategoryContainer()
|
|
{
|
|
msg_NewsArtSet *newSet = m_set->Create(m_set->Output(), m_host);
|
|
MSG_FolderInfoCategoryContainer *retFolder =
|
|
new MSG_FolderInfoCategoryContainer(m_name, newSet, m_subscribed, m_host, m_depth);
|
|
if (retFolder)
|
|
{
|
|
retFolder->GetSubFolders()->Add(this);
|
|
}
|
|
// due to load time "optimization" for unsubscribed category containers, we may not know this!
|
|
SetFlag(MSG_FOLDER_FLAG_CATEGORY);
|
|
return retFolder;
|
|
}
|
|
|
|
MSG_FolderInfoNews *MSG_FolderInfoCategoryContainer::CloneIntoNewsFolder()
|
|
{
|
|
msg_NewsArtSet *newSet = m_set->Create(m_set->Output(), m_host);
|
|
MSG_FolderInfoNews *retFolder = new MSG_FolderInfoNews(m_name, newSet, m_subscribed, m_host, m_depth);
|
|
if (retFolder)
|
|
retFolder->GetSubFolders()->InsertAt(0, m_subFolders);
|
|
|
|
return retFolder;
|
|
}
|
|
|
|
// Well, this is a long shot, but lets try always returning the root
|
|
// category when this gets called.
|
|
MSG_FolderInfoNews *MSG_FolderInfoCategoryContainer::GetNewsFolderInfo()
|
|
{
|
|
return GetRootCategory();
|
|
}
|
|
|
|
MSG_FolderInfoNews *MSG_FolderInfoCategoryContainer::GetRootCategory()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
|
|
MSG_FolderInfoContainer::MSG_FolderInfoContainer(const char* name, uint8 depth, XPCompareFunc* func)
|
|
: MSG_FolderInfo(name, depth, func)
|
|
{
|
|
m_flags |= MSG_FOLDER_FLAG_DIRECTORY;
|
|
m_numUnreadMessages = 0;
|
|
}
|
|
|
|
MSG_FolderInfoContainer::~MSG_FolderInfoContainer()
|
|
{
|
|
}
|
|
|
|
FolderType
|
|
MSG_FolderInfoContainer::GetType()
|
|
{
|
|
return FOLDER_CONTAINERONLY;
|
|
}
|
|
|
|
char*
|
|
MSG_FolderInfoContainer::BuildUrl(MessageDB*, MessageKey)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
MsgERR
|
|
MSG_FolderInfoContainer::BeginCopyingMessages(MSG_FolderInfo *,
|
|
MessageDB *,
|
|
IDArray *,
|
|
MSG_UrlQueue *,
|
|
int32,
|
|
MessageCopyInfo *)
|
|
{
|
|
XP_ASSERT(0);
|
|
return eUNKNOWN;
|
|
}
|
|
|
|
|
|
int
|
|
MSG_FolderInfoContainer::FinishCopyingMessages(MWContext *,
|
|
MSG_FolderInfo *,
|
|
MSG_FolderInfo *,
|
|
MessageDB * /*sourceDB*/,
|
|
IDArray *,
|
|
int32,
|
|
msg_move_state *)
|
|
{
|
|
XP_ASSERT(0);
|
|
return -1;
|
|
}
|
|
|
|
MsgERR MSG_FolderInfoContainer::SaveMessages(IDArray*, const char *,
|
|
MSG_Pane *, MessageDB *,
|
|
int (*)(void *, int status) , void *)
|
|
{
|
|
XP_ASSERT(0);
|
|
return eUNKNOWN;
|
|
}
|
|
|
|
MsgERR
|
|
MSG_FolderInfoContainer::CreateSubfolder(const char *, MSG_FolderInfo**, int32 *)
|
|
{
|
|
XP_ASSERT(0);
|
|
return eUNKNOWN;
|
|
}
|
|
|
|
|
|
MsgERR
|
|
MSG_FolderInfoContainer::Delete()
|
|
{
|
|
XP_ASSERT(0);
|
|
return eUNKNOWN;
|
|
}
|
|
|
|
|
|
MsgERR
|
|
MSG_FolderInfoContainer::Adopt(MSG_FolderInfo *, int32*)
|
|
{
|
|
return MK_MSG_CANT_MOVE_FOLDER;
|
|
}
|
|
|
|
MSG_FolderInfoMail* MSG_FolderInfoContainer::FindMailPathname(const char* p)
|
|
{
|
|
for (int i=0 ; i < m_subFolders->GetSize() ; i++) {
|
|
MSG_FolderInfoMail* sub = (MSG_FolderInfoMail*) m_subFolders->GetAt(i);
|
|
FolderType folderType = sub->GetType();
|
|
XP_ASSERT((folderType == FOLDER_MAIL) ||
|
|
(folderType == FOLDER_IMAPMAIL)); // descends from MSG_FolderInfoMail
|
|
MSG_FolderInfoMail* result = sub->FindPathname(p);
|
|
if (result)
|
|
return result;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MSG_NewsFolderInfoContainer::MSG_NewsFolderInfoContainer(MSG_NewsHost* host,
|
|
uint8 depth)
|
|
: MSG_FolderInfoContainer(host->getFullUIName(), depth, NULL)
|
|
{
|
|
m_host = host;
|
|
m_prettyName = NULL;
|
|
}
|
|
|
|
MSG_NewsFolderInfoContainer::~MSG_NewsFolderInfoContainer()
|
|
{
|
|
FREEIF(m_prettyName);
|
|
}
|
|
|
|
XP_Bool
|
|
MSG_NewsFolderInfoContainer::IsDeletable()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
MsgERR
|
|
MSG_NewsFolderInfoContainer::Delete()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_NewsFolderInfoContainer::KnowsSearchNntpExtension()
|
|
{
|
|
return m_host->QueryExtension ("SEARCH");
|
|
}
|
|
|
|
XP_Bool MSG_NewsFolderInfoContainer::AllowsPosting()
|
|
{
|
|
// The news host can tell us in the connection banner if it doesn't
|
|
// allow posting. Honor that setting here.
|
|
if (!m_host || !m_host->GetPostingAllowed())
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int MSG_NewsFolderInfoContainer::GetNumSubFoldersToDisplay() const
|
|
{
|
|
// Since unsubscribed newsgroups have folderInfos in the tree, the news
|
|
// host will appear to have children (and the FEs draw the twisty)
|
|
// even when there are no subscribed groups. So consider whether the
|
|
// groups are subscribed when counting up children for display purposes
|
|
|
|
int numTotal, numSubscribed;
|
|
numTotal = numSubscribed = GetNumSubFolders();
|
|
|
|
for (int i = 0; i < numTotal; i++)
|
|
{
|
|
MSG_FolderInfoNews *newsFolder = GetSubFolder(i)->GetNewsFolderInfo();
|
|
XP_ASSERT(newsFolder);
|
|
if (newsFolder && !newsFolder->IsSubscribed())
|
|
numSubscribed--;
|
|
}
|
|
|
|
return numSubscribed;
|
|
}
|
|
|
|
/*
|
|
--------------------------------------------------------------
|
|
MSG_FolderIterator
|
|
--------------------------------------------------------------
|
|
*/
|
|
|
|
// Construct a new folder iterator.
|
|
// ### mw What if someone else messes with this folder tree
|
|
// while we're iterating over it?
|
|
MSG_FolderIterator::MSG_FolderIterator(MSG_FolderInfo *parent)
|
|
: m_masterParent(parent), m_currentParent(parent), m_currentIndex(-1)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
MSG_FolderIterator::~MSG_FolderIterator()
|
|
{
|
|
Init(); // This removes any stack we may have had.
|
|
}
|
|
|
|
// Initialize the iterator to a known state
|
|
// (pointing at the first element in the folder tree).
|
|
void
|
|
MSG_FolderIterator::Init(void)
|
|
{
|
|
// If we happen to have any stack left from a previous iteration,
|
|
// then remove it.
|
|
if (m_stack.GetSize() > 0)
|
|
{
|
|
// ### mw clumsy, but clean. I need to better understand the way that
|
|
// delete[] is used in XPPtrArray::SetSize(); perhaps I can
|
|
// call that instead of doing this.
|
|
for(int i=0; i < m_stack.GetSize(); i++)
|
|
{
|
|
MSG_FolderIteratorStackElement *elem =
|
|
(MSG_FolderIteratorStackElement *) m_stack.GetAt(i);
|
|
if (elem) delete elem;
|
|
}
|
|
m_stack.RemoveAt(0, m_stack.GetSize());
|
|
}
|
|
|
|
// Using a NULL parent means that we're operating on the
|
|
// master parent folder.
|
|
m_currentParent = NULL;
|
|
m_currentIndex = 0;
|
|
}
|
|
|
|
// Push the currently iterating folder onto the stack.
|
|
// This is used when one of its subfolders contains subfolders.
|
|
XP_Bool
|
|
MSG_FolderIterator::Push(void)
|
|
{
|
|
XP_Bool result = FALSE;
|
|
MSG_FolderInfo *nextParent = NextNoAdvance();
|
|
XP_ASSERT(nextParent != NULL);
|
|
|
|
// Create a new layer to add to the stack.
|
|
MSG_FolderIteratorStackElement *elem = new MSG_FolderIteratorStackElement;
|
|
|
|
XP_ASSERT(elem != NULL);
|
|
if (elem)
|
|
{
|
|
// Save current state info.
|
|
elem->m_parent = m_currentParent;
|
|
elem->m_currentIndex = m_currentIndex;
|
|
|
|
// Add the new layer to the stack at the end.
|
|
m_stack.InsertAt(m_stack.GetSize(), elem, 1);
|
|
|
|
// Reset the current parent/index to reflect the new element.
|
|
m_currentParent = nextParent;
|
|
m_currentIndex = 0;
|
|
|
|
result = TRUE; // signal that we successfully pushed
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Pop the most recently iterating folder container from the stack.
|
|
// This is typically used when one finishes iterating over a subfolder
|
|
// which itself contains subfolders.
|
|
XP_Bool
|
|
MSG_FolderIterator::Pop(void)
|
|
{
|
|
XP_Bool result = FALSE;
|
|
|
|
if (m_stack.GetSize() > 0)
|
|
{
|
|
// Get the most recent (highest numbered) element from the stack.
|
|
MSG_FolderIteratorStackElement *elem =
|
|
(MSG_FolderIteratorStackElement *) m_stack[m_stack.GetSize() - 1];
|
|
if (elem)
|
|
{
|
|
// Got a stack element. Shrink the stack.
|
|
m_stack.RemoveAt(m_stack.GetSize() - 1, 1);
|
|
|
|
// Restore state information from the popped stack element.
|
|
m_currentParent = elem->m_parent;
|
|
m_currentIndex = elem->m_currentIndex + 1;
|
|
|
|
// Now that we don't need it, delete the stack element.
|
|
delete elem;
|
|
|
|
// Signal that we successfully popped.
|
|
result = TRUE;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
MSG_FolderInfo *
|
|
MSG_FolderIterator::First()
|
|
{
|
|
// Reset our iteration state.
|
|
Init();
|
|
// We're now pointing at the first folder info element. Return it.
|
|
return Next();
|
|
}
|
|
|
|
MSG_FolderInfo *
|
|
MSG_FolderIterator::NextNoAdvance()
|
|
{
|
|
MSG_FolderInfo *returnInfo = NULL;
|
|
|
|
if ((!m_currentParent) && (m_currentIndex == 0))
|
|
returnInfo = m_masterParent;
|
|
else
|
|
{
|
|
// Have we run out of folders at this depth?
|
|
while ((m_currentParent) && (m_currentIndex >= m_currentParent->GetNumSubFolders()))
|
|
{
|
|
// Try to pop out.
|
|
if (!Pop()) // if we can't Pop, we must be done.
|
|
break;
|
|
}
|
|
if ((m_currentParent) &&
|
|
(m_currentIndex < m_currentParent->GetNumSubFolders()))
|
|
{
|
|
// Get the folder we're sitting on.
|
|
returnInfo = m_currentParent->GetSubFolder(m_currentIndex);
|
|
}
|
|
}
|
|
// Return the folder.
|
|
return returnInfo;
|
|
}
|
|
|
|
MSG_FolderInfo *
|
|
MSG_FolderIterator::Next()
|
|
{
|
|
MSG_FolderInfo *returnInfo = NextNoAdvance();
|
|
|
|
if (returnInfo)
|
|
// Advance to the next folder, if any.
|
|
Advance();
|
|
|
|
// Return the folder.
|
|
return returnInfo;
|
|
}
|
|
|
|
void
|
|
MSG_FolderIterator::Advance()
|
|
{
|
|
|
|
// If the current folder sits atop subfolders, push down
|
|
// into the subfolders.
|
|
MSG_FolderInfo *currFolder = NextNoAdvance();
|
|
|
|
if (currFolder && currFolder->HasSubFolders() > 0)
|
|
{
|
|
// Drill down to the next level.
|
|
Push();
|
|
}
|
|
else
|
|
{
|
|
// Increment from where we are.
|
|
m_currentIndex++;
|
|
}
|
|
}
|
|
|
|
// Test whether we have more folders through which to iterate.
|
|
XP_Bool
|
|
MSG_FolderIterator::More()
|
|
{
|
|
// We should be sitting on top of the next folder if it exists, so
|
|
// test the next folder for existence.
|
|
XP_Bool result = FALSE;
|
|
if (!m_currentParent && m_currentIndex == 0)
|
|
result = TRUE;
|
|
if (m_currentParent &&
|
|
m_currentIndex < m_currentParent->GetNumSubFolders())
|
|
result = TRUE;
|
|
|
|
return result;
|
|
}
|
|
|
|
// Return the advanced-to folder if successful, NULL otherwise.
|
|
MSG_FolderInfo *
|
|
MSG_FolderIterator::AdvanceToFolder(MSG_FolderInfo *desiredFolder)
|
|
{
|
|
MSG_FolderInfo *curFolder = First();
|
|
|
|
while(curFolder && curFolder != desiredFolder)
|
|
{
|
|
curFolder = Next();
|
|
}
|
|
return curFolder;
|
|
}
|