1// 2// Copyright (C) 2010 The Android Open Source Project 3// 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17#include "update_engine/common/multi_range_http_fetcher.h" 18 19#include <base/strings/stringprintf.h> 20 21#include <algorithm> 22#include <string> 23 24#include "update_engine/common/utils.h" 25 26namespace chromeos_update_engine { 27 28// Begins the transfer to the specified URL. 29// State change: Stopped -> Downloading 30// (corner case: Stopped -> Stopped for an empty request) 31void MultiRangeHttpFetcher::BeginTransfer(const std::string& url) { 32 CHECK(!base_fetcher_active_) << "BeginTransfer but already active."; 33 CHECK(!pending_transfer_ended_) << "BeginTransfer but pending."; 34 CHECK(!terminating_) << "BeginTransfer but terminating."; 35 36 if (ranges_.empty()) { 37 // Note that after the callback returns this object may be destroyed. 38 if (delegate_) 39 delegate_->TransferComplete(this, true); 40 return; 41 } 42 url_ = url; 43 current_index_ = 0; 44 bytes_received_this_range_ = 0; 45 LOG(INFO) << "starting first transfer"; 46 base_fetcher_->set_delegate(this); 47 StartTransfer(); 48} 49 50// State change: Downloading -> Pending transfer ended 51void MultiRangeHttpFetcher::TerminateTransfer() { 52 if (!base_fetcher_active_) { 53 LOG(INFO) << "Called TerminateTransfer but not active."; 54 // Note that after the callback returns this object may be destroyed. 55 if (delegate_) 56 delegate_->TransferTerminated(this); 57 return; 58 } 59 terminating_ = true; 60 61 if (!pending_transfer_ended_) { 62 base_fetcher_->TerminateTransfer(); 63 } 64} 65 66// State change: Stopped or Downloading -> Downloading 67void MultiRangeHttpFetcher::StartTransfer() { 68 if (current_index_ >= ranges_.size()) { 69 return; 70 } 71 72 Range range = ranges_[current_index_]; 73 LOG(INFO) << "starting transfer of range " << range.ToString(); 74 75 bytes_received_this_range_ = 0; 76 base_fetcher_->SetOffset(range.offset()); 77 if (range.HasLength()) 78 base_fetcher_->SetLength(range.length()); 79 else 80 base_fetcher_->UnsetLength(); 81 if (delegate_) 82 delegate_->SeekToOffset(range.offset()); 83 base_fetcher_active_ = true; 84 base_fetcher_->BeginTransfer(url_); 85} 86 87// State change: Downloading -> Downloading or Pending transfer ended 88void MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher, 89 const void* bytes, 90 size_t length) { 91 CHECK_LT(current_index_, ranges_.size()); 92 CHECK_EQ(fetcher, base_fetcher_.get()); 93 CHECK(!pending_transfer_ended_); 94 size_t next_size = length; 95 Range range = ranges_[current_index_]; 96 if (range.HasLength()) { 97 next_size = std::min(next_size, 98 range.length() - bytes_received_this_range_); 99 } 100 LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0"; 101 if (delegate_) { 102 delegate_->ReceivedBytes(this, bytes, next_size); 103 } 104 bytes_received_this_range_ += length; 105 if (range.HasLength() && bytes_received_this_range_ >= range.length()) { 106 // Terminates the current fetcher. Waits for its TransferTerminated 107 // callback before starting the next range so that we don't end up 108 // signalling the delegate that the whole multi-transfer is complete 109 // before all fetchers are really done and cleaned up. 110 pending_transfer_ended_ = true; 111 LOG(INFO) << "terminating transfer"; 112 fetcher->TerminateTransfer(); 113 } 114} 115 116// State change: Downloading or Pending transfer ended -> Stopped 117void MultiRangeHttpFetcher::TransferEnded(HttpFetcher* fetcher, 118 bool successful) { 119 CHECK(base_fetcher_active_) << "Transfer ended unexpectedly."; 120 CHECK_EQ(fetcher, base_fetcher_.get()); 121 pending_transfer_ended_ = false; 122 http_response_code_ = fetcher->http_response_code(); 123 LOG(INFO) << "TransferEnded w/ code " << http_response_code_; 124 if (terminating_) { 125 LOG(INFO) << "Terminating."; 126 Reset(); 127 // Note that after the callback returns this object may be destroyed. 128 if (delegate_) 129 delegate_->TransferTerminated(this); 130 return; 131 } 132 133 // If we didn't get enough bytes, it's failure 134 Range range = ranges_[current_index_]; 135 if (range.HasLength()) { 136 if (bytes_received_this_range_ < range.length()) { 137 // Failure 138 LOG(INFO) << "Didn't get enough bytes. Ending w/ failure."; 139 Reset(); 140 // Note that after the callback returns this object may be destroyed. 141 if (delegate_) 142 delegate_->TransferComplete(this, false); 143 return; 144 } 145 // We got enough bytes and there were bytes specified, so this is success. 146 successful = true; 147 } 148 149 // If we have another transfer, do that. 150 if (current_index_ + 1 < ranges_.size()) { 151 current_index_++; 152 LOG(INFO) << "Starting next transfer (" << current_index_ << ")."; 153 StartTransfer(); 154 return; 155 } 156 157 LOG(INFO) << "Done w/ all transfers"; 158 Reset(); 159 // Note that after the callback returns this object may be destroyed. 160 if (delegate_) 161 delegate_->TransferComplete(this, successful); 162} 163 164void MultiRangeHttpFetcher::TransferComplete(HttpFetcher* fetcher, 165 bool successful) { 166 LOG(INFO) << "Received transfer complete."; 167 TransferEnded(fetcher, successful); 168} 169 170void MultiRangeHttpFetcher::TransferTerminated(HttpFetcher* fetcher) { 171 LOG(INFO) << "Received transfer terminated."; 172 TransferEnded(fetcher, false); 173} 174 175void MultiRangeHttpFetcher::Reset() { 176 base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false; 177 current_index_ = 0; 178 bytes_received_this_range_ = 0; 179} 180 181std::string MultiRangeHttpFetcher::Range::ToString() const { 182 std::string range_str = base::StringPrintf("%jd+", offset()); 183 if (HasLength()) 184 range_str += std::to_string(length()); 185 else 186 range_str += "?"; 187 return range_str; 188} 189 190} // namespace chromeos_update_engine 191