--- a/mailnews/news/public/nsINntpIncomingServer.idl
+++ b/mailnews/news/public/nsINntpIncomingServer.idl
@@ -15,16 +15,17 @@
* 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):
+ * Joshua Cranmer <Pidgeot18@gmail.com>
*
* 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
@@ -35,20 +36,22 @@
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
interface nsILocalFile;
interface nsIMsgNewsFolder;
interface nsINNTPProtocol;
+interface nsNNTPProtocol;
+interface nsIChannel;
interface nsIURI;
interface nsIMsgWindow;
-[scriptable, uuid(325e8bac-7640-4263-a735-958140dda20b)]
+[scriptable, uuid(113e9e21-3313-4e73-83f1-0d675251b607)]
interface nsINntpIncomingServer : nsISupports {
/* the on-disk path to the newsrc file for this server */
attribute nsILocalFile newsrcFilePath;
/* the newsrc root path (the directories all the newsrc files live) */
attribute nsILocalFile newsrcRootPath;
/* ask the user before downloading more than maxArticles? */
@@ -74,30 +77,70 @@ interface nsINntpIncomingServer : nsISup
/* the server keeps track of all the newsgroups we are subscribed to */
void addNewsgroup(in AString name);
void removeNewsgroup(in AString name);
void writeNewsrcFile();
attribute boolean newsrcHasChanged;
+ /**
+ * The maximum number of connections to make to the server.
+ *
+ * This preference (internally max_cached_connections) controls how many
+ * connections we can make. A negative connection count is treated as only
+ * one connection, while 0 (the default) loads the default number of
+ * connections, presently 2.
+ */
attribute long maximumConnectionsNumber;
readonly attribute long numGroupsNeedingCounts;
readonly attribute nsISupports firstGroupNeedingCounts;
void displaySubscribedGroup(in nsIMsgNewsFolder msgFolder,
in long firstMessage, in long lastMessage,
in long totalMessages);
- void getNntpConnection(in nsIURI url, in nsIMsgWindow window,
- out nsINNTPProtocol aNntpConnection);
+ /**
+ * Get a new NNTP channel to run the URI.
+ *
+ * If the server has used up all of its connections, this will place the URI
+ * in the queue to be run when one is freed.
+ *
+ * @param uri The URI to run.
+ * @param window The standard message window object.
+ */
+ nsIChannel getNntpChannel(in nsIURI uri, in nsIMsgWindow window);
+ /**
+ * Enqueues a URI to be run when we have a free connection.
+ *
+ * If there is one already free, it will be immediately started.
+ *
+ * @param uri The URI to run.
+ * @param window The standard message window object.
+ * @param consumer An argument to be passed to nsINNTPProtocol:LoadNewUrl.
+ */
+ void loadNewsUrl(in nsIURI uri, in nsIMsgWindow window,
+ in nsISupports consumer);
+
+ /**
+ * Remove a connection from our connection cache.
+ *
+ * @param aNntpConnection The connection to be removed.
+ */
void removeConnection(in nsINNTPProtocol aNntpConnection);
+ /**
+ * Load the next URI in the queue to the given connection.
+ *
+ * @param aNntpConnection The newly-freed connection.
+ */
+ [noscript] void prepareForNextUrl(in nsNNTPProtocol aNntpConnection);
+
/* used for auto subscribing */
boolean containsNewsgroup(in AUTF8String name);
void subscribeToNewsgroup(in AUTF8String name);
/* used for the subscribe dialog.
name is encoded in |charset| (attribute declared above) */
[noscript] void addNewsgroupToList(in string name);
--- a/mailnews/news/src/Makefile.in
+++ b/mailnews/news/src/Makefile.in
@@ -75,16 +75,17 @@ CPPSRCS = \
nsNNTPArticleList.cpp \
nsNNTPNewsgroupList.cpp \
nsNNTPNewsgroupPost.cpp \
nsNNTPProtocol.cpp \
nsNntpUrl.cpp \
nsNntpService.cpp \
nsNewsFolder.cpp \
nsNntpIncomingServer.cpp \
+ nsNntpMockChannel.cpp \
nsNewsUtils.cpp \
nsNewsDownloadDialogArgs.cpp \
nsNewsDownloader.cpp \
$(NULL)
EXTRA_DSO_LDOPTS = \
$(MOZ_COMPONENT_LIBS) \
$(MOZ_UNICHARUTIL_LIBS) \
--- a/mailnews/news/src/nsNNTPProtocol.cpp
+++ b/mailnews/news/src/nsNNTPProtocol.cpp
@@ -5251,18 +5251,21 @@ nsresult nsNNTPProtocol::ProcessProtocol
* a new connection. If it was not new lets start it over
* again. But only if we didn't have any successful protocol
* dialog at all.
*/
FinishMemCacheEntry(PR_FALSE); // cleanup mem cache entry
if (m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NOTFOUND && m_responseCode != MK_NNTP_RESPONSE_ARTICLE_NONEXIST)
return CloseConnection();
case NEWS_FREE:
- m_lastActiveTimeStamp = PR_Now(); // remmeber when we last used this connection.
- return CleanupAfterRunningUrl();
+ // Remember when we last used this connection
+ m_lastActiveTimeStamp = PR_Now();
+ CleanupAfterRunningUrl();
+ if (m_nntpServer)
+ m_nntpServer->PrepareForNextUrl(this);
case NEWS_FINISHED:
return NS_OK;
break;
default:
/* big error */
return NS_ERROR_FAILURE;
} // end switch
--- a/mailnews/news/src/nsNntpIncomingServer.cpp
+++ b/mailnews/news/src/nsNntpIncomingServer.cpp
@@ -512,79 +512,147 @@ nsNntpIncomingServer::CreateProtocolInst
if (NS_SUCCEEDED(rv) && *aNntpConnection)
mConnectionCache.AppendObject(*aNntpConnection);
return rv;
}
/* By default, allow the user to open at most this many connections to one news host */
#define kMaxConnectionsPerHost 2
-NS_IMETHODIMP
+nsresult
nsNntpIncomingServer::GetNntpConnection(nsIURI * aUri, nsIMsgWindow *aMsgWindow,
- nsINNTPProtocol ** aNntpConnection)
+ nsINNTPProtocol ** aNntpConnection)
{
- nsresult rv = NS_OK;
- nsCOMPtr<nsINNTPProtocol> connection;
- nsCOMPtr<nsINNTPProtocol> freeConnection;
- PRBool isBusy = PR_TRUE;
-
-
+ // 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.
PRInt32 maxConnections = kMaxConnectionsPerHost;
- rv = GetMaximumConnectionsNumber(&maxConnections);
+ nsresult rv = GetMaximumConnectionsNumber(&maxConnections);
if (NS_FAILED(rv) || maxConnections == 0)
{
maxConnections = kMaxConnectionsPerHost;
- rv = SetMaximumConnectionsNumber(maxConnections);
+ SetMaximumConnectionsNumber(maxConnections);
}
else if (maxConnections < 1)
- { // forced to use at least 1
+ {
maxConnections = 1;
- rv = SetMaximumConnectionsNumber(maxConnections);
+ SetMaximumConnectionsNumber(maxConnections);
}
- *aNntpConnection = nsnull;
- // iterate through the connection cache for a connection that can handle this url.
+ // Find a non-busy connection
+ nsCOMPtr<nsINNTPProtocol> connection;
PRInt32 cnt = mConnectionCache.Count();
-
-#ifdef DEBUG_seth
- printf("XXX there are %d nntp connections in the conn cache.\n", (int)cnt);
-#endif
- for (PRInt32 i = 0; i < cnt && isBusy; i++)
+ for (PRInt32 i = 0; i < cnt; i++)
{
connection = mConnectionCache[i];
if (connection)
- rv = connection->GetIsBusy(&isBusy);
- if (NS_FAILED(rv))
{
- connection = nsnull;
- continue;
- }
- if (!freeConnection && !isBusy && connection)
- {
- freeConnection = connection;
+ PRBool isBusy;
+ connection->GetIsBusy(&isBusy);
+ if (!isBusy)
+ break;
+ connection = nsnull;
}
}
- if (ConnectionTimeOut(freeConnection))
- freeConnection = nsnull;
+ if (ConnectionTimeOut(connection))
+ {
+ connection = nsnull;
+ // We have one less connection, since we closed this one.
+ --cnt;
+ }
- // if we got here and we have a connection, then we should return it!
- if (!isBusy && freeConnection)
+ if (connection)
+ {
+ NS_IF_ADDREF(*aNntpConnection = connection);
+ connection->SetIsCachedConnection(PR_TRUE);
+ }
+ else if (cnt < maxConnections)
{
- *aNntpConnection = freeConnection;
- freeConnection->SetIsCachedConnection(PR_TRUE);
- NS_IF_ADDREF(*aNntpConnection);
+ // We have room for another connection. Create this connection and return
+ // it to the caller.
+ rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
}
- else // have no queueing mechanism - just create the protocol instance.
+ else
{
- rv = CreateProtocolInstance(aNntpConnection, aUri, aMsgWindow);
+ // We maxed out our connection count. The caller must therefore enqueue the
+ // call.
+ *aNntpConnection = nsnull;
+ return NS_OK;
}
- return rv;
+
+ // 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;
--- a/mailnews/news/src/nsNntpIncomingServer.h
+++ b/mailnews/news/src/nsNntpIncomingServer.h
@@ -52,16 +52,19 @@
#include "nsISubscribableServer.h"
#include "nsITimer.h"
#include "nsILocalFile.h"
#include "nsITreeView.h"
#include "nsITreeSelection.h"
#include "nsIAtom.h"
#include "nsCOMArray.h"
+#include "nsNntpMockChannel.h"
+#include "nsAutoPtr.h"
+
class nsINntpUrl;
class nsIMsgMailNewsUrl;
/* get some implementation from nsMsgIncomingServer */
class nsNntpIncomingServer : public nsMsgIncomingServer,
public nsINntpIncomingServer,
public nsIUrlListener,
public nsISubscribableServer,
@@ -100,20 +103,24 @@ public:
NS_IMETHOD GetSocketType(PRInt32 *aSocketType); // override nsMsgIncomingServer impl
NS_IMETHOD SetSocketType(PRInt32 aSocketType); // override nsMsgIncomingServer impl
nsresult AppendIfSearchMatch(nsCString& newsgroupName);
protected:
virtual nsresult CreateRootFolderFromUri(const nsCString &serverUri,
nsIMsgFolder **rootFolder);
- nsresult CreateProtocolInstance(nsINNTPProtocol ** aNntpConnection, nsIURI *url,
- nsIMsgWindow *window);
+ nsresult GetNntpConnection(nsIURI *url, nsIMsgWindow *window,
+ nsINNTPProtocol **aNntpConnection);
+ nsresult CreateProtocolInstance(nsINNTPProtocol **aNntpConnection,
+ nsIURI *url, nsIMsgWindow *window);
PRBool ConnectionTimeOut(nsINNTPProtocol* aNntpConnection);
nsCOMArray<nsINNTPProtocol> mConnectionCache;
+ nsTArray<nsRefPtr<nsNntpMockChannel> > m_queuedChannels;
+
NS_IMETHOD GetServerRequiresPasswordForBiff(PRBool *aServerRequiresPasswordForBiff);
nsresult SetupNewsrcSaveTimer();
static void OnNewsrcSaveTimer(nsITimer *timer, void *voidIncomingServer);
void WriteLine(nsIOutputStream *stream, nsCString &str);
private:
nsCStringArray mSubscribedNewsgroups;
nsCStringArray mGroupsOnServer;
new file mode 100644
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.cpp
@@ -0,0 +1,322 @@
+/* -*- 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
+ * Mozilla Messaging Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Joshua Cranmer <Pidgeot18@gmail.com>
+ *
+ * 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 "nsNntpMockChannel.h"
+
+#include "msgCore.h"
+#include "nsNNTPProtocol.h"
+#include "nsNetUtil.h"
+
+NS_IMPL_THREADSAFE_ISUPPORTS2(nsNntpMockChannel, nsIChannel, nsIRequest)
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow)
+: m_url(aUri),
+ m_msgWindow(aMsgWindow),
+ m_channelState(CHANNEL_UNOPENED),
+ m_protocol(nsnull),
+ m_cancelStatus(NS_OK),
+ m_loadFlags(0),
+ m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer)
+: m_url(aUri),
+ m_context(aConsumer),
+ m_msgWindow(aMsgWindow),
+ m_channelState(CHANNEL_OPEN_WITH_LOAD),
+ m_protocol(nsnull),
+ m_cancelStatus(NS_OK),
+ m_loadFlags(0),
+ m_contentLength(-1)
+{
+}
+
+nsNntpMockChannel::~nsNntpMockChannel()
+{
+}
+
+#define FORWARD_CALL(function, argument) \
+ if (m_protocol) \
+ return m_protocol->function(argument);
+
+////////////////////////
+// nsIRequest methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetName(nsACString &result)
+{
+ FORWARD_CALL(GetName, result)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::IsPending(PRBool *result)
+{
+ FORWARD_CALL(IsPending, result)
+ // We haven't been loaded yet, so we're still pending.
+ *result = PR_TRUE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetStatus(nsresult *status)
+{
+ FORWARD_CALL(GetStatus, status)
+ *status = m_cancelStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Cancel(nsresult status)
+{
+ m_cancelStatus = status;
+ m_channelState = CHANNEL_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Suspend()
+{
+ NS_NOTREACHED("nsNntpMockChannel::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::Resume()
+{
+ NS_NOTREACHED("nsNntpMockChannel::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ FORWARD_CALL(SetLoadGroup, aLoadGroup)
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ FORWARD_CALL(GetLoadGroup, aLoadGroup)
+ NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ FORWARD_CALL(GetLoadFlags, aLoadFlags)
+ *aLoadFlags = m_loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ FORWARD_CALL(SetLoadFlags, aLoadFlags)
+ m_loadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+////////////////////////
+// nsIChannel methods //
+////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::GetOriginalURI(nsIURI **aURI)
+{
+ FORWARD_CALL(GetOriginalURI, aURI)
+ NS_IF_ADDREF(*aURI = m_originalUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOriginalURI(nsIURI *aURI)
+{
+ FORWARD_CALL(SetOriginalURI, aURI)
+ m_originalUrl = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetOwner(nsISupports **owner)
+{
+ FORWARD_CALL(GetOwner, owner)
+ NS_IF_ADDREF(*owner = m_owner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetOwner(nsISupports *aOwner)
+{
+ FORWARD_CALL(SetOwner, aOwner)
+ m_owner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
+{
+ FORWARD_CALL(GetNotificationCallbacks, callbacks)
+ NS_IF_ADDREF(*callbacks = m_notificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ FORWARD_CALL(SetNotificationCallbacks, aCallbacks)
+ m_notificationCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetSecurityInfo(nsISupports **securityInfo)
+{
+ FORWARD_CALL(GetSecurityInfo, securityInfo)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentType(nsACString &aContentType)
+{
+ FORWARD_CALL(GetContentType, aContentType)
+ aContentType = m_contentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentType(const nsACString &aContentType)
+{
+ FORWARD_CALL(SetContentType, aContentType)
+ return NS_ParseContentType(aContentType, m_contentType, m_contentCharset);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentCharset(nsACString &aCharset)
+{
+ FORWARD_CALL(GetContentCharset, aCharset)
+ aCharset = m_contentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::SetContentCharset(const nsACString &aCharset)
+{
+ FORWARD_CALL(SetContentCharset, aCharset)
+ m_contentCharset = aCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNntpMockChannel::GetContentLength(PRInt32 *length)
+{
+ FORWARD_CALL(GetContentLength, length)
+ *length = m_contentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNntpMockChannel::SetContentLength(PRInt32 aLength)
+{
+ FORWARD_CALL(SetContentLength, aLength)
+ m_contentLength = aLength;
+ return NS_OK;
+}
+
+////////////////////////////////////////
+// nsIChannel and nsNNTPProtocol glue //
+////////////////////////////////////////
+
+NS_IMETHODIMP nsNntpMockChannel::Open(nsIInputStream **_retval)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsNntpMockChannel::AsyncOpen(nsIStreamListener *listener,
+ nsISupports *ctxt)
+{
+ m_channelState = CHANNEL_OPEN_WITH_ASYNC;
+ m_channelListener = listener;
+ m_context = ctxt;
+ return NS_OK;
+}
+
+nsresult
+nsNntpMockChannel::AttachNNTPConnection(nsNNTPProtocol &protocol)
+{
+ // First things first. Were we canceled? If so, tell the protocol.
+ if (m_channelState == CHANNEL_CLOSED || m_channelState == CHANNEL_UNOPENED)
+ return NS_ERROR_FAILURE;
+
+ // We're going to active the protocol now. Note that if the user has
+ // interacted with us through the nsIChannel API, we need to pass it to the
+ // protocol instance. We also need to initialize it. For best results, we're
+ // going to initialize the code and then set the values.
+ nsresult rv = protocol.Initialize(m_url, m_msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Variable fun
+ protocol.SetLoadGroup(m_loadGroup);
+ protocol.SetLoadFlags(m_loadFlags);
+ protocol.SetOriginalURI(m_originalUrl);
+ protocol.SetOwner(m_owner);
+ protocol.SetNotificationCallbacks(m_notificationCallbacks);
+ protocol.SetContentType(m_contentType);
+
+ // Now that we've set up the protocol, attach it to ourselves so that we can
+ // forward all future calls to the protocol instance. We do not refcount this
+ // instance, since the server will be owning all of them: once the server
+ // releases its reference, the protocol instance is no longer usable anyways.
+ m_protocol = &protocol;
+
+ switch (m_channelState)
+ {
+ case CHANNEL_OPEN_WITH_LOAD:
+ rv = protocol.LoadNewsUrl(m_url, m_context);
+ break;
+ case CHANNEL_OPEN_WITH_ASYNC:
+ rv = protocol.AsyncOpen(m_channelListener, m_context);
+ break;
+ default:
+ NS_NOTREACHED("Unknown channel state got us here.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we fail, that means that loading the NNTP protocol failed. Since we
+ // essentially promised that we would load (by virtue of returning NS_OK to
+ // AsyncOpen), we must now tell our listener the bad news.
+ if (NS_FAILED(rv) && m_channelListener)
+ m_channelListener->OnStopRequest(this, m_context, rv);
+
+ // Returning a failure code is our way of telling the server that this URL
+ // isn't going to run, so it should give the connection the next URL in the
+ // queue.
+ return rv;
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/news/src/nsNntpMockChannel.h
@@ -0,0 +1,97 @@
+/* -*- 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
+ * Mozilla Messaging Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Joshua Cranmer <Pidgeot18@gmail.com>
+ *
+ * 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 ***** */
+
+#ifndef nsNntpMockChannel_h___
+#define nsNntpMockChannel_h___
+
+#include "nsIChannel.h"
+#include "nsIMsgWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+
+class nsNNTPProtocol;
+
+class nsNntpMockChannel : public nsIChannel
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+
+ nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow);
+ nsNntpMockChannel(nsIURI *aUri, nsIMsgWindow *aMsgWindow,
+ nsISupports *aConsumer);
+ virtual ~nsNntpMockChannel();
+
+ nsresult AttachNNTPConnection(nsNNTPProtocol &protocol);
+protected:
+ // The URL we will be running
+ nsCOMPtr<nsIURI> m_url;
+
+ // Variables for arguments to pass into the opening phase.
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ nsCOMPtr<nsISupports> m_context;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+
+ // The state we're in
+ enum
+ {
+ CHANNEL_UNOPENED, //!< No one bothered to open this yet
+ CHANNEL_OPEN_WITH_LOAD, //!< We should open with LoadNewsUrl
+ CHANNEL_OPEN_WITH_ASYNC, //!< We should open with AsyncOpen
+ CHANNEL_CLOSED //!< We were closed and should not open
+ } m_channelState;
+
+ // The protocol instance
+ nsNNTPProtocol *m_protocol;
+
+ // Temporary variables for accessors before we get to the actual instance.
+ nsresult m_cancelStatus;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsLoadFlags m_loadFlags;
+
+ nsCOMPtr<nsIURI> m_originalUrl;
+ nsCOMPtr<nsISupports> m_owner;
+ nsCOMPtr<nsIInterfaceRequestor> m_notificationCallbacks;
+ nsCString m_contentType;
+ nsCString m_contentCharset;
+ PRInt32 m_contentLength;
+};
+
+#endif // nsNntpMockChannel_h___
--- a/mailnews/news/src/nsNntpService.cpp
+++ b/mailnews/news/src/nsNntpService.cpp
@@ -41,17 +41,17 @@
*
* ***** END LICENSE BLOCK ***** */
#include "msgCore.h" // precompiled header...
#include "nntpCore.h"
#include "nsISupportsObsolete.h"
#include "nsMsgNewsCID.h"
#include "nsINntpUrl.h"
-#include "nsNNTPProtocol.h"
+#include "nsIMsgNewsFolder.h"
#include "nsNNTPNewsgroupPost.h"
#include "nsIMsgMailSession.h"
#include "nsIMsgIdentity.h"
#include "nsString.h"
#include "nsReadableUtils.h"
#include "nsNewsUtils.h"
#include "nsNewsDatabase.h"
#include "nsMsgDBCID.h"
@@ -1082,17 +1082,17 @@ nsNntpService::CreateNewsAccount(const c
// Now save the new acct info to pref file.
rv = accountManager->SaveAccountInfo();
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
nsresult
-nsNntpService::GetProtocolForUri(nsIURI *aUri, nsIMsgWindow *aMsgWindow, nsINNTPProtocol **aProtocol)
+nsNntpService::GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aServer)
{
nsCAutoString hostName;
nsCAutoString scheme;
nsCAutoString path;
PRInt32 port = 0;
nsresult rv;
rv = aUri->GetAsciiHost(hostName);
@@ -1180,16 +1180,18 @@ nsNntpService::GetProtocolForUri(nsIURI
if (NS_FAILED(rv)) return rv;
if (!server) return NS_ERROR_FAILURE;
nntpServer = do_QueryInterface(server, &rv);
if (!nntpServer || NS_FAILED(rv))
return rv;
+ NS_IF_ADDREF(*aServer = nntpServer);
+
nsCAutoString spec;
rv = aUri->GetSpec(spec);
NS_ENSURE_SUCCESS(rv,rv);
#if 0 // this not ready yet.
nsNewsAction action = nsINntpUrl::ActionUnknown;
nsCOMPtr <nsINntpUrl> nntpUrl = do_QueryInterface(aUri);
if (nntpUrl) {
@@ -1219,20 +1221,17 @@ nsNntpService::GetProtocolForUri(nsIURI
PRBool hasMsgOffline = PR_FALSE;
folder->HasMsgOffline(key, &hasMsgOffline);
nsCOMPtr<nsIMsgMailNewsUrl> msgUrl (do_QueryInterface(aUri));
if (msgUrl)
msgUrl->SetMsgIsInLocalCache(hasMsgOffline);
}
}
- rv = nntpServer->GetNntpConnection(aUri, aMsgWindow, aProtocol);
- if (NS_FAILED(rv) || !*aProtocol)
- return NS_ERROR_OUT_OF_MEMORY;
- return rv;
+ return NS_OK;
}
PRBool nsNntpService::WeAreOffline()
{
nsresult rv = NS_OK;
PRBool offline = PR_FALSE;
nsCOMPtr<nsIIOService> netService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
@@ -1246,25 +1245,21 @@ nsresult
nsNntpService::RunNewsUrl(nsIURI * aUri, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer)
{
nsresult rv;
if (WeAreOffline())
return NS_MSG_ERROR_OFFLINE;
// almost there...now create a nntp protocol instance to run the url in...
- nsCOMPtr <nsINNTPProtocol> nntpProtocol;
- rv = GetProtocolForUri(aUri, aMsgWindow, getter_AddRefs(nntpProtocol));
+ nsCOMPtr<nsINntpIncomingServer> server;
+ rv = GetServerForUri(aUri, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
- if (NS_SUCCEEDED(rv))
- rv = nntpProtocol->Initialize(aUri, aMsgWindow);
- if (NS_FAILED(rv)) return rv;
-
- rv = nntpProtocol->LoadNewsUrl(aUri, aConsumer);
- return rv;
+ return server->LoadNewsUrl(aUri, aMsgWindow, aConsumer);
}
NS_IMETHODIMP nsNntpService::GetNewNews(nsINntpIncomingServer *nntpServer, const char *uri, PRBool aGetOld, nsIUrlListener * aUrlListener, nsIMsgWindow *aMsgWindow, nsIURI **_retval)
{
NS_ENSURE_ARG_POINTER(uri);
NS_LOCK_INSTANCE();
nsresult rv = NS_OK;
@@ -1404,23 +1399,20 @@ NS_IMETHODIMP nsNntpService::NewURI(cons
NS_ADDREF(*_retval = nntpUri);
return NS_OK;
}
NS_IMETHODIMP nsNntpService::NewChannel(nsIURI *aURI, nsIChannel **_retval)
{
NS_ENSURE_ARG_POINTER(aURI);
nsresult rv = NS_OK;
- nsCOMPtr <nsINNTPProtocol> nntpProtocol;
- rv = GetProtocolForUri(aURI, nsnull, getter_AddRefs(nntpProtocol));
- if (NS_SUCCEEDED(rv))
- rv = nntpProtocol->Initialize(aURI, nsnull);
- if (NS_FAILED(rv)) return rv;
-
- return CallQueryInterface(nntpProtocol, _retval);
+ nsCOMPtr<nsINntpIncomingServer> server;
+ rv = GetServerForUri(aURI, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetNntpChannel(aURI, nsnull, _retval);
}
NS_IMETHODIMP
nsNntpService::SetDefaultLocalPath(nsILocalFile *aPath)
{
NS_ENSURE_ARG(aPath);
return NS_SetPersistentFile(PREF_MAIL_ROOT_NNTP_REL, PREF_MAIL_ROOT_NNTP, aPath);
}
@@ -1778,24 +1770,30 @@ nsNntpService::HandleContent(const char
if (PL_strncasecmp(aContentType, "x-application-newsgroup", 23) == 0)
{
nsCOMPtr<nsIURI> uri;
rv = aChannel->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
if (uri)
{
nsCString uriStr;
+ rv = uri->GetSpec(uriStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRInt32 cutChar = uriStr.FindChar('?');
+ NS_ASSERTION(cutChar > 0, "No query in the list-ids?");
+ if (cutChar > 0)
+ uriStr.SetLength(cutChar);
+
+ // Try to get a valid folder...
nsCOMPtr <nsIMsgFolder> msgFolder;
- nsCOMPtr <nsINNTPProtocol> protocol = do_QueryInterface(aChannel);
- if (protocol)
- protocol->GetCurrentFolder(getter_AddRefs(msgFolder));
+ GetFolderFromUri(uriStr.get(), getter_AddRefs(msgFolder));
+
+ // ... and only go on if we have a valid URI (i.e., valid folder)
if (msgFolder)
- msgFolder->GetURI(uriStr);
-
- if (!uriStr.IsEmpty())
{
nsCOMPtr <nsIURI> originalUri;
aChannel->GetOriginalURI(getter_AddRefs(originalUri));
if (originalUri)
{
nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(originalUri);
if (mailUrl)
{
--- a/mailnews/news/src/nsNntpService.h
+++ b/mailnews/news/src/nsNntpService.h
@@ -89,17 +89,17 @@ protected:
nsresult FindHostFromGroup(nsCString &host, nsCString &groupName);
nsresult FindServerWithNewsgroup(nsCString &host, nsCString &groupName);
nsresult CreateMessageIDURL(nsIMsgFolder *folder, nsMsgKey key, char **url);
nsresult GetMessageFromUrl(nsIURI *aUrl, nsIMsgWindow *aMsgWindow, nsISupports *aDisplayConsumer);
// a convience routine used to put together news urls
nsresult ConstructNntpUrl(const char * urlString, nsIUrlListener *aUrlListener, nsIMsgWindow * aMsgWindow, const char *originalMessageUri, PRInt32 action, nsIURI ** aUrl);
nsresult CreateNewsAccount(const char *aHostname, PRBool aIsSecure, PRInt32 aPort, nsIMsgIncomingServer **aServer);
- nsresult GetProtocolForUri(nsIURI *aUri, nsIMsgWindow *aMsgWindow, nsINNTPProtocol **aProtocol);
+ nsresult GetServerForUri(nsIURI *aUri, nsINntpIncomingServer **aProtocol);
// a convience routine to run news urls
nsresult RunNewsUrl (nsIURI * aUrl, nsIMsgWindow *aMsgWindow, nsISupports * aConsumer);
// a convience routine to go from folder uri to msg folder
nsresult GetFolderFromUri(const char *uri, nsIMsgFolder **folder);
static PRBool findNewsServerWithGroup(nsISupports *aElement, void *data);
nsresult DecomposeNewsMessageURI(const char * aMessageURI, nsIMsgFolder ** aFolder, nsMsgKey *aMsgKey);
PRBool mPrintingOperation; // Flag for printing operations
--- a/mailnews/news/test/unit/head_server_setup.js
+++ b/mailnews/news/test/unit/head_server_setup.js
@@ -91,16 +91,18 @@ function setupLocalServer(port) {
server.valid = true;
// Subscribe to certain posts
server.QueryInterface(Ci.nsINntpIncomingServer);
groups.forEach(function (element) {
if (element[1])
server.subscribeToNewsgroup(element[0]);
});
+ // Only allow one connection
+ server.maximumConnectionsNumber = 1;
_server = server;
return server;
}
const URLCreator = Cc["@mozilla.org/messenger/messageservice;1?type=news"]
.getService(Ci.nsINntpService)
@@ -111,42 +113,36 @@ function setupProtocolTest(port, newsUrl
var url;
if (newsUrl instanceof Ci.nsIMsgMailNewsUrl) {
url = newsUrl;
} else {
url = URLCreator.newURI(newsUrl, null, null);
}
server = setupLocalServer(port);
- var connection = {};
- server.getNntpConnection(url, null, connection);
- connection = connection.value;
-
var listener = {
onStartRequest : function () {},
onStopRequest : function () {
if (!this.called) {
this.called = true;
- connection.CloseConnection();
+ server.closeCachedConnections();
this.called = false;
}
},
onDataAvailable : function () {},
QueryInterface : function (iid) {
if (iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
}
listener.called = false;
-
- connection.Initialize(url, null);
- connection.LoadNewsUrl(url, listener);
+ server.loadNewsUrl(url, null, listener);
}
function create_post(baseURL, file) {
var url = URLCreator.newURI(baseURL, null, null);
url.QueryInterface(Ci.nsINntpUrl);
var post = Cc["@mozilla.org/messenger/nntpnewsgrouppost;1"]
.createInstance(Ci.nsINNTPNewsgroupPost);
--- a/mailnews/news/test/unit/test_server.js
+++ b/mailnews/news/test/unit/test_server.js
@@ -102,11 +102,33 @@ function testRFC977() {
}
server.stop();
var thread = gThreadManager.currentThread;
while (thread.hasPendingEvents())
thread.processNextEvent(true);
}
+function testConnectionLimit() {
+ var handler = new NNTP_RFC977_handler(daemon);
+ var server = new nsMailServer(handler);
+ server.start(NNTP_PORT);
+
+ var prefix = "news://localhost:"+NNTP_PORT+"/";
+ var transaction;
+
+ // To test make connections limit, we run two URIs simultaneously.
+ setupProtocolTest(NNTP_PORT, prefix+"*");
+ setupProtocolTest(NNTP_PORT, prefix+"TSS1@nntp.test");
+ server.performTest();
+ // We should have length one... which means this must be a transaction object,
+ // containing only us and them
+ do_check_true('us' in server.playTransaction());
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents())
+ thread.processNextEvent(true);
+}
function run_test() {
testRFC977();
+ testConnectionLimit();
}