diff options
author | achuith@chromium.org <achuith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-25 00:00:32 +0000 |
---|---|---|
committer | achuith@chromium.org <achuith@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-25 00:00:32 +0000 |
commit | f99a712d8b469025ccafdbe006fcebd6544271f8 (patch) | |
tree | a9fd5e293918d875a9f3de7fe9c286da7cd29a88 | |
parent | 72e39d4c4ea617083668e883fc0186dea640ca8e (diff) | |
download | chromium_src-f99a712d8b469025ccafdbe006fcebd6544271f8.zip chromium_src-f99a712d8b469025ccafdbe006fcebd6544271f8.tar.gz chromium_src-f99a712d8b469025ccafdbe006fcebd6544271f8.tar.bz2 |
GDataDB support with leveldb.
* Define GDataDB interface with methods to Put, Get and Delete. Also define a path-based iterator.
* GDataLevelDB implements GDataDB using leveldb.
* Add methods SerializeToString and FromProtoString to serialize GDataEntry to strings and vice versa.
* GDataDBTests test Put, Get, Delete for files and directories.
* Iterator tests in GDataDBTests.
* GDataDBFactory class to create GDataLevelDB instance.
TODO:
* There is no integration with GDataRootDirectory/GDataFileSystem yet.
BUG=chromium-os:29232
TEST=unittests pass.
Review URL: https://chromiumcodereview.appspot.com/10168025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133815 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_db.h | 69 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_db_factory.cc | 22 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_db_factory.h | 25 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_db_unittest.cc | 266 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_files.cc | 70 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_files.h | 28 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_leveldb.cc | 204 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_leveldb.h | 60 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 5 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 |
10 files changed, 736 insertions, 14 deletions
diff --git a/chrome/browser/chromeos/gdata/gdata_db.h b/chrome/browser/chromeos/gdata/gdata_db.h new file mode 100644 index 0000000..67d1e58 --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_db.h @@ -0,0 +1,69 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_CHROMEOS_GDATA_GDATA_DB_H_ +#define CHROME_BROWSER_CHROMEOS_GDATA_GDATA_DB_H_ +#pragma once + +#include <string> + +#include "base/memory/scoped_ptr.h" + +class FilePath; + +namespace gdata { + +class GDataEntry; +class GDataDBIter; + +// GData Database interface class. +class GDataDB { + public: + enum Status { + DB_OK = 0, + DB_KEY_NOT_FOUND, // Key not found. + DB_CORRUPTION, // Database file corrupt. + DB_IO_ERROR, // File I/O error. + DB_INTERNAL_ERROR, + }; + + virtual ~GDataDB() {} + + // Puts |entry| to the database. + virtual Status Put(const GDataEntry& entry) = 0; + + // Deletes a database entry with key |resource_id| or |path| respectively. + virtual Status DeleteByResourceId(const std::string& resource_id) = 0; + virtual Status DeleteByPath(const FilePath& path) = 0; + + // Fetches a GDataEntry* by key |resource_id| or |path| respectively. + virtual Status GetByResourceId(const std::string& resource_id, + scoped_ptr<GDataEntry>* entry) = 0; + virtual Status GetByPath(const FilePath& path, + scoped_ptr<GDataEntry>* entry) = 0; + + // Creates an iterator to fetch all GDataEntry's under |path|. + // Will not return NULL. + virtual scoped_ptr<GDataDBIter> CreateIterator(const FilePath& path) = 0; + + protected: + GDataDB() {} +}; + +// GData Database Iterator interface class. +class GDataDBIter { + public: + virtual ~GDataDBIter() {} + + // Fetches the next |entry| in the iteration sequence. Returns false when + // there are no more entries. + virtual bool GetNext(std::string* path, scoped_ptr<GDataEntry>* entry) = 0; + + protected: + GDataDBIter() {} +}; + +} // namespace gdata + +#endif // CHROME_BROWSER_CHROMEOS_GDATA_GDATA_DB_H_ diff --git a/chrome/browser/chromeos/gdata/gdata_db_factory.cc b/chrome/browser/chromeos/gdata/gdata_db_factory.cc new file mode 100644 index 0000000..31c88ca --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_db_factory.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2012 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/chromeos/gdata/gdata_db_factory.h" + +#include "base/file_path.h" +#include "base/logging.h" +#include "chrome/browser/chromeos/gdata/gdata_leveldb.h" + +namespace gdata { +namespace db_factory { + +scoped_ptr<GDataDB> CreateGDataDB(const FilePath& db_path) { + DVLOG(1) << "CreateGDataDB " << db_path.value(); + GDataLevelDB* level_db = new GDataLevelDB(); + level_db->Init(db_path); + return scoped_ptr<GDataDB>(level_db); +} + +} // namespace db_factory +} // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_db_factory.h b/chrome/browser/chromeos/gdata/gdata_db_factory.h new file mode 100644 index 0000000..ad88e1f --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_db_factory.h @@ -0,0 +1,25 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_CHROMEOS_GDATA_GDATA_DB_FACTORY_H_ +#define CHROME_BROWSER_CHROMEOS_GDATA_GDATA_DB_FACTORY_H_ +#pragma once + +#include "base/memory/scoped_ptr.h" + +class FilePath; + +namespace gdata { + +class GDataDB; + +namespace db_factory { + +// Factory method to create an instance of GDataDB. +scoped_ptr<GDataDB> CreateGDataDB(const FilePath& db_path); + +} // namespace db_factory +} // namespace gdata + +#endif // CHROME_BROWSER_CHROMEOS_GDATA_GDATA_DB_FACTORY_H_ diff --git a/chrome/browser/chromeos/gdata/gdata_db_unittest.cc b/chrome/browser/chromeos/gdata/gdata_db_unittest.cc new file mode 100644 index 0000000..f35db48 --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_db_unittest.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2012 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/chromeos/gdata/gdata_db.h" + +#include "base/string_number_conversions.h" +#include "chrome/browser/chromeos/gdata/gdata_db_factory.h" +#include "chrome/browser/chromeos/gdata/gdata_files.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "chrome/test/base/testing_profile.h" + +namespace gdata { +namespace { + +class GDataDBTest : public testing::Test { + public: + GDataDBTest() { + } + + virtual ~GDataDBTest() { + } + + protected: + // testing::Test implementation. + virtual void SetUp() OVERRIDE; + + // Tests GDataDB::GetPath and GDataDB::ResourceId, ensuring that an entry + // matching |source| does not exist. + void TestGetNotFound(const GDataEntry& source); + + // Tests GDataDB::GetPath and GDataDB::ResourceId, ensuring that an entry + // matching |source| exists. + void TestGetFound(const GDataEntry& source); + + // Initialize the database with the following entries: + // dir1 + // dir2 + // dir1/dir3 + // dir1/file4 + // dir1/file5 + // dir2/file6 + // dir2/file7 + // dir2/file8 + // dir1/dir3/file9 + // dir1/dir3/file10 + void InitDB(); + + // Helper functions to add a directory/file, incrementing index. + GDataDirectory* AddDirectory(GDataDirectory* parent, int sequence_id); + GDataFile* AddFile(GDataDirectory* parent, int sequence_id); + + // Tests GDataDB::NewIterator and GDataDBIter::GetNext. + // Creates an iterator with start at |parent|, and iterates comparing with + // expected |filenames|. + void TestIter(const std::string& parent, + const char* file_paths[], + size_t file_paths_size); + + scoped_ptr<TestingProfile> profile_; + scoped_ptr<GDataDB> gdata_db_; + GDataRootDirectory root_; +}; + +void GDataDBTest::SetUp() { + profile_.reset(new TestingProfile()); + gdata_db_ = db_factory::CreateGDataDB( + profile_->GetPath().Append("testdb")); +} + +void GDataDBTest::TestGetNotFound(const GDataEntry& source) { + scoped_ptr<GDataEntry> entry; + GDataDB::Status status = gdata_db_->GetByPath(source.GetFilePath(), &entry); + EXPECT_EQ(GDataDB::DB_KEY_NOT_FOUND, status); + EXPECT_FALSE(entry.get()); + + status = gdata_db_->GetByResourceId(source.resource_id(), &entry); + EXPECT_EQ(GDataDB::DB_KEY_NOT_FOUND, status); + EXPECT_FALSE(entry.get()); +} + +void GDataDBTest::TestGetFound(const GDataEntry& source) { + scoped_ptr<GDataEntry> entry; + GDataDB::Status status = gdata_db_->GetByPath(source.GetFilePath(), &entry); + EXPECT_EQ(GDataDB::DB_OK, status); + ASSERT_TRUE(entry.get()); + EXPECT_EQ(source.file_name(), entry->file_name()); + EXPECT_EQ(source.resource_id(), entry->resource_id()); + EXPECT_EQ(source.content_url(), entry->content_url()); + entry.reset(); + + status = gdata_db_->GetByResourceId(source.resource_id(), &entry); + EXPECT_EQ(GDataDB::DB_OK, status); + ASSERT_TRUE(entry.get()); + EXPECT_EQ(source.file_name(), entry->file_name()); + EXPECT_EQ(source.resource_id(), entry->resource_id()); + EXPECT_EQ(source.content_url(), entry->content_url()); +} + +void GDataDBTest::InitDB() { + int sequence_id = 1; + GDataDirectory* dir1 = AddDirectory(NULL, sequence_id++); + GDataDirectory* dir2 = AddDirectory(NULL, sequence_id++); + GDataDirectory* dir3 = AddDirectory(dir1, sequence_id++); + + AddFile(dir1, sequence_id++); + AddFile(dir1, sequence_id++); + + AddFile(dir2, sequence_id++); + AddFile(dir2, sequence_id++); + AddFile(dir2, sequence_id++); + + AddFile(dir3, sequence_id++); + AddFile(dir3, sequence_id++); +} + +GDataDirectory* GDataDBTest::AddDirectory(GDataDirectory* parent, + int sequence_id) { + GDataDirectory* dir = new GDataDirectory(parent ? parent : &root_, &root_); + const std::string dir_name = "dir" + base::IntToString(sequence_id); + const std::string resource_id = std::string("dir_resource_id:") + + dir_name; + dir->set_file_name(dir_name); + dir->set_resource_id(resource_id); + GDataDB::Status status = gdata_db_->Put(*dir); + EXPECT_EQ(GDataDB::DB_OK, status); + DVLOG(1) << "AddDirectory " << dir->GetFilePath().value() + << ", " << resource_id; + return dir; +} + +GDataFile* GDataDBTest::AddFile(GDataDirectory* parent, + int sequence_id) { + GDataFile* file = new GDataFile(parent, &root_); + const std::string file_name = "file" + base::IntToString(sequence_id); + const std::string resource_id = std::string("file_resource_id:") + + file_name; + file->set_file_name(file_name); + file->set_resource_id(resource_id); + GDataDB::Status status = gdata_db_->Put(*file); + EXPECT_EQ(GDataDB::DB_OK, status); + DVLOG(1) << "AddFile " << file->GetFilePath().value() + << ", " << resource_id; + return file; +} + +void GDataDBTest::TestIter(const std::string& parent, + const char* file_paths[], + size_t file_paths_size) { + scoped_ptr<GDataDBIter> iter = gdata_db_->CreateIterator( + FilePath::FromUTF8Unsafe(parent)); + for (size_t i = 0; ; ++i) { + scoped_ptr<GDataEntry> entry; + std::string path; + if (!iter->GetNext(&path, &entry)) { + EXPECT_EQ(i, file_paths_size); + break; + } + ASSERT_LT(i, file_paths_size); + // TODO(achuith): Also test entry->GetFilePath(). + EXPECT_EQ(FilePath(file_paths[i]).BaseName().value(), entry->file_name()); + EXPECT_EQ(file_paths[i], path); + DVLOG(1) << "Iter " << path; + } +} + +} // namespace + +TEST_F(GDataDBTest, PutTest) { + GDataDirectory* dir = new GDataDirectory(&root_, &root_); + dir->set_file_name("dir"); + dir->set_resource_id("dir_resource_id"); + dir->set_content_url(GURL("http://content/dir")); + dir->set_upload_url(GURL("http://upload/dir")); + + TestGetNotFound(*dir); + + GDataDB::Status status = gdata_db_->Put(*dir); + EXPECT_EQ(GDataDB::DB_OK, status); + + TestGetFound(*dir); + + scoped_ptr<GDataEntry> entry; + gdata_db_->GetByPath(dir->GetFilePath(), &entry); + EXPECT_EQ(dir->upload_url(), entry->AsGDataDirectory()->upload_url()); + EXPECT_TRUE(entry->AsGDataDirectory()->file_info().is_directory); + + status = gdata_db_->DeleteByPath(dir->GetFilePath()); + EXPECT_EQ(GDataDB::DB_OK, status); + + TestGetNotFound(*dir); + + GDataFile* file = new GDataFile(dir, &root_); + file->set_file_name("file1"); + file->set_resource_id("file1_resource_id"); + file->set_content_url(GURL("http://content/dir1/file1")); + file->set_file_md5("file1_md5"); + + TestGetNotFound(*file); + + status = gdata_db_->Put(*file); + EXPECT_EQ(GDataDB::DB_OK, status); + + TestGetFound(*file); + + gdata_db_->GetByPath(file->GetFilePath(), &entry); + EXPECT_EQ(file->file_md5(), entry->AsGDataFile()->file_md5()); + EXPECT_FALSE(entry->AsGDataFile()->file_info().is_directory); + + status = gdata_db_->DeleteByPath(file->GetFilePath()); + EXPECT_EQ(GDataDB::DB_OK, status); + + TestGetNotFound(*file); +} + +TEST_F(GDataDBTest, IterTest) { + InitDB(); + + const char* dir1_children[] = { + "dir1", + "dir1/dir3", + "dir1/dir3/file10", + "dir1/dir3/file9", + "dir1/file4", + "dir1/file5", + }; + TestIter("dir1", dir1_children, arraysize(dir1_children)); + + const char* dir2_children[] = { + "dir2", + "dir2/file6", + "dir2/file7", + "dir2/file8", + }; + TestIter("dir2", dir2_children, arraysize(dir2_children)); + + const char* dir3_children[] = { + "dir1/dir3", + "dir1/dir3/file10", + "dir1/dir3/file9", + }; + TestIter("dir1/dir3", dir3_children, arraysize(dir3_children)); + + const char* file10[] = { + "dir1/dir3/file10", + }; + TestIter(file10[0], file10, arraysize(file10)); + + const char* all_entries[] = { + "dir1", + "dir1/dir3", + "dir1/dir3/file10", + "dir1/dir3/file9", + "dir1/file4", + "dir1/file5", + "dir2", + "dir2/file6", + "dir2/file7", + "dir2/file8", + }; + TestIter("", all_entries, arraysize(all_entries)); + + TestIter("dir4", NULL, 0); +} + +} // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_files.cc b/chrome/browser/chromeos/gdata/gdata_files.cc index 9dcd99a..43a3cca 100644 --- a/chrome/browser/chromeos/gdata/gdata_files.cc +++ b/chrome/browser/chromeos/gdata/gdata_files.cc @@ -78,10 +78,20 @@ GDataRootDirectory* GDataEntry::AsGDataRootDirectory() { return NULL; } -FilePath GDataEntry::GetFilePath() { +const GDataFile* GDataEntry::AsGDataFileConst() const { + // cast away const and call the non-const version. This is safe. + return const_cast<GDataEntry*>(this)->AsGDataFile(); +} + +const GDataDirectory* GDataEntry::AsGDataDirectoryConst() const { + // cast away const and call the non-const version. This is safe. + return const_cast<GDataEntry*>(this)->AsGDataDirectory(); +} + +FilePath GDataEntry::GetFilePath() const { FilePath path; std::vector<FilePath::StringType> parts; - for (GDataEntry* entry = this; entry != NULL; entry = entry->parent()) + for (const GDataEntry* entry = this; entry != NULL; entry = entry->parent_) parts.push_back(entry->file_name()); // Paste paths parts back together in reverse order from upward tree @@ -133,6 +143,7 @@ GDataFile::GDataFile(GDataDirectory* parent, GDataRootDirectory* root) : GDataEntry(parent, root), kind_(gdata::DocumentEntry::UNKNOWN), is_hosted_document_(false) { + file_info_.is_directory = false; } GDataFile::~GDataFile() { @@ -581,6 +592,7 @@ void GDataEntry::ToProto(GDataEntryProto* proto) const { } void GDataFile::FromProto(const GDataFileProto& proto) { + DCHECK(!proto.gdata_entry().file_info().is_directory()); GDataEntry::FromProto(proto.gdata_entry()); kind_ = DocumentEntry::EntryKind(proto.kind()); thumbnail_url_ = GURL(proto.thumbnail_url()); @@ -595,6 +607,7 @@ void GDataFile::FromProto(const GDataFileProto& proto) { void GDataFile::ToProto(GDataFileProto* proto) const { GDataEntry::ToProto(proto->mutable_gdata_entry()); + DCHECK(!proto->gdata_entry().file_info().is_directory()); proto->set_kind(kind_); proto->set_thumbnail_url(thumbnail_url_.spec()); proto->set_alternate_url(alternate_url_.spec()); @@ -607,6 +620,7 @@ void GDataFile::ToProto(GDataFileProto* proto) const { } void GDataDirectory::FromProto(const GDataDirectoryProto& proto) { + DCHECK(proto.gdata_entry().file_info().is_directory()); GDataEntry::FromProto(proto.gdata_entry()); refresh_time_ = base::Time::FromInternalValue(proto.refresh_time()); start_feed_url_ = GURL(proto.start_feed_url()); @@ -627,6 +641,7 @@ void GDataDirectory::FromProto(const GDataDirectoryProto& proto) { void GDataDirectory::ToProto(GDataDirectoryProto* proto) const { GDataEntry::ToProto(proto->mutable_gdata_entry()); + DCHECK(proto->gdata_entry().file_info().is_directory()); proto->set_refresh_time(refresh_time_.ToInternalValue()); proto->set_start_feed_url(start_feed_url_.spec()); proto->set_next_feed_url(next_feed_url_.spec()); @@ -654,18 +669,61 @@ void GDataRootDirectory::ToProto(GDataRootDirectoryProto* proto) const { proto->set_largest_changestamp(largest_changestamp_); } -void GDataRootDirectory::SerializeToString(std::string* proto_string) const { +void GDataEntry::SerializeToString(std::string* serialized_proto) const { + const GDataFile* file = AsGDataFileConst(); + const GDataDirectory* dir = AsGDataDirectoryConst(); + + if (file) { + scoped_ptr<GDataFileProto> proto(new GDataFileProto()); + file->ToProto(proto.get()); + const bool ok = proto->SerializeToString(serialized_proto); + DCHECK(ok); + } else if (dir) { + scoped_ptr<GDataDirectoryProto> proto(new GDataDirectoryProto()); + dir->ToProto(proto.get()); + const bool ok = proto->SerializeToString(serialized_proto); + DCHECK(ok); + } +} + +// static +scoped_ptr<GDataEntry> GDataEntry::FromProtoString( + const std::string& serialized_proto) { + // First try to parse as GDataDirectoryProto. Note that this can succeed for + // a serialized_proto that's really a GDataFileProto - we have to check + // is_directory to be sure. + scoped_ptr<GDataDirectoryProto> dir_proto(new GDataDirectoryProto()); + bool ok = dir_proto->ParseFromString(serialized_proto); + if (ok && dir_proto->gdata_entry().file_info().is_directory()) { + GDataDirectory* dir = new GDataDirectory(NULL, NULL); + dir->FromProto(*dir_proto); + return scoped_ptr<GDataEntry>(dir); + } + + scoped_ptr<GDataFileProto> file_proto(new GDataFileProto()); + ok = file_proto->ParseFromString(serialized_proto); + if (ok) { + DCHECK(!file_proto->gdata_entry().file_info().is_directory()); + GDataFile* file = new GDataFile(NULL, NULL); + file->FromProto(*file_proto); + return scoped_ptr<GDataEntry>(file); + } + return scoped_ptr<GDataEntry>(NULL); +} + +void GDataRootDirectory::SerializeToString( + std::string* serialized_proto) const { scoped_ptr<GDataRootDirectoryProto> proto( new GDataRootDirectoryProto()); ToProto(proto.get()); - const bool ok = proto->SerializeToString(proto_string); + const bool ok = proto->SerializeToString(serialized_proto); DCHECK(ok); } -bool GDataRootDirectory::ParseFromString(const std::string& proto_string) { +bool GDataRootDirectory::ParseFromString(const std::string& serialized_proto) { scoped_ptr<GDataRootDirectoryProto> proto( new GDataRootDirectoryProto()); - bool ok = proto->ParseFromString(proto_string); + bool ok = proto->ParseFromString(serialized_proto); if (ok) { FromProto(*proto.get()); set_origin(FROM_CACHE); diff --git a/chrome/browser/chromeos/gdata/gdata_files.h b/chrome/browser/chromeos/gdata/gdata_files.h index b7e9031..1944235 100644 --- a/chrome/browser/chromeos/gdata/gdata_files.h +++ b/chrome/browser/chromeos/gdata/gdata_files.h @@ -59,15 +59,27 @@ class GDataEntry { public: explicit GDataEntry(GDataDirectory* parent, GDataRootDirectory* root); virtual ~GDataEntry(); + virtual GDataFile* AsGDataFile(); virtual GDataDirectory* AsGDataDirectory(); virtual GDataRootDirectory* AsGDataRootDirectory(); + // const versions of AsGDataFile and AsGDataDirectory. + const GDataFile* AsGDataFileConst() const; + const GDataDirectory* AsGDataDirectoryConst() const; + // Converts DocumentEntry into GDataEntry. static GDataEntry* FromDocumentEntry(GDataDirectory* parent, DocumentEntry* doc, GDataRootDirectory* root); + // Serialize/Parse to/from string via proto classes. + // TODO(achuith): Correctly set up parent_ and root_ links in + // FromProtoString. + void SerializeToString(std::string* serialized_proto) const; + static scoped_ptr<GDataEntry> FromProtoString( + const std::string& serialized_proto); + // Convert to/from proto. void FromProto(const GDataEntryProto& proto); void ToProto(GDataEntryProto* proto) const; @@ -81,20 +93,20 @@ class GDataEntry { GDataDirectory* parent() { return parent_; } const base::PlatformFileInfo& file_info() const { return file_info_; } + const FilePath::StringType& file_name() const { return file_name_; } - const FilePath::StringType& title() const { - return title_; - } - void set_title(const FilePath::StringType& title) { - title_ = title; - } void set_file_name(const FilePath::StringType& name) { file_name_ = name; } + const FilePath::StringType& title() const { return title_; } + void set_title(const FilePath::StringType& title) { title_ = title; } + // The unique resource ID associated with this file system entry. const std::string& resource_id() const { return resource_id_; } + void set_resource_id(const std::string& res_id) { resource_id_ = res_id; } // The content URL is used for downloading regular files as is. const GURL& content_url() const { return content_url_; } + void set_content_url(const GURL& url) { content_url_ = url; } // The edit URL is used for removing files and hosted documents. const GURL& edit_url() const { return edit_url_; } @@ -111,7 +123,7 @@ class GDataEntry { // Returns virtual file path representing this file system entry. This path // corresponds to file path expected by public methods of GDataFileSyste // class. - FilePath GetFilePath(); + FilePath GetFilePath() const; // Sets |file_name_| based on the value of |title_| without name // de-duplication (see AddEntry() for details on de-duplication). @@ -221,6 +233,7 @@ class GDataFile : public GDataEntry { const std::string& etag() const { return etag_; } const std::string& id() const { return id_; } const std::string& file_md5() const { return file_md5_; } + void set_file_md5(const std::string& file_md5) { file_md5_ = file_md5; } const std::string& document_extension() const { return document_extension_; } bool is_hosted_document() const { return is_hosted_document_; } @@ -398,7 +411,6 @@ class GDataRootDirectory : public GDataDirectory { void set_serialized_size(size_t size) { serialized_size_ = size; } // GDataEntry implementation. - virtual GDataRootDirectory* AsGDataRootDirectory() OVERRIDE; // Add the entry to resource map. diff --git a/chrome/browser/chromeos/gdata/gdata_leveldb.cc b/chrome/browser/chromeos/gdata/gdata_leveldb.cc new file mode 100644 index 0000000..1966627 --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_leveldb.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2012 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/chromeos/gdata/gdata_leveldb.h" + +#include <string> + +#include "base/logging.h" +#include "chrome/browser/chromeos/gdata/gdata_files.h" +#include "leveldb/write_batch.h" + +namespace gdata { +namespace { + +const char kResourceIdPrefix[] = "id:"; +const char kPathPrefix[] = "path:"; + +// Append prefix id: to |resource_id|. +std::string ResourceIdToKey(const std::string& resource_id) { + return std::string(kResourceIdPrefix) + resource_id; +} + +// Append prefix path: to |path|. +std::string PathToKey(const FilePath& path) { + return std::string(kPathPrefix) + path.value(); +} + +GDataDB::Status GetStatus(const leveldb::Status& db_status) { + if (db_status.ok()) + return GDataDB::DB_OK; + + if (db_status.IsNotFound()) + return GDataDB::DB_KEY_NOT_FOUND; + + if (db_status.IsCorruption()) + return GDataDB::DB_CORRUPTION; + + if (db_status.IsIOError()) + return GDataDB::DB_IO_ERROR; + + NOTREACHED(); + return GDataDB::DB_INTERNAL_ERROR; +} + +} // namespace + +GDataLevelDB::GDataLevelDB() { +} + +GDataLevelDB::~GDataLevelDB() { +} + +void GDataLevelDB::Init(const FilePath& db_path) { + leveldb::DB* level_db = NULL; + leveldb::Options options; + options.create_if_missing = true; + leveldb::Status db_status = leveldb::DB::Open(options, + db_path.Append("level_db").value(), &level_db); + DCHECK(level_db); + // TODO(achuith): If db cannot be opened, we should try to recover it. + // If that fails, we should just delete it and create a new file. + DCHECK(db_status.ok()); + level_db_.reset(level_db); +} + +GDataDB::Status GDataLevelDB::Put(const GDataEntry& entry) { + // Write the serialized proto. + std::string serialized_proto; + entry.SerializeToString(&serialized_proto); + + leveldb::WriteBatch batch; + const std::string resource_id_key = + ResourceIdToKey(entry.resource_id()); + const std::string path_key = PathToKey(entry.GetFilePath()); + batch.Put(leveldb::Slice(resource_id_key), leveldb::Slice(serialized_proto)); + // Note we store the resource_id without prefix when it's the value. + batch.Put(leveldb::Slice(path_key), leveldb::Slice(entry.resource_id())); + leveldb::Status db_status = level_db_->Write( + leveldb::WriteOptions(), + &batch); + + DVLOG(1) << "GDataLevelDB::Put resource_id key = " << resource_id_key + << ", path key = " << path_key; + return GetStatus(db_status); +} + +GDataDB::Status GDataLevelDB::DeleteByResourceId( + const std::string& resource_id) { + scoped_ptr<GDataEntry> entry; + Status status = GetByResourceId(resource_id, &entry); + if (status == DB_KEY_NOT_FOUND) + return DB_OK; + else if (status != DB_OK) + return status; + + leveldb::WriteBatch batch; + const std::string resource_id_key = ResourceIdToKey(resource_id); + const std::string path_key = PathToKey(entry->GetFilePath()); + batch.Delete(leveldb::Slice(resource_id_key)); + batch.Delete(leveldb::Slice(path_key)); + + leveldb::Status db_status = level_db_->Write(leveldb::WriteOptions(), + &batch); + return GetStatus(db_status); +} + +GDataDB::Status GDataLevelDB::DeleteByPath( + const FilePath& path) { + std::string resource_id; + const Status status = ResourceIdForPath(path, &resource_id); + if (status != DB_OK) + return status; + return DeleteByResourceId(resource_id); +} + +GDataDB::Status GDataLevelDB::GetByResourceId(const std::string& resource_id, + scoped_ptr<GDataEntry>* entry) { + entry->reset(); + std::string serialized_proto; + const std::string resource_id_key = ResourceIdToKey(resource_id); + const leveldb::Status db_status = level_db_->Get(leveldb::ReadOptions(), + leveldb::Slice(resource_id_key), &serialized_proto); + + if (db_status.IsNotFound()) + return DB_KEY_NOT_FOUND; + + if (db_status.ok()) { + DCHECK(!serialized_proto.empty()); + *entry = GDataEntry::FromProtoString(serialized_proto); + DCHECK(entry->get()); + return DB_OK; + } + return GetStatus(db_status); +} + +GDataDB::Status GDataLevelDB::GetByPath(const FilePath& path, + scoped_ptr<GDataEntry>* entry) { + entry->reset(); + std::string resource_id; + const Status status = ResourceIdForPath(path, &resource_id); + if (status != DB_OK) + return status; + return GetByResourceId(resource_id, entry); +} + +GDataDB::Status GDataLevelDB::ResourceIdForPath(const FilePath& path, + std::string* resource_id) { + const std::string path_key = PathToKey(path); + const leveldb::Status db_status = level_db_->Get( + leveldb::ReadOptions(), path_key, resource_id); + + return GetStatus(db_status); +} + +scoped_ptr<GDataDBIter> GDataLevelDB::CreateIterator(const FilePath& path) { + return scoped_ptr<GDataDBIter>(new GDataLevelDBIter( + scoped_ptr<leveldb::Iterator>( + level_db_->NewIterator(leveldb::ReadOptions())), + this, + path)); +} + +GDataLevelDBIter::GDataLevelDBIter(scoped_ptr<leveldb::Iterator> level_db_iter, + GDataDB* db, + const FilePath& path) + : level_db_iter_(level_db_iter.Pass()), + db_(db), + path_(path) { + const std::string path_key = PathToKey(path); + level_db_iter_->Seek(leveldb::Slice(path_key)); +} + +GDataLevelDBIter::~GDataLevelDBIter() { +} + +bool GDataLevelDBIter::GetNext(std::string* path, + scoped_ptr<GDataEntry>* entry) { + DCHECK(path); + DCHECK(entry); + path->clear(); + entry->reset(); + + if (!level_db_iter_->Valid()) + return false; + + // Only consider keys under |path|. + const std::string path_key = PathToKey(path_); + leveldb::Slice key_slice(level_db_iter_->key()); + if (!key_slice.starts_with(path_key)) + return false; + + GDataDB::Status status = + db_->GetByResourceId(level_db_iter_->value().ToString(), entry); + DCHECK_EQ(GDataDB::DB_OK, status); + + key_slice.remove_prefix(sizeof(kPathPrefix) - 1); + path->assign(key_slice.ToString()); + + level_db_iter_->Next(); + return true; +} + +} // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_leveldb.h b/chrome/browser/chromeos/gdata/gdata_leveldb.h new file mode 100644 index 0000000..e2385cc --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_leveldb.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 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 CHROME_BROWSER_CHROMEOS_GDATA_GDATA_LEVELDB_H_ +#define CHROME_BROWSER_CHROMEOS_GDATA_GDATA_LEVELDB_H_ +#pragma once + +#include <leveldb/db.h> +#include <string> + +#include "base/file_path.h" +#include "chrome/browser/chromeos/gdata/gdata_db.h" + +namespace gdata { + +class GDataLevelDB : public GDataDB { + public: + GDataLevelDB(); + + void Init(const FilePath& db_path); + + private: + virtual ~GDataLevelDB(); + + // GDataDB implementation. + virtual Status Put(const GDataEntry& file) OVERRIDE; + virtual Status DeleteByResourceId(const std::string& resource_id) OVERRIDE; + virtual Status DeleteByPath(const FilePath& path) OVERRIDE; + virtual Status GetByResourceId(const std::string& resource_id, + scoped_ptr<GDataEntry>* file) OVERRIDE; + virtual Status GetByPath(const FilePath& path, + scoped_ptr<GDataEntry>* file) OVERRIDE; + virtual scoped_ptr<GDataDBIter> CreateIterator(const FilePath& path) OVERRIDE; + + // Returns |resource_id| for |path| by looking up path_db_. + Status ResourceIdForPath(const FilePath& path, std::string* resource_id); + + scoped_ptr<leveldb::DB> level_db_; +}; + +class GDataLevelDBIter : public GDataDBIter { + public: + GDataLevelDBIter(scoped_ptr<leveldb::Iterator> level_db_iter, + GDataDB* db, const FilePath& path); + private: + virtual ~GDataLevelDBIter(); + + // GDataDBIter implementation. + virtual bool GetNext(std::string* path, + scoped_ptr<GDataEntry>* entry) OVERRIDE; + + scoped_ptr<leveldb::Iterator> level_db_iter_; + GDataDB* db_; + const FilePath path_; +}; + +} // namespace gdata + +#endif // CHROME_BROWSER_CHROMEOS_GDATA_GDATA_LEVELDB_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 76845ad..3e3d25d 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -509,6 +509,9 @@ 'browser/chromeos/external_protocol_dialog.h', 'browser/chromeos/gdata/gdata_auth_service.cc', 'browser/chromeos/gdata/gdata_auth_service.h', + 'browser/chromeos/gdata/gdata_db.h', + 'browser/chromeos/gdata/gdata_db_factory.cc', + 'browser/chromeos/gdata/gdata_db_factory.h', 'browser/chromeos/gdata/gdata_documents_service.cc', 'browser/chromeos/gdata/gdata_documents_service.h', 'browser/chromeos/gdata/gdata_download_observer.cc', @@ -520,6 +523,8 @@ 'browser/chromeos/gdata/gdata_file_system_proxy.h', 'browser/chromeos/gdata/gdata_files.cc', 'browser/chromeos/gdata/gdata_files.h', + 'browser/chromeos/gdata/gdata_leveldb.cc', + 'browser/chromeos/gdata/gdata_leveldb.h', 'browser/chromeos/gdata/gdata_operation_registry.cc', 'browser/chromeos/gdata/gdata_operation_registry.h', 'browser/chromeos/gdata/gdata_operations.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 016a6bf..15cff58 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1203,6 +1203,7 @@ 'browser/chromeos/dbus/proxy_resolution_service_provider_unittest.cc', 'browser/chromeos/extensions/file_browser_notifications_unittest.cc', 'browser/chromeos/external_metrics_unittest.cc', + 'browser/chromeos/gdata/gdata_db_unittest.cc', 'browser/chromeos/gdata/gdata_file_system_unittest.cc', 'browser/chromeos/gdata/gdata_files_unittest.cc', 'browser/chromeos/gdata/gdata_operation_registry_unittest.cc', |