drive: Use title recovered from trashed DB when DB corruption happens

Add ResourceMetadataStorage::RecoveredCacheInfo
Recover title in ResourceMetadataStorage::RecoverCacheInfoFromTrashedResourceMap
Use title in FileCache::RecoverFilesFromCacheDirectory

BUG=277333
TEST=unit_tests

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@232980 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/chromeos/drive/drive_integration_service.cc b/chrome/browser/chromeos/drive/drive_integration_service.cc
index aae5cf2..b5bdb7c 100644
--- a/chrome/browser/chromeos/drive/drive_integration_service.cc
+++ b/chrome/browser/chromeos/drive/drive_integration_service.cc
@@ -143,14 +143,15 @@
           .InsertBeforeExtensionASCII(base::StringPrintf(" (%d)", uniquifier));
     }
 
-    std::map<std::string, FileCacheEntry> recovered_cache_entries;
-    metadata_storage->RecoverCacheEntriesFromTrashedResourceMap(
-        &recovered_cache_entries);
+    internal::ResourceMetadataStorage::RecoveredCacheInfoMap
+        recovered_cache_info;
+    metadata_storage->RecoverCacheInfoFromTrashedResourceMap(
+        &recovered_cache_info);
 
     LOG(INFO) << "DB could not be opened for some reasons. "
               << "Recovering cache files to " << dest_directory.value();
     if (!cache->RecoverFilesFromCacheDirectory(dest_directory,
-                                               recovered_cache_entries)) {
+                                               recovered_cache_info)) {
       LOG(WARNING) << "Failed to recover cache files.";
       return FILE_ERROR_FAILED;
     }
diff --git a/chrome/browser/chromeos/drive/file_cache.cc b/chrome/browser/chromeos/drive/file_cache.cc
index 05448b5..f5c6751 100644
--- a/chrome/browser/chromeos/drive/file_cache.cc
+++ b/chrome/browser/chromeos/drive/file_cache.cc
@@ -395,7 +395,8 @@
 
 bool FileCache::RecoverFilesFromCacheDirectory(
     const base::FilePath& dest_directory,
-    const std::map<std::string, FileCacheEntry>& recovered_cache_entries) {
+    const ResourceMetadataStorage::RecoveredCacheInfoMap&
+        recovered_cache_info) {
   int file_number = 1;
 
   base::FileEnumerator enumerator(cache_file_directory_,
@@ -413,14 +414,13 @@
     // 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.
-    std::map<std::string, FileCacheEntry>::const_iterator it =
-        recovered_cache_entries.find(id);
-    if (it != recovered_cache_entries.end()) {
-      const FileCacheEntry& recovered_entry = it->second;
-      // Due to the DB corruption, |recovered_entry| might be recovered from old
-      // revision. Perform MD5 check even when is_dirty() is false just in case.
-      if (!recovered_entry.is_dirty() &&
-          recovered_entry.md5() == util::GetMd5Digest(current)) {
+    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)) {
         base::DeleteFile(current, false /* recursive */);
         continue;
       }
@@ -437,13 +437,18 @@
     if (read_result == 0)  // Skip empty files.
       continue;
 
-    // Decide file name with sniffed mime type.
+    // 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 (net::SniffMimeType(&content[0], read_result,
-                           net::FilePathToFileURL(current), std::string(),
-                           &mime_type) ||
-        net::SniffMimeTypeFromLocalData(&content[0], read_result, &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"));
diff --git a/chrome/browser/chromeos/drive/file_cache.h b/chrome/browser/chromeos/drive/file_cache.h
index 1127ec99..cc595372 100644
--- a/chrome/browser/chromeos/drive/file_cache.h
+++ b/chrome/browser/chromeos/drive/file_cache.h
@@ -157,11 +157,12 @@
 
   // Moves files in the cache directory which are not manged by FileCache to
   // |dest_directory|.
-  // |recovered_cache_entries| should contain cache entries recovered from the
-  // trashed metadata DB. It is used to ignore non-dirty files.
+  // |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 std::map<std::string, FileCacheEntry>& recovered_cache_entries);
+      const ResourceMetadataStorage::RecoveredCacheInfoMap&
+          recovered_cache_info);
 
  private:
   friend class FileCacheTest;
diff --git a/chrome/browser/chromeos/drive/file_cache_unittest.cc b/chrome/browser/chromeos/drive/file_cache_unittest.cc
index 493fe0f2..23a6149 100644
--- a/chrome/browser/chromeos/drive/file_cache_unittest.cc
+++ b/chrome/browser/chromeos/drive/file_cache_unittest.cc
@@ -768,23 +768,37 @@
   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_entries|.
+  // Insert a dirty entry with "id_baz" to |recovered_cache_info|.
   // This should not prevent the file from being recovered.
-  std::map<std::string, FileCacheEntry> recovered_cache_entries;
-  recovered_cache_entries["id_baz"].set_is_dirty(true);
+  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_entries));
+                                                     recovered_cache_info));
 
   // Only two files should be recovered.
   EXPECT_TRUE(base::PathExists(dest_directory));
-  EXPECT_TRUE(base::ContentsEqual(src_path,
-                                  dest_directory.Append("image00000001.png")));
-  EXPECT_TRUE(base::ContentsEqual(src_path,
-                                  dest_directory.Append("image00000002.png")));
-  EXPECT_FALSE(base::PathExists(dest_directory.Append("image00000003.png")));
+  // 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, Iterator) {
diff --git a/chrome/browser/chromeos/drive/resource_metadata_storage.cc b/chrome/browser/chromeos/drive/resource_metadata_storage.cc
index d07dbffb..f3b61a6 100644
--- a/chrome/browser/chromeos/drive/resource_metadata_storage.cc
+++ b/chrome/browser/chromeos/drive/resource_metadata_storage.cc
@@ -477,8 +477,8 @@
   return resource_map_;
 }
 
-void ResourceMetadataStorage::RecoverCacheEntriesFromTrashedResourceMap(
-    std::map<std::string, FileCacheEntry>* out_entries) {
+void ResourceMetadataStorage::RecoverCacheInfoFromTrashedResourceMap(
+    RecoveredCacheInfoMap* out_info) {
   const base::FilePath trashed_resource_map_path =
       directory_path_.Append(kTrashedResourceMapDBName);
 
@@ -526,8 +526,20 @@
     if (IsCacheEntryKey(it->key())) {
       const std::string& id = GetIdFromCacheEntryKey(it->key());
       FileCacheEntry cache_entry;
-      if (cache_entry.ParseFromArray(it->value().data(), it->value().size()))
-        (*out_entries)[id] = cache_entry;
+      if (cache_entry.ParseFromArray(it->value().data(), it->value().size())) {
+        RecoveredCacheInfo* info = &(*out_info)[id];
+        info->is_dirty = cache_entry.is_dirty();
+        info->md5 = cache_entry.md5();
+
+        // Get title from ResourceEntry if available.
+        std::string serialized_entry;
+        ResourceEntry entry;
+        if (resource_map->Get(leveldb::ReadOptions(),
+                              leveldb::Slice(id),
+                              &serialized_entry).ok() &&
+            entry.ParseFromString(serialized_entry))
+          info->title = entry.title();
+      }
     }
   }
 }
@@ -734,6 +746,11 @@
   return make_scoped_ptr(new CacheEntryIterator(it.Pass()));
 }
 
+ResourceMetadataStorage::RecoveredCacheInfo::RecoveredCacheInfo()
+    : is_dirty(false) {}
+
+ResourceMetadataStorage::RecoveredCacheInfo::~RecoveredCacheInfo() {}
+
 bool ResourceMetadataStorage::GetIdByResourceId(
     const std::string& resource_id,
     std::string* out_id) {
diff --git a/chrome/browser/chromeos/drive/resource_metadata_storage.h b/chrome/browser/chromeos/drive/resource_metadata_storage.h
index a6d3868..6ab0159 100644
--- a/chrome/browser/chromeos/drive/resource_metadata_storage.h
+++ b/chrome/browser/chromeos/drive/resource_metadata_storage.h
@@ -105,6 +105,17 @@
     DISALLOW_COPY_AND_ASSIGN(CacheEntryIterator);
   };
 
+  // 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,
                            const ResourceIdCanonicalizer& id_canonicalizer);
@@ -124,9 +135,8 @@
   // Initializes this object.
   bool Initialize();
 
-  // Collects FileCacheEntry from trashed resource map DB.
-  void RecoverCacheEntriesFromTrashedResourceMap(
-      std::map<std::string, FileCacheEntry>* out_entries);
+  // Collects cache info from trashed resource map DB.
+  void RecoverCacheInfoFromTrashedResourceMap(RecoveredCacheInfoMap* out_info);
 
   // Sets the largest changestamp.
   bool SetLargestChangestamp(int64 largest_changestamp);
diff --git a/chrome/browser/chromeos/drive/resource_metadata_storage_unittest.cc b/chrome/browser/chromeos/drive/resource_metadata_storage_unittest.cc
index 865b7d13..0094c31e 100644
--- a/chrome/browser/chromeos/drive/resource_metadata_storage_unittest.cc
+++ b/chrome/browser/chromeos/drive/resource_metadata_storage_unittest.cc
@@ -446,18 +446,21 @@
   cache_entry.set_md5("md5_foo");
   EXPECT_TRUE(storage_->PutCacheEntry("id_foo", cache_entry));
   cache_entry.set_md5("md5_bar");
+  cache_entry.set_is_dirty(true);
   EXPECT_TRUE(storage_->PutCacheEntry("id_bar", cache_entry));
 
   // Put entry with id_foo.
   ResourceEntry entry;
   entry.set_local_id("id_foo");
   entry.set_base_name("foo");
+  entry.set_title("foo");
   EXPECT_TRUE(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");
   EXPECT_TRUE(storage_->PutEntry(entry));
 
   // Remove parent-child relationship to make the DB invalid.
@@ -470,11 +473,15 @@
   ASSERT_TRUE(storage_->Initialize());
 
   // Recover cache entries from the trashed DB.
-  std::map<std::string, FileCacheEntry> recovered_cache_entries;
-  storage_->RecoverCacheEntriesFromTrashedResourceMap(&recovered_cache_entries);
-  EXPECT_EQ(2U, recovered_cache_entries.size());
-  EXPECT_EQ("md5_foo", recovered_cache_entries["id_foo"].md5());
-  EXPECT_EQ("md5_bar", recovered_cache_entries["id_bar"].md5());
+  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) {