| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/download/download_file_impl.h" |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/message_loop_proxy.h" |
| #include "base/time.h" |
| #include "content/browser/byte_stream.h" |
| #include "content/browser/download/download_create_info.h" |
| #include "content/browser/download/download_interrupt_reasons_impl.h" |
| #include "content/browser/download/download_net_log_parameters.h" |
| #include "content/browser/download/download_stats.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/download_destination_observer.h" |
| #include "content/public/browser/power_save_blocker.h" |
| #include "net/base/io_buffer.h" |
| |
| namespace content { |
| |
| const int kUpdatePeriodMs = 500; |
| const int kMaxTimeBlockingFileThreadMs = 1000; |
| |
| int DownloadFile::number_active_objects_ = 0; |
| |
| DownloadFileImpl::DownloadFileImpl( |
| scoped_ptr<DownloadSaveInfo> save_info, |
| const base::FilePath& default_download_directory, |
| const GURL& url, |
| const GURL& referrer_url, |
| bool calculate_hash, |
| scoped_ptr<ByteStreamReader> stream, |
| const net::BoundNetLog& bound_net_log, |
| scoped_ptr<PowerSaveBlocker> power_save_blocker, |
| base::WeakPtr<DownloadDestinationObserver> observer) |
| : file_(save_info->file_path, |
| url, |
| referrer_url, |
| save_info->offset, |
| calculate_hash, |
| save_info->hash_state, |
| save_info->file_stream.Pass(), |
| bound_net_log), |
| default_download_directory_(default_download_directory), |
| stream_reader_(stream.Pass()), |
| bytes_seen_(0), |
| bound_net_log_(bound_net_log), |
| observer_(observer), |
| weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| power_save_blocker_(power_save_blocker.Pass()) { |
| } |
| |
| DownloadFileImpl::~DownloadFileImpl() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| --number_active_objects_; |
| } |
| |
| void DownloadFileImpl::Initialize(const InitializeCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| update_timer_.reset(new base::RepeatingTimer<DownloadFileImpl>()); |
| DownloadInterruptReason result = |
| file_.Initialize(default_download_directory_); |
| if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); |
| return; |
| } |
| |
| stream_reader_->RegisterCallback( |
| base::Bind(&DownloadFileImpl::StreamActive, weak_factory_.GetWeakPtr())); |
| |
| download_start_ = base::TimeTicks::Now(); |
| |
| // Primarily to make reset to zero in restart visible to owner. |
| SendUpdate(); |
| |
| // Initial pull from the straw. |
| StreamActive(); |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, base::Bind( |
| callback, DOWNLOAD_INTERRUPT_REASON_NONE)); |
| |
| ++number_active_objects_; |
| } |
| |
| DownloadInterruptReason DownloadFileImpl::AppendDataToFile( |
| const char* data, size_t data_len) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| if (!update_timer_->IsRunning()) { |
| update_timer_->Start(FROM_HERE, |
| base::TimeDelta::FromMilliseconds(kUpdatePeriodMs), |
| this, &DownloadFileImpl::SendUpdate); |
| } |
| return file_.AppendDataToFile(data, data_len); |
| } |
| |
| void DownloadFileImpl::RenameAndUniquify( |
| const base::FilePath& full_path, |
| const RenameCompletionCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| base::FilePath new_path(full_path); |
| |
| int uniquifier = file_util::GetUniquePathNumber( |
| new_path, base::FilePath::StringType()); |
| if (uniquifier > 0) { |
| new_path = new_path.InsertBeforeExtensionASCII( |
| base::StringPrintf(" (%d)", uniquifier)); |
| } |
| |
| DownloadInterruptReason reason = file_.Rename(new_path); |
| if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| // Make sure our information is updated, since we're about to |
| // error out. |
| SendUpdate(); |
| |
| // Null out callback so that we don't do any more stream processing. |
| stream_reader_->RegisterCallback(base::Closure()); |
| |
| new_path.clear(); |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(callback, reason, new_path)); |
| } |
| |
| void DownloadFileImpl::RenameAndAnnotate( |
| const base::FilePath& full_path, |
| const RenameCompletionCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| base::FilePath new_path(full_path); |
| |
| DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE; |
| // Short circuit null rename. |
| if (full_path != file_.full_path()) |
| reason = file_.Rename(new_path); |
| |
| if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) { |
| // Doing the annotation after the rename rather than before leaves |
| // a very small window during which the file has the final name but |
| // hasn't been marked with the Mark Of The Web. However, it allows |
| // anti-virus scanners on Windows to actually see the data |
| // (http://crbug.com/127999) under the correct name (which is information |
| // it uses). |
| reason = file_.AnnotateWithSourceInformation(); |
| } |
| |
| if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| // Make sure our information is updated, since we're about to |
| // error out. |
| SendUpdate(); |
| |
| // Null out callback so that we don't do any more stream processing. |
| stream_reader_->RegisterCallback(base::Closure()); |
| |
| new_path.clear(); |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(callback, reason, new_path)); |
| } |
| |
| void DownloadFileImpl::Detach() { |
| file_.Detach(); |
| } |
| |
| void DownloadFileImpl::Cancel() { |
| file_.Cancel(); |
| } |
| |
| base::FilePath DownloadFileImpl::FullPath() const { |
| return file_.full_path(); |
| } |
| |
| bool DownloadFileImpl::InProgress() const { |
| return file_.in_progress(); |
| } |
| |
| int64 DownloadFileImpl::BytesSoFar() const { |
| return file_.bytes_so_far(); |
| } |
| |
| int64 DownloadFileImpl::CurrentSpeed() const { |
| return file_.CurrentSpeed(); |
| } |
| |
| bool DownloadFileImpl::GetHash(std::string* hash) { |
| return file_.GetHash(hash); |
| } |
| |
| std::string DownloadFileImpl::GetHashState() { |
| return file_.GetHashState(); |
| } |
| |
| void DownloadFileImpl::StreamActive() { |
| base::TimeTicks start(base::TimeTicks::Now()); |
| base::TimeTicks now; |
| scoped_refptr<net::IOBuffer> incoming_data; |
| size_t incoming_data_size = 0; |
| size_t total_incoming_data_size = 0; |
| size_t num_buffers = 0; |
| ByteStreamReader::StreamState state(ByteStreamReader::STREAM_EMPTY); |
| DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE; |
| base::TimeDelta delta( |
| base::TimeDelta::FromMilliseconds(kMaxTimeBlockingFileThreadMs)); |
| |
| // Take care of any file local activity required. |
| do { |
| state = stream_reader_->Read(&incoming_data, &incoming_data_size); |
| |
| switch (state) { |
| case ByteStreamReader::STREAM_EMPTY: |
| break; |
| case ByteStreamReader::STREAM_HAS_DATA: |
| { |
| ++num_buffers; |
| base::TimeTicks write_start(base::TimeTicks::Now()); |
| reason = AppendDataToFile( |
| incoming_data.get()->data(), incoming_data_size); |
| disk_writes_time_ += (base::TimeTicks::Now() - write_start); |
| bytes_seen_ += incoming_data_size; |
| total_incoming_data_size += incoming_data_size; |
| } |
| break; |
| case ByteStreamReader::STREAM_COMPLETE: |
| { |
| reason = stream_reader_->GetStatus(); |
| SendUpdate(); |
| base::TimeTicks close_start(base::TimeTicks::Now()); |
| file_.Finish(); |
| base::TimeTicks now(base::TimeTicks::Now()); |
| disk_writes_time_ += (now - close_start); |
| RecordFileBandwidth( |
| bytes_seen_, disk_writes_time_, now - download_start_); |
| update_timer_.reset(); |
| } |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| now = base::TimeTicks::Now(); |
| } while (state == ByteStreamReader::STREAM_HAS_DATA && |
| reason == DOWNLOAD_INTERRUPT_REASON_NONE && |
| now - start <= delta); |
| |
| // If we're stopping to yield the thread, post a task so we come back. |
| if (state == ByteStreamReader::STREAM_HAS_DATA && |
| now - start > delta) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DownloadFileImpl::StreamActive, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| if (total_incoming_data_size) |
| RecordFileThreadReceiveBuffers(num_buffers); |
| |
| RecordContiguousWriteTime(now - start); |
| |
| // Take care of communication with our observer. |
| if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| // Error case for both upstream source and file write. |
| // Shut down processing and signal an error to our observer. |
| // Our observer will clean us up. |
| stream_reader_->RegisterCallback(base::Closure()); |
| weak_factory_.InvalidateWeakPtrs(); |
| SendUpdate(); // Make info up to date before error. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&DownloadDestinationObserver::DestinationError, |
| observer_, reason)); |
| } else if (state == ByteStreamReader::STREAM_COMPLETE) { |
| // Signal successful completion and shut down processing. |
| stream_reader_->RegisterCallback(base::Closure()); |
| weak_factory_.InvalidateWeakPtrs(); |
| std::string hash; |
| if (!GetHash(&hash) || file_.IsEmptyHash(hash)) |
| hash.clear(); |
| SendUpdate(); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &DownloadDestinationObserver::DestinationCompleted, |
| observer_, hash)); |
| } |
| if (bound_net_log_.IsLoggingAllEvents()) { |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_STREAM_DRAINED, |
| base::Bind(&FileStreamDrainedNetLogCallback, total_incoming_data_size, |
| num_buffers)); |
| } |
| } |
| |
| void DownloadFileImpl::SendUpdate() { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&DownloadDestinationObserver::DestinationUpdate, |
| observer_, BytesSoFar(), CurrentSpeed(), GetHashState())); |
| } |
| |
| // static |
| int DownloadFile::GetNumberOfDownloadFiles() { |
| return number_active_objects_; |
| } |
| |
| } // namespace content |