/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** 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) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Seth Spitzer * Håkan Waara * Pierre Phaneuf * * 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 "nsIPrefBranch.h" #include "nsIPrefService.h" #include "prlog.h" #include "msgCore.h" // precompiled header... #include "nntpCore.h" #include "nsNewsFolder.h" #include "nsMsgFolderFlags.h" #include "prprf.h" #include "prsystem.h" #include "nsISupportsArray.h" #include "nsIServiceManager.h" #include "nsIEnumerator.h" #include "nsINntpService.h" #include "nsIFolderListener.h" #include "nsCOMPtr.h" #include "nsIRDFService.h" #include "nsIRDFDataSource.h" #include "nsRDFCID.h" #include "nsFileStream.h" #include "nsMsgDBCID.h" #include "nsMsgNewsCID.h" #include "nsMsgUtils.h" #include "nsNewsUtils.h" #include "nsCOMPtr.h" #include "nsIMsgMailSession.h" #include "nsIMsgIncomingServer.h" #include "nsINntpIncomingServer.h" #include "nsINewsDatabase.h" #include "nsMsgBaseCID.h" #include "nsIMsgWindow.h" #include "nsIDocShell.h" #include "nsIPrompt.h" #include "nsIWindowWatcher.h" #include "nsXPIDLString.h" #include "nsIObserverService.h" #include "nsNetUtil.h" #include "nsIAuthPrompt.h" #include "nsIURL.h" #include "nsNetCID.h" #include "nsINntpUrl.h" #include "nsNewsSummarySpec.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsReadableUtils.h" #include "nsNewsDownloader.h" #include "nsIStringBundle.h" #include "nsEscape.h" #include "nsMsgI18N.h" #include "nsNativeCharsetUtils.h" #include "nsIMsgAccountManager.h" // we need this because of an egcs 1.0 (and possibly gcc) compiler bug // that doesn't allow you to call ::nsISupports::GetIID() inside of a class // that multiply inherits from nsISupports static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); static NS_DEFINE_CID(kCNewsDB, NS_NEWSDB_CID); // ###tw This really ought to be the most // efficient file reading size for the current // operating system. #define NEWSRC_FILE_BUFFER_SIZE 1024 #define NEWS_SCHEME "news:" #define SNEWS_SCHEME "snews:" //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// nsMsgNewsFolder::nsMsgNewsFolder(void) : nsMsgLineBuffer(nsnull, PR_FALSE), mExpungedBytes(0), mGettingNews(PR_FALSE), mInitialized(PR_FALSE), mOptionLines(""), mUnsubscribedNewsgroupLines(""), m_downloadMessageForOfflineUse(PR_FALSE), m_downloadingMultipleMessages(PR_FALSE), mReadSet(nsnull), mGroupUsername(nsnull), mGroupPassword(nsnull) { MOZ_COUNT_CTOR(nsNewsFolder); // double count these for now. /* we're parsing the newsrc file, and the line breaks are platform specific. * if MSG_LINEBREAK != CRLF, then we aren't looking for CRLF */ if (PL_strcmp(MSG_LINEBREAK, CRLF)) SetLookingForCRLF(PR_FALSE); } nsMsgNewsFolder::~nsMsgNewsFolder(void) { MOZ_COUNT_DTOR(nsNewsFolder); delete mReadSet; PR_Free(mGroupUsername); PR_Free(mGroupPassword); } NS_IMPL_ADDREF_INHERITED(nsMsgNewsFolder, nsMsgDBFolder) NS_IMPL_RELEASE_INHERITED(nsMsgNewsFolder, nsMsgDBFolder) NS_IMETHODIMP nsMsgNewsFolder::QueryInterface(REFNSIID aIID, void** aInstancePtr) { if (!aInstancePtr) return NS_ERROR_NULL_POINTER; *aInstancePtr = nsnull; if (aIID.Equals(NS_GET_IID(nsIMsgNewsFolder))) { *aInstancePtr = NS_STATIC_CAST(nsIMsgNewsFolder*, this); } if(*aInstancePtr) { AddRef(); return NS_OK; } return nsMsgDBFolder::QueryInterface(aIID, aInstancePtr); } //////////////////////////////////////////////////////////////////////////////// nsresult nsMsgNewsFolder::CreateSubFolders(nsFileSpec &path) { nsresult rv = NS_OK; PRBool isNewsServer = PR_FALSE; rv = GetIsServer(&isNewsServer); if (NS_FAILED(rv)) return rv; if (isNewsServer) { nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; rv = nntpServer->GetNewsrcFilePath(getter_AddRefs(mNewsrcFilePath)); if (NS_FAILED(rv)) return rv; rv = LoadNewsrcFileAndCreateNewsgroups(); if (NS_FAILED(rv)) return rv; } else { // is not a host, so it has no newsgroups. (what about categories??) rv = NS_OK; } return rv; } NS_IMETHODIMP nsMsgNewsFolder::AddNewsgroup(const nsACString &name, const char *setStr, nsIMsgFolder **child) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(child); NS_ENSURE_ARG_POINTER(setStr); nsCOMPtr rdf = do_GetService(kRDFServiceCID, &rv); if (NS_FAILED(rv)) return rv; if (!rdf) return NS_ERROR_FAILURE; nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; nsCAutoString uri(mURI); uri.Append('/'); // URI should use UTF-8 // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax) // we are handling newsgroup names in UTF-8 NS_ConvertUTF8toUTF16 nameUtf16(name); nsCAutoString escapedName; rv = NS_MsgEscapeEncodeURLPath(nameUtf16, escapedName); if (NS_FAILED(rv)) return rv; rv = nntpServer->AddNewsgroup(nameUtf16); if (NS_FAILED(rv)) return rv; uri.Append(escapedName); nsCOMPtr res; rv = rdf->GetResource(uri, getter_AddRefs(res)); if (NS_FAILED(rv)) return rv; nsCOMPtr folder(do_QueryInterface(res, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr newsFolder(do_QueryInterface(res, &rv)); if (NS_FAILED(rv)) return rv; // cache this for when we open the db rv = newsFolder->SetReadSetFromStr(setStr); rv = folder->SetParent(this); NS_ENSURE_SUCCESS(rv,rv); // this what shows up in the UI rv = folder->SetName(nameUtf16.get()); NS_ENSURE_SUCCESS(rv,rv); rv = folder->SetFlag(MSG_FOLDER_FLAG_NEWSGROUP); if (NS_FAILED(rv)) return rv; PRUint32 numExistingGroups; rv = Count(&numExistingGroups); NS_ENSURE_SUCCESS(rv,rv); // add 9000 to prevent this problem: 1,10,11,2,3,4,5 // We use 9000 instead of 1000 so newsgroups will sort to bottom of flat folder views rv = folder->SetSortOrder(numExistingGroups + 9000); NS_ENSURE_SUCCESS(rv,rv); //convert to an nsISupports before appending nsCOMPtr folderSupports(do_QueryInterface(folder)); if(folderSupports) mSubFolders->AppendElement(folderSupports); *child = folder; folder->SetParent(this); NS_ADDREF(*child); return rv; } nsresult nsMsgNewsFolder::ParseFolder(nsFileSpec& path) { NS_ASSERTION(0,"ParseFolder not implemented"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgNewsFolder::Enumerate(nsIEnumerator **result) { #if 0 nsresult rv = NS_OK; // for now, news folders contain both messages and folders // server is a folder, and it contains folders // newsgroup is a folder, and it contains messages // // eventually the top level server will not be a folder // and news folders will only contain messages nsIEnumerator* folders; nsIEnumerator* messages; rv = GetSubFolders(&folders); if (NS_FAILED(rv)) return rv; rv = GetMessages(nsnull, &messages); if (NS_FAILED(rv)) return rv; return NS_NewConjoiningEnumerator(folders, messages, (nsIBidirectionalEnumerator**)result); #endif NS_ASSERTION(PR_FALSE, "obsolete, right?"); return NS_ERROR_FAILURE; } nsresult nsMsgNewsFolder::AddDirectorySeparator(nsFileSpec &path) { nsresult rv = NS_OK; if (mURI.Equals(kNewsRootURI)) { // don't concat the full separator with .sbd } else { // see if there's a dir with the same name ending with .sbd // unfortunately we can't just say: // path += sep; // here because of the way nsFileSpec concatenates nsAutoString str; str.AssignWithConversion(nsFilePath(path)); #if 0 nsAutoString sep; rv = nsGetNewsFolderSeparator(sep); if (NS_FAILED(rv)) return rv; str += sep; #endif path = nsFilePath(str); } return rv; } NS_IMETHODIMP nsMsgNewsFolder::GetSubFolders(nsIEnumerator* *result) { nsresult rv; if (!mInitialized) { // do this first, so we make sure to do it, even on failure. // see bug #70494 mInitialized = PR_TRUE; nsCOMPtr pathSpec; rv = GetPath(getter_AddRefs(pathSpec)); if (NS_FAILED(rv)) return rv; nsFileSpec path; rv = pathSpec->GetFileSpec(&path); if (NS_FAILED(rv)) return rv; nsFileSpec tempPath(path.GetNativePathCString(), PR_TRUE); rv = CreateSubFolders(path); if (NS_FAILED(rv)) return rv; // force ourselves to get initialized from cache // Don't care if it fails. this will fail the first time after // migration, but we continue on. see #66018 (void)UpdateSummaryTotals(PR_FALSE); } rv = mSubFolders->Enumerate(result); return rv; } //Makes sure the database is open and exists. If the database is valid then //returns NS_OK. Otherwise returns a failure error value. nsresult nsMsgNewsFolder::GetDatabase(nsIMsgWindow *aMsgWindow) { nsresult rv; if (!mDatabase) { nsCOMPtr pathSpec; rv = GetPath(getter_AddRefs(pathSpec)); if (NS_FAILED(rv)) return rv; nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); nsresult folderOpen = msgDBService->OpenFolderDB(this, PR_TRUE, PR_FALSE, getter_AddRefs(mDatabase)); if (NS_FAILED(folderOpen) && folderOpen != NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) folderOpen = msgDBService->OpenFolderDB(this, PR_TRUE, PR_TRUE, getter_AddRefs(mDatabase)); if (NS_FAILED(folderOpen) && folderOpen != NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) return folderOpen; if(mAddListener) rv = mDatabase->AddListener(this); nsCOMPtr db = do_QueryInterface(mDatabase, &rv); if (NS_FAILED(rv)) return rv; rv = db->SetReadSet(mReadSet); if (NS_FAILED(rv)) return rv; rv = UpdateSummaryTotals(PR_TRUE); if (NS_FAILED(rv)) return rv; } return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::UpdateFolder(nsIMsgWindow *aWindow) { // Get news.get_messages_on_select pref nsresult rv; PRBool getMessagesOnSelect = PR_TRUE; nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); if NS_SUCCEEDED(rv) prefBranch->GetBoolPref("news.get_messages_on_select", &getMessagesOnSelect); // Only if news.get_messages_on_select is true do we get new messages automatically if (getMessagesOnSelect) { rv = GetDatabase(aWindow); // want this cached... if (NS_SUCCEEDED(rv)) { if (mDatabase) { nsCOMPtr retentionSettings; nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings)); if (NS_SUCCEEDED(rv)) rv = mDatabase->ApplyRetentionSettings(retentionSettings, PR_FALSE); } rv = AutoCompact(aWindow); NS_ENSURE_SUCCESS(rv,rv); // GetNewMessages has to be the last rv set before we get to the next check, so // that we'll have rv set to NS_MSG_ERROR_OFFLINE when offline and send // a folder loaded notification to the front end. rv = GetNewMessages(aWindow, nsnull); } if (rv != NS_MSG_ERROR_OFFLINE) return rv; } // We're not getting messages because either get_messages_on_select is // false or we're offline. Send an immediate folder loaded notification. NotifyFolderEvent(mFolderLoadedAtom); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetCanSubscribe(PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = PR_FALSE; PRBool isNewsServer = PR_FALSE; nsresult rv = GetIsServer(&isNewsServer); if (NS_FAILED(rv)) return rv; // you can only subscribe to news servers, not news groups *aResult = isNewsServer; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetCanFileMessages(PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); // you can't file messages into a news server or news group *aResult = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetCanDeleteMessages(PRBool *aCanDeleteMessages) { NS_ENSURE_ARG_POINTER(aCanDeleteMessages); *aCanDeleteMessages = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetCanCreateSubfolders(PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = PR_FALSE; // you can't create subfolders on a news server or a news group return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetCanRename(PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = PR_FALSE; // you can't rename a news server or a news group return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetCanCompact(PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = PR_FALSE; // you can't compact a news server or a news group return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetMessages(nsIMsgWindow *aMsgWindow, nsISimpleEnumerator* *result) { nsresult rv = GetDatabase(aMsgWindow); *result = nsnull; if(NS_SUCCEEDED(rv)) rv = mDatabase->EnumerateMessages(result); return rv; } NS_IMETHODIMP nsMsgNewsFolder::GetFolderURL(char **url) { NS_ENSURE_ARG_POINTER(url); nsXPIDLCString hostName; nsresult rv = GetHostname(getter_Copies(hostName)); nsXPIDLString groupName; rv = GetName(getter_Copies(groupName)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); PRInt32 port; PRBool isSecure = PR_FALSE; rv = server->GetIsSecure(&isSecure); NS_ENSURE_SUCCESS(rv, rv); rv = server->GetPort(&port); NS_ENSURE_SUCCESS(rv, rv); const char *newsScheme = (isSecure) ? SNEWS_SCHEME : NEWS_SCHEME; nsXPIDLCString escapedName; rv = NS_MsgEscapeEncodeURLPath(groupName, escapedName); NS_ENSURE_SUCCESS(rv, rv); *url = PR_smprintf("%s//%s:%ld/%s", newsScheme, hostName.get(), port, escapedName.get()); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::SetNewsrcHasChanged(PRBool newsrcHasChanged) { nsresult rv; nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; return nntpServer->SetNewsrcHasChanged(newsrcHasChanged); } NS_IMETHODIMP nsMsgNewsFolder::CreateSubfolder(const PRUnichar *newsgroupName, nsIMsgWindow *msgWindow) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(newsgroupName); if (!*newsgroupName) return NS_ERROR_FAILURE; nsCOMPtr child; // Create an empty database for this mail folder, set its name from the user nsCOMPtr newsDBFactory; nsCOMPtr newsDB; //Now let's create the actual new folder rv = AddNewsgroup(NS_ConvertUTF16toUTF8(newsgroupName), "", getter_AddRefs(child)); if (NS_SUCCEEDED(rv)) SetNewsrcHasChanged(PR_TRUE); // subscribe UI does this - but maybe we got here through auto-subscribe if(NS_SUCCEEDED(rv) && child){ nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; nsCAutoString dataCharset; rv = nntpServer->GetCharset(dataCharset); if (NS_FAILED(rv)) return rv; child->SetCharset(dataCharset.get()); NotifyItemAdded(child); } return rv; } NS_IMETHODIMP nsMsgNewsFolder::Delete() { nsresult rv = GetDatabase(nsnull); if(NS_SUCCEEDED(rv)) { mDatabase->ForceClosed(); mDatabase = nsnull; } nsCOMPtr pathSpec; rv = GetPath(getter_AddRefs(pathSpec)); if (NS_FAILED(rv)) return rv; nsFileSpec path; rv = pathSpec->GetFileSpec(&path); if (NS_FAILED(rv)) return rv; // delete local store, if it exists if (path.Exists()) path.Delete(PR_FALSE); // Remove summary file. nsNewsSummarySpec summarySpec(path); summarySpec.Delete(PR_FALSE); nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; nsAutoString name; rv = GetUnicodeName(name); NS_ENSURE_SUCCESS(rv,rv); rv = nntpServer->RemoveNewsgroup(name); NS_ENSURE_SUCCESS(rv,rv); rv = SetNewsrcHasChanged(PR_TRUE); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsMsgNewsFolder::Rename(const PRUnichar *newName, nsIMsgWindow *msgWindow) { NS_ASSERTION(0,"Rename not implemented"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgNewsFolder::GetAbbreviatedName(PRUnichar * *aAbbreviatedName) { nsresult rv = NS_OK; if (!aAbbreviatedName) return NS_ERROR_NULL_POINTER; rv = nsMsgDBFolder::GetPrettyName(aAbbreviatedName); if(NS_FAILED(rv)) return rv; // only do this for newsgroup names, not for newsgroup hosts. PRBool isNewsServer = PR_FALSE; rv = GetIsServer(&isNewsServer); if (NS_FAILED(rv)) return rv; if (!isNewsServer) { nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; PRBool abbreviate = PR_TRUE; rv = nntpServer->GetAbbreviate(&abbreviate); if (NS_FAILED(rv)) return rv; if (abbreviate) { rv = AbbreviatePrettyName(aAbbreviatedName, 1 /* hardcoded for now */); } } return rv; } // original code from Oleg Rekutin // rekusha@asan.com // Public domain, created by Oleg Rekutin // // takes a newsgroup name, number of words from the end to leave unabberviated // the newsgroup name, will get reset to the following format: // x.x.x, where x is the first letter of each word and with the // exception of last 'fullwords' words, which are left intact. // If a word has a dash in it, it is abbreviated as a-b, where // 'a' is the first letter of the part of the word before the // dash and 'b' is the first letter of the part of the word after // the dash nsresult nsMsgNewsFolder::AbbreviatePrettyName(PRUnichar ** prettyName, PRInt32 fullwords) { if (!prettyName) return NS_ERROR_NULL_POINTER; nsAutoString name(*prettyName); PRInt32 totalwords = 0; // total no. of words // get the total no. of words PRInt32 pos = 0; while(1) { pos = name.FindChar('.', pos); if(pos == -1) { totalwords++; break; } else { totalwords++; pos++; } } // get the no. of words to abbreviate PRInt32 abbrevnum = totalwords - fullwords; if (abbrevnum < 1) return NS_OK; // nothing to abbreviate // build the ellipsis nsAutoString out; out += name[0]; PRInt32 length = name.Length(); PRInt32 newword = 0; // == 2 if done with all abbreviated words fullwords = 0; PRUnichar currentChar; for (PRInt32 i = 1; i < length; i++) { // this temporary assignment is needed to fix an intel mac compiler bug. // See Bug #327037 for details. currentChar = name[i]; if (newword < 2) { switch (currentChar) { case '.': fullwords++; // check if done with all abbreviated words... if (fullwords == abbrevnum) newword = 2; else newword = 1; break; case '-': newword = 1; break; default: if (newword) newword = 0; else continue; } } out += currentChar; } if (!prettyName) return NS_ERROR_NULL_POINTER; // we are going to set *prettyName to something else, so free what was there PR_Free(*prettyName); *prettyName = ToNewUnicode(out); return (*prettyName) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } NS_IMETHODIMP nsMsgNewsFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo, nsIMsgDatabase **db) { nsresult openErr; if(!db || !folderInfo) return NS_ERROR_NULL_POINTER; openErr = GetDatabase(nsnull); *db = mDatabase; if (mDatabase) { NS_ADDREF(*db); if (NS_SUCCEEDED(openErr)) openErr = (*db)->GetDBFolderInfo(folderInfo); } return openErr; } /* this used to be MSG_FolderInfoNews::UpdateSummaryFromNNTPInfo() */ NS_IMETHODIMP nsMsgNewsFolder::UpdateSummaryFromNNTPInfo(PRInt32 oldest, PRInt32 youngest, PRInt32 total) { nsresult rv = NS_OK; PRBool newsrcHasChanged = PR_FALSE; PRInt32 oldUnreadMessages = mNumUnreadMessages; PRInt32 oldTotalMessages = mNumTotalMessages; /* First, mark all of the articles now known to be expired as read. */ if (oldest > 1) { nsXPIDLCString oldSet; nsXPIDLCString newSet; mReadSet->Output(getter_Copies(oldSet)); mReadSet->AddRange(1, oldest - 1); rv = mReadSet->Output(getter_Copies(newSet)); if (!oldSet.Equals(newSet)) newsrcHasChanged = PR_TRUE; } /* 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; PRInt32 unread = mReadSet->CountMissingInRange(oldest, youngest); NS_ASSERTION(unread >= 0,"CountMissingInRange reported unread < 0"); if (unread < 0) // servers can send us stuff like "211 0 41 40 nz.netstatus" // we should handle it gracefully. unread = 0; 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; PRInt32 deltaInDB = mNumTotalMessages - mNumUnreadMessages; //PRint32 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; } PRInt32 pendingUnreadDelta = unread - mNumUnreadMessages - mNumPendingUnreadMessages; PRInt32 pendingTotalDelta = total - mNumTotalMessages - mNumPendingTotalMessages; ChangeNumPendingUnread(pendingUnreadDelta); ChangeNumPendingTotalMessages(pendingTotalDelta); #if 0 m_nntpHighwater = youngest; m_nntpTotalArticles = total; #endif return rv; } NS_IMETHODIMP nsMsgNewsFolder::UpdateSummaryTotals(PRBool force) { if (!mNotifyCountChanges) return NS_OK; PRInt32 oldUnreadMessages = mNumUnreadMessages; PRInt32 oldTotalMessages = mNumTotalMessages; //We need to read this info from the database nsresult ret = ReadDBFolderInfo(force); if (NS_SUCCEEDED(ret)) { //Need to notify listeners that total count changed. if(oldTotalMessages != mNumTotalMessages) NotifyIntPropertyChanged(kTotalMessagesAtom, oldTotalMessages, mNumTotalMessages); if(oldUnreadMessages != mNumUnreadMessages) NotifyIntPropertyChanged(kTotalUnreadMessagesAtom, oldUnreadMessages, mNumUnreadMessages); FlushToFolderCache(); } return ret; } NS_IMETHODIMP nsMsgNewsFolder::GetExpungedBytesCount(PRUint32 *count) { if(!count) return NS_ERROR_NULL_POINTER; *count = mExpungedBytes; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetDeletable(PRBool *deletable) { // NS_ASSERTION(0,"GetDeletable() not implemented"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgNewsFolder::GetRequiresCleanup(PRBool *requiresCleanup) { // NS_ASSERTION(0,"GetRequiresCleanup not implemented"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsMsgNewsFolder::GetSizeOnDisk(PRUint32 *size) { // NS_ASSERTION(0, "GetSizeOnDisk not implemented"); return NS_ERROR_NOT_IMPLEMENTED; } /* this is news, so remember that DeleteMessage is really CANCEL. */ NS_IMETHODIMP nsMsgNewsFolder::DeleteMessages(nsISupportsArray *messages, nsIMsgWindow *aMsgWindow, PRBool deleteStorage, PRBool isMove, nsIMsgCopyServiceListener* listener, PRBool allowUndo) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(messages); NS_ENSURE_ARG_POINTER(aMsgWindow); PRUint32 count = 0; rv = messages->Count(&count); NS_ENSURE_SUCCESS(rv,rv); if (count != 1) { nsCOMPtr bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bundle; rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle)); NS_ENSURE_SUCCESS(rv, rv); nsXPIDLString alertText; rv = bundle->GetStringFromName(NS_LITERAL_STRING("onlyCancelOneMessage").get(), getter_Copies(alertText)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr dialog; rv = aMsgWindow->GetPromptDialog(getter_AddRefs(dialog)); NS_ENSURE_SUCCESS(rv,rv); if (dialog) { rv = dialog->Alert(nsnull, (const PRUnichar *) alertText); NS_ENSURE_SUCCESS(rv,rv); } // return failure, since the cancel failed return NS_ERROR_FAILURE; } nsCOMPtr nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr msgHdr(do_QueryElementAt(messages, 0)); // for cancel, we need to // turn "newsmessage://sspitzer@news.mozilla.org/netscape.test#5428" // into "news://sspitzer@news.mozilla.org/23423@netscape.com" nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv,rv); nsXPIDLCString serverURI; rv = server->GetServerURI(getter_Copies(serverURI)); NS_ENSURE_SUCCESS(rv,rv); nsXPIDLCString messageID; rv = msgHdr->GetMessageId(getter_Copies(messageID)); NS_ENSURE_SUCCESS(rv,rv); // we need to escape the message ID, // it might contain characters which will mess us up later, like # // see bug #120502 char *escapedMessageID = nsEscape(messageID.get(), url_Path); if (!escapedMessageID) return NS_ERROR_OUT_OF_MEMORY; nsCAutoString cancelURL((const char *)serverURI); cancelURL += '/'; cancelURL += escapedMessageID; cancelURL += "?cancel"; PR_Free(escapedMessageID); nsXPIDLCString messageURI; rv = GetUriForMsg(msgHdr, getter_Copies(messageURI)); NS_ENSURE_SUCCESS(rv,rv); rv = nntpService->CancelMessage(cancelURL.get(), messageURI, nsnull /* consumer */, nsnull, aMsgWindow, nsnull); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsMsgNewsFolder::GetNewMessages(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener) { return GetNewsMessages(aMsgWindow, PR_FALSE, aListener); } NS_IMETHODIMP nsMsgNewsFolder::GetNextNMessages(nsIMsgWindow *aMsgWindow) { return GetNewsMessages(aMsgWindow, PR_TRUE, nsnull); } nsresult nsMsgNewsFolder::GetNewsMessages(nsIMsgWindow *aMsgWindow, PRBool aGetOld, nsIUrlListener *aUrlListener) { nsresult rv = NS_OK; PRBool isNewsServer = PR_FALSE; rv = GetIsServer(&isNewsServer); if (NS_FAILED(rv)) return rv; if (isNewsServer) // get new messages only works on a newsgroup, not a news server return NS_OK; nsCOMPtr nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; nsCOMPtr resultUri; rv = nntpService->GetNewNews(nntpServer, mURI.get(), aGetOld, this, aMsgWindow, getter_AddRefs(resultUri)); if (aUrlListener && NS_SUCCEEDED(rv) && resultUri) { nsCOMPtr msgUrl (do_QueryInterface(resultUri)); if (msgUrl) msgUrl->RegisterListener(aUrlListener); } return rv; } nsresult nsMsgNewsFolder::LoadNewsrcFileAndCreateNewsgroups() { nsresult rv = NS_OK; if (!mNewsrcFilePath) return NS_ERROR_FAILURE; PRBool exists = PR_FALSE; rv = mNewsrcFilePath->Exists(&exists); if (NS_FAILED(rv)) return rv; if (!exists) // it is ok for the newsrc file to not exist yet return NS_OK; char *buffer = nsnull; rv = mNewsrcFilePath->OpenStreamForReading(); NS_ENSURE_SUCCESS(rv,rv); PRInt32 numread = 0; if (NS_FAILED(m_newsrcInputStream.GrowBuffer(NEWSRC_FILE_BUFFER_SIZE))) return NS_ERROR_FAILURE; while (1) { buffer = m_newsrcInputStream.GetBuffer(); rv = mNewsrcFilePath->Read(&buffer, NEWSRC_FILE_BUFFER_SIZE, &numread); NS_ENSURE_SUCCESS(rv,rv); if (numread == 0) { break; } else { rv = BufferInput(m_newsrcInputStream.GetBuffer(), numread); if (NS_FAILED(rv)) { break; } } } mNewsrcFilePath->CloseStream(); return rv; } PRInt32 nsMsgNewsFolder::HandleLine(char* line, PRUint32 line_size) { return HandleNewsrcLine(line, line_size); } PRInt32 nsMsgNewsFolder::HandleNewsrcLine(char* line, PRUint32 line_size) { nsresult rv; /* guard against blank line lossage */ if (line[0] == '#' || line[0] == nsCRT::CR || line[0] == nsCRT::LF) return 0; line[line_size] = 0; if ((line[0] == 'o' || line[0] == 'O') && !PL_strncasecmp (line, "options", 7)) return RememberLine(line); char *s = nsnull; char *setStr = nsnull; char *end = line + line_size; for (s = line; s < end; s++) if ((*s == ':') || (*s == '!')) break; if (*s == 0) /* What is this?? Well, don't just throw it away... */ return RememberLine(line); PRBool subscribed = (*s == ':'); setStr = s+1; *s = '\0'; if (*line == '\0') return 0; // previous versions of Communicator poluted the // newsrc files with articles // (this would happen when you clicked on a link like // news://news.mozilla.org/3746EF3F.6080309@netscape.com) // // legal newsgroup names can't contain @ or % // // News group names are structured into parts separated by dots, // for example "netscape.public.mozilla.mail-news". // Each part may be up to 14 characters long, and should consist // only of letters, digits, "+" and "-", with at least one letter // // @ indicates an article and %40 is @ escaped. // previous versions of Communicator also dumped // the escaped version into the newsrc file // // So lines like this in a newsrc file should be ignored: // 3746EF3F.6080309@netscape.com: // 3746EF3F.6080309%40netscape.com: if (PL_strstr(line,"@") || PL_strstr(line,"%40")) // skipping, it contains @ or %40 subscribed = PR_FALSE; if (subscribed) { // we're subscribed, so add it nsCOMPtr child; rv = AddNewsgroup(nsDependentCString(line), setStr, getter_AddRefs(child)); if (NS_FAILED(rv)) return -1; } else { rv = RememberUnsubscribedGroup(line, setStr); if (NS_FAILED(rv)) return -1; } return 0; } nsresult nsMsgNewsFolder::RememberUnsubscribedGroup(const char *newsgroup, const char *setStr) { if (newsgroup) { mUnsubscribedNewsgroupLines += newsgroup; mUnsubscribedNewsgroupLines += "! "; if (setStr) mUnsubscribedNewsgroupLines += setStr; else mUnsubscribedNewsgroupLines += MSG_LINEBREAK; } return NS_OK; } PRInt32 nsMsgNewsFolder::RememberLine(const char* line) { mOptionLines = line; mOptionLines += MSG_LINEBREAK; return 0; } nsresult nsMsgNewsFolder::ForgetLine() { mOptionLines = ""; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetGroupUsername(char **aGroupUsername) { NS_ENSURE_ARG_POINTER(aGroupUsername); nsresult rv; if (mGroupUsername) { *aGroupUsername = nsCRT::strdup(mGroupUsername); rv = NS_OK; } else { rv = NS_ERROR_FAILURE; } return rv; } NS_IMETHODIMP nsMsgNewsFolder::SetGroupUsername(const char *aGroupUsername) { PR_FREEIF(mGroupUsername); if (aGroupUsername) mGroupUsername = nsCRT::strdup(aGroupUsername); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetGroupPassword(char **aGroupPassword) { NS_ENSURE_ARG_POINTER(aGroupPassword); nsresult rv; if (mGroupPassword) { *aGroupPassword = nsCRT::strdup(mGroupPassword); mPrevPassword = mGroupPassword; rv = NS_OK; } else { rv = NS_ERROR_FAILURE; } return rv; } NS_IMETHODIMP nsMsgNewsFolder::SetGroupPassword(const char *aGroupPassword) { PR_FREEIF(mGroupPassword); if (aGroupPassword) mGroupPassword = nsCRT::strdup(aGroupPassword); return NS_OK; } nsresult nsMsgNewsFolder::CreateNewsgroupUsernameUrlForSignon(const char *inUriStr, char **result) { return CreateNewsgroupUrlForSignon(inUriStr, "username", result); } nsresult nsMsgNewsFolder::CreateNewsgroupPasswordUrlForSignon(const char *inUriStr, char **result) { return CreateNewsgroupUrlForSignon(inUriStr, "password", result); } nsresult nsMsgNewsFolder::CreateNewsgroupUrlForSignon(const char *inUriStr, const char *ref, char **result) { nsresult rv; PRInt32 port = 0; nsCOMPtr url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); if (NS_FAILED(rv)) return rv; nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; PRBool singleSignon = PR_TRUE; rv = nntpServer->GetSingleSignon(&singleSignon); if (singleSignon) { nsXPIDLCString serverURI; rv = server->GetServerURI(getter_Copies(serverURI)); if (NS_FAILED(rv)) return rv; rv = url->SetSpec(serverURI); if (NS_FAILED(rv)) return rv; } else { rv = url->SetSpec(nsDependentCString(inUriStr)); if (NS_FAILED(rv)) return rv; } rv = url->GetPort(&port); if (NS_FAILED(rv)) return rv; if (port <= 0) { nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); if (NS_FAILED(rv)) return rv; PRBool isSecure = PR_FALSE; rv = server->GetIsSecure(&isSecure); if (NS_FAILED(rv)) return rv; rv = url->SetPort((isSecure) ? SECURE_NEWS_PORT: NEWS_PORT); if (NS_FAILED(rv)) return rv; } rv = url->SetRef(nsDependentCString(ref)); if (NS_FAILED(rv)) return rv; nsCAutoString spec; rv = url->GetSpec(spec); if (NS_FAILED(rv)) return rv; *result = ToNewCString(spec); return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } NS_IMETHODIMP nsMsgNewsFolder::ForgetGroupUsername() { nsresult rv; nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); NS_ENSURE_SUCCESS(rv,rv); rv = SetGroupUsername(nsnull); if (NS_FAILED(rv)) return rv; nsXPIDLCString signonURL; rv = CreateNewsgroupUsernameUrlForSignon(mURI.get(), getter_Copies(signonURL)); if (NS_FAILED(rv)) return rv; nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), signonURL); //this is need to make sure wallet service has been created rv = CreateServicesForPasswordManager(); NS_ENSURE_SUCCESS(rv, rv); rv = observerService->NotifyObservers(uri, "login-failed", nsnull); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsMsgNewsFolder::ForgetGroupPassword() { nsresult rv = NS_OK; nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); NS_ENSURE_SUCCESS(rv,rv); rv = SetGroupPassword(nsnull); if (NS_FAILED(rv)) return rv; nsXPIDLCString signonURL; rv = CreateNewsgroupPasswordUrlForSignon(mURI.get(), getter_Copies(signonURL)); if (NS_FAILED(rv)) return rv; nsCOMPtr uri; NS_NewURI(getter_AddRefs(uri), signonURL); //this is need to make sure wallet service has been created rv = CreateServicesForPasswordManager(); NS_ENSURE_SUCCESS(rv, rv); rv = observerService->NotifyObservers(uri, "login-failed", nsnull); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsMsgNewsFolder::GetGroupPasswordWithUI(const PRUnichar * aPromptMessage, const PRUnichar *aPromptTitle, nsIMsgWindow* aMsgWindow, char **aGroupPassword) { nsresult rv = NS_OK; NS_ENSURE_ARG_POINTER(aGroupPassword); if (!mGroupPassword) { // prompt the user for the password nsCOMPtr dialog; #ifdef DEBUG_seth NS_ASSERTION(aMsgWindow,"no msg window"); #endif if (aMsgWindow) { nsCOMPtr docShell; rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); if (NS_FAILED(rv)) return rv; dialog = do_GetInterface(docShell, &rv); if (NS_FAILED(rv)) return rv; } else { nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); if (wwatch) wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog)); if (!dialog) return NS_ERROR_FAILURE; } NS_ASSERTION(dialog,"we didn't get a net prompt"); if (dialog) { PRBool okayValue = PR_TRUE; nsXPIDLCString signonURL; rv = CreateNewsgroupPasswordUrlForSignon(mURI.get(), getter_Copies(signonURL)); if (NS_FAILED(rv)) return rv; PRUnichar *uniGroupPassword = nsnull; if (!mPrevPassword.IsEmpty()) uniGroupPassword = ToNewUnicode(NS_ConvertASCIItoUTF16(mPrevPassword)); rv = dialog->PromptPassword(aPromptTitle, aPromptMessage, NS_ConvertASCIItoUTF16(NS_STATIC_CAST(const char*, signonURL)).get(), nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, &uniGroupPassword, &okayValue); nsAutoString uniPasswordAdopted; uniPasswordAdopted.Adopt(uniGroupPassword); if (NS_FAILED(rv)) return rv; if (!okayValue) // if the user pressed cancel, just return NULL; { *aGroupPassword = nsnull; return rv; } // we got a password back...so remember it rv = SetGroupPassword(NS_LossyConvertUTF16toASCII(uniPasswordAdopted).get()); if (NS_FAILED(rv)) return rv; } // if we got a prompt dialog } // if the password is empty return GetGroupPassword(aGroupPassword); } NS_IMETHODIMP nsMsgNewsFolder::GetGroupUsernameWithUI(const PRUnichar * aPromptMessage, const PRUnichar *aPromptTitle, nsIMsgWindow* aMsgWindow, char **aGroupUsername) { nsresult rv = NS_ERROR_FAILURE; NS_ENSURE_ARG_POINTER(aGroupUsername); if (!mGroupUsername) { // prompt the user for the username nsCOMPtr dialog; #ifdef DEBUG_seth NS_ASSERTION(aMsgWindow,"no msg window"); #endif if (aMsgWindow) { // prompt the user for the password nsCOMPtr docShell; rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); if (NS_FAILED(rv)) return rv; dialog = do_GetInterface(docShell, &rv); if (NS_FAILED(rv)) return rv; } else { nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); if (wwatch) wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog)); if (!dialog) return NS_ERROR_FAILURE; } NS_ASSERTION(dialog,"we didn't get a net prompt"); if (dialog) { nsXPIDLString uniGroupUsername; PRBool okayValue = PR_TRUE; nsXPIDLCString signonURL; rv = CreateNewsgroupUsernameUrlForSignon(mURI.get(), getter_Copies(signonURL)); if (NS_FAILED(rv)) return rv; rv = dialog->Prompt(aPromptTitle, aPromptMessage, NS_ConvertASCIItoUCS2(signonURL).get(), nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, NS_ConvertASCIItoUTF16(mPrevUsername).get(), getter_Copies(uniGroupUsername), &okayValue); if (NS_FAILED(rv)) return rv; if (!okayValue) // if the user pressed cancel, just return NULL; { *aGroupUsername= nsnull; return rv; } // we got a username back, remember it rv = SetGroupUsername(NS_LossyConvertUCS2toASCII(uniGroupUsername).get()); if (NS_FAILED(rv)) return rv; } // if we got a prompt dialog } // if the password is empty rv = GetGroupUsername(aGroupUsername); mPrevUsername.Assign(*aGroupUsername); return rv; } nsresult nsMsgNewsFolder::CreateBaseMessageURI(const char *aURI) { nsresult rv; rv = nsCreateNewsBaseMessageURI(aURI, &mBaseMessageURI); return rv; } NS_IMETHODIMP nsMsgNewsFolder::GetNewsrcLine(char **newsrcLine) { nsresult rv; if (!newsrcLine) return NS_ERROR_NULL_POINTER; nsXPIDLString newsgroupNameUtf16; rv = GetName(getter_Copies(newsgroupNameUtf16)); if (NS_FAILED(rv)) return rv; NS_ConvertUTF16toUTF8 newsgroupName(newsgroupNameUtf16); nsCAutoString newsrcLineStr; newsrcLineStr = newsgroupName; newsrcLineStr += ':'; if (mReadSet) { nsXPIDLCString setStr; mReadSet->Output(getter_Copies(setStr)); if (NS_SUCCEEDED(rv)) { newsrcLineStr += ' '; newsrcLineStr += setStr; newsrcLineStr.AppendLiteral(MSG_LINEBREAK); } } *newsrcLine = ToNewCString(newsrcLineStr); if (!*newsrcLine) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::SetReadSetFromStr(const char *newsrcLine) { if (!newsrcLine) return NS_ERROR_NULL_POINTER; delete mReadSet; mReadSet = nsMsgKeySet::Create(newsrcLine); if (!mReadSet) return NS_ERROR_OUT_OF_MEMORY; // Now that mReadSet is recreated, make sure it's stored in the db as well. nsCOMPtr db = do_QueryInterface(mDatabase); if (db) // it's ok not to have a db here. db->SetReadSet(mReadSet); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetUnsubscribedNewsgroupLines(char **aUnsubscribedNewsgroupLines) { if (!aUnsubscribedNewsgroupLines) return NS_ERROR_NULL_POINTER; if (!mUnsubscribedNewsgroupLines.IsEmpty()) *aUnsubscribedNewsgroupLines= ToNewCString(mUnsubscribedNewsgroupLines); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetOptionLines(char **optionLines) { if (!optionLines) return NS_ERROR_NULL_POINTER; if (!mOptionLines.IsEmpty()) *optionLines = ToNewCString(mOptionLines); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::OnReadChanged(nsIDBChangeListener * aInstigator) { return SetNewsrcHasChanged(PR_TRUE); } NS_IMETHODIMP nsMsgNewsFolder::GetUnicodeName(nsAString & aName) { nsXPIDLString newsgroupName; nsresult rv = GetName(getter_Copies(newsgroupName)); if (NS_SUCCEEDED(rv)) aName = newsgroupName; return rv; } NS_IMETHODIMP nsMsgNewsFolder::GetRawName(nsACString & aRawName) { nsresult rv; if (mRawName.IsEmpty()) { nsXPIDLString name; rv = GetName(getter_Copies(name)); NS_ENSURE_SUCCESS(rv,rv); // convert to the server-side encoding nsCAutoString tmpStr; nsCOMPtr nntpServer; rv = GetNntpServer(getter_AddRefs(nntpServer)); NS_ENSURE_SUCCESS(rv,rv); nsCAutoString dataCharset; rv = nntpServer->GetCharset(dataCharset); NS_ENSURE_SUCCESS(rv,rv); rv = nsMsgI18NConvertFromUnicode(dataCharset.get(), name, tmpStr); if (NS_FAILED(rv)) { LossyCopyUTF16toASCII(name,tmpStr); } mRawName = tmpStr; } aRawName = mRawName; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetNntpServer(nsINntpIncomingServer **result) { nsresult rv; NS_ENSURE_ARG_POINTER(result); nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); if (NS_FAILED(rv)) return rv; if (!server) return NS_ERROR_NULL_POINTER; nsCOMPtr nntpServer; rv = server->QueryInterface(NS_GET_IID(nsINntpIncomingServer), getter_AddRefs(nntpServer)); if (NS_FAILED(rv)) return rv; NS_IF_ADDREF(*result = nntpServer); return NS_OK; } // this gets called after the message actually gets cancelled // it removes the cancelled message from the db NS_IMETHODIMP nsMsgNewsFolder::RemoveMessage(nsMsgKey key) { nsresult rv = GetDatabase(nsnull); NS_ENSURE_SUCCESS(rv, rv); // if GetDatabase succeeds, mDatabase will be non-null return mDatabase->DeleteMessage(key, nsnull, PR_FALSE); } NS_IMETHODIMP nsMsgNewsFolder::CancelComplete() { NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::CancelFailed() { NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetSaveArticleOffline(PRBool *aBool) { NS_ENSURE_ARG(aBool); *aBool = m_downloadMessageForOfflineUse; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::SetSaveArticleOffline(PRBool aBool) { m_downloadMessageForOfflineUse = aBool; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::DownloadAllForOffline(nsIUrlListener *listener, nsIMsgWindow *msgWindow) { nsMsgKeyArray srcKeyArray; SetSaveArticleOffline(PR_TRUE); nsresult rv; // build up message keys. if (mDatabase) { nsCOMPtr enumerator; rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator)); if (NS_SUCCEEDED(rv) && enumerator) { PRBool hasMore; while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr pHeader; rv = enumerator->GetNext(getter_AddRefs(pHeader)); NS_ASSERTION(NS_SUCCEEDED(rv), "nsMsgDBEnumerator broken"); if (pHeader && NS_SUCCEEDED(rv)) { PRBool shouldStoreMsgOffline = PR_FALSE; nsMsgKey msgKey; pHeader->GetMessageKey(&msgKey); MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline); if (shouldStoreMsgOffline) srcKeyArray.Add(msgKey); } } } } DownloadNewsArticlesToOfflineStore *downloadState = new DownloadNewsArticlesToOfflineStore(msgWindow, mDatabase, this); if (!downloadState) return NS_ERROR_OUT_OF_MEMORY; m_downloadingMultipleMessages = PR_TRUE; return downloadState->DownloadArticles(msgWindow, this, &srcKeyArray); } NS_IMETHODIMP nsMsgNewsFolder::DownloadMessagesForOffline(nsISupportsArray *messages, nsIMsgWindow *window) { nsMsgKeyArray srcKeyArray; SetSaveArticleOffline(PR_TRUE); // ### TODO need to clear this when we've finished PRUint32 count = 0; PRUint32 i; nsresult rv = messages->Count(&count); NS_ENSURE_SUCCESS(rv, rv); // build up message keys. for (i = 0; i < count; i++) { nsMsgKey key; nsCOMPtr msgDBHdr = do_QueryElementAt(messages, i, &rv); if (msgDBHdr) rv = msgDBHdr->GetMessageKey(&key); if (NS_SUCCEEDED(rv)) srcKeyArray.Add(key); } DownloadNewsArticlesToOfflineStore *downloadState = new DownloadNewsArticlesToOfflineStore(window, mDatabase, this); if (!downloadState) return NS_ERROR_OUT_OF_MEMORY; m_downloadingMultipleMessages = PR_TRUE; return downloadState->DownloadArticles(window, this, &srcKeyArray); } // line does not have a line terminator (e.g., CR or CRLF) NS_IMETHODIMP nsMsgNewsFolder::NotifyDownloadedLine(const char *line, nsMsgKey keyOfArticle) { nsresult rv = NS_OK; if (m_downloadMessageForOfflineUse && !m_offlineHeader) { GetMessageHeader(keyOfArticle, getter_AddRefs(m_offlineHeader)); rv = StartNewOfflineMessage(); } m_numOfflineMsgLines++; if (m_tempMessageStream) { // line now contains the linebreak. if (line[0] == '.' && line[MSG_LINEBREAK_LEN + 1] == 0) { // end of article. if (m_offlineHeader) EndNewOfflineMessage(); if (m_tempMessageStream && !m_downloadingMultipleMessages) { m_tempMessageStream->Close(); m_tempMessageStream = nsnull; } } else { PRUint32 count = 0; rv = m_tempMessageStream->Write(line, strlen(line), &count); NS_ASSERTION(NS_SUCCEEDED(rv), "failed to write to stream"); } } return rv; } NS_IMETHODIMP nsMsgNewsFolder::NotifyFinishedDownloadinghdrs() { ChangeNumPendingTotalMessages(-GetNumPendingTotalMessages()); ChangeNumPendingUnread(-GetNumPendingUnread()); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) { nsresult rv; rv = GetDatabase(nsnull); if (mDatabase) ApplyRetentionSettings(); return rv; } NS_IMETHODIMP nsMsgNewsFolder::ApplyRetentionSettings() { return nsMsgDBFolder::ApplyRetentionSettings(PR_FALSE); } NS_IMETHODIMP nsMsgNewsFolder::GetMessageIdForKey(nsMsgKey key, char **result) { nsresult rv = GetDatabase(nsnull); if (!mDatabase) return rv; nsCOMPtr hdr; rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr)); NS_ENSURE_SUCCESS(rv,rv); if (!hdr) return NS_ERROR_INVALID_ARG; return hdr->GetMessageId(result); } NS_IMETHODIMP nsMsgNewsFolder::SetSortOrder(PRInt32 order) { mSortOrder = order; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetSortOrder(PRInt32 *order) { NS_ENSURE_ARG_POINTER(order); *order = mSortOrder; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetPersistElided(PRBool *aPersistElided) { nsresult rv; PRBool isNewsServer = PR_FALSE; rv = GetIsServer(&isNewsServer); NS_ENSURE_SUCCESS(rv,rv); // persist the open / closed state, if not a server // this doesn't matter right now, but it will if we ever add categories if (!isNewsServer) { *aPersistElided = PR_TRUE; return NS_OK; } nsCOMPtr prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); rv = prefBranch->GetBoolPref("news.persist_server_open_state_in_folderpane", aPersistElided); NS_ENSURE_SUCCESS(rv,rv); return rv; } NS_IMETHODIMP nsMsgNewsFolder::Shutdown(PRBool shutdownChildren) { if (mFilterList) { // close the filter log stream nsresult rv = mFilterList->SetLogStream(nsnull); NS_ENSURE_SUCCESS(rv,rv); mFilterList = nsnull; } mInitialized = PR_FALSE; if (mReadSet) { // the nsINewsDatabase holds a weak ref to the readset, // and we outlive the db, so it's safe to delete it here. nsCOMPtr db = do_QueryInterface(mDatabase); if (db) db->SetReadSet(nsnull); delete mReadSet; mReadSet = nsnull; } return nsMsgDBFolder::Shutdown(shutdownChildren); } NS_IMETHODIMP nsMsgNewsFolder::SetFilterList(nsIMsgFilterList *aFilterList) { if (mIsServer) { nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv,rv); return server->SetFilterList(aFilterList); } mFilterList = aFilterList; return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::GetFilterList(nsIMsgWindow *aMsgWindow, nsIMsgFilterList **aResult) { if (mIsServer) { nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv,rv); return server->GetFilterList(aMsgWindow, aResult); } if (!mFilterList) { nsCOMPtr thisFolder; nsresult rv = GetPath(getter_AddRefs(thisFolder)); NS_ENSURE_SUCCESS(rv, rv); mFilterFile = do_CreateInstance(NS_FILESPEC_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); // in 4.x, the news filter file was // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.dat // where the summary file was // C:\Program Files\Netscape\Users\meer\News\host-news.mcom.com\mcom.test.snm // we make the rules file ".dat" in mozilla, so that migration works. rv = mFilterFile->FromFileSpec(thisFolder); NS_ENSURE_SUCCESS(rv, rv); // NOTE: // we don't we need to call NS_MsgHashIfNecessary() // it's already been hashed, if necessary nsXPIDLCString filterFileName; rv = mFilterFile->GetLeafName(getter_Copies(filterFileName)); NS_ENSURE_SUCCESS(rv,rv); filterFileName.Append(".dat"); rv = mFilterFile->SetLeafName(filterFileName.get()); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr filterService = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = filterService->OpenFilterList(mFilterFile, this, aMsgWindow, getter_AddRefs(mFilterList)); NS_ENSURE_SUCCESS(rv, rv); } NS_IF_ADDREF(*aResult = mFilterList); return NS_OK; } NS_IMETHODIMP nsMsgNewsFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) { if (m_tempMessageStream) { m_tempMessageStream->Close(); m_tempMessageStream = nsnull; } m_downloadingMultipleMessages = PR_FALSE; return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); }