summaryrefslogtreecommitdiffstats
path: root/webkit/fileapi/file_system_directory_database.cc
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/fileapi/file_system_directory_database.cc
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/fileapi/file_system_directory_database.cc')
-rw-r--r--webkit/fileapi/file_system_directory_database.cc396
1 files changed, 396 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