/core/externals/google-toolbox-for-mac/AppKit/GTMGoogleSearch.m
http://macfuse.googlecode.com/ · Objective C · 541 lines · 414 code · 58 blank · 69 comment · 56 complexity · 45343677cbdc2b7bc08819044d616336 MD5 · raw file
- //
- // GTMGoogleSearch.m
- //
- // Copyright 2006-2009 Google Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may not
- // use this file except in compliance with the License. You may obtain a copy
- // of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- // License for the specific language governing permissions and limitations under
- // the License.
- //
- #import "GTMGoogleSearch.h"
- #import "GTMDefines.h"
- #if GTM_IPHONE_SDK
- #import <UIKit/UIKit.h>
- #else
- #import <AppKit/AppKit.h>
- #endif // GTM_IPHONE_SDK
- typedef struct {
- NSString *language;
- NSString *country;
- // we don't include a language, we'll use what we get from the OS
- NSString *defaultDomain;
- } LanguageDefaultInfo;
- //
- // this is a seed mapping from languages to domains for google search.
- // this doesn't have to be complete, as it is just a seed.
- //
- //
- static LanguageDefaultInfo kLanguageListDefaultMappingTable[] = {
- // order is important, first match is taken
- // if country is |nil|, then only language has to match
- { @"en", @"US", @"com" }, // english - united states
- { @"en", @"GB", @"co.uk" }, // english - united kingdom
- { @"en", @"CA", @"ca" }, // english - canada
- { @"en", @"AU", @"com.au" }, // english - australia
- { @"en", @"NZ", @"com" }, // english - new zealand
- { @"en", @"IE", @"ie" }, // english - ireland
- { @"en", @"IN", @"co.in" }, // english - india
- { @"en", @"PH", @"com.ph" }, // english - philippines
- { @"en", @"SG", @"com.sg" }, // english - singapore
- { @"en", @"ZA", @"co.za" }, // english - south africa
- { @"en", @"IL", @"co.il" }, // english - israel
- { @"en", nil , @"com" }, // english (catch all)
- { @"fr", @"CA", @"ca" }, // french - canada
- { @"fr", @"CH", @"ch" }, // french - switzerland
- { @"fr", nil , @"fr" }, // france
- { @"it", nil , @"it" }, // italy
- { @"de", @"AT", @"at" }, // german - austria
- { @"de", nil , @"de" }, // germany
- { @"es", @"MX", @"com.mx" }, // spanish - mexico
- { @"es", @"AR", @"com.ar" }, // spanish - argentina
- { @"es", @"CL", @"cl" }, // spanish - chile
- { @"es", @"CO", @"com.co" }, // spanish - colombia
- { @"es", @"PE", @"com.pe" }, // spanish - peru
- { @"es", @"VE", @"co.ve" }, // venezuela
- { @"es", nil , @"es" }, // spain
- { @"zh", @"TW", @"com.tw" }, // taiwan
- { @"zh", @"HK", @"com.hk" }, // hong kong
- { @"zh", nil , @"cn" }, // chinese (catch all)
- { @"ja", nil , @"co.jp" }, // japan
- { @"ko", nil , @"co.kr" }, // korea
- { @"nl", @"BE", @"be" }, // dutch - belgium
- { @"nl", nil , @"nl" }, // (dutch) netherlands
- { @"ru", nil , @"ru" }, // russia
- { @"pt", @"BZ", @"com.br"}, // portuguese - brazil
- { @"pt", nil , @"pt" }, // portugal
- { @"sv", nil , @"se" }, // sweden
- { @"nn", nil , @"no" }, // norway (two variants)
- { @"nb", nil , @"no" }, // norway (two variants)
- { @"da", nil , @"dk" }, // denmark
- { @"fi", nil , @"fi" }, // finland
- { @"bg", nil , @"bg" }, // bulgaria
- { @"hr", nil , @"hr" }, // croatia
- { @"cx", nil , @"cz" }, // czech republic
- { @"el", nil , @"gr" }, // greece
- { @"hu", nil , @"co.hu" }, // hungary
- { @"ro", nil , @"ro" }, // romania
- { @"sk", nil , @"sk" }, // slovakia
- { @"sl", nil , @"si" }, // slovenia
- { @"tr", nil , @"com.tr" }, // turkey
- { @"my", nil , @"com.my" }, // malaysia
- { @"th", nil , @"co.th" }, // thailand
- { @"uk", nil , @"com.ua" }, // ukraine
- { @"vi", nil , @"com.vn" }, // vietnam
- { @"af", nil , @"com.za" }, // south africa (afrikaans)
- { @"hi", nil , @"co.in" }, // india (hindi)
- { @"id", nil , @"co.id" }, // indonesia
- { @"pl", nil , @"pl" }, // poland
- };
- // the notification we use for syncing up instances in different processes
- static NSString *const kNotificationName
- = @"com.google.GoogleSearchAllApps.prefsWritten";
- // this is the bundle id we use for the pref file used for all apps
- static CFStringRef const kAllAppsBuildIdentifier
- = CFSTR("com.google.GoogleSearchAllApps");
- static CFStringRef const kPreferredDomainPrefKey
- = CFSTR("com.google.PreferredDomain");
- static CFStringRef const kPreferredLanguagePrefKey
- = CFSTR("com.google.PreferredLanguage");
- static NSString *const kDefaultDomain = @"com";
- static NSString *const kDefaultLanguage = @"en";
- #define SEARCH_URL_TEMPLATE @"http://www.google.%@/%@?%@"
- @interface GTMGoogleSearch (PrivateMethods)
- - (void)defaultDomain:(NSString**)preferedDomain
- language:(NSString**)preferredLanguage;
- - (void)reloadAllAppCachedValues:(NSNotification*)notification;
- - (void)updateAllAppsDomain:(NSString*)domain language:(NSString*)language;
- @end
- @implementation GTMGoogleSearch
- + (GTMGoogleSearch *)sharedInstance {
- static GTMGoogleSearch *obj;
- if (!obj) {
- obj = [[self alloc] init];
- }
- return obj;
- }
- - (id)init {
- self = [super init];
- if (self != nil) {
- #if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- // register for the notification
- NSDistributedNotificationCenter *distCenter =
- [NSDistributedNotificationCenter defaultCenter];
- [distCenter addObserver:self
- selector:@selector(reloadAllAppCachedValues:)
- name:kNotificationName
- object:nil];
- #endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- // load the allApps value
- [self reloadAllAppCachedValues:nil];
- // load the cur app value
- CFStringRef domain
- = CFPreferencesCopyValue(kPreferredDomainPrefKey,
- kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFStringRef lang = CFPreferencesCopyValue(kPreferredLanguagePrefKey,
- kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- // make sure we got values for both and domain is not empty
- if (domain && CFStringGetLength(domain) == 0) {
- CFRelease(domain);
- domain = nil;
- if (lang) {
- CFRelease(lang);
- lang = nil;
- }
- }
-
- curAppCachedDomain_ = (NSString *)domain;
- curAppCachedLanguage_ = (NSString *)lang;
-
- NSBundle *bundle = [NSBundle mainBundle];
-
- NSDictionary *appArgs
- = [bundle objectForInfoDictionaryKey:GTMGoogleSearchClientAppArgsKey];
- globalSearchArguments_ = [appArgs retain];
- }
- return self;
- }
- #if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- - (void)finalize {
- [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
- [super finalize];
- }
- #endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- - (void)dealloc {
- #if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
- #endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- [allAppsCachedDomain_ release];
- [allAppsCachedLanguage_ release];
- [curAppCachedDomain_ release];
- [curAppCachedLanguage_ release];
- [globalSearchArguments_ release];
- [super dealloc];
- }
- - (void)preferredDomain:(NSString **)domain
- language:(NSString**)language
- areCurrentAppOnly:(BOOL*)currentAppOnly {
- BOOL localCurrentAppOnly = YES;
- NSString *localDomain = curAppCachedDomain_;
- NSString *localLanguage = curAppCachedLanguage_;
- // if either one wasn't there, drop both, and use any app if we can
- if (!localDomain || !localLanguage) {
- localCurrentAppOnly = NO;
- localDomain = allAppsCachedDomain_;
- localLanguage = allAppsCachedLanguage_;
- // if we didn't get anything from the prefs, go with the defaults
- if (!localDomain || !localLanguage) {
- // if either one wasn't there, drop both, and use defaults
- [self defaultDomain:&localDomain language:&localLanguage];
- }
- }
- if (!localDomain || !localLanguage) {
- _GTMDevLog(@"GTMGoogleSearch: Failed to get the preferred domain/language "
- @"from prefs or defaults");
- }
- if (language) {
- *language = [[localLanguage retain] autorelease];
- }
- if (domain) {
- *domain = [[localDomain retain] autorelease];
- }
- if (currentAppOnly) {
- *currentAppOnly = localCurrentAppOnly;
- }
- }
- - (void)updatePreferredDomain:(NSString*)domain
- language:(NSString*)language
- currentApplicationOnly:(BOOL)currentAppOnly {
- // valid inputs?
- if (!domain || ![domain length] || !language) {
- return;
- }
- if (currentAppOnly) {
- // if they are the same, don't do anything
- if ((domain == nil && curAppCachedDomain_ == nil &&
- language == nil && curAppCachedLanguage_ == nil) ||
- ([domain isEqualToString:curAppCachedDomain_] &&
- [language isEqualToString:curAppCachedLanguage_])) {
- return;
- }
- // save them out
- CFPreferencesSetValue(kPreferredDomainPrefKey,
- (CFStringRef)domain,
- kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFPreferencesSetValue(kPreferredLanguagePrefKey,
- (CFStringRef)language,
- kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFPreferencesSynchronize(kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- // update our locals
- [curAppCachedDomain_ release];
- [curAppCachedLanguage_ release];
- curAppCachedDomain_ = [domain copy];
- curAppCachedLanguage_ = [language copy];
- } else {
- // Set the "any application" values
- [self updateAllAppsDomain:domain language:language];
- // Clear the current application values (if there were any)
- [self clearPreferredDomainAndLanguageForCurrentApplication];
- }
- }
- - (void)clearPreferredDomainAndLanguageForCurrentApplication {
- // flush what's in the file
- CFPreferencesSetValue(kPreferredDomainPrefKey,
- NULL,
- kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFPreferencesSetValue(kPreferredLanguagePrefKey,
- NULL,
- kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFPreferencesSynchronize(kCFPreferencesCurrentApplication,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- // clear our locals
- [curAppCachedDomain_ release];
- [curAppCachedLanguage_ release];
- curAppCachedDomain_ = nil;
- curAppCachedLanguage_ = nil;
- }
- - (void)clearPreferredDomainAndLanguageForAllApps {
- // nil/nil to clear things out, this will also update our cached values.
- [self updateAllAppsDomain:nil language:nil];
- }
- - (NSDictionary *)globalSearchArguments {
- return globalSearchArguments_;
- }
- - (void)setGlobalSearchArguments:(NSDictionary *)args {
- [globalSearchArguments_ autorelease];
- globalSearchArguments_ = [args copy];
- }
- - (NSString*)searchURLFor:(NSString*)queryText
- ofType:(NSString*)type
- arguments:(NSDictionary *)localArgs {
- if (!type) {
- return nil;
- }
-
- NSString *language;
- NSString *domain;
- [self preferredDomain:&domain
- language:&language
- areCurrentAppOnly:NULL];
- NSMutableDictionary *args
- = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- @"UTF-8", @"ie",
- @"UTF-8", @"oe",
- language, @"hl",
- nil];
- if (queryText) {
- [args setObject:queryText forKey:@"q"];
- }
-
- NSDictionary *globalSearchArgs = [self globalSearchArguments];
- if (globalSearchArgs) {
- [args addEntriesFromDictionary:globalSearchArgs];
- }
- if (localArgs) {
- [args addEntriesFromDictionary:localArgs];
- }
-
- NSMutableArray *clientArgs = [NSMutableArray array];
- NSString *key;
- NSNull *nsNull = [NSNull null];
- GTM_FOREACH_KEY(key, args) {
- NSString *object = [args objectForKey:key];
- if (![object isEqual:nsNull]) {
- #if DEBUG
- // In debug we check key and object for things that should be escaped.
- // Note that percent is not in there because escaped strings will have
- // percents in them
- NSCharacterSet *cs = [NSCharacterSet characterSetWithCharactersInString:
- @"!*'();:@&=+$,/?#[] "];
- NSRange range = [key rangeOfCharacterFromSet:cs];
- if (range.location != NSNotFound) {
- _GTMDevLog(@"Unescaped string %@ in argument pair {%@, %@} in -[%@ %@]",
- key, key, object, [self class], NSStringFromSelector(_cmd));
- }
- range = [object rangeOfCharacterFromSet:cs];
- if (range.location != NSNotFound) {
- _GTMDevLog(@"Unescaped string %@ in argument pair {%@,%@ } in -[%@ %@]",
- object, key, object, [self class],
- NSStringFromSelector(_cmd));
- }
- #endif // DEBUG
- NSString *arg = [NSString stringWithFormat:@"%@=%@", key, object];
- [clientArgs addObject:arg];
- }
- }
- NSString *clientArg = [clientArgs componentsJoinedByString:@"&"];
- NSString *url = [NSString stringWithFormat:SEARCH_URL_TEMPLATE,
- domain, type, clientArg];
- return url;
- }
- - (BOOL)performQuery:(NSString*)queryText
- ofType:(NSString *)type
- arguments:(NSDictionary *)localArgs {
- BOOL success = NO;
- NSString *urlString = [self searchURLFor:queryText
- ofType:type
- arguments:localArgs];
- if (urlString) {
- NSURL *url = [NSURL URLWithString:urlString];
- if (url) {
- #if GTM_IPHONE_SDK
- success = [[UIApplication sharedApplication] openURL:url];
- #else // GTM_IPHONE_SDK
- success = [[NSWorkspace sharedWorkspace] openURL:url];
- #endif // GTM_IPHONE_SDK
- }
- }
- return success;
- }
- @end
- @implementation GTMGoogleSearch (PrivateMethods)
- - (void)defaultDomain:(NSString**)preferredDomain
- language:(NSString**)preferredLanguage {
- // must have both
- if (!preferredDomain || !preferredLanguage) {
- return;
- }
- // make sure they are clear to start
- *preferredDomain = nil;
- *preferredLanguage = nil;
- // loop over their language list trying to find something we have in
- // out default table.
- NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
- NSArray* languages = [defs objectForKey:@"AppleLanguages"];
- // the current locale is only based on what languages the running apps is
- // localized to, so we stick that at the end in case we weren't able to
- // find anything else as a match, we'll match that.
- languages =
- [languages arrayByAddingObject:[[NSLocale currentLocale] localeIdentifier]];
- NSEnumerator *enumerator = [languages objectEnumerator];
- NSString *localeIdentifier;
- while ((localeIdentifier = [enumerator nextObject])) {
- NSDictionary *localeParts
- = [NSLocale componentsFromLocaleIdentifier:localeIdentifier];
- NSString *localeLanguage = [localeParts objectForKey:NSLocaleLanguageCode];
- // we don't use NSLocaleScriptCode for now
- NSString *localeCountry = [localeParts objectForKey:NSLocaleCountryCode];
- LanguageDefaultInfo *scan = kLanguageListDefaultMappingTable;
- LanguageDefaultInfo *end = (scan + (sizeof(kLanguageListDefaultMappingTable)
- / sizeof(LanguageDefaultInfo)));
- // find a match
- // check language, and if country is not nil, check that
- for ( ; scan < end ; ++scan) {
- if ([localeLanguage isEqualToString:scan->language] &&
- (!(scan->country) || [localeCountry isEqualToString:scan->country])) {
- *preferredDomain = scan->defaultDomain;
- *preferredLanguage = localeLanguage;
- return; // out of here
- }
- }
- }
- *preferredDomain = kDefaultDomain;
- *preferredLanguage = kDefaultLanguage;
- }
- // -reloadAllAppCachedValues:
- //
- - (void)reloadAllAppCachedValues:(NSNotification*)notification {
- // drop the old...
- [allAppsCachedDomain_ release];
- [allAppsCachedLanguage_ release];
- allAppsCachedDomain_ = nil;
- allAppsCachedLanguage_ = nil;
- // load the new
- CFPreferencesSynchronize(kAllAppsBuildIdentifier,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFStringRef domain = CFPreferencesCopyValue(kPreferredDomainPrefKey,
- kAllAppsBuildIdentifier,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFStringRef lang = CFPreferencesCopyValue(kPreferredLanguagePrefKey,
- kAllAppsBuildIdentifier,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- // make sure we got values for both and domain is not empty
- if (domain && CFStringGetLength(domain) == 0) {
- CFRelease(domain);
- domain = nil;
- if (lang) {
- CFRelease(lang);
- lang = nil;
- }
- }
- allAppsCachedDomain_ = (NSString *)domain;
- allAppsCachedLanguage_ = (NSString *)lang;
- }
- // -updateAllAppsDomain:language:
- //
- - (void)updateAllAppsDomain:(NSString*)domain language:(NSString*)language {
- // domain and language can be nil to clear the values
- // if they are the same, don't do anything
- if ((domain == nil && allAppsCachedDomain_ == nil &&
- language == nil && allAppsCachedLanguage_ == nil) ||
- ([domain isEqualToString:allAppsCachedDomain_] &&
- [language isEqualToString:allAppsCachedLanguage_])) {
- return;
- }
- // write it to the file
- CFPreferencesSetValue(kPreferredDomainPrefKey,
- (CFStringRef)domain,
- kAllAppsBuildIdentifier,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFPreferencesSetValue(kPreferredLanguagePrefKey,
- (CFStringRef)language,
- kAllAppsBuildIdentifier,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- CFPreferencesSynchronize(kAllAppsBuildIdentifier,
- kCFPreferencesCurrentUser,
- kCFPreferencesAnyHost);
- // update our values
- [allAppsCachedDomain_ release];
- [allAppsCachedLanguage_ release];
- allAppsCachedDomain_ = [domain copy];
- allAppsCachedLanguage_ = [language copy];
- #if GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- // NOTE: we'll go ahead and reload when this comes back to ourselves since
- // there is a race here if two folks wrote at about the same time.
- NSDistributedNotificationCenter *distCenter =
- [NSDistributedNotificationCenter defaultCenter];
- [distCenter postNotificationName:kNotificationName
- object:nil
- userInfo:nil];
- #endif // GTM_GOOGLE_SEARCH_SUPPORTS_DISTRIBUTED_NOTIFICATIONS
- }
- @end