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"
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"
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
,
40 const GURL
& referrer_url
,
42 scoped_ptr
<ByteStreamReader
> stream
,
43 const net::BoundNetLog
& bound_net_log
,
44 base::WeakPtr
<DownloadDestinationObserver
> observer
)
45 : file_(save_info
->file_path
,
50 save_info
->hash_state
,
51 save_info
->file
.Pass(),
53 default_download_directory_(default_download_directory
),
54 stream_reader_(stream
.Pass()),
56 bound_net_log_(bound_net_log
),
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
));
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.
86 // Initial pull from the straw.
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
,
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
,
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()) {
153 base::GetUniquePathNumber(new_path
, base::FilePath::StringType());
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
166 if (ShouldRetryFailedRename(reason
) && file_
.in_progress() &&
168 int attempt_number
= kMaxRenameRetries
- retries_left
;
169 BrowserThread::PostDelayedTask(
172 base::Bind(&DownloadFileImpl::RenameWithRetryInternal
,
173 weak_factory_
.GetWeakPtr(),
177 time_of_first_failure
.is_null() ? base::TimeTicks::Now()
178 : time_of_first_failure
,
180 GetRetryDelayForFailedRename(attempt_number
));
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
196 reason
= file_
.AnnotateWithSourceInformation();
199 if (reason
!= DOWNLOAD_INTERRUPT_REASON_NONE
) {
200 // Make sure our information is updated, since we're about to
204 // Null out callback so that we don't do any more stream processing.
205 stream_reader_
->RegisterCallback(base::Closure());
210 BrowserThread::PostTask(
211 BrowserThread::UI
, FROM_HERE
,
212 base::Bind(callback
, reason
, new_path
));
215 void DownloadFileImpl::Detach() {
219 void DownloadFileImpl::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());
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.
261 state
= stream_reader_
->Read(&incoming_data
, &incoming_data_size
);
264 case ByteStreamReader::STREAM_EMPTY
:
266 case ByteStreamReader::STREAM_HAS_DATA
:
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
;
277 case ByteStreamReader::STREAM_COMPLETE
:
279 reason
= static_cast<DownloadInterruptReason
>(
280 stream_reader_
->GetStatus());
282 base::TimeTicks
close_start(base::TimeTicks::Now());
284 base::TimeTicks
now(base::TimeTicks::Now());
285 disk_writes_time_
+= (now
- close_start
);
287 bytes_seen_
, disk_writes_time_
, now
- download_start_
);
288 update_timer_
.reset();
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
,
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();
331 if (!GetHash(&hash
) || file_
.IsEmptyHash(hash
))
334 BrowserThread::PostTask(
335 BrowserThread::UI
, FROM_HERE
,
337 &DownloadDestinationObserver::DestinationCompleted
,
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
,
348 void DownloadFileImpl::SendUpdate() {
349 BrowserThread::PostTask(
350 BrowserThread::UI
, FROM_HERE
,
351 base::Bind(&DownloadDestinationObserver::DestinationUpdate
,
352 observer_
, file_
.bytes_so_far(), CurrentSpeed(),
357 int DownloadFile::GetNumberOfDownloadFiles() {
358 return number_active_objects_
;
361 } // namespace content