| Index: ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
|
| diff --git a/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..edbfd6a7017fbf4c428d2872d7e850c9c57606a5
|
| --- /dev/null
|
| +++ b/ios/chrome/app/spotlight/bookmarks_spotlight_manager.mm
|
| @@ -0,0 +1,348 @@
|
| +// 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/bookmarks_spotlight_manager.h"
|
| +
|
| +#include <memory>
|
| +
|
| +#import <CoreSpotlight/CoreSpotlight.h>
|
| +
|
| +#include "base/ios/weak_nsobject.h"
|
| +#include "base/metrics/histogram.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "base/version.h"
|
| +#include "components/bookmarks/browser/base_bookmark_model_observer.h"
|
| +#include "components/bookmarks/browser/bookmark_model.h"
|
| +#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
|
| +#include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
|
| +
|
| +namespace {
|
| +// Limit the size of the initial indexing. This will not limit the size of the
|
| +// index as new bookmarks can be added afterwards.
|
| +const int kMaxInitialIndexSize = 1000;
|
| +
|
| +// Minimum delay between two global indexing of bookmarks.
|
| +const int kDelayBetweenTwoIndexingInSeconds = 7 * 86400; // One week.
|
| +
|
| +} // namespace
|
| +
|
| +class SpotlightBookmarkModelBridge;
|
| +
|
| +// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
|
| +@interface BookmarksSpotlightManager () {
|
| + base::WeakNSProtocol<id<BookmarkUpdatedDelegate>> _delegate;
|
| +
|
| + // Bridge to register for bookmark changes.
|
| + std::unique_ptr<SpotlightBookmarkModelBridge> _bookmarkModelBridge;
|
| +
|
| + // Keep a reference to detach before deallocing. Life cycle of _bookmarkModel
|
| + // is longer than life cycle of a SpotlightManager as
|
| + // |BookmarkModelBeingDeleted| will cause deletion of SpotlightManager.
|
| + bookmarks::BookmarkModel* _bookmarkModel; // weak
|
| +
|
| + // Number of nodes indexed in initial scan.
|
| + NSUInteger _nodesIndexed;
|
| +
|
| + // Tracks whether initial indexing has been done.
|
| + BOOL _initialIndexDone;
|
| +}
|
| +
|
| +// Detaches the |SpotlightBookmarkModelBridge| from the bookmark model. The
|
| +// manager must not be used after calling this method.
|
| +- (void)detachBookmarkModel;
|
| +
|
| +// Removes the node from the Spotlight index.
|
| +- (void)removeNodeFromIndex:(const bookmarks::BookmarkNode*)node;
|
| +
|
| +// Clears all the bookmarks in the Spotlight index then index the bookmarks in
|
| +// the model.
|
| +- (void)clearAndReindexModel;
|
| +
|
| +// Refreshes all nodes in the subtree of node.
|
| +// If |initial| is YES, limit the number of nodes to kMaxInitialIndexSize.
|
| +- (void)refreshNodeInIndex:(const bookmarks::BookmarkNode*)node
|
| + initial:(BOOL)initial;
|
| +
|
| +// Returns true is the current index is too old or from an incompatible version.
|
| +- (BOOL)shouldReindex;
|
| +
|
| +@end
|
| +
|
| +// Handles notification that bookmarks has been removed changed so we can update
|
| +// the Spotlight index.
|
| +class SpotlightBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
|
| + public:
|
| + explicit SpotlightBookmarkModelBridge(BookmarksSpotlightManager* owner)
|
| + : owner_(owner){};
|
| +
|
| + ~SpotlightBookmarkModelBridge() override{};
|
| +
|
| + void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* parent,
|
| + int old_index,
|
| + const bookmarks::BookmarkNode* node,
|
| + const std::set<GURL>& removed_urls) override {}
|
| +
|
| + void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* parent,
|
| + int old_index,
|
| + const bookmarks::BookmarkNode* node) override {
|
| + [owner_ removeNodeFromIndex:node];
|
| + }
|
| +
|
| + void BookmarkModelBeingDeleted(bookmarks::BookmarkModel* model) override {
|
| + [owner_ detachBookmarkModel];
|
| + };
|
| +
|
| + void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
|
| + bool ids_reassigned) override {
|
| + [owner_ reindexBookmarksIfNeeded];
|
| + }
|
| +
|
| + void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* parent,
|
| + int index) override {
|
| + [owner_ refreshNodeInIndex:parent->GetChild(index) initial:NO];
|
| + }
|
| +
|
| + void OnWillChangeBookmarkNode(bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* node) override {
|
| + [owner_ removeNodeFromIndex:node];
|
| + }
|
| +
|
| + void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* node) override {
|
| + [owner_ refreshNodeInIndex:node initial:NO];
|
| + }
|
| +
|
| + void BookmarkNodeFaviconChanged(
|
| + bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* node) override {
|
| + [owner_ refreshNodeInIndex:node initial:NO];
|
| + }
|
| +
|
| + void BookmarkAllUserNodesRemoved(
|
| + bookmarks::BookmarkModel* model,
|
| + const std::set<GURL>& removed_urls) override {
|
| + [owner_ clearAllSpotlightItems:nil];
|
| + }
|
| +
|
| + void BookmarkNodeChildrenReordered(
|
| + bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* node) override{};
|
| +
|
| + void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
|
| + const bookmarks::BookmarkNode* old_parent,
|
| + int old_index,
|
| + const bookmarks::BookmarkNode* new_parent,
|
| + int new_index) override {
|
| + [owner_ refreshNodeInIndex:new_parent->GetChild(new_index) initial:NO];
|
| + };
|
| +
|
| + private:
|
| + __unsafe_unretained BookmarksSpotlightManager* owner_; // Weak.
|
| +};
|
| +
|
| +@implementation BookmarksSpotlightManager
|
| +
|
| ++ (BookmarksSpotlightManager*)bookmarksSpotlightManagerWithBrowserState:
|
| + (ios::ChromeBrowserState*)browserState {
|
| + return [[[BookmarksSpotlightManager alloc]
|
| + initWithLargeIconService:IOSChromeLargeIconServiceFactory::
|
| + GetForBrowserState(browserState)
|
| + bookmarkModel:ios::BookmarkModelFactory::GetForBrowserState(
|
| + browserState)] autorelease];
|
| +}
|
| +
|
| +- (instancetype)
|
| +initWithLargeIconService:(favicon::LargeIconService*)largeIconService
|
| + bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel {
|
| + self = [super initWithLargeIconService:largeIconService
|
| + domain:spotlight::DOMAIN_BOOKMARKS];
|
| + if (self) {
|
| + _bookmarkModelBridge.reset(new SpotlightBookmarkModelBridge(self));
|
| + _bookmarkModel = bookmarkModel;
|
| + bookmarkModel->AddObserver(_bookmarkModelBridge.get());
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)dealloc {
|
| + [self detachBookmarkModel];
|
| + [super dealloc];
|
| +}
|
| +
|
| +- (void)detachBookmarkModel {
|
| + [self cancelAllLargeIconPendingTasks];
|
| + if (_bookmarkModelBridge.get()) {
|
| + _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
|
| + _bookmarkModelBridge.reset();
|
| + }
|
| +}
|
| +
|
| +- (id<BookmarkUpdatedDelegate>)delegate {
|
| + return _delegate;
|
| +}
|
| +
|
| +- (void)setDelegate:(id<BookmarkUpdatedDelegate>)delegate {
|
| + _delegate.reset(delegate);
|
| +}
|
| +
|
| +- (void)getParentKeywordsForNode:(const bookmarks::BookmarkNode*)node
|
| + inArray:(NSMutableArray*)keywords {
|
| + if (!node) {
|
| + return;
|
| + }
|
| + if (node->is_folder() && !_bookmarkModel->is_permanent_node(node)) {
|
| + [keywords addObject:base::SysUTF16ToNSString(node->GetTitle())];
|
| + }
|
| + [self getParentKeywordsForNode:node->parent() inArray:keywords];
|
| +}
|
| +
|
| +- (void)removeNodeFromIndex:(const bookmarks::BookmarkNode*)node {
|
| + if (node->is_url()) {
|
| + GURL url(node->url());
|
| + NSString* title = base::SysUTF16ToNSString(node->GetTitle());
|
| + NSString* spotlightID = [self spotlightIDForURL:url title:title];
|
| + base::WeakNSObject<BookmarksSpotlightManager> weakself(self);
|
| + BlockWithError completion = ^(NSError* error) {
|
| + dispatch_async(dispatch_get_main_queue(), ^{
|
| + [weakself refreshItemsWithURL:url title:nil];
|
| + [_delegate bookmarkUpdated];
|
| + });
|
| + };
|
| + spotlight::DeleteItemsWithIdentifiers(@[ spotlightID ], completion);
|
| + return;
|
| + }
|
| + int childCount = node->child_count();
|
| + for (int child = 0; child < childCount; child++) {
|
| + [self removeNodeFromIndex:node->GetChild(child)];
|
| + }
|
| +}
|
| +
|
| +- (BOOL)shouldReindex {
|
| + NSDate* date = [[NSUserDefaults standardUserDefaults]
|
| + objectForKey:@(spotlight::kSpotlightLastIndexingDateKey)];
|
| + if (!date) {
|
| + return YES;
|
| + }
|
| + NSDate* expirationDate =
|
| + [date dateByAddingTimeInterval:kDelayBetweenTwoIndexingInSeconds];
|
| + if ([expirationDate compare:[NSDate date]] == NSOrderedAscending) {
|
| + return YES;
|
| + }
|
| + NSNumber* lastIndexedVersionString = [[NSUserDefaults standardUserDefaults]
|
| + objectForKey:@(spotlight::kSpotlightLastIndexingVersionKey)];
|
| + if (!lastIndexedVersionString) {
|
| + return YES;
|
| + }
|
| +
|
| + if ([lastIndexedVersionString integerValue] <
|
| + spotlight::kCurrentSpotlightIndexVersion) {
|
| + return YES;
|
| + }
|
| + return NO;
|
| +}
|
| +
|
| +- (void)reindexBookmarksIfNeeded {
|
| + if (!_bookmarkModel->loaded() || _initialIndexDone) {
|
| + return;
|
| + }
|
| + _initialIndexDone = YES;
|
| + if ([self shouldReindex]) {
|
| + [self clearAndReindexModel];
|
| + }
|
| +}
|
| +
|
| +- (void)addKeywords:(NSArray*)keywords
|
| + toSearchableItem:(CSSearchableItem*)item {
|
| + NSSet* itemKeywords = [NSSet setWithArray:[[item attributeSet] keywords]];
|
| + itemKeywords = [itemKeywords setByAddingObjectsFromArray:keywords];
|
| + [[item attributeSet] setKeywords:[itemKeywords allObjects]];
|
| +}
|
| +
|
| +- (void)refreshNodeInIndex:(const bookmarks::BookmarkNode*)node
|
| + initial:(BOOL)initial {
|
| + if (initial && _nodesIndexed > kMaxInitialIndexSize) {
|
| + return;
|
| + }
|
| + if (node->is_url()) {
|
| + _nodesIndexed++;
|
| + [self refreshItemsWithURL:node->url() title:nil];
|
| + if (!initial) {
|
| + [_delegate bookmarkUpdated];
|
| + }
|
| + return;
|
| + }
|
| + int childCount = node->child_count();
|
| + for (int child = 0; child < childCount; child++) {
|
| + [self refreshNodeInIndex:node->GetChild(child) initial:initial];
|
| + }
|
| +}
|
| +
|
| +- (NSArray*)spotlightItemsWithURL:(const GURL&)URL
|
| + favicon:(UIImage*)favicon
|
| + defaultTitle:(NSString*)defaultTitle {
|
| + base::scoped_nsobject<NSMutableDictionary> spotlightItems(
|
| + [[NSMutableDictionary alloc] init]);
|
| + std::vector<const bookmarks::BookmarkNode*> nodes;
|
| + _bookmarkModel->GetNodesByURL(URL, &nodes);
|
| + for (auto node : nodes) {
|
| + NSString* nodeTitle = base::SysUTF16ToNSString(node->GetTitle());
|
| + NSString* spotlightID = [self spotlightIDForURL:URL title:nodeTitle];
|
| + CSSearchableItem* item = [spotlightItems objectForKey:spotlightID];
|
| + if (!item) {
|
| + item = [[super spotlightItemsWithURL:URL
|
| + favicon:favicon
|
| + defaultTitle:nodeTitle] objectAtIndex:0];
|
| + }
|
| + base::scoped_nsobject<NSMutableArray> nodeKeywords(
|
| + [[NSMutableArray alloc] init]);
|
| + [self getParentKeywordsForNode:node inArray:nodeKeywords.get()];
|
| + [self addKeywords:nodeKeywords toSearchableItem:item];
|
| + [spotlightItems setObject:item forKey:spotlightID];
|
| + }
|
| + return [spotlightItems allValues];
|
| +}
|
| +
|
| +- (void)clearAndReindexModel {
|
| + [self cancelAllLargeIconPendingTasks];
|
| + base::WeakNSObject<BookmarksSpotlightManager> weakself(self);
|
| + BlockWithError completion = ^(NSError* error) {
|
| + if (!error) {
|
| + dispatch_async(dispatch_get_main_queue(), ^{
|
| + base::scoped_nsobject<BookmarksSpotlightManager> strongSelf(
|
| + [weakself retain]);
|
| + if (!strongSelf)
|
| + return;
|
| +
|
| + NSDate* startOfReindexing = [NSDate date];
|
| + strongSelf.get()->_nodesIndexed = 0;
|
| + [strongSelf
|
| + refreshNodeInIndex:strongSelf.get()->_bookmarkModel->root_node()
|
| + initial:YES];
|
| + NSDate* endOfReindexing = [NSDate date];
|
| + NSTimeInterval indexingDuration =
|
| + [endOfReindexing timeIntervalSinceDate:startOfReindexing];
|
| + UMA_HISTOGRAM_TIMES(
|
| + "IOS.Spotlight.BookmarksIndexingDuration",
|
| + base::TimeDelta::FromMillisecondsD(1000 * indexingDuration));
|
| + UMA_HISTOGRAM_COUNTS_1000("IOS.Spotlight.BookmarksInitialIndexSize",
|
| + [strongSelf pendingLargeIconTasksCount]);
|
| + [[NSUserDefaults standardUserDefaults]
|
| + setObject:endOfReindexing
|
| + forKey:@(spotlight::kSpotlightLastIndexingDateKey)];
|
| +
|
| + [[NSUserDefaults standardUserDefaults]
|
| + setObject:[NSNumber numberWithInteger:
|
| + spotlight::kCurrentSpotlightIndexVersion]
|
| + forKey:@(spotlight::kSpotlightLastIndexingVersionKey)];
|
| + [_delegate bookmarkUpdated];
|
| + });
|
| + }
|
| + };
|
| + [self clearAllSpotlightItems:completion];
|
| +}
|
| +
|
| +@end
|
|
|