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/spotlight_util.h" |
| 6 |
| 7 #import <CoreSpotlight/CoreSpotlight.h> |
| 8 |
| 9 #include "base/metrics/histogram.h" |
| 10 #include "base/strings/sys_string_conversions.h" |
| 11 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 12 #include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h" |
| 13 #include "url/gurl.h" |
| 14 |
| 15 namespace { |
| 16 // This enum is used for Histogram. Items should not be removed or reordered and |
| 17 // this enum should be kept synced with histograms.xml. |
| 18 // The three states correspond to: |
| 19 // - SPOTLIGHT_UNSUPPORTED: Framework CoreSpotlight is not found and |
| 20 // [CSSearchableIndex class] returns nil. |
| 21 // - SPOTLIGHT_UNAVAILABLE: Framework is loaded but [CSSearchableIndex |
| 22 // isIndexingAvailable] return NO. Note: It is unclear if this state is |
| 23 // reachable (I could not find configuration where CoreSpotlight was loaded but |
| 24 // [CSSearchableIndex isIndexingAvailable] returned NO. |
| 25 // - SPOTLIGHT_AVAILABLE: Framework is loaded and [CSSearchableIndex |
| 26 // isIndexingAvailable] returns YES. Note: This does not mean the actual |
| 27 // indexing will happen. If the user disables Spotlight in the system settings, |
| 28 // [CSSearchableIndex isIndexingAvailable] still returns YES. |
| 29 enum Availability { |
| 30 SPOTLIGHT_UNSUPPORTED = 0, |
| 31 SPOTLIGHT_UNAVAILABLE, |
| 32 SPOTLIGHT_AVAILABLE, |
| 33 SPOTLIGHT_AVAILABILITY_COUNT |
| 34 }; |
| 35 |
| 36 // Documentation says that failed deletion should be retried. Set a maximum |
| 37 // value to avoid infinite loop. |
| 38 const int kMaxDeletionAttempts = 5; |
| 39 |
| 40 // Execute blockName block with up to retryCount retries on error. Execute |
| 41 // callback when done. |
| 42 void DoWithRetry(BlockWithError callback, |
| 43 NSUInteger retryCount, |
| 44 void (^blockName)(BlockWithError error)) { |
| 45 BlockWithError retryCallback = ^(NSError* error) { |
| 46 if (error && retryCount > 0) { |
| 47 DoWithRetry(callback, retryCount - 1, blockName); |
| 48 } else { |
| 49 if (callback) { |
| 50 callback(error); |
| 51 } |
| 52 } |
| 53 }; |
| 54 blockName(retryCallback); |
| 55 } |
| 56 |
| 57 // Execute blockName block with up to kMaxDeletionAttempts retries on error. |
| 58 // Execute callback when done. |
| 59 void DoWithRetry(BlockWithError completion, |
| 60 void (^blockName)(BlockWithError error)) { |
| 61 DoWithRetry(completion, kMaxDeletionAttempts, blockName); |
| 62 } |
| 63 |
| 64 } // namespace |
| 65 |
| 66 namespace spotlight { |
| 67 |
| 68 // NSUserDefaults key of entry containing date of the latest bookmarks indexing. |
| 69 const char kSpotlightLastIndexingDateKey[] = "SpotlightLastIndexingDate"; |
| 70 |
| 71 // NSUserDefault key of entry containing Chrome version of the latest bookmarks |
| 72 // indexing. |
| 73 const char kSpotlightLastIndexingVersionKey[] = "SpotlightLastIndexingVersion"; |
| 74 |
| 75 // The current version of the Spotlight index format. |
| 76 // Change this value if there are change int the information indexed in |
| 77 // Spotlight. This will force reindexation on next startup. |
| 78 // Value is stored in |kSpotlightLastIndexingVersionKey|. |
| 79 const int kCurrentSpotlightIndexVersion = 2; |
| 80 |
| 81 Domain SpotlightDomainFromString(NSString* domain) { |
| 82 SpotlightProvider* provider = |
| 83 ios::GetChromeBrowserProvider()->GetSpotlightProvider(); |
| 84 if ([domain hasPrefix:[provider->GetBookmarkDomain() |
| 85 stringByAppendingString:@"."]]) { |
| 86 return DOMAIN_BOOKMARKS; |
| 87 } else if ([domain hasPrefix:[provider->GetTopSitesDomain() |
| 88 stringByAppendingString:@"."]]) { |
| 89 return DOMAIN_TOPSITES; |
| 90 } else if ([domain hasPrefix:[provider->GetActionsDomain() |
| 91 stringByAppendingString:@"."]]) { |
| 92 return DOMAIN_ACTIONS; |
| 93 } |
| 94 // On normal flow, it is not possible to reach this point. When testing the |
| 95 // app, it may be possible though if the app is downgraded. |
| 96 NOTREACHED(); |
| 97 return DOMAIN_UNKNOWN; |
| 98 } |
| 99 |
| 100 NSString* StringFromSpotlightDomain(Domain domain) { |
| 101 SpotlightProvider* provider = |
| 102 ios::GetChromeBrowserProvider()->GetSpotlightProvider(); |
| 103 switch (domain) { |
| 104 case DOMAIN_BOOKMARKS: |
| 105 return provider->GetBookmarkDomain(); |
| 106 case DOMAIN_TOPSITES: |
| 107 return provider->GetTopSitesDomain(); |
| 108 case DOMAIN_ACTIONS: |
| 109 return provider->GetActionsDomain(); |
| 110 default: |
| 111 // On normal flow, it is not possible to reach this point. When testing |
| 112 // the app, it may be possible though if the app is downgraded. |
| 113 NOTREACHED(); |
| 114 return nil; |
| 115 } |
| 116 } |
| 117 |
| 118 void DeleteItemsWithIdentifiers(NSArray* items, BlockWithError callback) { |
| 119 void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) { |
| 120 [[CSSearchableIndex defaultSearchableIndex] |
| 121 deleteSearchableItemsWithIdentifiers:items |
| 122 completionHandler:errorBlock]; |
| 123 }; |
| 124 |
| 125 DoWithRetry(callback, deleteItems); |
| 126 } |
| 127 |
| 128 void DeleteSearchableDomainItems(Domain domain, BlockWithError callback) { |
| 129 void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) { |
| 130 [[CSSearchableIndex defaultSearchableIndex] |
| 131 deleteSearchableItemsWithDomainIdentifiers:@[ StringFromSpotlightDomain( |
| 132 domain) ] |
| 133 completionHandler:errorBlock]; |
| 134 }; |
| 135 |
| 136 DoWithRetry(callback, deleteItems); |
| 137 } |
| 138 |
| 139 void ClearAllSpotlightEntries(BlockWithError callback) { |
| 140 BlockWithError augmentedCallback = ^(NSError* error) { |
| 141 [[NSUserDefaults standardUserDefaults] |
| 142 removeObjectForKey:@(kSpotlightLastIndexingDateKey)]; |
| 143 if (callback) { |
| 144 callback(error); |
| 145 } |
| 146 }; |
| 147 |
| 148 void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) { |
| 149 [[CSSearchableIndex defaultSearchableIndex] |
| 150 deleteAllSearchableItemsWithCompletionHandler:errorBlock]; |
| 151 }; |
| 152 |
| 153 DoWithRetry(augmentedCallback, deleteItems); |
| 154 } |
| 155 |
| 156 bool IsSpotlightAvailable() { |
| 157 bool provided = ios::GetChromeBrowserProvider() |
| 158 ->GetSpotlightProvider() |
| 159 ->IsSpotlightEnabled(); |
| 160 if (!provided) { |
| 161 // The product does not support Spotlight, do not go further. |
| 162 return false; |
| 163 } |
| 164 bool loaded = !![CSSearchableIndex class]; |
| 165 bool available = loaded && [CSSearchableIndex isIndexingAvailable]; |
| 166 static dispatch_once_t once; |
| 167 dispatch_once(&once, ^{ |
| 168 Availability availability = SPOTLIGHT_UNSUPPORTED; |
| 169 if (loaded) { |
| 170 availability = SPOTLIGHT_UNAVAILABLE; |
| 171 } |
| 172 if (available) { |
| 173 availability = SPOTLIGHT_AVAILABLE; |
| 174 } |
| 175 UMA_HISTOGRAM_ENUMERATION("IOS.Spotlight.Availability", availability, |
| 176 SPOTLIGHT_AVAILABILITY_COUNT); |
| 177 }); |
| 178 return loaded && available; |
| 179 } |
| 180 |
| 181 void ClearSpotlightIndexWithCompletion(BlockWithError completion) { |
| 182 DCHECK(IsSpotlightAvailable()); |
| 183 ClearAllSpotlightEntries(completion); |
| 184 } |
| 185 |
| 186 NSString* GetSpotlightCustomAttributeItemID() { |
| 187 return ios::GetChromeBrowserProvider() |
| 188 ->GetSpotlightProvider() |
| 189 ->GetCustomAttributeItemID(); |
| 190 } |
| 191 |
| 192 void GetURLForSpotlightItemID(NSString* itemID, BlockWithNSURL completion) { |
| 193 NSString* queryString = |
| 194 [NSString stringWithFormat:@"%@ == \"%@\"", |
| 195 GetSpotlightCustomAttributeItemID(), itemID]; |
| 196 |
| 197 CSSearchQuery* query = |
| 198 [[CSSearchQuery alloc] initWithQueryString:queryString |
| 199 attributes:@[ @"contentURL" ]]; |
| 200 |
| 201 [query setFoundItemsHandler:^(NSArray<CSSearchableItem*>* items) { |
| 202 if ([items count] == 1) { |
| 203 CSSearchableItem* searchableItem = [items objectAtIndex:0]; |
| 204 if (searchableItem) { |
| 205 completion([[searchableItem attributeSet] contentURL]); |
| 206 return; |
| 207 } |
| 208 } |
| 209 completion(nil); |
| 210 |
| 211 }]; |
| 212 |
| 213 [query start]; |
| 214 } |
| 215 |
| 216 } // namespace spotlight |
OLD | NEW |