• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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/chromeos/imageburner/burn_manager.h"
6 
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/strings/string_util.h"
10 #include "base/threading/worker_pool.h"
11 #include "chromeos/dbus/dbus_thread_manager.h"
12 #include "chromeos/dbus/image_burner_client.h"
13 #include "chromeos/network/network_state.h"
14 #include "chromeos/network/network_state_handler.h"
15 #include "chromeos/system/statistics_provider.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "grit/generated_resources.h"
18 #include "net/url_request/url_fetcher.h"
19 #include "net/url_request/url_request_context_getter.h"
20 #include "net/url_request/url_request_status.h"
21 #include "third_party/zlib/google/zip.h"
22 
23 using content::BrowserThread;
24 
25 namespace chromeos {
26 namespace imageburner {
27 
28 namespace {
29 
30 const char kConfigFileUrl[] =
31     "https://dl.google.com/dl/edgedl/chromeos/recovery/recovery.conf";
32 const char kTempImageFolderName[] = "chromeos_image";
33 
34 const char kImageZipFileName[] = "chromeos_image.bin.zip";
35 
36 const int64 kBytesImageDownloadProgressReportInterval = 10240;
37 
38 BurnManager* g_burn_manager = NULL;
39 
40 // Cretes a directory and calls |callback| with the result on UI thread.
CreateDirectory(const base::FilePath & path,base::Callback<void (bool success)> callback)41 void CreateDirectory(const base::FilePath& path,
42                      base::Callback<void(bool success)> callback) {
43   const bool success = base::CreateDirectory(path);
44   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
45                           base::Bind(callback, success));
46 }
47 
48 // Unzips |source_zip_file| and sets the filename of the unzipped image to
49 // |source_image_file|.
UnzipImage(const base::FilePath & source_zip_file,const std::string & image_name,scoped_refptr<base::RefCountedString> source_image_file)50 void UnzipImage(const base::FilePath& source_zip_file,
51                 const std::string& image_name,
52                 scoped_refptr<base::RefCountedString> source_image_file) {
53   if (zip::Unzip(source_zip_file, source_zip_file.DirName())) {
54     source_image_file->data() =
55         source_zip_file.DirName().Append(image_name).value();
56   }
57 }
58 
59 }  // namespace
60 
61 const char kName[] = "name";
62 const char kHwid[] = "hwid";
63 const char kFileName[] = "file";
64 const char kUrl[] = "url";
65 
66 ////////////////////////////////////////////////////////////////////////////////
67 //
68 // ConfigFile
69 //
70 ////////////////////////////////////////////////////////////////////////////////
ConfigFile()71 ConfigFile::ConfigFile() {
72 }
73 
ConfigFile(const std::string & file_content)74 ConfigFile::ConfigFile(const std::string& file_content) {
75   reset(file_content);
76 }
77 
~ConfigFile()78 ConfigFile::~ConfigFile() {
79 }
80 
reset(const std::string & file_content)81 void ConfigFile::reset(const std::string& file_content) {
82   clear();
83 
84   std::vector<std::string> lines;
85   Tokenize(file_content, "\n", &lines);
86 
87   std::vector<std::string> key_value_pair;
88   for (size_t i = 0; i < lines.size(); ++i) {
89     if (lines[i].empty())
90       continue;
91 
92     key_value_pair.clear();
93     Tokenize(lines[i], "=", &key_value_pair);
94     // Skip lines that don't contain key-value pair and lines without a key.
95     if (key_value_pair.size() != 2 || key_value_pair[0].empty())
96       continue;
97 
98     ProcessLine(key_value_pair);
99   }
100 
101   // Make sure last block has at least one hwid associated with it.
102   DeleteLastBlockIfHasNoHwid();
103 }
104 
clear()105 void ConfigFile::clear() {
106   config_struct_.clear();
107 }
108 
GetProperty(const std::string & property_name,const std::string & hwid) const109 const std::string& ConfigFile::GetProperty(
110     const std::string& property_name,
111     const std::string& hwid) const {
112   // We search for block that has desired hwid property, and if we find it, we
113   // return its property_name property.
114   for (BlockList::const_iterator block_it = config_struct_.begin();
115        block_it != config_struct_.end();
116        ++block_it) {
117     if (block_it->hwids.find(hwid) != block_it->hwids.end()) {
118       PropertyMap::const_iterator property =
119           block_it->properties.find(property_name);
120       if (property != block_it->properties.end()) {
121         return property->second;
122       } else {
123         return base::EmptyString();
124       }
125     }
126   }
127 
128   return base::EmptyString();
129 }
130 
131 // Check if last block has a hwid associated with it, and erase it if it
132 // doesn't,
DeleteLastBlockIfHasNoHwid()133 void ConfigFile::DeleteLastBlockIfHasNoHwid() {
134   if (!config_struct_.empty() && config_struct_.back().hwids.empty()) {
135     config_struct_.pop_back();
136   }
137 }
138 
ProcessLine(const std::vector<std::string> & line)139 void ConfigFile::ProcessLine(const std::vector<std::string>& line) {
140   // If line contains name key, new image block is starting, so we have to add
141   // new entry to our data structure.
142   if (line[0] == kName) {
143     // If there was no hardware class defined for previous block, we can
144     // disregard is since we won't be abble to access any of its properties
145     // anyway. This should not happen, but let's be defensive.
146     DeleteLastBlockIfHasNoHwid();
147     config_struct_.resize(config_struct_.size() + 1);
148   }
149 
150   // If we still haven't added any blocks to data struct, we disregard this
151   // line. Again, this should never happen.
152   if (config_struct_.empty())
153     return;
154 
155   ConfigFileBlock& last_block = config_struct_.back();
156 
157   if (line[0] == kHwid) {
158     // Check if line contains hwid property. If so, add it to set of hwids
159     // associated with current block.
160     last_block.hwids.insert(line[1]);
161   } else {
162     // Add new block property.
163     last_block.properties.insert(std::make_pair(line[0], line[1]));
164   }
165 }
166 
ConfigFileBlock()167 ConfigFile::ConfigFileBlock::ConfigFileBlock() {
168 }
169 
~ConfigFileBlock()170 ConfigFile::ConfigFileBlock::~ConfigFileBlock() {
171 }
172 
173 ////////////////////////////////////////////////////////////////////////////////
174 //
175 // StateMachine
176 //
177 ////////////////////////////////////////////////////////////////////////////////
StateMachine()178 StateMachine::StateMachine()
179     : download_started_(false),
180       download_finished_(false),
181       state_(INITIAL) {
182 }
183 
~StateMachine()184 StateMachine::~StateMachine() {
185 }
186 
OnError(int error_message_id)187 void StateMachine::OnError(int error_message_id) {
188   if (state_ == INITIAL)
189     return;
190   if (!download_finished_)
191     download_started_ = false;
192 
193   state_ = INITIAL;
194   FOR_EACH_OBSERVER(Observer, observers_, OnError(error_message_id));
195 }
196 
OnSuccess()197 void StateMachine::OnSuccess() {
198   if (state_ == INITIAL)
199     return;
200   state_ = INITIAL;
201   OnStateChanged();
202 }
203 
204 ////////////////////////////////////////////////////////////////////////////////
205 //
206 // BurnManager
207 //
208 ////////////////////////////////////////////////////////////////////////////////
209 
BurnManager(const base::FilePath & downloads_directory,scoped_refptr<net::URLRequestContextGetter> context_getter)210 BurnManager::BurnManager(
211     const base::FilePath& downloads_directory,
212     scoped_refptr<net::URLRequestContextGetter> context_getter)
213     : device_handler_(disks::DiskMountManager::GetInstance()),
214       image_dir_created_(false),
215       unzipping_(false),
216       cancelled_(false),
217       burning_(false),
218       block_burn_signals_(false),
219       image_dir_(downloads_directory.Append(kTempImageFolderName)),
220       config_file_url_(kConfigFileUrl),
221       config_file_fetched_(false),
222       state_machine_(new StateMachine()),
223       url_request_context_getter_(context_getter),
224       bytes_image_download_progress_last_reported_(0),
225       weak_ptr_factory_(this) {
226   NetworkHandler::Get()->network_state_handler()->AddObserver(
227       this, FROM_HERE);
228   base::WeakPtr<BurnManager> weak_ptr(weak_ptr_factory_.GetWeakPtr());
229   device_handler_.SetCallbacks(
230       base::Bind(&BurnManager::NotifyDeviceAdded, weak_ptr),
231       base::Bind(&BurnManager::NotifyDeviceRemoved, weak_ptr));
232   DBusThreadManager::Get()->GetImageBurnerClient()->SetEventHandlers(
233       base::Bind(&BurnManager::OnBurnFinished,
234                  weak_ptr_factory_.GetWeakPtr()),
235       base::Bind(&BurnManager::OnBurnProgressUpdate,
236                  weak_ptr_factory_.GetWeakPtr()));
237 }
238 
~BurnManager()239 BurnManager::~BurnManager() {
240   if (image_dir_created_) {
241     base::DeleteFile(image_dir_, true);
242   }
243   if (NetworkHandler::IsInitialized()) {
244     NetworkHandler::Get()->network_state_handler()->RemoveObserver(
245         this, FROM_HERE);
246   }
247   DBusThreadManager::Get()->GetImageBurnerClient()->ResetEventHandlers();
248 }
249 
250 // static
Initialize(const base::FilePath & downloads_directory,scoped_refptr<net::URLRequestContextGetter> context_getter)251 void BurnManager::Initialize(
252     const base::FilePath& downloads_directory,
253     scoped_refptr<net::URLRequestContextGetter> context_getter) {
254   if (g_burn_manager) {
255     LOG(WARNING) << "BurnManager was already initialized";
256     return;
257   }
258   g_burn_manager = new BurnManager(downloads_directory, context_getter);
259   VLOG(1) << "BurnManager initialized";
260 }
261 
262 // static
Shutdown()263 void BurnManager::Shutdown() {
264   if (!g_burn_manager) {
265     LOG(WARNING) << "BurnManager::Shutdown() called with NULL manager";
266     return;
267   }
268   delete g_burn_manager;
269   g_burn_manager = NULL;
270   VLOG(1) << "BurnManager Shutdown completed";
271 }
272 
273 // static
GetInstance()274 BurnManager* BurnManager::GetInstance() {
275   return g_burn_manager;
276 }
277 
AddObserver(Observer * observer)278 void BurnManager::AddObserver(Observer* observer) {
279   observers_.AddObserver(observer);
280 }
281 
RemoveObserver(Observer * observer)282 void BurnManager::RemoveObserver(Observer* observer) {
283   observers_.RemoveObserver(observer);
284 }
285 
GetBurnableDevices()286 std::vector<disks::DiskMountManager::Disk> BurnManager::GetBurnableDevices() {
287   return device_handler_.GetBurnableDevices();
288 }
289 
Cancel()290 void BurnManager::Cancel() {
291   OnError(IDS_IMAGEBURN_USER_ERROR);
292 }
293 
OnError(int message_id)294 void BurnManager::OnError(int message_id) {
295   // If we are in intial state, error has already been dispached.
296   if (state_machine_->state() == StateMachine::INITIAL) {
297     return;
298   }
299 
300   // Remember burner state, since it will be reset after OnError call.
301   StateMachine::State state = state_machine_->state();
302 
303   // Dispach error. All hadlers' OnError event will be called before returning
304   // from this. This includes us, too.
305   state_machine_->OnError(message_id);
306 
307   // Cancel and clean up the current task.
308   // Note: the cancellation of this class looks not handled correctly.
309   // In particular, there seems no clean-up code for creating a temporary
310   // directory, or fetching config files. Also, there seems an issue
311   // about the cancellation of burning.
312   // TODO(hidehiko): Fix the issue.
313   if (state  == StateMachine::DOWNLOADING) {
314     CancelImageFetch();
315   } else if (state == StateMachine::BURNING) {
316     // Burn library doesn't send cancelled signal upon CancelBurnImage
317     // invokation.
318     CancelBurnImage();
319   }
320   ResetTargetPaths();
321 }
322 
CreateImageDir()323 void BurnManager::CreateImageDir() {
324   if (!image_dir_created_) {
325     BrowserThread::PostBlockingPoolTask(
326         FROM_HERE,
327         base::Bind(CreateDirectory,
328                    image_dir_,
329                    base::Bind(&BurnManager::OnImageDirCreated,
330                               weak_ptr_factory_.GetWeakPtr())));
331   } else {
332     const bool success = true;
333     OnImageDirCreated(success);
334   }
335 }
336 
OnImageDirCreated(bool success)337 void BurnManager::OnImageDirCreated(bool success) {
338   if (!success) {
339     // Failed to create the directory. Finish the burning process
340     // with failure state.
341     OnError(IDS_IMAGEBURN_DOWNLOAD_ERROR);
342     return;
343   }
344 
345   image_dir_created_ = true;
346   zip_image_file_path_ = image_dir_.Append(kImageZipFileName);
347   FetchConfigFile();
348 }
349 
GetImageDir()350 base::FilePath BurnManager::GetImageDir() {
351   if (!image_dir_created_)
352     return base::FilePath();
353   return image_dir_;
354 }
355 
FetchConfigFile()356 void BurnManager::FetchConfigFile() {
357   if (config_file_fetched_) {
358     // The config file is already fetched. So start to fetch the image.
359     FetchImage();
360     return;
361   }
362 
363   if (config_fetcher_.get())
364     return;
365 
366   config_fetcher_.reset(net::URLFetcher::Create(
367       config_file_url_, net::URLFetcher::GET, this));
368   config_fetcher_->SetRequestContext(url_request_context_getter_.get());
369   config_fetcher_->Start();
370 }
371 
FetchImage()372 void BurnManager::FetchImage() {
373   if (state_machine_->download_finished()) {
374     DoBurn();
375     return;
376   }
377 
378   if (state_machine_->download_started()) {
379     // The image downloading is already started. Do nothing.
380     return;
381   }
382 
383   tick_image_download_start_ = base::TimeTicks::Now();
384   bytes_image_download_progress_last_reported_ = 0;
385   image_fetcher_.reset(net::URLFetcher::Create(image_download_url_,
386                                                net::URLFetcher::GET,
387                                                this));
388   image_fetcher_->SetRequestContext(url_request_context_getter_.get());
389   image_fetcher_->SaveResponseToFileAtPath(
390       zip_image_file_path_,
391       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
392   image_fetcher_->Start();
393 
394   state_machine_->OnDownloadStarted();
395 }
396 
CancelImageFetch()397 void BurnManager::CancelImageFetch() {
398   image_fetcher_.reset();
399 }
400 
DoBurn()401 void BurnManager::DoBurn() {
402   if (state_machine_->state() == StateMachine::BURNING)
403     return;
404 
405   if (unzipping_) {
406     // We have unzip in progress, maybe it was "cancelled" before and did not
407     // finish yet. In that case, let's pretend cancel did not happen.
408     cancelled_ = false;
409     UpdateBurnStatus(UNZIP_STARTED, ImageBurnStatus());
410     return;
411   }
412 
413   source_image_path_.clear();
414 
415   unzipping_ = true;
416   cancelled_ = false;
417   UpdateBurnStatus(UNZIP_STARTED, ImageBurnStatus());
418 
419   const bool task_is_slow = true;
420   scoped_refptr<base::RefCountedString> result(new base::RefCountedString);
421   base::WorkerPool::PostTaskAndReply(
422       FROM_HERE,
423       base::Bind(UnzipImage, zip_image_file_path_, image_file_name_, result),
424       base::Bind(&BurnManager::OnImageUnzipped,
425                  weak_ptr_factory_.GetWeakPtr(),
426                  result),
427       task_is_slow);
428   state_machine_->OnBurnStarted();
429 }
430 
CancelBurnImage()431 void BurnManager::CancelBurnImage() {
432   // At the moment, we cannot really stop uzipping or burning. Instead we
433   // prevent events from being sent to listeners.
434   if (burning_)
435     block_burn_signals_ = true;
436   cancelled_ = true;
437 }
438 
OnURLFetchComplete(const net::URLFetcher * source)439 void BurnManager::OnURLFetchComplete(const net::URLFetcher* source) {
440   // TODO(hidehiko): Split the handler implementation into two, for
441   // the config file fetcher and the image file fetcher.
442   const bool success =
443       source->GetStatus().status() == net::URLRequestStatus::SUCCESS;
444 
445   if (source == config_fetcher_.get()) {
446     // Handler for the config file fetcher.
447     std::string data;
448     if (success)
449       config_fetcher_->GetResponseAsString(&data);
450     config_fetcher_.reset();
451     ConfigFileFetched(success, data);
452     return;
453   }
454 
455   if (source == image_fetcher_.get()) {
456     // Handler for the image file fetcher.
457     state_machine_->OnDownloadFinished();
458     if (!success) {
459       OnError(IDS_IMAGEBURN_DOWNLOAD_ERROR);
460       return;
461     }
462     DoBurn();
463     return;
464   }
465 
466   NOTREACHED();
467 }
468 
OnURLFetchDownloadProgress(const net::URLFetcher * source,int64 current,int64 total)469 void BurnManager::OnURLFetchDownloadProgress(const net::URLFetcher* source,
470                                              int64 current,
471                                              int64 total) {
472   if (source == image_fetcher_.get()) {
473     if (current >= bytes_image_download_progress_last_reported_ +
474         kBytesImageDownloadProgressReportInterval) {
475       bytes_image_download_progress_last_reported_ = current;
476       base::TimeDelta estimated_remaining_time;
477       if (current > 0) {
478         // Extrapolate from the elapsed time.
479         const base::TimeDelta elapsed_time =
480             base::TimeTicks::Now() - tick_image_download_start_;
481         estimated_remaining_time = elapsed_time * (total - current) / current;
482       }
483 
484       // TODO(hidehiko): We should be able to clean the state check here.
485       if (state_machine_->state() == StateMachine::DOWNLOADING) {
486         FOR_EACH_OBSERVER(
487             Observer, observers_,
488             OnProgressWithRemainingTime(
489                 DOWNLOADING, current, total, estimated_remaining_time));
490       }
491     }
492   }
493 }
494 
DefaultNetworkChanged(const NetworkState * network)495 void BurnManager::DefaultNetworkChanged(const NetworkState* network) {
496   // TODO(hidehiko): Split this into a class to write tests.
497   if (state_machine_->state() == StateMachine::INITIAL && network)
498     FOR_EACH_OBSERVER(Observer, observers_, OnNetworkDetected());
499 
500   if (state_machine_->state() == StateMachine::DOWNLOADING && !network)
501     OnError(IDS_IMAGEBURN_NETWORK_ERROR);
502 }
503 
UpdateBurnStatus(BurnEvent event,const ImageBurnStatus & status)504 void BurnManager::UpdateBurnStatus(BurnEvent event,
505                                    const ImageBurnStatus& status) {
506   if (cancelled_)
507     return;
508 
509   if (event == BURN_FAIL || event == BURN_SUCCESS) {
510     burning_ = false;
511     if (block_burn_signals_) {
512       block_burn_signals_ = false;
513       return;
514     }
515   }
516 
517   if (block_burn_signals_ && event == BURN_UPDATE)
518     return;
519 
520   // Notify observers.
521   switch (event) {
522     case BURN_SUCCESS:
523       // The burning task is successfully done.
524       // Update the state.
525       ResetTargetPaths();
526       state_machine_->OnSuccess();
527       FOR_EACH_OBSERVER(Observer, observers_, OnSuccess());
528       break;
529     case BURN_FAIL:
530       OnError(IDS_IMAGEBURN_BURN_ERROR);
531       break;
532     case BURN_UPDATE:
533       FOR_EACH_OBSERVER(
534           Observer, observers_,
535           OnProgress(BURNING, status.amount_burnt, status.total_size));
536       break;
537     case(UNZIP_STARTED):
538       FOR_EACH_OBSERVER(Observer, observers_, OnProgress(UNZIPPING, 0, 0));
539       break;
540     case UNZIP_FAIL:
541       OnError(IDS_IMAGEBURN_EXTRACTING_ERROR);
542       break;
543     case UNZIP_COMPLETE:
544       // We ignore this.
545       break;
546     default:
547       NOTREACHED();
548       break;
549   }
550 }
551 
ConfigFileFetched(bool fetched,const std::string & content)552 void BurnManager::ConfigFileFetched(bool fetched, const std::string& content) {
553   if (config_file_fetched_)
554     return;
555 
556   // Get image file name and image download URL.
557   std::string hwid;
558   if (fetched && system::StatisticsProvider::GetInstance()->
559       GetMachineStatistic(system::kHardwareClassKey, &hwid)) {
560     ConfigFile config_file(content);
561     image_file_name_ = config_file.GetProperty(kFileName, hwid);
562     image_download_url_ = GURL(config_file.GetProperty(kUrl, hwid));
563   }
564 
565   // Error check.
566   if (fetched && !image_file_name_.empty() && !image_download_url_.is_empty()) {
567     config_file_fetched_ = true;
568   } else {
569     fetched = false;
570     image_file_name_.clear();
571     image_download_url_ = GURL();
572   }
573 
574   if (!fetched) {
575     OnError(IDS_IMAGEBURN_DOWNLOAD_ERROR);
576     return;
577   }
578 
579   FetchImage();
580 }
581 
OnImageUnzipped(scoped_refptr<base::RefCountedString> source_image_file)582 void BurnManager::OnImageUnzipped(
583     scoped_refptr<base::RefCountedString> source_image_file) {
584   source_image_path_ = base::FilePath(source_image_file->data());
585 
586   bool success = !source_image_path_.empty();
587   UpdateBurnStatus(success ? UNZIP_COMPLETE : UNZIP_FAIL, ImageBurnStatus());
588 
589   unzipping_ = false;
590   if (cancelled_) {
591     cancelled_ = false;
592     return;
593   }
594 
595   if (!success)
596     return;
597 
598   burning_ = true;
599 
600   chromeos::disks::DiskMountManager::GetInstance()->UnmountDeviceRecursively(
601       target_device_path_.value(),
602       base::Bind(&BurnManager::OnDevicesUnmounted,
603                  weak_ptr_factory_.GetWeakPtr()));
604 }
605 
OnDevicesUnmounted(bool success)606 void BurnManager::OnDevicesUnmounted(bool success) {
607   if (!success) {
608     UpdateBurnStatus(BURN_FAIL, ImageBurnStatus(0, 0));
609     return;
610   }
611 
612   DBusThreadManager::Get()->GetImageBurnerClient()->BurnImage(
613       source_image_path_.value(),
614       target_file_path_.value(),
615       base::Bind(&BurnManager::OnBurnImageFail,
616                  weak_ptr_factory_.GetWeakPtr()));
617 }
618 
OnBurnImageFail()619 void BurnManager::OnBurnImageFail() {
620   UpdateBurnStatus(BURN_FAIL, ImageBurnStatus(0, 0));
621 }
622 
OnBurnFinished(const std::string & target_path,bool success,const std::string & error)623 void BurnManager::OnBurnFinished(const std::string& target_path,
624                                  bool success,
625                                  const std::string& error) {
626   UpdateBurnStatus(success ? BURN_SUCCESS : BURN_FAIL, ImageBurnStatus(0, 0));
627 }
628 
OnBurnProgressUpdate(const std::string & target_path,int64 amount_burnt,int64 total_size)629 void BurnManager::OnBurnProgressUpdate(const std::string& target_path,
630                                        int64 amount_burnt,
631                                        int64 total_size) {
632   UpdateBurnStatus(BURN_UPDATE, ImageBurnStatus(amount_burnt, total_size));
633 }
634 
NotifyDeviceAdded(const disks::DiskMountManager::Disk & disk)635 void BurnManager::NotifyDeviceAdded(
636     const disks::DiskMountManager::Disk& disk) {
637   FOR_EACH_OBSERVER(Observer, observers_, OnDeviceAdded(disk));
638 }
639 
NotifyDeviceRemoved(const disks::DiskMountManager::Disk & disk)640 void BurnManager::NotifyDeviceRemoved(
641     const disks::DiskMountManager::Disk& disk) {
642   FOR_EACH_OBSERVER(Observer, observers_, OnDeviceRemoved(disk));
643 
644   if (target_device_path_.value() == disk.device_path()) {
645     // The device is removed during the burning process.
646     // Note: in theory, this is not a part of notification, but cancelling
647     // the running burning task. However, there is no good place to be in the
648     // current code.
649     // TODO(hidehiko): Clean this up after refactoring.
650     OnError(IDS_IMAGEBURN_DEVICE_NOT_FOUND_ERROR);
651   }
652 }
653 
654 }  // namespace imageburner
655 }  // namespace chromeos
656