Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / browser / download / download_file_impl.cc
blob203c6892373913ef175386d46d3cf8d58468aed5
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.
5 #include "content/browser/download/download_file_impl.h"
7 #include <string>
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"
23 namespace content {
25 const int kUpdatePeriodMs = 500;
26 const int kMaxTimeBlockingFileThreadMs = 1000;
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;
34 int DownloadFile::number_active_objects_ = 0;
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) {
61 DownloadFileImpl::~DownloadFileImpl() {
62 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
63 --number_active_objects_;
66 void DownloadFileImpl::Initialize(const InitializeCallback& callback) {
67 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
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;
78 stream_reader_->RegisterCallback(
79 base::Bind(&DownloadFileImpl::StreamActive, weak_factory_.GetWeakPtr()));
81 download_start_ = base::TimeTicks::Now();
83 // Primarily to make reset to zero in restart visible to owner.
84 SendUpdate();
86 // Initial pull from the straw.
87 StreamActive();
89 BrowserThread::PostTask(
90 BrowserThread::UI, FROM_HERE, base::Bind(
91 callback, DOWNLOAD_INTERRUPT_REASON_NONE));
93 ++number_active_objects_;
96 DownloadInterruptReason DownloadFileImpl::AppendDataToFile(
97 const char* data, size_t data_len) {
98 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
100 if (!update_timer_->IsRunning()) {
101 update_timer_->Start(FROM_HERE,
102 base::TimeDelta::FromMilliseconds(kUpdatePeriodMs),
103 this, &DownloadFileImpl::SendUpdate);
105 rate_estimator_.Increment(data_len);
106 return file_.AppendDataToFile(data, data_len);
109 void DownloadFileImpl::RenameAndUniquify(
110 const base::FilePath& full_path,
111 const RenameCompletionCallback& callback) {
112 RenameWithRetryInternal(
113 full_path, UNIQUIFY, kMaxRenameRetries, base::TimeTicks(), callback);
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);
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);
137 bool DownloadFileImpl::ShouldRetryFailedRename(DownloadInterruptReason reason) {
138 return reason == DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR;
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_CURRENTLY_ON(BrowserThread::FILE);
149 base::FilePath new_path(full_path);
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));
159 DownloadInterruptReason reason = file_.Rename(new_path);
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;
184 if (!time_of_first_failure.is_null())
185 RecordDownloadFileRenameResultAfterRetry(
186 base::TimeTicks::Now() - time_of_first_failure, reason);
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();
199 if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
200 // Make sure our information is updated, since we're about to
201 // error out.
202 SendUpdate();
204 // Null out callback so that we don't do any more stream processing.
205 stream_reader_->RegisterCallback(base::Closure());
207 new_path.clear();
210 BrowserThread::PostTask(
211 BrowserThread::UI, FROM_HERE,
212 base::Bind(callback, reason, new_path));
215 void DownloadFileImpl::Detach() {
216 file_.Detach();
219 void DownloadFileImpl::Cancel() {
220 file_.Cancel();
223 base::FilePath DownloadFileImpl::FullPath() const {
224 return file_.full_path();
227 bool DownloadFileImpl::InProgress() const {
228 return file_.in_progress();
231 int64 DownloadFileImpl::CurrentSpeed() const {
232 return rate_estimator_.GetCountPerSecond();
235 bool DownloadFileImpl::GetHash(std::string* hash) {
236 return file_.GetHash(hash);
239 std::string DownloadFileImpl::GetHashState() {
240 return file_.GetHashState();
243 void DownloadFileImpl::SetClientGuid(const std::string& guid) {
244 file_.SetClientGuid(guid);
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));
259 // Take care of any file local activity required.
260 do {
261 state = stream_reader_->Read(&incoming_data, &incoming_data_size);
263 switch (state) {
264 case ByteStreamReader::STREAM_EMPTY:
265 break;
266 case ByteStreamReader::STREAM_HAS_DATA:
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;
276 break;
277 case ByteStreamReader::STREAM_COMPLETE:
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();
290 break;
291 default:
292 NOTREACHED();
293 break;
295 now = base::TimeTicks::Now();
296 } while (state == ByteStreamReader::STREAM_HAS_DATA &&
297 reason == DOWNLOAD_INTERRUPT_REASON_NONE &&
298 now - start <= delta);
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()));
309 if (total_incoming_data_size)
310 RecordFileThreadReceiveBuffers(num_buffers);
312 RecordContiguousWriteTime(now - start);
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));
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));
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()));
356 // static
357 int DownloadFile::GetNumberOfDownloadFiles() {
358 return number_active_objects_;
361 } // namespace content