blob: 423d581e5adeed049a80bce6daa608521638b574 [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 <utility>
#include "base/metrics/histogram.h"
#include "chrome/browser/chromeos/gdata/drive_resource_metadata.h"
#include "chrome/browser/chromeos/gdata/drive_files.h"
#include "chrome/browser/chromeos/gdata/gdata_wapi_feed_processor.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace gdata {
FeedToFileResourceMapUmaStats::FeedToFileResourceMapUmaStats()
: num_regular_files(0),
num_hosted_documents(0) {
}
FeedToFileResourceMapUmaStats::~FeedToFileResourceMapUmaStats() {
}
GDataWapiFeedProcessor::GDataWapiFeedProcessor(
DriveResourceMetadata* resource_metadata)
: resource_metadata_(resource_metadata) {
}
GDataWapiFeedProcessor::~GDataWapiFeedProcessor() {
}
DriveFileError GDataWapiFeedProcessor::ApplyFeeds(
const ScopedVector<DocumentFeed>& feed_list,
int64 start_changestamp,
int64 root_feed_changestamp,
std::set<FilePath>* changed_dirs) {
bool is_delta_feed = start_changestamp != 0;
resource_metadata_->set_origin(FROM_SERVER);
int64 delta_feed_changestamp = 0;
FeedToFileResourceMapUmaStats uma_stats;
FileResourceIdMap file_map;
DriveFileError error = FeedToFileResourceMap(feed_list,
&file_map,
&delta_feed_changestamp,
&uma_stats);
if (error != DRIVE_FILE_OK)
return error;
ApplyFeedFromFileUrlMap(
is_delta_feed,
is_delta_feed ? delta_feed_changestamp : root_feed_changestamp,
&file_map,
changed_dirs);
// Shouldn't record histograms when processing delta feeds.
if (!is_delta_feed)
UpdateFileCountUmaHistograms(uma_stats);
return DRIVE_FILE_OK;
}
void GDataWapiFeedProcessor::UpdateFileCountUmaHistograms(
const FeedToFileResourceMapUmaStats& uma_stats) const {
const int num_total_files =
uma_stats.num_hosted_documents + uma_stats.num_regular_files;
UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles",
uma_stats.num_regular_files);
UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
uma_stats.num_hosted_documents);
UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
for (FeedToFileResourceMapUmaStats::EntryKindToCountMap::const_iterator iter =
uma_stats.num_files_with_entry_kind.begin();
iter != uma_stats.num_files_with_entry_kind.end();
++iter) {
const DriveEntryKind kind = iter->first;
const int count = iter->second;
for (int i = 0; i < count; ++i) {
UMA_HISTOGRAM_ENUMERATION(
"Drive.EntryKind", kind, ENTRY_KIND_MAX_VALUE);
}
}
for (FeedToFileResourceMapUmaStats::FileFormatToCountMap::const_iterator
iter = uma_stats.num_files_with_file_format.begin();
iter != uma_stats.num_files_with_file_format.end();
++iter) {
const DriveFileFormat format = iter->first;
const int count = iter->second;
for (int i = 0; i < count; ++i) {
UMA_HISTOGRAM_ENUMERATION(
"Drive.FileFormat", format, FILE_FORMAT_MAX_VALUE);
}
}
}
void GDataWapiFeedProcessor::ApplyFeedFromFileUrlMap(
bool is_delta_feed,
int64 feed_changestamp,
FileResourceIdMap* file_map,
std::set<FilePath>* changed_dirs) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(changed_dirs);
if (!is_delta_feed) { // Full update.
resource_metadata_->root()->RemoveChildren();
changed_dirs->insert(resource_metadata_->root()->GetFilePath());
}
resource_metadata_->set_largest_changestamp(feed_changestamp);
scoped_ptr<DriveResourceMetadata> orphaned_resources(
new DriveResourceMetadata);
// Go through all entries generated by the feed and apply them to the local
// snapshot of the file system.
for (FileResourceIdMap::iterator it = file_map->begin();
it != file_map->end();) {
// Ensure that the entry is deleted, unless the ownership is explicitly
// transferred by entry.release().
scoped_ptr<DriveEntry> entry(it->second);
DCHECK_EQ(it->first, entry->resource_id());
// Erase the entry so the deleted entry won't be referenced.
file_map->erase(it++);
DriveEntry* old_entry =
resource_metadata_->GetEntryByResourceId(entry->resource_id());
DriveDirectory* dest_dir = NULL;
if (entry->is_deleted()) { // Deleted file/directory.
DVLOG(1) << "Removing file " << entry->base_name();
if (!old_entry)
continue;
dest_dir = old_entry->parent();
if (!dest_dir) {
NOTREACHED();
continue;
}
RemoveEntryFromDirectoryAndCollectChangedDirectories(
dest_dir, old_entry, changed_dirs);
} else if (old_entry) { // Change or move of existing entry.
// Please note that entry rename is just a special case of change here
// since name is just one of the properties that can change.
DVLOG(1) << "Changed file " << entry->base_name();
dest_dir = old_entry->parent();
if (!dest_dir) {
NOTREACHED();
continue;
}
// Move children files over if we are dealing with directories.
if (old_entry->AsDriveDirectory() && entry->AsDriveDirectory()) {
entry->AsDriveDirectory()->TakeOverEntries(
old_entry->AsDriveDirectory());
}
// Remove the old instance of this entry.
RemoveEntryFromDirectoryAndCollectChangedDirectories(
dest_dir, old_entry, changed_dirs);
// Did we actually move the new file to another directory?
if (dest_dir->resource_id() != entry->parent_resource_id()) {
changed_dirs->insert(dest_dir->GetFilePath());
dest_dir = FindDirectoryForNewEntry(entry.get(),
*file_map,
orphaned_resources.get());
}
DCHECK(dest_dir);
AddEntryToDirectoryAndCollectChangedDirectories(
entry.release(),
dest_dir,
orphaned_resources.get(),
changed_dirs);
} else { // Adding a new file.
dest_dir = FindDirectoryForNewEntry(entry.get(),
*file_map,
orphaned_resources.get());
DCHECK(dest_dir);
AddEntryToDirectoryAndCollectChangedDirectories(
entry.release(),
dest_dir,
orphaned_resources.get(),
changed_dirs);
}
// Record changed directory if this was a delta feed and the parent
// directory is already properly rooted within its parent.
if (dest_dir && (dest_dir->parent() ||
dest_dir == resource_metadata_->root()) &&
dest_dir != orphaned_resources->root() && is_delta_feed) {
changed_dirs->insert(dest_dir->GetFilePath());
}
}
// All entry must be erased from the map.
DCHECK(file_map->empty());
}
// static
void GDataWapiFeedProcessor::AddEntryToDirectoryAndCollectChangedDirectories(
DriveEntry* entry,
DriveDirectory* directory,
DriveResourceMetadata* orphaned_resources,
std::set<FilePath>* changed_dirs) {
directory->AddEntry(entry);
if (entry->AsDriveDirectory() && directory != orphaned_resources->root())
changed_dirs->insert(entry->GetFilePath());
}
// static
void GDataWapiFeedProcessor::
RemoveEntryFromDirectoryAndCollectChangedDirectories(
DriveDirectory* directory,
DriveEntry* entry,
std::set<FilePath>* changed_dirs) {
// Get the list of all sub-directory paths, so we can notify their listeners
// that they are smoked.
DriveDirectory* dir = entry->AsDriveDirectory();
if (dir)
dir->GetChildDirectoryPaths(changed_dirs);
directory->RemoveEntry(entry);
}
DriveDirectory* GDataWapiFeedProcessor::FindDirectoryForNewEntry(
DriveEntry* new_entry,
const FileResourceIdMap& file_map,
DriveResourceMetadata* orphaned_resources) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DriveDirectory* dir = NULL;
// Added file.
const std::string& parent_id = new_entry->parent_resource_id();
if (parent_id.empty()) {
dir = resource_metadata_->root();
DVLOG(1) << "Root parent for " << new_entry->base_name();
} else {
DriveEntry* entry = resource_metadata_->GetEntryByResourceId(parent_id);
dir = entry ? entry->AsDriveDirectory() : NULL;
if (!dir) {
// The parent directory was also added with this set of feeds.
FileResourceIdMap::const_iterator find_iter =
file_map.find(parent_id);
dir = (find_iter != file_map.end() &&
find_iter->second) ?
find_iter->second->AsDriveDirectory() : NULL;
if (dir) {
DVLOG(1) << "Found parent for " << new_entry->base_name()
<< " in file_map " << parent_id;
} else {
DVLOG(1) << "Adding orphan " << new_entry->GetFilePath().value();
dir = orphaned_resources->root();
}
}
}
return dir;
}
DriveFileError GDataWapiFeedProcessor::FeedToFileResourceMap(
const ScopedVector<DocumentFeed>& feed_list,
FileResourceIdMap* file_map,
int64* feed_changestamp,
FeedToFileResourceMapUmaStats* uma_stats) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(uma_stats);
DriveFileError error = DRIVE_FILE_OK;
uma_stats->num_regular_files = 0;
uma_stats->num_hosted_documents = 0;
uma_stats->num_files_with_entry_kind.clear();
uma_stats->num_files_with_file_format.clear();
for (size_t i = 0; i < feed_list.size(); ++i) {
const DocumentFeed* feed = feed_list[i];
// Get upload url from the root feed. Links for all other collections will
// be handled in GDatadirectory::FromDocumentEntry();
if (i == 0) {
const Link* root_feed_upload_link =
feed->GetLinkByType(Link::LINK_RESUMABLE_CREATE_MEDIA);
if (root_feed_upload_link)
resource_metadata_->root()->set_upload_url(
root_feed_upload_link->href());
*feed_changestamp = feed->largest_changestamp();
DCHECK_GE(*feed_changestamp, 0);
}
for (ScopedVector<DocumentEntry>::const_iterator iter =
feed->entries().begin();
iter != feed->entries().end(); ++iter) {
DocumentEntry* doc = *iter;
scoped_ptr<DriveEntry> entry =
resource_metadata_->FromDocumentEntry(*doc);
// Some document entries don't map into files (i.e. sites).
if (!entry.get())
continue;
// Count the number of files.
DriveFile* as_file = entry->AsDriveFile();
if (as_file) {
if (as_file->is_hosted_document()) {
++uma_stats->num_hosted_documents;
} else {
++uma_stats->num_regular_files;
const FilePath::StringType extension =
FilePath(as_file->base_name()).Extension();
const DriveFileFormat format = GetDriveFileFormat(extension);
++uma_stats->num_files_with_file_format[format];
}
++uma_stats->num_files_with_entry_kind[as_file->kind()];
}
FileResourceIdMap::iterator map_entry =
file_map->find(entry->resource_id());
// An entry with the same self link may already exist, so we need to
// release the existing DriveEntry instance before overwriting the
// entry with another DriveEntry instance.
if (map_entry != file_map->end()) {
LOG(WARNING) << "Found duplicate file "
<< map_entry->second->base_name();
delete map_entry->second;
file_map->erase(map_entry);
}
// Must use this temporary because entry.release() may be evaluated
// first in make_pair.
const std::string& resource_id = entry->resource_id();
file_map->insert(std::make_pair(resource_id, entry.release()));
}
}
if (error != DRIVE_FILE_OK) {
// If the code above fails to parse a feed, any DriveEntry instance
// added to |file_by_url| is not managed by a DriveDirectory instance,
// so we need to explicitly release them here.
STLDeleteValues(file_map);
}
return error;
}
} // namespace gdata