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 //Take no action if we didn't complete the application load
307 if (!completedApplicationLoad) return;
309 //Let the status controller we'll be closing so it can keep track of connected accounts for use with the global statuses
310 [statusController beginClosing];
312 //Let the content controller know we'll be closing so it can take action before chats are closed as a group
313 [contentController beginClosing];
315 //Preference controller needs to close the prefs window before the plugins that control it are unloaded
316 [preferenceController beginClosing];
318 //Close the controllers in reverse order
319 [pluginLoader closeController]; //should always unload first. Plugins rely on all the controllers.
320 [componentLoader closeController];
321 [contactAlertsController closeController];
322 [fileTransferController closeController];
323 [statusController closeController];
324 [dockController closeController];
325 [interfaceController closeController];
326 [contentController closeController];
327 [contactController closeController];
328 [accountController closeController];
329 [emoticonController closeController];
330 [soundController closeController];
331 [menuController closeController];
332 [applescriptabilityController closeController];
333 [debugController closeController];
334 [toolbarController closeController];
335 [preferenceController closeController];
337 [self deleteTemporaryFiles];
340 - (void)deleteTemporaryFiles
342 [[NSFileManager defaultManager] removeFilesInDirectory:[self cachesPath]
348 //Menu Item Hooks ------------------------------------------------------------------------------------------------------
349 #pragma mark Menu Item Hooks
351 - (IBAction)showAboutBox:(id)sender
353 [[LNAboutBoxController aboutBoxController] showWindow:nil];
357 - (IBAction)showHelp:(id)sender{
358 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_TRAC_PAGE]];
360 - (IBAction)reportABug:(id)sender{
361 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_TRAC_PAGE]];
363 - (IBAction)sendFeedback:(id)sender{
364 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FEEDBACK_PAGE]];
366 - (IBAction)showForums:(id)sender{
367 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_FORUM_PAGE]];
369 - (IBAction)showXtras:(id)sender{
370 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:ADIUM_XTRAS_PAGE]];
373 //Last call to perform actions before the app shuffles off its mortal coil and joins the bleeding choir invisible
374 - (IBAction)confirmQuit:(id)sender
376 [NSApp terminate:nil];
379 - (IBAction)launchJeeves:(id)sender
381 [[NSWorkspace sharedWorkspace] launchApplication:PATH_TO_IMPORTER];
386 //Other -------------------------------------------------------------------------------------------------------
388 //If Adium was launched by double-clicking an associated file, we get this call after willFinishLaunching but before
390 - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
392 NSString *extension = [filename pathExtension];
393 NSString *destination = nil;
394 NSString *errorMessage = nil;
395 NSString *fileDescription = nil, *prefsButton = nil;
396 BOOL success = NO, requiresRestart = NO;
399 [prefsCategory release]; prefsCategory = nil;
400 [advancedPrefsName release]; advancedPrefsName = nil;
402 //Specify a file extension and a human-readable description of what the files of this type do
403 if ([extension caseInsensitiveCompare:@"AdiumPlugin"] == NSOrderedSame){
404 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Plugins"];
405 //Plugins haven't been loaded yet if the application isn't done loading, so only request a restart if it has finished loading already
406 requiresRestart = completedApplicationLoad;
407 fileDescription = AILocalizedString(@"Adium plugin",nil);
409 } else if ([extension caseInsensitiveCompare:@"AdiumIcon"] == NSOrderedSame){
410 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Dock Icons"];
411 fileDescription = AILocalizedString(@"dock icon set",nil);
412 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
413 prefsCategory = @"appearance";
415 } else if ([extension caseInsensitiveCompare:@"AdiumSoundset"] == NSOrderedSame){
416 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Sounds"];
417 fileDescription = AILocalizedString(@"sound set",nil);
418 prefsButton = AILocalizedString(@"Open Event Prefs",nil);
419 prefsCategory = @"events";
421 } else if ([extension caseInsensitiveCompare:@"AdiumEmoticonset"] == NSOrderedSame){
422 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Emoticons"];
423 fileDescription = AILocalizedString(@"emoticon set",nil);
424 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
425 prefsCategory = @"appearance";
427 } else if ([extension caseInsensitiveCompare:@"AdiumScripts"] == NSOrderedSame) {
428 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Scripts"];
429 fileDescription = AILocalizedString(@"AppleScript set",nil);
431 } else if ([extension caseInsensitiveCompare:@"AdiumMessageStyle"] == NSOrderedSame){
432 if ([NSApp isOnPantherOrBetter]){
433 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Message Styles"];
434 fileDescription = AILocalizedString(@"message style",nil);
435 prefsButton = AILocalizedString(@"Open Message Prefs",nil);
436 prefsCategory = @"messages";
438 errorMessage = AILocalizedString(@"Sorry, but Adium Message Styles are not supported in OS X 10.2 (Jaguar).",nil);
440 } else if ([extension caseInsensitiveCompare:@"ListLayout"] == NSOrderedSame){
441 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Contact List"];
442 fileDescription = AILocalizedString(@"contact list layout",nil);
443 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
444 prefsCategory = @"appearance";
446 } else if ([extension caseInsensitiveCompare:@"ListTheme"] == NSOrderedSame){
447 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Contact List"];
448 fileDescription = AILocalizedString(@"contact list theme",nil);
449 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
450 prefsCategory = @"appearance";
452 } else if ([extension caseInsensitiveCompare:@"AdiumServiceIcons"] == NSOrderedSame){
453 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Service Icons"];
454 fileDescription = AILocalizedString(@"service icons",nil);
455 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
456 prefsCategory = @"appearance";
458 } else if ([extension caseInsensitiveCompare:@"AdiumStatusIcons"] == NSOrderedSame){
459 NSString *packName = [[filename lastPathComponent] stringByDeletingPathExtension];
461 //Can't do this because the preferenceController isn't ready yet
462 NSString *defaultPackName = [[self preferenceController] defaultPreferenceForKey:@"Status Icon Pack"
466 NSString *defaultPackName = @"Gems";
468 if (![packName isEqualToString:defaultPackName]) {
469 destination = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:@"Status Icons"];
470 fileDescription = AILocalizedString(@"status icons",nil);
471 prefsButton = AILocalizedString(@"Open Appearance Prefs",nil);
472 prefsCategory = @"appearance";
474 errorMessage = [NSString stringWithFormat:AILocalizedString(@"%@ is the name of the default status icon pack; this pack therefore can not be installed.",nil),
480 NSString *destinationFilePath = [destination stringByAppendingPathComponent:[filename lastPathComponent]];
482 NSString *alertTitle = nil;
483 NSString *alertMsg = nil;
486 if([filename isEqualToString:destinationFilePath]) {
487 // Don't copy the file if it's already in the right place!!
488 alertTitle= AILocalizedString(@"Installation Successful","Title of installation successful window");
490 format = AILocalizedString(@"Installation of the %@ %@ was successful because the file was already in the correct location.",
491 "Installation introduction, like 'Installation of the message style Fiat was successful...'.");
493 alertMsg = [NSString stringWithFormat:format,
495 [[filename lastPathComponent] stringByDeletingPathExtension]];
498 //Trash the old file if one exists (since we know it isn't ourself)
499 [[NSFileManager defaultManager] trashFileAtPath:destinationFilePath];
501 //Ensure the directory exists
502 [[NSFileManager defaultManager] createDirectoryAtPath:destination attributes:nil];
504 //Perform the copy and display an alert informing the user of its success or failure
505 if ([[NSFileManager defaultManager] copyPath:filename
506 toPath:destinationFilePath
509 alertTitle = AILocalizedString(@"Installation Successful","Title of installation successful window");
510 alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was successful.",
511 "Installation sentence, like 'Installation of the message style Fiat was successful.'."),
513 [[filename lastPathComponent] stringByDeletingPathExtension]];
515 if (requiresRestart){
516 alertMsg = [alertMsg stringByAppendingString:AILocalizedString(@" Please restart Adium.",nil)];
521 alertTitle = AILocalizedString(@"Installation Failed","Title of installation failed window");
522 alertMsg = [NSString stringWithFormat:AILocalizedString(@"Installation of the %@ %@ was unsuccessful.",
523 "Installation failed sentence, like 'Installation of the message style Fiat was unsuccessful.'."),
525 [[filename lastPathComponent] stringByDeletingPathExtension]];
529 [[self notificationCenter] postNotificationName:Adium_Xtras_Changed
530 object:[[filename lastPathComponent] pathExtension]];
532 buttonPressed = NSRunInformationalAlertPanel(alertTitle,alertMsg,nil,prefsButton,nil);
534 // User clicked the "open prefs" button
535 if(buttonPressed == NSAlertAlternateReturn){
536 //If we're done loading the app, open the prefs now; if not, it'll be done once the load is finished
537 //so the controllers and plugins have had a chance to initialize
538 if(completedApplicationLoad) {
539 [self openAppropriatePreferencesIfNeeded];
542 //If the user didn't press the "open prefs" button, clear the pref opening information
543 [prefsCategory release]; prefsCategory = nil;
544 [advancedPrefsName release]; advancedPrefsName = nil;
549 errorMessage = AILocalizedString(@"An error occurred while installing the X(tra).",nil);
552 NSRunAlertPanel(AILocalizedString(@"Installation Failed","Title of installation failed window"),
560 - (BOOL)application:(NSApplication *)theApplication openTempFile:(NSString *)filename
564 success = [self application:theApplication openFile:filename];
565 [[NSFileManager defaultManager] removeFileAtPath:filename handler:nil];
570 - (void)openAppropriatePreferencesIfNeeded
573 if([prefsCategory isEqualToString:@"advanced"]){
574 [preferenceController openPreferencesToAdvancedPane:advancedPrefsName];
576 [preferenceController openPreferencesToCategoryWithIdentifier:prefsCategory];
579 [prefsCategory release]; prefsCategory = nil;
584 * @brief Create a resource folder in the Library/Application\ Support/Adium\ 2.0 folder.
586 * Pass it the name of the folder (e.g. @"Scripts").
587 * If it is found to already in a library folder, return that pathname, using the same order of preference as
588 * -[AIAdium resourcePathsForName:]. Otherwise, create it in the user library and return the pathname to it.
590 - (NSString *)createResourcePathForName:(NSString *)name
592 NSString *targetPath; //This is the subfolder for the user domain (i.e. ~/L/AS/Adium\ 2.0).
593 NSFileManager *defaultManager;
594 NSArray *existingResourcePaths;
596 defaultManager = [NSFileManager defaultManager];
597 existingResourcePaths = [self resourcePathsForName:name];
598 targetPath = [ADIUM_APPLICATION_SUPPORT_DIRECTORY stringByAppendingPathComponent:name];
601 If the targetPath doesn't exist, create it, as this method was called to ensure that it exists
602 for creating files in the user domain.
604 if([existingResourcePaths indexOfObject:targetPath] == NSNotFound) {
605 if(![defaultManager createDirectoryAtPath:targetPath attributes:nil]) {
608 //If the directory could not be created, there may be a file in the way. Death to file.
609 error = ![defaultManager trashFileAtPath:targetPath];
611 if (!error) error = ![defaultManager createDirectoryAtPath:targetPath attributes:nil];
617 result = NSRunCriticalAlertPanel([NSString stringWithFormat:AILocalizedString(@"Could not create the %@ folder.",nil), name],
618 AILocalizedString(@"Try running Repair Permissions from Disk Utility.",nil),
619 AILocalizedString(@"OK",nil),
620 AILocalizedString(@"Launch Disk Utility",nil),
622 if (result == NSAlertAlternateReturn){
623 [[NSWorkspace sharedWorkspace] launchApplication:@"Disk Utility"];
628 targetPath = [existingResourcePaths objectAtIndex:0];
635 * @brief Return zero or more resource pathnames to an filename
637 * Searches in the Application Support folders and the Resources/ folder of the Adium.app bundle.
638 * Only those pathnames that exist are returned. The Adium bundle's resource path will be the last item in the array,
639 * so precedence is given to the user and system Application Support folders.
641 * Pass nil to receive an array of paths to existing Adium Application Support folders (plus the Resouces folder).
643 * Example: If you call[adium resourcePathsForName:@"Scripts"], and there's a
644 * Scripts folder in ~/Library/Application Support/Adium\ 2.0 and in /Library/Application Support/Adium\ 2.0, but not
645 * in /System/Library/ApplicationSupport/Adium\ 2.0 or /Network/Library/Application Support/Adium\ 2.0.
646 * The array you get back will be { @"/Users/username/Library/Application Support/Adium 2.0/Scripts",
647 * @"/Library/Application Support/Adium 2.0/Scripts" }.
649 * @param name The full name (including extension as appropriate) of the resource for which to search
651 - (NSArray *)resourcePathsForName:(NSString *)name
653 NSArray *librarySearchPaths;
654 NSEnumerator *searchPathEnumerator;
655 NSString *adiumFolderName, *path;
656 NSMutableArray *pathArray = [NSMutableArray arrayWithCapacity:4];
657 NSFileManager *defaultManager = [NSFileManager defaultManager];
660 adiumFolderName = (name ? [ADIUM_SUBFOLDER_OF_LIBRARY stringByAppendingPathComponent:name] : ADIUM_SUBFOLDER_OF_LIBRARY);
662 //Find Library directories in all domains except /System (as of Panther, that's ~/Library, /Library, and /Network/Library)
663 librarySearchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask - NSSystemDomainMask, YES);
664 searchPathEnumerator = [librarySearchPaths objectEnumerator];
666 //Copy each discovered path into the pathArray after adding our subfolder path
667 while(path = [searchPathEnumerator nextObject]){
670 fullPath = [path stringByAppendingPathComponent:adiumFolderName];
671 if(([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
674 [pathArray addObject:fullPath];
678 //Add the path to the resource in Adium's bundle
680 path = [[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:name] stringByExpandingTildeInPath];
681 if(([defaultManager fileExistsAtPath:path isDirectory:&isDir]) &&
683 [pathArray addObject:path];
692 * @brief Returns an array of the paths to all of the resources for a given name, filtering out those without a certain extension
693 * @param name The full name (including extension as appropriate) of the resource for which to search
694 * @param extensions The extension(s) of the resources for which to search, either an NSString or an NSArray
696 - (NSArray *)allResourcesForName:(NSString *)name withExtensions:(id)extensions {
697 NSMutableArray *resources = [NSMutableArray array];
698 NSEnumerator *pathEnumerator;
699 NSEnumerator *resourceEnumerator;
700 NSString *resourceDir;
701 NSString *resourcePath;
702 BOOL extensionsArray = [extensions isKindOfClass:[NSArray class]];
703 NSEnumerator *extensionsEnumerator;
706 // Get every path that can contain these resources
707 pathEnumerator = [[self resourcePathsForName:name] objectEnumerator];
709 while (resourceDir = [pathEnumerator nextObject]) {
710 resourceEnumerator = [[[NSFileManager defaultManager] directoryContentsAtPath:resourceDir] objectEnumerator];
712 while (resourcePath = [resourceEnumerator nextObject]) {
713 // Add each resource to the array
714 if (extensionsArray) {
715 extensionsEnumerator = [extensions objectEnumerator];
716 while (extension = [extensionsEnumerator nextObject]) {
717 if ([[resourcePath pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame)
718 [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
722 if ([[resourcePath pathExtension] caseInsensitiveCompare:extensions] == NSOrderedSame)
723 [resources addObject:[resourceDir stringByAppendingPathComponent:resourcePath]];
732 * @brief Return the path to be used for caching files for this user.
734 * @result A cached, tilde-expanded full path.
736 - (NSString *)cachesPath
738 static NSString *cachesPath = nil;
741 NSString *generalAdiumCachesPath;
742 NSFileManager *defaultManager = [NSFileManager defaultManager];
744 generalAdiumCachesPath = [[[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"] stringByAppendingPathComponent:@"Adium"] stringByExpandingTildeInPath];
745 cachesPath = [[generalAdiumCachesPath stringByAppendingPathComponent:[[self loginController] currentUser]] retain];
747 //Ensure our cache path exists
748 if([defaultManager createDirectoriesForPath:cachesPath]){
749 //If we have to make directories, try to move old cache files into the new directory
750 NSEnumerator *enumerator;
754 enumerator = [[defaultManager directoryContentsAtPath:generalAdiumCachesPath] objectEnumerator];
755 while(filename = [enumerator nextObject]){
756 NSString *fullPath = [generalAdiumCachesPath stringByAppendingPathComponent:filename];
758 if(([defaultManager fileExistsAtPath:fullPath isDirectory:&isDir]) &&
760 [defaultManager movePath:fullPath
761 toPath:[cachesPath stringByAppendingPathComponent:filename]
771 - (NSString *)pathOfPackWithName:(NSString *)name extension:(NSString *)extension resourceFolderName:(NSString *)folderName
773 NSFileManager *fileManager = [NSFileManager defaultManager];
774 NSString *packFileName = [name stringByAppendingPathExtension:extension];
775 NSEnumerator *enumerator = [[self resourcePathsForName:folderName] objectEnumerator];
776 NSString *resourcePath;
778 //Search all our resource paths for the requested pack
779 while(resourcePath = [enumerator nextObject]){
780 NSString *packPath = [resourcePath stringByAppendingPathComponent:packFileName];
781 if([fileManager fileExistsAtPath:packPath]) return([packPath stringByExpandingTildeInPath]);
787 //If this is the first time running a version, post Adium_versionUpgraded with information about the old and new versions.
788 /*- (NSDictionary *)versionUpgradeDict
790 NSString *currentVersionString, *lastLaunchedVersionString;
791 float currentVersion, lastLaunchedVersion;
792 NSNumber *currentVersionNumber;
793 NSDictionary *versionUpgradeDict = nil;
795 currentVersionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:kCFBundleVersionKey];
796 lastLaunchedVersionString = [[self preferenceController] preferenceForKey:KEY_LAST_VERSION_LAUNCHED
797 group:PREF_GROUP_GENERAL];
800 //Friendly reminder that we are running with the beta flag on
801 NSString *spaces1, *spaces2;
802 unsigned length = [currentVersionString length];
804 spaces1 = [@"" stringByPaddingToLength:(length / 2)
807 if (length % 2 == 0){
808 //An even length is one space too much
809 spaces2 = [@"" stringByPaddingToLength:(length / 2) - 1
813 //An odd length is okay
817 NSLog(@"#### %@THIS IS A BETA RELEASE!%@ ####",spaces1,spaces2);
818 NSLog(@"#### Loading Adium X BETA Release v%@ ####",currentVersionString);
820 AILog(@"#### %@THIS IS A BETA RELEASE!%@ ####",spaces1,spaces2);
821 AILog(@"#### Loading Adium X BETA Release v%@ ####",currentVersionString);
823 currentVersionString = [self processBetaVersionString:currentVersionString];
824 lastLaunchedVersionString = [self processBetaVersionString:lastLaunchedVersionString];
827 currentVersion = [currentVersionString floatValue];
828 currentVersionNumber = [NSNumber numberWithFloat:currentVersion];
830 lastLaunchedVersion = [lastLaunchedVersionString floatValue];
832 if (!lastLaunchedVersion || !currentVersion || currentVersion > lastLaunchedVersion){
834 if (lastLaunchedVersion){
836 NSNumber *lastLaunchedVersionNumber = [NSNumber numberWithFloat:lastLaunchedVersion];
838 versionUpgradeDict = [NSDictionary dictionaryWithObjectsAndKeys:lastLaunchedVersionNumber, @"lastLaunchedVersion",
839 currentVersionNumber,@"currentVersion",
842 versionUpgradeDict = [NSDictionary dictionaryWithObject:currentVersionNumber
843 forKey:@"currentVersion"];
847 //Remember that we have now run in this version.
848 if(versionUpgradeDict){
849 [[self preferenceController] setPreference:currentVersionString
850 forKey:KEY_LAST_VERSION_LAUNCHED
851 group:PREF_GROUP_GENERAL];
854 return(versionUpgradeDict);
857 - (NSString *)processBetaVersionString:(NSString *)inString
859 NSString *returnString = nil;
861 if ([inString isEqualToString:@"0.7b1"]){
862 returnString = @"0.68";
863 }else if ([inString isEqualToString:@"0.7b2"]){
864 returnString = @"0.681";
865 }else if ([inString isEqualToString:@"0.7b3"]){
866 returnString = @"0.682";
867 }else if ([inString isEqualToString:@"0.7b4"]){
868 returnString = @"0.683";
869 }else if ([inString isEqualToString:@"0.7b5"]){
870 returnString = @"0.684";
871 }else if ([inString isEqualToString:@"0.7b6"]){
872 returnString = @"0.685";
873 }else if ([inString isEqualToString:@"0.7b7"]){
874 returnString = @"0.686";
875 }else if ([inString isEqualToString:@"0.7b8"]){
876 returnString = @"0.687";
879 return(returnString ? returnString : inString);
882 #pragma mark Scripting
883 - (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key {
886 if([key isEqualToString:@"applescriptabilityController"] ||
887 [key isEqualToString:@"interfaceController"] ){