Move chrome/browser/chromeos/drive/resource* (+deps) into components/drive.

Files moved from chrome/browser/chromeos/drive into components/drive:
- fake_free_disk_space_getter*
- file_cache*
- file_system_core_util*
- resource_entry_conversion*
- resource_metadata*
- resource_metadata_storage*

Other changes:
- Removed unneeded includes where needed and possible.
- Hidden usage of cryptohome::kMinFreeSpaceInBytes behind OS_CHROMEOS ifdefs
  and provided an equivalent constant shared by other OS-es.
- Hidden usage of base::SetPosixFilePermissions behind OS_CHROMEOS ifdef
  as it was only needed to support cros_disks service on ChromeOS.
- Moved 3 functions back from file_system_core_util into file_system_core.
  The 3 functions are ChromeOS-specific (dealing with where GDrive is mounted
  on ChromeOS) and moving them back helps avoid adding a new dependency on
  components/drive from chromeos directories.
- When adding a third_party/leveldatabase dependency to gyp/gn/deps files
  under components/drive, I realized that gn is not consistent with gyp, by
  not explicitly listing a dependency on third_party/cacheinvalidation in
  components/drive/BUILD.gn.

Test steps:
1. Verify that things still build via GYP (and unit tests pass).
   $ GYP_DEFINES="use_goma=1 gomadir=... chromeos=1" gclient sync
   $ ninja -C out/Debug -j 150 chrome unit_tests \
         interactive_ui_tests browser_tests drive
   $ out/Debug/unit_tests

2. Verify that things still build via GN.
    $ gn gen out/Default --args='target_os="chromeos" use_goma=true'
    $ ninja -C out/Default -j 150 chrome unit_tests \
          interactive_ui_tests browser_tests components/drive

TEST=Please see "Test steps" above.
BUG=257943, 498951
[email protected], [email protected], [email protected], [email protected], [email protected],

Review URL: https://codereview.chromium.org/1296483003

Cr-Commit-Position: refs/heads/master@{#344637}
diff --git a/components/drive/BUILD.gn b/components/drive/BUILD.gn
index 37193a3..08a98145 100644
--- a/components/drive/BUILD.gn
+++ b/components/drive/BUILD.gn
@@ -20,10 +20,14 @@
     "drive_uploader.h",
     "event_logger.cc",
     "event_logger.h",
+    "file_cache.cc",
+    "file_cache.h",
     "file_change.cc",
     "file_change.h",
     "file_errors.cc",
     "file_errors.h",
+    "file_system_core_util.cc",
+    "file_system_core_util.h",
     "job_list.cc",
     "job_list.h",
     "job_queue.cc",
@@ -32,6 +36,12 @@
     "job_scheduler.h",
     "local_file_reader.cc",
     "local_file_reader.h",
+    "resource_entry_conversion.cc",
+    "resource_entry_conversion.h",
+    "resource_metadata.cc",
+    "resource_metadata.h",
+    "resource_metadata_storage.cc",
+    "resource_metadata_storage.h",
     "service/drive_api_service.cc",
     "service/drive_api_service.h",
     "service/drive_service_interface.cc",
@@ -46,6 +56,8 @@
 
     "//google_apis:google_apis",
     "//net:net",
+    "//third_party/cacheinvalidation:cacheinvalidation",
+    "//third_party/leveldatabase:leveldatabase",
     "//third_party/re2:re2",
   ]
   public_deps = [
@@ -64,6 +76,8 @@
   sources = [
     "drive_test_util.cc",
     "drive_test_util.h",
+    "fake_free_disk_space_getter.cc",
+    "fake_free_disk_space_getter.h",
     "service/dummy_drive_service.cc",
     "service/dummy_drive_service.h",
     "service/fake_drive_service.cc",
diff --git a/components/drive/DEPS b/components/drive/DEPS
index 2d849cf..7c978cd7 100644
--- a/components/drive/DEPS
+++ b/components/drive/DEPS
@@ -4,6 +4,7 @@
   "+google_apis",
   "+google/cacheinvalidation/types.pb.h",
   "+net",
+  "+third_party/leveldatabase",
   "+third_party/re2",
 ]
 
@@ -24,14 +25,20 @@
 
   # The following test dependencies should be removed to fully componentize this
   # directory. crbug.com/498951
-  r"(job_scheduler_unittest.cc"
+  r"(file_cache_unittest.cc"
+  r"|file_system_core_util_unittest.cc"
+  r"|job_scheduler_unittest.cc"
+  r"|resource_metadata_storage_unittest.cc"
+  r"|resource_metadata_unittest.cc"
   r")": [
     "+content/public/test/test_browser_thread_bundle.h",
   ],
 
   # The dependency below is ok and can stay here for the long-term, because it
   # is guarded by #if defined(OS_CHROMEOS) in the source code.
-  "drive_test_util\.h": [
+  r"(drive_test_util.h"
+  r"|file_cache.cc"
+  r")": [
     "+third_party/cros_system_api/constants/cryptohome.h",
   ],
 }
diff --git a/components/drive/fake_free_disk_space_getter.cc b/components/drive/fake_free_disk_space_getter.cc
new file mode 100644
index 0000000..22df758
--- /dev/null
+++ b/components/drive/fake_free_disk_space_getter.cc
@@ -0,0 +1,31 @@
+// 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 "components/drive/fake_free_disk_space_getter.h"
+
+#include "components/drive/drive_test_util.h"
+
+namespace drive {
+
+FakeFreeDiskSpaceGetter::FakeFreeDiskSpaceGetter()
+    : default_value_(test_util::kLotsOfSpace) {
+}
+
+FakeFreeDiskSpaceGetter::~FakeFreeDiskSpaceGetter() {
+}
+
+void FakeFreeDiskSpaceGetter::PushFakeValue(int64 value) {
+  fake_values_.push_back(value);
+}
+
+int64 FakeFreeDiskSpaceGetter::AmountOfFreeDiskSpace() {
+  if (fake_values_.empty())
+    return default_value_;
+
+  const int64 value = fake_values_.front();
+  fake_values_.pop_front();
+  return value;
+}
+
+}  // namespace drive
diff --git a/components/drive/fake_free_disk_space_getter.h b/components/drive/fake_free_disk_space_getter.h
new file mode 100644
index 0000000..ba6dd3de
--- /dev/null
+++ b/components/drive/fake_free_disk_space_getter.h
@@ -0,0 +1,44 @@
+// 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 COMPONENTS_DRIVE_FAKE_FREE_DISK_SPACE_GETTER_H_
+#define COMPONENTS_DRIVE_FAKE_FREE_DISK_SPACE_GETTER_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "components/drive/file_cache.h"
+
+namespace drive {
+
+// This class is used to report fake free disk space. In particular, this
+// class can be used to simulate a case where disk is full, or nearly full.
+class FakeFreeDiskSpaceGetter : public internal::FreeDiskSpaceGetterInterface {
+ public:
+  FakeFreeDiskSpaceGetter();
+  ~FakeFreeDiskSpaceGetter() override;
+
+  void set_default_value(int64 value) { default_value_ = value; }
+
+  // Pushes the given value to the back of the fake value list.
+  //
+  // If the fake value list is empty, AmountOfFreeDiskSpace() will return
+  // |default_value_| repeatedly.
+  // Otherwise, AmountOfFreeDiskSpace() will return the value at the front of
+  // the list and removes it from the list.
+  void PushFakeValue(int64 value);
+
+  // FreeDiskSpaceGetterInterface overrides.
+  int64 AmountOfFreeDiskSpace() override;
+
+ private:
+  std::list<int64> fake_values_;
+  int64 default_value_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeFreeDiskSpaceGetter);
+};
+
+}  // namespace drive
+
+#endif  // COMPONENTS_DRIVE_FAKE_FREE_DISK_SPACE_GETTER_H_
diff --git a/components/drive/file_cache.cc b/components/drive/file_cache.cc
new file mode 100644
index 0000000..749dd8f
--- /dev/null
+++ b/components/drive/file_cache.cc
@@ -0,0 +1,626 @@
+// 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 "components/drive/file_cache.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/resource_metadata_storage.h"
+#include "google_apis/drive/task_util.h"
+#include "net/base/filename_util.h"
+#include "net/base/mime_sniffer.h"
+#include "net/base/mime_util.h"
+#if defined(OS_CHROMEOS)
+#include "third_party/cros_system_api/constants/cryptohome.h"
+#endif
+
+namespace drive {
+namespace internal {
+namespace {
+
+// Returns ID extracted from the path.
+std::string GetIdFromPath(const base::FilePath& path) {
+  return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
+}
+
+}  // namespace
+
+FileCache::FileCache(ResourceMetadataStorage* storage,
+                     const base::FilePath& cache_file_directory,
+                     base::SequencedTaskRunner* blocking_task_runner,
+                     FreeDiskSpaceGetterInterface* free_disk_space_getter)
+    : cache_file_directory_(cache_file_directory),
+      blocking_task_runner_(blocking_task_runner),
+      storage_(storage),
+      free_disk_space_getter_(free_disk_space_getter),
+      weak_ptr_factory_(this) {
+  DCHECK(blocking_task_runner_.get());
+}
+
+FileCache::~FileCache() {
+  // Must be on the sequenced worker pool, as |metadata_| must be deleted on
+  // the sequenced worker pool.
+  AssertOnSequencedWorkerPool();
+}
+
+base::FilePath FileCache::GetCacheFilePath(const std::string& id) const {
+  return cache_file_directory_.Append(
+      base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id)));
+}
+
+void FileCache::AssertOnSequencedWorkerPool() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+}
+
+bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
+  return cache_file_directory_.IsParent(path);
+}
+
+bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
+  AssertOnSequencedWorkerPool();
+
+  // Do nothing and return if we have enough space.
+  if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
+    return true;
+
+  // Otherwise, try to free up the disk space.
+  DVLOG(1) << "Freeing up disk space for " << num_bytes;
+
+  // Remove all entries unless specially marked.
+  scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator();
+  for (; !it->IsAtEnd(); it->Advance()) {
+    if (it->GetValue().file_specific_info().has_cache_state() &&
+        !it->GetValue().file_specific_info().cache_state().is_pinned() &&
+        !it->GetValue().file_specific_info().cache_state().is_dirty() &&
+        !mounted_files_.count(it->GetID())) {
+      ResourceEntry entry(it->GetValue());
+      entry.mutable_file_specific_info()->clear_cache_state();
+      storage_->PutEntry(entry);
+    }
+  }
+  if (it->HasError())
+    return false;
+
+  // Remove all files which have no corresponding cache entries.
+  base::FileEnumerator enumerator(cache_file_directory_,
+                                  false,  // not recursive
+                                  base::FileEnumerator::FILES);
+  ResourceEntry entry;
+  for (base::FilePath current = enumerator.Next(); !current.empty();
+       current = enumerator.Next()) {
+    std::string id = GetIdFromPath(current);
+    FileError error = storage_->GetEntry(id, &entry);
+    if (error == FILE_ERROR_NOT_FOUND ||
+        (error == FILE_ERROR_OK &&
+         !entry.file_specific_info().cache_state().is_present()))
+      base::DeleteFile(current, false /* recursive */);
+    else if (error != FILE_ERROR_OK)
+      return false;
+  }
+
+  // Check the disk space again.
+  return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
+}
+
+FileError FileCache::GetFile(const std::string& id,
+                             base::FilePath* cache_file_path) {
+  AssertOnSequencedWorkerPool();
+  DCHECK(cache_file_path);
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!entry.file_specific_info().cache_state().is_present())
+    return FILE_ERROR_NOT_FOUND;
+
+  *cache_file_path = GetCacheFilePath(id);
+  return FILE_ERROR_OK;
+}
+
+FileError FileCache::Store(const std::string& id,
+                           const std::string& md5,
+                           const base::FilePath& source_path,
+                           FileOperationType file_operation_type) {
+  AssertOnSequencedWorkerPool();
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  int64 file_size = 0;
+  if (file_operation_type == FILE_OPERATION_COPY) {
+    if (!base::GetFileSize(source_path, &file_size)) {
+      LOG(WARNING) << "Couldn't get file size for: " << source_path.value();
+      return FILE_ERROR_FAILED;
+    }
+  }
+  if (!FreeDiskSpaceIfNeededFor(file_size))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+
+  // If file is mounted, return error.
+  if (mounted_files_.count(id))
+    return FILE_ERROR_IN_USE;
+
+  base::FilePath dest_path = GetCacheFilePath(id);
+  bool success = false;
+  switch (file_operation_type) {
+    case FILE_OPERATION_MOVE:
+      success = base::Move(source_path, dest_path);
+      break;
+    case FILE_OPERATION_COPY:
+      success = base::CopyFile(source_path, dest_path);
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  if (!success) {
+    LOG(ERROR) << "Failed to store: "
+               << "source_path = " << source_path.value() << ", "
+               << "dest_path = " << dest_path.value() << ", "
+               << "file_operation_type = " << file_operation_type;
+    return FILE_ERROR_FAILED;
+  }
+
+  // Now that file operations have completed, update metadata.
+  FileCacheEntry* cache_state =
+      entry.mutable_file_specific_info()->mutable_cache_state();
+  cache_state->set_md5(md5);
+  cache_state->set_is_present(true);
+  if (md5.empty())
+    cache_state->set_is_dirty(true);
+  return storage_->PutEntry(entry);
+}
+
+FileError FileCache::Pin(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned(
+      true);
+  return storage_->PutEntry(entry);
+}
+
+FileError FileCache::Unpin(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+
+  // Unpinning a file means its entry must exist in cache.
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  // Now that file operations have completed, update metadata.
+  if (entry.file_specific_info().cache_state().is_present()) {
+    entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned(
+        false);
+  } else {
+    // Remove the existing entry if we are unpinning a non-present file.
+    entry.mutable_file_specific_info()->clear_cache_state();
+  }
+  error = storage_->PutEntry(entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  // Now it's a chance to free up space if needed.
+  FreeDiskSpaceIfNeededFor(0);
+
+  return FILE_ERROR_OK;
+}
+
+FileError FileCache::MarkAsMounted(const std::string& id,
+                                   base::FilePath* cache_file_path) {
+  AssertOnSequencedWorkerPool();
+  DCHECK(cache_file_path);
+
+  // Get cache entry associated with the id and md5
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!entry.file_specific_info().cache_state().is_present())
+    return FILE_ERROR_NOT_FOUND;
+
+  if (mounted_files_.count(id))
+    return FILE_ERROR_INVALID_OPERATION;
+
+  base::FilePath path = GetCacheFilePath(id);
+
+#if defined(OS_CHROMEOS)
+  // Ensure the file is readable to cros_disks. See crbug.com/236994.
+  if (!base::SetPosixFilePermissions(
+          path,
+          base::FILE_PERMISSION_READ_BY_USER |
+          base::FILE_PERMISSION_WRITE_BY_USER |
+          base::FILE_PERMISSION_READ_BY_GROUP |
+          base::FILE_PERMISSION_READ_BY_OTHERS))
+    return FILE_ERROR_FAILED;
+#endif
+
+  mounted_files_.insert(id);
+
+  *cache_file_path = path;
+  return FILE_ERROR_OK;
+}
+
+FileError FileCache::OpenForWrite(
+    const std::string& id,
+    scoped_ptr<base::ScopedClosureRunner>* file_closer) {
+  AssertOnSequencedWorkerPool();
+
+  // Marking a file dirty means its entry and actual file blob must exist in
+  // cache.
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!entry.file_specific_info().cache_state().is_present()) {
+    LOG(WARNING) << "Can't mark dirty a file that wasn't cached: " << id;
+    return FILE_ERROR_NOT_FOUND;
+  }
+
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(true);
+  entry.mutable_file_specific_info()->mutable_cache_state()->clear_md5();
+  error = storage_->PutEntry(entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  write_opened_files_[id]++;
+  file_closer->reset(new base::ScopedClosureRunner(
+      base::Bind(&google_apis::RunTaskWithTaskRunner,
+                 blocking_task_runner_,
+                 base::Bind(&FileCache::CloseForWrite,
+                            weak_ptr_factory_.GetWeakPtr(),
+                            id))));
+  return FILE_ERROR_OK;
+}
+
+bool FileCache::IsOpenedForWrite(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+  return write_opened_files_.count(id) != 0;
+}
+
+FileError FileCache::UpdateMd5(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+
+  if (IsOpenedForWrite(id))
+    return FILE_ERROR_IN_USE;
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!entry.file_specific_info().cache_state().is_present())
+    return FILE_ERROR_NOT_FOUND;
+
+  const std::string& md5 =
+      util::GetMd5Digest(GetCacheFilePath(id), &in_shutdown_);
+  if (in_shutdown_.IsSet())
+    return FILE_ERROR_ABORT;
+  if (md5.empty())
+    return FILE_ERROR_NOT_FOUND;
+
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_md5(md5);
+  return storage_->PutEntry(entry);
+}
+
+FileError FileCache::ClearDirty(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+
+  if (IsOpenedForWrite(id))
+    return FILE_ERROR_IN_USE;
+
+  // Clearing a dirty file means its entry and actual file blob must exist in
+  // cache.
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!entry.file_specific_info().cache_state().is_present()) {
+    LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
+                 << id;
+    return FILE_ERROR_NOT_FOUND;
+  }
+
+  // If a file is not dirty (it should have been marked dirty via OpenForWrite),
+  // clearing its dirty state is an invalid operation.
+  if (!entry.file_specific_info().cache_state().is_dirty()) {
+    LOG(WARNING) << "Can't clear dirty state of a non-dirty file: " << id;
+    return FILE_ERROR_INVALID_OPERATION;
+  }
+
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(
+      false);
+  return storage_->PutEntry(entry);
+}
+
+FileError FileCache::Remove(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+
+  ResourceEntry entry;
+
+  // If entry doesn't exist, nothing to do.
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error == FILE_ERROR_NOT_FOUND)
+    return FILE_ERROR_OK;
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!entry.file_specific_info().has_cache_state())
+    return FILE_ERROR_OK;
+
+  // Cannot delete a mounted file.
+  if (mounted_files_.count(id))
+    return FILE_ERROR_IN_USE;
+
+  // Delete the file.
+  base::FilePath path = GetCacheFilePath(id);
+  if (!base::DeleteFile(path, false /* recursive */))
+    return FILE_ERROR_FAILED;
+
+  // Now that all file operations have completed, remove from metadata.
+  entry.mutable_file_specific_info()->clear_cache_state();
+  return storage_->PutEntry(entry);
+}
+
+bool FileCache::ClearAll() {
+  AssertOnSequencedWorkerPool();
+
+  // Remove files.
+  base::FileEnumerator enumerator(cache_file_directory_,
+                                  false,  // not recursive
+                                  base::FileEnumerator::FILES);
+  for (base::FilePath file = enumerator.Next(); !file.empty();
+       file = enumerator.Next())
+    base::DeleteFile(file, false /* recursive */);
+
+  return true;
+}
+
+bool FileCache::Initialize() {
+  AssertOnSequencedWorkerPool();
+
+  // Older versions do not clear MD5 when marking entries dirty.
+  // Clear MD5 of all dirty entries to deal with old data.
+  scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator();
+  for (; !it->IsAtEnd(); it->Advance()) {
+    if (it->GetValue().file_specific_info().cache_state().is_dirty()) {
+      ResourceEntry new_entry(it->GetValue());
+      new_entry.mutable_file_specific_info()->mutable_cache_state()->
+          clear_md5();
+      if (storage_->PutEntry(new_entry) != FILE_ERROR_OK)
+        return false;
+    }
+  }
+  if (it->HasError())
+    return false;
+
+  if (!RenameCacheFilesToNewFormat())
+    return false;
+  return true;
+}
+
+void FileCache::Destroy() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  in_shutdown_.Set();
+
+  // Destroy myself on the blocking pool.
+  // Note that base::DeletePointer<> cannot be used as the destructor of this
+  // class is private.
+  blocking_task_runner_->PostTask(
+      FROM_HERE,
+      base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this)));
+}
+
+void FileCache::DestroyOnBlockingPool() {
+  AssertOnSequencedWorkerPool();
+  delete this;
+}
+
+bool FileCache::RecoverFilesFromCacheDirectory(
+    const base::FilePath& dest_directory,
+    const ResourceMetadataStorage::RecoveredCacheInfoMap&
+        recovered_cache_info) {
+  int file_number = 1;
+
+  base::FileEnumerator enumerator(cache_file_directory_,
+                                  false,  // not recursive
+                                  base::FileEnumerator::FILES);
+  for (base::FilePath current = enumerator.Next(); !current.empty();
+       current = enumerator.Next()) {
+    const std::string& id = GetIdFromPath(current);
+    ResourceEntry entry;
+    FileError error = storage_->GetEntry(id, &entry);
+    if (error != FILE_ERROR_OK && error != FILE_ERROR_NOT_FOUND)
+      return false;
+    if (error == FILE_ERROR_OK &&
+        entry.file_specific_info().cache_state().is_present()) {
+      // This file is managed by FileCache, no need to recover it.
+      continue;
+    }
+
+    // If a cache entry which is non-dirty and has matching MD5 is found in
+    // |recovered_cache_entries|, it means the current file is already uploaded
+    // to the server. Just delete it instead of recovering it.
+    ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it =
+        recovered_cache_info.find(id);
+    if (it != recovered_cache_info.end()) {
+      // Due to the DB corruption, cache info might be recovered from old
+      // revision. Perform MD5 check even when is_dirty is false just in case.
+      if (!it->second.is_dirty &&
+          it->second.md5 == util::GetMd5Digest(current, &in_shutdown_)) {
+        base::DeleteFile(current, false /* recursive */);
+        continue;
+      }
+    }
+
+    // Read file contents to sniff mime type.
+    std::vector<char> content(net::kMaxBytesToSniff);
+    const int read_result =
+        base::ReadFile(current, &content[0], content.size());
+    if (read_result < 0) {
+      LOG(WARNING) << "Cannot read: " << current.value();
+      return false;
+    }
+    if (read_result == 0)  // Skip empty files.
+      continue;
+
+    // Use recovered file name if available, otherwise decide file name with
+    // sniffed mime type.
+    base::FilePath dest_base_name(FILE_PATH_LITERAL("file"));
+    std::string mime_type;
+    if (it != recovered_cache_info.end() && !it->second.title.empty()) {
+      // We can use a file name recovered from the trashed DB.
+      dest_base_name = base::FilePath::FromUTF8Unsafe(it->second.title);
+    } else if (net::SniffMimeType(&content[0], read_result,
+                                  net::FilePathToFileURL(current),
+                                  std::string(), &mime_type) ||
+               net::SniffMimeTypeFromLocalData(&content[0], read_result,
+                                               &mime_type)) {
+      // Change base name for common mime types.
+      if (net::MatchesMimeType("image/*", mime_type)) {
+        dest_base_name = base::FilePath(FILE_PATH_LITERAL("image"));
+      } else if (net::MatchesMimeType("video/*", mime_type)) {
+        dest_base_name = base::FilePath(FILE_PATH_LITERAL("video"));
+      } else if (net::MatchesMimeType("audio/*", mime_type)) {
+        dest_base_name = base::FilePath(FILE_PATH_LITERAL("audio"));
+      }
+
+      // Estimate extension from mime type.
+      std::vector<base::FilePath::StringType> extensions;
+      base::FilePath::StringType extension;
+      if (net::GetPreferredExtensionForMimeType(mime_type, &extension))
+        extensions.push_back(extension);
+      else
+        net::GetExtensionsForMimeType(mime_type, &extensions);
+
+      // Add extension if possible.
+      if (!extensions.empty())
+        dest_base_name = dest_base_name.AddExtension(extensions[0]);
+    }
+
+    // Add file number to the file name and move.
+    const base::FilePath& dest_path = dest_directory.Append(dest_base_name)
+        .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number++));
+    if (!base::CreateDirectory(dest_directory) ||
+        !base::Move(current, dest_path)) {
+      LOG(WARNING) << "Failed to move: " << current.value()
+                   << " to " << dest_path.value();
+      return false;
+    }
+  }
+  UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption",
+                       file_number - 1);
+  return true;
+}
+
+FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) {
+  AssertOnSequencedWorkerPool();
+  DCHECK(IsUnderFileCacheDirectory(file_path));
+
+  std::string id = GetIdFromPath(file_path);
+
+  // Get the entry associated with the id.
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  std::set<std::string>::iterator it = mounted_files_.find(id);
+  if (it == mounted_files_.end())
+    return FILE_ERROR_INVALID_OPERATION;
+
+  mounted_files_.erase(it);
+  return FILE_ERROR_OK;
+}
+
+bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
+                                  const base::FilePath& path) {
+  int64 free_space = 0;
+  if (free_disk_space_getter_)
+    free_space = free_disk_space_getter_->AmountOfFreeDiskSpace();
+  else
+    free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
+
+  // Subtract this as if this portion does not exist.
+#if defined(OS_CHROMEOS)
+  const int64 kMinFreeBytes = cryptohome::kMinFreeSpaceInBytes;
+#else
+  const int64 kMinFreeBytes = 512ull * 1024ull * 1024ull;  // 512MB
+#endif
+  free_space -= kMinFreeBytes;
+  return (free_space >= num_bytes);
+}
+
+bool FileCache::RenameCacheFilesToNewFormat() {
+  base::FileEnumerator enumerator(cache_file_directory_,
+                                  false,  // not recursive
+                                  base::FileEnumerator::FILES);
+  for (base::FilePath current = enumerator.Next(); !current.empty();
+       current = enumerator.Next()) {
+    base::FilePath new_path = current.RemoveExtension();
+    if (!new_path.Extension().empty()) {
+      // Delete files with multiple extensions.
+      if (!base::DeleteFile(current, false /* recursive */))
+        return false;
+      continue;
+    }
+    const std::string& id = GetIdFromPath(new_path);
+    new_path = GetCacheFilePath(util::CanonicalizeResourceId(id));
+    if (new_path != current && !base::Move(current, new_path))
+      return false;
+  }
+  return true;
+}
+
+void FileCache::CloseForWrite(const std::string& id) {
+  AssertOnSequencedWorkerPool();
+
+  std::map<std::string, int>::iterator it = write_opened_files_.find(id);
+  if (it == write_opened_files_.end())
+    return;
+
+  DCHECK_LT(0, it->second);
+  --it->second;
+  if (it->second == 0)
+    write_opened_files_.erase(it);
+
+  // Update last modified date.
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK) {
+    LOG(ERROR) << "Failed to get entry: " << id << ", "
+               << FileErrorToString(error);
+    return;
+  }
+  entry.mutable_file_info()->set_last_modified(
+      base::Time::Now().ToInternalValue());
+  error = storage_->PutEntry(entry);
+  if (error != FILE_ERROR_OK) {
+    LOG(ERROR) << "Failed to put entry: " << id << ", "
+               << FileErrorToString(error);
+  }
+}
+
+}  // namespace internal
+}  // namespace drive
diff --git a/components/drive/file_cache.h b/components/drive/file_cache.h
new file mode 100644
index 0000000..a523b4149
--- /dev/null
+++ b/components/drive/file_cache.h
@@ -0,0 +1,196 @@
+// 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 COMPONENTS_DRIVE_FILE_CACHE_H_
+#define COMPONENTS_DRIVE_FILE_CACHE_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/synchronization/cancellation_flag.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/resource_metadata_storage.h"
+
+namespace base {
+class ScopedClosureRunner;
+class SequencedTaskRunner;
+}  // namespace base
+
+namespace drive {
+
+namespace internal {
+
+// Interface class used for getting the free disk space. Tests can inject an
+// implementation that reports fake free disk space.
+class FreeDiskSpaceGetterInterface {
+ public:
+  virtual ~FreeDiskSpaceGetterInterface() {}
+  virtual int64 AmountOfFreeDiskSpace() = 0;
+};
+
+// FileCache is used to maintain cache states of FileSystem.
+//
+// All non-static public member functions, unless mentioned otherwise (see
+// GetCacheFilePath() for example), should be run with |blocking_task_runner|.
+class FileCache {
+ public:
+  // Enum defining type of file operation e.g. copy or move, etc.
+  enum FileOperationType {
+    FILE_OPERATION_MOVE = 0,
+    FILE_OPERATION_COPY,
+  };
+
+  // |cache_file_directory| stores cached files.
+  //
+  // |blocking_task_runner| indicates the blocking worker pool for cache
+  // operations. All operations on this FileCache must be run on this runner.
+  // Must not be null.
+  //
+  // |free_disk_space_getter| is used to inject a custom free disk space
+  // getter for testing. NULL must be passed for production code.
+  //
+  // Must be called on the UI thread.
+  FileCache(ResourceMetadataStorage* storage,
+            const base::FilePath& cache_file_directory,
+            base::SequencedTaskRunner* blocking_task_runner,
+            FreeDiskSpaceGetterInterface* free_disk_space_getter);
+
+  // Returns true if the given path is under drive cache directory, i.e.
+  // <user_profile_dir>/GCache/v1
+  //
+  // Can be called on any thread.
+  bool IsUnderFileCacheDirectory(const base::FilePath& path) const;
+
+  // Frees up disk space to store a file with |num_bytes| size content, while
+  // keeping cryptohome::kMinFreeSpaceInBytes bytes on the disk, if needed.
+  // Returns true if we successfully manage to have enough space, otherwise
+  // false.
+  bool FreeDiskSpaceIfNeededFor(int64 num_bytes);
+
+  // Checks if file corresponding to |id| exists in cache, and returns
+  // FILE_ERROR_OK with |cache_file_path| storing the path to the file.
+  // |cache_file_path| must not be null.
+  FileError GetFile(const std::string& id, base::FilePath* cache_file_path);
+
+  // Stores |source_path| as a cache of the remote content of the file
+  // with |id| and |md5|.
+  // Pass an empty string as MD5 to mark the entry as dirty.
+  FileError Store(const std::string& id,
+                  const std::string& md5,
+                  const base::FilePath& source_path,
+                  FileOperationType file_operation_type);
+
+  // Pins the specified entry.
+  FileError Pin(const std::string& id);
+
+  // Unpins the specified entry.
+  FileError Unpin(const std::string& id);
+
+  // Sets the state of the cache entry corresponding to |id| as mounted.
+  FileError MarkAsMounted(const std::string& id,
+                          base::FilePath* cache_file_path);
+
+  // Sets the state of the cache entry corresponding to file_path as unmounted.
+  FileError MarkAsUnmounted(const base::FilePath& file_path);
+
+  // Opens the cache file corresponding to |id| for write. |file_closer| should
+  // be kept alive until writing finishes.
+  // This method must be called before writing to cache files.
+  FileError OpenForWrite(const std::string& id,
+                         scoped_ptr<base::ScopedClosureRunner>* file_closer);
+
+  // Returns true if the cache file corresponding to |id| is write-opened.
+  bool IsOpenedForWrite(const std::string& id);
+
+  // Calculates MD5 of the cache file and updates the stored value.
+  FileError UpdateMd5(const std::string& id);
+
+  // Clears dirty state of the specified entry.
+  FileError ClearDirty(const std::string& id);
+
+  // Removes the specified cache entry and delete cache files if available.
+  FileError Remove(const std::string& id);
+
+  // Removes all the files in the cache directory.
+  bool ClearAll();
+
+  // Initializes the cache. Returns true on success.
+  bool Initialize();
+
+  // Destroys this cache. This function posts a task to the blocking task
+  // runner to safely delete the object.
+  // Must be called on the UI thread.
+  void Destroy();
+
+  // Moves files in the cache directory which are not managed by FileCache to
+  // |dest_directory|.
+  // |recovered_cache_info| should contain cache info recovered from the trashed
+  // metadata DB. It is used to ignore non-dirty files.
+  bool RecoverFilesFromCacheDirectory(
+      const base::FilePath& dest_directory,
+      const ResourceMetadataStorage::RecoveredCacheInfoMap&
+          recovered_cache_info);
+
+ private:
+  friend class FileCacheTest;
+
+  ~FileCache();
+
+  // Returns absolute path of the file if it were cached or to be cached.
+  //
+  // Can be called on any thread.
+  base::FilePath GetCacheFilePath(const std::string& id) const;
+
+  // Checks whether the current thread is on the right sequenced worker pool
+  // with the right sequence ID. If not, DCHECK will fail.
+  void AssertOnSequencedWorkerPool();
+
+  // Destroys the cache on the blocking pool.
+  void DestroyOnBlockingPool();
+
+  // Returns true if we have sufficient space to store the given number of
+  // bytes, while keeping cryptohome::kMinFreeSpaceInBytes bytes on the disk.
+  bool HasEnoughSpaceFor(int64 num_bytes, const base::FilePath& path);
+
+  // Renames cache files from old "prefix:id.md5" format to the new format.
+  // TODO(hashimoto): Remove this method at some point.
+  bool RenameCacheFilesToNewFormat();
+
+  // This method must be called after writing to a cache file.
+  // Used to implement OpenForWrite().
+  void CloseForWrite(const std::string& id);
+
+  const base::FilePath cache_file_directory_;
+
+  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+
+  base::CancellationFlag in_shutdown_;
+
+  ResourceMetadataStorage* storage_;
+
+  FreeDiskSpaceGetterInterface* free_disk_space_getter_;  // Not owned.
+
+  // IDs of files being write-opened.
+  std::map<std::string, int> write_opened_files_;
+
+  // IDs of files marked mounted.
+  std::set<std::string> mounted_files_;
+
+  base::ThreadChecker thread_checker_;
+
+  // Note: This should remain the last member so it'll be destroyed and
+  // invalidate its weak pointers before any other members are destroyed.
+  // This object should be accessed only on |blocking_task_runner_|.
+  base::WeakPtrFactory<FileCache> weak_ptr_factory_;
+  DISALLOW_COPY_AND_ASSIGN(FileCache);
+};
+
+}  // namespace internal
+}  // namespace drive
+
+#endif  // COMPONENTS_DRIVE_FILE_CACHE_H_
diff --git a/components/drive/file_cache_unittest.cc b/components/drive/file_cache_unittest.cc
new file mode 100644
index 0000000..c9ecc1c7
--- /dev/null
+++ b/components/drive/file_cache_unittest.cc
@@ -0,0 +1,562 @@
+// 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 "components/drive/file_cache.h"
+
+#include <string>
+#include <vector>
+
+#include "base/callback_helpers.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/md5.h"
+#include "base/path_service.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/resource_metadata_storage.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace internal {
+namespace {
+
+const char kCacheFileDirectory[] = "files";
+
+}  // namespace
+
+// Tests FileCache methods working with the blocking task runner.
+class FileCacheTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    const base::FilePath metadata_dir = temp_dir_.path().AppendASCII("meta");
+    cache_files_dir_ = temp_dir_.path().AppendASCII(kCacheFileDirectory);
+
+    ASSERT_TRUE(base::CreateDirectory(metadata_dir));
+    ASSERT_TRUE(base::CreateDirectory(cache_files_dir_));
+
+    fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
+
+    metadata_storage_.reset(new ResourceMetadataStorage(
+        metadata_dir,
+        base::ThreadTaskRunnerHandle::Get().get()));
+    ASSERT_TRUE(metadata_storage_->Initialize());
+
+    cache_.reset(new FileCache(
+        metadata_storage_.get(),
+        cache_files_dir_,
+        base::ThreadTaskRunnerHandle::Get().get(),
+        fake_free_disk_space_getter_.get()));
+    ASSERT_TRUE(cache_->Initialize());
+  }
+
+  static bool RenameCacheFilesToNewFormat(FileCache* cache) {
+    return cache->RenameCacheFilesToNewFormat();
+  }
+
+  content::TestBrowserThreadBundle thread_bundle_;
+  base::ScopedTempDir temp_dir_;
+  base::FilePath cache_files_dir_;
+
+  scoped_ptr<ResourceMetadataStorage, test_util::DestroyHelperForTests>
+      metadata_storage_;
+  scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
+  scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
+};
+
+TEST_F(FileCacheTest, RecoverFilesFromCacheDirectory) {
+  base::FilePath dir_source_root;
+  EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &dir_source_root));
+  const base::FilePath src_path =
+      dir_source_root.AppendASCII("chrome/test/data/chromeos/drive/image.png");
+
+  // Store files. This file should not be moved.
+  ResourceEntry entry;
+  entry.set_local_id("id_foo");
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store("id_foo", "md5", src_path,
+                                         FileCache::FILE_OPERATION_COPY));
+
+  // Set up files in the cache directory. These files should be moved.
+  const base::FilePath file_directory =
+      temp_dir_.path().AppendASCII(kCacheFileDirectory);
+  ASSERT_TRUE(base::CopyFile(src_path, file_directory.AppendASCII("id_bar")));
+  ASSERT_TRUE(base::CopyFile(src_path, file_directory.AppendASCII("id_baz")));
+
+  // Insert a dirty entry with "id_baz" to |recovered_cache_info|.
+  // This should not prevent the file from being recovered.
+  ResourceMetadataStorage::RecoveredCacheInfoMap recovered_cache_info;
+  recovered_cache_info["id_baz"].is_dirty = true;
+  recovered_cache_info["id_baz"].title = "baz.png";
+
+  // Recover files.
+  const base::FilePath dest_directory = temp_dir_.path().AppendASCII("dest");
+  EXPECT_TRUE(cache_->RecoverFilesFromCacheDirectory(dest_directory,
+                                                     recovered_cache_info));
+
+  // Only two files should be recovered.
+  EXPECT_TRUE(base::PathExists(dest_directory));
+  // base::FileEnumerator does not guarantee the order.
+  if (base::PathExists(dest_directory.AppendASCII("baz00000001.png"))) {
+    EXPECT_TRUE(base::ContentsEqual(
+        src_path,
+        dest_directory.AppendASCII("baz00000001.png")));
+    EXPECT_TRUE(base::ContentsEqual(
+        src_path,
+        dest_directory.AppendASCII("image00000002.png")));
+  } else {
+    EXPECT_TRUE(base::ContentsEqual(
+        src_path,
+        dest_directory.AppendASCII("image00000001.png")));
+    EXPECT_TRUE(base::ContentsEqual(
+        src_path,
+        dest_directory.AppendASCII("baz00000002.png")));
+  }
+  EXPECT_FALSE(base::PathExists(
+      dest_directory.AppendASCII("image00000003.png")));
+}
+
+TEST_F(FileCacheTest, FreeDiskSpaceIfNeededFor) {
+  base::FilePath src_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
+
+  // Store a file as a 'temporary' file and remember the path.
+  const std::string id_tmp = "id_tmp", md5_tmp = "md5_tmp";
+
+  ResourceEntry entry;
+  entry.set_local_id(id_tmp);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  ASSERT_EQ(FILE_ERROR_OK,
+            cache_->Store(id_tmp, md5_tmp, src_file,
+                          FileCache::FILE_OPERATION_COPY));
+  base::FilePath tmp_path;
+  ASSERT_EQ(FILE_ERROR_OK, cache_->GetFile(id_tmp, &tmp_path));
+
+  // Store a file as a pinned file and remember the path.
+  const std::string id_pinned = "id_pinned", md5_pinned = "md5_pinned";
+  entry.Clear();
+  entry.set_local_id(id_pinned);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  ASSERT_EQ(FILE_ERROR_OK,
+            cache_->Store(id_pinned, md5_pinned, src_file,
+                          FileCache::FILE_OPERATION_COPY));
+  ASSERT_EQ(FILE_ERROR_OK, cache_->Pin(id_pinned));
+  base::FilePath pinned_path;
+  ASSERT_EQ(FILE_ERROR_OK, cache_->GetFile(id_pinned, &pinned_path));
+
+  // Call FreeDiskSpaceIfNeededFor().
+  fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace);
+  fake_free_disk_space_getter_->PushFakeValue(0);
+  const int64 kNeededBytes = 1;
+  EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
+
+  // Only 'temporary' file gets removed.
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_tmp, &entry));
+  EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
+  EXPECT_FALSE(base::PathExists(tmp_path));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_pinned, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+  EXPECT_TRUE(base::PathExists(pinned_path));
+
+  // Returns false when disk space cannot be freed.
+  fake_free_disk_space_getter_->set_default_value(0);
+  EXPECT_FALSE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
+}
+
+TEST_F(FileCacheTest, GetFile) {
+  const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
+  const std::string src_contents = "test";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
+                                                        src_contents));
+  std::string id("id1");
+  std::string md5(base::MD5String(src_contents));
+
+  const base::FilePath cache_file_directory =
+      temp_dir_.path().AppendASCII(kCacheFileDirectory);
+
+  // Try to get an existing file from cache.
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file_path,
+                                         FileCache::FILE_OPERATION_COPY));
+  base::FilePath cache_file_path;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
+  EXPECT_EQ(
+      cache_file_directory.AppendASCII(util::EscapeCacheFileName(id)).value(),
+      cache_file_path.value());
+
+  std::string contents;
+  EXPECT_TRUE(base::ReadFileToString(cache_file_path, &contents));
+  EXPECT_EQ(src_contents, contents);
+
+  // Get file from cache with different id.
+  id = "id2";
+  entry.Clear();
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->GetFile(id, &cache_file_path));
+
+  // Pin a non-existent file.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id));
+
+  // Get the non-existent pinned file from cache.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->GetFile(id, &cache_file_path));
+
+  // Get a previously pinned and stored file from cache.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file_path,
+                                         FileCache::FILE_OPERATION_COPY));
+
+  EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
+  EXPECT_EQ(
+      cache_file_directory.AppendASCII(util::EscapeCacheFileName(id)).value(),
+      cache_file_path.value());
+
+  contents.clear();
+  EXPECT_TRUE(base::ReadFileToString(cache_file_path, &contents));
+  EXPECT_EQ(src_contents, contents);
+}
+
+TEST_F(FileCacheTest, Store) {
+  const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
+  const std::string src_contents = "test";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
+                                                        src_contents));
+  std::string id("id");
+  std::string md5(base::MD5String(src_contents));
+
+  // Store a file.
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+      id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+  EXPECT_EQ(md5, entry.file_specific_info().cache_state().md5());
+
+  base::FilePath cache_file_path;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
+  EXPECT_TRUE(base::ContentsEqual(src_file_path, cache_file_path));
+
+  // Store a non-existent file.
+  EXPECT_EQ(FILE_ERROR_FAILED, cache_->Store(
+      id, md5, base::FilePath::FromUTF8Unsafe("non_existent_file"),
+      FileCache::FILE_OPERATION_COPY));
+
+  // Passing empty MD5 marks the entry as dirty.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+      id, std::string(), src_file_path, FileCache::FILE_OPERATION_COPY));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+  EXPECT_TRUE(entry.file_specific_info().cache_state().md5().empty());
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+  // No free space available.
+  fake_free_disk_space_getter_->set_default_value(0);
+
+  EXPECT_EQ(FILE_ERROR_NO_LOCAL_SPACE, cache_->Store(
+      id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
+}
+
+TEST_F(FileCacheTest, PinAndUnpin) {
+  const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
+  const std::string src_contents = "test";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
+                                                        src_contents));
+  std::string id("id_present");
+  std::string md5(base::MD5String(src_contents));
+
+  // Store a file.
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+      id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_FALSE(entry.file_specific_info().cache_state().is_pinned());
+
+  // Pin the existing file.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_pinned());
+
+  // Unpin the file.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Unpin(id));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_FALSE(entry.file_specific_info().cache_state().is_pinned());
+
+  // Pin a non-present file.
+  std::string id_non_present = "id_non_present";
+  entry.Clear();
+  entry.set_local_id(id_non_present);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id_non_present));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_non_present, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_pinned());
+
+  // Unpin the previously pinned non-existent file.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Unpin(id_non_present));
+
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_non_present, &entry));
+  EXPECT_FALSE(entry.file_specific_info().has_cache_state());
+
+  // Unpin a file that doesn't exist in cache and is not pinned.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->Unpin("id_non_existent"));
+}
+
+TEST_F(FileCacheTest, MountUnmount) {
+  const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
+  const std::string src_contents = "test";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
+                                                        src_contents));
+  std::string id("id_present");
+  std::string md5(base::MD5String(src_contents));
+
+  // Store a file.
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+      id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
+
+  // Mark the file mounted.
+  base::FilePath cache_file_path;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->MarkAsMounted(id, &cache_file_path));
+
+  // Try to remove it.
+  EXPECT_EQ(FILE_ERROR_IN_USE, cache_->Remove(id));
+
+  // Clear mounted state of the file.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->MarkAsUnmounted(cache_file_path));
+
+  // Try to remove again.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(id));
+}
+
+TEST_F(FileCacheTest, OpenForWrite) {
+  // Prepare a file.
+  base::FilePath src_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
+
+  const std::string id = "id";
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, "md5", src_file,
+                                         FileCache::FILE_OPERATION_COPY));
+  EXPECT_EQ(0, entry.file_info().last_modified());
+
+  // Entry is not dirty nor opened.
+  EXPECT_FALSE(cache_->IsOpenedForWrite(id));
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+
+  // Open (1).
+  scoped_ptr<base::ScopedClosureRunner> file_closer1;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer1));
+  EXPECT_TRUE(cache_->IsOpenedForWrite(id));
+
+  // Entry is dirty.
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+  // Open (2).
+  scoped_ptr<base::ScopedClosureRunner> file_closer2;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer2));
+  EXPECT_TRUE(cache_->IsOpenedForWrite(id));
+
+  // Close (1).
+  file_closer1.reset();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(cache_->IsOpenedForWrite(id));
+
+  // last_modified is updated.
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_NE(0, entry.file_info().last_modified());
+
+  // Close (2).
+  file_closer2.reset();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(cache_->IsOpenedForWrite(id));
+
+  // Try to open non-existent file.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+            cache_->OpenForWrite("nonexistent_id", &file_closer1));
+}
+
+TEST_F(FileCacheTest, UpdateMd5) {
+  // Store test data.
+  const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
+  const std::string contents_before = "before";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
+                                                        contents_before));
+  std::string id("id1");
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, base::MD5String(contents_before),
+                                         src_file_path,
+                                         FileCache::FILE_OPERATION_COPY));
+
+  // Modify the cache file.
+  scoped_ptr<base::ScopedClosureRunner> file_closer;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer));
+  base::FilePath cache_file_path;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
+  const std::string contents_after = "after";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(cache_file_path,
+                                                        contents_after));
+
+  // Cannot update MD5 of an opend file.
+  EXPECT_EQ(FILE_ERROR_IN_USE, cache_->UpdateMd5(id));
+
+  // Close file.
+  file_closer.reset();
+  base::RunLoop().RunUntilIdle();
+
+  // MD5 was cleared by OpenForWrite().
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().md5().empty());
+
+  // Update MD5.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->UpdateMd5(id));
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_EQ(base::MD5String(contents_after),
+            entry.file_specific_info().cache_state().md5());
+}
+
+TEST_F(FileCacheTest, ClearDirty) {
+  // Prepare a file.
+  base::FilePath src_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
+
+  const std::string id = "id";
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, "md5", src_file,
+                                         FileCache::FILE_OPERATION_COPY));
+
+  // Open the file.
+  scoped_ptr<base::ScopedClosureRunner> file_closer;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer));
+
+  // Entry is dirty.
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+  // Cannot clear the dirty bit of an opened entry.
+  EXPECT_EQ(FILE_ERROR_IN_USE, cache_->ClearDirty(id));
+
+  // Close the file and clear the dirty bit.
+  file_closer.reset();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(FILE_ERROR_OK, cache_->ClearDirty(id));
+
+  // Entry is not dirty.
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
+  EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+}
+
+TEST_F(FileCacheTest, Remove) {
+  const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
+  const std::string src_contents = "test";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
+                                                        src_contents));
+  std::string id("id");
+  std::string md5(base::MD5String(src_contents));
+
+  // First store a file to cache.
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  base::FilePath src_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+      id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
+
+  base::FilePath cache_file_path;
+  EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
+
+  // Then try to remove existing file from cache.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(id));
+  EXPECT_FALSE(base::PathExists(cache_file_path));
+}
+
+TEST_F(FileCacheTest, RenameCacheFilesToNewFormat) {
+  const base::FilePath file_directory =
+      temp_dir_.path().AppendASCII(kCacheFileDirectory);
+
+  // File with an old style "<prefix>:<ID>.<MD5>" name.
+  ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
+      file_directory.AppendASCII("file:id_koo.md5"), "koo"));
+
+  // File with multiple extensions should be removed.
+  ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
+      file_directory.AppendASCII("id_kyu.md5.mounted"), "kyu (mounted)"));
+  ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
+      file_directory.AppendASCII("id_kyu.md5"), "kyu"));
+
+  // Rename and verify the result.
+  EXPECT_TRUE(RenameCacheFilesToNewFormat(cache_.get()));
+  std::string contents;
+  EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_koo"),
+                                     &contents));
+  EXPECT_EQ("koo", contents);
+  contents.clear();
+  EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_kyu"),
+                                     &contents));
+  EXPECT_EQ("kyu", contents);
+
+  // Rename again.
+  EXPECT_TRUE(RenameCacheFilesToNewFormat(cache_.get()));
+
+  // Files with new style names are not affected.
+  contents.clear();
+  EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_koo"),
+                                     &contents));
+  EXPECT_EQ("koo", contents);
+  contents.clear();
+  EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_kyu"),
+                                     &contents));
+  EXPECT_EQ("kyu", contents);
+}
+
+TEST_F(FileCacheTest, ClearAll) {
+  const std::string id("1a2b");
+  const std::string md5("abcdef0123456789");
+
+  // Store an existing file.
+  ResourceEntry entry;
+  entry.set_local_id(id);
+  EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
+  base::FilePath src_file;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
+  ASSERT_EQ(FILE_ERROR_OK,
+            cache_->Store(id, md5, src_file, FileCache::FILE_OPERATION_COPY));
+
+  // Clear cache.
+  EXPECT_TRUE(cache_->ClearAll());
+
+  // Verify that the cache is removed.
+  EXPECT_TRUE(base::IsDirectoryEmpty(cache_files_dir_));
+}
+
+}  // namespace internal
+}  // namespace drive
diff --git a/components/drive/file_system_core_util.cc b/components/drive/file_system_core_util.cc
new file mode 100644
index 0000000..1e9f00ba
--- /dev/null
+++ b/components/drive/file_system_core_util.cc
@@ -0,0 +1,142 @@
+// Copyright 2015 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 "components/drive/file_system_core_util.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/i18n/icu_string_conversions.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/pref_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_pref_names.h"
+#include "components/drive/job_list.h"
+
+namespace drive {
+namespace util {
+
+namespace {
+
+std::string ReadStringFromGDocFile(const base::FilePath& file_path,
+                                   const std::string& key) {
+  const int64 kMaxGDocSize = 4096;
+  int64 file_size = 0;
+  if (!base::GetFileSize(file_path, &file_size) || file_size > kMaxGDocSize) {
+    LOG(WARNING) << "File too large to be a GDoc file " << file_path.value();
+    return std::string();
+  }
+
+  JSONFileValueDeserializer reader(file_path);
+  std::string error_message;
+  scoped_ptr<base::Value> root_value(reader.Deserialize(NULL, &error_message));
+  if (!root_value) {
+    LOG(WARNING) << "Failed to parse " << file_path.value() << " as JSON."
+                 << " error = " << error_message;
+    return std::string();
+  }
+
+  base::DictionaryValue* dictionary_value = NULL;
+  std::string result;
+  if (!root_value->GetAsDictionary(&dictionary_value) ||
+      !dictionary_value->GetString(key, &result)) {
+    LOG(WARNING) << "No value for the given key is stored in "
+                 << file_path.value() << ". key = " << key;
+    return std::string();
+  }
+
+  return result;
+}
+
+}  // namespace
+
+const base::FilePath& GetDriveGrandRootPath() {
+  CR_DEFINE_STATIC_LOCAL(
+      base::FilePath, grand_root_path,
+      (base::FilePath::FromUTF8Unsafe(kDriveGrandRootDirName)));
+  return grand_root_path;
+}
+
+const base::FilePath& GetDriveMyDriveRootPath() {
+  CR_DEFINE_STATIC_LOCAL(
+      base::FilePath, drive_root_path,
+      (GetDriveGrandRootPath().AppendASCII(kDriveMyDriveRootDirName)));
+  return drive_root_path;
+}
+
+std::string EscapeCacheFileName(const std::string& filename) {
+  // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape
+  std::string escaped;
+  for (size_t i = 0; i < filename.size(); ++i) {
+    char c = filename[i];
+    if (c == '%' || c == '.' || c == '/') {
+      base::StringAppendF(&escaped, "%%%02X", c);
+    } else {
+      escaped.push_back(c);
+    }
+  }
+  return escaped;
+}
+
+std::string UnescapeCacheFileName(const std::string& filename) {
+  std::string unescaped;
+  for (size_t i = 0; i < filename.size(); ++i) {
+    char c = filename[i];
+    if (c == '%' && i + 2 < filename.length()) {
+      c = (base::HexDigitToInt(filename[i + 1]) << 4) +
+          base::HexDigitToInt(filename[i + 2]);
+      i += 2;
+    }
+    unescaped.push_back(c);
+  }
+  return unescaped;
+}
+
+std::string NormalizeFileName(const std::string& input) {
+  DCHECK(base::IsStringUTF8(input));
+
+  std::string output;
+  if (!base::ConvertToUtf8AndNormalize(input, base::kCodepageUTF8, &output))
+    output = input;
+  base::ReplaceChars(output, "/", "_", &output);
+  if (!output.empty() && output.find_first_not_of('.', 0) == std::string::npos)
+    output = "_";
+  return output;
+}
+
+void EmptyFileOperationCallback(FileError error) {
+}
+
+bool CreateGDocFile(const base::FilePath& file_path,
+                    const GURL& url,
+                    const std::string& resource_id) {
+  std::string content =
+      base::StringPrintf("{\"url\": \"%s\", \"resource_id\": \"%s\"}",
+                         url.spec().c_str(), resource_id.c_str());
+  return base::WriteFile(file_path, content.data(), content.size()) ==
+         static_cast<int>(content.size());
+}
+
+GURL ReadUrlFromGDocFile(const base::FilePath& file_path) {
+  return GURL(ReadStringFromGDocFile(file_path, "url"));
+}
+
+std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path) {
+  return ReadStringFromGDocFile(file_path, "resource_id");
+}
+
+}  // namespace util
+}  // namespace drive
diff --git a/components/drive/file_system_core_util.h b/components/drive/file_system_core_util.h
new file mode 100644
index 0000000..c273cbe6
--- /dev/null
+++ b/components/drive/file_system_core_util.h
@@ -0,0 +1,90 @@
+// Copyright 2015 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 COMPONENTS_DRIVE_FILE_SYSTEM_CORE_UTIL_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_CORE_UTIL_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "components/drive/file_errors.h"
+#include "url/gurl.h"
+
+namespace drive {
+
+class DriveAppRegistry;
+class DriveServiceInterface;
+class FileSystemInterface;
+
+namespace util {
+
+// "drive" diretory's local ID is fixed to this value.
+const char kDriveGrandRootLocalId[] = "<drive>";
+
+// "drive/other" diretory's local ID is fixed to this value.
+const char kDriveOtherDirLocalId[] = "<other>";
+
+// "drive/trash" diretory's local ID is fixed to this value.
+const char kDriveTrashDirLocalId[] = "<trash>";
+
+// The directory names used for the Google Drive file system tree. These names
+// are used in URLs for the file manager, hence user-visible.
+const char kDriveGrandRootDirName[] = "drive";
+const char kDriveMyDriveRootDirName[] = "root";
+const char kDriveOtherDirName[] = "other";
+const char kDriveTrashDirName[] = "trash";
+
+// Returns the path of the top root of the pseudo tree.
+const base::FilePath& GetDriveGrandRootPath();
+
+// Returns the path of the directory representing "My Drive".
+const base::FilePath& GetDriveMyDriveRootPath();
+
+// Escapes a file name in Drive cache.
+// Replaces percent ('%'), period ('.') and slash ('/') with %XX (hex)
+std::string EscapeCacheFileName(const std::string& filename);
+
+// Unescapes a file path in Drive cache.
+// This is the inverse of EscapeCacheFileName.
+std::string UnescapeCacheFileName(const std::string& filename);
+
+// Converts the given string to a form suitable as a file name. Specifically,
+// - Normalizes in Unicode Normalization Form C.
+// - Replaces slashes '/' with '_'.
+// - Replaces the whole input with "_" if the all input characters are '.'.
+// |input| must be a valid UTF-8 encoded string.
+std::string NormalizeFileName(const std::string& input);
+
+// Does nothing with |error|. Used with functions taking FileOperationCallback.
+void EmptyFileOperationCallback(FileError error);
+
+// Helper to destroy objects which needs Destroy() to be called on destruction.
+struct DestroyHelper {
+  template <typename T>
+  void operator()(T* object) const {
+    if (object)
+      object->Destroy();
+  }
+};
+
+// Creates a GDoc file with given values.
+//
+// GDoc files are used to represent hosted documents on local filesystems.
+// A GDoc file contains a JSON whose content is a URL to view the document and
+// a resource ID of the entry.
+bool CreateGDocFile(const base::FilePath& file_path,
+                    const GURL& url,
+                    const std::string& resource_id);
+
+// Reads URL from a GDoc file.
+GURL ReadUrlFromGDocFile(const base::FilePath& file_path);
+
+// Reads resource ID from a GDoc file.
+std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path);
+
+}  // namespace util
+}  // namespace drive
+
+#endif  // COMPONENTS_DRIVE_FILE_SYSTEM_CORE_UTIL_H_
diff --git a/components/drive/file_system_core_util_unittest.cc b/components/drive/file_system_core_util_unittest.cc
new file mode 100644
index 0000000..663da28
--- /dev/null
+++ b/components/drive/file_system_core_util_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright 2015 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 "components/drive/file_system_core_util.h"
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace util {
+
+class FileSystemUtilTest : public testing::Test {
+  content::TestBrowserThreadBundle thread_bundle_;
+};
+
+TEST_F(FileSystemUtilTest, EscapeUnescapeCacheFileName) {
+  const std::string kUnescapedFileName(
+      "tmp:`~!@#$%^&*()-_=+[{|]}\\\\;\',<.>/?");
+  const std::string kEscapedFileName(
+      "tmp:`~!@#$%25^&*()-_=+[{|]}\\\\;\',<%2E>%2F?");
+  EXPECT_EQ(kEscapedFileName, EscapeCacheFileName(kUnescapedFileName));
+  EXPECT_EQ(kUnescapedFileName, UnescapeCacheFileName(kEscapedFileName));
+}
+
+TEST_F(FileSystemUtilTest, NormalizeFileName) {
+  EXPECT_EQ("", NormalizeFileName(""));
+  EXPECT_EQ("foo", NormalizeFileName("foo"));
+  // Slash
+  EXPECT_EQ("foo_zzz", NormalizeFileName("foo/zzz"));
+  EXPECT_EQ("___", NormalizeFileName("///"));
+  // Japanese hiragana "hi" + semi-voiced-mark is normalized to "pi".
+  EXPECT_EQ("\xE3\x81\xB4", NormalizeFileName("\xE3\x81\xB2\xE3\x82\x9A"));
+  // Dot
+  EXPECT_EQ("_", NormalizeFileName("."));
+  EXPECT_EQ("_", NormalizeFileName(".."));
+  EXPECT_EQ("_", NormalizeFileName("..."));
+  EXPECT_EQ(".bashrc", NormalizeFileName(".bashrc"));
+  EXPECT_EQ("._", NormalizeFileName("./"));
+}
+
+TEST_F(FileSystemUtilTest, GDocFile) {
+  base::ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+  GURL url(
+      "https://docs.google.com/document/d/"
+      "1YsCnrMxxgp7LDdtlFDt-WdtEIth89vA9inrILtvK-Ug/edit");
+  std::string resource_id("1YsCnrMxxgp7LDdtlFDt-WdtEIth89vA9inrILtvK-Ug");
+
+  // Read and write gdoc.
+  base::FilePath file = temp_dir.path().AppendASCII("test.gdoc");
+  EXPECT_TRUE(CreateGDocFile(file, url, resource_id));
+  EXPECT_EQ(url, ReadUrlFromGDocFile(file));
+  EXPECT_EQ(resource_id, ReadResourceIdFromGDocFile(file));
+
+  // Read and write gsheet.
+  file = temp_dir.path().AppendASCII("test.gsheet");
+  EXPECT_TRUE(CreateGDocFile(file, url, resource_id));
+  EXPECT_EQ(url, ReadUrlFromGDocFile(file));
+  EXPECT_EQ(resource_id, ReadResourceIdFromGDocFile(file));
+
+  // Read and write gslides.
+  file = temp_dir.path().AppendASCII("test.gslides");
+  EXPECT_TRUE(CreateGDocFile(file, url, resource_id));
+  EXPECT_EQ(url, ReadUrlFromGDocFile(file));
+  EXPECT_EQ(resource_id, ReadResourceIdFromGDocFile(file));
+
+  // Read and write gdraw.
+  file = temp_dir.path().AppendASCII("test.gdraw");
+  EXPECT_TRUE(CreateGDocFile(file, url, resource_id));
+  EXPECT_EQ(url, ReadUrlFromGDocFile(file));
+  EXPECT_EQ(resource_id, ReadResourceIdFromGDocFile(file));
+
+  // Read and write gtable.
+  file = temp_dir.path().AppendASCII("test.gtable");
+  EXPECT_TRUE(CreateGDocFile(file, url, resource_id));
+  EXPECT_EQ(url, ReadUrlFromGDocFile(file));
+  EXPECT_EQ(resource_id, ReadResourceIdFromGDocFile(file));
+
+  // Non GDoc file.
+  file = temp_dir.path().AppendASCII("test.txt");
+  std::string data = "Hello world!";
+  EXPECT_TRUE(google_apis::test_util::WriteStringToFile(file, data));
+  EXPECT_TRUE(ReadUrlFromGDocFile(file).is_empty());
+  EXPECT_TRUE(ReadResourceIdFromGDocFile(file).empty());
+}
+
+}  // namespace util
+}  // namespace drive
diff --git a/components/drive/resource_entry_conversion.cc b/components/drive/resource_entry_conversion.cc
new file mode 100644
index 0000000..a4fa83d
--- /dev/null
+++ b/components/drive/resource_entry_conversion.cc
@@ -0,0 +1,141 @@
+// 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 "components/drive/resource_entry_conversion.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_system_core_util.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+
+bool ConvertChangeResourceToResourceEntry(
+    const google_apis::ChangeResource& input,
+    ResourceEntry* out_entry,
+    std::string* out_parent_resource_id) {
+  DCHECK(out_entry);
+  DCHECK(out_parent_resource_id);
+
+  ResourceEntry converted;
+  std::string parent_resource_id;
+  if (input.file() &&
+      !ConvertFileResourceToResourceEntry(*input.file(), &converted,
+                                          &parent_resource_id))
+      return false;
+
+  converted.set_resource_id(input.file_id());
+  converted.set_deleted(converted.deleted() || input.is_deleted());
+  converted.set_modification_date(input.modification_date().ToInternalValue());
+
+  out_entry->Swap(&converted);
+  swap(*out_parent_resource_id, parent_resource_id);
+  return true;
+}
+
+bool ConvertFileResourceToResourceEntry(
+    const google_apis::FileResource& input,
+    ResourceEntry* out_entry,
+    std::string* out_parent_resource_id) {
+  DCHECK(out_entry);
+  DCHECK(out_parent_resource_id);
+  ResourceEntry converted;
+
+  // For regular files, the 'filename' and 'title' attribute in the metadata
+  // may be different (e.g. due to rename). To be consistent with the web
+  // interface and other client to use the 'title' attribute, instead of
+  // 'filename', as the file name in the local snapshot.
+  converted.set_title(input.title());
+  converted.set_base_name(util::NormalizeFileName(converted.title()));
+  converted.set_resource_id(input.file_id());
+
+  // Gets parent Resource ID. On drive.google.com, a file can have multiple
+  // parents or no parent, but we are forcing a tree-shaped structure (i.e. no
+  // multi-parent or zero-parent entries). Therefore the first found "parent" is
+  // used for the entry. Tracked in http://crbug.com/158904.
+  std::string parent_resource_id;
+  if (!input.parents().empty())
+    parent_resource_id = input.parents()[0].file_id();
+
+  converted.set_deleted(input.labels().is_trashed());
+  converted.set_shared_with_me(!input.shared_with_me_date().is_null());
+  converted.set_shared(input.shared());
+
+  PlatformFileInfoProto* file_info = converted.mutable_file_info();
+
+  file_info->set_last_modified(input.modified_date().ToInternalValue());
+  // If the file has never been viewed (last_viewed_by_me_date().is_null() ==
+  // true), then we will set the last_accessed field in the protocol buffer to
+  // 0.
+  file_info->set_last_accessed(
+      input.last_viewed_by_me_date().ToInternalValue());
+  file_info->set_creation_time(input.created_date().ToInternalValue());
+
+  if (input.IsDirectory()) {
+    file_info->set_is_directory(true);
+  } else {
+    FileSpecificInfo* file_specific_info =
+        converted.mutable_file_specific_info();
+    if (!input.IsHostedDocument()) {
+      file_info->set_size(input.file_size());
+      file_specific_info->set_md5(input.md5_checksum());
+      file_specific_info->set_is_hosted_document(false);
+    } else {
+      // Attach .g<something> extension to hosted documents so we can special
+      // case their handling in UI.
+      // TODO(satorux): Figure out better way how to pass input info like kind
+      // to UI through the File API stack.
+      const std::string document_extension =
+          drive::util::GetHostedDocumentExtension(input.mime_type());
+      file_specific_info->set_document_extension(document_extension);
+      converted.set_base_name(
+          util::NormalizeFileName(converted.title() + document_extension));
+
+      // We don't know the size of hosted docs and it does not matter since
+      // it has no effect on the quota.
+      file_info->set_size(0);
+      file_specific_info->set_is_hosted_document(true);
+    }
+    file_info->set_is_directory(false);
+    file_specific_info->set_content_mime_type(input.mime_type());
+
+    if (!input.alternate_link().is_empty())
+      file_specific_info->set_alternate_url(input.alternate_link().spec());
+
+    const int64 image_width = input.image_media_metadata().width();
+    if (image_width != -1)
+      file_specific_info->set_image_width(image_width);
+
+    const int64 image_height = input.image_media_metadata().height();
+    if (image_height != -1)
+      file_specific_info->set_image_height(image_height);
+
+    const int64 image_rotation = input.image_media_metadata().rotation();
+    if (image_rotation != -1)
+      file_specific_info->set_image_rotation(image_rotation);
+  }
+
+  out_entry->Swap(&converted);
+  swap(*out_parent_resource_id, parent_resource_id);
+  return true;
+}
+
+void ConvertResourceEntryToFileInfo(const ResourceEntry& entry,
+                                    base::File::Info* file_info) {
+  file_info->size = entry.file_info().size();
+  file_info->is_directory = entry.file_info().is_directory();
+  file_info->is_symbolic_link = entry.file_info().is_symbolic_link();
+  file_info->last_modified = base::Time::FromInternalValue(
+      entry.file_info().last_modified());
+  file_info->last_accessed = base::Time::FromInternalValue(
+      entry.file_info().last_accessed());
+  file_info->creation_time = base::Time::FromInternalValue(
+      entry.file_info().creation_time());
+}
+
+}  // namespace drive
diff --git a/components/drive/resource_entry_conversion.h b/components/drive/resource_entry_conversion.h
new file mode 100644
index 0000000..2de228e
--- /dev/null
+++ b/components/drive/resource_entry_conversion.h
@@ -0,0 +1,53 @@
+// 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 COMPONENTS_DRIVE_RESOURCE_ENTRY_CONVERSION_H_
+#define COMPONENTS_DRIVE_RESOURCE_ENTRY_CONVERSION_H_
+
+#include <string>
+
+#include "base/files/file.h"
+
+namespace google_apis {
+class ChangeResource;
+class FileResource;
+}
+
+namespace drive {
+
+class ResourceEntry;
+
+// Converts a google_apis::ChangeResource into a drive::ResourceEntry.
+// If the conversion succeeded, return true and sets the result to |out_entry|.
+// |out_parent_resource_id| will be set to the resource ID of the parent entry.
+// If failed, it returns false and keeps output arguments untouched.
+//
+// Every entry is guaranteed to have one parent resource ID in ResourceMetadata.
+// This requirement is needed to represent contents in Drive as a file system
+// tree, and achieved as follows:
+//
+// 1) Entries without parents are allowed on drive.google.com. These entries are
+// collected to "drive/other", and have "drive/other" as the parent.
+//
+// 2) Entries with multiple parents are allowed on drive.google.com. For these
+// entries, the first parent is chosen.
+bool ConvertChangeResourceToResourceEntry(
+    const google_apis::ChangeResource& input,
+    ResourceEntry* out_entry,
+    std::string* out_parent_resource_id);
+
+// Converts a google_apis::FileResource into a drive::ResourceEntry.
+// Also see the comment for ConvertChangeResourceToResourceEntry above.
+bool ConvertFileResourceToResourceEntry(
+    const google_apis::FileResource& input,
+    ResourceEntry* out_entry,
+    std::string* out_parent_resource_id);
+
+// Converts the resource entry to the platform file info.
+void ConvertResourceEntryToFileInfo(const ResourceEntry& entry,
+                                    base::File::Info* file_info);
+
+}  // namespace drive
+
+#endif  // COMPONENTS_DRIVE_RESOURCE_ENTRY_CONVERSION_H_
diff --git a/components/drive/resource_entry_conversion_unittest.cc b/components/drive/resource_entry_conversion_unittest.cc
new file mode 100644
index 0000000..b3fe252e
--- /dev/null
+++ b/components/drive/resource_entry_conversion_unittest.cc
@@ -0,0 +1,374 @@
+// 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 "components/drive/resource_entry_conversion.h"
+
+#include "base/time/time.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+
+namespace {
+
+base::Time GetTestTime() {
+  // 2011-12-14-T00:40:47.330Z
+  base::Time::Exploded exploded;
+  exploded.year = 2011;
+  exploded.month = 12;
+  exploded.day_of_month = 14;
+  exploded.day_of_week = 2;  // Tuesday
+  exploded.hour = 0;
+  exploded.minute = 40;
+  exploded.second = 47;
+  exploded.millisecond = 330;
+  return base::Time::FromUTCExploded(exploded);
+}
+
+}  // namespace
+
+TEST(ResourceEntryConversionTest, ConvertToResourceEntry_File) {
+  google_apis::FileResource file_resource;
+  file_resource.set_title("File 1.mp3");
+  file_resource.set_file_id("resource_id");
+  file_resource.set_created_date(GetTestTime());
+  file_resource.set_modified_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(10));
+  file_resource.set_mime_type("audio/mpeg");
+  file_resource.set_alternate_link(GURL("https://file_link_alternate"));
+  file_resource.set_file_size(892721);
+  file_resource.set_md5_checksum("3b4382ebefec6e743578c76bbd0575ce");
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+      file_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(file_resource.title(), entry.title());
+  EXPECT_EQ(file_resource.title(), entry.base_name());
+  EXPECT_EQ(file_resource.file_id(), entry.resource_id());
+  EXPECT_EQ("", parent_resource_id);
+
+  EXPECT_FALSE(entry.deleted());
+  EXPECT_FALSE(entry.shared_with_me());
+  EXPECT_FALSE(entry.shared());
+
+  EXPECT_EQ(file_resource.modified_date().ToInternalValue(),
+            entry.file_info().last_modified());
+  // Last accessed value equal to 0 means that the file has never been viewed.
+  EXPECT_EQ(0, entry.file_info().last_accessed());
+  EXPECT_EQ(file_resource.created_date().ToInternalValue(),
+            entry.file_info().creation_time());
+
+  EXPECT_EQ(file_resource.mime_type(),
+            entry.file_specific_info().content_mime_type());
+  EXPECT_FALSE(entry.file_specific_info().is_hosted_document());
+  EXPECT_EQ(file_resource.alternate_link().spec(),
+            entry.file_specific_info().alternate_url());
+
+  // Regular file specific fields.
+  EXPECT_EQ(file_resource.file_size(), entry.file_info().size());
+  EXPECT_EQ(file_resource.md5_checksum(), entry.file_specific_info().md5());
+  EXPECT_FALSE(entry.file_info().is_directory());
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertFileResourceToResourceEntry_HostedDocument) {
+  google_apis::FileResource file_resource;
+  file_resource.set_title("Document 1");
+  file_resource.set_file_id("resource_id");
+  file_resource.set_created_date(GetTestTime());
+  file_resource.set_modified_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(10));
+  file_resource.set_last_viewed_by_me_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(20));
+  file_resource.set_mime_type(util::kGoogleDocumentMimeType);
+  file_resource.set_alternate_link(GURL("https://file_link_alternate"));
+  // Do not set file size to represent a hosted document.
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+      file_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(file_resource.title(), entry.title());
+  EXPECT_EQ(file_resource.title() + ".gdoc",
+            entry.base_name());  // The suffix added.
+  EXPECT_EQ(".gdoc", entry.file_specific_info().document_extension());
+  EXPECT_EQ(file_resource.file_id(), entry.resource_id());
+  EXPECT_EQ("", parent_resource_id);
+
+  EXPECT_FALSE(entry.deleted());
+  EXPECT_FALSE(entry.shared_with_me());
+  EXPECT_FALSE(entry.shared());
+
+  EXPECT_EQ(file_resource.modified_date().ToInternalValue(),
+            entry.file_info().last_modified());
+  EXPECT_EQ(file_resource.last_viewed_by_me_date().ToInternalValue(),
+            entry.file_info().last_accessed());
+  EXPECT_EQ(file_resource.created_date().ToInternalValue(),
+            entry.file_info().creation_time());
+
+  EXPECT_EQ(file_resource.mime_type(),
+            entry.file_specific_info().content_mime_type());
+  EXPECT_TRUE(entry.file_specific_info().is_hosted_document());
+  EXPECT_EQ(file_resource.alternate_link().spec(),
+            entry.file_specific_info().alternate_url());
+
+  // The size should be 0 for a hosted document.
+  EXPECT_EQ(0, entry.file_info().size());
+  EXPECT_FALSE(entry.file_info().is_directory());
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertFileResourceToResourceEntry_Directory) {
+  google_apis::FileResource file_resource;
+  file_resource.set_title("Folder");
+  file_resource.set_file_id("resource_id");
+  file_resource.set_created_date(GetTestTime());
+  file_resource.set_modified_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(10));
+  file_resource.set_last_viewed_by_me_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(20));
+  file_resource.set_mime_type(util::kDriveFolderMimeType);
+
+  google_apis::ParentReference parent;
+  parent.set_file_id("parent_resource_id");
+  file_resource.mutable_parents()->push_back(parent);
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+      file_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(file_resource.title(), entry.title());
+  EXPECT_EQ(file_resource.title(), entry.base_name());
+  EXPECT_EQ(file_resource.file_id(), entry.resource_id());
+  // The parent resource ID should be obtained as this is a sub directory
+  // under a non-root directory.
+  EXPECT_EQ(parent.file_id(), parent_resource_id);
+
+  EXPECT_FALSE(entry.deleted());
+  EXPECT_FALSE(entry.shared_with_me());
+  EXPECT_FALSE(entry.shared());
+
+  EXPECT_EQ(file_resource.modified_date().ToInternalValue(),
+            entry.file_info().last_modified());
+  EXPECT_EQ(file_resource.last_viewed_by_me_date().ToInternalValue(),
+            entry.file_info().last_accessed());
+  EXPECT_EQ(file_resource.created_date().ToInternalValue(),
+            entry.file_info().creation_time());
+
+  EXPECT_TRUE(entry.file_info().is_directory());
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertFileResourceToResourceEntry_DeletedHostedDocument) {
+  google_apis::FileResource file_resource;
+  file_resource.set_title("Document 1");
+  file_resource.set_file_id("resource_id");
+  file_resource.set_created_date(GetTestTime());
+  file_resource.set_modified_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(10));
+  file_resource.set_last_viewed_by_me_date(
+      GetTestTime() + base::TimeDelta::FromSeconds(20));
+  file_resource.set_mime_type(util::kGoogleDocumentMimeType);
+  file_resource.set_alternate_link(GURL("https://file_link_alternate"));
+  file_resource.mutable_labels()->set_trashed(true);
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+      file_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(file_resource.title(), entry.title());
+  EXPECT_EQ(file_resource.title() + ".gdoc", entry.base_name());
+  EXPECT_EQ(file_resource.file_id(), entry.resource_id());
+  EXPECT_EQ("", parent_resource_id);
+
+  EXPECT_TRUE(entry.deleted());  // The document was deleted.
+  EXPECT_FALSE(entry.shared_with_me());
+  EXPECT_FALSE(entry.shared());
+
+  EXPECT_EQ(file_resource.modified_date().ToInternalValue(),
+            entry.file_info().last_modified());
+  EXPECT_EQ(file_resource.last_viewed_by_me_date().ToInternalValue(),
+            entry.file_info().last_accessed());
+  EXPECT_EQ(file_resource.created_date().ToInternalValue(),
+            entry.file_info().creation_time());
+
+  EXPECT_EQ(file_resource.mime_type(),
+            entry.file_specific_info().content_mime_type());
+  EXPECT_TRUE(entry.file_specific_info().is_hosted_document());
+  EXPECT_EQ(file_resource.alternate_link().spec(),
+            entry.file_specific_info().alternate_url());
+
+  // The size should be 0 for a hosted document.
+  EXPECT_EQ(0, entry.file_info().size());
+}
+
+TEST(ResourceEntryConversionTest, ConvertChangeResourceToResourceEntry) {
+  google_apis::ChangeResource change_resource;
+  change_resource.set_file(make_scoped_ptr(new google_apis::FileResource));
+  change_resource.set_file_id("resource_id");
+  change_resource.set_modification_date(GetTestTime());
+
+  google_apis::FileResource* file_resource = change_resource.mutable_file();
+  file_resource->set_title("File 1.mp3");
+  file_resource->set_file_id("resource_id");
+  // Set dummy file size to declare that this is a regular file.
+  file_resource->set_file_size(12345);
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertChangeResourceToResourceEntry(
+      change_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(change_resource.file_id(), entry.resource_id());
+  EXPECT_EQ(change_resource.modification_date().ToInternalValue(),
+            entry.modification_date());
+
+  EXPECT_EQ(file_resource->title(), entry.title());
+  EXPECT_EQ(file_resource->title(), entry.base_name());
+  EXPECT_EQ("", parent_resource_id);
+
+  EXPECT_FALSE(entry.deleted());
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertChangeResourceToResourceEntry_Trashed) {
+  google_apis::ChangeResource change_resource;
+  change_resource.set_file(make_scoped_ptr(new google_apis::FileResource));
+  change_resource.set_file_id("resource_id");
+  change_resource.set_modification_date(GetTestTime());
+
+  google_apis::FileResource* file_resource = change_resource.mutable_file();
+  file_resource->set_title("File 1.mp3");
+  file_resource->set_file_id("resource_id");
+  // Set dummy file size to declare that this is a regular file.
+  file_resource->set_file_size(12345);
+  file_resource->mutable_labels()->set_trashed(true);
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertChangeResourceToResourceEntry(
+      change_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(change_resource.file_id(), entry.resource_id());
+  EXPECT_EQ(change_resource.modification_date().ToInternalValue(),
+            entry.modification_date());
+
+  EXPECT_EQ(file_resource->title(), entry.title());
+  EXPECT_EQ(file_resource->title(), entry.base_name());
+  EXPECT_EQ("", parent_resource_id);
+
+  EXPECT_TRUE(entry.deleted());
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertChangeResourceToResourceEntry_Deleted) {
+  google_apis::ChangeResource change_resource;
+  change_resource.set_deleted(true);
+  change_resource.set_file_id("resource_id");
+  change_resource.set_modification_date(GetTestTime());
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertChangeResourceToResourceEntry(
+      change_resource, &entry, &parent_resource_id));
+
+  EXPECT_EQ(change_resource.file_id(), entry.resource_id());
+  EXPECT_EQ("", parent_resource_id);
+
+  EXPECT_TRUE(entry.deleted());
+
+  EXPECT_EQ(change_resource.modification_date().ToInternalValue(),
+            entry.modification_date());
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertFileResourceToResourceEntry_SharedWithMeEntry) {
+  google_apis::FileResource file_resource;
+  file_resource.set_shared(true);
+  file_resource.set_shared_with_me_date(GetTestTime());
+
+  ResourceEntry entry;
+  std::string parent_resource_id;
+  EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+      file_resource, &entry, &parent_resource_id));
+  EXPECT_TRUE(entry.shared_with_me());
+  EXPECT_TRUE(entry.shared());
+}
+
+TEST(ResourceEntryConversionTest, ToPlatformFileInfo) {
+  ResourceEntry entry;
+  entry.mutable_file_info()->set_size(12345);
+  entry.mutable_file_info()->set_is_directory(true);
+  entry.mutable_file_info()->set_is_symbolic_link(true);
+  entry.mutable_file_info()->set_creation_time(999);
+  entry.mutable_file_info()->set_last_modified(123456789);
+  entry.mutable_file_info()->set_last_accessed(987654321);
+
+  base::File::Info file_info;
+  ConvertResourceEntryToFileInfo(entry, &file_info);
+  EXPECT_EQ(entry.file_info().size(), file_info.size);
+  EXPECT_EQ(entry.file_info().is_directory(), file_info.is_directory);
+  EXPECT_EQ(entry.file_info().is_symbolic_link(), file_info.is_symbolic_link);
+  EXPECT_EQ(base::Time::FromInternalValue(entry.file_info().creation_time()),
+            file_info.creation_time);
+  EXPECT_EQ(base::Time::FromInternalValue(entry.file_info().last_modified()),
+            file_info.last_modified);
+  EXPECT_EQ(base::Time::FromInternalValue(entry.file_info().last_accessed()),
+            file_info.last_accessed);
+}
+
+TEST(ResourceEntryConversionTest,
+     ConvertFileResourceToResourceEntry_ImageMediaMetadata) {
+  google_apis::FileResource entry_all_fields;
+  google_apis::FileResource entry_zero_fields;
+  google_apis::FileResource entry_no_fields;
+
+  entry_all_fields.mutable_image_media_metadata()->set_width(640);
+  entry_all_fields.mutable_image_media_metadata()->set_height(480);
+  entry_all_fields.mutable_image_media_metadata()->set_rotation(90);
+
+  entry_zero_fields.mutable_image_media_metadata()->set_width(0);
+  entry_zero_fields.mutable_image_media_metadata()->set_height(0);
+  entry_zero_fields.mutable_image_media_metadata()->set_rotation(0);
+
+  {
+    ResourceEntry entry;
+    std::string parent_resource_id;
+    EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+        entry_all_fields, &entry, &parent_resource_id));
+    EXPECT_EQ(640, entry.file_specific_info().image_width());
+    EXPECT_EQ(480, entry.file_specific_info().image_height());
+    EXPECT_EQ(90, entry.file_specific_info().image_rotation());
+  }
+  {
+    ResourceEntry entry;
+    std::string parent_resource_id;
+    EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+        entry_zero_fields, &entry, &parent_resource_id));
+    EXPECT_TRUE(entry.file_specific_info().has_image_width());
+    EXPECT_TRUE(entry.file_specific_info().has_image_height());
+    EXPECT_TRUE(entry.file_specific_info().has_image_rotation());
+    EXPECT_EQ(0, entry.file_specific_info().image_width());
+    EXPECT_EQ(0, entry.file_specific_info().image_height());
+    EXPECT_EQ(0, entry.file_specific_info().image_rotation());
+  }
+  {
+    ResourceEntry entry;
+    std::string parent_resource_id;
+    EXPECT_TRUE(ConvertFileResourceToResourceEntry(
+        entry_no_fields, &entry, &parent_resource_id));
+    EXPECT_FALSE(entry.file_specific_info().has_image_width());
+    EXPECT_FALSE(entry.file_specific_info().has_image_height());
+    EXPECT_FALSE(entry.file_specific_info().has_image_rotation());
+  }
+}
+
+}  // namespace drive
diff --git a/components/drive/resource_metadata.cc b/components/drive/resource_metadata.cc
new file mode 100644
index 0000000..25250ff
--- /dev/null
+++ b/components/drive/resource_metadata.cc
@@ -0,0 +1,607 @@
+// 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 "components/drive/resource_metadata.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/guid.h"
+#include "base/location.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/resource_metadata_storage.h"
+
+namespace drive {
+namespace internal {
+namespace {
+
+// Returns true if enough disk space is available for DB operation.
+// TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
+bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath& path) {
+  const int64 kRequiredDiskSpaceInMB = 128;  // 128 MB seems to be large enough.
+  return base::SysInfo::AmountOfFreeDiskSpace(path) >=
+      kRequiredDiskSpaceInMB * (1 << 20);
+}
+
+// Returns a file name with a uniquifier appended. (e.g. "File (1).txt")
+std::string GetUniquifiedName(const std::string& name, int uniquifier) {
+  base::FilePath name_path = base::FilePath::FromUTF8Unsafe(name);
+  name_path = name_path.InsertBeforeExtensionASCII(
+      base::StringPrintf(" (%d)", uniquifier));
+  return name_path.AsUTF8Unsafe();
+}
+
+// Returns true when there is no entry with the specified name under the parent
+// other than the specified entry.
+FileError EntryCanUseName(ResourceMetadataStorage* storage,
+                          const std::string& parent_local_id,
+                          const std::string& local_id,
+                          const std::string& base_name,
+                          bool* result) {
+  std::string existing_entry_id;
+  FileError error = storage->GetChild(parent_local_id, base_name,
+                                      &existing_entry_id);
+  if (error == FILE_ERROR_OK)
+    *result = existing_entry_id == local_id;
+  else if (error == FILE_ERROR_NOT_FOUND)
+    *result = true;
+  else
+    return error;
+  return FILE_ERROR_OK;
+}
+
+// Returns true when the ID is used by an immutable entry.
+bool IsImmutableEntry(const std::string& id) {
+  return id == util::kDriveGrandRootLocalId ||
+      id == util::kDriveOtherDirLocalId ||
+      id == util::kDriveTrashDirLocalId;
+}
+
+}  // namespace
+
+ResourceMetadata::ResourceMetadata(
+    ResourceMetadataStorage* storage,
+    FileCache* cache,
+    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
+    : blocking_task_runner_(blocking_task_runner),
+      storage_(storage),
+      cache_(cache) {
+}
+
+FileError ResourceMetadata::Initialize() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  return SetUpDefaultEntries();
+}
+
+void ResourceMetadata::Destroy() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  blocking_task_runner_->PostTask(
+      FROM_HERE,
+      base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
+                 base::Unretained(this)));
+}
+
+FileError ResourceMetadata::Reset() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+
+  FileError error = storage_->SetLargestChangestamp(0);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  // Remove all root entries.
+  scoped_ptr<Iterator> it = GetIterator();
+  for (; !it->IsAtEnd(); it->Advance()) {
+    if (it->GetValue().parent_local_id().empty()) {
+      error = RemoveEntryRecursively(it->GetID());
+      if (error != FILE_ERROR_OK)
+        return error;
+    }
+  }
+  if (it->HasError())
+    return FILE_ERROR_FAILED;
+
+  return SetUpDefaultEntries();
+}
+
+ResourceMetadata::~ResourceMetadata() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+}
+
+FileError ResourceMetadata::SetUpDefaultEntries() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  // Initialize "/drive".
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(util::kDriveGrandRootLocalId, &entry);
+  if (error == FILE_ERROR_NOT_FOUND) {
+    ResourceEntry root;
+    root.mutable_file_info()->set_is_directory(true);
+    root.set_local_id(util::kDriveGrandRootLocalId);
+    root.set_title(util::kDriveGrandRootDirName);
+    root.set_base_name(util::kDriveGrandRootDirName);
+    error = storage_->PutEntry(root);
+    if (error != FILE_ERROR_OK)
+      return error;
+  } else if (error == FILE_ERROR_OK) {
+    if (!entry.resource_id().empty()) {
+      // Old implementations used kDriveGrandRootLocalId as a resource ID.
+      entry.clear_resource_id();
+      error = storage_->PutEntry(entry);
+      if (error != FILE_ERROR_OK)
+        return error;
+    }
+  } else {
+    return error;
+  }
+
+  // Initialize "/drive/other".
+  error = storage_->GetEntry(util::kDriveOtherDirLocalId, &entry);
+  if (error == FILE_ERROR_NOT_FOUND) {
+    ResourceEntry other_dir;
+    other_dir.mutable_file_info()->set_is_directory(true);
+    other_dir.set_local_id(util::kDriveOtherDirLocalId);
+    other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
+    other_dir.set_title(util::kDriveOtherDirName);
+    error = PutEntryUnderDirectory(other_dir);
+    if (error != FILE_ERROR_OK)
+      return error;
+  } else if (error == FILE_ERROR_OK) {
+    if (!entry.resource_id().empty()) {
+      // Old implementations used kDriveOtherDirLocalId as a resource ID.
+      entry.clear_resource_id();
+      error = storage_->PutEntry(entry);
+      if (error != FILE_ERROR_OK)
+        return error;
+    }
+  } else {
+    return error;
+  }
+
+  // Initialize "drive/trash".
+  error = storage_->GetEntry(util::kDriveTrashDirLocalId, &entry);
+  if (error == FILE_ERROR_NOT_FOUND) {
+    ResourceEntry trash_dir;
+    trash_dir.mutable_file_info()->set_is_directory(true);
+    trash_dir.set_local_id(util::kDriveTrashDirLocalId);
+    trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
+    trash_dir.set_title(util::kDriveTrashDirName);
+    error = PutEntryUnderDirectory(trash_dir);
+    if (error != FILE_ERROR_OK)
+      return error;
+  } else if (error != FILE_ERROR_OK) {
+    return error;
+  }
+
+  // Initialize "drive/root".
+  std::string child_id;
+  error = storage_->GetChild(
+      util::kDriveGrandRootLocalId, util::kDriveMyDriveRootDirName, &child_id);
+  if (error == FILE_ERROR_NOT_FOUND) {
+    ResourceEntry mydrive;
+    mydrive.mutable_file_info()->set_is_directory(true);
+    mydrive.set_parent_local_id(util::kDriveGrandRootLocalId);
+    mydrive.set_title(util::kDriveMyDriveRootDirName);
+
+    std::string local_id;
+    error = AddEntry(mydrive, &local_id);
+    if (error != FILE_ERROR_OK)
+      return error;
+  } else if (error != FILE_ERROR_OK) {
+    return error;
+  }
+  return FILE_ERROR_OK;
+}
+
+void ResourceMetadata::DestroyOnBlockingPool() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  delete this;
+}
+
+FileError ResourceMetadata::GetLargestChangestamp(int64* out_value) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  return storage_->GetLargestChangestamp(out_value);
+}
+
+FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+
+  return storage_->SetLargestChangestamp(value);
+}
+
+FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
+                                     std::string* out_id) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(entry.local_id().empty());
+
+  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+
+  ResourceEntry parent;
+  FileError error = storage_->GetEntry(entry.parent_local_id(), &parent);
+  if (error != FILE_ERROR_OK)
+    return error;
+  if (!parent.file_info().is_directory())
+    return FILE_ERROR_NOT_A_DIRECTORY;
+
+  // Multiple entries with the same resource ID should not be present.
+  std::string local_id;
+  ResourceEntry existing_entry;
+  if (!entry.resource_id().empty()) {
+    error = storage_->GetIdByResourceId(entry.resource_id(), &local_id);
+    if (error == FILE_ERROR_OK)
+      error = storage_->GetEntry(local_id, &existing_entry);
+
+    if (error == FILE_ERROR_OK)
+      return FILE_ERROR_EXISTS;
+    else if (error != FILE_ERROR_NOT_FOUND)
+      return error;
+  }
+
+  // Generate unique local ID when needed.
+  // We don't check for ID collisions as its probability is extremely low.
+  if (local_id.empty())
+    local_id = base::GenerateGUID();
+
+  ResourceEntry new_entry(entry);
+  new_entry.set_local_id(local_id);
+
+  error = PutEntryUnderDirectory(new_entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  *out_id = local_id;
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadata::RemoveEntry(const std::string& id) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+
+  // Disallow deletion of default entries.
+  if (IsImmutableEntry(id))
+    return FILE_ERROR_ACCESS_DENIED;
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  return RemoveEntryRecursively(id);
+}
+
+FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
+                                                 ResourceEntry* out_entry) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(!id.empty());
+  DCHECK(out_entry);
+
+  return storage_->GetEntry(id, out_entry);
+}
+
+FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
+                                                   ResourceEntry* out_entry) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(out_entry);
+
+  std::string id;
+  FileError error = GetIdByPath(path, &id);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  return GetResourceEntryById(id, out_entry);
+}
+
+FileError ResourceMetadata::ReadDirectoryByPath(
+    const base::FilePath& path,
+    ResourceEntryVector* out_entries) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(out_entries);
+
+  std::string id;
+  FileError error = GetIdByPath(path, &id);
+  if (error != FILE_ERROR_OK)
+    return error;
+  return ReadDirectoryById(id, out_entries);
+}
+
+FileError ResourceMetadata::ReadDirectoryById(
+    const std::string& id,
+    ResourceEntryVector* out_entries) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(out_entries);
+
+  ResourceEntry entry;
+  FileError error = GetResourceEntryById(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  if (!entry.file_info().is_directory())
+    return FILE_ERROR_NOT_A_DIRECTORY;
+
+  std::vector<std::string> children;
+  error = storage_->GetChildren(id, &children);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  ResourceEntryVector entries(children.size());
+  for (size_t i = 0; i < children.size(); ++i) {
+    error = storage_->GetEntry(children[i], &entries[i]);
+    if (error != FILE_ERROR_OK)
+      return error;
+  }
+  out_entries->swap(entries);
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+
+  ResourceEntry old_entry;
+  FileError error = storage_->GetEntry(entry.local_id(), &old_entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  if (IsImmutableEntry(entry.local_id()) ||
+      old_entry.file_info().is_directory() !=  // Reject incompatible input.
+      entry.file_info().is_directory())
+    return FILE_ERROR_INVALID_OPERATION;
+
+  if (!entry.resource_id().empty()) {
+    // Multiple entries cannot share the same resource ID.
+    std::string local_id;
+    FileError error = GetIdByResourceId(entry.resource_id(), &local_id);
+    switch (error) {
+      case FILE_ERROR_OK:
+        if (local_id != entry.local_id())
+          return FILE_ERROR_INVALID_OPERATION;
+        break;
+
+      case FILE_ERROR_NOT_FOUND:
+        break;
+
+      default:
+        return error;
+    }
+  }
+
+  // Make sure that the new parent exists and it is a directory.
+  ResourceEntry new_parent;
+  error = storage_->GetEntry(entry.parent_local_id(), &new_parent);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  if (!new_parent.file_info().is_directory())
+    return FILE_ERROR_NOT_A_DIRECTORY;
+
+  // Do not overwrite cache states.
+  // Cache state should be changed via FileCache.
+  ResourceEntry updated_entry(entry);
+  if (old_entry.file_specific_info().has_cache_state()) {
+    *updated_entry.mutable_file_specific_info()->mutable_cache_state() =
+        old_entry.file_specific_info().cache_state();
+  } else if (updated_entry.file_specific_info().has_cache_state()) {
+    updated_entry.mutable_file_specific_info()->clear_cache_state();
+  }
+  // Remove from the old parent and add it to the new parent with the new data.
+  return PutEntryUnderDirectory(updated_entry);
+}
+
+FileError ResourceMetadata::GetSubDirectoriesRecursively(
+    const std::string& id,
+    std::set<base::FilePath>* sub_directories) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  std::vector<std::string> children;
+  FileError error = storage_->GetChildren(id, &children);
+  if (error != FILE_ERROR_OK)
+    return error;
+  for (size_t i = 0; i < children.size(); ++i) {
+    ResourceEntry entry;
+    error = storage_->GetEntry(children[i], &entry);
+    if (error != FILE_ERROR_OK)
+      return error;
+    if (entry.file_info().is_directory()) {
+      base::FilePath path;
+      error = GetFilePath(children[i], &path);
+      if (error != FILE_ERROR_OK)
+        return error;
+      sub_directories->insert(path);
+      GetSubDirectoriesRecursively(children[i], sub_directories);
+    }
+  }
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadata::GetChildId(const std::string& parent_local_id,
+                                       const std::string& base_name,
+                                       std::string* out_child_id) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  return storage_->GetChild(parent_local_id, base_name, out_child_id);
+}
+
+scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  return storage_->GetIterator();
+}
+
+FileError ResourceMetadata::GetFilePath(const std::string& id,
+                                        base::FilePath* out_file_path) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  base::FilePath path;
+  if (!entry.parent_local_id().empty()) {
+    error = GetFilePath(entry.parent_local_id(), &path);
+    if (error != FILE_ERROR_OK)
+      return error;
+  } else if (entry.local_id() != util::kDriveGrandRootLocalId) {
+    DVLOG(1) << "Entries not under the grand root don't have paths.";
+    return FILE_ERROR_NOT_FOUND;
+  }
+  path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
+  *out_file_path = path;
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
+                                        std::string* out_id) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  // Start from the root.
+  std::vector<base::FilePath::StringType> components;
+  file_path.GetComponents(&components);
+  if (components.empty() ||
+      components[0] != util::GetDriveGrandRootPath().value())
+    return FILE_ERROR_NOT_FOUND;
+
+  // Iterate over the remaining components.
+  std::string id = util::kDriveGrandRootLocalId;
+  for (size_t i = 1; i < components.size(); ++i) {
+    const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
+    std::string child_id;
+    FileError error = storage_->GetChild(id, component, &child_id);
+    if (error != FILE_ERROR_OK)
+      return error;
+    id = child_id;
+  }
+  *out_id = id;
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id,
+                                              std::string* out_local_id) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  return storage_->GetIdByResourceId(resource_id, out_local_id);
+}
+
+FileError ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(!entry.local_id().empty());
+  DCHECK(!entry.parent_local_id().empty());
+
+  std::string base_name;
+  FileError error = GetDeduplicatedBaseName(entry, &base_name);
+  if (error != FILE_ERROR_OK)
+    return error;
+  ResourceEntry updated_entry(entry);
+  updated_entry.set_base_name(base_name);
+  return storage_->PutEntry(updated_entry);
+}
+
+FileError ResourceMetadata::GetDeduplicatedBaseName(
+    const ResourceEntry& entry,
+    std::string* base_name) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+  DCHECK(!entry.parent_local_id().empty());
+  DCHECK(!entry.title().empty());
+
+  // The entry name may have been changed due to prior name de-duplication.
+  // We need to first restore the file name based on the title before going
+  // through name de-duplication again when it is added to another directory.
+  *base_name = entry.title();
+  if (entry.has_file_specific_info() &&
+      entry.file_specific_info().is_hosted_document()) {
+    *base_name += entry.file_specific_info().document_extension();
+  }
+  *base_name = util::NormalizeFileName(*base_name);
+
+  // If |base_name| is not used, just return it.
+  bool can_use_name = false;
+  FileError error = EntryCanUseName(storage_, entry.parent_local_id(),
+                                    entry.local_id(), *base_name,
+                                    &can_use_name);
+  if (error != FILE_ERROR_OK || can_use_name)
+    return error;
+
+  // Find an unused number with binary search.
+  int smallest_known_unused_modifier = 1;
+  while (true) {
+    error = EntryCanUseName(storage_, entry.parent_local_id(), entry.local_id(),
+                            GetUniquifiedName(*base_name,
+                                              smallest_known_unused_modifier),
+                            &can_use_name);
+    if (error != FILE_ERROR_OK)
+      return error;
+    if (can_use_name)
+      break;
+
+    const int delta = base::RandInt(1, smallest_known_unused_modifier);
+    if (smallest_known_unused_modifier <= INT_MAX - delta) {
+      smallest_known_unused_modifier += delta;
+    } else {  // No luck finding an unused number. Try again.
+      smallest_known_unused_modifier = 1;
+    }
+  }
+
+  int largest_known_used_modifier = 1;
+  while (smallest_known_unused_modifier - largest_known_used_modifier > 1) {
+    const int modifier = largest_known_used_modifier +
+        (smallest_known_unused_modifier - largest_known_used_modifier) / 2;
+
+    error = EntryCanUseName(storage_, entry.parent_local_id(), entry.local_id(),
+                            GetUniquifiedName(*base_name, modifier),
+                            &can_use_name);
+    if (error != FILE_ERROR_OK)
+      return error;
+    if (can_use_name) {
+      smallest_known_unused_modifier = modifier;
+    } else {
+      largest_known_used_modifier = modifier;
+    }
+  }
+  *base_name = GetUniquifiedName(*base_name, smallest_known_unused_modifier);
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
+  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
+
+  ResourceEntry entry;
+  FileError error = storage_->GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  if (entry.file_info().is_directory()) {
+    std::vector<std::string> children;
+    error = storage_->GetChildren(id, &children);
+    if (error != FILE_ERROR_OK)
+      return error;
+    for (size_t i = 0; i < children.size(); ++i) {
+      error = RemoveEntryRecursively(children[i]);
+      if (error != FILE_ERROR_OK)
+        return error;
+    }
+  }
+
+  error = cache_->Remove(id);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  return storage_->RemoveEntry(id);
+}
+
+}  // namespace internal
+}  // namespace drive
diff --git a/components/drive/resource_metadata.h b/components/drive/resource_metadata.h
new file mode 100644
index 0000000..3fe27513
--- /dev/null
+++ b/components/drive/resource_metadata.h
@@ -0,0 +1,146 @@
+// 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 COMPONENTS_DRIVE_RESOURCE_METADATA_H_
+#define COMPONENTS_DRIVE_RESOURCE_METADATA_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/resource_metadata_storage.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace drive {
+
+typedef std::vector<ResourceEntry> ResourceEntryVector;
+
+namespace internal {
+
+class FileCache;
+
+// Storage for Drive Metadata.
+// All methods except the constructor and Destroy() function must be run with
+// |blocking_task_runner| unless otherwise noted.
+class ResourceMetadata {
+ public:
+  typedef ResourceMetadataStorage::Iterator Iterator;
+
+  ResourceMetadata(
+      ResourceMetadataStorage* storage,
+      FileCache* cache,
+      scoped_refptr<base::SequencedTaskRunner> blocking_task_runner);
+
+  // Initializes this object.
+  // This method should be called before any other methods.
+  FileError Initialize() WARN_UNUSED_RESULT;
+
+  // Destroys this object.  This method posts a task to |blocking_task_runner_|
+  // to safely delete this object.
+  // Must be called on the UI thread.
+  void Destroy();
+
+  // Resets this object.
+  FileError Reset();
+
+  // Returns the largest changestamp.
+  FileError GetLargestChangestamp(int64* out_value);
+
+  // Sets the largest changestamp.
+  FileError SetLargestChangestamp(int64 value);
+
+  // Adds |entry| to the metadata tree based on its parent_local_id.
+  FileError AddEntry(const ResourceEntry& entry, std::string* out_id);
+
+  // Removes entry with |id| from its parent.
+  FileError RemoveEntry(const std::string& id);
+
+  // Finds an entry (a file or a directory) by |id|.
+  FileError GetResourceEntryById(const std::string& id,
+                                 ResourceEntry* out_entry);
+
+  // Synchronous version of GetResourceEntryByPathOnUIThread().
+  FileError GetResourceEntryByPath(const base::FilePath& file_path,
+                                   ResourceEntry* out_entry);
+
+  // Finds and reads a directory by |file_path|.
+  FileError ReadDirectoryByPath(const base::FilePath& file_path,
+                                ResourceEntryVector* out_entries);
+
+  // Finds and reads a directory by |id|.
+  FileError ReadDirectoryById(const std::string& id,
+                              ResourceEntryVector* out_entries);
+
+  // Replaces an existing entry with the same local ID as |entry|.
+  FileError RefreshEntry(const ResourceEntry& entry);
+
+  // Recursively gets directories under the entry pointed to by |id|.
+  FileError GetSubDirectoriesRecursively(
+      const std::string& id,
+      std::set<base::FilePath>* sub_directories);
+
+  // Returns the id of the resource named |base_name| directly under
+  // the directory with |parent_local_id|.
+  // If not found, empty string will be returned.
+  FileError GetChildId(const std::string& parent_local_id,
+                       const std::string& base_name,
+                       std::string* out_child_id);
+
+  // Returns an object to iterate over entries.
+  scoped_ptr<Iterator> GetIterator();
+
+  // Returns virtual file path of the entry.
+  FileError GetFilePath(const std::string& id, base::FilePath* out_file_path);
+
+  // Returns ID of the entry at the given path.
+  FileError GetIdByPath(const base::FilePath& file_path, std::string* out_id);
+
+  // Returns the local ID associated with the given resource ID.
+  FileError GetIdByResourceId(const std::string& resource_id,
+                              std::string* out_local_id);
+
+ private:
+  // Note: Use Destroy() to delete this object.
+  ~ResourceMetadata();
+
+  // Sets up entries which should be present by default.
+  FileError SetUpDefaultEntries();
+
+  // Used to implement Destroy().
+  void DestroyOnBlockingPool();
+
+  // Puts an entry under its parent directory. Removes the child from the old
+  // parent if there is. This method will also do name de-duplication to ensure
+  // that the exposed presentation path does not have naming conflicts. Two
+  // files with the same name "Foo" will be renamed to "Foo (1)" and "Foo (2)".
+  FileError PutEntryUnderDirectory(const ResourceEntry& entry);
+
+  // Returns an unused base name for |entry|.
+  FileError GetDeduplicatedBaseName(const ResourceEntry& entry,
+                                    std::string* base_name);
+
+  // Removes the entry and its descendants.
+  FileError RemoveEntryRecursively(const std::string& id);
+
+  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+
+  ResourceMetadataStorage* storage_;
+  FileCache* cache_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(ResourceMetadata);
+};
+
+}  // namespace internal
+}  // namespace drive
+
+#endif  // COMPONENTS_DRIVE_RESOURCE_METADATA_H_
diff --git a/components/drive/resource_metadata_storage.cc b/components/drive/resource_metadata_storage.cc
new file mode 100644
index 0000000..fbba7a9
--- /dev/null
+++ b/components/drive/resource_metadata_storage.cc
@@ -0,0 +1,1064 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/resource_metadata_storage.h"
+
+#include <map>
+#include <set>
+
+#include "base/bind.h"
+#include "base/containers/hash_tables.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_restrictions.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.h"
+#include "third_party/leveldatabase/env_chromium.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+// Enum to describe DB initialization status.
+enum DBInitStatus {
+  DB_INIT_SUCCESS,
+  DB_INIT_NOT_FOUND,
+  DB_INIT_CORRUPTION,
+  DB_INIT_IO_ERROR,
+  DB_INIT_FAILED,
+  DB_INIT_INCOMPATIBLE,
+  DB_INIT_BROKEN,
+  DB_INIT_OPENED_EXISTING_DB,
+  DB_INIT_CREATED_NEW_DB,
+  DB_INIT_REPLACED_EXISTING_DB_WITH_NEW_DB,
+  DB_INIT_MAX_VALUE,
+};
+
+// Enum to describe DB validity check failure reason.
+enum CheckValidityFailureReason {
+  CHECK_VALIDITY_FAILURE_INVALID_HEADER,
+  CHECK_VALIDITY_FAILURE_BROKEN_ID_ENTRY,
+  CHECK_VALIDITY_FAILURE_BROKEN_ENTRY,
+  CHECK_VALIDITY_FAILURE_INVALID_LOCAL_ID,
+  CHECK_VALIDITY_FAILURE_INVALID_PARENT_ID,
+  CHECK_VALIDITY_FAILURE_BROKEN_CHILD_MAP,
+  CHECK_VALIDITY_FAILURE_CHILD_ENTRY_COUNT_MISMATCH,
+  CHECK_VALIDITY_FAILURE_ITERATOR_ERROR,
+  CHECK_VALIDITY_FAILURE_MAX_VALUE,
+};
+
+// The name of the DB which stores the metadata.
+const base::FilePath::CharType kResourceMapDBName[] =
+    FILE_PATH_LITERAL("resource_metadata_resource_map.db");
+
+// The name of the DB which couldn't be opened, but is preserved just in case.
+const base::FilePath::CharType kPreservedResourceMapDBName[] =
+    FILE_PATH_LITERAL("resource_metadata_preserved_resource_map.db");
+
+// The name of the DB which couldn't be opened, and was replaced with a new one.
+const base::FilePath::CharType kTrashedResourceMapDBName[] =
+    FILE_PATH_LITERAL("resource_metadata_trashed_resource_map.db");
+
+// Meant to be a character which never happen to be in real IDs.
+const char kDBKeyDelimeter = '\0';
+
+// String used as a suffix of a key for a cache entry.
+const char kCacheEntryKeySuffix[] = "CACHE";
+
+// String used as a prefix of a key for a resource-ID-to-local-ID entry.
+const char kIdEntryKeyPrefix[] = "ID";
+
+// Returns a string to be used as the key for the header.
+std::string GetHeaderDBKey() {
+  std::string key;
+  key.push_back(kDBKeyDelimeter);
+  key.append("HEADER");
+  return key;
+}
+
+// Returns true if |key| is a key for a child entry.
+bool IsChildEntryKey(const leveldb::Slice& key) {
+  return !key.empty() && key[key.size() - 1] == kDBKeyDelimeter;
+}
+
+// Returns true if |key| is a key for a cache entry.
+bool IsCacheEntryKey(const leveldb::Slice& key) {
+  // A cache entry key should end with |kDBKeyDelimeter + kCacheEntryKeySuffix|.
+  const leveldb::Slice expected_suffix(kCacheEntryKeySuffix,
+                                       arraysize(kCacheEntryKeySuffix) - 1);
+  if (key.size() < 1 + expected_suffix.size() ||
+      key[key.size() - expected_suffix.size() - 1] != kDBKeyDelimeter)
+    return false;
+
+  const leveldb::Slice key_substring(
+      key.data() + key.size() - expected_suffix.size(), expected_suffix.size());
+  return key_substring.compare(expected_suffix) == 0;
+}
+
+// Returns ID extracted from a cache entry key.
+std::string GetIdFromCacheEntryKey(const leveldb::Slice& key) {
+  DCHECK(IsCacheEntryKey(key));
+  // Drop the suffix |kDBKeyDelimeter + kCacheEntryKeySuffix| from the key.
+  const size_t kSuffixLength = arraysize(kCacheEntryKeySuffix) - 1;
+  const int id_length = key.size() - 1 - kSuffixLength;
+  return std::string(key.data(), id_length);
+}
+
+// Returns a string to be used as a key for a resource-ID-to-local-ID entry.
+std::string GetIdEntryKey(const std::string& resource_id) {
+  std::string key;
+  key.push_back(kDBKeyDelimeter);
+  key.append(kIdEntryKeyPrefix);
+  key.push_back(kDBKeyDelimeter);
+  key.append(resource_id);
+  return key;
+}
+
+// Returns true if |key| is a key for a resource-ID-to-local-ID entry.
+bool IsIdEntryKey(const leveldb::Slice& key) {
+  // A resource-ID-to-local-ID entry key should start with
+  // |kDBKeyDelimeter + kIdEntryKeyPrefix + kDBKeyDelimeter|.
+  const leveldb::Slice expected_prefix(kIdEntryKeyPrefix,
+                                       arraysize(kIdEntryKeyPrefix) - 1);
+  if (key.size() < 2 + expected_prefix.size())
+    return false;
+  const leveldb::Slice key_substring(key.data() + 1, expected_prefix.size());
+  return key[0] == kDBKeyDelimeter &&
+      key_substring.compare(expected_prefix) == 0 &&
+      key[expected_prefix.size() + 1] == kDBKeyDelimeter;
+}
+
+// Returns the resource ID extracted from a resource-ID-to-local-ID entry key.
+std::string GetResourceIdFromIdEntryKey(const leveldb::Slice& key) {
+  DCHECK(IsIdEntryKey(key));
+  // Drop the prefix |kDBKeyDelimeter + kIdEntryKeyPrefix + kDBKeyDelimeter|
+  // from the key.
+  const size_t kPrefixLength = arraysize(kIdEntryKeyPrefix) - 1;
+  const int offset = kPrefixLength + 2;
+  return std::string(key.data() + offset, key.size() - offset);
+}
+
+// Converts leveldb::Status to DBInitStatus.
+DBInitStatus LevelDBStatusToDBInitStatus(const leveldb::Status& status) {
+  if (status.ok())
+    return DB_INIT_SUCCESS;
+  if (status.IsNotFound())
+    return DB_INIT_NOT_FOUND;
+  if (status.IsCorruption())
+    return DB_INIT_CORRUPTION;
+  if (status.IsIOError())
+    return DB_INIT_IO_ERROR;
+  return DB_INIT_FAILED;
+}
+
+// Converts leveldb::Status to FileError.
+FileError LevelDBStatusToFileError(const leveldb::Status& status) {
+  if (status.ok())
+    return FILE_ERROR_OK;
+  if (status.IsNotFound())
+    return FILE_ERROR_NOT_FOUND;
+  if (leveldb_env::IndicatesDiskFull(status))
+    return FILE_ERROR_NO_LOCAL_SPACE;
+  return FILE_ERROR_FAILED;
+}
+
+ResourceMetadataHeader GetDefaultHeaderEntry() {
+  ResourceMetadataHeader header;
+  header.set_version(ResourceMetadataStorage::kDBVersion);
+  return header;
+}
+
+bool MoveIfPossible(const base::FilePath& from, const base::FilePath& to) {
+  return !base::PathExists(from) || base::Move(from, to);
+}
+
+void RecordCheckValidityFailure(CheckValidityFailureReason reason) {
+  UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBValidityCheckFailureReason",
+                            reason,
+                            CHECK_VALIDITY_FAILURE_MAX_VALUE);
+}
+
+}  // namespace
+
+ResourceMetadataStorage::Iterator::Iterator(scoped_ptr<leveldb::Iterator> it)
+  : it_(it.Pass()) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(it_);
+
+  // Skip the header entry.
+  // Note: The header entry comes before all other entries because its key
+  // starts with kDBKeyDelimeter. (i.e. '\0')
+  it_->Seek(leveldb::Slice(GetHeaderDBKey()));
+
+  Advance();
+}
+
+ResourceMetadataStorage::Iterator::~Iterator() {
+  base::ThreadRestrictions::AssertIOAllowed();
+}
+
+bool ResourceMetadataStorage::Iterator::IsAtEnd() const {
+  base::ThreadRestrictions::AssertIOAllowed();
+  return !it_->Valid();
+}
+
+std::string ResourceMetadataStorage::Iterator::GetID() const {
+  return it_->key().ToString();
+}
+
+const ResourceEntry& ResourceMetadataStorage::Iterator::GetValue() const {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!IsAtEnd());
+  return entry_;
+}
+
+void ResourceMetadataStorage::Iterator::Advance() {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!IsAtEnd());
+
+  for (it_->Next() ; it_->Valid(); it_->Next()) {
+    if (!IsChildEntryKey(it_->key()) &&
+        !IsIdEntryKey(it_->key()) &&
+        entry_.ParseFromArray(it_->value().data(), it_->value().size())) {
+      break;
+    }
+  }
+}
+
+bool ResourceMetadataStorage::Iterator::HasError() const {
+  base::ThreadRestrictions::AssertIOAllowed();
+  return !it_->status().ok();
+}
+
+// static
+bool ResourceMetadataStorage::UpgradeOldDB(
+    const base::FilePath& directory_path) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  static_assert(
+      kDBVersion == 13,
+      "database version and this function must be updated at the same time");
+
+  const base::FilePath resource_map_path =
+      directory_path.Append(kResourceMapDBName);
+  const base::FilePath preserved_resource_map_path =
+      directory_path.Append(kPreservedResourceMapDBName);
+
+  if (base::PathExists(preserved_resource_map_path)) {
+    // Preserved DB is found. The previous attempt to create a new DB should not
+    // be successful. Discard the imperfect new DB and restore the old DB.
+    if (!base::DeleteFile(resource_map_path, false /* recursive */) ||
+        !base::Move(preserved_resource_map_path, resource_map_path))
+      return false;
+  }
+
+  if (!base::PathExists(resource_map_path))
+    return false;
+
+  // Open DB.
+  leveldb::DB* db = NULL;
+  leveldb::Options options;
+  options.max_open_files = 0;  // Use minimum.
+  options.create_if_missing = false;
+  options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
+  if (!leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db).ok())
+    return false;
+  scoped_ptr<leveldb::DB> resource_map(db);
+
+  // Check DB version.
+  std::string serialized_header;
+  ResourceMetadataHeader header;
+  if (!resource_map->Get(leveldb::ReadOptions(),
+                         leveldb::Slice(GetHeaderDBKey()),
+                         &serialized_header).ok() ||
+      !header.ParseFromString(serialized_header))
+    return false;
+  UMA_HISTOGRAM_SPARSE_SLOWLY("Drive.MetadataDBVersionBeforeUpgradeCheck",
+                              header.version());
+
+  if (header.version() == kDBVersion) {
+    // Before r272134, UpgradeOldDB() was not deleting unused ID entries.
+    // Delete unused ID entries to fix crbug.com/374648.
+    std::set<std::string> used_ids;
+
+    scoped_ptr<leveldb::Iterator> it(
+        resource_map->NewIterator(leveldb::ReadOptions()));
+    it->Seek(leveldb::Slice(GetHeaderDBKey()));
+    it->Next();
+    for (; it->Valid(); it->Next()) {
+      if (IsCacheEntryKey(it->key())) {
+        used_ids.insert(GetIdFromCacheEntryKey(it->key()));
+      } else if (!IsChildEntryKey(it->key()) && !IsIdEntryKey(it->key())) {
+        used_ids.insert(it->key().ToString());
+      }
+    }
+    if (!it->status().ok())
+      return false;
+
+    leveldb::WriteBatch batch;
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      if (IsIdEntryKey(it->key()) && !used_ids.count(it->value().ToString()))
+        batch.Delete(it->key());
+    }
+    if (!it->status().ok())
+      return false;
+
+    return resource_map->Write(leveldb::WriteOptions(), &batch).ok();
+  } else if (header.version() < 6) {  // Too old, nothing can be done.
+    return false;
+  } else if (header.version() < 11) {  // Cache entries can be reused.
+    leveldb::ReadOptions options;
+    options.verify_checksums = true;
+    scoped_ptr<leveldb::Iterator> it(resource_map->NewIterator(options));
+
+    leveldb::WriteBatch batch;
+    // First, remove all entries.
+    for (it->SeekToFirst(); it->Valid(); it->Next())
+      batch.Delete(it->key());
+
+    // Put ID entries and cache entries.
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      if (IsCacheEntryKey(it->key())) {
+        FileCacheEntry cache_entry;
+        if (!cache_entry.ParseFromArray(it->value().data(), it->value().size()))
+          return false;
+
+        // The resource ID might be in old WAPI format. We need to canonicalize
+        // to the format of API service currently in use.
+        const std::string& id = GetIdFromCacheEntryKey(it->key());
+        const std::string& id_new = util::CanonicalizeResourceId(id);
+
+        // Before v11, resource ID was directly used as local ID. Such entries
+        // can be migrated by adding an identity ID mapping.
+        batch.Put(GetIdEntryKey(id_new), id_new);
+
+        // Put cache state into a ResourceEntry.
+        ResourceEntry entry;
+        entry.set_local_id(id_new);
+        entry.set_resource_id(id_new);
+        *entry.mutable_file_specific_info()->mutable_cache_state() =
+            cache_entry;
+
+        std::string serialized_entry;
+        if (!entry.SerializeToString(&serialized_entry)) {
+          DLOG(ERROR) << "Failed to serialize the entry: " << id;
+          return false;
+        }
+        batch.Put(id_new, serialized_entry);
+      }
+    }
+    if (!it->status().ok())
+      return false;
+
+    // Put header with the latest version number.
+    std::string serialized_header;
+    if (!GetDefaultHeaderEntry().SerializeToString(&serialized_header))
+      return false;
+    batch.Put(GetHeaderDBKey(), serialized_header);
+
+    return resource_map->Write(leveldb::WriteOptions(), &batch).ok();
+  } else if (header.version() < 12) {  // Cache and ID map entries are reusable.
+    leveldb::ReadOptions options;
+    options.verify_checksums = true;
+    scoped_ptr<leveldb::Iterator> it(resource_map->NewIterator(options));
+
+    // First, get the set of local IDs associated with cache entries.
+    std::set<std::string> cached_entry_ids;
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      if (IsCacheEntryKey(it->key()))
+        cached_entry_ids.insert(GetIdFromCacheEntryKey(it->key()));
+    }
+    if (!it->status().ok())
+      return false;
+
+    // Remove all entries except used ID entries.
+    leveldb::WriteBatch batch;
+    std::map<std::string, std::string> local_id_to_resource_id;
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      const bool is_used_id = IsIdEntryKey(it->key()) &&
+          cached_entry_ids.count(it->value().ToString());
+      if (is_used_id) {
+        local_id_to_resource_id[it->value().ToString()] =
+            GetResourceIdFromIdEntryKey(it->key());
+      } else {
+        batch.Delete(it->key());
+      }
+    }
+    if (!it->status().ok())
+      return false;
+
+    // Put cache entries.
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      if (IsCacheEntryKey(it->key())) {
+        const std::string& id = GetIdFromCacheEntryKey(it->key());
+
+        std::map<std::string, std::string>::const_iterator iter_resource_id =
+            local_id_to_resource_id.find(id);
+        if (iter_resource_id == local_id_to_resource_id.end())
+          continue;
+
+        FileCacheEntry cache_entry;
+        if (!cache_entry.ParseFromArray(it->value().data(), it->value().size()))
+          return false;
+
+        // Put cache state into a ResourceEntry.
+        ResourceEntry entry;
+        entry.set_local_id(id);
+        entry.set_resource_id(iter_resource_id->second);
+        *entry.mutable_file_specific_info()->mutable_cache_state() =
+            cache_entry;
+
+        std::string serialized_entry;
+        if (!entry.SerializeToString(&serialized_entry)) {
+          DLOG(ERROR) << "Failed to serialize the entry: " << id;
+          return false;
+        }
+        batch.Put(id, serialized_entry);
+      }
+    }
+    if (!it->status().ok())
+      return false;
+
+    // Put header with the latest version number.
+    std::string serialized_header;
+    if (!GetDefaultHeaderEntry().SerializeToString(&serialized_header))
+      return false;
+    batch.Put(GetHeaderDBKey(), serialized_header);
+
+    return resource_map->Write(leveldb::WriteOptions(), &batch).ok();
+  } else if (header.version() < 13) {  // Reuse all entries.
+    leveldb::ReadOptions options;
+    options.verify_checksums = true;
+    scoped_ptr<leveldb::Iterator> it(resource_map->NewIterator(options));
+
+    // First, get local ID to resource ID map.
+    std::map<std::string, std::string> local_id_to_resource_id;
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      if (IsIdEntryKey(it->key())) {
+        local_id_to_resource_id[it->value().ToString()] =
+            GetResourceIdFromIdEntryKey(it->key());
+      }
+    }
+    if (!it->status().ok())
+      return false;
+
+    leveldb::WriteBatch batch;
+    // Merge cache entries to ResourceEntry.
+    for (it->SeekToFirst(); it->Valid(); it->Next()) {
+      if (IsCacheEntryKey(it->key())) {
+        const std::string& id = GetIdFromCacheEntryKey(it->key());
+
+        FileCacheEntry cache_entry;
+        if (!cache_entry.ParseFromArray(it->value().data(), it->value().size()))
+          return false;
+
+        std::string serialized_entry;
+        leveldb::Status status = resource_map->Get(options,
+                                                   leveldb::Slice(id),
+                                                   &serialized_entry);
+
+        std::map<std::string, std::string>::const_iterator iter_resource_id =
+            local_id_to_resource_id.find(id);
+
+        // No need to keep cache-only entries without resource ID.
+        if (status.IsNotFound() &&
+            iter_resource_id == local_id_to_resource_id.end())
+          continue;
+
+        ResourceEntry entry;
+        if (status.ok()) {
+          if (!entry.ParseFromString(serialized_entry))
+            return false;
+        } else if (status.IsNotFound()) {
+          entry.set_local_id(id);
+          entry.set_resource_id(iter_resource_id->second);
+        } else {
+          DLOG(ERROR) << "Failed to get the entry: " << id;
+          return false;
+        }
+        *entry.mutable_file_specific_info()->mutable_cache_state() =
+            cache_entry;
+
+        if (!entry.SerializeToString(&serialized_entry)) {
+          DLOG(ERROR) << "Failed to serialize the entry: " << id;
+          return false;
+        }
+        batch.Delete(it->key());
+        batch.Put(id, serialized_entry);
+      }
+    }
+    if (!it->status().ok())
+      return false;
+
+    // Put header with the latest version number.
+    header.set_version(ResourceMetadataStorage::kDBVersion);
+    std::string serialized_header;
+    if (!header.SerializeToString(&serialized_header))
+      return false;
+    batch.Put(GetHeaderDBKey(), serialized_header);
+
+    return resource_map->Write(leveldb::WriteOptions(), &batch).ok();
+  }
+
+  LOG(WARNING) << "Unexpected DB version: " << header.version();
+  return false;
+}
+
+ResourceMetadataStorage::ResourceMetadataStorage(
+    const base::FilePath& directory_path,
+    base::SequencedTaskRunner* blocking_task_runner)
+    : directory_path_(directory_path),
+      cache_file_scan_is_needed_(true),
+      blocking_task_runner_(blocking_task_runner) {
+}
+
+void ResourceMetadataStorage::Destroy() {
+  blocking_task_runner_->PostTask(
+      FROM_HERE,
+      base::Bind(&ResourceMetadataStorage::DestroyOnBlockingPool,
+                 base::Unretained(this)));
+}
+
+bool ResourceMetadataStorage::Initialize() {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  resource_map_.reset();
+
+  const base::FilePath resource_map_path =
+      directory_path_.Append(kResourceMapDBName);
+  const base::FilePath preserved_resource_map_path =
+      directory_path_.Append(kPreservedResourceMapDBName);
+  const base::FilePath trashed_resource_map_path =
+      directory_path_.Append(kTrashedResourceMapDBName);
+
+  // Discard unneeded DBs.
+  if (!base::DeleteFile(preserved_resource_map_path, true /* recursive */) ||
+      !base::DeleteFile(trashed_resource_map_path, true /* recursive */)) {
+    LOG(ERROR) << "Failed to remove unneeded DBs.";
+    return false;
+  }
+
+  // Try to open the existing DB.
+  leveldb::DB* db = NULL;
+  leveldb::Options options;
+  options.max_open_files = 0;  // Use minimum.
+  options.create_if_missing = false;
+  options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
+
+  DBInitStatus open_existing_result = DB_INIT_NOT_FOUND;
+  leveldb::Status status;
+  if (base::PathExists(resource_map_path)) {
+    status = leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db);
+    open_existing_result = LevelDBStatusToDBInitStatus(status);
+  }
+
+  if (open_existing_result == DB_INIT_SUCCESS) {
+    resource_map_.reset(db);
+
+    // Check the validity of existing DB.
+    int db_version = -1;
+    ResourceMetadataHeader header;
+    if (GetHeader(&header) == FILE_ERROR_OK)
+      db_version = header.version();
+
+    bool should_discard_db = true;
+    if (db_version != kDBVersion) {
+      open_existing_result = DB_INIT_INCOMPATIBLE;
+      DVLOG(1) << "Reject incompatible DB.";
+    } else if (!CheckValidity()) {
+      open_existing_result = DB_INIT_BROKEN;
+      LOG(ERROR) << "Reject invalid DB.";
+    } else {
+      should_discard_db = false;
+    }
+
+    if (should_discard_db)
+      resource_map_.reset();
+    else
+      cache_file_scan_is_needed_ = false;
+  }
+
+  UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBOpenExistingResult",
+                            open_existing_result,
+                            DB_INIT_MAX_VALUE);
+
+  DBInitStatus init_result = DB_INIT_OPENED_EXISTING_DB;
+
+  // Failed to open the existing DB, create new DB.
+  if (!resource_map_) {
+    // Move the existing DB to the preservation path. The moved old DB is
+    // deleted once the new DB creation succeeds, or is restored later in
+    // UpgradeOldDB() when the creation fails.
+    MoveIfPossible(resource_map_path, preserved_resource_map_path);
+
+    // Create DB.
+    options.max_open_files = 0;  // Use minimum.
+    options.create_if_missing = true;
+    options.error_if_exists = true;
+    options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
+
+    status = leveldb::DB::Open(options, resource_map_path.AsUTF8Unsafe(), &db);
+    if (status.ok()) {
+      resource_map_.reset(db);
+
+      // Set up header and trash the old DB.
+      if (PutHeader(GetDefaultHeaderEntry()) == FILE_ERROR_OK &&
+          MoveIfPossible(preserved_resource_map_path,
+                         trashed_resource_map_path)) {
+        init_result = open_existing_result == DB_INIT_NOT_FOUND ?
+            DB_INIT_CREATED_NEW_DB : DB_INIT_REPLACED_EXISTING_DB_WITH_NEW_DB;
+      } else {
+        init_result = DB_INIT_FAILED;
+        resource_map_.reset();
+      }
+    } else {
+      LOG(ERROR) << "Failed to create resource map DB: " << status.ToString();
+      init_result = LevelDBStatusToDBInitStatus(status);
+    }
+  }
+
+  UMA_HISTOGRAM_ENUMERATION("Drive.MetadataDBInitResult",
+                            init_result,
+                            DB_INIT_MAX_VALUE);
+  return resource_map_;
+}
+
+void ResourceMetadataStorage::RecoverCacheInfoFromTrashedResourceMap(
+    RecoveredCacheInfoMap* out_info) {
+  const base::FilePath trashed_resource_map_path =
+      directory_path_.Append(kTrashedResourceMapDBName);
+
+  if (!base::PathExists(trashed_resource_map_path))
+    return;
+
+  leveldb::Options options;
+  options.max_open_files = 0;  // Use minimum.
+  options.create_if_missing = false;
+  options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
+
+  // Trashed DB may be broken, repair it first.
+  leveldb::Status status;
+  status = leveldb::RepairDB(trashed_resource_map_path.AsUTF8Unsafe(), options);
+  if (!status.ok()) {
+    LOG(ERROR) << "Failed to repair trashed DB: " << status.ToString();
+    return;
+  }
+
+  // Open it.
+  leveldb::DB* db = NULL;
+  status = leveldb::DB::Open(options, trashed_resource_map_path.AsUTF8Unsafe(),
+                             &db);
+  if (!status.ok()) {
+    LOG(ERROR) << "Failed to open trashed DB: " << status.ToString();
+    return;
+  }
+  scoped_ptr<leveldb::DB> resource_map(db);
+
+  // Check DB version.
+  std::string serialized_header;
+  ResourceMetadataHeader header;
+  if (!resource_map->Get(leveldb::ReadOptions(),
+                         leveldb::Slice(GetHeaderDBKey()),
+                         &serialized_header).ok() ||
+      !header.ParseFromString(serialized_header) ||
+      header.version() != kDBVersion) {
+    LOG(ERROR) << "Incompatible DB version: " << header.version();
+    return;
+  }
+
+  // Collect cache entries.
+  scoped_ptr<leveldb::Iterator> it(
+      resource_map->NewIterator(leveldb::ReadOptions()));
+  for (it->SeekToFirst(); it->Valid(); it->Next()) {
+    if (!IsChildEntryKey(it->key()) &&
+        !IsIdEntryKey(it->key())) {
+      const std::string id = it->key().ToString();
+      ResourceEntry entry;
+      if (entry.ParseFromArray(it->value().data(), it->value().size()) &&
+          entry.file_specific_info().has_cache_state()) {
+        RecoveredCacheInfo* info = &(*out_info)[id];
+        info->is_dirty = entry.file_specific_info().cache_state().is_dirty();
+        info->md5 = entry.file_specific_info().cache_state().md5();
+        info->title = entry.title();
+      }
+    }
+  }
+}
+
+FileError ResourceMetadataStorage::SetLargestChangestamp(
+    int64 largest_changestamp) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  ResourceMetadataHeader header;
+  FileError error = GetHeader(&header);
+  if (error != FILE_ERROR_OK) {
+    DLOG(ERROR) << "Failed to get the header.";
+    return error;
+  }
+  header.set_largest_changestamp(largest_changestamp);
+  return PutHeader(header);
+}
+
+FileError ResourceMetadataStorage::GetLargestChangestamp(
+    int64* largest_changestamp) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  ResourceMetadataHeader header;
+  FileError error = GetHeader(&header);
+  if (error != FILE_ERROR_OK) {
+    DLOG(ERROR) << "Failed to get the header.";
+    return error;
+  }
+  *largest_changestamp = header.largest_changestamp();
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadataStorage::PutEntry(const ResourceEntry& entry) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  const std::string& id = entry.local_id();
+  DCHECK(!id.empty());
+
+  // Try to get existing entry.
+  std::string serialized_entry;
+  leveldb::Status status = resource_map_->Get(leveldb::ReadOptions(),
+                                              leveldb::Slice(id),
+                                              &serialized_entry);
+  if (!status.ok() && !status.IsNotFound())  // Unexpected errors.
+    return LevelDBStatusToFileError(status);
+
+  ResourceEntry old_entry;
+  if (status.ok() && !old_entry.ParseFromString(serialized_entry))
+    return FILE_ERROR_FAILED;
+
+  // Construct write batch.
+  leveldb::WriteBatch batch;
+
+  // Remove from the old parent.
+  if (!old_entry.parent_local_id().empty()) {
+    batch.Delete(GetChildEntryKey(old_entry.parent_local_id(),
+                                  old_entry.base_name()));
+  }
+  // Add to the new parent.
+  if (!entry.parent_local_id().empty())
+    batch.Put(GetChildEntryKey(entry.parent_local_id(), entry.base_name()), id);
+
+  // Refresh resource-ID-to-local-ID mapping entry.
+  if (old_entry.resource_id() != entry.resource_id()) {
+    // Resource ID should not change.
+    DCHECK(old_entry.resource_id().empty() || entry.resource_id().empty());
+
+    if (!old_entry.resource_id().empty())
+      batch.Delete(GetIdEntryKey(old_entry.resource_id()));
+    if (!entry.resource_id().empty())
+      batch.Put(GetIdEntryKey(entry.resource_id()), id);
+  }
+
+  // Put the entry itself.
+  if (!entry.SerializeToString(&serialized_entry)) {
+    DLOG(ERROR) << "Failed to serialize the entry: " << id;
+    return FILE_ERROR_FAILED;
+  }
+  batch.Put(id, serialized_entry);
+
+  status = resource_map_->Write(leveldb::WriteOptions(), &batch);
+  return LevelDBStatusToFileError(status);
+}
+
+FileError ResourceMetadataStorage::GetEntry(const std::string& id,
+                                            ResourceEntry* out_entry) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!id.empty());
+
+  std::string serialized_entry;
+  const leveldb::Status status = resource_map_->Get(leveldb::ReadOptions(),
+                                                    leveldb::Slice(id),
+                                                    &serialized_entry);
+  if (!status.ok())
+    return LevelDBStatusToFileError(status);
+  if (!out_entry->ParseFromString(serialized_entry))
+    return FILE_ERROR_FAILED;
+  return FILE_ERROR_OK;
+}
+
+FileError ResourceMetadataStorage::RemoveEntry(const std::string& id) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!id.empty());
+
+  ResourceEntry entry;
+  FileError error = GetEntry(id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+
+  leveldb::WriteBatch batch;
+
+  // Remove from the parent.
+  if (!entry.parent_local_id().empty())
+    batch.Delete(GetChildEntryKey(entry.parent_local_id(), entry.base_name()));
+
+  // Remove resource ID-local ID mapping entry.
+  if (!entry.resource_id().empty())
+    batch.Delete(GetIdEntryKey(entry.resource_id()));
+
+  // Remove the entry itself.
+  batch.Delete(id);
+
+  const leveldb::Status status = resource_map_->Write(leveldb::WriteOptions(),
+                                                      &batch);
+  return LevelDBStatusToFileError(status);
+}
+
+scoped_ptr<ResourceMetadataStorage::Iterator>
+ResourceMetadataStorage::GetIterator() {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  scoped_ptr<leveldb::Iterator> it(
+      resource_map_->NewIterator(leveldb::ReadOptions()));
+  return make_scoped_ptr(new Iterator(it.Pass()));
+}
+
+FileError ResourceMetadataStorage::GetChild(const std::string& parent_id,
+                                            const std::string& child_name,
+                                            std::string* child_id) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!parent_id.empty());
+  DCHECK(!child_name.empty());
+
+  const leveldb::Status status =
+      resource_map_->Get(
+          leveldb::ReadOptions(),
+          leveldb::Slice(GetChildEntryKey(parent_id, child_name)),
+          child_id);
+  return LevelDBStatusToFileError(status);
+}
+
+FileError ResourceMetadataStorage::GetChildren(
+    const std::string& parent_id,
+    std::vector<std::string>* children) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!parent_id.empty());
+
+  // Iterate over all entries with keys starting with |parent_id|.
+  scoped_ptr<leveldb::Iterator> it(
+      resource_map_->NewIterator(leveldb::ReadOptions()));
+  for (it->Seek(parent_id);
+       it->Valid() && it->key().starts_with(leveldb::Slice(parent_id));
+       it->Next()) {
+    if (IsChildEntryKey(it->key()))
+      children->push_back(it->value().ToString());
+  }
+  return LevelDBStatusToFileError(it->status());
+}
+
+ResourceMetadataStorage::RecoveredCacheInfo::RecoveredCacheInfo()
+    : is_dirty(false) {}
+
+ResourceMetadataStorage::RecoveredCacheInfo::~RecoveredCacheInfo() {}
+
+FileError ResourceMetadataStorage::GetIdByResourceId(
+    const std::string& resource_id,
+    std::string* out_id) {
+  base::ThreadRestrictions::AssertIOAllowed();
+  DCHECK(!resource_id.empty());
+
+  const leveldb::Status status = resource_map_->Get(
+      leveldb::ReadOptions(),
+      leveldb::Slice(GetIdEntryKey(resource_id)),
+      out_id);
+  return LevelDBStatusToFileError(status);
+}
+
+ResourceMetadataStorage::~ResourceMetadataStorage() {
+  base::ThreadRestrictions::AssertIOAllowed();
+}
+
+void ResourceMetadataStorage::DestroyOnBlockingPool() {
+  delete this;
+}
+
+// static
+std::string ResourceMetadataStorage::GetChildEntryKey(
+    const std::string& parent_id,
+    const std::string& child_name) {
+  DCHECK(!parent_id.empty());
+  DCHECK(!child_name.empty());
+
+  std::string key = parent_id;
+  key.push_back(kDBKeyDelimeter);
+  key.append(child_name);
+  key.push_back(kDBKeyDelimeter);
+  return key;
+}
+
+FileError ResourceMetadataStorage::PutHeader(
+    const ResourceMetadataHeader& header) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  std::string serialized_header;
+  if (!header.SerializeToString(&serialized_header)) {
+    DLOG(ERROR) << "Failed to serialize the header";
+    return FILE_ERROR_FAILED;
+  }
+
+  const leveldb::Status status = resource_map_->Put(
+      leveldb::WriteOptions(),
+      leveldb::Slice(GetHeaderDBKey()),
+      leveldb::Slice(serialized_header));
+  return LevelDBStatusToFileError(status);
+}
+
+FileError ResourceMetadataStorage::GetHeader(ResourceMetadataHeader* header) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  std::string serialized_header;
+  const leveldb::Status status = resource_map_->Get(
+      leveldb::ReadOptions(),
+      leveldb::Slice(GetHeaderDBKey()),
+      &serialized_header);
+  if (!status.ok())
+    return LevelDBStatusToFileError(status);
+  return header->ParseFromString(serialized_header) ?
+      FILE_ERROR_OK : FILE_ERROR_FAILED;
+}
+
+bool ResourceMetadataStorage::CheckValidity() {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  // Perform read with checksums verification enabled.
+  leveldb::ReadOptions options;
+  options.verify_checksums = true;
+
+  scoped_ptr<leveldb::Iterator> it(resource_map_->NewIterator(options));
+  it->SeekToFirst();
+
+  // DB is organized like this:
+  //
+  // <key>                          : <value>
+  // "\0HEADER"                     : ResourceMetadataHeader
+  // "\0ID\0|resource ID 1|"        : Local ID associated to resource ID 1.
+  // "\0ID\0|resource ID 2|"        : Local ID associated to resource ID 2.
+  // ...
+  // "|ID of A|"                    : ResourceEntry for entry A.
+  // "|ID of A|\0|child name 1|\0"  : ID of the 1st child entry of entry A.
+  // "|ID of A|\0|child name 2|\0"  : ID of the 2nd child entry of entry A.
+  // ...
+  // "|ID of A|\0|child name n|\0"  : ID of the nth child entry of entry A.
+  // "|ID of B|"                    : ResourceEntry for entry B.
+  // ...
+
+  // Check the header.
+  ResourceMetadataHeader header;
+  if (!it->Valid() ||
+      it->key() != GetHeaderDBKey() ||  // Header entry must come first.
+      !header.ParseFromArray(it->value().data(), it->value().size()) ||
+      header.version() != kDBVersion) {
+    DLOG(ERROR) << "Invalid header detected. version = " << header.version();
+    RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_INVALID_HEADER);
+    return false;
+  }
+
+  // First scan. Remember relationships between IDs.
+  typedef base::hash_map<std::string, std::string> KeyToIdMapping;
+  KeyToIdMapping local_id_to_resource_id_map;
+  KeyToIdMapping child_key_to_local_id_map;
+  std::set<std::string> resource_entries;
+  std::string first_resource_entry_key;
+  for (it->Next(); it->Valid(); it->Next()) {
+    if (IsChildEntryKey(it->key())) {
+      child_key_to_local_id_map[it->key().ToString()] = it->value().ToString();
+      continue;
+    }
+
+    if (IsIdEntryKey(it->key())) {
+      const auto result = local_id_to_resource_id_map.insert(std::make_pair(
+          it->value().ToString(),
+          GetResourceIdFromIdEntryKey(it->key().ToString())));
+      // Check that no local ID is associated with more than one resource ID.
+      if (!result.second) {
+        DLOG(ERROR) << "Broken ID entry.";
+        RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_ID_ENTRY);
+        return false;
+      }
+      continue;
+    }
+
+    // Remember the key of the first resource entry record, so the second scan
+    // can start from this point.
+    if (first_resource_entry_key.empty())
+      first_resource_entry_key = it->key().ToString();
+
+    resource_entries.insert(it->key().ToString());
+  }
+
+  // Second scan. Verify relationships and resource entry correctness.
+  size_t num_entries_with_parent = 0;
+  ResourceEntry entry;
+  for (it->Seek(first_resource_entry_key); it->Valid(); it->Next()) {
+    if (IsChildEntryKey(it->key()))
+      continue;
+
+    if (!entry.ParseFromArray(it->value().data(), it->value().size())) {
+      DLOG(ERROR) << "Broken entry detected.";
+      RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_ENTRY);
+      return false;
+    }
+
+    // Resource-ID-to-local-ID mapping without entry for the local ID is OK,
+    // but if it exists, then the resource ID must be consistent.
+    const auto mapping_it =
+        local_id_to_resource_id_map.find(it->key().ToString());
+    if (mapping_it != local_id_to_resource_id_map.end() &&
+        entry.resource_id() != mapping_it->second) {
+      DLOG(ERROR) << "Broken ID entry.";
+      RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_ID_ENTRY);
+      return false;
+    }
+
+    // If the parent is referenced, then confirm that it exists and check the
+    // parent-child relationships.
+    if (!entry.parent_local_id().empty()) {
+      const auto mapping_it = resource_entries.find(entry.parent_local_id());
+      if (mapping_it == resource_entries.end()) {
+        DLOG(ERROR) << "Parent entry not found.";
+        RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_INVALID_PARENT_ID);
+        return false;
+      }
+
+      // Check if parent-child relationship is stored correctly.
+      const auto child_mapping_it = child_key_to_local_id_map.find(
+          GetChildEntryKey(entry.parent_local_id(), entry.base_name()));
+      if (child_mapping_it == child_key_to_local_id_map.end() ||
+          leveldb::Slice(child_mapping_it->second) != it->key()) {
+        DLOG(ERROR) << "Child map is broken.";
+        RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_BROKEN_CHILD_MAP);
+        return false;
+      }
+      ++num_entries_with_parent;
+    }
+  }
+
+  if (!it->status().ok()) {
+    DLOG(ERROR) << "Error during checking resource map. status = "
+                << it->status().ToString();
+    RecordCheckValidityFailure(CHECK_VALIDITY_FAILURE_ITERATOR_ERROR);
+    return false;
+  }
+
+  if (child_key_to_local_id_map.size() != num_entries_with_parent) {
+    DLOG(ERROR) << "Child entry count mismatch.";
+    RecordCheckValidityFailure(
+        CHECK_VALIDITY_FAILURE_CHILD_ENTRY_COUNT_MISMATCH);
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace internal
+}  // namespace drive
diff --git a/components/drive/resource_metadata_storage.h b/components/drive/resource_metadata_storage.h
new file mode 100644
index 0000000..8f8c695c
--- /dev/null
+++ b/components/drive/resource_metadata_storage.h
@@ -0,0 +1,172 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_RESOURCE_METADATA_STORAGE_H_
+#define COMPONENTS_DRIVE_RESOURCE_METADATA_STORAGE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_errors.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace leveldb {
+class DB;
+class Iterator;
+}
+
+namespace drive {
+
+class ResourceEntry;
+class ResourceMetadataHeader;
+
+namespace internal {
+
+// Storage for ResourceMetadata which is responsible to manage resource
+// entries and child-parent relationships between entries.
+class ResourceMetadataStorage {
+ public:
+  // This should be incremented when incompatibility change is made to DB
+  // format.
+  static const int kDBVersion = 13;
+
+  // Object to iterate over entries stored in this storage.
+  class Iterator {
+   public:
+    explicit Iterator(scoped_ptr<leveldb::Iterator> it);
+    ~Iterator();
+
+    // Returns true if this iterator cannot advance any more and does not point
+    // to a valid entry. Get() and Advance() should not be called in such cases.
+    bool IsAtEnd() const;
+
+    // Returns the ID of the entry currently pointed by this object.
+    std::string GetID() const;
+
+    // Returns the entry currently pointed by this object.
+    const ResourceEntry& GetValue() const;
+
+    // Advances to the next entry.
+    void Advance();
+
+    // Returns true if this object has encountered any error.
+    bool HasError() const;
+
+   private:
+    ResourceEntry entry_;
+    scoped_ptr<leveldb::Iterator> it_;
+
+    DISALLOW_COPY_AND_ASSIGN(Iterator);
+  };
+
+  // Cache information recovered from trashed DB.
+  struct RecoveredCacheInfo {
+    RecoveredCacheInfo();
+    ~RecoveredCacheInfo();
+
+    bool is_dirty;
+    std::string md5;
+    std::string title;
+  };
+  typedef std::map<std::string, RecoveredCacheInfo> RecoveredCacheInfoMap;
+
+  // Returns true if the DB was successfully upgraded to the newest version.
+  static bool UpgradeOldDB(const base::FilePath& directory_path);
+
+  ResourceMetadataStorage(const base::FilePath& directory_path,
+                          base::SequencedTaskRunner* blocking_task_runner);
+
+  const base::FilePath& directory_path() const { return directory_path_; }
+
+  // Returns true when cache entries were not loaded to the DB during
+  // initialization.
+  bool cache_file_scan_is_needed() const { return cache_file_scan_is_needed_; }
+
+  // Destroys this object.
+  void Destroy();
+
+  // Initializes this object.
+  bool Initialize();
+
+  // Collects cache info from trashed resource map DB.
+  void RecoverCacheInfoFromTrashedResourceMap(RecoveredCacheInfoMap* out_info);
+
+  // Sets the largest changestamp.
+  FileError SetLargestChangestamp(int64 largest_changestamp);
+
+  // Gets the largest changestamp.
+  FileError GetLargestChangestamp(int64* largest_changestamp);
+
+  // Puts the entry to this storage.
+  FileError PutEntry(const ResourceEntry& entry);
+
+  // Gets an entry stored in this storage.
+  FileError GetEntry(const std::string& id, ResourceEntry* out_entry);
+
+  // Removes an entry from this storage.
+  FileError RemoveEntry(const std::string& id);
+
+  // Returns an object to iterate over entries stored in this storage.
+  scoped_ptr<Iterator> GetIterator();
+
+  // Returns the ID of the parent's child.
+  FileError GetChild(const std::string& parent_id,
+                     const std::string& child_name,
+                     std::string* child_id);
+
+  // Returns the IDs of the parent's children.
+  FileError GetChildren(const std::string& parent_id,
+                        std::vector<std::string>* children);
+
+  // Returns the local ID associated with the given resource ID.
+  FileError GetIdByResourceId(const std::string& resource_id,
+                              std::string* out_id);
+
+ private:
+  friend class ResourceMetadataStorageTest;
+
+  // To destruct this object, use Destroy().
+  ~ResourceMetadataStorage();
+
+  // Used to implement Destroy().
+  void DestroyOnBlockingPool();
+
+  // Returns a string to be used as a key for child entry.
+  static std::string GetChildEntryKey(const std::string& parent_id,
+                                      const std::string& child_name);
+
+  // Puts header.
+  FileError PutHeader(const ResourceMetadataHeader& header);
+
+  // Gets header.
+  FileError GetHeader(ResourceMetadataHeader* out_header);
+
+  // Checks validity of the data.
+  bool CheckValidity();
+
+  // Path to the directory where the data is stored.
+  base::FilePath directory_path_;
+
+  bool cache_file_scan_is_needed_;
+
+  // Entries stored in this storage.
+  scoped_ptr<leveldb::DB> resource_map_;
+
+  scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(ResourceMetadataStorage);
+};
+
+}  // namespace internal
+}  // namespace drive
+
+#endif  // COMPONENTS_DRIVE_RESOURCE_METADATA_STORAGE_H_
diff --git a/components/drive/resource_metadata_storage_unittest.cc b/components/drive/resource_metadata_storage_unittest.cc
new file mode 100644
index 0000000..5596938
--- /dev/null
+++ b/components/drive/resource_metadata_storage_unittest.cc
@@ -0,0 +1,633 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/resource_metadata_storage.h"
+
+#include <algorithm>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_split.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_test_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace drive {
+namespace internal {
+
+class ResourceMetadataStorageTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    storage_.reset(new ResourceMetadataStorage(
+        temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+    ASSERT_TRUE(storage_->Initialize());
+  }
+
+  // Overwrites |storage_|'s version.
+  void SetDBVersion(int version) {
+    ResourceMetadataHeader header;
+    ASSERT_EQ(FILE_ERROR_OK, storage_->GetHeader(&header));
+    header.set_version(version);
+    EXPECT_EQ(FILE_ERROR_OK, storage_->PutHeader(header));
+  }
+
+  bool CheckValidity() {
+    return storage_->CheckValidity();
+  }
+
+  leveldb::DB* resource_map() { return storage_->resource_map_.get(); }
+
+  // Puts a child entry.
+  void PutChild(const std::string& parent_id,
+                const std::string& child_base_name,
+                const std::string& child_id) {
+    storage_->resource_map_->Put(
+        leveldb::WriteOptions(),
+        ResourceMetadataStorage::GetChildEntryKey(parent_id, child_base_name),
+        child_id);
+  }
+
+  // Removes a child entry.
+  void RemoveChild(const std::string& parent_id,
+                   const std::string& child_base_name) {
+    storage_->resource_map_->Delete(
+        leveldb::WriteOptions(),
+        ResourceMetadataStorage::GetChildEntryKey(parent_id, child_base_name));
+  }
+
+  content::TestBrowserThreadBundle thread_bundle_;
+  base::ScopedTempDir temp_dir_;
+  scoped_ptr<ResourceMetadataStorage,
+             test_util::DestroyHelperForTests> storage_;
+};
+
+TEST_F(ResourceMetadataStorageTest, LargestChangestamp) {
+  const int64 kLargestChangestamp = 1234567890;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->SetLargestChangestamp(kLargestChangestamp));
+  int64 value = 0;
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetLargestChangestamp(&value));
+  EXPECT_EQ(kLargestChangestamp, value);
+}
+
+TEST_F(ResourceMetadataStorageTest, PutEntry) {
+  const std::string key1 = "abcdefg";
+  const std::string key2 = "abcd";
+  const std::string key3 = "efgh";
+  const std::string name2 = "ABCD";
+  const std::string name3 = "EFGH";
+
+  // key1 not found.
+  ResourceEntry result;
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetEntry(key1, &result));
+
+  // Put entry1.
+  ResourceEntry entry1;
+  entry1.set_local_id(key1);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry1));
+
+  // key1 found.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(key1, &result));
+
+  // key2 not found.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetEntry(key2, &result));
+
+  // Put entry2 as a child of entry1.
+  ResourceEntry entry2;
+  entry2.set_local_id(key2);
+  entry2.set_parent_local_id(key1);
+  entry2.set_base_name(name2);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry2));
+
+  // key2 found.
+  std::string child_id;
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(key2, &result));
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetChild(key1, name2, &child_id));
+  EXPECT_EQ(key2, child_id);
+
+  // Put entry3 as a child of entry2.
+  ResourceEntry entry3;
+  entry3.set_local_id(key3);
+  entry3.set_parent_local_id(key2);
+  entry3.set_base_name(name3);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry3));
+
+  // key3 found.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(key3, &result));
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetChild(key2, name3, &child_id));
+  EXPECT_EQ(key3, child_id);
+
+  // Change entry3's parent to entry1.
+  entry3.set_parent_local_id(key1);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry3));
+
+  // entry3 is a child of entry1 now.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetChild(key2, name3, &child_id));
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetChild(key1, name3, &child_id));
+  EXPECT_EQ(key3, child_id);
+
+  // Remove entries.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(key3));
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetEntry(key3, &result));
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(key2));
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetEntry(key2, &result));
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(key1));
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetEntry(key1, &result));
+}
+
+TEST_F(ResourceMetadataStorageTest, Iterator) {
+  // Prepare data.
+  std::vector<std::string> keys;
+
+  keys.push_back("entry1");
+  keys.push_back("entry2");
+  keys.push_back("entry3");
+  keys.push_back("entry4");
+
+  for (size_t i = 0; i < keys.size(); ++i) {
+    ResourceEntry entry;
+    entry.set_local_id(keys[i]);
+    EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+  }
+
+  // Iterate and check the result.
+  std::map<std::string, ResourceEntry> found_entries;
+  scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator();
+  ASSERT_TRUE(it);
+  for (; !it->IsAtEnd(); it->Advance()) {
+    const ResourceEntry& entry = it->GetValue();
+    found_entries[it->GetID()] = entry;
+  }
+  EXPECT_FALSE(it->HasError());
+
+  EXPECT_EQ(keys.size(), found_entries.size());
+  for (size_t i = 0; i < keys.size(); ++i)
+    EXPECT_EQ(1U, found_entries.count(keys[i]));
+}
+
+TEST_F(ResourceMetadataStorageTest, GetIdByResourceId) {
+  const std::string local_id = "local_id";
+  const std::string resource_id = "resource_id";
+
+  // Resource ID to local ID mapping is not stored yet.
+  std::string id;
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+            storage_->GetIdByResourceId(resource_id, &id));
+
+  // Put an entry with the resource ID.
+  ResourceEntry entry;
+  entry.set_local_id(local_id);
+  entry.set_resource_id(resource_id);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+
+  // Can get local ID by resource ID.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetIdByResourceId(resource_id, &id));
+  EXPECT_EQ(local_id, id);
+
+  // Resource ID to local ID mapping is removed.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(local_id));
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+            storage_->GetIdByResourceId(resource_id, &id));
+}
+
+TEST_F(ResourceMetadataStorageTest, GetChildren) {
+  const std::string parents_id[] = { "mercury", "venus", "mars", "jupiter",
+                                     "saturn" };
+  std::vector<base::StringPairs> children_name_id(arraysize(parents_id));
+  // Skip children_name_id[0/1] here because Mercury and Venus have no moon.
+  children_name_id[2].push_back(std::make_pair("phobos", "mars_i"));
+  children_name_id[2].push_back(std::make_pair("deimos", "mars_ii"));
+  children_name_id[3].push_back(std::make_pair("io", "jupiter_i"));
+  children_name_id[3].push_back(std::make_pair("europa", "jupiter_ii"));
+  children_name_id[3].push_back(std::make_pair("ganymede", "jupiter_iii"));
+  children_name_id[3].push_back(std::make_pair("calisto", "jupiter_iv"));
+  children_name_id[4].push_back(std::make_pair("mimas", "saturn_i"));
+  children_name_id[4].push_back(std::make_pair("enceladus", "saturn_ii"));
+  children_name_id[4].push_back(std::make_pair("tethys", "saturn_iii"));
+  children_name_id[4].push_back(std::make_pair("dione", "saturn_iv"));
+  children_name_id[4].push_back(std::make_pair("rhea", "saturn_v"));
+  children_name_id[4].push_back(std::make_pair("titan", "saturn_vi"));
+  children_name_id[4].push_back(std::make_pair("iapetus", "saturn_vii"));
+
+  // Put parents.
+  for (size_t i = 0; i < arraysize(parents_id); ++i) {
+    ResourceEntry entry;
+    entry.set_local_id(parents_id[i]);
+    EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+  }
+
+  // Put children.
+  for (size_t i = 0; i < children_name_id.size(); ++i) {
+    for (size_t j = 0; j < children_name_id[i].size(); ++j) {
+      ResourceEntry entry;
+      entry.set_local_id(children_name_id[i][j].second);
+      entry.set_parent_local_id(parents_id[i]);
+      entry.set_base_name(children_name_id[i][j].first);
+      EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+    }
+  }
+
+  // Try to get children.
+  for (size_t i = 0; i < children_name_id.size(); ++i) {
+    std::vector<std::string> children;
+    storage_->GetChildren(parents_id[i], &children);
+    EXPECT_EQ(children_name_id[i].size(), children.size());
+    for (size_t j = 0; j < children_name_id[i].size(); ++j) {
+      EXPECT_EQ(1, std::count(children.begin(),
+                              children.end(),
+                              children_name_id[i][j].second));
+    }
+  }
+}
+
+TEST_F(ResourceMetadataStorageTest, OpenExistingDB) {
+  const std::string parent_id1 = "abcdefg";
+  const std::string child_name1 = "WXYZABC";
+  const std::string child_id1 = "qwerty";
+
+  ResourceEntry entry1;
+  entry1.set_local_id(parent_id1);
+  ResourceEntry entry2;
+  entry2.set_local_id(child_id1);
+  entry2.set_parent_local_id(parent_id1);
+  entry2.set_base_name(child_name1);
+
+  // Put some data.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry1));
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry2));
+
+  // Close DB and reopen.
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // Can read data.
+  ResourceEntry result;
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(parent_id1, &result));
+
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(child_id1, &result));
+  EXPECT_EQ(parent_id1, result.parent_local_id());
+  EXPECT_EQ(child_name1, result.base_name());
+
+  std::string child_id;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->GetChild(parent_id1, child_name1, &child_id));
+  EXPECT_EQ(child_id1, child_id);
+}
+
+TEST_F(ResourceMetadataStorageTest, IncompatibleDB_M29) {
+  const int64 kLargestChangestamp = 1234567890;
+  const std::string title = "title";
+
+  // Construct M29 version DB.
+  SetDBVersion(6);
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->SetLargestChangestamp(kLargestChangestamp));
+
+  leveldb::WriteBatch batch;
+
+  // Put a file entry and its cache entry.
+  ResourceEntry entry;
+  std::string serialized_entry;
+  entry.set_title(title);
+  entry.set_resource_id("file:abcd");
+  EXPECT_TRUE(entry.SerializeToString(&serialized_entry));
+  batch.Put("file:abcd", serialized_entry);
+
+  FileCacheEntry cache_entry;
+  EXPECT_TRUE(cache_entry.SerializeToString(&serialized_entry));
+  batch.Put(std::string("file:abcd") + '\0' + "CACHE", serialized_entry);
+
+  EXPECT_TRUE(resource_map()->Write(leveldb::WriteOptions(), &batch).ok());
+
+  // Upgrade and reopen.
+  storage_.reset();
+  EXPECT_TRUE(ResourceMetadataStorage::UpgradeOldDB(temp_dir_.path()));
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // Resource-ID-to-local-ID mapping is added.
+  std::string id;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->GetIdByResourceId("abcd", &id));  // "file:" is dropped.
+
+  // Data is erased, except cache entries.
+  int64 largest_changestamp = 0;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->GetLargestChangestamp(&largest_changestamp));
+  EXPECT_EQ(0, largest_changestamp);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.title().empty());
+  EXPECT_TRUE(entry.file_specific_info().has_cache_state());
+}
+
+TEST_F(ResourceMetadataStorageTest, IncompatibleDB_M32) {
+  const int64 kLargestChangestamp = 1234567890;
+  const std::string title = "title";
+  const std::string resource_id = "abcd";
+  const std::string local_id = "local-abcd";
+
+  // Construct M32 version DB.
+  SetDBVersion(11);
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->SetLargestChangestamp(kLargestChangestamp));
+
+  leveldb::WriteBatch batch;
+
+  // Put a file entry and its cache and id entry.
+  ResourceEntry entry;
+  std::string serialized_entry;
+  entry.set_title(title);
+  entry.set_local_id(local_id);
+  entry.set_resource_id(resource_id);
+  EXPECT_TRUE(entry.SerializeToString(&serialized_entry));
+  batch.Put(local_id, serialized_entry);
+
+  FileCacheEntry cache_entry;
+  EXPECT_TRUE(cache_entry.SerializeToString(&serialized_entry));
+  batch.Put(local_id + '\0' + "CACHE", serialized_entry);
+
+  batch.Put('\0' + std::string("ID") + '\0' + resource_id, local_id);
+
+  EXPECT_TRUE(resource_map()->Write(leveldb::WriteOptions(), &batch).ok());
+
+  // Upgrade and reopen.
+  storage_.reset();
+  EXPECT_TRUE(ResourceMetadataStorage::UpgradeOldDB(temp_dir_.path()));
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // Data is erased, except cache and id mapping entries.
+  std::string id;
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetIdByResourceId(resource_id, &id));
+  EXPECT_EQ(local_id, id);
+  int64 largest_changestamp = 0;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->GetLargestChangestamp(&largest_changestamp));
+  EXPECT_EQ(0, largest_changestamp);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(id, &entry));
+  EXPECT_TRUE(entry.title().empty());
+  EXPECT_TRUE(entry.file_specific_info().has_cache_state());
+}
+
+TEST_F(ResourceMetadataStorageTest, IncompatibleDB_M33) {
+  const int64 kLargestChangestamp = 1234567890;
+  const std::string title = "title";
+  const std::string resource_id = "abcd";
+  const std::string local_id = "local-abcd";
+  const std::string md5 = "md5";
+  const std::string resource_id2 = "efgh";
+  const std::string local_id2 = "local-efgh";
+  const std::string md5_2 = "md5_2";
+
+  // Construct M33 version DB.
+  SetDBVersion(12);
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->SetLargestChangestamp(kLargestChangestamp));
+
+  leveldb::WriteBatch batch;
+
+  // Put a file entry and its cache and id entry.
+  ResourceEntry entry;
+  std::string serialized_entry;
+  entry.set_title(title);
+  entry.set_local_id(local_id);
+  entry.set_resource_id(resource_id);
+  EXPECT_TRUE(entry.SerializeToString(&serialized_entry));
+  batch.Put(local_id, serialized_entry);
+
+  FileCacheEntry cache_entry;
+  cache_entry.set_md5(md5);
+  EXPECT_TRUE(cache_entry.SerializeToString(&serialized_entry));
+  batch.Put(local_id + '\0' + "CACHE", serialized_entry);
+
+  batch.Put('\0' + std::string("ID") + '\0' + resource_id, local_id);
+
+  // Put another cache entry which is not accompanied by a ResourceEntry.
+  cache_entry.set_md5(md5_2);
+  EXPECT_TRUE(cache_entry.SerializeToString(&serialized_entry));
+  batch.Put(local_id2 + '\0' + "CACHE", serialized_entry);
+  batch.Put('\0' + std::string("ID") + '\0' + resource_id2, local_id2);
+
+  EXPECT_TRUE(resource_map()->Write(leveldb::WriteOptions(), &batch).ok());
+
+  // Upgrade and reopen.
+  storage_.reset();
+  EXPECT_TRUE(ResourceMetadataStorage::UpgradeOldDB(temp_dir_.path()));
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // No data is lost.
+  int64 largest_changestamp = 0;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->GetLargestChangestamp(&largest_changestamp));
+  EXPECT_EQ(kLargestChangestamp, largest_changestamp);
+
+  std::string id;
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetIdByResourceId(resource_id, &id));
+  EXPECT_EQ(local_id, id);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(id, &entry));
+  EXPECT_EQ(title, entry.title());
+  EXPECT_EQ(md5, entry.file_specific_info().cache_state().md5());
+
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetIdByResourceId(resource_id2, &id));
+  EXPECT_EQ(local_id2, id);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetEntry(id, &entry));
+  EXPECT_EQ(md5_2, entry.file_specific_info().cache_state().md5());
+}
+
+TEST_F(ResourceMetadataStorageTest, IncompatibleDB_Unknown) {
+  const int64 kLargestChangestamp = 1234567890;
+  const std::string key1 = "abcd";
+
+  // Put some data.
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->SetLargestChangestamp(kLargestChangestamp));
+  ResourceEntry entry;
+  entry.set_local_id(key1);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+
+  // Set newer version, upgrade and reopen DB.
+  SetDBVersion(ResourceMetadataStorage::kDBVersion + 1);
+  storage_.reset();
+  EXPECT_FALSE(ResourceMetadataStorage::UpgradeOldDB(temp_dir_.path()));
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // Data is erased because of the incompatible version.
+  int64 largest_changestamp = 0;
+  EXPECT_EQ(FILE_ERROR_OK,
+            storage_->GetLargestChangestamp(&largest_changestamp));
+  EXPECT_EQ(0, largest_changestamp);
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, storage_->GetEntry(key1, &entry));
+}
+
+TEST_F(ResourceMetadataStorageTest, DeleteUnusedIDEntries) {
+  leveldb::WriteBatch batch;
+
+  // Put an ID entry with a corresponding ResourceEntry.
+  ResourceEntry entry;
+  entry.set_local_id("id1");
+  entry.set_resource_id("resource_id1");
+
+  std::string serialized_entry;
+  EXPECT_TRUE(entry.SerializeToString(&serialized_entry));
+  batch.Put("id1", serialized_entry);
+  batch.Put('\0' + std::string("ID") + '\0' + "resource_id1", "id1");
+
+  // Put an ID entry without any corresponding entries.
+  batch.Put('\0' + std::string("ID") + '\0' + "resource_id2", "id3");
+
+  EXPECT_TRUE(resource_map()->Write(leveldb::WriteOptions(), &batch).ok());
+
+  // Upgrade and reopen.
+  storage_.reset();
+  EXPECT_TRUE(ResourceMetadataStorage::UpgradeOldDB(temp_dir_.path()));
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // Only the unused entry is deleted.
+  std::string id;
+  EXPECT_EQ(FILE_ERROR_OK, storage_->GetIdByResourceId("resource_id1", &id));
+  EXPECT_EQ("id1", id);
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+            storage_->GetIdByResourceId("resource_id2", &id));
+}
+
+TEST_F(ResourceMetadataStorageTest, WrongPath) {
+  // Create a file.
+  base::FilePath path;
+  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path));
+
+  storage_.reset(new ResourceMetadataStorage(
+      path, base::ThreadTaskRunnerHandle::Get().get()));
+  // Cannot initialize DB beacause the path does not point a directory.
+  ASSERT_FALSE(storage_->Initialize());
+}
+
+TEST_F(ResourceMetadataStorageTest, RecoverCacheEntriesFromTrashedResourceMap) {
+  // Put entry with id_foo.
+  ResourceEntry entry;
+  entry.set_local_id("id_foo");
+  entry.set_base_name("foo");
+  entry.set_title("foo");
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_md5("md5_foo");
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+
+  // Put entry with id_bar as a id_foo's child.
+  entry.set_local_id("id_bar");
+  entry.set_parent_local_id("id_foo");
+  entry.set_base_name("bar");
+  entry.set_title("bar");
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_md5("md5_bar");
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(true);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+
+  // Remove parent-child relationship to make the DB invalid.
+  RemoveChild("id_foo", "bar");
+  EXPECT_FALSE(CheckValidity());
+
+  // Reopen. This should result in trashing the DB.
+  storage_.reset(new ResourceMetadataStorage(
+      temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+  ASSERT_TRUE(storage_->Initialize());
+
+  // Recover cache entries from the trashed DB.
+  ResourceMetadataStorage::RecoveredCacheInfoMap recovered_cache_info;
+  storage_->RecoverCacheInfoFromTrashedResourceMap(&recovered_cache_info);
+  EXPECT_EQ(2U, recovered_cache_info.size());
+  EXPECT_FALSE(recovered_cache_info["id_foo"].is_dirty);
+  EXPECT_EQ("md5_foo", recovered_cache_info["id_foo"].md5);
+  EXPECT_EQ("foo", recovered_cache_info["id_foo"].title);
+  EXPECT_TRUE(recovered_cache_info["id_bar"].is_dirty);
+  EXPECT_EQ("md5_bar", recovered_cache_info["id_bar"].md5);
+  EXPECT_EQ("bar", recovered_cache_info["id_bar"].title);
+}
+
+TEST_F(ResourceMetadataStorageTest, CheckValidity) {
+  const std::string key1 = "foo";
+  const std::string name1 = "hoge";
+  const std::string key2 = "bar";
+  const std::string name2 = "fuga";
+  const std::string key3 = "boo";
+  const std::string name3 = "piyo";
+
+  // Empty storage is valid.
+  EXPECT_TRUE(CheckValidity());
+
+  // Put entry with key1.
+  ResourceEntry entry;
+  entry.set_local_id(key1);
+  entry.set_base_name(name1);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+  EXPECT_TRUE(CheckValidity());
+
+  // Put entry with key2 under key1.
+  entry.set_local_id(key2);
+  entry.set_parent_local_id(key1);
+  entry.set_base_name(name2);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+  EXPECT_TRUE(CheckValidity());
+
+  RemoveChild(key1, name2);
+  EXPECT_FALSE(CheckValidity());  // Missing parent-child relationship.
+
+  // Add back parent-child relationship between key1 and key2.
+  PutChild(key1, name2, key2);
+  EXPECT_TRUE(CheckValidity());
+
+  // Add parent-child relationship between key2 and key3.
+  PutChild(key2, name3, key3);
+  EXPECT_FALSE(CheckValidity());  // key3 is not stored in the storage.
+
+  // Put entry with key3 under key2.
+  entry.set_local_id(key3);
+  entry.set_parent_local_id(key2);
+  entry.set_base_name(name3);
+  EXPECT_EQ(FILE_ERROR_OK, storage_->PutEntry(entry));
+  EXPECT_TRUE(CheckValidity());
+
+  // Parent-child relationship with wrong name.
+  RemoveChild(key2, name3);
+  EXPECT_FALSE(CheckValidity());
+  PutChild(key2, name2, key3);
+  EXPECT_FALSE(CheckValidity());
+
+  // Fix up the relationship between key2 and key3.
+  RemoveChild(key2, name2);
+  EXPECT_FALSE(CheckValidity());
+  PutChild(key2, name3, key3);
+  EXPECT_TRUE(CheckValidity());
+
+  // Remove key2.
+  RemoveChild(key1, name2);
+  EXPECT_FALSE(CheckValidity());
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(key2));
+  EXPECT_FALSE(CheckValidity());
+
+  // Remove key3.
+  RemoveChild(key2, name3);
+  EXPECT_FALSE(CheckValidity());
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(key3));
+  EXPECT_TRUE(CheckValidity());
+
+  // Remove key1.
+  EXPECT_EQ(FILE_ERROR_OK, storage_->RemoveEntry(key1));
+  EXPECT_TRUE(CheckValidity());
+}
+
+}  // namespace internal
+}  // namespace drive
diff --git a/components/drive/resource_metadata_unittest.cc b/components/drive/resource_metadata_unittest.cc
new file mode 100644
index 0000000..15beb4d
--- /dev/null
+++ b/components/drive/resource_metadata_unittest.cc
@@ -0,0 +1,709 @@
+// 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 "components/drive/resource_metadata.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/stringprintf.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system_core_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace internal {
+namespace {
+
+// The changestamp of the resource metadata used in
+// ResourceMetadataTest.
+const int64 kTestChangestamp = 100;
+
+// Returns the sorted base names from |entries|.
+std::vector<std::string> GetSortedBaseNames(
+    const ResourceEntryVector& entries) {
+  std::vector<std::string> base_names;
+  for (size_t i = 0; i < entries.size(); ++i)
+    base_names.push_back(entries[i].base_name());
+  std::sort(base_names.begin(), base_names.end());
+
+  return base_names;
+}
+
+// Creates a ResourceEntry for a directory with explicitly set resource_id.
+ResourceEntry CreateDirectoryEntryWithResourceId(
+    const std::string& title,
+    const std::string& resource_id,
+    const std::string& parent_local_id) {
+  ResourceEntry entry;
+  entry.set_title(title);
+  entry.set_resource_id(resource_id);
+  entry.set_parent_local_id(parent_local_id);
+  entry.mutable_file_info()->set_is_directory(true);
+  entry.mutable_directory_specific_info()->set_changestamp(kTestChangestamp);
+  return entry;
+}
+
+// Creates a ResourceEntry for a directory.
+ResourceEntry CreateDirectoryEntry(const std::string& title,
+                                   const std::string& parent_local_id) {
+  return CreateDirectoryEntryWithResourceId(
+      title, "id:" + title, parent_local_id);
+}
+
+// Creates a ResourceEntry for a file with explicitly set resource_id.
+ResourceEntry CreateFileEntryWithResourceId(
+    const std::string& title,
+    const std::string& resource_id,
+    const std::string& parent_local_id) {
+  ResourceEntry entry;
+  entry.set_title(title);
+  entry.set_resource_id(resource_id);
+  entry.set_parent_local_id(parent_local_id);
+  entry.mutable_file_info()->set_is_directory(false);
+  entry.mutable_file_info()->set_size(1024);
+  entry.mutable_file_specific_info()->set_md5("md5:" + title);
+  return entry;
+}
+
+// Creates a ResourceEntry for a file.
+ResourceEntry CreateFileEntry(const std::string& title,
+                              const std::string& parent_local_id) {
+  return CreateFileEntryWithResourceId(title, "id:" + title, parent_local_id);
+}
+
+// Creates the following files/directories
+// drive/root/dir1/
+// drive/root/dir2/
+// drive/root/dir1/dir3/
+// drive/root/dir1/file4
+// drive/root/dir1/file5
+// drive/root/dir2/file6
+// drive/root/dir2/file7
+// drive/root/dir2/file8
+// drive/root/dir1/dir3/file9
+// drive/root/dir1/dir3/file10
+void SetUpEntries(ResourceMetadata* resource_metadata) {
+  std::string local_id;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->GetIdByPath(
+      util::GetDriveMyDriveRootPath(), &local_id));
+  const std::string root_local_id = local_id;
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateDirectoryEntry("dir1", root_local_id), &local_id));
+  const std::string local_id_dir1 = local_id;
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateDirectoryEntry("dir2", root_local_id), &local_id));
+  const std::string local_id_dir2 = local_id;
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateDirectoryEntry("dir3", local_id_dir1), &local_id));
+  const std::string local_id_dir3 = local_id;
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file4", local_id_dir1), &local_id));
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file5", local_id_dir1), &local_id));
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file6", local_id_dir2), &local_id));
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file7", local_id_dir2), &local_id));
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file8", local_id_dir2), &local_id));
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file9", local_id_dir3), &local_id));
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(
+      CreateFileEntry("file10", local_id_dir3), &local_id));
+
+  ASSERT_EQ(FILE_ERROR_OK,
+            resource_metadata->SetLargestChangestamp(kTestChangestamp));
+}
+
+}  // namespace
+
+// Tests for methods running on the blocking task runner.
+class ResourceMetadataTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    metadata_storage_.reset(new ResourceMetadataStorage(
+        temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+    ASSERT_TRUE(metadata_storage_->Initialize());
+
+    fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
+    cache_.reset(new FileCache(metadata_storage_.get(),
+                               temp_dir_.path(),
+                               base::ThreadTaskRunnerHandle::Get().get(),
+                               fake_free_disk_space_getter_.get()));
+    ASSERT_TRUE(cache_->Initialize());
+
+    resource_metadata_.reset(new ResourceMetadata(
+        metadata_storage_.get(), cache_.get(),
+        base::ThreadTaskRunnerHandle::Get()));
+
+    ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
+
+    SetUpEntries(resource_metadata_.get());
+  }
+
+  base::ScopedTempDir temp_dir_;
+  content::TestBrowserThreadBundle thread_bundle_;
+  scoped_ptr<ResourceMetadataStorage, test_util::DestroyHelperForTests>
+      metadata_storage_;
+  scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
+  scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
+  scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests>
+      resource_metadata_;
+};
+
+TEST_F(ResourceMetadataTest, LargestChangestamp) {
+  const int64 kChangestamp = 123456;
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->SetLargestChangestamp(kChangestamp));
+  int64 changestamp = 0;
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetLargestChangestamp(&changestamp));
+  EXPECT_EQ(kChangestamp, changestamp);
+}
+
+TEST_F(ResourceMetadataTest, GetResourceEntryByPath) {
+  // Confirm that an existing file is found.
+  ResourceEntry entry;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &entry));
+  EXPECT_EQ("file4", entry.base_name());
+
+  // Confirm that a non existing file is not found.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/non_existing"), &entry));
+
+  // Confirm that the root is found.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive"), &entry));
+
+  // Confirm that a non existing file is not found at the root level.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("non_existing"), &entry));
+
+   // Confirm that an entry is not found with a wrong root.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("non_existing/root"), &entry));
+}
+
+TEST_F(ResourceMetadataTest, ReadDirectoryByPath) {
+  // Confirm that an existing directory is found.
+  ResourceEntryVector entries;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->ReadDirectoryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1"), &entries));
+  ASSERT_EQ(3U, entries.size());
+  // The order is not guaranteed so we should sort the base names.
+  std::vector<std::string> base_names = GetSortedBaseNames(entries);
+  EXPECT_EQ("dir3", base_names[0]);
+  EXPECT_EQ("file4", base_names[1]);
+  EXPECT_EQ("file5", base_names[2]);
+
+  // Confirm that a non existing directory is not found.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->ReadDirectoryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/non_existing"), &entries));
+
+  // Confirm that reading a file results in FILE_ERROR_NOT_A_DIRECTORY.
+  EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY, resource_metadata_->ReadDirectoryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &entries));
+}
+
+TEST_F(ResourceMetadataTest, RefreshEntry) {
+  base::FilePath drive_file_path;
+  ResourceEntry entry;
+
+  // Get file9.
+  std::string file_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3/file9"), &file_id));
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetResourceEntryById(file_id, &entry));
+  EXPECT_EQ("file9", entry.base_name());
+  EXPECT_TRUE(!entry.file_info().is_directory());
+  EXPECT_EQ("md5:file9", entry.file_specific_info().md5());
+
+  // Rename it.
+  ResourceEntry file_entry(entry);
+  file_entry.set_title("file100");
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->RefreshEntry(file_entry));
+
+  base::FilePath path;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetFilePath(file_id, &path));
+  EXPECT_EQ("drive/root/dir1/dir3/file100", path.AsUTF8Unsafe());
+  entry.Clear();
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetResourceEntryById(file_id, &entry));
+  EXPECT_EQ("file100", entry.base_name());
+  EXPECT_TRUE(!entry.file_info().is_directory());
+  EXPECT_EQ("md5:file9", entry.file_specific_info().md5());
+
+  // Update the file md5.
+  const std::string updated_md5("md5:updated");
+  file_entry = entry;
+  file_entry.mutable_file_specific_info()->set_md5(updated_md5);
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->RefreshEntry(file_entry));
+
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetFilePath(file_id, &path));
+  EXPECT_EQ("drive/root/dir1/dir3/file100", path.AsUTF8Unsafe());
+  entry.Clear();
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetResourceEntryById(file_id, &entry));
+  EXPECT_EQ("file100", entry.base_name());
+  EXPECT_TRUE(!entry.file_info().is_directory());
+  EXPECT_EQ(updated_md5, entry.file_specific_info().md5());
+
+  // Make sure we get the same thing from GetResourceEntryByPath.
+  entry.Clear();
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3/file100"), &entry));
+  EXPECT_EQ("file100", entry.base_name());
+  ASSERT_TRUE(!entry.file_info().is_directory());
+  EXPECT_EQ(updated_md5, entry.file_specific_info().md5());
+
+  // Get dir2.
+  entry.Clear();
+  std::string dir_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir2"), &dir_id));
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetResourceEntryById(dir_id, &entry));
+  EXPECT_EQ("dir2", entry.base_name());
+  ASSERT_TRUE(entry.file_info().is_directory());
+
+  // Get dir3's ID.
+  std::string dir3_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3"), &dir3_id));
+
+  // Change the name to dir100 and change the parent to drive/dir1/dir3.
+  ResourceEntry dir_entry(entry);
+  dir_entry.set_title("dir100");
+  dir_entry.set_parent_local_id(dir3_id);
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->RefreshEntry(dir_entry));
+
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetFilePath(dir_id, &path));
+  EXPECT_EQ("drive/root/dir1/dir3/dir100", path.AsUTF8Unsafe());
+  entry.Clear();
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetResourceEntryById(dir_id, &entry));
+  EXPECT_EQ("dir100", entry.base_name());
+  EXPECT_TRUE(entry.file_info().is_directory());
+  EXPECT_EQ("id:dir2", entry.resource_id());
+
+  // Make sure the children have moved over. Test file6.
+  entry.Clear();
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3/dir100/file6"),
+      &entry));
+  EXPECT_EQ("file6", entry.base_name());
+
+  // Make sure dir2 no longer exists.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir2"), &entry));
+
+  // Make sure that directory cannot move under a file.
+  dir_entry.set_parent_local_id(file_id);
+  EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY,
+            resource_metadata_->RefreshEntry(dir_entry));
+
+  // Cannot refresh root.
+  dir_entry.Clear();
+  dir_entry.set_local_id(util::kDriveGrandRootLocalId);
+  dir_entry.set_title("new-root-name");
+  dir_entry.set_parent_local_id(dir3_id);
+  EXPECT_EQ(FILE_ERROR_INVALID_OPERATION,
+            resource_metadata_->RefreshEntry(dir_entry));
+}
+
+TEST_F(ResourceMetadataTest, RefreshEntry_ResourceIDCheck) {
+  // Get an entry with a non-empty resource ID.
+  ResourceEntry entry;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1"), &entry));
+  EXPECT_FALSE(entry.resource_id().empty());
+
+  // Add a new entry with an empty resource ID.
+  ResourceEntry new_entry;
+  new_entry.set_parent_local_id(entry.local_id());
+  new_entry.set_title("new entry");
+  std::string local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(new_entry, &local_id));
+
+  // Try to refresh the new entry with a used resource ID.
+  new_entry.set_local_id(local_id);
+  new_entry.set_resource_id(entry.resource_id());
+  EXPECT_EQ(FILE_ERROR_INVALID_OPERATION,
+            resource_metadata_->RefreshEntry(new_entry));
+}
+
+TEST_F(ResourceMetadataTest, RefreshEntry_DoNotOverwriteCacheState) {
+  ResourceEntry entry;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &entry));
+
+  // Try to set MD5 with RefreshEntry.
+  entry.mutable_file_specific_info()->mutable_cache_state()->set_md5("md5");
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->RefreshEntry(entry));
+
+  // Cache state is unchanged.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().md5().empty());
+
+  // Pin the file.
+  EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(entry.local_id()));
+
+  // Try to clear the cache state with RefreshEntry.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &entry));
+  entry.mutable_file_specific_info()->clear_cache_state();
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->RefreshEntry(entry));
+
+  // Cache state is not cleared.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &entry));
+  EXPECT_TRUE(entry.file_specific_info().cache_state().is_pinned());
+}
+
+TEST_F(ResourceMetadataTest, GetSubDirectoriesRecursively) {
+  std::set<base::FilePath> sub_directories;
+
+  // file9: not a directory, so no children.
+  std::string local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3/file9"), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetSubDirectoriesRecursively(
+      local_id, &sub_directories));
+  EXPECT_TRUE(sub_directories.empty());
+
+  // dir2: no child directories.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir2"), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetSubDirectoriesRecursively(
+      local_id, &sub_directories));
+  EXPECT_TRUE(sub_directories.empty());
+  const std::string dir2_id = local_id;
+
+  // dir1: dir3 is the only child
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1"), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetSubDirectoriesRecursively(
+      local_id, &sub_directories));
+  EXPECT_EQ(1u, sub_directories.size());
+  EXPECT_EQ(1u, sub_directories.count(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3")));
+  sub_directories.clear();
+
+  // Add a few more directories to make sure deeper nesting works.
+  // dir2/dir100
+  // dir2/dir101
+  // dir2/dir101/dir102
+  // dir2/dir101/dir103
+  // dir2/dir101/dir104
+  // dir2/dir101/dir104/dir105
+  // dir2/dir101/dir104/dir105/dir106
+  // dir2/dir101/dir104/dir105/dir106/dir107
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir100", dir2_id), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir101", dir2_id), &local_id));
+  const std::string dir101_id = local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir102", dir101_id), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir103", dir101_id), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir104", dir101_id), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir105", local_id), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir106", local_id), &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("dir107", local_id), &local_id));
+
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetSubDirectoriesRecursively(
+      dir2_id, &sub_directories));
+  EXPECT_EQ(8u, sub_directories.size());
+  EXPECT_EQ(1u, sub_directories.count(base::FilePath::FromUTF8Unsafe(
+      "drive/root/dir2/dir101")));
+  EXPECT_EQ(1u, sub_directories.count(base::FilePath::FromUTF8Unsafe(
+      "drive/root/dir2/dir101/dir104")));
+  EXPECT_EQ(1u, sub_directories.count(base::FilePath::FromUTF8Unsafe(
+      "drive/root/dir2/dir101/dir104/dir105/dir106/dir107")));
+}
+
+TEST_F(ResourceMetadataTest, AddEntry) {
+  // Add a file to dir3.
+  std::string local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3"), &local_id));
+  ResourceEntry file_entry = CreateFileEntry("file100", local_id);
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(file_entry, &local_id));
+  base::FilePath path;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetFilePath(local_id, &path));
+  EXPECT_EQ("drive/root/dir1/dir3/file100", path.AsUTF8Unsafe());
+
+  // Add a directory.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1"), &local_id));
+  ResourceEntry dir_entry = CreateDirectoryEntry("dir101", local_id);
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(dir_entry, &local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetFilePath(local_id, &path));
+  EXPECT_EQ("drive/root/dir1/dir101", path.AsUTF8Unsafe());
+
+  // Add to an invalid parent.
+  ResourceEntry file_entry3 = CreateFileEntry("file103", "id:invalid");
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+            resource_metadata_->AddEntry(file_entry3, &local_id));
+
+  // Add an existing file.
+  EXPECT_EQ(FILE_ERROR_EXISTS,
+            resource_metadata_->AddEntry(file_entry, &local_id));
+}
+
+TEST_F(ResourceMetadataTest, RemoveEntry) {
+  // Make sure file9 is found.
+  std::string file9_local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3/file9"),
+      &file9_local_id));
+  ResourceEntry entry;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      file9_local_id, &entry));
+  EXPECT_EQ("file9", entry.base_name());
+
+  // Remove file9.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->RemoveEntry(file9_local_id));
+
+  // file9 should no longer exist.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryById(
+      file9_local_id, &entry));
+
+  // Look for dir3.
+  std::string dir3_local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/dir3"), &dir3_local_id));
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      dir3_local_id, &entry));
+  EXPECT_EQ("dir3", entry.base_name());
+
+  // Remove dir3.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->RemoveEntry(dir3_local_id));
+
+  // dir3 should no longer exist.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryById(
+      dir3_local_id, &entry));
+
+  // Remove unknown local_id using RemoveEntry.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->RemoveEntry("foo"));
+
+  // Try removing root. This should fail.
+  EXPECT_EQ(FILE_ERROR_ACCESS_DENIED, resource_metadata_->RemoveEntry(
+      util::kDriveGrandRootLocalId));
+}
+
+TEST_F(ResourceMetadataTest, GetResourceEntryById_RootDirectory) {
+  // Look up the root directory by its ID.
+  ResourceEntry entry;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      util::kDriveGrandRootLocalId, &entry));
+  EXPECT_EQ("drive", entry.base_name());
+}
+
+TEST_F(ResourceMetadataTest, GetResourceEntryById) {
+  // Get file4 by path.
+  std::string local_id;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/dir1/file4"), &local_id));
+
+  // Confirm that an existing file is found.
+  ResourceEntry entry;
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      local_id, &entry));
+  EXPECT_EQ("file4", entry.base_name());
+
+  // Confirm that a non existing file is not found.
+  EXPECT_EQ(FILE_ERROR_NOT_FOUND, resource_metadata_->GetResourceEntryById(
+      "non_existing", &entry));
+}
+
+TEST_F(ResourceMetadataTest, Iterate) {
+  scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata_->GetIterator();
+  ASSERT_TRUE(it);
+
+  int file_count = 0, directory_count = 0;
+  for (; !it->IsAtEnd(); it->Advance()) {
+    if (!it->GetValue().file_info().is_directory())
+      ++file_count;
+    else
+      ++directory_count;
+  }
+
+  EXPECT_EQ(7, file_count);
+  EXPECT_EQ(7, directory_count);
+}
+
+TEST_F(ResourceMetadataTest, DuplicatedNames) {
+  std::string root_local_id;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root"), &root_local_id));
+
+  ResourceEntry entry;
+
+  // When multiple entries with the same title are added in a single directory,
+  // their base_names are de-duped.
+  // - drive/root/foo
+  // - drive/root/foo (1)
+  std::string dir_id_0;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntryWithResourceId(
+          "foo", "foo0", root_local_id), &dir_id_0));
+  std::string dir_id_1;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntryWithResourceId(
+          "foo", "foo1", root_local_id), &dir_id_1));
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      dir_id_0, &entry));
+  EXPECT_EQ("foo", entry.base_name());
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      dir_id_1, &entry));
+  EXPECT_EQ("foo (1)", entry.base_name());
+
+  // - drive/root/foo/bar.txt
+  // - drive/root/foo/bar (1).txt
+  // - drive/root/foo/bar (2).txt
+  // ...
+  // - drive/root/foo/bar (99).txt
+  std::vector<std::string> file_ids(100);
+  for (size_t i = 0; i < file_ids.size(); ++i) {
+    ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+        CreateFileEntryWithResourceId(
+            "bar.txt", base::StringPrintf("bar%d", static_cast<int>(i)),
+            dir_id_0), &file_ids[i]));
+  }
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      file_ids[0], &entry));
+  EXPECT_EQ("bar.txt", entry.base_name());
+  for (size_t i = 1; i < file_ids.size(); ++i) {
+    ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+        file_ids[i], &entry)) << i;
+    EXPECT_EQ(base::StringPrintf("bar (%d).txt", static_cast<int>(i)),
+              entry.base_name());
+  }
+
+  // Same name but different parent. No renaming.
+  // - drive/root/foo (1)/bar.txt
+  std::string file_id_3;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateFileEntryWithResourceId(
+          "bar.txt", "bar_different_parent", dir_id_1), &file_id_3));
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      file_id_3, &entry));
+  EXPECT_EQ("bar.txt", entry.base_name());
+
+  // Checks that the entries can be looked up by the de-duped paths.
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/foo/bar (2).txt"), &entry));
+  EXPECT_EQ("bar2", entry.resource_id());
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root/foo (1)/bar.txt"), &entry));
+  EXPECT_EQ("bar_different_parent", entry.resource_id());
+}
+
+TEST_F(ResourceMetadataTest, EncodedNames) {
+  std::string root_local_id;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+      base::FilePath::FromUTF8Unsafe("drive/root"), &root_local_id));
+
+  ResourceEntry entry;
+
+  std::string dir_id;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateDirectoryEntry("\\(^o^)/", root_local_id), &dir_id));
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      dir_id, &entry));
+  EXPECT_EQ("\\(^o^)_", entry.base_name());
+
+  std::string file_id;
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+      CreateFileEntryWithResourceId("Slash /.txt", "myfile", dir_id),
+      &file_id));
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryById(
+      file_id, &entry));
+  EXPECT_EQ("Slash _.txt", entry.base_name());
+
+  ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->GetResourceEntryByPath(
+      base::FilePath::FromUTF8Unsafe(
+          "drive/root/\\(^o^)_/Slash _.txt"),
+      &entry));
+  EXPECT_EQ("myfile", entry.resource_id());
+}
+
+TEST_F(ResourceMetadataTest, Reset) {
+  // The grand root has "root" which is not empty.
+  std::vector<ResourceEntry> entries;
+  ASSERT_EQ(FILE_ERROR_OK,
+            resource_metadata_->ReadDirectoryByPath(
+                base::FilePath::FromUTF8Unsafe("drive/root"), &entries));
+  ASSERT_FALSE(entries.empty());
+
+  // Reset.
+  EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->Reset());
+
+  // change stamp should be reset.
+  int64 changestamp = 0;
+  EXPECT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetLargestChangestamp(&changestamp));
+  EXPECT_EQ(0, changestamp);
+
+  // root should continue to exist.
+  ResourceEntry entry;
+  ASSERT_EQ(FILE_ERROR_OK,
+            resource_metadata_->GetResourceEntryByPath(
+                base::FilePath::FromUTF8Unsafe("drive"), &entry));
+  EXPECT_EQ("drive", entry.base_name());
+  ASSERT_TRUE(entry.file_info().is_directory());
+  EXPECT_EQ(util::kDriveGrandRootLocalId, entry.local_id());
+
+  // There are "other", "trash" and "root" under "drive".
+  ASSERT_EQ(FILE_ERROR_OK,
+            resource_metadata_->ReadDirectoryByPath(
+                base::FilePath::FromUTF8Unsafe("drive"), &entries));
+  EXPECT_EQ(3U, entries.size());
+
+  // The "other" directory should be empty.
+  ASSERT_EQ(FILE_ERROR_OK,
+            resource_metadata_->ReadDirectoryByPath(
+                base::FilePath::FromUTF8Unsafe("drive/other"), &entries));
+  EXPECT_TRUE(entries.empty());
+
+  // The "trash" directory should be empty.
+  ASSERT_EQ(FILE_ERROR_OK,
+            resource_metadata_->ReadDirectoryByPath(
+                base::FilePath::FromUTF8Unsafe("drive/trash"), &entries));
+  EXPECT_TRUE(entries.empty());
+}
+
+}  // namespace internal
+}  // namespace drive