1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/webui/chromeos/imageburner_ui.h"
6
7#include <algorithm>
8
9#include "base/i18n/rtl.h"
10#include "base/memory/singleton.h"
11#include "base/message_loop.h"
12#include "base/path_service.h"
13#include "base/string_util.h"
14#include "base/task.h"
15#include "base/utf_string_conversions.h"
16#include "base/values.h"
17#include "chrome/browser/download/download_types.h"
18#include "chrome/browser/download/download_util.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/common/chrome_paths.h"
21#include "chrome/common/jstemplate_builder.h"
22#include "chrome/common/url_constants.h"
23#include "chrome/common/zip.h"
24#include "content/browser/browser_thread.h"
25#include "content/browser/tab_contents/tab_contents.h"
26#include "grit/browser_resources.h"
27#include "grit/generated_resources.h"
28#include "grit/locale_settings.h"
29#include "ui/base/l10n/l10n_util.h"
30#include "ui/base/resource/resource_bundle.h"
31
32static const char kPropertyPath[] = "path";
33static const char kPropertyTitle[] = "title";
34static const char kPropertyDirectory[] = "isDirectory";
35static const char kImageBaseURL[] =
36    "http://chrome-master.mtv.corp.google.com/chromeos/dev-channel/";
37static const char kImageFetcherName[] = "LATEST-x86-generic";
38static const char kImageDownloadURL[] =
39    "https://dl.google.com/dl/chromeos/recovery/latest_mario_beta_channel";
40static const char kImageFileName[] = "chromeos_image.bin.zip";
41static const char kTempImageFolderName[] = "chromeos_image";
42
43////////////////////////////////////////////////////////////////////////////////
44//
45// ImageBurnUIHTMLSource
46//
47////////////////////////////////////////////////////////////////////////////////
48
49class ImageBurnUIHTMLSource : public ChromeURLDataManager::DataSource {
50 public:
51  ImageBurnUIHTMLSource()
52      : DataSource(chrome::kChromeUIImageBurnerHost, MessageLoop::current()) {
53  }
54
55  // Called when the network layer has requested a resource underneath
56  // the path we registered.
57  virtual void StartDataRequest(const std::string& path,
58                                bool is_incognito,
59                                int request_id) {
60    DictionaryValue localized_strings;
61    localized_strings.SetString("burnConfirmText1",
62        l10n_util::GetStringUTF16(IDS_IMAGEBURN_CONFIM_BURN1));
63    localized_strings.SetString("burnConfirmText2",
64        l10n_util::GetStringUTF16(IDS_IMAGEBURN_CONFIM_BURN2));
65    localized_strings.SetString("burnUnsuccessfulMessage",
66        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_UNSUCCESSFUL));
67    localized_strings.SetString("burnSuccessfulMessage",
68        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_SUCCESSFUL));
69    localized_strings.SetString("downloadAbortedMessage",
70        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_UNSUCCESSFUL));
71    localized_strings.SetString("title",
72        l10n_util::GetStringUTF16(IDS_IMAGEBURN_TITLE));
73    localized_strings.SetString("listTitle",
74        l10n_util::GetStringUTF16(IDS_IMAGEBURN_ROOT_LIST_TITLE));
75    localized_strings.SetString("downloadStatusStart",
76        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_STARTING_STATUS));
77    localized_strings.SetString("downloadStatusInProgress",
78        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_IN_PROGRESS_STATUS));
79    localized_strings.SetString("downloadStatusComplete",
80        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_COMPLETE_STATUS));
81    localized_strings.SetString("downloadStatusCanceled",
82        l10n_util::GetStringUTF16(IDS_IMAGEBURN_DOWNLOAD_CANCELED_STATUS));
83    localized_strings.SetString("burnStatusStart",
84        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_STARTING_STATUS));
85    localized_strings.SetString("burnStatusInProgress",
86        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_IN_PROGRESS_STATUS));
87    localized_strings.SetString("burnStatusComplete",
88        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_COMPLETE_STATUS));
89    localized_strings.SetString("burnStatusCanceled",
90        l10n_util::GetStringUTF16(IDS_IMAGEBURN_BURN_CANCELED_STATUS));
91
92    SetFontAndTextDirection(&localized_strings);
93
94    static const base::StringPiece imageburn_html(
95        ResourceBundle::GetSharedInstance().GetRawDataResource(
96        IDR_IMAGEBURNER_HTML));
97    const std::string full_html = jstemplate_builder::GetI18nTemplateHtml(
98        imageburn_html, &localized_strings);
99
100    scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes);
101    html_bytes->data.resize(full_html.size());
102    std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin());
103
104    SendResponse(request_id, html_bytes);
105  }
106
107  virtual std::string GetMimeType(const std::string&) const {
108    return "text/html";
109  }
110
111 private:
112  virtual ~ImageBurnUIHTMLSource() {}
113
114  DISALLOW_COPY_AND_ASSIGN(ImageBurnUIHTMLSource);
115};
116
117////////////////////////////////////////////////////////////////////////////////
118//
119// ImageBurnTaskProxy
120//
121////////////////////////////////////////////////////////////////////////////////
122
123class ImageBurnTaskProxy
124  : public base::RefCountedThreadSafe<ImageBurnTaskProxy> {
125 public:
126  explicit ImageBurnTaskProxy(const base::WeakPtr<ImageBurnHandler>& handler)
127      : handler_(handler) {}
128
129  void CreateImageDir() {
130    if (handler_)
131      handler_->CreateImageDirOnFileThread(this);
132  }
133
134  void OnImageDirCreated(bool success) {
135    if (handler_)
136      handler_->OnImageDirCreatedOnUIThread(success);
137  }
138
139  void BurnImage() {
140    if (handler_)
141      handler_->BurnImageOnFileThread();
142    DeleteOnUIThread();
143  }
144
145  void UnzipImage() {
146    if (handler_)
147      handler_->UnzipImageOnFileThread(this);
148  }
149
150  void UnzipComplete(bool success) {
151    if (handler_)
152      handler_->UnzipComplete(success);
153  }
154
155  // ImageBurnTaskProxy is created on the UI thread, so in some cases,
156  // we need to post back to the UI thread for destruction.
157  void DeleteOnUIThread() {
158    BrowserThread::PostTask(
159        BrowserThread::UI, FROM_HERE,
160        NewRunnableMethod(this, &ImageBurnTaskProxy::DoNothing));
161  }
162
163  void DoNothing() {}
164
165 private:
166  base::WeakPtr<ImageBurnHandler> handler_;
167
168  friend class base::RefCountedThreadSafe<ImageBurnTaskProxy>;
169  ~ImageBurnTaskProxy() {}
170
171  DISALLOW_COPY_AND_ASSIGN(ImageBurnTaskProxy);
172};
173
174////////////////////////////////////////////////////////////////////////////////
175//
176// ImageBurnHandler
177//
178////////////////////////////////////////////////////////////////////////////////
179
180ImageBurnHandler::ImageBurnHandler(TabContents* contents)
181    :tab_contents_(contents),
182     download_manager_(NULL),
183     download_item_observer_added_(false),
184     active_download_item_(NULL),
185     resource_manager_(NULL) {
186  chromeos::CrosLibrary::Get()->GetMountLibrary()->AddObserver(this);
187  chromeos::CrosLibrary::Get()->GetBurnLibrary()->AddObserver(this);
188  resource_manager_ = ImageBurnResourceManager::GetInstance();
189  zip_image_file_path_.clear();
190  image_file_path_.clear();
191  image_target_.clear();
192}
193
194ImageBurnHandler::~ImageBurnHandler() {
195  chromeos::CrosLibrary::Get()->GetMountLibrary()->RemoveObserver(this);
196  chromeos::CrosLibrary::Get()->GetBurnLibrary()->RemoveObserver(this);
197  if (active_download_item_)
198    active_download_item_->RemoveObserver(this);
199  if (download_manager_)
200      download_manager_->RemoveObserver(this);
201}
202
203WebUIMessageHandler* ImageBurnHandler::Attach(WebUI* web_ui) {
204  return WebUIMessageHandler::Attach(web_ui);
205}
206
207void ImageBurnHandler::RegisterMessages() {
208  web_ui_->RegisterMessageCallback("getRoots",
209      NewCallback(this, &ImageBurnHandler::HandleGetRoots));
210  web_ui_->RegisterMessageCallback("downloadImage",
211      NewCallback(this, &ImageBurnHandler::HandleDownloadImage));
212  web_ui_->RegisterMessageCallback("burnImage",
213      NewCallback(this, &ImageBurnHandler::HandleBurnImage));
214  web_ui_->RegisterMessageCallback("cancelBurnImage",
215      NewCallback(this, &ImageBurnHandler::HandleCancelBurnImage));
216}
217
218void ImageBurnHandler::DiskChanged(chromeos::MountLibraryEventType event,
219                                   const chromeos::MountLibrary::Disk* disk) {
220  if (event == chromeos::MOUNT_DISK_REMOVED ||
221      event == chromeos::MOUNT_DISK_CHANGED ||
222      event == chromeos::MOUNT_DISK_UNMOUNTED) {
223    web_ui_->CallJavascriptFunction("rootsChanged");
224  }
225}
226
227void ImageBurnHandler::DeviceChanged(chromeos::MountLibraryEventType event,
228                                     const std::string& device_path) {
229  if (event == chromeos::MOUNT_DEVICE_REMOVED)
230    web_ui_->CallJavascriptFunction("rootsChanged");
231}
232
233
234void ImageBurnHandler::ProgressUpdated(chromeos::BurnLibrary* object,
235                                       chromeos::BurnEventType evt,
236                                       const ImageBurnStatus& status) {
237  UpdateBurnProgress(status.amount_burnt, status.total_size,
238                     status.target_path, evt);
239  if (evt == chromeos::BURN_COMPLETE) {
240    FinalizeBurn(true);
241  } else if (evt == chromeos::BURN_CANCELED) {
242    FinalizeBurn(false);
243  }
244}
245
246void ImageBurnHandler::OnDownloadUpdated(DownloadItem* download) {
247  if (download->IsCancelled()) {
248    DownloadCompleted(false);  // Should stop observation.
249    DCHECK(!download_item_observer_added_);
250  } else if (download->IsComplete()) {
251    zip_image_file_path_ = download->full_path();
252    DownloadCompleted(true);  // Should stop observation.
253    DCHECK(!download_item_observer_added_);
254  } else if (download->IsPartialDownload()) {
255      scoped_ptr<DictionaryValue> result_value(
256          download_util::CreateDownloadItemValue(download, 0));
257      web_ui_->CallJavascriptFunction("downloadUpdated", *result_value);
258  }
259}
260
261void ImageBurnHandler::OnDownloadOpened(DownloadItem* download) {
262  if (download->safety_state() == DownloadItem::DANGEROUS)
263    download->DangerousDownloadValidated();
264}
265
266void ImageBurnHandler::ModelChanged() {
267  // Find our item and observe it.
268  std::vector<DownloadItem*> downloads;
269  download_manager_->GetTemporaryDownloads(
270      resource_manager_->GetImageDir(), &downloads);
271  if (download_item_observer_added_)
272    return;
273  for (std::vector<DownloadItem*>::const_iterator it = downloads.begin();
274      it != downloads.end();
275      ++it) {
276    if ((*it)->original_url() == *image_download_url_) {
277      download_item_observer_added_ = true;
278      (*it)->AddObserver(this);
279      active_download_item_ = *it;
280      break;
281    }
282  }
283}
284
285void ImageBurnHandler::OnImageDirCreated(bool success,
286                                         ImageBurnTaskProxy* task) {
287  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
288  // Transfer to UI thread.
289  BrowserThread::PostTask(
290      BrowserThread::UI, FROM_HERE,
291      NewRunnableMethod(task, &ImageBurnTaskProxy::OnImageDirCreated,
292                        success));
293}
294
295void ImageBurnHandler::OnDownloadStarted(bool success) {
296  if (success)
297    resource_manager_->set_download_started(true);
298  else
299    DownloadCompleted(false);
300}
301
302void ImageBurnHandler::HandleGetRoots(const ListValue* args) {
303  ListValue results_value;
304  DictionaryValue info_value;
305  chromeos::MountLibrary* mount_lib =
306      chromeos::CrosLibrary::Get()->GetMountLibrary();
307  const chromeos::MountLibrary::DiskMap& disks = mount_lib->disks();
308  if (!resource_manager_->burn_in_progress()) {
309    for (chromeos::MountLibrary::DiskMap::const_iterator iter =  disks.begin();
310         iter != disks.end();
311         ++iter) {
312      if (iter->second->is_parent()) {
313        FilePath disk_path = FilePath(iter->second->system_path()).DirName();
314        std::string title = "/dev/" + disk_path.BaseName().value();
315        if (!iter->second->on_boot_device()) {
316          DictionaryValue* page_value = new DictionaryValue();
317          page_value->SetString(std::string(kPropertyTitle), title);
318          page_value->SetString(std::string(kPropertyPath), title);
319          page_value->SetBoolean(std::string(kPropertyDirectory), true);
320          results_value.Append(page_value);
321        }
322      }
323    }
324  }
325
326  info_value.SetString("functionCall", "getRoots");
327  info_value.SetString(std::string(kPropertyPath), "");
328  web_ui_->CallJavascriptFunction("browseFileResult",
329                                  info_value, results_value);
330}
331
332void ImageBurnHandler::HandleDownloadImage(const ListValue* args) {
333  ExtractTargetedDeviceSystemPath(args);
334  if (resource_manager_->GetImageDir().empty()) {
335    // Create image dir on File thread.
336    scoped_refptr<ImageBurnTaskProxy> task =
337        new ImageBurnTaskProxy(AsWeakPtr());
338    BrowserThread::PostTask(
339        BrowserThread::FILE, FROM_HERE,
340        NewRunnableMethod(task.get(), &ImageBurnTaskProxy::CreateImageDir));
341  } else {
342    OnImageDirCreatedOnUIThread(true);
343  }
344}
345
346void ImageBurnHandler::DownloadCompleted(bool success) {
347  resource_manager_->SetDownloadFinished(success);
348  if (active_download_item_) {
349    active_download_item_->RemoveObserver(this);
350    active_download_item_ = NULL;
351  }
352  download_item_observer_added_ = false;
353  if (download_manager_)
354    download_manager_->RemoveObserver(this);
355
356  if (success) {
357    UnzipImage();
358  } else {
359    UnzipComplete(success);
360  }
361}
362
363void ImageBurnHandler::UnzipComplete(bool success) {
364  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
365
366  DictionaryValue signal_value;
367  if (success) {
368    signal_value.SetString("state", "COMPLETE");
369    web_ui_->CallJavascriptFunction("downloadUpdated", signal_value);
370    web_ui_->CallJavascriptFunction("promptUserDownloadFinished");
371  } else {
372    signal_value.SetString("state", "CANCELLED");
373    web_ui_->CallJavascriptFunction("downloadUpdated", signal_value);
374    web_ui_->CallJavascriptFunction("alertUserDownloadAborted");
375  }
376}
377
378void ImageBurnHandler::HandleBurnImage(const ListValue* args) {
379  scoped_refptr<ImageBurnTaskProxy> task = new ImageBurnTaskProxy(AsWeakPtr());
380  BrowserThread::PostTask(
381        BrowserThread::FILE, FROM_HERE,
382        NewRunnableMethod(task.get(), &ImageBurnTaskProxy::BurnImage));
383}
384
385void ImageBurnHandler::HandleCancelBurnImage(const ListValue* args) {
386  image_target_.clear();
387}
388
389void ImageBurnHandler::CreateImageDirOnFileThread(ImageBurnTaskProxy* task) {
390  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
391
392  resource_manager_->CreateImageDir(this, task);
393}
394
395void ImageBurnHandler::OnImageDirCreatedOnUIThread(bool success) {
396  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
397  if (success) {
398    zip_image_file_path_ =
399        resource_manager_->GetImageDir().Append(kImageFileName);
400    resource_manager_->CreateImageUrl(tab_contents_, this);
401  } else {
402    DownloadCompleted(success);
403  }
404}
405
406void ImageBurnHandler::BurnImageOnFileThread() {
407  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
408
409  if (resource_manager_->burn_in_progress())
410    return;
411  resource_manager_->set_burn_in_progress(true);
412
413  if (chromeos::CrosLibrary::Get()->GetBurnLibrary()->
414      DoBurn(image_file_path_, image_target_)) {
415    DictionaryValue signal_value;
416    signal_value.SetString("state", "IN_PROGRESS");
417    signal_value.SetString("path", image_target_.value());
418    signal_value.SetInteger("received", 0);
419    signal_value.SetString("progress_status_text", "");
420    web_ui_->CallJavascriptFunction("burnProgressUpdated", signal_value);
421  }
422}
423
424void ImageBurnHandler::FinalizeBurn(bool successful) {
425  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
426  web_ui_->CallJavascriptFunction(successful ? "burnSuccessful"
427                                             : "burnUnsuccessful");
428  resource_manager_->set_burn_in_progress(false);
429}
430
431void ImageBurnHandler::UpdateBurnProgress(int64 total_burnt,
432                                          int64 image_size,
433                                          const std::string& path,
434                                          chromeos::BurnEventType event) {
435  DictionaryValue progress_value;
436  progress_value.SetString("progress_status_text",
437      GetBurnProgressText(total_burnt, image_size));
438  if (event == chromeos::BURN_UPDATED)
439    progress_value.SetString("state", "IN_PROGRESS");
440  else if (event == chromeos::BURN_CANCELED)
441    progress_value.SetString("state", "CANCELLED");
442  else if (event == chromeos::BURN_COMPLETE)
443    progress_value.SetString("state", "COMPLETE");
444  progress_value.SetInteger("received", total_burnt);
445  progress_value.SetInteger("total", image_size);
446  progress_value.SetString("path", path);
447
448  web_ui_->CallJavascriptFunction("burnProgressUpdated", progress_value);
449}
450
451string16 ImageBurnHandler::GetBurnProgressText(int64 total_burnt,
452                                               int64 image_size) {
453  DataUnits amount_units = GetByteDisplayUnits(total_burnt);
454  string16 burnt_size = FormatBytes(total_burnt, amount_units, true);
455
456  base::i18n::AdjustStringForLocaleDirection(&burnt_size);
457
458  if (image_size) {
459    amount_units = GetByteDisplayUnits(image_size);
460    string16 total_text = FormatBytes(image_size, amount_units, true);
461    base::i18n::AdjustStringForLocaleDirection(&total_text);
462
463    return l10n_util::GetStringFUTF16(IDS_IMAGEBURN_BURN_PROGRESS,
464                                      burnt_size,
465                                      total_text);
466  } else {
467    return l10n_util::GetStringFUTF16(IDS_IMAGEBURN_BURN_PROGRESS_SIZE_UNKNOWN,
468                                      burnt_size);
469  }
470}
471
472void ImageBurnHandler::OnImageUrlCreated(GURL* image_url, bool success) {
473  if (!success) {
474    DownloadCompleted(false);
475    return;
476  }
477  image_download_url_ = image_url;
478
479  download_manager_ = tab_contents_->profile()->GetDownloadManager();
480  download_manager_->AddObserver(this);
481
482  if (!resource_manager_->download_started()) {
483    resource_manager_->set_download_started(true);
484    if (!resource_manager_->image_download_requested()) {
485      resource_manager_->set_image_download_requested(true);
486      ImageBurnDownloader::GetInstance()->AddListener(this,
487          *image_download_url_);
488      ImageBurnDownloader::GetInstance()->DownloadFile(*image_download_url_,
489                                                       zip_image_file_path_,
490                                                       tab_contents_);
491    }
492  } else if (resource_manager_->download_finished()) {
493    DownloadCompleted(true);
494  }
495}
496
497void ImageBurnHandler::ExtractTargetedDeviceSystemPath(
498    const ListValue* list_value) {
499  Value* list_member;
500  if (list_value->Get(0, &list_member) &&
501      list_member->GetType() == Value::TYPE_STRING) {
502    const StringValue* string_value =
503        static_cast<const StringValue*>(list_member);
504    std::string image_dest;
505    string_value->GetAsString(&image_dest);
506    image_target_ = FilePath(image_dest);
507  } else {
508    LOG(ERROR) << "Unable to get path string";
509  }
510}
511
512void ImageBurnHandler::UnzipImage() {
513  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
514
515  scoped_refptr<ImageBurnTaskProxy> task = new ImageBurnTaskProxy(AsWeakPtr());
516  BrowserThread::PostTask(
517        BrowserThread::FILE, FROM_HERE,
518        NewRunnableMethod(task.get(), &ImageBurnTaskProxy::UnzipImage));
519}
520
521void ImageBurnHandler::UnzipImageOnFileThread(ImageBurnTaskProxy* task) {
522  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
523
524  bool success = UnzipImageImpl();
525  BrowserThread::PostTask(
526        BrowserThread::UI, FROM_HERE,
527        NewRunnableMethod(task, &ImageBurnTaskProxy::UnzipComplete, success));
528}
529
530bool ImageBurnHandler::UnzipImageImpl() {
531  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
532
533  const FilePath& img_dir = resource_manager_->GetImageDir();
534  if (!Unzip(zip_image_file_path_, img_dir))
535    return false;
536
537  image_file_path_.clear();
538  file_util::FileEnumerator file_enumerator(
539      img_dir, false,  // recursive
540      file_util::FileEnumerator::FILES);
541  for (FilePath path = file_enumerator.Next();
542      !path.empty();
543       path = file_enumerator.Next()) {
544    if (path != zip_image_file_path_) {
545      image_file_path_ = path;
546      break;
547    }
548  }
549  return !image_file_path_.empty();
550}
551
552////////////////////////////////////////////////////////////////////////////////
553//
554// ImageBurnResourceManager
555//
556////////////////////////////////////////////////////////////////////////////////
557
558ImageBurnResourceManager::ImageBurnResourceManager()
559    : image_download_requested_(false),
560      download_started_(false),
561      download_finished_(false),
562      burn_in_progress_(false),
563      download_manager_(NULL),
564      download_item_observer_added_(false),
565      active_download_item_(NULL),
566      image_url_(new GURL(kImageDownloadURL)),
567      config_file_url_(std::string(kImageBaseURL) + kImageFetcherName),
568      config_file_requested_(false),
569      config_file_fetched_(true) {
570  image_dir_.clear();
571}
572
573ImageBurnResourceManager::~ImageBurnResourceManager() {
574  if (!image_dir_.empty()) {
575    file_util::Delete(image_dir_, true);
576  }
577  if (active_download_item_)
578    active_download_item_->RemoveObserver(this);
579  if (download_manager_)
580    download_manager_->RemoveObserver(this);
581}
582
583// static
584ImageBurnResourceManager* ImageBurnResourceManager::GetInstance() {
585  return Singleton<ImageBurnResourceManager>::get();
586}
587
588void ImageBurnResourceManager::OnDownloadUpdated(DownloadItem* download) {
589  if (download->IsCancelled()) {
590    image_url_.reset();
591    ConfigFileFetched(false);
592
593    // ConfigFileFetched should remove observer.
594    DCHECK(!download_item_observer_added_);
595    DCHECK(active_download_item_ == NULL);
596  } else if (download->IsComplete()) {
597    std::string image_url;
598    if (file_util::ReadFileToString(config_file_path_, &image_url)) {
599      image_url_.reset(new GURL(std::string(kImageBaseURL) + image_url));
600      ConfigFileFetched(true);
601    } else {
602      image_url_.reset();
603      ConfigFileFetched(false);
604    }
605  }
606}
607
608void ImageBurnResourceManager::ModelChanged() {
609  std::vector<DownloadItem*> downloads;
610  download_manager_->GetTemporaryDownloads(GetImageDir(), &downloads);
611  if (download_item_observer_added_)
612    return;
613  for (std::vector<DownloadItem*>::const_iterator it = downloads.begin();
614      it != downloads.end();
615      ++it) {
616    if ((*it)->url() == config_file_url_) {
617      download_item_observer_added_ = true;
618      (*it)->AddObserver(this);
619      active_download_item_ = *it;
620      break;
621    }
622  }
623}
624
625void ImageBurnResourceManager::OnDownloadStarted(bool success) {
626  if (!success)
627    ConfigFileFetched(false);
628}
629
630void ImageBurnResourceManager::CreateImageDir(
631    Delegate* delegate,
632    ImageBurnTaskProxy* task) {
633  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
634
635  bool success = true;
636  if (image_dir_.empty()) {
637    CHECK(PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &image_dir_));
638    image_dir_ = image_dir_.Append(kTempImageFolderName);
639    success = file_util::CreateDirectory(image_dir_);
640  }
641  delegate->OnImageDirCreated(success, task);
642}
643
644const FilePath& ImageBurnResourceManager::GetImageDir() {
645  return image_dir_;
646}
647
648void ImageBurnResourceManager::SetDownloadFinished(bool finished) {
649  if (!download_started_)
650    return;
651  if (!finished)
652    download_started_ = false;
653  download_finished_ = finished;
654}
655
656void ImageBurnResourceManager::CreateImageUrl(TabContents* tab_contents,
657    Delegate* delegate) {
658  if (config_file_fetched_) {
659    delegate->OnImageUrlCreated(image_url_.get(), true);
660    return;
661  }
662  downloaders_.push_back(delegate);
663
664  if (config_file_requested_)
665    return;
666  config_file_requested_ = true;
667
668  config_file_path_ = GetImageDir().Append(kImageFetcherName);
669
670  download_manager_ = tab_contents->profile()->GetDownloadManager();
671  download_manager_->AddObserver(this);
672
673  ImageBurnDownloader* downloader = ImageBurnDownloader::GetInstance();
674  downloader->AddListener(this, config_file_url_);
675  downloader->DownloadFile(config_file_url_, config_file_path_, tab_contents);
676}
677
678void ImageBurnResourceManager::ConfigFileFetched(bool fetched) {
679  if (active_download_item_) {
680    active_download_item_->RemoveObserver(this);
681    active_download_item_ = NULL;
682  }
683  download_item_observer_added_ = false;
684  if (download_manager_)
685    download_manager_->RemoveObserver(this);
686  if (!fetched)
687    config_file_requested_ = false;
688  config_file_fetched_ = fetched;
689  for (size_t i = 0; i < downloaders_.size(); ++i)
690    downloaders_[i]->OnImageUrlCreated(image_url_.get(), fetched);
691  downloaders_.clear();
692}
693
694////////////////////////////////////////////////////////////////////////////////
695//
696// ImageBurnDownloaderTaskProxy
697//
698////////////////////////////////////////////////////////////////////////////////
699
700class ImageBurnDownloaderTaskProxy
701    : public base::RefCountedThreadSafe<ImageBurnDownloaderTaskProxy> {
702 public:
703  explicit ImageBurnDownloaderTaskProxy() {}
704
705  void CreateFileStream(const GURL& url,
706                        const FilePath& target_path,
707                        TabContents* tab_contents) {
708    ImageBurnDownloader::GetInstance()->CreateFileStreamOnFileThread(url,
709        target_path, tab_contents, this);
710  }
711
712  void OnFileStreamCreated(const GURL& url,
713                           const FilePath& file_path,
714                           TabContents* tab_contents,
715                           net::FileStream* created_file_stream) {
716    ImageBurnDownloader::GetInstance()->OnFileStreamCreatedOnUIThread(url,
717        file_path, tab_contents, created_file_stream);
718  }
719
720 private:
721  ~ImageBurnDownloaderTaskProxy() {}
722
723  friend class base::RefCountedThreadSafe<ImageBurnDownloaderTaskProxy>;
724
725  DISALLOW_COPY_AND_ASSIGN(ImageBurnDownloaderTaskProxy);
726};
727
728////////////////////////////////////////////////////////////////////////////////
729//
730// ImageBurnDownloader
731//
732////////////////////////////////////////////////////////////////////////////////
733
734// static
735ImageBurnDownloader* ImageBurnDownloader::GetInstance() {
736  return Singleton<ImageBurnDownloader>::get();
737}
738
739void ImageBurnDownloader::DownloadFile(const GURL& url,
740    const FilePath& file_path, TabContents* tab_contents) {
741  // First we have to create file stream we will download file to.
742  // That has to be done on File thread.
743  scoped_refptr<ImageBurnDownloaderTaskProxy> task =
744      new ImageBurnDownloaderTaskProxy();
745  BrowserThread::PostTask(
746      BrowserThread::FILE, FROM_HERE,
747      NewRunnableMethod(task.get(),
748          &ImageBurnDownloaderTaskProxy::CreateFileStream, url, file_path,
749          tab_contents));
750}
751
752void ImageBurnDownloader::CreateFileStreamOnFileThread(
753    const GURL& url, const FilePath& file_path,
754    TabContents* tab_contents, ImageBurnDownloaderTaskProxy* task) {
755
756  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
757  DCHECK(!file_path.empty());
758
759  scoped_ptr<net::FileStream> file_stream(new net::FileStream);
760  if (file_stream->Open(file_path, base::PLATFORM_FILE_CREATE_ALWAYS |
761                        base::PLATFORM_FILE_WRITE))
762    file_stream.reset(NULL);
763
764  // Call callback method on UI thread.
765  BrowserThread::PostTask(
766        BrowserThread::UI, FROM_HERE,
767        NewRunnableMethod(task,
768            &ImageBurnDownloaderTaskProxy::OnFileStreamCreated,
769            url, file_path, tab_contents, file_stream.release()));
770}
771
772void ImageBurnDownloader::OnFileStreamCreatedOnUIThread(const GURL& url,
773    const FilePath& file_path, TabContents* tab_contents,
774    net::FileStream* created_file_stream) {
775  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
776
777  if (created_file_stream) {
778    DownloadManager* download_manager =
779        tab_contents->profile()->GetDownloadManager();
780    DownloadSaveInfo save_info;
781    save_info.file_path = file_path;
782    save_info.file_stream = linked_ptr<net::FileStream>(created_file_stream);
783    DownloadStarted(true, url);
784    download_manager->DownloadUrlToFile(url,
785                                        tab_contents->GetURL(),
786                                        tab_contents->encoding(),
787                                        save_info,
788                                        tab_contents);
789  } else {
790    DownloadStarted(false, url);
791  }
792}
793
794void ImageBurnDownloader::AddListener(Listener* listener, const GURL& url) {
795  listeners_.insert(std::make_pair(url, listener));
796}
797
798void ImageBurnDownloader::DownloadStarted(bool success, const GURL& url) {
799  std::pair<ListenerMap::iterator, ListenerMap::iterator> listener_range =
800      listeners_.equal_range(url);
801  for (ListenerMap::iterator current_listener = listener_range.first;
802       current_listener != listener_range.second;
803       ++current_listener) {
804    current_listener->second->OnDownloadStarted(success);
805  }
806  listeners_.erase(listener_range.first, listener_range.second);
807}
808
809////////////////////////////////////////////////////////////////////////////////
810//
811// ImageBurnUI
812//
813////////////////////////////////////////////////////////////////////////////////
814ImageBurnUI::ImageBurnUI(TabContents* contents) : WebUI(contents) {
815  ImageBurnHandler* handler = new ImageBurnHandler(contents);
816  AddMessageHandler((handler)->Attach(this));
817  ImageBurnUIHTMLSource* html_source = new ImageBurnUIHTMLSource();
818  contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
819}
820