| // Copyright 2015 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/profiles/profile_statistics.h" | 
 |  | 
 | #include <set> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ref_counted.h" | 
 | #include "base/prefs/pref_service.h" | 
 | #include "base/task_runner.h" | 
 | #include "base/time/time.h" | 
 | #include "chrome/browser/bookmarks/bookmark_model_factory.h" | 
 | #include "chrome/browser/browser_process.h" | 
 | #include "chrome/browser/history/history_service_factory.h" | 
 | #include "chrome/browser/password_manager/password_store_factory.h" | 
 | #include "chrome/browser/profiles/profile_attributes_entry.h" | 
 | #include "chrome/browser/profiles/profile_attributes_storage.h" | 
 | #include "chrome/browser/profiles/profile_manager.h" | 
 | #include "components/bookmarks/browser/bookmark_model.h" | 
 | #include "components/bookmarks/browser/bookmark_model_observer.h" | 
 | #include "components/history/core/browser/history_service.h" | 
 | #include "components/password_manager/core/browser/password_store.h" | 
 | #include "components/password_manager/core/browser/password_store_consumer.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 |  | 
 | namespace { | 
 |  | 
 | struct ProfileStatValue { | 
 |   int count; | 
 |   bool success;  // false means the statistics failed to load | 
 | }; | 
 |  | 
 | int CountBookmarksFromNode(const bookmarks::BookmarkNode* node) { | 
 |   int count = 0; | 
 |   if (node->is_url()) { | 
 |     ++count; | 
 |   } else { | 
 |     for (int i = 0; i < node->child_count(); ++i) | 
 |       count += CountBookmarksFromNode(node->GetChild(i)); | 
 |   } | 
 |   return count; | 
 | } | 
 |  | 
 | class ProfileStatisticsAggregator | 
 |     : public base::RefCountedThreadSafe<ProfileStatisticsAggregator> { | 
 |   // This class is used internally by GetProfileStatistics and | 
 |   // StoreProfileStatisticsToCache. | 
 |   // | 
 |   // The class collects statistical information about the profile and returns | 
 |   // the information via a callback function. Currently bookmarks, history, | 
 |   // logins and preferences are counted. | 
 |   // | 
 |   // The class is RefCounted because this is needed for CancelableTaskTracker | 
 |   // to function properly. Once all tasks are run (or cancelled) the instance is | 
 |   // automatically destructed. | 
 |  | 
 |  public: | 
 |   ProfileStatisticsAggregator(Profile* profile, | 
 |       const profiles::ProfileStatisticsCallback& callback, | 
 |       base::CancelableTaskTracker* tracker); | 
 |  | 
 |  private: | 
 |   friend class base::RefCountedThreadSafe<ProfileStatisticsAggregator>; | 
 |   ~ProfileStatisticsAggregator() {} | 
 |  | 
 |   void Init(); | 
 |  | 
 |   // Callback functions | 
 |   // Normal callback. Appends result to |profile_category_stats_|, and then call | 
 |   // the external callback. All other callbacks call this function. | 
 |   void StatisticsCallback(const char* category, ProfileStatValue result); | 
 |   // Callback for reporting success. | 
 |   void StatisticsCallbackSuccess(const char* category, int count); | 
 |   // Callback for reporting failure. | 
 |   void StatisticsCallbackFailure(const char* category); | 
 |   // Callback for history. | 
 |   void StatisticsCallbackHistory(history::HistoryCountResult result); | 
 |  | 
 |   // Bookmark counting. | 
 |   void WaitOrCountBookmarks(); | 
 |   void CountBookmarks(bookmarks::BookmarkModel* bookmark_model); | 
 |  | 
 |   class BookmarkModelHelper | 
 |       : public bookmarks::BookmarkModelObserver { | 
 |    public: | 
 |     explicit BookmarkModelHelper(ProfileStatisticsAggregator* parent) | 
 |         : parent_(parent) {} | 
 |  | 
 |     void BookmarkModelLoaded(bookmarks::BookmarkModel* model, | 
 |                              bool ids_reassigned) | 
 |         override { | 
 |       // Remove observer before release, otherwise it may become a dangling | 
 |       // reference. | 
 |       model->RemoveObserver(this); | 
 |       parent_->CountBookmarks(model); | 
 |       parent_->Release(); | 
 |     } | 
 |  | 
 |     void BookmarkNodeMoved(bookmarks::BookmarkModel* model, | 
 |                            const bookmarks::BookmarkNode* old_parent, | 
 |                            int old_index, | 
 |                            const bookmarks::BookmarkNode* new_parent, | 
 |                            int new_index) override {} | 
 |  | 
 |     void BookmarkNodeAdded(bookmarks::BookmarkModel* model, | 
 |                            const bookmarks::BookmarkNode* parent, | 
 |                            int index) override {} | 
 |  | 
 |     void BookmarkNodeRemoved(bookmarks::BookmarkModel* model, | 
 |         const bookmarks::BookmarkNode* parent, | 
 |         int old_index, const bookmarks::BookmarkNode* node, | 
 |         const std::set<GURL>& no_longer_bookmarked) override {} | 
 |  | 
 |     void BookmarkNodeChanged(bookmarks::BookmarkModel* model, | 
 |         const bookmarks::BookmarkNode* node) override {} | 
 |  | 
 |     void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model, | 
 |         const bookmarks::BookmarkNode* node) override {} | 
 |  | 
 |     void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model, | 
 |         const bookmarks::BookmarkNode* node) override {} | 
 |  | 
 |     void BookmarkAllUserNodesRemoved(bookmarks::BookmarkModel* model, | 
 |         const std::set<GURL>& removed_urls) override {} | 
 |  | 
 |    private: | 
 |     ProfileStatisticsAggregator* parent_ = nullptr; | 
 |   }; | 
 |  | 
 |   // Password counting | 
 |   class PasswordStoreConsumerHelper | 
 |       : public password_manager::PasswordStoreConsumer { | 
 |    public: | 
 |     explicit PasswordStoreConsumerHelper(ProfileStatisticsAggregator* parent) | 
 |         : parent_(parent) {} | 
 |  | 
 |     void OnGetPasswordStoreResults( | 
 |         ScopedVector<autofill::PasswordForm> results) override { | 
 |       parent_->StatisticsCallbackSuccess(profiles::kProfileStatisticsPasswords, | 
 |                                          results.size()); | 
 |     } | 
 |  | 
 |    private: | 
 |     ProfileStatisticsAggregator* parent_ = nullptr; | 
 |  | 
 |     DISALLOW_COPY_AND_ASSIGN(PasswordStoreConsumerHelper); | 
 |   }; | 
 |  | 
 |   // Preference counting. | 
 |   ProfileStatValue CountPrefs() const; | 
 |  | 
 |   Profile* profile_; | 
 |   profiles::ProfileCategoryStats profile_category_stats_; | 
 |  | 
 |   // Callback function to be called when results arrive. Will be called | 
 |   // multiple times (once for each statistics). | 
 |   const profiles::ProfileStatisticsCallback callback_; | 
 |  | 
 |   base::CancelableTaskTracker* tracker_; | 
 |   scoped_ptr<base::CancelableTaskTracker> default_tracker_; | 
 |  | 
 |   // Bookmark counting | 
 |   scoped_ptr<BookmarkModelHelper> bookmark_model_helper_; | 
 |  | 
 |   // Password counting. | 
 |   PasswordStoreConsumerHelper password_store_consumer_helper_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(ProfileStatisticsAggregator); | 
 | }; | 
 |  | 
 | ProfileStatisticsAggregator::ProfileStatisticsAggregator( | 
 |     Profile* profile, | 
 |     const profiles::ProfileStatisticsCallback& callback, | 
 |     base::CancelableTaskTracker* tracker) | 
 |     : profile_(profile), | 
 |       callback_(callback), | 
 |       tracker_(tracker), | 
 |       password_store_consumer_helper_(this) { | 
 |   if (!tracker_) { | 
 |     default_tracker_.reset(new base::CancelableTaskTracker); | 
 |     tracker_ = default_tracker_.get(); | 
 |   } | 
 |   Init(); | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::Init() { | 
 |   DCHECK(profile_); | 
 |  | 
 |   // Initiate bookmark counting (async). Post to UI thread. | 
 |   tracker_->PostTask( | 
 |       content::BrowserThread::GetMessageLoopProxyForThread( | 
 |           content::BrowserThread::UI).get(), | 
 |       FROM_HERE, | 
 |       base::Bind(&ProfileStatisticsAggregator::WaitOrCountBookmarks, this)); | 
 |  | 
 |   // Initiate history counting (async). | 
 |   history::HistoryService* history_service = | 
 |       HistoryServiceFactory::GetForProfileWithoutCreating(profile_); | 
 |  | 
 |   if (history_service) { | 
 |     history_service->GetHistoryCount( | 
 |         base::Time(), | 
 |         base::Time::Max(), | 
 |         base::Bind(&ProfileStatisticsAggregator::StatisticsCallbackHistory, | 
 |                    this), | 
 |         tracker_); | 
 |   } else { | 
 |     StatisticsCallbackFailure(profiles::kProfileStatisticsBrowsingHistory); | 
 |   } | 
 |  | 
 |   // Initiate stored password counting (async). | 
 |   // TODO(anthonyvd): make password task cancellable. | 
 |   scoped_refptr<password_manager::PasswordStore> password_store = | 
 |       PasswordStoreFactory::GetForProfile( | 
 |           profile_, ServiceAccessType::EXPLICIT_ACCESS); | 
 |   if (password_store) { | 
 |     password_store->GetAutofillableLogins(&password_store_consumer_helper_); | 
 |   } else { | 
 |     StatisticsCallbackFailure(profiles::kProfileStatisticsPasswords); | 
 |   } | 
 |  | 
 |   // Initiate preference counting (async). Post to UI thread. | 
 |   tracker_->PostTaskAndReplyWithResult( | 
 |       content::BrowserThread::GetMessageLoopProxyForThread( | 
 |           content::BrowserThread::UI).get(), | 
 |       FROM_HERE, | 
 |       base::Bind(&ProfileStatisticsAggregator::CountPrefs, this), | 
 |       base::Bind(&ProfileStatisticsAggregator::StatisticsCallback, | 
 |                  this, profiles::kProfileStatisticsSettings)); | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::StatisticsCallback( | 
 |     const char* category, ProfileStatValue result) { | 
 |   profiles::ProfileCategoryStat datum; | 
 |   datum.category = category; | 
 |   datum.count = result.count; | 
 |   datum.success = result.success; | 
 |   profile_category_stats_.push_back(datum); | 
 |   if (!callback_.is_null()) | 
 |     callback_.Run(profile_category_stats_); | 
 |  | 
 |   if (result.success) { | 
 |     profiles::SetProfileStatisticsInCache(profile_->GetPath(), datum.category, | 
 |                                           result.count); | 
 |   } | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::StatisticsCallbackSuccess( | 
 |     const char* category, int count) { | 
 |   ProfileStatValue result; | 
 |   result.count = count; | 
 |   result.success = true; | 
 |   StatisticsCallback(category, result); | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::StatisticsCallbackFailure( | 
 |     const char* category) { | 
 |   ProfileStatValue result; | 
 |   result.count = 0; | 
 |   result.success = false; | 
 |   StatisticsCallback(category, result); | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::StatisticsCallbackHistory( | 
 |     history::HistoryCountResult result) { | 
 |   ProfileStatValue result_converted; | 
 |   result_converted.count = result.count; | 
 |   result_converted.success = result.success; | 
 |   StatisticsCallback(profiles::kProfileStatisticsBrowsingHistory, | 
 |                      result_converted); | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::CountBookmarks( | 
 |     bookmarks::BookmarkModel* bookmark_model) { | 
 |   int count = CountBookmarksFromNode(bookmark_model->bookmark_bar_node()) + | 
 |               CountBookmarksFromNode(bookmark_model->other_node()) + | 
 |               CountBookmarksFromNode(bookmark_model->mobile_node()); | 
 |  | 
 |   StatisticsCallbackSuccess(profiles::kProfileStatisticsBookmarks, count); | 
 | } | 
 |  | 
 | void ProfileStatisticsAggregator::WaitOrCountBookmarks() { | 
 |   bookmarks::BookmarkModel* bookmark_model = | 
 |       BookmarkModelFactory::GetForProfileIfExists(profile_); | 
 |  | 
 |   if (bookmark_model) { | 
 |     if (bookmark_model->loaded()) { | 
 |       CountBookmarks(bookmark_model); | 
 |     } else { | 
 |       AddRef(); | 
 |       bookmark_model_helper_.reset(new BookmarkModelHelper(this)); | 
 |       bookmark_model->AddObserver(bookmark_model_helper_.get()); | 
 |     } | 
 |   } else { | 
 |     StatisticsCallbackFailure(profiles::kProfileStatisticsBookmarks); | 
 |   } | 
 | } | 
 |  | 
 | ProfileStatValue ProfileStatisticsAggregator::CountPrefs() const { | 
 |   const PrefService* pref_service = profile_->GetPrefs(); | 
 |  | 
 |   ProfileStatValue result; | 
 |   if (pref_service) { | 
 |     scoped_ptr<base::DictionaryValue> prefs = | 
 |         pref_service->GetPreferenceValuesWithoutPathExpansion(); | 
 |  | 
 |     int count = 0; | 
 |     for (base::DictionaryValue::Iterator it(*(prefs.get())); | 
 |          !it.IsAtEnd(); it.Advance()) { | 
 |       const PrefService::Preference* pref = pref_service-> | 
 |                                                 FindPreference(it.key()); | 
 |       // Skip all dictionaries (which must be empty by the function call above). | 
 |       if (it.value().GetType() != base::Value::TYPE_DICTIONARY && | 
 |         pref && pref->IsUserControlled() && !pref->IsDefaultValue()) { | 
 |         ++count; | 
 |       } | 
 |     } | 
 |  | 
 |     result.count = count; | 
 |     result.success = true; | 
 |   } else { | 
 |     result.count = 0; | 
 |     result.success = false; | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace profiles { | 
 |  | 
 | // Constants for the categories in ProfileCategoryStats | 
 | const char kProfileStatisticsBrowsingHistory[] = "BrowsingHistory"; | 
 | const char kProfileStatisticsPasswords[] = "Passwords"; | 
 | const char kProfileStatisticsBookmarks[] = "Bookmarks"; | 
 | const char kProfileStatisticsSettings[] = "Settings"; | 
 |  | 
 | void GatherProfileStatistics(Profile* profile, | 
 |                              const ProfileStatisticsCallback& callback, | 
 |                              base::CancelableTaskTracker* tracker) { | 
 |   DCHECK(profile); | 
 |   if (profile->IsOffTheRecord() || profile->IsSystemProfile()) { | 
 |     NOTREACHED(); | 
 |     return; | 
 |   } | 
 |  | 
 |   scoped_refptr<ProfileStatisticsAggregator> aggregator = | 
 |       new ProfileStatisticsAggregator(profile, callback, tracker); | 
 | } | 
 |  | 
 | ProfileCategoryStats GetProfileStatisticsFromCache( | 
 |     const base::FilePath& profile_path) { | 
 |   ProfileAttributesEntry* entry = nullptr; | 
 |   bool has_entry = g_browser_process->profile_manager()-> | 
 |       GetProfileAttributesStorage(). | 
 |       GetProfileAttributesWithPath(profile_path, &entry); | 
 |  | 
 |   ProfileCategoryStats stats; | 
 |   ProfileCategoryStat stat; | 
 |  | 
 |   stat.category = kProfileStatisticsBrowsingHistory; | 
 |   stat.success = has_entry ? entry->HasStatsBrowsingHistory() : false; | 
 |   stat.count = stat.success ? entry->GetStatsBrowsingHistory() : 0; | 
 |   stats.push_back(stat); | 
 |  | 
 |   stat.category = kProfileStatisticsPasswords; | 
 |   stat.success = has_entry ? entry->HasStatsPasswords() : false; | 
 |   stat.count = stat.success ? entry->GetStatsPasswords() : 0; | 
 |   stats.push_back(stat); | 
 |  | 
 |   stat.category = kProfileStatisticsBookmarks; | 
 |   stat.success = has_entry ? entry->HasStatsBookmarks() : false; | 
 |   stat.count = stat.success ? entry->GetStatsBookmarks() : 0; | 
 |   stats.push_back(stat); | 
 |  | 
 |   stat.category = kProfileStatisticsSettings; | 
 |   stat.success = has_entry ? entry->HasStatsSettings() : false; | 
 |   stat.count = stat.success ? entry->GetStatsSettings() : 0; | 
 |   stats.push_back(stat); | 
 |  | 
 |   return stats; | 
 | } | 
 |  | 
 | void SetProfileStatisticsInCache(const base::FilePath& profile_path, | 
 |                                  const std::string& category, int count) { | 
 |   // If local_state() is null, profile_manager() will seg-fault. | 
 |   if (!g_browser_process || !g_browser_process->local_state()) | 
 |     return; | 
 |  | 
 |   // profile_manager() may return a null pointer. | 
 |   ProfileManager* profile_manager = g_browser_process->profile_manager(); | 
 |   if (!profile_manager) | 
 |     return; | 
 |  | 
 |   ProfileAttributesEntry* entry = nullptr; | 
 |   if (!profile_manager->GetProfileAttributesStorage(). | 
 |       GetProfileAttributesWithPath(profile_path, &entry)) | 
 |     return; | 
 |  | 
 |   if (category == kProfileStatisticsBrowsingHistory) { | 
 |     entry->SetStatsBrowsingHistory(count); | 
 |   } else if (category == kProfileStatisticsPasswords) { | 
 |     entry->SetStatsPasswords(count); | 
 |   } else if (category == kProfileStatisticsBookmarks) { | 
 |     entry->SetStatsBookmarks(count); | 
 |   } else if (category == kProfileStatisticsSettings) { | 
 |     entry->SetStatsSettings(count); | 
 |   } else { | 
 |     NOTREACHED(); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace profiles |