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