Mozilla/mozilla/mailnews/base/util/nsMsgDBFolder.cpp
naving%netscape.com 684c365224 117835 r=cavin sr=bienvenu fixing invalid folder name containing japanese characters when you restart after creating new local folders. Also cleaning up local folders code that deals with checking for duplicate
folder names, renaming/deleting local folders


git-svn-id: svn://10.0.0.236/trunk@133057 18797224-902f-48f8-a5cc-f745e15eee43
2002-11-06 01:30:31 +00:00

1845 lines
54 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "msgCore.h"
#include "nsReadableUtils.h"
#include "nsMsgDBFolder.h"
#include "nsMsgFolderFlags.h"
#include "nsIPref.h"
#include "nsIFileChannel.h"
#include "nsIMsgFolderCache.h"
#include "nsIMsgFolderCacheElement.h"
#include "nsMsgBaseCID.h"
#include "nsIMsgMailNewsUrl.h"
#include "nsIMsgAccountManager.h"
#include "nsXPIDLString.h"
#include "nsLocalFolderSummarySpec.h"
#include "nsIFileStream.h"
#include "nsIChannel.h"
#include "nsITransport.h"
#include "nsIFileTransportService.h"
#include "nsIMsgFolderCompactor.h"
#include "nsIDocShell.h"
#include "nsIMsgWindow.h"
#include "nsIPrompt.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsAbBaseCID.h"
#include "nsIAbMDBDirectory.h"
#include "nsISpamSettings.h"
#include "nsIMsgFilterPlugin.h"
#include "nsIMsgMailSession.h"
#include "nsIRDFService.h"
#ifdef DEBUG_bienvenu
#define DO_FILTER_PLUGIN
#endif
#include <time.h>
#define oneHour 3600000000
#include "nsMsgUtils.h"
static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID);
static PRTime gtimeOfLastPurgeCheck; //variable to know when to check for purge_threshhold
#define PREF_MAIL_PROMPT_PURGE_THRESHOLD "mail.prompt_purge_threshhold"
#define PREF_MAIL_PURGE_THRESHOLD "mail.purge_threshhold"
nsIAtom* nsMsgDBFolder::mFolderLoadedAtom=nsnull;
nsIAtom* nsMsgDBFolder::mDeleteOrMoveMsgCompletedAtom=nsnull;
nsIAtom* nsMsgDBFolder::mDeleteOrMoveMsgFailedAtom=nsnull;
nsrefcnt nsMsgDBFolder::mInstanceCount=0;
NS_IMPL_ISUPPORTS_INHERITED2(nsMsgDBFolder, nsMsgFolder,
nsIDBChangeListener,
nsIUrlListener)
nsMsgDBFolder::nsMsgDBFolder(void)
: mAddListener(PR_TRUE), mNewMessages(PR_FALSE), mGettingNewMessages(PR_FALSE)
{
if (mInstanceCount++ <=0) {
mFolderLoadedAtom = NS_NewAtom("FolderLoaded");
mDeleteOrMoveMsgCompletedAtom = NS_NewAtom("DeleteOrMoveMsgCompleted");
mDeleteOrMoveMsgFailedAtom = NS_NewAtom("DeleteOrMoveMsgFailed");
LL_I2L(gtimeOfLastPurgeCheck, 0);
}
}
nsMsgDBFolder::~nsMsgDBFolder(void)
{
if (--mInstanceCount == 0) {
NS_IF_RELEASE(mFolderLoadedAtom);
NS_IF_RELEASE(mDeleteOrMoveMsgCompletedAtom);
NS_IF_RELEASE(mDeleteOrMoveMsgFailedAtom);
}
//shutdown but don't shutdown children.
Shutdown(PR_FALSE);
}
NS_IMETHODIMP nsMsgDBFolder::Shutdown(PRBool shutdownChildren)
{
if(mDatabase)
{
mDatabase->RemoveListener(this);
mDatabase->Close(PR_TRUE);
mDatabase = nsnull;
}
if(shutdownChildren)
{
PRUint32 count;
nsresult rv = mSubFolders->Count(&count);
if(NS_SUCCEEDED(rv))
{
for (PRUint32 i = 0; i < count; i++)
{
nsCOMPtr<nsISupports> childFolderSupports = getter_AddRefs(mSubFolders->ElementAt(i));
if(childFolderSupports)
{
nsCOMPtr<nsIFolder> childFolder = do_QueryInterface(childFolderSupports);
if(childFolder)
childFolder->Shutdown(PR_TRUE);
}
}
}
// Ask base class shutdown itself.
nsMsgFolder::Shutdown(shutdownChildren);
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::ForceDBClosed ()
{
NotifyStoreClosedAllHeaders();
PRUint32 cnt = 0, i;
if (mSubFolders)
{
nsCOMPtr<nsISupports> aSupport;
nsCOMPtr<nsIMsgFolder> child;
mSubFolders->Count(&cnt);
if (cnt > 0)
for (i = 0; i < cnt; i++)
{
aSupport = getter_AddRefs(mSubFolders->ElementAt(i));
child = do_QueryInterface(aSupport);
if (child)
child->ForceDBClosed();
}
}
if (mDatabase)
{
mDatabase->ForceClosed();
mDatabase = nsnull;
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::StartFolderLoading(void)
{
if(mDatabase)
mDatabase->RemoveListener(this);
mAddListener = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::EndFolderLoading(void)
{
if(mDatabase)
mDatabase->AddListener(this);
mAddListener = PR_TRUE;
UpdateSummaryTotals(PR_TRUE);
//GGGG check for new mail here and call SetNewMessages...?? -- ONE OF THE 2 PLACES
if(mDatabase)
{
nsresult rv;
PRBool hasNewMessages;
rv = mDatabase->HasNew(&hasNewMessages);
SetHasNewMessages(hasNewMessages);
}
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::GetExpungedBytes(PRUint32 *count)
{
if(!count)
return NS_ERROR_NULL_POINTER;
if (mDatabase)
{
nsresult rv;
nsCOMPtr<nsIDBFolderInfo> folderInfo;
rv = mDatabase->GetDBFolderInfo(getter_AddRefs(folderInfo));
if (NS_FAILED(rv)) return rv;
rv = folderInfo->GetExpungedBytes((PRInt32 *) count);
if (NS_SUCCEEDED(rv))
mExpungedBytes = *count; // sync up with the database
return rv;
}
else
{
ReadDBFolderInfo(PR_FALSE);
*count = mExpungedBytes;
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetCharset(PRUnichar * *aCharset)
{
nsresult rv = NS_OK;
if(!aCharset)
return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if(NS_SUCCEEDED(rv))
{
nsXPIDLCString charset;
rv = folderInfo->GetCharPtrCharacterSet(getter_Copies(charset));
if(NS_SUCCEEDED(rv))
{
*aCharset = ToNewUnicode(charset);
}
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::SetCharset(const PRUnichar * aCharset)
{
nsresult rv;
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if(NS_SUCCEEDED(rv))
{
rv = folderInfo->SetCharacterSet(aCharset);
db->Commit(nsMsgDBCommitType::kLargeCommit);
mCharset.Assign(aCharset); // synchronize member variable
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::GetCharsetOverride(PRBool *aCharsetOverride)
{
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if(NS_SUCCEEDED(rv))
rv = folderInfo->GetCharacterSetOverride(aCharsetOverride);
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::SetCharsetOverride(PRBool aCharsetOverride)
{
nsresult rv;
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if(NS_SUCCEEDED(rv))
{
rv = folderInfo->SetCharacterSetOverride(aCharsetOverride);
db->Commit(nsMsgDBCommitType::kLargeCommit);
mCharsetOverride = aCharsetOverride; // synchronize member variable
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::GetHasNewMessages(PRBool *hasNewMessages)
{
if(!hasNewMessages)
return NS_ERROR_NULL_POINTER;
nsresult rv = NS_OK;
*hasNewMessages = mNewMessages;
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::SetHasNewMessages(PRBool curNewMessages)
{
if (curNewMessages != mNewMessages)
{
/** @params
* nsIAtom* property, PRBool oldValue, PRBool newValue
*/
PRBool oldNewMessages = mNewMessages;
mNewMessages = curNewMessages;
NotifyBoolPropertyChanged(kNewMessagesAtom, oldNewMessages, curNewMessages);
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetGettingNewMessages(PRBool *gettingNewMessages)
{
if(!gettingNewMessages)
return NS_ERROR_NULL_POINTER;
nsresult rv = NS_OK;
*gettingNewMessages = mGettingNewMessages;
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::SetGettingNewMessages(PRBool gettingNewMessages)
{
mGettingNewMessages = gettingNewMessages;
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetFirstNewMessage(nsIMsgDBHdr **firstNewMessage)
{
//If there's not a db then there can't be new messages. Return failure since you
//should use HasNewMessages first.
if(!mDatabase)
return NS_ERROR_FAILURE;
nsresult rv;
nsMsgKey key;
rv = mDatabase->GetFirstNew(&key);
if(NS_FAILED(rv))
return rv;
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
if(NS_FAILED(rv))
return rv;
return mDatabase->GetMsgHdrForKey(key, firstNewMessage);
}
NS_IMETHODIMP nsMsgDBFolder::ClearNewMessages()
{
nsresult rv = NS_OK;
//If there's no db then there's nothing to clear.
if(mDatabase)
{
rv = mDatabase->ClearNewList(PR_TRUE);
}
return rv;
}
// helper function that gets the cache element that corresponds to the passed in file spec.
// This could be static, or could live in another class - it's not specific to the current
// nsMsgDBFolder. If it lived at a higher level, we could cache the account manager and folder cache.
nsresult nsMsgDBFolder::GetFolderCacheElemFromFileSpec(nsIFileSpec *fileSpec, nsIMsgFolderCacheElement **cacheElement)
{
nsresult result;
if (!fileSpec || !cacheElement)
return NS_ERROR_NULL_POINTER;
nsCOMPtr <nsIMsgFolderCache> folderCache;
#ifdef DEBUG_bienvenu1
PRBool exists;
NS_ASSERTION(NS_SUCCEEDED(fileSpec->Exists(&exists)) && exists, "whoops, file doesn't exist, mac will break");
#endif
nsCOMPtr<nsIMsgAccountManager> accountMgr =
do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &result);
if(NS_SUCCEEDED(result))
{
result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
if (NS_SUCCEEDED(result) && folderCache)
{
nsXPIDLCString persistentPath;
fileSpec->GetPersistentDescriptorString(getter_Copies(persistentPath));
result = folderCache->GetCacheElement(persistentPath, PR_FALSE, cacheElement);
}
}
return result;
}
nsresult nsMsgDBFolder::ReadDBFolderInfo(PRBool force)
{
// 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
nsresult result=NS_ERROR_FAILURE;
// don't need to reload from cache if we've already read from cache,
// and, we might get stale info, so don't do it.
if (!mInitializedFromCache)
{
nsCOMPtr <nsIFileSpec> dbPath;
result = GetFolderCacheKey(getter_AddRefs(dbPath));
if (dbPath)
{
nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
result = GetFolderCacheElemFromFileSpec(dbPath, getter_AddRefs(cacheElement));
if (NS_SUCCEEDED(result) && cacheElement)
{
result = ReadFromFolderCacheElem(cacheElement);
}
}
}
// if (m_master->InitFolderFromCache (this))
// return err;
if (force || !mInitializedFromCache)
{
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
result = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if(NS_SUCCEEDED(result))
{
mIsCachable = PR_TRUE;
if (folderInfo)
{
if (!mInitializedFromCache)
{
folderInfo->GetFlags((PRInt32 *)&mFlags);
#ifdef DEBUG_bienvenu1
nsXPIDLString name;
GetName(getter_Copies(name));
NS_ASSERTION(Compare(name, kLocalizedTrashName) || (mFlags & MSG_FOLDER_FLAG_TRASH), "lost trash flag");
#endif
mInitializedFromCache = PR_TRUE;
}
folderInfo->GetNumMessages(&mNumTotalMessages);
folderInfo->GetNumNewMessages(&mNumUnreadMessages);
folderInfo->GetExpungedBytes((PRInt32 *)&mExpungedBytes);
nsXPIDLCString utf8Name;
folderInfo->GetFolderName(getter_Copies(utf8Name));
if (!utf8Name.IsEmpty())
mName = NS_ConvertUTF8toUCS2(utf8Name.get());
//These should be put in IMAP folder only.
//folderInfo->GetImapTotalPendingMessages(&mNumPendingTotalMessages);
//folderInfo->GetImapUnreadPendingMessages(&mNumPendingUnreadMessages);
PRBool defaultUsed;
folderInfo->GetCharacterSet(&mCharset, &defaultUsed);
if (defaultUsed)
mCharset.Assign(NS_LITERAL_STRING(""));
folderInfo->GetCharacterSetOverride(&mCharsetOverride);
if (db) {
PRBool hasnew;
nsresult rv;
rv = db->HasNew(&hasnew);
if (NS_FAILED(rv)) return rv;
if (!hasnew && mNumPendingUnreadMessages <= 0) {
ClearFlag(MSG_FOLDER_FLAG_GOT_NEW);
}
}
}
}
folderInfo = nsnull;
if (db)
db->Close(PR_FALSE);
}
return result;
}
nsresult nsMsgDBFolder::SendFlagNotifications(nsISupports *item, PRUint32 oldFlags, PRUint32 newFlags)
{
nsresult rv = NS_OK;
PRUint32 changedFlags = oldFlags ^ newFlags;
if((changedFlags & MSG_FLAG_READ) && (changedFlags & MSG_FLAG_NEW))
{
//..so..if the msg is read in the folder and the folder has new msgs clear the account level and status bar biffs.
rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags);
rv = SetBiffState(nsMsgBiffState_NoMail);
}
else if(changedFlags & (MSG_FLAG_READ | MSG_FLAG_REPLIED | MSG_FLAG_FORWARDED
| MSG_FLAG_IMAP_DELETED | MSG_FLAG_NEW | MSG_FLAG_OFFLINE))
{
rv = NotifyPropertyFlagChanged(item, kStatusAtom, oldFlags, newFlags);
}
else if((changedFlags & MSG_FLAG_MARKED))
{
rv = NotifyPropertyFlagChanged(item, kFlaggedAtom, oldFlags, newFlags);
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::DownloadMessagesForOffline(nsISupportsArray *messages, nsIMsgWindow *)
{
NS_ASSERTION(PR_FALSE, "imap and news need to override this");
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow)
{
NS_ASSERTION(PR_FALSE, "imap and news need to override this");
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetOfflineStoreInputStream(nsIInputStream **stream)
{
nsresult rv = NS_ERROR_NULL_POINTER;
if (mPath)
rv = mPath->GetInputStream(stream);
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::GetOfflineFileTransport(nsMsgKey msgKey, PRUint32 *offset, PRUint32 *size, nsITransport **aFileChannel)
{
NS_ENSURE_ARG(aFileChannel);
*offset = *size = 0;
nsresult rv;
rv = nsComponentManager::CreateInstance(NS_LOCALFILECHANNEL_CONTRACTID, nsnull,
NS_GET_IID(nsIFileChannel), (void **) aFileChannel);
if (*aFileChannel)
{
nsXPIDLCString nativePath;
mPath->GetNativePath(getter_Copies(nativePath));
nsCOMPtr <nsILocalFile> localStore;
rv = NS_NewNativeLocalFile(nativePath, PR_TRUE, getter_AddRefs(localStore));
if (NS_SUCCEEDED(rv) && localStore)
{
NS_DEFINE_CID(kFileTransportServiceCID, NS_FILETRANSPORTSERVICE_CID);
nsCOMPtr<nsIFileTransportService> fts =
do_GetService(kFileTransportServiceCID, &rv);
if (NS_FAILED(rv))
return rv;
rv = fts->CreateTransport(localStore,
PR_RDWR | PR_CREATE_FILE,
0664,
PR_TRUE,
aFileChannel);
if (NS_SUCCEEDED(rv))
{
nsresult rv = GetDatabase(nsnull);
NS_ENSURE_SUCCESS(rv, NS_OK);
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
if (hdr && NS_SUCCEEDED(rv))
{
hdr->GetMessageOffset(offset);
hdr->GetOfflineMessageSize(size);
}
}
}
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::GetOfflineStoreOutputStream(nsIOutputStream **outputStream)
{
nsresult rv = NS_ERROR_NULL_POINTER;
if (mPath)
{
// the following code doesn't work for a host of reasons - the transfer offset
// is ignored for output streams. The buffering used by file channels does not work
// if transfer offsets are coerced to work, etc.
#if 0
nsCOMPtr<nsIFileChannel> fileChannel = do_CreateInstance(NS_LOCALFILECHANNEL_CONTRACTID);
if (fileChannel)
{
nsCOMPtr <nsILocalFile> localStore;
rv = NS_NewLocalFile(nativePath, PR_TRUE, getter_AddRefs(localStore));
if (NS_SUCCEEDED(rv) && localStore)
{
rv = fileChannel->Init(localStore, PR_CREATE_FILE | PR_RDWR, 0);
if (NS_FAILED(rv))
return rv;
rv = fileChannel->Open(outputStream);
if (NS_FAILED(rv))
return rv;
}
}
#endif
nsCOMPtr<nsISupports> supports;
nsFileSpec fileSpec;
mPath->GetFileSpec(&fileSpec);
rv = NS_NewIOFileStream(getter_AddRefs(supports), fileSpec, PR_WRONLY | PR_CREATE_FILE, 00700);
NS_ENSURE_SUCCESS(rv, rv);
supports->QueryInterface(NS_GET_IID(nsIOutputStream), (void **) outputStream);
nsCOMPtr <nsIRandomAccessStore> seekable = do_QueryInterface(supports);
if (seekable)
seekable->Seek(nsISeekableStream::NS_SEEK_END, 0);
}
return rv;
}
// XXX todo
// move these to a common location and remove all the hard coded ".msf"
#define SUMMARY_SUFFIX ".msf"
#define SUMMARY_SUFFIX_LEN 4
// path coming in is the root path without the leaf name,
// on the way out, it's the whole path.
nsresult nsMsgDBFolder::CreateFileSpecForDB(const char *userLeafName, nsFileSpec &path, nsIFileSpec **dbFileSpec)
{
NS_ENSURE_ARG_POINTER(dbFileSpec);
NS_ENSURE_ARG_POINTER(userLeafName);
nsCAutoString proposedDBName(userLeafName);
NS_MsgHashIfNecessary(proposedDBName);
// (note, the caller of this will be using the dbFileSpec to call db->Open()
// will turn the path into summary spec, and append the ".msf" extension)
//
// we want db->Open() to create a new summary file
// so we have to jump through some hoops to make sure the .msf it will
// create is unique. now that we've got the "safe" proposedDBName,
// we append ".msf" to see if the file exists. if so, we make the name
// unique and then string off the ".msf" so that we pass the right thing
// into Open(). this isn't ideal, since this is not atomic
// but it will make do.
proposedDBName+= SUMMARY_SUFFIX;
path += proposedDBName.get();
if (path.Exists())
{
path.MakeUnique();
proposedDBName = path.GetLeafName();
}
// now, take the ".msf" off
proposedDBName.Truncate(proposedDBName.Length() - SUMMARY_SUFFIX_LEN);
path.SetLeafName(proposedDBName.get());
NS_NewFileSpecWithSpec(path, dbFileSpec);
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::GetMsgDatabase(nsIMsgWindow *aMsgWindow,
nsIMsgDatabase** aMsgDatabase)
{
GetDatabase(aMsgWindow);
if (!aMsgDatabase || !mDatabase)
return NS_ERROR_NULL_POINTER;
*aMsgDatabase = mDatabase;
NS_ADDREF(*aMsgDatabase);
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase *aMsgDatabase)
{
if (mDatabase)
{
// commit here - db might go away when all these refs are released.
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
mDatabase->RemoveListener(this);
mDatabase->ClearCachedHdrs();
}
mDatabase = aMsgDatabase;
if (aMsgDatabase)
aMsgDatabase->AddListener(this);
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::OnReadChanged(nsIDBChangeListener * aInstigator)
{
/* do nothing. if you care about this, over ride it. see nsNewsFolder.cpp */
return NS_OK;
}
// 1. When the status of a message changes.
NS_IMETHODIMP nsMsgDBFolder::OnKeyChange(nsMsgKey aKeyChanged, PRUint32 aOldFlags, PRUint32 aNewFlags,
nsIDBChangeListener * aInstigator)
{
nsCOMPtr<nsIMsgDBHdr> pMsgDBHdr;
nsresult rv = mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(pMsgDBHdr));
if(NS_SUCCEEDED(rv) && pMsgDBHdr)
{
nsCOMPtr<nsISupports> msgSupports(do_QueryInterface(pMsgDBHdr, &rv));
if(NS_SUCCEEDED(rv))
SendFlagNotifications(msgSupports, aOldFlags, aNewFlags);
UpdateSummaryTotals(PR_TRUE);
}
// The old state was new message state
// We check and see if this state has changed
if(aOldFlags & MSG_FLAG_NEW)
{
// state changing from new to something else
if (!(aNewFlags & MSG_FLAG_NEW))
{
CheckWithNewMessagesStatus(PR_FALSE);
}
}
return NS_OK;
}
nsresult nsMsgDBFolder::CheckWithNewMessagesStatus(PRBool messageAdded)
{
nsresult rv;
PRBool hasNewMessages;
if (messageAdded)
{
SetHasNewMessages(PR_TRUE);
}
else // message modified or deleted
{
if(mDatabase)
{
rv = mDatabase->HasNew(&hasNewMessages);
SetHasNewMessages(hasNewMessages);
}
}
return NS_OK;
}
// 3. When a message gets deleted, we need to see if it was new
// When we lose a new message we need to check if there are still new messages
NS_IMETHODIMP nsMsgDBFolder::OnKeyDeleted(nsMsgKey aKeyChanged, nsMsgKey aParentKey, PRInt32 aFlags,
nsIDBChangeListener * aInstigator)
{
// check to see if a new message is being deleted
// as in this case, if there is only one new message and it's being deleted
// the folder newness has to be cleared.
CheckWithNewMessagesStatus(PR_FALSE);
//Do both flat and thread notifications
return OnKeyAddedOrDeleted(aKeyChanged, aParentKey, aFlags, aInstigator, PR_FALSE, PR_TRUE, PR_TRUE);
}
// 2. When a new messages gets added, we need to see if it's new.
NS_IMETHODIMP nsMsgDBFolder::OnKeyAdded(nsMsgKey aKeyChanged, nsMsgKey aParentKey , PRInt32 aFlags,
nsIDBChangeListener * aInstigator)
{
if(aFlags & MSG_FLAG_NEW) {
CheckWithNewMessagesStatus(PR_TRUE);
}
//Do both flat and thread notifications
return OnKeyAddedOrDeleted(aKeyChanged, aParentKey, aFlags, aInstigator, PR_TRUE, PR_TRUE, PR_TRUE);
}
nsresult nsMsgDBFolder::OnKeyAddedOrDeleted(nsMsgKey aKeyChanged, nsMsgKey aParentKey , PRInt32 aFlags,
nsIDBChangeListener * aInstigator, PRBool added, PRBool doFlat, PRBool doThread)
{
nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
nsresult rv = mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(msgDBHdr));
if(NS_SUCCEEDED(rv) && msgDBHdr)
{
nsCOMPtr<nsISupports> msgSupports(do_QueryInterface(msgDBHdr));
nsCOMPtr<nsISupports> folderSupports;
rv = QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(folderSupports));
if(msgSupports && NS_SUCCEEDED(rv) && doFlat)
{
if(added)
NotifyItemAdded(folderSupports, msgSupports, "flatMessageView");
else
NotifyItemDeleted(folderSupports, msgSupports, "flatMessageView");
}
if(msgSupports && folderSupports)
{
if(added)
NotifyItemAdded(folderSupports, msgSupports, "threadMessageView");
else
NotifyItemDeleted(folderSupports, msgSupports, "threadMessageView");
}
UpdateSummaryTotals(PR_TRUE);
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent,
nsIDBChangeListener * aInstigator)
{
//In reality we probably want to just change the parent because otherwise we will lose things like
//selection.
//First delete the child from the old threadParent
OnKeyAddedOrDeleted(aKeyChanged, oldParent, 0, aInstigator, PR_FALSE, PR_FALSE, PR_TRUE);
//Then add it to the new threadParent
OnKeyAddedOrDeleted(aKeyChanged, newParent, 0, aInstigator, PR_TRUE, PR_FALSE, PR_TRUE);
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *
instigator)
{
if (mDatabase)
{
mDatabase->RemoveListener(this);
mDatabase = nsnull;
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(PRBool *retval)
{
PRInt32 numTotalMessages;
NS_ENSURE_ARG_POINTER(retval);
// is there any reason to return false?
if (!mDatabase)
*retval = PR_TRUE;
else if (NS_SUCCEEDED(GetTotalMessages(PR_FALSE, &numTotalMessages)) && numTotalMessages <= 0)
*retval = PR_TRUE;
else
*retval = PR_FALSE;
return NS_OK;
}
nsresult nsMsgDBFolder::MsgFitsDownloadCriteria(nsMsgKey msgKey, PRBool *result)
{
if(!mDatabase)
return NS_ERROR_FAILURE;
nsresult rv;
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
if(NS_FAILED(rv))
return rv;
if (hdr)
{
PRUint32 msgFlags = 0;
hdr->GetFlags(&msgFlags);
// check if we already have this message body offline
if (! (msgFlags & MSG_FLAG_OFFLINE))
{
*result = PR_TRUE;
// check against the server download size limit .
nsCOMPtr <nsIMsgIncomingServer> incomingServer;
rv = GetServer(getter_AddRefs(incomingServer));
if (NS_SUCCEEDED(rv) && incomingServer)
{
PRBool limitDownloadSize = PR_FALSE;
rv = incomingServer->GetLimitOfflineMessageSize(&limitDownloadSize);
NS_ENSURE_SUCCESS(rv, rv);
if (limitDownloadSize)
{
PRInt32 maxDownloadMsgSize = 0;
PRUint32 msgSize;
hdr->GetMessageSize(&msgSize);
rv = incomingServer->GetMaxMessageSize(&maxDownloadMsgSize);
NS_ENSURE_SUCCESS(rv, rv);
maxDownloadMsgSize *= 1024;
if (msgSize > (PRUint32) maxDownloadMsgSize)
*result = PR_FALSE;
}
}
}
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetSupportsOffline(PRBool *aSupportsOffline)
{
NS_ENSURE_ARG_POINTER(aSupportsOffline);
nsCOMPtr<nsIMsgIncomingServer> server;
nsresult rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv,rv);
if (!server) return NS_ERROR_FAILURE;
PRInt32 offlineSupportLevel;
rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
NS_ENSURE_SUCCESS(rv,rv);
*aSupportsOffline = (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR);
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::ShouldStoreMsgOffline(nsMsgKey msgKey, PRBool *result)
{
NS_ENSURE_ARG(result);
PRUint32 flags = 0;
*result = PR_FALSE;
GetFlags(&flags);
if (flags & MSG_FOLDER_FLAG_OFFLINE)
return MsgFitsDownloadCriteria(msgKey, result);
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::HasMsgOffline(nsMsgKey msgKey, PRBool *result)
{
NS_ENSURE_ARG(result);
*result = PR_FALSE;
if(!mDatabase)
return NS_ERROR_FAILURE;
nsresult rv;
nsCOMPtr<nsIMsgDBHdr> hdr;
rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
if(NS_FAILED(rv))
return rv;
if (hdr)
{
PRUint32 msgFlags = 0;
hdr->GetFlags(&msgFlags);
// check if we already have this message body offline
if ((msgFlags & MSG_FLAG_OFFLINE))
*result = PR_TRUE;
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetFlags(PRUint32 *_retval)
{
ReadDBFolderInfo(PR_FALSE);
*_retval = mFlags;
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
{
nsresult rv = NS_OK;
nsXPIDLCString charset;
element->GetInt32Property("flags", (PRInt32 *) &mFlags);
PRBool persistElided = PR_TRUE;
rv = GetPersistElided(&persistElided);
NS_ENSURE_SUCCESS(rv,rv);
// we aren't persisting elided, set the folder as closed
if (!persistElided) {
mFlags |= MSG_FOLDER_FLAG_ELIDED;
}
element->GetInt32Property("totalMsgs", &mNumTotalMessages);
element->GetInt32Property("totalUnreadMsgs", &mNumUnreadMessages);
element->GetInt32Property("pendingUnreadMsgs", &mNumPendingUnreadMessages);
element->GetInt32Property("pendingMsgs", &mNumPendingTotalMessages);
element->GetInt32Property("expungedBytes", (PRInt32 *) &mExpungedBytes);
element->GetInt32Property("folderSize", (PRInt32 *) &mFolderSize);
element->GetStringProperty("charset", getter_Copies(charset));
#ifdef DEBUG_bienvenu1
char *uri;
GetURI(&uri);
printf("read total %ld for %s\n", mNumTotalMessages, uri);
PR_Free(uri);
#endif
mCharset.AssignWithConversion(charset.get());
mInitializedFromCache = PR_TRUE;
return rv;
}
nsresult nsMsgDBFolder::GetFolderCacheKey(nsIFileSpec **aFileSpec)
{
nsresult rv;
nsCOMPtr <nsIFileSpec> path;
rv = GetPath(getter_AddRefs(path));
// now we put a new file spec in aFileSpec, because we're going to change it.
rv = NS_NewFileSpec(aFileSpec);
if (NS_SUCCEEDED(rv) && *aFileSpec)
{
nsIFileSpec *dbPath = *aFileSpec;
dbPath->FromFileSpec(path);
// if not a server, we need to convert to a db Path with .msf on the end
PRBool isServer = PR_FALSE;
GetIsServer(&isServer);
// if it's a server, we don't need the .msf appended to the name
if (!isServer)
{
nsFileSpec folderName;
dbPath->GetFileSpec(&folderName);
nsLocalFolderSummarySpec summarySpec(folderName);
dbPath->SetFromFileSpec(summarySpec);
}
}
return rv;
}
nsresult nsMsgDBFolder::FlushToFolderCache()
{
nsresult rv;
nsCOMPtr<nsIMsgAccountManager> accountManager =
do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && accountManager)
{
nsCOMPtr<nsIMsgFolderCache> folderCache;
rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
if (NS_SUCCEEDED(rv) && folderCache)
rv = WriteToFolderCache(folderCache, PR_FALSE);
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCache(nsIMsgFolderCache *folderCache, PRBool deep)
{
nsCOMPtr <nsIEnumerator> aEnumerator;
nsresult rv;
if (folderCache)
{
nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
nsCOMPtr <nsIFileSpec> dbPath;
rv = GetFolderCacheKey(getter_AddRefs(dbPath));
#ifdef DEBUG_bienvenu1
PRBool exists;
NS_ASSERTION(NS_SUCCEEDED(dbPath->Exists(&exists)) && exists, "file spec we're adding to cache should exist");
#endif
if (NS_SUCCEEDED(rv) && dbPath)
{
nsXPIDLCString persistentPath;
dbPath->GetPersistentDescriptorString(getter_Copies(persistentPath));
rv = folderCache->GetCacheElement(persistentPath, PR_TRUE, getter_AddRefs(cacheElement));
if (NS_SUCCEEDED(rv) && cacheElement)
rv = WriteToFolderCacheElem(cacheElement);
}
}
if (!deep)
return rv;
rv = GetSubFolders(getter_AddRefs(aEnumerator));
if(NS_FAILED(rv))
return rv;
nsCOMPtr<nsISupports> aItem;
rv = aEnumerator->First();
if (NS_FAILED(rv))
return NS_OK; // it's OK, there are no sub-folders.
while(NS_SUCCEEDED(rv))
{
rv = aEnumerator->CurrentItem(getter_AddRefs(aItem));
if (NS_FAILED(rv)) break;
nsCOMPtr<nsIMsgFolder> aMsgFolder(do_QueryInterface(aItem, &rv));
if (NS_SUCCEEDED(rv))
{
if (folderCache)
{
rv = aMsgFolder->WriteToFolderCache(folderCache, PR_TRUE);
if (NS_FAILED(rv))
break;
}
}
rv = aEnumerator->Next();
if (NS_FAILED(rv))
{
rv = NS_OK;
break;
}
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCacheElem(nsIMsgFolderCacheElement *element)
{
nsresult rv = NS_OK;
element->SetInt32Property("flags", (PRInt32) mFlags);
element->SetInt32Property("totalMsgs", mNumTotalMessages);
element->SetInt32Property("totalUnreadMsgs", mNumUnreadMessages);
element->SetInt32Property("pendingUnreadMsgs", mNumPendingUnreadMessages);
element->SetInt32Property("pendingMsgs", mNumPendingTotalMessages);
element->SetInt32Property("expungedBytes", mExpungedBytes);
element->SetInt32Property("folderSize", mFolderSize);
nsCAutoString mcharsetC;
mcharsetC.AssignWithConversion(mCharset);
element->SetStringProperty("charset", mcharsetC.get());
#ifdef DEBUG_bienvenu1
char *uri;
GetURI(&uri);
printf("writing total %ld for %s\n", mNumTotalMessages, uri);
PR_Free(uri);
#endif
return rv;
}
NS_IMETHODIMP
nsMsgDBFolder::SetFlag(PRUint32 flag)
{
ReadDBFolderInfo(PR_FALSE);
return nsMsgFolder::SetFlag(flag);
}
NS_IMETHODIMP
nsMsgDBFolder::AddMessageDispositionState(nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag)
{
NS_ENSURE_ARG_POINTER(aMessage);
nsresult rv = GetDatabase(nsnull);
NS_ENSURE_SUCCESS(rv, NS_OK);
nsMsgKey msgKey;
aMessage->GetMessageKey(&msgKey);
if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
mDatabase->MarkReplied(msgKey, PR_TRUE, nsnull);
else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
mDatabase->MarkForwarded(msgKey, PR_TRUE, nsnull);
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::MarkAllMessagesRead(void)
{
// ### fix me need nsIMsgWindow
nsresult rv = GetDatabase(nsnull);
if(NS_SUCCEEDED(rv))
{
EnableNotifications(allMessageCountNotifications, PR_FALSE, PR_TRUE /*dbBatching*/);
rv = mDatabase->MarkAllRead(nsnull);
EnableNotifications(allMessageCountNotifications, PR_TRUE, PR_TRUE /*dbBatching*/);
mDatabase->SetSummaryValid(PR_TRUE);
mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
}
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::MarkThreadRead(nsIMsgThread *thread)
{
nsresult rv = GetDatabase(nsnull);
if(NS_SUCCEEDED(rv))
return mDatabase->MarkThreadRead(thread, nsnull, nsnull);
return rv;
}
NS_IMETHODIMP
nsMsgDBFolder::OnStartRunningUrl(nsIURI *aUrl)
{
NS_PRECONDITION(aUrl, "just a sanity check");
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode)
{
NS_PRECONDITION(aUrl, "just a sanity check");
nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
if (mailUrl)
{
PRBool updatingFolder = PR_FALSE;
if (NS_SUCCEEDED(mailUrl->GetUpdatingFolder(&updatingFolder)) && updatingFolder)
NotifyFolderEvent(mFolderLoadedAtom);
// be sure to remove ourselves as a url listener
mailUrl->UnRegisterListener(this);
}
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetRetentionSettings(nsIMsgRetentionSettings **settings)
{
NS_ENSURE_ARG_POINTER(settings);
nsresult rv = NS_OK;
if (!m_retentionSettings)
{
GetDatabase(nsnull);
if (mDatabase)
{
// get the settings from the db - if the settings from the db say the folder
// is not overriding the incoming server settings, get the settings from the
// server.
rv = mDatabase->GetMsgRetentionSettings(getter_AddRefs(m_retentionSettings));
if (NS_SUCCEEDED(rv) && m_retentionSettings)
{
PRBool useServerDefaults;
m_retentionSettings->GetUseServerDefaults(&useServerDefaults);
if (useServerDefaults)
{
nsCOMPtr <nsIMsgIncomingServer> incomingServer;
rv = GetServer(getter_AddRefs(incomingServer));
if (NS_SUCCEEDED(rv) && incomingServer)
incomingServer->GetRetentionSettings(getter_AddRefs(m_retentionSettings));
}
}
}
}
*settings = m_retentionSettings;
NS_IF_ADDREF(*settings);
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::SetRetentionSettings(nsIMsgRetentionSettings *settings)
{
m_retentionSettings = settings;
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::GetDownloadSettings(nsIMsgDownloadSettings **settings)
{
NS_ENSURE_ARG_POINTER(settings);
nsresult rv = NS_OK;
if (!m_downloadSettings)
{
GetDatabase(nsnull);
if (mDatabase)
{
// get the settings from the db - if the settings from the db say the folder
// is not overriding the incoming server settings, get the settings from the
// server.
rv = mDatabase->GetMsgDownloadSettings(getter_AddRefs(m_downloadSettings));
if (NS_SUCCEEDED(rv) && m_downloadSettings)
{
PRBool useServerDefaults;
m_downloadSettings->GetUseServerDefaults(&useServerDefaults);
if (useServerDefaults)
{
nsCOMPtr <nsIMsgIncomingServer> incomingServer;
rv = GetServer(getter_AddRefs(incomingServer));
if (NS_SUCCEEDED(rv) && incomingServer)
incomingServer->GetDownloadSettings(getter_AddRefs(m_downloadSettings));
}
}
}
}
*settings = m_downloadSettings;
NS_IF_ADDREF(*settings);
return rv;
}
NS_IMETHODIMP nsMsgDBFolder::SetDownloadSettings(nsIMsgDownloadSettings *settings)
{
m_downloadSettings = settings;
return NS_OK;
}
NS_IMETHODIMP nsMsgDBFolder::IsCommandEnabled(const char *command, PRBool *result)
{
NS_ENSURE_ARG_POINTER(result);
*result = PR_TRUE;
return NS_OK;
}
nsresult nsMsgDBFolder::NotifyStoreClosedAllHeaders()
{
// don't need this anymore.
return NS_OK;
}
nsresult nsMsgDBFolder::WriteStartOfNewLocalMessage()
{
nsCAutoString result;
char *ct;
PRUint32 writeCount;
time_t now = time ((time_t*) 0);
ct = ctime(&now);
ct[24] = 0;
result = "From - ";
result += ct;
result += MSG_LINEBREAK;
nsCOMPtr <nsISeekableStream> seekable;
PRUint32 curStorePos;
if (m_offlineHeader)
seekable = do_QueryInterface(m_tempMessageStream);
if (seekable)
{
seekable->Tell(&curStorePos);
m_offlineHeader->SetMessageOffset(curStorePos);
}
m_tempMessageStream->Write(result.get(), result.Length(),
&writeCount);
if (seekable)
{
m_tempMessageStream->Flush();
seekable->Tell(&curStorePos);
m_offlineHeader->SetStatusOffset(curStorePos);
}
result = "X-Mozilla-Status: 0001";
result += MSG_LINEBREAK;
m_tempMessageStream->Write(result.get(), result.Length(),
&writeCount);
result = "X-Mozilla-Status2: 00000000";
result += MSG_LINEBREAK;
nsresult rv = m_tempMessageStream->Write(result.get(), result.Length(),
&writeCount);
return rv;
}
nsresult nsMsgDBFolder::StartNewOfflineMessage()
{
nsresult rv = GetOfflineStoreOutputStream(getter_AddRefs(m_tempMessageStream));
if (NS_SUCCEEDED(rv))
WriteStartOfNewLocalMessage();
m_numOfflineMsgLines = 0;
return rv;
}
nsresult nsMsgDBFolder::EndNewOfflineMessage()
{
nsCOMPtr <nsIRandomAccessStore> seekable;
PRUint32 curStorePos;
PRUint32 messageOffset;
nsMsgKey messageKey;
nsresult rv = GetDatabase(nsnull);
NS_ENSURE_SUCCESS(rv, rv);
m_offlineHeader->GetMessageKey(&messageKey);
if (m_tempMessageStream)
seekable = do_QueryInterface(m_tempMessageStream);
mDatabase->MarkOffline(messageKey, PR_TRUE, nsnull);
if (seekable)
{
m_tempMessageStream->Flush();
seekable->Tell(&curStorePos);
m_offlineHeader->GetMessageOffset(&messageOffset);
m_offlineHeader->SetOfflineMessageSize(curStorePos - messageOffset);
m_offlineHeader->SetLineCount(m_numOfflineMsgLines);
}
m_offlineHeader = nsnull;
return NS_OK;
}
nsresult nsMsgDBFolder::CompactOfflineStore(nsIMsgWindow *inWindow)
{
nsresult rv;
nsCOMPtr <nsIMsgFolderCompactor> folderCompactor = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && folderCompactor)
rv = folderCompactor->Compact(this, inWindow);
return rv;
}
nsresult
nsMsgDBFolder::AutoCompact(nsIMsgWindow *aWindow)
{
NS_ENSURE_ARG_POINTER(aWindow);
PRBool prompt;
nsresult rv = GetPromptPurgeThreshold(&prompt);
NS_ENSURE_SUCCESS(rv, rv);
PRTime timeNow = PR_Now(); //time in microseconds
PRTime timeAfterOneHourOfLastPurgeCheck;
LL_ADD(timeAfterOneHourOfLastPurgeCheck, gtimeOfLastPurgeCheck, oneHour);
if (LL_CMP(timeAfterOneHourOfLastPurgeCheck, <, timeNow) && prompt)
{
gtimeOfLastPurgeCheck = timeNow;
nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv))
{
nsCOMPtr<nsISupportsArray> allServers;
accountMgr->GetAllServers(getter_AddRefs(allServers));
NS_ENSURE_SUCCESS(rv,rv);
PRUint32 numServers, serverIndex=0;
rv = allServers->Count(&numServers);
PRInt32 offlineSupportLevel;
if ( numServers > 0 )
{
nsCOMPtr <nsISupports> serverSupports = getter_AddRefs(allServers->ElementAt(serverIndex));
nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(serverSupports);
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsISupportsArray> folderArray;
nsCOMPtr<nsISupportsArray> offlineFolderArray;
NS_NewISupportsArray(getter_AddRefs(folderArray));
NS_NewISupportsArray(getter_AddRefs(offlineFolderArray));
PRInt32 totalExpungedBytes=0;
PRInt32 offlineExpungedBytes =0;
PRInt32 localExpungedBytes = 0;
do
{
nsCOMPtr<nsIFolder> rootFolder;
rv = server->GetRootFolder(getter_AddRefs(rootFolder));
if(NS_SUCCEEDED(rv) && rootFolder)
{
rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsISupportsArray> allDescendents;
NS_NewISupportsArray(getter_AddRefs(allDescendents));
rootFolder->ListDescendents(allDescendents);
PRUint32 cnt=0;
rv = allDescendents->Count(&cnt);
NS_ENSURE_SUCCESS(rv,rv);
PRUint32 expungedBytes=0;
if (offlineSupportLevel > 0)
{
PRUint32 flags;
for (PRUint32 i=0; i< cnt;i++)
{
nsCOMPtr<nsISupports> supports = getter_AddRefs(allDescendents->ElementAt(i));
nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(supports, &rv);
expungedBytes = 0;
folder->GetFlags(&flags);
if (flags & MSG_FOLDER_FLAG_OFFLINE)
folder->GetExpungedBytes(&expungedBytes);
if (expungedBytes > 0 )
{
offlineFolderArray->AppendElement(supports);
offlineExpungedBytes += expungedBytes;
}
}
}
else //pop or local
{
for (PRUint32 i=0; i< cnt;i++)
{
nsCOMPtr<nsISupports> supports = getter_AddRefs(allDescendents->ElementAt(i));
nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(supports, &rv);
folder->GetExpungedBytes(&expungedBytes);
if (expungedBytes > 0 )
{
folderArray->AppendElement(supports);
localExpungedBytes += expungedBytes;
}
}
}
}
serverSupports = getter_AddRefs(allServers->ElementAt(++serverIndex));
server = do_QueryInterface(serverSupports, &rv);
}
while (serverIndex < numServers);
totalExpungedBytes = localExpungedBytes + offlineExpungedBytes;
PRInt32 purgeThreshold;
rv = GetPurgeThreshold(&purgeThreshold);
NS_ENSURE_SUCCESS(rv, rv);
if (totalExpungedBytes > (purgeThreshold*1024))
{
nsXPIDLString confirmString;
PRBool okToCompact = PR_FALSE;
rv = GetStringFromBundle("autoCompactAllFolders", getter_Copies(confirmString));
if (NS_SUCCEEDED(rv) && confirmString)
ThrowConfirmationPrompt(aWindow, confirmString.get(), &okToCompact);
if (okToCompact)
{
if ( localExpungedBytes > 0)
{
nsCOMPtr <nsISupports> aSupports = getter_AddRefs(folderArray->ElementAt(0));
nsCOMPtr <nsIMsgFolder> msgFolder = do_QueryInterface(aSupports, &rv);
if (msgFolder && NS_SUCCEEDED(rv))
if (offlineExpungedBytes > 0)
msgFolder->CompactAll(nsnull, aWindow, folderArray, PR_TRUE, offlineFolderArray);
else
msgFolder->CompactAll(nsnull, aWindow, folderArray, PR_FALSE, nsnull);
}
else if (offlineExpungedBytes > 0)
CompactAllOfflineStores(aWindow, offlineFolderArray);
}
}
}
}
}
return rv;
}
NS_IMETHODIMP
nsMsgDBFolder::CompactAllOfflineStores(nsIMsgWindow *aWindow, nsISupportsArray *aOfflineFolderArray)
{
nsresult rv= NS_OK;
nsCOMPtr <nsIMsgFolderCompactor> folderCompactor;
folderCompactor = do_CreateInstance(NS_MSGOFFLINESTORECOMPACTOR_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && folderCompactor)
rv = folderCompactor->CompactAll(aOfflineFolderArray, aWindow, PR_FALSE, nsnull);
return rv;
}
nsresult
nsMsgDBFolder::GetPromptPurgeThreshold(PRBool *aPrompt)
{
NS_ENSURE_ARG(aPrompt);
nsresult rv;
nsCOMPtr<nsIPref> prefService = do_GetService(NS_PREF_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && prefService)
{
rv = prefService->GetBoolPref(PREF_MAIL_PROMPT_PURGE_THRESHOLD, aPrompt);
if (NS_FAILED(rv))
{
*aPrompt = PR_FALSE;
rv = NS_OK;
}
}
return rv;
}
nsresult
nsMsgDBFolder::GetPurgeThreshold(PRInt32 *aThreshold)
{
NS_ENSURE_ARG(aThreshold);
nsresult rv;
nsCOMPtr<nsIPref> prefService = do_GetService(NS_PREF_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && prefService)
{
rv = prefService->GetIntPref(PREF_MAIL_PURGE_THRESHOLD, aThreshold);
if (NS_FAILED(rv))
{
*aThreshold = 0;
rv = NS_OK;
}
}
return rv;
}
NS_IMETHODIMP //called on the folder that is renamed or about to be deleted
nsMsgDBFolder::MatchOrChangeFilterDestination(nsIMsgFolder *newFolder, PRBool caseInsensitive, PRBool *found)
{
nsresult rv = NS_OK;
nsXPIDLCString oldUri;
rv = GetURI(getter_Copies(oldUri));
NS_ENSURE_SUCCESS(rv,rv);
nsXPIDLCString newUri;
if (newFolder) //for matching uri's this will be null
{
rv = newFolder->GetURI(getter_Copies(newUri));
NS_ENSURE_SUCCESS(rv,rv);
}
nsCOMPtr<nsIMsgFilterList> filterList;
nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv))
{
nsCOMPtr<nsISupportsArray> allServers;
rv = accountMgr->GetAllServers(getter_AddRefs(allServers));
if (NS_SUCCEEDED(rv) && allServers)
{
PRUint32 numServers,
rv = allServers->Count(&numServers);
for (PRUint32 serverIndex=0; serverIndex < numServers; serverIndex++)
{
nsCOMPtr <nsISupports> serverSupports = getter_AddRefs(allServers->ElementAt(serverIndex));
nsCOMPtr <nsIMsgIncomingServer> server = do_QueryInterface(serverSupports, &rv);
if (server && NS_SUCCEEDED(rv))
{
PRBool canHaveFilters;
rv = server->GetCanHaveFilters(&canHaveFilters);
if (NS_SUCCEEDED(rv) && canHaveFilters)
{
rv = server->GetFilterList(nsnull, getter_AddRefs(filterList));
if (filterList && NS_SUCCEEDED(rv))
{
rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri, caseInsensitive, found);
if (found && newFolder && newUri)
rv = filterList->SaveToDefaultFile();
}
}
}
}
}
}
return rv;
}
NS_IMETHODIMP
nsMsgDBFolder::GetDBTransferInfo(nsIDBFolderInfo **aTransferInfo)
{
nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
nsCOMPtr <nsIMsgDatabase> db;
GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
if (dbFolderInfo)
dbFolderInfo->GetTransferInfo(aTransferInfo);
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::SetDBTransferInfo(nsIDBFolderInfo *aTransferInfo)
{
NS_ENSURE_ARG(aTransferInfo);
nsCOMPtr <nsIDBFolderInfo> dbFolderInfo;
nsCOMPtr <nsIMsgDatabase> db;
GetMsgDatabase(nsnull, getter_AddRefs(db));
if (db)
{
db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
if(dbFolderInfo)
dbFolderInfo->InitFromTransferInfo(aTransferInfo);
}
return NS_OK;
}
NS_IMETHODIMP
nsMsgDBFolder::GetStringProperty(const char *propertyName, char **propertyValue)
{
NS_ENSURE_ARG_POINTER(propertyName);
NS_ENSURE_ARG_POINTER(propertyValue);
nsCOMPtr <nsIFileSpec> dbPath;
nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath));
if (dbPath)
{
nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
rv = GetFolderCacheElemFromFileSpec(dbPath, getter_AddRefs(cacheElement));
if (cacheElement) //try to get from cache
rv = cacheElement->GetStringProperty(propertyName, propertyValue);
if (NS_FAILED(rv)) //if failed, then try to get from db
{
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
PRBool exists;
rv = dbPath->Exists(&exists);
if (NS_FAILED(rv) || !exists)
return NS_MSG_ERROR_FOLDER_MISSING;
rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if (NS_SUCCEEDED(rv))
rv = folderInfo->GetCharPtrProperty(propertyName, propertyValue);
}
}
return rv;
}
NS_IMETHODIMP
nsMsgDBFolder::SetStringProperty(const char *propertyName, const char *propertyValue)
{
NS_ENSURE_ARG_POINTER(propertyName);
NS_ENSURE_ARG_POINTER(propertyValue);
nsCOMPtr <nsIFileSpec> dbPath;
GetFolderCacheKey(getter_AddRefs(dbPath));
if (dbPath)
{
nsCOMPtr <nsIMsgFolderCacheElement> cacheElement;
GetFolderCacheElemFromFileSpec(dbPath, getter_AddRefs(cacheElement));
if (cacheElement) //try to set in the cache
cacheElement->SetStringProperty(propertyName, propertyValue);
}
nsCOMPtr<nsIDBFolderInfo> folderInfo;
nsCOMPtr<nsIMsgDatabase> db;
nsresult rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
if(NS_SUCCEEDED(rv))
{
folderInfo->SetCharPtrProperty(propertyName, propertyValue);
db->Commit(nsMsgDBCommitType::kLargeCommit); //commiting the db also commits the cache
}
return NS_OK;
}
// sub-classes need to override
nsresult
nsMsgDBFolder::SpamFilterClassifyMessage(const char *aURI, nsIJunkMailPlugin *aJunkMailPlugin)
{
return aJunkMailPlugin->ClassifyMessage(aURI, nsnull);
}
/**
* Call the filter plugins (XXX currently just one)
*/
NS_IMETHODIMP
nsMsgDBFolder::CallFilterPlugins()
{
nsCOMPtr<nsIMsgIncomingServer> server;
nsCOMPtr<nsISpamSettings> spamSettings;
nsCOMPtr<nsIAbMDBDirectory> whiteListDirectory;
nsCOMPtr<nsIMsgHeaderParser> headerParser;
PRBool useWhiteList = PR_FALSE;
PRInt32 spamLevel = 0;
nsXPIDLCString whiteListAbURI;
nsresult rv = GetServer(getter_AddRefs(server));
NS_ENSURE_SUCCESS(rv, rv);
rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
nsCOMPtr <nsIMsgFilterPlugin> filterPlugin;
server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
if (!filterPlugin) // it's not an error not to have the filter plugin.
return NS_OK;
NS_ENSURE_SUCCESS(rv, rv);
spamSettings->GetLevel(&spamLevel);
if (spamLevel == 0)
return NS_OK;
nsCOMPtr<nsIMsgMailSession> mailSession =
do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (!mDatabase)
{
rv = GetDatabase(nsnull); // XXX is nsnull a reasonable arg here?
NS_ENSURE_SUCCESS(rv, rv);
}
// get the list of new messages
//
nsMsgKeyArray *newMessageKeys;
rv = mDatabase->GetNewList(&newMessageKeys);
NS_ENSURE_SUCCESS(rv, rv);
// if there weren't any, just return
//
if (!newMessageKeys)
return NS_OK;
spamSettings->GetUseWhiteList(&useWhiteList);
if (useWhiteList)
{
spamSettings->GetWhiteListAbURI(getter_Copies(whiteListAbURI));
NS_ENSURE_SUCCESS(rv, rv);
if (!whiteListAbURI.IsEmpty())
{
nsCOMPtr <nsIRDFService> rdfService = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr <nsIRDFResource> resource;
rv = rdfService->GetResource(whiteListAbURI, getter_AddRefs(resource));
NS_ENSURE_SUCCESS(rv, rv);
whiteListDirectory = do_QueryInterface(resource, &rv);
NS_ENSURE_SUCCESS(rv, rv);
headerParser = do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
// if we can't get the db, we probably want to continue firing spam filters.
}
// tell the plugin this is the beginning of a batch. this is an
// optimization, so if it fails, try to continue anyway.
//
rv = filterPlugin->StartBatch();
if (NS_FAILED(rv)) {
NS_WARNING("nsMsgDBFilter::CallFilterPlugins(): "
"filterPlugin->StartBatch failed");
}
// for each message...
//
nsXPIDLCString uri;
PRUint32 numNewMessages = newMessageKeys->GetSize();
for ( PRUint32 i=0 ; i < numNewMessages ; ++i )
{
nsXPIDLCString junkScore;
nsCOMPtr <nsIMsgDBHdr> msgHdr;
nsMsgKey msgKey = newMessageKeys->GetAt(i);
rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
if (!NS_SUCCEEDED(rv))
continue;
msgHdr->GetStringProperty("junkscore", getter_Copies(junkScore));
if (!junkScore.IsEmpty()) // ignore already scored messages.
continue;
// check whitelist first:
if (whiteListDirectory)
{
if (NS_SUCCEEDED(rv))
{
PRBool cardExists = PR_FALSE;
nsXPIDLCString author;
nsXPIDLCString authorEmailAddress;
msgHdr->GetAuthor(getter_Copies(author));
rv = headerParser->ExtractHeaderAddressMailboxes(nsnull, author.get(), getter_Copies(authorEmailAddress));
// don't want to abort the rest of the scoring.
if (NS_SUCCEEDED(rv))
rv = whiteListDirectory->HasCardForEmailAddress(authorEmailAddress, &cardExists);
if (NS_SUCCEEDED(rv) && cardExists)
{
// mark this msg as non-junk, because we whitelisted it.
mDatabase->SetStringProperty(msgKey, "junkscore", "0");
mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "plugin");
continue; // skip this msg since it's in the white list
}
}
}
// generate a URI for the message
//
rv = GenerateMessageURI(newMessageKeys->GetAt(i), getter_Copies(uri));
if (NS_FAILED(rv))
{
NS_WARNING("nsMsgDBFolder::CallFilterPlugins(): could not"
" generate URI for message");
continue; // continue through the array
}
// filterMsg
//
nsCOMPtr <nsIJunkMailPlugin> junkMailPlugin = do_QueryInterface(filterPlugin);
rv = SpamFilterClassifyMessage(uri, junkMailPlugin);
if (NS_FAILED(rv))
{
NS_WARNING("nsMsgDBFolder::CallFilterPlugins(): filter plugin"
" call failed");
continue; // continue through the array
}
}
// this batch is done
//
rv = filterPlugin->EndBatch();
if (NS_FAILED(rv)) {
NS_WARNING("nsMsgDBFilter::CallFilterPlugins(): "
"filterPlugin->EndBatch() failed");
}
NS_DELETEXPCOM(newMessageKeys);
return rv;
}