Home | History | Annotate | Download | only in drive
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/drive/change_list_processor.h"
      6 
      7 #include "base/metrics/histogram.h"
      8 #include "chrome/browser/chromeos/drive/drive.pb.h"
      9 #include "chrome/browser/chromeos/drive/file_system_util.h"
     10 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
     11 #include "chrome/browser/chromeos/drive/resource_metadata.h"
     12 #include "chrome/browser/google_apis/drive_api_parser.h"
     13 #include "chrome/browser/google_apis/gdata_wapi_parser.h"
     14 
     15 namespace drive {
     16 namespace internal {
     17 
     18 ChangeList::ChangeList(const google_apis::ResourceList& resource_list)
     19     : largest_changestamp_(resource_list.largest_changestamp()) {
     20   resource_list.GetNextFeedURL(&next_url_);
     21 
     22   entries_.resize(resource_list.entries().size());
     23   size_t entries_index = 0;
     24   for (size_t i = 0; i < resource_list.entries().size(); ++i) {
     25     if (ConvertToResourceEntry(*resource_list.entries()[i],
     26                                &entries_[entries_index]))
     27       ++entries_index;
     28   }
     29   entries_.resize(entries_index);
     30 }
     31 
     32 ChangeList::~ChangeList() {}
     33 
     34 class ChangeListProcessor::ChangeListToEntryMapUMAStats {
     35  public:
     36   ChangeListToEntryMapUMAStats()
     37     : num_regular_files_(0),
     38       num_hosted_documents_(0),
     39       num_shared_with_me_entries_(0) {
     40   }
     41 
     42   // Increments number of files.
     43   void IncrementNumFiles(bool is_hosted_document) {
     44     is_hosted_document ? num_hosted_documents_++ : num_regular_files_++;
     45   }
     46 
     47   // Increments number of shared-with-me entries.
     48   void IncrementNumSharedWithMeEntries() {
     49     num_shared_with_me_entries_++;
     50   }
     51 
     52   // Updates UMA histograms with file counts.
     53   void UpdateFileCountUmaHistograms() {
     54     const int num_total_files = num_hosted_documents_ + num_regular_files_;
     55     UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_);
     56     UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
     57                          num_hosted_documents_);
     58     UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
     59     UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
     60                          num_shared_with_me_entries_);
     61   }
     62 
     63  private:
     64   int num_regular_files_;
     65   int num_hosted_documents_;
     66   int num_shared_with_me_entries_;
     67 };
     68 
     69 ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata)
     70   : resource_metadata_(resource_metadata) {
     71 }
     72 
     73 ChangeListProcessor::~ChangeListProcessor() {
     74 }
     75 
     76 void ChangeListProcessor::Apply(
     77     scoped_ptr<google_apis::AboutResource> about_resource,
     78     ScopedVector<ChangeList> change_lists,
     79     bool is_delta_update) {
     80   DCHECK(is_delta_update || about_resource.get());
     81 
     82   int64 largest_changestamp = 0;
     83   if (is_delta_update) {
     84     if (!change_lists.empty()) {
     85       // The changestamp appears in the first page of the change list.
     86       // The changestamp does not appear in the full resource list.
     87       largest_changestamp = change_lists[0]->largest_changestamp();
     88       DCHECK_GE(change_lists[0]->largest_changestamp(), 0);
     89     }
     90   } else if (about_resource.get()) {
     91     largest_changestamp = about_resource->largest_change_id();
     92 
     93     DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id();
     94     DCHECK(!about_resource->root_folder_id().empty());
     95   } else {
     96     // A full update without AboutResouce will have no effective changestamp.
     97     NOTREACHED();
     98   }
     99 
    100   ChangeListToEntryMapUMAStats uma_stats;
    101   ConvertToMap(change_lists.Pass(), &entry_map_, &uma_stats);
    102 
    103   // Add the largest changestamp for directories.
    104   for (ResourceEntryMap::iterator it = entry_map_.begin();
    105        it != entry_map_.end(); ++it) {
    106     if (it->second.file_info().is_directory()) {
    107       it->second.mutable_directory_specific_info()->set_changestamp(
    108           largest_changestamp);
    109     }
    110   }
    111 
    112   ApplyEntryMap(is_delta_update, about_resource.Pass());
    113 
    114   // Update the root entry and finish.
    115   UpdateRootEntry(largest_changestamp);
    116 
    117   // Update changestamp.
    118   FileError error = resource_metadata_->SetLargestChangestamp(
    119       largest_changestamp);
    120   DLOG_IF(ERROR, error != FILE_ERROR_OK) << "SetLargestChangeStamp failed: "
    121                                          << FileErrorToString(error);
    122 
    123   // Shouldn't record histograms when processing delta update.
    124   if (!is_delta_update)
    125     uma_stats.UpdateFileCountUmaHistograms();
    126 }
    127 
    128 void ChangeListProcessor::ApplyEntryMap(
    129     bool is_delta_update,
    130     scoped_ptr<google_apis::AboutResource> about_resource) {
    131   if (!is_delta_update) {  // Full update.
    132     DCHECK(about_resource);
    133 
    134     FileError error = resource_metadata_->Reset();
    135 
    136     LOG_IF(ERROR, error != FILE_ERROR_OK) << "Failed to reset: "
    137                                           << FileErrorToString(error);
    138 
    139     changed_dirs_.insert(util::GetDriveGrandRootPath());
    140     changed_dirs_.insert(util::GetDriveMyDriveRootPath());
    141 
    142     // Create the MyDrive root directory.
    143     ApplyEntry(util::CreateMyDriveRootEntry(about_resource->root_folder_id()));
    144   }
    145 
    146   // Apply all entries to the metadata.
    147   while (!entry_map_.empty()) {
    148     // Start from entry_map_.begin() and traverse ancestors.
    149     std::vector<ResourceEntryMap::iterator> entries;
    150     for (ResourceEntryMap::iterator it = entry_map_.begin();
    151          it != entry_map_.end();
    152          it = entry_map_.find(it->second.parent_resource_id())) {
    153       DCHECK_EQ(it->first, it->second.resource_id());
    154       entries.push_back(it);
    155     }
    156 
    157     // Apply the parent first.
    158     std::reverse(entries.begin(), entries.end());
    159     for (size_t i = 0; i < entries.size(); ++i) {
    160       ResourceEntryMap::iterator it = entries[i];
    161       ApplyEntry(it->second);
    162       entry_map_.erase(it);
    163     }
    164   }
    165 }
    166 
    167 void ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
    168   // Lookup the entry.
    169   ResourceEntry existing_entry;
    170   FileError error = resource_metadata_->GetResourceEntryById(
    171       entry.resource_id(), &existing_entry);
    172 
    173   if (error == FILE_ERROR_OK) {
    174     if (entry.deleted()) {
    175       // Deleted file/directory.
    176       RemoveEntry(entry);
    177     } else {
    178       // Entry exists and needs to be refreshed.
    179       RefreshEntry(entry);
    180     }
    181   } else if (error == FILE_ERROR_NOT_FOUND && !entry.deleted()) {
    182     // Adding a new entry.
    183     AddEntry(entry);
    184   }
    185 }
    186 
    187 void ChangeListProcessor::AddEntry(const ResourceEntry& entry) {
    188   FileError error = resource_metadata_->AddEntry(entry);
    189 
    190   if (error == FILE_ERROR_OK) {
    191     base::FilePath file_path =
    192         resource_metadata_->GetFilePath(entry.resource_id());
    193     // Notify if a directory has been created.
    194     if (entry.file_info().is_directory())
    195       changed_dirs_.insert(file_path);
    196 
    197     // Notify parent.
    198     changed_dirs_.insert(file_path.DirName());
    199   }
    200 }
    201 
    202 void ChangeListProcessor::RemoveEntry(const ResourceEntry& entry) {
    203   std::set<base::FilePath> child_directories;
    204   if (entry.file_info().is_directory()) {
    205     resource_metadata_->GetChildDirectories(entry.resource_id(),
    206                                             &child_directories);
    207   }
    208 
    209   base::FilePath file_path =
    210       resource_metadata_->GetFilePath(entry.resource_id());
    211 
    212   FileError error = resource_metadata_->RemoveEntry(entry.resource_id());
    213 
    214   if (error == FILE_ERROR_OK) {
    215     // Notify parent.
    216     changed_dirs_.insert(file_path.DirName());
    217 
    218     // Notify children, if any.
    219     changed_dirs_.insert(child_directories.begin(), child_directories.end());
    220 
    221     // If entry is a directory, notify self.
    222     if (entry.file_info().is_directory())
    223       changed_dirs_.insert(file_path);
    224   }
    225 }
    226 
    227 void ChangeListProcessor::RefreshEntry(const ResourceEntry& entry) {
    228   base::FilePath old_file_path =
    229       resource_metadata_->GetFilePath(entry.resource_id());
    230 
    231   FileError error = resource_metadata_->RefreshEntry(entry);
    232 
    233   if (error == FILE_ERROR_OK) {
    234     base::FilePath new_file_path =
    235         resource_metadata_->GetFilePath(entry.resource_id());
    236 
    237     // Notify old parent.
    238     changed_dirs_.insert(old_file_path.DirName());
    239 
    240     // Notify new parent.
    241     changed_dirs_.insert(new_file_path.DirName());
    242 
    243     // Notify self if entry is a directory.
    244     if (entry.file_info().is_directory()) {
    245       // Notify new self.
    246       changed_dirs_.insert(new_file_path);
    247       // Notify old self.
    248       changed_dirs_.insert(old_file_path);
    249     }
    250   }
    251 }
    252 
    253 // static
    254 void ChangeListProcessor::ConvertToMap(
    255     ScopedVector<ChangeList> change_lists,
    256     ResourceEntryMap* entry_map,
    257     ChangeListToEntryMapUMAStats* uma_stats) {
    258   for (size_t i = 0; i < change_lists.size(); ++i) {
    259     ChangeList* change_list = change_lists[i];
    260 
    261     std::vector<ResourceEntry>* entries = change_list->mutable_entries();
    262     for (size_t i = 0; i < entries->size(); ++i) {
    263       ResourceEntry* entry = &(*entries)[i];
    264       // Some document entries don't map into files (i.e. sites).
    265       if (entry->resource_id().empty())
    266         continue;
    267 
    268       // Count the number of files.
    269       if (uma_stats) {
    270         if (!entry->file_info().is_directory()) {
    271           uma_stats->IncrementNumFiles(
    272               entry->file_specific_info().is_hosted_document());
    273         }
    274         if (entry->shared_with_me())
    275           uma_stats->IncrementNumSharedWithMeEntries();
    276       }
    277 
    278       (*entry_map)[entry->resource_id()].Swap(entry);
    279       LOG_IF(WARNING, !entry->resource_id().empty())
    280           << "Found duplicated file: " << entry->base_name();
    281     }
    282   }
    283 }
    284 
    285 void ChangeListProcessor::UpdateRootEntry(int64 largest_changestamp) {
    286   ResourceEntry root;
    287   FileError error = resource_metadata_->GetResourceEntryByPath(
    288       util::GetDriveMyDriveRootPath(), &root);
    289 
    290   if (error != FILE_ERROR_OK) {
    291     // TODO(satorux): Need to trigger recovery if root is corrupt.
    292     LOG(WARNING) << "Failed to get the entry for root directory";
    293     return;
    294   }
    295 
    296   // The changestamp should always be updated.
    297   root.mutable_directory_specific_info()->set_changestamp(largest_changestamp);
    298 
    299   error = resource_metadata_->RefreshEntry(root);
    300 
    301   LOG_IF(WARNING, error != FILE_ERROR_OK) << "Failed to refresh root directory";
    302 }
    303 
    304 }  // namespace internal
    305 }  // namespace drive
    306