OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/app/spotlight/bookmarks_spotlight_manager.h" |
| 6 |
| 7 #include <memory> |
| 8 |
| 9 #import <CoreSpotlight/CoreSpotlight.h> |
| 10 |
| 11 #include "base/ios/weak_nsobject.h" |
| 12 #include "base/metrics/histogram.h" |
| 13 #include "base/strings/sys_string_conversions.h" |
| 14 #include "base/version.h" |
| 15 #include "components/bookmarks/browser/base_bookmark_model_observer.h" |
| 16 #include "components/bookmarks/browser/bookmark_model.h" |
| 17 #include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" |
| 18 #include "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h" |
| 19 |
| 20 namespace { |
| 21 // Limit the size of the initial indexing. This will not limit the size of the |
| 22 // index as new bookmarks can be added afterwards. |
| 23 const int kMaxInitialIndexSize = 1000; |
| 24 |
| 25 // Minimum delay between two global indexing of bookmarks. |
| 26 const int kDelayBetweenTwoIndexingInSeconds = 7 * 86400; // One week. |
| 27 |
| 28 } // namespace |
| 29 |
| 30 class SpotlightBookmarkModelBridge; |
| 31 |
| 32 // Called from the BrowserBookmarkModelBridge from C++ -> ObjC. |
| 33 @interface BookmarksSpotlightManager () { |
| 34 base::WeakNSProtocol<id<BookmarkUpdatedDelegate>> _delegate; |
| 35 |
| 36 // Bridge to register for bookmark changes. |
| 37 std::unique_ptr<SpotlightBookmarkModelBridge> _bookmarkModelBridge; |
| 38 |
| 39 // Keep a reference to detach before deallocing. Life cycle of _bookmarkModel |
| 40 // is longer than life cycle of a SpotlightManager as |
| 41 // |BookmarkModelBeingDeleted| will cause deletion of SpotlightManager. |
| 42 bookmarks::BookmarkModel* _bookmarkModel; // weak |
| 43 |
| 44 // Number of nodes indexed in initial scan. |
| 45 NSUInteger _nodesIndexed; |
| 46 |
| 47 // Tracks whether initial indexing has been done. |
| 48 BOOL _initialIndexDone; |
| 49 } |
| 50 |
| 51 // Detaches the |SpotlightBookmarkModelBridge| from the bookmark model. The |
| 52 // manager must not be used after calling this method. |
| 53 - (void)detachBookmarkModel; |
| 54 |
| 55 // Removes the node from the Spotlight index. |
| 56 - (void)removeNodeFromIndex:(const bookmarks::BookmarkNode*)node; |
| 57 |
| 58 // Clears all the bookmarks in the Spotlight index then index the bookmarks in |
| 59 // the model. |
| 60 - (void)clearAndReindexModel; |
| 61 |
| 62 // Refreshes all nodes in the subtree of node. |
| 63 // If |initial| is YES, limit the number of nodes to kMaxInitialIndexSize. |
| 64 - (void)refreshNodeInIndex:(const bookmarks::BookmarkNode*)node |
| 65 initial:(BOOL)initial; |
| 66 |
| 67 // Returns true is the current index is too old or from an incompatible version. |
| 68 - (BOOL)shouldReindex; |
| 69 |
| 70 @end |
| 71 |
| 72 // Handles notification that bookmarks has been removed changed so we can update |
| 73 // the Spotlight index. |
| 74 class SpotlightBookmarkModelBridge : public bookmarks::BookmarkModelObserver { |
| 75 public: |
| 76 explicit SpotlightBookmarkModelBridge(BookmarksSpotlightManager* owner) |
| 77 : owner_(owner){}; |
| 78 |
| 79 ~SpotlightBookmarkModelBridge() override{}; |
| 80 |
| 81 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model, |
| 82 const bookmarks::BookmarkNode* parent, |
| 83 int old_index, |
| 84 const bookmarks::BookmarkNode* node, |
| 85 const std::set<GURL>& removed_urls) override {} |
| 86 |
| 87 void OnWillRemoveBookmarks(bookmarks::BookmarkModel* model, |
| 88 const bookmarks::BookmarkNode* parent, |
| 89 int old_index, |
| 90 const bookmarks::BookmarkNode* node) override { |
| 91 [owner_ removeNodeFromIndex:node]; |
| 92 } |
| 93 |
| 94 void BookmarkModelBeingDeleted(bookmarks::BookmarkModel* model) override { |
| 95 [owner_ detachBookmarkModel]; |
| 96 }; |
| 97 |
| 98 void BookmarkModelLoaded(bookmarks::BookmarkModel* model, |
| 99 bool ids_reassigned) override { |
| 100 [owner_ reindexBookmarksIfNeeded]; |
| 101 } |
| 102 |
| 103 void BookmarkNodeAdded(bookmarks::BookmarkModel* model, |
| 104 const bookmarks::BookmarkNode* parent, |
| 105 int index) override { |
| 106 [owner_ refreshNodeInIndex:parent->GetChild(index) initial:NO]; |
| 107 } |
| 108 |
| 109 void OnWillChangeBookmarkNode(bookmarks::BookmarkModel* model, |
| 110 const bookmarks::BookmarkNode* node) override { |
| 111 [owner_ removeNodeFromIndex:node]; |
| 112 } |
| 113 |
| 114 void BookmarkNodeChanged(bookmarks::BookmarkModel* model, |
| 115 const bookmarks::BookmarkNode* node) override { |
| 116 [owner_ refreshNodeInIndex:node initial:NO]; |
| 117 } |
| 118 |
| 119 void BookmarkNodeFaviconChanged( |
| 120 bookmarks::BookmarkModel* model, |
| 121 const bookmarks::BookmarkNode* node) override { |
| 122 [owner_ refreshNodeInIndex:node initial:NO]; |
| 123 } |
| 124 |
| 125 void BookmarkAllUserNodesRemoved( |
| 126 bookmarks::BookmarkModel* model, |
| 127 const std::set<GURL>& removed_urls) override { |
| 128 [owner_ clearAllSpotlightItems:nil]; |
| 129 } |
| 130 |
| 131 void BookmarkNodeChildrenReordered( |
| 132 bookmarks::BookmarkModel* model, |
| 133 const bookmarks::BookmarkNode* node) override{}; |
| 134 |
| 135 void BookmarkNodeMoved(bookmarks::BookmarkModel* model, |
| 136 const bookmarks::BookmarkNode* old_parent, |
| 137 int old_index, |
| 138 const bookmarks::BookmarkNode* new_parent, |
| 139 int new_index) override { |
| 140 [owner_ refreshNodeInIndex:new_parent->GetChild(new_index) initial:NO]; |
| 141 }; |
| 142 |
| 143 private: |
| 144 __unsafe_unretained BookmarksSpotlightManager* owner_; // Weak. |
| 145 }; |
| 146 |
| 147 @implementation BookmarksSpotlightManager |
| 148 |
| 149 + (BookmarksSpotlightManager*)bookmarksSpotlightManagerWithBrowserState: |
| 150 (ios::ChromeBrowserState*)browserState { |
| 151 return [[[BookmarksSpotlightManager alloc] |
| 152 initWithLargeIconService:IOSChromeLargeIconServiceFactory:: |
| 153 GetForBrowserState(browserState) |
| 154 bookmarkModel:ios::BookmarkModelFactory::GetForBrowserState( |
| 155 browserState)] autorelease]; |
| 156 } |
| 157 |
| 158 - (instancetype) |
| 159 initWithLargeIconService:(favicon::LargeIconService*)largeIconService |
| 160 bookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel { |
| 161 self = [super initWithLargeIconService:largeIconService |
| 162 domain:spotlight::DOMAIN_BOOKMARKS]; |
| 163 if (self) { |
| 164 _bookmarkModelBridge.reset(new SpotlightBookmarkModelBridge(self)); |
| 165 _bookmarkModel = bookmarkModel; |
| 166 bookmarkModel->AddObserver(_bookmarkModelBridge.get()); |
| 167 } |
| 168 return self; |
| 169 } |
| 170 |
| 171 - (void)dealloc { |
| 172 [self detachBookmarkModel]; |
| 173 [super dealloc]; |
| 174 } |
| 175 |
| 176 - (void)detachBookmarkModel { |
| 177 [self cancelAllLargeIconPendingTasks]; |
| 178 if (_bookmarkModelBridge.get()) { |
| 179 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get()); |
| 180 _bookmarkModelBridge.reset(); |
| 181 } |
| 182 } |
| 183 |
| 184 - (id<BookmarkUpdatedDelegate>)delegate { |
| 185 return _delegate; |
| 186 } |
| 187 |
| 188 - (void)setDelegate:(id<BookmarkUpdatedDelegate>)delegate { |
| 189 _delegate.reset(delegate); |
| 190 } |
| 191 |
| 192 - (void)getParentKeywordsForNode:(const bookmarks::BookmarkNode*)node |
| 193 inArray:(NSMutableArray*)keywords { |
| 194 if (!node) { |
| 195 return; |
| 196 } |
| 197 if (node->is_folder() && !_bookmarkModel->is_permanent_node(node)) { |
| 198 [keywords addObject:base::SysUTF16ToNSString(node->GetTitle())]; |
| 199 } |
| 200 [self getParentKeywordsForNode:node->parent() inArray:keywords]; |
| 201 } |
| 202 |
| 203 - (void)removeNodeFromIndex:(const bookmarks::BookmarkNode*)node { |
| 204 if (node->is_url()) { |
| 205 GURL url(node->url()); |
| 206 NSString* title = base::SysUTF16ToNSString(node->GetTitle()); |
| 207 NSString* spotlightID = [self spotlightIDForURL:url title:title]; |
| 208 base::WeakNSObject<BookmarksSpotlightManager> weakself(self); |
| 209 BlockWithError completion = ^(NSError* error) { |
| 210 dispatch_async(dispatch_get_main_queue(), ^{ |
| 211 [weakself refreshItemsWithURL:url title:nil]; |
| 212 [_delegate bookmarkUpdated]; |
| 213 }); |
| 214 }; |
| 215 spotlight::DeleteItemsWithIdentifiers(@[ spotlightID ], completion); |
| 216 return; |
| 217 } |
| 218 int childCount = node->child_count(); |
| 219 for (int child = 0; child < childCount; child++) { |
| 220 [self removeNodeFromIndex:node->GetChild(child)]; |
| 221 } |
| 222 } |
| 223 |
| 224 - (BOOL)shouldReindex { |
| 225 NSDate* date = [[NSUserDefaults standardUserDefaults] |
| 226 objectForKey:@(spotlight::kSpotlightLastIndexingDateKey)]; |
| 227 if (!date) { |
| 228 return YES; |
| 229 } |
| 230 NSDate* expirationDate = |
| 231 [date dateByAddingTimeInterval:kDelayBetweenTwoIndexingInSeconds]; |
| 232 if ([expirationDate compare:[NSDate date]] == NSOrderedAscending) { |
| 233 return YES; |
| 234 } |
| 235 NSNumber* lastIndexedVersionString = [[NSUserDefaults standardUserDefaults] |
| 236 objectForKey:@(spotlight::kSpotlightLastIndexingVersionKey)]; |
| 237 if (!lastIndexedVersionString) { |
| 238 return YES; |
| 239 } |
| 240 |
| 241 if ([lastIndexedVersionString integerValue] < |
| 242 spotlight::kCurrentSpotlightIndexVersion) { |
| 243 return YES; |
| 244 } |
| 245 return NO; |
| 246 } |
| 247 |
| 248 - (void)reindexBookmarksIfNeeded { |
| 249 if (!_bookmarkModel->loaded() || _initialIndexDone) { |
| 250 return; |
| 251 } |
| 252 _initialIndexDone = YES; |
| 253 if ([self shouldReindex]) { |
| 254 [self clearAndReindexModel]; |
| 255 } |
| 256 } |
| 257 |
| 258 - (void)addKeywords:(NSArray*)keywords |
| 259 toSearchableItem:(CSSearchableItem*)item { |
| 260 NSSet* itemKeywords = [NSSet setWithArray:[[item attributeSet] keywords]]; |
| 261 itemKeywords = [itemKeywords setByAddingObjectsFromArray:keywords]; |
| 262 [[item attributeSet] setKeywords:[itemKeywords allObjects]]; |
| 263 } |
| 264 |
| 265 - (void)refreshNodeInIndex:(const bookmarks::BookmarkNode*)node |
| 266 initial:(BOOL)initial { |
| 267 if (initial && _nodesIndexed > kMaxInitialIndexSize) { |
| 268 return; |
| 269 } |
| 270 if (node->is_url()) { |
| 271 _nodesIndexed++; |
| 272 [self refreshItemsWithURL:node->url() title:nil]; |
| 273 if (!initial) { |
| 274 [_delegate bookmarkUpdated]; |
| 275 } |
| 276 return; |
| 277 } |
| 278 int childCount = node->child_count(); |
| 279 for (int child = 0; child < childCount; child++) { |
| 280 [self refreshNodeInIndex:node->GetChild(child) initial:initial]; |
| 281 } |
| 282 } |
| 283 |
| 284 - (NSArray*)spotlightItemsWithURL:(const GURL&)URL |
| 285 favicon:(UIImage*)favicon |
| 286 defaultTitle:(NSString*)defaultTitle { |
| 287 base::scoped_nsobject<NSMutableDictionary> spotlightItems( |
| 288 [[NSMutableDictionary alloc] init]); |
| 289 std::vector<const bookmarks::BookmarkNode*> nodes; |
| 290 _bookmarkModel->GetNodesByURL(URL, &nodes); |
| 291 for (auto node : nodes) { |
| 292 NSString* nodeTitle = base::SysUTF16ToNSString(node->GetTitle()); |
| 293 NSString* spotlightID = [self spotlightIDForURL:URL title:nodeTitle]; |
| 294 CSSearchableItem* item = [spotlightItems objectForKey:spotlightID]; |
| 295 if (!item) { |
| 296 item = [[super spotlightItemsWithURL:URL |
| 297 favicon:favicon |
| 298 defaultTitle:nodeTitle] objectAtIndex:0]; |
| 299 } |
| 300 base::scoped_nsobject<NSMutableArray> nodeKeywords( |
| 301 [[NSMutableArray alloc] init]); |
| 302 [self getParentKeywordsForNode:node inArray:nodeKeywords.get()]; |
| 303 [self addKeywords:nodeKeywords toSearchableItem:item]; |
| 304 [spotlightItems setObject:item forKey:spotlightID]; |
| 305 } |
| 306 return [spotlightItems allValues]; |
| 307 } |
| 308 |
| 309 - (void)clearAndReindexModel { |
| 310 [self cancelAllLargeIconPendingTasks]; |
| 311 base::WeakNSObject<BookmarksSpotlightManager> weakself(self); |
| 312 BlockWithError completion = ^(NSError* error) { |
| 313 if (!error) { |
| 314 dispatch_async(dispatch_get_main_queue(), ^{ |
| 315 base::scoped_nsobject<BookmarksSpotlightManager> strongSelf( |
| 316 [weakself retain]); |
| 317 if (!strongSelf) |
| 318 return; |
| 319 |
| 320 NSDate* startOfReindexing = [NSDate date]; |
| 321 strongSelf.get()->_nodesIndexed = 0; |
| 322 [strongSelf |
| 323 refreshNodeInIndex:strongSelf.get()->_bookmarkModel->root_node() |
| 324 initial:YES]; |
| 325 NSDate* endOfReindexing = [NSDate date]; |
| 326 NSTimeInterval indexingDuration = |
| 327 [endOfReindexing timeIntervalSinceDate:startOfReindexing]; |
| 328 UMA_HISTOGRAM_TIMES( |
| 329 "IOS.Spotlight.BookmarksIndexingDuration", |
| 330 base::TimeDelta::FromMillisecondsD(1000 * indexingDuration)); |
| 331 UMA_HISTOGRAM_COUNTS_1000("IOS.Spotlight.BookmarksInitialIndexSize", |
| 332 [strongSelf pendingLargeIconTasksCount]); |
| 333 [[NSUserDefaults standardUserDefaults] |
| 334 setObject:endOfReindexing |
| 335 forKey:@(spotlight::kSpotlightLastIndexingDateKey)]; |
| 336 |
| 337 [[NSUserDefaults standardUserDefaults] |
| 338 setObject:[NSNumber numberWithInteger: |
| 339 spotlight::kCurrentSpotlightIndexVersion] |
| 340 forKey:@(spotlight::kSpotlightLastIndexingVersionKey)]; |
| 341 [_delegate bookmarkUpdated]; |
| 342 }); |
| 343 } |
| 344 }; |
| 345 [self clearAllSpotlightItems:completion]; |
| 346 } |
| 347 |
| 348 @end |
OLD | NEW |