1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/download/download_file_impl.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/files/file_util.h" 11#include "base/message_loop/message_loop_proxy.h" 12#include "base/strings/stringprintf.h" 13#include "base/time/time.h" 14#include "content/browser/byte_stream.h" 15#include "content/browser/download/download_create_info.h" 16#include "content/browser/download/download_interrupt_reasons_impl.h" 17#include "content/browser/download/download_net_log_parameters.h" 18#include "content/browser/download/download_stats.h" 19#include "content/public/browser/browser_thread.h" 20#include "content/public/browser/download_destination_observer.h" 21#include "net/base/io_buffer.h" 22 23namespace content { 24 25const int kUpdatePeriodMs = 500; 26const int kMaxTimeBlockingFileThreadMs = 1000; 27 28// These constants control the default retry behavior for failing renames. Each 29// retry is performed after a delay that is twice the previous delay. The 30// initial delay is specified by kInitialRenameRetryDelayMs. 31const int kMaxRenameRetries = 3; 32const int kInitialRenameRetryDelayMs = 200; 33 34int DownloadFile::number_active_objects_ = 0; 35 36DownloadFileImpl::DownloadFileImpl( 37 scoped_ptr<DownloadSaveInfo> save_info, 38 const base::FilePath& default_download_directory, 39 const GURL& url, 40 const GURL& referrer_url, 41 bool calculate_hash, 42 scoped_ptr<ByteStreamReader> stream, 43 const net::BoundNetLog& bound_net_log, 44 base::WeakPtr<DownloadDestinationObserver> observer) 45 : file_(save_info->file_path, 46 url, 47 referrer_url, 48 save_info->offset, 49 calculate_hash, 50 save_info->hash_state, 51 save_info->file.Pass(), 52 bound_net_log), 53 default_download_directory_(default_download_directory), 54 stream_reader_(stream.Pass()), 55 bytes_seen_(0), 56 bound_net_log_(bound_net_log), 57 observer_(observer), 58 weak_factory_(this) { 59} 60 61DownloadFileImpl::~DownloadFileImpl() { 62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 63 --number_active_objects_; 64} 65 66void DownloadFileImpl::Initialize(const InitializeCallback& callback) { 67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 68 69 update_timer_.reset(new base::RepeatingTimer<DownloadFileImpl>()); 70 DownloadInterruptReason result = 71 file_.Initialize(default_download_directory_); 72 if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { 73 BrowserThread::PostTask( 74 BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); 75 return; 76 } 77 78 stream_reader_->RegisterCallback( 79 base::Bind(&DownloadFileImpl::StreamActive, weak_factory_.GetWeakPtr())); 80 81 download_start_ = base::TimeTicks::Now(); 82 83 // Primarily to make reset to zero in restart visible to owner. 84 SendUpdate(); 85 86 // Initial pull from the straw. 87 StreamActive(); 88 89 BrowserThread::PostTask( 90 BrowserThread::UI, FROM_HERE, base::Bind( 91 callback, DOWNLOAD_INTERRUPT_REASON_NONE)); 92 93 ++number_active_objects_; 94} 95 96DownloadInterruptReason DownloadFileImpl::AppendDataToFile( 97 const char* data, size_t data_len) { 98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 99 100 if (!update_timer_->IsRunning()) { 101 update_timer_->Start(FROM_HERE, 102 base::TimeDelta::FromMilliseconds(kUpdatePeriodMs), 103 this, &DownloadFileImpl::SendUpdate); 104 } 105 rate_estimator_.Increment(data_len); 106 return file_.AppendDataToFile(data, data_len); 107} 108 109void DownloadFileImpl::RenameAndUniquify( 110 const base::FilePath& full_path, 111 const RenameCompletionCallback& callback) { 112 RenameWithRetryInternal( 113 full_path, UNIQUIFY, kMaxRenameRetries, base::TimeTicks(), callback); 114} 115 116void DownloadFileImpl::RenameAndAnnotate( 117 const base::FilePath& full_path, 118 const RenameCompletionCallback& callback) { 119 RenameWithRetryInternal(full_path, 120 ANNOTATE_WITH_SOURCE_INFORMATION, 121 kMaxRenameRetries, 122 base::TimeTicks(), 123 callback); 124} 125 126base::TimeDelta DownloadFileImpl::GetRetryDelayForFailedRename( 127 int attempt_number) { 128 DCHECK_GE(attempt_number, 0); 129 // |delay| starts at kInitialRenameRetryDelayMs and increases by a factor of 130 // 2 at each subsequent retry. Assumes that |retries_left| starts at 131 // kMaxRenameRetries. Also assumes that kMaxRenameRetries is less than the 132 // number of bits in an int. 133 return base::TimeDelta::FromMilliseconds(kInitialRenameRetryDelayMs) * 134 (1 << attempt_number); 135} 136 137bool DownloadFileImpl::ShouldRetryFailedRename(DownloadInterruptReason reason) { 138 return reason == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR; 139} 140 141void DownloadFileImpl::RenameWithRetryInternal( 142 const base::FilePath& full_path, 143 RenameOption option, 144 int retries_left, 145 base::TimeTicks time_of_first_failure, 146 const RenameCompletionCallback& callback) { 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 148 149 base::FilePath new_path(full_path); 150 151 if ((option & UNIQUIFY) && full_path != file_.full_path()) { 152 int uniquifier = 153 base::GetUniquePathNumber(new_path, base::FilePath::StringType()); 154 if (uniquifier > 0) 155 new_path = new_path.InsertBeforeExtensionASCII( 156 base::StringPrintf(" (%d)", uniquifier)); 157 } 158 159 DownloadInterruptReason reason = file_.Rename(new_path); 160 161 // Attempt to retry the rename if possible. If the rename failed and the 162 // subsequent open also failed, then in_progress() would be false. We don't 163 // try to retry renames if the in_progress() was false to begin with since we 164 // have less assurance that the file at file_.full_path() was the one we were 165 // working with. 166 if (ShouldRetryFailedRename(reason) && file_.in_progress() && 167 retries_left > 0) { 168 int attempt_number = kMaxRenameRetries - retries_left; 169 BrowserThread::PostDelayedTask( 170 BrowserThread::FILE, 171 FROM_HERE, 172 base::Bind(&DownloadFileImpl::RenameWithRetryInternal, 173 weak_factory_.GetWeakPtr(), 174 full_path, 175 option, 176 --retries_left, 177 time_of_first_failure.is_null() ? base::TimeTicks::Now() 178 : time_of_first_failure, 179 callback), 180 GetRetryDelayForFailedRename(attempt_number)); 181 return; 182 } 183 184 if (!time_of_first_failure.is_null()) 185 RecordDownloadFileRenameResultAfterRetry( 186 base::TimeTicks::Now() - time_of_first_failure, reason); 187 188 if (reason == DOWNLOAD_INTERRUPT_REASON_NONE && 189 (option & ANNOTATE_WITH_SOURCE_INFORMATION)) { 190 // Doing the annotation after the rename rather than before leaves 191 // a very small window during which the file has the final name but 192 // hasn't been marked with the Mark Of The Web. However, it allows 193 // anti-virus scanners on Windows to actually see the data 194 // (http://crbug.com/127999) under the correct name (which is information 195 // it uses). 196 reason = file_.AnnotateWithSourceInformation(); 197 } 198 199 if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { 200 // Make sure our information is updated, since we're about to 201 // error out. 202 SendUpdate(); 203 204 // Null out callback so that we don't do any more stream processing. 205 stream_reader_->RegisterCallback(base::Closure()); 206 207 new_path.clear(); 208 } 209 210 BrowserThread::PostTask( 211 BrowserThread::UI, FROM_HERE, 212 base::Bind(callback, reason, new_path)); 213} 214 215void DownloadFileImpl::Detach() { 216 file_.Detach(); 217} 218 219void DownloadFileImpl::Cancel() { 220 file_.Cancel(); 221} 222 223base::FilePath DownloadFileImpl::FullPath() const { 224 return file_.full_path(); 225} 226 227bool DownloadFileImpl::InProgress() const { 228 return file_.in_progress(); 229} 230 231int64 DownloadFileImpl::CurrentSpeed() const { 232 return rate_estimator_.GetCountPerSecond(); 233} 234 235bool DownloadFileImpl::GetHash(std::string* hash) { 236 return file_.GetHash(hash); 237} 238 239std::string DownloadFileImpl::GetHashState() { 240 return file_.GetHashState(); 241} 242 243void DownloadFileImpl::SetClientGuid(const std::string& guid) { 244 file_.SetClientGuid(guid); 245} 246 247void DownloadFileImpl::StreamActive() { 248 base::TimeTicks start(base::TimeTicks::Now()); 249 base::TimeTicks now; 250 scoped_refptr<net::IOBuffer> incoming_data; 251 size_t incoming_data_size = 0; 252 size_t total_incoming_data_size = 0; 253 size_t num_buffers = 0; 254 ByteStreamReader::StreamState state(ByteStreamReader::STREAM_EMPTY); 255 DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE; 256 base::TimeDelta delta( 257 base::TimeDelta::FromMilliseconds(kMaxTimeBlockingFileThreadMs)); 258 259 // Take care of any file local activity required. 260 do { 261 state = stream_reader_->Read(&incoming_data, &incoming_data_size); 262 263 switch (state) { 264 case ByteStreamReader::STREAM_EMPTY: 265 break; 266 case ByteStreamReader::STREAM_HAS_DATA: 267 { 268 ++num_buffers; 269 base::TimeTicks write_start(base::TimeTicks::Now()); 270 reason = AppendDataToFile( 271 incoming_data.get()->data(), incoming_data_size); 272 disk_writes_time_ += (base::TimeTicks::Now() - write_start); 273 bytes_seen_ += incoming_data_size; 274 total_incoming_data_size += incoming_data_size; 275 } 276 break; 277 case ByteStreamReader::STREAM_COMPLETE: 278 { 279 reason = static_cast<DownloadInterruptReason>( 280 stream_reader_->GetStatus()); 281 SendUpdate(); 282 base::TimeTicks close_start(base::TimeTicks::Now()); 283 file_.Finish(); 284 base::TimeTicks now(base::TimeTicks::Now()); 285 disk_writes_time_ += (now - close_start); 286 RecordFileBandwidth( 287 bytes_seen_, disk_writes_time_, now - download_start_); 288 update_timer_.reset(); 289 } 290 break; 291 default: 292 NOTREACHED(); 293 break; 294 } 295 now = base::TimeTicks::Now(); 296 } while (state == ByteStreamReader::STREAM_HAS_DATA && 297 reason == DOWNLOAD_INTERRUPT_REASON_NONE && 298 now - start <= delta); 299 300 // If we're stopping to yield the thread, post a task so we come back. 301 if (state == ByteStreamReader::STREAM_HAS_DATA && 302 now - start > delta) { 303 BrowserThread::PostTask( 304 BrowserThread::FILE, FROM_HERE, 305 base::Bind(&DownloadFileImpl::StreamActive, 306 weak_factory_.GetWeakPtr())); 307 } 308 309 if (total_incoming_data_size) 310 RecordFileThreadReceiveBuffers(num_buffers); 311 312 RecordContiguousWriteTime(now - start); 313 314 // Take care of communication with our observer. 315 if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { 316 // Error case for both upstream source and file write. 317 // Shut down processing and signal an error to our observer. 318 // Our observer will clean us up. 319 stream_reader_->RegisterCallback(base::Closure()); 320 weak_factory_.InvalidateWeakPtrs(); 321 SendUpdate(); // Make info up to date before error. 322 BrowserThread::PostTask( 323 BrowserThread::UI, FROM_HERE, 324 base::Bind(&DownloadDestinationObserver::DestinationError, 325 observer_, reason)); 326 } else if (state == ByteStreamReader::STREAM_COMPLETE) { 327 // Signal successful completion and shut down processing. 328 stream_reader_->RegisterCallback(base::Closure()); 329 weak_factory_.InvalidateWeakPtrs(); 330 std::string hash; 331 if (!GetHash(&hash) || file_.IsEmptyHash(hash)) 332 hash.clear(); 333 SendUpdate(); 334 BrowserThread::PostTask( 335 BrowserThread::UI, FROM_HERE, 336 base::Bind( 337 &DownloadDestinationObserver::DestinationCompleted, 338 observer_, hash)); 339 } 340 if (bound_net_log_.IsLogging()) { 341 bound_net_log_.AddEvent( 342 net::NetLog::TYPE_DOWNLOAD_STREAM_DRAINED, 343 base::Bind(&FileStreamDrainedNetLogCallback, total_incoming_data_size, 344 num_buffers)); 345 } 346} 347 348void DownloadFileImpl::SendUpdate() { 349 BrowserThread::PostTask( 350 BrowserThread::UI, FROM_HERE, 351 base::Bind(&DownloadDestinationObserver::DestinationUpdate, 352 observer_, file_.bytes_so_far(), CurrentSpeed(), 353 GetHashState())); 354} 355 356// static 357int DownloadFile::GetNumberOfDownloadFiles() { 358 return number_active_objects_; 359} 360 361} // namespace content 362