summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/download/download_item.cc32
-rw-r--r--chrome/browser/download/download_item.h23
-rw-r--r--chrome/browser/download/download_manager.cc181
-rw-r--r--chrome/browser/download/download_manager.h19
-rw-r--r--chrome/browser/download/download_manager_unittest.cc15
-rw-r--r--chrome/browser/download/save_package.cc3
6 files changed, 167 insertions, 106 deletions
diff --git a/chrome/browser/download/download_item.cc b/chrome/browser/download/download_item.cc
index cb753e8..620828b 100644
--- a/chrome/browser/download/download_item.cc
+++ b/chrome/browser/download/download_item.cc
@@ -27,6 +27,23 @@
#include "chrome/common/pref_names.h"
#include "ui/base/l10n/l10n_util.h"
+// A DownloadItem normally goes through the following states:
+// * Created (when download starts)
+// * Made visible to consumers (e.g. Javascript) after the
+// destination file has been determined.
+// * Entered into the history database.
+// * Made visible in the download shelf.
+// * All data is received. Note that the actual data download occurs
+// in parallel with the above steps, but until those steps are
+// complete, completion of the data download will be ignored.
+// * Download file is renamed to its final name, and possibly
+// auto-opened.
+// TODO(rdsmith): This progress should be reflected in
+// DownloadItem::DownloadState and a state transition table/state diagram.
+//
+// TODO(rdsmith): This description should be updated to reflect the cancel
+// pathways.
+
namespace {
// Update frequency (milliseconds).
@@ -102,9 +119,12 @@ DownloadItem::DownloadItem(DownloadManager* download_manager,
is_extension_install_(info.is_extension_install),
name_finalized_(false),
is_temporary_(false),
+ all_data_saved_(false),
opened_(false) {
if (state_ == IN_PROGRESS)
state_ = CANCELLED;
+ if (state_ == COMPLETE)
+ all_data_saved_ = true;
Init(false /* don't start progress timer */);
}
@@ -139,6 +159,7 @@ DownloadItem::DownloadItem(DownloadManager* download_manager,
is_extension_install_(info.is_extension_install),
name_finalized_(false),
is_temporary_(!info.save_info.file_path.empty()),
+ all_data_saved_(false),
opened_(false) {
Init(true /* start progress timer */);
}
@@ -174,6 +195,7 @@ DownloadItem::DownloadItem(DownloadManager* download_manager,
is_extension_install_(false),
name_finalized_(false),
is_temporary_(false),
+ all_data_saved_(false),
opened_(false) {
Init(true /* start progress timer */);
}
@@ -299,8 +321,14 @@ void DownloadItem::Cancel(bool update_history) {
download_manager_->DownloadCancelled(id_);
}
-void DownloadItem::OnAllDataSaved(int64 size) {
+void DownloadItem::MarkAsComplete() {
+ DCHECK(all_data_saved_);
state_ = COMPLETE;
+}
+
+void DownloadItem::OnAllDataSaved(int64 size) {
+ DCHECK(!all_data_saved_);
+ all_data_saved_ = true;
UpdateSize(size);
StopProgressTimer();
}
@@ -324,7 +352,7 @@ void DownloadItem::Finished() {
auto_opened_ = true;
}
- // Notify our observers that we are complete (the call to OnAllDataSaved()
+ // Notify our observers that we are complete (the call to MarkAsComplete()
// set the state to complete but did not notify).
UpdateObservers();
diff --git a/chrome/browser/download/download_item.h b/chrome/browser/download/download_item.h
index bc32f63..eaa9a55 100644
--- a/chrome/browser/download/download_item.h
+++ b/chrome/browser/download/download_item.h
@@ -40,8 +40,19 @@ class DownloadItem {
public:
enum DownloadState {
IN_PROGRESS,
+
+ // Note that COMPLETE indicates that the download has gotten all of its
+ // data, has figured out its final destination file, has been entered
+ // into the history store, and has been shown in the UI. The only
+ // operations remaining are acceptance by the user of
+ // a dangerous download (if dangerous), renaming the file
+ // to the final name, and auto-opening it (if it auto-opens).
COMPLETE,
+
CANCELLED,
+
+ // This state indicates that the download item is about to be destroyed,
+ // and observers seeing this state should release all references.
REMOVING
};
@@ -128,9 +139,13 @@ class DownloadItem {
// when resuming a download (assuming the server supports byte ranges).
void Cancel(bool update_history);
- // Called when all data has been saved.
+ // Called when all data has been saved. Only has display effects.
void OnAllDataSaved(int64 size);
+ // Called when ready to consider the data received and move on to the
+ // next stage.
+ void MarkAsComplete();
+
// Called when the entire download operation (including renaming etc)
// is finished.
void Finished();
@@ -152,6 +167,9 @@ class DownloadItem {
// total size).
int PercentComplete() const;
+ // Whether or not this download has saved all of its data.
+ bool all_data_saved() const { return all_data_saved_; }
+
// Update the fields that may have changed in DownloadCreateInfo as a
// result of analyzing the file and figuring out its type, location, etc.
// May only be called once.
@@ -340,6 +358,9 @@ class DownloadItem {
// True if the item was downloaded temporarily.
bool is_temporary_;
+ // True if we've saved all the data for the download.
+ bool all_data_saved_;
+
// Did the user open the item either directly or indirectly (such as by
// setting always open files of this type)? The shelf also sets this field
// when the user closes the shelf before the item has been opened but should
diff --git a/chrome/browser/download/download_manager.cc b/chrome/browser/download/download_manager.cc
index 740177e..7e66e50 100644
--- a/chrome/browser/download/download_manager.cc
+++ b/chrome/browser/download/download_manager.cc
@@ -240,7 +240,7 @@ bool DownloadManager::Init(Profile* profile) {
// history system of a new download. Since this method can be called while the
// history service thread is still reading the persistent state, we do not
// insert the new DownloadItem into 'history_downloads_' or inform our
-// observers at this point. OnCreateDatabaseEntryComplete() handles that
+// observers at this point. OnCreateDownloadEntryComplete() handles that
// finalization of the the download creation as a callback from the
// history thread.
void DownloadManager::StartDownload(DownloadCreateInfo* info) {
@@ -438,6 +438,8 @@ void DownloadManager::CreateDownloadItem(DownloadCreateInfo* info) {
void DownloadManager::AttachDownloadItem(DownloadCreateInfo* info,
const FilePath& target_path) {
+ VLOG(20) << __FUNCTION__ << "()" << " info = " << info->DebugString();
+
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
scoped_ptr<DownloadCreateInfo> infop(info);
@@ -458,29 +460,25 @@ void DownloadManager::AttachDownloadItem(DownloadCreateInfo* info,
info->is_extension_install,
info->original_name);
in_progress_[info->download_id] = download;
+ UpdateAppIcon(); // Reflect entry into in_progress_.
- bool download_finished = ContainsKey(pending_finished_downloads_,
- info->download_id);
-
- VLOG(20) << __FUNCTION__ << "()"
- << " target_path = \"" << target_path.value() << "\""
- << " download_finished = " << download_finished
- << " info = " << info->DebugString()
- << " download = " << download->DebugString(true);
-
- if (download_finished || info->is_dangerous) {
- // The download has already finished or the download is not safe.
- // We can now rename the file to its final name (or its tentative name
- // in dangerous download cases).
+ // Rename to intermediate name.
+ if (info->is_dangerous) {
+ // The download is not safe. We can now rename the file to its
+ // tentative name using OnFinalDownloadName (the actual final
+ // name after user confirmation will be set in
+ // ProceedWithFinishedDangerousDownload).
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
NewRunnableMethod(
file_manager_, &DownloadFileManager::OnFinalDownloadName,
- download->id(), target_path, !info->is_dangerous,
+ download->id(), target_path, false,
make_scoped_refptr(this)));
} else {
- // The download hasn't finished and it is a safe download. We need to
- // rename it to its intermediate '.crdownload' path.
+ // The download is a safe download. We need to
+ // rename it to its intermediate '.crdownload' path. The final
+ // name after user confirmation will be set from
+ // DownloadItem::OnSafeDownloadFinished.
FilePath download_path = download_util::GetCrDownloadPath(target_path);
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
@@ -490,17 +488,8 @@ void DownloadManager::AttachDownloadItem(DownloadCreateInfo* info,
download->Rename(download_path);
}
- if (download_finished) {
- // If the download already completed by the time we reached this point, then
- // notify observers that it did.
- OnAllDataSaved(info->download_id,
- pending_finished_downloads_[info->download_id]);
- }
-
download_history_->AddEntry(*info, download,
NewCallback(this, &DownloadManager::OnCreateDownloadEntryComplete));
-
- UpdateAppIcon();
}
void DownloadManager::UpdateDownload(int32 download_id, int64 size) {
@@ -510,73 +499,93 @@ void DownloadManager::UpdateDownload(int32 download_id, int64 size) {
DownloadItem* download = it->second;
if (download->state() == DownloadItem::IN_PROGRESS) {
download->Update(size);
+ UpdateAppIcon(); // Reflect size updates.
download_history_->UpdateEntry(download);
}
}
- UpdateAppIcon();
}
void DownloadManager::OnAllDataSaved(int32 download_id, int64 size) {
VLOG(20) << __FUNCTION__ << "()" << " download_id = " << download_id
<< " size = " << size;
- DownloadMap::iterator it = in_progress_.find(download_id);
- if (it == in_progress_.end()) {
- // The download is done, but the user hasn't selected a final location for
- // it yet (the Save As dialog box is probably still showing), so just keep
- // track of the fact that this download id is complete, when the
- // DownloadItem is constructed later we'll notify its completion then.
- DCHECK(!ContainsKey(pending_finished_downloads_, download_id));
- pending_finished_downloads_[download_id] = size;
- VLOG(20) << __FUNCTION__ << "()" << " Added download_id = " << download_id
- << " to pending_finished_downloads_";
- return;
- }
- // Remove the id from the list of pending ids.
- PendingFinishedMap::iterator erase_it =
- pending_finished_downloads_.find(download_id);
- if (erase_it != pending_finished_downloads_.end()) {
- pending_finished_downloads_.erase(erase_it);
- VLOG(20) << __FUNCTION__ << "()" << " Removed download_id = " << download_id
- << " from pending_finished_downloads_";
- }
+ DCHECK_EQ(1U, active_downloads_.count(download_id));
+ DownloadItem* download = active_downloads_[download_id];
+ download->OnAllDataSaved(size);
- DownloadItem* download = it->second;
+ MaybeCompleteDownload(download);
+}
- VLOG(20) << __FUNCTION__ << "()"
- << " download = " << download->DebugString(true);
+bool DownloadManager::IsDownloadReadyForCompletion(DownloadItem* download) {
+ // If we don't have all the data, the download is not ready for
+ // completion.
+ if (!download->all_data_saved())
+ return false;
- download->OnAllDataSaved(size);
+ // If the download isn't active (e.g. has been cancelled) it's not
+ // ready for completion.
+ if (active_downloads_.count(download->id()) == 0)
+ return false;
- // Clean up will happen when the history system create callback runs if we
- // don't have a valid db_handle yet.
- if (download->db_handle() != DownloadHistory::kUninitializedHandle) {
- in_progress_.erase(it);
- download_history_->UpdateEntry(download);
- }
+ // If the download hasn't been inserted into the history system
+ // (which occurs strictly after file name determination, intermediate
+ // file rename, and UI display) then it's not ready for completion.
+ return (download->db_handle() != DownloadHistory::kUninitializedHandle);
+}
- UpdateAppIcon();
+void DownloadManager::MaybeCompleteDownload(DownloadItem* download) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(20) << __FUNCTION__ << "()" << " download = "
+ << download->DebugString(false);
- // If this a dangerous download not yet validated by the user, don't do
- // anything. When the user notifies us, it will trigger a call to
- // ProceedWithFinishedDangerousDownload.
- if (download->safety_state() == DownloadItem::DANGEROUS) {
+ if (!IsDownloadReadyForCompletion(download))
return;
- }
- if (download->safety_state() == DownloadItem::DANGEROUS_BUT_VALIDATED) {
- // We first need to rename the downloaded file from its temporary name to
- // its final name before we can continue.
- BrowserThread::PostTask(
- BrowserThread::FILE, FROM_HERE,
- NewRunnableMethod(
- this, &DownloadManager::ProceedWithFinishedDangerousDownload,
- download->db_handle(),
- download->full_path(), download->target_name()));
- return;
+ // TODO(rdsmith): DCHECK that we only pass through this point
+ // once per download. The natural way to do this is by a state
+ // transition on the DownloadItem.
+
+ // Confirm we're in the proper set of states to be here;
+ // in in_progress_, have all data, have a history handle.
+ DCHECK_EQ(1u, in_progress_.count(download->id()));
+ DCHECK(download->all_data_saved());
+ DCHECK(download->db_handle() != DownloadHistory::kUninitializedHandle);
+ DCHECK_EQ(1u, history_downloads_.count(download->db_handle()));
+
+ VLOG(20) << __FUNCTION__ << "()" << " executing: download = "
+ << download->DebugString(false);
+
+ // Remove the id from in_progress
+ in_progress_.erase(download->id());
+ UpdateAppIcon(); // Reflect removal from in_progress_.
+
+ // Final update of download item and history.
+ download->MarkAsComplete();
+ download_history_->UpdateEntry(download);
+
+ switch (download->safety_state()) {
+ case DownloadItem::DANGEROUS:
+ // If this a dangerous download not yet validated by the user, don't do
+ // anything. When the user notifies us, it will trigger a call to
+ // ProceedWithFinishedDangerousDownload.
+ return;
+ case DownloadItem::DANGEROUS_BUT_VALIDATED:
+ // The dangerous download has been validated by the user. We first
+ // need to rename the downloaded file from its temporary name to
+ // its final name. We will continue the download processing in the
+ // callback.
+ BrowserThread::PostTask(
+ BrowserThread::FILE, FROM_HERE,
+ NewRunnableMethod(
+ this, &DownloadManager::ProceedWithFinishedDangerousDownload,
+ download->db_handle(),
+ download->full_path(), download->target_name()));
+ return;
+ case DownloadItem::SAFE:
+ // The download is safe; just finish it.
+ download->OnSafeDownloadFinished(file_manager_);
+ return;
}
-
- download->OnSafeDownloadFinished(file_manager_);
}
void DownloadManager::RemoveFromActiveList(int32 download_id) {
@@ -667,13 +676,13 @@ void DownloadManager::DownloadCancelled(int32 download_id) {
if (download->db_handle() != DownloadHistory::kUninitializedHandle) {
in_progress_.erase(it);
active_downloads_.erase(download_id);
+ UpdateAppIcon(); // Reflect removal from in_progress_.
download_history_->UpdateEntry(download);
}
DownloadCancelledInternal(download_id,
download->render_process_id(),
download->request_id());
- UpdateAppIcon();
}
void DownloadManager::DownloadCancelledInternal(int download_id,
@@ -993,23 +1002,21 @@ void DownloadManager::OnCreateDownloadEntryComplete(
// Inform interested objects about the new download.
NotifyModelChanged();
- // If this download has been completed before we've received the db handle,
+ // If this download has been cancelled before we've received the DB handle,
// post one final message to the history service so that it can be properly
// in sync with the DownloadItem's completion status, and also inform any
// observers so that they get more than just the start notification.
+ //
+ // Otherwise, try to complete the download
if (download->state() != DownloadItem::IN_PROGRESS) {
+ DCHECK_EQ(DownloadItem::CANCELLED, download->state());
in_progress_.erase(it);
- // TODO(ahendrickson) -- We don't actually know whether or not we can
- // remove the download item from the |active_downloads_| map, as there
- // is no state in |DownloadItem::DownloadState| to indicate that the
- // downloads system is done with an item. Fix this when we have a
- // proper final state to check for.
active_downloads_.erase(info.download_id);
download_history_->UpdateEntry(download);
download->UpdateObservers();
+ } else {
+ MaybeCompleteDownload(download);
}
-
- UpdateAppIcon();
}
void DownloadManager::ShowDownloadInBrowser(const DownloadCreateInfo& info,
@@ -1057,9 +1064,9 @@ DownloadItem* DownloadManager::GetDownloadItem(int id) {
void DownloadManager::AssertContainersConsistent() const {
#if !defined(NDEBUG)
// Turn everything into sets.
- DownloadSet in_progress_set, history_set;
+ DownloadSet active_set, history_set;
const DownloadMap* input_maps[] = {&active_downloads_, &history_downloads_};
- DownloadSet* local_sets[] = {&in_progress_set, &history_set};
+ DownloadSet* local_sets[] = {&active_set, &history_set};
DCHECK_EQ(ARRAYSIZE_UNSAFE(input_maps), ARRAYSIZE_UNSAFE(local_sets));
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_maps); i++) {
for (DownloadMap::const_iterator it = input_maps[i]->begin();
@@ -1069,7 +1076,7 @@ void DownloadManager::AssertContainersConsistent() const {
}
// Check if each set is fully present in downloads, and create a union.
- const DownloadSet* all_sets[] = {&in_progress_set, &history_set,
+ const DownloadSet* all_sets[] = {&active_set, &history_set,
&save_page_as_downloads_};
DownloadSet downloads_union;
for (int i = 0; i < static_cast<int>(ARRAYSIZE_UNSAFE(all_sets)); i++) {
diff --git a/chrome/browser/download/download_manager.h b/chrome/browser/download/download_manager.h
index 61b17be..c00437a 100644
--- a/chrome/browser/download/download_manager.h
+++ b/chrome/browser/download/download_manager.h
@@ -125,6 +125,17 @@ class DownloadManager
void PauseDownload(int32 download_id, bool pause);
void RemoveDownload(int64 download_handle);
+ // Determine if the download is ready for completion, i.e. has had
+ // all data received, and completed the filename determination and
+ // history insertion.
+ bool IsDownloadReadyForCompletion(DownloadItem* download);
+
+ // If all pre-requisites have been met, complete download processing, i.e.
+ // do internal cleanup, file rename, and potentially auto-open.
+ // (Dangerous downloads still may block on user acceptance after this
+ // point.)
+ void MaybeCompleteDownload(DownloadItem* download);
+
// Called when the download is renamed to its final name.
void DownloadRenamedToFinalName(int download_id, const FilePath& full_path);
@@ -369,14 +380,6 @@ class DownloadManager
// user wants us to prompt for a save location for each download.
FilePath last_download_path_;
- // Keep track of downloads that are completed before the user selects the
- // destination, so that observers are appropriately notified of completion
- // after this determination is made.
- // The map is of download_id->remaining size (bytes), both of which are
- // required when calling OnAllDataSaved.
- typedef std::map<int32, int64> PendingFinishedMap;
- PendingFinishedMap pending_finished_downloads_;
-
// The "Save As" dialog box used to ask the user where a file should be
// saved.
scoped_refptr<SelectFileDialog> select_file_dialog_;
diff --git a/chrome/browser/download/download_manager_unittest.cc b/chrome/browser/download/download_manager_unittest.cc
index 610fb98..4b7dd8a 100644
--- a/chrome/browser/download/download_manager_unittest.cc
+++ b/chrome/browser/download/download_manager_unittest.cc
@@ -157,28 +157,29 @@ const struct {
bool will_delete_crdownload;
int expected_rename_count;
} kDownloadRenameCases[] = {
- // Safe download, download finishes BEFORE rename.
- // Needs to be renamed only once. Crdownload file needs to be deleted.
+ // Safe download, download finishes BEFORE file name determined.
+ // Renamed twice (linear path through UI). Crdownload file does not need
+ // to be deleted.
{ FILE_PATH_LITERAL("foo.zip"),
false,
true,
- true,
- 1, },
- // Dangerous download, download finishes BEFORE rename.
+ false,
+ 2, },
+ // Dangerous download, download finishes BEFORE file name determined.
// Needs to be renamed only once.
{ FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
true,
true,
false,
1, },
- // Safe download, download finishes AFTER rename.
+ // Safe download, download finishes AFTER file name determined.
// Needs to be renamed twice.
{ FILE_PATH_LITERAL("foo.zip"),
false,
false,
false,
2, },
- // Dangerous download, download finishes AFTER rename.
+ // Dangerous download, download finishes AFTER file name determined.
// Needs to be renamed only once.
{ FILE_PATH_LITERAL("Unconfirmed xxx.crdownload"),
true,
diff --git a/chrome/browser/download/save_package.cc b/chrome/browser/download/save_package.cc
index f7833d5..e2cebcc 100644
--- a/chrome/browser/download/save_package.cc
+++ b/chrome/browser/download/save_package.cc
@@ -687,8 +687,9 @@ void SavePackage::Finish() {
save_ids));
download_->OnAllDataSaved(all_save_items_count_);
+ download_->MarkAsComplete();
// Notify download observers that we are complete (the call
- // to OnAllDataSaved() set the state to complete but did not notify).
+ // to OnReadyToFinish() set the state to complete but did not notify).
download_->UpdateObservers();
NotificationService::current()->Notify(