From 955558e21a21520c7f389b574ca5a18fc260b2dc Mon Sep 17 00:00:00 2001 From: Ioannis Charalampidis Date: Wed, 26 Jun 2013 11:41:16 +0200 Subject: [PATCH 1/4] FIREBREATH-236 Extended SimpleStreamHelper Added progress and data chunk feedback --- src/PluginCore/BrowserStreamRequest.h | 81 ++++++++++++++++++ src/PluginCore/SimpleStreamHelper.cpp | 116 +++++++++++++++++++------- src/PluginCore/SimpleStreamHelper.h | 18 +++- 3 files changed, 180 insertions(+), 35 deletions(-) diff --git a/src/PluginCore/BrowserStreamRequest.h b/src/PluginCore/BrowserStreamRequest.h index 72cf1f0d..88603518 100644 --- a/src/PluginCore/BrowserStreamRequest.h +++ b/src/PluginCore/BrowserStreamRequest.h @@ -87,6 +87,9 @@ namespace FB { private: PluginEventSinkPtr sinkPtr; HttpCallback callback; + HttpProgressCallback progressCallback; + HttpChunkCallback chunkCallback; + HttpCompletedCallback completedCallback; bool accepted; std::string postdata; std::string postheaders; @@ -168,6 +171,83 @@ namespace FB { //////////////////////////////////////////////////////////////////////////////////////////////////// void setBufferSize(size_t size) { internalBufferSize = size; } + //////////////////////////////////////////////////////////////////////////////////////////////////// + /// @fn void FB::BrowserStreamRequest::setProgressCallback(const HttpProgressCallback& ptr); + /// + /// @brief If you have registered a callback and you want to receive progress events you can + /// register an HttpProgressCallback. + /// + /// This is used in conjuction with setCallback. It registers a callback function that will be + /// called for every chunk of data being downloaded. The first argument on the HttpProgressCallback + /// is the bytes currently received. The second is either 0 if no Content-Length header was received, + /// otherwise, it's the total number of bytes of the object received. + /// + /// @param ptr const HttpProgressCallback & A callback that will receive the progress events + /// + /// @author wavesoft + //////////////////////////////////////////////////////////////////////////////////////////////////// + void setProgressCallback(const HttpProgressCallback& ptr) { progressCallback = ptr; } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + /// @fn FB::PluginEventSinkPtr FB::BrowserStreamRequest::getProgressCallback(); + /// + /// @brief Returns the HttpProgressCallback functor assigned to the object, or a NULL HttpProgressCallback + /// if none. + /// + /// @author wavesoft + //////////////////////////////////////////////////////////////////////////////////////////////////// + HttpProgressCallback getProgressCallback() const { return progressCallback; } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + /// @fn void FB::BrowserStreamRequest::setCompletedCallback(const HttpCompletedCallback& ptr); + /// + /// @brief This function registers a HttpCompletedCallback function that will be fired when the + /// download process is completed. + /// + /// This is used in conjuction with setCallback or setChunkCallback. It will be fired when the + /// download process is completed. + /// + /// @param ptr const HttpProgressCallback & A callback that will receive the progress events + /// + /// @author wavesoft + //////////////////////////////////////////////////////////////////////////////////////////////////// + void setCompletedCallback(const HttpCompletedCallback& ptr) { completedCallback = ptr; } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + /// @fn FB::PluginEventSinkPtr FB::BrowserStreamRequest::getCompletedCallback(); + /// + /// @brief Returns the HttpCompletedCallback functor assigned to the object, or a NULL HttpProgressCallback + /// if none. + /// + /// @author wavesoft + //////////////////////////////////////////////////////////////////////////////////////////////////// + HttpCompletedCallback getCompletedCallback() const { return completedCallback; } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + /// @fn void FB::BrowserStreamRequest::setChunkCallback(const HttpProgressCallback& ptr); + /// + /// @brief This function is an alternative to setCallback. This function will be called for every + /// data chunk that arrives. + /// + /// This is used in with or without setCallback, but like setCallback, it can't be used in conjunction + /// with setEventSink. This function will be fired for every chunk of incoming data. + /// + /// @param ptr const HttpChunkCallback & A callback that will be called for every incoming data chunk + /// + /// @author wavesoft + //////////////////////////////////////////////////////////////////////////////////////////////////// + void setChunkCallback(const HttpChunkCallback& ptr) { chunkCallback = ptr; } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + /// @fn FB::PluginEventSinkPtr FB::BrowserStreamRequest::getChunkCallback(); + /// + /// @brief Returns the HttpChunkCallback functor assigned to the object, or a NULL HttpProgressCallback + /// if none. + /// + /// @author wavesoft + //////////////////////////////////////////////////////////////////////////////////////////////////// + HttpChunkCallback getChunkCallback() const { return chunkCallback; } + //////////////////////////////////////////////////////////////////////////////////////////////////// /// @fn void FB::BrowserStreamRequest::setEventSink(const PluginEventSinkPtr& ptr); /// @@ -185,6 +265,7 @@ namespace FB { /// @author taxilian //////////////////////////////////////////////////////////////////////////////////////////////////// void setEventSink(const PluginEventSinkPtr& ptr) { sinkPtr = ptr; accepted = true; } + //////////////////////////////////////////////////////////////////////////////////////////////////// /// @fn FB::PluginEventSinkPtr FB::BrowserStreamRequest::getEventSink(); /// diff --git a/src/PluginCore/SimpleStreamHelper.cpp b/src/PluginCore/SimpleStreamHelper.cpp index d18c63c9..2159f32c 100644 --- a/src/PluginCore/SimpleStreamHelper.cpp +++ b/src/PluginCore/SimpleStreamHelper.cpp @@ -14,6 +14,7 @@ Copyright 2011 Richard Bateman, \**********************************************************/ #include "BrowserHost.h" +#include #include #include #include "precompiled_headers.h" // On windows, everything above this line in PCH @@ -50,7 +51,7 @@ FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncPost(const FB::BrowserHos FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncRequest( const FB::BrowserHostConstPtr& host, const BrowserStreamRequest& req ) { - if (!req.getCallback()) { + if (!req.getCallback() && !req.getChunkCallback()) { throw std::runtime_error("Invalid callback"); } if (!host->isMainThread()) { @@ -68,7 +69,18 @@ FB::SimpleStreamHelperPtr FB::SimpleStreamHelper::AsyncRequest( const FB::Browse // This must be run from the main thread return host->CallOnMainThread(boost::bind(&AsyncRequest, host, stream, req)); } - FB::SimpleStreamHelperPtr ptr(boost::make_shared(req.getCallback(), req.internalBufferSize)); + + // Create a SimpleStreamHelper instance + FB::SimpleStreamHelperPtr ptr( + boost::make_shared( + req.getCallback(), + req.getProgressCallback(), + req.getChunkCallback(), + req.getCompletedCallback(), + req.internalBufferSize + ) + ); + // This is kinda a weird trick; it's responsible for freeing itself, unless something decides // to hold a reference to it. ptr->keepReference(ptr); @@ -106,7 +118,7 @@ struct SyncHTTPHelper }; -FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousRequest( const FB::BrowserHostPtr& host, const BrowserStreamRequest& req ) +FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousRequest( const FB::BrowserHostPtr& host, BrowserStreamRequest& req ) { // We can't ever block on the main thread, so SynchronousGet can't be called from there. // Also, if you could block the main thread, that still wouldn't work because the request @@ -144,8 +156,14 @@ FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousPost( const FB::Bro return SynchronousRequest(host, req); } -FB::SimpleStreamHelper::SimpleStreamHelper( const HttpCallback& callback, const size_t blockSize ) - : blockSize(blockSize), received(0), callback(callback) +FB::SimpleStreamHelper::SimpleStreamHelper( + const HttpCallback& callback, + const HttpProgressCallback& progressCallback, + const HttpChunkCallback& chunkCallback, + const HttpCompletedCallback& completedCallback, + const size_t blockSize ) + + : blockSize(blockSize), received(0), callback(callback), progressCallback(progressCallback), chunkCallback(chunkCallback), completedCallback(completedCallback) { } @@ -155,6 +173,12 @@ bool FB::SimpleStreamHelper::onStreamCompleted( FB::StreamCompletedEvent *evt, F if (!evt->success) { if (callback) callback(false, FB::HeaderMap(), boost::shared_array(), received); + if (completedCallback) + completedCallback(false, FB::HeaderMap()); + + progressCallback.clear(); + chunkCallback.clear(); + completedCallback.clear(); callback.clear(); self.reset(); return false; @@ -175,50 +199,78 @@ bool FB::SimpleStreamHelper::onStreamCompleted( FB::StreamCompletedEvent *evt, F // Free all the old blocks blocks.clear(); } - if (callback && stream) { + if ((callback || completedCallback) && stream) { std::multimap headers; headers = parse_http_headers(stream->getHeaders()); - callback(true, headers, data, received); + if (callback) + callback(true, headers, data, received); + if (completedCallback) + completedCallback(true, headers); } + + progressCallback.clear(); + chunkCallback.clear(); + completedCallback.clear(); callback.clear(); self.reset(); return false; // Always return false to make sure the browserhost knows to let go of the object } -bool FB::SimpleStreamHelper::onStreamOpened( FB::StreamOpenedEvent *evt, FB::BrowserStream * ) +bool FB::SimpleStreamHelper::onStreamOpened( FB::StreamOpenedEvent *evt, FB::BrowserStream * stream ) { + + std::multimap headers; + headers = parse_http_headers(stream->getHeaders()); + total = 0; + if (headers.find("Content-Length") != headers.end()) + total = boost::lexical_cast( headers.find("Content-Length")->second ); + // We can't reliably find the actual length, so we won't try return false; } -bool FB::SimpleStreamHelper::onStreamDataArrived( FB::StreamDataArrivedEvent *evt, FB::BrowserStream * ) +bool FB::SimpleStreamHelper::onStreamDataArrived( FB::StreamDataArrivedEvent *evt, FB::BrowserStream * s ) { + // Forward the received buffer size received += evt->getLength(); - const uint8_t* buf = reinterpret_cast(evt->getData()); - const uint8_t* endbuf = buf + evt->getLength(); - - int len = evt->getLength(); - int offset = evt->getDataPosition(); - while (buf < endbuf) { - size_t n = offset / blockSize; - size_t pos = offset % blockSize; - if (blocks.size() < n+1) { - blocks.push_back(boost::shared_array(new uint8_t[blockSize])); - } - uint8_t *destBuf = blocks.back().get(); - //if (pos + len > ) - int curLen = len; - if (pos + len >= blockSize) { - // If there isn't room in the current block, copy what there is room for - // and loop - curLen = blockSize-pos; + + // Call the chunk callback + if (chunkCallback) + chunkCallback( evt->getData(), evt->getLength() ); + + // If we have a simple callback, build buffer + if (callback) { + const uint8_t* buf = reinterpret_cast(evt->getData()); + const uint8_t* endbuf = buf + evt->getLength(); + int offset = evt->getDataPosition(); + int len = evt->getLength(); + while (buf < endbuf) { + size_t n = offset / blockSize; + size_t pos = offset % blockSize; + if (blocks.size() < n+1) { + blocks.push_back(boost::shared_array(new uint8_t[blockSize])); + } + uint8_t *destBuf = blocks.back().get(); + //if (pos + len > ) + int curLen = len; + if (pos + len >= blockSize) { + // If there isn't room in the current block, copy what there is room for + // and loop + curLen = blockSize-pos; + } + // Copy the bytes that fit in this buffer + std::copy(buf, buf+curLen, destBuf+pos); + buf += curLen; + offset += curLen; + len -= curLen; } - // Copy the bytes that fit in this buffer - std::copy(buf, buf+curLen, destBuf+pos); - buf += curLen; - offset += curLen; - len -= curLen; } + + // Call the progress callback + if (progressCallback) + progressCallback( received, total ); + + // Forward the event return false; } diff --git a/src/PluginCore/SimpleStreamHelper.h b/src/PluginCore/SimpleStreamHelper.h index 3b8c1ae0..507f1fb3 100644 --- a/src/PluginCore/SimpleStreamHelper.h +++ b/src/PluginCore/SimpleStreamHelper.h @@ -32,7 +32,10 @@ namespace FB { FB_FORWARD_PTR(SimpleStreamHelper); typedef std::multimap HeaderMap; - typedef boost::function&, const size_t)> HttpCallback; + typedef boost::function&, const size_t)> HttpCallback; + typedef boost::function HttpChunkCallback; + typedef boost::function HttpProgressCallback; + typedef boost::function HttpCompletedCallback; //////////////////////////////////////////////////////////////////////////////////////////////////// /// @class HttpStreamResponse @@ -241,7 +244,7 @@ namespace FB { /// @author taxilian /// @since 1.7 //////////////////////////////////////////////////////////////////////////////////////////////////// - static HttpStreamResponsePtr SynchronousRequest(const FB::BrowserHostPtr& host, const BrowserStreamRequest& req ); + static HttpStreamResponsePtr SynchronousRequest(const FB::BrowserHostPtr& host, BrowserStreamRequest& req ); //////////////////////////////////////////////////////////////////////////////////////////////////// /// @fn public static FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousPost(const FB::BrowserHostPtr& host, const FB::URI& uri, const std::string& postdata, const bool cache = true, const size_t bufferSize = 128*1024) @@ -280,7 +283,12 @@ namespace FB { public: - SimpleStreamHelper( const HttpCallback& callback, const size_t blockSize = 128*1024 ); + SimpleStreamHelper( + const HttpCallback& callback, + const HttpProgressCallback& progressCallback, + const HttpChunkCallback& chunkCallback, + const HttpCompletedCallback& completedCallback, + const size_t blockSize = 128*1024 ); virtual bool onStreamDataArrived(FB::StreamDataArrivedEvent *evt, FB::BrowserStream *); virtual bool onStreamOpened(FB::StreamOpenedEvent *evt, FB::BrowserStream *); @@ -293,7 +301,11 @@ namespace FB { boost::shared_array data; const size_t blockSize; size_t received; + size_t total; HttpCallback callback; + HttpProgressCallback progressCallback; + HttpChunkCallback chunkCallback; + HttpCompletedCallback completedCallback; private: void keepReference(const SimpleStreamHelperPtr& ptr); From bba13a8d31095b528c314cf4860b6bb063d1a71e Mon Sep 17 00:00:00 2001 From: Ioannis Charalampidis Date: Wed, 26 Jun 2013 11:42:13 +0200 Subject: [PATCH 2/4] FIREBREATH-237 Fixed SynchronousRequest Fixed a bug on SimpleStreamHelper::SynchronousRequest --- src/PluginCore/SimpleStreamHelper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PluginCore/SimpleStreamHelper.cpp b/src/PluginCore/SimpleStreamHelper.cpp index 2159f32c..fc895e07 100644 --- a/src/PluginCore/SimpleStreamHelper.cpp +++ b/src/PluginCore/SimpleStreamHelper.cpp @@ -127,6 +127,7 @@ FB::HttpStreamResponsePtr FB::SimpleStreamHelper::SynchronousRequest( const FB:: SyncHTTPHelper helper; try { FB::HttpCallback cb(boost::bind(&SyncHTTPHelper::getURLCallback, &helper, _1, _2, _3, _4)); + req.setCallback(cb); FB::SimpleStreamHelperPtr ptr = AsyncRequest(host, req); helper.setPtr(ptr); helper.waitForDone(); From fd45bb9ec3d496a01580b6b7ebfc4ca760a61dfc Mon Sep 17 00:00:00 2001 From: Ioannis Charalampidis Date: Wed, 26 Jun 2013 11:47:24 +0200 Subject: [PATCH 3/4] FIREBREATH-236 Better integration of the patch Integrating the more advanced SimpleStreamHelper interface with other components of FireBreath core. --- src/NpapiCore/NpapiPlugin.cpp | 3 ++- src/ScriptingCore/BrowserHost.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NpapiCore/NpapiPlugin.cpp b/src/NpapiCore/NpapiPlugin.cpp index e9cb9e0d..6c06b117 100644 --- a/src/NpapiCore/NpapiPlugin.cpp +++ b/src/NpapiCore/NpapiPlugin.cpp @@ -335,7 +335,8 @@ NPError NpapiPlugin::NewStream(NPMIMEType type, NPStream* stream, NPBool seekabl newstream->AttachObserver(sink); } else { HttpCallback callback(streamReq.getCallback()); - if (callback) { + HttpChunkCallback chunkCallback(streamReq.getChunkCallback()); + if (callback || chunkCallback) { SimpleStreamHelper::AsyncRequest(m_npHost, newstream, streamReq); } else { FBLOG_WARN("NpapiPlugin", "Unsolicited request accepted but no callback or sink provided"); diff --git a/src/ScriptingCore/BrowserHost.cpp b/src/ScriptingCore/BrowserHost.cpp index 30c42e09..6cacf8bf 100644 --- a/src/ScriptingCore/BrowserHost.cpp +++ b/src/ScriptingCore/BrowserHost.cpp @@ -360,7 +360,7 @@ FB::BrowserStreamPtr FB::BrowserHost::createStream( const std::string& url, FB::BrowserStreamPtr FB::BrowserHost::createStream( const BrowserStreamRequest& req, const bool enable_async ) const { assertMainThread(); - if (enable_async && req.getCallback() && !req.getEventSink()) { + if (enable_async && (req.getCallback() || req.getChunkCallback()) && !req.getEventSink()) { // If a callback was provided, use SimpleStreamHelper to create it; // This will actually call back into this function with an event sink BrowserStreamRequest newReq(req); From 67fb047bf074b07687f81352fd8479197bad1902 Mon Sep 17 00:00:00 2001 From: Ioannis Charalampidis Date: Wed, 26 Jun 2013 12:30:24 +0200 Subject: [PATCH 4/4] FIREBREATH-236 Using stream->getLength() Using FB::BrowserStream->getLength() instead of custom header parsing function. --- src/PluginCore/SimpleStreamHelper.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/PluginCore/SimpleStreamHelper.cpp b/src/PluginCore/SimpleStreamHelper.cpp index fc895e07..4e0e1f74 100644 --- a/src/PluginCore/SimpleStreamHelper.cpp +++ b/src/PluginCore/SimpleStreamHelper.cpp @@ -219,13 +219,6 @@ bool FB::SimpleStreamHelper::onStreamCompleted( FB::StreamCompletedEvent *evt, F bool FB::SimpleStreamHelper::onStreamOpened( FB::StreamOpenedEvent *evt, FB::BrowserStream * stream ) { - - std::multimap headers; - headers = parse_http_headers(stream->getHeaders()); - total = 0; - if (headers.find("Content-Length") != headers.end()) - total = boost::lexical_cast( headers.find("Content-Length")->second ); - // We can't reliably find the actual length, so we won't try return false; } @@ -269,7 +262,7 @@ bool FB::SimpleStreamHelper::onStreamDataArrived( FB::StreamDataArrivedEvent *ev // Call the progress callback if (progressCallback) - progressCallback( received, total ); + progressCallback( received, s->getLength() ); // Forward the event return false;