// Copyright 2013 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/extensions/api/image_writer_private/operation.h" #include <utility> #include "base/files/file_enumerator.h" #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/threading/worker_pool.h" #include "build/build_config.h" #include "chrome/browser/extensions/api/image_writer_private/error_messages.h" #include "chrome/browser/extensions/api/image_writer_private/operation_manager.h" #include "content/public/browser/browser_thread.h" #include "third_party/zlib/google/zip_reader.h" namespace extensions { namespace image_writer { using content::BrowserThread; const int kMD5BufferSize = 1024; #if defined(OS_CHROMEOS) // Chrome OS only has a 1 GB temporary partition. This is too small to hold our // unzipped image. Fortunately we mount part of the temporary partition under // /var/tmp. const char kChromeOSTempRoot[] = "/var/tmp"; #endif #if !defined(OS_CHROMEOS) static base::LazyInstance<scoped_refptr<ImageWriterUtilityClient> > g_utility_client = LAZY_INSTANCE_INITIALIZER; #endif Operation::Operation(base::WeakPtr<OperationManager> manager, const ExtensionId& extension_id, const std::string& device_path) : manager_(manager), extension_id_(extension_id), #if defined(OS_WIN) device_path_(base::FilePath::FromUTF8Unsafe(device_path)), #else device_path_(device_path), #endif stage_(image_writer_api::STAGE_UNKNOWN), progress_(0), zip_reader_(new zip::ZipReader) { } Operation::~Operation() {} void Operation::Cancel() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); stage_ = image_writer_api::STAGE_NONE; CleanUp(); } void Operation::Abort() { Error(error::kAborted); } int Operation::GetProgress() { return progress_; } image_writer_api::Stage Operation::GetStage() { return stage_; } #if !defined(OS_CHROMEOS) // static void Operation::SetUtilityClientForTesting( scoped_refptr<ImageWriterUtilityClient> client) { g_utility_client.Get() = client; } #endif void Operation::Start() { #if defined(OS_CHROMEOS) if (!temp_dir_.CreateUniqueTempDirUnderPath( base::FilePath(kChromeOSTempRoot))) { #else if (!temp_dir_.CreateUniqueTempDir()) { #endif Error(error::kTempDirError); return; } AddCleanUpFunction( base::Bind(base::IgnoreResult(&base::ScopedTempDir::Delete), base::Unretained(&temp_dir_))); StartImpl(); } void Operation::Unzip(const base::Closure& continuation) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (IsCancelled()) { return; } if (image_path_.Extension() != FILE_PATH_LITERAL(".zip")) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation); return; } SetStage(image_writer_api::STAGE_UNZIP); if (!(zip_reader_->Open(image_path_) && zip_reader_->AdvanceToNextEntry() && zip_reader_->OpenCurrentEntryInZip())) { Error(error::kUnzipGenericError); return; } if (zip_reader_->HasMore()) { Error(error::kUnzipInvalidArchive); return; } // Create a new target to unzip to. The original file is opened by the // zip_reader_. zip::ZipReader::EntryInfo* entry_info = zip_reader_->current_entry_info(); if (entry_info) { image_path_ = temp_dir_.path().Append(entry_info->file_path().BaseName()); } else { Error(error::kTempDirError); return; } zip_reader_->ExtractCurrentEntryToFilePathAsync( image_path_, base::Bind(&Operation::CompleteAndContinue, this, continuation), base::Bind(&Operation::OnUnzipFailure, this), base::Bind(&Operation::OnUnzipProgress, this, zip_reader_->current_entry_info()->original_size())); } void Operation::Finish() { if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::Finish, this)); return; } CleanUp(); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&OperationManager::OnComplete, manager_, extension_id_)); } void Operation::Error(const std::string& error_message) { if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::Error, this, error_message)); return; } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&OperationManager::OnError, manager_, extension_id_, stage_, progress_, error_message)); CleanUp(); } void Operation::SetProgress(int progress) { if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::SetProgress, this, progress)); return; } if (progress <= progress_) { return; } if (IsCancelled()) { return; } progress_ = progress; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&OperationManager::OnProgress, manager_, extension_id_, stage_, progress_)); } void Operation::SetStage(image_writer_api::Stage stage) { if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::SetStage, this, stage)); return; } if (IsCancelled()) { return; } stage_ = stage; progress_ = 0; BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&OperationManager::OnProgress, manager_, extension_id_, stage_, progress_)); } bool Operation::IsCancelled() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); return stage_ == image_writer_api::STAGE_NONE; } void Operation::AddCleanUpFunction(const base::Closure& callback) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); cleanup_functions_.push_back(callback); } void Operation::CompleteAndContinue(const base::Closure& continuation) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); SetProgress(kProgressComplete); BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation); } #if !defined(OS_CHROMEOS) void Operation::StartUtilityClient() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (g_utility_client.Get().get()) { image_writer_client_ = g_utility_client.Get(); return; } if (!image_writer_client_.get()) { image_writer_client_ = new ImageWriterUtilityClient(); AddCleanUpFunction(base::Bind(&Operation::StopUtilityClient, this)); } } void Operation::StopUtilityClient() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ImageWriterUtilityClient::Shutdown, image_writer_client_)); } void Operation::WriteImageProgress(int64_t total_bytes, int64_t curr_bytes) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (IsCancelled()) { return; } int progress = kProgressComplete * curr_bytes / total_bytes; if (progress > GetProgress()) { SetProgress(progress); } } #endif void Operation::GetMD5SumOfFile( const base::FilePath& file_path, int64_t file_size, int progress_offset, int progress_scale, const base::Callback<void(const std::string&)>& callback) { if (IsCancelled()) { return; } base::MD5Init(&md5_context_); base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); if (!file.IsValid()) { Error(error::kImageOpenError); return; } if (file_size <= 0) { file_size = file.GetLength(); if (file_size < 0) { Error(error::kImageOpenError); return; } } BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::MD5Chunk, this, Passed(std::move(file)), 0, file_size, progress_offset, progress_scale, callback)); } void Operation::MD5Chunk( base::File file, int64_t bytes_processed, int64_t bytes_total, int progress_offset, int progress_scale, const base::Callback<void(const std::string&)>& callback) { if (IsCancelled()) return; CHECK_LE(bytes_processed, bytes_total); scoped_ptr<char[]> buffer(new char[kMD5BufferSize]); int read_size = std::min(bytes_total - bytes_processed, static_cast<int64_t>(kMD5BufferSize)); if (read_size == 0) { // Nothing to read, we are done. base::MD5Digest digest; base::MD5Final(&digest, &md5_context_); callback.Run(base::MD5DigestToBase16(digest)); } else { int len = file.Read(bytes_processed, buffer.get(), read_size); if (len == read_size) { // Process data. base::MD5Update(&md5_context_, base::StringPiece(buffer.get(), len)); int percent_curr = ((bytes_processed + len) * progress_scale) / bytes_total + progress_offset; SetProgress(percent_curr); BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&Operation::MD5Chunk, this, Passed(std::move(file)), bytes_processed + len, bytes_total, progress_offset, progress_scale, callback)); // Skip closing the file. return; } else { // We didn't read the bytes we expected. Error(error::kHashReadError); } } } void Operation::OnUnzipFailure() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); Error(error::kUnzipGenericError); } void Operation::OnUnzipProgress(int64_t total_bytes, int64_t progress_bytes) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); int progress_percent = kProgressComplete * progress_bytes / total_bytes; SetProgress(progress_percent); } void Operation::CleanUp() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); for (std::vector<base::Closure>::iterator it = cleanup_functions_.begin(); it != cleanup_functions_.end(); ++it) { it->Run(); } cleanup_functions_.clear(); } } // namespace image_writer } // namespace extensions