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