French updates
[adiumx.git] / Source / AIAdium.m
blob4cc0b1f8c4db28b691175d6e6f64181cac716cee
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         //Let the status controller we'll be closing so it can keep track of connected accounts for use with the global statuses
307         [statusController beginClosing];
309         //Let the content controller know we'll be closing so it can take action before chats are closed as a group
310         [contentController beginClosing];
312         //Preference controller needs to close the prefs window before the plugins that control it are unloaded
313         [preferenceController beginClosing];
315     //Close the controllers in reverse order
316     [pluginLoader closeController]; //should always unload first.  Plugins rely on all the controllers.
317         [componentLoader closeController];
318     [contactAlertsController closeController];
319     [fileTransferController closeController];
320         [statusController closeController];
321     [dockController closeController];
322     [interfaceController closeController];
323     [contentController closeController];
324     [contactController closeController];
325     [accountController closeController];
326         [emoticonController closeController];
327     [soundController closeController];
328     [menuController closeController];
329     [applescriptabilityController closeController];
330         [debugController closeController];
331         [toolbarController closeController];
332     [preferenceController closeController];
333         
334         [self deleteTemporaryFiles];
337 - (void)deleteTemporaryFiles
339         [[NSFileManager defaultManager] removeFilesInDirectory:[self cachesPath]
340                                                                                                 withPrefix:@"TEMP"
341                                                                                          movingToTrash:NO];
345 //Menu Item Hooks ------------------------------------------------------------------------------------------------------
346 #pragma mark Menu Item Hooks
347 //Show the about box
348 - (IBAction)showAboutBox:(id)sender
350     [[LNAboutBoxController aboutBoxController] showWindow:nil];
353 //Show our help
354 - (IBAction)showHelp:(id)sender{
355     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_TRAC_PAGE]];
357 - (IBAction)reportABug:(id)sender{
358     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_TRAC_PAGE]];
360 - (IBAction)sendFeedback:(id)sender{
361     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FEEDBACK_PAGE]];
363 - (IBAction)showForums:(id)sender{
364     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FORUM_PAGE]];
366 - (IBAction)showXtras:(id)sender{
367     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_XTRAS_PAGE]];
370 //Last call to perform actions before the app shuffles off its mortal coil and joins the bleeding choir invisible
371 - (IBAction)confirmQuit:(id)sender
373         [NSApp terminate:nil];
376 - (IBAction)launchJeeves:(id)sender
378     [[NSWorkspace sharedWorkspace] launchApplication:PATH_TO_IMPORTER];
383 //Other -------------------------------------------------------------------------------------------------------
384 #pragma mark Other
385 //If Adium was launched by double-clicking an associated file, we get this call after willFinishLaunching but before
386 //didFinishLaunching
387 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
389     NSString                    *extension = [filename pathExtension];
390     NSString                    *destination = nil;
391         NSString                        *errorMessage = nil;
392     NSString                    *fileDescription = nil, *prefsButton = nil;
393         BOOL                            success = NO, requiresRestart = NO;
394         int                                     buttonPressed;
395         
396         [prefsCategory release]; prefsCategory = nil;
397     [advancedPrefsName release]; advancedPrefsName = nil;
398         
399     //Specify a file extension and a human-readable description of what the files of this type do
400     if ([extension caseInsensitiveCompare:@"AdiumPlugin"] == NSOrderedSame){
401         destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Plugins"];
402         //Plugins haven't been loaded yet if the application isn't done loading, so only request a restart if it has finished loading already 
403         requiresRestart = completedApplicationLoad;
404         fileDescription = AILocalizedString(@"Adium plugin",nil);
406     } else if ([extension caseInsensitiveCompare:@"AdiumIcon"] == NSOrderedSame){
407                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Dock Icons"];
408         fileDescription = AILocalizedString(@"dock icon set",nil);
409                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
410                 prefsCategory = @"appearance";
412         } else if ([extension caseInsensitiveCompare:@"AdiumSoundset"] == NSOrderedSame){
413                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Sounds"];
414                 fileDescription = AILocalizedString(@"sound set",nil);
415                 prefsButton = AILocalizedString(@"Open Event Prefs",nil);
416                 prefsCategory = @"events";
418         } else if ([extension caseInsensitiveCompare:@"AdiumEmoticonset"] == NSOrderedSame){
419                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Emoticons"];
420                 fileDescription = AILocalizedString(@"emoticon set",nil);
421                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
422                 prefsCategory = @"appearance";
423                 
424         } else if ([extension caseInsensitiveCompare:@"AdiumScripts"] == NSOrderedSame) {
425                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Scripts"];
426                 fileDescription = AILocalizedString(@"AppleScript set",nil);
427                 
428         } else if ([extension caseInsensitiveCompare:@"AdiumMessageStyle"] == NSOrderedSame){
429                 if ([NSApp isOnPantherOrBetter]){
430                         destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Message Styles"];
431                         fileDescription = AILocalizedString(@"message style",nil);
432                         prefsButton = AILocalizedString(@"Open Message Prefs",nil);
433                         prefsCategory = @"messages";
434                 }else{
435                         errorMessage = AILocalizedString(@"Sorry, but Adium Message Styles are not supported in OS X 10.2 (Jaguar).",nil);
436                 }
437         } else if ([extension caseInsensitiveCompare:@"ListLayout"] == NSOrderedSame){
438                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Contact List"];
439                 fileDescription = AILocalizedString(@"contact list layout",nil);
440                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
441                 prefsCategory = @"appearance";
442                 
443         } else if ([extension caseInsensitiveCompare:@"ListTheme"] == NSOrderedSame){
444                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Contact List"];
445                 fileDescription = AILocalizedString(@"contact list theme",nil);
446                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
447                 prefsCategory = @"appearance";
448                 
449         } else if ([extension caseInsensitiveCompare:@"AdiumServiceIcons"] == NSOrderedSame){
450                 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Service Icons"];
451                 fileDescription = AILocalizedString(@"service icons",nil);
452                 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
453                 prefsCategory = @"appearance";
454                 
455         } else if ([extension caseInsensitiveCompare:@"AdiumStatusIcons"] == NSOrderedSame){
456                 NSString        *packName = [[filename lastPathComponent] stringByDeletingPathExtension];
458  //Can't do this because the preferenceController isn't ready yet
459  NSString       *defaultPackName = [[self preferenceController] defaultPreferenceForKey:@"Status Icon Pack"
460                                                                                                                                                           group:@"Appearance"
461                                                                                                                                                          object:nil];
463                 NSString        *defaultPackName = @"Gems";
465                 if (![packName isEqualToString:defaultPackName]) {
466                         destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Status Icons"];
467                         fileDescription = AILocalizedString(@"status icons",nil);
468                         prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
469                         prefsCategory = @"appearance";
470                 } else {
471                         errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ is the name of the default status icon pack; this pack therefore can not be installed.",nil),
472                                 packName];
473                 }
474         }
476     if (destination){
477         NSString    *destinationFilePath = [destination stringByAppendingPathComponent:[filename lastPathComponent]];
478         
479         NSString        *alertTitle = nil;
480         NSString        *alertMsg = nil;
481                 NSString        *format;
482                 
483                 if([filename isEqualToString:destinationFilePath]) {
484                         // Don't copy the file if it's already in the right place!!
485                         alertTitle= AILocalizedString(@"Installation Successful","Title of installation successful window");
486                         
487                         format = AILocalizedString(@"Installation of the %@ %@ was successful because the file was already in the correct location.",
488                                                                            "Installation introduction, like 'Installation of the message style Fiat was successful...'.");
489                         
490                         alertMsg = [NSString stringWithFormat:format,
491                                 fileDescription,
492                                 [[filename lastPathComponent] stringByDeletingPathExtension]];
493                         
494                 } else {
495                         //Trash the old file if one exists (since we know it isn't ourself)
496                         [[NSFileManager defaultManager] trashFileAtPath:destinationFilePath];
497                         
498                         //Ensure the directory exists
499                         [[NSFileManager defaultManager] createDirectoryAtPath:destination attributes:nil];
500                         
501                         //Perform the copy and display an alert informing the user of its success or failure
502                         if ([[NSFileManager defaultManager] copyPath:filename 
503                                                                                                   toPath:destinationFilePath 
504                                                                                                  handler:nil]){
505                                 
506                                 alertTitle = AILocalizedString(@"Installation Successful","Title of installation successful window");
507                                 alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was successful.",
508                                                                                                                                                    "Installation sentence, like 'Installation of the message style Fiat was successful.'."),
509                                         fileDescription,
510                                         [[filename lastPathComponent] stringByDeletingPathExtension]];
511                                 
512                                 if (requiresRestart){
513                                         alertMsg = [alertMsg stringByAppendingString:AILocalizedString(@" Please restart Adium.",nil)];
514                                 }
515                                 
516                                 success = YES;
517                         }else{
518                                 alertTitle = AILocalizedString(@"Installation Failed","Title of installation failed window");
519                                 alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was unsuccessful.",
520                                                                                                                                                 "Installation failed sentence, like 'Installation of the message style Fiat was unsuccessful.'."),
521                                         fileDescription,
522                                         [[filename lastPathComponent] stringByDeletingPathExtension]];
523                         }
524                 }
525                 
526                 [[self notificationCenter] postNotificationName:Adium_Xtras_Changed
527                                                                                                  object:[[filename lastPathComponent] pathExtension]];
528                 
529         buttonPressed = NSRunInformationalAlertPanel(alertTitle,alertMsg,nil,prefsButton,nil);
530                 
531                 // User clicked the "open prefs" button
532                 if(buttonPressed == NSAlertAlternateReturn){
533                         //If we're done loading the app, open the prefs now; if not, it'll be done once the load is finished
534                         //so the controllers and plugins have had a chance to initialize
535                         if(completedApplicationLoad) {
536                                 [self openAppropriatePreferencesIfNeeded];
537                         }
538                 }else{
539                         //If the user didn't press the "open prefs" button, clear the pref opening information
540                         [prefsCategory release]; prefsCategory = nil;
541                         [advancedPrefsName release]; advancedPrefsName = nil;
542                 }
543                 
544     }else{
545                 if (!errorMessage){
546                         errorMessage = AILocalizedString(@"An error occurred while installing the X(tra).",nil);
547                 }
548                 
549                 NSRunAlertPanel(AILocalizedString(@"Installation Failed","Title of installation failed window"),
550                                                 errorMessage,
551                                                 nil,nil,nil);
552         }
554     return success;
557 - (BOOL)application:(NSApplication *)theApplication openTempFile:(NSString *)filename
559         BOOL success;
560         
561         success = [self application:theApplication openFile:filename];
562         [[NSFileManager defaultManager] removeFileAtPath:filename handler:nil];
563         
564         return(success);
567 - (void)openAppropriatePreferencesIfNeeded
569         if (prefsCategory){
570                 if([prefsCategory isEqualToString:@"advanced"]){
571                         [preferenceController openPreferencesToAdvancedPane:advancedPrefsName];
572                 }else{
573                         [preferenceController openPreferencesToCategoryWithIdentifier:prefsCategory];
574                 }
575                 
576                 [prefsCategory release]; prefsCategory = nil;
577         }
581  * @brief Create a resource folder in the Library/Application\ Support/Adium\ 2.0 folder.
583  * Pass it the name of the folder (e.g. @"Scripts").
584  * If it is found to already in a library folder, return that pathname, using the same order of preference as
585  * -[AIAdium resourcePathsForName:]. Otherwise, create it in the user library and return the pathname to it.
586  */
587 - (NSString *)createResourcePathForName:(NSString *)name
589     NSString            *targetPath;    //This is the subfolder for the user domain (i.e. ~/L/AS/Adium\ 2.0).
590     NSFileManager       *defaultManager;
591     NSArray                     *existingResourcePaths;
593         defaultManager = [NSFileManager defaultManager];
594         existingResourcePaths = [self resourcePathsForName:name];
595         targetPath = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:name]; 
596         
597     /*
598          If the targetPath doesn't exist, create it, as this method was called to ensure that it exists
599          for creating files in the user domain.
600          */
601     if([existingResourcePaths indexOfObject:targetPath] == NSNotFound) {
602         if(![defaultManager createDirectoryAtPath:targetPath attributes:nil]) {
603                         BOOL error;
604                         
605                         //If the directory could not be created, there may be a file in the way. Death to file.
606                         error = ![defaultManager trashFileAtPath:targetPath];
608                         if (!error) error = ![defaultManager createDirectoryAtPath:targetPath attributes:nil];
610                         if (error){
611                                 targetPath = nil;
612                                 
613                                 int result;
614                                 result = NSRunCriticalAlertPanel([NSString stringWithFormat:AILocalizedString(@"Could not create the %@ folder.",nil), name],
615                                                                                                  AILocalizedString(@"Try running Repair Permissions from Disk Utility.",nil),
616                                                                                                  AILocalizedString(@"OK",nil), 
617                                                                                                  AILocalizedString(@"Launch Disk Utility",nil), 
618                                                                                                  nil);
619                                 if (result == NSAlertAlternateReturn){
620                                         [[NSWorkspace sharedWorkspace] launchApplication:@"Disk Utility"];
621                                 }
622                         }
623                 }
624     } else {
625         targetPath = [existingResourcePaths objectAtIndex:0];
626     }
628     return targetPath;
632  * @brief Return zero or more resource pathnames to an filename 
634  * Searches in the Application Support folders and the Resources/ folder of the Adium.app bundle.
635  * Only those pathnames that exist are returned.  The Adium bundle's resource path will be the last item in the array,
636  * so precedence is given to the user and system Application Support folders.
637  * 
638  * Pass nil to receive an array of paths to existing Adium Application Support folders (plus the Resouces folder).
640  * Example: If you call[adium resourcePathsForName:@"Scripts"], and there's a
641  * Scripts folder in ~/Library/Application Support/Adium\ 2.0 and in /Library/Application Support/Adium\ 2.0, but not
642  * in /System/Library/ApplicationSupport/Adium\ 2.0 or /Network/Library/Application Support/Adium\ 2.0.
643  * The array you get back will be { @"/Users/username/Library/Application Support/Adium 2.0/Scripts",
644  * @"/Library/Application Support/Adium 2.0/Scripts" }.
646  * @param name The full name (including extension as appropriate) of the resource for which to search
647  */
648 - (NSArray *)resourcePathsForName:(NSString *)name
650         NSArray                 *librarySearchPaths;
651         NSEnumerator    *searchPathEnumerator;
652         NSString                *adiumFolderName, *path;
653         NSMutableArray  *pathArray = [NSMutableArray arrayWithCapacity:4];
654         NSFileManager   *defaultManager = [NSFileManager defaultManager];
655         BOOL                    isDir;
656                         
657         adiumFolderName = (name ? [ADIUM_SUBFOLDER_OF_LIBRARY stringByAppendingPathComponent:name] : ADIUM_SUBFOLDER_OF_LIBRARY);
659         //Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
660         librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
661         searchPathEnumerator = [librarySearchPaths objectEnumerator];
663         //Copy each discovered path into the pathArray after adding our subfolder path
664         while(path = [searchPathEnumerator nextObject]){
665                 NSString        *fullPath;
666                 
667                 fullPath = [path stringByAppendingPathComponent:adiumFolderName];
668                 if(([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
669                    (isDir)){
670                         
671                         [pathArray addObject:fullPath];
672                 }
673         }
674         
675         //Add the path to the resource in Adium's bundle
676         if(name){
677                 path = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name] stringByExpandingTildeInPath];
678                 if(([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
679                    (isDir)){
680                         [pathArray addObject:path];
681                 }
682         }
683     
684         return(pathArray);
689  * @brief Returns an array of the paths to all of the resources for a given name, filtering out those without a certain extension
690  * @param name The full name (including extension as appropriate) of the resource for which to search
691  * @param extensions The extension(s) of the resources for which to search, either an NSString or an NSArray
692  */
693 - (NSArray *)allResourcesForName:(NSString *)name withExtensions:(id)extensions {
694         NSMutableArray *resources = [NSMutableArray array];
695         NSEnumerator *pathEnumerator;
696         NSEnumerator *resourceEnumerator;
697         NSString *resourceDir;
698         NSString *resourcePath;
699         BOOL extensionsArray = [extensions isKindOfClass:[NSArray class]];
700         NSEnumerator *extensionsEnumerator;
701         NSString *extension;
702         
703         // Get every path that can contain these resources
704         pathEnumerator = [[self resourcePathsForName:name] objectEnumerator];
705         
706         while (resourceDir = [pathEnumerator nextObject]) {
707                 resourceEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:resourceDir] objectEnumerator];
708                 
709                 while (resourcePath = [resourceEnumerator nextObject]) {
710                         // Add each resource to the array
711                         if (extensionsArray) {
712                                 extensionsEnumerator = [extensions objectEnumerator];
713                                 while (extension = [extensionsEnumerator nextObject]) {
714                                         if ([[resourcePath pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame)
715                                                 [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
716                                 }
717                         }
718                         else {
719                                 if ([[resourcePath pathExtension] caseInsensitiveCompare:extensions] == NSOrderedSame)
720                                         [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
721                         }
722                 }
723         }
725         return resources;
729  * @brief Return the path to be used for caching files for this user.
731  * @result A cached, tilde-expanded full path.
732  */
733 - (NSString *)cachesPath
735         static NSString *cachesPath = nil;
737         if(!cachesPath){
738                 NSString                *generalAdiumCachesPath;
739                 NSFileManager   *defaultManager = [NSFileManager defaultManager];
741                 generalAdiumCachesPath = [[[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:@"Adium"] stringByExpandingTildeInPath];
742                 cachesPath = [[generalAdiumCachesPath stringByAppendingPathComponent:[[self loginController] currentUser]] retain];
744                 //Ensure our cache path exists
745                 if([defaultManager createDirectoriesForPath:cachesPath]){
746                         //If we have to make directories, try to move old cache files into the new directory
747                         NSEnumerator    *enumerator;
748                         NSString                *filename;
749                         BOOL                    isDir;
751                         enumerator = [[defaultManager directoryContentsAtPath:generalAdiumCachesPath] objectEnumerator];
752                         while(filename = [enumerator nextObject]){
753                                 NSString        *fullPath = [generalAdiumCachesPath stringByAppendingPathComponent:filename];
754                                 
755                                 if(([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
756                                    (!isDir)){
757                                         [defaultManager movePath:fullPath
758                                                                           toPath:[cachesPath stringByAppendingPathComponent:filename]
759                                                                          handler:nil];
760                                 }
761                         }
762                 }
763         }
764         
765         return cachesPath;
768 - (NSString *)pathOfPackWithName:(NSString *)name extension:(NSString *)extension resourceFolderName:(NSString *)folderName
770         NSFileManager   *fileManager = [NSFileManager defaultManager];
771     NSString            *packFileName = [name stringByAppendingPathExtension:extension];
772     NSEnumerator        *enumerator = [[self resourcePathsForName:folderName] objectEnumerator];
773     NSString            *resourcePath;
775         //Search all our resource paths for the requested pack
776     while(resourcePath = [enumerator nextObject]){
777                 NSString *packPath = [resourcePath stringByAppendingPathComponent:packFileName];
778                 if([fileManager fileExistsAtPath:packPath]) return([packPath stringByExpandingTildeInPath]);
779         }
781     return(nil);        
784 //If this is the first time running a version, post Adium_versionUpgraded with information about the old and new versions.
785 /*- (NSDictionary *)versionUpgradeDict
787         NSString        *currentVersionString, *lastLaunchedVersionString;
788         float       currentVersion, lastLaunchedVersion;
789         NSNumber        *currentVersionNumber;
790         NSDictionary    *versionUpgradeDict = nil;
791         
792         currentVersionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:kCFBundleVersionKey];
793         lastLaunchedVersionString = [[self preferenceController] preferenceForKey:KEY_LAST_VERSION_LAUNCHED
794                                                                                                                                                 group:PREF_GROUP_GENERAL];      
795         // ##### BETA ONLY
796 #if BETA_RELEASE
797         //Friendly reminder that we are running with the beta flag on
798         NSString        *spaces1, *spaces2;
799         unsigned        length = [currentVersionString length];
800         
801         spaces1 = [@"" stringByPaddingToLength:(length / 2)
802                                                                 withString:@" "
803                                                    startingAtIndex:0];  
804         if (length % 2 == 0){
805                 //An even length is one space too much
806                 spaces2 = [@"" stringByPaddingToLength:(length / 2) - 1
807                                                                         withString:@" "
808                                                            startingAtIndex:0];                  
809         }else{
810                 //An odd length is okay
811                 spaces2 = spaces1;
812         }
813         
814         NSLog(@"####     %@THIS IS A BETA RELEASE!%@     ####",spaces1,spaces2);
815         NSLog(@"#### Loading Adium X BETA Release v%@ ####",currentVersionString);
817         AILog(@"####     %@THIS IS A BETA RELEASE!%@     ####",spaces1,spaces2);
818         AILog(@"#### Loading Adium X BETA Release v%@ ####",currentVersionString);
820         currentVersionString = [self processBetaVersionString:currentVersionString];
821         lastLaunchedVersionString = [self processBetaVersionString:lastLaunchedVersionString];
822 #endif  
823         
824         currentVersion = [currentVersionString floatValue];
825         currentVersionNumber = [NSNumber numberWithFloat:currentVersion];
826         
827         lastLaunchedVersion = [lastLaunchedVersionString floatValue];   
829         if (!lastLaunchedVersion || !currentVersion || currentVersion > lastLaunchedVersion){
830                 
831                 if (lastLaunchedVersion){
832                         
833                         NSNumber                *lastLaunchedVersionNumber = [NSNumber numberWithFloat:lastLaunchedVersion];
834                         
835                         versionUpgradeDict = [NSDictionary dictionaryWithObjectsAndKeys:lastLaunchedVersionNumber, @"lastLaunchedVersion",
836                                 currentVersionNumber,@"currentVersion",
837                                 nil];
838                 }else{
839                         versionUpgradeDict = [NSDictionary dictionaryWithObject:currentVersionNumber
840                                                                                                                          forKey:@"currentVersion"];                     
841                 }
842         }
843         
844         //Remember that we have now run in this version.
845         if(versionUpgradeDict){
846                 [[self preferenceController] setPreference:currentVersionString
847                                                                                         forKey:KEY_LAST_VERSION_LAUNCHED
848                                                                                          group:PREF_GROUP_GENERAL];
849          }
850         
851         return(versionUpgradeDict);
854 - (NSString *)processBetaVersionString:(NSString *)inString
856         NSString        *returnString = nil;
857         
858         if ([inString isEqualToString:@"0.7b1"]){
859                 returnString = @"0.68";
860         }else if ([inString isEqualToString:@"0.7b2"]){
861                 returnString = @"0.681";
862         }else if ([inString isEqualToString:@"0.7b3"]){
863                 returnString = @"0.682";
864         }else if ([inString isEqualToString:@"0.7b4"]){
865                 returnString = @"0.683";
866         }else if ([inString isEqualToString:@"0.7b5"]){
867                 returnString = @"0.684";
868         }else if ([inString isEqualToString:@"0.7b6"]){
869                 returnString = @"0.685";
870         }else if ([inString isEqualToString:@"0.7b7"]){
871                 returnString = @"0.686";
872         }else if ([inString isEqualToString:@"0.7b8"]){
873                 returnString = @"0.687";
874         }
875         
876         return(returnString ? returnString : inString);
879 #pragma mark Scripting
880 - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key {
881         BOOL handleKey = NO;
882         
883         if([key isEqualToString:@"applescriptabilityController"] || 
884            [key isEqualToString:@"interfaceController"] ){
885                 handleKey = YES;
886                 
887         }
888         
889         return handleKey;
892 @end