|
boredzo@1385
|
1 |
//
|
|
boredzo@1385
|
2 |
// GrowlPluginController.m
|
|
boredzo@1385
|
3 |
// Growl
|
|
boredzo@1385
|
4 |
//
|
|
boredzo@1385
|
5 |
// Created by Nelson Elhage on 8/25/04.
|
|
ingmarstein@3040
|
6 |
// Copyright 2004-2006 The Growl Project. All rights reserved.
|
|
boredzo@1385
|
7 |
//
|
|
boredzo@1385
|
8 |
// This file is under the BSD License, refer to License.txt for details
|
|
boredzo@1385
|
9 |
|
|
boredzo@1385
|
10 |
#import "GrowlPluginController.h"
|
|
ingmarstein@2470
|
11 |
#import "GrowlPreferencesController.h"
|
|
ingmarstein@2695
|
12 |
#import "GrowlDisplayPlugin.h"
|
|
boredzo@2479
|
13 |
#import "GrowlDefinesInternal.h"
|
|
ingmarstein@2641
|
14 |
#include "CFDictionaryAdditions.h"
|
|
ingmarstein@2641
|
15 |
#include "CFMutableDictionaryAdditions.h"
|
|
boredzo@1385
|
16 |
|
|
eridius@2773
|
17 |
#import "GrowlPathUtilities.h"
|
|
eridius@2773
|
18 |
#import "GrowlNonCopyingMutableDictionary.h"
|
|
rudy@2947
|
19 |
|
|
eridius@2773
|
20 |
#import "NSSetAdditions.h"
|
|
eridius@2773
|
21 |
#import "NSWorkspaceAdditions.h"
|
|
eridius@2773
|
22 |
#import "GrowlWebKitPluginHandler.h"
|
|
eridius@2773
|
23 |
|
|
ingmarstein@1790
|
24 |
@interface GrowlPluginController (PRIVATE)
|
|
eridius@2773
|
25 |
- (void) registerDefaultPluginHandlers;
|
|
ingmarstein@1947
|
26 |
- (void) findPluginsInDirectory:(NSString *)dir;
|
|
ingmarstein@1790
|
27 |
- (void) pluginInstalledSelector:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
|
|
ingmarstein@1790
|
28 |
- (void) pluginExistsSelector:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
|
|
boredzo@1385
|
29 |
@end
|
|
boredzo@1385
|
30 |
|
|
ingmarstein@1986
|
31 |
@interface WebCoreCache
|
|
ingmarstein@1986
|
32 |
+ (void) empty;
|
|
ingmarstein@1986
|
33 |
@end
|
|
ingmarstein@1986
|
34 |
|
|
boredzo@2469
|
35 |
//for use as CFSetCallBacks.equal
|
|
boredzo@2469
|
36 |
static Boolean caseInsensitiveStringComparator(const void *value1, const void *value2);
|
|
eridius@2773
|
37 |
//for use as CFSetCallBacks.hash
|
|
eridius@2773
|
38 |
static CFHashCode passthroughStringHash(const void *value);
|
|
eridius@2773
|
39 |
//for use on array of matching plug-in handlers in -openPluginAtPath:
|
|
Rudy@4246
|
40 |
NSInteger comparePluginHandlerRegistrationOrder(id a, id b, void *context);
|
|
eridius@2773
|
41 |
|
|
eridius@2773
|
42 |
#pragma mark -
|
|
eridius@2773
|
43 |
|
|
eridius@2773
|
44 |
NSString *GrowlPluginControllerWillAddPluginHandlerNotification = @"GrowlPluginControllerWillAddPluginHandlerNotification";
|
|
eridius@2773
|
45 |
NSString *GrowlPluginControllerDidAddPluginHandlerNotification = @"GrowlPluginControllerDidAddPluginHandlerNotification";
|
|
eridius@2773
|
46 |
NSString *GrowlPluginControllerWillRemovePluginHandlerNotification = @"GrowlPluginControllerWillRemovePluginHandlerNotification";
|
|
eridius@2773
|
47 |
NSString *GrowlPluginControllerDidRemovePluginHandlerNotification = @"GrowlPluginControllerDidRemovePluginHandlerNotification";
|
|
eridius@2773
|
48 |
|
|
eridius@2773
|
49 |
//Info.plist keys for plug-in bundles.
|
|
eridius@2773
|
50 |
NSString *GrowlPluginInfoKeyName = @"CFBundleName";
|
|
eridius@2773
|
51 |
NSString *GrowlPluginInfoKeyAuthor = @"GrowlPluginAuthor";
|
|
eridius@2773
|
52 |
NSString *GrowlPluginInfoKeyVersion = @"CFBundleVersion";
|
|
eridius@2773
|
53 |
NSString *GrowlPluginInfoKeyDescription = @"GrowlPluginDescription";
|
|
eridius@2773
|
54 |
//keys in plug-in description dictionaries (also includes the above).
|
|
eridius@2773
|
55 |
NSString *GrowlPluginInfoKeyBundle = @"GrowlPluginBundle";
|
|
eridius@2773
|
56 |
NSString *GrowlPluginInfoKeyTypes = @"GrowlPluginType";
|
|
eridius@2773
|
57 |
NSString *GrowlPluginInfoKeyPath = @"GrowlPluginPath";
|
|
eridius@2773
|
58 |
NSString *GrowlPluginInfoKeyHumanReadableName = @"GrowlPluginHumanReadableName";
|
|
eridius@2773
|
59 |
NSString *GrowlPluginInfoKeyIdentifier = @"GrowlPluginIdentifier";
|
|
eridius@2773
|
60 |
NSString *GrowlPluginInfoKeyInstance = @"GrowlPluginInstance";
|
|
eridius@2773
|
61 |
|
|
eridius@2773
|
62 |
/*******************************************************************************
|
|
eridius@2773
|
63 |
* _____ ___ ____ ___
|
|
eridius@2773
|
64 |
* |_ _/ _ \| _ \ / _ \
|
|
eridius@2773
|
65 |
* | || | | | | | | | | |
|
|
eridius@2773
|
66 |
* | || |_| | |_| | |_| |
|
|
eridius@2773
|
67 |
* |_| \___/|____/ \___/
|
|
eridius@2773
|
68 |
*
|
|
eridius@2773
|
69 |
*******************************************************************************
|
|
eridius@2773
|
70 |
*
|
|
eridius@2773
|
71 |
* - Use identifier strings for all loaded plug-ins (simpler than using
|
|
eridius@2773
|
72 |
* human-readable names) (DONE though this will probably only be used for
|
|
eridius@2773
|
73 |
* storage of plug-in prefs)
|
|
eridius@2773
|
74 |
* - Use plug-in dictionaries (DONE)
|
|
eridius@2773
|
75 |
* - Use GrowlNonCopyingMutableDictionary instead of NSMapTable (DONE)
|
|
eridius@2773
|
76 |
* - Write the built-in plug-in handler (DONE)
|
|
eridius@2773
|
77 |
* - Add a WebKit plug-in handler (jkp)
|
|
eridius@2773
|
78 |
* - Better localize human-readable names
|
|
eridius@2773
|
79 |
*/
|
|
boredzo@1385
|
80 |
|
|
boredzo@1385
|
81 |
@implementation GrowlPluginController
|
|
boredzo@1385
|
82 |
|
|
boredzo@2465
|
83 |
+ (GrowlPluginController *) sharedController {
|
|
ofri@2581
|
84 |
return [self sharedInstance];
|
|
ofri@2581
|
85 |
}
|
|
ofri@2581
|
86 |
|
|
ofri@2581
|
87 |
- (id) initSingleton {
|
|
ofri@2581
|
88 |
if ((self = [super initSingleton])) {
|
|
eridius@2773
|
89 |
bundlesToLazilyInstantiateAnInstanceFrom = [[NSMutableSet alloc] init];
|
|
ingmarstein@2943
|
90 |
|
|
eridius@2773
|
91 |
pluginsByIdentifier = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
92 |
pluginIdentifiersByPath = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
93 |
pluginIdentifiersByBundle = [[GrowlNonCopyingMutableDictionary alloc] init];
|
|
eridius@2773
|
94 |
pluginIdentifiersByInstance = [[GrowlNonCopyingMutableDictionary alloc] init];
|
|
eridius@2773
|
95 |
|
|
eridius@2773
|
96 |
pluginsByName = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
97 |
pluginsByAuthor = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
98 |
pluginsByVersion = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
99 |
pluginsByFilename = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
100 |
pluginsByType = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
101 |
pluginHumanReadableNames = [[NSCountedSet alloc] init];
|
|
eridius@2773
|
102 |
|
|
evands@3839
|
103 |
loadedBundleIdentifiers = [[NSMutableSet alloc] init];
|
|
evands@3839
|
104 |
|
|
eridius@2773
|
105 |
allPluginHandlers = [[NSMutableArray alloc] init];
|
|
eridius@2773
|
106 |
pluginHandlers = [[NSMutableDictionary alloc] init];
|
|
eridius@2773
|
107 |
handlersForPlugins = [[GrowlNonCopyingMutableDictionary alloc] init];
|
|
eridius@2773
|
108 |
|
|
eridius@2773
|
109 |
displayPlugins = [[NSMutableArray alloc] init];
|
|
bgannin@3280
|
110 |
disabledPlugins = [[NSMutableArray alloc] init];
|
|
eridius@2773
|
111 |
|
|
eridius@2773
|
112 |
[self registerDefaultPluginHandlers];
|
|
eridius@2773
|
113 |
|
|
eridius@2773
|
114 |
enum { builtInTypesCount = 4U };
|
|
eridius@2773
|
115 |
NSString *builtInTypesArray[builtInTypesCount] = {
|
|
eridius@2773
|
116 |
GROWL_STYLE_EXTENSION,
|
|
eridius@2773
|
117 |
GROWL_VIEW_EXTENSION,
|
|
eridius@2773
|
118 |
GROWL_PATHWAY_EXTENSION,
|
|
eridius@2773
|
119 |
GROWL_PLUGIN_EXTENSION,
|
|
eridius@2773
|
120 |
};
|
|
eridius@2773
|
121 |
CFSetCallBacks callbacks = kCFCopyStringSetCallBacks;
|
|
ingmarstein@2540
|
122 |
callbacks.equal = caseInsensitiveStringComparator;
|
|
eridius@2773
|
123 |
callbacks.hash = passthroughStringHash;
|
|
eridius@2773
|
124 |
builtInTypes = (NSSet *)CFSetCreate(kCFAllocatorDefault,
|
|
eridius@2773
|
125 |
(const void **)builtInTypesArray,
|
|
eridius@2773
|
126 |
builtInTypesCount,
|
|
eridius@2773
|
127 |
&callbacks);
|
|
eridius@2773
|
128 |
|
|
evands@3839
|
129 |
//Find plugins inside GHA itself first
|
|
evands@3839
|
130 |
[self findPluginsInDirectory:[[GrowlPathUtilities helperAppBundle] builtInPlugInsPath]];
|
|
evands@3839
|
131 |
|
|
evands@3839
|
132 |
/* Then find plug-ins in Library/Application Support/Growl/Plugins directories. This allows GHA to override externally installed plugins,
|
|
evands@3839
|
133 |
* which are fairly common as some 3rd party plugins have been rolled into the Growl distribution.
|
|
evands@3839
|
134 |
*/
|
|
boredzo@2465
|
135 |
NSArray *libraries = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES);
|
|
boredzo@2465
|
136 |
NSEnumerator *enumerator = [libraries objectEnumerator];
|
|
boredzo@2465
|
137 |
NSString *dir;
|
|
ingmarstein@1579
|
138 |
while ((dir = [enumerator nextObject])) {
|
|
ingmarstein@1947
|
139 |
dir = [dir stringByAppendingPathComponent:@"Application Support/Growl/Plugins"];
|
|
ingmarstein@1947
|
140 |
[self findPluginsInDirectory:dir];
|
|
evands@3839
|
141 |
}
|
|
boredzo@1385
|
142 |
}
|
|
ingmarstein@1639
|
143 |
|
|
boredzo@1385
|
144 |
return self;
|
|
boredzo@1385
|
145 |
}
|
|
boredzo@1385
|
146 |
|
|
ofri@2581
|
147 |
- (void) destroy {
|
|
ingmarstein@2943
|
148 |
[pluginsByIdentifier release];
|
|
ingmarstein@2943
|
149 |
[pluginIdentifiersByPath release];
|
|
ingmarstein@2943
|
150 |
[pluginIdentifiersByBundle release];
|
|
eridius@2773
|
151 |
[pluginIdentifiersByInstance release];
|
|
eridius@2773
|
152 |
|
|
eridius@2773
|
153 |
[pluginsByName release];
|
|
eridius@2773
|
154 |
[pluginsByAuthor release];
|
|
eridius@2773
|
155 |
[pluginsByVersion release];
|
|
eridius@2773
|
156 |
[pluginsByFilename release];
|
|
eridius@2773
|
157 |
[pluginsByType release];
|
|
eridius@2773
|
158 |
[pluginHumanReadableNames release];
|
|
eridius@2773
|
159 |
|
|
evands@3839
|
160 |
[loadedBundleIdentifiers release];
|
|
evands@3839
|
161 |
|
|
eridius@2773
|
162 |
[bundlesToLazilyInstantiateAnInstanceFrom release];
|
|
eridius@2773
|
163 |
[displayPlugins release];
|
|
bgannin@3280
|
164 |
[disabledPlugins release];
|
|
eridius@2773
|
165 |
|
|
eridius@2773
|
166 |
[allPluginHandlers release];
|
|
eridius@2773
|
167 |
[pluginHandlers release];
|
|
eridius@2773
|
168 |
[handlersForPlugins release];
|
|
eridius@2773
|
169 |
|
|
eridius@2773
|
170 |
[builtInTypes release];
|
|
eridius@2773
|
171 |
|
|
eridius@2773
|
172 |
[cache_allPlugins release];
|
|
eridius@2773
|
173 |
[cache_allPluginsArray release];
|
|
eridius@2773
|
174 |
[cache_registeredPluginTypes release];
|
|
eridius@2773
|
175 |
[cache_registeredPluginNames release];
|
|
eridius@2773
|
176 |
[cache_registeredPluginNamesArray release];
|
|
eridius@2773
|
177 |
[cache_allPluginInstances release];
|
|
eridius@2773
|
178 |
[cache_displayPlugins release];
|
|
boredzo@2465
|
179 |
|
|
ofri@2581
|
180 |
[super destroy];
|
|
boredzo@2465
|
181 |
}
|
|
boredzo@2465
|
182 |
|
|
boredzo@1385
|
183 |
#pragma mark -
|
|
boredzo@1385
|
184 |
|
|
eridius@2773
|
185 |
- (void) registerDefaultPluginHandlers {
|
|
eridius@2773
|
186 |
//register ourselves for display plug-ins (non-WebKit), pathway plug-ins, and functional plug-ins.
|
|
eridius@2773
|
187 |
NSSet *types = [[NSSet alloc] initWithObjects:
|
|
eridius@2773
|
188 |
//display plug-ins
|
|
eridius@2773
|
189 |
GROWL_VIEW_EXTENSION,
|
|
eridius@2773
|
190 |
NSFileTypeForHFSTypeCode(FOUR_CHAR_CODE('DISP')),
|
|
eridius@2773
|
191 |
//pathway plug-ins
|
|
eridius@2773
|
192 |
GROWL_PATHWAY_EXTENSION,
|
|
eridius@2773
|
193 |
NSFileTypeForHFSTypeCode(FOUR_CHAR_CODE('PWAY')),
|
|
eridius@2773
|
194 |
//generic functional plug-ins
|
|
eridius@2773
|
195 |
GROWL_PLUGIN_EXTENSION,
|
|
eridius@2773
|
196 |
NSFileTypeForHFSTypeCode(FOUR_CHAR_CODE('GEXT')),
|
|
eridius@2773
|
197 |
nil];
|
|
eridius@2773
|
198 |
|
|
eridius@2773
|
199 |
[self addPluginHandler:self forPluginTypes:types];
|
|
eridius@2773
|
200 |
|
|
eridius@2773
|
201 |
[types release];
|
|
eridius@2773
|
202 |
|
|
eridius@2773
|
203 |
[GrowlWebKitPluginHandler sharedInstance]; // Calling this here will cause the handler to register
|
|
eridius@2773
|
204 |
}
|
|
eridius@2773
|
205 |
|
|
eridius@2773
|
206 |
#pragma mark -
|
|
eridius@2773
|
207 |
#pragma mark GrowlPluginHandler protocol conformance
|
|
eridius@2773
|
208 |
|
|
eridius@2773
|
209 |
//the method that dispatches incoming plug-ins to plug-in handlers is -dispatchPluginAtPath:.
|
|
eridius@2773
|
210 |
//this is for handling plug-ins of the built-in types.
|
|
eridius@2773
|
211 |
- (BOOL)loadPluginWithBundle:(NSBundle *)bundle {
|
|
eridius@2773
|
212 |
[self addPluginInstance:nil fromBundle:bundle];
|
|
eridius@2773
|
213 |
|
|
eridius@2773
|
214 |
return YES;
|
|
eridius@2773
|
215 |
}
|
|
eridius@2773
|
216 |
|
|
eridius@2773
|
217 |
#pragma mark -
|
|
eridius@2773
|
218 |
#pragma mark Plugin-handler handling
|
|
eridius@2773
|
219 |
|
|
eridius@2773
|
220 |
- (void) addPluginHandler:(id <GrowlPluginHandler>)handler forPluginTypes:(NSSet *)types {
|
|
eridius@2773
|
221 |
NSParameterAssert(handler != nil);
|
|
eridius@2773
|
222 |
NSParameterAssert(types != nil);
|
|
eridius@2773
|
223 |
|
|
eridius@2773
|
224 |
if (![types count]) {
|
|
eridius@2773
|
225 |
NSLog(@"Warning: -[%@ addPluginHandler:forPluginTypes:] called with an empty set of file types. This may be indicative of a bug in Growl or a plug-in. The handler was %@.", [self class], handler);
|
|
eridius@2773
|
226 |
} else {
|
|
eridius@2773
|
227 |
//make sure nobody tries to register a plug-in handler for a built-in type.
|
|
eridius@2773
|
228 |
if (builtInTypes) {
|
|
eridius@2773
|
229 |
NSMutableSet *builtInMutable = [builtInTypes mutableCopy];
|
|
eridius@2773
|
230 |
[builtInMutable intersectSet:types];
|
|
eridius@2773
|
231 |
|
|
eridius@2773
|
232 |
//the intersection must be empty; if it isn't, at least one of the types for which this handler is attempting to register is a built-in type.
|
|
eridius@2773
|
233 |
NSAssert2([builtInMutable count] == 0U, @"Something attempted to register a plug-in handler for one or more reserved file types (%@). The handler was %@.", builtInMutable, handler);
|
|
eridius@2773
|
234 |
|
|
eridius@2773
|
235 |
[builtInMutable release];
|
|
eridius@2773
|
236 |
}
|
|
eridius@2773
|
237 |
|
|
eridius@2773
|
238 |
NSEnumerator *typeEnum = [types objectEnumerator];
|
|
eridius@2773
|
239 |
NSString *type;
|
|
eridius@2773
|
240 |
while ((type = [typeEnum nextObject])) {
|
|
eridius@2773
|
241 |
//normalise: strip leading ., if any.
|
|
Rudy@4246
|
242 |
NSUInteger i = 0U, len = [type length];
|
|
eridius@2773
|
243 |
for (; i < len; ++i) {
|
|
eridius@2773
|
244 |
if ([type characterAtIndex:i] != '.')
|
|
eridius@2773
|
245 |
break;
|
|
eridius@2773
|
246 |
}
|
|
eridius@2773
|
247 |
NSAssert2(i < len, @"Something tried to register a plug-in handler for a file type consisting entirely of %u full stops ('.'). The handler was %@.", len, handler);
|
|
eridius@2773
|
248 |
if (i)
|
|
eridius@2773
|
249 |
type = [type substringFromIndex:i];
|
|
eridius@2773
|
250 |
|
|
eridius@2773
|
251 |
NSMutableArray *handlers = [pluginHandlers objectForKey:type];
|
|
eridius@2773
|
252 |
if (!handlers) {
|
|
eridius@2773
|
253 |
handlers = [[NSMutableArray alloc] initWithCapacity:1U];
|
|
eridius@2773
|
254 |
[pluginHandlers setObject:handlers forKey:type];
|
|
eridius@2773
|
255 |
[handlers release];
|
|
eridius@2773
|
256 |
}
|
|
eridius@2773
|
257 |
[handlers addObject:handler];
|
|
eridius@2773
|
258 |
}
|
|
eridius@2773
|
259 |
|
|
boredzo@2954
|
260 |
[allPluginHandlers addObject:handler];
|
|
boredzo@2954
|
261 |
|
|
eridius@2773
|
262 |
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
|
rudy@3304
|
263 |
NSDictionary *notificationUserInfo = [[[NSDictionary alloc] initWithObjectsAndKeys:
|
|
eridius@2773
|
264 |
handler, @"GrowlPluginHandler",
|
|
rudy@3304
|
265 |
nil] autorelease];
|
|
eridius@2773
|
266 |
|
|
eridius@2773
|
267 |
[nc postNotificationName:GrowlPluginControllerWillAddPluginHandlerNotification
|
|
eridius@2773
|
268 |
object:self
|
|
eridius@2773
|
269 |
userInfo:notificationUserInfo];
|
|
eridius@2773
|
270 |
|
|
eridius@2773
|
271 |
[allPluginHandlers addObject:handler];
|
|
eridius@2773
|
272 |
|
|
eridius@2773
|
273 |
//add the handler as an observer for various notifications.
|
|
eridius@2773
|
274 |
if ([handler respondsToSelector:@selector(growlPluginControllerWillAddPluginHandler:)]) {
|
|
eridius@2773
|
275 |
[nc addObserver:handler
|
|
eridius@2773
|
276 |
selector:@selector(growlPluginControllerWillAddPluginHandler:)
|
|
eridius@2773
|
277 |
name:GrowlPluginControllerWillAddPluginHandlerNotification
|
|
eridius@2773
|
278 |
object:self];
|
|
eridius@2773
|
279 |
}
|
|
eridius@2773
|
280 |
if ([handler respondsToSelector:@selector(growlPluginControllerDidAddPluginHandler:)]) {
|
|
eridius@2773
|
281 |
[nc addObserver:handler
|
|
eridius@2773
|
282 |
selector:@selector(growlPluginControllerDidAddPluginHandler:)
|
|
eridius@2773
|
283 |
name:GrowlPluginControllerDidAddPluginHandlerNotification
|
|
eridius@2773
|
284 |
object:self];
|
|
eridius@2773
|
285 |
}
|
|
eridius@2773
|
286 |
if ([handler respondsToSelector:@selector(growlPluginControllerWillRemovePluginHandler:)]) {
|
|
eridius@2773
|
287 |
[nc addObserver:handler
|
|
eridius@2773
|
288 |
selector:@selector(growlPluginControllerWillRemovePluginHandler:)
|
|
eridius@2773
|
289 |
name:GrowlPluginControllerWillRemovePluginHandlerNotification
|
|
eridius@2773
|
290 |
object:self];
|
|
eridius@2773
|
291 |
}
|
|
eridius@2773
|
292 |
if ([handler respondsToSelector:@selector(growlPluginControllerDidRemovePluginHandler:)]) {
|
|
eridius@2773
|
293 |
[nc addObserver:handler
|
|
eridius@2773
|
294 |
selector:@selector(growlPluginControllerDidRemovePluginHandler:)
|
|
eridius@2773
|
295 |
name:GrowlPluginControllerDidRemovePluginHandlerNotification
|
|
eridius@2773
|
296 |
object:self];
|
|
eridius@2773
|
297 |
}
|
|
eridius@2773
|
298 |
|
|
eridius@2773
|
299 |
[nc postNotificationName:GrowlPluginControllerDidAddPluginHandlerNotification
|
|
eridius@2773
|
300 |
object:self
|
|
eridius@2773
|
301 |
userInfo:notificationUserInfo];
|
|
eridius@2773
|
302 |
}
|
|
eridius@2773
|
303 |
}
|
|
bgannin@3280
|
304 |
|
|
eridius@2773
|
305 |
- (void) removePluginHandler:(id <GrowlPluginHandler>)handler forPluginTypes:(NSSet *)extensions {
|
|
eridius@2773
|
306 |
NSParameterAssert(handler != nil);
|
|
eridius@2773
|
307 |
|
|
eridius@2773
|
308 |
[allPluginHandlers removeObjectIdenticalTo:handler];
|
|
eridius@2773
|
309 |
|
|
eridius@2773
|
310 |
if (!extensions)
|
|
eridius@2773
|
311 |
extensions = (NSSet *)[pluginHandlers allKeysForObject:handler];
|
|
eridius@2773
|
312 |
|
|
eridius@2773
|
313 |
NSEnumerator *extEnum = [extensions objectEnumerator];
|
|
eridius@2773
|
314 |
NSString *ext;
|
|
eridius@2773
|
315 |
while ((ext = [extEnum nextObject])) {
|
|
eridius@2773
|
316 |
NSMutableArray *handlers = [pluginHandlers objectForKey:ext];
|
|
eridius@2773
|
317 |
if (handlers) {
|
|
Rudy@4246
|
318 |
NSUInteger idx = [handlers indexOfObjectIdenticalTo:handler];
|
|
eridius@2773
|
319 |
if (idx != NSNotFound)
|
|
eridius@2773
|
320 |
[handlers removeObjectAtIndex:idx];
|
|
eridius@2773
|
321 |
}
|
|
eridius@2773
|
322 |
}
|
|
eridius@2773
|
323 |
}
|
|
eridius@2773
|
324 |
|
|
eridius@2773
|
325 |
- (NSArray *) allPluginHandlers {
|
|
eridius@2773
|
326 |
return [[allPluginHandlers copy] autorelease];
|
|
eridius@2773
|
327 |
}
|
|
eridius@2773
|
328 |
|
|
eridius@2773
|
329 |
#pragma mark -
|
|
eridius@2773
|
330 |
|
|
eridius@2773
|
331 |
//private method.
|
|
eridius@2773
|
332 |
- (NSDictionary *) addPluginInstance:(GrowlPlugin *)plugin fromPath:(NSString *)path bundle:(NSBundle *)bundle {
|
|
evands@3839
|
333 |
//If we're passed a bundle, refuse to load it if we've already loaded a bundle with the same identifier, instead returning early.
|
|
evands@3839
|
334 |
NSString *bundleIdentifier = (bundle ? [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey] : nil);
|
|
evands@3839
|
335 |
if (bundleIdentifier && [loadedBundleIdentifiers containsObject:[bundle objectForInfoDictionaryKey:(NSString *)kCFBundleIdentifierKey]]) {
|
|
evands@3839
|
336 |
return nil;
|
|
evands@3839
|
337 |
}
|
|
evands@3839
|
338 |
|
|
evands@3839
|
339 |
//Look up the identifier for the plugin. We try to look up the identifier by the instance, by the bundle; and by the pathname, in that order.
|
|
eridius@2773
|
340 |
NSString *identifier = nil;
|
|
eridius@2773
|
341 |
if (plugin)
|
|
eridius@2773
|
342 |
identifier = [pluginIdentifiersByInstance objectForKey:plugin];
|
|
eridius@2773
|
343 |
else if (bundle)
|
|
eridius@2773
|
344 |
identifier = [pluginIdentifiersByBundle objectForKey:bundle];
|
|
eridius@2773
|
345 |
else if (path)
|
|
eridius@2773
|
346 |
identifier = [pluginIdentifiersByPath objectForKey:path];
|
|
eridius@2773
|
347 |
|
|
evands@3839
|
348 |
/* If we have an identifier, look up the plug-in dictionary.
|
|
evands@3839
|
349 |
* If we have a plug-in dictionary but no instance (the identifier was retrieved by bundle or by path), attempt to retrieve the instance from the dictionary.
|
|
evands@3839
|
350 |
*/
|
|
eridius@2773
|
351 |
NSMutableDictionary *pluginDict = identifier ? [pluginsByIdentifier objectForKey:identifier] : nil;
|
|
eridius@2773
|
352 |
if (pluginDict && !plugin)
|
|
eridius@2773
|
353 |
plugin = [pluginDict pluginInstance];
|
|
eridius@2773
|
354 |
|
|
boredzo@3587
|
355 |
//Assert that we have an instance OR a bundle. We need at least one to proceed.
|
|
eridius@2773
|
356 |
NSAssert1(plugin || bundle, @"Cannot load plug-ins lazily without a bundle (path: %@)", path);
|
|
eridius@2773
|
357 |
|
|
boredzo@3587
|
358 |
//Get the plug-in's name, author, and version. All three come from the plug-in instance if it exists and responds to -name/-author/-version; if both requirements are not satisfied, the information is retrieved from the bundle's Info.plist.
|
|
bgannin@3280
|
359 |
NSString *name = plugin ? ([plugin respondsToSelector:@selector(name)] ? [plugin name] : [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey])
|
|
bgannin@3280
|
360 |
: [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];
|
|
bgannin@3280
|
361 |
NSString *author = plugin ? ([plugin respondsToSelector:@selector(author)] ? [plugin author] : [bundle objectForInfoDictionaryKey:GrowlPluginInfoKeyAuthor])
|
|
bgannin@3280
|
362 |
: [bundle objectForInfoDictionaryKey:GrowlPluginInfoKeyAuthor];
|
|
bgannin@3280
|
363 |
NSString *version = plugin ? ([plugin respondsToSelector:@selector(version)] ? [plugin version] : [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey])
|
|
bgannin@3280
|
364 |
: [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
|
|
boredzo@3587
|
365 |
|
|
boredzo@3587
|
366 |
//If we don't have a pathname, get it as the bundle's pathname.
|
|
eridius@2773
|
367 |
if (!path)
|
|
eridius@2773
|
368 |
path = [bundle bundlePath];
|
|
boredzo@2957
|
369 |
NSString *extension = [path pathExtension];
|
|
boredzo@2957
|
370 |
NSString *fileType = nil;
|
|
eridius@2773
|
371 |
|
|
boredzo@3587
|
372 |
//Assert that we have a name, author, and version. (We got the path first so we can use it in the assertion message.)
|
|
eridius@2773
|
373 |
NSAssert5((name != nil) && (author != nil) && (version != nil),
|
|
eridius@2773
|
374 |
@"Cannot load plug-in at path %@ (plug-in instance's class: %@). One of these is (null), but they must all not be:\n"
|
|
eridius@2773
|
375 |
@"\t"@" name: %@\n"
|
|
eridius@2773
|
376 |
@"\t"@" author: %@\n"
|
|
eridius@2773
|
377 |
@"\t"@"version: %@\n",
|
|
eridius@2773
|
378 |
path, [plugin class], name, author, version);
|
|
eridius@2773
|
379 |
|
|
boredzo@3587
|
380 |
//In case we got the names from the plug-in instance and it gave us a mutable string for some reason, make copies for ourselves.
|
|
boredzo@3587
|
381 |
//Note: This isn't a performance hit when the strings are immutable. -copy = -retain in that situation. Thanks, Apple!
|
|
eridius@2773
|
382 |
name = [name copy];
|
|
eridius@2773
|
383 |
author = [author copy];
|
|
eridius@2773
|
384 |
version = [version copy];
|
|
eridius@2773
|
385 |
|
|
boredzo@3587
|
386 |
//If we don't have an identifier yet, forge it.
|
|
eridius@2773
|
387 |
if (!identifier)
|
|
eridius@2773
|
388 |
identifier = [NSString stringWithFormat:@"Name: %@ Author: %@ Path: %@", name, author, path];
|
|
boredzo@3587
|
389 |
|
|
boredzo@3587
|
390 |
//If we don't have an instance but we do have a bundle, see if we've previously queued the bundle for lazy instantiation.
|
|
boredzo@2954
|
391 |
if (!plugin && bundle) {
|
|
boredzo@3587
|
392 |
if (![bundlesToLazilyInstantiateAnInstanceFrom containsObject:bundle]) {
|
|
boredzo@3587
|
393 |
//We haven't previously queued it: Queue it.
|
|
boredzo@3587
|
394 |
[bundlesToLazilyInstantiateAnInstanceFrom addObject:bundle];
|
|
boredzo@3587
|
395 |
} else {
|
|
boredzo@3587
|
396 |
//We have: This is our cue to instantiate it.
|
|
boredzo@3587
|
397 |
plugin = [[[bundle principalClass] alloc] init];
|
|
boredzo@3587
|
398 |
//Dequeue it, because we don't want to hit this branch again for this plug-in.
|
|
boredzo@3587
|
399 |
[bundlesToLazilyInstantiateAnInstanceFrom removeObject:bundle];
|
|
boredzo@3587
|
400 |
//Stash the plug-in instance in the plug-in dictionary. This retains the instance and means that we'll never hit the lazy-instantiation machinery again (because plugin will be non-nil).
|
|
boredzo@3587
|
401 |
[pluginDict setObject:plugin forKey:GrowlPluginInfoKeyInstance];
|
|
boredzo@3587
|
402 |
}
|
|
boredzo@3587
|
403 |
}
|
|
boredzo@3587
|
404 |
|
|
boredzo@3587
|
405 |
if (!plugin && bundle) {
|
|
boredzo@3587
|
406 |
//*Still* no plug-in! Again we check whether it's queued for instantiation (bug?).
|
|
boredzo@2954
|
407 |
if (![bundlesToLazilyInstantiateAnInstanceFrom containsObject:bundle])
|
|
boredzo@2954
|
408 |
[bundlesToLazilyInstantiateAnInstanceFrom addObject:bundle];
|
|
boredzo@2954
|
409 |
else {
|
|
boredzo@3587
|
410 |
//Apparently it is. Instantiate it, but don't stash the plug-in instance in the plug-in dictionary (why not?).
|
|
boredzo@2954
|
411 |
plugin = [[[bundle principalClass] alloc] init];
|
|
boredzo@2954
|
412 |
[bundlesToLazilyInstantiateAnInstanceFrom removeObject:bundle];
|
|
boredzo@3587
|
413 |
}
|
|
boredzo@3587
|
414 |
}
|
|
boredzo@3587
|
415 |
|
|
boredzo@3587
|
416 |
/*If we don't actually have a plug-in dictionary, create it.
|
|
boredzo@3587
|
417 |
*Elements of a plug-in dictionary:
|
|
boredzo@3587
|
418 |
* Plug-in name
|
|
boredzo@3587
|
419 |
* Author
|
|
boredzo@3587
|
420 |
* Version
|
|
boredzo@3587
|
421 |
* Pathname
|
|
boredzo@3587
|
422 |
* Identifier
|
|
boredzo@3587
|
423 |
* Instance (later; see above lazy-instantiation code)
|
|
boredzo@3587
|
424 |
* Plug-in's HFS type and filename extension (combined in a set)
|
|
boredzo@3587
|
425 |
*/
|
|
boredzo@2957
|
426 |
BOOL pluginDictIsNew = !pluginDict;
|
|
eridius@2773
|
427 |
if (!pluginDict) {
|
|
eridius@2773
|
428 |
pluginDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
eridius@2773
|
429 |
name, GrowlPluginInfoKeyName,
|
|
eridius@2773
|
430 |
author, GrowlPluginInfoKeyAuthor,
|
|
eridius@2773
|
431 |
version, GrowlPluginInfoKeyVersion,
|
|
eridius@2773
|
432 |
path, GrowlPluginInfoKeyPath,
|
|
boredzo@2957
|
433 |
identifier, GrowlPluginInfoKeyIdentifier,
|
|
eridius@2773
|
434 |
nil];
|
|
bgannin@3280
|
435 |
NSString *description = ([plugin respondsToSelector:@selector(pluginDescription)] ? [plugin pluginDescription] : nil);
|
|
bgannin@3280
|
436 |
|
|
eridius@2773
|
437 |
if (description)
|
|
eridius@2773
|
438 |
[pluginDict setObject:description forKey:GrowlPluginInfoKeyDescription];
|
|
eridius@2773
|
439 |
|
|
eridius@2773
|
440 |
[[NSWorkspace sharedWorkspace] getFileType:&fileType creatorCode:NULL forFile:path];
|
|
eridius@2773
|
441 |
|
|
boredzo@3587
|
442 |
//Record the file types (HFS and filename extension) that the plug-in possessed at this time. These help determine what kind of plug-in it is (e.g. .growlView = custom view; .growlStyle = WebKit display).
|
|
evands@2927
|
443 |
NSSet *types = nil;
|
|
eridius@2773
|
444 |
if (extension) {
|
|
evands@2917
|
445 |
#warning problem here...
|
|
evands@2917
|
446 |
///XXX when there is no file type it is coming back as \'\'...im guessing this means no type, but it still tests as true so each plugin is registered against that type...wrong???
|
|
eridius@2773
|
447 |
if (fileType)
|
|
eridius@2773
|
448 |
types = [NSSet setWithObjects:extension, fileType, nil];
|
|
eridius@2773
|
449 |
else
|
|
eridius@2773
|
450 |
types = [NSSet setWithObject:extension];
|
|
eridius@2773
|
451 |
} else if (fileType)
|
|
eridius@2773
|
452 |
types = [NSSet setWithObject:fileType];
|
|
ingmarstein@2943
|
453 |
|
|
evands@2927
|
454 |
if (types)
|
|
evands@2927
|
455 |
[pluginDict setObject:types forKey:GrowlPluginInfoKeyTypes];
|
|
boredzo@2957
|
456 |
}
|
|
boredzo@2957
|
457 |
|
|
boredzo@3587
|
458 |
//We have a bundle. If no previous bundle was stored in the plug-in dictionary (why wouldn't there be?), store this bundle there. Also register the identifier as being the one for this bundle.
|
|
boredzo@2957
|
459 |
if (bundle) {
|
|
boredzo@2957
|
460 |
if (![pluginDict objectForKey:GrowlPluginInfoKeyBundle])
|
|
boredzo@2957
|
461 |
[pluginDict setObject:bundle forKey:GrowlPluginInfoKeyBundle];
|
|
boredzo@2957
|
462 |
[pluginIdentifiersByBundle setObject:identifier forKey:bundle];
|
|
boredzo@2957
|
463 |
}
|
|
boredzo@3587
|
464 |
//We have an instance. If no previous instance was stored in the plug-in dictionary (why wouldn't there be?), store this instance there. Also register the identifier as being the one for this instance.
|
|
boredzo@2957
|
465 |
if (plugin) {
|
|
boredzo@2957
|
466 |
if (![pluginDict objectForKey:GrowlPluginInfoKeyInstance])
|
|
boredzo@2957
|
467 |
[pluginDict setObject:plugin forKey:GrowlPluginInfoKeyInstance];
|
|
boredzo@2957
|
468 |
[pluginIdentifiersByInstance setObject:identifier forKey:plugin];
|
|
boredzo@2957
|
469 |
}
|
|
boredzo@2957
|
470 |
|
|
boredzo@3587
|
471 |
//If we just created the dictionary (and got done filling it out), start storing it in places.
|
|
boredzo@2957
|
472 |
if (pluginDictIsNew) {
|
|
eridius@2773
|
473 |
[pluginsByIdentifier setObject:pluginDict forKey:identifier];
|
|
eridius@2773
|
474 |
[pluginIdentifiersByPath setObject:identifier forKey:path];
|
|
ingmarstein@2943
|
475 |
|
|
eridius@2773
|
476 |
#define ADD_TO_DICT(dictName, key, value) \
|
|
eridius@2773
|
477 |
do { \
|
|
eridius@2773
|
478 |
NSMutableSet *plugins = [dictName objectForKey:key]; \
|
|
eridius@2773
|
479 |
if (plugins) \
|
|
eridius@2773
|
480 |
[plugins addObject:value]; \
|
|
eridius@2773
|
481 |
else \
|
|
eridius@2773
|
482 |
[dictName setObject:[NSMutableSet setWithObject:value] forKey:key]; \
|
|
eridius@2773
|
483 |
} while(0)
|
|
eridius@2773
|
484 |
ADD_TO_DICT(pluginsByName, name, pluginDict);
|
|
eridius@2773
|
485 |
ADD_TO_DICT(pluginsByAuthor, author, pluginDict);
|
|
eridius@2773
|
486 |
ADD_TO_DICT(pluginsByVersion, version, pluginDict);
|
|
eridius@2773
|
487 |
ADD_TO_DICT(pluginsByFilename, [path lastPathComponent], pluginDict);
|
|
ingmarstein@2943
|
488 |
|
|
eridius@2773
|
489 |
ADD_TO_DICT(pluginsByType, extension, pluginDict);
|
|
ingmarstein@2943
|
490 |
ADD_TO_DICT(pluginsByType, fileType, pluginDict);
|
|
eridius@2773
|
491 |
#undef ADD_TO_DICT
|
|
eridius@2773
|
492 |
}
|
|
evands@2927
|
493 |
|
|
boredzo@3587
|
494 |
//Release our copies.
|
|
eridius@2773
|
495 |
[name release];
|
|
eridius@2773
|
496 |
[author release];
|
|
eridius@2773
|
497 |
[version release];
|
|
eridius@2773
|
498 |
|
|
boredzo@3587
|
499 |
//Invalidate non-display plug-in caches.
|
|
eridius@2773
|
500 |
[cache_allPlugins release];
|
|
eridius@2773
|
501 |
cache_allPlugins = nil;
|
|
eridius@2773
|
502 |
[cache_allPluginsArray release];
|
|
eridius@2773
|
503 |
cache_allPluginsArray = nil;
|
|
eridius@2773
|
504 |
[cache_registeredPluginTypes release];
|
|
eridius@2773
|
505 |
cache_registeredPluginTypes = nil;
|
|
eridius@2773
|
506 |
[cache_registeredPluginNames release];
|
|
eridius@2773
|
507 |
cache_registeredPluginNames = nil;
|
|
eridius@2773
|
508 |
[cache_registeredPluginNamesArray release];
|
|
eridius@2773
|
509 |
cache_registeredPluginNamesArray = nil;
|
|
eridius@2773
|
510 |
|
|
boredzo@3587
|
511 |
//Special handling if this plug-in is a display.
|
|
eridius@2773
|
512 |
if ([self pluginWithDictionaryIsDisplayPlugin:pluginDict]) {
|
|
boredzo@3587
|
513 |
//If it doesn't respond to -requiresPositioning, it's old. Add it as a disabled plug-in.
|
|
bgannin@3280
|
514 |
if(![[pluginDict valueForKey:GrowlPluginInfoKeyInstance] respondsToSelector:@selector(requiresPositioning)]) {
|
|
bgannin@3280
|
515 |
[disabledPlugins addObject:[pluginDict valueForKey:GrowlPluginInfoKeyName]];
|
|
bgannin@3280
|
516 |
}
|
|
bgannin@3280
|
517 |
else {
|
|
boredzo@3587
|
518 |
//It responds to -requiresPositioning, so add it as a(n enabled) display plug-in.
|
|
bgannin@3280
|
519 |
[displayPlugins addObject:pluginDict];
|
|
bgannin@3280
|
520 |
}
|
|
bgannin@3280
|
521 |
|
|
boredzo@3587
|
522 |
//Invalidate display plug-in cache.
|
|
eridius@2773
|
523 |
[cache_displayPlugins release];
|
|
eridius@2773
|
524 |
cache_displayPlugins = nil;
|
|
eridius@2773
|
525 |
}
|
|
eridius@2773
|
526 |
|
|
evands@3839
|
527 |
//Store the bundle identifier so we know we've loaded it.
|
|
evands@3839
|
528 |
if (bundleIdentifier) {
|
|
evands@3839
|
529 |
[loadedBundleIdentifiers addObject:bundleIdentifier];
|
|
evands@3839
|
530 |
}
|
|
evands@3839
|
531 |
|
|
eridius@2773
|
532 |
return pluginDict;
|
|
eridius@2773
|
533 |
}
|
|
eridius@2773
|
534 |
|
|
bgannin@3280
|
535 |
- (NSArray *) disabledPlugins {
|
|
bgannin@3280
|
536 |
return disabledPlugins;
|
|
bgannin@3280
|
537 |
}
|
|
bgannin@3280
|
538 |
|
|
bgannin@3280
|
539 |
- (BOOL) disabledPluginsPresent {
|
|
bgannin@3280
|
540 |
return ([disabledPlugins count] > 0);
|
|
bgannin@3280
|
541 |
}
|
|
bgannin@3280
|
542 |
|
|
eridius@2773
|
543 |
- (NSDictionary *) addPluginInstance:(GrowlPlugin *)plugin fromBundle:(NSBundle *)bundle {
|
|
eridius@2773
|
544 |
return [self addPluginInstance:plugin fromPath:nil bundle:bundle];
|
|
eridius@2773
|
545 |
}
|
|
eridius@2773
|
546 |
- (NSDictionary *) addPluginInstance:(GrowlPlugin *)plugin fromPath:(NSString *)path {
|
|
eridius@2773
|
547 |
return [self addPluginInstance:plugin fromPath:path bundle:nil];
|
|
eridius@2773
|
548 |
}
|
|
eridius@2773
|
549 |
|
|
eridius@2773
|
550 |
#pragma mark -
|
|
eridius@2773
|
551 |
|
|
eridius@2773
|
552 |
- (NSSet *) registeredPluginTypes {
|
|
eridius@2773
|
553 |
if (!cache_registeredPluginTypes)
|
|
eridius@2773
|
554 |
cache_registeredPluginTypes = [[NSSet alloc] initWithArray:[pluginHandlers allKeys]];
|
|
eridius@2773
|
555 |
|
|
eridius@2773
|
556 |
return cache_registeredPluginTypes;
|
|
eridius@2773
|
557 |
}
|
|
eridius@2773
|
558 |
|
|
eridius@2773
|
559 |
- (NSSet *) registeredPluginNames {
|
|
eridius@2773
|
560 |
if (!cache_registeredPluginNames)
|
|
eridius@2773
|
561 |
cache_registeredPluginNames = [[NSSet alloc] initWithArray:[self registeredPluginNamesArray]];
|
|
eridius@2773
|
562 |
return cache_registeredPluginNames;
|
|
eridius@2773
|
563 |
}
|
|
bgannin@3280
|
564 |
|
|
eridius@2773
|
565 |
- (NSArray *) registeredPluginNamesArray {
|
|
eridius@2773
|
566 |
if (!cache_registeredPluginNamesArray) {
|
|
eridius@2773
|
567 |
cache_registeredPluginNamesArray = [[[pluginsByIdentifier allValues] valueForKey:GrowlPluginInfoKeyName] retain];
|
|
eridius@2773
|
568 |
}
|
|
eridius@2773
|
569 |
return cache_registeredPluginNamesArray;
|
|
eridius@2773
|
570 |
}
|
|
eridius@2773
|
571 |
|
|
eridius@2773
|
572 |
- (NSArray *) registeredPluginNamesArrayForType:(NSString *)type {
|
|
eridius@2773
|
573 |
#warning this should be cached per type
|
|
eridius@2773
|
574 |
return [[[pluginsByType valueForKey:type] allObjects] valueForKey:GrowlPluginInfoKeyName];
|
|
eridius@2773
|
575 |
}
|
|
eridius@2773
|
576 |
|
|
eridius@2773
|
577 |
|
|
eridius@2773
|
578 |
#pragma mark -
|
|
eridius@2773
|
579 |
|
|
eridius@2773
|
580 |
- (NSArray *) allPluginDictionariesArray {
|
|
eridius@2773
|
581 |
if (!cache_allPluginsArray)
|
|
eridius@2773
|
582 |
cache_allPluginsArray = [[pluginsByIdentifier allValues] copy];
|
|
eridius@2773
|
583 |
return cache_allPluginsArray;
|
|
eridius@2773
|
584 |
}
|
|
eridius@2773
|
585 |
- (NSSet *) allPluginDictionaries {
|
|
eridius@2773
|
586 |
if (!cache_allPlugins)
|
|
eridius@2773
|
587 |
cache_allPlugins = [[NSMutableSet alloc] initWithArray:[self allPluginDictionariesArray]];
|
|
eridius@2773
|
588 |
return cache_allPlugins;
|
|
eridius@2773
|
589 |
}
|
|
eridius@2773
|
590 |
- (NSArray *) allPluginInstances {
|
|
eridius@2773
|
591 |
if (!cache_allPluginInstances)
|
|
eridius@2773
|
592 |
cache_allPluginInstances = [[[self allPluginDictionaries] valueForKey:GrowlPluginInfoKeyInstance] retain];
|
|
eridius@2773
|
593 |
return cache_allPluginInstances;
|
|
eridius@2773
|
594 |
}
|
|
eridius@2773
|
595 |
|
|
eridius@2773
|
596 |
- (NSSet *) pluginDictionariesWithName:(NSString *)name author:(NSString *)author version:(NSString *)version type:(NSString *)type {
|
|
eridius@2773
|
597 |
NSMutableSet *matches = [[[self allPluginDictionaries] mutableCopy] autorelease];
|
|
eridius@2773
|
598 |
|
|
eridius@2773
|
599 |
if ([matches count]) {
|
|
eridius@2773
|
600 |
if (name)
|
|
boredzo@3003
|
601 |
[matches intersectSet:[pluginsByName objectForKey:name]];
|
|
eridius@2773
|
602 |
if (author)
|
|
boredzo@3003
|
603 |
[matches intersectSet:[pluginsByAuthor objectForKey:author]];
|
|
eridius@2773
|
604 |
if (version)
|
|
boredzo@3003
|
605 |
[matches intersectSet:[pluginsByVersion objectForKey:version]];
|
|
eridius@2773
|
606 |
if (type)
|
|
boredzo@3003
|
607 |
[matches intersectSet:[pluginsByType objectForKey:type]];
|
|
eridius@2773
|
608 |
}
|
|
eridius@2773
|
609 |
|
|
eridius@2773
|
610 |
return matches;
|
|
eridius@2773
|
611 |
}
|
|
eridius@2773
|
612 |
- (NSDictionary *) pluginDictionaryWithName:(NSString *)name author:(NSString *)author version:(NSString *)version type:(NSString *)type {
|
|
eridius@2773
|
613 |
NSSet *matches = [self pluginDictionariesWithName:name author:author version:version type:type];
|
|
ingmarstein@2941
|
614 |
if ([matches count] == 1U)
|
|
eridius@2773
|
615 |
return [matches anyObject];
|
|
eridius@2773
|
616 |
else
|
|
eridius@2773
|
617 |
return nil;
|
|
eridius@2773
|
618 |
}
|
|
eridius@2773
|
619 |
- (NSDictionary *) pluginDictionaryWithName:(NSString *)name {
|
|
eridius@2773
|
620 |
return [self pluginDictionaryWithName:name author:nil version:nil type:nil];
|
|
eridius@2773
|
621 |
}
|
|
eridius@2773
|
622 |
- (GrowlPlugin *) pluginInstanceWithName:(NSString *)name author:(NSString *)author version:(NSString *)version type:(NSString *)type {
|
|
eridius@2773
|
623 |
NSDictionary *pluginDict = [self pluginDictionaryWithName:name author:author version:version type:type];
|
|
eridius@2773
|
624 |
GrowlPlugin *instance = [pluginDict pluginInstance];
|
|
eridius@2773
|
625 |
if (!instance) {
|
|
eridius@2773
|
626 |
NSBundle *bundle = [pluginDict pluginBundle];
|
|
eridius@2773
|
627 |
if (bundle) {
|
|
eridius@2773
|
628 |
[self addPluginInstance:nil fromBundle:bundle]; //causes instantiation
|
|
eridius@2773
|
629 |
instance = [pluginDict pluginInstance];
|
|
eridius@2773
|
630 |
}
|
|
eridius@2773
|
631 |
}
|
|
eridius@2773
|
632 |
return instance;
|
|
eridius@2773
|
633 |
}
|
|
eridius@2773
|
634 |
- (GrowlPlugin *) pluginInstanceWithName:(NSString *)name {
|
|
eridius@2773
|
635 |
return [self pluginInstanceWithName:name author:nil version:nil type:nil];
|
|
eridius@2773
|
636 |
}
|
|
eridius@2773
|
637 |
|
|
eridius@2773
|
638 |
#pragma mark -
|
|
eridius@2773
|
639 |
|
|
eridius@2773
|
640 |
- (NSString *) humanReadableNameForPluginWithDictionary:(NSDictionary *)pluginDict {
|
|
eridius@2773
|
641 |
NSString *humanReadableName = [pluginDict pluginHumanReadableName];
|
|
eridius@2773
|
642 |
|
|
eridius@2773
|
643 |
if (!humanReadableName) {
|
|
eridius@2773
|
644 |
NSString *name = [pluginDict pluginName];
|
|
eridius@2773
|
645 |
if ([[pluginsByName objectForKey:name] count] == 1U)
|
|
eridius@2773
|
646 |
humanReadableName = name;
|
|
eridius@2773
|
647 |
else {
|
|
eridius@2773
|
648 |
NSString *author = [pluginDict pluginAuthor];
|
|
eridius@2773
|
649 |
if ([[pluginsByAuthor objectForKey:author] count] == 1U)
|
|
eridius@2773
|
650 |
humanReadableName = [NSString stringWithFormat:@"%@ (by %@)", name, author]; //XXX LOCALIZEME
|
|
eridius@2773
|
651 |
else {
|
|
eridius@2773
|
652 |
NSString *filename = [[pluginDict pluginPath] lastPathComponent];
|
|
eridius@2773
|
653 |
if ([[pluginsByFilename objectForKey:filename] count] == 1U)
|
|
eridius@2773
|
654 |
humanReadableName = [NSString stringWithFormat:@"%@ (filename %@)", name, filename]; //XXX LOCALIZEME
|
|
eridius@2773
|
655 |
else {
|
|
eridius@2773
|
656 |
humanReadableName = [NSString stringWithFormat:@"%@ (by %@, filename %@)", name, author, filename]; //XXX LOCALIZEME
|
|
Rudy@4246
|
657 |
NSUInteger count = [pluginHumanReadableNames countForObject:humanReadableName];
|
|
eridius@2773
|
658 |
[pluginHumanReadableNames addObject:humanReadableName];
|
|
eridius@2773
|
659 |
if (count > 1U)
|
|
eridius@2773
|
660 |
humanReadableName = [NSString stringWithFormat:@"%@ %u", humanReadableName, count];
|
|
eridius@2773
|
661 |
}
|
|
eridius@2773
|
662 |
}
|
|
eridius@2773
|
663 |
}
|
|
eridius@2773
|
664 |
|
|
eridius@2773
|
665 |
if ([pluginDict isKindOfClass:[NSMutableDictionary class]]) {
|
|
eridius@2773
|
666 |
//save the name for later retrieval.
|
|
eridius@2773
|
667 |
[(NSMutableDictionary *)pluginDict setObject:humanReadableName forKey:GrowlPluginInfoKeyHumanReadableName];
|
|
eridius@2773
|
668 |
}
|
|
eridius@2773
|
669 |
}
|
|
eridius@2773
|
670 |
|
|
eridius@2773
|
671 |
return humanReadableName;
|
|
eridius@2773
|
672 |
}
|
|
eridius@2773
|
673 |
|
|
eridius@2773
|
674 |
- (BOOL) pluginWithDictionaryIsDisplayPlugin:(NSDictionary *)pluginDict {
|
|
eridius@2773
|
675 |
GrowlPlugin *instance = [pluginDict pluginInstance];
|
|
eridius@2773
|
676 |
if (instance)
|
|
eridius@2773
|
677 |
return [instance isKindOfClass:[GrowlDisplayPlugin class]];
|
|
eridius@2773
|
678 |
else {
|
|
eridius@2773
|
679 |
NSBundle *bundle = [pluginDict pluginBundle];
|
|
eridius@2773
|
680 |
NSAssert1(bundle, @"no instance or bundle in plug-in dictionary! description of dictionary follows\n%@", pluginDict);
|
|
eridius@2773
|
681 |
Class principalClass = [bundle principalClass];
|
|
eridius@2773
|
682 |
NSAssert1(bundle, @"bundle in plug-in dictionary has no principal class! description of dictionary follows\n%@", pluginDict);
|
|
eridius@2773
|
683 |
return [principalClass isSubclassOfClass:[GrowlDisplayPlugin class]];
|
|
eridius@2773
|
684 |
}
|
|
eridius@2773
|
685 |
}
|
|
eridius@2773
|
686 |
|
|
eridius@2773
|
687 |
#pragma mark -
|
|
evands@2916
|
688 |
#warning XXX all of this could potentially go bye-bye if it is not needed
|
|
boredzo@2465
|
689 |
|
|
boredzo@2465
|
690 |
- (NSArray *) displayPlugins {
|
|
eridius@2773
|
691 |
if (!cache_displayPlugins)
|
|
eridius@2773
|
692 |
cache_displayPlugins = [[NSArray alloc] initWithArray:displayPlugins];
|
|
eridius@2773
|
693 |
return cache_displayPlugins;
|
|
eridius@2773
|
694 |
}
|
|
eridius@2773
|
695 |
|
|
eridius@2773
|
696 |
#pragma mark -
|
|
eridius@2773
|
697 |
|
|
eridius@2773
|
698 |
- (NSSet *) displayPluginDictionariesWithName:(NSString *)name author:(NSString *)author version:(NSString *)version type:(NSString *)type {
|
|
eridius@2773
|
699 |
NSMutableSet *matches = (NSMutableSet *)[self pluginDictionariesWithName:name
|
|
eridius@2773
|
700 |
author:author
|
|
eridius@2773
|
701 |
version:version
|
|
eridius@2773
|
702 |
type:type];
|
|
eridius@2773
|
703 |
|
|
eridius@2773
|
704 |
NSSet *copyForIteration = [matches copy];
|
|
eridius@2773
|
705 |
NSEnumerator *matchesEnum = [copyForIteration objectEnumerator];
|
|
eridius@2773
|
706 |
NSDictionary *pluginDict;
|
|
eridius@2773
|
707 |
while ((pluginDict = [matchesEnum nextObject])) {
|
|
eridius@2773
|
708 |
if (![self pluginWithDictionaryIsDisplayPlugin:pluginDict])
|
|
eridius@2773
|
709 |
[matches removeObject:pluginDict];
|
|
eridius@2773
|
710 |
}
|
|
evands@3999
|
711 |
[copyForIteration release];
|
|
eridius@2773
|
712 |
|
|
eridius@2773
|
713 |
return matches;
|
|
eridius@2773
|
714 |
}
|
|
bgannin@3280
|
715 |
|
|
eridius@2773
|
716 |
- (NSDictionary *) displayPluginDictionaryWithName:(NSString *)name author:(NSString *)author version:(NSString *)version type:(NSString *)type {
|
|
eridius@2773
|
717 |
NSSet *matches = [self displayPluginDictionariesWithName:name
|
|
eridius@2773
|
718 |
author:author
|
|
eridius@2773
|
719 |
version:version
|
|
eridius@2773
|
720 |
type:type];
|
|
eridius@2773
|
721 |
if ([matches count] == 1U)
|
|
eridius@2773
|
722 |
return [matches anyObject];
|
|
eridius@2773
|
723 |
else
|
|
eridius@2773
|
724 |
return nil;
|
|
eridius@2773
|
725 |
}
|
|
bgannin@3280
|
726 |
|
|
eridius@2773
|
727 |
- (GrowlDisplayPlugin *) displayPluginInstanceWithName:(NSString *)name author:(NSString *)author version:(NSString *)version type:(NSString *)type {
|
|
eridius@2773
|
728 |
GrowlPlugin *plugin = [self pluginInstanceWithName:name author:author version:version type:type];
|
|
eridius@2773
|
729 |
if (plugin && [plugin isKindOfClass:[GrowlDisplayPlugin class]])
|
|
eridius@2773
|
730 |
return (GrowlDisplayPlugin *)plugin;
|
|
eridius@2773
|
731 |
else
|
|
eridius@2773
|
732 |
return nil;
|
|
eridius@2773
|
733 |
}
|
|
eridius@2773
|
734 |
|
|
eridius@2773
|
735 |
#pragma mark -
|
|
eridius@2773
|
736 |
#pragma mark Finding and using installed plug-ins
|
|
ingmarstein@2540
|
737 |
|
|
ingmarstein@1947
|
738 |
- (void) findPluginsInDirectory:(NSString *)dir {
|
|
ingmarstein@1624
|
739 |
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
|
|
ingmarstein@1624
|
740 |
NSString *file;
|
|
ingmarstein@1579
|
741 |
while ((file = [enumerator nextObject])) {
|
|
eridius@2773
|
742 |
NSString *fullPath = [dir stringByAppendingPathComponent:file];
|
|
eridius@2773
|
743 |
|
|
ingmarstein@1947
|
744 |
NSString *pathExtension = [file pathExtension];
|
|
eridius@2773
|
745 |
NSString *fileType;
|
|
eridius@2773
|
746 |
[[NSWorkspace sharedWorkspace] getFileType:&fileType creatorCode:NULL forFile:fullPath];
|
|
eridius@2773
|
747 |
if ([pluginHandlers objectForKey:pathExtension] || (fileType && [pluginHandlers objectForKey:fileType])) {
|
|
eridius@2773
|
748 |
[self dispatchPluginAtPath:fullPath];
|
|
ingmarstein@1624
|
749 |
[enumerator skipDescendents];
|
|
boredzo@1385
|
750 |
}
|
|
boredzo@1385
|
751 |
}
|
|
boredzo@1385
|
752 |
}
|
|
boredzo@1385
|
753 |
|
|
eridius@2773
|
754 |
- (void) dispatchPluginAtPath:(NSString *)path {
|
|
eridius@2773
|
755 |
//get all the relevant handlers, by extension and by type.
|
|
eridius@2773
|
756 |
NSArray *handlersByExtension = [pluginHandlers objectForKey:[path pathExtension]];
|
|
eridius@2773
|
757 |
NSString *fileType;
|
|
eridius@2773
|
758 |
[[NSWorkspace sharedWorkspace] getFileType:&fileType creatorCode:NULL forFile:path];
|
|
eridius@2773
|
759 |
NSArray *handlersByType = [pluginHandlers objectForKey:fileType];
|
|
eridius@2773
|
760 |
|
|
eridius@2773
|
761 |
//strip duplicates by making a set from both arrays.
|
|
eridius@2773
|
762 |
NSMutableSet *allMatchingHandlersSet = handlersByExtension ? [NSMutableSet setWithArray:handlersByExtension] : [NSMutableSet set];
|
|
eridius@2773
|
763 |
if (handlersByType)
|
|
eridius@2773
|
764 |
[allMatchingHandlersSet unionSet:[NSSet setWithArray:handlersByType]];
|
|
eridius@2773
|
765 |
|
|
rudy@3304
|
766 |
NSMutableArray *allMatchingHandlers = [[[allMatchingHandlersSet allObjects] mutableCopy] autorelease];
|
|
eridius@2773
|
767 |
[allMatchingHandlers sortUsingFunction:comparePluginHandlerRegistrationOrder context:self];
|
|
eridius@2773
|
768 |
|
|
ingmarstein@1925
|
769 |
NSBundle *pluginBundle = [[NSBundle alloc] initWithPath:path];
|
|
ingmarstein@1628
|
770 |
|
|
eridius@2773
|
771 |
NSEnumerator *handlersEnum = [allMatchingHandlers objectEnumerator];
|
|
eridius@2773
|
772 |
id <GrowlPluginHandler> handler;
|
|
eridius@2773
|
773 |
while ((handler = [handlersEnum nextObject])) {
|
|
eridius@2773
|
774 |
BOOL success = NO;
|
|
eridius@2773
|
775 |
if (pluginBundle && [handler respondsToSelector:@selector(loadPluginWithBundle:)])
|
|
Rudy@4246
|
776 |
success = (NSUInteger)[handler performSelector:@selector(loadPluginWithBundle:) withObject:pluginBundle];
|
|
eridius@2773
|
777 |
else if ([handler respondsToSelector:@selector(loadPluginAtPath:)])
|
|
Rudy@4246
|
778 |
success = (NSUInteger)[handler performSelector:@selector(loadPluginAtPath:) withObject:path];
|
|
eridius@2773
|
779 |
else if ([handler respondsToSelector:@selector(loadPluginAtURL:)])
|
|
Rudy@4246
|
780 |
success = (NSUInteger)[handler performSelector:@selector(loadPluginAtURL:) withObject:[NSURL fileURLWithPath:path]];
|
|
eridius@2773
|
781 |
else
|
|
eridius@2773
|
782 |
NSLog(@"warning: while loading plug-in at %@, tried to use plug-in handler %@, but it appears incapable of handling a plug-in", path, handler); //XXX should do this diagnostic when adding the handler
|
|
eridius@2773
|
783 |
}
|
|
eridius@2773
|
784 |
|
|
eridius@2773
|
785 |
[pluginBundle release];
|
|
eridius@2773
|
786 |
}
|
|
eridius@2773
|
787 |
|
|
eridius@2773
|
788 |
#pragma mark -
|
|
eridius@2773
|
789 |
#pragma mark Installing plug-ins
|
|
boredzo@1385
|
790 |
|
|
boredzo@1385
|
791 |
- (void) pluginInstalledSelector:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
|
|
ingmarstein@1865
|
792 |
#pragma unused(sheet, contextInfo)
|
|
ingmarstein@1586
|
793 |
if (returnCode == NSAlertAlternateReturn) {
|
|
ofri@2683
|
794 |
NSBundle *prefPane = [GrowlPathUtilities growlPrefPaneBundle];
|
|
ingmarstein@1586
|
795 |
|
|
ingmarstein@2641
|
796 |
if (prefPane && ![[NSWorkspace sharedWorkspace] openFile:[prefPane bundlePath]])
|
|
ingmarstein@1790
|
797 |
NSLog(@"Could not open Growl PrefPane");
|
|
boredzo@1385
|
798 |
}
|
|
boredzo@1385
|
799 |
}
|
|
boredzo@1385
|
800 |
|
|
boredzo@1385
|
801 |
- (void) pluginExistsSelector:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
|
|
ingmarstein@1865
|
802 |
#pragma unused(sheet)
|
|
boredzo@1385
|
803 |
NSString *filename = (NSString *)contextInfo;
|
|
ingmarstein@1579
|
804 |
|
|
ingmarstein@1579
|
805 |
if (returnCode == NSAlertAlternateReturn) {
|
|
eridius@2773
|
806 |
//'Yes' to 'Do you want to overwrite [the installed plug-in with the version you double-clicked]?'
|
|
boredzo@1385
|
807 |
NSString *pluginFile = [filename lastPathComponent];
|
|
ingmarstein@1947
|
808 |
NSString *destination = [[NSHomeDirectory()
|
|
ingmarstein@1947
|
809 |
stringByAppendingPathComponent:@"Library/Application Support/Growl/Plugins"]
|
|
ingmarstein@1790
|
810 |
stringByAppendingPathComponent:pluginFile];
|
|
boredzo@1385
|
811 |
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
boredzo@1385
|
812 |
|
|
boredzo@1385
|
813 |
// first remove old copy if present
|
|
boredzo@1385
|
814 |
[fileManager removeFileAtPath:destination handler:nil];
|
|
boredzo@1385
|
815 |
|
|
boredzo@1385
|
816 |
// copy new version to destination
|
|
ingmarstein@1579
|
817 |
if ([fileManager copyPath:filename toPath:destination handler:nil]) {
|
|
eridius@2773
|
818 |
[self dispatchPluginAtPath:destination];
|
|
eridius@2773
|
819 |
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
|
Rudy@4413
|
820 |
if([self _hasNativeArchitecture:destination])
|
|
Rudy@4413
|
821 |
NSBeginInformationalAlertSheet( NSLocalizedString( @"Plugin installed", @"" ),
|
|
boredzo@2465
|
822 |
NSLocalizedString( @"No", @"" ),
|
|
boredzo@1385
|
823 |
NSLocalizedString( @"Yes", @"" ),
|
|
boredzo@1385
|
824 |
nil, nil, self,
|
|
boredzo@1385
|
825 |
@selector(pluginInstalledSelector:returnCode:contextInfo:),
|
|
boredzo@1385
|
826 |
NULL, NULL,
|
|
boredzo@1385
|
827 |
NSLocalizedString( @"Plugin '%@' has been installed successfully. Do you want to configure it now?", @"" ),
|
|
boredzo@1385
|
828 |
[pluginFile stringByDeletingPathExtension] );
|
|
boredzo@1385
|
829 |
} else {
|
|
eridius@2773
|
830 |
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
|
boredzo@1385
|
831 |
NSBeginCriticalAlertSheet( NSLocalizedString( @"Plugin not installed", @"" ),
|
|
boredzo@1385
|
832 |
NSLocalizedString( @"OK", @"" ),
|
|
boredzo@1385
|
833 |
nil, nil, nil, self, NULL, NULL, NULL,
|
|
boredzo@1385
|
834 |
NSLocalizedString( @"There was an error while installing the plugin '%@'.", @"" ),
|
|
boredzo@1385
|
835 |
[pluginFile stringByDeletingPathExtension] );
|
|
boredzo@1385
|
836 |
}
|
|
boredzo@1385
|
837 |
}
|
|
ingmarstein@1625
|
838 |
|
|
boredzo@1385
|
839 |
[filename release];
|
|
boredzo@1385
|
840 |
}
|
|
boredzo@1385
|
841 |
|
|
eridius@2773
|
842 |
- (void) installPluginFromPath:(NSString *)filename {
|
|
boredzo@1385
|
843 |
NSString *pluginFile = [filename lastPathComponent];
|
|
ingmarstein@1947
|
844 |
NSString *destination = [[NSHomeDirectory()
|
|
ingmarstein@1947
|
845 |
stringByAppendingPathComponent:@"Library/Application Support/Growl/Plugins"]
|
|
ingmarstein@1790
|
846 |
stringByAppendingPathComponent:pluginFile];
|
|
boredzo@1385
|
847 |
// retain a copy of the filename because it is passed as context to the sheetDidEnd selectors
|
|
boredzo@1385
|
848 |
NSString *filenameCopy = [[NSString alloc] initWithString:filename];
|
|
boredzo@1385
|
849 |
|
|
Rudy@4413
|
850 |
//Check to see if we've got valid architectures in this plugin for our use, if not, bail.
|
|
Rudy@4413
|
851 |
if(![self _hasNativeArchitecture:filenameCopy]) {
|
|
Rudy@4413
|
852 |
NSBeginAlertSheet( NSLocalizedString( @"Plugin missing native architecture", @"" ),
|
|
Rudy@4413
|
853 |
NSLocalizedString( @"No", @"" ),
|
|
Rudy@4413
|
854 |
NSLocalizedString( @"Yes", @"" ), nil, nil, self,
|
|
Rudy@4413
|
855 |
NULL, @selector(pluginExistsSelector:returnCode:contextInfo:),
|
|
Rudy@4413
|
856 |
filenameCopy,
|
|
hg@4425
|
857 |
NSLocalizedString( @"Plugin '%@' will not work on this Mac running this version of Mac OS X. Install it anyway?", @"" ),
|
|
Rudy@4413
|
858 |
[pluginFile stringByDeletingPathExtension] );
|
|
Rudy@4413
|
859 |
}
|
|
Rudy@4413
|
860 |
else {
|
|
Rudy@4413
|
861 |
if ([[NSFileManager defaultManager] fileExistsAtPath:destination]) {
|
|
Rudy@4413
|
862 |
// plugin already exists at destination
|
|
Rudy@4413
|
863 |
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
|
Rudy@4413
|
864 |
NSBeginAlertSheet( NSLocalizedString( @"Plugin already exists", @"" ),
|
|
Rudy@4413
|
865 |
NSLocalizedString( @"No", @"" ),
|
|
Rudy@4413
|
866 |
NSLocalizedString( @"Yes", @"" ), nil, nil, self,
|
|
Rudy@4413
|
867 |
NULL, @selector(pluginExistsSelector:returnCode:contextInfo:),
|
|
Rudy@4413
|
868 |
filenameCopy,
|
|
Rudy@4413
|
869 |
NSLocalizedString( @"Plugin '%@' is already installed, do you want to overwrite it?", @"" ),
|
|
Rudy@4413
|
870 |
[pluginFile stringByDeletingPathExtension] );
|
|
Rudy@4413
|
871 |
} else {
|
|
Rudy@4413
|
872 |
[self pluginExistsSelector:nil returnCode:NSAlertAlternateReturn contextInfo:filenameCopy];
|
|
Rudy@4413
|
873 |
}
|
|
Rudy@4413
|
874 |
}
|
|
Rudy@4413
|
875 |
}
|
|
Rudy@4413
|
876 |
|
|
Rudy@4413
|
877 |
- (BOOL)_hasNativeArchitecture:(NSString*)filename {
|
|
Rudy@4413
|
878 |
BOOL result = NO;
|
|
Rudy@4413
|
879 |
NSInteger currentArchitecture = 0;
|
|
Rudy@4413
|
880 |
#if defined(__ppc__) && __ppc__
|
|
Rudy@4413
|
881 |
currentArchitecture = NSBundleExecutableArchitecturePPC;
|
|
Rudy@4413
|
882 |
#elif defined(__i386__) && __i386__
|
|
Rudy@4413
|
883 |
currentArchitecture = NSBundleExecutableArchitectureI386;
|
|
Rudy@4413
|
884 |
#elif defined(__x86_64__) && __x86_64__
|
|
Rudy@4413
|
885 |
currentArchitecture = NSBundleExecutableArchitectureX86_64;
|
|
Rudy@4413
|
886 |
#else
|
|
Rudy@4413
|
887 |
#error unsupported architecture
|
|
Rudy@4413
|
888 |
#endif
|
|
Rudy@4413
|
889 |
NSBundle *pluginBundle = [NSBundle bundleWithPath:filename];
|
|
Rudy@4423
|
890 |
NSString *executablePath = [pluginBundle executablePath];
|
|
Rudy@4423
|
891 |
//we check to see if there is actually an executable in this plugin, it could be a growlStyle, under which we accept it as valid.
|
|
Rudy@4423
|
892 |
if(executablePath && [[NSFileManager defaultManager] fileExistsAtPath:executablePath]) {
|
|
Rudy@4423
|
893 |
NSArray *pluginArchitectures = [pluginBundle executableArchitectures];
|
|
Rudy@4423
|
894 |
if([pluginArchitectures containsObject:[NSNumber numberWithInteger:currentArchitecture]])
|
|
Rudy@4423
|
895 |
result = YES;
|
|
Rudy@4423
|
896 |
}
|
|
Rudy@4423
|
897 |
else {
|
|
Rudy@4413
|
898 |
result = YES;
|
|
Rudy@4423
|
899 |
}
|
|
Rudy@4423
|
900 |
|
|
Rudy@4413
|
901 |
return result;
|
|
boredzo@1385
|
902 |
}
|
|
boredzo@1385
|
903 |
|
|
boredzo@1385
|
904 |
@end
|
|
boredzo@2469
|
905 |
|
|
boredzo@2469
|
906 |
static Boolean caseInsensitiveStringComparator(const void *value1, const void *value2) {
|
|
boredzo@2469
|
907 |
Class NSStringClass = [NSString class];
|
|
boredzo@2469
|
908 |
return [(id)value1 isKindOfClass:NSStringClass] \
|
|
boredzo@2469
|
909 |
&& [(id)value2 isKindOfClass:NSStringClass] \
|
|
boredzo@2469
|
910 |
&& ([(NSString *)value1 caseInsensitiveCompare:(NSString *)value2] == NSOrderedSame);
|
|
boredzo@2469
|
911 |
}
|
|
eridius@2773
|
912 |
|
|
eridius@2773
|
913 |
static CFHashCode passthroughStringHash(const void *value) {
|
|
eridius@2773
|
914 |
return [[(NSString *)value lowercaseString] hash];
|
|
eridius@2773
|
915 |
}
|
|
eridius@2773
|
916 |
|
|
eridius@2773
|
917 |
#pragma mark -
|
|
eridius@2773
|
918 |
|
|
eridius@2773
|
919 |
@implementation NSDictionary (GrowlPluginKeys)
|
|
eridius@2773
|
920 |
|
|
eridius@2773
|
921 |
- (NSString *) pluginName {
|
|
eridius@2773
|
922 |
return [self objectForKey:GrowlPluginInfoKeyName];
|
|
eridius@2773
|
923 |
}
|
|
eridius@2773
|
924 |
- (NSString *) pluginAuthor {
|
|
eridius@2773
|
925 |
return [self objectForKey:GrowlPluginInfoKeyAuthor];
|
|
eridius@2773
|
926 |
}
|
|
eridius@2773
|
927 |
- (NSString *) pluginDescription {
|
|
eridius@2773
|
928 |
return [self objectForKey:GrowlPluginInfoKeyDescription];
|
|
eridius@2773
|
929 |
}
|
|
eridius@2773
|
930 |
- (NSString *) pluginVersion {
|
|
eridius@2773
|
931 |
return [self objectForKey:GrowlPluginInfoKeyVersion];
|
|
eridius@2773
|
932 |
}
|
|
eridius@2773
|
933 |
- (NSBundle *) pluginBundle {
|
|
eridius@2773
|
934 |
return [self objectForKey:GrowlPluginInfoKeyBundle];
|
|
eridius@2773
|
935 |
}
|
|
eridius@2773
|
936 |
- (NSString *) pluginPath {
|
|
eridius@2773
|
937 |
return [self objectForKey:GrowlPluginInfoKeyPath];
|
|
eridius@2773
|
938 |
}
|
|
eridius@2773
|
939 |
- (NSSet *) pluginTypes {
|
|
eridius@2773
|
940 |
return [self objectForKey:GrowlPluginInfoKeyTypes];
|
|
eridius@2773
|
941 |
}
|
|
eridius@2773
|
942 |
- (NSString *) pluginHumanReadableName {
|
|
eridius@2773
|
943 |
return [self objectForKey:GrowlPluginInfoKeyHumanReadableName];
|
|
eridius@2773
|
944 |
}
|
|
eridius@2773
|
945 |
- (NSString *) pluginIdentifier {
|
|
eridius@2773
|
946 |
return [self objectForKey:GrowlPluginInfoKeyIdentifier];
|
|
eridius@2773
|
947 |
}
|
|
eridius@2773
|
948 |
- (GrowlPlugin *) pluginInstance {
|
|
eridius@2773
|
949 |
return [self objectForKey:GrowlPluginInfoKeyInstance];
|
|
eridius@2773
|
950 |
}
|
|
eridius@2773
|
951 |
|
|
eridius@2773
|
952 |
@end
|
|
eridius@2773
|
953 |
|
|
eridius@2773
|
954 |
#define ASSERT_IN_FUNCTION(condition, desc, ...) \
|
|
eridius@2773
|
955 |
[[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithCString:__func__] \
|
|
eridius@2773
|
956 |
file:[NSString stringWithCString:__FILE__] \
|
|
eridius@2773
|
957 |
lineNumber:__LINE__ \
|
|
eridius@2773
|
958 |
description:desc, __VA_ARGS__];
|
|
eridius@2773
|
959 |
|
|
Rudy@4246
|
960 |
NSInteger comparePluginHandlerRegistrationOrder(id a, id b, void *context) {
|
|
eridius@2773
|
961 |
GrowlPluginController *self = (GrowlPluginController *)context;
|
|
eridius@2773
|
962 |
NSArray *allPluginHandlers = [self allPluginHandlers];
|
|
eridius@2773
|
963 |
|
|
Rudy@4246
|
964 |
NSUInteger aIndex = [allPluginHandlers indexOfObjectIdenticalTo:a];
|
|
Rudy@4246
|
965 |
NSUInteger bIndex = [allPluginHandlers indexOfObjectIdenticalTo:b];
|
|
eridius@2773
|
966 |
|
|
eridius@2773
|
967 |
ASSERT_IN_FUNCTION(aIndex != NSNotFound, @"Attempted to compare two plug-in handlers, but the first object was not a (registered) plug-in handler! Description of object: %@", a);
|
|
eridius@2773
|
968 |
ASSERT_IN_FUNCTION(bIndex != NSNotFound, @"Attempted to compare two plug-in handlers, but the second object was not a (registered) plug-in handler! Description of object: %@", b);
|
|
eridius@2773
|
969 |
|
|
ingmarstein@2941
|
970 |
if (aIndex < bIndex)
|
|
eridius@2773
|
971 |
return NSOrderedAscending;
|
|
ingmarstein@2941
|
972 |
else if (aIndex > bIndex)
|
|
eridius@2773
|
973 |
return NSOrderedDescending;
|
|
eridius@2773
|
974 |
else
|
|
eridius@2773
|
975 |
return NSOrderedSame;
|
|
eridius@2773
|
976 |
}
|