/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsNntpIncomingServer.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsNewsFolder.h"
#include "nsIMsgFolder.h"
#include "nsIFile.h"
#include "nsCOMPtr.h"
#include "nsINntpService.h"
#include "nsINNTPProtocol.h"
#include "nsMsgNewsCID.h"
#include "nsNNTPProtocol.h"
#include "nsIDirectoryService.h"
#include "nsMailDirServiceDefs.h"
#include "nsMsgUtils.h"
#include "nsIPrompt.h"
#include "nsIStringBundle.h"
#include "nntpCore.h"
#include "nsIWindowWatcher.h"
#include "nsITreeColumns.h"
#include "nsIDOMElement.h"
#include "nsMsgFolderFlags.h"
#include "nsMsgI18N.h"
#include "nsUnicharUtils.h"
#include "nsILineInputStream.h"
#include "nsNetUtil.h"
#include "nsISimpleEnumerator.h"
#include "nsMsgUtils.h"
#include "mozilla/Services.h"
#include "nsITreeBoxObject.h"
#define INVALID_VERSION 0
#define VALID_VERSION 2
#define NEW_NEWS_DIR_NAME "News"
#define PREF_MAIL_NEWSRC_ROOT "mail.newsrc_root"
#define PREF_MAIL_NEWSRC_ROOT_REL "mail.newsrc_root-rel"
#define PREF_MAILNEWS_VIEW_DEFAULT_CHARSET "mailnews.view_default_charset"
#define HOSTINFO_FILE_NAME "hostinfo.dat"
#define NEWS_DELIMITER '.'
// this platform specific junk is so the newsrc filenames we create
// will resemble the migrated newsrc filenames.
#if defined(XP_UNIX)
#define NEWSRC_FILE_PREFIX "newsrc-"
#define NEWSRC_FILE_SUFFIX ""
#else
#define NEWSRC_FILE_PREFIX ""
#define NEWSRC_FILE_SUFFIX ".rc"
#endif /* XP_UNIX */
// ###tw This really ought to be the most
// efficient file reading size for the current
// operating system.
#define HOSTINFO_FILE_BUFFER_SIZE 1024
#include "nsMsgUtils.h"
/**
* A comparator class to do cases insensitive comparisons for nsTArray.Sort()
*/
class nsCStringLowerCaseComparator
{
public:
bool Equals(const nsCString &a, const nsCString &b) const
{
return a.Equals(b, nsCaseInsensitiveCStringComparator());
}
bool LessThan(const nsCString &a, const nsCString &b) const
{
return Compare(a, b, nsCaseInsensitiveCStringComparator()) < 0;
}
};
static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
NS_IMPL_ADDREF_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
NS_IMPL_RELEASE_INHERITED(nsNntpIncomingServer, nsMsgIncomingServer)
NS_INTERFACE_MAP_BEGIN(nsNntpIncomingServer)
NS_INTERFACE_MAP_ENTRY(nsINntpIncomingServer)
NS_INTERFACE_MAP_ENTRY(nsIUrlListener)
NS_INTERFACE_MAP_ENTRY(nsISubscribableServer)
NS_INTERFACE_MAP_ENTRY(nsITreeView)
NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer)
nsNntpIncomingServer::nsNntpIncomingServer()
{
mNewsrcHasChanged = false;
mGetOnlyNew = true;
mHostInfoLoaded = false;
mHostInfoHasChanged = false;
mVersion = INVALID_VERSION;
mLastGroupDate = 0;
mUniqueId = 0;
mHasSeenBeginGroups = false;
mPostingAllowed = false;
mLastUpdatedTime = 0;
// these atoms are used for subscribe search
mSubscribedAtom = MsgGetAtom("subscribed");
mNntpAtom = MsgGetAtom("nntp");
// we have server wide and per group filters
m_canHaveFilters = true;
SetupNewsrcSaveTimer();
}
nsNntpIncomingServer::~nsNntpIncomingServer()
{
nsresult rv;
if (mNewsrcSaveTimer) {
mNewsrcSaveTimer->Cancel();
mNewsrcSaveTimer = nullptr;
}
rv = ClearInner();
NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed");
rv = CloseCachedConnections();
NS_ASSERTION(NS_SUCCEEDED(rv), "CloseCachedConnections failed");
}
NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, NotifyOn, "notify.on")
NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, MarkOldRead, "mark_old_read")
NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, Abbreviate, "abbreviate")
NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, PushAuth, "always_authenticate")
NS_IMPL_SERVERPREF_BOOL(nsNntpIncomingServer, SingleSignon, "singleSignon")
NS_IMPL_SERVERPREF_INT(nsNntpIncomingServer, MaxArticles, "max_articles")
nsresult
nsNntpIncomingServer::CreateRootFolderFromUri(const nsCString &serverUri,
nsIMsgFolder **rootFolder)
{
nsMsgNewsFolder *newRootFolder = new nsMsgNewsFolder;
if (!newRootFolder)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*rootFolder = newRootFolder);
newRootFolder->Init(serverUri.get());
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetNewsrcFilePath(nsIFile **aNewsrcFilePath)
{
nsresult rv;
if (mNewsrcFilePath)
{
*aNewsrcFilePath = mNewsrcFilePath;
NS_IF_ADDREF(*aNewsrcFilePath);
return NS_OK;
}
rv = GetFileValue("newsrc.file-rel", "newsrc.file", aNewsrcFilePath);
if (NS_SUCCEEDED(rv) && *aNewsrcFilePath)
{
mNewsrcFilePath = *aNewsrcFilePath;
return rv;
}
rv = GetNewsrcRootPath(getter_AddRefs(mNewsrcFilePath));
if (NS_FAILED(rv)) return rv;
nsCString hostname;
rv = GetHostName(hostname);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString newsrcFileName(NEWSRC_FILE_PREFIX);
newsrcFileName.Append(hostname);
newsrcFileName.Append(NEWSRC_FILE_SUFFIX);
rv = mNewsrcFilePath->AppendNative(newsrcFileName);
rv = mNewsrcFilePath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
NS_ENSURE_SUCCESS(rv, rv);
rv = SetNewsrcFilePath(mNewsrcFilePath);
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*aNewsrcFilePath = mNewsrcFilePath);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetNewsrcFilePath(nsIFile *aFile)
{
NS_ENSURE_ARG_POINTER(aFile);
bool exists;
nsresult rv = aFile->Exists(&exists);
if (!exists)
{
rv = aFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664);
if (NS_FAILED(rv)) return rv;
}
return SetFileValue("newsrc.file-rel", "newsrc.file", aFile);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetLocalStoreType(nsACString& type)
{
type.AssignLiteral("news");
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetNewsrcRootPath(nsIFile *aNewsrcRootPath)
{
NS_ENSURE_ARG(aNewsrcRootPath);
return NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, aNewsrcRootPath);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetNewsrcRootPath(nsIFile **aNewsrcRootPath)
{
NS_ENSURE_ARG_POINTER(aNewsrcRootPath);
*aNewsrcRootPath = nullptr;
bool havePref;
nsresult rv = NS_GetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL,
PREF_MAIL_NEWSRC_ROOT,
NS_APP_NEWS_50_DIR,
havePref,
aNewsrcRootPath);
NS_ENSURE_SUCCESS(rv, rv);
bool exists;
rv = (*aNewsrcRootPath)->Exists(&exists);
if (NS_SUCCEEDED(rv) && !exists)
rv = (*aNewsrcRootPath)->Create(nsIFile::DIRECTORY_TYPE, 0775);
NS_ENSURE_SUCCESS(rv, rv);
if (!havePref || !exists)
{
rv = NS_SetPersistentFile(PREF_MAIL_NEWSRC_ROOT_REL, PREF_MAIL_NEWSRC_ROOT, *aNewsrcRootPath);
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
}
return rv;
}
/* static */ void nsNntpIncomingServer::OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer)
{
nsNntpIncomingServer *incomingServer = (nsNntpIncomingServer*)voidIncomingServer;
incomingServer->WriteNewsrcFile();
}
nsresult nsNntpIncomingServer::SetupNewsrcSaveTimer()
{
int64_t ms(300000); // hard code, 5 minutes.
//Convert biffDelay into milliseconds
uint32_t timeInMSUint32 = (uint32_t)ms;
//Can't currently reset a timer when it's in the process of
//calling Notify. So, just release the timer here and create a new one.
if(mNewsrcSaveTimer)
mNewsrcSaveTimer->Cancel();
mNewsrcSaveTimer = do_CreateInstance("@mozilla.org/timer;1");
mNewsrcSaveTimer->InitWithFuncCallback(OnNewsrcSaveTimer, (void*)this, timeInMSUint32,
nsITimer::TYPE_REPEATING_SLACK);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetCharset(const nsACString & aCharset)
{
return SetCharValue("charset", aCharset);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCharset(nsACString & aCharset)
{
//first we get the per-server settings mail.server.<serverkey>.charset
nsresult rv = GetCharValue("charset", aCharset);
NS_ENSURE_SUCCESS(rv, rv);
//if the per-server setting is empty,we get the default charset from
//mailnews.view_default_charset setting and set it as per-server preference.
if (aCharset.IsEmpty()) {
nsString defaultCharset;
rv = NS_GetLocalizedUnicharPreferenceWithDefault(nullptr,
PREF_MAILNEWS_VIEW_DEFAULT_CHARSET,
NS_LITERAL_STRING("ISO-8859-1"), defaultCharset);
NS_ENSURE_SUCCESS(rv, rv);
LossyCopyUTF16toASCII(defaultCharset, aCharset);
SetCharset(aCharset);
}
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::WriteNewsrcFile()
{
nsresult rv;
bool newsrcHasChanged;
rv = GetNewsrcHasChanged(&newsrcHasChanged);
if (NS_FAILED(rv)) return rv;
#ifdef DEBUG_NEWS
nsCString hostname;
rv = GetHostName(hostname);
if (NS_FAILED(rv)) return rv;
#endif /* DEBUG_NEWS */
if (newsrcHasChanged) {
#ifdef DEBUG_NEWS
printf("write newsrc file for %s\n", hostname.get());
#endif
nsCOMPtr <nsIFile> newsrcFile;
rv = GetNewsrcFilePath(getter_AddRefs(newsrcFile));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIOutputStream> newsrcStream;
nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(newsrcStream), newsrcFile, -1, 00600);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsISimpleEnumerator> subFolders;
nsCOMPtr<nsIMsgFolder> rootFolder;
rv = GetRootFolder(getter_AddRefs(rootFolder));
if (NS_FAILED(rv)) return rv;
nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
if (NS_FAILED(rv)) return rv;
uint32_t bytesWritten;
nsCString optionLines;
rv = newsFolder->GetOptionLines(optionLines);
if (NS_SUCCEEDED(rv) && !optionLines.IsEmpty()) {
newsrcStream->Write(optionLines.get(), optionLines.Length(), &bytesWritten);
#ifdef DEBUG_NEWS
printf("option lines:\n%s", optionLines.get());
#endif /* DEBUG_NEWS */
}
#ifdef DEBUG_NEWS
else {
printf("no option lines to write out\n");
}
#endif /* DEBUG_NEWS */
nsCString unsubscribedLines;
rv = newsFolder->GetUnsubscribedNewsgroupLines(unsubscribedLines);
if (NS_SUCCEEDED(rv) && !unsubscribedLines.IsEmpty()) {
newsrcStream->Write(unsubscribedLines.get(), unsubscribedLines.Length(), &bytesWritten);
#ifdef DEBUG_NEWS
printf("unsubscribedLines:\n%s", unsubscribedLines.get());
#endif /* DEBUG_NEWS */
}
#ifdef DEBUG_NEWS
else {
printf("no unsubscribed lines to write out\n");
}
#endif /* DEBUG_NEWS */
rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders));
if (NS_FAILED(rv)) return rv;
bool moreFolders;
while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
moreFolders) {
nsCOMPtr<nsISupports> child;
rv = subFolders->GetNext(getter_AddRefs(child));
if (NS_SUCCEEDED(rv) && child) {
newsFolder = do_QueryInterface(child, &rv);
if (NS_SUCCEEDED(rv) && newsFolder) {
nsCString newsrcLine;
rv = newsFolder->GetNewsrcLine(newsrcLine);
if (NS_SUCCEEDED(rv) && !newsrcLine.IsEmpty()) {
// write the line to the newsrc file
newsrcStream->Write(newsrcLine.get(), newsrcLine.Length(), &bytesWritten);
}
}
}
}
newsrcStream->Close();
rv = SetNewsrcHasChanged(false);
if (NS_FAILED(rv)) return rv;
}
#ifdef DEBUG_NEWS
else {
printf("no need to write newsrc file for %s, it was not dirty\n", (hostname.get()));
}
#endif /* DEBUG_NEWS */
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetNewsrcHasChanged(bool aNewsrcHasChanged)
{
mNewsrcHasChanged = aNewsrcHasChanged;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetNewsrcHasChanged(bool *aNewsrcHasChanged)
{
if (!aNewsrcHasChanged) return NS_ERROR_NULL_POINTER;
*aNewsrcHasChanged = mNewsrcHasChanged;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::CloseCachedConnections()
{
nsresult rv;
nsCOMPtr<nsINNTPProtocol> connection;
// iterate through the connection cache and close the connections.
int32_t cnt = mConnectionCache.Count();
for (int32_t i = 0; i < cnt; ++i)
{
connection = mConnectionCache[0];
if (connection)
{
rv = connection->CloseConnection();
// We need to do this instead of RemoveObjectAt(0) because the
// above call will likely cause the object to be removed from the
// array anyway
mConnectionCache.RemoveObject(connection);
}
}
rv = WriteNewsrcFile();
if (NS_FAILED(rv)) return rv;
if (!mGetOnlyNew && !mHostInfoLoaded)
{
rv = WriteHostInfoFile();
NS_ENSURE_SUCCESS(rv,rv);
}
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetMaximumConnectionsNumber(int32_t *aMaxConnections)
{
NS_ENSURE_ARG_POINTER(aMaxConnections);
nsresult rv = GetIntValue("max_cached_connections", aMaxConnections);
// Get our maximum connection count. We need at least 1. If the value is 0,
// we use the default. If it's negative, we treat that as 1.
if (NS_SUCCEEDED(rv) && *aMaxConnections > 0)
return NS_OK;
*aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 2 : 1;
(void)SetMaximumConnectionsNumber(*aMaxConnections);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections)
{
return SetIntValue("max_cached_connections", aMaxConnections);
}
bool
nsNntpIncomingServer::ConnectionTimeOut(nsINNTPProtocol* aConnection)
{
bool retVal = false;
if (!aConnection)
return retVal;
PRTime lastActiveTimeStamp;
if (NS_FAILED(aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp)))
return retVal;
if (PR_Now() - lastActiveTimeStamp >= PRTime(170) * PR_USEC_PER_SEC)
{
#ifdef DEBUG_seth
printf("XXX connection timed out, close it, and remove it from the connection cache\n");
#endif
aConnection->CloseConnection();
mConnectionCache.RemoveObject(aConnection);
retVal = true;
}
return retVal;
}
nsresult
nsNntpIncomingServer::CreateProtocolInstance(nsINNTPProtocol ** aNntpConnection, nsIURI *url,
nsIMsgWindow *aMsgWindow)
{
// create a new connection and add it to the connection cache
// we may need to flag the protocol connection as busy so we don't get
// a race
// condition where someone else goes through this code
nsNNTPProtocol *protocolInstance = new nsNNTPProtocol(this, url, aMsgWindow);
if (!protocolInstance)
return NS_ERROR_OUT_OF_MEMORY;
nsresult rv = protocolInstance->QueryInterface(NS_GET_IID(nsINNTPProtocol), (void **) aNntpConnection);
// take the protocol instance and add it to the connectionCache
if (NS_SUCCEEDED(rv) && *aNntpConnection)
mConnectionCache.AppendObject(*aNntpConnection);
return rv;
}
nsresult
nsNntpIncomingServer::GetNntpConnection(nsIURI * aUri, nsIMsgWindow *aMsgWindow,
nsINNTPProtocol ** aNntpConnection)
{
int32_t maxConnections;
(void)GetMaximumConnectionsNumber(&maxConnections);
// Find a non-busy connection
nsCOMPtr<nsINNTPProtocol> connection;
int32_t cnt = mConnectionCache.Count();
for (int32_t i = 0; i < cnt; i++)
{
connection = mConnectionCache[i];
if (connection)
{
bool isBusy;
connection->GetIsBusy(&isBusy);
if (!isBusy)
break;
connection = nullptr;
}
}
if (ConnectionTimeOut(connection))
{
connection = nullptr;
// We have one less connection, since we closed this one.
--cnt;
}
if (connection)
{
NS_IF_ADDREF(*aNntpConnection = connection);
connection->SetIsCachedConnection(true);
}
else if (cnt < maxConnections)
{
// We have room for another connection. Create this connection and return
// it to the caller.
nsresult rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
NS_ENSURE_SUCCESS(rv, rv);
}
else
{
// We maxed out our connection count. The caller must therefore enqueue the
// call.
*aNntpConnection = nullptr;
return NS_OK;
}
// Initialize the URI here and now.
return (*aNntpConnection)->Initialize(aUri, aMsgWindow);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetNntpChannel(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
nsIChannel **aChannel)
{
NS_ENSURE_ARG_POINTER(aChannel);
nsCOMPtr<nsINNTPProtocol> protocol;
nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
NS_ENSURE_SUCCESS(rv, rv);
if (protocol)
return CallQueryInterface(protocol, aChannel);
// No protocol? We need our mock channel.
nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow);
if (!channel)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aChannel = channel);
m_queuedChannels.AppendElement(channel);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::LoadNewsUrl(nsIURI *aURI, nsIMsgWindow *aMsgWindow,
nsISupports *aConsumer)
{
nsCOMPtr<nsINNTPProtocol> protocol;
nsresult rv = GetNntpConnection(aURI, aMsgWindow, getter_AddRefs(protocol));
NS_ENSURE_SUCCESS(rv, rv);
if (protocol)
return protocol->LoadNewsUrl(aURI, aConsumer);
// No protocol? We need our mock channel.
nsNntpMockChannel *channel = new nsNntpMockChannel(aURI, aMsgWindow,
aConsumer);
if (!channel)
return NS_ERROR_OUT_OF_MEMORY;
m_queuedChannels.AppendElement(channel);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::PrepareForNextUrl(nsNNTPProtocol *aConnection)
{
NS_ENSURE_ARG(aConnection);
// Start the connection on the next URL in the queue. If it can't get a URL to
// work, drop that URL (the channel will handle failure notification) and move
// on.
while (m_queuedChannels.Length() > 0)
{
nsRefPtr<nsNntpMockChannel> channel = m_queuedChannels[0];
m_queuedChannels.RemoveElementAt(0);
nsresult rv = channel->AttachNNTPConnection(*aConnection);
// If this succeeded, the connection is now running the URL.
if (NS_SUCCEEDED(rv))
return NS_OK;
}
// No queued uris.
return NS_OK;
}
/* void RemoveConnection (in nsINNTPProtocol aNntpConnection); */
NS_IMETHODIMP nsNntpIncomingServer::RemoveConnection(nsINNTPProtocol *aNntpConnection)
{
if (aNntpConnection)
mConnectionCache.RemoveObject(aNntpConnection);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::PerformExpand(nsIMsgWindow *aMsgWindow)
{
// Get news.update_unread_on_expand pref
nsresult rv;
bool updateUnreadOnExpand = true;
nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv))
prefBranch->GetBoolPref("news.update_unread_on_expand", &updateUnreadOnExpand);
// Only if news.update_unread_on_expand is true do we update the unread counts
if (updateUnreadOnExpand)
return DownloadMail(aMsgWindow);
return NS_OK;
}
nsresult
nsNntpIncomingServer::DownloadMail(nsIMsgWindow *aMsgWindow)
{
nsCOMPtr<nsIMsgFolder> rootFolder;
nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISimpleEnumerator> groups;
rv = rootFolder->GetSubFolders(getter_AddRefs(groups));
NS_ENSURE_SUCCESS(rv, rv);
bool hasNext;
while (NS_SUCCEEDED(rv = groups->HasMoreElements(&hasNext)) && hasNext)
{
nsCOMPtr<nsISupports> nextGroup;
rv = groups->GetNext(getter_AddRefs(nextGroup));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMsgFolder> group(do_QueryInterface(nextGroup));
rv = group->GetNewMessages(aMsgWindow, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
}
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::DisplaySubscribedGroup(nsIMsgNewsFolder *aMsgFolder, int32_t aFirstMessage, int32_t aLastMessage, int32_t aTotalMessages)
{
nsresult rv;
if (!aMsgFolder) return NS_ERROR_NULL_POINTER;
#ifdef DEBUG_NEWS
printf("DisplaySubscribedGroup(...,%ld,%ld,%ld)\n",aFirstMessage,aLastMessage,aTotalMessages);
#endif
rv = aMsgFolder->UpdateSummaryFromNNTPInfo(aFirstMessage,aLastMessage,aTotalMessages);
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
{
// Biff will force a download of the messages. If the user doesn't want this
// (e.g., there is a lot of high-traffic newsgroups), the better option is to
// just ignore biff.
return PerformExpand(aMsgWindow);
}
NS_IMETHODIMP nsNntpIncomingServer::GetServerRequiresPasswordForBiff(bool *aServerRequiresPasswordForBiff)
{
NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
*aServerRequiresPasswordForBiff = false; // for news, biff is getting the unread counts
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::OnStartRunningUrl(nsIURI *url)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::OnStopRunningUrl(nsIURI *url, nsresult exitCode)
{
nsresult rv;
rv = UpdateSubscribed();
if (NS_FAILED(rv)) return rv;
rv = StopPopulating(mMsgWindow);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::ContainsNewsgroup(const nsACString &aName,
bool *containsGroup)
{
NS_ENSURE_ARG_POINTER(containsGroup);
NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
if (mSubscribedNewsgroups.Length() == 0)
{
// If this is empty, we may need to discover folders
nsCOMPtr<nsIMsgFolder> rootFolder;
GetRootFolder(getter_AddRefs(rootFolder));
if (rootFolder)
{
nsCOMPtr<nsISimpleEnumerator> subfolders;
rootFolder->GetSubFolders(getter_AddRefs(subfolders));
}
}
nsAutoCString unescapedName;
MsgUnescapeString(aName, 0, unescapedName);
*containsGroup = mSubscribedNewsgroups.Contains(aName);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SubscribeToNewsgroup(const nsACString &aName)
{
NS_ASSERTION(!aName.IsEmpty(), "no name");
NS_ENSURE_FALSE(aName.IsEmpty(), NS_ERROR_FAILURE);
// If we already have this newsgroup, do nothing and report success.
bool containsGroup = false;
nsresult rv = ContainsNewsgroup(aName, &containsGroup);
NS_ENSURE_SUCCESS(rv, rv);
if (containsGroup)
return NS_OK;
nsCOMPtr<nsIMsgFolder> msgfolder;
rv = GetRootMsgFolder(getter_AddRefs(msgfolder));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(msgfolder, NS_ERROR_FAILURE);
return msgfolder->CreateSubfolder(NS_ConvertUTF8toUTF16(aName), nullptr);
}
bool
writeGroupToHostInfoFile(nsCString &aElement, void *aData)
{
nsIOutputStream *stream;
stream = (nsIOutputStream *)aData;
NS_ASSERTION(stream, "no stream");
if (!stream) {
// stop, something is bad.
return false;
}
return true;
}
void nsNntpIncomingServer::WriteLine(nsIOutputStream *stream, nsCString &str)
{
uint32_t bytesWritten;
str.Append(MSG_LINEBREAK);
stream->Write(str.get(), str.Length(), &bytesWritten);
}
nsresult
nsNntpIncomingServer::WriteHostInfoFile()
{
if (!mHostInfoHasChanged)
return NS_OK;
mLastUpdatedTime = uint32_t(PR_Now() / PR_USEC_PER_SEC);
nsCString hostname;
nsresult rv = GetHostName(hostname);
NS_ENSURE_SUCCESS(rv,rv);
if (!mHostInfoFile)
return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIOutputStream> hostInfoStream;
rv = MsgNewBufferedFileOutputStream(getter_AddRefs(hostInfoStream), mHostInfoFile, -1, 00600);
NS_ENSURE_SUCCESS(rv, rv);
// todo, missing some formatting, see the 4.x code
nsAutoCString header("# News host information file.");
WriteLine(hostInfoStream, header);
header.Assign("# This is a generated file! Do not edit.");
WriteLine(hostInfoStream, header);
header.Truncate();
WriteLine(hostInfoStream, header);
nsAutoCString version("version=");
version.AppendInt(VALID_VERSION);
WriteLine(hostInfoStream, version);
nsAutoCString newsrcname("newsrcname=");
newsrcname.Append(hostname);
WriteLine(hostInfoStream, hostname);
nsAutoCString dateStr("lastgroupdate=");
dateStr.AppendInt(mLastUpdatedTime);
WriteLine(hostInfoStream, dateStr);
dateStr = "uniqueid=";
dateStr.AppendInt(mUniqueId);
WriteLine(hostInfoStream, dateStr);
header.Assign(MSG_LINEBREAK"begingroups");
WriteLine(hostInfoStream, header);
// XXX todo, sort groups first?
uint32_t length = mGroupsOnServer.Length();
for (uint32_t i = 0; i < length; ++i)
{
uint32_t bytesWritten;
hostInfoStream->Write(mGroupsOnServer[i].get(), mGroupsOnServer[i].Length(),
&bytesWritten);
hostInfoStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten);
}
hostInfoStream->Close();
mHostInfoHasChanged = false;
return NS_OK;
}
nsresult
nsNntpIncomingServer::LoadHostInfoFile()
{
nsresult rv;
// we haven't loaded it yet
mHostInfoLoaded = false;
rv = GetLocalPath(getter_AddRefs(mHostInfoFile));
if (NS_FAILED(rv)) return rv;
if (!mHostInfoFile) return NS_ERROR_FAILURE;
rv = mHostInfoFile->AppendNative(NS_LITERAL_CSTRING(HOSTINFO_FILE_NAME));
if (NS_FAILED(rv)) return rv;
bool exists;
rv = mHostInfoFile->Exists(&exists);
if (NS_FAILED(rv)) return rv;
// it is ok if the hostinfo.dat file does not exist.
if (!exists) return NS_OK;
nsCOMPtr<nsIInputStream> fileStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), mHostInfoFile);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv));
NS_ENSURE_SUCCESS(rv, rv);
bool more = true;
nsCString line;
while (more && NS_SUCCEEDED(rv))
{
rv = lineInputStream->ReadLine(line, &more);
if (line.IsEmpty())
continue;
HandleLine(line.get(), line.Length());
}
mHasSeenBeginGroups = false;
fileStream->Close();
return UpdateSubscribed();
}
NS_IMETHODIMP
nsNntpIncomingServer::StartPopulatingWithUri(nsIMsgWindow *aMsgWindow, bool aForceToServer, const char *uri)
{
nsresult rv = NS_OK;
#ifdef DEBUG_seth
printf("StartPopulatingWithUri(%s)\n",uri);
#endif
rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri);
NS_ENSURE_SUCCESS(rv,rv);
rv = StopPopulating(mMsgWindow);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SubscribeCleanup()
{
nsresult rv = NS_OK;
rv = ClearInner();
NS_ENSURE_SUCCESS(rv,rv);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::StartPopulating(nsIMsgWindow *aMsgWindow, bool aForceToServer, bool aGetOnlyNew)
{
nsresult rv;
mMsgWindow = aMsgWindow;
rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew);
NS_ENSURE_SUCCESS(rv,rv);
rv = SetDelimiter(NEWS_DELIMITER);
if (NS_FAILED(rv)) return rv;
rv = SetShowFullName(true);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsINntpService> nntpService = do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv,rv);
mHostInfoLoaded = false;
mVersion = INVALID_VERSION;
mGroupsOnServer.Clear();
mGetOnlyNew = aGetOnlyNew;
if (!aForceToServer) {
rv = LoadHostInfoFile();
if (NS_FAILED(rv)) return rv;
}
// mHostInfoLoaded can be false if we failed to load anything
if (aForceToServer || !mHostInfoLoaded || (mVersion != VALID_VERSION)) {
// set these to true, so when we are done and we call WriteHostInfoFile()
// we'll write out to hostinfo.dat
mHostInfoHasChanged = true;
mVersion = VALID_VERSION;
mGroupsOnServer.Clear();
rv = nntpService->GetListOfGroupsOnServer(this, aMsgWindow, aGetOnlyNew);
if (NS_FAILED(rv)) return rv;
}
else {
rv = StopPopulating(aMsgWindow);
if (NS_FAILED(rv)) return rv;
}
return NS_OK;
}
/**
* This method is the entry point for |nsNNTPProtocol| class. |aName| is now
* encoded in the serverside character encoding, but we need to handle
* newsgroup names in UTF-8 internally, So we convert |aName| to
* UTF-8 here for later use.
**/
NS_IMETHODIMP
nsNntpIncomingServer::AddNewsgroupToList(const char *aName)
{
nsresult rv;
nsAutoString newsgroupName;
nsAutoCString dataCharset;
rv = GetCharset(dataCharset);
NS_ENSURE_SUCCESS(rv,rv);
rv = nsMsgI18NConvertToUnicode(dataCharset.get(),
nsDependentCString(aName),
newsgroupName);
#ifdef DEBUG_jungshik
NS_ASSERTION(NS_SUCCEEDED(rv), "newsgroup name conversion failed");
#endif
if (NS_FAILED(rv)) {
CopyASCIItoUTF16(nsDependentCString(aName), newsgroupName);
}
rv = AddTo(NS_ConvertUTF16toUTF8(newsgroupName),
false, true, true);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetIncomingServer(nsIMsgIncomingServer *aServer)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->SetIncomingServer(aServer);
}
NS_IMETHODIMP
nsNntpIncomingServer::SetShowFullName(bool showFullName)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->SetShowFullName(showFullName);
}
nsresult
nsNntpIncomingServer::ClearInner()
{
nsresult rv = NS_OK;
if (mInner) {
rv = mInner->SetSubscribeListener(nullptr);
NS_ENSURE_SUCCESS(rv,rv);
rv = mInner->SetIncomingServer(nullptr);
NS_ENSURE_SUCCESS(rv,rv);
mInner = nullptr;
}
return NS_OK;
}
nsresult
nsNntpIncomingServer::EnsureInner()
{
nsresult rv = NS_OK;
if (mInner) return NS_OK;
mInner = do_CreateInstance(kSubscribableServerCID,&rv);
NS_ENSURE_SUCCESS(rv,rv);
if (!mInner) return NS_ERROR_FAILURE;
rv = SetIncomingServer(this);
NS_ENSURE_SUCCESS(rv,rv);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetDelimiter(char *aDelimiter)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->GetDelimiter(aDelimiter);
}
NS_IMETHODIMP
nsNntpIncomingServer::SetDelimiter(char aDelimiter)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->SetDelimiter(aDelimiter);
}
NS_IMETHODIMP
nsNntpIncomingServer::SetAsSubscribed(const nsACString &path)
{
mTempSubscribed.AppendElement(path);
if (mGetOnlyNew && (!mGroupsOnServer.Contains(path)))
return NS_OK;
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->SetAsSubscribed(path);
}
NS_IMETHODIMP
nsNntpIncomingServer::UpdateSubscribed()
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
mTempSubscribed.Clear();
uint32_t length = mSubscribedNewsgroups.Length();
for (uint32_t i = 0; i < length; ++i)
SetAsSubscribed(mSubscribedNewsgroups[i]);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::AddTo(const nsACString &aName, bool addAsSubscribed,
bool aSubscribable, bool changeIfExists)
{
NS_ASSERTION(MsgIsUTF8(aName), "Non-UTF-8 newsgroup name");
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
rv = AddGroupOnServer(aName);
NS_ENSURE_SUCCESS(rv,rv);
rv = mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
NS_ENSURE_SUCCESS(rv,rv);
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::StopPopulating(nsIMsgWindow *aMsgWindow)
{
nsresult rv = NS_OK;
nsCOMPtr<nsISubscribeListener> listener;
rv = GetSubscribeListener(getter_AddRefs(listener));
NS_ENSURE_SUCCESS(rv,rv);
if (!listener)
return NS_ERROR_FAILURE;
rv = listener->OnDonePopulating();
NS_ENSURE_SUCCESS(rv,rv);
rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
rv = mInner->StopPopulating(aMsgWindow);
NS_ENSURE_SUCCESS(rv,rv);
if (!mGetOnlyNew && !mHostInfoLoaded)
{
rv = WriteHostInfoFile();
NS_ENSURE_SUCCESS(rv,rv);
}
//xxx todo when do I set this to null?
//rv = ClearInner();
//NS_ENSURE_SUCCESS(rv,rv);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetSubscribeListener(nsISubscribeListener *aListener)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->SetSubscribeListener(aListener);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSubscribeListener(nsISubscribeListener **aListener)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->GetSubscribeListener(aListener);
}
NS_IMETHODIMP
nsNntpIncomingServer::Subscribe(const char16_t *aUnicharName)
{
return SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(aUnicharName));
}
NS_IMETHODIMP
nsNntpIncomingServer::Unsubscribe(const char16_t *aUnicharName)
{
NS_ENSURE_ARG_POINTER(aUnicharName);
nsresult rv;
nsCOMPtr <nsIMsgFolder> serverFolder;
rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
if (NS_FAILED(rv))
return rv;
if (!serverFolder)
return NS_ERROR_FAILURE;
// to handle non-ASCII newsgroup names, we store them internally as escaped.
// so we need to escape and encode the name, in order to find it.
nsAutoCString escapedName;
rv = NS_MsgEscapeEncodeURLPath(nsDependentString(aUnicharName), escapedName);
nsCOMPtr <nsIMsgFolder> newsgroupFolder;
rv = serverFolder->FindSubFolder(escapedName,
getter_AddRefs(newsgroupFolder));
if (NS_FAILED(rv))
return rv;
if (!newsgroupFolder)
return NS_ERROR_FAILURE;
rv = serverFolder->PropagateDelete(newsgroupFolder, true /* delete storage */, nullptr);
if (NS_FAILED(rv))
return rv;
// since we've unsubscribed to a newsgroup, the newsrc needs to be written out
rv = SetNewsrcHasChanged(true);
if (NS_FAILED(rv))
return rv;
return NS_OK;
}
nsresult
nsNntpIncomingServer::HandleLine(const char* line, uint32_t line_size)
{
NS_ASSERTION(line, "line is null");
if (!line)
return NS_OK;
// skip blank lines and comments
if (line[0] == '#' || line[0] == '\0')
return NS_OK;
// ###TODO - make this truly const, maybe pass in an nsCString &
if (mHasSeenBeginGroups) {
// v1 hostinfo files had additional data fields delimited by commas.
// with v2 hostinfo files, the additional data fields are removed.
char *commaPos = (char *) PL_strchr(line,',');
if (commaPos) *commaPos = 0;
// newsrc entries are all in UTF-8
#ifdef DEBUG_jungshik
NS_ASSERTION(MsgIsUTF8(nsDependentCString(line)), "newsrc line is not utf-8");
#endif
nsresult rv = AddTo(nsDependentCString(line), false, true, true);
NS_ASSERTION(NS_SUCCEEDED(rv),"failed to add line");
if (NS_SUCCEEDED(rv)) {
// since we've seen one group, we can claim we've loaded the
// hostinfo file
mHostInfoLoaded = true;
}
}
else {
if (PL_strncmp(line,"begingroups", 11) == 0) {
mHasSeenBeginGroups = true;
}
char*equalPos = (char *) PL_strchr(line, '=');
if (equalPos) {
*equalPos++ = '\0';
if (PL_strcmp(line, "lastgroupdate") == 0) {
mLastUpdatedTime = strtoul(equalPos, nullptr, 10);
} else if (PL_strcmp(line, "uniqueid") == 0) {
mUniqueId = strtol(equalPos, nullptr, 16);
} else if (PL_strcmp(line, "version") == 0) {
mVersion = strtol(equalPos, nullptr, 16);
}
}
}
return NS_OK;
}
nsresult
nsNntpIncomingServer::AddGroupOnServer(const nsACString &aName)
{
mGroupsOnServer.AppendElement(aName);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::AddNewsgroup(const nsAString &aName)
{
// handle duplicates?
mSubscribedNewsgroups.AppendElement(NS_ConvertUTF16toUTF8(aName));
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::RemoveNewsgroup(const nsAString &aName)
{
// handle duplicates?
mSubscribedNewsgroups.RemoveElement(NS_ConvertUTF16toUTF8(aName));
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetState(const nsACString &path, bool state,
bool *stateChanged)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
rv = mInner->SetState(path, state, stateChanged);
if (*stateChanged) {
if (state)
mTempSubscribed.AppendElement(path);
else
mTempSubscribed.RemoveElement(path);
}
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::HasChildren(const nsACString &path, bool *aHasChildren)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->HasChildren(path, aHasChildren);
}
NS_IMETHODIMP
nsNntpIncomingServer::IsSubscribed(const nsACString &path,
bool *aIsSubscribed)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->IsSubscribed(path, aIsSubscribed);
}
NS_IMETHODIMP
nsNntpIncomingServer::IsSubscribable(const nsACString &path,
bool *aIsSubscribable)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->IsSubscribable(path, aIsSubscribable);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetLeafName(const nsACString &path, nsAString &aLeafName)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->GetLeafName(path, aLeafName);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetFirstChildURI(const nsACString &path, nsACString &aResult)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->GetFirstChildURI(path, aResult);
}
NS_IMETHODIMP
nsNntpIncomingServer::GetChildren(const nsACString &aPath,
nsISimpleEnumerator **aResult)
{
nsresult rv = EnsureInner();
NS_ENSURE_SUCCESS(rv,rv);
return mInner->GetChildren(aPath, aResult);
}
NS_IMETHODIMP
nsNntpIncomingServer::CommitSubscribeChanges()
{
nsresult rv;
// we force the newrc to be dirty, so it will get written out when
// we call WriteNewsrcFile()
rv = SetNewsrcHasChanged(true);
NS_ENSURE_SUCCESS(rv,rv);
return WriteNewsrcFile();
}
NS_IMETHODIMP
nsNntpIncomingServer::ForgetPassword()
{
nsresult rv;
// clear password of root folder (for the news account)
nsCOMPtr<nsIMsgFolder> rootFolder;
rv = GetRootFolder(getter_AddRefs(rootFolder));
NS_ENSURE_SUCCESS(rv,rv);
if (!rootFolder) return NS_ERROR_FAILURE;
nsCOMPtr <nsIMsgNewsFolder> newsFolder = do_QueryInterface(rootFolder, &rv);
NS_ENSURE_SUCCESS(rv,rv);
if (!newsFolder) return NS_ERROR_FAILURE;
rv = newsFolder->ForgetAuthenticationCredentials();
NS_ENSURE_SUCCESS(rv,rv);
// clear password of all child folders
nsCOMPtr<nsISimpleEnumerator> subFolders;
rv = rootFolder->GetSubFolders(getter_AddRefs(subFolders));
NS_ENSURE_SUCCESS(rv,rv);
bool moreFolders = false;
nsresult return_rv = NS_OK;
while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
moreFolders) {
nsCOMPtr<nsISupports> child;
rv = subFolders->GetNext(getter_AddRefs(child));
if (NS_SUCCEEDED(rv) && child) {
newsFolder = do_QueryInterface(child, &rv);
if (NS_SUCCEEDED(rv) && newsFolder) {
rv = newsFolder->ForgetAuthenticationCredentials();
if (NS_FAILED(rv)) return_rv = rv;
}
else {
return_rv = NS_ERROR_FAILURE;
}
}
}
return return_rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSupportsExtensions(bool *aSupportsExtensions)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetSupportsExtensions(bool aSupportsExtensions)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::AddExtension(const char *extension)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::QueryExtension(const char *extension, bool *result)
{
#ifdef DEBUG_seth
printf("no extension support yet\n");
#endif
*result = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetPostingAllowed(bool *aPostingAllowed)
{
*aPostingAllowed = mPostingAllowed;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetPostingAllowed(bool aPostingAllowed)
{
mPostingAllowed = aPostingAllowed;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetLastUpdatedTime(uint32_t *aLastUpdatedTime)
{
*aLastUpdatedTime = mLastUpdatedTime;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetLastUpdatedTime(uint32_t aLastUpdatedTime)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::AddPropertyForGet(const char *name, const char *value)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::QueryPropertyForGet(const char *name, char **value)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::AddSearchableGroup(const nsAString &name)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::QuerySearchableGroup(const nsAString &name, bool *result)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::AddSearchableHeader(const char *name)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::QuerySearchableHeader(const char *name, bool *result)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::FindGroup(const nsACString &name, nsIMsgNewsFolder **result)
{
NS_ENSURE_ARG_POINTER(result);
nsresult rv;
nsCOMPtr <nsIMsgFolder> serverFolder;
rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
NS_ENSURE_SUCCESS(rv,rv);
if (!serverFolder) return NS_ERROR_FAILURE;
// Escape the name for using FindSubFolder
nsAutoCString escapedName;
rv = MsgEscapeString(name, nsINetUtil::ESCAPE_URL_PATH, escapedName);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr <nsIMsgFolder> subFolder;
rv = serverFolder->FindSubFolder(escapedName, getter_AddRefs(subFolder));
NS_ENSURE_SUCCESS(rv,rv);
if (!subFolder) return NS_ERROR_FAILURE;
rv = subFolder->QueryInterface(NS_GET_IID(nsIMsgNewsFolder), (void**)result);
NS_ENSURE_SUCCESS(rv,rv);
if (!*result) return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetFirstGroupNeedingExtraInfo(nsACString &result)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetGroupNeedsExtraInfo(const nsACString &name,
bool needsExtraInfo)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::GroupNotFound(nsIMsgWindow *aMsgWindow,
const nsAString &aName, bool aOpening)
{
nsresult rv;
nsCOMPtr <nsIPrompt> prompt;
if (aMsgWindow) {
rv = aMsgWindow->GetPromptDialog(getter_AddRefs(prompt));
NS_ASSERTION(NS_SUCCEEDED(rv), "no prompt from the msg window");
}
if (!prompt) {
nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
rv = wwatch->GetNewPrompter(nullptr, getter_AddRefs(prompt));
NS_ENSURE_SUCCESS(rv,rv);
}
nsCOMPtr <nsIStringBundleService> bundleService =
mozilla::services::GetStringBundleService();
NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
nsCOMPtr <nsIStringBundle> bundle;
rv = bundleService->CreateBundle(NEWS_MSGS_URL, getter_AddRefs(bundle));
NS_ENSURE_SUCCESS(rv,rv);
nsCString hostname;
rv = GetRealHostName(hostname);
NS_ENSURE_SUCCESS(rv,rv);
NS_ConvertUTF8toUTF16 hostStr(hostname);
nsString groupName(aName);
const char16_t *formatStrings[2] = { groupName.get(), hostStr.get() };
nsString confirmText;
rv = bundle->FormatStringFromName(
MOZ_UTF16("autoUnsubscribeText"),
formatStrings, 2,
getter_Copies(confirmText));
NS_ENSURE_SUCCESS(rv,rv);
bool confirmResult = false;
rv = prompt->Confirm(nullptr, confirmText.get(), &confirmResult);
NS_ENSURE_SUCCESS(rv,rv);
if (confirmResult) {
rv = Unsubscribe(groupName.get());
NS_ENSURE_SUCCESS(rv,rv);
}
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetPrettyNameForGroup(const nsAString &name,
const nsAString &prettyName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCanSearchMessages(bool *canSearchMessages)
{
NS_ENSURE_ARG_POINTER(canSearchMessages);
*canSearchMessages = true;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetOfflineSupportLevel(int32_t *aSupportLevel)
{
NS_ENSURE_ARG_POINTER(aSupportLevel);
nsresult rv;
rv = GetIntValue("offline_support_level", aSupportLevel);
if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv;
// set default value
*aSupportLevel = OFFLINE_SUPPORT_LEVEL_EXTENDED;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetDefaultCopiesAndFoldersPrefsToServer(bool *aCopiesAndFoldersOnServer)
{
NS_ENSURE_ARG_POINTER(aCopiesAndFoldersOnServer);
/**
* When a news account is created, the copies and folder prefs for the
* associated identity don't point to folders on the server.
* This makes sense, since there is no "Drafts" folder on a news server.
* They'll point to the ones on "Local Folders"
*/
*aCopiesAndFoldersOnServer = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCanCreateFoldersOnServer(bool *aCanCreateFoldersOnServer)
{
NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
// No folder creation on news servers. Return false.
*aCanCreateFoldersOnServer = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetSearchValue(const nsAString &aSearchValue)
{
nsCString searchValue = NS_ConvertUTF16toUTF8(aSearchValue);
MsgCompressWhitespace(searchValue);
if (mTree) {
mTree->BeginUpdateBatch();
mTree->RowCountChanged(0, -mSubscribeSearchResult.Length());
}
nsTArray<nsCString> searchStringParts;
if (!searchValue.IsEmpty())
ParseString(searchValue, ' ', searchStringParts);
mSubscribeSearchResult.Clear();
uint32_t length = mGroupsOnServer.Length();
for (uint32_t i = 0; i < length; i++)
{
// check that all parts of the search string occur
bool found = true;
for (uint32_t j = 0; j < searchStringParts.Length(); ++j) {
if (MsgFind(mGroupsOnServer[i], searchStringParts[j], true, 0) == kNotFound) {
found = false;
break;
}
}
if (found)
mSubscribeSearchResult.AppendElement(mGroupsOnServer[i]);
}
nsCStringLowerCaseComparator comparator;
mSubscribeSearchResult.Sort(comparator);
if (mTree)
{
mTree->RowCountChanged(0, mSubscribeSearchResult.Length());
mTree->EndUpdateBatch();
}
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSupportsSubscribeSearch(bool *retVal)
{
*retVal = true;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetRowCount(int32_t *aRowCount)
{
*aRowCount = mSubscribeSearchResult.Length();
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSelection(nsITreeSelection * *aSelection)
{
*aSelection = mTreeSelection;
NS_IF_ADDREF(*aSelection);
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetSelection(nsITreeSelection * aSelection)
{
mTreeSelection = aSelection;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetRowProperties(int32_t index, nsAString& properties)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties)
{
if (!IsValidRow(row))
return NS_ERROR_UNEXPECTED;
NS_ENSURE_ARG_POINTER(col);
const char16_t* colID;
col->GetIdConst(&colID);
if (colID[0] == 's') {
// if <name> is in our temporary list of subscribed groups
// add the "subscribed" property so the check mark shows up
// in the "subscribedCol"
if (mSearchResultSortDescending)
row = mSubscribeSearchResult.Length() - 1 - row;
if (mTempSubscribed.Contains(mSubscribeSearchResult.ElementAt(row))) {
properties.AssignLiteral("subscribed");
}
}
else if (colID[0] == 'n') {
// add the "nntp" property to the "nameCol"
// so we get the news folder icon in the search view
properties.AssignLiteral("nntp");
}
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetColumnProperties(nsITreeColumn* col, nsAString& properties)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsContainer(int32_t index, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsContainerOpen(int32_t index, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsContainerEmpty(int32_t index, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsSeparator(int32_t index, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsSorted(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::CanDrop(int32_t index,
int32_t orientation,
nsIDOMDataTransfer *dataTransfer,
bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::Drop(int32_t row,
int32_t orientation,
nsIDOMDataTransfer *dataTransfer)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetParentIndex(int32_t rowIndex, int32_t *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetLevel(int32_t index, int32_t *_retval)
{
*_retval = 0;
return NS_OK;
}
bool
nsNntpIncomingServer::IsValidRow(int32_t row)
{
return ((row >= 0) && (row < (int32_t)mSubscribeSearchResult.Length()));
}
NS_IMETHODIMP
nsNntpIncomingServer::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
{
if (!IsValidRow(row))
return NS_ERROR_UNEXPECTED;
NS_ENSURE_ARG_POINTER(col);
const char16_t* colID;
col->GetIdConst(&colID);
nsresult rv = NS_OK;
if (colID[0] == 'n') {
nsAutoCString str;
if (mSearchResultSortDescending)
row = mSubscribeSearchResult.Length() - 1 - row;
// some servers have newsgroup names that are non ASCII. we store
// those as escaped. unescape here so the UI is consistent
rv = NS_MsgDecodeUnescapeURLPath(mSubscribeSearchResult.ElementAt(row), _retval);
}
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetTree(nsITreeBoxObject *tree)
{
mTree = tree;
if (!tree)
return NS_OK;
nsCOMPtr<nsITreeColumns> cols;
tree->GetColumns(getter_AddRefs(cols));
if (!cols)
return NS_OK;
nsCOMPtr<nsITreeColumn> col;
cols->GetKeyColumn(getter_AddRefs(col));
if (!col)
return NS_OK;
nsCOMPtr<nsIDOMElement> element;
col->GetElement(getter_AddRefs(element));
if (!element)
return NS_OK;
nsAutoString dir;
element->GetAttribute(NS_LITERAL_STRING("sortDirection"), dir);
mSearchResultSortDescending = dir.EqualsLiteral("descending");
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::ToggleOpenState(int32_t index)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::CycleHeader(nsITreeColumn* col)
{
NS_ENSURE_ARG_POINTER(col);
bool cycler;
col->GetCycler(&cycler);
if (!cycler) {
NS_NAMED_LITERAL_STRING(dir, "sortDirection");
nsCOMPtr<nsIDOMElement> element;
col->GetElement(getter_AddRefs(element));
mSearchResultSortDescending = !mSearchResultSortDescending;
element->SetAttribute(dir, mSearchResultSortDescending ?
NS_LITERAL_STRING("descending") : NS_LITERAL_STRING("ascending"));
mTree->Invalidate();
}
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SelectionChanged()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::CycleCell(int32_t row, nsITreeColumn* col)
{
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval)
{
*_retval = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::PerformAction(const char16_t *action)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::PerformActionOnRow(const char16_t *action, int32_t row)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetCanFileMessagesOnServer(bool *aCanFileMessagesOnServer)
{
NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
// No folder creation on news servers. Return false.
*aCanFileMessagesOnServer = false;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetFilterScope(nsMsgSearchScopeValue *filterScope)
{
NS_ENSURE_ARG_POINTER(filterScope);
*filterScope = nsMsgSearchScope::newsFilter;
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSearchScope(nsMsgSearchScopeValue *searchScope)
{
NS_ENSURE_ARG_POINTER(searchScope);
if (WeAreOffline()) {
// This value is set to the localNewsBody scope to be compatible with
// the legacy default value.
*searchScope = nsMsgSearchScope::localNewsBody;
}
else {
*searchScope = nsMsgSearchScope::news;
}
return NS_OK;
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSocketType(int32_t *aSocketType)
{
NS_ENSURE_ARG_POINTER(aSocketType);
if (!mPrefBranch)
return NS_ERROR_NOT_INITIALIZED;
nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType);
if (NS_FAILED(rv))
{
if (!mDefPrefBranch)
return NS_ERROR_NOT_INITIALIZED;
rv = mDefPrefBranch->GetIntPref("socketType", aSocketType);
if (NS_FAILED(rv))
*aSocketType = nsMsgSocketType::plain;
}
// nsMsgIncomingServer::GetSocketType migrates old isSecure to socketType
// style for mail. Unfortunately, a bug caused news socketType 0 to be stored
// in the prefs even for isSecure true, so the migration wouldn't happen :(
// Now that we know the socket, make sure isSecure true + socketType 0
// doesn't mix. Migrate if that's the case here.
if (*aSocketType == nsMsgSocketType::plain)
{
bool isSecure = false;
nsresult rv2 = mPrefBranch->GetBoolPref("isSecure", &isSecure);
if (NS_SUCCEEDED(rv2) && isSecure)
{
*aSocketType = nsMsgSocketType::SSL;
// Don't call virtual method in case overrides call GetSocketType.
nsMsgIncomingServer::SetSocketType(*aSocketType);
}
}
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::SetSocketType(int32_t aSocketType)
{
if (!mPrefBranch)
return NS_ERROR_NOT_INITIALIZED;
nsresult rv = nsMsgIncomingServer::SetSocketType(aSocketType);
if (NS_SUCCEEDED(rv))
{
bool isSecure = false;
if (NS_SUCCEEDED(mPrefBranch->GetBoolPref("isSecure", &isSecure)))
{
// Must keep isSecure in sync since we migrate based on it... if it's set.
rv = mPrefBranch->SetBoolPref("isSecure",
aSocketType == nsMsgSocketType::SSL);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return rv;
}
NS_IMETHODIMP
nsNntpIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
const nsACString& newName,
bool hostnameChanged)
{
nsresult rv;
// 1. Do common things in the base class.
rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName, hostnameChanged);
NS_ENSURE_SUCCESS(rv,rv);
// 2. Remove file hostinfo.dat so that the new subscribe
// list will be reloaded from the new server.
nsCOMPtr <nsIFile> hostInfoFile;
rv = GetLocalPath(getter_AddRefs(hostInfoFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = hostInfoFile->AppendNative(NS_LITERAL_CSTRING(HOSTINFO_FILE_NAME));
NS_ENSURE_SUCCESS(rv, rv);
hostInfoFile->Remove(false);
// 3.Unsubscribe and then subscribe the existing groups to clean up the article numbers
// in the rc file (this is because the old and new servers may maintain different
// numbers for the same articles if both servers handle the same groups).
nsCOMPtr <nsIMsgFolder> serverFolder;
rv = GetRootMsgFolder(getter_AddRefs(serverFolder));
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsISimpleEnumerator> subFolders;
rv = serverFolder->GetSubFolders(getter_AddRefs(subFolders));
NS_ENSURE_SUCCESS(rv,rv);
nsTArray<nsString> groupList;
nsString folderName;
// Prepare the group list
bool hasMore;
while (NS_SUCCEEDED(subFolders->HasMoreElements(&hasMore)) && hasMore)
{
nsCOMPtr<nsISupports> item;
subFolders->GetNext(getter_AddRefs(item));
nsCOMPtr<nsIMsgFolder> newsgroupFolder(do_QueryInterface(item));
if (!newsgroupFolder)
continue;
rv = newsgroupFolder->GetName(folderName);
NS_ENSURE_SUCCESS(rv,rv);
groupList.AppendElement(folderName);
}
// If nothing subscribed then we're done.
if (groupList.Length() == 0)
return NS_OK;
// Now unsubscribe & subscribe.
uint32_t i;
uint32_t cnt = groupList.Length();
nsAutoCString cname;
for (i = 0; i < cnt; i++)
{
// unsubscribe.
rv = Unsubscribe(groupList[i].get());
NS_ENSURE_SUCCESS(rv,rv);
}
for (i = 0; i < cnt; i++)
{
// subscribe.
rv = SubscribeToNewsgroup(NS_ConvertUTF16toUTF8(groupList[i]));
NS_ENSURE_SUCCESS(rv,rv);
}
// Force updating the rc file.
return CommitSubscribeChanges();
}
NS_IMETHODIMP
nsNntpIncomingServer::GetSortOrder(int32_t* aSortOrder)
{
NS_ENSURE_ARG_POINTER(aSortOrder);
*aSortOrder = 500000000;
return NS_OK;
}