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).
2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
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>
52 #import <Carbon/Carbon.h>
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"
59 //Portable Adium prefs key
60 #define PORTABLE_ADIUM_KEY @"Preference Folder Location"
62 #define ALWAYS_RUN_SETUP_WIZARD FALSE
64 static NSString *prefsCategory;
72 // The version comparison code here is courtesy of Kevin Ballard, adapted from MacPAD. Thanks, Kevin!
74 int AIGetCharType(NSString *character)
76 if ([character isEqualToString:@"."]) {
78 } else if ([character isEqualToString:@"0"] || [character intValue] != 0) {
85 NSArray *AISplitVersionString(NSString *version)
89 int i, n, oldType, newType;
90 NSMutableArray *parts = [NSMutableArray array];
91 if ([version length] == 0) {
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];
106 [s setString:character];
108 // Add character to string and continue
109 [s appendString:character];
114 // Add the last part onto the array
115 [parts addObject:[NSString stringWithString:s]];
119 //newVersion, currentVersion
120 NSComparisonResult AICustomVersionComparison(NSString *versionA, NSString *versionB)
122 NSArray *partsA = AISplitVersionString(versionA);
123 NSArray *partsB = AISplitVersionString(versionB);
125 NSString *partA, *partB;
126 int i, n, typeA, typeB, intA, intB;
128 n = MIN([partsA count], [partsB count]);
129 for (i = 0; i < n; ++i) {
130 partA = [partsA objectAtIndex:i];
131 partB = [partsB objectAtIndex:i];
133 typeA = AIGetCharType(partA);
134 typeB = AIGetCharType(partB);
137 if (typeA == typeB) {
138 // Same type; we can compare
139 if (typeA == kNumberType) {
140 intA = [partA intValue];
141 intB = [partB intValue];
143 return NSOrderedAscending;
144 } else if (intA < intB) {
145 return NSOrderedDescending;
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;
165 // Not the same type? Now we have to do some validity checking
166 if (typeA != kStringType && typeB == kStringType) {
168 return NSOrderedAscending;
169 } else if (typeA == kStringType && typeB != kStringType) {
171 return NSOrderedDescending;
173 // One is a number and the other is a period. The period is invalid
174 if (typeA == kNumberType) {
175 return NSOrderedAscending;
177 return NSOrderedDescending;
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;
190 if ([partsA count] > [partsB count]) {
191 missingPart = [partsA objectAtIndex:n];
192 shorterResult = NSOrderedDescending;
193 largerResult = NSOrderedAscending;
195 missingPart = [partsB objectAtIndex:n];
196 shorterResult = NSOrderedAscending;
197 largerResult = NSOrderedDescending;
200 missingType = AIGetCharType(missingPart);
202 if (missingType == kStringType) {
203 // It's a string. Shorter version wins
204 return shorterResult;
206 // It's a number/period. Larger version wins
211 // The 2 strings are identical
212 return NSOrderedSame;
215 @interface AIAdium (PRIVATE)
216 - (void)completeLogin;
217 - (void)openAppropriatePreferencesIfNeeded;
218 - (void)configureHelp;
219 - (void)deleteTemporaryFiles;
222 @implementation AIAdium
227 if ((self = [super init])) {
228 [AIObject _setSharedAdiumInstance:self];
234 //Core Controllers -----------------------------------------------------------------------------------------------------
235 #pragma mark Core Controllers
236 - (NSObject <AILoginController> *)loginController{
237 return loginController;
239 - (NSObject <AIMenuController> *)menuController{
240 return menuController;
242 - (NSObject <AIAccountController> *)accountController{
243 return accountController;
245 - (NSObject <AIChatController> *)chatController{
246 return chatController;
248 - (NSObject <AIContentController> *)contentController{
249 return contentController;
251 - (NSObject <AIContactController> *)contactController{
252 return contactController;
254 - (NSObject <AIEmoticonController> *)emoticonController{
255 return emoticonController;
257 - (NSObject <AISoundController> *)soundController{
258 return soundController;
260 - (NSObject <AIInterfaceController> *)interfaceController{
261 return interfaceController;
263 - (NSObject <AIPreferenceController> *)preferenceController{
264 return preferenceController;
266 - (NSObject <AIToolbarController> *)toolbarController{
267 return toolbarController;
269 - (NSObject <AIDockController> *)dockController{
270 return dockController;
272 - (NSObject <AIFileTransferController> *)fileTransferController{
273 return fileTransferController;
275 - (NSObject <AIContactAlertsController> *)contactAlertsController{
276 return contactAlertsController;
278 - (NSObject <AIApplescriptabilityController> *)applescriptabilityController{
279 return applescriptabilityController;
281 - (NSObject <AIDebugController> *)debugController{
282 return debugController;
284 - (NSObject <AIStatusController> *)statusController{
285 return statusController;
288 //Loaders --------------------------------------------------------------------------------------------------------
291 - (AICoreComponentLoader *)componentLoader
293 return componentLoader;
296 //Notifications --------------------------------------------------------------------------------------------------------
297 #pragma mark Notifications
298 //Return the shared Adium notification center
299 - (NSNotificationCenter *)notificationCenter
301 if (notificationCenter == nil) {
302 notificationCenter = [[NSNotificationCenter alloc] init];
305 return notificationCenter;
309 //Startup and Shutdown -------------------------------------------------------------------------------------------------
310 #pragma mark Startup and Shutdown
311 //Adium is almost done launching, init
312 - (void)applicationWillFinishLaunching:(NSNotification *)notification
314 notificationCenter = nil;
315 completedApplicationLoad = NO;
316 advancedPrefsName = nil;
318 queuedURLEvents = nil;
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);
324 [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
325 andSelector:@selector(handleURLEvent:withReplyEvent:)
326 forEventClass:kInternetEventClass
327 andEventID:kAEGetURL];
330 - (void)handleURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
332 if (!completedApplicationLoad) {
333 if (!queuedURLEvents) {
334 queuedURLEvents = [[NSMutableArray alloc] init];
336 [queuedURLEvents addObject:[[event descriptorAtIndex:1] stringValue]];
338 [AdiumURLHandling handleURLEvent:[[event descriptorAtIndex:1] stringValue]];
342 //Adium has finished launching
343 - (void)applicationDidFinishLaunching:(NSNotification *)notification
345 //Begin loading and initing the components
346 loginController = [[AILoginController alloc] init];
349 [loginController requestUserNotifyingTarget:self selector:@selector(completeLogin)];
352 //Forward a re-open message to the interface controller
353 - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
355 return [interfaceController handleReopenWithVisibleWindows:flag];
358 //Called by the login controller when a user has been selected, continue logging in
359 - (void)completeLogin
361 NSAutoreleasePool *pool;
363 pool = [[NSAutoreleasePool alloc] init];
365 /* Init the controllers.
366 * Menu and interface controllers are created by MainMenu.nib when it loads.
368 preferenceController = [[AIPreferenceController alloc] init];
369 toolbarController = [[AIToolbarController alloc] init];
372 debugController = [[ESDebugController alloc] init];
374 debugController = nil;
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];
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];
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];
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
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];
422 pool = [[NSAutoreleasePool alloc] init];
423 [applescriptabilityController controllerDidLoad];
424 [statusController controllerDidLoad];
426 //Open the preferences if we were unable to because application:openFile: was called before we got here
427 [self openAppropriatePreferencesIfNeeded];
429 //If no accounts are setup, run the setup wizard
430 if (([[accountController accounts] count] == 0) || ALWAYS_RUN_SETUP_WIZARD) {
431 [AdiumSetupWizard runWizard];
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];
441 [queuedURLEvents release]; queuedURLEvents = nil;
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];
451 completedApplicationLoad = YES;
453 [self configureHelp];
455 [[NSDistributedNotificationCenter defaultCenter] addObserver:self
456 selector:@selector(systemTimeZoneDidChange:)
457 name:@"NSSystemTimeZoneDidChangeDistributedNotification"
460 //Broadcast our presence
461 NSConnection *connection = [NSConnection defaultConnection];
462 [connection setRootObject:self];
463 [connection registerName:@"com.adiumX.adiumX"];
465 [[self notificationCenter] postNotificationName:AIApplicationDidFinishLoadingNotification object:nil];
466 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:AIApplicationDidFinishLoadingNotification object:nil];
471 //Give all the controllers a chance to close down
472 - (void)applicationWillTerminate:(NSNotification *)notification
474 //Take no action if we didn't complete the application load
475 if (!completedApplicationLoad) return;
477 [[self notificationCenter] postNotificationName:AIAppWillTerminateNotification object:nil];
479 //Close the preference window before we shut down the plugins that compose it
480 [preferenceController closePreferenceWindow:nil];
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
502 [self deleteTemporaryFiles];
505 - (void)deleteTemporaryFiles
507 [[NSFileManager defaultManager] removeFilesInDirectory:[self cachesPath]
513 //Menu Item Hooks ------------------------------------------------------------------------------------------------------
514 #pragma mark Menu Item Hooks
516 - (IBAction)showAboutBox:(id)sender
518 [[LNAboutBoxController aboutBoxController] showWindow:nil];
521 - (IBAction)reportABug:(id)sender{
522 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_REPORT_BUG_PAGE]];
524 - (IBAction)sendFeedback:(id)sender{
525 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FEEDBACK_PAGE]];
527 - (IBAction)showForums:(id)sender{
528 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FORUM_PAGE]];
530 - (IBAction)showXtras:(id)sender{
531 [[AIXtrasManager sharedManager] showXtras];
534 - (IBAction)contibutingToAdium:(id)sender
536 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://trac.adiumx.com/wiki/ContributingToAdium"]];
538 - (IBAction)donate:(id)sender
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-----"]];
543 - (void)unreadQuitQuestion:(NSNumber *)number userInfo:(id)info
545 AITextAndButtonsReturnCode result = [number intValue];
548 case AITextAndButtonsDefaultReturn:
550 //Should we ask about File Transfers here?????
551 [NSApp terminate:nil];
553 case AITextAndButtonsOtherReturn:
555 [[self preferenceController] setPreference:[NSNumber numberWithBool:YES]
556 forKey:@"Suppress Quit Confirmation for Unread Messages"
557 group:@"Confirmations"];
558 [NSApp terminate:nil];
566 - (void)fileTransferQuitQuestion:(NSNumber *)number userInfo:(id)info
568 AITextAndButtonsReturnCode result = [number intValue];
571 case AITextAndButtonsDefaultReturn:
573 [NSApp terminate:nil];
575 case AITextAndButtonsOtherReturn:
577 [[self preferenceController] setPreference:[NSNumber numberWithBool:YES]
578 forKey:@"Suppress Quit Confirmation for File Transfers"
579 group:@"Confirmations"];
580 [NSApp terminate:nil];
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
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.
595 [NSObject cancelPreviousPerformRequestsWithTarget:self
596 selector:@selector(reallyConfirmQuit)
598 [self performSelector:@selector(reallyConfirmQuit)
603 - (void)reallyConfirmQuit
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)
612 defaultButton:AILocalizedString(@"Quit", nil)
613 alternateButton:AILocalizedString(@"Cancel", nil)
614 otherButton:AILocalizedString(@"Don't ask again", nil)
616 selector:@selector(unreadQuitQuestion:userInfo:)
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)
628 defaultButton:AILocalizedString(@"Quit", nil)
629 alternateButton:AILocalizedString(@"Cancel", nil)
630 otherButton:AILocalizedString(@"Don't ask again", nil)
632 selector:@selector(fileTransferQuitQuestion:userInfo:)
638 [NSApp terminate:nil];
642 //Other -------------------------------------------------------------------------------------------------------
644 //If Adium was launched by double-clicking an associated file, we get this call after willFinishLaunching but before
646 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
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;
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
663 //Queue the request until Adium is done launching if Adium is not ready
664 [queuedLogPathToShow release]; queuedLogPathToShow = [filename retain];
667 //Don't continue to the xtras installation code. Return YES because we handled the open.
671 /* Installation of Xtras below this point */
673 [prefsCategory release]; prefsCategory = nil;
674 [advancedPrefsName release]; advancedPrefsName = nil;
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);
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";
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";
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";
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";
708 } else if ([extension caseInsensitiveCompare:@"AdiumScripts"] == NSOrderedSame) {
709 destination = [AISearchPathForDirectoriesInDomains(AIScriptsDirectory, NSUserDomainMask, /*expandTilde*/ YES) objectAtIndex:0];
710 fileDescription = AILocalizedString(@"AppleScript set",nil);
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";
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";
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";
735 } else if ([extension caseInsensitiveCompare:@"AdiumStatusIcons"] == NSOrderedSame) {
736 NSString *packName = [[filename lastPathComponent] stringByDeletingPathExtension];
738 //Can't do this because the preferenceController isn't ready yet
739 NSString *defaultPackName = [[self preferenceController] defaultPreferenceForKey:@"Status Icon Pack"
743 NSString *defaultPackName = @"iBubble Status";
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";
751 errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ is the name of the default status icon pack; this pack therefore can not be installed.",nil),
757 NSString *destinationFilePath = [destination stringByAppendingPathComponent:[filename lastPathComponent]];
759 NSString *alertTitle = nil;
760 NSString *alertMsg = nil;
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");
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...'.");
770 alertMsg = [NSString stringWithFormat:format,
772 [[filename lastPathComponent] stringByDeletingPathExtension]];
775 //Trash the old file if one exists (since we know it isn't ourself)
776 [[NSFileManager defaultManager] trashFileAtPath:destinationFilePath];
778 //Ensure the directory exists
779 [[NSFileManager defaultManager] createDirectoryAtPath:destination attributes:nil];
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
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.'."),
790 [[filename lastPathComponent] stringByDeletingPathExtension]];
792 if (requiresRestart) {
793 alertMsg = [alertMsg stringByAppendingString:AILocalizedString(@" Please restart Adium.",nil)];
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.'."),
802 [[filename lastPathComponent] stringByDeletingPathExtension]];
806 [[self notificationCenter] postNotificationName:AIXtrasDidChangeNotification
807 object:[[filename lastPathComponent] pathExtension]];
809 buttonPressed = NSRunInformationalAlertPanel(alertTitle,alertMsg,nil,prefsButton,nil);
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];
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;
826 errorMessage = AILocalizedString(@"An error occurred while installing the X(tra).",nil);
829 NSRunAlertPanel(AILocalizedString(@"Installation Failed","Title of installation failed window"),
837 - (BOOL)application:(NSApplication *)theApplication openTempFile:(NSString *)filename
841 success = [self application:theApplication openFile:filename];
842 [[NSFileManager defaultManager] removeFileAtPath:filename handler:nil];
847 - (void)openAppropriatePreferencesIfNeeded
850 if ([prefsCategory isEqualToString:@"advanced"]) {
851 [preferenceController openPreferencesToAdvancedPane:advancedPrefsName];
853 [preferenceController openPreferencesToCategoryWithIdentifier:prefsCategory];
856 [prefsCategory release]; prefsCategory = nil;
861 * @brief Returns the location of Adium's preference folder
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.
866 - (NSString *)applicationSupportDirectory
868 //Path to the preferences folder
869 static NSString *_preferencesFolderPath = nil;
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];
878 return _preferencesFolderPath;
882 * @brief Create a resource folder in the Library/Application\ Support/Adium\ 2.0 folder.
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.
888 - (NSString *)createResourcePathForName:(NSString *)name
890 NSString *targetPath; //This is the subfolder for the user domain (i.e. ~/L/AS/Adium\ 2.0).
891 NSFileManager *defaultManager;
892 NSArray *existingResourcePaths;
894 defaultManager = [NSFileManager defaultManager];
895 existingResourcePaths = [self resourcePathsForName:name];
896 targetPath = [[self applicationSupportDirectory] stringByAppendingPathComponent:name];
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.
902 if ([existingResourcePaths indexOfObject:targetPath] == NSNotFound) {
903 if (![defaultManager createDirectoryAtPath:targetPath attributes:nil]) {
906 //If the directory could not be created, there may be a file in the way. Death to file.
907 error = ![defaultManager trashFileAtPath:targetPath];
909 if (!error) error = ![defaultManager createDirectoryAtPath:targetPath attributes:nil];
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),
920 if (result == NSAlertAlternateReturn) {
921 [[NSWorkspace sharedWorkspace] launchApplication:@"Disk Utility"];
926 targetPath = [existingResourcePaths objectAtIndex:0];
933 * @brief Return zero or more resource pathnames to an filename
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.
939 * Pass nil to receive an array of paths to existing Adium Application Support folders (plus the Resouces folder).
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" }.
947 * @param name The full name (including extension as appropriate) of the resource for which to search
949 - (NSArray *)resourcePathsForName:(NSString *)name
951 NSArray *librarySearchPaths;
952 NSEnumerator *searchPathEnumerator;
953 NSString *adiumFolderName, *path;
954 NSMutableArray *pathArray = [NSMutableArray arrayWithCapacity:4];
955 NSFileManager *defaultManager = [NSFileManager defaultManager];
958 adiumFolderName = (name ?
959 [[@"Application Support" stringByAppendingPathComponent:@"Adium 2.0"] stringByAppendingPathComponent:name] :
960 [@"Application Support" stringByAppendingPathComponent:@"Adium 2.0"]);
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];
966 //Copy each discovered path into the pathArray after adding our subfolder path
967 while ((path = [searchPathEnumerator nextObject])) {
970 fullPath = [path stringByAppendingPathComponent:adiumFolderName];
971 if (([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
974 [pathArray addObject:fullPath];
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.
982 [[self applicationSupportDirectory] stringByAppendingPathComponent:name] :
983 [self applicationSupportDirectory]);
984 if (![pathArray containsObject:path] &&
985 ([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
987 //Our application support directory should always be first
988 if ([pathArray count]) {
989 [pathArray insertObject:path atIndex:0];
991 [pathArray addObject:path];
995 //Add the path to the resource in Adium's bundle
997 path = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name] stringByExpandingTildeInPath];
998 if (([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
1000 [pathArray addObject:path];
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
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;
1023 // Get every path that can contain these resources
1024 pathEnumerator = [[self resourcePathsForName:name] objectEnumerator];
1026 while ((resourceDir = [pathEnumerator nextObject])) {
1027 resourceEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:resourceDir] objectEnumerator];
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]];
1039 if ([[resourcePath pathExtension] caseInsensitiveCompare:extensions] == NSOrderedSame)
1040 [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
1049 * @brief Return the path to be used for caching files for this user.
1051 * @result A cached, tilde-expanded full path.
1053 - (NSString *)cachesPath
1055 static NSString *cachesPath = nil;
1058 NSString *generalAdiumCachesPath;
1059 NSFileManager *defaultManager = [NSFileManager defaultManager];
1061 generalAdiumCachesPath = [[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:@"Adium"];
1062 cachesPath = [[generalAdiumCachesPath stringByAppendingPathComponent:[[self loginController] currentUser]] retain];
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;
1071 enumerator = [[defaultManager directoryContentsAtPath:generalAdiumCachesPath] objectEnumerator];
1072 while ((filename = [enumerator nextObject])) {
1073 NSString *fullPath = [generalAdiumCachesPath stringByAppendingPathComponent:filename];
1075 if (([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
1077 [defaultManager movePath:fullPath
1078 toPath:[cachesPath stringByAppendingPathComponent:filename]
1088 - (NSString *)pathOfPackWithName:(NSString *)name extension:(NSString *)extension resourceFolderName:(NSString *)folderName
1090 NSFileManager *fileManager = [NSFileManager defaultManager];
1091 NSString *packFileName = [name stringByAppendingPathExtension:extension];
1092 NSEnumerator *enumerator = [[self resourcePathsForName:folderName] objectEnumerator];
1093 NSString *resourcePath;
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];
1104 - (void)systemTimeZoneDidChange:(NSNotification *)inNotification
1106 [NSTimeZone resetSystemTimeZone];
1109 - (NSApplication *)application
1111 return [NSApplication sharedApplication];
1114 #pragma mark Scripting
1115 - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key {
1116 BOOL handleKey = NO;
1118 if ([key isEqualToString:@"applescriptabilityController"] ||
1119 [key isEqualToString:@"interfaceController"] ) {
1128 - (void)configureHelp
1130 CFBundleRef myApplicationBundle;
1131 CFURLRef myBundleURL;
1134 if ((myApplicationBundle = CFBundleGetMainBundle())) {
1135 myBundleURL = CFBundleCopyBundleURL(myApplicationBundle);
1137 if (CFURLGetFSRef(myBundleURL, &myBundleRef)) {
1138 AHRegisterHelpBook(&myBundleRef);
1143 #pragma mark Sparkle Delegate Methods
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]
1149 //For a beta release, always use the beta appcast
1150 #define UPDATE_TYPE_DICT BETA_UPDATE_DICT
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)
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]
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.
1168 - (NSMutableArray *)updaterCustomizeProfileInfo:(NSMutableArray *)profileInfo
1170 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
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];
1176 if (abs([defaults integerForKey:@"AILastSubmittedProfileDate2"] - now) >= 7) {
1177 [defaults setInteger:now forKey:@"AILastSubmittedProfileDate2"];
1179 NSString *value = ([defaults boolForKey:@"AIHasSentSparkleProfileInfo"]) ? @"no" : @"yes";
1181 NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
1182 @"FirstSubmission", @"key",
1183 @"First Time Submitting Profile Information", @"visibleKey",
1185 value, @"visibleValue",
1188 [profileInfo addObject:entry];
1190 [defaults setBool:YES forKey:@"AIHasSentSparkleProfileInfo"];
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]]];
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]];
1209 entry = [NSDictionary dictionaryWithObjectsAndKeys:
1210 @"IMServices", @"key",
1211 @"IM Services Used", @"visibleKey",
1212 accountInfoString, @"value",
1213 accountInfo, @"visibleValue",
1215 [profileInfo addObject:entry];
1219 [profileInfo addObject:UPDATE_GENERATION_DICT];
1220 [profileInfo addObject:UPDATE_TYPE_DICT];
1224 - (NSComparisonResult) compareVersion:(NSString *)newVersion toVersion:(NSString *)currentVersion
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
1232 return NSOrderedSame;