普通文本  |  820行  |  28.38 KB

// Copyright (c) 2011 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/ui/webui/chromeos/imageburner_ui.h"

#include <algorithm>

#include "base/i18n/rtl.h"
#include "base/memory/singleton.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/task.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/download/download_types.h"
#include "chrome/browser/download/download_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/zip.h"
#include "content/browser/browser_thread.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"

static const char kPropertyPath[] = "path";
static const char kPropertyTitle[] = "title";
static const char kPropertyDirectory[] = "isDirectory";
static const char kImageBaseURL[] =
    "http://chrome-master.mtv.corp.google.com/chromeos/dev-channel/";
static const char kImageFetcherName[] = "LATEST-x86-generic";
static const char kImageDownloadURL[] =
    "https://dl.google.com/dl/chromeos/recovery/latest_mario_beta_channel";
static const char kImageFileName[] = "chromeos_image.bin.zip";
static const char kTempImageFolderName[] = "chromeos_image";

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnUIHTMLSource
//
////////////////////////////////////////////////////////////////////////////////

class ImageBurnUIHTMLSource : public ChromeURLDataManager::DataSource {
 public:
  ImageBurnUIHTMLSource()
      : DataSource(chrome::kChromeUIImageBurnerHost, MessageLoop::current()) {
  }

  // Called when the network layer has requested a resource underneath
  // the path we registered.
  virtual void StartDataRequest(const std::string& path,
                                bool is_incognito,
                                int request_id) {
    DictionaryValue localized_strings;
    localized_strings.SetString("burnConfirmText1",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_CONFIM_BURN1));
    localized_strings.SetString("burnConfirmText2",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_CONFIM_BURN2));
    localized_strings.SetString("burnUnsuccessfulMessage",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_UNSUCCESSFUL));
    localized_strings.SetString("burnSuccessfulMessage",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_SUCCESSFUL));
    localized_strings.SetString("downloadAbortedMessage",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_UNSUCCESSFUL));
    localized_strings.SetString("title",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_TITLE));
    localized_strings.SetString("listTitle",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_ROOT_LIST_TITLE));
    localized_strings.SetString("downloadStatusStart",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_STARTING_STATUS));
    localized_strings.SetString("downloadStatusInProgress",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_IN_PROGRESS_STATUS));
    localized_strings.SetString("downloadStatusComplete",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_COMPLETE_STATUS));
    localized_strings.SetString("downloadStatusCanceled",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_CANCELED_STATUS));
    localized_strings.SetString("burnStatusStart",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_STARTING_STATUS));
    localized_strings.SetString("burnStatusInProgress",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_IN_PROGRESS_STATUS));
    localized_strings.SetString("burnStatusComplete",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_COMPLETE_STATUS));
    localized_strings.SetString("burnStatusCanceled",
        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_CANCELED_STATUS));

    SetFontAndTextDirection(&localized_strings);

    static const base::StringPiece imageburn_html(
        ResourceBundle::GetSharedInstance().GetRawDataResource(
        IDR_IMAGEBURNER_HTML));
    const std::string full_html = jstemplate_builder::GetI18nTemplateHtml(
        imageburn_html, &localized_strings);

    scoped_refptr<RefCountedBytes> 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);
  }

  virtual std::string GetMimeType(const std::string&) const {
    return "text/html";
  }

 private:
  virtual ~ImageBurnUIHTMLSource() {}

  DISALLOW_COPY_AND_ASSIGN(ImageBurnUIHTMLSource);
};

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnTaskProxy
//
////////////////////////////////////////////////////////////////////////////////

class ImageBurnTaskProxy
  : public base::RefCountedThreadSafe<ImageBurnTaskProxy> {
 public:
  explicit ImageBurnTaskProxy(const base::WeakPtr<ImageBurnHandler>& handler)
      : handler_(handler) {}

  void CreateImageDir() {
    if (handler_)
      handler_->CreateImageDirOnFileThread(this);
  }

  void OnImageDirCreated(bool success) {
    if (handler_)
      handler_->OnImageDirCreatedOnUIThread(success);
  }

  void BurnImage() {
    if (handler_)
      handler_->BurnImageOnFileThread();
    DeleteOnUIThread();
  }

  void UnzipImage() {
    if (handler_)
      handler_->UnzipImageOnFileThread(this);
  }

  void UnzipComplete(bool success) {
    if (handler_)
      handler_->UnzipComplete(success);
  }

  // ImageBurnTaskProxy is created on the UI thread, so in some cases,
  // we need to post back to the UI thread for destruction.
  void DeleteOnUIThread() {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(this, &ImageBurnTaskProxy::DoNothing));
  }

  void DoNothing() {}

 private:
  base::WeakPtr<ImageBurnHandler> handler_;

  friend class base::RefCountedThreadSafe<ImageBurnTaskProxy>;
  ~ImageBurnTaskProxy() {}

  DISALLOW_COPY_AND_ASSIGN(ImageBurnTaskProxy);
};

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnHandler
//
////////////////////////////////////////////////////////////////////////////////

ImageBurnHandler::ImageBurnHandler(TabContents* contents)
    :tab_contents_(contents),
     download_manager_(NULL),
     download_item_observer_added_(false),
     active_download_item_(NULL),
     resource_manager_(NULL) {
  chromeos::CrosLibrary::Get()->GetMountLibrary()->AddObserver(this);
  chromeos::CrosLibrary::Get()->GetBurnLibrary()->AddObserver(this);
  resource_manager_ = ImageBurnResourceManager::GetInstance();
  zip_image_file_path_.clear();
  image_file_path_.clear();
  image_target_.clear();
}

ImageBurnHandler::~ImageBurnHandler() {
  chromeos::CrosLibrary::Get()->GetMountLibrary()->RemoveObserver(this);
  chromeos::CrosLibrary::Get()->GetBurnLibrary()->RemoveObserver(this);
  if (active_download_item_)
    active_download_item_->RemoveObserver(this);
  if (download_manager_)
      download_manager_->RemoveObserver(this);
}

WebUIMessageHandler* ImageBurnHandler::Attach(WebUI* web_ui) {
  return WebUIMessageHandler::Attach(web_ui);
}

void ImageBurnHandler::RegisterMessages() {
  web_ui_->RegisterMessageCallback("getRoots",
      NewCallback(this, &ImageBurnHandler::HandleGetRoots));
  web_ui_->RegisterMessageCallback("downloadImage",
      NewCallback(this, &ImageBurnHandler::HandleDownloadImage));
  web_ui_->RegisterMessageCallback("burnImage",
      NewCallback(this, &ImageBurnHandler::HandleBurnImage));
  web_ui_->RegisterMessageCallback("cancelBurnImage",
      NewCallback(this, &ImageBurnHandler::HandleCancelBurnImage));
}

void ImageBurnHandler::DiskChanged(chromeos::MountLibraryEventType event,
                                   const chromeos::MountLibrary::Disk* disk) {
  if (event == chromeos::MOUNT_DISK_REMOVED ||
      event == chromeos::MOUNT_DISK_CHANGED ||
      event == chromeos::MOUNT_DISK_UNMOUNTED) {
    web_ui_->CallJavascriptFunction("rootsChanged");
  }
}

void ImageBurnHandler::DeviceChanged(chromeos::MountLibraryEventType event,
                                     const std::string& device_path) {
  if (event == chromeos::MOUNT_DEVICE_REMOVED)
    web_ui_->CallJavascriptFunction("rootsChanged");
}


void ImageBurnHandler::ProgressUpdated(chromeos::BurnLibrary* object,
                                       chromeos::BurnEventType evt,
                                       const ImageBurnStatus& status) {
  UpdateBurnProgress(status.amount_burnt, status.total_size,
                     status.target_path, evt);
  if (evt == chromeos::BURN_COMPLETE) {
    FinalizeBurn(true);
  } else if (evt == chromeos::BURN_CANCELED) {
    FinalizeBurn(false);
  }
}

void ImageBurnHandler::OnDownloadUpdated(DownloadItem* download) {
  if (download->IsCancelled()) {
    DownloadCompleted(false);  // Should stop observation.
    DCHECK(!download_item_observer_added_);
  } else if (download->IsComplete()) {
    zip_image_file_path_ = download->full_path();
    DownloadCompleted(true);  // Should stop observation.
    DCHECK(!download_item_observer_added_);
  } else if (download->IsPartialDownload()) {
      scoped_ptr<DictionaryValue> result_value(
          download_util::CreateDownloadItemValue(download, 0));
      web_ui_->CallJavascriptFunction("downloadUpdated", *result_value);
  }
}

void ImageBurnHandler::OnDownloadOpened(DownloadItem* download) {
  if (download->safety_state() == DownloadItem::DANGEROUS)
    download->DangerousDownloadValidated();
}

void ImageBurnHandler::ModelChanged() {
  // Find our item and observe it.
  std::vector<DownloadItem*> downloads;
  download_manager_->GetTemporaryDownloads(
      resource_manager_->GetImageDir(), &downloads);
  if (download_item_observer_added_)
    return;
  for (std::vector<DownloadItem*>::const_iterator it = downloads.begin();
      it != downloads.end();
      ++it) {
    if ((*it)->original_url() == *image_download_url_) {
      download_item_observer_added_ = true;
      (*it)->AddObserver(this);
      active_download_item_ = *it;
      break;
    }
  }
}

void ImageBurnHandler::OnImageDirCreated(bool success,
                                         ImageBurnTaskProxy* task) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  // Transfer to UI thread.
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      NewRunnableMethod(task, &ImageBurnTaskProxy::OnImageDirCreated,
                        success));
}

void ImageBurnHandler::OnDownloadStarted(bool success) {
  if (success)
    resource_manager_->set_download_started(true);
  else
    DownloadCompleted(false);
}

void ImageBurnHandler::HandleGetRoots(const ListValue* args) {
  ListValue results_value;
  DictionaryValue info_value;
  chromeos::MountLibrary* mount_lib =
      chromeos::CrosLibrary::Get()->GetMountLibrary();
  const chromeos::MountLibrary::DiskMap& disks = mount_lib->disks();
  if (!resource_manager_->burn_in_progress()) {
    for (chromeos::MountLibrary::DiskMap::const_iterator iter =  disks.begin();
         iter != disks.end();
         ++iter) {
      if (iter->second->is_parent()) {
        FilePath disk_path = FilePath(iter->second->system_path()).DirName();
        std::string title = "/dev/" + disk_path.BaseName().value();
        if (!iter->second->on_boot_device()) {
          DictionaryValue* page_value = new DictionaryValue();
          page_value->SetString(std::string(kPropertyTitle), title);
          page_value->SetString(std::string(kPropertyPath), title);
          page_value->SetBoolean(std::string(kPropertyDirectory), true);
          results_value.Append(page_value);
        }
      }
    }
  }

  info_value.SetString("functionCall", "getRoots");
  info_value.SetString(std::string(kPropertyPath), "");
  web_ui_->CallJavascriptFunction("browseFileResult",
                                  info_value, results_value);
}

void ImageBurnHandler::HandleDownloadImage(const ListValue* args) {
  ExtractTargetedDeviceSystemPath(args);
  if (resource_manager_->GetImageDir().empty()) {
    // Create image dir on File thread.
    scoped_refptr<ImageBurnTaskProxy> task =
        new ImageBurnTaskProxy(AsWeakPtr());
    BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        NewRunnableMethod(task.get(), &ImageBurnTaskProxy::CreateImageDir));
  } else {
    OnImageDirCreatedOnUIThread(true);
  }
}

void ImageBurnHandler::DownloadCompleted(bool success) {
  resource_manager_->SetDownloadFinished(success);
  if (active_download_item_) {
    active_download_item_->RemoveObserver(this);
    active_download_item_ = NULL;
  }
  download_item_observer_added_ = false;
  if (download_manager_)
    download_manager_->RemoveObserver(this);

  if (success) {
    UnzipImage();
  } else {
    UnzipComplete(success);
  }
}

void ImageBurnHandler::UnzipComplete(bool success) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  DictionaryValue signal_value;
  if (success) {
    signal_value.SetString("state", "COMPLETE");
    web_ui_->CallJavascriptFunction("downloadUpdated", signal_value);
    web_ui_->CallJavascriptFunction("promptUserDownloadFinished");
  } else {
    signal_value.SetString("state", "CANCELLED");
    web_ui_->CallJavascriptFunction("downloadUpdated", signal_value);
    web_ui_->CallJavascriptFunction("alertUserDownloadAborted");
  }
}

void ImageBurnHandler::HandleBurnImage(const ListValue* args) {
  scoped_refptr<ImageBurnTaskProxy> task = new ImageBurnTaskProxy(AsWeakPtr());
  BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        NewRunnableMethod(task.get(), &ImageBurnTaskProxy::BurnImage));
}

void ImageBurnHandler::HandleCancelBurnImage(const ListValue* args) {
  image_target_.clear();
}

void ImageBurnHandler::CreateImageDirOnFileThread(ImageBurnTaskProxy* task) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  resource_manager_->CreateImageDir(this, task);
}

void ImageBurnHandler::OnImageDirCreatedOnUIThread(bool success) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (success) {
    zip_image_file_path_ =
        resource_manager_->GetImageDir().Append(kImageFileName);
    resource_manager_->CreateImageUrl(tab_contents_, this);
  } else {
    DownloadCompleted(success);
  }
}

void ImageBurnHandler::BurnImageOnFileThread() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  if (resource_manager_->burn_in_progress())
    return;
  resource_manager_->set_burn_in_progress(true);

  if (chromeos::CrosLibrary::Get()->GetBurnLibrary()->
      DoBurn(image_file_path_, image_target_)) {
    DictionaryValue signal_value;
    signal_value.SetString("state", "IN_PROGRESS");
    signal_value.SetString("path", image_target_.value());
    signal_value.SetInteger("received", 0);
    signal_value.SetString("progress_status_text", "");
    web_ui_->CallJavascriptFunction("burnProgressUpdated", signal_value);
  }
}

void ImageBurnHandler::FinalizeBurn(bool successful) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  web_ui_->CallJavascriptFunction(successful ? "burnSuccessful"
                                             : "burnUnsuccessful");
  resource_manager_->set_burn_in_progress(false);
}

void ImageBurnHandler::UpdateBurnProgress(int64 total_burnt,
                                          int64 image_size,
                                          const std::string& path,
                                          chromeos::BurnEventType event) {
  DictionaryValue progress_value;
  progress_value.SetString("progress_status_text",
      GetBurnProgressText(total_burnt, image_size));
  if (event == chromeos::BURN_UPDATED)
    progress_value.SetString("state", "IN_PROGRESS");
  else if (event == chromeos::BURN_CANCELED)
    progress_value.SetString("state", "CANCELLED");
  else if (event == chromeos::BURN_COMPLETE)
    progress_value.SetString("state", "COMPLETE");
  progress_value.SetInteger("received", total_burnt);
  progress_value.SetInteger("total", image_size);
  progress_value.SetString("path", path);

  web_ui_->CallJavascriptFunction("burnProgressUpdated", progress_value);
}

string16 ImageBurnHandler::GetBurnProgressText(int64 total_burnt,
                                               int64 image_size) {
  DataUnits amount_units = GetByteDisplayUnits(total_burnt);
  string16 burnt_size = FormatBytes(total_burnt, amount_units, true);

  base::i18n::AdjustStringForLocaleDirection(&burnt_size);

  if (image_size) {
    amount_units = GetByteDisplayUnits(image_size);
    string16 total_text = FormatBytes(image_size, amount_units, true);
    base::i18n::AdjustStringForLocaleDirection(&total_text);

    return l10n_util::GetStringFUTF16(IDS_IMAGEBURN_BURN_PROGRESS,
                                      burnt_size,
                                      total_text);
  } else {
    return l10n_util::GetStringFUTF16(IDS_IMAGEBURN_BURN_PROGRESS_SIZE_UNKNOWN,
                                      burnt_size);
  }
}

void ImageBurnHandler::OnImageUrlCreated(GURL* image_url, bool success) {
  if (!success) {
    DownloadCompleted(false);
    return;
  }
  image_download_url_ = image_url;

  download_manager_ = tab_contents_->profile()->GetDownloadManager();
  download_manager_->AddObserver(this);

  if (!resource_manager_->download_started()) {
    resource_manager_->set_download_started(true);
    if (!resource_manager_->image_download_requested()) {
      resource_manager_->set_image_download_requested(true);
      ImageBurnDownloader::GetInstance()->AddListener(this,
          *image_download_url_);
      ImageBurnDownloader::GetInstance()->DownloadFile(*image_download_url_,
                                                       zip_image_file_path_,
                                                       tab_contents_);
    }
  } else if (resource_manager_->download_finished()) {
    DownloadCompleted(true);
  }
}

void ImageBurnHandler::ExtractTargetedDeviceSystemPath(
    const ListValue* list_value) {
  Value* list_member;
  if (list_value->Get(0, &list_member) &&
      list_member->GetType() == Value::TYPE_STRING) {
    const StringValue* string_value =
        static_cast<const StringValue*>(list_member);
    std::string image_dest;
    string_value->GetAsString(&image_dest);
    image_target_ = FilePath(image_dest);
  } else {
    LOG(ERROR) << "Unable to get path string";
  }
}

void ImageBurnHandler::UnzipImage() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  scoped_refptr<ImageBurnTaskProxy> task = new ImageBurnTaskProxy(AsWeakPtr());
  BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        NewRunnableMethod(task.get(), &ImageBurnTaskProxy::UnzipImage));
}

void ImageBurnHandler::UnzipImageOnFileThread(ImageBurnTaskProxy* task) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  bool success = UnzipImageImpl();
  BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(task, &ImageBurnTaskProxy::UnzipComplete, success));
}

bool ImageBurnHandler::UnzipImageImpl() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  const FilePath& img_dir = resource_manager_->GetImageDir();
  if (!Unzip(zip_image_file_path_, img_dir))
    return false;

  image_file_path_.clear();
  file_util::FileEnumerator file_enumerator(
      img_dir, false,  // recursive
      file_util::FileEnumerator::FILES);
  for (FilePath path = file_enumerator.Next();
      !path.empty();
       path = file_enumerator.Next()) {
    if (path != zip_image_file_path_) {
      image_file_path_ = path;
      break;
    }
  }
  return !image_file_path_.empty();
}

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnResourceManager
//
////////////////////////////////////////////////////////////////////////////////

ImageBurnResourceManager::ImageBurnResourceManager()
    : image_download_requested_(false),
      download_started_(false),
      download_finished_(false),
      burn_in_progress_(false),
      download_manager_(NULL),
      download_item_observer_added_(false),
      active_download_item_(NULL),
      image_url_(new GURL(kImageDownloadURL)),
      config_file_url_(std::string(kImageBaseURL) + kImageFetcherName),
      config_file_requested_(false),
      config_file_fetched_(true) {
  image_dir_.clear();
}

ImageBurnResourceManager::~ImageBurnResourceManager() {
  if (!image_dir_.empty()) {
    file_util::Delete(image_dir_, true);
  }
  if (active_download_item_)
    active_download_item_->RemoveObserver(this);
  if (download_manager_)
    download_manager_->RemoveObserver(this);
}

// static
ImageBurnResourceManager* ImageBurnResourceManager::GetInstance() {
  return Singleton<ImageBurnResourceManager>::get();
}

void ImageBurnResourceManager::OnDownloadUpdated(DownloadItem* download) {
  if (download->IsCancelled()) {
    image_url_.reset();
    ConfigFileFetched(false);

    // ConfigFileFetched should remove observer.
    DCHECK(!download_item_observer_added_);
    DCHECK(active_download_item_ == NULL);
  } else if (download->IsComplete()) {
    std::string image_url;
    if (file_util::ReadFileToString(config_file_path_, &image_url)) {
      image_url_.reset(new GURL(std::string(kImageBaseURL) + image_url));
      ConfigFileFetched(true);
    } else {
      image_url_.reset();
      ConfigFileFetched(false);
    }
  }
}

void ImageBurnResourceManager::ModelChanged() {
  std::vector<DownloadItem*> downloads;
  download_manager_->GetTemporaryDownloads(GetImageDir(), &downloads);
  if (download_item_observer_added_)
    return;
  for (std::vector<DownloadItem*>::const_iterator it = downloads.begin();
      it != downloads.end();
      ++it) {
    if ((*it)->url() == config_file_url_) {
      download_item_observer_added_ = true;
      (*it)->AddObserver(this);
      active_download_item_ = *it;
      break;
    }
  }
}

void ImageBurnResourceManager::OnDownloadStarted(bool success) {
  if (!success)
    ConfigFileFetched(false);
}

void ImageBurnResourceManager::CreateImageDir(
    Delegate* delegate,
    ImageBurnTaskProxy* task) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));

  bool success = true;
  if (image_dir_.empty()) {
    CHECK(PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &image_dir_));
    image_dir_ = image_dir_.Append(kTempImageFolderName);
    success = file_util::CreateDirectory(image_dir_);
  }
  delegate->OnImageDirCreated(success, task);
}

const FilePath& ImageBurnResourceManager::GetImageDir() {
  return image_dir_;
}

void ImageBurnResourceManager::SetDownloadFinished(bool finished) {
  if (!download_started_)
    return;
  if (!finished)
    download_started_ = false;
  download_finished_ = finished;
}

void ImageBurnResourceManager::CreateImageUrl(TabContents* tab_contents,
    Delegate* delegate) {
  if (config_file_fetched_) {
    delegate->OnImageUrlCreated(image_url_.get(), true);
    return;
  }
  downloaders_.push_back(delegate);

  if (config_file_requested_)
    return;
  config_file_requested_ = true;

  config_file_path_ = GetImageDir().Append(kImageFetcherName);

  download_manager_ = tab_contents->profile()->GetDownloadManager();
  download_manager_->AddObserver(this);

  ImageBurnDownloader* downloader = ImageBurnDownloader::GetInstance();
  downloader->AddListener(this, config_file_url_);
  downloader->DownloadFile(config_file_url_, config_file_path_, tab_contents);
}

void ImageBurnResourceManager::ConfigFileFetched(bool fetched) {
  if (active_download_item_) {
    active_download_item_->RemoveObserver(this);
    active_download_item_ = NULL;
  }
  download_item_observer_added_ = false;
  if (download_manager_)
    download_manager_->RemoveObserver(this);
  if (!fetched)
    config_file_requested_ = false;
  config_file_fetched_ = fetched;
  for (size_t i = 0; i < downloaders_.size(); ++i)
    downloaders_[i]->OnImageUrlCreated(image_url_.get(), fetched);
  downloaders_.clear();
}

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnDownloaderTaskProxy
//
////////////////////////////////////////////////////////////////////////////////

class ImageBurnDownloaderTaskProxy
    : public base::RefCountedThreadSafe<ImageBurnDownloaderTaskProxy> {
 public:
  explicit ImageBurnDownloaderTaskProxy() {}

  void CreateFileStream(const GURL& url,
                        const FilePath& target_path,
                        TabContents* tab_contents) {
    ImageBurnDownloader::GetInstance()->CreateFileStreamOnFileThread(url,
        target_path, tab_contents, this);
  }

  void OnFileStreamCreated(const GURL& url,
                           const FilePath& file_path,
                           TabContents* tab_contents,
                           net::FileStream* created_file_stream) {
    ImageBurnDownloader::GetInstance()->OnFileStreamCreatedOnUIThread(url,
        file_path, tab_contents, created_file_stream);
  }

 private:
  ~ImageBurnDownloaderTaskProxy() {}

  friend class base::RefCountedThreadSafe<ImageBurnDownloaderTaskProxy>;

  DISALLOW_COPY_AND_ASSIGN(ImageBurnDownloaderTaskProxy);
};

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnDownloader
//
////////////////////////////////////////////////////////////////////////////////

// static
ImageBurnDownloader* ImageBurnDownloader::GetInstance() {
  return Singleton<ImageBurnDownloader>::get();
}

void ImageBurnDownloader::DownloadFile(const GURL& url,
    const FilePath& file_path, TabContents* tab_contents) {
  // First we have to create file stream we will download file to.
  // That has to be done on File thread.
  scoped_refptr<ImageBurnDownloaderTaskProxy> task =
      new ImageBurnDownloaderTaskProxy();
  BrowserThread::PostTask(
      BrowserThread::FILE, FROM_HERE,
      NewRunnableMethod(task.get(),
          &ImageBurnDownloaderTaskProxy::CreateFileStream, url, file_path,
          tab_contents));
}

void ImageBurnDownloader::CreateFileStreamOnFileThread(
    const GURL& url, const FilePath& file_path,
    TabContents* tab_contents, ImageBurnDownloaderTaskProxy* task) {

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  DCHECK(!file_path.empty());

  scoped_ptr<net::FileStream> file_stream(new net::FileStream);
  if (file_stream->Open(file_path, base::PLATFORM_FILE_CREATE_ALWAYS |
                        base::PLATFORM_FILE_WRITE))
    file_stream.reset(NULL);

  // Call callback method on UI thread.
  BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(task,
            &ImageBurnDownloaderTaskProxy::OnFileStreamCreated,
            url, file_path, tab_contents, file_stream.release()));
}

void ImageBurnDownloader::OnFileStreamCreatedOnUIThread(const GURL& url,
    const FilePath& file_path, TabContents* tab_contents,
    net::FileStream* created_file_stream) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (created_file_stream) {
    DownloadManager* download_manager =
        tab_contents->profile()->GetDownloadManager();
    DownloadSaveInfo save_info;
    save_info.file_path = file_path;
    save_info.file_stream = linked_ptr<net::FileStream>(created_file_stream);
    DownloadStarted(true, url);
    download_manager->DownloadUrlToFile(url,
                                        tab_contents->GetURL(),
                                        tab_contents->encoding(),
                                        save_info,
                                        tab_contents);
  } else {
    DownloadStarted(false, url);
  }
}

void ImageBurnDownloader::AddListener(Listener* listener, const GURL& url) {
  listeners_.insert(std::make_pair(url, listener));
}

void ImageBurnDownloader::DownloadStarted(bool success, const GURL& url) {
  std::pair<ListenerMap::iterator, ListenerMap::iterator> listener_range =
      listeners_.equal_range(url);
  for (ListenerMap::iterator current_listener = listener_range.first;
       current_listener != listener_range.second;
       ++current_listener) {
    current_listener->second->OnDownloadStarted(success);
  }
  listeners_.erase(listener_range.first, listener_range.second);
}

////////////////////////////////////////////////////////////////////////////////
//
// ImageBurnUI
//
////////////////////////////////////////////////////////////////////////////////
ImageBurnUI::ImageBurnUI(TabContents* contents) : WebUI(contents) {
  ImageBurnHandler* handler = new ImageBurnHandler(contents);
  AddMessageHandler((handler)->Attach(this));
  ImageBurnUIHTMLSource* html_source = new ImageBurnUIHTMLSource();
  contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
}