summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorharaken@google.com <haraken@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-15 08:17:48 +0000
committerharaken@google.com <haraken@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-15 08:17:48 +0000
commit9fc11467641e127b12c1f341b0b093c7bc483421 (patch)
tree955fc9619ef757918e488d57a84c25b6cece9f76
parent8eeba55977af9d5c3fe8a8f5d5e5579d0f9d02a4 (diff)
downloadchromium_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
-rw-r--r--chrome/app/generated_resources.grd11
-rw-r--r--chrome/browser/download/download_item.cc22
-rw-r--r--chrome/browser/download/download_item.h19
-rw-r--r--chrome/browser/download/download_item_model.cc6
-rw-r--r--chrome/browser/download/download_manager.cc43
-rw-r--r--chrome/browser/download/download_manager.h18
-rw-r--r--chrome/browser/download/download_manager_unittest.cc131
-rw-r--r--chrome/browser/download/download_shelf_context_menu.cc2
-rw-r--r--chrome/browser/download/download_util.cc7
-rw-r--r--chrome/browser/resources/downloads.js46
-rw-r--r--chrome/browser/ui/cocoa/download/download_item_cell.h1
-rw-r--r--chrome/browser/ui/cocoa/download/download_item_cell.mm31
-rw-r--r--chrome/browser/ui/gtk/download/download_item_gtk.cc53
-rw-r--r--chrome/browser/ui/gtk/download/download_item_gtk.h3
-rw-r--r--chrome/browser/ui/views/download/download_item_view.cc11
-rw-r--r--chrome/browser/ui/views/download/download_item_view.h1
-rw-r--r--chrome/browser/ui/webui/downloads_dom_handler.cc14
-rw-r--r--chrome/browser/ui/webui/downloads_ui.cc1
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);