diff options
author | tommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-11 01:58:25 +0000 |
---|---|---|
committer | tommycli@chromium.org <tommycli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-11 01:58:25 +0000 |
commit | f386bb5b7433903e3e105c7725906366f4a85559 (patch) | |
tree | ae63efdb0fb245791df7fa33e4a474a007685f78 /webkit | |
parent | 677ad365c17980f74f03c1992e6e1795723f9cfb (diff) | |
download | chromium_src-f386bb5b7433903e3e105c7725906366f4a85559.zip chromium_src-f386bb5b7433903e3e105c7725906366f4a85559.tar.gz chromium_src-f386bb5b7433903e3e105c7725906366f4a85559.tar.bz2 |
Simple PMP reader to parse Picasa's metadata
For Media Galleries project.
BUG=151701
Review URL: https://chromiumcodereview.appspot.com/12704024
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@193537 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_column_reader.cc | 186 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_column_reader.h | 67 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_column_reader_unittest.cc | 179 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_constants.h | 46 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_table_reader.cc | 88 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_table_reader.h | 48 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_table_reader_unittest.cc | 72 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_test_helper.cc | 172 | ||||
-rw-r--r-- | webkit/fileapi/media/picasa/pmp_test_helper.h | 55 | ||||
-rw-r--r-- | webkit/fileapi/webkit_fileapi.gypi | 5 |
10 files changed, 918 insertions, 0 deletions
diff --git a/webkit/fileapi/media/picasa/pmp_column_reader.cc b/webkit/fileapi/media/picasa/pmp_column_reader.cc new file mode 100644 index 0000000..3f6bcc4 --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_column_reader.cc @@ -0,0 +1,186 @@ +// 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 "webkit/fileapi/media/picasa/pmp_column_reader.h" + +#include <cstring> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" + +namespace picasaimport { + +namespace { + +const size_t kPmpMaxFilesize = 50*1024*1024; // Maximum of 50 MB. + +} // namespace + +PmpColumnReader::PmpColumnReader() + : length_(0), + field_type_(PMP_TYPE_INVALID), + rows_(0) { } + +PmpColumnReader::~PmpColumnReader() { } + +bool PmpColumnReader::Init(const base::FilePath& filepath, uint32* rows_read) { + DCHECK(!data_.get()); + base::ThreadRestrictions::AssertIOAllowed(); + + int64 length = 0; // Signed temporary. + if (!file_util::GetFileSize(filepath, &length)) + return false; + + length_ = length; + + if (length_ < kPmpHeaderSize || length_ > kPmpMaxFilesize) + return false; + + data_.reset(new uint8[length_]); + + char* data_begin = reinterpret_cast<char*>(data_.get()); + + return file_util::ReadFile(filepath, data_begin, length_) && + ParseData(rows_read); +} + +bool PmpColumnReader::ReadString(const uint32 row, std::string* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row); + + if (field_type_ != PMP_TYPE_STRING || row >= rows_) + return false; + + *result = strings_[row]; + return true; +} + +bool PmpColumnReader::ReadUInt32(const uint32 row, uint32* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(uint32)); + + if (field_type_ != PMP_TYPE_UINT32 || row >= rows_) + return false; + + *result = reinterpret_cast<uint32*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadDouble64(const uint32 row, double* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(double)); + + if (field_type_ != PMP_TYPE_DOUBLE64 || row >= rows_) + return false; + + *result = reinterpret_cast<double*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadUInt8(const uint32 row, uint8* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(uint8)); + + if (field_type_ != PMP_TYPE_UINT8 || row >= rows_) + return false; + + *result = reinterpret_cast<uint8*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ReadUInt64(const uint32 row, uint64* result) const { + DCHECK(data_.get() != NULL); + DCHECK_GT(length_, kPmpHeaderSize + row * sizeof(uint64)); + + if (field_type_ != PMP_TYPE_UINT64 || row >= rows_) + return false; + + *result = reinterpret_cast<uint64*>(data_.get() + kPmpHeaderSize)[row]; + return true; +} + +bool PmpColumnReader::ParseData(uint32* rows_read) { + DCHECK(data_.get() != NULL); + DCHECK_GE(length_, kPmpHeaderSize); + + // Check all magic bytes. + if (memcmp(&kPmpMagic1, &data_[kPmpMagic1Offset], sizeof(kPmpMagic1)) != 0 || + memcmp(&kPmpMagic2, &data_[kPmpMagic2Offset], sizeof(kPmpMagic2)) != 0 || + memcmp(&kPmpMagic3, &data_[kPmpMagic3Offset], sizeof(kPmpMagic3)) != 0 || + memcmp(&kPmpMagic4, &data_[kPmpMagic4Offset], sizeof(kPmpMagic4)) != 0) { + return false; + } + + uint16 field_type_data = + *(reinterpret_cast<uint16*>(&data_[kPmpFieldType1Offset])); + + // Verify if field type matches second declaration + if (field_type_data != + *(reinterpret_cast<uint16*>(&data_[kPmpFieldType2Offset]))) { + return false; + } + + field_type_ = static_cast<PmpFieldType>(field_type_data); + + rows_ = *(reinterpret_cast<uint32*>(&data_[kPmpRowCountOffset])); + + size_t body_length = length_ - kPmpHeaderSize; + size_t expected_body_length = 0; + switch (field_type_) { + case PMP_TYPE_STRING: + expected_body_length = IndexStrings(); + break; + case PMP_TYPE_UINT32: + expected_body_length = rows_ * sizeof(uint32); + break; + case PMP_TYPE_DOUBLE64: + expected_body_length = rows_ * sizeof(double); + break; + case PMP_TYPE_UINT8: + expected_body_length = rows_ * sizeof(uint8); + break; + case PMP_TYPE_UINT64: + expected_body_length = rows_ * sizeof(uint64); + break; + default: + return false; + break; + } + + if (body_length == expected_body_length && rows_read) + *rows_read = rows_; + return body_length == expected_body_length; +} + +long PmpColumnReader::IndexStrings() { + DCHECK(data_.get() != NULL); + DCHECK_GE(length_, kPmpHeaderSize); + + strings_.reserve(rows_); + + size_t bytes_parsed = kPmpHeaderSize; + const uint8* data_cursor = data_.get() + kPmpHeaderSize; + + while (strings_.size() < rows_) { + const uint8* string_end = static_cast<const uint8*>( + memchr(data_cursor, '\0', length_ - bytes_parsed)); + + // Fail if cannot find null termination. String runs on past file end. + if (string_end == NULL) + return -1; + + // Length of string. (+1 to include the termination character). + ptrdiff_t length_in_bytes = string_end - data_cursor + 1; + + strings_.push_back(reinterpret_cast<const char*>(data_cursor)); + data_cursor += length_in_bytes; + bytes_parsed += length_in_bytes; + } + + return bytes_parsed - kPmpHeaderSize; +} + +} // namespace picasaimport diff --git a/webkit/fileapi/media/picasa/pmp_column_reader.h b/webkit/fileapi/media/picasa/pmp_column_reader.h new file mode 100644 index 0000000..1266574 --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_column_reader.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef WEBKIT_FILEAPI_MEDIA_PICASA_PMP_COLUMN_READER_H_ +#define WEBKIT_FILEAPI_MEDIA_PICASA_PMP_COLUMN_READER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "webkit/fileapi/media/picasa/pmp_constants.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class FilePath; +} + +namespace picasaimport { + +// Reads a single PMP column from a file. +class WEBKIT_STORAGE_EXPORT_PRIVATE PmpColumnReader { + public: + PmpColumnReader(); + virtual ~PmpColumnReader(); + + // Returns true if read successfully. + // |rows_read| is undefined if returns false. + bool Init(const base::FilePath& filepath, uint32* rows_read); + + // These functions read the value of that |row| into |result|. + // Functions return false if the column is of the wrong type or the row + // is out of range. + bool ReadString(const uint32 row, std::string* result) const; + bool ReadUInt32(const uint32 row, uint32* result) const; + bool ReadDouble64(const uint32 row, double* result) const; + bool ReadUInt8(const uint32 row, uint8* result) const; + bool ReadUInt64(const uint32 row, uint64* result) const; + + // Returns the native encoding of field_type. + PmpFieldType field_type() const { + return field_type_; + } + + private: + bool ParseData(uint32* rows_read); + // Returns the number of bytes parsed in the body, or, -1 on failure. + long IndexStrings(); + + // Source data + scoped_array<uint8> data_; + size_t length_; + + // Header data + PmpFieldType field_type_; + uint32 rows_; + + // Index of string start locations if fields are strings. Empty otherwise. + std::vector<const char*> strings_; + + DISALLOW_COPY_AND_ASSIGN(PmpColumnReader); +}; + +} // namespace picasaimport + +#endif // WEBKIT_FILEAPI_MEDIA_PICASA_PMP_COLUMN_READER_H_ diff --git a/webkit/fileapi/media/picasa/pmp_column_reader_unittest.cc b/webkit/fileapi/media/picasa/pmp_column_reader_unittest.cc new file mode 100644 index 0000000..556cc6b --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_column_reader_unittest.cc @@ -0,0 +1,179 @@ +// 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 <algorithm> +#include <vector> + +#include "base/file_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/media/picasa/pmp_column_reader.h" +#include "webkit/fileapi/media/picasa/pmp_constants.h" +#include "webkit/fileapi/media/picasa/pmp_test_helper.h" + +namespace { + +using picasaimport::PmpColumnReader; +using picasaimport::PmpTestHelper; + +// Overridden version of Read method to make test code templatable. +bool DoRead(const PmpColumnReader* reader, uint32 row, std::string* target) { + return reader->ReadString(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint32* target) { + return reader->ReadUInt32(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, double* target) { + return reader->ReadDouble64(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint8* target) { + return reader->ReadUInt8(row, target); +} + +bool DoRead(const PmpColumnReader* reader, uint32 row, uint64* target) { + return reader->ReadUInt64(row, target); +} + +// TestValid +template<class T> +void TestValid(const picasaimport::PmpFieldType field_type, + const std::vector<T>& elems) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader; + + uint32 rows_read = 0xFF; + + std::vector<uint8> data = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + ASSERT_TRUE(test_helper.InitColumnReaderFromBytes(&reader, data, &rows_read)); + EXPECT_EQ(elems.size(), rows_read); + + for (uint32 i = 0; i < elems.size() && i < rows_read; i++) { + T target; + EXPECT_TRUE(DoRead(&reader, i, &target)); + EXPECT_EQ(elems[i], target); + } +} + +template<class T> +void TestMalformed(const picasaimport::PmpFieldType field_type, + const std::vector<T>& elems) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader1, reader2, reader3, reader4; + + std::vector<uint8> data_too_few_declared_rows = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size()-1, elems); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader1, data_too_few_declared_rows, NULL)); + + std::vector<uint8> data_too_many_declared_rows = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size()+1, elems); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader2, data_too_many_declared_rows, NULL)); + + std::vector<uint8> data_truncated = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + data_truncated.resize(data_truncated.size()-10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader3, data_truncated, NULL)); + + std::vector<uint8> data_padded = + PmpTestHelper::MakeHeaderAndBody(field_type, elems.size(), elems); + data_padded.resize(data_padded.size()+10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader4, data_padded, NULL)); +} + +template<class T> +void TestPrimitive(const picasaimport::PmpFieldType field_type) { + // Make an ascending vector of the primitive. + uint32 n = 100; + std::vector<T> data(n, 0); + for (uint32 i = 0; i < n; i++) { + data[i] = i*3; + } + + TestValid<T>(field_type, data); + TestMalformed<T>(field_type, data); +} + + +TEST(PmpColumnReaderTest, HeaderParsingAndValidation) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + PmpColumnReader reader1, reader2, reader3, reader4, reader5; + + // Good header. + uint32 rows_read = 0xFF; + std::vector<uint8> good_header = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + ASSERT_TRUE(test_helper.InitColumnReaderFromBytes( + &reader1, good_header, &rows_read)); + EXPECT_EQ(0U, rows_read) << "Read non-zero rows from header-only data."; + + // Botch up elements of the header. + std::vector<uint8> bad_magic_byte = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + bad_magic_byte[0] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader2, bad_magic_byte, NULL)); + + // Corrupt means the type fields don't agree. + std::vector<uint8> corrupt_type = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + corrupt_type[picasaimport::kPmpFieldType1Offset] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader3, corrupt_type, NULL)); + + std::vector<uint8> invalid_type = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + invalid_type[picasaimport::kPmpFieldType1Offset] = 0xff; + invalid_type[picasaimport::kPmpFieldType2Offset] = 0xff; + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader4, invalid_type, NULL)); + + std::vector<uint8> incomplete_header = PmpTestHelper::MakeHeader( + picasaimport::PMP_TYPE_STRING, 0); + incomplete_header.resize(10); + EXPECT_FALSE(test_helper.InitColumnReaderFromBytes( + &reader5, incomplete_header, NULL)); +} + +TEST(PmpColumnReaderTest, StringParsing) { + std::vector<std::string> empty_strings(100, ""); + + // Test empty strings read okay. + TestValid(picasaimport::PMP_TYPE_STRING, empty_strings); + + std::vector<std::string> mixed_strings; + mixed_strings.push_back(""); + mixed_strings.push_back("Hello"); + mixed_strings.push_back("World"); + mixed_strings.push_back(""); + mixed_strings.push_back("123123"); + mixed_strings.push_back("Q"); + mixed_strings.push_back(""); + + // Test that a mixed set of strings read correctly. + TestValid(picasaimport::PMP_TYPE_STRING, mixed_strings); + + // Test with the data messed up in a variety of ways. + TestMalformed(picasaimport::PMP_TYPE_STRING, mixed_strings); +} + +TEST(PmpColumnReaderTest, PrimitiveParsing) { + TestPrimitive<uint32>(picasaimport::PMP_TYPE_UINT32); + TestPrimitive<double>(picasaimport::PMP_TYPE_DOUBLE64); + TestPrimitive<uint8>(picasaimport::PMP_TYPE_UINT8); + TestPrimitive<uint64>(picasaimport::PMP_TYPE_UINT64); +} + +} // namespace diff --git a/webkit/fileapi/media/picasa/pmp_constants.h b/webkit/fileapi/media/picasa/pmp_constants.h new file mode 100644 index 0000000..52013b3 --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_constants.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef WEBKIT_FILEAPI_MEDIA_PICASA_PMP_CONSTANTS_H_ +#define WEBKIT_FILEAPI_MEDIA_PICASA_PMP_CONSTANTS_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace picasaimport { + +// PMP file format. +// Info derived from: http://sbktech.blogspot.com/2011/12/picasa-pmp-format.html + +const char* const kPmpExtension = "pmp"; + +const size_t kPmpHeaderSize = 20; + +const int kPmpMagic1Offset = 0; +const int kPmpMagic2Offset = 6; +const int kPmpMagic3Offset = 8; +const int kPmpMagic4Offset = 14; + +const uint32 kPmpMagic1 = 0x3fcccccd; +const uint16 kPmpMagic2 = 0x1332; +const uint32 kPmpMagic3 = 0x00000002; +const uint16 kPmpMagic4 = 0x1332; + +const int kPmpFieldType1Offset = 4; +const int kPmpFieldType2Offset = 12; +const int kPmpRowCountOffset = 16; + +enum PmpFieldType { + PMP_TYPE_STRING = 0x00, + PMP_TYPE_UINT32 = 0x01, + PMP_TYPE_DOUBLE64 = 0x02, + PMP_TYPE_UINT8 = 0x03, + PMP_TYPE_UINT64 = 0x04, + PMP_TYPE_INVALID = 0xff +}; + +} // namespace picasaimport + +#endif // WEBKIT_FILEAPI_MEDIA_PICASA_PMP_CONSTANTS_H_ diff --git a/webkit/fileapi/media/picasa/pmp_table_reader.cc b/webkit/fileapi/media/picasa/pmp_table_reader.cc new file mode 100644 index 0000000..c8f76d4 --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_table_reader.cc @@ -0,0 +1,88 @@ +// 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 "webkit/fileapi/media/picasa/pmp_table_reader.h" + +#include <algorithm> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "webkit/fileapi/media/picasa/pmp_column_reader.h" +#include "webkit/fileapi/media/picasa/pmp_constants.h" + +namespace picasaimport { + +namespace { + +COMPILE_ASSERT(sizeof(double) == 8, double_must_be_8_bytes_long); + +} // namespace + +PmpTableReader::PmpTableReader() : column_readers_(), max_row_count_(0) { } + +PmpTableReader::~PmpTableReader() { } + +bool PmpTableReader::Init(const std::string& table_name, + const base::FilePath& directory_path, + const std::vector<std::string>& columns) { + DCHECK(!columns.empty()); + + if (!column_readers_.empty()) + return false; + + if (!file_util::DirectoryExists(directory_path)) + return false; + + std::string table_prefix = table_name + "_"; + std::string indicator_file_name = table_prefix + "0"; + + base::FilePath indicator_file = directory_path.Append( + base::FilePath::FromUTF8Unsafe(indicator_file_name)); + + // Look for the indicator_file file, indicating table existence. + if (!file_util::PathExists(indicator_file) || + file_util::DirectoryExists(indicator_file)) { + return false; + } + + ScopedVector<PmpColumnReader> column_readers; + uint32 max_row_count = 0; + + for (std::vector<std::string>::const_iterator it = columns.begin(); + it != columns.end(); ++it) { + std::string filename = table_prefix + *it + "." + kPmpExtension; + + base::FilePath column_file_path = directory_path.Append( + base::FilePath::FromUTF8Unsafe(filename)); + + PmpColumnReader* column_reader = new PmpColumnReader(); + column_readers.push_back(column_reader); + + uint32 row_cnt; + + if (!column_reader->Init(column_file_path, &row_cnt)) + return false; + + max_row_count = std::max(max_row_count, row_cnt); + } + + column_readers_ = column_readers.Pass(); + max_row_count_ = max_row_count; + + return true; +} + +uint32 PmpTableReader::RowCount() const { + return max_row_count_; +} + +std::vector<const PmpColumnReader*> PmpTableReader::GetColumns() const { + std::vector<const PmpColumnReader*> readers( + column_readers_.begin(), column_readers_.end()); + return readers; +} + +} // namespace picasaimport diff --git a/webkit/fileapi/media/picasa/pmp_table_reader.h b/webkit/fileapi/media/picasa/pmp_table_reader.h new file mode 100644 index 0000000..de2627f --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_table_reader.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef WEBKIT_FILEAPI_MEDIA_PICASA_PMP_TABLE_READER_H_ +#define WEBKIT_FILEAPI_MEDIA_PICASA_PMP_TABLE_READER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_vector.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class FilePath; +} + +namespace picasaimport { + +class PmpColumnReader; + +class WEBKIT_STORAGE_EXPORT_PRIVATE PmpTableReader { + public: + PmpTableReader(); + + virtual ~PmpTableReader(); + + // |columns| parameter will define, in-order, the columns returned by + // subsequent columns to GetColumns() if Init() succeeds. + bool Init(const std::string& table_name, + const base::FilePath& directory_path, + const std::vector<std::string>& columns); + + // Returns a const "view" of the current contents of |column_readers_|. + std::vector<const PmpColumnReader*> GetColumns() const; + + uint32 RowCount() const; + + private: + ScopedVector<PmpColumnReader> column_readers_; + uint32 max_row_count_; + + DISALLOW_COPY_AND_ASSIGN(PmpTableReader); +}; + +} // namespace picasaimport + +#endif // WEBKIT_FILEAPI_MEDIA_PICASA_PMP_TABLE_READER_H_ diff --git a/webkit/fileapi/media/picasa/pmp_table_reader_unittest.cc b/webkit/fileapi/media/picasa/pmp_table_reader_unittest.cc new file mode 100644 index 0000000..66d3fa1 --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_table_reader_unittest.cc @@ -0,0 +1,72 @@ +// 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 <algorithm> +#include <vector> + +#include "base/file_util.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/media/picasa/pmp_column_reader.h" +#include "webkit/fileapi/media/picasa/pmp_constants.h" +#include "webkit/fileapi/media/picasa/pmp_table_reader.h" +#include "webkit/fileapi/media/picasa/pmp_test_helper.h" + +namespace { + +using picasaimport::PmpTestHelper; + +TEST(PmpTableReaderTest, RowCountAndFieldType) { + PmpTestHelper test_helper; + ASSERT_TRUE(test_helper.Init()); + + std::string table_name = "testtable"; + + std::vector<std::string> column_names; + column_names.push_back("strings"); + column_names.push_back("uint32s"); + column_names.push_back("doubles"); + + const std::vector<std::string> strings_vector(10, "Hello"); + const std::vector<uint32> uint32s_vector(30, 42); + const std::vector<double> doubles_vector(20, 0.5); + + picasaimport::PmpFieldType column_field_types[] = { + picasaimport::PMP_TYPE_STRING, + picasaimport::PMP_TYPE_UINT32, + picasaimport::PMP_TYPE_DOUBLE64 + }; + + const uint32 max_rows = uint32s_vector.size(); + + base::FilePath indicator_path = test_helper.GetTempDirPath().Append( + base::FilePath::FromUTF8Unsafe(table_name + "_0")); + + ASSERT_EQ(0, file_util::WriteFile(indicator_path, NULL, 0)); + // Write three column files, one each for strings, uint32s, and doubles. + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + table_name, column_names[0], column_field_types[0], strings_vector)); + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + table_name, column_names[1], column_field_types[1], uint32s_vector)); + + ASSERT_TRUE(test_helper.WriteColumnFileFromVector( + table_name, column_names[2], column_field_types[2], doubles_vector)); + + picasaimport::PmpTableReader table_reader; + ASSERT_TRUE(table_reader.Init( + table_name, test_helper.GetTempDirPath(), column_names)); + + EXPECT_EQ(max_rows, table_reader.RowCount()); + + const std::vector<const picasaimport::PmpColumnReader*> column_readers = + table_reader.GetColumns(); + + for (int i = 0; i < 3; i++) { + EXPECT_EQ(column_field_types[i], column_readers[i]->field_type()); + } +} + +} // namespace diff --git a/webkit/fileapi/media/picasa/pmp_test_helper.cc b/webkit/fileapi/media/picasa/pmp_test_helper.cc new file mode 100644 index 0000000..69f92ee --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_test_helper.cc @@ -0,0 +1,172 @@ +// 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 "webkit/fileapi/media/picasa/pmp_test_helper.h" + +#include <algorithm> +#include <iterator> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "webkit/fileapi/media/picasa/pmp_column_reader.h" + +namespace picasaimport { + +namespace { + +bool WriteToFile(const base::FilePath& path, std::vector<uint8> data) { + // Cast for usage in WriteFile function + const char* data_char = reinterpret_cast<const char*>(&data[0]); + size_t bytes_written = file_util::WriteFile(path, data_char, data.size()); + return (bytes_written == data.size()); +} + +// Flatten a vector of elements into an array of bytes. +template<class T> +std::vector<uint8> Flatten(const std::vector<T>& elems) { + const uint8* elems0 = reinterpret_cast<const uint8*>(&elems[0]); + std::vector<uint8> data_body(elems0, elems0 + sizeof(T)*elems.size()); + + return data_body; +} + +// Custom specialization for std::string. +template<> +std::vector<uint8> Flatten(const std::vector<std::string>& strings) { + std::vector<uint8> totalchars; + + for(std::vector<std::string>::const_iterator it = strings.begin(); + it != strings.end(); ++it) { + std::copy(it->begin(), it->end(), std::back_inserter(totalchars)); + totalchars.push_back('\0'); // Add the null termination too. + } + + return totalchars; +} + +// Returns a new vector with the concatenated contents of |a| and |b|. +std::vector<uint8> CombinedVectors(const std::vector<uint8>& a, + const std::vector<uint8>& b) { + std::vector<uint8> total; + + std::copy(a.begin(), a.end(), std::back_inserter(total)); + std::copy(b.begin(), b.end(), std::back_inserter(total)); + + return total; +} + +} // namespace + +PmpTestHelper::PmpTestHelper() { } + +bool PmpTestHelper::Init() { + return temp_dir_.CreateUniqueTempDir(); +} + +base::FilePath PmpTestHelper::GetTempDirPath() { + DCHECK(temp_dir_.IsValid()); + return temp_dir_.path(); +} + +template<class T> +bool PmpTestHelper::WriteColumnFileFromVector( + const std::string& table_name, const std::string& column_name, + const PmpFieldType field_type, const std::vector<T>& elements_vector) { + DCHECK(temp_dir_.IsValid()); + + std::string file_name = table_name + "_" + column_name + "." + kPmpExtension; + + base::FilePath path = temp_dir_.path().Append( + base::FilePath::FromUTF8Unsafe(file_name)); + + std::vector<uint8> data = PmpTestHelper::MakeHeaderAndBody( + field_type, elements_vector.size(), elements_vector); + + return WriteToFile(path, data); +} + +// Explicit Instantiation for all the valid types. +template bool PmpTestHelper::WriteColumnFileFromVector<std::string>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<std::string>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint32>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<uint32>&); +template bool PmpTestHelper::WriteColumnFileFromVector<double>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<double>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint8>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<uint8>&); +template bool PmpTestHelper::WriteColumnFileFromVector<uint64>( + const std::string&, const std::string&, const PmpFieldType, + const std::vector<uint64>&); + +bool PmpTestHelper::InitColumnReaderFromBytes( + PmpColumnReader* const reader, const std::vector<uint8>& data, + uint32* rows_read) { + DCHECK(temp_dir_.IsValid()); + + base::FilePath temp_path; + + if (!file_util::CreateTemporaryFileInDir(temp_dir_.path(), &temp_path) || + !WriteToFile(temp_path, data)) { + return false; + } + + bool success = reader->Init(temp_path, rows_read); + + file_util::Delete(temp_path, true); + + return success; + +} + +// Return a vector so we don't have to worry about memory management. +std::vector<uint8> PmpTestHelper::MakeHeader(const PmpFieldType field_type, + const uint32 row_count) { + std::vector<uint8> header(picasaimport::kPmpHeaderSize); + + // Copy in magic bytes. + memcpy(&header[picasaimport::kPmpMagic1Offset], &picasaimport::kPmpMagic1, + sizeof(picasaimport::kPmpMagic1)); + memcpy(&header[picasaimport::kPmpMagic2Offset], &picasaimport::kPmpMagic2, + sizeof(picasaimport::kPmpMagic2)); + memcpy(&header[picasaimport::kPmpMagic3Offset], &picasaimport::kPmpMagic3, + sizeof(picasaimport::kPmpMagic3)); + memcpy(&header[picasaimport::kPmpMagic4Offset], &picasaimport::kPmpMagic4, + sizeof(picasaimport::kPmpMagic4)); + + // Copy in field type. + memcpy(&header[picasaimport::kPmpFieldType1Offset], &field_type, 2); + memcpy(&header[picasaimport::kPmpFieldType2Offset], &field_type, 2); + + // Copy in row count. + memcpy(&header[picasaimport::kPmpRowCountOffset], &row_count, 4); + + return header; +} + +template<class T> +std::vector<uint8> PmpTestHelper::MakeHeaderAndBody( + const PmpFieldType field_type, const uint32 row_count, + const std::vector<T>& elems) { + return CombinedVectors(PmpTestHelper::MakeHeader(field_type, row_count), + Flatten(elems)); +} + +// Explicit Instantiation for all the valid types. +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<std::string>( + const PmpFieldType, const uint32, const std::vector<std::string>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint32>( + const PmpFieldType, const uint32, const std::vector<uint32>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<double>( + const PmpFieldType, const uint32, const std::vector<double>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint8>( + const PmpFieldType, const uint32, const std::vector<uint8>&); +template std::vector<uint8> PmpTestHelper::MakeHeaderAndBody<uint64>( + const PmpFieldType, const uint32, const std::vector<uint64>&); + +} // namespace picasaimport diff --git a/webkit/fileapi/media/picasa/pmp_test_helper.h b/webkit/fileapi/media/picasa/pmp_test_helper.h new file mode 100644 index 0000000..2da2118 --- /dev/null +++ b/webkit/fileapi/media/picasa/pmp_test_helper.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef WEBKIT_FILEAPI_MEDIA_PICASA_PMP_TEST_HELPER_H_ +#define WEBKIT_FILEAPI_MEDIA_PICASA_PMP_TEST_HELPER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/files/scoped_temp_dir.h" +#include "webkit/fileapi/media/picasa/pmp_constants.h" + +namespace base { +class FilePath; +} // namespace base + +namespace picasaimport { + +class PmpColumnReader; + +// A helper class used for unit tests only +class PmpTestHelper { + public: + PmpTestHelper(); + + bool Init(); + + base::FilePath GetTempDirPath(); + + template<class T> + bool WriteColumnFileFromVector(const std::string& table_name, + const std::string& column_name, + const PmpFieldType field_type, + const std::vector<T>& elements_vector); + + bool InitColumnReaderFromBytes(PmpColumnReader* const reader, + const std::vector<uint8>& data, + uint32* rows_read); + + static std::vector<uint8> MakeHeader(const PmpFieldType field_type, + const uint32 row_count); + + template<class T> + static std::vector<uint8> MakeHeaderAndBody(const PmpFieldType field_type, + const uint32 row_count, + const std::vector<T>& elems); + + private: + base::ScopedTempDir temp_dir_; +}; + +} // namespace picasaimport + +#endif // WEBKIT_FILEAPI_MEDIA_PICASA_PMP_TEST_HELPER_H_ diff --git a/webkit/fileapi/webkit_fileapi.gypi b/webkit/fileapi/webkit_fileapi.gypi index d0a36a1..cbcffaf 100644 --- a/webkit/fileapi/webkit_fileapi.gypi +++ b/webkit/fileapi/webkit_fileapi.gypi @@ -74,6 +74,11 @@ '../fileapi/media/mtp_device_file_system_config.h', '../fileapi/media/native_media_file_util.cc', '../fileapi/media/native_media_file_util.h', + '../fileapi/media/picasa/pmp_column_reader.cc', + '../fileapi/media/picasa/pmp_column_reader.h', + '../fileapi/media/picasa/pmp_constants.h', + '../fileapi/media/picasa/pmp_table_reader.cc', + '../fileapi/media/picasa/pmp_table_reader.h', '../fileapi/mount_points.cc', '../fileapi/mount_points.h', '../fileapi/native_file_util.cc', |