blob: 58b45fec177d88321911b574bf07b37034a77697 [file] [log] [blame]
// 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/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"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
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:
__weak TopSitesSpotlightManager* owner_;
};
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:
__weak TopSitesSpotlightManager* owner_;
};
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:
__weak TopSitesSpotlightManager* owner_;
};
@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)];
}
- (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 {
__weak 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;
__weak TopSitesSpotlightManager* weakSelf = self;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, static_cast<int64_t>(1 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
TopSitesSpotlightManager* strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf updateAllTopSitesSpotlightItems];
strongSelf->_isReindexPending = false;
});
}
#pragma mark -
#pragma mark SyncObserverModelBridge
- (void)onSyncStateChanged {
[self updateAllTopSitesSpotlightItems];
}
@end