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/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 "content/public/browser/power_save_blocker.h"
22#include "net/base/io_buffer.h"
23
24namespace content {
25
26const int kUpdatePeriodMs = 500;
27const int kMaxTimeBlockingFileThreadMs = 1000;
28
29int DownloadFile::number_active_objects_ = 0;
30
31DownloadFileImpl::DownloadFileImpl(
32    scoped_ptr<DownloadSaveInfo> save_info,
33    const base::FilePath& default_download_directory,
34    const GURL& url,
35    const GURL& referrer_url,
36    bool calculate_hash,
37    scoped_ptr<ByteStreamReader> stream,
38    const net::BoundNetLog& bound_net_log,
39    scoped_ptr<PowerSaveBlocker> power_save_blocker,
40    base::WeakPtr<DownloadDestinationObserver> observer)
41        : file_(save_info->file_path,
42                url,
43                referrer_url,
44                save_info->offset,
45                calculate_hash,
46                save_info->hash_state,
47                save_info->file_stream.Pass(),
48                bound_net_log),
49          default_download_directory_(default_download_directory),
50          stream_reader_(stream.Pass()),
51          bytes_seen_(0),
52          bound_net_log_(bound_net_log),
53          observer_(observer),
54          weak_factory_(this),
55          power_save_blocker_(power_save_blocker.Pass()) {
56}
57
58DownloadFileImpl::~DownloadFileImpl() {
59  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
60  --number_active_objects_;
61}
62
63void DownloadFileImpl::Initialize(const InitializeCallback& callback) {
64  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
65
66  update_timer_.reset(new base::RepeatingTimer<DownloadFileImpl>());
67  DownloadInterruptReason result =
68      file_.Initialize(default_download_directory_);
69  if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
70    BrowserThread::PostTask(
71        BrowserThread::UI, FROM_HERE, base::Bind(callback, result));
72    return;
73  }
74
75  stream_reader_->RegisterCallback(
76      base::Bind(&DownloadFileImpl::StreamActive, weak_factory_.GetWeakPtr()));
77
78  download_start_ = base::TimeTicks::Now();
79
80  // Primarily to make reset to zero in restart visible to owner.
81  SendUpdate();
82
83  // Initial pull from the straw.
84  StreamActive();
85
86  BrowserThread::PostTask(
87      BrowserThread::UI, FROM_HERE, base::Bind(
88          callback, DOWNLOAD_INTERRUPT_REASON_NONE));
89
90  ++number_active_objects_;
91}
92
93DownloadInterruptReason DownloadFileImpl::AppendDataToFile(
94    const char* data, size_t data_len) {
95  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
96
97  if (!update_timer_->IsRunning()) {
98    update_timer_->Start(FROM_HERE,
99                         base::TimeDelta::FromMilliseconds(kUpdatePeriodMs),
100                         this, &DownloadFileImpl::SendUpdate);
101  }
102  rate_estimator_.Increment(data_len);
103  return file_.AppendDataToFile(data, data_len);
104}
105
106void DownloadFileImpl::RenameAndUniquify(
107    const base::FilePath& full_path,
108    const RenameCompletionCallback& callback) {
109  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
110
111  base::FilePath new_path(full_path);
112
113  int uniquifier = file_util::GetUniquePathNumber(
114      new_path, base::FilePath::StringType());
115  if (uniquifier > 0) {
116    new_path = new_path.InsertBeforeExtensionASCII(
117        base::StringPrintf(" (%d)", uniquifier));
118  }
119
120  DownloadInterruptReason reason = file_.Rename(new_path);
121  if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
122    // Make sure our information is updated, since we're about to
123    // error out.
124    SendUpdate();
125
126    // Null out callback so that we don't do any more stream processing.
127    stream_reader_->RegisterCallback(base::Closure());
128
129    new_path.clear();
130  }
131
132  BrowserThread::PostTask(
133      BrowserThread::UI, FROM_HERE,
134      base::Bind(callback, reason, new_path));
135}
136
137void DownloadFileImpl::RenameAndAnnotate(
138    const base::FilePath& full_path,
139    const RenameCompletionCallback& callback) {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
141
142  base::FilePath new_path(full_path);
143
144  DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
145  // Short circuit null rename.
146  if (full_path != file_.full_path())
147    reason = file_.Rename(new_path);
148
149  if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) {
150    // Doing the annotation after the rename rather than before leaves
151    // a very small window during which the file has the final name but
152    // hasn't been marked with the Mark Of The Web.  However, it allows
153    // anti-virus scanners on Windows to actually see the data
154    // (http://crbug.com/127999) under the correct name (which is information
155    // it uses).
156    reason = file_.AnnotateWithSourceInformation();
157  }
158
159  if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
160    // Make sure our information is updated, since we're about to
161    // error out.
162    SendUpdate();
163
164    // Null out callback so that we don't do any more stream processing.
165    stream_reader_->RegisterCallback(base::Closure());
166
167    new_path.clear();
168  }
169
170  BrowserThread::PostTask(
171      BrowserThread::UI, FROM_HERE,
172      base::Bind(callback, reason, new_path));
173}
174
175void DownloadFileImpl::Detach() {
176  file_.Detach();
177}
178
179void DownloadFileImpl::Cancel() {
180  file_.Cancel();
181}
182
183base::FilePath DownloadFileImpl::FullPath() const {
184  return file_.full_path();
185}
186
187bool DownloadFileImpl::InProgress() const {
188  return file_.in_progress();
189}
190
191int64 DownloadFileImpl::CurrentSpeed() const {
192  return rate_estimator_.GetCountPerSecond();
193}
194
195bool DownloadFileImpl::GetHash(std::string* hash) {
196  return file_.GetHash(hash);
197}
198
199std::string DownloadFileImpl::GetHashState() {
200  return file_.GetHashState();
201}
202
203void DownloadFileImpl::SetClientGuid(const std::string& guid) {
204  file_.SetClientGuid(guid);
205}
206
207void DownloadFileImpl::StreamActive() {
208  base::TimeTicks start(base::TimeTicks::Now());
209  base::TimeTicks now;
210  scoped_refptr<net::IOBuffer> incoming_data;
211  size_t incoming_data_size = 0;
212  size_t total_incoming_data_size = 0;
213  size_t num_buffers = 0;
214  ByteStreamReader::StreamState state(ByteStreamReader::STREAM_EMPTY);
215  DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
216  base::TimeDelta delta(
217      base::TimeDelta::FromMilliseconds(kMaxTimeBlockingFileThreadMs));
218
219  // Take care of any file local activity required.
220  do {
221    state = stream_reader_->Read(&incoming_data, &incoming_data_size);
222
223    switch (state) {
224      case ByteStreamReader::STREAM_EMPTY:
225        break;
226      case ByteStreamReader::STREAM_HAS_DATA:
227        {
228          ++num_buffers;
229          base::TimeTicks write_start(base::TimeTicks::Now());
230          reason = AppendDataToFile(
231              incoming_data.get()->data(), incoming_data_size);
232          disk_writes_time_ += (base::TimeTicks::Now() - write_start);
233          bytes_seen_ += incoming_data_size;
234          total_incoming_data_size += incoming_data_size;
235        }
236        break;
237      case ByteStreamReader::STREAM_COMPLETE:
238        {
239          reason = static_cast<DownloadInterruptReason>(
240              stream_reader_->GetStatus());
241          SendUpdate();
242          base::TimeTicks close_start(base::TimeTicks::Now());
243          file_.Finish();
244          base::TimeTicks now(base::TimeTicks::Now());
245          disk_writes_time_ += (now - close_start);
246          RecordFileBandwidth(
247              bytes_seen_, disk_writes_time_, now - download_start_);
248          update_timer_.reset();
249        }
250        break;
251      default:
252        NOTREACHED();
253        break;
254    }
255    now = base::TimeTicks::Now();
256  } while (state == ByteStreamReader::STREAM_HAS_DATA &&
257           reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
258           now - start <= delta);
259
260  // If we're stopping to yield the thread, post a task so we come back.
261  if (state == ByteStreamReader::STREAM_HAS_DATA &&
262      now - start > delta) {
263    BrowserThread::PostTask(
264        BrowserThread::FILE, FROM_HERE,
265        base::Bind(&DownloadFileImpl::StreamActive,
266                   weak_factory_.GetWeakPtr()));
267  }
268
269  if (total_incoming_data_size)
270    RecordFileThreadReceiveBuffers(num_buffers);
271
272  RecordContiguousWriteTime(now - start);
273
274  // Take care of communication with our observer.
275  if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
276    // Error case for both upstream source and file write.
277    // Shut down processing and signal an error to our observer.
278    // Our observer will clean us up.
279    stream_reader_->RegisterCallback(base::Closure());
280    weak_factory_.InvalidateWeakPtrs();
281    SendUpdate();                       // Make info up to date before error.
282    BrowserThread::PostTask(
283        BrowserThread::UI, FROM_HERE,
284        base::Bind(&DownloadDestinationObserver::DestinationError,
285                   observer_, reason));
286  } else if (state == ByteStreamReader::STREAM_COMPLETE) {
287    // Signal successful completion and shut down processing.
288    stream_reader_->RegisterCallback(base::Closure());
289    weak_factory_.InvalidateWeakPtrs();
290    std::string hash;
291    if (!GetHash(&hash) || file_.IsEmptyHash(hash))
292      hash.clear();
293    SendUpdate();
294    BrowserThread::PostTask(
295        BrowserThread::UI, FROM_HERE,
296        base::Bind(
297            &DownloadDestinationObserver::DestinationCompleted,
298            observer_, hash));
299  }
300  if (bound_net_log_.IsLoggingAllEvents()) {
301    bound_net_log_.AddEvent(
302        net::NetLog::TYPE_DOWNLOAD_STREAM_DRAINED,
303        base::Bind(&FileStreamDrainedNetLogCallback, total_incoming_data_size,
304                   num_buffers));
305  }
306}
307
308void DownloadFileImpl::SendUpdate() {
309  BrowserThread::PostTask(
310      BrowserThread::UI, FROM_HERE,
311      base::Bind(&DownloadDestinationObserver::DestinationUpdate,
312                 observer_, file_.bytes_so_far(), CurrentSpeed(),
313                 GetHashState()));
314}
315
316// static
317int DownloadFile::GetNumberOfDownloadFiles() {
318  return number_active_objects_;
319}
320
321}  // namespace content
322