AU: MultiHttpFetcher cleanup/rewrite
This is the first of many CLs to cleanup/refactor/unfork the
HttpFetcher classes.
This CL changes MultiHttpFetcher to MultiRangeHTTPFetcher, makes it
work with a single base fetcher, and un-templatizes it.
Also, fix a (new?) bug in SConstruct w/ setting CCFLAGS.
TEST=unittests, tested an interrupted/resumed update on device.
BUG=10395
Review URL: http://codereview.chromium.org/5835004
Change-Id: I8422358a6d425233987dd799c5ee7c87135d85fd
diff --git a/SConstruct b/SConstruct
index 6b0646a..615d18c 100644
--- a/SConstruct
+++ b/SConstruct
@@ -230,7 +230,7 @@
env.GlibMarshal('marshal.glibmarshal.c', 'marshal.list')
if ARGUMENTS.get('debug', 0):
- env['CCFLAGS'] += ' -fprofile-arcs -ftest-coverage'
+ env['CCFLAGS'] += ['-fprofile-arcs', '-ftest-coverage']
env['LIBS'] += ['bz2', 'gcov']
sources = Split("""action_processor.cc
@@ -258,6 +258,7 @@
libcurl_http_fetcher.cc
marshal.glibmarshal.c
metadata.cc
+ multi_range_http_fetcher.cc
omaha_hash_calculator.cc
omaha_request_action.cc
omaha_request_params.cc
@@ -361,4 +362,4 @@
unittest_cmd = unittest_env.Program('update_engine_unittests',
unittest_sources + unittest_main)
Clean(unittest_cmd, Glob('*.gcda') + Glob('*.gcno') + Glob('*.gcov') +
- Split('html app.info'))
\ No newline at end of file
+ Split('html app.info'))
diff --git a/http_fetcher.h b/http_fetcher.h
index a50c760..e4f791a 100644
--- a/http_fetcher.h
+++ b/http_fetcher.h
@@ -86,6 +86,12 @@
virtual void set_idle_seconds(int seconds) {}
virtual void set_retry_seconds(int seconds) {}
+ ProxyResolver* proxy_resolver() const { return proxy_resolver_; }
+
+ // These are used for testing:
+ virtual void SetConnectionAsExpensive(bool is_expensive) {}
+ virtual void SetBuildType(bool is_official) {}
+
protected:
// The URL we're actively fetching from
std::string url_;
@@ -122,13 +128,11 @@
// Called if the fetcher seeks to a particular offset.
virtual void SeekToOffset(off_t offset) {}
- // Called when the transfer has completed successfully or been aborted through
- // means other than TerminateTransfer. It's OK to destroy the |fetcher| object
- // in this callback.
+ // When a transfer has completed, exactly one of these two methods will be
+ // called. TransferTerminated is called when the transfer has been aborted
+ // through TerminateTransfer. TransferComplete is called in all other
+ // situations. It's OK to destroy the |fetcher| object in this callback.
virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
-
- // Called when the transfer has been aborted through TerminateTransfer. It's
- // OK to destroy the |fetcher| object in this callback.
virtual void TransferTerminated(HttpFetcher* fetcher) {}
};
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
index a42bb3b..ccda0b6 100644
--- a/http_fetcher_unittest.cc
+++ b/http_fetcher_unittest.cc
@@ -16,10 +16,11 @@
#include "update_engine/libcurl_http_fetcher.h"
#include "update_engine/mock_http_fetcher.h"
-#include "update_engine/multi_http_fetcher.h"
+#include "update_engine/multi_range_http_fetcher.h"
#include "update_engine/proxy_resolver.h"
using std::make_pair;
+using std::pair;
using std::string;
using std::vector;
@@ -170,16 +171,16 @@
};
template <>
-class HttpFetcherTest<MultiHttpFetcher<LibcurlHttpFetcher> >
+class HttpFetcherTest<MultiRangeHTTPFetcher>
: public HttpFetcherTest<LibcurlHttpFetcher> {
public:
HttpFetcher* NewLargeFetcher() {
- MultiHttpFetcher<LibcurlHttpFetcher> *ret =
- new MultiHttpFetcher<LibcurlHttpFetcher>(
- reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
- MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect
- ranges(1, make_pair(0, -1));
- ret->set_ranges(ranges);
+ ProxyResolver* resolver =
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_);
+ MultiRangeHTTPFetcher *ret =
+ new MultiRangeHTTPFetcher(new LibcurlHttpFetcher(resolver));
+ ret->ClearRanges();
+ ret->AddRange(0, -1);
// Speed up test execution.
ret->set_idle_seconds(1);
ret->set_retry_seconds(1);
@@ -193,7 +194,7 @@
typedef ::testing::Types<LibcurlHttpFetcher,
MockHttpFetcher,
- MultiHttpFetcher<LibcurlHttpFetcher> >
+ MultiRangeHTTPFetcher>
HttpFetcherTestTypes;
TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
@@ -344,7 +345,7 @@
callback_once_ = false;
// |fetcher| can be destroyed during this callback.
fetcher_.reset(NULL);
- }
+ }
void TerminateTransfer() {
CHECK(once_);
once_ = false;
@@ -651,7 +652,7 @@
void MultiTest(HttpFetcher* fetcher_in,
const string& url,
- const MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect& ranges,
+ const vector<pair<off_t, off_t> >& ranges,
const string& expected_prefix,
off_t expected_size,
int expected_response_code) {
@@ -660,10 +661,15 @@
MultiHttpFetcherTestDelegate delegate(expected_response_code);
delegate.loop_ = loop;
delegate.fetcher_.reset(fetcher_in);
- MultiHttpFetcher<LibcurlHttpFetcher>* multi_fetcher =
- dynamic_cast<MultiHttpFetcher<LibcurlHttpFetcher>*>(fetcher_in);
+ MultiRangeHTTPFetcher* multi_fetcher =
+ dynamic_cast<MultiRangeHTTPFetcher*>(fetcher_in);
ASSERT_TRUE(multi_fetcher);
- multi_fetcher->set_ranges(ranges);
+ multi_fetcher->ClearRanges();
+ for (vector<pair<off_t, off_t> >::const_iterator it = ranges.begin(),
+ e = ranges.end(); it != e; ++it) {
+ LOG(INFO) << "Adding range";
+ multi_fetcher->AddRange(it->first, it->second);
+ }
multi_fetcher->SetConnectionAsExpensive(false);
multi_fetcher->SetBuildType(false);
multi_fetcher->set_delegate(&delegate);
@@ -687,7 +693,7 @@
typename TestFixture::HttpServer server;
ASSERT_TRUE(server.started_);
- MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+ vector<pair<off_t, off_t> > ranges;
ranges.push_back(make_pair(0, 25));
ranges.push_back(make_pair(99, -1));
MultiTest(this->NewLargeFetcher(),
@@ -704,7 +710,7 @@
typename TestFixture::HttpServer server;
ASSERT_TRUE(server.started_);
- MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+ vector<pair<off_t, off_t> > ranges;
ranges.push_back(make_pair(0, 24));
MultiTest(this->NewLargeFetcher(),
this->BigUrl(),
@@ -720,7 +726,7 @@
typename TestFixture::HttpServer server;
ASSERT_TRUE(server.started_);
- MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+ vector<pair<off_t, off_t> > ranges;
ranges.push_back(make_pair(kBigSize - 2, -1));
ranges.push_back(make_pair(kBigSize - 3, -1));
MultiTest(this->NewLargeFetcher(),
@@ -737,9 +743,10 @@
typename TestFixture::HttpServer server;
ASSERT_TRUE(server.started_);
- MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+ vector<pair<off_t, off_t> > ranges;
ranges.push_back(make_pair(kBigSize - 2, 4));
for (int i = 0; i < 2; ++i) {
+ LOG(INFO) << "i = " << i;
MultiTest(this->NewLargeFetcher(),
this->BigUrl(),
ranges,
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index cdc7523..16d0239 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -156,6 +156,7 @@
retry_count_ = 0;
no_network_retry_count_ = 0;
http_response_code_ = 0;
+ terminate_requested_ = false;
ResolveProxiesForUrl(url);
ResumeTransfer(url);
CurlPerformOnce();
diff --git a/multi_http_fetcher.h b/multi_http_fetcher.h
deleted file mode 100644
index 3d5ee11..0000000
--- a/multi_http_fetcher.h
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_HTTP_FETCHER_H__
-#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_HTTP_FETCHER_H__
-
-#include <deque>
-#include <tr1/memory>
-#include <utility>
-#include <vector>
-
-#include "update_engine/http_fetcher.h"
-#include "update_engine/utils.h"
-
-// This class is a simple wrapper around an HttpFetcher. The client
-// specifies a vector of byte ranges. MultiHttpFetcher will fetch bytes
-// from those offsets. Pass -1 as a length to specify unlimited length.
-// It really only would make sense for the last range specified to have
-// unlimited length.
-
-namespace chromeos_update_engine {
-
-template<typename BaseHttpFetcher>
-class MultiHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
- public:
- typedef std::vector<std::pair<off_t, off_t> > RangesVect;
- typedef std::vector<std::tr1::shared_ptr<BaseHttpFetcher> > FetchersVect;
-
- MultiHttpFetcher(ProxyResolver* proxy_resolver)
- : HttpFetcher(proxy_resolver),
- sent_transfer_complete_(false),
- pending_next_fetcher_(false),
- current_index_(0),
- bytes_received_this_fetcher_(0) {}
- ~MultiHttpFetcher() {}
-
- void set_ranges(const RangesVect& ranges) {
- ranges_ = ranges;
- fetchers_.resize(ranges_.size()); // Allocate the fetchers
- for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher>
- >::iterator it = fetchers_.begin(), e = fetchers_.end();
- it != e; ++it) {
- (*it) = std::tr1::shared_ptr<BaseHttpFetcher>(
- new BaseHttpFetcher(proxy_resolver_));
- (*it)->set_delegate(this);
- }
- LOG(INFO) << "done w/ list";
- }
-
- void SetOffset(off_t offset) {} // for now, doesn't support this
-
- // Begins the transfer to the specified URL.
- void BeginTransfer(const std::string& url) {
- url_ = url;
- if (ranges_.empty()) {
- if (delegate_)
- delegate_->TransferComplete(this, true);
- return;
- }
- current_index_ = 0;
- LOG(INFO) << "starting first transfer";
- StartTransfer();
- }
-
- void TransferTerminated(HttpFetcher* fetcher) {
- LOG(INFO) << "Received transfer terminated.";
- if (pending_next_fetcher_) {
- pending_next_fetcher_ = false;
- if (++current_index_ >= ranges_.size()) {
- SendTransferComplete(fetcher, true);
- } else {
- StartTransfer();
- }
- return;
- }
- current_index_ = ranges_.size();
- sent_transfer_complete_ = true; // a fib
- if (delegate_) {
- // Note that after the callback returns this object may be destroyed.
- delegate_->TransferTerminated(this);
- }
- }
-
- void TerminateTransfer() {
- // If the current fetcher is already being terminated, just wait for its
- // TransferTerminated callback.
- if (pending_next_fetcher_) {
- pending_next_fetcher_ = false;
- return;
- }
- // If there's a current active fetcher terminate it and wait for its
- // TransferTerminated callback.
- if (current_index_ < fetchers_.size()) {
- fetchers_[current_index_]->TerminateTransfer();
- return;
- }
- // Transfer is terminated before it got started and before any ranges were
- // added.
- TransferTerminated(this);
- }
-
- void Pause() {
- if (current_index_ < fetchers_.size())
- fetchers_[current_index_]->Pause();
- }
-
- void Unpause() {
- if (current_index_ < fetchers_.size())
- fetchers_[current_index_]->Unpause();
- }
-
- // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
- void set_idle_seconds(int seconds) {
- for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
- it = fetchers_.begin(),
- e = fetchers_.end(); it != e; ++it) {
- (*it)->set_idle_seconds(seconds);
- }
- }
- void set_retry_seconds(int seconds) {
- for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
- it = fetchers_.begin(),
- e = fetchers_.end(); it != e; ++it) {
- (*it)->set_retry_seconds(seconds);
- }
- }
- void SetConnectionAsExpensive(bool is_expensive) {
- for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
- it = fetchers_.begin(),
- e = fetchers_.end(); it != e; ++it) {
- (*it)->SetConnectionAsExpensive(is_expensive);
- }
- }
- void SetBuildType(bool is_official) {
- for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
- it = fetchers_.begin(),
- e = fetchers_.end(); it != e; ++it) {
- (*it)->SetBuildType(is_official);
- }
- }
-
- virtual void SetProxies(const std::deque<std::string>& proxies) {
- for (typename FetchersVect::iterator it = fetchers_.begin(),
- e = fetchers_.end(); it != e; ++it) {
- (*it)->SetProxies(proxies);
- }
- }
-
- private:
- void SendTransferComplete(HttpFetcher* fetcher, bool successful) {
- if (sent_transfer_complete_)
- return;
- LOG(INFO) << "Sending transfer complete";
- sent_transfer_complete_ = true;
- http_response_code_ = fetcher->http_response_code();
- if (delegate_)
- delegate_->TransferComplete(this, successful);
- }
-
- void StartTransfer() {
- if (current_index_ >= ranges_.size()) {
- return;
- }
- LOG(INFO) << "Starting a transfer @" << ranges_[current_index_].first << "("
- << ranges_[current_index_].second << ")";
- bytes_received_this_fetcher_ = 0;
- fetchers_[current_index_]->SetOffset(ranges_[current_index_].first);
- if (delegate_)
- delegate_->SeekToOffset(ranges_[current_index_].first);
- fetchers_[current_index_]->BeginTransfer(url_);
- }
-
- void ReceivedBytes(HttpFetcher* fetcher, const char* bytes, int length) {
- TEST_AND_RETURN(current_index_ < ranges_.size());
- TEST_AND_RETURN(fetcher == fetchers_[current_index_].get());
- TEST_AND_RETURN(!pending_next_fetcher_);
- off_t next_size = length;
- if (ranges_[current_index_].second >= 0) {
- next_size = std::min(next_size,
- ranges_[current_index_].second -
- bytes_received_this_fetcher_);
- }
- LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
- if (delegate_) {
- delegate_->ReceivedBytes(this, bytes, next_size);
- }
- bytes_received_this_fetcher_ += length;
- if (ranges_[current_index_].second >= 0 &&
- bytes_received_this_fetcher_ >= ranges_[current_index_].second) {
- // Terminates the current fetcher. Waits for its TransferTerminated
- // callback before starting the next fetcher so that we don't end up
- // signalling the delegate that the whole multi-transfer is complete
- // before all fetchers are really done and cleaned up.
- pending_next_fetcher_ = true;
- fetcher->TerminateTransfer();
- }
- }
-
- void TransferComplete(HttpFetcher* fetcher, bool successful) {
- TEST_AND_RETURN(!pending_next_fetcher_);
- LOG(INFO) << "Received transfer complete.";
- if (current_index_ >= ranges_.size()) {
- SendTransferComplete(fetcher, true);
- return;
- }
- if (ranges_[current_index_].second < 0) {
- // We're done with the current operation
- current_index_++;
- if (current_index_ >= ranges_.size() || !successful) {
- SendTransferComplete(fetcher, successful);
- } else {
- // Do the next transfer
- StartTransfer();
- }
- return;
- }
- if (bytes_received_this_fetcher_ < ranges_[current_index_].second) {
- LOG(WARNING) << "Received insufficient bytes from fetcher. "
- << "Ending early";
- SendTransferComplete(fetcher, false);
- return;
- }
- LOG(INFO) << "Got spurious TransferComplete. Ignoring.";
- }
-
- // If true, do not send any more data or TransferComplete to the delegate.
- bool sent_transfer_complete_;
-
- // If true, the next fetcher needs to be started when TransferTerminated is
- // received from the current fetcher.
- bool pending_next_fetcher_;
-
- RangesVect ranges_;
- FetchersVect fetchers_;
-
- RangesVect::size_type current_index_; // index into ranges_, fetchers_
- off_t bytes_received_this_fetcher_;
-
- DISALLOW_COPY_AND_ASSIGN(MultiHttpFetcher);
-};
-
-} // namespace chromeos_update_engine
-
-#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_HTTP_FETCHER_H__
diff --git a/multi_range_http_fetcher.cc b/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..16d2c09
--- /dev/null
+++ b/multi_range_http_fetcher.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/multi_range_http_fetcher.h"
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+// Begins the transfer to the specified URL.
+// State change: Stopped -> Downloading
+// (corner case: Stopped -> Stopped for an empty request)
+void MultiRangeHTTPFetcher::BeginTransfer(const std::string& url) {
+ CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
+ CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
+ CHECK(!terminating_) << "BeginTransfer but terminating.";
+
+ if (ranges_.empty()) {
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, true);
+ return;
+ }
+ url_ = url;
+ current_index_ = 0;
+ bytes_received_this_range_ = 0;
+ LOG(INFO) << "starting first transfer";
+ base_fetcher_->set_delegate(this);
+ StartTransfer();
+}
+
+// State change: Downloading -> Pending transfer ended
+void MultiRangeHTTPFetcher::TerminateTransfer() {
+ if (!base_fetcher_active_) {
+ LOG(INFO) << "Called TerminateTransfer but not active.";
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferTerminated(this);
+ return;
+ }
+ terminating_ = true;
+
+ if (!pending_transfer_ended_) {
+ base_fetcher_->TerminateTransfer();
+ }
+}
+
+// State change: Stopped or Downloading -> Downloading
+void MultiRangeHTTPFetcher::StartTransfer() {
+ if (current_index_ >= ranges_.size()) {
+ return;
+ }
+ LOG(INFO) << "Starting a transfer @" << ranges_[current_index_].first << "("
+ << ranges_[current_index_].second << ")";
+ bytes_received_this_range_ = 0;
+ base_fetcher_->SetOffset(ranges_[current_index_].first);
+ if (delegate_)
+ delegate_->SeekToOffset(ranges_[current_index_].first);
+ base_fetcher_active_ = true;
+ base_fetcher_->BeginTransfer(url_);
+}
+
+// State change: Downloading -> Downloading or Pending transfer ended
+void MultiRangeHTTPFetcher::ReceivedBytes(HttpFetcher* fetcher,
+ const char* bytes,
+ int length) {
+ CHECK_LT(current_index_, ranges_.size());
+ CHECK_EQ(fetcher, base_fetcher_.get());
+ CHECK(!pending_transfer_ended_);
+ off_t next_size = length;
+ if (ranges_[current_index_].second >= 0) {
+ next_size = std::min(next_size,
+ ranges_[current_index_].second -
+ bytes_received_this_range_);
+ }
+ LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
+ if (delegate_) {
+ delegate_->ReceivedBytes(this, bytes, next_size);
+ }
+ bytes_received_this_range_ += length;
+ if (ranges_[current_index_].second >= 0 &&
+ bytes_received_this_range_ >= ranges_[current_index_].second) {
+ // Terminates the current fetcher. Waits for its TransferTerminated
+ // callback before starting the next range so that we don't end up
+ // signalling the delegate that the whole multi-transfer is complete
+ // before all fetchers are really done and cleaned up.
+ pending_transfer_ended_ = true;
+ LOG(INFO) << "terminating transfer";
+ fetcher->TerminateTransfer();
+ }
+}
+
+// State change: Downloading or Pending transfer ended -> Stopped
+void MultiRangeHTTPFetcher::TransferEnded(HttpFetcher* fetcher,
+ bool successful) {
+ CHECK(base_fetcher_active_) << "Transfer ended unexpectedly.";
+ CHECK_EQ(fetcher, base_fetcher_.get());
+ pending_transfer_ended_ = false;
+ http_response_code_ = fetcher->http_response_code();
+ LOG(INFO) << "TransferEnded w/ code " << http_response_code_;
+ if (terminating_) {
+ LOG(INFO) << "Terminating.";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferTerminated(this);
+ return;
+ }
+
+ // If we didn't get enough bytes, it's failure
+ if (ranges_[current_index_].second >= 0) {
+ if (bytes_received_this_range_ < ranges_[current_index_].second) {
+ // Failure
+ LOG(INFO) << "Didn't get enough bytes. Ending w/ failure.";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, false);
+ return;
+ }
+ // We got enough bytes and there were bytes specified, so this is success.
+ successful = true;
+ }
+
+ // If we have another fetcher, use that.
+ if (current_index_ + 1 < ranges_.size()) {
+ current_index_++;
+ LOG(INFO) << "Starting next transfer (" << current_index_ << ").";
+ StartTransfer();
+ return;
+ }
+
+ LOG(INFO) << "Done w/ all transfers";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, successful);
+}
+
+void MultiRangeHTTPFetcher::TransferComplete(HttpFetcher* fetcher,
+ bool successful) {
+ LOG(INFO) << "Received transfer complete.";
+ TransferEnded(fetcher, successful);
+}
+
+void MultiRangeHTTPFetcher::TransferTerminated(HttpFetcher* fetcher) {
+ LOG(INFO) << "Received transfer terminated.";
+ TransferEnded(fetcher, false);
+}
+
+void MultiRangeHTTPFetcher::Reset() {
+ base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false;
+ current_index_ = 0;
+ bytes_received_this_range_ = 0;
+}
+
+} // namespace chromeos_update_engine
diff --git a/multi_range_http_fetcher.h b/multi_range_http_fetcher.h
new file mode 100644
index 0000000..8a54bde
--- /dev/null
+++ b/multi_range_http_fetcher.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H__
+
+#include <deque>
+#include <utility>
+#include <vector>
+
+#include <base/scoped_ptr.h>
+
+#include "update_engine/http_fetcher.h"
+
+// This class is a simple wrapper around an HttpFetcher. The client
+// specifies a vector of byte ranges. MultiRangeHTTPFetcher will fetch bytes
+// from those offsets, using the same bash fetcher for all ranges. Thus, the
+// fetcher must support beginning a transfter after one has stopped. Pass -1
+// as a length to specify unlimited length. It really only would make sense
+// for the last range specified to have unlimited length, tho it is legal for
+// other entries to have unlimited length.
+
+// There are three states a MultiRangeHTTPFetcher object will be in:
+// - Stopped (start state)
+// - Downloading
+// - Pending transfer ended
+// Various functions below that might change state indicate possible
+// state changes.
+
+namespace chromeos_update_engine {
+
+class MultiRangeHTTPFetcher : public HttpFetcher, public HttpFetcherDelegate {
+ public:
+ // Takes ownership of the passed in fetcher.
+ explicit MultiRangeHTTPFetcher(HttpFetcher* base_fetcher)
+ : HttpFetcher(base_fetcher->proxy_resolver()),
+ base_fetcher_(base_fetcher),
+ base_fetcher_active_(false),
+ pending_transfer_ended_(false),
+ terminating_(false),
+ current_index_(0),
+ bytes_received_this_range_(0) {}
+ ~MultiRangeHTTPFetcher() {}
+
+ void ClearRanges() { ranges_.clear(); }
+
+ void AddRange(off_t offset, off_t size) {
+ ranges_.push_back(std::make_pair(offset, size));
+ }
+
+ virtual void SetOffset(off_t offset) {} // for now, doesn't support this
+
+ // Begins the transfer to the specified URL.
+ // State change: Stopped -> Downloading
+ // (corner case: Stopped -> Stopped for an empty request)
+ virtual void BeginTransfer(const std::string& url);
+
+ // State change: Downloading -> Pending transfer ended
+ virtual void TerminateTransfer();
+
+ virtual void Pause() { base_fetcher_->Pause(); }
+
+ virtual void Unpause() { base_fetcher_->Unpause(); }
+
+ // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
+ virtual void set_idle_seconds(int seconds) {
+ base_fetcher_->set_idle_seconds(seconds);
+ }
+ virtual void set_retry_seconds(int seconds) {
+ base_fetcher_->set_retry_seconds(seconds);
+ }
+ virtual void SetConnectionAsExpensive(bool is_expensive) {
+ base_fetcher_->SetConnectionAsExpensive(is_expensive);
+ }
+ virtual void SetBuildType(bool is_official) {
+ base_fetcher_->SetBuildType(is_official);
+ }
+ virtual void SetProxies(const std::deque<std::string>& proxies) {
+ base_fetcher_->SetProxies(proxies);
+ }
+
+ private:
+ // pair<offset, length>:
+ typedef std::vector<std::pair<off_t, off_t> > RangesVect;
+
+ // State change: Stopped or Downloading -> Downloading
+ void StartTransfer();
+
+// State change: Downloading -> Downloading or Pending transfer ended
+ virtual void ReceivedBytes(HttpFetcher* fetcher,
+ const char* bytes,
+ int length);
+
+ // State change: Pending transfer ended -> Stopped
+ void TransferEnded(HttpFetcher* fetcher, bool successful);
+ // These two call TransferEnded():
+ virtual void TransferComplete(HttpFetcher* fetcher, bool successful);
+ virtual void TransferTerminated(HttpFetcher* fetcher);
+
+ void Reset();
+
+ scoped_ptr<HttpFetcher> base_fetcher_;
+
+ // If true, do not send any more data or TransferComplete to the delegate.
+ bool base_fetcher_active_;
+
+ // If true, the next fetcher needs to be started when TransferTerminated is
+ // received from the current fetcher.
+ bool pending_transfer_ended_;
+
+ // True if we are waiting for base fetcher to terminate b/c we are
+ // ourselves terminating.
+ bool terminating_;
+
+ RangesVect ranges_;
+
+ RangesVect::size_type current_index_; // index into ranges_
+ off_t bytes_received_this_range_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiRangeHTTPFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H__
diff --git a/update_attempter.cc b/update_attempter.cc
index 85500e6..8139687 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -22,7 +22,7 @@
#include "update_engine/download_action.h"
#include "update_engine/filesystem_copier_action.h"
#include "update_engine/libcurl_http_fetcher.h"
-#include "update_engine/multi_http_fetcher.h"
+#include "update_engine/multi_range_http_fetcher.h"
#include "update_engine/omaha_request_action.h"
#include "update_engine/omaha_request_params.h"
#include "update_engine/omaha_response_handler_action.h"
@@ -185,8 +185,8 @@
OmahaEvent::kTypeUpdateDownloadStarted),
new LibcurlHttpFetcher(GetProxyResolver())));
shared_ptr<DownloadAction> download_action(
- new DownloadAction(prefs_, new MultiHttpFetcher<LibcurlHttpFetcher>(
- GetProxyResolver())));
+ new DownloadAction(prefs_, new MultiRangeHTTPFetcher(
+ new LibcurlHttpFetcher(GetProxyResolver()))));
shared_ptr<OmahaRequestAction> download_finished_action(
new OmahaRequestAction(prefs_,
omaha_request_params_,
@@ -554,15 +554,14 @@
}
void UpdateAttempter::SetupDownload() {
- MultiHttpFetcher<LibcurlHttpFetcher>* fetcher =
- dynamic_cast<MultiHttpFetcher<LibcurlHttpFetcher>*>(
- download_action_->http_fetcher());
- MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+ MultiRangeHTTPFetcher* fetcher =
+ dynamic_cast<MultiRangeHTTPFetcher*>(download_action_->http_fetcher());
+ fetcher->ClearRanges();
if (response_handler_action_->install_plan().is_resume) {
// Resuming an update so fetch the update manifest metadata first.
int64_t manifest_metadata_size = 0;
prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
- ranges.push_back(make_pair(0, manifest_metadata_size));
+ fetcher->AddRange(0, manifest_metadata_size);
// If there're remaining unprocessed data blobs, fetch them. Be careful not
// to request data beyond the end of the payload to avoid 416 HTTP response
// error codes.
@@ -570,12 +569,11 @@
prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
uint64_t resume_offset = manifest_metadata_size + next_data_offset;
if (resume_offset < response_handler_action_->install_plan().size) {
- ranges.push_back(make_pair(resume_offset, -1));
+ fetcher->AddRange(resume_offset, -1);
}
} else {
- ranges.push_back(make_pair(0, -1));
+ fetcher->AddRange(0, -1);
}
- fetcher->set_ranges(ranges);
}
} // namespace chromeos_update_engine