Revert 187767 "Add a unified observer to replace NetworkManagerO..."
[chromium-blink-merge.git] / chrome / browser / chromeos / imageburner / burn_manager.cc
blobcae766722c4d5f65f938b73299ff524939f99e22
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"
7 #include "base/bind.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;
23 namespace chromeos {
24 namespace imageburner {
26 namespace {
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));
49 } // namespace
51 const char kName[] = "name";
52 const char kHwid[] = "hwid";
53 const char kFileName[] = "file";
54 const char kUrl[] = "url";
56 ////////////////////////////////////////////////////////////////////////////////
58 // ConfigFile
60 ////////////////////////////////////////////////////////////////////////////////
61 ConfigFile::ConfigFile() {
64 ConfigFile::ConfigFile(const std::string& file_content) {
65 reset(file_content);
68 ConfigFile::~ConfigFile() {
71 void ConfigFile::reset(const std::string& file_content) {
72 clear();
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) {
79 if (lines[i].empty())
80 continue;
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())
86 continue;
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();
106 ++block_it) {
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;
112 } else {
113 return EmptyString();
118 return EmptyString();
121 // Check if last block has a hwid associated with it, and erase it if it
122 // doesn't,
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())
143 return;
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]);
151 } else {
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 ////////////////////////////////////////////////////////////////////////////////
165 // StateMachine
167 ////////////////////////////////////////////////////////////////////////////////
168 StateMachine::StateMachine()
169 : download_started_(false),
170 download_finished_(false),
171 state_(INITIAL) {
174 StateMachine::~StateMachine() {
177 void StateMachine::OnError(int error_message_id) {
178 if (state_ == INITIAL)
179 return;
180 if (!download_finished_)
181 download_started_ = false;
183 state_ = INITIAL;
184 FOR_EACH_OBSERVER(Observer, observers_, OnError(error_message_id));
187 void StateMachine::OnSuccess() {
188 if (state_ == INITIAL)
189 return;
190 state_ = INITIAL;
191 OnStateChanged();
194 ////////////////////////////////////////////////////////////////////////////////
196 // BurnManager
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);
223 // static
224 void BurnManager::Initialize() {
225 if (g_burn_manager) {
226 LOG(WARNING) << "BurnManager was already initialized";
227 return;
229 g_burn_manager = new BurnManager();
230 VLOG(1) << "BurnManager initialized";
233 // static
234 void BurnManager::Shutdown() {
235 if (!g_burn_manager) {
236 LOG(WARNING) << "BurnManager::Shutdown() called with NULL manager";
237 return;
239 delete g_burn_manager;
240 g_burn_manager = NULL;
241 VLOG(1) << "BurnManager Shutdown completed";
244 // static
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) {
272 return;
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) {
289 CancelImageFetch();
290 } else if (state == StateMachine::BURNING) {
291 // Burn library doesn't send cancelled signal upon CancelBurnImage
292 // invokation.
293 CancelBurnImage();
295 ResetTargetPaths();
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(
303 FROM_HERE,
304 base::Bind(CreateDirectory,
305 image_dir_,
306 base::Bind(&BurnManager::OnImageDirCreated,
307 weak_ptr_factory_.GetWeakPtr())));
308 } else {
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() {
320 return image_dir_;
323 void BurnManager::FetchConfigFile() {
324 if (config_file_fetched_) {
325 FOR_EACH_OBSERVER(Observer, observers_, OnConfigFileFetched(true));
326 return;
329 if (config_fetcher_.get())
330 return;
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,
344 this));
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) {
371 const bool success =
372 source->GetStatus().status() == net::URLRequestStatus::SUCCESS;
373 if (source == config_fetcher_.get()) {
374 std::string data;
375 if (success)
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,
386 int64 current,
387 int64 total) {
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;
393 if (current > 0) {
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;
399 FOR_EACH_OBSERVER(
400 Observer, observers_,
401 OnImageFileFetchDownloadProgressUpdated(
402 current, total, estimated_remaining_time));
407 void BurnManager::BurnProgressUpdated(BurnLibrary* object,
408 BurnEvent event,
409 const ImageBurnStatus& status) {
410 if (event == BURN_SUCCESS) {
411 // The burning task is successfully done.
412 // Update the state.
413 state_machine_->OnSuccess();
416 // Proxy the BurnLibrary callback to observers.
417 FOR_EACH_OBSERVER(
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_)
433 return;
435 // Get image file name and image download URL.
436 std::string hwid;
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));
444 // Error check.
445 if (fetched && !image_file_name_.empty() && !image_download_url_.is_empty()) {
446 config_file_fetched_ = true;
447 } else {
448 fetched = false;
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
469 // current code.
470 // TODO(hidehiko): Clean this up after refactoring.
471 OnError(IDS_IMAGEBURN_DEVICE_NOT_FOUND_ERROR);
475 } // namespace imageburner
476 } // namespace chromeos