Source/AIAdium.m
author Peter Hosey
Tue Aug 14 02:59:20 2007 +0000 (2007-08-14)
branchadium-1.0
changeset 18554 30d173574771
parent 18281 e45de3601283
child 18690 305588b612ab
permissions -rw-r--r--
The first generation of appcasts has ended.

The second generation of appcasts has begun.

The difference is that generation 1 appcasts cannot have the `minimumSystemVersion` element, whereas generation 2 appcasts should (and must have the `generation=2` key in their URL query).
     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  */
    16 
    17 #import "AIAdium.h"
    18 #import "AdiumURLHandling.h"
    19 #import "AIAccountController.h"
    20 #import "AIChatController.h"
    21 #import "AIContactController.h"
    22 #import "AIContentController.h"
    23 #import "AICoreComponentLoader.h"
    24 #import "AICorePluginLoader.h"
    25 #import "AIDockController.h"
    26 #import "AIEmoticonController.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 "AIXtrasManager.h"
    40 #import "AdiumSetupWizard.h"
    41 #import "ESTextAndButtonsWindowController.h"
    42 #import "AIAppearancePreferences.h"
    43 #import <Adium/AIAdiumProtocol.h>
    44 #import <Adium/AIPathUtilities.h>
    45 #import <AIUtilities/AIFileManagerAdditions.h>
    46 #import <AIUtilities/AIApplicationAdditions.h>
    47 #import <AIUtilities/AICalendarDateAdditions.h>
    48 #import <Sparkle/SUConstants.h>
    49 #import <Sparkle/SUUtilities.h>
    50 
    51 //For Apple Help
    52 #import <Carbon/Carbon.h>
    53 
    54 #define ADIUM_TRAC_PAGE						@"http://trac.adiumx.com/"
    55 #define ADIUM_REPORT_BUG_PAGE				@"http://trac.adiumx.com/wiki/ReportingBugs"
    56 #define ADIUM_FORUM_PAGE					AILocalizedString(@"http://forum.adiumx.com/","Adium forums page. Localized only if a translated version exists.")
    57 #define ADIUM_FEEDBACK_PAGE					@"mailto:feedback@adiumx.com"
    58 
    59 //Portable Adium prefs key
    60 #define PORTABLE_ADIUM_KEY					@"Preference Folder Location"
    61 
    62 #define ALWAYS_RUN_SETUP_WIZARD FALSE
    63 
    64 static NSString	*prefsCategory;
    65 
    66 enum {
    67     kNumberType,
    68     kStringType,
    69     kPeriodType
    70 };
    71 
    72 // The version comparison code here is courtesy of Kevin Ballard, adapted from MacPAD. Thanks, Kevin!
    73 
    74 int AIGetCharType(NSString *character)
    75 {
    76     if ([character isEqualToString:@"."]) {
    77         return kPeriodType;
    78     } else if ([character isEqualToString:@"0"] || [character intValue] != 0) {
    79         return kNumberType;
    80     } else {
    81         return kStringType;
    82     }	
    83 }
    84 
    85 NSArray *AISplitVersionString(NSString *version)
    86 {
    87     NSString *character;
    88     NSMutableString *s;
    89     int i, n, oldType, newType;
    90     NSMutableArray *parts = [NSMutableArray array];
    91     if ([version length] == 0) {
    92         // Nothing to do here
    93         return parts;
    94     }
    95     s = [[[version substringToIndex:1] mutableCopy] autorelease];
    96     oldType = AIGetCharType(s);
    97     n = [version length] - 1;
    98     for (i = 1; i <= n; ++i) {
    99         character = [version substringWithRange:NSMakeRange(i, 1)];
   100         newType = AIGetCharType(character);
   101         if (oldType != newType || oldType == kPeriodType) {
   102             // We've reached a new segment
   103 			NSString *aPart = [[NSString alloc] initWithString:s];
   104             [parts addObject:aPart];
   105 			[aPart release];
   106             [s setString:character];
   107         } else {
   108             // Add character to string and continue
   109             [s appendString:character];
   110         }
   111         oldType = newType;
   112     }
   113     
   114     // Add the last part onto the array
   115     [parts addObject:[NSString stringWithString:s]];
   116     return parts;
   117 }
   118 
   119 //newVersion, currentVersion
   120 NSComparisonResult AICustomVersionComparison(NSString *versionA, NSString *versionB)
   121 {
   122 	NSArray *partsA = AISplitVersionString(versionA);
   123     NSArray *partsB = AISplitVersionString(versionB);
   124     
   125     NSString *partA, *partB;
   126     int i, n, typeA, typeB, intA, intB;
   127     
   128     n = MIN([partsA count], [partsB count]);
   129     for (i = 0; i < n; ++i) {
   130         partA = [partsA objectAtIndex:i];
   131         partB = [partsB objectAtIndex:i];
   132         
   133         typeA = AIGetCharType(partA);
   134         typeB = AIGetCharType(partB);
   135         
   136         // Compare types
   137         if (typeA == typeB) {
   138             // Same type; we can compare
   139             if (typeA == kNumberType) {
   140                 intA = [partA intValue];
   141                 intB = [partB intValue];
   142                 if (intA > intB) {
   143                     return NSOrderedAscending;
   144                 } else if (intA < intB) {
   145                     return NSOrderedDescending;
   146                 }
   147             } else if (typeA == kStringType) {
   148                 NSComparisonResult result = [partA compare:partB];
   149                 if (result != NSOrderedSame) {
   150 					if ([partB isEqualToString:@"rc"])
   151 						return NSOrderedDescending;
   152 					if ([partA isEqualToString:@"rc"])
   153 						return NSOrderedAscending;
   154 					if ([partB isEqualToString:@"b"])
   155 						return NSOrderedDescending;
   156 					if ([partA isEqualToString:@"b"])
   157 						return NSOrderedAscending;
   158 					if ([partB isEqualToString:@"a"])
   159 						return NSOrderedDescending;
   160 					if ([partA isEqualToString:@"a"])
   161 						return NSOrderedAscending;
   162                 }
   163             }
   164         } else {
   165             // Not the same type? Now we have to do some validity checking
   166             if (typeA != kStringType && typeB == kStringType) {
   167                 // typeA wins
   168                 return NSOrderedAscending;
   169             } else if (typeA == kStringType && typeB != kStringType) {
   170                 // typeB wins
   171                 return NSOrderedDescending;
   172             } else {
   173                 // One is a number and the other is a period. The period is invalid
   174                 if (typeA == kNumberType) {
   175                     return NSOrderedAscending;
   176                 } else {
   177                     return NSOrderedDescending;
   178                 }
   179             }
   180         }
   181     }
   182     // The versions are equal up to the point where they both still have parts
   183     // Lets check to see if one is larger than the other
   184     if ([partsA count] != [partsB count]) {
   185         // Yep. Lets get the next part of the larger
   186         // n holds the value we want
   187         NSString *missingPart;
   188         int missingType, shorterResult, largerResult;
   189         
   190         if ([partsA count] > [partsB count]) {
   191             missingPart = [partsA objectAtIndex:n];
   192             shorterResult = NSOrderedDescending;
   193             largerResult = NSOrderedAscending;
   194         } else {
   195             missingPart = [partsB objectAtIndex:n];
   196             shorterResult = NSOrderedAscending;
   197             largerResult = NSOrderedDescending;
   198         }
   199         
   200         missingType = AIGetCharType(missingPart);
   201         // Check the type
   202         if (missingType == kStringType) {
   203             // It's a string. Shorter version wins
   204             return shorterResult;
   205         } else {
   206             // It's a number/period. Larger version wins
   207             return largerResult;
   208         }
   209     }
   210     
   211     // The 2 strings are identical
   212     return NSOrderedSame;
   213 }
   214 
   215 @interface AIAdium (PRIVATE)
   216 - (void)completeLogin;
   217 - (void)openAppropriatePreferencesIfNeeded;
   218 - (void)configureHelp;
   219 - (void)deleteTemporaryFiles;
   220 @end
   221 
   222 @implementation AIAdium
   223 
   224 //Init
   225 - (id)init
   226 {
   227 	if ((self = [super init])) {
   228 		[AIObject _setSharedAdiumInstance:self];
   229 	}
   230 
   231 	return self;
   232 }
   233 
   234 //Core Controllers -----------------------------------------------------------------------------------------------------
   235 #pragma mark Core Controllers
   236 - (NSObject <AILoginController> *)loginController{
   237     return loginController;
   238 }
   239 - (NSObject <AIMenuController> *)menuController{
   240     return menuController;
   241 }
   242 - (NSObject <AIAccountController> *)accountController{
   243     return accountController;
   244 }
   245 - (NSObject <AIChatController> *)chatController{
   246 	return chatController;
   247 }
   248 - (NSObject <AIContentController> *)contentController{
   249     return contentController;
   250 }
   251 - (NSObject <AIContactController> *)contactController{
   252     return contactController;
   253 }
   254 - (NSObject <AIEmoticonController> *)emoticonController{
   255     return emoticonController;
   256 }
   257 - (NSObject <AISoundController> *)soundController{
   258     return soundController;
   259 }
   260 - (NSObject <AIInterfaceController> *)interfaceController{
   261     return interfaceController;
   262 }
   263 - (NSObject <AIPreferenceController> *)preferenceController{
   264     return preferenceController;
   265 }
   266 - (NSObject <AIToolbarController> *)toolbarController{
   267     return toolbarController;
   268 }
   269 - (NSObject <AIDockController> *)dockController{
   270     return dockController;
   271 }
   272 - (NSObject <AIFileTransferController> *)fileTransferController{
   273     return fileTransferController;    
   274 }
   275 - (NSObject <AIContactAlertsController> *)contactAlertsController{
   276     return contactAlertsController;
   277 }
   278 - (NSObject <AIApplescriptabilityController> *)applescriptabilityController{
   279 	return applescriptabilityController;
   280 }
   281 - (NSObject <AIDebugController> *)debugController{
   282 	return debugController;
   283 }
   284 - (NSObject <AIStatusController> *)statusController{
   285     return statusController;
   286 }
   287 
   288 //Loaders --------------------------------------------------------------------------------------------------------
   289 #pragma mark Loaders
   290 
   291 - (AICoreComponentLoader *)componentLoader
   292 {
   293 	return componentLoader;
   294 }
   295 
   296 //Notifications --------------------------------------------------------------------------------------------------------
   297 #pragma mark Notifications
   298 //Return the shared Adium notification center
   299 - (NSNotificationCenter *)notificationCenter
   300 {
   301     if (notificationCenter == nil) {
   302         notificationCenter = [[NSNotificationCenter alloc] init];
   303     }
   304             
   305     return notificationCenter;
   306 }
   307 
   308 
   309 //Startup and Shutdown -------------------------------------------------------------------------------------------------
   310 #pragma mark Startup and Shutdown
   311 //Adium is almost done launching, init
   312 - (void)applicationWillFinishLaunching:(NSNotification *)notification
   313 {
   314     notificationCenter = nil;
   315     completedApplicationLoad = NO;
   316 	advancedPrefsName = nil;
   317 	prefsCategory = nil;
   318 	queuedURLEvents = nil;
   319 	
   320     //Ignore SIGPIPE, which is a harmless error signal
   321     //sent when write() or similar function calls fail due to a broken pipe in the network connection
   322     signal(SIGPIPE, SIG_IGN);
   323 	
   324 	[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self 
   325 												   andSelector:@selector(handleURLEvent:withReplyEvent:)
   326 												 forEventClass:kInternetEventClass
   327 													andEventID:kAEGetURL];
   328 }
   329 
   330 - (void)handleURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
   331 {
   332 	if (!completedApplicationLoad) {
   333 		if (!queuedURLEvents) {
   334 			queuedURLEvents = [[NSMutableArray alloc] init];
   335 		}
   336 		[queuedURLEvents addObject:[[event descriptorAtIndex:1] stringValue]];
   337 	} else {
   338 		[AdiumURLHandling handleURLEvent:[[event descriptorAtIndex:1] stringValue]];
   339 	}
   340 }
   341 
   342 //Adium has finished launching
   343 - (void)applicationDidFinishLaunching:(NSNotification *)notification
   344 {
   345 	//Begin loading and initing the components
   346 	loginController = [[AILoginController alloc] init];
   347     
   348     //Begin Login
   349     [loginController requestUserNotifyingTarget:self selector:@selector(completeLogin)];
   350 }
   351 
   352 //Forward a re-open message to the interface controller
   353 - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
   354 {
   355     return [interfaceController handleReopenWithVisibleWindows:flag];
   356 }
   357 
   358 //Called by the login controller when a user has been selected, continue logging in
   359 - (void)completeLogin
   360 {
   361 	NSAutoreleasePool *pool;
   362 
   363 	pool = [[NSAutoreleasePool alloc] init];
   364 
   365 	/* Init the controllers.
   366 	 * Menu and interface controllers are created by MainMenu.nib when it loads.
   367 	 */
   368 	preferenceController = [[AIPreferenceController alloc] init];
   369 	toolbarController = [[AIToolbarController alloc] init];
   370 
   371 #ifdef DEBUG_BUILD
   372 	debugController = [[ESDebugController alloc] init];
   373 #else
   374 	debugController = nil;
   375 #endif
   376 
   377 	contactAlertsController = [[ESContactAlertsController alloc] init];
   378 	soundController = [[AISoundController alloc] init];
   379 	emoticonController = [[AIEmoticonController alloc] init];
   380 	accountController = [[AIAccountController alloc] init];
   381 	contactController = [[AIContactController alloc] init];
   382 	chatController = [[AIChatController alloc] init];
   383 	contentController = [[AIContentController alloc] init];
   384 	dockController = [[AIDockController alloc] init];
   385 	fileTransferController = [[ESFileTransferController alloc] init];
   386 	applescriptabilityController = [[ESApplescriptabilityController alloc] init];
   387 	statusController = [[AIStatusController alloc] init];
   388 
   389 	//Finish setting up the preference controller before the components and plugins load so they can read prefs 
   390 	[preferenceController controllerDidLoad];
   391 	[debugController controllerDidLoad];
   392 	//Safety for when we remove previously included list xtras
   393 	[AIAppearancePreferences migrateOldListSettingsIfNeeded];
   394 	[pool release];
   395 
   396 	//Plugins and components should always init last, since they rely on everything else.
   397 	pool = [[NSAutoreleasePool alloc] init];
   398 	componentLoader = [[AICoreComponentLoader alloc] init];
   399 	pluginLoader = [[AICorePluginLoader alloc] init];
   400 	[pool release];
   401 
   402 	//Finish initing
   403 	pool = [[NSAutoreleasePool alloc] init];
   404 	[AdiumURLHandling registerURLTypes];		//Asks the user questions so must load after components
   405 	[menuController controllerDidLoad];			//Loaded by nib
   406 	[accountController controllerDidLoad];		//** Before contactController so accounts and services are available for contact creation
   407 	[contactController controllerDidLoad];		//** Before interfaceController so the contact list is available to the interface
   408 	[interfaceController controllerDidLoad];	//Loaded by nib
   409 	[pool release];
   410 
   411 	pool = [[NSAutoreleasePool alloc] init];
   412 	[toolbarController controllerDidLoad];
   413 	[contactAlertsController controllerDidLoad];
   414 	[soundController controllerDidLoad];
   415 	[emoticonController controllerDidLoad];
   416 	[chatController controllerDidLoad];
   417 	[contentController controllerDidLoad];
   418 	[dockController controllerDidLoad];
   419 	[fileTransferController controllerDidLoad];
   420 	[pool release];
   421 
   422 	pool = [[NSAutoreleasePool alloc] init];
   423 	[applescriptabilityController controllerDidLoad];
   424 	[statusController controllerDidLoad];
   425 
   426 	//Open the preferences if we were unable to because application:openFile: was called before we got here
   427 	[self openAppropriatePreferencesIfNeeded];
   428 
   429 	//If no accounts are setup, run the setup wizard
   430 	if (([[accountController accounts] count] == 0) || ALWAYS_RUN_SETUP_WIZARD) {
   431 		[AdiumSetupWizard runWizard];
   432 	}
   433 
   434 	//Process any delayed URL events 
   435 	if (queuedURLEvents) {
   436 		NSString *eventString = nil;
   437 		NSEnumerator *e  = [queuedURLEvents objectEnumerator];
   438 		while ((eventString = [e nextObject])) {
   439 			[AdiumURLHandling handleURLEvent:eventString];
   440 		}
   441 		[queuedURLEvents release]; queuedURLEvents = nil;
   442 	}
   443 	
   444 	//If we were asked to open a log at launch, do it now
   445 	if (queuedLogPathToShow) {
   446 		[[self notificationCenter] postNotificationName:AIShowLogAtPathNotification
   447 												 object:queuedLogPathToShow];
   448 		[queuedLogPathToShow release];
   449 	}
   450 	
   451 	completedApplicationLoad = YES;
   452 
   453 	[self configureHelp];
   454 	
   455 	[[NSDistributedNotificationCenter defaultCenter] addObserver:self
   456 														selector:@selector(systemTimeZoneDidChange:)
   457 															name:@"NSSystemTimeZoneDidChangeDistributedNotification"
   458 														  object:nil];
   459 
   460 	//Broadcast our presence
   461 	NSConnection *connection = [NSConnection defaultConnection];
   462 	[connection setRootObject:self];
   463 	[connection registerName:@"com.adiumX.adiumX"];
   464 
   465 	[[self notificationCenter] postNotificationName:AIApplicationDidFinishLoadingNotification object:nil];
   466 	[[NSDistributedNotificationCenter defaultCenter]  postNotificationName:AIApplicationDidFinishLoadingNotification object:nil];
   467 
   468 	[pool release];
   469 }
   470 
   471 //Give all the controllers a chance to close down
   472 - (void)applicationWillTerminate:(NSNotification *)notification
   473 {
   474 	//Take no action if we didn't complete the application load
   475 	if (!completedApplicationLoad) return;
   476 
   477 	[[self notificationCenter] postNotificationName:AIAppWillTerminateNotification object:nil];
   478 	
   479 	//Close the preference window before we shut down the plugins that compose it
   480 	[preferenceController closePreferenceWindow:nil];
   481 
   482     //Close the controllers in reverse order
   483 	[pluginLoader controllerWillClose]; 				//** First because plugins rely on all the controllers
   484 	[componentLoader controllerWillClose];				//** First because components rely on all the controllers
   485 	[statusController controllerWillClose];				//** Before accountController so account states are saved before being set to offline
   486     [chatController controllerWillClose];				//** Before interfaceController so chats can be correctly closed
   487 	[contactAlertsController controllerWillClose];
   488     [fileTransferController controllerWillClose];
   489     [dockController controllerWillClose];
   490     [interfaceController controllerWillClose];
   491     [contentController controllerWillClose];
   492     [contactController controllerWillClose];
   493     [accountController controllerWillClose];
   494 	[emoticonController controllerWillClose];
   495     [soundController controllerWillClose];
   496     [menuController controllerWillClose];
   497     [applescriptabilityController controllerWillClose];
   498 	[debugController controllerWillClose];
   499 	[toolbarController controllerWillClose];
   500     [preferenceController controllerWillClose];			//** Last since other controllers may want to write preferences as they close
   501 	
   502 	[self deleteTemporaryFiles];
   503 }
   504 
   505 - (void)deleteTemporaryFiles
   506 {
   507 	[[NSFileManager defaultManager] removeFilesInDirectory:[self cachesPath]
   508 												withPrefix:@"TEMP"
   509 											 movingToTrash:NO];
   510 }
   511 
   512 
   513 //Menu Item Hooks ------------------------------------------------------------------------------------------------------
   514 #pragma mark Menu Item Hooks
   515 //Show the about box
   516 - (IBAction)showAboutBox:(id)sender
   517 {
   518     [[LNAboutBoxController aboutBoxController] showWindow:nil];
   519 }
   520 
   521 - (IBAction)reportABug:(id)sender{
   522     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_REPORT_BUG_PAGE]];
   523 }
   524 - (IBAction)sendFeedback:(id)sender{
   525     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FEEDBACK_PAGE]];
   526 }
   527 - (IBAction)showForums:(id)sender{
   528     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FORUM_PAGE]];
   529 }
   530 - (IBAction)showXtras:(id)sender{
   531 	[[AIXtrasManager sharedManager] showXtras];
   532 }
   533 
   534 - (IBAction)contibutingToAdium:(id)sender
   535 {
   536 	[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://trac.adiumx.com/wiki/ContributingToAdium"]];
   537 }
   538 - (IBAction)donate:(id)sender
   539 {
   540 	[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&submit.x=57&submit.y=8&encrypted=-----BEGIN+PKCS7-----%0D%0AMIIHFgYJKoZIhvcNAQcEoIIHBzCCBwMCAQExggEwMIIBLAIBADCBlDCBjjELMAkG%0D%0AA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQw%0D%0AEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UE%0D%0AAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJ%0D%0AKoZIhvcNAQEBBQAEgYAFR5tF%2BRKUV3BS49vJraDG%2BIoWDoZMieUT%2FJJ1Fzjsr511%0D%0Au7hS1F2piJuHuqmm%2F0r8Kf8oaycOo74K3zLmUQ6T6hUS6%2Bh6lZAoIlhI3A1YmqIP%0D%0AdrdY%2FtfKRbWfolDumJ9Mdv%2FzJxPnpdQiTN5K1PMrPYE6GgPWE9WC4V9lqstSmTEL%0D%0AMAkGBSsOAwIaBQAwgZMGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIjtd%2BN9o4ZB6A%0D%0AcIbH8ZjOLmE35xBQ%2F93chtzIcRXHhIQJVpBRCkyJkdTD3libP3F7TgkrLij1DBxg%0D%0AfFlE0V%2FGTk29Ys%2FwsPO7hNs3YSNuSz0HT5F6sa8aXwFtMCE%2FgB1Ha4qdtYY%2BNETJ%0D%0AEETwNMLefjhaBfI%2BnRxl2K2gggOHMIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0B%0D%0AAQUFADCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3Vu%0D%0AdGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9j%0D%0AZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBh%0D%0AbC5jb20wHhcNMDQwMjEzMTAxMzE1WhcNMzUwMjEzMTAxMzE1WjCBjjELMAkGA1UE%0D%0ABhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYD%0D%0AVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQI%0D%0AbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wgZ8wDQYJKoZI%0D%0AhvcNAQEBBQADgY0AMIGJAoGBAMFHTt38RMxLXJyO2SmS%2BNdl72T7oKJ4u4uw%2B6aw%0D%0AntALWh03PewmIJuzbALScsTS4sZoS1fKciBGoh11gIfHzylvkdNe%2FhJl66%2FRGqrj%0D%0A5rFb08sAABNTzDTiqqNpJeBsYs%2Fc2aiGozptX2RlnBktH%2BSUNpAajW724Nv2Wvhi%0D%0Af6sFAgMBAAGjge4wgeswHQYDVR0OBBYEFJaffLvGbxe9WT9S1wob7BDWZJRrMIG7%0D%0ABgNVHSMEgbMwgbCAFJaffLvGbxe9WT9S1wob7BDWZJRroYGUpIGRMIGOMQswCQYD%0D%0AVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDAS%0D%0ABgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQD%0D%0AFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbYIBADAMBgNV%0D%0AHRMEBTADAQH%2FMA0GCSqGSIb3DQEBBQUAA4GBAIFfOlaagFrl71%2Bjq6OKidbWFSE%2B%0D%0AQ4FqROvdgIONth%2B8kSK%2F%2FY%2F4ihuE4Ymvzn5ceE3S%2FiBSQQMjyvb%2Bs2TWbQYDwcp1%0D%0A29OPIbD9epdr4tJOUNiSojw7BHwYRiPh58S1xGlFgHFXwrEBb3dgNbMUa%2Bu4qect%0D%0AsMAXpVHnD9wIyfmHMYIBmjCCAZYCAQEwgZQwgY4xCzAJBgNVBAYTAlVTMQswCQYD%0D%0AVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFs%0D%0AIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRww%0D%0AGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tAgEAMAkGBSsOAwIaBQCgXTAYBgkq%0D%0AhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0wNDAzMjUwNDQ0%0D%0AMzRaMCMGCSqGSIb3DQEJBDEWBBRzTAS6zk5cmMeC49IorY8CM%2BkX0TANBgkqhkiG%0D%0A9w0BAQEFAASBgBsyRfMv9mSyoYq00wIB7BmUHFGq5x%2Ffnr8M24XbKjhkyeULk2NC%0D%0As4jbCgaWNg6grvccJtjbvmDskMKt%2BdS%2BEAkeWwm1Zf%2F%2B5u1fMyb5vo1NNcRIs5oq%0D%0A7SvXiLTPRzVqzQdhVs7PoZG0i0RRIb0tMeo1IssZeB2GE5Nsg0D8PwpB%0D%0A-----END+PKCS7-----"]];
   541 }
   542 
   543 - (void)unreadQuitQuestion:(NSNumber *)number userInfo:(id)info
   544 {
   545 	AITextAndButtonsReturnCode result = [number intValue];
   546 	switch(result)
   547 	{
   548 		case AITextAndButtonsDefaultReturn:
   549 			//Quit
   550 			//Should we ask about File Transfers here?????
   551 			[NSApp terminate:nil];
   552 			break;
   553 		case AITextAndButtonsOtherReturn:
   554 			//Don't Ask Again
   555 			[[self preferenceController] setPreference:[NSNumber numberWithBool:YES]
   556 												 forKey:@"Suppress Quit Confirmation for Unread Messages"
   557 												  group:@"Confirmations"];
   558 			[NSApp terminate:nil];
   559 			break;
   560 		default:
   561 			//Cancel
   562 			break;
   563 	}
   564 }
   565 
   566 - (void)fileTransferQuitQuestion:(NSNumber *)number userInfo:(id)info
   567 {
   568 	AITextAndButtonsReturnCode result = [number intValue];
   569 	switch(result)
   570 	{
   571 		case AITextAndButtonsDefaultReturn:
   572 			//Quit
   573 			[NSApp terminate:nil];
   574 			break;
   575 		case AITextAndButtonsOtherReturn:
   576 			//Don't Ask Again
   577 			[[self preferenceController] setPreference:[NSNumber numberWithBool:YES]
   578 												 forKey:@"Suppress Quit Confirmation for File Transfers"
   579 												  group:@"Confirmations"];
   580 			[NSApp terminate:nil];
   581 			break;
   582 		default:
   583 			//Cancel
   584 			break;
   585 	}
   586 }
   587 
   588 //Last call to perform actions before the app shuffles off its mortal coil and joins the bleeding choir invisible
   589 - (IBAction)confirmQuit:(id)sender
   590 {
   591 	/* We may have received a message or begun a file transfer while the menu was open, if this is reached via a menu item.
   592 	 * Wait one last run loop before beginning to quit so that activity can be registered, since menus run in
   593 	 * a different run loop mode, NSEventTrackingRunLoopMode.
   594 	 */
   595 	[NSObject cancelPreviousPerformRequestsWithTarget:self
   596 											 selector:@selector(reallyConfirmQuit)
   597 											   object:nil];
   598 	[self performSelector:@selector(reallyConfirmQuit)
   599 			   withObject:nil
   600 			   afterDelay:0];
   601 }
   602 
   603 - (void)reallyConfirmQuit
   604 {
   605 	BOOL allowQuit = YES;
   606 	if (([chatController unviewedContentCount] > 0) &&
   607 		(![[preferenceController preferenceForKey:@"Suppress Quit Confirmation for Unread Messages"
   608 											group:@"Confirmations"] boolValue])) {
   609 		[[self interfaceController] displayQuestion:AILocalizedString(@"Confirm Quit", nil)
   610 									withDescription:AILocalizedString(@"You have unread messages.\nAre you sure you want to quit?", nil)
   611 									withWindowTitle:nil
   612 									  defaultButton:AILocalizedString(@"Quit", nil)
   613 									alternateButton:AILocalizedString(@"Cancel", nil)
   614 										otherButton:AILocalizedString(@"Don't ask again", nil)
   615 											 target:self
   616 										   selector:@selector(unreadQuitQuestion:userInfo:)
   617 										   userInfo:nil];
   618 		allowQuit = NO;
   619 	} 
   620 	
   621 	if (allowQuit &&
   622 		([fileTransferController activeTransferCount] > 0) &&
   623 		(![[preferenceController preferenceForKey:@"Suppress Quit Confirmation for File Transfers"
   624 											group:@"Confirmations"]  boolValue])) {
   625 		[[self interfaceController] displayQuestion:AILocalizedString(@"Confirm Quit", nil)
   626 									withDescription:AILocalizedString(@"You have file transfers in progress.\nAre you sure you want to quit?", nil)
   627 									withWindowTitle:nil
   628 									  defaultButton:AILocalizedString(@"Quit", nil)
   629 									alternateButton:AILocalizedString(@"Cancel", nil)
   630 										otherButton:AILocalizedString(@"Don't ask again", nil)
   631 											 target:self
   632 										   selector:@selector(fileTransferQuitQuestion:userInfo:)
   633 										   userInfo:nil];
   634 		allowQuit = NO;
   635 	}
   636 	
   637 	if (allowQuit) {
   638 		[NSApp terminate:nil];
   639 	}
   640 }
   641 
   642 //Other -------------------------------------------------------------------------------------------------------
   643 #pragma mark Other
   644 //If Adium was launched by double-clicking an associated file, we get this call after willFinishLaunching but before
   645 //didFinishLaunching
   646 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
   647 {
   648     NSString			*extension = [filename pathExtension];
   649     NSString			*destination = nil;
   650 	NSString			*errorMessage = nil;
   651     NSString			*fileDescription = nil, *prefsButton = nil;
   652 	BOOL				success = NO, requiresRestart = NO;
   653 	int					buttonPressed;
   654 	
   655 	if (([extension caseInsensitiveCompare:@"AdiumLog"] == NSOrderedSame) ||
   656 		([extension caseInsensitiveCompare:@"AdiumHtmlLog"] == NSOrderedSame) ||
   657 		([extension caseInsensitiveCompare:@"chatlog"] == NSOrderedSame)) {
   658 		if (completedApplicationLoad) {
   659 			//Request display of the log immediately if Adium is ready
   660 			[[self notificationCenter] postNotificationName:AIShowLogAtPathNotification
   661 													 object:filename];
   662 		} else {
   663 			//Queue the request until Adium is done launching if Adium is not ready
   664 			[queuedLogPathToShow release]; queuedLogPathToShow = [filename retain];
   665 		}
   666 		
   667 		//Don't continue to the xtras installation code. Return YES because we handled the open.
   668 		return YES;
   669 	}	
   670 	
   671 	/* Installation of Xtras below this point */
   672 
   673 	[prefsCategory release]; prefsCategory = nil;
   674     [advancedPrefsName release]; advancedPrefsName = nil;
   675 
   676     //Specify a file extension and a human-readable description of what the files of this type do
   677     if ([extension caseInsensitiveCompare:@"AdiumPlugin"] == NSOrderedSame) {
   678         destination = [AISearchPathForDirectoriesInDomains(AIPluginsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   679         //Plugins haven't been loaded yet if the application isn't done loading, so only request a restart if it has finished loading already 
   680         requiresRestart = completedApplicationLoad;
   681         fileDescription = AILocalizedString(@"Adium plugin",nil);
   682 
   683     } else if ([extension caseInsensitiveCompare:@"AdiumLibpurplePlugin"] == NSOrderedSame) {
   684         destination = [AISearchPathForDirectoriesInDomains(AIPluginsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   685         //Plugins haven't been loaded yet if the application isn't done loading, so only request a restart if it has finished loading already 
   686         requiresRestart = completedApplicationLoad;
   687         fileDescription = AILocalizedString(@"Adium plugin",nil);
   688 		extension = @"AdiumLibpurplePlugin";
   689 
   690 	} else if ([extension caseInsensitiveCompare:@"AdiumIcon"] == NSOrderedSame) {
   691 		destination = [AISearchPathForDirectoriesInDomains(AIDockIconsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   692         fileDescription = AILocalizedString(@"dock icon set",nil);
   693 		prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
   694 		prefsCategory = @"appearance";
   695 
   696 	} else if ([extension caseInsensitiveCompare:@"AdiumSoundset"] == NSOrderedSame) {
   697 		destination = [AISearchPathForDirectoriesInDomains(AISoundsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   698 		fileDescription = AILocalizedString(@"sound set",nil);
   699 		prefsButton = AILocalizedString(@"Open Event Prefs",nil);
   700 		prefsCategory = @"events";
   701 
   702 	} else if ([extension caseInsensitiveCompare:@"AdiumEmoticonset"] == NSOrderedSame) {
   703 		destination = [AISearchPathForDirectoriesInDomains(AIEmoticonsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   704 		fileDescription = AILocalizedString(@"emoticon set",nil);
   705 		prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
   706 		prefsCategory = @"appearance";
   707 		
   708 	} else if ([extension caseInsensitiveCompare:@"AdiumScripts"] == NSOrderedSame) {
   709 		destination = [AISearchPathForDirectoriesInDomains(AIScriptsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   710 		fileDescription = AILocalizedString(@"AppleScript set",nil);
   711 		
   712 	} else if ([extension caseInsensitiveCompare:@"AdiumMessageStyle"] == NSOrderedSame) {
   713 		destination = [AISearchPathForDirectoriesInDomains(AIMessageStylesDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   714 		fileDescription = AILocalizedString(@"message style",nil);
   715 		prefsButton = AILocalizedString(@"Open Message Prefs",nil);
   716 		prefsCategory = @"messages";
   717 	} else if ([extension caseInsensitiveCompare:@"ListLayout"] == NSOrderedSame) {
   718 		destination = [AISearchPathForDirectoriesInDomains(AIContactListDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   719 		fileDescription = AILocalizedString(@"contact list layout",nil);
   720 		prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
   721 		prefsCategory = @"appearance";
   722 		
   723 	} else if ([extension caseInsensitiveCompare:@"ListTheme"] == NSOrderedSame) {
   724 		destination = [AISearchPathForDirectoriesInDomains(AIContactListDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   725 		fileDescription = AILocalizedString(@"contact list theme",nil);
   726 		prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
   727 		prefsCategory = @"appearance";
   728 		
   729 	} else if ([extension caseInsensitiveCompare:@"AdiumServiceIcons"] == NSOrderedSame) {
   730 		destination = [AISearchPathForDirectoriesInDomains(AIServiceIconsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   731 		fileDescription = AILocalizedString(@"service icons",nil);
   732 		prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
   733 		prefsCategory = @"appearance";
   734 		
   735 	} else if ([extension caseInsensitiveCompare:@"AdiumStatusIcons"] == NSOrderedSame) {
   736 		NSString	*packName = [[filename lastPathComponent] stringByDeletingPathExtension];
   737 /*
   738  //Can't do this because the preferenceController isn't ready yet
   739  NSString	*defaultPackName = [[self preferenceController] defaultPreferenceForKey:@"Status Icon Pack"
   740 																			  group:@"Appearance"
   741 																			 object:nil];
   742 */
   743 		NSString	*defaultPackName = @"iBubble Status";
   744 
   745 		if (![packName isEqualToString:defaultPackName]) {
   746 			destination = [AISearchPathForDirectoriesInDomains(AIStatusIconsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
   747 			fileDescription = AILocalizedString(@"status icons",nil);
   748 			prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
   749 			prefsCategory = @"appearance";
   750 		} else {
   751 			errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ is the name of the default status icon pack; this pack therefore can not be installed.",nil),
   752 				packName];
   753 		}
   754 	}
   755 
   756     if (destination) {
   757         NSString    *destinationFilePath = [destination stringByAppendingPathComponent:[filename lastPathComponent]];
   758         
   759         NSString	*alertTitle = nil;
   760         NSString	*alertMsg = nil;
   761 		NSString	*format;
   762 		
   763 		if ([filename isEqualToString:destinationFilePath]) {
   764 			// Don't copy the file if it's already in the right place!!
   765 			alertTitle= AILocalizedString(@"Installation Successful","Title of installation successful window");
   766 			
   767 			format = AILocalizedString(@"Installation of the %@ %@ was successful because the file was already in the correct location.",
   768 									   "Installation introduction, like 'Installation of the message style Fiat was successful...'.");
   769 			
   770 			alertMsg = [NSString stringWithFormat:format,
   771 				fileDescription,
   772 				[[filename lastPathComponent] stringByDeletingPathExtension]];
   773 			
   774 		} else {
   775 			//Trash the old file if one exists (since we know it isn't ourself)
   776 			[[NSFileManager defaultManager] trashFileAtPath:destinationFilePath];
   777 			
   778 			//Ensure the directory exists
   779 			[[NSFileManager defaultManager] createDirectoryAtPath:destination attributes:nil];
   780 			
   781 			//Perform the copy and display an alert informing the user of its success or failure
   782 			if ([[NSFileManager defaultManager] copyPath:filename 
   783 												  toPath:destinationFilePath 
   784 												 handler:nil]) {
   785 				
   786 				alertTitle = AILocalizedString(@"Installation Successful","Title of installation successful window");
   787 				alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was successful.",
   788 																		   "Installation sentence, like 'Installation of the message style Fiat was successful.'."),
   789 					fileDescription,
   790 					[[filename lastPathComponent] stringByDeletingPathExtension]];
   791 				
   792 				if (requiresRestart) {
   793 					alertMsg = [alertMsg stringByAppendingString:AILocalizedString(@" Please restart Adium.",nil)];
   794 				}
   795 				
   796 				success = YES;
   797 			} else {
   798 				alertTitle = AILocalizedString(@"Installation Failed","Title of installation failed window");
   799 				alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was unsuccessful.",
   800 																		"Installation failed sentence, like 'Installation of the message style Fiat was unsuccessful.'."),
   801 					fileDescription,
   802 					[[filename lastPathComponent] stringByDeletingPathExtension]];
   803 			}
   804 		}
   805 		
   806 		[[self notificationCenter] postNotificationName:AIXtrasDidChangeNotification
   807 												 object:[[filename lastPathComponent] pathExtension]];
   808 		
   809         buttonPressed = NSRunInformationalAlertPanel(alertTitle,alertMsg,nil,prefsButton,nil);
   810 		
   811 		// User clicked the "open prefs" button
   812 		if (buttonPressed == NSAlertAlternateReturn) {
   813 			//If we're done loading the app, open the prefs now; if not, it'll be done once the load is finished
   814 			//so the controllers and plugins have had a chance to initialize
   815 			if (completedApplicationLoad) {
   816 				[self openAppropriatePreferencesIfNeeded];
   817 			}
   818 		} else {
   819 			//If the user didn't press the "open prefs" button, clear the pref opening information
   820 			[prefsCategory release]; prefsCategory = nil;
   821 			[advancedPrefsName release]; advancedPrefsName = nil;
   822 		}
   823 		
   824     } else {
   825 		if (!errorMessage) {
   826 			errorMessage = AILocalizedString(@"An error occurred while installing the X(tra).",nil);
   827 		}
   828 		
   829 		NSRunAlertPanel(AILocalizedString(@"Installation Failed","Title of installation failed window"),
   830 						errorMessage,
   831 						nil,nil,nil);
   832 	}
   833 
   834     return success;
   835 }
   836 
   837 - (BOOL)application:(NSApplication *)theApplication openTempFile:(NSString *)filename
   838 {
   839 	BOOL success;
   840 	
   841 	success = [self application:theApplication openFile:filename];
   842 	[[NSFileManager defaultManager] removeFileAtPath:filename handler:nil];
   843 	
   844 	return success;
   845 }
   846 
   847 - (void)openAppropriatePreferencesIfNeeded
   848 {
   849 	if (prefsCategory) {
   850 		if ([prefsCategory isEqualToString:@"advanced"]) {
   851 			[preferenceController openPreferencesToAdvancedPane:advancedPrefsName];
   852 		} else {
   853 			[preferenceController openPreferencesToCategoryWithIdentifier:prefsCategory];
   854 		}
   855 		
   856 		[prefsCategory release]; prefsCategory = nil;
   857 	}
   858 }
   859 
   860 /*!
   861  * @brief Returns the location of Adium's preference folder
   862  * 
   863  * This may be specified in our bundle's info dictionary keyed as PORTABLE_ADIUM_KEY
   864  * or, by default, be within the system's 'application support' directory.
   865  */
   866 - (NSString *)applicationSupportDirectory
   867 {
   868 	//Path to the preferences folder
   869 	static NSString *_preferencesFolderPath = nil;
   870 	
   871     //Determine the preferences path if neccessary
   872 	if (!_preferencesFolderPath) {
   873 		_preferencesFolderPath = [[[[[NSBundle mainBundle] infoDictionary] objectForKey:PORTABLE_ADIUM_KEY] stringByExpandingTildeInPath] retain];
   874 		if (!_preferencesFolderPath)
   875 			_preferencesFolderPath = [[[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Application Support"] stringByAppendingPathComponent:@"Adium 2.0"] retain];
   876 	}
   877 	
   878 	return _preferencesFolderPath;
   879 }
   880 
   881 /*!
   882  * @brief Create a resource folder in the Library/Application\ Support/Adium\ 2.0 folder.
   883  *
   884  * Pass it the name of the folder (e.g. @"Scripts").
   885  * If it is found to already in a library folder, return that pathname, using the same order of preference as
   886  * -[AIAdium resourcePathsForName:]. Otherwise, create it in the user library and return the pathname to it.
   887  */
   888 - (NSString *)createResourcePathForName:(NSString *)name
   889 {
   890     NSString		*targetPath;    //This is the subfolder for the user domain (i.e. ~/L/AS/Adium\ 2.0).
   891     NSFileManager	*defaultManager;
   892     NSArray			*existingResourcePaths;
   893 
   894 	defaultManager = [NSFileManager defaultManager];
   895 	existingResourcePaths = [self resourcePathsForName:name];
   896 	targetPath = [[self applicationSupportDirectory] stringByAppendingPathComponent:name];	
   897 	
   898     /*
   899 	 If the targetPath doesn't exist, create it, as this method was called to ensure that it exists
   900 	 for creating files in the user domain.
   901 	 */
   902     if ([existingResourcePaths indexOfObject:targetPath] == NSNotFound) {
   903         if (![defaultManager createDirectoryAtPath:targetPath attributes:nil]) {
   904 			BOOL error;
   905 			
   906 			//If the directory could not be created, there may be a file in the way. Death to file.
   907 			error = ![defaultManager trashFileAtPath:targetPath];
   908 
   909 			if (!error) error = ![defaultManager createDirectoryAtPath:targetPath attributes:nil];
   910 
   911 			if (error) {
   912 				targetPath = nil;
   913 				
   914 				int result;
   915 				result = NSRunCriticalAlertPanel([NSString stringWithFormat:AILocalizedString(@"Could not create the %@ folder.",nil), name],
   916 												 AILocalizedString(@"Try running Repair Permissions from Disk Utility.",nil),
   917 												 AILocalizedString(@"OK",nil), 
   918 												 AILocalizedString(@"Launch Disk Utility",nil), 
   919 												 nil);
   920 				if (result == NSAlertAlternateReturn) {
   921 					[[NSWorkspace sharedWorkspace] launchApplication:@"Disk Utility"];
   922 				}
   923 			}
   924 		}
   925     } else {
   926         targetPath = [existingResourcePaths objectAtIndex:0];
   927     }
   928 
   929     return targetPath;
   930 }
   931 
   932 /*!
   933  * @brief Return zero or more resource pathnames to an filename 
   934  *
   935  * Searches in the Application Support folders and the Resources/ folder of the Adium.app bundle.
   936  * Only those pathnames that exist are returned.  The Adium bundle's resource path will be the last item in the array,
   937  * so precedence is given to the user and system Application Support folders.
   938  * 
   939  * Pass nil to receive an array of paths to existing Adium Application Support folders (plus the Resouces folder).
   940  *
   941  * Example: If you call[adium resourcePathsForName:@"Scripts"], and there's a
   942  * Scripts folder in ~/Library/Application Support/Adium\ 2.0 and in /Library/Application Support/Adium\ 2.0, but not
   943  * in /System/Library/ApplicationSupport/Adium\ 2.0 or /Network/Library/Application Support/Adium\ 2.0.
   944  * The array you get back will be { @"/Users/username/Library/Application Support/Adium 2.0/Scripts",
   945  * @"/Library/Application Support/Adium 2.0/Scripts" }.
   946  *
   947  * @param name The full name (including extension as appropriate) of the resource for which to search
   948  */
   949 - (NSArray *)resourcePathsForName:(NSString *)name
   950 {
   951 	NSArray			*librarySearchPaths;
   952 	NSEnumerator	*searchPathEnumerator;
   953 	NSString		*adiumFolderName, *path;
   954 	NSMutableArray  *pathArray = [NSMutableArray arrayWithCapacity:4];
   955 	NSFileManager	*defaultManager = [NSFileManager defaultManager];
   956 	BOOL			isDir;
   957 			
   958 	adiumFolderName = (name ?
   959 					   [[@"Application Support" stringByAppendingPathComponent:@"Adium 2.0"] stringByAppendingPathComponent:name] :
   960 					   [@"Application Support" stringByAppendingPathComponent:@"Adium 2.0"]);
   961 
   962 	//Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
   963 	librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
   964 	searchPathEnumerator = [librarySearchPaths objectEnumerator];
   965 
   966 	//Copy each discovered path into the pathArray after adding our subfolder path
   967 	while ((path = [searchPathEnumerator nextObject])) {
   968 		NSString	*fullPath;
   969 		
   970 		fullPath = [path stringByAppendingPathComponent:adiumFolderName];
   971 		if (([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
   972 			(isDir)) {
   973 			
   974 			[pathArray addObject:fullPath];
   975 		}
   976 	}
   977 	
   978 	/* Check our application support directory directly. It may have been covered by the NSSearchPathForDirectoriesInDomains() search,
   979 	 * or it may be distinct via the Portable Adium preference.
   980 	 */
   981 	path = (name ?
   982 			[[self applicationSupportDirectory] stringByAppendingPathComponent:name] :
   983 			[self applicationSupportDirectory]);
   984 	if (![pathArray containsObject:path] &&
   985 		([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
   986 		(isDir)) {
   987 		//Our application support directory should always be first
   988 		if ([pathArray count]) {
   989 			[pathArray insertObject:path atIndex:0];
   990 		} else {
   991 			[pathArray addObject:path];			
   992 		}
   993 	}
   994 
   995 	//Add the path to the resource in Adium's bundle
   996 	if (name) {
   997 		path = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name] stringByExpandingTildeInPath];
   998 		if (([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
   999 		   (isDir)) {
  1000 			[pathArray addObject:path];
  1001 		}
  1002 	}
  1003     
  1004 	return pathArray;
  1005 }
  1006 
  1007 
  1008 /*!
  1009  * @brief Returns an array of the paths to all of the resources for a given name, filtering out those without a certain extension
  1010  * @param name The full name (including extension as appropriate) of the resource for which to search
  1011  * @param extensions The extension(s) of the resources for which to search, either an NSString or an NSArray
  1012  */
  1013 - (NSArray *)allResourcesForName:(NSString *)name withExtensions:(id)extensions {
  1014 	NSMutableArray *resources = [NSMutableArray array];
  1015 	NSEnumerator *pathEnumerator;
  1016 	NSEnumerator *resourceEnumerator;
  1017 	NSString *resourceDir;
  1018 	NSString *resourcePath;
  1019 	BOOL extensionsArray = [extensions isKindOfClass:[NSArray class]];
  1020 	NSEnumerator *extensionsEnumerator;
  1021 	NSString *extension;
  1022 	
  1023 	// Get every path that can contain these resources
  1024 	pathEnumerator = [[self resourcePathsForName:name] objectEnumerator];
  1025 	
  1026 	while ((resourceDir = [pathEnumerator nextObject])) {
  1027 		resourceEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:resourceDir] objectEnumerator];
  1028 		
  1029 		while ((resourcePath = [resourceEnumerator nextObject])) {
  1030 			// Add each resource to the array
  1031 			if (extensionsArray) {
  1032 				extensionsEnumerator = [extensions objectEnumerator];
  1033 				while ((extension = [extensionsEnumerator nextObject])) {
  1034 					if ([[resourcePath pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame)
  1035 						[resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
  1036 				}
  1037 			}
  1038 			else {
  1039 				if ([[resourcePath pathExtension] caseInsensitiveCompare:extensions] == NSOrderedSame)
  1040 					[resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
  1041 			}
  1042 		}
  1043 	}
  1044 
  1045 	return resources;
  1046 }
  1047 
  1048 /*!
  1049  * @brief Return the path to be used for caching files for this user.
  1050  *
  1051  * @result A cached, tilde-expanded full path.
  1052  */
  1053 - (NSString *)cachesPath
  1054 {
  1055 	static NSString *cachesPath = nil;
  1056 
  1057 	if (!cachesPath) {
  1058 		NSString		*generalAdiumCachesPath;
  1059 		NSFileManager	*defaultManager = [NSFileManager defaultManager];
  1060 
  1061 		generalAdiumCachesPath = [[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:@"Adium"];
  1062 		cachesPath = [[generalAdiumCachesPath stringByAppendingPathComponent:[[self loginController] currentUser]] retain];
  1063 
  1064 		//Ensure our cache path exists
  1065 		if ([defaultManager createDirectoriesForPath:cachesPath]) {
  1066 			//If we have to make directories, try to move old cache files into the new directory
  1067 			NSEnumerator	*enumerator;
  1068 			NSString		*filename;
  1069 			BOOL			isDir;
  1070 
  1071 			enumerator = [[defaultManager directoryContentsAtPath:generalAdiumCachesPath] objectEnumerator];
  1072 			while ((filename = [enumerator nextObject])) {
  1073 				NSString	*fullPath = [generalAdiumCachesPath stringByAppendingPathComponent:filename];
  1074 				
  1075 				if (([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
  1076 				   (!isDir)) {
  1077 					[defaultManager movePath:fullPath
  1078 									  toPath:[cachesPath stringByAppendingPathComponent:filename]
  1079 									 handler:nil];
  1080 				}
  1081 			}
  1082 		}
  1083 	}
  1084 	
  1085 	return cachesPath;
  1086 }
  1087 
  1088 - (NSString *)pathOfPackWithName:(NSString *)name extension:(NSString *)extension resourceFolderName:(NSString *)folderName
  1089 {
  1090 	NSFileManager	*fileManager = [NSFileManager defaultManager];
  1091     NSString		*packFileName = [name stringByAppendingPathExtension:extension];
  1092     NSEnumerator	*enumerator = [[self resourcePathsForName:folderName] objectEnumerator];
  1093     NSString		*resourcePath;
  1094 
  1095 	//Search all our resource paths for the requested pack
  1096     while ((resourcePath = [enumerator nextObject])) {
  1097 		NSString *packPath = [resourcePath stringByAppendingPathComponent:packFileName];
  1098 		if ([fileManager fileExistsAtPath:packPath]) return [packPath stringByExpandingTildeInPath];
  1099 	}
  1100 
  1101     return nil;	
  1102 }
  1103 
  1104 - (void)systemTimeZoneDidChange:(NSNotification *)inNotification
  1105 {
  1106 	[NSTimeZone resetSystemTimeZone];
  1107 }
  1108 
  1109 - (NSApplication *)application
  1110 {
  1111 	return [NSApplication sharedApplication];
  1112 }
  1113 
  1114 #pragma mark Scripting
  1115 - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key {
  1116 	BOOL handleKey = NO;
  1117 	
  1118 	if ([key isEqualToString:@"applescriptabilityController"] || 
  1119 	   [key isEqualToString:@"interfaceController"] ) {
  1120 		handleKey = YES;
  1121 		
  1122 	}
  1123 	
  1124 	return handleKey;
  1125 }
  1126 
  1127 #pragma mark Help
  1128 - (void)configureHelp
  1129 {
  1130 	CFBundleRef myApplicationBundle;
  1131 	CFURLRef myBundleURL;
  1132 	FSRef myBundleRef;
  1133 
  1134 	if ((myApplicationBundle = CFBundleGetMainBundle())) {
  1135 		myBundleURL = CFBundleCopyBundleURL(myApplicationBundle);
  1136 
  1137 		if (CFURLGetFSRef(myBundleURL, &myBundleRef)) {
  1138 			AHRegisterHelpBook(&myBundleRef);
  1139 		}
  1140 	}
  1141 }
  1142 
  1143 #pragma mark Sparkle Delegate Methods
  1144 
  1145 #define BETA_UPDATE_DICT [NSDictionary dictionaryWithObjectsAndKeys:@"type", @"key", @"Update Type", @"visibleKey", @"beta", @"value", @"Beta or Release Versions", @"visibleValue", nil]
  1146 #define RELEASE_UPDATE_DICT [NSDictionary dictionaryWithObjectsAndKeys:@"type", @"key", @"Update Type", @"visibleKey", @"release", @"value", @"Release Versions Only", @"visibleValue", nil]
  1147 
  1148 #ifdef BETA_RELEASE
  1149 //For a beta release, always use the beta appcast
  1150 #define UPDATE_TYPE_DICT BETA_UPDATE_DICT
  1151 #else
  1152 //For a release, use the beta appcast if AIAlwaysUpdateToBetas is enabled; otherwise, use the release appcast
  1153 #define UPDATE_TYPE_DICT ([[NSUserDefaults standardUserDefaults] boolForKey:@"AIAlwaysUpdateToBetas"] ? BETA_UPDATE_DICT : RELEASE_UPDATE_DICT)
  1154 #endif
  1155 
  1156 //The first generation ended with 1.0.5 and 1.1. Our Sparkle Plus up to that point had a bug that left it unable to properly handle the sparkle:minimumSystemVersion element.
  1157 //The second generation began with 1.0.6 and 1.1.1, with a Sparkle Plus that can handle that element.
  1158 #define UPDATE_GENERATION_DICT [NSDictionary dictionaryWithObjectsAndKeys:@"generation", @"key", @"Appcast generation number", @"visibleKey", @"2", @"value", @"2", @"visibleValue", nil]
  1159 
  1160 /* This method gives the delegate the opportunity to customize the information that will
  1161  * be included with update checks.  Add or remove items from the dictionary as desired.
  1162  * Each entry in profileInfo is an NSDictionary with the following keys:
  1163  *		key: 		The key to be used  when reporting data to the server
  1164  *		visibleKey:	Alternate version of key to be used in UI displays of profile information
  1165  *		value:		Value to be used when reporting data to the server
  1166  *		visibleValue:	Alternate version of value to be used in UI displays of profile information.
  1167  */
  1168 - (NSMutableArray *)updaterCustomizeProfileInfo:(NSMutableArray *)profileInfo
  1169 {
  1170 	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  1171 	
  1172 	//If we're not sending profile information, return just the type of update we're looking for
  1173  	if ([[defaults objectForKey:SUSendProfileInfoKey] boolValue]) {
  1174  		int now = [[NSCalendarDate date] dayOfCommonEra];
  1175  
  1176  		if (abs([defaults integerForKey:@"AILastSubmittedProfileDate2"] - now) >= 7) {
  1177  			[defaults setInteger:now forKey:@"AILastSubmittedProfileDate2"];
  1178  			
  1179  			NSString *value = ([defaults boolForKey:@"AIHasSentSparkleProfileInfo"]) ? @"no" : @"yes";
  1180  			
  1181  			NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
  1182  				@"FirstSubmission", @"key", 
  1183  				@"First Time Submitting Profile Information", @"visibleKey",
  1184  				value, @"value",
  1185  				value, @"visibleValue",
  1186  				nil];
  1187  			
  1188  			[profileInfo addObject:entry];
  1189  			
  1190  			[defaults setBool:YES forKey:@"AIHasSentSparkleProfileInfo"];
  1191  			
  1192  			/*************** Include info about what IM services are used ************/
  1193  			NSMutableString *accountInfo = [NSMutableString string];
  1194  			NSCountedSet *condensedAccountInfo = [NSCountedSet set];
  1195  			NSEnumerator *accountEnu = [[[self accountController] accounts] objectEnumerator];
  1196  			AIAccount *account = nil;
  1197  			while ((account = [accountEnu nextObject])) {
  1198  				NSString *serviceID = [account serviceID];
  1199  				[accountInfo appendFormat:@"%@, ", serviceID];
  1200  				if([serviceID isEqualToString:@"Yahoo! Japan"]) serviceID = @"YJ";
  1201  				[condensedAccountInfo addObject:[NSString stringWithFormat:@"%@", [serviceID substringToIndex:2]]]; 
  1202  			}
  1203  			
  1204  			NSMutableString *accountInfoString = [NSMutableString string];
  1205  			NSEnumerator *infoEnu = [[[condensedAccountInfo allObjects] sortedArrayUsingSelector:@selector(compare:)] objectEnumerator];
  1206  			while ((value = [infoEnu nextObject]))
  1207  				[accountInfoString appendFormat:@"%@%d", value, [condensedAccountInfo countForObject:value]];
  1208  			
  1209  			entry = [NSDictionary dictionaryWithObjectsAndKeys:
  1210  				@"IMServices", @"key", 
  1211  				@"IM Services Used", @"visibleKey",
  1212  				accountInfoString, @"value",
  1213  				accountInfo, @"visibleValue",
  1214  				nil];
  1215  			[profileInfo addObject:entry];
  1216  		}
  1217  	}
  1218  
  1219  	[profileInfo addObject:UPDATE_GENERATION_DICT];
  1220  	[profileInfo addObject:UPDATE_TYPE_DICT];
  1221  	return profileInfo;
  1222 }
  1223 
  1224 - (NSComparisonResult) compareVersion:(NSString *)newVersion toVersion:(NSString *)currentVersion
  1225 {
  1226 	//Allow updating from betas to anything, and anything to non-betas
  1227 	//Careful! a15 is fine, but A15 is not, because it would hit the A in Adium.
  1228 	NSCharacterSet *guardCharacters = [NSCharacterSet characterSetWithCharactersInString:@"abBrcRC"];
  1229 	if([currentVersion rangeOfCharacterFromSet:guardCharacters].location != NSNotFound || !([newVersion rangeOfCharacterFromSet:guardCharacters].location != NSNotFound))
  1230 		return AICustomVersionComparison(newVersion, currentVersion); //handles rc > b > a properly
  1231 	else 
  1232 		return NSOrderedSame;
  1233 }
  1234 
  1235 @end