Chromium Code Reviews| Index: content/browser/download/download_item_impl.cc |
| diff --git a/content/browser/download/download_item_impl.cc b/content/browser/download/download_item_impl.cc |
| index d0984add840f7819230ee1b52fbbf8977952da43..582aa8517a045ba4ad266134102b32793cea03db 100644 |
| --- a/content/browser/download/download_item_impl.cc |
| +++ b/content/browser/download/download_item_impl.cc |
| @@ -46,6 +46,7 @@ |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/download_persistent_store_info.h" |
| +#include "content/public/browser/download_url_parameters.h" |
| #include "net/base/net_util.h" |
| using content::BrowserThread; |
| @@ -58,7 +59,7 @@ using content::WebContents; |
| namespace { |
| -static void DeleteDownloadedFile(const FilePath& path) { |
| +void DeleteDownloadedFile(const FilePath& path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| // Make sure we only delete files. |
| @@ -99,6 +100,8 @@ class NullDownloadRequestHandle : public DownloadRequestHandleInterface { |
| virtual void PauseRequest() const OVERRIDE {} |
| virtual void ResumeRequest() const OVERRIDE {} |
| virtual void CancelRequest() const OVERRIDE {} |
| + virtual void SetRequestId(int new_request_id) OVERRIDE {} |
| + virtual int RequestId() const OVERRIDE { return -1; } |
| virtual std::string DebugString() const OVERRIDE { |
| return "Null DownloadRequestHandle"; |
| } |
| @@ -119,6 +122,9 @@ const char DownloadItem::kEmptyFileHash[] = ""; |
| } |
| +// The maximum number of attempts we will make to resume automatically. |
| +const int DownloadItemImpl::kMaxAutoResumeAttempts = 5; |
| + |
| // Our download table ID starts at 1, so we use 0 to represent a download that |
| // has started, but has not yet had its data persisted in the table. We use fake |
| // database handles in incognito mode starting at -1 and progressively getting |
| @@ -149,6 +155,8 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, |
| db_handle_(info.db_handle), |
| delegate_(delegate), |
| is_paused_(false), |
| + is_resuming_(false), |
| + auto_resume_count_(0), |
| open_when_complete_(false), |
| file_externally_removed_(false), |
| safety_state_(SAFE), |
| @@ -174,10 +182,8 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, |
| DownloadItemImpl::DownloadItemImpl( |
| DownloadItemImplDelegate* delegate, |
| const DownloadCreateInfo& info, |
| - scoped_ptr<DownloadRequestHandleInterface> request_handle, |
| const net::BoundNetLog& bound_net_log) |
| - : request_handle_(request_handle.Pass()), |
| - download_id_(info.download_id), |
| + : download_id_(info.download_id), |
| target_disposition_( |
| (info.prompt_user_for_save_location) ? |
| TARGET_DISPOSITION_PROMPT : TARGET_DISPOSITION_OVERWRITE), |
| @@ -203,6 +209,8 @@ DownloadItemImpl::DownloadItemImpl( |
| db_handle_(DownloadItem::kUninitializedHandle), |
| delegate_(delegate), |
| is_paused_(false), |
| + is_resuming_(false), |
| + auto_resume_count_(0), |
| open_when_complete_(false), |
| file_externally_removed_(false), |
| safety_state_(SAFE), |
| @@ -258,6 +266,8 @@ DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate, |
| db_handle_(DownloadItem::kUninitializedHandle), |
| delegate_(delegate), |
| is_paused_(false), |
| + is_resuming_(false), |
| + auto_resume_count_(0), |
| open_when_complete_(false), |
| file_externally_removed_(false), |
| safety_state_(SAFE), |
| @@ -321,35 +331,42 @@ void DownloadItemImpl::DangerousDownloadValidated() { |
| UpdateObservers(); |
| - delegate_->MaybeCompleteDownload(this); |
| + delegate_->MaybeCompleteDownload(download_id_.local()); |
| } |
| void DownloadItemImpl::TogglePause() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| - DCHECK(state_ == IN_PROGRESS_INTERNAL || state_ == COMPLETING_INTERNAL); |
| - if (is_paused_) |
| - request_handle_->ResumeRequest(); |
| - else |
| - request_handle_->PauseRequest(); |
| - is_paused_ = !is_paused_; |
| + DCHECK(IsPartialDownload()); |
| + if (IsInProgress()) { |
| + if (is_paused_) |
| + request_handle_->ResumeRequest(); |
| + else |
| + request_handle_->PauseRequest(); |
| + is_paused_ = !is_paused_; |
| + } else if (IsInterrupted()) { |
| + auto_resume_count_ = 0; // User input resets the counter. |
| + delegate_->RestartInterruptedDownload( |
| + this, content::DownloadUrlParameters::OnStartedCallback()); |
| + } |
| + |
| UpdateObservers(); |
| } |
| void DownloadItemImpl::Cancel(bool user_cancel) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| - last_reason_ = user_cancel ? |
| - content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED : |
| - content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN; |
| - |
| VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true); |
| - if (state_ != IN_PROGRESS_INTERNAL) { |
| + if (state_ != IN_PROGRESS_INTERNAL && state_ != INTERRUPTED_INTERNAL) { |
| // Small downloads might be complete before this method has |
| // a chance to run. |
| return; |
| } |
| + last_reason_ = user_cancel ? |
| + content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED : |
| + content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN; |
| + |
| download_stats::RecordDownloadCount(download_stats::CANCELLED_COUNT); |
| TransitionTo(CANCELLED_INTERNAL); |
| @@ -456,6 +473,87 @@ bool DownloadItemImpl::IsPaused() const { |
| return is_paused_; |
| } |
| +bool DownloadItemImpl::IsResuming() const { |
| + return is_resuming_; |
| +} |
| + |
| +void DownloadItemImpl::SetRequest( |
| + scoped_ptr<DownloadRequestHandleInterface> request_handle) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + request_handle_ = request_handle.Pass(); |
| +} |
| + |
| +DownloadItem::ResumeMode DownloadItemImpl::CanResumeInterrupted() const { |
| + if (!IsInterrupted() || !is_persisted_) |
| + return RESUME_MODE_INVALID; |
| + |
| + ResumeMode mode = RESUME_MODE_INVALID; |
| + |
| + switch(last_reason_) { |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR: |
| + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT: |
| + mode = RESUME_MODE_IMMEDIATE_CONTINUE; // Continue immediately. |
| + break; |
| + |
| + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_PRECONDITION: |
| + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE: |
| + mode = RESUME_MODE_IMMEDIATE_RESTART; // Restart immediately. |
| + break; |
| + |
| + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED: |
| + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED: |
| + case content::DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN: |
| + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED: |
| + case content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN: |
| + case content::DOWNLOAD_INTERRUPT_REASON_CRASH: |
| + mode = RESUME_MODE_USER_CONTINUE; // Continue via user input. |
| + break; |
| + |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED: |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED: |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE: |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG: |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE: |
| + mode = RESUME_MODE_USER_RESTART; // Restart via user input. |
| + break; |
| + |
| + case content::DOWNLOAD_INTERRUPT_REASON_NONE: |
| + case content::DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED: |
| + case content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT: |
| + case content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED: |
| + break; |
| + } |
| + |
| + // Check if we have exhausted the number of automatic retry attempts. |
| + if (auto_resume_count_ >= kMaxAutoResumeAttempts) { |
| + if (mode == RESUME_MODE_IMMEDIATE_CONTINUE) |
| + mode = RESUME_MODE_USER_CONTINUE; |
| + else if (mode == RESUME_MODE_IMMEDIATE_RESTART) |
| + mode = RESUME_MODE_USER_RESTART; |
| + } |
| + |
| + return mode; |
| +} |
| + |
| +void DownloadItemImpl::AutoResumeIfValid() { |
| + ResumeMode mode = CanResumeInterrupted(); |
| + |
| + if ((mode == RESUME_MODE_IMMEDIATE_RESTART) || |
| + (mode == RESUME_MODE_IMMEDIATE_CONTINUE)) { |
| + if (mode == RESUME_MODE_IMMEDIATE_RESTART) { |
| + received_bytes_ = 0; // Restart instead of continuing. |
| + hash_state_ = ""; |
| + last_modified_time_ = ""; |
| + etag_ = ""; |
| + } |
| + |
| + auto_resume_count_++; |
| + |
| + delegate_->RestartInterruptedDownload( |
| + this, content::DownloadUrlParameters::OnStartedCallback()); |
| + } |
| +} |
| + |
| bool DownloadItemImpl::IsTemporary() const { |
| return is_temporary_; |
| } |
| @@ -464,10 +562,9 @@ bool DownloadItemImpl::IsPersisted() const { |
| return is_persisted_; |
| } |
| -// TODO(ahendrickson) -- Move |INTERRUPTED| from |IsCancelled()| to |
| -// |IsPartialDownload()|, when resuming interrupted downloads is implemented. |
| bool DownloadItemImpl::IsPartialDownload() const { |
| - return InternalToExternalState(state_) == IN_PROGRESS; |
| + DownloadState state = InternalToExternalState(state_); |
| + return (state == IN_PROGRESS) || (state == INTERRUPTED); |
|
benjhayden
2012/10/15 17:59:37
Have you audited all the callers of IsPartialDownl
|
| } |
| bool DownloadItemImpl::IsInProgress() const { |
| @@ -475,8 +572,7 @@ bool DownloadItemImpl::IsInProgress() const { |
| } |
| bool DownloadItemImpl::IsCancelled() const { |
| - DownloadState external_state = InternalToExternalState(state_); |
| - return external_state == CANCELLED || external_state == INTERRUPTED; |
|
benjhayden
2012/10/15 17:59:37
Have you audited all the callers of IsCancelled()
|
| + return InternalToExternalState(state_) == CANCELLED; |
| } |
| bool DownloadItemImpl::IsInterrupted() const { |
| @@ -664,6 +760,21 @@ bool DownloadItemImpl::CanOpenDownload() { |
| return !file_externally_removed_; |
| } |
| +bool DownloadItemImpl::CanResumeDownload() const { |
| + // Do not allow interrupted downloads to be resumed until the target name |
| + // has been determined, and the user has validated any dangerous items. |
| + // TODO(rdsmith) -- Add a state indicating that filename determination has |
| + // occurred. |
| + if (GetTargetFilePath().empty()) |
| + return false; |
| + |
| + if (GetSafetyState() == DANGEROUS) |
| + return false; |
| + |
| + return IsInProgress() || |
| + (CanResumeInterrupted() != DownloadItem::RESUME_MODE_INVALID); |
| +} |
| + |
| bool DownloadItemImpl::ShouldOpenFileBasedOnExtension() { |
| return delegate_->ShouldOpenFileBasedOnExtension(GetUserVerifiedFilePath()); |
| } |
| @@ -760,7 +871,11 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { |
| " received = %" PRId64 |
| " reason = %s" |
| " paused = %c" |
| + " resuming = %c" |
| + " resume_mode = %s" |
| " safety = %s" |
| + " all_data_saved = %c" |
| + " persisted = %c" |
| " last_modified = '%s'" |
| " etag = '%s'" |
| " url_chain = \n\t\"%s\"\n\t" |
| @@ -771,7 +886,11 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { |
| GetReceivedBytes(), |
| InterruptReasonDebugString(last_reason_).c_str(), |
| IsPaused() ? 'T' : 'F', |
| + IsResuming() ? 'T' : 'F', |
| + DebugResumeModeString(CanResumeInterrupted()), |
| DebugSafetyStateString(GetSafetyState()), |
| + AllDataSaved() ? 'T' : 'F', |
| + IsPersisted() ? 'T' : 'F', |
| GetLastModifiedTime().c_str(), |
| GetETag().c_str(), |
| url_list.c_str(), |
| @@ -797,6 +916,7 @@ void DownloadItemImpl::NotifyRemoved() { |
| void DownloadItemImpl::OnDownloadedFileRemoved() { |
| file_externally_removed_ = true; |
| UpdateObservers(); |
| + delegate_->MaybeCompleteDownload(download_id_.local()); |
|
benjhayden
2012/10/15 17:59:37
Sorry, what's this doing? I looked in download_man
|
| } |
| void DownloadItemImpl::OffThreadCancel() { |
| @@ -809,6 +929,19 @@ void DownloadItemImpl::OffThreadCancel() { |
| delegate_->GetDownloadFileManager(), download_id_)); |
| } |
| +void DownloadItemImpl::OffThreadInterrupt() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + request_handle_->CancelRequest(); |
| + |
| + BrowserThread::PostTask( |
| + BrowserThread::FILE, |
| + FROM_HERE, |
| + base::Bind(&DownloadFileManager::InterruptDownload, |
| + delegate_->GetDownloadFileManager(), |
| + download_id_, |
| + base::Closure())); |
| +} |
| + |
| // An error occurred somewhere. |
| void DownloadItemImpl::Interrupt(content::DownloadInterruptReason reason) { |
| // Somewhat counter-intuitively, it is possible for us to receive an |
| @@ -825,10 +958,25 @@ void DownloadItemImpl::Interrupt(content::DownloadInterruptReason reason) { |
| return; |
| last_reason_ = reason; |
| + is_resuming_ = false; |
| TransitionTo(INTERRUPTED_INTERNAL); |
| download_stats::RecordDownloadInterrupted( |
| reason, received_bytes_, total_bytes_); |
| delegate_->DownloadStopped(this); |
| + |
| + AutoResumeIfValid(); |
| +} |
| + |
| +void DownloadItemImpl::Resume( |
| + scoped_ptr<DownloadRequestHandleInterface> req_handle) { |
| + DVLOG(20) << __FUNCTION__ << "() " << DebugString(true); |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + |
| + if (state_ != INTERRUPTED_INTERNAL) |
| + return; |
| + request_handle_ = req_handle.Pass(); |
| + is_resuming_ = true; |
| + TransitionTo(IN_PROGRESS_INTERNAL); |
| } |
| void DownloadItemImpl::SetTotalBytes(int64 total_bytes) { |
| @@ -1014,7 +1162,7 @@ void DownloadItemImpl::OnDownloadRenamedToIntermediateName( |
| void DownloadItemImpl::MaybeCompleteDownload() { |
| // TODO(rdsmith): Move logic for this function here. |
| - delegate_->MaybeCompleteDownload(this); |
| + delegate_->MaybeCompleteDownload(download_id_.local()); |
| } |
| // Called by DownloadManagerImpl::MaybeCompleteDownload() when it has |
| @@ -1269,6 +1417,10 @@ DownloadItemImpl::ExternalToInternalState( |
| return MAX_DOWNLOAD_INTERNAL_STATE; |
| } |
| +const net::BoundNetLog& DownloadItemImpl::GetBoundNetLog() const { |
| + return bound_net_log_; |
| +} |
| + |
| const char* DownloadItemImpl::DebugDownloadStateString( |
| DownloadInternalState state) { |
| switch (state) { |
| @@ -1287,3 +1439,22 @@ const char* DownloadItemImpl::DebugDownloadStateString( |
| return "unknown"; |
| }; |
| } |
| + |
| +const char* DownloadItemImpl::DebugResumeModeString( |
| + DownloadItem::ResumeMode mode) { |
| + switch (mode) { |
| + case DownloadItem::RESUME_MODE_INVALID: |
| + return "INVALID"; |
| + case DownloadItem::RESUME_MODE_IMMEDIATE_CONTINUE: |
| + return "IMMEDIATE_CONTINUE"; |
| + case DownloadItem::RESUME_MODE_IMMEDIATE_RESTART: |
| + return "IMMEDIATE_RESTART"; |
| + case DownloadItem::RESUME_MODE_USER_CONTINUE: |
| + return "USER_CONTINUE"; |
| + case DownloadItem::RESUME_MODE_USER_RESTART: |
| + return "USER_RESTART"; |
| + default: |
| + NOTREACHED() << "Unknown resume mode " << mode; |
| + return "unknown"; |
| + } |
| +} |