Commit ec6524fe authored by haven@chromium.org's avatar haven@chromium.org

Adds asynchronous unzip functions to ZipReader

Updates ImageWriterPrivate to use the new asynchronous functions.

BUG=324091

Review URL: https://codereview.chromium.org/92873003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@245393 0039d316-1c4b-4281-b951-d872f2087c98
parent f791d5dc
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include "chrome/browser/extensions/api/image_writer_private/operation.h" #include "chrome/browser/extensions/api/image_writer_private/operation.h"
#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h" #include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "third_party/zlib/google/zip.h"
namespace extensions { namespace extensions {
namespace image_writer { namespace image_writer {
...@@ -179,18 +178,18 @@ void Operation::CleanUp() { ...@@ -179,18 +178,18 @@ void Operation::CleanUp() {
cleanup_functions_.clear(); cleanup_functions_.clear();
} }
void Operation::UnzipStart(scoped_ptr<base::FilePath> zip_file) { void Operation::UnzipStart(scoped_ptr<base::FilePath> zip_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
if (IsCancelled()) { if (IsCancelled()) {
return; return;
} }
DVLOG(1) << "Starting unzip stage for " << zip_file->value(); DVLOG(1) << "Starting unzip stage for " << zip_path->value();
SetStage(image_writer_api::STAGE_UNZIP); SetStage(image_writer_api::STAGE_UNZIP);
base::FilePath tmp_dir; base::FilePath tmp_dir;
if (!base::CreateTemporaryDirInDir(zip_file->DirName(), if (!base::CreateTemporaryDirInDir(zip_path->DirName(),
FILE_PATH_LITERAL("image_writer"), FILE_PATH_LITERAL("image_writer"),
&tmp_dir)) { &tmp_dir)) {
Error(error::kTempDirError); Error(error::kTempDirError);
...@@ -199,39 +198,34 @@ void Operation::UnzipStart(scoped_ptr<base::FilePath> zip_file) { ...@@ -199,39 +198,34 @@ void Operation::UnzipStart(scoped_ptr<base::FilePath> zip_file) {
AddCleanUpFunction(base::Bind(&RemoveTempDirectory, tmp_dir)); AddCleanUpFunction(base::Bind(&RemoveTempDirectory, tmp_dir));
if (!zip::Unzip(*zip_file, tmp_dir)) { if (!base::CreateTemporaryFileInDir(tmp_dir, &image_path_)) {
Error(error::kUnzipGenericError); DLOG(ERROR) << "Failed create temporary unzip target in "
<< tmp_dir.value();
Error(error::kTempDirError);
return; return;
} }
base::FileEnumerator file_enumerator(tmp_dir, if (!(zip_reader_.Open(*zip_path) &&
false, zip_reader_.AdvanceToNextEntry() &&
base::FileEnumerator::FILES); zip_reader_.OpenCurrentEntryInZip())) {
DLOG(ERROR) << "Failed to open zip file.";
scoped_ptr<base::FilePath> unzipped_file( Error(error::kUnzipGenericError);
new base::FilePath(file_enumerator.Next()));
if (unzipped_file->empty()) {
Error(error::kUnzipInvalidArchive);
return; return;
} }
if (!file_enumerator.Next().empty()) { if (zip_reader_.HasMore()) {
DLOG(ERROR) << "Image zip has more than one file.";
Error(error::kUnzipInvalidArchive); Error(error::kUnzipInvalidArchive);
return; return;
} }
DVLOG(1) << "Successfully unzipped as " << unzipped_file->value(); zip_reader_.ExtractCurrentEntryToFilePathAsync(
image_path_,
SetProgress(kProgressComplete); base::Bind(&Operation::OnUnzipSuccess, this),
base::Bind(&Operation::OnUnzipFailure, this),
image_path_ = *unzipped_file; base::Bind(&Operation::OnUnzipProgress,
this,
BrowserThread::PostTask( zip_reader_.current_entry_info()->original_size()));
BrowserThread::FILE,
FROM_HERE,
base::Bind(&Operation::WriteStart,
this));
} }
void Operation::GetMD5SumOfFile( void Operation::GetMD5SumOfFile(
...@@ -327,5 +321,23 @@ void Operation::MD5Chunk( ...@@ -327,5 +321,23 @@ void Operation::MD5Chunk(
} }
} }
void Operation::OnUnzipSuccess() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
SetProgress(kProgressComplete);
WriteStart();
}
void Operation::OnUnzipFailure() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
Error(error::kUnzipGenericError);
}
void Operation::OnUnzipProgress(int64 total_bytes, int64 progress_bytes) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
int progress_percent = 100 * progress_bytes / total_bytes;
SetProgress(progress_percent);
}
} // namespace image_writer } // namespace image_writer
} // namespace extensions } // namespace extensions
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "chrome/browser/extensions/api/image_writer_private/image_writer_utils.h" #include "chrome/browser/extensions/api/image_writer_private/image_writer_utils.h"
#include "chrome/common/cancelable_task_tracker.h" #include "chrome/common/cancelable_task_tracker.h"
#include "chrome/common/extensions/api/image_writer_private.h" #include "chrome/common/extensions/api/image_writer_private.h"
#include "third_party/zlib/google/zip_reader.h"
namespace image_writer_api = extensions::api::image_writer_private; namespace image_writer_api = extensions::api::image_writer_private;
...@@ -37,8 +38,7 @@ class OperationManager; ...@@ -37,8 +38,7 @@ class OperationManager;
// Run, Complete. Start and Complete run on the UI thread and are responsible // Run, Complete. Start and Complete run on the UI thread and are responsible
// for advancing to the next stage and other UI interaction. The Run phase does // for advancing to the next stage and other UI interaction. The Run phase does
// the work on the FILE thread and calls SendProgress or Error as appropriate. // the work on the FILE thread and calls SendProgress or Error as appropriate.
class Operation class Operation : public base::RefCountedThreadSafe<Operation> {
: public base::RefCountedThreadSafe<Operation> {
public: public:
typedef base::Callback<void(bool, const std::string&)> StartWriteCallback; typedef base::Callback<void(bool, const std::string&)> StartWriteCallback;
typedef base::Callback<void(bool, const std::string&)> CancelWriteCallback; typedef base::Callback<void(bool, const std::string&)> CancelWriteCallback;
...@@ -143,6 +143,7 @@ class Operation ...@@ -143,6 +143,7 @@ class Operation
void OnBurnError(); void OnBurnError();
#endif #endif
// Incrementally calculates the MD5 sum of a file.
void MD5Chunk(scoped_ptr<image_writer_utils::ImageReader> reader, void MD5Chunk(scoped_ptr<image_writer_utils::ImageReader> reader,
int64 bytes_processed, int64 bytes_processed,
int64 bytes_total, int64 bytes_total,
...@@ -150,6 +151,11 @@ class Operation ...@@ -150,6 +151,11 @@ class Operation
int progress_scale, int progress_scale,
const base::Callback<void(scoped_ptr<std::string>)>& callback); const base::Callback<void(scoped_ptr<std::string>)>& callback);
// Callbacks for zip::ZipReader.
void OnUnzipSuccess();
void OnUnzipFailure();
void OnUnzipProgress(int64 total_bytes, int64 progress_bytes);
// Runs all cleanup functions. // Runs all cleanup functions.
void CleanUp(); void CleanUp();
...@@ -162,6 +168,9 @@ class Operation ...@@ -162,6 +168,9 @@ class Operation
// memory here. This requires that we only do one MD5 sum at a time. // memory here. This requires that we only do one MD5 sum at a time.
base::MD5Context md5_context_; base::MD5Context md5_context_;
// Zip reader for unzip operations.
zip::ZipReader zip_reader_;
// CleanUp operations that must be run. All these functions are run on the // CleanUp operations that must be run. All these functions are run on the
// FILE thread. // FILE thread.
std::vector<base::Closure> cleanup_functions_; std::vector<base::Closure> cleanup_functions_;
......
...@@ -21,13 +21,6 @@ ...@@ -21,13 +21,6 @@
namespace extensions { namespace extensions {
namespace image_writer { namespace image_writer {
using testing::_;
using testing::Lt;
using testing::AnyNumber;
using testing::AtLeast;
namespace {
// A fake for the EventRouter. If tests require monitoring of interaction with // A fake for the EventRouter. If tests require monitoring of interaction with
// the event router put the logic here. // the event router put the logic here.
class FakeEventRouter : public extensions::EventRouter { class FakeEventRouter : public extensions::EventRouter {
...@@ -63,6 +56,8 @@ BrowserContextKeyedService* BuildFakeExtensionSystem( ...@@ -63,6 +56,8 @@ BrowserContextKeyedService* BuildFakeExtensionSystem(
return new FakeExtensionSystem(static_cast<Profile*>(profile)); return new FakeExtensionSystem(static_cast<Profile*>(profile));
} }
namespace {
class ImageWriterOperationManagerTest class ImageWriterOperationManagerTest
: public ImageWriterUnitTestBase { : public ImageWriterUnitTestBase {
public: public:
...@@ -75,7 +70,8 @@ class ImageWriterOperationManagerTest ...@@ -75,7 +70,8 @@ class ImageWriterOperationManagerTest
protected: protected:
ImageWriterOperationManagerTest() ImageWriterOperationManagerTest()
: started_(false), : started_(false),
start_success_(false) {} start_success_(false) {
}
virtual void SetUp() OVERRIDE { virtual void SetUp() OVERRIDE {
ImageWriterUnitTestBase::SetUp(); ImageWriterUnitTestBase::SetUp();
......
...@@ -2,39 +2,148 @@ ...@@ -2,39 +2,148 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "chrome/browser/extensions/api/image_writer_private/error_messages.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.h"
#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
#include "chrome/browser/extensions/api/image_writer_private/test_utils.h" #include "chrome/browser/extensions/api/image_writer_private/test_utils.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/zip.h"
namespace extensions { namespace extensions {
namespace image_writer { namespace image_writer {
namespace { namespace {
class ImageWriterOperationTest : public ImageWriterUnitTestBase { using testing::_;
}; using testing::AnyNumber;
using testing::AtLeast;
using testing::Gt;
using testing::Lt;
class DummyOperation : public Operation { // This class gives us access to the protected methods of Operation so that we
// can call them directly. It also allows us to selectively disable some
// phases.
class OperationForTest : public Operation {
public: public:
DummyOperation(base::WeakPtr<OperationManager> manager, OperationForTest(base::WeakPtr<OperationManager> manager,
const ExtensionId& extension_id, const ExtensionId& extension_id,
const std::string& storage_unit_id) const std::string& storage_unit_id)
: Operation(manager, extension_id, storage_unit_id) {}; : Operation(manager, extension_id, storage_unit_id) {}
virtual void Start() OVERRIDE {};
virtual void Start() OVERRIDE {
}
void UnzipStart(scoped_ptr<base::FilePath> zip_file) {
Operation::UnzipStart(zip_file.Pass());
}
void WriteStart() {
Operation::WriteStart();
}
void VerifyWriteStart() {
Operation::VerifyWriteStart();
}
void Finish() {
Operation::Finish();
}
private: private:
virtual ~DummyOperation() {}; virtual ~OperationForTest() {};
};
class ImageWriterOperationTest : public ImageWriterUnitTestBase {
protected:
virtual void SetUp() OVERRIDE {
ImageWriterUnitTestBase::SetUp();
// Create the zip file.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(),
&image_file_));
ASSERT_TRUE(base::CreateTemporaryFile(&zip_file_));
scoped_ptr<char[]> buffer(new char[kTestFileSize]);
memset(buffer.get(), kImagePattern, kTestFileSize);
file_util::WriteFile(image_file_, buffer.get(), kTestFileSize);
zip::Zip(temp_dir_.path(), zip_file_, true);
}
virtual void TearDown() OVERRIDE {
ImageWriterUnitTestBase::TearDown();
}
base::ScopedTempDir temp_dir_;
base::FilePath image_file_;
base::FilePath zip_file_;
}; };
TEST_F(ImageWriterOperationTest, Create) { } // namespace
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
// Tests a successful unzip.
TEST_F(ImageWriterOperationTest, Unzip) {
MockOperationManager manager;
scoped_refptr<OperationForTest> operation(
new OperationForTest(manager.AsWeakPtr(),
kDummyExtensionId,
test_device_path_.AsUTF8Unsafe()));
scoped_ptr<base::FilePath> zip_file(new base::FilePath(zip_file_));
// At least one progress report > 0% and < 100%.
EXPECT_CALL(manager, OnProgress(kDummyExtensionId,
image_writer_api::STAGE_UNZIP,
Lt(100))).Times(AtLeast(1));
// At least one progress report at 100%.
EXPECT_CALL(manager, OnProgress(kDummyExtensionId,
image_writer_api::STAGE_UNZIP,
100)).Times(AtLeast(1));
// At least one progress report at 0%.
EXPECT_CALL(manager, OnProgress(kDummyExtensionId,
image_writer_api::STAGE_UNZIP,
0)).Times(AtLeast(1));
// Any number of additional progress calls in later stages.
EXPECT_CALL(manager, OnProgress(kDummyExtensionId,
Gt(image_writer_api::STAGE_UNZIP),
_)).Times(AnyNumber());
// One completion call.
EXPECT_CALL(manager, OnComplete(kDummyExtensionId)).Times(1);
// No errors
EXPECT_CALL(manager, OnError(_, _, _, _)).Times(0);
content::BrowserThread::PostTask(content::BrowserThread::FILE,
FROM_HERE,
base::Bind(&OperationForTest::UnzipStart,
operation,
base::Passed(&zip_file)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(base::ContentsEqual(image_file_, test_device_path_));
}
#endif
TEST_F(ImageWriterOperationTest, Creation) {
MockOperationManager manager; MockOperationManager manager;
scoped_refptr<Operation> op( scoped_refptr<Operation> op(
new DummyOperation(manager.AsWeakPtr(), new OperationForTest(manager.AsWeakPtr(),
kDummyExtensionId, kDummyExtensionId,
test_device_path_.AsUTF8Unsafe())); test_device_path_.AsUTF8Unsafe()));
EXPECT_EQ(0, op->GetProgress()); EXPECT_EQ(0, op->GetProgress());
EXPECT_EQ(image_writer_api::STAGE_UNKNOWN, op->GetStage()); EXPECT_EQ(image_writer_api::STAGE_UNKNOWN, op->GetStage());
} }
} // namespace
} // namespace image_writer } // namespace image_writer
} // namespace extensions } // namespace extensions
...@@ -51,9 +51,7 @@ class ImageWriterFakeImageBurnerClient ...@@ -51,9 +51,7 @@ class ImageWriterFakeImageBurnerClient
} // namespace } // namespace
#endif #endif
MockOperationManager::MockOperationManager() MockOperationManager::MockOperationManager() : OperationManager(NULL) {}
: OperationManager(NULL) {}
MockOperationManager::~MockOperationManager() {} MockOperationManager::~MockOperationManager() {}
ImageWriterUnitTestBase::ImageWriterUnitTestBase() {} ImageWriterUnitTestBase::ImageWriterUnitTestBase() {}
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/file_util.h" #include "base/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "net/base/file_stream.h" #include "net/base/file_stream.h"
...@@ -68,7 +69,8 @@ ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip, ...@@ -68,7 +69,8 @@ ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip,
} }
} }
ZipReader::ZipReader() { ZipReader::ZipReader()
: weak_ptr_factory_(this) {
Reset(); Reset();
} }
...@@ -241,6 +243,65 @@ bool ZipReader::ExtractCurrentEntryToFilePath( ...@@ -241,6 +243,65 @@ bool ZipReader::ExtractCurrentEntryToFilePath(
return success; return success;
} }
void ZipReader::ExtractCurrentEntryToFilePathAsync(
const base::FilePath& output_file_path,
const SuccessCallback& success_callback,
const FailureCallback& failure_callback,
const ProgressCallback& progress_callback) {
DCHECK(zip_file_);
DCHECK(current_entry_info_.get());
// If this is a directory, just create it and return.
if (current_entry_info()->is_directory()) {
if (base::CreateDirectory(output_file_path)) {
base::MessageLoopProxy::current()->PostTask(FROM_HERE, success_callback);
} else {
DVLOG(1) << "Unzip failed: unable to create directory.";
base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
}
return;
}
if (unzOpenCurrentFile(zip_file_) != UNZ_OK) {
DVLOG(1) << "Unzip failed: unable to open current zip entry.";
base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
return;
}
base::FilePath output_dir_path = output_file_path.DirName();
if (!base::CreateDirectory(output_dir_path)) {
DVLOG(1) << "Unzip failed: unable to create containing directory.";
base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
return;
}
const int flags = (base::PLATFORM_FILE_CREATE_ALWAYS |
base::PLATFORM_FILE_WRITE);
bool created = false;
base::PlatformFileError platform_file_error;
base::PlatformFile output_file = CreatePlatformFile(output_file_path,
flags,
&created,
&platform_file_error);
if (platform_file_error != base::PLATFORM_FILE_OK) {
DVLOG(1) << "Unzip failed: unable to create platform file at "
<< output_file_path.value();
base::MessageLoopProxy::current()->PostTask(FROM_HERE, failure_callback);
return;
}
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&ZipReader::ExtractChunk,
weak_ptr_factory_.GetWeakPtr(),
output_file,
success_callback,
failure_callback,
progress_callback,
0 /* initial offset */));
}
bool ZipReader::ExtractCurrentEntryIntoDirectory( bool ZipReader::ExtractCurrentEntryIntoDirectory(
const base::FilePath& output_directory_path) { const base::FilePath& output_directory_path) {
DCHECK(current_entry_info_.get()); DCHECK(current_entry_info_.get());
...@@ -313,4 +374,53 @@ void ZipReader::Reset() { ...@@ -313,4 +374,53 @@ void ZipReader::Reset() {
current_entry_info_.reset(); current_entry_info_.reset();
} }
void ZipReader::ExtractChunk(base::PlatformFile output_file,
const SuccessCallback& success_callback,
const FailureCallback& failure_callback,
const ProgressCallback& progress_callback,
const int64 offset) {
char buffer[internal::kZipBufSize];
const int num_bytes_read = unzReadCurrentFile(zip_file_,
buffer,
internal::kZipBufSize);
if (num_bytes_read == 0) {
unzCloseCurrentFile(zip_file_);
base::ClosePlatformFile(output_file);
success_callback.Run();
} else if (num_bytes_read < 0) {
DVLOG(1) << "Unzip failed: error while reading zipfile "
<< "(" << num_bytes_read << ")";
base::ClosePlatformFile(output_file);
failure_callback.Run();
} else {
if (num_bytes_read != base::WritePlatformFile(output_file,
offset,
buffer,
num_bytes_read)) {
DVLOG(1) << "Unzip failed: unable to write all bytes to target.";
base::ClosePlatformFile(output_file);
failure_callback.Run();
return;
}
int64 current_progress = offset + num_bytes_read;
progress_callback.Run(current_progress);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&ZipReader::ExtractChunk,
weak_ptr_factory_.GetWeakPtr(),
output_file,
success_callback,
failure_callback,
progress_callback,
current_progress));
}
}
} // namespace zip } // namespace zip
// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_ #ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
#define THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_ #define THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_
#include <string> #include <string>
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/callback.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/platform_file.h" #include "base/platform_file.h"
#include "base/time/time.h" #include "base/time/time.h"
...@@ -42,6 +43,14 @@ namespace zip { ...@@ -42,6 +43,14 @@ namespace zip {
// //
class ZipReader { class ZipReader {
public: public:
// A callback that is called when the operation is successful.
typedef base::Closure SuccessCallback;
// A callback that is called when the operation fails.
typedef base::Closure FailureCallback;
// A callback that is called periodically during the operation with the number
// of bytes that have been processed so far.
typedef base::Callback<void(int64)> ProgressCallback;
// This class represents information of an entry (file or directory) in // This class represents information of an entry (file or directory) in
// a zip file. // a zip file.
class EntryInfo { class EntryInfo {
...@@ -139,6 +148,18 @@ class ZipReader { ...@@ -139,6 +148,18 @@ class ZipReader {
// timestamp is not valid, the timestamp will be set to the current time. // timestamp is not valid, the timestamp will be set to the current time.
bool ExtractCurrentEntryToFilePath(const base::FilePath& output_file_path); bool ExtractCurrentEntryToFilePath(const base::FilePath& output_file_path);
// Asynchronously extracts the current entry to the given output file path.
// If the current entry is a directory it just creates the directory
// synchronously instead. OpenCurrentEntryInZip() must be called beforehand.
// success_callback will be called on success and failure_callback will be
// called on failure. progress_callback will be called at least once.
// Callbacks will be posted to the current MessageLoop in-order.
void ExtractCurrentEntryToFilePathAsync(
const base::FilePath& output_file_path,
const SuccessCallback& success_callback,
const FailureCallback& failure_callback,
const ProgressCallback& progress_callback);
// Extracts the current entry to the given output directory path using // Extracts the current entry to the given output directory path using
// ExtractCurrentEntryToFilePath(). Sub directories are created as needed // ExtractCurrentEntryToFilePath(). Sub directories are created as needed
// based on the file path of the current entry. For example, if the file // based on the file path of the current entry. For example, if the file
...@@ -176,11 +197,21 @@ class ZipReader { ...@@ -176,11 +197,21 @@ class ZipReader {
// Resets the internal state. // Resets the internal state.
void Reset(); void Reset();
// Extracts a chunk of the file to the target. Will post a task for the next
// chunk and success/failure/progress callbacks as necessary.
void ExtractChunk(base::PlatformFile target_file,
const SuccessCallback& success_callback,
const FailureCallback& failure_callback,
const ProgressCallback& progress_callback,
const int64 offset);
unzFile zip_file_; unzFile zip_file_;
int num_entries_; int num_entries_;
bool reached_end_; bool reached_end_;
scoped_ptr<EntryInfo> current_entry_info_; scoped_ptr<EntryInfo> current_entry_info_;
base::WeakPtrFactory<ZipReader> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ZipReader); DISALLOW_COPY_AND_ASSIGN(ZipReader);
}; };
......
...@@ -7,12 +7,14 @@ ...@@ -7,12 +7,14 @@
#include <set> #include <set>
#include <string> #include <string>
#include "base/bind.h"
#include "base/file_util.h" #include "base/file_util.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/md5.h" #include "base/md5.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/platform_file.h" #include "base/platform_file.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -21,6 +23,8 @@ ...@@ -21,6 +23,8 @@
namespace { namespace {
const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6";
// Wrap PlatformFiles in a class so that we don't leak them in tests. // Wrap PlatformFiles in a class so that we don't leak them in tests.
class PlatformFileWrapper { class PlatformFileWrapper {
public: public:
...@@ -61,6 +65,48 @@ class PlatformFileWrapper { ...@@ -61,6 +65,48 @@ class PlatformFileWrapper {
base::PlatformFile file_; base::PlatformFile file_;
}; };
// A mock that provides methods that can be used as callbacks in asynchronous
// unzip functions. Tracks the number of calls and number of bytes reported.
// Assumes that progress callbacks will be executed in-order.
class MockUnzipListener : public base::SupportsWeakPtr<MockUnzipListener> {
public:
MockUnzipListener()
: success_calls_(0),
failure_calls_(0),
progress_calls_(0),
current_progress_(0) {
}
// Success callback for async functions.
void OnUnzipSuccess() {
success_calls_++;
}
// Failure callback for async functions.
void OnUnzipFailure() {
failure_calls_++;
}
// Progress callback for async functions.
void OnUnzipProgress(int64 progress) {
DCHECK(progress > current_progress_);
progress_calls_++;
current_progress_ = progress;
}
int success_calls() { return success_calls_; }
int failure_calls() { return failure_calls_; }
int progress_calls() { return progress_calls_; }
int current_progress() { return current_progress_; }
private:
int success_calls_;
int failure_calls_;
int progress_calls_;
int64 current_progress_;
};
} // namespace } // namespace
namespace zip { namespace zip {
...@@ -113,6 +159,16 @@ class ZipReaderTest : public PlatformTest { ...@@ -113,6 +159,16 @@ class ZipReaderTest : public PlatformTest {
return true; return true;
} }
bool CompareFileAndMD5(const base::FilePath& path,
const std::string expected_md5) {
// Read the output file and compute the MD5.
std::string output;
if (!base::ReadFileToString(path, &output))
return false;
const std::string md5 = base::MD5String(output);
return expected_md5 == md5;
}
// The path to temporary directory used to contain the test operations. // The path to temporary directory used to contain the test operations.
base::FilePath test_dir_; base::FilePath test_dir_;
// The path to the test data directory where test.zip etc. are located. // The path to the test data directory where test.zip etc. are located.
...@@ -128,6 +184,8 @@ class ZipReaderTest : public PlatformTest { ...@@ -128,6 +184,8 @@ class ZipReaderTest : public PlatformTest {
std::set<base::FilePath> test_zip_contents_; std::set<base::FilePath> test_zip_contents_;
base::ScopedTempDir temp_dir_; base::ScopedTempDir temp_dir_;
base::MessageLoop message_loop_;
}; };
TEST_F(ZipReaderTest, Open_ValidZipFile) { TEST_F(ZipReaderTest, Open_ValidZipFile) {
...@@ -220,8 +278,7 @@ TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_RegularFile) { ...@@ -220,8 +278,7 @@ TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_RegularFile) {
ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
&output)); &output));
const std::string md5 = base::MD5String(output); const std::string md5 = base::MD5String(output);
const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; EXPECT_EQ(kQuuxExpectedMD5, md5);
EXPECT_EQ(kExpectedMD5, md5);
// quux.txt should be larger than kZipBufSize so that we can exercise // quux.txt should be larger than kZipBufSize so that we can exercise
// the loop in ExtractCurrentEntry(). // the loop in ExtractCurrentEntry().
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
...@@ -241,8 +298,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) { ...@@ -241,8 +298,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) {
ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
&output)); &output));
const std::string md5 = base::MD5String(output); const std::string md5 = base::MD5String(output);
const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; EXPECT_EQ(kQuuxExpectedMD5, md5);
EXPECT_EQ(kExpectedMD5, md5);
// quux.txt should be larger than kZipBufSize so that we can exercise // quux.txt should be larger than kZipBufSize so that we can exercise
// the loop in ExtractCurrentEntry(). // the loop in ExtractCurrentEntry().
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
...@@ -264,8 +320,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) { ...@@ -264,8 +320,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) {
ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
&output)); &output));
const std::string md5 = base::MD5String(output); const std::string md5 = base::MD5String(output);
const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; EXPECT_EQ(kQuuxExpectedMD5, md5);
EXPECT_EQ(kExpectedMD5, md5);
// quux.txt should be larger than kZipBufSize so that we can exercise // quux.txt should be larger than kZipBufSize so that we can exercise
// the loop in ExtractCurrentEntry(). // the loop in ExtractCurrentEntry().
EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size());
...@@ -296,8 +351,7 @@ TEST_F(ZipReaderTest, ExtractCurrentEntryIntoDirectory_RegularFile) { ...@@ -296,8 +351,7 @@ TEST_F(ZipReaderTest, ExtractCurrentEntryIntoDirectory_RegularFile) {
ASSERT_TRUE(base::ReadFileToString( ASSERT_TRUE(base::ReadFileToString(
test_dir_.AppendASCII("foo/bar/quux.txt"), &output)); test_dir_.AppendASCII("foo/bar/quux.txt"), &output));
const std::string md5 = base::MD5String(output); const std::string md5 = base::MD5String(output);
const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; EXPECT_EQ(kQuuxExpectedMD5, md5);
EXPECT_EQ(kExpectedMD5, md5);
} }
TEST_F(ZipReaderTest, current_entry_info_RegularFile) { TEST_F(ZipReaderTest, current_entry_info_RegularFile) {
...@@ -428,4 +482,75 @@ TEST_F(ZipReaderTest, OpenFromString) { ...@@ -428,4 +482,75 @@ TEST_F(ZipReaderTest, OpenFromString) {
EXPECT_EQ(std::string("This is a test.\n"), actual); EXPECT_EQ(std::string("This is a test.\n"), actual);
} }
// Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest, ExtractToFileAsync_RegularFile) {
MockUnzipListener listener;
ZipReader reader;
base::FilePath target_file = test_dir_.AppendASCII("quux.txt");
base::FilePath target_path(FILE_PATH_LITERAL("foo/bar/quux.txt"));
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
reader.ExtractCurrentEntryToFilePathAsync(
target_file,
base::Bind(&MockUnzipListener::OnUnzipSuccess,
listener.AsWeakPtr()),
base::Bind(&MockUnzipListener::OnUnzipFailure,
listener.AsWeakPtr()),
base::Bind(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_LE(1, listener.progress_calls());
std::string output;
ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"),
&output));
const std::string md5 = base::MD5String(output);
EXPECT_EQ(kQuuxExpectedMD5, md5);
int64 file_size = 0;
ASSERT_TRUE(base::GetFileSize(target_file, &file_size));
EXPECT_EQ(file_size, listener.current_progress());
}
// Verifies that the asynchronous extraction to a file works.
TEST_F(ZipReaderTest, ExtractToFileAsync_Directory) {
MockUnzipListener listener;
ZipReader reader;
base::FilePath target_file = test_dir_.AppendASCII("foo");
base::FilePath target_path(FILE_PATH_LITERAL("foo/"));
ASSERT_TRUE(reader.Open(test_zip_file_));
ASSERT_TRUE(reader.LocateAndOpenEntry(target_path));
reader.ExtractCurrentEntryToFilePathAsync(
target_file,
base::Bind(&MockUnzipListener::OnUnzipSuccess,
listener.AsWeakPtr()),
base::Bind(&MockUnzipListener::OnUnzipFailure,
listener.AsWeakPtr()),
base::Bind(&MockUnzipListener::OnUnzipProgress,
listener.AsWeakPtr()));
EXPECT_EQ(0, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_EQ(0, listener.progress_calls());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, listener.success_calls());
EXPECT_EQ(0, listener.failure_calls());
EXPECT_GE(0, listener.progress_calls());
ASSERT_TRUE(base::DirectoryExists(target_file));
}
} // namespace zip } // namespace zip
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment