// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/dom_ui/downloads_ui.h" #include "app/l10n_util.h" #include "base/gfx/png_encoder.h" #include "base/string_piece.h" #include "base/thread.h" #include "base/time_format.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/dom_ui/chrome_url_data_manager.h" #include "chrome/browser/dom_ui/fileicon_source.h" #include "chrome/browser/download/download_manager.h" #if defined(OS_WIN) // TODO(port): re-enable when download_util is ported #include "chrome/browser/download/download_util.h" #endif #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/profile.h" #include "chrome/common/jstemplate_builder.h" #include "chrome/common/time_format.h" #include "chrome/common/url_constants.h" #include "grit/browser_resources.h" #include "grit/generated_resources.h" namespace { // Maximum number of downloads to show. TODO(glen): Remove this and instead // stuff the downloads down the pipe slowly. static const int kMaxDownloads = 150; /////////////////////////////////////////////////////////////////////////////// // // DownloadsHTMLSource // /////////////////////////////////////////////////////////////////////////////// class DownloadsUIHTMLSource : public ChromeURLDataManager::DataSource { public: DownloadsUIHTMLSource(); // Called when the network layer has requested a resource underneath // the path we registered. virtual void StartDataRequest(const std::string& path, int request_id); virtual std::string GetMimeType(const std::string&) const { return "text/html"; } private: DISALLOW_COPY_AND_ASSIGN(DownloadsUIHTMLSource); }; DownloadsUIHTMLSource::DownloadsUIHTMLSource() : DataSource(chrome::kChromeUIDownloadsHost, MessageLoop::current()) { } void DownloadsUIHTMLSource::StartDataRequest(const std::string& path, int request_id) { DictionaryValue localized_strings; localized_strings.SetString(L"title", l10n_util::GetString(IDS_DOWNLOAD_TITLE)); localized_strings.SetString(L"searchbutton", l10n_util::GetString(IDS_DOWNLOAD_SEARCH_BUTTON)); localized_strings.SetString(L"no_results", l10n_util::GetString(IDS_DOWNLOAD_SEARCH_BUTTON)); localized_strings.SetString(L"searchresultsfor", l10n_util::GetString(IDS_DOWNLOAD_SEARCHRESULTSFOR)); localized_strings.SetString(L"downloads", l10n_util::GetString(IDS_DOWNLOAD_TITLE)); localized_strings.SetString(L"clear_all", l10n_util::GetString(IDS_DOWNLOAD_LINK_CLEAR_ALL)); // Status. localized_strings.SetString(L"status_cancelled", l10n_util::GetString(IDS_DOWNLOAD_TAB_CANCELLED)); localized_strings.SetString(L"status_paused", l10n_util::GetString(IDS_DOWNLOAD_PROGRESS_PAUSED)); // Dangerous file. localized_strings.SetString(L"danger_desc", l10n_util::GetStringF(IDS_PROMPT_DANGEROUS_DOWNLOAD, L"%s")); localized_strings.SetString(L"danger_save", l10n_util::GetString(IDS_SAVE_DOWNLOAD)); localized_strings.SetString(L"danger_discard", l10n_util::GetString(IDS_DISCARD_DOWNLOAD)); // Controls. localized_strings.SetString(L"control_pause", l10n_util::GetString(IDS_DOWNLOAD_LINK_PAUSE)); localized_strings.SetString(L"control_showinfolder", l10n_util::GetString(IDS_DOWNLOAD_LINK_SHOW)); localized_strings.SetString(L"control_cancel", l10n_util::GetString(IDS_DOWNLOAD_LINK_CANCEL)); localized_strings.SetString(L"control_resume", l10n_util::GetString(IDS_DOWNLOAD_LINK_RESUME)); SetFontAndTextDirection(&localized_strings); static const StringPiece downloads_html( ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_DOWNLOADS_HTML)); const std::string full_html = jstemplate_builder::GetTemplateHtml( downloads_html, &localized_strings, "t"); scoped_refptr html_bytes(new RefCountedBytes); html_bytes->data.resize(full_html.size()); std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); SendResponse(request_id, html_bytes); } /////////////////////////////////////////////////////////////////////////////// // // DownloadsDOMHandler // /////////////////////////////////////////////////////////////////////////////// // The handler for Javascript messages related to the "downloads" view, // also observes changes to the download manager. class DownloadsDOMHandler : public DOMMessageHandler, public DownloadManager::Observer, public DownloadItem::Observer { public: explicit DownloadsDOMHandler(DOMUI* dom_ui, DownloadManager* dlm); virtual ~DownloadsDOMHandler(); void Init(); // DownloadItem::Observer interface virtual void OnDownloadUpdated(DownloadItem* download); virtual void OnDownloadOpened(DownloadItem* download) { } // DownloadManager::Observer interface virtual void ModelChanged(); virtual void SetDownloads(std::vector& downloads); // Callback for the "getDownloads" message. void HandleGetDownloads(const Value* value); // Callback for the "openFile" message - opens the file in the shell. void HandleOpenFile(const Value* value); // Callback for the "drag" message - initiates a file object drag. void HandleDrag(const Value* value); // Callback for the "saveDangerous" message - specifies that the user // wishes to save a dangerous file. void HandleSaveDangerous(const Value* value); // Callback for the "discardDangerous" message - specifies that the user // wishes to discard (remove) a dangerous file. void HandleDiscardDangerous(const Value* value); // Callback for the "show" message - shows the file in explorer. void HandleShow(const Value* value); // Callback for the "pause" message - pauses the file download. void HandlePause(const Value* value); // Callback for the "cancel" message - cancels the download. void HandleCancel(const Value* value); // Callback for the "clearAll" message - clears all the downloads. void HandleClearAll(const Value* value); private: // Send the current list of downloads to the page. void SendCurrentDownloads(); // Creates a representation of a download in a format that the downloads // HTML page can understand. DictionaryValue* CreateDownloadItemValue(DownloadItem* download, int id); // Clear all download items and their observers. void ClearDownloadItems(); // Return the download that corresponds to a given id. DownloadItem* GetDownloadById(int id); // Return the download that is referred to in a given value. DownloadItem* GetDownloadByValue(const Value* value); // Get the localized status text for an in-progress download. std::wstring GetProgressStatusText(DownloadItem* download); // Current search text. std::wstring search_text_; // Our model DownloadManager* download_manager_; // The current set of visible DownloadItems for this view received from the // DownloadManager. DownloadManager owns the DownloadItems. The vector is // kept in order, sorted by ascending start time. typedef std::vector OrderedDownloads; OrderedDownloads download_items_; DISALLOW_COPY_AND_ASSIGN(DownloadsDOMHandler); }; // Sort DownloadItems into descending order by their start time. class DownloadItemSorter : public std::binary_function { public: bool operator()(const DownloadItem* lhs, const DownloadItem* rhs) { return lhs->start_time() > rhs->start_time(); } }; DownloadsDOMHandler::DownloadsDOMHandler(DOMUI* dom_ui, DownloadManager* dlm) : DOMMessageHandler(dom_ui), search_text_(), download_manager_(dlm) { dom_ui_->RegisterMessageCallback("getDownloads", NewCallback(this, &DownloadsDOMHandler::HandleGetDownloads)); dom_ui_->RegisterMessageCallback("openFile", NewCallback(this, &DownloadsDOMHandler::HandleOpenFile)); dom_ui_->RegisterMessageCallback("drag", NewCallback(this, &DownloadsDOMHandler::HandleDrag)); dom_ui_->RegisterMessageCallback("saveDangerous", NewCallback(this, &DownloadsDOMHandler::HandleSaveDangerous)); dom_ui_->RegisterMessageCallback("discardDangerous", NewCallback(this, &DownloadsDOMHandler::HandleDiscardDangerous)); dom_ui_->RegisterMessageCallback("show", NewCallback(this, &DownloadsDOMHandler::HandleShow)); dom_ui_->RegisterMessageCallback("togglepause", NewCallback(this, &DownloadsDOMHandler::HandlePause)); dom_ui_->RegisterMessageCallback("resume", NewCallback(this, &DownloadsDOMHandler::HandlePause)); dom_ui_->RegisterMessageCallback("cancel", NewCallback(this, &DownloadsDOMHandler::HandleCancel)); dom_ui_->RegisterMessageCallback("clearAll", NewCallback(this, &DownloadsDOMHandler::HandleClearAll)); // Create our fileicon data source. g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(&chrome_url_data_manager, &ChromeURLDataManager::AddDataSource, new FileIconSource())); } DownloadsDOMHandler::~DownloadsDOMHandler() { ClearDownloadItems(); download_manager_->RemoveObserver(this); } // DownloadsDOMHandler, public: ----------------------------------------------- void DownloadsDOMHandler::Init() { download_manager_->AddObserver(this); } void DownloadsDOMHandler::OnDownloadUpdated(DownloadItem* download) { // Get the id for the download. Our downloads are sorted latest to first, // and the id is the index into that list. We should be careful of sync // errors between the UI and the download_items_ list (we may wish to use // something other than 'id'). OrderedDownloads::iterator it = find(download_items_.begin(), download_items_.end(), download); if (it == download_items_.end()) return; const int id = static_cast(it - download_items_.begin()); ListValue results_value; results_value.Append(CreateDownloadItemValue(download, id)); dom_ui_->CallJavascriptFunction(L"downloadUpdated", results_value); } // A download has started or been deleted. Query our DownloadManager for the // current set of downloads, which will call us back in SetDownloads once it // has retrieved them. void DownloadsDOMHandler::ModelChanged() { ClearDownloadItems(); download_manager_->GetDownloads(this, search_text_); } void DownloadsDOMHandler::SetDownloads( std::vector& downloads) { ClearDownloadItems(); // Swap new downloads in. download_items_.swap(downloads); sort(download_items_.begin(), download_items_.end(), DownloadItemSorter()); // Scan for any in progress downloads and add ourself to them as an observer. for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (static_cast(it - download_items_.begin()) > kMaxDownloads) break; DownloadItem* download = *it; if (download->state() == DownloadItem::IN_PROGRESS) { // 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); } } SendCurrentDownloads(); } void DownloadsDOMHandler::HandleGetDownloads(const Value* value) { std::wstring new_search = ExtractStringValue(value); if (search_text_.compare(new_search) != 0) { search_text_ = new_search; ClearDownloadItems(); download_manager_->GetDownloads(this, search_text_); } else { SendCurrentDownloads(); } } void DownloadsDOMHandler::HandleOpenFile(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) download_manager_->OpenDownload(file, NULL); } void DownloadsDOMHandler::HandleDrag(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) { IconManager* im = g_browser_process->icon_manager(); SkBitmap* icon = im->LookupIcon(file->full_path(), IconLoader::NORMAL); download_util::DragDownload(file, icon); } } void DownloadsDOMHandler::HandleSaveDangerous(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) download_manager_->DangerousDownloadValidated(file); } void DownloadsDOMHandler::HandleDiscardDangerous(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) file->Remove(true); } void DownloadsDOMHandler::HandleShow(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) download_manager_->ShowDownloadInShell(file); } void DownloadsDOMHandler::HandlePause(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) file->TogglePause(); } void DownloadsDOMHandler::HandleCancel(const Value* value) { DownloadItem* file = GetDownloadByValue(value); if (file) file->Cancel(true); } void DownloadsDOMHandler::HandleClearAll(const Value* value) { download_manager_->RemoveAllDownloads(); } // DownloadsDOMHandler, private: ---------------------------------------------- void DownloadsDOMHandler::SendCurrentDownloads() { ListValue results_value; for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { int index = static_cast(it - download_items_.begin()); if (index > kMaxDownloads) break; results_value.Append(CreateDownloadItemValue(*it,index)); } dom_ui_->CallJavascriptFunction(L"downloadsList", results_value); } DictionaryValue* DownloadsDOMHandler::CreateDownloadItemValue( DownloadItem* download, int id) { DictionaryValue* file_value = new DictionaryValue(); file_value->SetInteger(L"started", static_cast(download->start_time().ToTimeT())); file_value->SetString(L"since_string", TimeFormat::RelativeDate(download->start_time(), NULL)); file_value->SetString(L"date_string", base::TimeFormatShortDate(download->start_time())); file_value->SetInteger(L"id", id); file_value->SetString(L"file_path", download->full_path().ToWStringHack()); file_value->SetString(L"file_name", download->GetFileName().ToWStringHack()); file_value->SetString(L"url", download->url().spec()); if (download->state() == DownloadItem::IN_PROGRESS) { if (download->safety_state() == DownloadItem::DANGEROUS) { file_value->SetString(L"state", L"DANGEROUS"); } else if (download->is_paused()) { file_value->SetString(L"state", L"PAUSED"); } else { file_value->SetString(L"state", L"IN_PROGRESS"); } file_value->SetString(L"progress_status_text", GetProgressStatusText(download)); file_value->SetInteger(L"percent", static_cast(download->PercentComplete())); file_value->SetInteger(L"received", static_cast(download->received_bytes())); } else if (download->state() == DownloadItem::CANCELLED) { file_value->SetString(L"state", L"CANCELLED"); } else if (download->state() == DownloadItem::COMPLETE) { if (download->safety_state() == DownloadItem::DANGEROUS) { file_value->SetString(L"state", L"DANGEROUS"); } else { file_value->SetString(L"state", L"COMPLETE"); } } file_value->SetInteger(L"total", static_cast(download->total_bytes())); return file_value; } void DownloadsDOMHandler::ClearDownloadItems() { // Clear out old state and remove self as observer for each download. for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { (*it)->RemoveObserver(this); } download_items_.clear(); } DownloadItem* DownloadsDOMHandler::GetDownloadById(int id) { for (OrderedDownloads::iterator it = download_items_.begin(); it != download_items_.end(); ++it) { if (static_cast(it - download_items_.begin() == id)) { return (*it); } } return NULL; } DownloadItem* DownloadsDOMHandler::GetDownloadByValue(const Value* value) { int id; if (ExtractIntegerValue(value, &id)) { return GetDownloadById(id); } return NULL; } std::wstring DownloadsDOMHandler::GetProgressStatusText( DownloadItem* download) { int64 total = download->total_bytes(); int64 size = download->received_bytes(); DataUnits amount_units = GetByteDisplayUnits(size); std::wstring received_size = FormatBytes(size, amount_units, true); std::wstring amount = received_size; // Adjust both strings for the locale direction since we don't yet know which // string we'll end up using for constructing the final progress string. std::wstring amount_localized; if (l10n_util::AdjustStringForLocaleDirection(amount, &amount_localized)) { amount.assign(amount_localized); received_size.assign(amount_localized); } if (total) { amount_units = GetByteDisplayUnits(total); std::wstring total_text = FormatBytes(total, amount_units, true); std::wstring total_text_localized; if (l10n_util::AdjustStringForLocaleDirection(total_text, &total_text_localized)) total_text.assign(total_text_localized); amount = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SIZE, received_size, total_text); } else { amount.assign(received_size); } amount_units = GetByteDisplayUnits(download->CurrentSpeed()); std::wstring speed_text = FormatSpeed(download->CurrentSpeed(), amount_units, true); std::wstring speed_text_localized; if (l10n_util::AdjustStringForLocaleDirection(speed_text, &speed_text_localized)) speed_text.assign(speed_text_localized); return l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SPEED, speed_text, amount); } } // namespace /////////////////////////////////////////////////////////////////////////////// // // DownloadsUI // /////////////////////////////////////////////////////////////////////////////// DownloadsUI::DownloadsUI(TabContents* contents) : DOMUI(contents) { DownloadManager* dlm = GetProfile()->GetDownloadManager(); DownloadsDOMHandler* handler = new DownloadsDOMHandler(this, dlm); AddMessageHandler(handler); handler->Init(); DownloadsUIHTMLSource* html_source = new DownloadsUIHTMLSource(); // Set up the chrome://downloads/ source. g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(&chrome_url_data_manager, &ChromeURLDataManager::AddDataSource, html_source)); }