blob: 6170949cceedf092547755f30f48f5c752786ad2 [file] [log] [blame]
// 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 "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/threading/worker_pool.h"
#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
#include "chrome/browser/extensions/api/image_writer_private/operation.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.h"
namespace extensions {
namespace image_writer {
using content::BrowserThread;
namespace {
const int kMD5BufferSize = 1024;
void RemoveTempDirectory(const base::FilePath path) {
base::DeleteFile(path, true);
}
} // namespace
Operation::Operation(base::WeakPtr<OperationManager> manager,
const ExtensionId& extension_id,
const std::string& storage_unit_id)
: manager_(manager),
extension_id_(extension_id),
storage_unit_id_(storage_unit_id),
stage_(image_writer_api::STAGE_UNKNOWN),
progress_(0) {
}
Operation::~Operation() {
}
void Operation::Cancel() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DVLOG(1) << "Cancelling image writing operation for ext: " << extension_id_;
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_;
}
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 (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_));
}
void Operation::Finish() {
if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
BrowserThread::PostTask(BrowserThread::FILE,
FROM_HERE,
base::Bind(&Operation::Finish, this));
return;
}
DVLOG(1) << "Write operation complete.";
CleanUp();
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&OperationManager::OnComplete,
manager_,
extension_id_));
}
bool Operation::IsCancelled() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
return stage_ == image_writer_api::STAGE_NONE;
}
void Operation::AddCleanUpFunction(base::Closure callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
cleanup_functions_.push_back(callback);
}
void Operation::CleanUp() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
for (std::vector<base::Closure>::iterator it = cleanup_functions_.begin();
it != cleanup_functions_.end();
++it) {
it->Run();
}
cleanup_functions_.clear();
}
void Operation::UnzipStart(scoped_ptr<base::FilePath> zip_file) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
if (IsCancelled()) {
return;
}
DVLOG(1) << "Starting unzip stage for " << zip_file->value();
SetStage(image_writer_api::STAGE_UNZIP);
base::FilePath tmp_dir;
if (!base::CreateTemporaryDirInDir(zip_file->DirName(),
FILE_PATH_LITERAL("image_writer"),
&tmp_dir)) {
Error(error::kTempDir);
return;
}
AddCleanUpFunction(base::Bind(&RemoveTempDirectory, tmp_dir));
if (!zip::Unzip(*zip_file, tmp_dir)) {
Error(error::kUnzip);
return;
}
base::FileEnumerator file_enumerator(tmp_dir,
false,
base::FileEnumerator::FILES);
scoped_ptr<base::FilePath> unzipped_file(
new base::FilePath(file_enumerator.Next()));
if (unzipped_file->empty()) {
Error(error::kEmptyUnzip);
return;
}
if (!file_enumerator.Next().empty()) {
Error(error::kMultiFileZip);
return;
}
DVLOG(1) << "Successfully unzipped as " << unzipped_file->value();
SetProgress(kProgressComplete);
image_path_ = *unzipped_file;
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&Operation::WriteStart,
this));
}
void Operation::GetMD5SumOfFile(
scoped_ptr<base::FilePath> file_path,
int64 file_size,
int progress_offset,
int progress_scale,
const base::Callback<void(scoped_ptr<std::string>)>& callback) {
if (IsCancelled()) {
return;
}
base::MD5Init(&md5_context_);
scoped_ptr<image_writer_utils::ImageReader> reader(
new image_writer_utils::ImageReader());
if (!reader->Open(*file_path)) {
Error(error::kOpenImage);
return;
}
if (file_size <= 0) {
file_size = reader->GetSize();
}
BrowserThread::PostTask(BrowserThread::FILE,
FROM_HERE,
base::Bind(&Operation::MD5Chunk,
this,
base::Passed(&reader),
0,
file_size,
progress_offset,
progress_scale,
callback));
}
void Operation::MD5Chunk(
scoped_ptr<image_writer_utils::ImageReader> reader,
int64 bytes_processed,
int64 bytes_total,
int progress_offset,
int progress_scale,
const base::Callback<void(scoped_ptr<std::string>)>& callback) {
if (IsCancelled()) {
reader->Close();
return;
}
char buffer[kMD5BufferSize];
int len;
if (bytes_total - bytes_processed <= kMD5BufferSize) {
len = reader->Read(buffer, bytes_total - bytes_processed);
} else {
len = reader->Read(buffer, kMD5BufferSize);
}
if (len > 0) {
base::MD5Update(&md5_context_, base::StringPiece(buffer, len));
int percent_prev = (bytes_processed * progress_scale + progress_offset) /
(bytes_total);
int percent_curr = ((bytes_processed + len) * progress_scale +
progress_offset) /
(bytes_total);
if (percent_curr > percent_prev) {
SetProgress(progress_);
}
BrowserThread::PostTask(BrowserThread::FILE,
FROM_HERE,
base::Bind(&Operation::MD5Chunk,
this,
base::Passed(&reader),
bytes_processed + len,
bytes_total,
progress_offset,
progress_scale,
callback));
} else if (len == 0) {
if (bytes_processed + len < bytes_total) {
reader->Close();
Error(error::kPrematureEndOfFile);
} else {
base::MD5Digest digest;
base::MD5Final(&digest, &md5_context_);
scoped_ptr<std::string> hash(
new std::string(base::MD5DigestToBase16(digest)));
callback.Run(hash.Pass());
}
} else { // len < 0
reader->Close();
Error(error::kReadImage);
}
}
} // namespace image_writer
} // namespace extensions