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