summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorericu@google.com <ericu@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-05 23:52:45 +0000
committerericu@google.com <ericu@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-05-05 23:52:45 +0000
commitca8dfe8b30b745c17b9a25ebe09d1de23d50e9b4 (patch)
tree86fd615d05c26e88ebed0187dc573fef52e2fd4b /webkit
parentdabd6f450e9594a8962ef6f79447a8bfdc1c9f05 (diff)
downloadchromium_src-ca8dfe8b30b745c17b9a25ebe09d1de23d50e9b4.zip
chromium_src-ca8dfe8b30b745c17b9a25ebe09d1de23d50e9b4.tar.gz
chromium_src-ca8dfe8b30b745c17b9a25ebe09d1de23d50e9b4.tar.bz2
Resubmission of 6898057, with build fixes.
BUG=none TEST=unit tests TBR=michaeln@chromium.org git-svn-id: svn://svn.chromium.org/chrome/trunk/src@84356 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/fileapi/file_system_directory_database.cc396
-rw-r--r--webkit/fileapi/file_system_directory_database.h85
-rw-r--r--webkit/fileapi/file_system_directory_database_unittest.cc275
-rw-r--r--webkit/fileapi/webkit_fileapi.gypi2
-rw-r--r--webkit/tools/test_shell/test_shell.gypi1
5 files changed, 759 insertions, 0 deletions
diff --git a/webkit/fileapi/file_system_directory_database.cc b/webkit/fileapi/file_system_directory_database.cc
new file mode 100644
index 0000000..04d1596
--- /dev/null
+++ b/webkit/fileapi/file_system_directory_database.cc
@@ -0,0 +1,396 @@
+// 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.
+
+#include "webkit/fileapi/file_system_directory_database.h"
+
+#include "base/pickle.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#include "third_party/leveldb/include/leveldb/iterator.h"
+#include "third_party/leveldb/include/leveldb/write_batch.h"
+
+namespace {
+
+bool PickleFromFileInfo(
+ const fileapi::FileSystemDirectoryDatabase::FileInfo& info,
+ Pickle* pickle) {
+ DCHECK(pickle);
+ std::string data_path;
+#if defined(OS_POSIX)
+ data_path = info.data_path.value();
+#elif defined(OS_WIN)
+ data_path = base::SysWideToUTF8(info.data_path.value());
+#endif
+ if (pickle->WriteInt64(info.parent_id) &&
+ pickle->WriteString(data_path) &&
+ pickle->WriteString(info.name) &&
+ pickle->WriteInt64(info.modification_time.ToInternalValue()))
+ return true;
+
+ NOTREACHED();
+ return false;
+}
+
+bool FileInfoFromPickle(
+ const Pickle& pickle,
+ fileapi::FileSystemDirectoryDatabase::FileInfo* info) {
+ void* iter = NULL;
+ std::string data_path;
+ int64 internal_time;
+
+ if (pickle.ReadInt64(&iter, &info->parent_id) &&
+ pickle.ReadString(&iter, &data_path) &&
+ pickle.ReadString(&iter, &info->name) &&
+ pickle.ReadInt64(&iter, &internal_time)) {
+#if defined(OS_POSIX)
+ info->data_path = FilePath(data_path);
+#elif defined(OS_WIN)
+ info->data_path = FilePath(base::SysUTF8ToWide(data_path));
+#endif
+ info->modification_time = base::Time::FromInternalValue(internal_time);
+ return true;
+ }
+ LOG(ERROR) << "Pickle could not be digested!";
+ return false;
+}
+
+const char kChildLookupPrefix[] = "CHILD_OF:";
+const char kChildLookupSeparator[] = ":";
+const char kLastFileIdKey[] = "LAST_FILE_ID";
+
+std::string GetChildLookupKey(
+ fileapi::FileSystemDirectoryDatabase::FileId parent_id,
+ const std::string& child_name) {
+ // TODO(ericu): child_name may need to be case-folded, pending discussion on
+ // public-webapps.
+ return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
+ std::string(kChildLookupSeparator) + child_name;
+}
+
+std::string GetChildListingKeyPrefix(
+ fileapi::FileSystemDirectoryDatabase::FileId parent_id) {
+ return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
+ std::string(kChildLookupSeparator);
+}
+
+const char* LastFileIdKey() {
+ return kLastFileIdKey;
+}
+
+std::string GetFileLookupKey(
+ fileapi::FileSystemDirectoryDatabase::FileId file_id) {
+ return base::Int64ToString(file_id);
+}
+
+} // namespace
+
+namespace fileapi {
+
+FileSystemDirectoryDatabase::FileInfo::FileInfo() {
+}
+
+FileSystemDirectoryDatabase::FileInfo::~FileInfo() {
+}
+
+FileSystemDirectoryDatabase::FileSystemDirectoryDatabase(const FilePath& path) {
+#if defined(OS_POSIX)
+ path_ = path.value();
+#elif defined(OS_WIN)
+ path_ = base::SysWideToUTF8(path.value());
+#endif
+}
+
+FileSystemDirectoryDatabase::~FileSystemDirectoryDatabase() {
+}
+
+bool FileSystemDirectoryDatabase::GetChildWithName(
+ FileId parent_id, const std::string& name, FileId* child_id) {
+ if (!Init())
+ return false;
+ DCHECK(child_id);
+ std::string child_key = GetChildLookupKey(parent_id, name);
+ std::string child_id_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
+ if (status.IsNotFound())
+ return false;
+ if (status.ok())
+ return base::StringToInt64(child_id_string, child_id);
+ HandleError(status);
+ return false;
+}
+
+bool FileSystemDirectoryDatabase::ListChildren(
+ FileId parent_id, std::vector<FileId>* children) {
+ // Check to add later: fail if parent is a file, in debug builds.
+ if (!Init())
+ return false;
+ DCHECK(children);
+ std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ iter->Seek(child_key_prefix);
+ children->clear();
+ while(iter->Valid() &&
+ StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
+ std::string child_id_string = iter->value().ToString();
+ FileId child_id;
+ if (!base::StringToInt64(child_id_string, &child_id)) {
+ LOG(ERROR) << "Hit database corruption!";
+ return false;
+ }
+ children->push_back(child_id);
+ iter->Next();
+ }
+ return true;
+}
+
+bool FileSystemDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
+ // TODO(ericu): Should we always be able to look up the root, just for
+ // consistency?
+ if (!Init())
+ return false;
+ DCHECK(info);
+ std::string file_key = GetFileLookupKey(file_id);
+ std::string file_data_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
+ if (status.ok()) {
+ return FileInfoFromPickle(
+ Pickle(file_data_string.data(), file_data_string.length()), info);
+ }
+ HandleError(status);
+ return false;
+}
+
+bool FileSystemDirectoryDatabase::AddFileInfo(
+ const FileInfo& info, FileId* file_id) {
+ if (!Init())
+ return false;
+ DCHECK(file_id);
+ std::string child_key = GetChildLookupKey(info.parent_id, info.name);
+ std::string child_id_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
+ if (status.ok()) {
+ LOG(ERROR) << "File exists already!";
+ return false;
+ }
+ if (!status.IsNotFound()) {
+ HandleError(status);
+ return false;
+ }
+
+ if (!VerifyIsDirectory(info.parent_id))
+ return false;
+
+ // This would be a fine place to limit the number of files in a directory, if
+ // we decide to add that restriction.
+
+ FileId temp_id;
+ if (!GetLastFileId(&temp_id))
+ return false;
+ ++temp_id;
+
+ leveldb::WriteBatch batch;
+ if (!AddFileInfoHelper(info, temp_id, &batch))
+ return false;
+
+ batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
+ status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(status);
+ return false;
+ }
+ *file_id = temp_id;
+ return true;
+}
+
+bool FileSystemDirectoryDatabase::RemoveFileInfo(FileId file_id) {
+ if (!Init())
+ return false;
+ leveldb::WriteBatch batch;
+ if (!RemoveFileInfoHelper(file_id, &batch))
+ return false;
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(status);
+ return false;
+ }
+ return true;
+}
+
+bool FileSystemDirectoryDatabase::UpdateFileInfo(
+ FileId file_id, const FileInfo& new_info) {
+ // TODO: We should also check to see that this doesn't create a loop, but
+ // perhaps only in a debug build.
+ if (!Init())
+ return false;
+ DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
+ FileInfo old_info;
+ if (!GetFileInfo(file_id, &old_info))
+ return false;
+ if (old_info.parent_id != new_info.parent_id &&
+ !VerifyIsDirectory(new_info.parent_id))
+ return false;
+ if (old_info.parent_id != new_info.parent_id ||
+ old_info.name != new_info.name) {
+ // Check for name clashes.
+ FileId temp_id;
+ if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
+ LOG(ERROR) << "Name collision on move.";
+ return false;
+ }
+ }
+ leveldb::WriteBatch batch;
+ if (!RemoveFileInfoHelper(file_id, &batch) ||
+ !AddFileInfoHelper(new_info, file_id, &batch))
+ return false;
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(status);
+ return false;
+ }
+ return true;
+}
+
+bool FileSystemDirectoryDatabase::UpdateModificationTime(
+ FileId file_id, const base::Time& modification_time) {
+ FileInfo info;
+ if (!GetFileInfo(file_id, &info))
+ return false;
+ info.modification_time = modification_time;
+ Pickle pickle;
+ if (!PickleFromFileInfo(info, &pickle))
+ return false;
+ leveldb::Status status = db_->Put(
+ leveldb::WriteOptions(),
+ GetFileLookupKey(file_id),
+ leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
+ pickle.size()));
+ if (!status.ok()) {
+ HandleError(status);
+ return false;
+ }
+ return true;
+}
+
+bool FileSystemDirectoryDatabase::Init() {
+ if (db_.get())
+ return true;
+
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::DB* db;
+ leveldb::Status status = leveldb::DB::Open(options, path_, &db);
+ if (status.ok()) {
+ db_.reset(db);
+ return true;
+ }
+ HandleError(status);
+ return false;
+}
+
+bool FileSystemDirectoryDatabase::GetLastFileId(FileId* file_id) {
+ if (!Init())
+ return false;
+ DCHECK(file_id);
+ std::string id_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
+ if (status.ok())
+ return base::StringToInt64(id_string, file_id);
+ if (!status.IsNotFound()) {
+ HandleError(status);
+ return false;
+ }
+ // Verify that this is a totally new database, and initialize it.
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ iter->SeekToFirst();
+ if (iter->Valid()) { // DB was not empty, but had no last fileId!
+ LOG(ERROR) << "File system origin database is corrupt!";
+ return false;
+ }
+ // This is always the first write into the database. If we ever add a
+ // version number, they should go in in a single transaction.
+ FileInfo root;
+ root.parent_id = 0;
+ root.modification_time = base::Time::Now();
+ leveldb::WriteBatch batch;
+ if (!AddFileInfoHelper(root, 0, &batch))
+ return false;
+ batch.Put(LastFileIdKey(), base::Int64ToString(0));
+ status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(status);
+ return false;
+ }
+ *file_id = 0;
+ return true;
+}
+
+bool FileSystemDirectoryDatabase::VerifyIsDirectory(FileId file_id) {
+ FileInfo info;
+ if (!file_id)
+ return true; // The root is a directory.
+ if (!GetFileInfo(file_id, &info))
+ return false;
+ if (!info.data_path.empty()) {
+ LOG(ERROR) << "New parent directory is a file!";
+ return false;
+ }
+ return true;
+}
+
+// This does very few safety checks!
+bool FileSystemDirectoryDatabase::AddFileInfoHelper(
+ const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
+ std::string id_string = GetFileLookupKey(file_id);
+ if (!file_id) {
+ // The root directory doesn't need to be looked up by path from its parent.
+ DCHECK(!info.parent_id);
+ DCHECK(info.data_path.empty());
+ } else {
+ std::string child_key = GetChildLookupKey(info.parent_id, info.name);
+ batch->Put(child_key, id_string);
+ }
+ Pickle pickle;
+ if (!PickleFromFileInfo(info, &pickle))
+ return false;
+ batch->Put(
+ id_string,
+ leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
+ pickle.size()));
+ return true;
+}
+
+// This does very few safety checks!
+bool FileSystemDirectoryDatabase::RemoveFileInfoHelper(
+ FileId file_id, leveldb::WriteBatch* batch) {
+ DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
+ FileInfo info;
+ if (!GetFileInfo(file_id, &info))
+ return false;
+ if (info.data_path.empty()) { // It's a directory
+ std::vector<FileId> children;
+ if (!ListChildren(file_id, &children))
+ return false;
+ if(children.size()) {
+ LOG(ERROR) << "Can't remove a directory with children.";
+ return false;
+ }
+ }
+ batch->Delete(GetChildLookupKey(info.parent_id, info.name));
+ batch->Delete(GetFileLookupKey(file_id));
+ return true;
+}
+
+void FileSystemDirectoryDatabase::HandleError(leveldb::Status status) {
+ LOG(ERROR) << "FileSystemDirectoryDatabase failed with error: " <<
+ status.ToString();
+ db_.reset();
+}
+
+} // namespace fileapi
diff --git a/webkit/fileapi/file_system_directory_database.h b/webkit/fileapi/file_system_directory_database.h
new file mode 100644
index 0000000..94b881d
--- /dev/null
+++ b/webkit/fileapi/file_system_directory_database.h
@@ -0,0 +1,85 @@
+// 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 WEBKIT_FILEAPI_FILE_SYSTEM_DIRECTORY_DATABASE_H
+#define WEBKIT_FILEAPI_FILE_SYSTEM_DIRECTORY_DATABASE_H
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "third_party/leveldb/include/leveldb/db.h"
+
+namespace leveldb {
+class WriteBatch;
+}
+
+namespace fileapi {
+
+// This class WILL NOT protect you against producing directory loops, giving an
+// empty directory a backing data file, giving two files the same backing file,
+// or pointing to a nonexistent backing file. It does no file IO other than
+// that involved with talking to its underlying database. It does not create or
+// in any way touch real files; it only creates path entries in its database.
+
+// TODO(ericu): Safe mode, which does more checks such as the above on debug
+// builds.
+// TODO(ericu): FSCK, for a full-database check [data file validation possibly
+// done elsewhere].
+// TODO(ericu): Add a method that will give a unique filename for a data file.
+class FileSystemDirectoryDatabase {
+ public:
+ typedef int64 FileId;
+
+ struct FileInfo {
+ FileInfo();
+ ~FileInfo();
+
+ bool is_directory() {
+ return data_path.empty();
+ }
+
+ FileId parent_id;
+ FilePath data_path;
+ std::string name;
+ // We could get this from the file if we touched it on every move.
+ base::Time modification_time;
+ };
+
+ FileSystemDirectoryDatabase(const FilePath& path);
+ ~FileSystemDirectoryDatabase();
+
+ bool GetChildWithName(
+ FileId parent_id, const std::string& name, FileId* child_id);
+ // ListChildren will succeed, returning 0 children, if parent_id doesn't
+ // exist.
+ bool ListChildren(FileId parent_id, std::vector<FileId>* children);
+ bool GetFileInfo(FileId file_id, FileInfo* info);
+ bool AddFileInfo(const FileInfo& info, FileId* file_id);
+ bool RemoveFileInfo(FileId file_id);
+ // This does a full update of the FileInfo, and is what you'd use for moves
+ // and renames. If you just want to update the modification_time, use
+ // UpdateModificationTime.
+ bool UpdateFileInfo(FileId file_id, const FileInfo& info);
+ bool UpdateModificationTime(
+ FileId file_id, const base::Time& modification_time);
+
+ private:
+ bool Init();
+ bool GetLastFileId(FileId* file_id);
+ bool VerifyIsDirectory(FileId file_id);
+ bool AddFileInfoHelper(
+ const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch);
+ bool RemoveFileInfoHelper(FileId file_id, leveldb::WriteBatch* batch);
+ void HandleError(leveldb::Status status);
+
+ std::string path_;
+ scoped_ptr<leveldb::DB> db_;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_FILEAPI_FILE_SYSTEM_DIRECTORY_DATABASE_H
diff --git a/webkit/fileapi/file_system_directory_database_unittest.cc b/webkit/fileapi/file_system_directory_database_unittest.cc
new file mode 100644
index 0000000..de19a04
--- /dev/null
+++ b/webkit/fileapi/file_system_directory_database_unittest.cc
@@ -0,0 +1,275 @@
+// 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.
+
+#include "webkit/fileapi/file_system_directory_database.h"
+
+#include "base/memory/scoped_temp_dir.h"
+#include "base/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace fileapi {
+
+class FileSystemDirectoryDatabaseTest : public testing::Test {
+ public:
+ typedef FileSystemDirectoryDatabase::FileId FileId;
+ typedef FileSystemDirectoryDatabase::FileInfo FileInfo;
+
+ FileSystemDirectoryDatabaseTest() {
+ EXPECT_TRUE(base_.CreateUniqueTempDir());
+ FilePath path = base_.path().AppendASCII("db");
+ db_.reset(new FileSystemDirectoryDatabase(path));
+ }
+
+ FileSystemDirectoryDatabase* db() {
+ return db_.get();
+ }
+
+ bool AddFileInfo(FileId parent_id, const std::string& name) {
+ FileId file_id;
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ return db_->AddFileInfo(info, &file_id);
+ }
+
+ protected:
+ // Common temp base for nondestructive uses.
+ ScopedTempDir base_;
+ scoped_ptr<FileSystemDirectoryDatabase> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemDirectoryDatabaseTest);
+};
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestMissingFileGetInfo) {
+ FileId file_id = 888;
+ FileInfo info;
+ EXPECT_FALSE(db()->GetFileInfo(file_id, &info));
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestMissingParentAddFileInfo) {
+ FileId parent_id = 7;
+ EXPECT_FALSE(AddFileInfo(parent_id, "foo"));
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestAddNameClash) {
+ FileInfo info;
+ FileId file_id;
+ info.parent_id = 0;
+ info.name = "dir 0";
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id));
+
+ // Check for name clash in the root directory.
+ std::string name = info.name;
+ EXPECT_FALSE(AddFileInfo(0, name));
+ name = "dir 1";
+ EXPECT_TRUE(AddFileInfo(0, name));
+
+ name = "subdir 0";
+ EXPECT_TRUE(AddFileInfo(file_id, name));
+
+ // Check for name clash in a subdirectory.
+ EXPECT_FALSE(AddFileInfo(file_id, name));
+ name = "subdir 1";
+ EXPECT_TRUE(AddFileInfo(file_id, name));
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestRenameNoMoveNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ std::string name0 = "foo";
+ std::string name1 = "bar";
+ std::string name2 = "bas";
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ EXPECT_TRUE(AddFileInfo(0, name1));
+ info.name = name1;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id0, info));
+ info.name = name2;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id0, info));
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestMoveSameNameNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ std::string name0 = "foo";
+ std::string name1 = "bar";
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ info.parent_id = 0;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
+ info.name = name1;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+}
+
+// TODO: Test UpdateFileInfo without move or rename.
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestMoveRenameNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ std::string name0 = "foo";
+ std::string name1 = "bar";
+ std::string name2 = "bas";
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
+ info.name = name1;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+ // Also test a successful move+rename.
+ info.parent_id = file_id0;
+ info.name = name2;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestRemoveWithChildren) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ info.parent_id = 0;
+ info.name = "foo";
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_FALSE(db()->RemoveFileInfo(file_id0));
+ EXPECT_TRUE(db()->RemoveFileInfo(file_id1));
+ EXPECT_TRUE(db()->RemoveFileInfo(file_id0));
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestGetChildWithName) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ std::string name0 = "foo";
+ std::string name1 = "bar";
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_NE(file_id0, file_id1);
+
+ FileId check_file_id;
+ EXPECT_FALSE(db()->GetChildWithName(0, name1, &check_file_id));
+ EXPECT_TRUE(db()->GetChildWithName(0, name0, &check_file_id));
+ EXPECT_EQ(file_id0, check_file_id);
+ EXPECT_FALSE(db()->GetChildWithName(file_id0, name0, &check_file_id));
+ EXPECT_TRUE(db()->GetChildWithName(file_id0, name1, &check_file_id));
+ EXPECT_EQ(file_id1, check_file_id);
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestListChildren) {
+ // No children in the root.
+ std::vector<FileId> children;
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_TRUE(children.empty());
+
+ // One child in the root.
+ FileId file_id0;
+ FileInfo info;
+ info.parent_id = 0;
+ info.name = "foo";
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_EQ(children.size(), 1UL);
+ EXPECT_EQ(children[0], file_id0);
+
+ // Two children in the root.
+ FileId file_id1;
+ info.name = "bar";
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_EQ(2UL, children.size());
+ if (children[0] == file_id0) {
+ EXPECT_EQ(children[1], file_id1);
+ } else {
+ EXPECT_EQ(children[1], file_id0);
+ EXPECT_EQ(children[0], file_id1);
+ }
+
+ // No children in a subdirectory.
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_TRUE(children.empty());
+
+ // One child in a subdirectory.
+ info.parent_id = file_id0;
+ info.name = "foo";
+ FileId file_id2;
+ FileId file_id3;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id2));
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_EQ(1UL, children.size());
+ EXPECT_EQ(children[0], file_id2);
+
+ // Two children in a subdirectory.
+ info.name = "bar";
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id3));
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_EQ(2UL, children.size());
+ if (children[0] == file_id2) {
+ EXPECT_EQ(children[1], file_id3);
+ } else {
+ EXPECT_EQ(children[1], file_id2);
+ EXPECT_EQ(children[0], file_id3);
+ }
+}
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestUpdateModificationTime) {
+ FileInfo info0;
+ FileId file_id;
+ info0.parent_id = 0;
+ info0.name = "name";
+ info0.data_path = FilePath().AppendASCII("fake path");
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &file_id));
+ FileInfo info1;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_EQ(info0.modification_time, info1.modification_time);
+
+ EXPECT_TRUE(db()->UpdateModificationTime(file_id, base::Time::UnixEpoch()));
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_NE(info0.modification_time, info1.modification_time);
+ EXPECT_EQ(info1.modification_time, base::Time::UnixEpoch());
+
+ EXPECT_FALSE(db()->UpdateModificationTime(999, base::Time::UnixEpoch()));
+}
+
+
+TEST_F(FileSystemDirectoryDatabaseTest, TestSimpleFileOperations) {
+ FileId file_id = 888;
+ FileInfo info0;
+ EXPECT_FALSE(db()->GetFileInfo(file_id, &info0));
+ info0.parent_id = 0;
+ info0.data_path = FilePath().AppendASCII("foo");
+ info0.name = "file name";
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &file_id));
+ FileInfo info1;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.modification_time, info1.modification_time);
+}
+
+} // namespace fileapi
diff --git a/webkit/fileapi/webkit_fileapi.gypi b/webkit/fileapi/webkit_fileapi.gypi
index cda2ec0..4d0ef77 100644
--- a/webkit/fileapi/webkit_fileapi.gypi
+++ b/webkit/fileapi/webkit_fileapi.gypi
@@ -21,6 +21,8 @@
'file_system_context.h',
'file_system_dir_url_request_job.cc',
'file_system_dir_url_request_job.h',
+ 'file_system_directory_database.cc',
+ 'file_system_directory_database.h',
'file_system_file_util.cc',
'file_system_file_util.h',
'file_system_file_util_proxy.cc',
diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi
index b9c632b..490b8aa 100644
--- a/webkit/tools/test_shell/test_shell.gypi
+++ b/webkit/tools/test_shell/test_shell.gypi
@@ -377,6 +377,7 @@
'../../database/database_util_unittest.cc',
'../../database/quota_table_unittest.cc',
'../../fileapi/file_system_context_unittest.cc',
+ '../../fileapi/file_system_directory_database_unittest.cc',
'../../fileapi/file_system_operation_unittest.cc',
'../../fileapi/file_system_origin_database_unittest.cc',
'../../fileapi/file_system_path_manager_unittest.cc',