diff options
author | haven@chromium.org <haven@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-17 00:36:28 +0000 |
---|---|---|
committer | haven@chromium.org <haven@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-17 00:36:28 +0000 |
commit | ec6524fe9b25cea3c1bfa05d8b816966edea0d88 (patch) | |
tree | bf73b45525eedbf916beaed50f5b17d7fa9955d5 /third_party/zlib | |
parent | f791d5dc5945d2410264922389399b9300d7f256 (diff) | |
download | chromium_src-ec6524fe9b25cea3c1bfa05d8b816966edea0d88.zip chromium_src-ec6524fe9b25cea3c1bfa05d8b816966edea0d88.tar.gz chromium_src-ec6524fe9b25cea3c1bfa05d8b816966edea0d88.tar.bz2 |
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
Diffstat (limited to 'third_party/zlib')
-rw-r--r-- | third_party/zlib/google/zip_reader.cc | 112 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader.h | 33 | ||||
-rw-r--r-- | third_party/zlib/google/zip_reader_unittest.cc | 141 |
3 files changed, 276 insertions, 10 deletions
diff --git a/third_party/zlib/google/zip_reader.cc b/third_party/zlib/google/zip_reader.cc index 8787254..7b7870e 100644 --- a/third_party/zlib/google/zip_reader.cc +++ b/third_party/zlib/google/zip_reader.cc @@ -6,6 +6,7 @@ #include "base/file_util.h" #include "base/logging.h" +#include "base/message_loop/message_loop.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "net/base/file_stream.h" @@ -68,7 +69,8 @@ ZipReader::EntryInfo::EntryInfo(const std::string& file_name_in_zip, } } -ZipReader::ZipReader() { +ZipReader::ZipReader() + : weak_ptr_factory_(this) { Reset(); } @@ -241,6 +243,65 @@ bool ZipReader::ExtractCurrentEntryToFilePath( 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( const base::FilePath& output_directory_path) { DCHECK(current_entry_info_.get()); @@ -313,4 +374,53 @@ void ZipReader::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 diff --git a/third_party/zlib/google/zip_reader.h b/third_party/zlib/google/zip_reader.h index a3e1ee8..5f0a167 100644 --- a/third_party/zlib/google/zip_reader.h +++ b/third_party/zlib/google/zip_reader.h @@ -1,16 +1,17 @@ // Copyright (c) 2011 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. - #ifndef THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_ #define THIRD_PARTY_ZLIB_GOOGLE_ZIP_READER_H_ #include <string> #include "base/basictypes.h" +#include "base/callback.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" #include "base/platform_file.h" #include "base/time/time.h" @@ -42,6 +43,14 @@ namespace zip { // class ZipReader { 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 // a zip file. class EntryInfo { @@ -139,6 +148,18 @@ class ZipReader { // timestamp is not valid, the timestamp will be set to the current time. 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 // ExtractCurrentEntryToFilePath(). Sub directories are created as needed // based on the file path of the current entry. For example, if the file @@ -176,11 +197,21 @@ class ZipReader { // Resets the internal state. 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_; int num_entries_; bool reached_end_; scoped_ptr<EntryInfo> current_entry_info_; + base::WeakPtrFactory<ZipReader> weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(ZipReader); }; diff --git a/third_party/zlib/google/zip_reader_unittest.cc b/third_party/zlib/google/zip_reader_unittest.cc index 09efd9a..2033f9f 100644 --- a/third_party/zlib/google/zip_reader_unittest.cc +++ b/third_party/zlib/google/zip_reader_unittest.cc @@ -7,12 +7,14 @@ #include <set> #include <string> +#include "base/bind.h" #include "base/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/md5.h" #include "base/path_service.h" #include "base/platform_file.h" +#include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" @@ -21,6 +23,8 @@ namespace { +const static std::string kQuuxExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; + // Wrap PlatformFiles in a class so that we don't leak them in tests. class PlatformFileWrapper { public: @@ -61,6 +65,48 @@ class PlatformFileWrapper { 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 zip { @@ -113,6 +159,16 @@ class ZipReaderTest : public PlatformTest { 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. base::FilePath test_dir_; // The path to the test data directory where test.zip etc. are located. @@ -128,6 +184,8 @@ class ZipReaderTest : public PlatformTest { std::set<base::FilePath> test_zip_contents_; base::ScopedTempDir temp_dir_; + + base::MessageLoop message_loop_; }; TEST_F(ZipReaderTest, Open_ValidZipFile) { @@ -220,8 +278,7 @@ TEST_F(ZipReaderTest, ExtractCurrentEntryToFilePath_RegularFile) { ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), &output)); const std::string md5 = base::MD5String(output); - const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; - EXPECT_EQ(kExpectedMD5, md5); + EXPECT_EQ(kQuuxExpectedMD5, md5); // quux.txt should be larger than kZipBufSize so that we can exercise // the loop in ExtractCurrentEntry(). EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); @@ -241,8 +298,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFilePath_RegularFile) { ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), &output)); const std::string md5 = base::MD5String(output); - const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; - EXPECT_EQ(kExpectedMD5, md5); + EXPECT_EQ(kQuuxExpectedMD5, md5); // quux.txt should be larger than kZipBufSize so that we can exercise // the loop in ExtractCurrentEntry(). EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); @@ -264,8 +320,7 @@ TEST_F(ZipReaderTest, PlatformFileExtractCurrentEntryToFd_RegularFile) { ASSERT_TRUE(base::ReadFileToString(test_dir_.AppendASCII("quux.txt"), &output)); const std::string md5 = base::MD5String(output); - const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; - EXPECT_EQ(kExpectedMD5, md5); + EXPECT_EQ(kQuuxExpectedMD5, md5); // quux.txt should be larger than kZipBufSize so that we can exercise // the loop in ExtractCurrentEntry(). EXPECT_LT(static_cast<size_t>(internal::kZipBufSize), output.size()); @@ -296,8 +351,7 @@ TEST_F(ZipReaderTest, ExtractCurrentEntryIntoDirectory_RegularFile) { ASSERT_TRUE(base::ReadFileToString( test_dir_.AppendASCII("foo/bar/quux.txt"), &output)); const std::string md5 = base::MD5String(output); - const std::string kExpectedMD5 = "d1ae4ac8a17a0e09317113ab284b57a6"; - EXPECT_EQ(kExpectedMD5, md5); + EXPECT_EQ(kQuuxExpectedMD5, md5); } TEST_F(ZipReaderTest, current_entry_info_RegularFile) { @@ -428,4 +482,75 @@ TEST_F(ZipReaderTest, OpenFromString) { 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 |