blob: 181dc9ddc55cf0ce567fa6bc7f2422809eec8224 [file] [log] [blame]
// 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 "chrome/browser/chromeos/drive/change_list_processor.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
#include "chrome/browser/chromeos/drive/resource_metadata.h"
#include "chrome/browser/google_apis/drive_api_parser.h"
#include "chrome/browser/google_apis/gdata_wapi_parser.h"
namespace drive {
namespace internal {
std::string DirectoryFetchInfo::ToString() const {
return ("resource_id: " + resource_id_ +
", changestamp: " + base::Int64ToString(changestamp_));
}
ChangeList::ChangeList(const google_apis::ResourceList& resource_list)
: largest_changestamp_(resource_list.largest_changestamp()) {
resource_list.GetNextFeedURL(&next_url_);
entries_.resize(resource_list.entries().size());
size_t entries_index = 0;
for (size_t i = 0; i < resource_list.entries().size(); ++i) {
if (ConvertToResourceEntry(*resource_list.entries()[i],
&entries_[entries_index]))
++entries_index;
}
entries_.resize(entries_index);
}
ChangeList::~ChangeList() {}
class ChangeListProcessor::ChangeListToEntryMapUMAStats {
public:
ChangeListToEntryMapUMAStats()
: num_regular_files_(0),
num_hosted_documents_(0),
num_shared_with_me_entries_(0) {
}
// Increments number of files.
void IncrementNumFiles(bool is_hosted_document) {
is_hosted_document ? num_hosted_documents_++ : num_regular_files_++;
}
// Increments number of shared-with-me entries.
void IncrementNumSharedWithMeEntries() {
num_shared_with_me_entries_++;
}
// Updates UMA histograms with file counts.
void UpdateFileCountUmaHistograms() {
const int num_total_files = num_hosted_documents_ + num_regular_files_;
UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_);
UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
num_hosted_documents_);
UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
num_shared_with_me_entries_);
}
private:
int num_regular_files_;
int num_hosted_documents_;
int num_shared_with_me_entries_;
};
ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata)
: resource_metadata_(resource_metadata) {
}
ChangeListProcessor::~ChangeListProcessor() {
}
void ChangeListProcessor::Apply(
scoped_ptr<google_apis::AboutResource> about_resource,
ScopedVector<ChangeList> change_lists,
bool is_delta_update) {
DCHECK(is_delta_update || about_resource.get());
int64 largest_changestamp = 0;
if (is_delta_update) {
if (!change_lists.empty()) {
// The changestamp appears in the first page of the change list.
// The changestamp does not appear in the full resource list.
largest_changestamp = change_lists[0]->largest_changestamp();
DCHECK_GE(change_lists[0]->largest_changestamp(), 0);
}
} else if (about_resource.get()) {
largest_changestamp = about_resource->largest_change_id();
DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id();
DCHECK(!about_resource->root_folder_id().empty());
} else {
// A full update without AboutResouce will have no effective changestamp.
NOTREACHED();
}
ChangeListToEntryMapUMAStats uma_stats;
ConvertToMap(change_lists.Pass(), &entry_map_, &uma_stats);
// Add the largest changestamp for directories.
for (ResourceEntryMap::iterator it = entry_map_.begin();
it != entry_map_.end(); ++it) {
if (it->second.file_info().is_directory()) {
it->second.mutable_directory_specific_info()->set_changestamp(
largest_changestamp);
}
}
ApplyEntryMap(is_delta_update, about_resource.Pass());
// Update the root entry and finish.
UpdateRootEntry(largest_changestamp);
// Update changestamp.
FileError error = resource_metadata_->SetLargestChangestamp(
largest_changestamp);
DLOG_IF(ERROR, error != FILE_ERROR_OK) << "SetLargestChangeStamp failed: "
<< FileErrorToString(error);
// Shouldn't record histograms when processing delta update.
if (!is_delta_update)
uma_stats.UpdateFileCountUmaHistograms();
}
void ChangeListProcessor::ApplyEntryMap(
bool is_delta_update,
scoped_ptr<google_apis::AboutResource> about_resource) {
if (!is_delta_update) { // Full update.
DCHECK(about_resource);
FileError error = resource_metadata_->Reset();
LOG_IF(ERROR, error != FILE_ERROR_OK) << "Failed to reset: "
<< FileErrorToString(error);
changed_dirs_.insert(util::GetDriveGrandRootPath());
changed_dirs_.insert(util::GetDriveMyDriveRootPath());
// Create the MyDrive root directory.
ApplyEntry(util::CreateMyDriveRootEntry(about_resource->root_folder_id()));
}
// Gather the set of changes in the old path.
// Note that we want to notify the change in both old and new paths (suppose
// /a/b/c is moved to /x/y/c. We want to notify both "/a/b" and "/x/y".)
// The old paths must be calculated before we apply any actual changes.
// The new paths are calculated after each change is applied. It correctly
// sets the new path because we apply changes in such an order (see below).
for (ResourceEntryMap::iterator it = entry_map_.begin();
it != entry_map_.end(); ++it) {
UpdateChangedDirs(it->second);
}
// Apply all entries except deleted ones to the metadata.
std::vector<ResourceEntry> deleted_entries;
deleted_entries.reserve(entry_map_.size());
while (!entry_map_.empty()) {
ResourceEntryMap::iterator it = entry_map_.begin();
// Process deleted entries later to avoid deleting moved entries under it.
if (it->second.deleted()) {
deleted_entries.push_back(ResourceEntry());
deleted_entries.back().Swap(&it->second);
entry_map_.erase(it);
continue;
}
// Start from entry_map_.begin() and traverse ancestors using the
// parent-child relashonships in the result (after this apply) tree.
// Then apply the topmost change first.
//
// By doing this, assuming the result tree does not contain any cycles, we
// can guarantee that no cycle is made during this apply (i.e. no entry gets
// moved under any of its descendants) because the following conditions are
// always satisfied in any move:
// - The new parent entry is not a descendant of the moved entry.
// - The new parent and its ancestors will no longer move during this apply.
std::vector<ResourceEntryMap::iterator> entries;
entries.push_back(entry_map_.begin());
for (std::string parent_id = entries.back()->second.parent_local_id();
!parent_id.empty();) {
ResourceEntryMap::iterator it_parent = entry_map_.find(parent_id);
if (it_parent != entry_map_.end()) {
// This entry is going to be updated, get the parent from the new data.
entries.push_back(it_parent);
parent_id = it_parent->second.parent_local_id();
} else {
// This entry is already updated or not going to be updated, get the
// parent from the current tree.
ResourceEntry parent_entry;
FileError error =
resource_metadata_->GetResourceEntryById(parent_id, &parent_entry);
if (error != FILE_ERROR_OK) {
LOG(ERROR) << "Cannot get the parent: " << FileErrorToString(error);
break;
}
parent_id = parent_entry.parent_local_id();
}
}
// Apply the parent first.
std::reverse(entries.begin(), entries.end());
for (size_t i = 0; i < entries.size(); ++i) {
ResourceEntryMap::iterator it = entries[i];
ApplyEntry(it->second);
entry_map_.erase(it);
}
}
// Apply deleted entries.
for (size_t i = 0; i < deleted_entries.size(); ++i)
ApplyEntry(deleted_entries[i]);
}
void ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
// Lookup the entry.
ResourceEntry existing_entry;
FileError error = resource_metadata_->GetResourceEntryById(
entry.resource_id(), &existing_entry);
if (error == FILE_ERROR_OK) {
if (entry.deleted()) {
// Deleted file/directory.
RemoveEntry(entry);
} else {
// Entry exists and needs to be refreshed.
RefreshEntry(entry);
}
} else if (error == FILE_ERROR_NOT_FOUND && !entry.deleted()) {
// Adding a new entry.
AddEntry(entry);
}
}
void ChangeListProcessor::AddEntry(const ResourceEntry& entry) {
FileError error = resource_metadata_->AddEntry(entry);
if (error == FILE_ERROR_OK)
UpdateChangedDirs(entry);
}
void ChangeListProcessor::RemoveEntry(const ResourceEntry& entry) {
resource_metadata_->RemoveEntry(entry.resource_id());
}
void ChangeListProcessor::RefreshEntry(const ResourceEntry& entry) {
FileError error =
resource_metadata_->RefreshEntry(entry.resource_id(), entry);
if (error == FILE_ERROR_OK)
UpdateChangedDirs(entry);
}
// static
void ChangeListProcessor::ConvertToMap(
ScopedVector<ChangeList> change_lists,
ResourceEntryMap* entry_map,
ChangeListToEntryMapUMAStats* uma_stats) {
for (size_t i = 0; i < change_lists.size(); ++i) {
ChangeList* change_list = change_lists[i];
std::vector<ResourceEntry>* entries = change_list->mutable_entries();
for (size_t i = 0; i < entries->size(); ++i) {
ResourceEntry* entry = &(*entries)[i];
// Some document entries don't map into files (i.e. sites).
if (entry->resource_id().empty())
continue;
// Count the number of files.
if (uma_stats) {
if (!entry->file_info().is_directory()) {
uma_stats->IncrementNumFiles(
entry->file_specific_info().is_hosted_document());
}
if (entry->shared_with_me())
uma_stats->IncrementNumSharedWithMeEntries();
}
(*entry_map)[entry->resource_id()].Swap(entry);
LOG_IF(WARNING, !entry->resource_id().empty())
<< "Found duplicated file: " << entry->base_name();
}
}
}
// static
FileError ChangeListProcessor::RefreshDirectory(
ResourceMetadata* resource_metadata,
const DirectoryFetchInfo& directory_fetch_info,
const ResourceEntryMap& entry_map,
base::FilePath* out_file_path) {
DCHECK(!directory_fetch_info.empty());
ResourceEntry directory;
FileError error = resource_metadata->GetResourceEntryById(
directory_fetch_info.resource_id(), &directory);
if (error != FILE_ERROR_OK)
return error;
if (!directory.file_info().is_directory())
return FILE_ERROR_NOT_A_DIRECTORY;
// Go through the entry map. Handle existing entries and new entries.
for (ResourceEntryMap::const_iterator it = entry_map.begin();
it != entry_map.end(); ++it) {
const ResourceEntry& entry = it->second;
// Skip if the parent resource ID does not match. This is needed to
// handle entries with multiple parents. For such entries, the first
// parent is picked and other parents are ignored, hence some entries may
// have a parent resource ID which does not match the target directory's.
if (entry.parent_local_id() != directory_fetch_info.resource_id()) {
DVLOG(1) << "Wrong-parent entry rejected: " << entry.resource_id();
continue;
}
error = resource_metadata->RefreshEntry(it->first, entry);
if (error == FILE_ERROR_NOT_FOUND) // If refreshing fails, try adding.
error = resource_metadata->AddEntry(entry);
if (error != FILE_ERROR_OK)
return error;
}
directory.mutable_directory_specific_info()->set_changestamp(
directory_fetch_info.changestamp());
error = resource_metadata->RefreshEntry(directory_fetch_info.resource_id(),
directory);
if (error != FILE_ERROR_OK)
return error;
*out_file_path = resource_metadata->GetFilePath(directory.resource_id());
return FILE_ERROR_OK;
}
void ChangeListProcessor::UpdateRootEntry(int64 largest_changestamp) {
std::string root_local_id;
FileError error = resource_metadata_->GetIdByPath(
util::GetDriveMyDriveRootPath(), &root_local_id);
ResourceEntry root;
if (error == FILE_ERROR_OK)
error = resource_metadata_->GetResourceEntryById(root_local_id, &root);
if (error != FILE_ERROR_OK) {
// TODO(satorux): Need to trigger recovery if root is corrupt.
LOG(WARNING) << "Failed to get the entry for root directory";
return;
}
// The changestamp should always be updated.
root.mutable_directory_specific_info()->set_changestamp(largest_changestamp);
error = resource_metadata_->RefreshEntry(root_local_id, root);
LOG_IF(WARNING, error != FILE_ERROR_OK) << "Failed to refresh root directory";
}
void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) {
base::FilePath file_path =
resource_metadata_->GetFilePath(entry.resource_id());
if (!file_path.empty()) {
// Notify parent.
changed_dirs_.insert(file_path.DirName());
if (entry.file_info().is_directory()) {
// Notify self if entry is a directory.
changed_dirs_.insert(file_path);
// Notify all descendants if it is a directory deletion.
if (entry.deleted()) {
std::set<base::FilePath> sub_directories;
resource_metadata_->GetSubDirectoriesRecursively(entry.resource_id(),
&sub_directories);
changed_dirs_.insert(sub_directories.begin(), sub_directories.end());
}
}
}
}
} // namespace internal
} // namespace drive