PageRenderTime 62ms CodeModel.GetById 15ms app.highlight 42ms RepoModel.GetById 2ms app.codeStats 0ms

/core/externals/update-engine/externals/google-toolbox-for-mac/AppKit/GTMGetURLHandler.m

http://macfuse.googlecode.com/
Objective C | 308 lines | 211 code | 19 blank | 78 comment | 29 complexity | 2b5f09eb320ffada04bc91ae45b66e30 MD5 | raw file
  1//
  2//  GTMGetURLHandler.m
  3//
  4//  Copyright 2008 Google Inc.
  5//
  6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7//  use this file except in compliance with the License.  You may obtain a copy
  8//  of the License at
  9// 
 10//  http://www.apache.org/licenses/LICENSE-2.0
 11// 
 12//  Unless required by applicable law or agreed to in writing, software
 13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 15//  License for the specific language governing permissions and limitations under
 16//  the License.
 17//
 18
 19// Add this class to your app to have get URL handled almost automatically for
 20// you. For each entry in your CFBundleURLTypes dictionaries, add a new
 21// key/object pair of GTMBundleURLClass/the name of the class you want
 22// to have handle the scheme(s).
 23// Then have that class respond to the class method:
 24//   + (BOOL)gtm_openURL:(NSURL*)url
 25// and voila, it will just work.
 26// Note that in Debug mode we will do extensive testing to make sure that this
 27// is all hooked up correctly, and will spew out to the console if we 
 28// find anything amiss.
 29//
 30// Example plist entry
 31// ...
 32// 
 33// <key>CFBundleURLTypes</key>
 34// <array>
 35//   <dict>
 36// 	   <key>CFBundleURLName</key>
 37// 	   <string>Google Suggestion URL</string>
 38//	   <key>GTMBundleURLClass</key>
 39//	   <string>GoogleSuggestURLHandler</string>
 40//	   <key>CFBundleURLSchemes</key>
 41//	   <array>
 42//	     <string>googlesuggest</string>
 43//       <string>googlesuggestextreme</string>
 44//	   </array>
 45//	 </dict>
 46// </array>
 47//
 48//
 49// Example implementation
 50// @interface GoogleSuggestURLHandler
 51// @end
 52// @implementation GoogleSuggestURLHandler
 53// + (BOOL)gtm_openURL:(NSURL*)url {
 54//   NSLog(@"%@", url);
 55// }
 56// @end
 57
 58#import <AppKit/AppKit.h>
 59#import "GTMNSAppleEventDescriptor+Foundation.h"
 60#import "GTMMethodCheck.h"
 61
 62static NSString *const kGTMBundleURLClassKey = @"GTMBundleURLClass";
 63// A variety of constants Apple really should have defined somewhere to
 64// allow the compiler to find your typos.
 65static NSString *const kGTMCFBundleURLSchemesKey = @"CFBundleURLSchemes";
 66static NSString *const kGTMCFBundleURLNameKey = @"CFBundleURLName";
 67static NSString *const kGTMCFBundleTypeRoleKey = @"CFBundleTypeRole";
 68static NSString *const kGTMCFBundleURLTypesKey = @"CFBundleURLTypes";
 69static NSString *const kGTMCFBundleViewerRole = @"Viewer";
 70static NSString *const kGTMCFBundleEditorRole = @"Editor";
 71
 72// Set this macro elsewhere is you want to force the
 73// bundle checks on/off. They are nice for debugging
 74// problems, but shouldn't be required in a release version
 75// unless you are paranoid about your users messing with your
 76// Info.plist
 77#ifndef GTM_CHECK_BUNDLE_URL_CLASSES
 78#define GTM_CHECK_BUNDLE_URL_CLASSES DEBUG
 79#endif  // GTM_CHECK_BUNDLE_URL_CLASSES
 80
 81@protocol GTMGetURLHandlerProtocol
 82+ (BOOL)gtm_openURL:(NSURL*)url;
 83@end
 84
 85@interface GTMGetURLHandler : NSObject {
 86  NSArray *urlTypes_;
 87}
 88- (id)initWithTypes:(NSArray*)urlTypes;
 89- (void)getUrl:(NSAppleEventDescriptor *)event 
 90withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
 91- (void)addError:(OSStatus)error 
 92 withDescription:(NSString*)string 
 93    toDescriptor:(NSAppleEventDescriptor *)desc;
 94+ (id)handlerForBundle:(NSBundle *)bundle;
 95+ (void)getUrl:(NSAppleEventDescriptor *)event 
 96withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
 97@end
 98
 99@implementation GTMGetURLHandler
100GTM_METHOD_CHECK(NSNumber, gtm_appleEventDescriptor);
101GTM_METHOD_CHECK(NSString, gtm_appleEventDescriptor);
102
103+ (void)load {
104  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
105  NSAppleEventManager *man = [NSAppleEventManager sharedAppleEventManager];
106  [man setEventHandler:self 
107           andSelector:@selector(getUrl:withReplyEvent:) 
108         forEventClass:kInternetEventClass 
109            andEventID:kAEGetURL]; 
110  [pool drain];
111}
112
113+ (void)getUrl:(NSAppleEventDescriptor *)event 
114withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
115  static GTMGetURLHandler *sHandler = nil;
116  if (!sHandler) {
117    NSBundle *bundle = [NSBundle mainBundle];
118    sHandler = [GTMGetURLHandler handlerForBundle:bundle];
119    if (sHandler) {
120      [sHandler retain];
121    }
122  }
123  [sHandler getUrl:event withReplyEvent:replyEvent];
124}
125  
126+ (id)handlerForBundle:(NSBundle *)bundle {
127  GTMGetURLHandler *handler = nil;
128  NSArray *urlTypes 
129    = [bundle objectForInfoDictionaryKey:kGTMCFBundleURLTypesKey];
130  if (urlTypes) {
131    handler = [[[GTMGetURLHandler alloc] initWithTypes:urlTypes] autorelease];
132  } else {
133    // COV_NF_START
134    // Hard to test it if we don't have it.
135    _GTMDevLog(@"If you don't have CFBundleURLTypes in your plist, you may want"
136               @" to remove GTMGetURLHandler.m from your project");
137    // COV_NF_END
138  }
139  return handler;
140}
141
142- (id)initWithTypes:(NSArray*)urlTypes {
143  if ((self = [super init])) {
144    urlTypes_ = [urlTypes retain];
145#if GTM_CHECK_BUNDLE_URL_CLASSES
146    // Some debug handling to check to make sure we can handle the
147    // classes properly. We check here instead of at init in case some of the
148    // handlers are being handled by plugins or other imported code that are 
149    // loaded after we have been initialized.
150    NSDictionary *urlType;
151    GTM_FOREACH_OBJECT(urlType, urlTypes_) {
152      NSString *className = [urlType objectForKey:kGTMBundleURLClassKey];
153      if ([className length]) {
154        Class cls = NSClassFromString(className);
155        if (cls) {
156          if (![cls respondsToSelector:@selector(gtm_openURL:)]) {
157            _GTMDevLog(@"Class %@ for URL handler %@ "
158                       @"(URL schemes: %@) doesn't respond to openURL:",
159                       className,
160                       [urlType objectForKey:kGTMCFBundleURLNameKey],
161                       [urlType objectForKey:kGTMCFBundleURLSchemesKey]);
162          }
163        } else {
164          _GTMDevLog(@"Unable to get class %@ for URL handler %@ "
165                     @"(URL schemes: %@)",
166                     className,
167                     [urlType objectForKey:kGTMCFBundleURLNameKey],
168                     [urlType objectForKey:kGTMCFBundleURLSchemesKey]);
169        }
170      } else {
171        NSString *role = [urlType objectForKey:kGTMCFBundleTypeRoleKey];
172        if ([role caseInsensitiveCompare:kGTMCFBundleViewerRole] == NSOrderedSame ||
173            [role caseInsensitiveCompare:kGTMCFBundleEditorRole] == NSOrderedSame) {
174          _GTMDevLog(@"Missing %@ for URL handler %@ "
175                     @"(URL schemes: %@)",
176                     kGTMBundleURLClassKey,
177                     [urlType objectForKey:kGTMCFBundleURLNameKey],
178                     [urlType objectForKey:kGTMCFBundleURLSchemesKey]);
179        }
180      }
181    }
182#endif  // GTM_CHECK_BUNDLE_URL_CLASSES
183  }
184  return self;
185}
186
187// COV_NF_START
188// Singleton is never dealloc'd
189- (void)dealloc {
190  [urlTypes_ release];
191  [super dealloc];
192}
193// COV_NF_END
194
195
196- (NSURL*)extractURLFromEvent:(NSAppleEventDescriptor*)event
197               withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
198  NSAppleEventDescriptor *desc 
199    = [event paramDescriptorForKeyword:keyDirectObject];
200  NSString *urlstring = [desc stringValue];
201  NSURL *url = [NSURL URLWithString:urlstring];
202  if (!url) {
203    // COV_NF_START
204    // Can't convince the OS to give me a bad URL
205    [self addError:errAECoercionFail 
206   withDescription:@"Unable to extract url from key direct object." 
207      toDescriptor:replyEvent];
208    // COV_NF_END
209  }
210  return url;
211}
212
213- (Class)getClassForScheme:(NSString *)scheme 
214            withReplyEvent:(NSAppleEventDescriptor*)replyEvent {
215  NSDictionary *urlType;
216  Class cls = nil;
217  NSString *typeScheme = nil;
218  GTM_FOREACH_OBJECT(urlType, urlTypes_) {
219    NSArray *schemes = [urlType objectForKey:kGTMCFBundleURLSchemesKey];
220    NSString *aScheme;
221    GTM_FOREACH_OBJECT(aScheme, schemes) {
222      if ([aScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
223        typeScheme = aScheme;
224        break;
225      }
226    }
227    if (typeScheme) {
228      break;
229    }
230  }
231  if (typeScheme) {
232    NSString *class = [urlType objectForKey:kGTMBundleURLClassKey];
233    if (class) {
234      cls = NSClassFromString(class);
235    }
236    if (!cls) {
237      NSString *errorString 
238        = [NSString stringWithFormat:@"Unable to instantiate class for "
239           @"%@:%@ for scheme:%@.", 
240           kGTMBundleURLClassKey, class, typeScheme];
241      [self addError:errAECorruptData 
242     withDescription:errorString
243        toDescriptor:replyEvent];
244    } else {
245      if (![cls respondsToSelector:@selector(gtm_openURL:)]) {
246        NSString *errorString 
247          = [NSString stringWithFormat:@"Class %@:%@ for scheme:%@ does not"
248             @"respond to gtm_openURL:",
249             kGTMBundleURLClassKey, class, typeScheme];
250        [self addError:errAECorruptData 
251       withDescription:errorString
252          toDescriptor:replyEvent];
253        cls = Nil;
254      }
255    }
256  } else {
257    // COV_NF_START
258    // Don't know how to force an URL that we don't respond to upon ourselves.
259    NSString *errorString 
260      = [NSString stringWithFormat:@"Unable to find handler for scheme %@.", 
261         scheme];
262    [self addError:errAECorruptData 
263   withDescription:errorString
264      toDescriptor:replyEvent];
265    // COV_NF_END
266
267  }
268  return cls;
269}
270
271- (void)getUrl:(NSAppleEventDescriptor *)event 
272withReplyEvent:(NSAppleEventDescriptor *)replyEvent {  
273  NSURL *url = [self extractURLFromEvent:event withReplyEvent:replyEvent];
274  if (!url) {
275    return;
276  }
277  NSString *scheme = [url scheme];
278  Class cls = [self getClassForScheme:scheme withReplyEvent:replyEvent];
279  if (!cls) {
280    return;
281  }
282  BOOL wasGood = [cls gtm_openURL:url];
283  if (!wasGood) {
284    NSString *errorString 
285    = [NSString stringWithFormat:@"[%@ gtm_openURL:] failed to handle %@", 
286       NSStringFromClass(cls), url];
287    [self addError:errAEEventNotHandled 
288   withDescription:errorString
289      toDescriptor:replyEvent];
290  }
291}
292
293- (void)addError:(OSStatus)error 
294 withDescription:(NSString*)string 
295    toDescriptor:(NSAppleEventDescriptor *)desc {
296  NSAppleEventDescriptor *errorDesc = nil;
297  if (error != noErr) {
298    NSNumber *errNum = [NSNumber numberWithLong:error];
299    errorDesc = [errNum gtm_appleEventDescriptor];
300    [desc setParamDescriptor:errorDesc forKeyword:keyErrorNumber];
301  }
302  if (string) {
303    errorDesc = [string gtm_appleEventDescriptor];
304    [desc setParamDescriptor:errorDesc forKeyword:keyErrorString];
305  }
306}
307@end
308