| Index: ios/chrome/app/spotlight/spotlight_util.mm
|
| diff --git a/ios/chrome/app/spotlight/spotlight_util.mm b/ios/chrome/app/spotlight/spotlight_util.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..93b24168dd676263cc7e25b38b6b4b50e7e5fc87
|
| --- /dev/null
|
| +++ b/ios/chrome/app/spotlight/spotlight_util.mm
|
| @@ -0,0 +1,216 @@
|
| +// 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/spotlight_util.h"
|
| +
|
| +#import <CoreSpotlight/CoreSpotlight.h>
|
| +
|
| +#include "base/metrics/histogram.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
|
| +#include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h"
|
| +#include "url/gurl.h"
|
| +
|
| +namespace {
|
| +// This enum is used for Histogram. Items should not be removed or reordered and
|
| +// this enum should be kept synced with histograms.xml.
|
| +// The three states correspond to:
|
| +// - SPOTLIGHT_UNSUPPORTED: Framework CoreSpotlight is not found and
|
| +// [CSSearchableIndex class] returns nil.
|
| +// - SPOTLIGHT_UNAVAILABLE: Framework is loaded but [CSSearchableIndex
|
| +// isIndexingAvailable] return NO. Note: It is unclear if this state is
|
| +// reachable (I could not find configuration where CoreSpotlight was loaded but
|
| +// [CSSearchableIndex isIndexingAvailable] returned NO.
|
| +// - SPOTLIGHT_AVAILABLE: Framework is loaded and [CSSearchableIndex
|
| +// isIndexingAvailable] returns YES. Note: This does not mean the actual
|
| +// indexing will happen. If the user disables Spotlight in the system settings,
|
| +// [CSSearchableIndex isIndexingAvailable] still returns YES.
|
| +enum Availability {
|
| + SPOTLIGHT_UNSUPPORTED = 0,
|
| + SPOTLIGHT_UNAVAILABLE,
|
| + SPOTLIGHT_AVAILABLE,
|
| + SPOTLIGHT_AVAILABILITY_COUNT
|
| +};
|
| +
|
| +// Documentation says that failed deletion should be retried. Set a maximum
|
| +// value to avoid infinite loop.
|
| +const int kMaxDeletionAttempts = 5;
|
| +
|
| +// Execute blockName block with up to retryCount retries on error. Execute
|
| +// callback when done.
|
| +void DoWithRetry(BlockWithError callback,
|
| + NSUInteger retryCount,
|
| + void (^blockName)(BlockWithError error)) {
|
| + BlockWithError retryCallback = ^(NSError* error) {
|
| + if (error && retryCount > 0) {
|
| + DoWithRetry(callback, retryCount - 1, blockName);
|
| + } else {
|
| + if (callback) {
|
| + callback(error);
|
| + }
|
| + }
|
| + };
|
| + blockName(retryCallback);
|
| +}
|
| +
|
| +// Execute blockName block with up to kMaxDeletionAttempts retries on error.
|
| +// Execute callback when done.
|
| +void DoWithRetry(BlockWithError completion,
|
| + void (^blockName)(BlockWithError error)) {
|
| + DoWithRetry(completion, kMaxDeletionAttempts, blockName);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace spotlight {
|
| +
|
| +// NSUserDefaults key of entry containing date of the latest bookmarks indexing.
|
| +const char kSpotlightLastIndexingDateKey[] = "SpotlightLastIndexingDate";
|
| +
|
| +// NSUserDefault key of entry containing Chrome version of the latest bookmarks
|
| +// indexing.
|
| +const char kSpotlightLastIndexingVersionKey[] = "SpotlightLastIndexingVersion";
|
| +
|
| +// The current version of the Spotlight index format.
|
| +// Change this value if there are change int the information indexed in
|
| +// Spotlight. This will force reindexation on next startup.
|
| +// Value is stored in |kSpotlightLastIndexingVersionKey|.
|
| +const int kCurrentSpotlightIndexVersion = 2;
|
| +
|
| +Domain SpotlightDomainFromString(NSString* domain) {
|
| + SpotlightProvider* provider =
|
| + ios::GetChromeBrowserProvider()->GetSpotlightProvider();
|
| + if ([domain hasPrefix:[provider->GetBookmarkDomain()
|
| + stringByAppendingString:@"."]]) {
|
| + return DOMAIN_BOOKMARKS;
|
| + } else if ([domain hasPrefix:[provider->GetTopSitesDomain()
|
| + stringByAppendingString:@"."]]) {
|
| + return DOMAIN_TOPSITES;
|
| + } else if ([domain hasPrefix:[provider->GetActionsDomain()
|
| + stringByAppendingString:@"."]]) {
|
| + return DOMAIN_ACTIONS;
|
| + }
|
| + // On normal flow, it is not possible to reach this point. When testing the
|
| + // app, it may be possible though if the app is downgraded.
|
| + NOTREACHED();
|
| + return DOMAIN_UNKNOWN;
|
| +}
|
| +
|
| +NSString* StringFromSpotlightDomain(Domain domain) {
|
| + SpotlightProvider* provider =
|
| + ios::GetChromeBrowserProvider()->GetSpotlightProvider();
|
| + switch (domain) {
|
| + case DOMAIN_BOOKMARKS:
|
| + return provider->GetBookmarkDomain();
|
| + case DOMAIN_TOPSITES:
|
| + return provider->GetTopSitesDomain();
|
| + case DOMAIN_ACTIONS:
|
| + return provider->GetActionsDomain();
|
| + default:
|
| + // On normal flow, it is not possible to reach this point. When testing
|
| + // the app, it may be possible though if the app is downgraded.
|
| + NOTREACHED();
|
| + return nil;
|
| + }
|
| +}
|
| +
|
| +void DeleteItemsWithIdentifiers(NSArray* items, BlockWithError callback) {
|
| + void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) {
|
| + [[CSSearchableIndex defaultSearchableIndex]
|
| + deleteSearchableItemsWithIdentifiers:items
|
| + completionHandler:errorBlock];
|
| + };
|
| +
|
| + DoWithRetry(callback, deleteItems);
|
| +}
|
| +
|
| +void DeleteSearchableDomainItems(Domain domain, BlockWithError callback) {
|
| + void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) {
|
| + [[CSSearchableIndex defaultSearchableIndex]
|
| + deleteSearchableItemsWithDomainIdentifiers:@[ StringFromSpotlightDomain(
|
| + domain) ]
|
| + completionHandler:errorBlock];
|
| + };
|
| +
|
| + DoWithRetry(callback, deleteItems);
|
| +}
|
| +
|
| +void ClearAllSpotlightEntries(BlockWithError callback) {
|
| + BlockWithError augmentedCallback = ^(NSError* error) {
|
| + [[NSUserDefaults standardUserDefaults]
|
| + removeObjectForKey:@(kSpotlightLastIndexingDateKey)];
|
| + if (callback) {
|
| + callback(error);
|
| + }
|
| + };
|
| +
|
| + void (^deleteItems)(BlockWithError) = ^(BlockWithError errorBlock) {
|
| + [[CSSearchableIndex defaultSearchableIndex]
|
| + deleteAllSearchableItemsWithCompletionHandler:errorBlock];
|
| + };
|
| +
|
| + DoWithRetry(augmentedCallback, deleteItems);
|
| +}
|
| +
|
| +bool IsSpotlightAvailable() {
|
| + bool provided = ios::GetChromeBrowserProvider()
|
| + ->GetSpotlightProvider()
|
| + ->IsSpotlightEnabled();
|
| + if (!provided) {
|
| + // The product does not support Spotlight, do not go further.
|
| + return false;
|
| + }
|
| + bool loaded = !![CSSearchableIndex class];
|
| + bool available = loaded && [CSSearchableIndex isIndexingAvailable];
|
| + static dispatch_once_t once;
|
| + dispatch_once(&once, ^{
|
| + Availability availability = SPOTLIGHT_UNSUPPORTED;
|
| + if (loaded) {
|
| + availability = SPOTLIGHT_UNAVAILABLE;
|
| + }
|
| + if (available) {
|
| + availability = SPOTLIGHT_AVAILABLE;
|
| + }
|
| + UMA_HISTOGRAM_ENUMERATION("IOS.Spotlight.Availability", availability,
|
| + SPOTLIGHT_AVAILABILITY_COUNT);
|
| + });
|
| + return loaded && available;
|
| +}
|
| +
|
| +void ClearSpotlightIndexWithCompletion(BlockWithError completion) {
|
| + DCHECK(IsSpotlightAvailable());
|
| + ClearAllSpotlightEntries(completion);
|
| +}
|
| +
|
| +NSString* GetSpotlightCustomAttributeItemID() {
|
| + return ios::GetChromeBrowserProvider()
|
| + ->GetSpotlightProvider()
|
| + ->GetCustomAttributeItemID();
|
| +}
|
| +
|
| +void GetURLForSpotlightItemID(NSString* itemID, BlockWithNSURL completion) {
|
| + NSString* queryString =
|
| + [NSString stringWithFormat:@"%@ == \"%@\"",
|
| + GetSpotlightCustomAttributeItemID(), itemID];
|
| +
|
| + CSSearchQuery* query =
|
| + [[CSSearchQuery alloc] initWithQueryString:queryString
|
| + attributes:@[ @"contentURL" ]];
|
| +
|
| + [query setFoundItemsHandler:^(NSArray<CSSearchableItem*>* items) {
|
| + if ([items count] == 1) {
|
| + CSSearchableItem* searchableItem = [items objectAtIndex:0];
|
| + if (searchableItem) {
|
| + completion([[searchableItem attributeSet] contentURL]);
|
| + return;
|
| + }
|
| + }
|
| + completion(nil);
|
| +
|
| + }];
|
| +
|
| + [query start];
|
| +}
|
| +
|
| +} // namespace spotlight
|
|
|