Index: ios/chrome/app/spotlight/topsites_spotlight_manager.mm |
diff --git a/ios/chrome/app/spotlight/topsites_spotlight_manager.mm b/ios/chrome/app/spotlight/topsites_spotlight_manager.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..33bb23153619d9bbcea0afaefdae94457613a9af |
--- /dev/null |
+++ b/ios/chrome/app/spotlight/topsites_spotlight_manager.mm |
@@ -0,0 +1,288 @@ |
+// 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. |
+ |
+#import "ios/chrome/app/spotlight/topsites_spotlight_manager.h" |
+ |
+#include <memory> |
+ |
+#include "base/ios/weak_nsobject.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/bookmarks/browser/bookmark_model.h" |
+#include "components/browser_sync/profile_sync_service.h" |
+#include "components/history/core/browser/history_types.h" |
+#include "components/history/core/browser/top_sites.h" |
+#include "components/history/core/browser/top_sites_observer.h" |
+#include "components/suggestions/suggestions_service.h" |
+#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" |
+#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h" |
+#include "ios/chrome/browser/history/top_sites_factory.h" |
+#include "ios/chrome/browser/suggestions/suggestions_service_factory.h" |
+#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h" |
+#include "ios/chrome/browser/sync/sync_observer_bridge.h" |
+#include "ios/chrome/browser/ui/ntp/google_landing_controller.h" |
+ |
+class SpotlightTopSitesBridge; |
+class SpotlightTopSitesCallbackBridge; |
+class SpotlightSuggestionsBridge; |
+ |
+@interface TopSitesSpotlightManager ()<SyncObserverModelBridge> { |
+ // Bridge to register for top sites changes. It's important that this instance |
+ // variable is released before the _topSite one. |
+ std::unique_ptr<SpotlightTopSitesBridge> _topSitesBridge; |
+ |
+ // Bridge to register for top sites callbacks. |
+ std::unique_ptr<SpotlightTopSitesCallbackBridge> _topSitesCallbackBridge; |
+ |
+ // Bridge to register for sync changes. |
+ std::unique_ptr<SyncObserverBridge> sync_observer_bridge_; |
+ |
+ // Bridge to register for suggestion changes. |
+ std::unique_ptr<SpotlightSuggestionsBridge> _suggestionsBridge; |
+ |
+ bookmarks::BookmarkModel* _bookmarkModel; // weak |
+ suggestions::SuggestionsService* _suggestionService; // weak |
+ browser_sync::ProfileSyncService* _syncService; // weak |
+ |
+ scoped_refptr<history::TopSites> _topSites; |
+ std::unique_ptr< |
+ suggestions::SuggestionsService::ResponseCallbackList::Subscription> |
+ _suggestionsServiceResponseSubscription; |
+ |
+ // Indicates if a reindex is pending. Reindexes made by calling the external |
+ // reindexTopSites method are executed at most every second. |
+ BOOL _isReindexPending; |
+} |
+@property(nonatomic, readonly) scoped_refptr<history::TopSites> topSites; |
+ |
+- (instancetype) |
+initWithLargeIconService:(favicon::LargeIconService*)largeIconService |
+ topSites:(scoped_refptr<history::TopSites>)topSites |
+ bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel |
+ profileSyncService:(browser_sync::ProfileSyncService*)syncService |
+ suggestionsService:(suggestions::SuggestionsService*)suggestionsService; |
+ |
+// Updates all indexed top sites from appropriate source, within limit of number |
+// of sites shown on NTP. |
+- (void)updateAllTopSitesSpotlightItems; |
+// Adds all top sites from appropriate source, within limit of number of sites |
+// shown on NTP. |
+- (void)addAllTopSitesSpotlightItems; |
+// Adds all top sites from TopSites source (most visited sites on device), |
+// within limit of number of sites shown on NTP. |
+- (void)addAllLocalTopSitesItems; |
+// Adds all top sites from Suggestions source (server-based), within limit of |
+// number of sites shown on NTP. |
+- (void)addAllSuggestionsTopSitesItems; |
+// Callback for topsites mostvisited, adds sites to spotlight. |
+- (void)onMostVisitedURLsAvailable: |
+ (const history::MostVisitedURLList&)top_sites; |
+// Callback for suggestions, adds sites to spotlight. |
+- (void)onSuggestionsProfileAvailable: |
+ (const suggestions::SuggestionsProfile&)suggestions_profile; |
+ |
+@end |
+ |
+class SpotlightTopSitesCallbackBridge |
+ : public base::SupportsWeakPtr<SpotlightTopSitesCallbackBridge> { |
+ public: |
+ explicit SpotlightTopSitesCallbackBridge(TopSitesSpotlightManager* owner) |
+ : owner_(owner) {} |
+ |
+ SpotlightTopSitesCallbackBridge() {} |
+ |
+ void OnMostVisitedURLsAvailable(const history::MostVisitedURLList& data) { |
+ [owner_ onMostVisitedURLsAvailable:data]; |
+ } |
+ |
+ private: |
+ __unsafe_unretained TopSitesSpotlightManager* owner_; // weak, owns us |
+}; |
+ |
+class SpotlightTopSitesBridge : public history::TopSitesObserver { |
+ public: |
+ explicit SpotlightTopSitesBridge(TopSitesSpotlightManager* owner) |
+ : owner_(owner) { |
+ owner.topSites->AddObserver(this); |
+ }; |
+ |
+ ~SpotlightTopSitesBridge() override { |
+ owner_.topSites->RemoveObserver(this); |
+ }; |
+ |
+ void TopSitesLoaded(history::TopSites* top_sites) override {} |
+ |
+ void TopSitesChanged(history::TopSites* top_sites, |
+ ChangeReason change_reason) override { |
+ [owner_ updateAllTopSitesSpotlightItems]; |
+ } |
+ |
+ private: |
+ __unsafe_unretained TopSitesSpotlightManager* owner_; // weak |
+}; |
+ |
+class SpotlightSuggestionsBridge |
+ : public base::SupportsWeakPtr<SpotlightSuggestionsBridge> { |
+ public: |
+ explicit SpotlightSuggestionsBridge(TopSitesSpotlightManager* owner) |
+ : owner_(owner) {} |
+ |
+ SpotlightSuggestionsBridge() {} |
+ |
+ void OnSuggestionsProfileAvailable( |
+ const suggestions::SuggestionsProfile& suggestions_profile) { |
+ [owner_ onSuggestionsProfileAvailable:suggestions_profile]; |
+ } |
+ |
+ private: |
+ __unsafe_unretained TopSitesSpotlightManager* owner_; // weak, owns us |
+}; |
+ |
+@implementation TopSitesSpotlightManager |
+@synthesize topSites = _topSites; |
+ |
++ (TopSitesSpotlightManager*)topSitesSpotlightManagerWithBrowserState: |
+ (ios::ChromeBrowserState*)browserState { |
+ return [[[TopSitesSpotlightManager alloc] |
+ initWithLargeIconService:IOSChromeLargeIconServiceFactory:: |
+ GetForBrowserState(browserState) |
+ topSites:ios::TopSitesFactory::GetForBrowserState( |
+ browserState) |
+ bookmarkModel:ios::BookmarkModelFactory::GetForBrowserState( |
+ browserState) |
+ profileSyncService:IOSChromeProfileSyncServiceFactory:: |
+ GetForBrowserState(browserState) |
+ suggestionsService:suggestions::SuggestionsServiceFactory:: |
+ GetForBrowserState(browserState)] |
+ autorelease]; |
+} |
+ |
+- (instancetype) |
+initWithLargeIconService:(favicon::LargeIconService*)largeIconService |
+ topSites:(scoped_refptr<history::TopSites>)topSites |
+ bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel |
+ profileSyncService:(browser_sync::ProfileSyncService*)syncService |
+ suggestionsService:(suggestions::SuggestionsService*)suggestionsService { |
+ self = [super initWithLargeIconService:largeIconService |
+ domain:spotlight::DOMAIN_TOPSITES]; |
+ if (self) { |
+ _topSites = topSites; |
+ _topSitesBridge.reset(new SpotlightTopSitesBridge(self)); |
+ _topSitesCallbackBridge.reset(new SpotlightTopSitesCallbackBridge(self)); |
+ _bookmarkModel = bookmarkModel; |
+ _isReindexPending = false; |
+ if (syncService && suggestionsService) { |
+ _suggestionsBridge.reset(new SpotlightSuggestionsBridge(self)); |
+ _syncService = syncService; |
+ _suggestionService = suggestionsService; |
+ _suggestionsServiceResponseSubscription = _suggestionService->AddCallback( |
+ base::Bind(&SpotlightSuggestionsBridge::OnSuggestionsProfileAvailable, |
+ _suggestionsBridge->AsWeakPtr())); |
+ sync_observer_bridge_.reset(new SyncObserverBridge(self, syncService)); |
+ } |
+ } |
+ return self; |
+} |
+ |
+- (void)updateAllTopSitesSpotlightItems { |
+ base::WeakNSObject<TopSitesSpotlightManager> weakSelf(self); |
+ [self clearAllSpotlightItems:^(NSError* error) { |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ [weakSelf addAllTopSitesSpotlightItems]; |
+ }); |
+ }]; |
+} |
+ |
+- (void)addAllTopSitesSpotlightItems { |
+ if (_suggestionService) { |
+ [self addAllSuggestionsTopSitesItems]; |
+ } else { |
+ [self addAllLocalTopSitesItems]; |
+ } |
+} |
+ |
+- (void)addAllLocalTopSitesItems { |
+ _topSites->GetMostVisitedURLs( |
+ base::Bind(&SpotlightTopSitesCallbackBridge::OnMostVisitedURLsAvailable, |
+ _topSitesCallbackBridge->AsWeakPtr()), |
+ true); |
+} |
+ |
+- (void)addAllSuggestionsTopSitesItems { |
+ if (!_suggestionService->FetchSuggestionsData()) { |
+ [self addAllLocalTopSitesItems]; |
+ } |
+} |
+ |
+- (BOOL)isURLBookmarked:(const GURL&)URL { |
+ if (!_bookmarkModel->loaded()) |
+ return NO; |
+ |
+ std::vector<const bookmarks::BookmarkNode*> nodes; |
+ _bookmarkModel->GetNodesByURL(URL, &nodes); |
+ return nodes.size() > 0; |
+} |
+ |
+- (void)onMostVisitedURLsAvailable: |
+ (const history::MostVisitedURLList&)top_sites { |
+ NSUInteger sitesToIndex = |
+ MIN(top_sites.size(), [GoogleLandingController maxSitesShown]); |
+ for (size_t i = 0; i < sitesToIndex; i++) { |
+ const GURL& URL = top_sites[i].url; |
+ |
+ // Check if the item is bookmarked, in which case it is already indexed. |
+ if ([self isURLBookmarked:URL]) { |
+ continue; |
+ } |
+ |
+ [self refreshItemsWithURL:URL |
+ title:base::SysUTF16ToNSString(top_sites[i].title)]; |
+ } |
+} |
+ |
+- (void)onSuggestionsProfileAvailable: |
+ (const suggestions::SuggestionsProfile&)suggestionsProfile { |
+ size_t size = suggestionsProfile.suggestions_size(); |
+ if (size) { |
+ NSUInteger sitesToIndex = |
+ MIN(size, [GoogleLandingController maxSitesShown]); |
+ for (size_t i = 0; i < sitesToIndex; i++) { |
+ const suggestions::ChromeSuggestion& suggestion = |
+ suggestionsProfile.suggestions(i); |
+ GURL URL = GURL(suggestion.url()); |
+ // Check if the item is bookmarked, in which case it is already indexed. |
+ if ([self isURLBookmarked:URL]) { |
+ continue; |
+ } |
+ |
+ std::string title = suggestion.title(); |
+ [self refreshItemsWithURL:URL title:base::SysUTF8ToNSString(title)]; |
+ } |
+ } else { |
+ [self addAllLocalTopSitesItems]; |
+ } |
+} |
+ |
+- (void)reindexTopSites { |
+ if (_isReindexPending) { |
+ return; |
+ } |
+ _isReindexPending = true; |
+ base::WeakNSObject<TopSitesSpotlightManager> weakSelf(self); |
+ dispatch_after( |
+ dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(1 * NSEC_PER_SEC)), |
+ dispatch_get_main_queue(), ^{ |
+ [weakSelf updateAllTopSitesSpotlightItems]; |
+ weakSelf.get()->_isReindexPending = false; |
+ }); |
+} |
+ |
+#pragma mark - |
+#pragma mark SyncObserverModelBridge |
+ |
+- (void)onSyncStateChanged { |
+ [self updateAllTopSitesSpotlightItems]; |
+} |
+ |
+@end |