diff options
author | ericu@google.com <ericu@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-05 23:52:45 +0000 |
---|---|---|
committer | ericu@google.com <ericu@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-05 23:52:45 +0000 |
commit | ca8dfe8b30b745c17b9a25ebe09d1de23d50e9b4 (patch) | |
tree | 86fd615d05c26e88ebed0187dc573fef52e2fd4b /webkit | |
parent | dabd6f450e9594a8962ef6f79447a8bfdc1c9f05 (diff) | |
download | chromium_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.cc | 396 | ||||
-rw-r--r-- | webkit/fileapi/file_system_directory_database.h | 85 | ||||
-rw-r--r-- | webkit/fileapi/file_system_directory_database_unittest.cc | 275 | ||||
-rw-r--r-- | webkit/fileapi/webkit_fileapi.gypi | 2 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gypi | 1 |
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', |