| // 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. |
| |
| // File method ordering: Methods in this file are in the same order as |
| // in download_item_impl.h, with the following exception: The public |
| // interface Start is placed in chronological order with the other |
| // (private) routines that together define a DownloadItem's state |
| // transitions as the download progresses. See "Download progression |
| // cascade" later in this file. |
| |
| // A regular DownloadItem (created for a download in this session of the |
| // browser) normally goes through the following states: |
| // * Created (when download starts) |
| // * Destination filename determined |
| // * Entered into the history database. |
| // * Made visible in the download shelf. |
| // * All the data is saved. Note that the actual data download occurs |
| // in parallel with the above steps, but until those steps are |
| // complete, the state of the data save will be ignored. |
| // * Download file is renamed to its final name, and possibly |
| // auto-opened. |
| |
| #include "content/browser/download/download_item_impl.h" |
| |
| #include <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/download/download_create_info.h" |
| #include "content/browser/download/download_file.h" |
| #include "content/browser/download/download_interrupt_reasons_impl.h" |
| #include "content/browser/download/download_item_impl_delegate.h" |
| #include "content/browser/download/download_request_handle.h" |
| #include "content/browser/download/download_stats.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/download_danger_type.h" |
| #include "content/public/browser/download_interrupt_reasons.h" |
| #include "content/public/browser/download_url_parameters.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/referrer.h" |
| #include "net/base/net_util.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| void DeleteDownloadedFile(const base::FilePath& path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| // Make sure we only delete files. |
| if (!base::DirectoryExists(path)) |
| base::DeleteFile(path, false); |
| } |
| |
| // Wrapper around DownloadFile::Detach and DownloadFile::Cancel that |
| // takes ownership of the DownloadFile and hence implicitly destroys it |
| // at the end of the function. |
| static base::FilePath DownloadFileDetach( |
| scoped_ptr<DownloadFile> download_file) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| base::FilePath full_path = download_file->FullPath(); |
| download_file->Detach(); |
| return full_path; |
| } |
| |
| static void DownloadFileCancel(scoped_ptr<DownloadFile> download_file) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| download_file->Cancel(); |
| } |
| |
| bool IsDownloadResumptionEnabled() { |
| return CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableDownloadResumption); |
| } |
| |
| } // namespace |
| |
| const uint32 DownloadItem::kInvalidId = 0; |
| |
| const char DownloadItem::kEmptyFileHash[] = ""; |
| |
| // The maximum number of attempts we will make to resume automatically. |
| const int DownloadItemImpl::kMaxAutoResumeAttempts = 5; |
| |
| // Constructor for reading from the history service. |
| DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, |
| uint32 download_id, |
| const base::FilePath& current_path, |
| const base::FilePath& target_path, |
| const std::vector<GURL>& url_chain, |
| const GURL& referrer_url, |
| const base::Time& start_time, |
| const base::Time& end_time, |
| const std::string& etag, |
| const std::string& last_modified, |
| int64 received_bytes, |
| int64 total_bytes, |
| DownloadItem::DownloadState state, |
| DownloadDangerType danger_type, |
| DownloadInterruptReason interrupt_reason, |
| bool opened, |
| const net::BoundNetLog& bound_net_log) |
| : is_save_package_download_(false), |
| download_id_(download_id), |
| current_path_(current_path), |
| target_path_(target_path), |
| target_disposition_(TARGET_DISPOSITION_OVERWRITE), |
| url_chain_(url_chain), |
| referrer_url_(referrer_url), |
| transition_type_(PAGE_TRANSITION_LINK), |
| has_user_gesture_(false), |
| total_bytes_(total_bytes), |
| received_bytes_(received_bytes), |
| bytes_per_sec_(0), |
| last_modified_time_(last_modified), |
| etag_(etag), |
| last_reason_(interrupt_reason), |
| start_tick_(base::TimeTicks()), |
| state_(ExternalToInternalState(state)), |
| danger_type_(danger_type), |
| start_time_(start_time), |
| end_time_(end_time), |
| delegate_(delegate), |
| is_paused_(false), |
| auto_resume_count_(0), |
| open_when_complete_(false), |
| file_externally_removed_(false), |
| auto_opened_(false), |
| is_temporary_(false), |
| all_data_saved_(state == COMPLETE), |
| destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), |
| opened_(opened), |
| delegate_delayed_complete_(false), |
| bound_net_log_(bound_net_log), |
| weak_ptr_factory_(this) { |
| delegate_->Attach(); |
| DCHECK_NE(IN_PROGRESS_INTERNAL, state_); |
| Init(false /* not actively downloading */, SRC_HISTORY_IMPORT); |
| } |
| |
| // Constructing for a regular download: |
| DownloadItemImpl::DownloadItemImpl( |
| DownloadItemImplDelegate* delegate, |
| uint32 download_id, |
| const DownloadCreateInfo& info, |
| const net::BoundNetLog& bound_net_log) |
| : is_save_package_download_(false), |
| download_id_(download_id), |
| target_disposition_( |
| (info.save_info->prompt_for_save_location) ? |
| TARGET_DISPOSITION_PROMPT : TARGET_DISPOSITION_OVERWRITE), |
| url_chain_(info.url_chain), |
| referrer_url_(info.referrer_url), |
| suggested_filename_(UTF16ToUTF8(info.save_info->suggested_name)), |
| forced_file_path_(info.save_info->file_path), |
| transition_type_(info.transition_type), |
| has_user_gesture_(info.has_user_gesture), |
| content_disposition_(info.content_disposition), |
| mime_type_(info.mime_type), |
| original_mime_type_(info.original_mime_type), |
| remote_address_(info.remote_address), |
| total_bytes_(info.total_bytes), |
| received_bytes_(0), |
| bytes_per_sec_(0), |
| last_modified_time_(info.last_modified), |
| etag_(info.etag), |
| last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE), |
| start_tick_(base::TimeTicks::Now()), |
| state_(IN_PROGRESS_INTERNAL), |
| danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), |
| start_time_(info.start_time), |
| delegate_(delegate), |
| is_paused_(false), |
| auto_resume_count_(0), |
| open_when_complete_(false), |
| file_externally_removed_(false), |
| auto_opened_(false), |
| is_temporary_(!info.save_info->file_path.empty()), |
| all_data_saved_(false), |
| destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), |
| opened_(false), |
| delegate_delayed_complete_(false), |
| bound_net_log_(bound_net_log), |
| weak_ptr_factory_(this) { |
| delegate_->Attach(); |
| Init(true /* actively downloading */, SRC_ACTIVE_DOWNLOAD); |
| |
| // Link the event sources. |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_URL_REQUEST, |
| info.request_bound_net_log.source().ToEventParametersCallback()); |
| |
| info.request_bound_net_log.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_STARTED, |
| bound_net_log_.source().ToEventParametersCallback()); |
| } |
| |
| // Constructing for the "Save Page As..." feature: |
| DownloadItemImpl::DownloadItemImpl( |
| DownloadItemImplDelegate* delegate, |
| uint32 download_id, |
| const base::FilePath& path, |
| const GURL& url, |
| const std::string& mime_type, |
| scoped_ptr<DownloadRequestHandleInterface> request_handle, |
| const net::BoundNetLog& bound_net_log) |
| : is_save_package_download_(true), |
| request_handle_(request_handle.Pass()), |
| download_id_(download_id), |
| current_path_(path), |
| target_path_(path), |
| target_disposition_(TARGET_DISPOSITION_OVERWRITE), |
| url_chain_(1, url), |
| referrer_url_(GURL()), |
| transition_type_(PAGE_TRANSITION_LINK), |
| has_user_gesture_(false), |
| mime_type_(mime_type), |
| original_mime_type_(mime_type), |
| total_bytes_(0), |
| received_bytes_(0), |
| bytes_per_sec_(0), |
| last_reason_(DOWNLOAD_INTERRUPT_REASON_NONE), |
| start_tick_(base::TimeTicks::Now()), |
| state_(IN_PROGRESS_INTERNAL), |
| danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), |
| start_time_(base::Time::Now()), |
| delegate_(delegate), |
| is_paused_(false), |
| auto_resume_count_(0), |
| open_when_complete_(false), |
| file_externally_removed_(false), |
| auto_opened_(false), |
| is_temporary_(false), |
| all_data_saved_(false), |
| destination_error_(content::DOWNLOAD_INTERRUPT_REASON_NONE), |
| opened_(false), |
| delegate_delayed_complete_(false), |
| bound_net_log_(bound_net_log), |
| weak_ptr_factory_(this) { |
| delegate_->Attach(); |
| Init(true /* actively downloading */, SRC_SAVE_PAGE_AS); |
| } |
| |
| DownloadItemImpl::~DownloadItemImpl() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Should always have been nuked before now, at worst in |
| // DownloadManager shutdown. |
| DCHECK(!download_file_.get()); |
| |
| FOR_EACH_OBSERVER(Observer, observers_, OnDownloadDestroyed(this)); |
| delegate_->AssertStateConsistent(this); |
| delegate_->Detach(); |
| } |
| |
| void DownloadItemImpl::AddObserver(Observer* observer) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| observers_.AddObserver(observer); |
| } |
| |
| void DownloadItemImpl::RemoveObserver(Observer* observer) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| observers_.RemoveObserver(observer); |
| } |
| |
| void DownloadItemImpl::UpdateObservers() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this)); |
| } |
| |
| void DownloadItemImpl::ValidateDangerousDownload() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!IsDone()); |
| DCHECK(IsDangerous()); |
| |
| VLOG(20) << __FUNCTION__ << " download=" << DebugString(true); |
| |
| if (IsDone() || !IsDangerous()) |
| return; |
| |
| RecordDangerousDownloadAccept(GetDangerType(), |
| GetTargetFilePath()); |
| |
| danger_type_ = DOWNLOAD_DANGER_TYPE_USER_VALIDATED; |
| |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED, |
| base::Bind(&ItemCheckedNetLogCallback, GetDangerType())); |
| |
| UpdateObservers(); |
| |
| MaybeCompleteDownload(); |
| } |
| |
| void DownloadItemImpl::StealDangerousDownload( |
| const AcquireFileCallback& callback) { |
| VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(IsDangerous()); |
| if (download_file_) { |
| BrowserThread::PostTaskAndReplyWithResult( |
| BrowserThread::FILE, |
| FROM_HERE, |
| base::Bind(&DownloadFileDetach, base::Passed(&download_file_)), |
| callback); |
| } else { |
| callback.Run(current_path_); |
| } |
| current_path_.clear(); |
| Remove(); |
| // We have now been deleted. |
| } |
| |
| void DownloadItemImpl::Pause() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Ignore irrelevant states. |
| if (state_ != IN_PROGRESS_INTERNAL || is_paused_) |
| return; |
| |
| request_handle_->PauseRequest(); |
| is_paused_ = true; |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::Resume() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| switch (state_) { |
| case IN_PROGRESS_INTERNAL: |
| if (!is_paused_) |
| return; |
| request_handle_->ResumeRequest(); |
| is_paused_ = false; |
| UpdateObservers(); |
| return; |
| |
| case COMPLETING_INTERNAL: |
| case COMPLETE_INTERNAL: |
| case CANCELLED_INTERNAL: |
| case RESUMING_INTERNAL: |
| return; |
| |
| case INTERRUPTED_INTERNAL: |
| auto_resume_count_ = 0; // User input resets the counter. |
| ResumeInterruptedDownload(); |
| return; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| NOTREACHED(); |
| } |
| } |
| |
| void DownloadItemImpl::Cancel(bool user_cancel) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); |
| if (state_ != IN_PROGRESS_INTERNAL && |
| state_ != INTERRUPTED_INTERNAL && |
| state_ != RESUMING_INTERNAL) { |
| // Small downloads might be complete before this method has a chance to run. |
| return; |
| } |
| |
| if (IsDangerous()) { |
| RecordDangerousDownloadDiscard( |
| user_cancel ? DOWNLOAD_DISCARD_DUE_TO_USER_ACTION |
| : DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN, |
| GetDangerType(), |
| GetTargetFilePath()); |
| } |
| |
| last_reason_ = user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED |
| : DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN; |
| |
| RecordDownloadCount(CANCELLED_COUNT); |
| |
| // TODO(rdsmith/benjhayden): Remove condition as part of |
| // |SavePackage| integration. |
| // |download_file_| can be NULL if Interrupt() is called after the |
| // download file has been released. |
| if (!is_save_package_download_ && download_file_) |
| ReleaseDownloadFile(true); |
| |
| if (state_ == IN_PROGRESS_INTERNAL) { |
| // Cancel the originating URL request unless it's already been cancelled |
| // by interrupt. |
| request_handle_->CancelRequest(); |
| } |
| |
| // Remove the intermediate file if we are cancelling an interrupted download. |
| // Continuable interruptions leave the intermediate file around. |
| if ((state_ == INTERRUPTED_INTERNAL || state_ == RESUMING_INTERNAL) && |
| !current_path_.empty()) { |
| BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DeleteDownloadedFile, current_path_)); |
| current_path_.clear(); |
| } |
| |
| TransitionTo(CANCELLED_INTERNAL, UPDATE_OBSERVERS); |
| } |
| |
| void DownloadItemImpl::Remove() { |
| VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| delegate_->AssertStateConsistent(this); |
| Cancel(true); |
| delegate_->AssertStateConsistent(this); |
| |
| NotifyRemoved(); |
| delegate_->DownloadRemoved(this); |
| // We have now been deleted. |
| } |
| |
| void DownloadItemImpl::OpenDownload() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (!IsDone()) { |
| // We don't honor the open_when_complete_ flag for temporary |
| // downloads. Don't set it because it shows up in the UI. |
| if (!IsTemporary()) |
| open_when_complete_ = !open_when_complete_; |
| return; |
| } |
| |
| if (state_ != COMPLETE_INTERNAL || file_externally_removed_) |
| return; |
| |
| // Ideally, we want to detect errors in opening and report them, but we |
| // don't generally have the proper interface for that to the external |
| // program that opens the file. So instead we spawn a check to update |
| // the UI if the file has been deleted in parallel with the open. |
| delegate_->CheckForFileRemoval(this); |
| RecordOpen(GetEndTime(), !GetOpened()); |
| opened_ = true; |
| FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this)); |
| delegate_->OpenDownload(this); |
| } |
| |
| void DownloadItemImpl::ShowDownloadInShell() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| delegate_->ShowDownloadInShell(this); |
| } |
| |
| uint32 DownloadItemImpl::GetId() const { |
| return download_id_; |
| } |
| |
| DownloadItem::DownloadState DownloadItemImpl::GetState() const { |
| return InternalToExternalState(state_); |
| } |
| |
| DownloadInterruptReason DownloadItemImpl::GetLastReason() const { |
| return last_reason_; |
| } |
| |
| bool DownloadItemImpl::IsPaused() const { |
| return is_paused_; |
| } |
| |
| bool DownloadItemImpl::IsTemporary() const { |
| return is_temporary_; |
| } |
| |
| bool DownloadItemImpl::CanResume() const { |
| if ((GetState() == IN_PROGRESS) && IsPaused()) |
| return true; |
| |
| if (state_ != INTERRUPTED_INTERNAL) |
| return false; |
| |
| // Downloads that don't have a WebContents should still be resumable, but this |
| // isn't currently the case. See ResumeInterruptedDownload(). |
| if (!GetWebContents()) |
| return false; |
| |
| ResumeMode resume_mode = GetResumeMode(); |
| return IsDownloadResumptionEnabled() && |
| (resume_mode == RESUME_MODE_USER_RESTART || |
| resume_mode == RESUME_MODE_USER_CONTINUE); |
| } |
| |
| bool DownloadItemImpl::IsDone() const { |
| switch (state_) { |
| case IN_PROGRESS_INTERNAL: |
| case COMPLETING_INTERNAL: |
| return false; |
| |
| case COMPLETE_INTERNAL: |
| case CANCELLED_INTERNAL: |
| return true; |
| |
| case INTERRUPTED_INTERNAL: |
| return !CanResume(); |
| |
| case RESUMING_INTERNAL: |
| return false; |
| |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| break; |
| } |
| NOTREACHED(); |
| return true; |
| } |
| |
| const GURL& DownloadItemImpl::GetURL() const { |
| return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.back(); |
| } |
| |
| const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const { |
| return url_chain_; |
| } |
| |
| const GURL& DownloadItemImpl::GetOriginalUrl() const { |
| // Be careful about taking the front() of possibly-empty vectors! |
| // http://crbug.com/190096 |
| return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.front(); |
| } |
| |
| const GURL& DownloadItemImpl::GetReferrerUrl() const { |
| return referrer_url_; |
| } |
| |
| std::string DownloadItemImpl::GetSuggestedFilename() const { |
| return suggested_filename_; |
| } |
| |
| std::string DownloadItemImpl::GetContentDisposition() const { |
| return content_disposition_; |
| } |
| |
| std::string DownloadItemImpl::GetMimeType() const { |
| return mime_type_; |
| } |
| |
| std::string DownloadItemImpl::GetOriginalMimeType() const { |
| return original_mime_type_; |
| } |
| |
| std::string DownloadItemImpl::GetRemoteAddress() const { |
| return remote_address_; |
| } |
| |
| bool DownloadItemImpl::HasUserGesture() const { |
| return has_user_gesture_; |
| }; |
| |
| PageTransition DownloadItemImpl::GetTransitionType() const { |
| return transition_type_; |
| }; |
| |
| const std::string& DownloadItemImpl::GetLastModifiedTime() const { |
| return last_modified_time_; |
| } |
| |
| const std::string& DownloadItemImpl::GetETag() const { |
| return etag_; |
| } |
| |
| bool DownloadItemImpl::IsSavePackageDownload() const { |
| return is_save_package_download_; |
| } |
| |
| const base::FilePath& DownloadItemImpl::GetFullPath() const { |
| return current_path_; |
| } |
| |
| const base::FilePath& DownloadItemImpl::GetTargetFilePath() const { |
| return target_path_; |
| } |
| |
| const base::FilePath& DownloadItemImpl::GetForcedFilePath() const { |
| // TODO(asanka): Get rid of GetForcedFilePath(). We should instead just |
| // require that clients respect GetTargetFilePath() if it is already set. |
| return forced_file_path_; |
| } |
| |
| base::FilePath DownloadItemImpl::GetFileNameToReportUser() const { |
| if (!display_name_.empty()) |
| return display_name_; |
| return target_path_.BaseName(); |
| } |
| |
| DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const { |
| return target_disposition_; |
| } |
| |
| const std::string& DownloadItemImpl::GetHash() const { |
| return hash_; |
| } |
| |
| const std::string& DownloadItemImpl::GetHashState() const { |
| return hash_state_; |
| } |
| |
| bool DownloadItemImpl::GetFileExternallyRemoved() const { |
| return file_externally_removed_; |
| } |
| |
| void DownloadItemImpl::DeleteFile() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if ((GetState() != DownloadItem::COMPLETE) || |
| file_externally_removed_) { |
| return; |
| } |
| BrowserThread::PostTaskAndReply( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DeleteDownloadedFile, current_path_), |
| base::Bind(&DownloadItemImpl::OnDownloadedFileRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| current_path_.clear(); |
| } |
| |
| bool DownloadItemImpl::IsDangerous() const { |
| #if defined(OS_WIN) |
| // TODO(noelutz): At this point only the windows views UI supports |
| // warnings based on dangerous content. |
| return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED); |
| #else |
| return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || |
| danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL); |
| #endif |
| } |
| |
| DownloadDangerType DownloadItemImpl::GetDangerType() const { |
| return danger_type_; |
| } |
| |
| bool DownloadItemImpl::TimeRemaining(base::TimeDelta* remaining) const { |
| if (total_bytes_ <= 0) |
| return false; // We never received the content_length for this download. |
| |
| int64 speed = CurrentSpeed(); |
| if (speed == 0) |
| return false; |
| |
| *remaining = base::TimeDelta::FromSeconds( |
| (total_bytes_ - received_bytes_) / speed); |
| return true; |
| } |
| |
| int64 DownloadItemImpl::CurrentSpeed() const { |
| if (is_paused_) |
| return 0; |
| return bytes_per_sec_; |
| } |
| |
| int DownloadItemImpl::PercentComplete() const { |
| // If the delegate is delaying completion of the download, then we have no |
| // idea how long it will take. |
| if (delegate_delayed_complete_ || total_bytes_ <= 0) |
| return -1; |
| |
| return static_cast<int>(received_bytes_ * 100.0 / total_bytes_); |
| } |
| |
| bool DownloadItemImpl::AllDataSaved() const { |
| return all_data_saved_; |
| } |
| |
| int64 DownloadItemImpl::GetTotalBytes() const { |
| return total_bytes_; |
| } |
| |
| int64 DownloadItemImpl::GetReceivedBytes() const { |
| return received_bytes_; |
| } |
| |
| base::Time DownloadItemImpl::GetStartTime() const { |
| return start_time_; |
| } |
| |
| base::Time DownloadItemImpl::GetEndTime() const { |
| return end_time_; |
| } |
| |
| bool DownloadItemImpl::CanShowInFolder() { |
| // A download can be shown in the folder if the downloaded file is in a known |
| // location. |
| return CanOpenDownload() && !GetFullPath().empty(); |
| } |
| |
| bool DownloadItemImpl::CanOpenDownload() { |
| // We can open the file or mark it for opening on completion if the download |
| // is expected to complete successfully. Exclude temporary downloads, since |
| // they aren't owned by the download system. |
| const bool is_complete = GetState() == DownloadItem::COMPLETE; |
| return (!IsDone() || is_complete) && !IsTemporary() && |
| !file_externally_removed_; |
| } |
| |
| bool DownloadItemImpl::ShouldOpenFileBasedOnExtension() { |
| return delegate_->ShouldOpenFileBasedOnExtension(GetTargetFilePath()); |
| } |
| |
| bool DownloadItemImpl::GetOpenWhenComplete() const { |
| return open_when_complete_; |
| } |
| |
| bool DownloadItemImpl::GetAutoOpened() { |
| return auto_opened_; |
| } |
| |
| bool DownloadItemImpl::GetOpened() const { |
| return opened_; |
| } |
| |
| BrowserContext* DownloadItemImpl::GetBrowserContext() const { |
| return delegate_->GetBrowserContext(); |
| } |
| |
| WebContents* DownloadItemImpl::GetWebContents() const { |
| // TODO(rdsmith): Remove null check after removing GetWebContents() from |
| // paths that might be used by DownloadItems created from history import. |
| // Currently such items have null request_handle_s, where other items |
| // (regular and SavePackage downloads) have actual objects off the pointer. |
| if (request_handle_) |
| return request_handle_->GetWebContents(); |
| return NULL; |
| } |
| |
| void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(AllDataSaved()); |
| VLOG(20) << __FUNCTION__ << " danger_type=" << danger_type |
| << " download=" << DebugString(true); |
| SetDangerType(danger_type); |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::SetOpenWhenComplete(bool open) { |
| open_when_complete_ = open; |
| } |
| |
| void DownloadItemImpl::SetIsTemporary(bool temporary) { |
| is_temporary_ = temporary; |
| } |
| |
| void DownloadItemImpl::SetOpened(bool opened) { |
| opened_ = opened; |
| } |
| |
| void DownloadItemImpl::SetDisplayName(const base::FilePath& name) { |
| display_name_ = name; |
| } |
| |
| std::string DownloadItemImpl::DebugString(bool verbose) const { |
| std::string description = |
| base::StringPrintf("{ id = %d" |
| " state = %s", |
| download_id_, |
| DebugDownloadStateString(state_)); |
| |
| // Construct a string of the URL chain. |
| std::string url_list("<none>"); |
| if (!url_chain_.empty()) { |
| std::vector<GURL>::const_iterator iter = url_chain_.begin(); |
| std::vector<GURL>::const_iterator last = url_chain_.end(); |
| url_list = (*iter).is_valid() ? (*iter).spec() : "<invalid>"; |
| ++iter; |
| for ( ; verbose && (iter != last); ++iter) { |
| url_list += " ->\n\t"; |
| const GURL& next_url = *iter; |
| url_list += next_url.is_valid() ? next_url.spec() : "<invalid>"; |
| } |
| } |
| |
| if (verbose) { |
| description += base::StringPrintf( |
| " total = %" PRId64 |
| " received = %" PRId64 |
| " reason = %s" |
| " paused = %c" |
| " resume_mode = %s" |
| " auto_resume_count = %d" |
| " danger = %d" |
| " all_data_saved = %c" |
| " last_modified = '%s'" |
| " etag = '%s'" |
| " has_download_file = %s" |
| " url_chain = \n\t\"%s\"\n\t" |
| " full_path = \"%" PRFilePath "\"\n\t" |
| " target_path = \"%" PRFilePath "\"", |
| GetTotalBytes(), |
| GetReceivedBytes(), |
| InterruptReasonDebugString(last_reason_).c_str(), |
| IsPaused() ? 'T' : 'F', |
| DebugResumeModeString(GetResumeMode()), |
| auto_resume_count_, |
| GetDangerType(), |
| AllDataSaved() ? 'T' : 'F', |
| GetLastModifiedTime().c_str(), |
| GetETag().c_str(), |
| download_file_.get() ? "true" : "false", |
| url_list.c_str(), |
| GetFullPath().value().c_str(), |
| GetTargetFilePath().value().c_str()); |
| } else { |
| description += base::StringPrintf(" url = \"%s\"", url_list.c_str()); |
| } |
| |
| description += " }"; |
| |
| return description; |
| } |
| |
| DownloadItemImpl::ResumeMode DownloadItemImpl::GetResumeMode() const { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // We can't continue without a handle on the intermediate file. |
| // We also can't continue if we don't have some verifier to make sure |
| // we're getting the same file. |
| const bool force_restart = |
| (current_path_.empty() || (etag_.empty() && last_modified_time_.empty())); |
| |
| // We won't auto-restart if we've used up our attempts or the |
| // download has been paused by user action. |
| const bool force_user = |
| (auto_resume_count_ >= kMaxAutoResumeAttempts || is_paused_); |
| |
| ResumeMode mode = RESUME_MODE_INVALID; |
| |
| switch(last_reason_) { |
| case DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR: |
| case DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT: |
| if (force_restart && force_user) |
| mode = RESUME_MODE_USER_RESTART; |
| else if (force_restart) |
| mode = RESUME_MODE_IMMEDIATE_RESTART; |
| else if (force_user) |
| mode = RESUME_MODE_USER_CONTINUE; |
| else |
| mode = RESUME_MODE_IMMEDIATE_CONTINUE; |
| break; |
| |
| case DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION: |
| case DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT: |
| if (force_user) |
| mode = RESUME_MODE_USER_RESTART; |
| else |
| mode = RESUME_MODE_IMMEDIATE_RESTART; |
| break; |
| |
| case DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED: |
| case DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED: |
| case DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN: |
| case DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED: |
| case DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN: |
| case DOWNLOAD_INTERRUPT_REASON_CRASH: |
| if (force_restart) |
| mode = RESUME_MODE_USER_RESTART; |
| else |
| mode = RESUME_MODE_USER_CONTINUE; |
| break; |
| |
| case DOWNLOAD_INTERRUPT_REASON_FILE_FAILED: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE: |
| mode = RESUME_MODE_USER_RESTART; |
| break; |
| |
| case DOWNLOAD_INTERRUPT_REASON_NONE: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED: |
| case DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT: |
| case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED: |
| case DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED: |
| mode = RESUME_MODE_INVALID; |
| break; |
| } |
| |
| return mode; |
| } |
| |
| void DownloadItemImpl::MergeOriginInfoOnResume( |
| const DownloadCreateInfo& new_create_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK_EQ(RESUMING_INTERNAL, state_); |
| DCHECK(!new_create_info.url_chain.empty()); |
| |
| // We are going to tack on any new redirects to our list of redirects. |
| // When a download is resumed, the URL used for the resumption request is the |
| // one at the end of the previous redirect chain. Tacking additional redirects |
| // to the end of this chain ensures that: |
| // - If the download needs to be resumed again, the ETag/Last-Modified headers |
| // will be used with the last server that sent them to us. |
| // - The redirect chain contains all the servers that were involved in this |
| // download since the initial request, in order. |
| std::vector<GURL>::const_iterator chain_iter = |
| new_create_info.url_chain.begin(); |
| if (*chain_iter == url_chain_.back()) |
| ++chain_iter; |
| |
| // Record some stats. If the precondition failed (the server returned |
| // HTTP_PRECONDITION_FAILED), then the download will automatically retried as |
| // a full request rather than a partial. Full restarts clobber validators. |
| int origin_state = 0; |
| if (chain_iter != new_create_info.url_chain.end()) |
| origin_state |= ORIGIN_STATE_ON_RESUMPTION_ADDITIONAL_REDIRECTS; |
| if (etag_ != new_create_info.etag || |
| last_modified_time_ != new_create_info.last_modified) |
| origin_state |= ORIGIN_STATE_ON_RESUMPTION_VALIDATORS_CHANGED; |
| if (content_disposition_ != new_create_info.content_disposition) |
| origin_state |= ORIGIN_STATE_ON_RESUMPTION_CONTENT_DISPOSITION_CHANGED; |
| RecordOriginStateOnResumption(new_create_info.save_info->offset != 0, |
| origin_state); |
| |
| url_chain_.insert( |
| url_chain_.end(), chain_iter, new_create_info.url_chain.end()); |
| etag_ = new_create_info.etag; |
| last_modified_time_ = new_create_info.last_modified; |
| content_disposition_ = new_create_info.content_disposition; |
| |
| // Don't update observers. This method is expected to be called just before a |
| // DownloadFile is created and Start() is called. The observers will be |
| // notified when the download transitions to the IN_PROGRESS state. |
| } |
| |
| void DownloadItemImpl::NotifyRemoved() { |
| FOR_EACH_OBSERVER(Observer, observers_, OnDownloadRemoved(this)); |
| } |
| |
| void DownloadItemImpl::OnDownloadedFileRemoved() { |
| file_externally_removed_ = true; |
| VLOG(20) << __FUNCTION__ << " download=" << DebugString(true); |
| UpdateObservers(); |
| } |
| |
| base::WeakPtr<DownloadDestinationObserver> |
| DownloadItemImpl::DestinationObserverAsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| const net::BoundNetLog& DownloadItemImpl::GetBoundNetLog() const { |
| return bound_net_log_; |
| } |
| |
| void DownloadItemImpl::SetTotalBytes(int64 total_bytes) { |
| total_bytes_ = total_bytes; |
| } |
| |
| void DownloadItemImpl::OnAllDataSaved(const std::string& final_hash) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DCHECK_EQ(IN_PROGRESS_INTERNAL, state_); |
| DCHECK(!all_data_saved_); |
| all_data_saved_ = true; |
| VLOG(20) << __FUNCTION__ << " download=" << DebugString(true); |
| |
| // Store final hash and null out intermediate serialized hash state. |
| hash_ = final_hash; |
| hash_state_ = ""; |
| |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::MarkAsComplete() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DCHECK(all_data_saved_); |
| end_time_ = base::Time::Now(); |
| TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS); |
| } |
| |
| void DownloadItemImpl::DestinationUpdate(int64 bytes_so_far, |
| int64 bytes_per_sec, |
| const std::string& hash_state) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(20) << __FUNCTION__ << " so_far=" << bytes_so_far |
| << " per_sec=" << bytes_per_sec << " download=" << DebugString(true); |
| |
| if (GetState() != IN_PROGRESS) { |
| // Ignore if we're no longer in-progress. This can happen if we race a |
| // Cancel on the UI thread with an update on the FILE thread. |
| // |
| // TODO(rdsmith): Arguably we should let this go through, as this means |
| // the download really did get further than we know before it was |
| // cancelled. But the gain isn't very large, and the code is more |
| // fragile if it has to support in progress updates in a non-in-progress |
| // state. This issue should be readdressed when we revamp performance |
| // reporting. |
| return; |
| } |
| bytes_per_sec_ = bytes_per_sec; |
| hash_state_ = hash_state; |
| received_bytes_ = bytes_so_far; |
| |
| // If we've received more data than we were expecting (bad server info?), |
| // revert to 'unknown size mode'. |
| if (received_bytes_ > total_bytes_) |
| total_bytes_ = 0; |
| |
| if (bound_net_log_.IsLoggingAllEvents()) { |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_UPDATED, |
| net::NetLog::Int64Callback("bytes_so_far", received_bytes_)); |
| } |
| |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::DestinationError(DownloadInterruptReason reason) { |
| // Postpone recognition of this error until after file name determination |
| // has completed and the intermediate file has been renamed to simplify |
| // resumption conditions. |
| if (current_path_.empty() || target_path_.empty()) |
| destination_error_ = reason; |
| else |
| Interrupt(reason); |
| } |
| |
| void DownloadItemImpl::DestinationCompleted(const std::string& final_hash) { |
| VLOG(20) << __FUNCTION__ << " download=" << DebugString(true); |
| if (GetState() != IN_PROGRESS) |
| return; |
| OnAllDataSaved(final_hash); |
| MaybeCompleteDownload(); |
| } |
| |
| // **** Download progression cascade |
| |
| void DownloadItemImpl::Init(bool active, |
| DownloadType download_type) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (active) |
| RecordDownloadCount(START_COUNT); |
| |
| std::string file_name; |
| if (download_type == SRC_HISTORY_IMPORT) { |
| // target_path_ works for History and Save As versions. |
| file_name = target_path_.AsUTF8Unsafe(); |
| } else { |
| // See if it's set programmatically. |
| file_name = forced_file_path_.AsUTF8Unsafe(); |
| // Possibly has a 'download' attribute for the anchor. |
| if (file_name.empty()) |
| file_name = suggested_filename_; |
| // From the URL file name. |
| if (file_name.empty()) |
| file_name = GetURL().ExtractFileName(); |
| } |
| |
| base::Callback<base::Value*(net::NetLog::LogLevel)> active_data = base::Bind( |
| &ItemActivatedNetLogCallback, this, download_type, &file_name); |
| if (active) { |
| bound_net_log_.BeginEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, active_data); |
| } else { |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, active_data); |
| } |
| |
| VLOG(20) << __FUNCTION__ << "() " << DebugString(true); |
| } |
| |
| // We're starting the download. |
| void DownloadItemImpl::Start( |
| scoped_ptr<DownloadFile> file, |
| scoped_ptr<DownloadRequestHandleInterface> req_handle) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!download_file_.get()); |
| DCHECK(file.get()); |
| DCHECK(req_handle.get()); |
| |
| download_file_ = file.Pass(); |
| request_handle_ = req_handle.Pass(); |
| |
| if (GetState() == CANCELLED) { |
| // The download was in the process of resuming when it was cancelled. Don't |
| // proceed. |
| ReleaseDownloadFile(true); |
| request_handle_->CancelRequest(); |
| return; |
| } |
| |
| TransitionTo(IN_PROGRESS_INTERNAL, UPDATE_OBSERVERS); |
| |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DownloadFile::Initialize, |
| // Safe because we control download file lifetime. |
| base::Unretained(download_file_.get()), |
| base::Bind(&DownloadItemImpl::OnDownloadFileInitialized, |
| weak_ptr_factory_.GetWeakPtr()))); |
| } |
| |
| void DownloadItemImpl::OnDownloadFileInitialized( |
| DownloadInterruptReason result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (result != DOWNLOAD_INTERRUPT_REASON_NONE) { |
| Interrupt(result); |
| // TODO(rdsmith/asanka): Arguably we should show this in the UI, but |
| // it's not at all clear what to show--we haven't done filename |
| // determination, so we don't know what name to display. OTOH, |
| // the failure mode of not showing the DI if the file initialization |
| // fails isn't a good one. Can we hack up a name based on the |
| // URLRequest? We'll need to make sure that initialization happens |
| // properly. Possibly the right thing is to have the UI handle |
| // this case specially. |
| return; |
| } |
| |
| delegate_->DetermineDownloadTarget( |
| this, base::Bind(&DownloadItemImpl::OnDownloadTargetDetermined, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Called by delegate_ when the download target path has been |
| // determined. |
| void DownloadItemImpl::OnDownloadTargetDetermined( |
| const base::FilePath& target_path, |
| TargetDisposition disposition, |
| DownloadDangerType danger_type, |
| const base::FilePath& intermediate_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If the |target_path| is empty, then we consider this download to be |
| // canceled. |
| if (target_path.empty()) { |
| Cancel(true); |
| return; |
| } |
| |
| // TODO(rdsmith,asanka): We are ignoring the possibility that the download |
| // has been interrupted at this point until we finish the intermediate |
| // rename and set the full path. That's dangerous, because we might race |
| // with resumption, either manual (because the interrupt is visible to the |
| // UI) or automatic. If we keep the "ignore an error on download until file |
| // name determination complete" semantics, we need to make sure that the |
| // error is kept completely invisible until that point. |
| |
| VLOG(20) << __FUNCTION__ << " " << target_path.value() << " " << disposition |
| << " " << danger_type << " " << DebugString(true); |
| |
| target_path_ = target_path; |
| target_disposition_ = disposition; |
| SetDangerType(danger_type); |
| |
| // We want the intermediate and target paths to refer to the same directory so |
| // that they are both on the same device and subject to same |
| // space/permission/availability constraints. |
| DCHECK(intermediate_path.DirName() == target_path.DirName()); |
| |
| // During resumption, we may choose to proceed with the same intermediate |
| // file. No rename is necessary if our intermediate file already has the |
| // correct name. |
| // |
| // The intermediate name may change from its original value during filename |
| // determination on resumption, for example if the reason for the interruption |
| // was the download target running out space, resulting in a user prompt. |
| if (intermediate_path == current_path_) { |
| OnDownloadRenamedToIntermediateName(DOWNLOAD_INTERRUPT_REASON_NONE, |
| intermediate_path); |
| return; |
| } |
| |
| // Rename to intermediate name. |
| // TODO(asanka): Skip this rename if AllDataSaved() is true. This avoids a |
| // spurious rename when we can just rename to the final |
| // filename. Unnecessary renames may cause bugs like |
| // http://crbug.com/74187. |
| DCHECK(!is_save_package_download_); |
| DCHECK(download_file_.get()); |
| DownloadFile::RenameCompletionCallback callback = |
| base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName, |
| weak_ptr_factory_.GetWeakPtr()); |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DownloadFile::RenameAndUniquify, |
| // Safe because we control download file lifetime. |
| base::Unretained(download_file_.get()), |
| intermediate_path, callback)); |
| } |
| |
| void DownloadItemImpl::OnDownloadRenamedToIntermediateName( |
| DownloadInterruptReason reason, |
| const base::FilePath& full_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(20) << __FUNCTION__ << " download=" << DebugString(true); |
| |
| if (DOWNLOAD_INTERRUPT_REASON_NONE != destination_error_) { |
| // Process destination error. If both |reason| and |destination_error_| |
| // refer to actual errors, we want to use the |destination_error_| as the |
| // argument to the Interrupt() routine, as it happened first. |
| if (reason == DOWNLOAD_INTERRUPT_REASON_NONE) |
| SetFullPath(full_path); |
| Interrupt(destination_error_); |
| destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE; |
| } else if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) { |
| Interrupt(reason); |
| // All file errors result in file deletion above; no need to cleanup. The |
| // current_path_ should be empty. Resuming this download will force a |
| // restart and a re-doing of filename determination. |
| DCHECK(current_path_.empty()); |
| } else { |
| SetFullPath(full_path); |
| UpdateObservers(); |
| MaybeCompleteDownload(); |
| } |
| } |
| |
| // When SavePackage downloads MHTML to GData (see |
| // SavePackageFilePickerChromeOS), GData calls MaybeCompleteDownload() like it |
| // does for non-SavePackage downloads, but SavePackage downloads never satisfy |
| // IsDownloadReadyForCompletion(). GDataDownloadObserver manually calls |
| // DownloadItem::UpdateObservers() when the upload completes so that SavePackage |
| // notices that the upload has completed and runs its normal Finish() pathway. |
| // MaybeCompleteDownload() is never the mechanism by which SavePackage completes |
| // downloads. SavePackage always uses its own Finish() to mark downloads |
| // complete. |
| void DownloadItemImpl::MaybeCompleteDownload() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!is_save_package_download_); |
| |
| if (!IsDownloadReadyForCompletion( |
| base::Bind(&DownloadItemImpl::MaybeCompleteDownload, |
| weak_ptr_factory_.GetWeakPtr()))) |
| return; |
| |
| // TODO(rdsmith): DCHECK that we only pass through this point |
| // once per download. The natural way to do this is by a state |
| // transition on the DownloadItem. |
| |
| // Confirm we're in the proper set of states to be here; |
| // have all data, have a history handle, (validated or safe). |
| DCHECK_EQ(IN_PROGRESS_INTERNAL, state_); |
| DCHECK(!IsDangerous()); |
| DCHECK(all_data_saved_); |
| |
| OnDownloadCompleting(); |
| } |
| |
| // Called by MaybeCompleteDownload() when it has determined that the download |
| // is ready for completion. |
| void DownloadItemImpl::OnDownloadCompleting() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (state_ != IN_PROGRESS_INTERNAL) |
| return; |
| |
| VLOG(20) << __FUNCTION__ << "()" |
| << " " << DebugString(true); |
| DCHECK(!GetTargetFilePath().empty()); |
| DCHECK(!IsDangerous()); |
| |
| // TODO(rdsmith/benjhayden): Remove as part of SavePackage integration. |
| if (is_save_package_download_) { |
| // Avoid doing anything on the file thread; there's nothing we control |
| // there. |
| // Strictly speaking, this skips giving the embedder a chance to open |
| // the download. But on a save package download, there's no real |
| // concept of opening. |
| Completed(); |
| return; |
| } |
| |
| DCHECK(download_file_.get()); |
| // Unilaterally rename; even if it already has the right name, |
| // we need theannotation. |
| DownloadFile::RenameCompletionCallback callback = |
| base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName, |
| weak_ptr_factory_.GetWeakPtr()); |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(&DownloadFile::RenameAndAnnotate, |
| base::Unretained(download_file_.get()), |
| GetTargetFilePath(), callback)); |
| } |
| |
| void DownloadItemImpl::OnDownloadRenamedToFinalName( |
| DownloadInterruptReason reason, |
| const base::FilePath& full_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!is_save_package_download_); |
| |
| // If a cancel or interrupt hit, we'll cancel the DownloadFile, which |
| // will result in deleting the file on the file thread. So we don't |
| // care about the name having been changed. |
| if (state_ != IN_PROGRESS_INTERNAL) |
| return; |
| |
| VLOG(20) << __FUNCTION__ << "()" |
| << " full_path = \"" << full_path.value() << "\"" |
| << " " << DebugString(false); |
| |
| if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) { |
| Interrupt(reason); |
| |
| // All file errors should have resulted in in file deletion above. On |
| // resumption we will need to re-do filename determination. |
| DCHECK(current_path_.empty()); |
| return; |
| } |
| |
| DCHECK(target_path_ == full_path); |
| |
| if (full_path != current_path_) { |
| // full_path is now the current and target file path. |
| DCHECK(!full_path.empty()); |
| SetFullPath(full_path); |
| } |
| |
| // Complete the download and release the DownloadFile. |
| DCHECK(download_file_.get()); |
| ReleaseDownloadFile(false); |
| |
| // We're not completely done with the download item yet, but at this |
| // point we're committed to complete the download. Cancels (or Interrupts, |
| // though it's not clear how they could happen) after this point will be |
| // ignored. |
| TransitionTo(COMPLETING_INTERNAL, DONT_UPDATE_OBSERVERS); |
| |
| if (delegate_->ShouldOpenDownload( |
| this, base::Bind(&DownloadItemImpl::DelayedDownloadOpened, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| Completed(); |
| } else { |
| delegate_delayed_complete_ = true; |
| UpdateObservers(); |
| } |
| } |
| |
| void DownloadItemImpl::DelayedDownloadOpened(bool auto_opened) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| auto_opened_ = auto_opened; |
| Completed(); |
| } |
| |
| void DownloadItemImpl::Completed() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| VLOG(20) << __FUNCTION__ << "() " << DebugString(false); |
| |
| DCHECK(all_data_saved_); |
| end_time_ = base::Time::Now(); |
| TransitionTo(COMPLETE_INTERNAL, UPDATE_OBSERVERS); |
| RecordDownloadCompleted(start_tick_, received_bytes_); |
| |
| if (auto_opened_) { |
| // If it was already handled by the delegate, do nothing. |
| } else if (GetOpenWhenComplete() || |
| ShouldOpenFileBasedOnExtension() || |
| IsTemporary()) { |
| // If the download is temporary, like in drag-and-drop, do not open it but |
| // we still need to set it auto-opened so that it can be removed from the |
| // download shelf. |
| if (!IsTemporary()) |
| OpenDownload(); |
| |
| auto_opened_ = true; |
| UpdateObservers(); |
| } |
| } |
| |
| void DownloadItemImpl::OnResumeRequestStarted(DownloadItem* item, |
| net::Error error) { |
| // If |item| is not NULL, then Start() has been called already, and nothing |
| // more needs to be done here. |
| if (item) { |
| DCHECK_EQ(net::OK, error); |
| DCHECK_EQ(static_cast<DownloadItem*>(this), item); |
| return; |
| } |
| // Otherwise, the request failed without passing through |
| // DownloadResourceHandler::OnResponseStarted. |
| if (error == net::OK) |
| error = net::ERR_FAILED; |
| DownloadInterruptReason reason = |
| ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_NETWORK); |
| DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason); |
| Interrupt(reason); |
| } |
| |
| // **** End of Download progression cascade |
| |
| // An error occurred somewhere. |
| void DownloadItemImpl::Interrupt(DownloadInterruptReason reason) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Somewhat counter-intuitively, it is possible for us to receive an |
| // interrupt after we've already been interrupted. The generation of |
| // interrupts from the file thread Renames and the generation of |
| // interrupts from disk writes go through two different mechanisms (driven |
| // by rename requests from UI thread and by write requests from IO thread, |
| // respectively), and since we choose not to keep state on the File thread, |
| // this is the place where the races collide. It's also possible for |
| // interrupts to race with cancels. |
| |
| // Whatever happens, the first one to hit the UI thread wins. |
| if (state_ != IN_PROGRESS_INTERNAL && state_ != RESUMING_INTERNAL) |
| return; |
| |
| last_reason_ = reason; |
| |
| ResumeMode resume_mode = GetResumeMode(); |
| |
| if (state_ == IN_PROGRESS_INTERNAL) { |
| // Cancel (delete file) if we're going to restart; no point in leaving |
| // data around we aren't going to use. Also cancel if resumption isn't |
| // enabled for the same reason. |
| ReleaseDownloadFile(resume_mode == RESUME_MODE_IMMEDIATE_RESTART || |
| resume_mode == RESUME_MODE_USER_RESTART || |
| !IsDownloadResumptionEnabled()); |
| |
| // Cancel the originating URL request. |
| request_handle_->CancelRequest(); |
| } else { |
| DCHECK(!download_file_.get()); |
| } |
| |
| // Reset all data saved, as even if we did save all the data we're going |
| // to go through another round of downloading when we resume. |
| // There's a potential problem here in the abstract, as if we did download |
| // all the data and then run into a continuable error, on resumption we |
| // won't download any more data. However, a) there are currently no |
| // continuable errors that can occur after we download all the data, and |
| // b) if there were, that would probably simply result in a null range |
| // request, which would generate a DestinationCompleted() notification |
| // from the DownloadFile, which would behave properly with setting |
| // all_data_saved_ to false here. |
| all_data_saved_ = false; |
| |
| TransitionTo(INTERRUPTED_INTERNAL, DONT_UPDATE_OBSERVERS); |
| RecordDownloadInterrupted(reason, received_bytes_, total_bytes_); |
| if (!GetWebContents()) |
| RecordDownloadCount(INTERRUPTED_WITHOUT_WEBCONTENTS); |
| |
| AutoResumeIfValid(); |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (destroy_file) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| // Will be deleted at end of task execution. |
| base::Bind(&DownloadFileCancel, base::Passed(&download_file_))); |
| // Avoid attempting to reuse the intermediate file by clearing out |
| // current_path_. |
| current_path_.clear(); |
| } else { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(&DownloadFileDetach), |
| // Will be deleted at end of task execution. |
| base::Passed(&download_file_))); |
| } |
| // Don't accept any more messages from the DownloadFile, and null |
| // out any previous "all data received". This also breaks links to |
| // other entities we've given out weak pointers to. |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| bool DownloadItemImpl::IsDownloadReadyForCompletion( |
| const base::Closure& state_change_notification) { |
| // If we don't have all the data, the download is not ready for |
| // completion. |
| if (!AllDataSaved()) |
| return false; |
| |
| // If the download is dangerous, but not yet validated, it's not ready for |
| // completion. |
| if (IsDangerous()) |
| return false; |
| |
| // If the download isn't active (e.g. has been cancelled) it's not |
| // ready for completion. |
| if (state_ != IN_PROGRESS_INTERNAL) |
| return false; |
| |
| // If the target filename hasn't been determined, then it's not ready for |
| // completion. This is checked in ReadyForDownloadCompletionDone(). |
| if (GetTargetFilePath().empty()) |
| return false; |
| |
| // This is checked in NeedsRename(). Without this conditional, |
| // browser_tests:DownloadTest.DownloadMimeType fails the DCHECK. |
| if (target_path_.DirName() != current_path_.DirName()) |
| return false; |
| |
| // Give the delegate a chance to hold up a stop sign. It'll call |
| // use back through the passed callback if it does and that state changes. |
| if (!delegate_->ShouldCompleteDownload(this, state_change_notification)) |
| return false; |
| |
| return true; |
| } |
| |
| void DownloadItemImpl::TransitionTo(DownloadInternalState new_state, |
| ShouldUpdateObservers notify_action) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (state_ == new_state) |
| return; |
| |
| DownloadInternalState old_state = state_; |
| state_ = new_state; |
| |
| switch (state_) { |
| case COMPLETING_INTERNAL: |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_COMPLETING, |
| base::Bind(&ItemCompletingNetLogCallback, received_bytes_, &hash_)); |
| break; |
| case COMPLETE_INTERNAL: |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_FINISHED, |
| base::Bind(&ItemFinishedNetLogCallback, auto_opened_)); |
| break; |
| case INTERRUPTED_INTERNAL: |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_INTERRUPTED, |
| base::Bind(&ItemInterruptedNetLogCallback, last_reason_, |
| received_bytes_, &hash_state_)); |
| break; |
| case IN_PROGRESS_INTERNAL: |
| if (old_state == INTERRUPTED_INTERNAL) { |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_RESUMED, |
| base::Bind(&ItemResumingNetLogCallback, |
| false, last_reason_, received_bytes_, &hash_state_)); |
| } |
| break; |
| case CANCELLED_INTERNAL: |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_CANCELED, |
| base::Bind(&ItemCanceledNetLogCallback, received_bytes_, |
| &hash_state_)); |
| break; |
| default: |
| break; |
| } |
| |
| VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true) |
| << " " << InternalToExternalState(old_state) |
| << " " << InternalToExternalState(state_); |
| |
| bool is_done = (state_ != IN_PROGRESS_INTERNAL && |
| state_ != COMPLETING_INTERNAL); |
| bool was_done = (old_state != IN_PROGRESS_INTERNAL && |
| old_state != COMPLETING_INTERNAL); |
| // Termination |
| if (is_done && !was_done) |
| bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE); |
| |
| // Resumption |
| if (was_done && !is_done) { |
| std::string file_name(target_path_.BaseName().AsUTF8Unsafe()); |
| bound_net_log_.BeginEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, |
| base::Bind(&ItemActivatedNetLogCallback, |
| this, SRC_ACTIVE_DOWNLOAD, |
| &file_name)); |
| } |
| |
| if (notify_action == UPDATE_OBSERVERS) |
| UpdateObservers(); |
| } |
| |
| void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type) { |
| if (danger_type != danger_type_) { |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED, |
| base::Bind(&ItemCheckedNetLogCallback, danger_type)); |
| } |
| danger_type_ = danger_type; |
| } |
| |
| void DownloadItemImpl::SetFullPath(const base::FilePath& new_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(20) << __FUNCTION__ << "()" |
| << " new_path = \"" << new_path.value() << "\"" |
| << " " << DebugString(true); |
| DCHECK(!new_path.empty()); |
| |
| bound_net_log_.AddEvent( |
| net::NetLog::TYPE_DOWNLOAD_ITEM_RENAMED, |
| base::Bind(&ItemRenamedNetLogCallback, ¤t_path_, &new_path)); |
| |
| current_path_ = new_path; |
| } |
| |
| void DownloadItemImpl::AutoResumeIfValid() { |
| DVLOG(20) << __FUNCTION__ << "() " << DebugString(true); |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| ResumeMode mode = GetResumeMode(); |
| |
| if (mode != RESUME_MODE_IMMEDIATE_RESTART && |
| mode != RESUME_MODE_IMMEDIATE_CONTINUE) { |
| return; |
| } |
| |
| auto_resume_count_++; |
| |
| ResumeInterruptedDownload(); |
| } |
| |
| void DownloadItemImpl::ResumeInterruptedDownload() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If the flag for downloads resumption isn't enabled, ignore |
| // this request. |
| const CommandLine& command_line = *CommandLine::ForCurrentProcess(); |
| if (!command_line.HasSwitch(switches::kEnableDownloadResumption)) |
| return; |
| |
| // If we're not interrupted, ignore the request; our caller is drunk. |
| if (state_ != INTERRUPTED_INTERNAL) |
| return; |
| |
| // If we can't get a web contents, we can't resume the download. |
| // TODO(rdsmith): Find some alternative web contents to use--this |
| // means we can't restart a download if it's a download imported |
| // from the history. |
| if (!GetWebContents()) |
| return; |
| |
| // Reset the appropriate state if restarting. |
| ResumeMode mode = GetResumeMode(); |
| if (mode == RESUME_MODE_IMMEDIATE_RESTART || |
| mode == RESUME_MODE_USER_RESTART) { |
| received_bytes_ = 0; |
| hash_state_ = ""; |
| last_modified_time_ = ""; |
| etag_ = ""; |
| } |
| |
| scoped_ptr<DownloadUrlParameters> download_params( |
| DownloadUrlParameters::FromWebContents(GetWebContents(), |
| GetOriginalUrl())); |
| |
| download_params->set_file_path(GetFullPath()); |
| download_params->set_offset(GetReceivedBytes()); |
| download_params->set_hash_state(GetHashState()); |
| download_params->set_last_modified(GetLastModifiedTime()); |
| download_params->set_etag(GetETag()); |
| download_params->set_callback( |
| base::Bind(&DownloadItemImpl::OnResumeRequestStarted, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| delegate_->ResumeInterruptedDownload(download_params.Pass(), GetId()); |
| // Just in case we were interrupted while paused. |
| is_paused_ = false; |
| |
| TransitionTo(RESUMING_INTERNAL, DONT_UPDATE_OBSERVERS); |
| } |
| |
| // static |
| DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState( |
| DownloadInternalState internal_state) { |
| switch (internal_state) { |
| case IN_PROGRESS_INTERNAL: |
| return IN_PROGRESS; |
| case COMPLETING_INTERNAL: |
| return IN_PROGRESS; |
| case COMPLETE_INTERNAL: |
| return COMPLETE; |
| case CANCELLED_INTERNAL: |
| return CANCELLED; |
| case INTERRUPTED_INTERNAL: |
| return INTERRUPTED; |
| case RESUMING_INTERNAL: |
| return INTERRUPTED; |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| break; |
| } |
| NOTREACHED(); |
| return MAX_DOWNLOAD_STATE; |
| } |
| |
| // static |
| DownloadItemImpl::DownloadInternalState |
| DownloadItemImpl::ExternalToInternalState( |
| DownloadState external_state) { |
| switch (external_state) { |
| case IN_PROGRESS: |
| return IN_PROGRESS_INTERNAL; |
| case COMPLETE: |
| return COMPLETE_INTERNAL; |
| case CANCELLED: |
| return CANCELLED_INTERNAL; |
| case INTERRUPTED: |
| return INTERRUPTED_INTERNAL; |
| default: |
| NOTREACHED(); |
| } |
| return MAX_DOWNLOAD_INTERNAL_STATE; |
| } |
| |
| const char* DownloadItemImpl::DebugDownloadStateString( |
| DownloadInternalState state) { |
| switch (state) { |
| case IN_PROGRESS_INTERNAL: |
| return "IN_PROGRESS"; |
| case COMPLETING_INTERNAL: |
| return "COMPLETING"; |
| case COMPLETE_INTERNAL: |
| return "COMPLETE"; |
| case CANCELLED_INTERNAL: |
| return "CANCELLED"; |
| case INTERRUPTED_INTERNAL: |
| return "INTERRUPTED"; |
| case RESUMING_INTERNAL: |
| return "RESUMING"; |
| case MAX_DOWNLOAD_INTERNAL_STATE: |
| break; |
| }; |
| NOTREACHED() << "Unknown download state " << state; |
| return "unknown"; |
| } |
| |
| const char* DownloadItemImpl::DebugResumeModeString(ResumeMode mode) { |
| switch (mode) { |
| case RESUME_MODE_INVALID: |
| return "INVALID"; |
| case RESUME_MODE_IMMEDIATE_CONTINUE: |
| return "IMMEDIATE_CONTINUE"; |
| case RESUME_MODE_IMMEDIATE_RESTART: |
| return "IMMEDIATE_RESTART"; |
| case RESUME_MODE_USER_CONTINUE: |
| return "USER_CONTINUE"; |
| case RESUME_MODE_USER_RESTART: |
| return "USER_RESTART"; |
| } |
| NOTREACHED() << "Unknown resume mode " << mode; |
| return "unknown"; |
| } |
| |
| } // namespace content |