diff options
author | asanka@chromium.org <asanka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-30 23:32:06 +0000 |
---|---|---|
committer | asanka@chromium.org <asanka@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-30 23:32:06 +0000 |
commit | 3d833de2590e5bcddec48b51691d088afb3d84e3 (patch) | |
tree | 66337201944575e32a60e36334076b4bb2be7874 /content/browser | |
parent | bb8f10a98c8f3a96025c47100c6473e2ce9645ad (diff) | |
download | chromium_src-3d833de2590e5bcddec48b51691d088afb3d84e3.zip chromium_src-3d833de2590e5bcddec48b51691d088afb3d84e3.tar.gz chromium_src-3d833de2590e5bcddec48b51691d088afb3d84e3.tar.bz2 |
Download filename determination refactor (1/3)
- Removes dependency on DownloadStateInfo in chrome/.
- Adds unit tests for ChromeDownloadManagerDelegate.
- Cleanup methods for filename determination in DownloadItem to eliminate setters.
BUG=78085
TEST=unit tests
Review URL: https://chromiumcodereview.appspot.com/10083010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@139682 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
-rw-r--r-- | content/browser/download/download_file_manager.cc | 66 | ||||
-rw-r--r-- | content/browser/download/download_file_manager.h | 74 | ||||
-rw-r--r-- | content/browser/download/download_file_manager_unittest.cc | 82 | ||||
-rw-r--r-- | content/browser/download/download_item_impl.cc | 302 | ||||
-rw-r--r-- | content/browser/download/download_item_impl.h | 90 | ||||
-rw-r--r-- | content/browser/download/download_item_impl_unittest.cc | 251 | ||||
-rw-r--r-- | content/browser/download/download_manager_impl.cc | 150 | ||||
-rw-r--r-- | content/browser/download/download_manager_impl.h | 10 | ||||
-rw-r--r-- | content/browser/download/download_manager_impl_unittest.cc | 677 | ||||
-rw-r--r-- | content/browser/download/download_state_info.cc | 41 | ||||
-rw-r--r-- | content/browser/download/download_state_info.h | 55 |
11 files changed, 965 insertions, 833 deletions
diff --git a/content/browser/download/download_file_manager.cc b/content/browser/download/download_file_manager.cc index a9ab8ff..40f1acb 100644 --- a/content/browser/download/download_file_manager.cc +++ b/content/browser/download/download_file_manager.cc @@ -335,25 +335,43 @@ void DownloadFileManager::OnDownloadManagerShutdown(DownloadManager* manager) { // There are 2 possible rename cases where this method can be called: // 1. tmp -> foo.crdownload (not final, safe) // 2. tmp-> Unconfirmed.xxx.crdownload (not final, dangerous) +// TODO(asanka): Merge with RenameCompletingDownloadFile() and move +// uniquification logic into DownloadFile. void DownloadFileManager::RenameInProgressDownloadFile( - DownloadId global_id, const FilePath& full_path) { + DownloadId global_id, + const FilePath& full_path, + bool overwrite_existing_file, + const RenameCompletionCallback& callback) { VLOG(20) << __FUNCTION__ << "()" << " id = " << global_id << " full_path = \"" << full_path.value() << "\""; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFile* download_file = GetDownloadFile(global_id); - if (!download_file) + if (!download_file) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(callback, FilePath())); return; + } VLOG(20) << __FUNCTION__ << "()" << " download_file = " << download_file->DebugString(); + FilePath new_path(full_path); + if (!overwrite_existing_file) { + int uniquifier = + file_util::GetUniquePathNumber(new_path, FILE_PATH_LITERAL("")); + if (uniquifier > 0) { + new_path = new_path.InsertBeforeExtensionASCII( + StringPrintf(" (%d)", uniquifier)); + } + } - net::Error rename_error = download_file->Rename(full_path); + net::Error rename_error = download_file->Rename(new_path); if (net::OK != rename_error) { - // Error. Between the time the UI thread generated 'full_path' to the time - // this code runs, something happened that prevents us from renaming. CancelDownloadOnRename(global_id, rename_error); + new_path.clear(); } + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(callback, new_path)); } // The DownloadManager in the UI thread has provided a final name for the @@ -366,23 +384,23 @@ void DownloadFileManager::RenameInProgressDownloadFile( void DownloadFileManager::RenameCompletingDownloadFile( DownloadId global_id, const FilePath& full_path, - bool overwrite_existing_file) { + bool overwrite_existing_file, + const RenameCompletionCallback& callback) { VLOG(20) << __FUNCTION__ << "()" << " id = " << global_id << " overwrite_existing_file = " << overwrite_existing_file << " full_path = \"" << full_path.value() << "\""; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DownloadFile* download_file = GetDownloadFile(global_id); - if (!download_file) + if (!download_file) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(callback, FilePath())); return; - - DCHECK(download_file->GetDownloadManager()); - DownloadManager* download_manager = download_file->GetDownloadManager(); + } VLOG(20) << __FUNCTION__ << "()" << " download_file = " << download_file->DebugString(); - int uniquifier = 0; FilePath new_path = full_path; if (!overwrite_existing_file) { // Make our name unique at this point, as if a dangerous file is @@ -392,7 +410,7 @@ void DownloadFileManager::RenameCompletingDownloadFile( // not exists yet, so the second file gets the same name. // This should not happen in the SAFE case, and we check for that in the UI // thread. - uniquifier = + int uniquifier = file_util::GetUniquePathNumber(new_path, FILE_PATH_LITERAL("")); if (uniquifier > 0) { new_path = new_path.InsertBeforeExtensionASCII( @@ -406,23 +424,27 @@ void DownloadFileManager::RenameCompletingDownloadFile( // Error. Between the time the UI thread generated 'full_path' to the time // this code runs, something happened that prevents us from renaming. CancelDownloadOnRename(global_id, rename_error); - return; - } - + new_path.clear(); + } else { #if defined(OS_MACOSX) - // Done here because we only want to do this once; see - // http://crbug.com/13120 for details. - download_file->AnnotateWithSourceInformation(); + // Done here because we only want to do this once; see + // http://crbug.com/13120 for details. + download_file->AnnotateWithSourceInformation(); #endif + } - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::Bind(&DownloadManager::OnDownloadRenamedToFinalName, - download_manager, global_id.local(), new_path, uniquifier)); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(callback, new_path)); +} + +int DownloadFileManager::NumberOfActiveDownloads() const { + return downloads_.size(); } // Called only from RenameInProgressDownloadFile and // RenameCompletingDownloadFile on the FILE thread. +// TODO(asanka): Use the RenameCompletionCallback instead of a separate +// OnDownloadInterrupted call. void DownloadFileManager::CancelDownloadOnRename( DownloadId global_id, net::Error rename_error) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); diff --git a/content/browser/download/download_file_manager.h b/content/browser/download/download_file_manager.h index 255dc3d..965a93c 100644 --- a/content/browser/download/download_file_manager.h +++ b/content/browser/download/download_file_manager.h @@ -44,6 +44,7 @@ #include "base/atomic_sequence_num.h" #include "base/basictypes.h" +#include "base/callback_forward.h" #include "base/gtest_prod_util.h" #include "base/hash_tables.h" #include "base/memory/ref_counted.h" @@ -70,9 +71,15 @@ class BoundNetLog; } // Manages all in progress downloads. +// Methods are virtual to allow mocks--this class is not intended +// to be a base class. class CONTENT_EXPORT DownloadFileManager : public base::RefCountedThreadSafe<DownloadFileManager> { public: + // Callback used with RenameInProgressDownloadFile() and + // RenameCompletingDownloadFile(). + typedef base::Callback<void(const FilePath&)> RenameCompletionCallback; + class DownloadFileFactory { public: virtual ~DownloadFileFactory() {} @@ -91,56 +98,66 @@ class CONTENT_EXPORT DownloadFileManager explicit DownloadFileManager(DownloadFileFactory* factory); // Called on shutdown on the UI thread. - void Shutdown(); + virtual void Shutdown(); // Called on UI thread to make DownloadFileManager start the download. - void StartDownload(DownloadCreateInfo* info, - const DownloadRequestHandle& request_handle); + virtual void StartDownload(DownloadCreateInfo* info, + const DownloadRequestHandle& request_handle); // Handlers for notifications sent from the IO thread and run on the // FILE thread. - void UpdateDownload(content::DownloadId global_id, - content::DownloadBuffer* buffer); + virtual void UpdateDownload(content::DownloadId global_id, + content::DownloadBuffer* buffer); // |reason| is the reason for interruption, if one occurs. // |security_info| contains SSL information (cert_id, cert_status, // security_bits, ssl_connection_status), which can be used to // fine-tune the error message. It is empty if the transaction // was not performed securely. - void OnResponseCompleted(content::DownloadId global_id, - content::DownloadInterruptReason reason, - const std::string& security_info); + virtual void OnResponseCompleted(content::DownloadId global_id, + content::DownloadInterruptReason reason, + const std::string& security_info); // Handlers for notifications sent from the UI thread and run on the // FILE thread. These are both terminal actions with respect to the // download file, as far as the DownloadFileManager is concerned -- if // anything happens to the download file after they are called, it will // be ignored. - void CancelDownload(content::DownloadId id); - void CompleteDownload(content::DownloadId id); + virtual void CancelDownload(content::DownloadId id); + virtual void CompleteDownload(content::DownloadId id); // Called on FILE thread by DownloadManager at the beginning of its shutdown. - void OnDownloadManagerShutdown(content::DownloadManager* manager); - - // The DownloadManager in the UI thread has provided an intermediate - // .crdownload name for the download specified by |id|. - void RenameInProgressDownloadFile(content::DownloadId id, - const FilePath& full_path); + virtual void OnDownloadManagerShutdown(content::DownloadManager* manager); + + // The DownloadManager in the UI thread has provided an intermediate name for + // the download specified by |id|. |overwrite_existing_file| indicates whether + // any existing file at |full_path| should be overwritten. If false, adds a + // uniquifier to |full_path| and uses the resulting name as the intermediate + // path for the download. Invokes |callback| with the new path on success. If + // the rename fails, calls CancelDownloadOnRename() and invokes |callback| + // with an empty FilePath(). + virtual void RenameInProgressDownloadFile( + content::DownloadId id, + const FilePath& full_path, + bool overwrite_existing_file, + const RenameCompletionCallback& callback); // The DownloadManager in the UI thread has provided a final name for the - // download specified by |id|. - // |overwrite_existing_file| prevents uniquification, and is used for SAFE - // downloads, as the user may have decided to overwrite the file. - // Sent from the UI thread and run on the FILE thread. - void RenameCompletingDownloadFile(content::DownloadId id, - const FilePath& full_path, - bool overwrite_existing_file); + // download specified by |id|. |overwrite_existing_file| prevents + // uniquification, and is used for SAFE downloads, as the user may have + // decided to overwrite the file. Sent from the UI thread and run on the FILE + // thread. Invokes |callback| with the new path on success. If the rename + // fails, calls CancelDownloadOnRename() and invokes |callback| with an empty + // FilePath(). + virtual void RenameCompletingDownloadFile( + content::DownloadId id, + const FilePath& full_path, + bool overwrite_existing_file, + const RenameCompletionCallback& callback); // The number of downloads currently active on the DownloadFileManager. // Primarily for testing. - int NumberOfActiveDownloads() const { - return downloads_.size(); - } + virtual int NumberOfActiveDownloads() const; void SetFileFactoryForTesting(scoped_ptr<DownloadFileFactory> file_factory) { download_file_factory_.reset(file_factory.release()); @@ -150,14 +167,15 @@ class CONTENT_EXPORT DownloadFileManager return download_file_factory_.get(); // Explicitly NOT a scoped_ptr. } + protected: + virtual ~DownloadFileManager(); + private: friend class base::RefCountedThreadSafe<DownloadFileManager>; friend class DownloadFileManagerTest; friend class DownloadManagerTest; FRIEND_TEST_ALL_PREFIXES(DownloadManagerTest, StartDownload); - ~DownloadFileManager(); - // Timer helpers for updating the UI about the current progress of a download. void StartUpdateTimer(); void StopUpdateTimer(); diff --git a/content/browser/download/download_file_manager_unittest.cc b/content/browser/download/download_file_manager_unittest.cc index 15eab3a..43863c4 100644 --- a/content/browser/download/download_file_manager_unittest.cc +++ b/content/browser/download/download_file_manager_unittest.cc @@ -35,6 +35,15 @@ using ::testing::StrEq; namespace { +// MockDownloadManager with the addition of a mock callback used for testing. +class TestDownloadManager : public MockDownloadManager { + public: + MOCK_METHOD2(OnDownloadRenamed, + void(int download_id, const FilePath& full_path)); + private: + ~TestDownloadManager() {} +}; + class MockDownloadFileFactory : public DownloadFileManager::DownloadFileFactory { @@ -136,7 +145,7 @@ class DownloadFileManagerTest : public testing::Test { } virtual void SetUp() { - download_manager_ = new MockDownloadManager(); + download_manager_ = new TestDownloadManager(); request_handle_.reset(new MockDownloadRequestHandle(download_manager_)); download_file_factory_ = new MockDownloadFileFactory; download_file_manager_ = new DownloadFileManager(download_file_factory_); @@ -295,33 +304,9 @@ class DownloadFileManagerTest : public testing::Test { net::Error rename_error, RenameFileState state, RenameFileOverwrite should_overwrite) { - // Expected call sequence: - // RenameInProgressDownloadFile/RenameCompletingDownloadFile - // GetDownloadFile - // DownloadFile::Rename - // - // On Error: - // CancelDownloadOnRename - // GetDownloadFile - // DownloadFile::GetDownloadManager - // No Manager: - // DownloadFile::CancelDownloadRequest/return - // Has Manager: - // DownloadFile::BytesSoFar - // Process one message in the message loop - // DownloadManager::OnDownloadInterrupted - // - // On Success, if final rename: - // Process one message in the message loop - // DownloadManager::OnDownloadRenamedToFinalName MockDownloadFile* file = download_file_factory_->GetExistingFile(id); ASSERT_TRUE(file != NULL); - if (state == COMPLETE || rename_error != net::OK) { - EXPECT_CALL(*file, GetDownloadManager()) - .Times(AtLeast(1)); - } - EXPECT_CALL(*file, Rename(unique_path)) .Times(1) .WillOnce(Return(rename_error)); @@ -332,13 +317,24 @@ class DownloadFileManagerTest : public testing::Test { .WillRepeatedly(Return(byte_count_[id])); EXPECT_CALL(*file, GetHashState()) .Times(AtLeast(1)); + EXPECT_CALL(*file, GetDownloadManager()) + .Times(AtLeast(1)); + } else if (state == COMPLETE) { +#if defined(OS_MACOSX) + EXPECT_CALL(*file, AnnotateWithSourceInformation()); +#endif } if (state == IN_PROGRESS) { - download_file_manager_->RenameInProgressDownloadFile(id, new_path); + download_file_manager_->RenameInProgressDownloadFile( + id, new_path, (should_overwrite == OVERWRITE), + base::Bind(&TestDownloadManager::OnDownloadRenamed, + download_manager_, id.local())); } else { // state == COMPLETE download_file_manager_->RenameCompletingDownloadFile( - id, new_path, (should_overwrite == OVERWRITE)); + id, new_path, (should_overwrite == OVERWRITE), + base::Bind(&TestDownloadManager::OnDownloadRenamed, + download_manager_, id.local())); } if (rename_error != net::OK) { @@ -350,11 +346,13 @@ class DownloadFileManagerTest : public testing::Test { content::ConvertNetErrorToInterruptReason( rename_error, content::DOWNLOAD_INTERRUPT_FROM_DISK))); + EXPECT_CALL(*download_manager_, + OnDownloadRenamed(id.local(), FilePath())); ProcessAllPendingMessages(); ++error_count_[id]; - } else if (state == COMPLETE) { - EXPECT_CALL(*download_manager_, OnDownloadRenamedToFinalName( - id.local(), unique_path, _)); + } else { + EXPECT_CALL(*download_manager_, + OnDownloadRenamed(id.local(), unique_path)); ProcessAllPendingMessages(); } } @@ -438,7 +436,7 @@ class DownloadFileManagerTest : public testing::Test { } protected: - scoped_refptr<MockDownloadManager> download_manager_; + scoped_refptr<TestDownloadManager> download_manager_; scoped_ptr<MockDownloadRequestHandle> request_handle_; MockDownloadFileFactory* download_file_factory_; scoped_refptr<DownloadFileManager> download_file_manager_; @@ -560,6 +558,28 @@ TEST_F(DownloadFileManagerTest, RenameInProgress) { CleanUp(dummy_id); } +TEST_F(DownloadFileManagerTest, RenameInProgressWithUniquification) { + // Same as StartDownload, at first. + DownloadCreateInfo* info = new DownloadCreateInfo; + DownloadId dummy_id(download_manager_.get(), kDummyDownloadId); + ScopedTempDir download_dir; + ASSERT_TRUE(download_dir.CreateUniqueTempDir()); + + StartDownload(info, dummy_id); + + UpdateDownload(dummy_id, kTestData1, strlen(kTestData1), net::OK); + UpdateDownload(dummy_id, kTestData2, strlen(kTestData2), net::OK); + FilePath foo(download_dir.path().Append(FILE_PATH_LITERAL("foo.txt"))); + FilePath unique_foo(foo.InsertBeforeExtension(FILE_PATH_LITERAL(" (1)"))); + ASSERT_EQ(0, file_util::WriteFile(foo, "", 0)); + RenameFile(dummy_id, foo, unique_foo, net::OK, IN_PROGRESS, DONT_OVERWRITE); + UpdateDownload(dummy_id, kTestData3, strlen(kTestData3), net::OK); + + OnResponseCompleted(dummy_id, content::DOWNLOAD_INTERRUPT_REASON_NONE, ""); + + CleanUp(dummy_id); +} + TEST_F(DownloadFileManagerTest, RenameInProgressWithError) { // Same as StartDownload, at first. DownloadCreateInfo* info = new DownloadCreateInfo; diff --git a/content/browser/download/download_item_impl.cc b/content/browser/download/download_item_impl.cc index b931744..2f967a8 100644 --- a/content/browser/download/download_item_impl.cc +++ b/content/browser/download/download_item_impl.cc @@ -164,15 +164,20 @@ DownloadItemImpl::DownloadItemImpl(Delegate* delegate, const DownloadPersistentStoreInfo& info, const net::BoundNetLog& bound_net_log) : download_id_(download_id), - full_path_(info.path), + current_path_(info.path), + target_path_(info.path), + target_disposition_(TARGET_DISPOSITION_OVERWRITE), url_chain_(1, info.url), referrer_url_(info.referrer_url), + transition_type_(content::PAGE_TRANSITION_LINK), + has_user_gesture_(false), total_bytes_(info.total_bytes), received_bytes_(info.received_bytes), bytes_per_sec_(0), last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE), start_tick_(base::TimeTicks()), state_(static_cast<DownloadState>(info.state)), + danger_type_(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), start_time_(info.start_time), end_time_(info.end_time), db_handle_(info.db_handle), @@ -189,7 +194,8 @@ DownloadItemImpl::DownloadItemImpl(Delegate* delegate, opened_(info.opened), open_enabled_(true), delegate_delayed_complete_(false), - bound_net_log_(bound_net_log) { + bound_net_log_(bound_net_log), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { delegate_->Attach(); if (IsInProgress()) state_ = CANCELLED; @@ -206,14 +212,17 @@ DownloadItemImpl::DownloadItemImpl( DownloadRequestHandleInterface* request_handle, bool is_otr, const net::BoundNetLog& bound_net_log) - : state_info_(info.save_info.file_path, - info.has_user_gesture, info.transition_type, - info.prompt_user_for_save_location), - request_handle_(request_handle), + : request_handle_(request_handle), download_id_(info.download_id), + target_disposition_( + (info.prompt_user_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), @@ -225,6 +234,7 @@ DownloadItemImpl::DownloadItemImpl( last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE), start_tick_(base::TimeTicks::Now()), state_(IN_PROGRESS), + danger_type_(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), start_time_(info.start_time), db_handle_(DownloadItem::kUninitializedHandle), delegate_(delegate), @@ -240,7 +250,8 @@ DownloadItemImpl::DownloadItemImpl( opened_(false), open_enabled_(true), delegate_delayed_complete_(false), - bound_net_log_(bound_net_log) { + bound_net_log_(bound_net_log), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { delegate_->Attach(); Init(true /* actively downloading */, download_net_logs::SRC_NEW_DOWNLOAD); @@ -269,9 +280,13 @@ DownloadItemImpl::DownloadItemImpl(Delegate* delegate, const net::BoundNetLog& bound_net_log) : request_handle_(new NullDownloadRequestHandle()), download_id_(download_id), - full_path_(path), + current_path_(path), + target_path_(path), + target_disposition_(TARGET_DISPOSITION_OVERWRITE), url_chain_(1, url), referrer_url_(GURL()), + transition_type_(content::PAGE_TRANSITION_LINK), + has_user_gesture_(false), mime_type_(mime_type), original_mime_type_(mime_type), total_bytes_(0), @@ -280,6 +295,7 @@ DownloadItemImpl::DownloadItemImpl(Delegate* delegate, last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE), start_tick_(base::TimeTicks::Now()), state_(IN_PROGRESS), + danger_type_(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), start_time_(base::Time::Now()), db_handle_(DownloadItem::kUninitializedHandle), delegate_(delegate), @@ -295,7 +311,8 @@ DownloadItemImpl::DownloadItemImpl(Delegate* delegate, opened_(false), open_enabled_(true), delegate_delayed_complete_(false), - bound_net_log_(bound_net_log) { + bound_net_log_(bound_net_log), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { delegate_->Attach(); Init(true /* actively downloading */, download_net_logs::SRC_SAVE_PAGE_AS); @@ -576,26 +593,35 @@ void DownloadItemImpl::TransitionTo(DownloadState new_state) { bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE, NULL); } -void DownloadItemImpl::UpdateSafetyState() { - SafetyState updated_value = state_info_.IsDangerous() ? - DownloadItem::DANGEROUS : DownloadItem::SAFE; +void DownloadItemImpl::SetDangerType(content::DownloadDangerType danger_type) { + danger_type_ = danger_type; + // Notify observers if the safety state has changed as a result of the new + // danger type. + SafetyState updated_value = IsDangerous() ? + DownloadItem::DANGEROUS : DownloadItem::SAFE; if (updated_value != safety_state_) { safety_state_ = updated_value; - bound_net_log_.AddEvent( net::NetLog::TYPE_DOWNLOAD_ITEM_SAFETY_STATE_UPDATED, make_scoped_refptr(new download_net_logs::ItemCheckedParameters( GetDangerType(), GetSafetyState()))); - UpdateObservers(); } } -void DownloadItemImpl::UpdateTarget() { +void DownloadItemImpl::SetFullPath(const FilePath& new_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + VLOG(20) << __FUNCTION__ << "()" + << " new_path = \"" << new_path.value() << "\"" + << " " << DebugString(true); + DCHECK(!new_path.empty()); + current_path_ = new_path; - if (state_info_.target_name.value().empty()) - state_info_.target_name = full_path_.BaseName(); + bound_net_log_.AddEvent( + net::NetLog::TYPE_DOWNLOAD_ITEM_RENAMED, + make_scoped_refptr( + new download_net_logs::ItemRenamedParameters( + current_path_.AsUTF8Unsafe(), new_path.AsUTF8Unsafe()))); } void DownloadItemImpl::Interrupted(int64 size, @@ -632,8 +658,10 @@ void DownloadItemImpl::Delete(DeleteReason reason) { NOTREACHED(); } - BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, - base::Bind(&DeleteDownloadedFile, full_path_)); + // TODO(asanka): Avoid deleting a file that is still owned by DownloadFile. + if (!current_path_.empty()) + BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + base::Bind(&DeleteDownloadedFile, current_path_)); Remove(); // We have now been deleted. } @@ -678,32 +706,6 @@ int DownloadItemImpl::PercentComplete() const { return static_cast<int>(received_bytes_ * 100.0 / total_bytes_); } -void DownloadItemImpl::OnPathDetermined(const FilePath& path) { - full_path_ = path; - // If we prompted the user, then target_name is stale. Allow it to be - // populated by UpdateTarget(). - if (PromptUserForSaveLocation()) - state_info_.target_name.clear(); - UpdateTarget(); -} - -void DownloadItemImpl::Rename(const FilePath& full_path) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - VLOG(20) << __FUNCTION__ << "()" - << " full_path = \"" << full_path.value() << "\"" - << " " << DebugString(true); - DCHECK(!full_path.empty()); - - bound_net_log_.AddEvent( - net::NetLog::TYPE_DOWNLOAD_ITEM_RENAMED, - make_scoped_refptr( - new download_net_logs::ItemRenamedParameters( - full_path_.AsUTF8Unsafe(), full_path.AsUTF8Unsafe()))); - - full_path_ = full_path; -} - void DownloadItemImpl::TogglePause() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -726,29 +728,32 @@ void DownloadItemImpl::OnDownloadCompleting(DownloadFileManager* file_manager) { DCHECK_NE(DANGEROUS, GetSafetyState()); DCHECK(file_manager); - // If we prompted the user for save location, then we should overwrite the - // target. Otherwise, if the danger state was NOT_DANGEROUS, we already - // uniquified the path and should overwrite. - bool should_overwrite = - (PromptUserForSaveLocation() || - GetDangerType() == content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + // TODO(asanka): Reduce code duplication across the NeedsRename() and + // !NeedsRename() completion pathways. if (NeedsRename()) { - BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, + bool should_overwrite = + (GetTargetDisposition() != DownloadItem::TARGET_DISPOSITION_UNIQUIFY); + DownloadFileManager::RenameCompletionCallback callback = + base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(file_manager)); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, base::Bind(&DownloadFileManager::RenameCompletingDownloadFile, - file_manager, download_id_, - GetTargetFilePath(), should_overwrite)); - return; + file_manager, GetGlobalId(), GetTargetFilePath(), + should_overwrite, callback)); + } else { + Completed(); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DownloadFileManager::CompleteDownload, + file_manager, download_id_)); } - - Completed(); - - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - base::Bind(&DownloadFileManager::CompleteDownload, - file_manager, download_id_)); } -void DownloadItemImpl::OnDownloadRenamedToFinalName(const FilePath& full_path) { +void DownloadItemImpl::OnDownloadRenamedToFinalName( + DownloadFileManager* file_manager, + const FilePath& full_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); VLOG(20) << __FUNCTION__ << "()" @@ -757,13 +762,32 @@ void DownloadItemImpl::OnDownloadRenamedToFinalName(const FilePath& full_path) { << " " << DebugString(false); DCHECK(NeedsRename()); - Rename(full_path); + if (!full_path.empty()) { + // full_path is now the current and target file path. + target_path_ = full_path; + SetFullPath(full_path); + delegate_->DownloadRenamedToFinalName(this); + + if (delegate_->ShouldOpenDownload(this)) + Completed(); + else + delegate_delayed_complete_ = true; + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DownloadFileManager::CompleteDownload, + file_manager, GetGlobalId())); + } +} - if (delegate_->ShouldOpenDownload(this)) { - Completed(); - } else { - delegate_delayed_complete_ = true; +void DownloadItemImpl::OnDownloadRenamedToIntermediateName( + const FilePath& full_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!full_path.empty()) { + SetFullPath(full_path); + UpdateObservers(); } + delegate_->DownloadRenamedToIntermediateName(this); } bool DownloadItemImpl::MatchesQuery(const string16& query) const { @@ -788,35 +812,33 @@ bool DownloadItemImpl::MatchesQuery(const string16& query) const { if (base::i18n::StringSearchIgnoringCaseAndAccents(query, url_formatted)) return true; + // TODO(asanka): Change this to GetTargetFilePath() once DownloadQuery has + // been modified to work with target paths. string16 path(GetFullPath().LossyDisplayName()); return base::i18n::StringSearchIgnoringCaseAndAccents(query, path); } -void DownloadItemImpl::SetFileCheckResults(const DownloadStateInfo& state) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true); - state_info_ = state; - VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true); - - UpdateSafetyState(); -} - content::DownloadDangerType DownloadItemImpl::GetDangerType() const { - return state_info_.danger; -} - -void DownloadItemImpl::SetDangerType(content::DownloadDangerType danger_type) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - state_info_.danger = danger_type; - UpdateSafetyState(); + return danger_type_; } +// TODO(asanka): Unify GetSafetyState() and IsDangerous(). bool DownloadItemImpl::IsDangerous() const { - return state_info_.IsDangerous(); + // TODO(noelutz): At this point only the windows views UI supports + // warnings based on dangerous content. +#ifdef OS_WIN + return (danger_type_ == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || + danger_type_ == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || + danger_type_ == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || + danger_type_ == content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT); +#else + return (danger_type_ == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || + danger_type_ == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL); +#endif } DownloadPersistentStoreInfo DownloadItemImpl::GetPersistentStoreInfo() const { + // TODO(asanka): Persist GetTargetFilePath() as well. return DownloadPersistentStoreInfo(GetFullPath(), GetURL(), GetReferrerUrl(), @@ -843,18 +865,61 @@ content::BrowserContext* DownloadItemImpl::GetBrowserContext() const { return delegate_->GetBrowserContext(); } -FilePath DownloadItemImpl::GetTargetFilePath() const { - return full_path_.DirName().Append(state_info_.target_name); +const FilePath& DownloadItemImpl::GetTargetFilePath() const { + return target_path_; +} + +DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const { + return target_disposition_; +} + +void DownloadItemImpl::OnTargetPathDetermined( + const FilePath& target_path, + TargetDisposition disposition, + content::DownloadDangerType danger_type) { + // TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + target_path_ = target_path; + target_disposition_ = disposition; + SetDangerType(danger_type); +} + +void DownloadItemImpl::OnTargetPathSelected(const FilePath& target_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK_EQ(TARGET_DISPOSITION_PROMPT, target_disposition_); + target_path_ = target_path; +} + +void DownloadItemImpl::OnContentCheckCompleted( + content::DownloadDangerType danger_type) { + // TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(AllDataSaved()); + SetDangerType(danger_type); +} + +void DownloadItemImpl::OnIntermediatePathDetermined( + DownloadFileManager* file_manager, + const FilePath& intermediate_path, + bool ok_to_overwrite) { + DownloadFileManager::RenameCompletionCallback callback = + base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName, + weak_ptr_factory_.GetWeakPtr()); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&DownloadFileManager::RenameInProgressDownloadFile, + file_manager, GetGlobalId(), intermediate_path, + ok_to_overwrite, callback)); +} + +const FilePath& DownloadItemImpl::GetFullPath() const { + return current_path_; } FilePath DownloadItemImpl::GetFileNameToReportUser() const { if (!display_name_.empty()) return display_name_; - if (state_info_.path_uniquifier > 0) { - return state_info_.target_name.InsertBeforeExtensionASCII( - StringPrintf(" (%d)", state_info_.path_uniquifier)); - } - return state_info_.target_name; + return target_path_.BaseName(); } void DownloadItemImpl::SetDisplayName(const FilePath& name) { @@ -863,7 +928,7 @@ void DownloadItemImpl::SetDisplayName(const FilePath& name) { FilePath DownloadItemImpl::GetUserVerifiedFilePath() const { return (safety_state_ == DownloadItem::SAFE) ? - GetTargetFilePath() : full_path_; + GetTargetFilePath() : GetFullPath(); } void DownloadItemImpl::OffThreadCancel(DownloadFileManager* file_manager) { @@ -880,17 +945,18 @@ void DownloadItemImpl::Init(bool active, download_net_logs::DownloadType download_type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - UpdateTarget(); if (active) download_stats::RecordDownloadCount(download_stats::START_COUNT); + if (target_path_.empty()) + target_path_ = current_path_; std::string file_name; if (download_type == download_net_logs::SRC_HISTORY_IMPORT) { - // full_path_ works for History and Save As versions. - file_name = full_path_.AsUTF8Unsafe(); + // target_path_ works for History and Save As versions. + file_name = target_path_.AsUTF8Unsafe(); } else { // See if it's set programmatically. - file_name = state_info_.force_file_name.AsUTF8Unsafe(); + file_name = forced_file_path_.AsUTF8Unsafe(); // Possibly has a 'download' attribute for the anchor. if (file_name.empty()) file_name = suggested_filename_; @@ -924,6 +990,11 @@ void DownloadItemImpl::Init(bool active, VLOG(20) << __FUNCTION__ << "() " << DebugString(true); } +bool DownloadItemImpl::NeedsRename() const { + DCHECK(target_path_.DirName() == current_path_.DirName()); + return target_path_ != current_path_; +} + // TODO(ahendrickson) -- Move |INTERRUPTED| from |IsCancelled()| to // |IsPartialDownload()|, when resuming interrupted downloads is implemented. bool DownloadItemImpl::IsPartialDownload() const { @@ -985,8 +1056,8 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { " last_modified = '%s'" " etag = '%s'" " url_chain = \n\t\"%s\"\n\t" - " target = \"%" PRFilePath "\"" - " full_path = \"%" PRFilePath "\"", + " full_path = \"%" PRFilePath "\"" + " target_path = \"%" PRFilePath "\"", GetDbHandle(), GetTotalBytes(), GetReceivedBytes(), @@ -997,8 +1068,8 @@ std::string DownloadItemImpl::DebugString(bool verbose) const { GetLastModifiedTime().c_str(), GetETag().c_str(), url_list.c_str(), - state_info_.target_name.value().c_str(), - GetFullPath().value().c_str()); + GetFullPath().value().c_str(), + GetTargetFilePath().value().c_str()); } else { description += base::StringPrintf(" url = \"%s\"", url_list.c_str()); } @@ -1012,10 +1083,6 @@ bool DownloadItemImpl::AllDataSaved() const { return all_data_saved_; } DownloadItem::DownloadState DownloadItemImpl::GetState() const { return state_; } -const FilePath& DownloadItemImpl::GetFullPath() const { return full_path_; } -void DownloadItemImpl::SetPathUniquifier(int uniquifier) { - state_info_.path_uniquifier = uniquifier; -} const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const { return url_chain_; } @@ -1086,15 +1153,20 @@ DownloadItem::SafetyState DownloadItemImpl::GetSafetyState() const { } bool DownloadItemImpl::IsOtr() const { return is_otr_; } bool DownloadItemImpl::GetAutoOpened() { return auto_opened_; } -const FilePath& DownloadItemImpl::GetTargetName() const { - return state_info_.target_name; -} -bool DownloadItemImpl::PromptUserForSaveLocation() const { - return state_info_.prompt_user_for_save_location; +FilePath DownloadItemImpl::GetTargetName() const { + return target_path_.BaseName(); } -const FilePath& DownloadItemImpl::GetSuggestedPath() const { - return state_info_.suggested_path; +const 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_; } +bool DownloadItemImpl::HasUserGesture() const { + return has_user_gesture_; +}; +content::PageTransition DownloadItemImpl::GetTransitionType() const { + return transition_type_; +}; bool DownloadItemImpl::IsTemporary() const { return is_temporary_; } void DownloadItemImpl::SetIsTemporary(bool temporary) { is_temporary_ = temporary; @@ -1108,10 +1180,6 @@ const std::string& DownloadItemImpl::GetETag() const { return etag_; } content::DownloadInterruptReason DownloadItemImpl::GetLastReason() const { return last_reason_; } -DownloadStateInfo DownloadItemImpl::GetStateInfo() const { return state_info_; } -bool DownloadItemImpl::NeedsRename() const { - return state_info_.target_name != full_path_.BaseName(); -} void DownloadItemImpl::MockDownloadOpenForTesting() { open_enabled_ = false; } DownloadItem::ExternalData* diff --git a/content/browser/download/download_item_impl.h b/content/browser/download/download_item_impl.h index 1bcdcd4..2b1d7c8 100644 --- a/content/browser/download/download_item_impl.h +++ b/content/browser/download/download_item_impl.h @@ -11,6 +11,7 @@ #include "base/basictypes.h" #include "base/file_path.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" #include "base/observer_list.h" #include "base/time.h" #include "base/timer.h" @@ -69,6 +70,8 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { virtual void DownloadCompleted(DownloadItem* download) = 0; virtual void DownloadOpened(DownloadItem* download) = 0; virtual void DownloadRemoved(DownloadItem* download) = 0; + virtual void DownloadRenamedToIntermediateName(DownloadItem* download) = 0; + virtual void DownloadRenamedToFinalName(DownloadItem* download) = 0; // Assert consistent state for delgate object at various transitions. virtual void AssertStateConsistent(DownloadItem* download) const = 0; @@ -137,13 +140,9 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { virtual bool TimeRemaining(base::TimeDelta* remaining) const OVERRIDE; virtual int64 CurrentSpeed() const OVERRIDE; virtual int PercentComplete() const OVERRIDE; - virtual void OnPathDetermined(const FilePath& path) OVERRIDE; virtual bool AllDataSaved() const OVERRIDE; - virtual void SetFileCheckResults(const DownloadStateInfo& state) OVERRIDE; - virtual void Rename(const FilePath& full_path) OVERRIDE; virtual void TogglePause() OVERRIDE; virtual void OnDownloadCompleting(DownloadFileManager* file_manager) OVERRIDE; - virtual void OnDownloadRenamedToFinalName(const FilePath& full_path) OVERRIDE; virtual bool MatchesQuery(const string16& query) const OVERRIDE; virtual bool IsPartialDownload() const OVERRIDE; virtual bool IsInProgress() const OVERRIDE; @@ -152,7 +151,18 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { virtual bool IsComplete() const OVERRIDE; virtual DownloadState GetState() const OVERRIDE; virtual const FilePath& GetFullPath() const OVERRIDE; - virtual void SetPathUniquifier(int uniquifier) OVERRIDE; + virtual const FilePath& GetTargetFilePath() const OVERRIDE; + virtual TargetDisposition GetTargetDisposition() const OVERRIDE; + virtual void OnTargetPathDetermined( + const FilePath& target_path, + TargetDisposition disposition, + content::DownloadDangerType danger_type) OVERRIDE; + virtual void OnTargetPathSelected(const FilePath& target_path) OVERRIDE; + virtual void OnContentCheckCompleted( + content::DownloadDangerType danger_type) OVERRIDE; + virtual void OnIntermediatePathDetermined(DownloadFileManager* file_manager, + const FilePath& path, + bool ok_to_overwrite) OVERRIDE; virtual const GURL& GetURL() const OVERRIDE; virtual const std::vector<GURL>& GetUrlChain() const OVERRIDE; virtual const GURL& GetOriginalUrl() const OVERRIDE; @@ -182,13 +192,13 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { virtual bool GetFileExternallyRemoved() const OVERRIDE; virtual SafetyState GetSafetyState() const OVERRIDE; virtual content::DownloadDangerType GetDangerType() const OVERRIDE; - virtual void SetDangerType(content::DownloadDangerType danger_type) OVERRIDE; virtual bool IsDangerous() const OVERRIDE; virtual bool GetAutoOpened() OVERRIDE; - virtual const FilePath& GetTargetName() const OVERRIDE; - virtual bool PromptUserForSaveLocation() const OVERRIDE; + virtual FilePath GetTargetName() const OVERRIDE; + virtual const FilePath& GetForcedFilePath() const OVERRIDE; + virtual bool HasUserGesture() const OVERRIDE; + virtual content::PageTransition GetTransitionType() const OVERRIDE; virtual bool IsOtr() const OVERRIDE; - virtual const FilePath& GetSuggestedPath() const OVERRIDE; virtual bool IsTemporary() const OVERRIDE; virtual void SetIsTemporary(bool temporary) OVERRIDE; virtual void SetOpened(bool opened) OVERRIDE; @@ -198,14 +208,11 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { virtual content::DownloadInterruptReason GetLastReason() const OVERRIDE; virtual content::DownloadPersistentStoreInfo GetPersistentStoreInfo() const OVERRIDE; - virtual DownloadStateInfo GetStateInfo() const OVERRIDE; virtual content::BrowserContext* GetBrowserContext() const OVERRIDE; virtual content::WebContents* GetWebContents() const OVERRIDE; - virtual FilePath GetTargetFilePath() const OVERRIDE; virtual FilePath GetFileNameToReportUser() const OVERRIDE; virtual void SetDisplayName(const FilePath& name) OVERRIDE; virtual FilePath GetUserVerifiedFilePath() const OVERRIDE; - virtual bool NeedsRename() const OVERRIDE; virtual void OffThreadCancel(DownloadFileManager* file_manager) OVERRIDE; virtual std::string DebugString(bool verbose) const OVERRIDE; virtual void MockDownloadOpenForTesting() OVERRIDE; @@ -220,6 +227,10 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { // this is. void Init(bool active, download_net_logs::DownloadType download_type); + // Returns true if the download still needs to be renamed to + // GetTargetFilePath(). + bool NeedsRename() const; + // Internal helper for maintaining consistent received and total sizes, and // hash state. void UpdateProgress(int64 bytes_so_far, const std::string& hash_state); @@ -242,20 +253,19 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { // Call to transition state; all state transitions should go through this. void TransitionTo(DownloadState new_state); - // Called when safety_state_ should be recomputed from is_dangerous_file - // and is_dangerous_url. - void UpdateSafetyState(); + // Set the |danger_type_| and invoke obserers if necessary. + void SetDangerType(content::DownloadDangerType danger_type); - // Helper function to recompute |state_info_.target_name| when - // it may have changed. (If it's non-null it should be left alone, - // otherwise updated from |full_path_|.) - void UpdateTarget(); + // Set the |current_path_| to |new_path|. + void SetFullPath(const FilePath& new_path); - // State information used by the download manager. - DownloadStateInfo state_info_; + // Callback invoked when the download has been renamed to its final name. + void OnDownloadRenamedToFinalName(DownloadFileManager* file_manager, + const FilePath& full_path); - // Display name for the download if different from the target filename. - FilePath display_name_; + // Callback invoked when the download has been renamed to its intermediate + // name. + void OnDownloadRenamedToIntermediateName(const FilePath& full_path); // The handle to the request information. Used for operations outside the // download system. @@ -264,8 +274,23 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { // Download ID assigned by DownloadResourceHandler. content::DownloadId download_id_; - // Full path to the downloaded or downloading file. - FilePath full_path_; + // Display name for the download. If this is empty, then the display name is + // considered to be |target_path_.BaseName()|. + FilePath display_name_; + + // Full path to the downloaded or downloading file. This is the path to the + // physical file, if one exists. The final target path is specified by + // |target_path_|. |current_path_| can be empty if the in-progress path hasn't + // been determined. + FilePath current_path_; + + // Target path of an in-progress download. We may be downloading to a + // temporary or intermediate file (specified by |current_path_|. Once the + // download completes, we will rename the file to |target_path_|. + FilePath target_path_; + + // Whether the target should be overwritten, uniquified or prompted for. + TargetDisposition target_disposition_; // The chain of redirects that leading up to and including the final URL. std::vector<GURL> url_chain_; @@ -278,6 +303,16 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { // http://www.whatwg.org/specs/web-apps/current-work/#downloading-hyperlinks std::string suggested_filename_; + // If non-empty, contains an externally supplied path that should be used as + // the target path. + FilePath forced_file_path_; + + // Page transition that triggerred the download. + content::PageTransition transition_type_; + + // Whether the download was triggered with a user gesture. + bool has_user_gesture_; + // Information from the request. // Content-disposition field from the header. std::string content_disposition_; @@ -331,6 +366,9 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { // The current state of this download. DownloadState state_; + // Current danger type for the download. + content::DownloadDangerType danger_type_; + // The views of this item in the download shelf and download contents. ObserverList<Observer> observers_; @@ -394,6 +432,8 @@ class CONTENT_EXPORT DownloadItemImpl : public content::DownloadItem { // Net log to use for this download. const net::BoundNetLog bound_net_log_; + base::WeakPtrFactory<DownloadItemImpl> weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(DownloadItemImpl); }; diff --git a/content/browser/download/download_item_impl_unittest.cc b/content/browser/download/download_item_impl_unittest.cc index d4c62a0..0700f88 100644 --- a/content/browser/download/download_item_impl_unittest.cc +++ b/content/browser/download/download_item_impl_unittest.cc @@ -6,6 +6,7 @@ #include "base/stl_util.h" #include "base/threading/thread.h" #include "content/browser/download/download_create_info.h" +#include "content/browser/download/download_file_manager.h" #include "content/browser/download/download_item_impl.h" #include "content/browser/download/download_request_handle.h" #include "content/public/browser/download_id.h" @@ -21,6 +22,9 @@ using content::DownloadItem; using content::DownloadManager; using content::MockDownloadItem; using content::WebContents; +using ::testing::_; +using ::testing::AllOf; +using ::testing::Property; DownloadId::Domain kValidDownloadItemIdDomain = "valid DownloadId::Domain"; @@ -36,6 +40,8 @@ class MockDelegate : public DownloadItemImpl::Delegate { MOCK_METHOD1(DownloadCompleted, void(DownloadItem* download)); MOCK_METHOD1(DownloadOpened, void(DownloadItem* download)); MOCK_METHOD1(DownloadRemoved, void(DownloadItem* download)); + MOCK_METHOD1(DownloadRenamedToIntermediateName, void(DownloadItem* download)); + MOCK_METHOD1(DownloadRenamedToFinalName, void(DownloadItem* download)); MOCK_CONST_METHOD1(AssertStateConsistent, void(DownloadItem* download)); }; @@ -49,8 +55,58 @@ class MockRequestHandle : public DownloadRequestHandleInterface { MOCK_CONST_METHOD0(DebugString, std::string()); }; +class MockDownloadFileFactory + : public DownloadFileManager::DownloadFileFactory { + public: + MOCK_METHOD5(CreateFile, + content::DownloadFile*(DownloadCreateInfo*, + const DownloadRequestHandle&, + DownloadManager*, + bool, + const net::BoundNetLog&)); +}; + +class MockDownloadFileManager : public DownloadFileManager { + public: + MockDownloadFileManager(); + MOCK_METHOD0(Shutdown, void()); + MOCK_METHOD2(StartDownload, + void(DownloadCreateInfo*, const DownloadRequestHandle&)); + MOCK_METHOD2(UpdateDownload, void(DownloadId, content::DownloadBuffer*)); + MOCK_METHOD3(OnResponseCompleted, + void(DownloadId, content::DownloadInterruptReason, + const std::string&)); + MOCK_METHOD1(CancelDownload, void(DownloadId)); + MOCK_METHOD1(CompleteDownload, void(DownloadId)); + MOCK_METHOD1(OnDownloadManagerShutdown, void(DownloadManager*)); + MOCK_METHOD4(RenameInProgressDownloadFile, + void(DownloadId, const FilePath&, bool, + const RenameCompletionCallback&)); + MOCK_METHOD4(RenameCompletingDownloadFile, + void(DownloadId, const FilePath&, bool, + const RenameCompletionCallback&)); + MOCK_CONST_METHOD0(NumberOfActiveDownloads, int()); + private: + ~MockDownloadFileManager() {} +}; + +// Schedules a task to invoke the RenameCompletionCallback with |new_path| on +// the UI thread. Should only be used as the action for +// MockDownloadFileManager::Rename*DownloadFile as follows: +// EXPECT_CALL(mock_download_file_manager, +// RenameInProgressDownloadFile(_,_,_,_)) +// .WillOnce(ScheduleRenameCallback(new_path)); +ACTION_P(ScheduleRenameCallback, new_path) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(arg3, new_path)); +} + +MockDownloadFileManager::MockDownloadFileManager() + : DownloadFileManager(new MockDownloadFileFactory) { } +} // namespace + class DownloadItemTest : public testing::Test { public: class MockObserver : public DownloadItem::Observer { @@ -78,7 +134,8 @@ class DownloadItemTest : public testing::Test { }; DownloadItemTest() - : ui_thread_(BrowserThread::UI, &loop_) { + : ui_thread_(BrowserThread::UI, &loop_), + file_thread_(BrowserThread::FILE, &loop_) { } ~DownloadItemTest() { @@ -125,10 +182,18 @@ class DownloadItemTest : public testing::Test { delete item; } + void RunAllPendingInMessageLoops() { + loop_.RunAllPending(); + } + + MockDelegate* mock_delegate() { + return &delegate_; + } + private: MessageLoopForUI loop_; - // UI thread. - content::TestBrowserThread ui_thread_; + content::TestBrowserThread ui_thread_; // UI thread + content::TestBrowserThread file_thread_; // FILE thread testing::NiceMock<MockDelegate> delegate_; std::set<DownloadItem*> allocated_downloads_; }; @@ -149,7 +214,6 @@ const FilePath::CharType kDummyPath[] = FILE_PATH_LITERAL("/testpath"); // void ShowDownloadInShell(); // void CompleteDelayedDownload(); // void OnDownloadCompleting(DownloadFileManager* file_manager); -// void OnDownloadRenamedToFinalName(const FilePath& full_path); // set_* mutators TEST_F(DownloadItemTest, NotificationAfterUpdate) { @@ -219,56 +283,110 @@ TEST_F(DownloadItemTest, NotificationAfterRemove) { ASSERT_TRUE(observer.CheckUpdated()); } -TEST_F(DownloadItemTest, NotificationAfterSetFileCheckResults) { - // Setting to safe should not trigger any notifications +TEST_F(DownloadItemTest, NotificationAfterOnTargetPathDetermined) { DownloadItem* safe_item = CreateDownloadItem(DownloadItem::IN_PROGRESS); MockObserver safe_observer(safe_item); - DownloadStateInfo state = safe_item->GetStateInfo();; - state.danger = content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS; - safe_item->SetFileCheckResults(state); - ASSERT_FALSE(safe_observer.CheckUpdated()); + // Calling OnTargetPathDetermined does not trigger notification if danger type + // is NOT_DANGEROUS. + safe_item->OnTargetPathDetermined( + FilePath(kDummyPath), DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + EXPECT_FALSE(safe_observer.CheckUpdated()); + + DownloadItem* dangerous_item = CreateDownloadItem(DownloadItem::IN_PROGRESS); + MockObserver dangerous_observer(dangerous_item); + + // Calling OnTargetPathDetermined does trigger notification if danger type + // anything other than NOT_DANGEROUS. + dangerous_item->OnTargetPathDetermined( + FilePath(kDummyPath), DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE); + EXPECT_TRUE(dangerous_observer.CheckUpdated()); +} - // Setting to unsafe url or unsafe file should trigger notification +TEST_F(DownloadItemTest, NotificationAfterOnTargetPathSelected) { + DownloadItem* item = CreateDownloadItem(DownloadItem::IN_PROGRESS); + MockObserver observer(item); + + item->OnTargetPathDetermined( + FilePath(kDummyPath), DownloadItem::TARGET_DISPOSITION_PROMPT, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + item->OnTargetPathSelected(FilePath(kDummyPath)); + EXPECT_FALSE(observer.CheckUpdated()); +} + +TEST_F(DownloadItemTest, NotificationAfterOnContentCheckCompleted) { + // Setting to NOT_DANGEROUS does not trigger a notification. + DownloadItem* safe_item = CreateDownloadItem(DownloadItem::IN_PROGRESS); + MockObserver safe_observer(safe_item); + + safe_item->OnTargetPathDetermined( + FilePath(kDummyPath), DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + EXPECT_FALSE(safe_observer.CheckUpdated()); + safe_item->OnAllDataSaved(1, ""); + EXPECT_TRUE(safe_observer.CheckUpdated()); + safe_item->OnContentCheckCompleted( + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + EXPECT_FALSE(safe_observer.CheckUpdated()); + + // Setting to unsafe url or unsafe file should trigger a notification. DownloadItem* unsafeurl_item = CreateDownloadItem(DownloadItem::IN_PROGRESS); MockObserver unsafeurl_observer(unsafeurl_item); - state = unsafeurl_item->GetStateInfo();; - state.danger = content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL; - unsafeurl_item->SetFileCheckResults(state); - ASSERT_TRUE(unsafeurl_observer.CheckUpdated()); + unsafeurl_item->OnTargetPathDetermined( + FilePath(kDummyPath), DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + EXPECT_FALSE(unsafeurl_observer.CheckUpdated()); + unsafeurl_item->OnAllDataSaved(1, ""); + EXPECT_TRUE(unsafeurl_observer.CheckUpdated()); + unsafeurl_item->OnContentCheckCompleted( + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL); + EXPECT_TRUE(unsafeurl_observer.CheckUpdated()); unsafeurl_item->DangerousDownloadValidated(); - ASSERT_TRUE(unsafeurl_observer.CheckUpdated()); + EXPECT_TRUE(unsafeurl_observer.CheckUpdated()); DownloadItem* unsafefile_item = CreateDownloadItem(DownloadItem::IN_PROGRESS); MockObserver unsafefile_observer(unsafefile_item); - state = unsafefile_item->GetStateInfo();; - state.danger = content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE; - unsafefile_item->SetFileCheckResults(state); - ASSERT_TRUE(unsafefile_observer.CheckUpdated()); + unsafefile_item->OnTargetPathDetermined( + FilePath(kDummyPath), DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + EXPECT_FALSE(unsafefile_observer.CheckUpdated()); + unsafefile_item->OnAllDataSaved(1, ""); + EXPECT_TRUE(unsafefile_observer.CheckUpdated()); + unsafefile_item->OnContentCheckCompleted( + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE); + EXPECT_TRUE(unsafefile_observer.CheckUpdated()); unsafefile_item->DangerousDownloadValidated(); - ASSERT_TRUE(unsafefile_observer.CheckUpdated()); -} - -TEST_F(DownloadItemTest, NotificationAfterOnPathDetermined) { - DownloadItem* item = CreateDownloadItem(DownloadItem::IN_PROGRESS); - MockObserver observer(item); - - // Calling OnPathDetermined does not trigger notification - item->OnPathDetermined(FilePath(kDummyPath)); - ASSERT_FALSE(observer.CheckUpdated()); + EXPECT_TRUE(unsafefile_observer.CheckUpdated()); } -TEST_F(DownloadItemTest, NotificationAfterRename) { +// DownloadItemImpl::OnIntermediatePathDetermined will schedule a task to run +// DownloadFileManager::RenameInProgressDownloadFile(). Once the rename +// completes, DownloadItemImpl receives a notification with the new file +// name. Check that observers are updated when the new filename is available and +// not before. +TEST_F(DownloadItemTest, NotificationAfterOnIntermediatePathDetermined) { DownloadItem* item = CreateDownloadItem(DownloadItem::IN_PROGRESS); MockObserver observer(item); - - // Calling Rename does not trigger notification - item->Rename(FilePath(kDummyPath)); - ASSERT_FALSE(observer.CheckUpdated()); + FilePath intermediate_path(kDummyPath); + FilePath new_intermediate_path(intermediate_path.AppendASCII("foo")); + scoped_refptr<MockDownloadFileManager> file_manager( + new MockDownloadFileManager); + EXPECT_CALL(*file_manager.get(), + RenameInProgressDownloadFile(_,intermediate_path,false,_)) + .WillOnce(ScheduleRenameCallback(new_intermediate_path)); + + item->OnIntermediatePathDetermined(file_manager.get(), intermediate_path, + false /* ok_to_overwrite */); + EXPECT_FALSE(observer.CheckUpdated()); + RunAllPendingInMessageLoops(); + EXPECT_TRUE(observer.CheckUpdated()); + EXPECT_EQ(new_intermediate_path, item->GetFullPath()); } TEST_F(DownloadItemTest, NotificationAfterTogglePause) { @@ -284,14 +402,11 @@ TEST_F(DownloadItemTest, NotificationAfterTogglePause) { TEST_F(DownloadItemTest, DisplayName) { DownloadItem* item = CreateDownloadItem(DownloadItem::IN_PROGRESS); - DownloadStateInfo info = item->GetStateInfo(); - info.target_name = FilePath(FILE_PATH_LITERAL("foo.bar")); - item->SetFileCheckResults(info); + item->OnTargetPathDetermined(FilePath(kDummyPath).AppendASCII("foo.bar"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); EXPECT_EQ(FILE_PATH_LITERAL("foo.bar"), item->GetFileNameToReportUser().value()); - item->SetPathUniquifier(1); - EXPECT_EQ(FILE_PATH_LITERAL("foo (1).bar"), - item->GetFileNameToReportUser().value()); item->SetDisplayName(FilePath(FILE_PATH_LITERAL("new.name"))); EXPECT_EQ(FILE_PATH_LITERAL("new.name"), item->GetFileNameToReportUser().value()); @@ -364,6 +479,60 @@ TEST_F(DownloadItemTest, ExternalData) { EXPECT_EQ(3, destructor_called); } +// Test that the delegate is invoked after the download file is renamed. +// Delegate::DownloadRenamedToIntermediateName() should be invoked when the +// download is renamed to the intermediate name. +// Delegate::DownloadRenamedToFinalName() should be invoked after the final +// rename. +TEST_F(DownloadItemTest, CallbackAfterRenameToIntermediateName) { + DownloadItem* item = CreateDownloadItem(DownloadItem::IN_PROGRESS); + FilePath intermediate_path(kDummyPath); + FilePath new_intermediate_path(intermediate_path.AppendASCII("foo")); + FilePath final_path(intermediate_path.AppendASCII("bar")); + scoped_refptr<MockDownloadFileManager> file_manager( + new MockDownloadFileManager); + EXPECT_CALL(*file_manager.get(), + RenameInProgressDownloadFile(item->GetGlobalId(), + intermediate_path, false, _)) + .WillOnce(ScheduleRenameCallback(new_intermediate_path)); + // DownloadItemImpl should invoke this callback on the delegate once the + // download is renamed to the intermediate name. Also check that GetFullPath() + // returns the intermediate path at the time of the call. + EXPECT_CALL(*mock_delegate(), + DownloadRenamedToIntermediateName( + AllOf(item, + Property(&DownloadItem::GetFullPath, + new_intermediate_path)))); + item->OnTargetPathDetermined(final_path, + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + item->OnIntermediatePathDetermined(file_manager.get(), intermediate_path, + false /* ok_to_overwrite */); + RunAllPendingInMessageLoops(); + // All the callbacks should have happened by now. + ::testing::Mock::VerifyAndClearExpectations(file_manager.get()); + ::testing::Mock::VerifyAndClearExpectations(mock_delegate()); + + EXPECT_CALL(*file_manager.get(), + RenameCompletingDownloadFile(item->GetGlobalId(), + final_path, true, _)) + .WillOnce(ScheduleRenameCallback(final_path)); + EXPECT_CALL(*file_manager.get(), CompleteDownload(item->GetGlobalId())); + // DownloadItemImpl should invoke this callback on the delegate after the + // final rename has completed. Also check that GetFullPath() and + // GetTargetFilePath() return the final path at the time of the call. + EXPECT_CALL(*mock_delegate(), + DownloadRenamedToFinalName( + AllOf(item, + Property(&DownloadItem::GetFullPath, final_path), + Property(&DownloadItem::GetTargetFilePath, + final_path)))); + item->OnDownloadCompleting(file_manager.get()); + RunAllPendingInMessageLoops(); + ::testing::Mock::VerifyAndClearExpectations(file_manager.get()); + ::testing::Mock::VerifyAndClearExpectations(mock_delegate()); +} + TEST(MockDownloadItem, Compiles) { MockDownloadItem mock_item; } diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc index aae7f8a..3296daa 100644 --- a/content/browser/download/download_manager_impl.cc +++ b/content/browser/download/download_manager_impl.cc @@ -259,7 +259,8 @@ void DownloadManagerImpl::GetTemporaryDownloads( for (DownloadMap::iterator it = history_downloads_.begin(); it != history_downloads_.end(); ++it) { if (it->second->IsTemporary() && - (dir_path.empty() || it->second->GetFullPath().DirName() == dir_path)) + (dir_path.empty() || + it->second->GetTargetFilePath().DirName() == dir_path)) result->push_back(it->second); } } @@ -271,7 +272,8 @@ void DownloadManagerImpl::GetAllDownloads( for (DownloadMap::iterator it = history_downloads_.begin(); it != history_downloads_.end(); ++it) { if (!it->second->IsTemporary() && - (dir_path.empty() || it->second->GetFullPath().DirName() == dir_path)) + (dir_path.empty() || + it->second->GetTargetFilePath().DirName() == dir_path)) result->push_back(it->second); } } @@ -377,29 +379,18 @@ void DownloadManagerImpl::RestartDownload(int32 download_id) { VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(true); - FilePath suggested_path = download->GetSuggestedPath(); - - if (download->PromptUserForSaveLocation()) { + if (download->GetTargetDisposition() == + DownloadItem::TARGET_DISPOSITION_PROMPT) { // We must ask the user for the place to put the download. WebContents* contents = download->GetWebContents(); - FilePath target_path; - // If |download| is a potentially dangerous file, then |suggested_path| - // contains the intermediate name instead of the final download - // filename. The latter is GetTargetName(). - if (download->GetDangerType() != - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) - target_path = suggested_path.DirName().Append(download->GetTargetName()); - else - target_path = suggested_path; - - delegate_->ChooseDownloadPath(contents, target_path, + delegate_->ChooseDownloadPath(contents, download->GetTargetFilePath(), download_id); FOR_EACH_OBSERVER(Observer, observers_, SelectFileDialogDisplayed(this, download_id)); } else { - // No prompting for download, just continue with the suggested name. - ContinueDownloadWithPath(download, suggested_path); + // No prompting for download, just continue with the current target path. + OnTargetPathAvailable(download); } } @@ -458,13 +449,9 @@ DownloadItem* DownloadManagerImpl::CreateSavePackageDownloadItem( return download; } -// For non-safe downloads with no prompting, |chosen_file| is the intermediate -// path for saving the in-progress download. The final target filename for these -// is |download->GetTargetName()|. For all other downloads (non-safe downloads -// for which we have prompted for a save location, and all safe downloads), -// |chosen_file| is the final target download path. -void DownloadManagerImpl::ContinueDownloadWithPath( - DownloadItem* download, const FilePath& chosen_file) { +// The target path for the download item is now valid. We proceed with the +// determination of an intermediate path. +void DownloadManagerImpl::OnTargetPathAvailable(DownloadItem* download) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(download); @@ -473,46 +460,24 @@ void DownloadManagerImpl::ContinueDownloadWithPath( DCHECK(ContainsKey(downloads_, download)); DCHECK(ContainsKey(active_downloads_, download_id)); - // Make sure the initial file name is set only once. - DCHECK(download->GetFullPath().empty()); - download->OnPathDetermined(chosen_file); - VLOG(20) << __FUNCTION__ << "()" << " download = " << download->DebugString(true); // Rename to intermediate name. - FilePath download_path; - if (download->GetDangerType() != - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) { - if (download->PromptUserForSaveLocation()) { - // When we prompt the user, we overwrite the FullPath with what the user - // wanted to use. Construct a file path using the previously determined - // intermediate filename and the new path. - // TODO(asanka): This can trample an in-progress download in the new - // target directory if it was using the same intermediate name. - FilePath file_name = download->GetSuggestedPath().BaseName(); - download_path = download->GetFullPath().DirName().Append(file_name); - } else { - // The download's name is already set to an intermediate name, so no need - // to override. - download_path = download->GetFullPath(); - } - } else { - // The download is a safe download. We need to rename it to its - // intermediate path. The final name after user confirmation will be set - // from DownloadItem::OnDownloadCompleting. - download_path = delegate_->GetIntermediatePath(download->GetFullPath()); - } - - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - base::Bind(&DownloadFileManager::RenameInProgressDownloadFile, - file_manager_, download->GetGlobalId(), - download_path)); - - download->Rename(download_path); - - delegate_->AddItemToPersistentStore(download); + // TODO(asanka): Skip this rename if download->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. + bool ok_to_overwrite = true; + FilePath intermediate_path = + delegate_->GetIntermediatePath(*download, &ok_to_overwrite); + // 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() == + download->GetTargetFilePath().DirName()); + download->OnIntermediatePathDetermined(file_manager_, intermediate_path, + ok_to_overwrite); } void DownloadManagerImpl::UpdateDownload(int32 download_id, @@ -650,8 +615,6 @@ void DownloadManagerImpl::MaybeCompleteDownload(DownloadItem* download) { << download->DebugString(false); delegate_->UpdateItemInPersistentStore(download); - - // Finish the download. download->OnDownloadCompleting(file_manager_); } @@ -669,38 +632,6 @@ void DownloadManagerImpl::DownloadCompleted(DownloadItem* download) { AssertStateConsistent(download); } -void DownloadManagerImpl::OnDownloadRenamedToFinalName( - int download_id, - const FilePath& full_path, - int uniquifier) { - VLOG(20) << __FUNCTION__ << "()" << " download_id = " << download_id - << " full_path = \"" << full_path.value() << "\"" - << " uniquifier = " << uniquifier; - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - DownloadItem* item = GetDownloadItem(download_id); - if (!item) - return; - - if (item->GetDangerType() == content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS || - item->PromptUserForSaveLocation()) { - DCHECK_EQ(0, uniquifier) - << "We should not uniquify user supplied filenames or safe filenames " - "that have already been uniquified."; - } - - BrowserThread::PostTask( - BrowserThread::FILE, FROM_HERE, - base::Bind(&DownloadFileManager::CompleteDownload, - file_manager_, item->GetGlobalId())); - - if (uniquifier) - item->SetPathUniquifier(uniquifier); - - item->OnDownloadRenamedToFinalName(full_path); - delegate_->UpdatePathForItemInPersistentStore(item, full_path); -} - void DownloadManagerImpl::CancelDownload(int32 download_id) { DownloadItem* download = GetActiveDownload(download_id); // A cancel at the right time could remove the download from the @@ -882,20 +813,22 @@ void DownloadManagerImpl::RemoveObserver(Observer* observer) { void DownloadManagerImpl::FileSelected(const FilePath& path, int32 download_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(!path.empty()); DownloadItem* download = GetActiveDownloadItem(download_id); if (!download) return; VLOG(20) << __FUNCTION__ << "()" << " path = \"" << path.value() << "\"" - << " download = " << download->DebugString(true); + << " download = " << download->DebugString(true); - // Retain the last directory that was picked by the user. Exclude temporary - // downloads since the path likely points at the location of a temporary file. - if (download->PromptUserForSaveLocation() && !download->IsTemporary()) + // Retain the last directory. Exclude temporary downloads since the path + // likely points at the location of a temporary file. + if (!download->IsTemporary()) last_download_path_ = path.DirName(); // Make sure the initial file name is set only once. - ContinueDownloadWithPath(download, path); + download->OnTargetPathSelected(path); + OnTargetPathAvailable(download); } void DownloadManagerImpl::FileSelectionCanceled(int32 download_id) { @@ -1174,6 +1107,23 @@ void DownloadManagerImpl::DownloadOpened(DownloadItem* download) { download_stats::RecordOpensOutstanding(num_unopened); } +void DownloadManagerImpl::DownloadRenamedToIntermediateName( + DownloadItem* download) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // If the rename failed, we receive an OnDownloadInterrupted() call before we + // receive the DownloadRenamedToIntermediateName() call. + delegate_->AddItemToPersistentStore(download); +} + +void DownloadManagerImpl::DownloadRenamedToFinalName( + DownloadItem* download) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // If the rename failed, we receive an OnDownloadInterrupted() call before we + // receive the DownloadRenamedToFinalName() call. + delegate_->UpdatePathForItemInPersistentStore(download, + download->GetFullPath()); +} + void DownloadManagerImpl::SetFileManagerForTesting( DownloadFileManager* file_manager) { file_manager_ = file_manager; diff --git a/content/browser/download/download_manager_impl.h b/content/browser/download/download_manager_impl.h index 7f267b2..ad1a027 100644 --- a/content/browser/download/download_manager_impl.h +++ b/content/browser/download/download_manager_impl.h @@ -50,9 +50,6 @@ class CONTENT_EXPORT DownloadManagerImpl int64 size, const std::string& hash_state, content::DownloadInterruptReason reason) OVERRIDE; - virtual void OnDownloadRenamedToFinalName(int download_id, - const FilePath& full_path, - int uniquifier) OVERRIDE; virtual int RemoveDownloadsBetween(base::Time remove_begin, base::Time remove_end) OVERRIDE; virtual int RemoveDownloads(base::Time remove_begin) OVERRIDE; @@ -107,6 +104,10 @@ class CONTENT_EXPORT DownloadManagerImpl virtual void DownloadOpened( content::DownloadItem* download) OVERRIDE; virtual void DownloadRemoved(content::DownloadItem* download) OVERRIDE; + virtual void DownloadRenamedToIntermediateName( + content::DownloadItem* download) OVERRIDE; + virtual void DownloadRenamedToFinalName( + content::DownloadItem* download) OVERRIDE; virtual void AssertStateConsistent( content::DownloadItem* download) const OVERRIDE; @@ -150,8 +151,7 @@ class CONTENT_EXPORT DownloadManagerImpl // Called back after a target path for the file to be downloaded to has been // determined, either automatically based on the suggested file name, or by // the user in a Save As dialog box. - void ContinueDownloadWithPath(content::DownloadItem* download, - const FilePath& chosen_file); + void OnTargetPathAvailable(content::DownloadItem* download); // Retrieves the download from the |download_id|. // Returns NULL if the download is not active. diff --git a/content/browser/download/download_manager_impl_unittest.cc b/content/browser/download/download_manager_impl_unittest.cc index 2f3bbe8..5337374 100644 --- a/content/browser/download/download_manager_impl_unittest.cc +++ b/content/browser/download/download_manager_impl_unittest.cc @@ -102,9 +102,14 @@ class TestDownloadManagerDelegate : public content::DownloadManagerDelegate { TestDownloadManagerDelegate() : mark_content_dangerous_(false), prompt_user_for_save_location_(false), + should_complete_download_(true), download_manager_(NULL) { } + void set_download_directory(const FilePath& path) { + download_directory_ = path; + } + void set_download_manager(content::DownloadManager* dm) { download_manager_ = dm; } @@ -115,22 +120,30 @@ class TestDownloadManagerDelegate : public content::DownloadManagerDelegate { virtual bool ShouldStartDownload(int32 download_id) OVERRIDE { DownloadItem* item = download_manager_->GetActiveDownloadItem(download_id); - DownloadStateInfo state = item->GetStateInfo(); FilePath path = net::GenerateFileName(item->GetURL(), item->GetContentDisposition(), item->GetReferrerCharset(), item->GetSuggestedFilename(), item->GetMimeType(), std::string()); + DownloadItem::TargetDisposition disposition = item->GetTargetDisposition(); if (!ShouldOpenFileBasedOnExtension(path) && prompt_user_for_save_location_) - state.prompt_user_for_save_location = true; - item->SetFileCheckResults(state); + disposition = DownloadItem::TARGET_DISPOSITION_PROMPT; + item->OnTargetPathDetermined(download_directory_.Append(path), + disposition, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); return true; } - virtual FilePath GetIntermediatePath( - const FilePath& suggested_path) OVERRIDE { - return GetTempDownloadPath(suggested_path); + virtual FilePath GetIntermediatePath(const DownloadItem& item, + bool* ok_to_overwrite) OVERRIDE { + if (intermediate_path_.empty()) { + *ok_to_overwrite = true; + return GetTempDownloadPath(item.GetTargetFilePath()); + } else { + *ok_to_overwrite = overwrite_intermediate_path_; + return intermediate_path_; + } } virtual void ChooseDownloadPath(WebContents* web_contents, @@ -177,38 +190,60 @@ class TestDownloadManagerDelegate : public content::DownloadManagerDelegate { mark_content_dangerous_ = dangerous; } + void SetIntermediatePath(const FilePath& intermediate_path, + bool overwrite_intermediate_path) { + intermediate_path_ = intermediate_path; + overwrite_intermediate_path_ = overwrite_intermediate_path; + } + + void SetShouldCompleteDownload(bool value) { + should_complete_download_ = value; + } + + void InvokeDownloadCompletionCallback() { + EXPECT_FALSE(download_completion_callback_.is_null()); + download_completion_callback_.Run(); + download_completion_callback_.Reset(); + } + virtual bool ShouldCompleteDownload( DownloadItem* item, - const base::Closure& complete_callback) { + const base::Closure& complete_callback) OVERRIDE { + download_completion_callback_ = complete_callback; if (mark_content_dangerous_) { CHECK(!complete_callback.is_null()); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&TestDownloadManagerDelegate::MarkContentDangerous, - base::Unretained(this), item->GetId(), complete_callback)); + base::Unretained(this), item->GetId())); mark_content_dangerous_ = false; return false; } else { - return true; + return should_complete_download_; } } private: void MarkContentDangerous( - int32 download_id, - const base::Closure& complete_callback) { + int32 download_id) { DownloadItem* item = download_manager_->GetActiveDownloadItem(download_id); if (!item) return; - item->SetDangerType(content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT); - complete_callback.Run(); + item->OnContentCheckCompleted( + content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT); + InvokeDownloadCompletionCallback(); } + FilePath download_directory_; FilePath expected_suggested_path_; FilePath file_selection_response_; + FilePath intermediate_path_; + bool overwrite_intermediate_path_; bool mark_content_dangerous_; bool prompt_user_for_save_location_; + bool should_complete_download_; DownloadManager* download_manager_; + base::Closure download_completion_callback_; DISALLOW_COPY_AND_ASSIGN(TestDownloadManagerDelegate); }; @@ -242,6 +277,15 @@ class DownloadManagerTest : public testing::Test { message_loop_.RunAllPending(); } + // Create a temporary directory as the downloads directory. + bool CreateTempDownloadsDirectory() { + if (!scoped_download_dir_.CreateUniqueTempDir()) + return false; + download_manager_delegate_->set_download_directory( + scoped_download_dir_.path()); + return true; + } + void AddDownloadToFileManager(int id, DownloadFile* download_file) { file_manager()->downloads_[DownloadId(kValidIdDomain, id)] = download_file; @@ -249,8 +293,8 @@ class DownloadManagerTest : public testing::Test { void AddMockDownloadToFileManager(int id, MockDownloadFile* download_file) { AddDownloadToFileManager(id, download_file); - ON_CALL(*download_file, GetDownloadManager()) - .WillByDefault(Return(download_manager_)); + EXPECT_CALL(*download_file, GetDownloadManager()) + .WillRepeatedly(Return(download_manager_)); } void OnResponseCompleted(int32 download_id, int64 size, @@ -263,7 +307,10 @@ class DownloadManagerTest : public testing::Test { } void ContinueDownloadWithPath(DownloadItem* download, const FilePath& path) { - download_manager_->ContinueDownloadWithPath(download, path); + download->OnTargetPathDetermined( + path, DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); + download_manager_->OnTargetPathAvailable(download); } void UpdateData(int32 id, const char* data, size_t length) { @@ -295,6 +342,12 @@ class DownloadManagerTest : public testing::Test { return download_manager_->GetActiveDownload(id); } + FilePath GetPathInDownloadsDir(const FilePath::StringType& fragment) { + DCHECK(scoped_download_dir_.IsValid()); + FilePath full_path(scoped_download_dir_.path().Append(fragment)); + return full_path.NormalizePathSeparators(); + } + protected: scoped_ptr<TestBrowserContext> browser_context; scoped_ptr<TestDownloadManagerDelegate> download_manager_delegate_; @@ -304,6 +357,7 @@ class DownloadManagerTest : public testing::Test { content::TestBrowserThread ui_thread_; content::TestBrowserThread file_thread_; scoped_refptr<content::DownloadBuffer> download_buffer_; + ScopedTempDir scoped_download_dir_; DownloadFileManager* file_manager() { if (!file_manager_) { @@ -329,9 +383,10 @@ class DownloadFileWithErrors : public DownloadFileImpl { virtual ~DownloadFileWithErrors() {} // BaseFile delegated functions. - virtual net::Error Initialize(); - virtual net::Error AppendDataToFile(const char* data, size_t data_len); - virtual net::Error Rename(const FilePath& full_path); + virtual net::Error Initialize() OVERRIDE; + virtual net::Error AppendDataToFile(const char* data, + size_t data_len) OVERRIDE; + virtual net::Error Rename(const FilePath& full_path) OVERRIDE; void set_forced_error(net::Error error) { forced_error_ = error; } void clear_forced_error() { forced_error_ = net::OK; } @@ -427,35 +482,6 @@ const struct { true, }, }; -const struct { - FilePath::StringType suggested_path; - content::DownloadDangerType danger; - bool finish_before_rename; - int expected_rename_count; -} kDownloadRenameCases[] = { - // Safe download, download finishes BEFORE file name determined. - // Renamed twice (linear path through UI). temp file does not need - // to be deleted. - { FILE_PATH_LITERAL("foo.zip"), content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - true, 2, }, - // Potentially dangerous download (e.g., file is dangerous), download finishes - // BEFORE file name determined. Needs to be renamed only once. - { FILE_PATH_LITERAL("Unconfirmed xxx.temp"), - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, true, 1, }, - { FILE_PATH_LITERAL("Unconfirmed xxx.temp"), - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, true, 1, }, - // Safe download, download finishes AFTER file name determined. - // Needs to be renamed twice. - { FILE_PATH_LITERAL("foo.zip"), content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - false, 2, }, - // Potentially dangerous download, download finishes AFTER file name - // determined. Needs to be renamed only once. - { FILE_PATH_LITERAL("Unconfirmed xxx.temp"), - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, false, 1, }, - { FILE_PATH_LITERAL("Unconfirmed xxx.temp"), - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, false, 1, }, -}; - // This is an observer that records what download IDs have opened a select // file dialog. class SelectFileObserver : public DownloadManager::Observer { @@ -510,12 +536,12 @@ class ItemObserver : public DownloadItem::Observer { private: // DownloadItem::Observer methods - virtual void OnDownloadUpdated(DownloadItem* download) { + virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE { DCHECK_EQ(tracked_, download); states_hit_ |= (1 << download->GetState()); was_updated_ = true; } - virtual void OnDownloadOpened(DownloadItem* download) { + virtual void OnDownloadOpened(DownloadItem* download) OVERRIDE{ DCHECK_EQ(tracked_, download); states_hit_ |= (1 << download->GetState()); was_opened_ = true; @@ -531,7 +557,9 @@ class ItemObserver : public DownloadItem::Observer { TEST_F(DownloadManagerTest, MAYBE_StartDownload) { content::TestBrowserThread io_thread(BrowserThread::IO, &message_loop_); + ASSERT_TRUE(CreateTempDownloadsDirectory()); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kStartDownloadCases); ++i) { + DVLOG(1) << "Test case " << i; download_manager_delegate_->set_prompt_user_for_save_location( kStartDownloadCases[i].prompt_for_download); @@ -569,373 +597,288 @@ TEST_F(DownloadManagerTest, MAYBE_StartDownload) { namespace { -enum PromptForSaveLocation { - DONT_PROMPT, - PROMPT +enum OverwriteDownloadPath { + DONT_OVERWRITE, + OVERWRITE }; -enum ValidateDangerousDownload { - DONT_VALIDATE, - VALIDATE +enum ResponseCompletionTime { + COMPLETES_BEFORE_RENAME, + COMPLETES_AFTER_RENAME }; -// Test cases to be used with DownloadFilenameTest. The paths that are used in -// test cases can contain "$dl" and "$alt" tokens which are replaced by a -// default download path, and an alternate download path in -// ExpandFilenameTestPath() below. +// Test cases to be used with DownloadFilenameTest. The paths are relative to +// the temporary downloads directory used for testing. const struct DownloadFilenameTestCase { - - // Fields to be set in DownloadStateInfo when calling SetFileCheckResults(). - const FilePath::CharType* suggested_path; - const FilePath::CharType* target_name; - PromptForSaveLocation prompt_user_for_save_location; - content::DownloadDangerType danger_type; + // DownloadItem properties prior to calling RestartDownload(). + const FilePath::CharType* target_path; + DownloadItem::TargetDisposition target_disposition; // If we receive a ChooseDownloadPath() call to prompt the user for a download - // location, |prompt_path| is the expected prompt path. The - // TestDownloadManagerDelegate will respond with |final_path|. If |final_path| - // is empty, then the file choose dialog be cancelled. - const FilePath::CharType* prompt_path; + // location, |expected_prompt_path| is the expected prompt path. The + // TestDownloadManagerDelegate will respond with |chosen_path|. If + // |chosen_path| is empty, then the file choose dialog be cancelled. + const FilePath::CharType* expected_prompt_path; + const FilePath::CharType* chosen_path; + + // Response to GetIntermediatePath(). If |intermediate_path| is empty, then a + // temporary path is constructed with + // GetTempDownloadPath(item->GetTargetFilePath()). + const FilePath::CharType* intermediate_path; + OverwriteDownloadPath overwrite_intermediate_path; + + // Determines when OnResponseCompleted() is called. If this is + // COMPLETES_BEFORE_RENAME, then the response completes before the filename is + // determines. + ResponseCompletionTime completion; // The expected intermediate path for the download. - const FilePath::CharType* intermediate_path; + const FilePath::CharType* expected_intermediate_path; // The expected final path for the download. - const FilePath::CharType* final_path; - - // If this is a dangerous download, then we will either validate the download - // or delete it depending on the value of |validate_dangerous_download|. - ValidateDangerousDownload validate_dangerous_download; + const FilePath::CharType* expected_final_path; } kDownloadFilenameTestCases[] = { + +#define TARGET FILE_PATH_LITERAL +#define INTERMEDIATE FILE_PATH_LITERAL +#define EXPECTED_PROMPT FILE_PATH_LITERAL +#define PROMPT_RESPONSE FILE_PATH_LITERAL +#define EXPECTED_INTERMEDIATE FILE_PATH_LITERAL +#define EXPECTED_FINAL FILE_PATH_LITERAL + { - // 0: A safe file is downloaded with no prompting. - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL(""), - DONT_PROMPT, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/foo.txt.temp"), - FILE_PATH_LITERAL("$dl/foo.txt"), - DONT_VALIDATE - }, - { - // 1: A safe file is downloaded with prompting. - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL(""), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL("$dl/foo.txt.temp"), - FILE_PATH_LITERAL("$dl/foo.txt"), - DONT_VALIDATE - }, - { - // 2: A safe file is downloaded. The filename is changed before the dialog - // completes. - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL(""), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL("$dl/bar.txt.temp"), - FILE_PATH_LITERAL("$dl/bar.txt"), - DONT_VALIDATE - }, - { - // 3: A safe file is downloaded. The download path is changed before the - // dialog completes. - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL(""), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - FILE_PATH_LITERAL("$dl/foo.txt"), - FILE_PATH_LITERAL("$alt/bar.txt.temp"), - FILE_PATH_LITERAL("$alt/bar.txt"), - DONT_VALIDATE - }, - { - // 4: Potentially dangerous content. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - DONT_PROMPT, - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/foo.exe"), - DONT_VALIDATE - }, - { - // 5: Potentially dangerous content. Uses "Save as." - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL("$dl/foo.exe"), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/foo.exe"), - DONT_VALIDATE + // 0: No prompting. + TARGET("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECTED_PROMPT(""), + PROMPT_RESPONSE(""), + + INTERMEDIATE("foo.txt.intermediate"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("foo.txt.intermediate"), + EXPECTED_FINAL("foo.txt"), }, { - // 6: Potentially dangerous content. Uses "Save as." The download filename - // is changed before the dialog completes. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL("$dl/foo.exe"), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/bar.exe"), - DONT_VALIDATE + // 1: With prompting. No rename. + TARGET("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECTED_PROMPT("foo.txt"), + PROMPT_RESPONSE("foo.txt"), + + INTERMEDIATE("foo.txt.intermediate"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("foo.txt.intermediate"), + EXPECTED_FINAL("foo.txt"), }, { - // 7: Potentially dangerous content. Uses "Save as." The download directory - // is changed before the dialog completes. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL("$dl/foo.exe"), - FILE_PATH_LITERAL("$alt/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$alt/bar.exe"), - DONT_VALIDATE + // 2: With prompting. Download is renamed. + TARGET("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECTED_PROMPT("foo.txt"), + PROMPT_RESPONSE("bar.txt"), + + INTERMEDIATE("bar.txt.intermediate"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("bar.txt.intermediate"), + EXPECTED_FINAL("bar.txt"), }, { - // 8: Dangerous content. Saved directly. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/foo.exe"), - VALIDATE + // 3: With prompting. Download path is changed. + TARGET("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECTED_PROMPT("foo.txt"), + PROMPT_RESPONSE("Foo/bar.txt"), + + INTERMEDIATE("Foo/bar.txt.intermediate"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("Foo/bar.txt.intermediate"), + EXPECTED_FINAL("Foo/bar.txt"), }, { - // 9: Dangerous content. Saved directly. Not validated. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - DONT_PROMPT, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL(""), - DONT_VALIDATE + // 4: No prompting. Intermediate path exists. Doesn't overwrite + // intermediate path. + TARGET("exists.png"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECTED_PROMPT(""), + PROMPT_RESPONSE(""), + + INTERMEDIATE("exists.png.temp"), + DONT_OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("exists.png (1).temp"), + EXPECTED_FINAL("exists.png"), }, { - // 10: Dangerous content. Uses "Save as." The download directory is changed - // before the dialog completes. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("foo.exe"), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, - FILE_PATH_LITERAL("$dl/foo.exe"), - FILE_PATH_LITERAL("$alt/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$alt/bar.exe"), - VALIDATE + // 5: No prompting. Intermediate path exists. Overwrites. + TARGET("exists.png"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECTED_PROMPT(""), + PROMPT_RESPONSE(""), + + INTERMEDIATE("exists.png.temp"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("exists.png.temp"), + EXPECTED_FINAL("exists.png"), }, { - // 11: A safe file is download. The target file exists, but we don't - // uniquify. Safe downloads are uniquified in ChromeDownloadManagerDelegate - // instead of DownloadManagerImpl. - FILE_PATH_LITERAL("$dl/exists.txt"), - FILE_PATH_LITERAL(""), - DONT_PROMPT, - content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/exists.txt.temp"), - FILE_PATH_LITERAL("$dl/exists.txt"), - DONT_VALIDATE + // 6: No prompting. Final path exists. Doesn't overwrite. + TARGET("exists.txt"), + DownloadItem::TARGET_DISPOSITION_UNIQUIFY, + + EXPECTED_PROMPT(""), + PROMPT_RESPONSE(""), + + INTERMEDIATE("exists.txt.temp"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("exists.txt.temp"), + EXPECTED_FINAL("exists (1).txt"), }, { - // 12: A potentially dangerous file is download. The target file exists. The - // target path is uniquified. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("exists.exe"), - DONT_PROMPT, - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/exists (1).exe"), - DONT_VALIDATE + // 7: No prompting. Final path exists. Overwrites. + TARGET("exists.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECTED_PROMPT(""), + PROMPT_RESPONSE(""), + + INTERMEDIATE("exists.txt.temp"), + OVERWRITE, + + COMPLETES_AFTER_RENAME, + EXPECTED_INTERMEDIATE("exists.txt.temp"), + EXPECTED_FINAL("exists.txt"), }, { - // 13: A dangerous file is download. The target file exists. The target path - // is uniquified. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("exists.exe"), - DONT_PROMPT, - content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL(""), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/exists (1).exe"), - VALIDATE + // 8: No prompting. Response completes before filename determination. + TARGET("foo.txt"), + DownloadItem::TARGET_DISPOSITION_OVERWRITE, + + EXPECTED_PROMPT(""), + PROMPT_RESPONSE(""), + + INTERMEDIATE("foo.txt.intermediate"), + OVERWRITE, + + COMPLETES_BEFORE_RENAME, + EXPECTED_INTERMEDIATE("foo.txt.intermediate"), + EXPECTED_FINAL("foo.txt"), }, { - // 14: A potentially dangerous file is download with prompting. The target - // file exists. The target path is not uniquified because the filename was - // given to us by the user. - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("exists.exe"), - PROMPT, - content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, - FILE_PATH_LITERAL("$dl/exists.exe"), - FILE_PATH_LITERAL("$dl/Unconfirmed xxx.download"), - FILE_PATH_LITERAL("$dl/exists.exe"), - DONT_VALIDATE + // 9: With prompting. Download path is changed. Response completes before + // filename determination. + TARGET("foo.txt"), + DownloadItem::TARGET_DISPOSITION_PROMPT, + + EXPECTED_PROMPT("foo.txt"), + PROMPT_RESPONSE("Foo/bar.txt"), + + INTERMEDIATE("Foo/bar.txt.intermediate"), + OVERWRITE, + + COMPLETES_BEFORE_RENAME, + EXPECTED_INTERMEDIATE("Foo/bar.txt.intermediate"), + EXPECTED_FINAL("Foo/bar.txt"), }, -}; -FilePath ExpandFilenameTestPath(const FilePath::CharType* template_path, - const FilePath& downloads_dir, - const FilePath& alternate_dir) { - FilePath::StringType path(template_path); - ReplaceSubstringsAfterOffset(&path, 0, FILE_PATH_LITERAL("$dl"), - downloads_dir.value()); - ReplaceSubstringsAfterOffset(&path, 0, FILE_PATH_LITERAL("$alt"), - alternate_dir.value()); - return FilePath(path).NormalizePathSeparators(); -} +#undef TARGET +#undef INTERMEDIATE +#undef EXPECTED_PROMPT +#undef PROMPT_RESPONSE +#undef EXPECTED_INTERMEDIATE +#undef EXPECTED_FINAL +}; } // namespace TEST_F(DownloadManagerTest, DownloadFilenameTest) { - ScopedTempDir scoped_dl_dir; - ASSERT_TRUE(scoped_dl_dir.CreateUniqueTempDir()); - - FilePath downloads_dir(scoped_dl_dir.path()); - FilePath alternate_dir(downloads_dir.Append(FILE_PATH_LITERAL("Foo"))); + ASSERT_TRUE(CreateTempDownloadsDirectory()); // We create a known file to test file uniquification. - file_util::WriteFile(downloads_dir.Append(FILE_PATH_LITERAL("exists.txt")), - "", 0); - file_util::WriteFile(downloads_dir.Append(FILE_PATH_LITERAL("exists.exe")), - "", 0); + ASSERT_TRUE(file_util::WriteFile( + GetPathInDownloadsDir(FILE_PATH_LITERAL("exists.txt")), "", 0) == 0); + ASSERT_TRUE(file_util::WriteFile( + GetPathInDownloadsDir(FILE_PATH_LITERAL("exists.png.temp")), "", 0) == 0); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDownloadFilenameTestCases); ++i) { + const DownloadFilenameTestCase& test_case = kDownloadFilenameTestCases[i]; scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); + + SCOPED_TRACE(testing::Message() << "Running test case " << i); info->download_id = DownloadId(kValidIdDomain, i); info->url_chain.push_back(GURL()); - MockDownloadFile* download_file(new NiceMock<MockDownloadFile>()); - FilePath suggested_path(ExpandFilenameTestPath( - kDownloadFilenameTestCases[i].suggested_path, - downloads_dir, alternate_dir)); - FilePath prompt_path(ExpandFilenameTestPath( - kDownloadFilenameTestCases[i].prompt_path, - downloads_dir, alternate_dir)); - FilePath intermediate_path(ExpandFilenameTestPath( - kDownloadFilenameTestCases[i].intermediate_path, - downloads_dir, alternate_dir)); - FilePath final_path(ExpandFilenameTestPath( - kDownloadFilenameTestCases[i].final_path, - downloads_dir, alternate_dir)); + MockDownloadFile* download_file( + new ::testing::StrictMock<MockDownloadFile>()); + FilePath target_path = GetPathInDownloadsDir(test_case.target_path); + FilePath expected_prompt_path = + GetPathInDownloadsDir(test_case.expected_prompt_path); + FilePath chosen_path = GetPathInDownloadsDir(test_case.chosen_path); + FilePath intermediate_path = + GetPathInDownloadsDir(test_case.intermediate_path); + FilePath expected_intermediate_path = + GetPathInDownloadsDir(test_case.expected_intermediate_path); + FilePath expected_final_path = + GetPathInDownloadsDir(test_case.expected_final_path); AddMockDownloadToFileManager(info->download_id.local(), download_file); - - EXPECT_CALL(*download_file, Rename(intermediate_path)) + EXPECT_CALL(*download_file, Rename(expected_intermediate_path)) .WillOnce(Return(net::OK)); - - if (!final_path.empty()) { - // If |final_path| is empty, its a signal that the download doesn't - // complete. Therefore it will only go through a single rename. - EXPECT_CALL(*download_file, Rename(final_path)) - .WillOnce(Return(net::OK)); - } + EXPECT_CALL(*download_file, Rename(expected_final_path)) + .WillOnce(Return(net::OK)); +#if defined(OS_MACOSX) + EXPECT_CALL(*download_file, AnnotateWithSourceInformation()); +#endif + EXPECT_CALL(*download_file, Detach()); download_manager_->CreateDownloadItem(info.get(), DownloadRequestHandle()); DownloadItem* download = GetActiveDownloadItem(i); ASSERT_TRUE(download != NULL); - DownloadStateInfo state = download->GetStateInfo(); - state.suggested_path = suggested_path; - state.danger = kDownloadFilenameTestCases[i].danger_type; - state.prompt_user_for_save_location = - (kDownloadFilenameTestCases[i].prompt_user_for_save_location == PROMPT); - state.target_name = FilePath(kDownloadFilenameTestCases[i].target_name); - if (state.danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT) { - // DANGEROUS_CONTENT will only be known once we have all the data. We let - // our TestDownloadManagerDelegate handle it. - state.danger = content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT; - download_manager_delegate_->SetMarkContentsDangerous(true); - } - download->SetFileCheckResults(state); + if (test_case.completion == COMPLETES_BEFORE_RENAME) + OnResponseCompleted(i, 1024, std::string("fake_hash")); + + download->OnTargetPathDetermined( + target_path, test_case.target_disposition, + content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS); download_manager_delegate_->SetFileSelectionExpectation( - prompt_path, final_path); + expected_prompt_path, chosen_path); + download_manager_delegate_->SetIntermediatePath( + intermediate_path, test_case.overwrite_intermediate_path); + download_manager_delegate_->SetShouldCompleteDownload(false); download_manager_->RestartDownload(i); message_loop_.RunAllPending(); - OnResponseCompleted(i, 1024, std::string("fake_hash")); - message_loop_.RunAllPending(); - - if (download->GetSafetyState() == DownloadItem::DANGEROUS) { - if (kDownloadFilenameTestCases[i].validate_dangerous_download == VALIDATE) - download->DangerousDownloadValidated(); - else - download->Delete(DownloadItem::DELETE_DUE_TO_USER_DISCARD); - message_loop_.RunAllPending(); - // |download| might be deleted when we get here. - } - } -} - -TEST_F(DownloadManagerTest, DownloadRenameTest) { - using ::testing::_; - using ::testing::CreateFunctor; - using ::testing::Invoke; - using ::testing::Return; - - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kDownloadRenameCases); ++i) { - // Normally, the download system takes ownership of info, and is - // responsible for deleting it. In these unit tests, however, we - // don't call the function that deletes it, so we do so ourselves. - scoped_ptr<DownloadCreateInfo> info(new DownloadCreateInfo); - info->download_id = DownloadId(kValidIdDomain, static_cast<int>(i)); - info->prompt_user_for_save_location = false; - info->url_chain.push_back(GURL()); - const FilePath new_path(kDownloadRenameCases[i].suggested_path); - - MockDownloadFile* download_file(new NiceMock<MockDownloadFile>()); - const DownloadId id = info->download_id; - ON_CALL(*download_file, GlobalId()) - .WillByDefault(ReturnRef(id)); - - AddMockDownloadToFileManager(info->download_id.local(), download_file); - - // |download_file| is owned by DownloadFileManager. - if (kDownloadRenameCases[i].expected_rename_count == 1) { - EXPECT_CALL(*download_file, Rename(new_path)) - .Times(1) - .WillOnce(Return(net::OK)); - } else { - ASSERT_EQ(2, kDownloadRenameCases[i].expected_rename_count); - FilePath temp(GetTempDownloadPath(new_path)); - EXPECT_CALL(*download_file, Rename(temp)) - .Times(1) - .WillOnce(Return(net::OK)); - EXPECT_CALL(*download_file, Rename(new_path)) - .Times(1) - .WillOnce(Return(net::OK)); - } - download_manager_->CreateDownloadItem(info.get(), DownloadRequestHandle()); - DownloadItem* download = GetActiveDownloadItem(i); - ASSERT_TRUE(download != NULL); - DownloadStateInfo state = download->GetStateInfo(); - state.danger = kDownloadRenameCases[i].danger; - download->SetFileCheckResults(state); - - if (kDownloadRenameCases[i].finish_before_rename) { + if (test_case.completion == COMPLETES_AFTER_RENAME) { OnResponseCompleted(i, 1024, std::string("fake_hash")); message_loop_.RunAllPending(); - FileSelected(new_path, i); - } else { - FileSelected(new_path, i); - message_loop_.RunAllPending(); - OnResponseCompleted(i, 1024, std::string("fake_hash")); } - // Validating the download item, so it will complete. - if (state.danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE) - download->DangerousDownloadValidated(); + + EXPECT_EQ(expected_intermediate_path.value(), + download->GetFullPath().value()); + download_manager_delegate_->SetShouldCompleteDownload(true); + download_manager_delegate_->InvokeDownloadCompletionCallback(); message_loop_.RunAllPending(); + EXPECT_EQ(expected_final_path.value(), + download->GetTargetFilePath().value()); } } @@ -964,8 +907,10 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { AddMockDownloadToFileManager(info->download_id.local(), download_file); EXPECT_CALL(*download_file, Rename(cr_path)) - .Times(1) .WillOnce(Return(net::OK)); + EXPECT_CALL(*download_file, AppendDataToFile(kTestData, kTestDataLen)) + .WillOnce(Return(net::OK)); + EXPECT_CALL(*download_file, Cancel()); download_manager_->CreateDownloadItem(info.get(), DownloadRequestHandle()); @@ -975,7 +920,6 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { scoped_ptr<ItemObserver> observer(new ItemObserver(download)); download_file->AppendDataToFile(kTestData, kTestDataLen); - ContinueDownloadWithPath(download, new_path); message_loop_.RunAllPending(); EXPECT_TRUE(GetActiveDownloadItem(0) != NULL); @@ -1107,8 +1051,10 @@ TEST_F(DownloadManagerTest, DownloadCancelTest) { // |download_file| is owned by DownloadFileManager. EXPECT_CALL(*download_file, Rename(cr_path)) - .Times(1) .WillOnce(Return(net::OK)); + EXPECT_CALL(*download_file, AppendDataToFile(kTestData, kTestDataLen)) + .WillOnce(Return(net::OK)); + EXPECT_CALL(*download_file, Cancel()); download_manager_->CreateDownloadItem(info.get(), DownloadRequestHandle()); @@ -1150,12 +1096,9 @@ TEST_F(DownloadManagerTest, MAYBE_DownloadOverwriteTest) { using ::testing::Invoke; using ::testing::Return; - // Create a temporary directory. - ScopedTempDir temp_dir_; - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - + ASSERT_TRUE(CreateTempDownloadsDirectory()); // File names we're using. - const FilePath new_path(temp_dir_.path().AppendASCII("foo.txt")); + const FilePath new_path(GetPathInDownloadsDir(FILE_PATH_LITERAL("foo.txt"))); const FilePath cr_path(GetTempDownloadPath(new_path)); EXPECT_FALSE(file_util::PathExists(new_path)); @@ -1241,12 +1184,10 @@ TEST_F(DownloadManagerTest, MAYBE_DownloadRemoveTest) { using ::testing::Invoke; using ::testing::Return; - // Create a temporary directory. - ScopedTempDir temp_dir_; - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(CreateTempDownloadsDirectory()); // File names we're using. - const FilePath new_path(temp_dir_.path().AppendASCII("foo.txt")); + const FilePath new_path(GetPathInDownloadsDir(FILE_PATH_LITERAL("foo.txt"))); const FilePath cr_path(GetTempDownloadPath(new_path)); EXPECT_FALSE(file_util::PathExists(new_path)); diff --git a/content/browser/download/download_state_info.cc b/content/browser/download/download_state_info.cc deleted file mode 100644 index b927fb0..0000000 --- a/content/browser/download/download_state_info.cc +++ /dev/null @@ -1,41 +0,0 @@ -// 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_state_info.h" - -#include "content/public/browser/download_item.h" - -DownloadStateInfo::DownloadStateInfo() - : path_uniquifier(0), - has_user_gesture(false), - transition_type(content::PAGE_TRANSITION_LINK), - prompt_user_for_save_location(false), - danger(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) { -} - -DownloadStateInfo::DownloadStateInfo(const FilePath& forced_name, - bool has_user_gesture, - content::PageTransition transition_type, - bool prompt_user_for_save_location) - : path_uniquifier(0), - has_user_gesture(has_user_gesture), - transition_type(transition_type), - prompt_user_for_save_location(prompt_user_for_save_location), - danger(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS), - force_file_name(forced_name) { -} - -bool DownloadStateInfo::IsDangerous() const { - // TODO(noelutz): At this point only the windows views UI supports - // warnings based on dangerous content. -#ifdef OS_WIN - return (danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || - danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || - danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || - danger == content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT); -#else - return (danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || - danger == content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL); -#endif -} diff --git a/content/browser/download/download_state_info.h b/content/browser/download/download_state_info.h deleted file mode 100644 index 2768a5f..0000000 --- a/content/browser/download/download_state_info.h +++ /dev/null @@ -1,55 +0,0 @@ -// 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. - -#ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_STATE_INFO_H_ -#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_STATE_INFO_H_ -#pragma once - -#include "base/file_path.h" -#include "content/common/content_export.h" -#include "content/public/browser/download_danger_type.h" -#include "content/public/common/page_transition_types.h" - -// Contains information relating to the process of determining what to do with -// the download. -struct DownloadStateInfo { - DownloadStateInfo(); - DownloadStateInfo(const FilePath& forced_name, - bool has_user_gesture, - content::PageTransition transition_type, - bool prompt_user_for_save_location); - - // Indicates if the download is dangerous. - CONTENT_EXPORT bool IsDangerous() const; - - // The original name for a dangerous download, specified by the request. - FilePath target_name; - - // The path where we save the download. Typically generated. - FilePath suggested_path; - - // A number that should be added to the suggested path to make it unique. - // 0 means no number should be appended. It is eventually incorporated - // into the final file name. - int path_uniquifier; - - // True if the download is the result of user action. - bool has_user_gesture; - - content::PageTransition transition_type; - - // True if we should display the 'save as...' UI and prompt the user - // for the download location. - // False if the UI should be suppressed and the download performed to the - // default location. - bool prompt_user_for_save_location; - - content::DownloadDangerType danger; - - // If non-empty, contains an externally supplied filename that should be used - // as the target path. - FilePath force_file_name; -}; - -#endif // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_STATE_INFO_H_ |