Merged [13645] and [13646]: Fixed one of the longest-standing Adium bugs: The Color...
[adiumx.git] / Source / AICorePluginLoader.m
blobe506f41304b15ed5b2298fa9fdccd534cd003e1e
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
18  Core - Plugin Loader
20  Loads external plugins (Including plugins stored within our application bundle).  Also responsible for warning the
21  user of old or incompatible plugins.
23  */
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;
45 @end
47 @implementation AICorePluginLoader
49 - (id)init
51         if((self = [super init])){
52                 pluginArray = [[NSMutableArray alloc] init];
53         }
55         return self;
58 //init
59 - (void)initController
61         //Init
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];
69         }
70         
71         
72         NSEnumerator    *enumerator = [[adium allResourcesForName:EXTERNAL_PLUGIN_FOLDER withExtensions:EXTENSION_ADIUM_PLUGIN] objectEnumerator];
73         NSString                *path;
74         
75         //Load any external plugins the user has installed
76         while(path = [enumerator nextObject]){
77                 [self loadPluginAtPath:path confirmLoading:YES];
78         }
79         
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];
86         }
87         
90 //Give all external plugins a chance to close
91 - (void)closeController
93     NSEnumerator        *enumerator = [pluginArray objectEnumerator];
94     AIPlugin            *plugin;
95         
96     while((plugin = [enumerator nextObject])){
97         [plugin uninstallPlugin];
98     }
101 - (void)dealloc
103         [pluginArray release];
104         pluginArray = nil;
106         [super dealloc];
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
115         if(confirmLoading){
116                 loadPlugin = [self confirmPluginAtPath:pluginPath];
117         }
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]){
123                 loadPlugin = NO;
124         }
125                 
126         //Load the plugin
127         if(loadPlugin){
128                 NSBundle                *pluginBundle;
129                 AIPlugin                *plugin = nil;
131                 AI_DURING
132                 if(pluginBundle = [NSBundle bundleWithPath:pluginPath]){                                                
133                         Class principalClass = [pluginBundle principalClass];
134                         if(principalClass){
135                                 plugin = [[principalClass alloc] init];
136                         }else{
137                                 NSLog(@"Failed to obtain principal class from plugin \"%@\" (\"%@\")!",[pluginPath lastPathComponent],pluginPath);
138                         }
139                         
140                         if(plugin){
141                                 [pluginArray addObject:plugin];
142                                 [plugin release];
143                         }else{
144                                 NSLog(@"Failed to initialize Plugin \"%@\" (\"%@\")!",[pluginPath lastPathComponent],pluginPath);
145                         }
146                 }else{
147                         NSLog(@"Failed to open Plugin \"%@\"!",[pluginPath lastPathComponent]);
148                 }
149                 
150                 AI_HANDLER      
151                 if(confirmLoading){
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",
158                                                                         @"Quit",
159                                                                         nil,
160                                                                         nil);
161                         [NSApp terminate:nil];                                  
162                 }
163                 AI_ENDHANDLER
164         }
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.",
176                                                                                 @"Disable", 
177                                                                                 @"Continue",
178                                                                                 nil) == NSAlertDefaultReturn){
179                         //Disable this plugin
180                         [self disablePlugin:pluginPath];
181                         loadPlugin = NO;
182                         
183                 }else{
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];
187                 }
188         }
189         
190         return(loadPlugin);
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];
199         
200         [[NSFileManager defaultManager] createDirectoriesForPath:disabledPath];
201         [[NSFileManager defaultManager] movePath:[basePath stringByAppendingPathComponent:pluginName]
202                                                                           toPath:[disabledPath stringByAppendingPathComponent:pluginName]
203                                                                          handler:nil];
206 @end