/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla 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/MPL/ * * 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) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Seth Spitzer * * Alternatively, the contents of this file may be used under the terms of * either of 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 MPL, 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 MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "msgCore.h" #include "nsMsgSearchDBView.h" #include "nsIMsgHdr.h" #include "nsIMsgThread.h" #include "nsQuickSort.h" #include "nsIDBFolderInfo.h" #include "nsXPIDLString.h" #include "nsMsgBaseCID.h" #include "nsIMsgCopyService.h" #include "nsICopyMsgStreamListener.h" #include "nsMsgUtils.h" #include "nsITreeColumns.h" #include "nsIMsgMessageService.h" nsMsgSearchDBView::nsMsgSearchDBView() { // don't try to display messages for the search pane. mSuppressMsgDisplay = PR_TRUE; } nsMsgSearchDBView::~nsMsgSearchDBView() { } NS_IMPL_ISUPPORTS_INHERITED3(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView, nsIMsgCopyServiceListener, nsIMsgSearchNotify) NS_IMETHODIMP nsMsgSearchDBView::Open(nsIMsgFolder *folder, nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder, nsMsgViewFlagsTypeValue viewFlags, PRInt32 *pCount) { nsresult rv; m_folders = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount); NS_ENSURE_SUCCESS(rv, rv); if (pCount) *pCount = 0; m_folder = nsnull; return rv; } NS_IMETHODIMP nsMsgSearchDBView::CopyDBView(nsMsgDBView *aNewMsgDBView, nsIMessenger *aMessengerInstance, nsIMsgWindow *aMsgWindow, nsIMsgDBViewCommandUpdater *aCmdUpdater) { nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater); nsMsgSearchDBView* newMsgDBView = (nsMsgSearchDBView *) aNewMsgDBView; // now copy all of our private member data newMsgDBView->mDestFolder = mDestFolder; newMsgDBView->mCommand = mCommand; newMsgDBView->mTotalIndices = mTotalIndices; newMsgDBView->mCurIndex = mCurIndex; if (m_folders) m_folders->Clone(getter_AddRefs(newMsgDBView->m_folders)); if (m_hdrsForEachFolder) m_hdrsForEachFolder->Clone(getter_AddRefs(newMsgDBView->m_hdrsForEachFolder)); if (m_copyListenerList) m_copyListenerList->Clone(getter_AddRefs(newMsgDBView->m_copyListenerList)); if (m_uniqueFoldersSelected) m_uniqueFoldersSelected->Clone(getter_AddRefs(newMsgDBView->m_uniqueFoldersSelected)); PRInt32 count = m_dbToUseList.Count(); for(PRInt32 i = 0; i < count; i++) { newMsgDBView->m_dbToUseList.AppendObject(m_dbToUseList[i]); // register the new view with the database so it gets notifications m_dbToUseList[i]->AddListener(newMsgDBView); } // nsUInt32Array* mTestIndices; return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::Close() { PRInt32 count = m_dbToUseList.Count(); for(PRInt32 i = 0; i < count; i++) m_dbToUseList[i]->RemoveListener(this); m_dbToUseList.Clear(); return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::GetCellText(PRInt32 aRow, nsITreeColumn* aCol, nsAString& aValue) { const PRUnichar* colID; aCol->GetIdConst(&colID); if (colID[0] == 'l' && colID[1] == 'o') // location, need to check for "lo" not just "l" to avoid "label" column { // XXX fix me by converting Fetch* to take an nsAString& parameter nsXPIDLString valueText; nsresult rv = FetchLocation(aRow, getter_Copies(valueText)); aValue.Assign(valueText); return rv; } else return nsMsgDBView::GetCellText(aRow, aCol, aValue); } nsresult nsMsgSearchDBView::FetchLocation(PRInt32 aRow, PRUnichar ** aLocationString) { nsCOMPtr folder; nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder)); NS_ENSURE_SUCCESS(rv,rv); rv = folder->GetPrettiestName(aLocationString); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, PRBool /*ensureListed*/) { return NS_OK; } nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index, nsIMsgDBHdr **msgHdr) { nsresult rv = NS_MSG_INVALID_DBVIEW_INDEX; nsCOMPtr folder = do_QueryElementAt(m_folders, index); if (folder) { nsCOMPtr db; rv = folder->GetMsgDatabase(mMsgWindow, getter_AddRefs(db)); NS_ENSURE_SUCCESS(rv, rv); if (db) rv = db->GetMsgHdrForKey(m_keys.GetAt(index), msgHdr); } return rv; } NS_IMETHODIMP nsMsgSearchDBView::GetFolderForViewIndex(nsMsgViewIndex index, nsIMsgFolder **aFolder) { return m_folders->QueryElementAt(index, NS_GET_IID(nsIMsgFolder), (void **) aFolder); } nsresult nsMsgSearchDBView::GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase **db) { nsCOMPtr aFolder; GetFolderForViewIndex(index, getter_AddRefs(aFolder)); if (aFolder) return aFolder->GetMsgDatabase(nsnull, db); else return NS_MSG_INVALID_DBVIEW_INDEX; } nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr *msgHdr, nsISupports *folder) { m_folders->AppendElement(folder); nsMsgKey msgKey; PRUint32 msgFlags; msgHdr->GetMessageKey(&msgKey); // nsMsgKey_None means it's not a valid hdr. if (msgKey != nsMsgKey_None) { msgHdr->GetFlags(&msgFlags); m_keys.Add(msgKey); m_levels.Add(0); m_flags.Add(msgFlags); // this needs to be called after we add the key, since RowCountChanged() will call our GetRowCount() if (mTree) mTree->RowCountChanged(GetSize() - 1, 1); } return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder *folder) { NS_ENSURE_ARG(aMsgHdr); NS_ENSURE_ARG(folder); nsCOMPtr supports = do_QueryInterface(folder); if (m_folders->IndexOf(supports) < 0 ) //do this just for new folder { nsCOMPtr dbToUse; nsCOMPtr folderInfo; folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(dbToUse)); if (dbToUse) { dbToUse->AddListener(this); m_dbToUseList.AppendObject(dbToUse); } } return AddHdrFromFolder(aMsgHdr, supports); } NS_IMETHODIMP nsMsgSearchDBView::OnSearchDone(nsresult status) { //we want to set imap delete model once the search is over because setting next //message after deletion will happen before deleting the message and search scope //can change with every search. mDeleteModel = nsMsgImapDeleteModels::MoveToTrash; //set to default in case it is non-imap folder nsCOMPtr curFolder = do_QueryElementAt(m_folders, 0); if (curFolder) GetImapDeleteModel(curFolder); return NS_OK; } // for now also acts as a way of resetting the search datasource NS_IMETHODIMP nsMsgSearchDBView::OnNewSearch() { PRInt32 oldSize = GetSize(); PRInt32 count = m_dbToUseList.Count(); for(PRInt32 j = 0; j < count; j++) m_dbToUseList[j]->RemoveListener(this); m_dbToUseList.Clear(); m_folders->Clear(); m_keys.RemoveAll(); m_levels.RemoveAll(); m_flags.RemoveAll(); // needs to happen after we remove the keys, since RowCountChanged() will call our GetRowCount() if (mTree) mTree->RowCountChanged(0, -oldSize); // mSearchResults->Clear(); return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer *instigator) { nsIMsgDatabase *db = NS_STATIC_CAST(nsIMsgDatabase *, instigator); if (db) { db->RemoveListener(this); m_dbToUseList.RemoveObject(db); } return NS_OK; } nsresult nsMsgSearchDBView::GetFolders(nsISupportsArray **aFolders) { NS_ENSURE_ARG_POINTER(aFolders); NS_IF_ADDREF(*aFolders = m_folders); return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::GetCommandStatus(nsMsgViewCommandTypeValue command, PRBool *selectable_p, nsMsgViewCommandCheckStateValue *selected_p) { if (command != nsMsgViewCommandType::runJunkControls) return nsMsgDBView::GetCommandStatus(command, selectable_p, selected_p); *selectable_p = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command, nsIMsgFolder *destFolder) { mCommand = command; mDestFolder = destFolder; return nsMsgDBView::DoCommandWithFolder(command, destFolder); } NS_IMETHODIMP nsMsgSearchDBView::DoCommand(nsMsgViewCommandTypeValue command) { mCommand = command; if (command == nsMsgViewCommandType::deleteMsg || command == nsMsgViewCommandType::deleteNoTrash || command == nsMsgViewCommandType::selectAll) return nsMsgDBView::DoCommand(command); nsresult rv = NS_OK; nsUInt32Array selection; GetSelectedIndices(&selection); nsMsgViewIndex *indices = selection.GetData(); PRInt32 numIndices = selection.GetSize(); // we need to break apart the selection by folders, and then call // ApplyCommandToIndices with the command and the indices in the // selection that are from that folder. nsUInt32Array *indexArrays; PRInt32 numArrays; rv = PartitionSelectionByFolder(indices, numIndices, &indexArrays, &numArrays); NS_ENSURE_SUCCESS(rv, rv); for (PRInt32 folderIndex = 0; folderIndex < numArrays; folderIndex++) { rv = ApplyCommandToIndices(command, indexArrays[folderIndex].GetData(), indexArrays[folderIndex].GetSize()); NS_ENSURE_SUCCESS(rv, rv); } return rv; } // This method just removes the specified line from the view. It does // NOT delete it from the database. nsresult nsMsgSearchDBView::RemoveByIndex(nsMsgViewIndex index) { if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX; m_folders->RemoveElementAt(index); return nsMsgDBView::RemoveByIndex(index); } nsresult nsMsgSearchDBView::DeleteMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool deleteStorage) { nsresult rv; GetFoldersAndHdrsForSelection(indices, numIndices); if (mDeleteModel != nsMsgImapDeleteModels::MoveToTrash) deleteStorage = PR_TRUE; if (!deleteStorage) rv = ProcessRequestsInOneFolder(window); else rv = ProcessRequestsInAllFolders(window); return rv; } nsresult nsMsgSearchDBView::CopyMessages(nsIMsgWindow *window, nsMsgViewIndex *indices, PRInt32 numIndices, PRBool isMove, nsIMsgFolder *destFolder) { nsresult rv; GetFoldersAndHdrsForSelection(indices, numIndices); rv = ProcessRequestsInOneFolder(window); return rv; } nsresult nsMsgSearchDBView::PartitionSelectionByFolder(nsMsgViewIndex *indices, PRInt32 numIndices, nsUInt32Array **indexArrays, PRInt32 *numArrays) { nsresult rv = NS_OK; nsCOMPtr uniqueFoldersSelected = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv); mCurIndex = 0; //Build unique folder list based on headers selected by the user for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++) { nsCOMPtr curSupports = getter_AddRefs(m_folders->ElementAt(indices[i])); if ( uniqueFoldersSelected->IndexOf(curSupports) < 0) uniqueFoldersSelected->AppendElement(curSupports); } PRUint32 numFolders =0; rv = uniqueFoldersSelected->Count(&numFolders); //group the headers selected by each folder *indexArrays = new nsUInt32Array[numFolders]; *numArrays = numFolders; NS_ENSURE_TRUE(*indexArrays, NS_ERROR_OUT_OF_MEMORY); for (PRUint32 folderIndex=0; folderIndex < numFolders; folderIndex++) { nsCOMPtr curFolder = do_QueryElementAt(uniqueFoldersSelected, folderIndex, &rv); for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++) { nsCOMPtr msgFolder = do_QueryElementAt(m_folders, indices[i], &rv); if (NS_SUCCEEDED(rv) && msgFolder && msgFolder == curFolder) (*indexArrays)[folderIndex].Add(indices[i]); } } return rv; } nsresult nsMsgSearchDBView::GetFoldersAndHdrsForSelection(nsMsgViewIndex *indices, PRInt32 numIndices) { nsresult rv = NS_OK; mCurIndex = 0; //initialize and clear from the last usage if (!m_uniqueFoldersSelected) { m_uniqueFoldersSelected = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); } else m_uniqueFoldersSelected->Clear(); if (!m_hdrsForEachFolder) { m_hdrsForEachFolder = do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); } else m_hdrsForEachFolder->Clear(); //Build unique folder list based on headers selected by the user for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++) { nsCOMPtr curSupports = getter_AddRefs(m_folders->ElementAt(indices[i])); if ( m_uniqueFoldersSelected->IndexOf(curSupports) < 0) m_uniqueFoldersSelected->AppendElement(curSupports); } PRUint32 numFolders =0; rv = m_uniqueFoldersSelected->Count(&numFolders); //group the headers selected by each folder NS_ENSURE_SUCCESS(rv,rv); for (PRUint32 folderIndex=0; folderIndex < numFolders; folderIndex++) { nsCOMPtr curFolder = do_QueryElementAt(m_uniqueFoldersSelected, folderIndex, &rv); nsCOMPtr msgHdrsForOneFolder; NS_NewISupportsArray(getter_AddRefs(msgHdrsForOneFolder)); for (nsMsgViewIndex i = 0; i < (nsMsgViewIndex) numIndices; i++) { nsCOMPtr msgFolder = do_QueryElementAt(m_folders, indices[i], &rv); if (NS_SUCCEEDED(rv) && msgFolder && msgFolder == curFolder) { nsCOMPtr msgHdr; rv = GetMsgHdrForViewIndex(indices[i],getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr hdrSupports = do_QueryInterface(msgHdr); msgHdrsForOneFolder->AppendElement(hdrSupports); } } nsCOMPtr supports = do_QueryInterface(msgHdrsForOneFolder, &rv); if (NS_SUCCEEDED(rv) && supports) m_hdrsForEachFolder->AppendElement(supports); } return rv; } // nsIMsgCopyServiceListener methods NS_IMETHODIMP nsMsgSearchDBView::OnStartCopy() { return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnProgress(PRUint32 aProgress, PRUint32 aProgressMax) { return NS_OK; } // believe it or not, these next two are msgcopyservice listener methods! NS_IMETHODIMP nsMsgSearchDBView::SetMessageKey(PRUint32 aMessageKey) { return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::GetMessageId(nsCString* aMessageId) { return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::OnStopCopy(nsresult aStatus) { nsresult rv = NS_OK; if (NS_SUCCEEDED(aStatus)) { mCurIndex++; PRUint32 numFolders =0; rv = m_uniqueFoldersSelected->Count(&numFolders); if ( mCurIndex < numFolders) ProcessRequestsInOneFolder(mMsgWindow); } return rv; } // end nsIMsgCopyServiceListener methods nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow *window) { nsresult rv = NS_OK; nsCOMPtr curFolder = do_QueryElementAt(m_uniqueFoldersSelected, mCurIndex); NS_ASSERTION(curFolder, "curFolder is null"); nsCOMPtr messageArray = do_QueryElementAt(m_hdrsForEachFolder, mCurIndex); NS_ASSERTION(messageArray, "messageArray is null"); // called for delete with trash, copy and move if (mCommand == nsMsgViewCommandType::deleteMsg) curFolder->DeleteMessages(messageArray, window, PR_FALSE /* delete storage */, PR_FALSE /* is move*/, this, PR_TRUE /*allowUndo*/); else { NS_ASSERTION(!(curFolder == mDestFolder), "The source folder and the destination folder are the same"); if (NS_SUCCEEDED(rv) && curFolder != mDestFolder) { nsCOMPtr copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { if (mCommand == nsMsgViewCommandType::moveMessages) copyService->CopyMessages(curFolder, messageArray, mDestFolder, PR_TRUE /* isMove */, this, window, PR_TRUE /*allowUndo*/); else if (mCommand == nsMsgViewCommandType::copyMessages) copyService->CopyMessages(curFolder, messageArray, mDestFolder, PR_FALSE /* isMove */, this, window, PR_TRUE /*allowUndo*/); } } } return rv; } nsresult nsMsgSearchDBView::ProcessRequestsInAllFolders(nsIMsgWindow *window) { PRUint32 numFolders =0; nsresult rv = m_uniqueFoldersSelected->Count(&numFolders); NS_ENSURE_SUCCESS(rv,rv); for (PRUint32 folderIndex=0; folderIndex < numFolders; folderIndex++) { nsCOMPtr curFolder = do_QueryElementAt(m_uniqueFoldersSelected, folderIndex); NS_ASSERTION (curFolder, "curFolder is null"); nsCOMPtr messageArray = do_QueryElementAt(m_hdrsForEachFolder, folderIndex); NS_ASSERTION(messageArray, "messageArray is null"); curFolder->DeleteMessages(messageArray, window, PR_TRUE /* delete storage */, PR_FALSE /* is move*/, nsnull/*copyServListener*/, PR_FALSE /*allowUndo*/ ); } return NS_OK; } NS_IMETHODIMP nsMsgSearchDBView::Sort(nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) { nsresult rv; PRInt32 rowCountBeforeSort = GetSize(); if (!rowCountBeforeSort) return NS_OK; nsMsgKey preservedKey; nsMsgKeyArray preservedSelection; SaveAndClearSelection(&preservedKey, &preservedSelection); rv = nsMsgDBView::Sort(sortType,sortOrder); // the sort may have changed the number of rows // before we restore the selection, tell the tree // do this before we call restore selection // this is safe when there is no selection. rv = AdjustRowCount(rowCountBeforeSort, GetSize()); RestoreSelection(preservedKey, &preservedSelection); if (mTree) mTree->Invalidate(); NS_ENSURE_SUCCESS(rv,rv); return rv; } // if nothing selected, return an NS_ERROR NS_IMETHODIMP nsMsgSearchDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr **hdr) { NS_ENSURE_ARG_POINTER(hdr); PRInt32 index; if (!mTreeSelection) return NS_ERROR_NULL_POINTER; nsresult rv = mTreeSelection->GetCurrentIndex(&index); NS_ENSURE_SUCCESS(rv,rv); rv = GetMsgHdrForViewIndex(index, hdr); NS_ENSURE_SUCCESS(rv,rv); return NS_OK; } nsresult nsMsgSearchDBView::GetFolderFromMsgURI(const char *aMsgURI, nsIMsgFolder **aFolder) { nsCOMPtr msgMessageService; nsresult rv = GetMessageServiceFromURI(aMsgURI, getter_AddRefs(msgMessageService)); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr msgHdr; rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv,rv); return msgHdr->GetFolder(aFolder); }