Index: netwerk/base/src/nsInputStreamChannel.cpp =================================================================== RCS file: /cvsroot/mozilla/netwerk/base/src/nsInputStreamChannel.cpp,v retrieving revision 1.81 diff -u -p -r1.81 nsInputStreamChannel.cpp --- netwerk/base/src/nsInputStreamChannel.cpp 30 Nov 2005 00:54:49 -0000 1.81 +++ netwerk/base/src/nsInputStreamChannel.cpp 9 Jan 2008 21:27:35 -0000 @@ -36,6 +36,231 @@ * ***** END LICENSE BLOCK ***** */ #include "nsInputStreamChannel.h" +#include "nsBaseContentStream.h" +#include "nsThreadUtils.h" +#include "nsTransportUtils.h" +#include "nsStreamUtils.h" +#include "nsMimeTypes.h" +#include "nsNetUtil.h" +#include "nsNetSegmentUtils.h" +#include "nsProxyRelease.h" + +//----------------------------------------------------------------------------- + +class nsGenericCopyEvent : public nsRunnable { +public: + nsGenericCopyEvent(nsIOutputStream *dest, nsIInputStream *source, PRInt64 len) + : mDest(dest) + , mSource(source) + , mLen(len) + , mStatus(NS_OK) + , mInterruptStatus(NS_OK) { + } + + // Read the current status of the Generic copy operation. + nsresult Status() { return mStatus; } + + // Call this method to perform the Generic copy synchronously. + void DoCopy(); + + // Call this method to perform the Generic copy on a background thread. The + // callback is dispatched when the Generic copy completes. + nsresult Dispatch(nsIRunnable *callback, + nsITransportEventSink *sink, + nsIEventTarget *target); + + // Call this method to interrupt a Generic copy operation that is occuring on + // a background thread. The status parameter passed to this function must + // be a failure code and is set as the status of this Generic copy operation. + void Interrupt(nsresult status) { + NS_ASSERTION(NS_FAILED(status), "must be a failure code"); + mInterruptStatus = status; + } + + NS_IMETHOD Run() { + DoCopy(); + return NS_OK; + } + +private: + nsCOMPtr mCallbackTarget; + nsCOMPtr mCallback; + nsCOMPtr mSink; + nsCOMPtr mDest; + nsCOMPtr mSource; + PRInt64 mLen; + nsresult mStatus; // modified on i/o thread only + nsresult mInterruptStatus; // modified on main thread only +}; + +void +nsGenericCopyEvent::DoCopy() +{ + // We'll copy in chunks this large by default. This size affects how + // frequently we'll check for interrupts. + const PRInt32 chunk = NET_DEFAULT_SEGMENT_SIZE * NET_DEFAULT_SEGMENT_COUNT; + + nsresult rv = NS_OK; + + PRInt64 len = mLen, progress = 0; + while (len) { + // If we've been interrupted, then stop copying. + rv = mInterruptStatus; + if (NS_FAILED(rv)) + break; + + PRInt32 num = PR_MIN((PRInt32) len, chunk); + + PRUint32 result; + rv = mSource->ReadSegments(NS_CopySegmentToStream, mDest, num, &result); + if (NS_FAILED(rv)) + break; + if (result != (PRUint32) num) { + rv = NS_ERROR_FILE_DISK_FULL; // stopped prematurely (out of disk space) + break; + } + + // Dispatch progress notification + if (mSink) { + progress += num; + mSink->OnTransportStatus(nsnull, nsITransport::STATUS_WRITING, progress, + mLen); + } + + len -= num; + } + + if (NS_FAILED(rv)) + mStatus = rv; + + // Close the output stream before notifying our callback so that others may + // freely "play" with the Generic. + mDest->Close(); + + // Notify completion + if (mCallback) { + mCallbackTarget->Dispatch(mCallback, NS_DISPATCH_NORMAL); + + // Release the callback on the target thread to avoid destroying stuff on + // the wrong thread. + nsIRunnable *doomed = nsnull; + mCallback.swap(doomed); + NS_ProxyRelease(mCallbackTarget, doomed); + } +} + +nsresult +nsGenericCopyEvent::Dispatch(nsIRunnable *callback, + nsITransportEventSink *sink, + nsIEventTarget *target) +{ + // Use the supplied event target for all asynchronous operations. + + mCallback = callback; + mCallbackTarget = target; + + // Build a coalescing proxy for progress events + nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(mSink), sink, + target, PR_TRUE); + if (NS_FAILED(rv)) + return rv; + + // Dispatch ourselves to I/O thread pool... + nsCOMPtr pool = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + return pool->Dispatch(this, NS_DISPATCH_NORMAL); +} + +//----------------------------------------------------------------------------- +// This is a dummy input stream that when read, performs the Generic copy. The +// copy happens on a background thread via mCopyEvent. + +class nsGenericUploadContentStream : public nsBaseContentStream { +public: + NS_DECL_ISUPPORTS_INHERITED + + nsGenericUploadContentStream(PRBool nonBlocking, + nsIOutputStream *dest, + nsIInputStream *source, + PRInt64 len, + nsITransportEventSink *sink) + : nsBaseContentStream(nonBlocking) + , mCopyEvent(new nsGenericCopyEvent(dest, source, len)) + , mSink(sink) { + } + + PRBool IsInitialized() { + return mCopyEvent != nsnull; + } + + NS_IMETHODIMP ReadSegments(nsWriteSegmentFun fun, void *closure, + PRUint32 count, PRUint32 *result); + NS_IMETHODIMP AsyncWait(nsIInputStreamCallback *callback, PRUint32 flags, + PRUint32 count, nsIEventTarget *target); + +private: + void OnCopyComplete(); + + nsRefPtr mCopyEvent; + nsCOMPtr mSink; +}; + +NS_IMPL_ISUPPORTS_INHERITED0(nsGenericUploadContentStream, + nsBaseContentStream) + +NS_IMETHODIMP +nsGenericUploadContentStream::ReadSegments(nsWriteSegmentFun fun, void *closure, + PRUint32 count, PRUint32 *result) +{ + *result = 0; // nothing is ever actually read from this stream + + if (IsClosed()) + return NS_OK; + + if (IsNonBlocking()) { + // Inform the caller that they will have to wait for the copy operation to + // complete asynchronously. We'll kick of the copy operation once they + // call AsyncWait. + return NS_BASE_STREAM_WOULD_BLOCK; + } + + // Perform copy synchronously, and then close out the stream. + mCopyEvent->DoCopy(); + nsresult status = mCopyEvent->Status(); + CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED); + return status; +} + +NS_IMETHODIMP +nsGenericUploadContentStream::AsyncWait(nsIInputStreamCallback *callback, + PRUint32 flags, PRUint32 count, + nsIEventTarget *target) +{ + nsresult rv = nsBaseContentStream::AsyncWait(callback, flags, count, target); + if (NS_FAILED(rv) || IsClosed()) + return rv; + + if (IsNonBlocking()) { + nsCOMPtr callback = + NS_NEW_RUNNABLE_METHOD(nsGenericUploadContentStream, this, + OnCopyComplete); + mCopyEvent->Dispatch(callback, mSink, target); + } + + return NS_OK; +} + +void +nsGenericUploadContentStream::OnCopyComplete() +{ + // This method is being called to indicate that we are done copying. + nsresult status = mCopyEvent->Status(); + + CloseWithStatus(NS_FAILED(status) ? status : NS_BASE_STREAM_CLOSED); +} //----------------------------------------------------------------------------- // nsInputStreamChannel @@ -43,6 +268,30 @@ nsresult nsInputStreamChannel::OpenContentStream(PRBool async, nsIInputStream **result) { + if (mUploadStream) { + nsGenericUploadContentStream *uploadStream = + new nsGenericUploadContentStream(async, mOutputStream, mUploadStream, + mUploadLength, this); + if (!uploadStream || !uploadStream->IsInitialized()) { + delete uploadStream; + return NS_ERROR_OUT_OF_MEMORY; + } + nsCOMPtr stream = uploadStream; + + SetContentLength64(0); + + // Since there isn't any content to speak of we just set the content-type + // to something other than "unknown" to avoid triggering the content-type + // sniffer code in nsBaseChannel. + // However, don't override explicitly set types. + if (!HasContentTypeHint()) + SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM)); + + *result = nsnull; + stream.swap(*result); + return NS_OK; + } + NS_ENSURE_TRUE(mContentStream, NS_ERROR_NOT_INITIALIZED); // If content length is unknown, then we must guess. In this case, we assume @@ -70,9 +319,14 @@ nsInputStreamChannel::OpenContentStream( //----------------------------------------------------------------------------- // nsInputStreamChannel::nsISupports -NS_IMPL_ISUPPORTS_INHERITED1(nsInputStreamChannel, - nsBaseChannel, - nsIInputStreamChannel) +NS_IMPL_ADDREF(nsInputStreamChannel) +NS_IMPL_RELEASE(nsInputStreamChannel) + +NS_INTERFACE_MAP_BEGIN(nsInputStreamChannel) +NS_INTERFACE_MAP_ENTRY(nsIInputStreamChannel) +NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIUploadChannel, mOutputStream) +NS_INTERFACE_MAP_ENTRY(nsIOutputIterator) +NS_INTERFACE_MAP_END_INHERITING(nsBaseChannel) //----------------------------------------------------------------------------- // nsInputStreamChannel::nsIInputStreamChannel @@ -99,3 +353,33 @@ nsInputStreamChannel::SetContentStream(n mContentStream = stream; return NS_OK; } + +NS_IMETHODIMP +nsInputStreamChannel::GetUploadStream(nsIInputStream **stream) +{ + NS_IF_ADDREF(*stream = mUploadStream); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::SetUploadStream(nsIInputStream *stream, const nsACString &aContentType, PRInt32 aContentLength) +{ + NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED); + mUploadStream = stream; + mUploadLength = aContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamChannel::PutElement(nsISupports *stream) +{ + mOutputStream = do_QueryInterface(stream); + return (!stream || mOutputStream) ? NS_OK : NS_ERROR_ILLEGAL_VALUE; +} + +NS_IMETHODIMP +nsInputStreamChannel::StepForward() +{ + return NS_OK; +} + Index: netwerk/base/src/nsInputStreamChannel.h =================================================================== RCS file: /cvsroot/mozilla/netwerk/base/src/nsInputStreamChannel.h,v retrieving revision 1.34 diff -u -p -r1.34 nsInputStreamChannel.h --- netwerk/base/src/nsInputStreamChannel.h 12 Nov 2005 18:17:14 -0000 1.34 +++ netwerk/base/src/nsInputStreamChannel.h 9 Jan 2008 21:27:35 -0000 @@ -41,14 +41,23 @@ #include "nsBaseChannel.h" #include "nsIInputStreamChannel.h" +#include "nsIOutputStream.h" +#include "nsIUploadChannel.h" +#include "nsISupportsIterators.h" + //----------------------------------------------------------------------------- class nsInputStreamChannel : public nsBaseChannel , public nsIInputStreamChannel + , public nsIUploadChannel +/* hack */ +, public nsIOutputIterator { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIINPUTSTREAMCHANNEL + NS_DECL_NSIUPLOADCHANNEL + NS_DECL_NSIOUTPUTITERATOR nsInputStreamChannel() {} @@ -59,6 +68,9 @@ protected: private: nsCOMPtr mContentStream; + nsCOMPtr mOutputStream; + nsCOMPtr mUploadStream; + PRInt64 mUploadLength; }; #endif // !nsInputStreamChannel_h__ Index: extensions/gnomevfs/nsGnomeVFSProtocolHandler.cpp =================================================================== RCS file: /cvsroot/mozilla/extensions/gnomevfs/nsGnomeVFSProtocolHandler.cpp,v retrieving revision 1.14 diff -u -p -r1.14 nsGnomeVFSProtocolHandler.cpp --- extensions/gnomevfs/nsGnomeVFSProtocolHandler.cpp 10 Nov 2006 23:49:04 -0000 1.14 +++ extensions/gnomevfs/nsGnomeVFSProtocolHandler.cpp 9 Jan 2008 21:27:35 -0000 @@ -58,6 +58,7 @@ extern "C" { #include "nsMimeTypes.h" #include "nsNetUtil.h" #include "nsINetUtil.h" +#include "nsISupportsIterators.h" #include "nsAutoPtr.h" #include "nsError.h" #include "prlog.h" @@ -759,6 +760,272 @@ nsGnomeVFSInputStream::IsNonBlocking(PRB //----------------------------------------------------------------------------- +class nsGnomeVFSOutputStream : public nsIOutputStream +{ + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + nsGnomeVFSOutputStream(const nsCString &uriSpec) + : mSpec(uriSpec) + , mChannel(nsnull) + , mHandle(nsnull) + , mBytesRemaining(PR_UINT32_MAX) + , mStatus(NS_OK) + , mDirList(nsnull) + , mDirListPtr(nsnull) + , mDirBufCursor(0) + , mDirOpen(PR_FALSE) {} + + ~nsGnomeVFSOutputStream() { Close(); } + + void SetChannel(nsIChannel *channel) + { + // We need to hold an owning reference to our channel. This is done + // so we can access the channel's notification callbacks to acquire + // a reference to a nsIAuthPrompt if we need to handle a GnomeVFS + // authentication callback. + // + // However, the channel can only be accessed on the main thread, so + // we have to be very careful with ownership. Moreover, it doesn't + // support threadsafe addref/release, so proxying is the answer. + // + // Also, it's important to note that this likely creates a reference + // cycle since the channel likely owns this stream. This reference + // cycle is broken in our Close method. + + NS_ADDREF(mChannel = channel); + } + + private: + GnomeVFSResult DoOpen(); + GnomeVFSResult DoWrite(const char *aBuf, PRUint32 aCount, PRUint32 *aCountWritten); + nsresult SetContentTypeOfChannel(const char *contentType); + + private: + nsCString mSpec; + nsIChannel *mChannel; // manually refcounted + GnomeVFSHandle *mHandle; + PRUint32 mBytesRemaining; + nsresult mStatus; + GList *mDirList; + GList *mDirListPtr; + nsCString mDirBuf; + PRUint32 mDirBufCursor; + PRPackedBool mDirOpen; +}; + +GnomeVFSResult +nsGnomeVFSOutputStream::DoOpen() +{ + GnomeVFSResult rv; + + NS_ASSERTION(mHandle == nsnull, "already open"); + + // Push a callback handler on the stack for this thread, so we can intercept + // authentication requests from GnomeVFS. We'll use the channel to get a + // nsIAuthPrompt instance. + + gnome_vfs_module_callback_push(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION, + AuthCallback, mChannel, NULL); + + // Query the mime type first (this could return NULL). + // + // XXX We need to do this up-front in order to determine how to open the URI. + // Unfortunately, the error code GNOME_VFS_ERROR_IS_DIRECTORY is not + // always returned by gnome_vfs_open when we pass it a URI to a directory! + // Otherwise, we could have used that as a way to failover to opening the + // URI as a directory. Also, it would have been ideal if + // gnome_vfs_get_file_info_from_handle were actually implemented by the + // smb:// module, since that would have allowed us to potentially save a + // round trip to the server to discover the mime type of the document in + // the case where gnome_vfs_open would have been used. (Oh well! /me + // throws hands up in the air and moves on...) + + GnomeVFSFileInfo info = {0}; + rv = gnome_vfs_get_file_info(mSpec.get(), &info, GNOME_VFS_FILE_INFO_DEFAULT); + if (rv == GNOME_VFS_OK) + { + if (info.type == GNOME_VFS_FILE_TYPE_DIRECTORY) + { + rv = GNOME_VFS_ERROR_IS_DIRECTORY; + } + else + { + rv = gnome_vfs_open(&mHandle, mSpec.get(), GNOME_VFS_OPEN_WRITE); + + LOG(("gnomevfs: gnome_vfs_open returned %d (%s) [spec=\"%s\"]\n", + rv, gnome_vfs_result_to_string(rv), mSpec.get())); + } + } + else if (rv == GNOME_VFS_ERROR_NOT_FOUND) + { + rv = gnome_vfs_create(&mHandle, mSpec.get(), GNOME_VFS_OPEN_WRITE, 1, 0644); + + LOG(("gnomevfs: gnome_vfs_create returned %d (%s) [spec=\"%s\"]\n", + rv, gnome_vfs_result_to_string(rv), mSpec.get())); + } + + gnome_vfs_module_callback_pop(GNOME_VFS_MODULE_CALLBACK_AUTHENTICATION); + + gnome_vfs_file_info_clear(&info); + return rv; +} + +GnomeVFSResult +nsGnomeVFSOutputStream::DoWrite(const char *aBuf, PRUint32 aCount, PRUint32 *aCountWritten) +{ + GnomeVFSResult rv; + + if (mHandle) + { + GnomeVFSFileSize bytesWritten; + rv = gnome_vfs_write(mHandle, aBuf, aCount, &bytesWritten); + if (rv == GNOME_VFS_OK) + { + *aCountWritten = bytesWritten; + } + } + else + { + NS_NOTREACHED("writing to what?"); + rv = GNOME_VFS_ERROR_GENERIC; + } + + return rv; +} + +nsresult +nsGnomeVFSOutputStream::SetContentTypeOfChannel(const char *contentType) +{ + // We need to proxy this call over to the main thread. We post an + // asynchronous event in this case so that we don't delay reading data, and + // we know that this is safe to do since the channel's reference will be + // released asynchronously as well. We trust the ordering of the main + // thread's event queue to protect us against memory corruption. + + nsresult rv; + nsCOMPtr ev = + new nsGnomeVFSSetContentTypeEvent(mChannel, contentType); + if (!ev) + { + rv = NS_ERROR_OUT_OF_MEMORY; + } + else + { + rv = NS_DispatchToMainThread(ev); + } + return rv; +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsGnomeVFSOutputStream, nsIOutputStream) + +NS_IMETHODIMP +nsGnomeVFSOutputStream::Close() +{ + if (mHandle) + { + gnome_vfs_close(mHandle); + mHandle = nsnull; + } + + if (mDirList) + { + // Destroy the list of GnomeVFSFileInfo objects... + g_list_foreach(mDirList, (GFunc) gnome_vfs_file_info_unref, nsnull); + g_list_free(mDirList); + mDirList = nsnull; + mDirListPtr = nsnull; + } + + if (mChannel) + { + nsresult rv = NS_OK; + + nsCOMPtr thread = do_GetMainThread(); + if (thread) + rv = NS_ProxyRelease(thread, mChannel); + + NS_ASSERTION(thread && NS_SUCCEEDED(rv), "leaking channel reference"); + mChannel = nsnull; + } + + mSpec.Truncate(); // free memory + + // Prevent future reads from re-opening the handle. + if (NS_SUCCEEDED(mStatus)) + mStatus = NS_BASE_STREAM_CLOSED; + + return NS_OK; +} + +NS_IMETHODIMP +nsGnomeVFSOutputStream::Flush() +{ + /* GnomeVFS is Sync */ + return NS_OK; +} + +NS_IMETHODIMP +nsGnomeVFSOutputStream::Write(const char *aBuf, + PRUint32 aCount, + PRUint32 *aWritten) +{ + if (mStatus == NS_BASE_STREAM_CLOSED) + return NS_OK; + if (NS_FAILED(mStatus)) + return mStatus; + + GnomeVFSResult rv = GNOME_VFS_OK; + + // If this is our first-time through here, then open the URI. + if (!mHandle && !mDirOpen) + rv = DoOpen(); + + if (rv == GNOME_VFS_OK) + rv = DoWrite(aBuf, aCount, aWritten); + + if (rv != GNOME_VFS_OK) + { + // If we reach here, we hit some kind of error. EOF is not an error. + mStatus = MapGnomeVFSResult(rv); + if (mStatus == NS_BASE_STREAM_CLOSED) + return NS_OK; + + LOG(("gnomevfs: result %d [%s] mapped to 0x%x\n", + rv, gnome_vfs_result_to_string(rv), mStatus)); + } + return mStatus; +} + +NS_IMETHODIMP +nsGnomeVFSOutputStream::WriteFrom(nsIInputStream *aFromStream, + PRUint32 aCount, + PRUint32 *aWritten) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGnomeVFSOutputStream::WriteSegments(nsReadSegmentFun aWriter, + void *aClosure, + PRUint32 aCount, + PRUint32 *aWritten) +{ + // There is no way to implement this using GnomeVFS, but fortunately + // that doesn't matter. Because we are a blocking input stream, Necko + // isn't going to call our WriteSegments method. + NS_NOTREACHED("nsGnomeVFSOutputStream::WriteSegments"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGnomeVFSOutputStream::IsNonBlocking(PRBool *aResult) +{ + *aResult = PR_FALSE; + return NS_OK; +} + class nsGnomeVFSProtocolHandler : public nsIProtocolHandler , public nsIObserver { @@ -932,17 +1199,24 @@ nsGnomeVFSProtocolHandler::NewChannel(ns nsRefPtr stream = new nsGnomeVFSInputStream(spec); if (!stream) - { - rv = NS_ERROR_OUT_OF_MEMORY; - } - else - { - // start out assuming an unknown content-type. we'll set the content-type - // to something better once we open the URI. - rv = NS_NewInputStreamChannel(aResult, aURI, stream, - NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE)); - if (NS_SUCCEEDED(rv)) - stream->SetChannel(*aResult); + return NS_ERROR_OUT_OF_MEMORY; + + // start out assuming an unknown content-type. we'll set the content-type + // to something better once we open the URI. + rv = NS_NewInputStreamChannel(aResult, aURI, stream, + NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE)); + if (NS_FAILED(rv)) + return rv; + stream->SetChannel(*aResult); + + nsCOMPtr upload(do_QueryInterface(*aResult)); + if (upload) { + nsRefPtr outputStream + = new nsGnomeVFSOutputStream(spec); + if (outputStream) { + outputStream->SetChannel(*aResult); + upload->PutElement(outputStream); + } } return rv; }