diff options
author | haraken@google.com <haraken@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-15 08:17:48 +0000 |
---|---|---|
committer | haraken@google.com <haraken@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-15 08:17:48 +0000 |
commit | 9fc11467641e127b12c1f341b0b093c7bc483421 (patch) | |
tree | 955fc9619ef757918e488d57a84c25b6cece9f76 | |
parent | 8eeba55977af9d5c3fe8a8f5d5e5579d0f9d02a4 (diff) | |
download | chromium_src-9fc11467641e127b12c1f341b0b093c7bc483421.zip chromium_src-9fc11467641e127b12c1f341b0b093c7bc483421.tar.gz chromium_src-9fc11467641e127b12c1f341b0b093c7bc483421.tar.bz2 |
Detect removed files and reflect the state in chrome://downloads and the download shelf
- Invalidate the links of removed files in chrome://downloads page
- Label "Removed" for the removed files in the download shelf
BUG=29093
TEST=Observe that in chrome://downloads, the downloaded and then removed files are labeled "Removed" and have no links, Observe that in the download shelf, the downloaded and then removed files are labeled "Removed", DownloadManagerTest.*
Review URL: http://codereview.chromium.org/6905049
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@89148 0039d316-1c4b-4281-b951-d872f2087c98
18 files changed, 348 insertions, 72 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 3f0d945..b78c394 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2181,9 +2181,12 @@ Other platform defines such as use_titlecase are declared in build/common.gypi. desc="Temporary status shown when a user has clicked to open a downloaded file."> Opening <ph name="FILE">$1<ex>image.jpg</ex></ph>... </message> - <message name="IDS_DOWNLOAD_STATUS_CANCELED" desc="Canceled text."> + <message name="IDS_DOWNLOAD_STATUS_CANCELED" desc="Text that appears under the downloaded files that have been canceled."> Canceled </message> + <message name="IDS_DOWNLOAD_STATUS_REMOVED" desc="Text that appears under the downloaded files that have been removed."> + Removed + </message> <message name="IDS_DOWNLOAD_UNCONFIRMED_PREFIX" desc="The prefix used in the unconfirmed download file."> Unconfirmed </message> @@ -2253,9 +2256,13 @@ Other platform defines such as use_titlecase are declared in build/common.gypi. </message> </if> <message name="IDS_DOWNLOAD_TAB_CANCELED" - desc="Cancel link text"> + desc="Text that appears next to the downloaded files that have been canceled"> Canceled </message> + <message name="IDS_DOWNLOAD_FILE_REMOVED" + desc="Text that appears next to the downloaded files that have been removed"> + Removed + </message> <message name="IDS_DOWNLOAD_TAB_PROGRESS_STATUS_TIME_UNKNOWN" desc="The status text for a download in progress in the download manager. This includes information such as speed and received byte counts and is used when we do not know the remaining time"> <ph name="SPEED">$1<ex>10kB/s</ex></ph> - <ph name="RECEIVED_AMOUNT">$2<ex>40kB</ex></ph> diff --git a/chrome/browser/download/download_item.cc b/chrome/browser/download/download_item.cc index 62aa272..1b2f7cf 100644 --- a/chrome/browser/download/download_item.cc +++ b/chrome/browser/download/download_item.cc @@ -132,6 +132,7 @@ DownloadItem::DownloadItem(DownloadManager* download_manager, download_manager_(download_manager), is_paused_(false), open_when_complete_(false), + file_externally_removed_(false), safety_state_(SAFE), auto_opened_(false), is_otr_(false), @@ -173,6 +174,7 @@ DownloadItem::DownloadItem(DownloadManager* download_manager, download_manager_(download_manager), is_paused_(false), open_when_complete_(false), + file_externally_removed_(false), safety_state_(SAFE), auto_opened_(false), is_otr_(is_otr), @@ -202,6 +204,7 @@ DownloadItem::DownloadItem(DownloadManager* download_manager, download_manager_(download_manager), is_paused_(false), open_when_complete_(false), + file_externally_removed_(false), safety_state_(SAFE), auto_opened_(false), is_otr_(is_otr), @@ -242,8 +245,13 @@ void DownloadItem::UpdateObservers() { FOR_EACH_OBSERVER(Observer, observers_, OnDownloadUpdated(this)); } +bool DownloadItem::CanShowInFolder() { + return !IsCancelled() && !file_externally_removed_; +} + bool DownloadItem::CanOpenDownload() { - return !Extension::IsExtension(state_info_.target_name); + return !Extension::IsExtension(state_info_.target_name) && + !file_externally_removed_; } bool DownloadItem::ShouldOpenFileBasedOnExtension() { @@ -265,7 +273,12 @@ void DownloadItem::OpenDownload() { if (IsPartialDownload()) { open_when_complete_ = !open_when_complete_; - } else if (IsComplete()) { + } else if (IsComplete() && !file_externally_removed_) { + // Ideally, we want to detect errors in opening and report them, but we + // don't generally have the proper interface for that to the external + // program that opens the file. So instead we spawn a check to update + // the UI if the file has been deleted in parallel with the open. + download_manager_->CheckForFileRemoval(this); opened_ = true; FOR_EACH_OBSERVER(Observer, observers_, OnDownloadOpened(this)); @@ -396,6 +409,11 @@ void DownloadItem::OnAllDataSaved(int64 size) { StopProgressTimer(); } +void DownloadItem::OnDownloadedFileRemoved() { + file_externally_removed_ = true; + UpdateObservers(); +} + void DownloadItem::Completed() { // TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved. CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); diff --git a/chrome/browser/download/download_item.h b/chrome/browser/download/download_item.h index ef262d6..9e501f2 100644 --- a/chrome/browser/download/download_item.h +++ b/chrome/browser/download/download_item.h @@ -135,7 +135,11 @@ class DownloadItem : public NotificationObserver { const NotificationSource& source, const NotificationDetails& details); - // Returns true if it is OK to open this download. + // Returns true if it is OK to open a folder which this file is inside. + bool CanShowInFolder(); + + // Returns true if it is OK to register the type of this file so that + // it opens automatically. bool CanOpenDownload(); // Tests if a file type should be opened automatically. @@ -170,13 +174,16 @@ class DownloadItem : public NotificationObserver { // when resuming a download (assuming the server supports byte ranges). void Cancel(bool update_history); - // Called when all data has been saved. Only has display effects. - void OnAllDataSaved(int64 size); - // Called by external code (SavePackage) using the DownloadItem interface // to display progress when the DownloadItem should be considered complete. void MarkAsComplete(); + // Called when all data has been saved. Only has display effects. + void OnAllDataSaved(int64 size); + + // Called when the downloaded file is removed. + void OnDownloadedFileRemoved(); + // Download operation had an error. // |size| is the amount of data received so far, and |os_error| is the error // code that the operation received. @@ -278,6 +285,7 @@ class DownloadItem : public NotificationObserver { bool is_paused() const { return is_paused_; } bool open_when_complete() const { return open_when_complete_; } void set_open_when_complete(bool open) { open_when_complete_ = open; } + bool file_externally_removed() const { return file_externally_removed_; } SafetyState safety_state() const { return safety_state_; } void set_safety_state(SafetyState safety_state) { safety_state_ = safety_state; @@ -432,6 +440,9 @@ class DownloadItem : public NotificationObserver { // A flag for indicating if the download should be opened at completion. bool open_when_complete_; + // A flag for indicating if the downloaded file is externally removed. + bool file_externally_removed_; + // Indicates if the download is considered potentially safe or dangerous // (executable files are typically considered dangerous). SafetyState safety_state_; diff --git a/chrome/browser/download/download_item_model.cc b/chrome/browser/download/download_item_model.cc index bbac94b..9eae34f 100644 --- a/chrome/browser/download/download_item_model.cc +++ b/chrome/browser/download/download_item_model.cc @@ -82,7 +82,11 @@ string16 DownloadItemModel::GetStatusText() { } break; case DownloadItem::COMPLETE: - status_text.clear(); + if (download_->file_externally_removed()) { + status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED); + } else { + status_text.clear(); + } break; case DownloadItem::CANCELLED: status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELED); diff --git a/chrome/browser/download/download_manager.cc b/chrome/browser/download/download_manager.cc index 8724641..0ba403a 100644 --- a/chrome/browser/download/download_manager.cc +++ b/chrome/browser/download/download_manager.cc @@ -274,6 +274,48 @@ void DownloadManager::StartDownload(int32 download_id) { NewCallback(this, &DownloadManager::CheckDownloadUrlDone)); } +void DownloadManager::CheckForHistoryFilesRemoval() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + for (DownloadMap::iterator it = history_downloads_.begin(); + it != history_downloads_.end(); ++it) { + CheckForFileRemoval(it->second); + } +} + +void DownloadManager::CheckForFileRemoval(DownloadItem* download_item) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (download_item->IsComplete() && + !download_item->file_externally_removed()) { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &DownloadManager::CheckForFileRemovalOnFileThread, + download_item->db_handle(), + download_item->GetTargetFilePath())); + } +} + +void DownloadManager::CheckForFileRemovalOnFileThread( + int64 db_handle, const FilePath& path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (!file_util::PathExists(path)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + NewRunnableMethod(this, + &DownloadManager::OnFileRemovalDetected, + db_handle)); + } +} + +void DownloadManager::OnFileRemovalDetected(int64 db_handle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DownloadMap::iterator it = history_downloads_.find(db_handle); + if (it != history_downloads_.end()) { + DownloadItem* download_item = it->second; + download_item->OnDownloadedFileRemoved(); + } +} + void DownloadManager::CheckDownloadUrlDone(int32 download_id, bool is_dangerous_url) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -1085,6 +1127,7 @@ void DownloadManager::OnQueryDownloadEntriesComplete( << " download = " << download->DebugString(true); } NotifyModelChanged(); + CheckForHistoryFilesRemoval(); } // Once the new DownloadItem's creation info has been committed to the history diff --git a/chrome/browser/download/download_manager.h b/chrome/browser/download/download_manager.h index 58d13cf..38a5ec6 100644 --- a/chrome/browser/download/download_manager.h +++ b/chrome/browser/download/download_manager.h @@ -235,6 +235,16 @@ class DownloadManager // Called when the user has validated the download of a dangerous file. void DangerousDownloadValidated(DownloadItem* download); + // Checks whether downloaded files still exist. Updates state of downloads + // that refer to removed files. The check runs in the background and may + // finish asynchronously after this method returns. + void CheckForHistoryFilesRemoval(); + + // Checks whether a downloaded file still exists and updates the file's state + // if the file is already removed. The check runs in the background and may + // finish asynchronously after this method returns. + void CheckForFileRemoval(DownloadItem* download_item); + // Callback function after url is checked with safebrowsing service. void CheckDownloadUrlDone(int32 download_id, bool is_dangerous_url); @@ -283,6 +293,14 @@ class DownloadManager virtual ~DownloadManager(); + // Called on the FILE thread to check the existence of a downloaded file. + void CheckForFileRemovalOnFileThread(int64 db_handle, const FilePath& path); + + // Called on the UI thread if the FILE thread detects the removal of + // the downloaded file. The UI thread updates the state of the file + // and then notifies this update to the file's observer. + void OnFileRemovalDetected(int64 db_handle); + // Called on the download thread to check whether the suggested file path // exists. We don't check if the file exists on the UI thread to avoid UI // stalls from interacting with the file system. diff --git a/chrome/browser/download/download_manager_unittest.cc b/chrome/browser/download/download_manager_unittest.cc index 0e947d1..8b6fcfe 100644 --- a/chrome/browser/download/download_manager_unittest.cc +++ b/chrome/browser/download/download_manager_unittest.cc @@ -6,14 +6,19 @@ #include <set> #include "base/file_util.h" +#include "base/i18n/number_formatting.h" +#include "base/i18n/rtl.h" #include "base/memory/scoped_ptr.h" #include "base/stl_util-inl.h" #include "base/string_util.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/download/download_create_info.h" #include "chrome/browser/download/download_file.h" #include "chrome/browser/download/download_file_manager.h" #include "chrome/browser/download/download_item.h" +#include "chrome/browser/download/download_item_model.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_prefs.h" #include "chrome/browser/download/download_status_updater.h" @@ -23,9 +28,11 @@ #include "chrome/common/pref_names.h" #include "chrome/test/testing_profile.h" #include "content/browser/browser_thread.h" +#include "grit/generated_resources.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock_mutant.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" class DownloadManagerTest : public testing::Test { public: @@ -396,6 +403,7 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { info->download_id = static_cast<int>(0); info->prompt_user_for_save_location = false; info->url_chain.push_back(GURL()); + info->total_bytes = static_cast<int64>(kTestDataLen); const FilePath new_path(FILE_PATH_LITERAL("foo.zip")); const FilePath cr_path(download_util::GetCrDownloadPath(new_path)); @@ -413,6 +421,8 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { DownloadItem* download = GetActiveDownloadItem(0); ASSERT_TRUE(download != NULL); + scoped_ptr<DownloadItemModel> download_item_model( + new DownloadItemModel(download)); EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state()); scoped_ptr<ItemObserver> observer(new ItemObserver(download)); @@ -423,11 +433,11 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { message_loop_.RunAllPending(); EXPECT_TRUE(GetActiveDownloadItem(0) != NULL); - OnDownloadError(0, 1024, -6); + int64 error_size = 3; + OnDownloadError(0, error_size, -6); message_loop_.RunAllPending(); EXPECT_TRUE(GetActiveDownloadItem(0) == NULL); - EXPECT_EQ(DownloadItem::INTERRUPTED, download->state()); EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS)); EXPECT_TRUE(observer->hit_state(DownloadItem::INTERRUPTED)); EXPECT_FALSE(observer->hit_state(DownloadItem::COMPLETE)); @@ -435,10 +445,19 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING)); EXPECT_TRUE(observer->was_updated()); EXPECT_FALSE(observer->was_opened()); + EXPECT_FALSE(download->file_externally_removed()); + EXPECT_EQ(DownloadItem::INTERRUPTED, download->state()); + DataUnits amount_units = GetByteDisplayUnits(kTestDataLen); + const string16 simple_size = FormatBytes(error_size, amount_units, false); + string16 simple_total = base::i18n::GetDisplayStringInLTRDirectionality( + FormatBytes(kTestDataLen, amount_units, true)); + EXPECT_EQ(download_item_model->GetStatusText(), + l10n_util::GetStringFUTF16(IDS_DOWNLOAD_STATUS_INTERRUPTED, + simple_size, + simple_total)); download->Cancel(true); - EXPECT_EQ(DownloadItem::INTERRUPTED, download->state()); EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS)); EXPECT_TRUE(observer->hit_state(DownloadItem::INTERRUPTED)); EXPECT_FALSE(observer->hit_state(DownloadItem::COMPLETE)); @@ -446,6 +465,10 @@ TEST_F(DownloadManagerTest, DownloadInterruptTest) { EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING)); EXPECT_TRUE(observer->was_updated()); EXPECT_FALSE(observer->was_opened()); + EXPECT_FALSE(download->file_externally_removed()); + EXPECT_EQ(DownloadItem::INTERRUPTED, download->state()); + EXPECT_EQ(download->received_bytes(), error_size); + EXPECT_EQ(download->total_bytes(), static_cast<int64>(kTestDataLen)); } TEST_F(DownloadManagerTest, DownloadCancelTest) { @@ -478,6 +501,8 @@ TEST_F(DownloadManagerTest, DownloadCancelTest) { DownloadItem* download = GetActiveDownloadItem(0); ASSERT_TRUE(download != NULL); + scoped_ptr<DownloadItemModel> download_item_model( + new DownloadItemModel(download)); EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state()); scoped_ptr<ItemObserver> observer(new ItemObserver(download)); @@ -499,6 +524,10 @@ TEST_F(DownloadManagerTest, DownloadCancelTest) { EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING)); EXPECT_TRUE(observer->was_updated()); EXPECT_FALSE(observer->was_opened()); + EXPECT_FALSE(download->file_externally_removed()); + EXPECT_EQ(DownloadItem::CANCELLED, download->state()); + EXPECT_EQ(download_item_model->GetStatusText(), + l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELED)); EXPECT_FALSE(file_util::PathExists(new_path)); EXPECT_FALSE(file_util::PathExists(cr_path)); @@ -544,6 +573,8 @@ TEST_F(DownloadManagerTest, DownloadOverwriteTest) { DownloadItem* download = GetActiveDownloadItem(0); ASSERT_TRUE(download != NULL); + scoped_ptr<DownloadItemModel> download_item_model( + new DownloadItemModel(download)); EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state()); scoped_ptr<ItemObserver> observer(new ItemObserver(download)); @@ -579,7 +610,9 @@ TEST_F(DownloadManagerTest, DownloadOverwriteTest) { EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING)); EXPECT_TRUE(observer->was_updated()); EXPECT_FALSE(observer->was_opened()); + EXPECT_FALSE(download->file_externally_removed()); EXPECT_EQ(DownloadItem::COMPLETE, download->state()); + EXPECT_EQ(download_item_model->GetStatusText(), ASCIIToUTF16("")); EXPECT_TRUE(file_util::PathExists(new_path)); EXPECT_FALSE(file_util::PathExists(cr_path)); @@ -588,3 +621,95 @@ TEST_F(DownloadManagerTest, DownloadOverwriteTest) { EXPECT_TRUE(file_util::ReadFileToString(new_path, &file_contents)); EXPECT_EQ(std::string(kTestData), file_contents); } + +TEST_F(DownloadManagerTest, DownloadRemoveTest) { + using ::testing::_; + using ::testing::CreateFunctor; + using ::testing::Invoke; + using ::testing::Return; + + // Create a temporary directory. + ScopedTempDir temp_dir_; + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + // File names we're using. + const FilePath new_path(temp_dir_.path().AppendASCII("foo.txt")); + const FilePath cr_path(download_util::GetCrDownloadPath(new_path)); + EXPECT_FALSE(file_util::PathExists(new_path)); + + // 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 = static_cast<int>(0); + info->prompt_user_for_save_location = true; + info->url_chain.push_back(GURL()); + + download_manager_->CreateDownloadItem(info.get()); + + DownloadItem* download = GetActiveDownloadItem(0); + ASSERT_TRUE(download != NULL); + scoped_ptr<DownloadItemModel> download_item_model( + new DownloadItemModel(download)); + + EXPECT_EQ(DownloadItem::IN_PROGRESS, download->state()); + scoped_ptr<ItemObserver> observer(new ItemObserver(download)); + + // Create and initialize the download file. We're bypassing the first part + // of the download process and skipping to the part after the final file + // name has been chosen, so we need to initialize the download file + // properly. + DownloadFile* download_file( + new DownloadFile(info.get(), download_manager_)); + download_file->Rename(cr_path); + // This creates the .crdownload version of the file. + download_file->Initialize(false); + // |download_file| is owned by DownloadFileManager. + AddDownloadToFileManager(info->download_id, download_file); + + ContinueDownloadWithPath(download, new_path); + message_loop_.RunAllPending(); + EXPECT_TRUE(GetActiveDownloadItem(0) != NULL); + + download_file->AppendDataToFile(kTestData, kTestDataLen); + + // Finish the download. + OnAllDataSaved(0, kTestDataLen, ""); + message_loop_.RunAllPending(); + + // Download is complete. + EXPECT_TRUE(GetActiveDownloadItem(0) == NULL); + EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS)); + EXPECT_FALSE(observer->hit_state(DownloadItem::CANCELLED)); + EXPECT_FALSE(observer->hit_state(DownloadItem::INTERRUPTED)); + EXPECT_TRUE(observer->hit_state(DownloadItem::COMPLETE)); + EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING)); + EXPECT_TRUE(observer->was_updated()); + EXPECT_FALSE(observer->was_opened()); + EXPECT_FALSE(download->file_externally_removed()); + EXPECT_EQ(DownloadItem::COMPLETE, download->state()); + EXPECT_EQ(download_item_model->GetStatusText(), ASCIIToUTF16("")); + + EXPECT_TRUE(file_util::PathExists(new_path)); + EXPECT_FALSE(file_util::PathExists(cr_path)); + + // Remove the downloaded file. + ASSERT_TRUE(file_util::Delete(new_path, false)); + download->OnDownloadedFileRemoved(); + message_loop_.RunAllPending(); + + EXPECT_TRUE(GetActiveDownloadItem(0) == NULL); + EXPECT_TRUE(observer->hit_state(DownloadItem::IN_PROGRESS)); + EXPECT_FALSE(observer->hit_state(DownloadItem::CANCELLED)); + EXPECT_FALSE(observer->hit_state(DownloadItem::INTERRUPTED)); + EXPECT_TRUE(observer->hit_state(DownloadItem::COMPLETE)); + EXPECT_FALSE(observer->hit_state(DownloadItem::REMOVING)); + EXPECT_TRUE(observer->was_updated()); + EXPECT_FALSE(observer->was_opened()); + EXPECT_TRUE(download->file_externally_removed()); + EXPECT_EQ(DownloadItem::COMPLETE, download->state()); + EXPECT_EQ(download_item_model->GetStatusText(), + l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED)); + + EXPECT_FALSE(file_util::PathExists(new_path)); +} diff --git a/chrome/browser/download/download_shelf_context_menu.cc b/chrome/browser/download/download_shelf_context_menu.cc index d019d7a..f1a42b7 100644 --- a/chrome/browser/download/download_shelf_context_menu.cc +++ b/chrome/browser/download/download_shelf_context_menu.cc @@ -26,7 +26,7 @@ bool DownloadShelfContextMenu::IsCommandIdEnabled(int command_id) const { switch (command_id) { case SHOW_IN_FOLDER: case OPEN_WHEN_COMPLETE: - return !download_item_->IsCancelled(); + return download_item_->CanShowInFolder(); case ALWAYS_OPEN_TYPE: return download_item_->CanOpenDownload(); case CANCEL: diff --git a/chrome/browser/download/download_util.cc b/chrome/browser/download/download_util.cc index 88801ea..cf8d1c4 100644 --- a/chrome/browser/download/download_util.cc +++ b/chrome/browser/download/download_util.cc @@ -665,6 +665,8 @@ DictionaryValue* CreateDownloadItemValue(DownloadItem* download, int id) { file_value->SetString("url", download->GetURL().spec()); file_value->SetBoolean("otr", download->is_otr()); file_value->SetInteger("total", static_cast<int>(download->total_bytes())); + file_value->SetBoolean("file_externally_removed", + download->file_externally_removed()); if (download->IsInProgress()) { if (download->safety_state() == DownloadItem::DANGEROUS) { @@ -701,11 +703,10 @@ DictionaryValue* CreateDownloadItemValue(DownloadItem* download, int id) { } else if (download->IsCancelled()) { file_value->SetString("state", "CANCELLED"); } else if (download->IsComplete()) { - if (download->safety_state() == DownloadItem::DANGEROUS) { + if (download->safety_state() == DownloadItem::DANGEROUS) file_value->SetString("state", "DANGEROUS"); - } else { + else file_value->SetString("state", "COMPLETE"); - } } else if (download->state() == DownloadItem::REMOVING) { file_value->SetString("state", "REMOVING"); } else { diff --git a/chrome/browser/resources/downloads.js b/chrome/browser/resources/downloads.js index 693685e..3c344a8 100644 --- a/chrome/browser/resources/downloads.js +++ b/chrome/browser/resources/downloads.js @@ -329,6 +329,7 @@ Download.prototype.update = function(download) { this.fileName_ = download.file_name; this.url_ = download.url; this.state_ = download.state; + this.fileExternallyRemoved_ = download.file_externally_removed; this.dangerType_ = download.danger_type; this.since_ = download.since_string; @@ -351,19 +352,24 @@ Download.prototype.update = function(download) { } else { this.nodeImg_.src = 'chrome://fileicon/' + this.filePath_; - if (this.state_ == Download.States.COMPLETE) { + if (this.state_ == Download.States.COMPLETE && + !this.fileExternallyRemoved_) { this.nodeFileLink_.innerHTML = this.fileName_; this.nodeFileLink_.href = this.filePath_; } else { this.nodeFileName_.innerHTML = this.fileName_; } - showInline(this.nodeFileLink_, this.state_ == Download.States.COMPLETE); + showInline(this.nodeFileLink_, + this.state_ == Download.States.COMPLETE && + !this.fileExternallyRemoved_); // nodeFileName_ has to be inline-block to avoid the 'interaction' with // nodeStatus_. If both are inline, it appears that their text contents // are merged before the bidi algorithm is applied leading to an // undesirable reordering. http://crbug.com/13216 - showInlineBlock(this.nodeFileName_, this.state_ != Download.States.COMPLETE); + showInlineBlock(this.nodeFileName_, + this.state_ != Download.States.COMPLETE || + this.fileExternallyRemoved_); if (this.state_ == Download.States.IN_PROGRESS) { this.nodeProgressForeground_.style.display = 'block'; @@ -396,7 +402,9 @@ Download.prototype.update = function(download) { } if (this.controlShow_) { - showInline(this.controlShow_, this.state_ == Download.States.COMPLETE); + showInline(this.controlShow_, + this.state_ == Download.States.COMPLETE && + !this.fileExternallyRemoved_); } showInline(this.controlRetry_, this.state_ == Download.States.CANCELLED); this.controlRetry_.href = this.url_; @@ -457,7 +465,8 @@ Download.prototype.getStatusText_ = function() { case Download.States.INTERRUPTED: return localStrings.getString('status_interrupted'); case Download.States.COMPLETE: - return ''; + return this.fileExternallyRemoved_ ? + localStrings.getString('status_removed') : ''; } } @@ -532,7 +541,15 @@ Download.prototype.cancel_ = function() { // Page: var downloads, localStrings, resultsTimeout; +/** + * The FIFO array that stores updates of download files to be appeared + * on the download page. It is guaranteed that the updates in this array + * are reflected to the download page in a FIFO order. +*/ +var fifo_results; + function load() { + fifo_results = new Array(); localStrings = new LocalStrings(); downloads = new Downloads(); $('term').focus(); @@ -541,12 +558,14 @@ function load() { } function setSearch(searchText) { + fifo_results.length = 0; downloads.clear(); downloads.setSearchText(searchText); chrome.send('getDownloads', [searchText.toString()]); } function clearAll() { + fifo_results.length = 0; downloads.clear(); downloads.setSearchText(''); chrome.send('clearAll', []); @@ -563,6 +582,7 @@ function downloadsList(results) { if (resultsTimeout) clearTimeout(resultsTimeout); window.console.log('results'); + fifo_results.length = 0; downloads.clear(); downloadUpdated(results); downloads.updateSummary(); @@ -576,13 +596,23 @@ function downloadUpdated(results) { if (!downloads) return; + fifo_results = fifo_results.concat(results); + tryDownloadUpdatedPeriodically(); +} + +/** + * Try to reflect as much updates as possible within 50ms. + * This function is scheduled again and again until all updates are reflected. + */ +function tryDownloadUpdatedPeriodically() { var start = Date.now(); - for (var i = 0; i < results.length; i++) { - downloads.updated(results[i]); + while (fifo_results.length) { + var result = fifo_results.shift(); + downloads.updated(result); // Do as much as we can in 50ms. if (Date.now() - start > 50) { clearTimeout(resultsTimeout); - resultsTimeout = setTimeout(downloadUpdated, 5, results.slice(i + 1)); + resultsTimeout = setTimeout(tryDownloadUpdatedPeriodically, 5); break; } } diff --git a/chrome/browser/ui/cocoa/download/download_item_cell.h b/chrome/browser/ui/cocoa/download/download_item_cell.h index 91bceb5..dbb79b4 100644 --- a/chrome/browser/ui/cocoa/download/download_item_cell.h +++ b/chrome/browser/ui/cocoa/download/download_item_cell.h @@ -43,6 +43,7 @@ enum DownloadItemMousePosition { BOOL isStatusTextVisible_; CGFloat titleY_; CGFloat statusAlpha_; + scoped_nsobject<NSAnimation> showStatusAnimation_; scoped_nsobject<NSAnimation> hideStatusAnimation_; scoped_ptr<ui::ThemeProvider> themeProvider_; diff --git a/chrome/browser/ui/cocoa/download/download_item_cell.mm b/chrome/browser/ui/cocoa/download/download_item_cell.mm index 234dc10..3e20cd0 100644 --- a/chrome/browser/ui/cocoa/download/download_item_cell.mm +++ b/chrome/browser/ui/cocoa/download/download_item_cell.mm @@ -68,6 +68,7 @@ const CGFloat kDropdownArrowHeight = 3; const CGFloat kDropdownAreaY = -2; // Duration of the two-lines-to-one-line animation, in seconds. +NSTimeInterval kShowStatusDuration = 0.3; NSTimeInterval kHideStatusDuration = 0.3; // Duration of the 'download complete' animation, in seconds. @@ -87,6 +88,7 @@ const int kInterruptedAnimationDuration = 2.5; @interface DownloadItemCell(Private) - (void)updateTrackingAreas:(id)sender; +- (void)showSecondaryTitle; - (void)hideSecondaryTitle; - (void)animation:(NSAnimation*)animation progressed:(NSAnimationProgress)progress; @@ -142,6 +144,8 @@ const int kInterruptedAnimationDuration = 2.5; [[NSNotificationCenter defaultCenter] removeObserver:self]; if ([completionAnimation_ isAnimating]) [completionAnimation_ stopAnimation]; + if ([showStatusAnimation_ isAnimating]) + [showStatusAnimation_ stopAnimation]; if ([hideStatusAnimation_ isAnimating]) [hideStatusAnimation_ stopAnimation]; if (trackingAreaButton_) { @@ -170,6 +174,7 @@ const int kInterruptedAnimationDuration = 2.5; // Set status text. NSString* statusString = base::SysUTF16ToNSString(statusText); [self setSecondaryTitle:statusString]; + [self showSecondaryTitle]; isStatusTextVisible_ = YES; } @@ -593,6 +598,21 @@ const int kInterruptedAnimationDuration = 2.5; kImageHeight); } +- (void)showSecondaryTitle { + if (!isStatusTextVisible_) { + // No core animation -- text in CA layers is not subpixel antialiased :-/ + showStatusAnimation_.reset([[DownloadItemCellAnimation alloc] + initWithDownloadItemCell:self + duration:kShowStatusDuration + animationCurve:NSAnimationEaseIn]); + [showStatusAnimation_.get() setDelegate:self]; + [showStatusAnimation_.get() startAnimation]; + } else { + // If the status line continues to be visible, don't show an animation + [self animation:nil progressed:0.0]; + } +} + - (void)hideSecondaryTitle { if (isStatusTextVisible_) { // No core animation -- text in CA layers is not subpixel antialiased :-/ @@ -611,7 +631,12 @@ const int kInterruptedAnimationDuration = 2.5; - (void)animation:(NSAnimation*)animation progressed:(NSAnimationProgress)progress { - if (animation == hideStatusAnimation_ || animation == nil) { + if (animation == showStatusAnimation_) { + titleY_ = (1 - progress)*kPrimaryTextOnlyPosTop + + kPrimaryTextPosTop; + statusAlpha_ = progress; + [[self controlView] setNeedsDisplay:YES]; + } else if (animation == hideStatusAnimation_ || animation == nil) { titleY_ = progress*kPrimaryTextOnlyPosTop + (1 - progress)*kPrimaryTextPosTop; statusAlpha_ = 1 - progress; @@ -622,7 +647,9 @@ const int kInterruptedAnimationDuration = 2.5; } - (void)animationDidEnd:(NSAnimation *)animation { - if (animation == hideStatusAnimation_) + if (animation == showStatusAnimation_) + showStatusAnimation_.reset(); + else if (animation == hideStatusAnimation_) hideStatusAnimation_.reset(); else if (animation == completionAnimation_) completionAnimation_.reset(); diff --git a/chrome/browser/ui/gtk/download/download_item_gtk.cc b/chrome/browser/ui/gtk/download/download_item_gtk.cc index 31b4303..c927494 100644 --- a/chrome/browser/ui/gtk/download/download_item_gtk.cc +++ b/chrome/browser/ui/gtk/download/download_item_gtk.cc @@ -125,23 +125,18 @@ DownloadItemGtk::DownloadItemGtk(DownloadShelfGtk* parent_shelf, g_object_unref(no_padding_style); name_label_ = gtk_label_new(NULL); - - UpdateNameLabel(); - - status_label_ = gtk_label_new(NULL); - g_signal_connect(status_label_, "destroy", - G_CALLBACK(gtk_widget_destroyed), &status_label_); // Left align and vertically center the labels. gtk_misc_set_alignment(GTK_MISC(name_label_), 0, 0.5); - gtk_misc_set_alignment(GTK_MISC(status_label_), 0, 0.5); // Until we switch to vector graphics, force the font size. gtk_util::ForceFontSizePixels(name_label_, kTextSize); - gtk_util::ForceFontSizePixels(status_label_, kTextSize); + + UpdateNameLabel(); + + status_label_ = NULL; // Stack the labels on top of one another. - GtkWidget* text_stack = gtk_vbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(text_stack), name_label_, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(text_stack), status_label_, FALSE, FALSE, 0); + text_stack_ = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(text_stack_), name_label_, TRUE, TRUE, 0); // We use a GtkFixed because we don't want it to have its own window. // This choice of widget is not critically important though. @@ -157,7 +152,7 @@ DownloadItemGtk::DownloadItemGtk(DownloadShelfGtk* parent_shelf, GtkWidget* body_hbox = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(body_.get()), body_hbox); gtk_box_pack_start(GTK_BOX(body_hbox), progress_area_.get(), FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(body_hbox), text_stack, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(body_hbox), text_stack_, TRUE, TRUE, 0); menu_button_ = gtk_button_new(); gtk_widget_set_app_paintable(menu_button_, TRUE); @@ -349,19 +344,7 @@ void DownloadItemGtk::OnDownloadUpdated(DownloadItem* download) { NOTREACHED(); } - // Now update the status label. We may have already removed it; if so, we - // do nothing. - if (!status_label_) { - return; - } - status_text_ = UTF16ToUTF8(download_model_->GetStatusText()); - // Remove the status text label. - if (status_text_.empty()) { - gtk_widget_destroy(status_label_); - return; - } - UpdateStatusLabel(status_text_); } @@ -498,8 +481,26 @@ void DownloadItemGtk::UpdateNameLabel() { } void DownloadItemGtk::UpdateStatusLabel(const std::string& status_text) { - if (!status_label_) + // If |status_text| is empty, only |name_label_| is displayed at the + // vertical center of |text_stack_|. Otherwise, |name_label_| is displayed + // on the upper half of |text_stack_| and |status_label_| is displayed + // on the lower half of |text_stack_|. + if (status_text.empty() && status_label_) { + gtk_widget_destroy(status_label_); return; + } + if (!status_label_) { + status_label_ = gtk_label_new(NULL); + g_signal_connect(status_label_, "destroy", + G_CALLBACK(gtk_widget_destroyed), &status_label_); + // Left align and vertically center the labels. + gtk_misc_set_alignment(GTK_MISC(status_label_), 0, 0.5); + // Until we switch to vector graphics, force the font size. + gtk_util::ForceFontSizePixels(status_label_, kTextSize); + + gtk_box_pack_start(GTK_BOX(text_stack_), status_label_, FALSE, FALSE, 0); + gtk_widget_show_all(hbox_.get()); + } GdkColor text_color; if (!theme_service_->UsingNativeTheme()) { @@ -785,7 +786,6 @@ gboolean DownloadItemGtk::OnButtonPress(GtkWidget* button, ShowPopupMenu(NULL, event); return TRUE; } - return FALSE; } @@ -833,7 +833,6 @@ gboolean DownloadItemGtk::OnMenuButtonPressEvent(GtkWidget* button, gtk_widget_queue_draw(button); return TRUE; } - return FALSE; } diff --git a/chrome/browser/ui/gtk/download/download_item_gtk.h b/chrome/browser/ui/gtk/download/download_item_gtk.h index 76f9dc4..d66fce6 100644 --- a/chrome/browser/ui/gtk/download/download_item_gtk.h +++ b/chrome/browser/ui/gtk/download/download_item_gtk.h @@ -160,6 +160,9 @@ class DownloadItemGtk : public DownloadItem::Observer, // animation. OwnedWidgetGtk body_; + // The widget that contains the texts of |name_label_| and |status_label_|. + GtkWidget* text_stack_; + // The GtkLabel that holds the download title text. GtkWidget* name_label_; diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc index 62a12e5..5097c7a 100644 --- a/chrome/browser/ui/views/download/download_item_view.cc +++ b/chrome/browser/ui/views/download/download_item_view.cc @@ -86,7 +86,6 @@ DownloadItemView::DownloadItemView(DownloadItem* download, parent_(parent), status_text_(UTF16ToWide( l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING))), - show_status_text_(true), body_state_(NORMAL), drop_down_state_(NORMAL), progress_angle_(download_util::kStartAngleDegrees), @@ -355,8 +354,6 @@ void DownloadItemView::OnDownloadUpdated(DownloadItem* download) { complete_animation_->SetSlideDuration(kInterruptedAnimationDurationMs); complete_animation_->SetTweenType(ui::Tween::LINEAR); complete_animation_->Show(); - if (status_text.empty()) - show_status_text_ = false; SchedulePaint(); LoadIcon(); break; @@ -370,8 +367,6 @@ void DownloadItemView::OnDownloadUpdated(DownloadItem* download) { complete_animation_->SetSlideDuration(kCompleteAnimationDurationMs); complete_animation_->SetTweenType(ui::Tween::LINEAR); complete_animation_->Show(); - if (status_text.empty()) - show_status_text_ = false; SchedulePaint(); LoadIcon(); break; @@ -723,7 +718,7 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) { // Draw status before button image to effectively lighten text. if (!IsDangerousMode()) { - if (show_status_text_) { + if (!status_text_.empty()) { int mirrored_x = GetMirroredXWithWidthInView( download_util::kSmallProgressIconSize, kTextWidth); // Add font_.height() to compensate for title, which is drawn later. @@ -856,8 +851,8 @@ void DownloadItemView::OnPaint(gfx::Canvas* canvas) { SkColor file_name_color = GetThemeProvider()->GetColor( ThemeService::COLOR_BOOKMARK_TEXT); int y = - box_y_ + (show_status_text_ ? kVerticalPadding : - (box_height_ - font_.GetHeight()) / 2); + box_y_ + (status_text_.empty() ? + ((box_height_ - font_.GetHeight()) / 2) : kVerticalPadding); // Draw the file's name. canvas->DrawStringInt(filename, font_, diff --git a/chrome/browser/ui/views/download/download_item_view.h b/chrome/browser/ui/views/download/download_item_view.h index 4fe8f46..3a4d6de 100644 --- a/chrome/browser/ui/views/download/download_item_view.h +++ b/chrome/browser/ui/views/download/download_item_view.h @@ -197,7 +197,6 @@ class DownloadItemView : public views::ButtonListener, // Elements of our particular download std::wstring status_text_; - bool show_status_text_; // The font used to print the file name and status. gfx::Font font_; diff --git a/chrome/browser/ui/webui/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads_dom_handler.cc index 82b12d0..50d816a 100644 --- a/chrome/browser/ui/webui/downloads_dom_handler.cc +++ b/chrome/browser/ui/webui/downloads_dom_handler.cc @@ -124,20 +124,12 @@ void DownloadsDOMHandler::ModelChanged() { &download_items_); sort(download_items_.begin(), download_items_.end(), DownloadItemSorter()); - // Scan for any in progress downloads and add ourself to them as an observer. + // Add ourself to all download items as an observer. for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (static_cast<int>(it - download_items_.begin()) > kMaxDownloads) break; - - DownloadItem* download = *it; - if (download->IsInProgress()) { - // We want to know what happens as the download progresses. - download->AddObserver(this); - } else if (download->safety_state() == DownloadItem::DANGEROUS) { - // We need to be notified when the user validates the dangerous download. - download->AddObserver(this); - } + (*it)->AddObserver(this); } SendCurrentDownloads(); @@ -151,6 +143,8 @@ void DownloadsDOMHandler::HandleGetDownloads(const ListValue* args) { } else { SendCurrentDownloads(); } + + download_manager_->CheckForHistoryFilesRemoval(); } void DownloadsDOMHandler::HandleOpenFile(const ListValue* args) { diff --git a/chrome/browser/ui/webui/downloads_ui.cc b/chrome/browser/ui/webui/downloads_ui.cc index e53c15d..3c67c71 100644 --- a/chrome/browser/ui/webui/downloads_ui.cc +++ b/chrome/browser/ui/webui/downloads_ui.cc @@ -63,6 +63,7 @@ DownloadsUIHTMLSource::DownloadsUIHTMLSource() // Status. AddLocalizedString("status_cancelled", IDS_DOWNLOAD_TAB_CANCELED); + AddLocalizedString("status_removed", IDS_DOWNLOAD_FILE_REMOVED); AddLocalizedString("status_paused", IDS_DOWNLOAD_PROGRESS_PAUSED); AddLocalizedString("status_interrupted", IDS_DOWNLOAD_PROGRESS_INTERRUPTED); |