// 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 #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& feed_list, int64 start_changestamp, int64 root_feed_changestamp, std::set* 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* 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 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 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* 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* 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& 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::const_iterator iter = feed->entries().begin(); iter != feed->entries().end(); ++iter) { DocumentEntry* doc = *iter; scoped_ptr 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