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