diff options
Diffstat (limited to 'chrome/browser/download/download_file_manager.cc')
-rw-r--r-- | chrome/browser/download/download_file_manager.cc | 288 |
1 files changed, 208 insertions, 80 deletions
diff --git a/chrome/browser/download/download_file_manager.cc b/chrome/browser/download/download_file_manager.cc index 3c1edf9..2862742 100644 --- a/chrome/browser/download/download_file_manager.cc +++ b/chrome/browser/download/download_file_manager.cc @@ -35,21 +35,6 @@ namespace { // cause it to become unresponsive (in milliseconds). const int kUpdatePeriodMs = 500; -DownloadManager* DownloadManagerForRenderViewHost(int render_process_id, - int render_view_id) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); - - TabContents* contents = tab_util::GetTabContentsByID(render_process_id, - render_view_id); - if (contents) { - Profile* profile = contents->profile(); - if (profile) - return profile->GetDownloadManager(); - } - - return NULL; -} - } // namespace DownloadFileManager::DownloadFileManager(ResourceDispatcherHost* rdh) @@ -58,29 +43,37 @@ DownloadFileManager::DownloadFileManager(ResourceDispatcherHost* rdh) } DownloadFileManager::~DownloadFileManager() { + // Check for clean shutdown. DCHECK(downloads_.empty()); } +// Called during the browser shutdown process to clean up any state (open files, +// timers) that live on the download_thread_. void DownloadFileManager::Shutdown() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + StopUpdateTimer(); ChromeThread::PostTask( ChromeThread::FILE, FROM_HERE, NewRunnableMethod(this, &DownloadFileManager::OnShutdown)); } +// Cease download thread operations. void DownloadFileManager::OnShutdown() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); - StopUpdateTimer(); STLDeleteValues(&downloads_); } -void DownloadFileManager::CreateDownloadFile( - DownloadCreateInfo* info, DownloadManager* download_manager) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); +// Notifications sent from the download thread and run on the UI thread. - scoped_ptr<DownloadFile> download_file( - new DownloadFile(info, download_manager)); - if (!download_file->Initialize()) { +// Lookup the DownloadManager for this TabContents' profile and inform it of +// a new download. +// TODO(paulg): When implementing download restart via the Downloads tab, +// there will be no 'render_process_id' or 'render_view_id'. +void DownloadFileManager::OnStartDownload(DownloadCreateInfo* info) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DownloadManager* manager = DownloadManagerFromRenderIds(info->child_id, + info->render_view_id); + if (!manager) { ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableFunction(&download_util::CancelDownloadRequest, @@ -91,26 +84,58 @@ void DownloadFileManager::CreateDownloadFile( return; } - DCHECK(GetDownloadFile(info->download_id) == NULL); - downloads_[info->download_id] = download_file.release(); - // TODO(phajdan.jr): fix the duplication of path info below. - info->path = info->save_info.file_path; - StartUpdateTimer(); - ChromeThread::PostTask( - ChromeThread::UI, FROM_HERE, - NewRunnableMethod(download_manager, - &DownloadManager::StartDownload, info)); + // Add the download manager to our request maps for future updates. We want to + // be able to cancel all in progress downloads when a DownloadManager is + // deleted, such as when a profile is closed. We also want to be able to look + // up the DownloadManager associated with a given request without having to + // rely on using tab information, since a tab may be closed while a download + // initiated from that tab is still in progress. + DownloadRequests& downloads = requests_[manager]; + downloads.insert(info->download_id); + + // TODO(paulg): The manager will exist when restarts are implemented. + DownloadManagerMap::iterator dit = managers_.find(info->download_id); + if (dit == managers_.end()) + managers_[info->download_id] = manager; + else + NOTREACHED(); + + // StartDownload will clean up |info|. + manager->StartDownload(info); +} + +// Update the Download Manager with the finish state, and remove the request +// tracking entries. +void DownloadFileManager::OnDownloadFinished(int id, + int64 bytes_so_far) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DownloadManager* manager = GetDownloadManager(id); + if (manager) + manager->DownloadFinished(id, bytes_so_far); + RemoveDownload(id, manager); + RemoveDownloadFromUIProgress(id); } +// Lookup one in-progress download. DownloadFile* DownloadFileManager::GetDownloadFile(int id) { DownloadFileMap::iterator it = downloads_.find(id); return it == downloads_.end() ? NULL : it->second; } +// The UI progress is updated on the file thread and removed on the UI thread. +void DownloadFileManager::RemoveDownloadFromUIProgress(int id) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + AutoLock lock(progress_lock_); + if (ui_progress_.find(id) != ui_progress_.end()) + ui_progress_.erase(id); +} + +// Throttle updates to the UI thread by only posting update notifications at a +// regularly controlled interval. void DownloadFileManager::StartUpdateTimer() { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); if (!update_timer_.IsRunning()) { update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdatePeriodMs), this, &DownloadFileManager::UpdateInProgressDownloads); @@ -118,22 +143,21 @@ void DownloadFileManager::StartUpdateTimer() { } void DownloadFileManager::StopUpdateTimer() { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); update_timer_.Stop(); } +// Our periodic timer has fired so send the UI thread updates on all in progress +// downloads. void DownloadFileManager::UpdateInProgressDownloads() { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); - for (DownloadFileMap::iterator i = downloads_.begin(); - i != downloads_.end(); ++i) { - int id = i->first; - DownloadFile* download_file = i->second; - DownloadManager* manager = download_file->GetDownloadManager(); - if (manager) { - ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, - NewRunnableMethod(manager, &DownloadManager::UpdateDownload, - id, download_file->bytes_so_far())); - } + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + AutoLock lock(progress_lock_); + ProgressMap::iterator it = ui_progress_.begin(); + for (; it != ui_progress_.end(); ++it) { + const int id = it->first; + DownloadManager* manager = GetDownloadManager(id); + if (manager) + manager->UpdateDownload(id, it->second); } } @@ -144,13 +168,21 @@ int DownloadFileManager::GetNextId() { return next_id_++; } +// Notifications sent from the IO thread and run on the download thread: + +// The IO thread created 'info', but the download thread (this method) uses it +// to create a DownloadFile, then passes 'info' to the UI thread where it is +// finally consumed and deleted. void DownloadFileManager::StartDownload(DownloadCreateInfo* info) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); DCHECK(info); - DownloadManager* manager = DownloadManagerForRenderViewHost( - info->child_id, info->render_view_id); - if (!manager) { + DownloadFile* download = new DownloadFile(info); + if (!download->Initialize()) { + // Couldn't open, cancel the operation. The UI thread does not yet know + // about this download so we have to clean up 'info'. We need to get back + // to the IO thread to cancel the network request and CancelDownloadRequest + // on the UI thread is the safe way to do that. ChromeThread::PostTask( ChromeThread::IO, FROM_HERE, NewRunnableFunction(&download_util::CancelDownloadRequest, @@ -158,12 +190,22 @@ void DownloadFileManager::StartDownload(DownloadCreateInfo* info) { info->child_id, info->request_id)); delete info; + delete download; return; } - ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, - NewRunnableMethod(this, &DownloadFileManager::CreateDownloadFile, - info, manager)); + DCHECK(GetDownloadFile(info->download_id) == NULL); + downloads_[info->download_id] = download; + // TODO(phajdan.jr): fix the duplication of path info below. + info->path = info->save_info.file_path; + { + AutoLock lock(progress_lock_); + ui_progress_[info->download_id] = info->received_bytes; + } + + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &DownloadFileManager::OnStartDownload, info)); } // We don't forward an update to the UI thread here, since we want to throttle @@ -179,14 +221,25 @@ void DownloadFileManager::UpdateDownload(int id, DownloadBuffer* buffer) { contents.swap(buffer->contents); } + // Keep track of how many bytes we have successfully saved to update + // our progress status in the UI. + int64 progress_bytes = 0; + DownloadFile* download = GetDownloadFile(id); for (size_t i = 0; i < contents.size(); ++i) { net::IOBuffer* data = contents[i].first; const int data_len = contents[i].second; - if (download) - download->AppendDataToFile(data->data(), data_len); + if (download) { + if (download->AppendDataToFile(data->data(), data_len)) + progress_bytes += data_len; + } data->Release(); } + + if (download) { + AutoLock lock(progress_lock_); + ui_progress_[download->id()] += progress_bytes; + } } void DownloadFileManager::DownloadFinished(int id, DownloadBuffer* buffer) { @@ -197,15 +250,18 @@ void DownloadFileManager::DownloadFinished(int id, DownloadBuffer* buffer) { DownloadFile* download = it->second; download->Finish(); - DownloadManager* download_manager = download->GetDownloadManager(); - if (download_manager) { - ChromeThread::PostTask( - ChromeThread::UI, FROM_HERE, - NewRunnableMethod( - download_manager, &DownloadManager::DownloadFinished, - id, download->bytes_so_far())); + int64 download_size = -1; + { + AutoLock lock(progress_lock_); + download_size = ui_progress_[download->id()]; } + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + this, &DownloadFileManager::OnDownloadFinished, + id, download_size)); + // We need to keep the download around until the UI thread has finalized // the name. if (download->path_renamed()) { @@ -215,7 +271,9 @@ void DownloadFileManager::DownloadFinished(int id, DownloadBuffer* buffer) { } if (downloads_.empty()) - StopUpdateTimer(); + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &DownloadFileManager::StopUpdateTimer)); } // This method will be sent via a user action, or shutdown on the UI thread, and @@ -228,26 +286,94 @@ void DownloadFileManager::CancelDownload(int id) { DownloadFile* download = it->second; download->Cancel(); + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + this, &DownloadFileManager::RemoveDownloadFromUIProgress, + download->id())); + if (download->path_renamed()) { downloads_.erase(it); delete download; } } - if (downloads_.empty()) - StopUpdateTimer(); + if (downloads_.empty()) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &DownloadFileManager::StopUpdateTimer)); + } } -void DownloadFileManager::OnDownloadManagerShutdown(DownloadManager* manager) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); +// Relate a download ID to its owning DownloadManager. +DownloadManager* DownloadFileManager::GetDownloadManager(int download_id) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DownloadManagerMap::iterator it = managers_.find(download_id); + if (it != managers_.end()) + return it->second; + return NULL; +} + +// Utility function for look up table maintenance, called on the UI thread. +// A manager may have multiple downloads in progress, so we just look up the +// one download (id) and remove it from the set, and remove the set if it +// becomes empty. +void DownloadFileManager::RemoveDownload(int id, DownloadManager* manager) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + if (manager) { + RequestMap::iterator it = requests_.find(manager); + if (it != requests_.end()) { + DownloadRequests& downloads = it->second; + DownloadRequests::iterator rit = downloads.find(id); + if (rit != downloads.end()) + downloads.erase(rit); + if (downloads.empty()) + requests_.erase(it); + } + } + + // A download can only have one manager, so remove it if it exists. + DownloadManagerMap::iterator dit = managers_.find(id); + if (dit != managers_.end()) + managers_.erase(dit); +} + +// Utility function for converting request IDs to a TabContents. Must be called +// only on the UI thread since Profile operations may create UI objects, such as +// the first call to profile->GetDownloadManager(). +// static +DownloadManager* DownloadFileManager::DownloadManagerFromRenderIds( + int render_process_id, int render_view_id) { + TabContents* contents = tab_util::GetTabContentsByID(render_process_id, + render_view_id); + if (contents) { + Profile* profile = contents->profile(); + if (profile) + return profile->GetDownloadManager(); + } + + return NULL; +} + +// Called by DownloadManagers in their destructor, and only on the UI thread. +void DownloadFileManager::RemoveDownloadManager(DownloadManager* manager) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); DCHECK(manager); + RequestMap::iterator it = requests_.find(manager); + if (it == requests_.end()) + return; - for (DownloadFileMap::iterator i = downloads_.begin(); - i != downloads_.end(); ++i) { - DownloadFile* download_file = i->second; - if (download_file->GetDownloadManager() == manager) - download_file->OnDownloadManagerShutdown(); + const DownloadRequests& requests = it->second; + DownloadRequests::const_iterator i = requests.begin(); + for (; i != requests.end(); ++i) { + DownloadManagerMap::iterator dit = managers_.find(*i); + if (dit != managers_.end()) { + DCHECK(dit->second == manager); + managers_.erase(dit); + } } + + requests_.erase(it); } // Actions from the UI thread and run on the download thread @@ -346,8 +472,11 @@ void DownloadFileManager::OnFinalDownloadName(int id, delete download; } - if (downloads_.empty()) - StopUpdateTimer(); + if (downloads_.empty()) { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &DownloadFileManager::StopUpdateTimer)); + } } // Called only from OnFinalDownloadName or OnIntermediateDownloadName @@ -359,14 +488,13 @@ void DownloadFileManager::CancelDownloadOnRename(int id) { if (!download) return; - DownloadManager* download_manager = download->GetDownloadManager(); - if (!download_manager) { + DownloadManagerMap::iterator dmit = managers_.find(download->id()); + if (dmit != managers_.end()) { + DownloadManager* dlm = dmit->second; + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(dlm, &DownloadManager::DownloadCancelled, id)); + } else { download->CancelDownloadRequest(resource_dispatcher_host_); - return; } - - ChromeThread::PostTask( - ChromeThread::UI, FROM_HERE, - NewRunnableMethod(download_manager, - &DownloadManager::DownloadCancelled, id)); } |