#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_change.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"
+#include "chrome/browser/drive/drive_api_util.h"
+#include "google_apis/drive/drive_api_parser.h"
namespace drive {
namespace internal {
int num_shared_with_me_entries_;
};
+// Returns true if it's OK to overwrite the local entry with the remote one.
+bool ShouldApplyChange(const ResourceEntry& local_entry,
+ const ResourceEntry& remote_entry) {
+ if (local_entry.metadata_edit_state() == ResourceEntry::CLEAN)
+ return true;
+ return base::Time::FromInternalValue(remote_entry.modification_date()) >
+ base::Time::FromInternalValue(local_entry.modification_date());
+}
+
} // namespace
std::string DirectoryFetchInfo::ToString() const {
- return ("resource_id: " + resource_id_ +
+ return ("local_id: " + local_id_ +
+ ", resource_id: " + resource_id_ +
", changestamp: " + base::Int64ToString(changestamp_));
}
ChangeList::ChangeList() {}
-ChangeList::ChangeList(const google_apis::ResourceList& resource_list)
- : largest_changestamp_(resource_list.largest_changestamp()) {
- resource_list.GetNextFeedURL(&next_url_);
+ChangeList::ChangeList(const google_apis::ChangeList& change_list)
+ : next_url_(change_list.next_link()),
+ largest_changestamp_(change_list.largest_change_id()) {
+ const ScopedVector<google_apis::ChangeResource>& items = change_list.items();
+ entries_.resize(items.size());
+ parent_resource_ids_.resize(items.size());
+ size_t entries_index = 0;
+ for (size_t i = 0; i < items.size(); ++i) {
+ if (ConvertChangeResourceToResourceEntry(
+ *items[i],
+ &entries_[entries_index],
+ &parent_resource_ids_[entries_index])) {
+ ++entries_index;
+ }
+ }
+ entries_.resize(entries_index);
+ parent_resource_ids_.resize(entries_index);
+}
- entries_.resize(resource_list.entries().size());
- parent_resource_ids_.resize(resource_list.entries().size());
+ChangeList::ChangeList(const google_apis::FileList& file_list)
+ : next_url_(file_list.next_link()),
+ largest_changestamp_(0) {
+ const ScopedVector<google_apis::FileResource>& items = file_list.items();
+ entries_.resize(items.size());
+ parent_resource_ids_.resize(items.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],
- &parent_resource_ids_[entries_index]))
+ for (size_t i = 0; i < items.size(); ++i) {
+ if (ConvertFileResourceToResourceEntry(
+ *items[i],
+ &entries_[entries_index],
+ &parent_resource_ids_[entries_index])) {
++entries_index;
+ }
}
entries_.resize(entries_index);
parent_resource_ids_.resize(entries_index);
ChangeList::~ChangeList() {}
ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata)
- : resource_metadata_(resource_metadata) {
+ : resource_metadata_(resource_metadata), changed_files_(new FileChange) {
}
ChangeListProcessor::~ChangeListProcessor() {
}
}
- FileError error = ApplyEntryMap(is_delta_update,
- largest_changestamp,
- about_resource.Pass());
+ FileError error = ApplyEntryMap(largest_changestamp, about_resource.Pass());
if (error != FILE_ERROR_OK) {
DLOG(ERROR) << "ApplyEntryMap failed: " << FileErrorToString(error);
return error;
}
FileError ChangeListProcessor::ApplyEntryMap(
- bool is_delta_update,
int64 changestamp,
scoped_ptr<google_apis::AboutResource> about_resource) {
DCHECK(about_resource);
ResourceEntry root;
FileError error = resource_metadata_->GetResourceEntryByPath(
util::GetDriveMyDriveRootPath(), &root);
- switch (error) {
- case FILE_ERROR_OK: // Refresh the existing My Drive root entry.
- root.mutable_directory_specific_info()->set_changestamp(changestamp);
- error = resource_metadata_->RefreshEntry(root);
- break;
- case FILE_ERROR_NOT_FOUND: { // Add the My Drive root directory.
- changed_dirs_.insert(util::GetDriveGrandRootPath());
- changed_dirs_.insert(util::GetDriveMyDriveRootPath());
-
- root = util::CreateMyDriveRootEntry(about_resource->root_folder_id());
- root.mutable_directory_specific_info()->set_changestamp(changestamp);
- std::string local_id;
- error = resource_metadata_->AddEntry(root, &local_id);
- break;
- }
- default:
- break;
+ if (error != FILE_ERROR_OK) {
+ LOG(ERROR) << "Failed to get root entry: " << FileErrorToString(error);
+ return error;
}
+
+ root.mutable_directory_specific_info()->set_changestamp(changestamp);
+ root.set_resource_id(about_resource->root_folder_id());
+ error = resource_metadata_->RefreshEntry(root);
if (error != FILE_ERROR_OK) {
LOG(ERROR) << "Failed to update root entry: " << FileErrorToString(error);
return error;
}
// Apply all entries except deleted ones to the metadata.
- std::vector<ResourceEntry> deleted_entries;
- deleted_entries.reserve(entry_map_.size());
+ std::vector<std::string> deleted_resource_ids;
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);
+ deleted_resource_ids.push_back(it->first);
entry_map_.erase(it);
continue;
}
it != entry_map_.end();) {
entries.push_back(it);
+ DCHECK(parent_resource_id_map_.count(it->first)) << it->first;
const std::string& parent_resource_id =
parent_resource_id_map_[it->first];
- DCHECK(!parent_resource_id.empty()) << it->first;
+
+ if (parent_resource_id.empty()) // This entry has no parent.
+ break;
ResourceEntryMap::iterator it_parent =
entry_map_.find(parent_resource_id);
FileError error = resource_metadata_->GetIdByResourceId(
parent_resource_id, &parent_local_id);
if (error != FILE_ERROR_OK) {
- LOG(ERROR) << "Failed to get local ID: " << parent_resource_id
- << ", error = " << FileErrorToString(error);
+ // See crbug.com/326043. In some complicated situations, parent folder
+ // for shared entries may be accessible (and hence its resource id is
+ // included), but not in the change/file list.
+ // In such a case, clear the parent and move it to drive/other.
+ if (error == FILE_ERROR_NOT_FOUND) {
+ parent_resource_id_map_[it->first] = "";
+ } else {
+ LOG(ERROR) << "Failed to get local ID: " << parent_resource_id
+ << ", error = " << FileErrorToString(error);
+ }
break;
}
ResourceEntry parent_entry;
// root entry, but we should better be defensive (see crbug.com/297259).
ResourceEntryMap::iterator it = entries[i];
if (it->first != root.resource_id()) {
- // TODO(hashimoto): Handle ApplyEntry errors correctly.
FileError error = ApplyEntry(it->second);
- DLOG_IF(WARNING, error != FILE_ERROR_OK)
- << "ApplyEntry failed: " << FileErrorToString(error)
- << ", title = " << it->second.title();
+ if (error != FILE_ERROR_OK) {
+ LOG(ERROR) << "ApplyEntry failed: " << FileErrorToString(error)
+ << ", title = " << it->second.title();
+ return error;
+ }
}
entry_map_.erase(it);
}
}
// Apply deleted entries.
- for (size_t i = 0; i < deleted_entries.size(); ++i) {
- // TODO(hashimoto): Handle ApplyEntry errors correctly.
- FileError error = ApplyEntry(deleted_entries[i]);
- DLOG_IF(WARNING, error != FILE_ERROR_OK)
- << "ApplyEntry failed: " << FileErrorToString(error)
- << ", title = " << deleted_entries[i].title();
+ for (size_t i = 0; i < deleted_resource_ids.size(); ++i) {
+ std::string local_id;
+ FileError error = resource_metadata_->GetIdByResourceId(
+ deleted_resource_ids[i], &local_id);
+ switch (error) {
+ case FILE_ERROR_OK:
+ error = resource_metadata_->RemoveEntry(local_id);
+ break;
+ case FILE_ERROR_NOT_FOUND:
+ error = FILE_ERROR_OK;
+ break;
+ default:
+ break;
+ }
+ if (error != FILE_ERROR_OK) {
+ LOG(ERROR) << "Failed to delete: " << FileErrorToString(error)
+ << ", resource_id = " << deleted_resource_ids[i];
+ return error;
+ }
}
return FILE_ERROR_OK;
}
FileError ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
+ DCHECK(!entry.deleted());
+ DCHECK(parent_resource_id_map_.count(entry.resource_id()));
+ const std::string& parent_resource_id =
+ parent_resource_id_map_[entry.resource_id()];
+
+ ResourceEntry new_entry(entry);
+ FileError error = SetParentLocalIdOfEntry(resource_metadata_, &new_entry,
+ parent_resource_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
// Lookup the entry.
std::string local_id;
- FileError error = resource_metadata_->GetIdByResourceId(entry.resource_id(),
- &local_id);
+ error = resource_metadata_->GetIdByResourceId(entry.resource_id(), &local_id);
+
ResourceEntry existing_entry;
if (error == FILE_ERROR_OK)
error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry);
- const FileError get_existing_entry_result = error;
- if (entry.deleted()) {
- // Deleted file/directory.
- switch (get_existing_entry_result) {
- case FILE_ERROR_OK:
- error = resource_metadata_->RemoveEntry(local_id);
- break;
- case FILE_ERROR_NOT_FOUND: // Already deleted.
- error = FILE_ERROR_OK;
- break;
- default:
- error = get_existing_entry_result;
- }
- } else {
- const std::string& parent_resource_id =
- parent_resource_id_map_[entry.resource_id()];
- DCHECK(!parent_resource_id.empty()) << entry.resource_id();
-
- std::string parent_local_id;
- error = resource_metadata_->GetIdByResourceId(
- parent_resource_id, &parent_local_id);
- if (error == FILE_ERROR_OK) {
- ResourceEntry new_entry(entry);
- new_entry.set_parent_local_id(parent_local_id);
-
- switch (get_existing_entry_result) {
- case FILE_ERROR_OK: // Entry exists and needs to be refreshed.
- new_entry.set_local_id(local_id);
+ switch (error) {
+ case FILE_ERROR_OK:
+ if (ShouldApplyChange(existing_entry, new_entry)) {
+ // Entry exists and needs to be refreshed.
+ new_entry.set_local_id(local_id);
+ error = resource_metadata_->RefreshEntry(new_entry);
+ } else {
+ if (entry.file_info().is_directory()) {
+ // No need to refresh, but update the changestamp.
+ new_entry = existing_entry;
+ new_entry.mutable_directory_specific_info()->set_changestamp(
+ new_entry.directory_specific_info().changestamp());
error = resource_metadata_->RefreshEntry(new_entry);
- break;
- case FILE_ERROR_NOT_FOUND: { // Adding a new entry.
- std::string local_id;
- error = resource_metadata_->AddEntry(new_entry, &local_id);
- break;
}
- default:
- error = get_existing_entry_result;
+ DVLOG(1) << "Change was discarded for: " << entry.resource_id();
}
-
- if (error == FILE_ERROR_OK)
- UpdateChangedDirs(entry);
+ break;
+ case FILE_ERROR_NOT_FOUND: { // Adding a new entry.
+ std::string local_id;
+ error = resource_metadata_->AddEntry(new_entry, &local_id);
+ break;
}
+ default:
+ return error;
}
+ if (error != FILE_ERROR_OK)
+ return error;
- return error;
+ UpdateChangedDirs(entry);
+ return FILE_ERROR_OK;
}
// static
FileError ChangeListProcessor::RefreshDirectory(
ResourceMetadata* resource_metadata,
const DirectoryFetchInfo& directory_fetch_info,
- ScopedVector<ChangeList> change_lists,
- base::FilePath* out_file_path) {
+ scoped_ptr<ChangeList> change_list,
+ std::vector<ResourceEntry>* out_refreshed_entries) {
DCHECK(!directory_fetch_info.empty());
- std::string directory_local_id;
- FileError error = resource_metadata->GetIdByResourceId(
- directory_fetch_info.resource_id(), &directory_local_id);
- if (error != FILE_ERROR_OK)
- return error;
-
ResourceEntry directory;
- error = resource_metadata->GetResourceEntryById(directory_local_id,
- &directory);
+ FileError error = resource_metadata->GetResourceEntryById(
+ directory_fetch_info.local_id(), &directory);
if (error != FILE_ERROR_OK)
return error;
if (!directory.file_info().is_directory())
return FILE_ERROR_NOT_A_DIRECTORY;
- 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];
- const std::string& parent_resource_id =
- change_list->parent_resource_ids()[i];
+ std::vector<ResourceEntry>* entries = change_list->mutable_entries();
+ for (size_t i = 0; i < entries->size(); ++i) {
+ ResourceEntry* entry = &(*entries)[i];
+ const std::string& parent_resource_id =
+ change_list->parent_resource_ids()[i];
+
+ // 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 (parent_resource_id != directory_fetch_info.resource_id()) {
+ DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id();
+ continue;
+ }
- // 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 (parent_resource_id != directory_fetch_info.resource_id()) {
- DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id();
- continue;
- }
+ entry->set_parent_local_id(directory_fetch_info.local_id());
- entry->set_parent_local_id(directory_local_id);
+ std::string local_id;
+ error = resource_metadata->GetIdByResourceId(entry->resource_id(),
+ &local_id);
+ if (error == FILE_ERROR_OK) {
+ entry->set_local_id(local_id);
+ error = resource_metadata->RefreshEntry(*entry);
+ }
- std::string local_id;
- error = resource_metadata->GetIdByResourceId(entry->resource_id(),
- &local_id);
- if (error == FILE_ERROR_OK) {
- entry->set_local_id(local_id);
- error = resource_metadata->RefreshEntry(*entry);
- }
+ if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding.
+ entry->clear_local_id();
+ error = resource_metadata->AddEntry(*entry, &local_id);
+ }
- if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding.
- std::string local_id;
- entry->clear_local_id();
- error = resource_metadata->AddEntry(*entry, &local_id);
- }
+ if (error != FILE_ERROR_OK)
+ return error;
- if (error != FILE_ERROR_OK)
- return error;
- }
+ ResourceEntry result_entry;
+ error = resource_metadata->GetResourceEntryById(local_id, &result_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ out_refreshed_entries->push_back(result_entry);
}
+ return FILE_ERROR_OK;
+}
- directory.mutable_directory_specific_info()->set_changestamp(
- directory_fetch_info.changestamp());
- error = resource_metadata->RefreshEntry(directory);
- if (error != FILE_ERROR_OK)
- return error;
-
- *out_file_path = resource_metadata->GetFilePath(directory_local_id);
+// static
+FileError ChangeListProcessor::SetParentLocalIdOfEntry(
+ ResourceMetadata* resource_metadata,
+ ResourceEntry* entry,
+ const std::string& parent_resource_id) {
+ std::string parent_local_id;
+ if (parent_resource_id.empty()) {
+ // Entries without parents should go under "other" directory.
+ parent_local_id = util::kDriveOtherDirLocalId;
+ } else {
+ FileError error = resource_metadata->GetIdByResourceId(
+ parent_resource_id, &parent_local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+ entry->set_parent_local_id(parent_local_id);
return FILE_ERROR_OK;
}
base::FilePath file_path;
if (resource_metadata_->GetIdByResourceId(
entry.resource_id(), &local_id) == FILE_ERROR_OK)
- file_path = resource_metadata_->GetFilePath(local_id);
+ resource_metadata_->GetFilePath(local_id, &file_path);
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(local_id,
- &sub_directories);
- changed_dirs_.insert(sub_directories.begin(), sub_directories.end());
- }
- }
+ FileChange::ChangeType type =
+ entry.deleted() ? FileChange::DELETE : FileChange::ADD_OR_UPDATE;
+ changed_files_->Update(file_path, entry, type);
}
}