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.
5 #include "chrome/browser/chromeos/imageburner/burn_manager.h"
8 #include "base/file_util.h"
9 #include "base/path_service.h"
10 #include "base/string_util.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/chromeos/cros/burn_library.h"
13 #include "chrome/browser/chromeos/cros/cros_library.h"
14 #include "chrome/browser/chromeos/system/statistics_provider.h"
15 #include "chrome/common/chrome_paths.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_status.h"
21 using content::BrowserThread
;
24 namespace imageburner
{
28 // Name for hwid in machine statistics.
29 const char kHwidStatistic
[] = "hardware_class";
31 const char kConfigFileUrl
[] =
32 "https://dl.google.com/dl/edgedl/chromeos/recovery/recovery.conf";
33 const char kTempImageFolderName
[] = "chromeos_image";
35 const char kImageZipFileName
[] = "chromeos_image.bin.zip";
37 const int64 kBytesImageDownloadProgressReportInterval
= 10240;
39 BurnManager
* g_burn_manager
= NULL
;
41 // Cretes a directory and calls |callback| with the result on UI thread.
42 void CreateDirectory(const base::FilePath
& path
,
43 base::Callback
<void(bool success
)> callback
) {
44 const bool success
= file_util::CreateDirectory(path
);
45 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
46 base::Bind(callback
, success
));
51 const char kName
[] = "name";
52 const char kHwid
[] = "hwid";
53 const char kFileName
[] = "file";
54 const char kUrl
[] = "url";
56 ////////////////////////////////////////////////////////////////////////////////
60 ////////////////////////////////////////////////////////////////////////////////
61 ConfigFile::ConfigFile() {
64 ConfigFile::ConfigFile(const std::string
& file_content
) {
68 ConfigFile::~ConfigFile() {
71 void ConfigFile::reset(const std::string
& file_content
) {
74 std::vector
<std::string
> lines
;
75 Tokenize(file_content
, "\n", &lines
);
77 std::vector
<std::string
> key_value_pair
;
78 for (size_t i
= 0; i
< lines
.size(); ++i
) {
82 key_value_pair
.clear();
83 Tokenize(lines
[i
], "=", &key_value_pair
);
84 // Skip lines that don't contain key-value pair and lines without a key.
85 if (key_value_pair
.size() != 2 || key_value_pair
[0].empty())
88 ProcessLine(key_value_pair
);
91 // Make sure last block has at least one hwid associated with it.
92 DeleteLastBlockIfHasNoHwid();
95 void ConfigFile::clear() {
96 config_struct_
.clear();
99 const std::string
& ConfigFile::GetProperty(
100 const std::string
& property_name
,
101 const std::string
& hwid
) const {
102 // We search for block that has desired hwid property, and if we find it, we
103 // return its property_name property.
104 for (BlockList::const_iterator block_it
= config_struct_
.begin();
105 block_it
!= config_struct_
.end();
107 if (block_it
->hwids
.find(hwid
) != block_it
->hwids
.end()) {
108 PropertyMap::const_iterator property
=
109 block_it
->properties
.find(property_name
);
110 if (property
!= block_it
->properties
.end()) {
111 return property
->second
;
113 return EmptyString();
118 return EmptyString();
121 // Check if last block has a hwid associated with it, and erase it if it
123 void ConfigFile::DeleteLastBlockIfHasNoHwid() {
124 if (!config_struct_
.empty() && config_struct_
.back().hwids
.empty()) {
125 config_struct_
.pop_back();
129 void ConfigFile::ProcessLine(const std::vector
<std::string
>& line
) {
130 // If line contains name key, new image block is starting, so we have to add
131 // new entry to our data structure.
132 if (line
[0] == kName
) {
133 // If there was no hardware class defined for previous block, we can
134 // disregard is since we won't be abble to access any of its properties
135 // anyway. This should not happen, but let's be defensive.
136 DeleteLastBlockIfHasNoHwid();
137 config_struct_
.resize(config_struct_
.size() + 1);
140 // If we still haven't added any blocks to data struct, we disregard this
141 // line. Again, this should never happen.
142 if (config_struct_
.empty())
145 ConfigFileBlock
& last_block
= config_struct_
.back();
147 if (line
[0] == kHwid
) {
148 // Check if line contains hwid property. If so, add it to set of hwids
149 // associated with current block.
150 last_block
.hwids
.insert(line
[1]);
152 // Add new block property.
153 last_block
.properties
.insert(std::make_pair(line
[0], line
[1]));
157 ConfigFile::ConfigFileBlock::ConfigFileBlock() {
160 ConfigFile::ConfigFileBlock::~ConfigFileBlock() {
163 ////////////////////////////////////////////////////////////////////////////////
167 ////////////////////////////////////////////////////////////////////////////////
168 StateMachine::StateMachine()
169 : download_started_(false),
170 download_finished_(false),
174 StateMachine::~StateMachine() {
177 void StateMachine::OnError(int error_message_id
) {
178 if (state_
== INITIAL
)
180 if (!download_finished_
)
181 download_started_
= false;
184 FOR_EACH_OBSERVER(Observer
, observers_
, OnError(error_message_id
));
187 void StateMachine::OnSuccess() {
188 if (state_
== INITIAL
)
194 ////////////////////////////////////////////////////////////////////////////////
198 ////////////////////////////////////////////////////////////////////////////////
200 BurnManager::BurnManager()
201 : device_handler_(disks::DiskMountManager::GetInstance()),
202 config_file_url_(kConfigFileUrl
),
203 config_file_fetched_(false),
204 state_machine_(new StateMachine()),
205 bytes_image_download_progress_last_reported_(0),
206 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
207 CrosLibrary::Get()->GetNetworkLibrary()->AddNetworkManagerObserver(this);
208 CrosLibrary::Get()->GetBurnLibrary()->AddObserver(this);
209 base::WeakPtr
<BurnManager
> weak_ptr(weak_ptr_factory_
.GetWeakPtr());
210 device_handler_
.SetCallbacks(
211 base::Bind(&BurnManager::NotifyDeviceAdded
, weak_ptr
),
212 base::Bind(&BurnManager::NotifyDeviceRemoved
, weak_ptr
));
215 BurnManager::~BurnManager() {
216 if (!image_dir_
.empty()) {
217 file_util::Delete(image_dir_
, true);
219 CrosLibrary::Get()->GetBurnLibrary()->RemoveObserver(this);
220 CrosLibrary::Get()->GetNetworkLibrary()->RemoveNetworkManagerObserver(this);
224 void BurnManager::Initialize() {
225 if (g_burn_manager
) {
226 LOG(WARNING
) << "BurnManager was already initialized";
229 g_burn_manager
= new BurnManager();
230 VLOG(1) << "BurnManager initialized";
234 void BurnManager::Shutdown() {
235 if (!g_burn_manager
) {
236 LOG(WARNING
) << "BurnManager::Shutdown() called with NULL manager";
239 delete g_burn_manager
;
240 g_burn_manager
= NULL
;
241 VLOG(1) << "BurnManager Shutdown completed";
245 BurnManager
* BurnManager::GetInstance() {
246 return g_burn_manager
;
249 void BurnManager::AddObserver(Observer
* observer
) {
250 observers_
.AddObserver(observer
);
253 void BurnManager::RemoveObserver(Observer
* observer
) {
254 observers_
.RemoveObserver(observer
);
257 std::vector
<disks::DiskMountManager::Disk
> BurnManager::GetBurnableDevices() {
258 return device_handler_
.GetBurnableDevices();
261 bool BurnManager::IsNetworkConnected() const {
262 return CrosLibrary::Get()->GetNetworkLibrary()->Connected();
265 void BurnManager::Cancel() {
266 OnError(IDS_IMAGEBURN_USER_ERROR
);
269 void BurnManager::OnError(int message_id
) {
270 // If we are in intial state, error has already been dispached.
271 if (state_machine_
->state() == StateMachine::INITIAL
) {
275 // Remember burner state, since it will be reset after OnError call.
276 StateMachine::State state
= state_machine_
->state();
278 // Dispach error. All hadlers' OnError event will be called before returning
279 // from this. This includes us, too.
280 state_machine_
->OnError(message_id
);
282 // Cancel and clean up the current task.
283 // Note: the cancellation of this class looks not handled correctly.
284 // In particular, there seems no clean-up code for creating a temporary
285 // directory, or fetching config files. Also, there seems an issue
286 // about the cancellation of BurnLibrary.
287 // TODO(hidehiko): Fix the issue.
288 if (state
== StateMachine::DOWNLOADING
) {
290 } else if (state
== StateMachine::BURNING
) {
291 // Burn library doesn't send cancelled signal upon CancelBurnImage
298 void BurnManager::CreateImageDir() {
299 if (image_dir_
.empty()) {
300 CHECK(PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS
, &image_dir_
));
301 image_dir_
= image_dir_
.Append(kTempImageFolderName
);
302 BrowserThread::PostBlockingPoolTask(
304 base::Bind(CreateDirectory
,
306 base::Bind(&BurnManager::OnImageDirCreated
,
307 weak_ptr_factory_
.GetWeakPtr())));
309 const bool success
= true;
310 OnImageDirCreated(success
);
314 void BurnManager::OnImageDirCreated(bool success
) {
315 zip_image_file_path_
= image_dir_
.Append(kImageZipFileName
);
316 FOR_EACH_OBSERVER(Observer
, observers_
, OnImageDirCreated(success
));
319 const base::FilePath
& BurnManager::GetImageDir() {
323 void BurnManager::FetchConfigFile() {
324 if (config_file_fetched_
) {
325 FOR_EACH_OBSERVER(Observer
, observers_
, OnConfigFileFetched(true));
329 if (config_fetcher_
.get())
332 config_fetcher_
.reset(net::URLFetcher::Create(
333 config_file_url_
, net::URLFetcher::GET
, this));
334 config_fetcher_
->SetRequestContext(
335 g_browser_process
->system_request_context());
336 config_fetcher_
->Start();
339 void BurnManager::FetchImage() {
340 tick_image_download_start_
= base::TimeTicks::Now();
341 bytes_image_download_progress_last_reported_
= 0;
342 image_fetcher_
.reset(net::URLFetcher::Create(image_download_url_
,
343 net::URLFetcher::GET
,
345 image_fetcher_
->SetRequestContext(
346 g_browser_process
->system_request_context());
347 image_fetcher_
->SaveResponseToFileAtPath(
348 zip_image_file_path_
,
349 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
350 image_fetcher_
->Start();
352 state_machine_
->OnDownloadStarted();
355 void BurnManager::CancelImageFetch() {
356 image_fetcher_
.reset();
359 void BurnManager::DoBurn() {
360 CrosLibrary::Get()->GetBurnLibrary()->DoBurn(
361 zip_image_file_path_
, image_file_name_
,
362 target_file_path(), target_device_path());
363 state_machine_
->OnBurnStarted();
366 void BurnManager::CancelBurnImage() {
367 CrosLibrary::Get()->GetBurnLibrary()->CancelBurnImage();
370 void BurnManager::OnURLFetchComplete(const net::URLFetcher
* source
) {
372 source
->GetStatus().status() == net::URLRequestStatus::SUCCESS
;
373 if (source
== config_fetcher_
.get()) {
376 config_fetcher_
->GetResponseAsString(&data
);
377 config_fetcher_
.reset();
378 ConfigFileFetched(success
, data
);
379 } else if (source
== image_fetcher_
.get()) {
380 state_machine_
->OnDownloadFinished();
381 FOR_EACH_OBSERVER(Observer
, observers_
, OnImageFileFetched(success
));
385 void BurnManager::OnURLFetchDownloadProgress(const net::URLFetcher
* source
,
388 if (source
== image_fetcher_
.get()) {
389 if (current
>= bytes_image_download_progress_last_reported_
+
390 kBytesImageDownloadProgressReportInterval
) {
391 bytes_image_download_progress_last_reported_
= current
;
392 base::TimeDelta estimated_remaining_time
;
394 // Extrapolate from the elapsed time.
395 const base::TimeDelta elapsed_time
=
396 base::TimeTicks::Now() - tick_image_download_start_
;
397 estimated_remaining_time
= elapsed_time
* (total
- current
) / current
;
400 Observer
, observers_
,
401 OnImageFileFetchDownloadProgressUpdated(
402 current
, total
, estimated_remaining_time
));
407 void BurnManager::BurnProgressUpdated(BurnLibrary
* object
,
409 const ImageBurnStatus
& status
) {
410 if (event
== BURN_SUCCESS
) {
411 // The burning task is successfully done.
413 state_machine_
->OnSuccess();
416 // Proxy the BurnLibrary callback to observers.
418 Observer
, observers_
, OnBurnProgressUpdated(event
, status
));
421 void BurnManager::OnNetworkManagerChanged(NetworkLibrary
* obj
) {
422 // TODO(hidehiko): Split this into a class to write tests.
423 if (state_machine_
->state() == StateMachine::INITIAL
&& IsNetworkConnected())
424 FOR_EACH_OBSERVER(Observer
, observers_
, OnNetworkDetected());
426 if (state_machine_
->state() == StateMachine::DOWNLOADING
&&
427 !IsNetworkConnected())
428 OnError(IDS_IMAGEBURN_NETWORK_ERROR
);
431 void BurnManager::ConfigFileFetched(bool fetched
, const std::string
& content
) {
432 if (config_file_fetched_
)
435 // Get image file name and image download URL.
437 if (fetched
&& system::StatisticsProvider::GetInstance()->
438 GetMachineStatistic(kHwidStatistic
, &hwid
)) {
439 ConfigFile
config_file(content
);
440 image_file_name_
= config_file
.GetProperty(kFileName
, hwid
);
441 image_download_url_
= GURL(config_file
.GetProperty(kUrl
, hwid
));
445 if (fetched
&& !image_file_name_
.empty() && !image_download_url_
.is_empty()) {
446 config_file_fetched_
= true;
449 image_file_name_
.clear();
450 image_download_url_
= GURL();
453 FOR_EACH_OBSERVER(Observer
, observers_
, OnConfigFileFetched(fetched
));
456 void BurnManager::NotifyDeviceAdded(
457 const disks::DiskMountManager::Disk
& disk
) {
458 FOR_EACH_OBSERVER(Observer
, observers_
, OnDeviceAdded(disk
));
461 void BurnManager::NotifyDeviceRemoved(
462 const disks::DiskMountManager::Disk
& disk
) {
463 FOR_EACH_OBSERVER(Observer
, observers_
, OnDeviceRemoved(disk
));
465 if (target_device_path_
.value() == disk
.device_path()) {
466 // The device is removed during the burning process.
467 // Note: in theory, this is not a part of notification, but cancelling
468 // the running burning task. However, there is no good place to be in the
470 // TODO(hidehiko): Clean this up after refactoring.
471 OnError(IDS_IMAGEBURN_DEVICE_NOT_FOUND_ERROR
);
475 } // namespace imageburner
476 } // namespace chromeos