Using {{{setDefaultPermitDenyForAccount}}} on the gaim thread. Changed an autoreleas...
[adiumx.git] / Source / AIAdium.m
blobea32c44d4b942a6206fe611ad26105987d53c277
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  */
17 #import "AIAccountController.h"
18 #import "AIContactController.h"
19 #import "AIContentController.h"
20 #import "AICoreComponentLoader.h"
21 #import "AICorePluginLoader.h"
22 #import "AICrashController.h"
23 #import "AIDockController.h"
24 #import "AIEmoticonController.h"
25 #import "AIExceptionController.h"
26 #import "AIInterfaceController.h"
27 #import "AILoginController.h"
28 #import "AIMenuController.h"
29 #import "AIPreferenceController.h"
30 #import "AISoundController.h"
31 #import "AIStatusController.h"
32 #import "AIToolbarController.h"
33 #import "ESApplescriptabilityController.h"
34 #import "ESContactAlertsController.h"
35 #import "ESDebugController.h"
36 #import "ESFileTransferController.h"
37 #import "LNAboutBoxController.h"
38 #import <AIUtilities/AIFileManagerAdditions.h>
39 #import <AIUtilities/CBApplicationAdditions.h>
41 //#define NEW_APPLICATION_SUPPORT_DIRECTORY
43 //Path to Adium's application support preferences
44 #ifdef NEW_APPLICATION_SUPPORT_DIRECTORY
45 #   define ADIUM_APPLICATION_SUPPORT_DIRECTORY  [[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Application Support"] stringByAppendingPathComponent:@"Adium X"]
46 #   define ADIUM_SUBFOLDER_OF_APP_SUPPORT               @"Adium X"
47 #   define ADIUM_SUBFOLDER_OF_LIBRARY                   [@"Application Support" stringByAppendingPathComponent:@"Adium X"]
48 #else
49 #   define ADIUM_APPLICATION_SUPPORT_DIRECTORY  [[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Application Support"] stringByAppendingPathComponent:@"Adium 2.0"]
50 #   define ADIUM_SUBFOLDER_OF_APP_SUPPORT               @"Adium 2.0"
51 #   define ADIUM_SUBFOLDER_OF_LIBRARY                   [@"Application Support" stringByAppendingPathComponent:@"Adium 2.0"]
52 #endif
54 #define ADIUM_TRAC_PAGE                                         @"http://trac.adiumx.com/"
55 #define ADIUM_FORUM_PAGE                                        AILocalizedString(@"http://forum.adiumx.com/","Adium forums page. Localize only if a translated version exists.")
56 #define ADIUM_XTRAS_PAGE                                        AILocalizedString(@"http://www.adiumxtras.com/","Adium xtras page. Localized only if a translated version exists.")
57 #define ADIUM_FEEDBACK_PAGE                                     @"mailto:feedback@adiumx.com"
59 //Portable Adium prefs key
60 #define PORTABLE_ADIUM_KEY                                      @"Preference Folder Location"
62 static NSString *prefsCategory;
64 @interface AIAdium (PRIVATE)
65 - (void)configureCrashReporter;
66 - (void)completeLogin;
67 - (void)openAppropriatePreferencesIfNeeded;
68 - (NSDictionary *)versionUpgradeDict;
70 - (NSString *)processBetaVersionString:(NSString *)inString;
71 - (void)deleteTemporaryFiles;
73 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
74 @end
76 @implementation AIAdium
78 //Init
79 - (id)init
81         if((self = [super init])) {
82                 [AIObject _setSharedAdiumInstance:self];
83         }
85         return self;
88 /*!
89  * @brief Returns the location of Adium's preference folder
90  * 
91  * This may be specified in our bundle's info dictionary keyed as PORTABLE_ADIUM_KEY
92  * or, by default, be within the system's 'application support' directory.
93  */
94 + (NSString *)applicationSupportDirectory
96         //Path to the preferences folder
97         static NSString *_preferencesFolderPath = nil;
99     //Determine the preferences path if neccessary
100         if(!_preferencesFolderPath){
101                 _preferencesFolderPath = [[[[[NSBundle mainBundle] infoDictionary] objectForKey:PORTABLE_ADIUM_KEY] stringByExpandingTildeInPath] retain];
102                 if (!_preferencesFolderPath)
103                         _preferencesFolderPath = [[ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByExpandingTildeInPath] retain];
104         }
106         return _preferencesFolderPath;
110 //Core Controllers -----------------------------------------------------------------------------------------------------
111 #pragma mark Core Controllers
112 - (AILoginController *)loginController{
113     return(loginController);
115 - (AIMenuController *)menuController{
116     return(menuController);
118 - (AIAccountController *)accountController{
119     return(accountController);
121 - (AIContentController *)contentController{
122     return(contentController);
124 //Forward partial code compatibility with the 0.90 trunk
125 - (AIContentController *)chatController{
126         return(contentController);
128 - (AIContactController *)contactController{
129     return(contactController);
131 - (AIEmoticonController *)emoticonController{
132     return(emoticonController);
134 - (AISoundController *)soundController{
135     return(soundController);
137 - (AIInterfaceController *)interfaceController{
138     return(interfaceController);
140 - (AIPreferenceController *)preferenceController{
141     return(preferenceController);
143 - (AIToolbarController *)toolbarController{
144     return(toolbarController);
146 - (AIDockController *)dockController{
147     return(dockController);
149 - (ESFileTransferController *)fileTransferController{
150     return(fileTransferController);    
152 - (ESContactAlertsController *)contactAlertsController{
153     return(contactAlertsController);
155 - (ESApplescriptabilityController *)applescriptabilityController{
156         return(applescriptabilityController);
158 - (ESDebugController *)debugController{
159         return(debugController);
161 - (AIStatusController *)statusController{
162     return(statusController);
165 //Loaders --------------------------------------------------------------------------------------------------------
166 #pragma mark Loaders
168 - (AICoreComponentLoader *)componentLoader
170         return componentLoader;
173 //Notifications --------------------------------------------------------------------------------------------------------
174 #pragma mark Notifications
175 //Return the shared Adium notification center
176 - (NSNotificationCenter *)notificationCenter
178     if(notificationCenter == nil){
179         notificationCenter = [[NSNotificationCenter alloc] init];
180     }
181             
182     return(notificationCenter);
186 //Startup and Shutdown -------------------------------------------------------------------------------------------------
187 #pragma mark Startup and Shutdown
188 //Adium is almost done launching, init
189 - (void)applicationWillFinishLaunching:(NSNotification *)notification
191     notificationCenter = nil;
192     completedApplicationLoad = NO;
193         advancedPrefsName = nil;
194         prefsCategory = nil;
196 #ifdef NEW_APPLICATION_SUPPORT_DIRECTORY
197         [self upgradePreferenceFolderFromAdium2ToAdium];
198 #endif
199         //Load the crash reporter
200 #ifdef CRASH_REPORTER
201 #warning Crash reporter enabled.
202     [AICrashController enableCrashCatching];
203     [AIExceptionController enableExceptionCatching];
204 #endif
205     //Ignore SIGPIPE, which is a harmless error signal
206     //sent when write() or similar function calls fail due to a broken pipe in the network connection
207     signal(SIGPIPE, SIG_IGN);
210 //Adium has finished launching
211 - (void)applicationDidFinishLaunching:(NSNotification *)notification
213         //Begin loading and initing the components
214     [loginController initController];
215     
216     //Begin Login
217     [loginController requestUserNotifyingTarget:self selector:@selector(completeLogin)];
220 //Forward a re-open message to the interface controller
221 - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
223     return([interfaceController handleReopenWithVisibleWindows:flag]);
226 //Called by the login controller when a user has been selected, continue logging in
227 - (void)completeLogin
229         //Init the controllers.
230         [preferenceController initController]; //should init first to allow other controllers access to their prefs
231         [toolbarController initController];
232         [menuController initController];
233         [debugController initController]; //should init after the menuController to add its menu item if needed
234         [contactAlertsController initController];
235         [soundController initController];
236         [accountController initController];
237         [contactController initController];
238         [contentController initController];
239         [interfaceController initController];
240         [dockController initController];
241         [fileTransferController initController];
242         [applescriptabilityController initController];
243         [statusController initController];
244         [emoticonController initController]; //Must load after the contentController since it may register a content filter
246         //should always init last.  Plugins rely on everything else.
247         [componentLoader initController];
248         [pluginLoader initController];
250         /* Account controller should finish initing before the contact controller
251          * so accounts and services are available for contact creation.
252          */
253         [preferenceController finishIniting];
254         [accountController finishIniting];
255         [contactController finishIniting];
256         [statusController finishIniting];
257         [interfaceController finishIniting];
259         //Open the preferences if we were unable to because application:openFile: was called before we got here
260         [self openAppropriatePreferencesIfNeeded];
262         //If no accounts are setup, open the account prefs
263         if([[accountController accountArray] count] == 0){
264                 [preferenceController openPreferencesToCategoryWithIdentifier:@"accounts"];
265         }
267         completedApplicationLoad = YES;
269         [[self notificationCenter] postNotificationName:Adium_CompletedApplicationLoad object:nil];
272 //Give all the controllers a chance to close down
273 - (void)applicationWillTerminate:(NSNotification *)notification
275         //Let the status controller we'll be closing so it can keep track of connected accounts for use with the global statuses
276         [statusController beginClosing];
278         //Let the content controller know we'll be closing so it can take action before chats are closed as a group
279         [contentController beginClosing];
281         //Preference controller needs to close the prefs window before the plugins that control it are unloaded
282         [preferenceController beginClosing];
284     //Close the controllers in reverse order
285     [pluginLoader closeController]; //should always unload first.  Plugins rely on all the controllers.
286         [componentLoader closeController];
287     [contactAlertsController closeController];
288     [fileTransferController closeController];
289         [statusController closeController];
290     [dockController closeController];
291     [interfaceController closeController];
292     [contentController closeController];
293     [contactController closeController];
294     [accountController closeController];
295         [emoticonController closeController];
296     [soundController closeController];
297     [menuController closeController];
298     [applescriptabilityController closeController];
299         [debugController closeController];
300         [toolbarController closeController];
301     [preferenceController closeController];
302         
303         [self deleteTemporaryFiles];
306 - (void)deleteTemporaryFiles
308         [[NSFileManager defaultManager] removeFilesInDirectory:[self cachesPath]
309                                                                                                 withPrefix:@"TEMP"
310                                                                                          movingToTrash:NO];
314 //Menu Item Hooks ------------------------------------------------------------------------------------------------------
315 #pragma mark Menu Item Hooks
316 //Show the about box
317 - (IBAction)showAboutBox:(id)sender
319     [[LNAboutBoxController aboutBoxController] showWindow:nil];
322 //Show our help
323 - (IBAction)showHelp:(id)sender{
324     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_TRAC_PAGE]];
326 - (IBAction)reportABug:(id)sender{
327     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_TRAC_PAGE]];
329 - (IBAction)sendFeedback:(id)sender{
330     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FEEDBACK_PAGE]];
332 - (IBAction)showForums:(id)sender{
333     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FORUM_PAGE]];
335 - (IBAction)showXtras:(id)sender{
336     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_XTRAS_PAGE]];
339 //Last call to perform actions before the app shuffles off its mortal coil and joins the bleeding choir invisible
340 - (IBAction)confirmQuit:(id)sender
342         [NSApp terminate:nil];
345 - (IBAction)launchJeeves:(id)sender
347     [[NSWorkspace sharedWorkspace] launchApplication:PATH_TO_IMPORTER];
352 //Other -------------------------------------------------------------------------------------------------------
353 #pragma mark Other
354 //If Adium was launched by double-clicking an associated file, we get this call after willFinishLaunching but before
355 //didFinishLaunching
356 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
358     NSString                    *extension = [filename pathExtension];
359     NSString                    *destination = nil;
360         NSString                        *errorMessage = nil;
361     NSString                    *fileDescription = nil, *prefsButton = nil;
362         BOOL                            success = NO, requiresRestart = NO;
363         int                                     buttonPressed;
364         
365         [prefsCategory release]; prefsCategory = nil;
366     [advancedPrefsName release]; advancedPrefsName = nil;
367         
368     //Specify a file extension and a human-readable description of what the files of this type do
369     if ([extension caseInsensitiveCompare:@"AdiumPlugin"] == NSOrderedSame){
370         destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Plugins"];
371         //Plugins haven't been loaded yet if the application isn't done loading, so only request a restart if it has finished loading already 
372         requiresRestart = completedApplicationLoad;
373         fileDescription = AILocalizedString(@"Adium plugin",nil);
375     } else if ([extension caseInsensitiveCompare:@"AdiumIcon"] == NSOrderedSame){
376                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Dock Icons"];
377         fileDescription = AILocalizedString(@"dock icon set",nil);
378                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
379                 prefsCategory = @"appearance";
381         } else if ([extension caseInsensitiveCompare:@"AdiumSoundset"] == NSOrderedSame){
382                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Sounds"];
383                 fileDescription = AILocalizedString(@"sound set",nil);
384                 prefsButton = AILocalizedString(@"Open Event Prefs",nil);
385                 prefsCategory = @"events";
387         } else if ([extension caseInsensitiveCompare:@"AdiumEmoticonset"] == NSOrderedSame){
388                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Emoticons"];
389                 fileDescription = AILocalizedString(@"emoticon set",nil);
390                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
391                 prefsCategory = @"appearance";
392                 
393         } else if ([extension caseInsensitiveCompare:@"AdiumScripts"] == NSOrderedSame) {
394                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Scripts"];
395                 fileDescription = AILocalizedString(@"AppleScript set",nil);
396                 
397         } else if ([extension caseInsensitiveCompare:@"AdiumMessageStyle"] == NSOrderedSame){
398                 if ([NSApp isOnPantherOrBetter]){
399                         destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Message Styles"];
400                         fileDescription = AILocalizedString(@"message style",nil);
401                         prefsButton = AILocalizedString(@"Open Message Prefs",nil);
402                         prefsCategory = @"messages";
403                 }else{
404                         errorMessage = AILocalizedString(@"Sorry, but Adium Message Styles are not supported in OS X 10.2 (Jaguar).",nil);
405                 }
406         } else if ([extension caseInsensitiveCompare:@"ListLayout"] == NSOrderedSame){
407                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Contact List"];
408                 fileDescription = AILocalizedString(@"contact list layout",nil);
409                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
410                 prefsCategory = @"appearance";
411                 
412         } else if ([extension caseInsensitiveCompare:@"ListTheme"] == NSOrderedSame){
413                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Contact List"];
414                 fileDescription = AILocalizedString(@"contact list theme",nil);
415                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
416                 prefsCategory = @"appearance";
417                 
418         } else if ([extension caseInsensitiveCompare:@"AdiumServiceIcons"] == NSOrderedSame){
419                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Service Icons"];
420                 fileDescription = AILocalizedString(@"service icons",nil);
421                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
422                 prefsCategory = @"appearance";
423                 
424         } else if ([extension caseInsensitiveCompare:@"AdiumStatusIcons"] == NSOrderedSame){
425                 NSString        *packName = [[filename lastPathComponent] stringByDeletingPathExtension];
427  //Can't do this because the preferenceController isn't ready yet
428  NSString       *defaultPackName = [[self preferenceController] defaultPreferenceForKey:@"Status Icon Pack"
429                                                                                                                                                           group:@"Appearance"
430                                                                                                                                                          object:nil];
432                 NSString        *defaultPackName = @"Gems";
434                 if (![packName isEqualToString:defaultPackName]) {
435                         destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Status Icons"];
436                         fileDescription = AILocalizedString(@"status icons",nil);
437                         prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
438                         prefsCategory = @"appearance";
439                 } else {
440                         errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ is the name of the default status icon pack; this pack therefore can not be installed.",nil),
441                                 packName];
442                 }
443         }
445     if (destination){
446         NSString    *destinationFilePath = [destination stringByAppendingPathComponent:[filename lastPathComponent]];
447         
448         NSString        *alertTitle = nil;
449         NSString        *alertMsg = nil;
450                 NSString        *format;
451                 
452                 if([filename isEqualToString:destinationFilePath]) {
453                         // Don't copy the file if it's already in the right place!!
454                         alertTitle= AILocalizedString(@"Installation Successful","Title of installation successful window");
455                         
456                         format = AILocalizedString(@"Installation of the %@ %@ was successful because the file was already in the correct location.",
457                                                                            "Installation introduction, like 'Installation of the message style Fiat was successful...'.");
458                         
459                         alertMsg = [NSString stringWithFormat:format,
460                                 fileDescription,
461                                 [[filename lastPathComponent] stringByDeletingPathExtension]];
462                         
463                 } else {
464                         //Trash the old file if one exists (since we know it isn't ourself)
465                         [[NSFileManager defaultManager] trashFileAtPath:destinationFilePath];
466                         
467                         //Ensure the directory exists
468                         [[NSFileManager defaultManager] createDirectoryAtPath:destination attributes:nil];
469                         
470                         //Perform the copy and display an alert informing the user of its success or failure
471                         if ([[NSFileManager defaultManager] copyPath:filename 
472                                                                                                   toPath:destinationFilePath 
473                                                                                                  handler:nil]){
474                                 
475                                 alertTitle = AILocalizedString(@"Installation Successful","Title of installation successful window");
476                                 alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was successful.",
477                                                                                                                                                    "Installation sentence, like 'Installation of the message style Fiat was successful.'."),
478                                         fileDescription,
479                                         [[filename lastPathComponent] stringByDeletingPathExtension]];
480                                 
481                                 if (requiresRestart){
482                                         alertMsg = [alertMsg stringByAppendingString:AILocalizedString(@" Please restart Adium.",nil)];
483                                 }
484                                 
485                                 success = YES;
486                         }else{
487                                 alertTitle = AILocalizedString(@"Installation Failed","Title of installation failed window");
488                                 alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was unsuccessful.",
489                                                                                                                                                 "Installation failed sentence, like 'Installation of the message style Fiat was unsuccessful.'."),
490                                         fileDescription,
491                                         [[filename lastPathComponent] stringByDeletingPathExtension]];
492                         }
493                 }
494                 
495                 [[self notificationCenter] postNotificationName:Adium_Xtras_Changed
496                                                                                                  object:[[filename lastPathComponent] pathExtension]];
497                 
498         buttonPressed = NSRunInformationalAlertPanel(alertTitle,alertMsg,nil,prefsButton,nil);
499                 
500                 // User clicked the "open prefs" button
501                 if(buttonPressed == NSAlertAlternateReturn){
502                         //If we're done loading the app, open the prefs now; if not, it'll be done once the load is finished
503                         //so the controllers and plugins have had a chance to initialize
504                         if(completedApplicationLoad) {
505                                 [self openAppropriatePreferencesIfNeeded];
506                         }
507                 }else{
508                         //If the user didn't press the "open prefs" button, clear the pref opening information
509                         [prefsCategory release]; prefsCategory = nil;
510                         [advancedPrefsName release]; advancedPrefsName = nil;
511                 }
512                 
513     }else{
514                 if (!errorMessage){
515                         errorMessage = AILocalizedString(@"An error occurred while installing the X(tra).",nil);
516                 }
517                 
518                 NSRunAlertPanel(AILocalizedString(@"Installation Failed","Title of installation failed window"),
519                                                 errorMessage,
520                                                 nil,nil,nil);
521         }
523     return success;
526 - (BOOL)application:(NSApplication *)theApplication openTempFile:(NSString *)filename
528         BOOL success;
529         
530         success = [self application:theApplication openFile:filename];
531         [[NSFileManager defaultManager] removeFileAtPath:filename handler:nil];
532         
533         return(success);
536 - (void)openAppropriatePreferencesIfNeeded
538         if (prefsCategory){
539                 if([prefsCategory isEqualToString:@"advanced"]){
540                         [preferenceController openPreferencesToAdvancedPane:advancedPrefsName];
541                 }else{
542                         [preferenceController openPreferencesToCategoryWithIdentifier:prefsCategory];
543                 }
544                 
545                 [prefsCategory release]; prefsCategory = nil;
546         }
550  * @brief Create a resource folder in the Library/Application\ Support/Adium\ 2.0 folder.
552  * Pass it the name of the folder (e.g. @"Scripts").
553  * If it is found to already in a library folder, return that pathname, using the same order of preference as
554  * -[AIAdium resourcePathsForName:]. Otherwise, create it in the user library and return the pathname to it.
555  */
556 - (NSString *)createResourcePathForName:(NSString *)name
558     NSString            *targetPath;    //This is the subfolder for the user domain (i.e. ~/L/AS/Adium\ 2.0).
559     NSFileManager       *defaultManager;
560     NSArray                     *existingResourcePaths;
562         defaultManager = [NSFileManager defaultManager];
563         existingResourcePaths = [self resourcePathsForName:name];
564         targetPath = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:name]; 
565         
566     /*
567          If the targetPath doesn't exist, create it, as this method was called to ensure that it exists
568          for creating files in the user domain.
569          */
570     if([existingResourcePaths indexOfObject:targetPath] == NSNotFound) {
571         if(![defaultManager createDirectoryAtPath:targetPath attributes:nil]) {
572                         BOOL error;
573                         
574                         //If the directory could not be created, there may be a file in the way. Death to file.
575                         error = ![defaultManager trashFileAtPath:targetPath];
577                         if (!error) error = ![defaultManager createDirectoryAtPath:targetPath attributes:nil];
579                         if (error){
580                                 targetPath = nil;
581                                 
582                                 int result;
583                                 result = NSRunCriticalAlertPanel([NSString stringWithFormat:AILocalizedString(@"Could not create the %@ folder.",nil), name],
584                                                                                                  AILocalizedString(@"Try running Repair Permissions from Disk Utility.",nil),
585                                                                                                  AILocalizedString(@"OK",nil), 
586                                                                                                  AILocalizedString(@"Launch Disk Utility",nil), 
587                                                                                                  nil);
588                                 if (result == NSAlertAlternateReturn){
589                                         [[NSWorkspace sharedWorkspace] launchApplication:@"Disk Utility"];
590                                 }
591                         }
592                 }
593     } else {
594         targetPath = [existingResourcePaths objectAtIndex:0];
595     }
597     return targetPath;
601  * @brief Return zero or more resource pathnames to an filename 
603  * Searches in the Application Support folders and the Resources/ folder of the Adium.app bundle.
604  * Only those pathnames that exist are returned.  The Adium bundle's resource path will be the last item in the array,
605  * so precedence is given to the user and system Application Support folders.
606  * 
607  * Pass nil to receive an array of paths to existing Adium Application Support folders (plus the Resouces folder).
609  * Example: If you call[adium resourcePathsForName:@"Scripts"], and there's a
610  * Scripts folder in ~/Library/Application Support/Adium\ 2.0 and in /Library/Application Support/Adium\ 2.0, but not
611  * in /System/Library/ApplicationSupport/Adium\ 2.0 or /Network/Library/Application Support/Adium\ 2.0.
612  * The array you get back will be { @"/Users/username/Library/Application Support/Adium 2.0/Scripts",
613  * @"/Library/Application Support/Adium 2.0/Scripts" }.
615  * @param name The full name (including extension as appropriate) of the resource for which to search
616  */
617 - (NSArray *)resourcePathsForName:(NSString *)name
619         NSArray                 *librarySearchPaths;
620         NSEnumerator    *searchPathEnumerator;
621         NSString                *adiumFolderName, *path;
622         NSMutableArray  *pathArray = [NSMutableArray arrayWithCapacity:4];
623         NSFileManager   *defaultManager = [NSFileManager defaultManager];
624         BOOL                    isDir;
625                         
626         adiumFolderName = (name ? [ADIUM_SUBFOLDER_OF_LIBRARY stringByAppendingPathComponent:name] : ADIUM_SUBFOLDER_OF_LIBRARY);
628         //Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
629         librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
630         searchPathEnumerator = [librarySearchPaths objectEnumerator];
632         //Copy each discovered path into the pathArray after adding our subfolder path
633         while(path = [searchPathEnumerator nextObject]){
634                 NSString        *fullPath;
635                 
636                 fullPath = [path stringByAppendingPathComponent:adiumFolderName];
637                 if(([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
638                    (isDir)){
639                         
640                         [pathArray addObject:fullPath];
641                 }
642         }
643         
644         //Add the path to the resource in Adium's bundle
645         if(name){
646                 path = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name] stringByExpandingTildeInPath];
647                 if(([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
648                    (isDir)){
649                         [pathArray addObject:path];
650                 }
651         }
652     
653         return(pathArray);
658  * @brief Returns an array of the paths to all of the resources for a given name, filtering out those without a certain extension
659  * @param name The full name (including extension as appropriate) of the resource for which to search
660  * @param extensions The extension(s) of the resources for which to search, either an NSString or an NSArray
661  */
662 - (NSArray *)allResourcesForName:(NSString *)name withExtensions:(id)extensions {
663         NSMutableArray *resources = [NSMutableArray array];
664         NSEnumerator *pathEnumerator;
665         NSEnumerator *resourceEnumerator;
666         NSString *resourceDir;
667         NSString *resourcePath;
668         BOOL extensionsArray = [extensions isKindOfClass:[NSArray class]];
669         NSEnumerator *extensionsEnumerator;
670         NSString *extension;
671         
672         // Get every path that can contain these resources
673         pathEnumerator = [[self resourcePathsForName:name] objectEnumerator];
674         
675         while (resourceDir = [pathEnumerator nextObject]) {
676                 resourceEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:resourceDir] objectEnumerator];
677                 
678                 while (resourcePath = [resourceEnumerator nextObject]) {
679                         // Add each resource to the array
680                         if (extensionsArray) {
681                                 extensionsEnumerator = [extensions objectEnumerator];
682                                 while (extension = [extensionsEnumerator nextObject]) {
683                                         if ([[resourcePath pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame)
684                                                 [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
685                                 }
686                         }
687                         else {
688                                 if ([[resourcePath pathExtension] caseInsensitiveCompare:extensions] == NSOrderedSame)
689                                         [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
690                         }
691                 }
692         }
694         return resources;
698  * @brief Return the path to be used for caching files for this user.
700  * @result A cached, tilde-expanded full path.
701  */
702 - (NSString *)cachesPath
704         static NSString *cachesPath = nil;
706         if(!cachesPath){
707                 NSString                *generalAdiumCachesPath;
708                 NSFileManager   *defaultManager = [NSFileManager defaultManager];
710                 generalAdiumCachesPath = [[[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:@"Adium"] stringByExpandingTildeInPath];
711                 cachesPath = [[generalAdiumCachesPath stringByAppendingPathComponent:[[self loginController] currentUser]] retain];
713                 //Ensure our cache path exists
714                 if([defaultManager createDirectoriesForPath:cachesPath]){
715                         //If we have to make directories, try to move old cache files into the new directory
716                         NSEnumerator    *enumerator;
717                         NSString                *filename;
718                         BOOL                    isDir;
720                         enumerator = [[defaultManager directoryContentsAtPath:generalAdiumCachesPath] objectEnumerator];
721                         while(filename = [enumerator nextObject]){
722                                 NSString        *fullPath = [generalAdiumCachesPath stringByAppendingPathComponent:filename];
723                                 
724                                 if(([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
725                                    (!isDir)){
726                                         [defaultManager movePath:fullPath
727                                                                           toPath:[cachesPath stringByAppendingPathComponent:filename]
728                                                                          handler:nil];
729                                 }
730                         }
731                 }
732         }
733         
734         return cachesPath;
737 - (NSString *)pathOfPackWithName:(NSString *)name extension:(NSString *)extension resourceFolderName:(NSString *)folderName
739         NSFileManager   *fileManager = [NSFileManager defaultManager];
740     NSString            *packFileName = [name stringByAppendingPathExtension:extension];
741     NSEnumerator        *enumerator = [[self resourcePathsForName:folderName] objectEnumerator];
742     NSString            *resourcePath;
744         //Search all our resource paths for the requested pack
745     while(resourcePath = [enumerator nextObject]){
746                 NSString *packPath = [resourcePath stringByAppendingPathComponent:packFileName];
747                 if([fileManager fileExistsAtPath:packPath]) return([packPath stringByExpandingTildeInPath]);
748         }
750     return(nil);        
753 //If this is the first time running a version, post Adium_versionUpgraded with information about the old and new versions.
754 /*- (NSDictionary *)versionUpgradeDict
756         NSString        *currentVersionString, *lastLaunchedVersionString;
757         float       currentVersion, lastLaunchedVersion;
758         NSNumber        *currentVersionNumber;
759         NSDictionary    *versionUpgradeDict = nil;
760         
761         currentVersionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:kCFBundleVersionKey];
762         lastLaunchedVersionString = [[self preferenceController] preferenceForKey:KEY_LAST_VERSION_LAUNCHED
763                                                                                                                                                 group:PREF_GROUP_GENERAL];      
764         // ##### BETA ONLY
765 #if BETA_RELEASE
766         //Friendly reminder that we are running with the beta flag on
767         NSString        *spaces1, *spaces2;
768         unsigned        length = [currentVersionString length];
769         
770         spaces1 = [@"" stringByPaddingToLength:(length / 2)
771                                                                 withString:@" "
772                                                    startingAtIndex:0];  
773         if (length % 2 == 0){
774                 //An even length is one space too much
775                 spaces2 = [@"" stringByPaddingToLength:(length / 2) - 1
776                                                                         withString:@" "
777                                                            startingAtIndex:0];                  
778         }else{
779                 //An odd length is okay
780                 spaces2 = spaces1;
781         }
782         
783         NSLog(@"####     %@THIS IS A BETA RELEASE!%@     ####",spaces1,spaces2);
784         NSLog(@"#### Loading Adium X BETA Release v%@ ####",currentVersionString);
786         AILog(@"####     %@THIS IS A BETA RELEASE!%@     ####",spaces1,spaces2);
787         AILog(@"#### Loading Adium X BETA Release v%@ ####",currentVersionString);
789         currentVersionString = [self processBetaVersionString:currentVersionString];
790         lastLaunchedVersionString = [self processBetaVersionString:lastLaunchedVersionString];
791 #endif  
792         
793         currentVersion = [currentVersionString floatValue];
794         currentVersionNumber = [NSNumber numberWithFloat:currentVersion];
795         
796         lastLaunchedVersion = [lastLaunchedVersionString floatValue];   
798         if (!lastLaunchedVersion || !currentVersion || currentVersion > lastLaunchedVersion){
799                 
800                 if (lastLaunchedVersion){
801                         
802                         NSNumber                *lastLaunchedVersionNumber = [NSNumber numberWithFloat:lastLaunchedVersion];
803                         
804                         versionUpgradeDict = [NSDictionary dictionaryWithObjectsAndKeys:lastLaunchedVersionNumber, @"lastLaunchedVersion",
805                                 currentVersionNumber,@"currentVersion",
806                                 nil];
807                 }else{
808                         versionUpgradeDict = [NSDictionary dictionaryWithObject:currentVersionNumber
809                                                                                                                          forKey:@"currentVersion"];                     
810                 }
811         }
812         
813         //Remember that we have now run in this version.
814         if(versionUpgradeDict){
815                 [[self preferenceController] setPreference:currentVersionString
816                                                                                         forKey:KEY_LAST_VERSION_LAUNCHED
817                                                                                          group:PREF_GROUP_GENERAL];
818          }
819         
820         return(versionUpgradeDict);
823 - (NSString *)processBetaVersionString:(NSString *)inString
825         NSString        *returnString = nil;
826         
827         if ([inString isEqualToString:@"0.7b1"]){
828                 returnString = @"0.68";
829         }else if ([inString isEqualToString:@"0.7b2"]){
830                 returnString = @"0.681";
831         }else if ([inString isEqualToString:@"0.7b3"]){
832                 returnString = @"0.682";
833         }else if ([inString isEqualToString:@"0.7b4"]){
834                 returnString = @"0.683";
835         }else if ([inString isEqualToString:@"0.7b5"]){
836                 returnString = @"0.684";
837         }else if ([inString isEqualToString:@"0.7b6"]){
838                 returnString = @"0.685";
839         }else if ([inString isEqualToString:@"0.7b7"]){
840                 returnString = @"0.686";
841         }else if ([inString isEqualToString:@"0.7b8"]){
842                 returnString = @"0.687";
843         }
844         
845         return(returnString ? returnString : inString);
848 #pragma mark Scripting
849 - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key {
850         BOOL handleKey = NO;
851         
852         if([key isEqualToString:@"applescriptabilityController"] || 
853            [key isEqualToString:@"interfaceController"] ){
854                 handleKey = YES;
855                 
856         }
857         
858         return handleKey;
861 @end