Home | History | Annotate | Download | only in download
      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 
     23 namespace content {
     24 
     25 const int kUpdatePeriodMs = 500;
     26 const 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.
     31 const int kMaxRenameRetries = 3;
     32 const int kInitialRenameRetryDelayMs = 200;
     33 
     34 int DownloadFile::number_active_objects_ = 0;
     35 
     36 DownloadFileImpl::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 
     61 DownloadFileImpl::~DownloadFileImpl() {
     62   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     63   --number_active_objects_;
     64 }
     65 
     66 void 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 
     96 DownloadInterruptReason 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 
    109 void DownloadFileImpl::RenameAndUniquify(
    110     const base::FilePath& full_path,
    111     const RenameCompletionCallback& callback) {
    112   RenameWithRetryInternal(
    113       full_path, UNIQUIFY, kMaxRenameRetries, base::TimeTicks(), callback);
    114 }
    115 
    116 void 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 
    126 base::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 
    137 bool DownloadFileImpl::ShouldRetryFailedRename(DownloadInterruptReason reason) {
    138   return reason == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
    139 }
    140 
    141 void 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 
    215 void DownloadFileImpl::Detach() {
    216   file_.Detach();
    217 }
    218 
    219 void DownloadFileImpl::Cancel() {
    220   file_.Cancel();
    221 }
    222 
    223 base::FilePath DownloadFileImpl::FullPath() const {
    224   return file_.full_path();
    225 }
    226 
    227 bool DownloadFileImpl::InProgress() const {
    228   return file_.in_progress();
    229 }
    230 
    231 int64 DownloadFileImpl::CurrentSpeed() const {
    232   return rate_estimator_.GetCountPerSecond();
    233 }
    234 
    235 bool DownloadFileImpl::GetHash(std::string* hash) {
    236   return file_.GetHash(hash);
    237 }
    238 
    239 std::string DownloadFileImpl::GetHashState() {
    240   return file_.GetHashState();
    241 }
    242 
    243 void DownloadFileImpl::SetClientGuid(const std::string& guid) {
    244   file_.SetClientGuid(guid);
    245 }
    246 
    247 void 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 
    348 void 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
    357 int DownloadFile::GetNumberOfDownloadFiles() {
    358   return number_active_objects_;
    359 }
    360 
    361 }  // namespace content
    362