2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 Loads external plugins (Including plugins stored within our application bundle). Also responsible for warning the
21 user of old or incompatible plugins.
25 #import "AICorePluginLoader.h"
26 #import <AIUtilities/AIFileManagerAdditions.h>
27 #import <AIUtilities/CBApplicationAdditions.h>
28 #import <AIUtilities/AIExceptionHandlingUtilities.h>
29 #import <Adium/AIPlugin.h>
31 #define DIRECTORY_INTERNAL_PLUGINS @"/Contents/PlugIns" //Path to the internal plugins
32 #define EXTERNAL_PLUGIN_FOLDER @"PlugIns" //Folder name of external plugins
33 #define EXTERNAL_DISABLED_PLUGIN_FOLDER @"PlugIns (Disabled)" //Folder name for disabled external plugins
34 #define EXTENSION_ADIUM_PLUGIN @"AdiumPlugin" //File extension of a plugin
36 #define WEBKIT_PLUGIN @"Webkit Message View.AdiumPlugin"
37 #define SMV_PLUGIN @"Standard Message View.AdiumPlugin"
38 #define CONFIRMED_PLUGINS @"Confirmed Plugins"
39 #define CONFIRMED_PLUGINS_VERSION @"Confirmed Plugin Version"
41 @interface AICorePluginLoader (PRIVATE)
42 - (void)loadPluginAtPath:(NSString *)pluginName confirmLoading:(BOOL)confirmLoading;
43 - (BOOL)confirmPluginAtPath:(NSString *)pluginPath;
44 - (void)disablePlugin:(NSString *)pluginPath;
47 @implementation AICorePluginLoader
51 if((self = [super init])){
52 pluginArray = [[NSMutableArray alloc] init];
59 - (void)initController
62 [adium createResourcePathForName:EXTERNAL_PLUGIN_FOLDER];
64 //If the Adium version has changed since our last run, warn the user that their external plugins may no longer work
65 NSString *lastVersion = [[NSUserDefaults standardUserDefaults] objectForKey:CONFIRMED_PLUGINS_VERSION];
66 if(![[NSApp applicationVersion] isEqualToString:lastVersion]){
67 [[NSUserDefaults standardUserDefaults] removeObjectForKey:CONFIRMED_PLUGINS];
68 [[NSUserDefaults standardUserDefaults] setObject:[NSApp applicationVersion] forKey:CONFIRMED_PLUGINS_VERSION];
72 NSEnumerator *enumerator = [[adium allResourcesForName:EXTERNAL_PLUGIN_FOLDER withExtensions:EXTENSION_ADIUM_PLUGIN] objectEnumerator];
75 //Load any external plugins the user has installed
76 while(path = [enumerator nextObject]){
77 [self loadPluginAtPath:path confirmLoading:YES];
80 NSString *internalPluginsPath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DIRECTORY_INTERNAL_PLUGINS] stringByExpandingTildeInPath];
81 //Load the plugins in our bundle
82 enumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:internalPluginsPath] objectEnumerator];
83 while (path = [enumerator nextObject]) {
84 if([[path pathExtension] caseInsensitiveCompare:EXTENSION_ADIUM_PLUGIN] == 0)
85 [self loadPluginAtPath:[internalPluginsPath stringByAppendingPathComponent:path] confirmLoading:NO];
90 //Give all external plugins a chance to close
91 - (void)closeController
93 NSEnumerator *enumerator = [pluginArray objectEnumerator];
96 while((plugin = [enumerator nextObject])){
97 [plugin uninstallPlugin];
103 [pluginArray release];
109 //Load plugins from the specified path
110 - (void)loadPluginAtPath:(NSString *)pluginPath confirmLoading:(BOOL)confirmLoading
112 BOOL loadPlugin = YES;
114 //Confirm the presence of external plugins with the user
116 loadPlugin = [self confirmPluginAtPath:pluginPath];
119 //Special case for webkit. Trying to load the webkit plugin on a 10.2 system will get us into trouble
120 //with linking (because webkit may not be present). This special case code recognizes the webkit plugin
121 //and skips it if webkit is not available.
122 if([[pluginPath lastPathComponent] isEqualToString:WEBKIT_PLUGIN] && ![NSApp isWebKitAvailable]){
128 NSBundle *pluginBundle;
129 AIPlugin *plugin = nil;
132 if(pluginBundle = [NSBundle bundleWithPath:pluginPath]){
133 Class principalClass = [pluginBundle principalClass];
135 plugin = [[principalClass alloc] init];
137 NSLog(@"Failed to obtain principal class from plugin \"%@\" (\"%@\")!",[pluginPath lastPathComponent],pluginPath);
141 [pluginArray addObject:plugin];
144 NSLog(@"Failed to initialize Plugin \"%@\" (\"%@\")!",[pluginPath lastPathComponent],pluginPath);
147 NSLog(@"Failed to open Plugin \"%@\"!",[pluginPath lastPathComponent]);
152 //The plugin encountered an exception while it was loading. There is no reason to leave this old
153 //or poorly coded plugin enabled so that it can cause more problems, so disable it and inform
154 //the user that they'll need to restart.
155 [self disablePlugin:pluginPath];
156 NSRunCriticalAlertPanel([NSString stringWithFormat:@"Error loading %@",[[pluginPath lastPathComponent] stringByDeletingPathExtension]],
157 @"An external plugin failed to load and has been disabled. Please relaunch Adium",
161 [NSApp terminate:nil];
167 //Confirm the presence of an external plugin with the user. Returns YES if the plugin should be loaded.
168 - (BOOL)confirmPluginAtPath:(NSString *)pluginPath
170 BOOL loadPlugin = YES;
171 NSArray *confirmed = [[NSUserDefaults standardUserDefaults] objectForKey:CONFIRMED_PLUGINS];
173 if(!confirmed || ![confirmed containsObject:[pluginPath lastPathComponent]]){
174 if(NSRunInformationalAlertPanel([NSString stringWithFormat:@"Disable %@?",[[pluginPath lastPathComponent] stringByDeletingPathExtension]],
175 @"External plugins may cause crashes and odd behavior after updating Adium. Disable this plugin if you experience any issues.",
178 nil) == NSAlertDefaultReturn){
179 //Disable this plugin
180 [self disablePlugin:pluginPath];
184 //Add this plugin to our confirmed list
185 confirmed = (confirmed ? [confirmed arrayByAddingObject:[pluginPath lastPathComponent]] : [NSArray arrayWithObject:[pluginPath lastPathComponent]]);
186 [[NSUserDefaults standardUserDefaults] setObject:confirmed forKey:CONFIRMED_PLUGINS];
193 //Move a plugin to the disabled plugins folder
194 - (void)disablePlugin:(NSString *)pluginPath
196 NSString *pluginName = [pluginPath lastPathComponent];
197 NSString *basePath = [pluginPath stringByDeletingLastPathComponent];
198 NSString *disabledPath = [[basePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:EXTERNAL_DISABLED_PLUGIN_FOLDER];
200 [[NSFileManager defaultManager] createDirectoriesForPath:disabledPath];
201 [[NSFileManager defaultManager] movePath:[basePath stringByAppendingPathComponent:pluginName]
202 toPath:[disabledPath stringByAppendingPathComponent:pluginName]