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 "AIContactController.h"
18 #import "AILoginController.h"
19 #import "AIPreferenceController.h"
20 #import "AIPreferenceWindowController.h"
21 #import "AIToolbarController.h"
22 #import <AIUtilities/AIDictionaryAdditions.h>
23 #import <AIUtilities/AIDictionaryAdditions.h>
24 #import <AIUtilities/AIFileManagerAdditions.h>
25 #import <AIUtilities/AIStringAdditions.h>
26 #import <AIUtilities/AIToolbarUtilities.h>
27 #import <AIUtilities/ESImageAdditions.h>
28 #import <Adium/AIListObject.h>
29 #import <Adium/AIPreferencePane.h>
31 #define PREFS_DEFAULT_PREFS @"PrefsPrefs.plist"
32 #define TITLE_OPEN_PREFERENCES AILocalizedString(@"Open Preferences",nil)
34 @interface AIPreferenceController (PRIVATE)
35 - (NSMutableDictionary *)loadPreferenceGroup:(NSString *)groupName;
36 - (void)savePreferences:(NSMutableDictionary *)prefDict forGroup:(NSString *)groupName;
37 - (NSDictionary *)cachedDefaultsForGroup:(NSString *)group object:(AIListObject *)object;
38 - (NSDictionary *)cachedPreferencesWithDefaultsForGroup:(NSString *)group object:(AIListObject *)object;
42 * @class AIPreferenceController
43 * @brief Preference Controller
45 * Handles loading and saving preferences, default preferences, and preference changed notifications
47 @implementation AIPreferenceController
52 - (void)initController
55 paneArray = [[NSMutableArray alloc] init];
57 defaults = [[NSMutableDictionary alloc] init];
58 prefCache = [[NSMutableDictionary alloc] init];
59 prefWithDefaultsCache = [[NSMutableDictionary alloc] init];
61 objectDefaults = [[NSMutableDictionary alloc] init];
62 objectPrefCache = [[NSMutableDictionary alloc] init];
63 objectPrefWithDefaultsCache = [[NSMutableDictionary alloc] init];
65 observers = [[NSMutableDictionary alloc] init];
66 delayedNotificationGroups = [[NSMutableSet alloc] init];
67 preferenceChangeDelays = 0;
70 userDirectory = [[[adium loginController] userDirectory] retain];
72 //Create the 'ByObject' and 'Accounts' object specific preference directory
73 [[NSFileManager defaultManager] createDirectoriesForPath:[userDirectory stringByAppendingPathComponent:OBJECT_PREFS_PATH]];
74 [[NSFileManager defaultManager] createDirectoriesForPath:[userDirectory stringByAppendingPathComponent:ACCOUNT_PREFS_PATH]];
76 //Register our default preferences
77 [self registerDefaults:[NSDictionary dictionaryNamed:PREFS_DEFAULT_PREFS forClass:[self class]] forGroup:PREF_GROUP_GENERAL];
81 * @brief Finish initialization
83 * Sets up the toolbar items.
84 * We can't do these in initing, since the toolbar controller hasn't loaded yet at that point.
88 NSToolbarItem *toolbarItem;
90 //Show preference window toolabr item
91 toolbarItem = [AIToolbarUtilities toolbarItemWithIdentifier:@"ShowPreferences"
92 label:AILocalizedString(@"Preferences",nil)
93 paletteLabel:TITLE_OPEN_PREFERENCES
94 toolTip:TITLE_OPEN_PREFERENCES
96 settingSelector:@selector(setImage:)
97 itemContent:[NSImage imageNamed:@"pref-general"
98 forClass:[self class]]
99 action:@selector(showPreferenceWindow:)
101 [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"General"];
105 * @brief Begin closing
107 * We must close the preference window before plugins and the other controllers are unloaded.
111 [AIPreferenceWindowController closePreferenceWindow];
117 - (void)closeController
119 //Preferences are (always) saved as they're modified, so there's no need to save them here.
127 [delayedNotificationGroups release]; delayedNotificationGroups = nil;
128 [paneArray release]; paneArray = nil;
129 [prefCache release]; prefCache = nil;
130 [objectPrefCache release]; objectPrefCache = nil;
135 * @brief Adium 2 to Adium X preference migration
137 * This code will move the preferences from "../Adium 2.0" to "../Adium X", if we ever want to do that.
138 * Not currently used.
140 - (void)movePreferenceFolderFromAdium2ToAdium
142 NSFileManager *manager = [NSFileManager defaultManager];
143 NSString *appSupport = [[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Application Support"];
144 NSString *oldPath, *newPath;
145 BOOL isDir, oldExists, newExists;
147 //Check for a preference folder in the old and new locations
148 oldPath = [appSupport stringByAppendingPathComponent:@"Adium 2.0"];
149 newPath = [appSupport stringByAppendingPathComponent:@"Adium X"];
150 oldExists = ([manager fileExistsAtPath:oldPath isDirectory:&isDir] && isDir);
151 newExists = ([manager fileExistsAtPath:newPath isDirectory:&isDir] && isDir);
153 //If we find an old preference folder (and no new one) migrate it to the new location
154 if(oldExists & !newExists){
155 [manager movePath:oldPath toPath:newPath handler:nil];
160 //Preference Window ----------------------------------------------------------------------------------------------------
161 #pragma mark Preference Window
163 * @brief Show the preference window
165 - (IBAction)showPreferenceWindow:(id)sender
167 [AIPreferenceWindowController openPreferenceWindow];
171 * @brief Show a specific category of the preference window
173 * Opens the preference window if necessary
175 * @param category The category to show
177 - (void)openPreferencesToCategoryWithIdentifier:(NSString *)identifier
179 [AIPreferenceWindowController openPreferenceWindowToCategoryWithIdentifier:identifier];
183 * @brief Show a specific category within the advanced pane of the preference window
185 * Opens the preference window if necessary
188 - (void)openPreferencesToAdvancedPane:(NSString *)paneName
190 [AIPreferenceWindowController openPreferenceWindowToAdvancedPane:paneName];
194 * @brief Add a view to the preferences
196 - (void)addPreferencePane:(AIPreferencePane *)inPane
198 [paneArray addObject:inPane];
202 * @brief Returns all currently available preference panes
204 - (NSArray *)paneArray
210 //Observing ------------------------------------------------------------------------------------------------------------
211 #pragma mark Observing
213 * @brief Register a preference observer
215 * The preference observer will be notified when preferences in group change and passed the preference dictionary for that group
216 * The observer must implement:
217 * - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
220 - (void)registerPreferenceObserver:(id)observer forGroup:(NSString *)group
222 NSMutableArray *groupObservers;
224 NSParameterAssert([observer respondsToSelector:@selector(preferencesChangedForGroup:key:object:preferenceDict:firstTime:)]);
226 //Fetch the observers for this group
227 if(!(groupObservers = [observers objectForKey:group])){
228 groupObservers = [[NSMutableArray alloc] init];
229 [observers setObject:groupObservers forKey:group];
230 [groupObservers release];
233 //Add our new observer
234 #ifdef TRACK_PREFERENCE_OBSERVERS
235 NSLog(@"adding observer: %@", observer);
237 [groupObservers addObject:[NSValue valueWithNonretainedObject:observer]];
239 //Blanket change notification for initialization
240 [observer preferencesChangedForGroup:group
243 preferenceDict:[self cachedPreferencesWithDefaultsForGroup:group object:nil]
248 * @brief Unregister a preference observer
250 - (void)unregisterPreferenceObserver:(id)observer
252 NSEnumerator *enumerator = [observers objectEnumerator];
253 NSMutableArray *observerArray;
254 NSValue *observerValue = [NSValue valueWithNonretainedObject:observer];
256 while(observerArray = [enumerator nextObject]){
257 #ifdef TRACK_PREFERENCE_OBSERVERS
258 NSLog(@"removing observer: %p", [observerValue nonretainedObjectValue]);
260 [observerArray removeObject:observerValue];
261 #ifdef TRACK_PREFERENCE_OBSERVERS
262 NSLog(@"...removed");
268 * @brief Broadcast a key changed notification.
270 * Broadcasts a group changed notification if key is nil.
272 * If notifications are delayed, remember the group that changed and broadcast this notification when the delay is
273 * lifted instead of immediately. Currently, our delayed notification system isn't setup to handle object-specific
274 * preferences, so always notify if there is an object present for now.
277 * @param group The group
278 * @param object The object, or nil if global
280 - (void)informObserversOfChangedKey:(NSString *)key inGroup:(NSString *)group object:(AIListObject *)object
282 if(!object && preferenceChangeDelays > 0){
283 [delayedNotificationGroups addObject:group];
285 NSDictionary *preferenceDict = [self cachedPreferencesWithDefaultsForGroup:group object:object];
286 NSEnumerator *enumerator = [[observers objectForKey:group] objectEnumerator];
287 NSValue *observerValue;
289 while(observerValue = [enumerator nextObject]){
290 id observer = [observerValue nonretainedObjectValue];
291 #ifdef TRACK_PREFERENCE_OBSERVERS
292 NSLog(@"informing observer %p", observer);
295 [observer preferencesChangedForGroup:group
298 preferenceDict:preferenceDict
305 * @brief Set if preference changed notifications should be delayed
307 * Changing large amounts of preferences at once causes a lot of notification overhead. This should be used like
308 * [lockFocus] / [unlockFocus] around groups of preference changes to improve performance.
310 - (void)delayPreferenceChangedNotifications:(BOOL)inDelay
313 preferenceChangeDelays++;
315 preferenceChangeDelays--;
318 //If changes are no longer delayed, save and notify of all preferences modified while delayed
319 if(!preferenceChangeDelays){
320 NSEnumerator *enumerator = [delayedNotificationGroups objectEnumerator];
323 [[adium contactController] delayListObjectNotifications];
325 while(group = [enumerator nextObject]){
326 [self informObserversOfChangedKey:nil inGroup:group object:nil];
329 [[adium contactController] endListObjectNotificationsDelay];
331 [delayedNotificationGroups removeAllObjects];
336 //Setting Preferences -------------------------------------------------------------------
337 #pragma mark Setting Preferences
339 * @brief Set a global preference
341 * Set and save a preference at the global level.
343 * @param value The preference, which must be plist-encodable
344 * @param key An arbitrary NSString key
345 * @param group An arbitrary NSString group
347 - (void)setPreference:(id)value forKey:(NSString *)key group:(NSString *)group{
348 [self setPreference:value forKey:key group:group object:nil];
352 * @brief Set a global or object-specific preference
354 * Set and save a preference. This should not be called directly from plugins or components. To set an object-specific
355 * preference, use the appropriate method on the object. To set a global preference, use setPreference:forKey:group:
357 - (void)setPreference:(id)value
358 forKey:(NSString *)key
359 group:(NSString *)group
360 object:(AIListObject *)object
362 NSMutableDictionary *prefDict = [self cachedPreferencesForGroup:group object:object];
367 [prefDict setObject:value forKey:key];
369 if([prefDict objectForKey:key]){
370 [prefDict removeObjectForKey:key];
376 //Update the preference cache with our changes
378 [self updateCachedPreferences:prefDict forGroup:group object:object];
379 [self informObserversOfChangedKey:key inGroup:group object:object];
384 //Retrieving Preferences ----------------------------------------------------------------
385 #pragma mark Retrieving Preferences
387 * @brief Retrieve a preference
389 - (id)preferenceForKey:(NSString *)key group:(NSString *)group
391 return([self preferenceForKey:key group:group objectIgnoringInheritance:nil]);
395 * @brief Retrieve an object specific preference with inheritance, ignoring defaults
397 * Should only be used within AIPreferenceController. See preferenceForKey:group:object: for details.
399 - (id)_noDefaultsPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
401 id result = [[self cachedPreferencesForGroup:group object:object] objectForKey:key];
403 //If there is no object specific preference, inherit the value from the object containing this one
404 if(!result && object){
405 return([self _noDefaultsPreferenceForKey:key group:group object:[object containingObject]]);
407 //If we have no object (either were passed no object initially or got here recursively) use defaults if necessary
413 * @brief Retrieve an object specific default preference with inheritance
415 - (id)defaultPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
417 //Don't use the defaults initially
418 id result = [[self cachedDefaultsForGroup:group object:object] objectForKey:key];
420 //If there is no object specific preference, inherit the value from the object containing this one
421 if(!result && object){
422 return([self defaultPreferenceForKey:key group:group object:[object containingObject]]);
424 //If we have no object (either were passed no object initially or got here recursively) use defaults if necessary
430 * @brief Retrieve an object specific preference with inheritance.
432 * Objects inherit from their containing objects, up to the global preference. If this entire tree has no preference
433 * defaults are searched starting with the object and continuing up to global.
435 - (id)preferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
437 //Don't use the defaults initially
438 id result = [self _noDefaultsPreferenceForKey:key group:group object:object];
440 //If no result, try defaults
441 if(!result) result = [self defaultPreferenceForKey:key group:group object:object];
447 * @brief Retrieve an object specific preference ignoring inheritance.
449 * If object is nil, this returns the global preference. Uses defaults only for the specified preference level,
450 * not inherited defaults, as expected.
452 - (id)preferenceForKey:(NSString *)key group:(NSString *)group objectIgnoringInheritance:(AIListObject *)object
454 //We are ignoring inheritance, so we can ignore inherited defaults, too, and use the cachedPreferencesWithDefaultsForGroup:object: dict
455 id result = [[self cachedPreferencesWithDefaultsForGroup:group object:object] objectForKey:key];
461 * @brief Retrieve all the preferences in a group
463 * @result A dictionary of preferences for the group, including default values as appropriate
465 - (NSDictionary *)preferencesForGroup:(NSString *)group
468 return([self cachedPreferencesWithDefaultsForGroup:group object:nil]);
471 //Defaults -------------------------------------------------------------------------------------------------------------
472 #pragma mark Defaults
474 * @brief Register a dictionary of defaults.
476 - (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group{
477 [self registerDefaults:defaultDict forGroup:group object:nil];
481 * @brief Register a dictionary of object-specific defaults.
483 - (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group object:(AIListObject *)object
485 NSMutableDictionary *targetDefaultsDict;
486 NSMutableDictionary *activeDefaultsCache;
487 NSMutableDictionary *actualDefaultsDict;
490 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
492 NSString *path = [object pathToPreferences];
493 NSString *uniqueID = [object internalObjectID];
494 cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
495 activeDefaultsCache = objectPrefWithDefaultsCache;
496 targetDefaultsDict = objectDefaults;
500 activeDefaultsCache = prefWithDefaultsCache;
501 targetDefaultsDict = defaults;
505 actualDefaultsDict = [targetDefaultsDict objectForKey:cacheKey];
506 if(!actualDefaultsDict) actualDefaultsDict = [NSMutableDictionary dictionary];
508 [actualDefaultsDict addEntriesFromDictionary:defaultDict];
509 [targetDefaultsDict setObject:actualDefaultsDict
512 //Now clear our current prefWithDefaults cache so it will be regenerated with these entries included on next call
513 [activeDefaultsCache removeObjectForKey:cacheKey];
517 * @brief Reset an advanced preference pane to default values
519 * This only works for preference panes, and only panes in the advanced preferences have a reset defaults button.
521 - (void)resetPreferencesInPane:(AIPreferencePane *)preferencePane
523 NSDictionary *allDefaults, *groupDefaults;
524 NSEnumerator *enumerator, *keyEnumerator;
525 NSString *group, *key;
527 [self delayPreferenceChangedNotifications:YES];
529 //Get the restorable prefs dictionary of the pref pane
530 allDefaults = [preferencePane restorablePreferences];
532 //They keys are preference groups, run through all of them
533 enumerator = [allDefaults keyEnumerator];
534 while(group = [enumerator nextObject]){
536 //Get the dictionary of keys for each group, and reset them all
537 groupDefaults = [allDefaults objectForKey:group];
538 keyEnumerator = [groupDefaults keyEnumerator];
539 while(key = [keyEnumerator nextObject]){
540 [[adium preferenceController] setPreference:[groupDefaults objectForKey:key]
546 [self delayPreferenceChangedNotifications:NO];
550 //Preference Cache -----------------------------------------------------------------------------------------------------
551 //We cache the preferences locally to avoid loading them each time we need a value
552 #pragma mark Preference Cache
554 * @brief Fetch cached preferences
556 * @param group The group
557 * @param object The object, or nil for global
559 - (NSMutableDictionary *)cachedPreferencesForGroup:(NSString *)group object:(AIListObject *)object
561 NSMutableDictionary *prefDict;
563 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
565 NSString *path = [object pathToPreferences];
566 NSString *uniqueID = [object internalObjectID];
567 NSString *cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
569 if(!(prefDict = [objectPrefCache objectForKey:cacheKey])){
570 prefDict = [NSMutableDictionary dictionaryAtPath:[userDirectory stringByAppendingPathComponent:path]
571 withName:[uniqueID safeFilenameString]
573 [objectPrefCache setObject:prefDict forKey:cacheKey];
577 if(!(prefDict = [prefCache objectForKey:group])){
578 prefDict = [NSMutableDictionary dictionaryAtPath:userDirectory
581 [prefCache setObject:prefDict forKey:group];
589 * @brief Return just the defaults for a specified group and object
591 * @param group The group
592 * @param object The object, or nil for global defaults
594 - (NSDictionary *)cachedDefaultsForGroup:(NSString *)group object:(AIListObject *)object
596 NSDictionary *sourceDefaultsDict;
599 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
601 NSString *path = [object pathToPreferences];
602 NSString *uniqueID = [object internalObjectID];
603 cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
604 sourceDefaultsDict = objectDefaults;
608 sourceDefaultsDict = defaults;
611 return([sourceDefaultsDict objectForKey:cacheKey]);
615 * @brief Return the result of taking the defaults and superceding them with any set preferences
617 * @param group The group
618 * @param object The object, or nil for global
620 - (NSDictionary *)cachedPreferencesWithDefaultsForGroup:(NSString *)group object:(AIListObject *)object
622 NSDictionary *prefWithDefaultsDict;
623 NSMutableDictionary *activeDefaultsCache;
624 NSDictionary *sourceDefaultsDict;
627 //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
629 NSString *path = [object pathToPreferences];
630 NSString *uniqueID = [object internalObjectID];
631 cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
632 activeDefaultsCache = objectPrefWithDefaultsCache;
633 sourceDefaultsDict = objectDefaults;
637 activeDefaultsCache = prefWithDefaultsCache;
638 sourceDefaultsDict = defaults;
642 if(!(prefWithDefaultsDict = [activeDefaultsCache objectForKey:cacheKey])){
643 NSDictionary *userPrefs = [self cachedPreferencesForGroup:group object:object];
644 NSDictionary *defaultPrefs = [sourceDefaultsDict objectForKey:cacheKey];
646 //Add the object's own preferences to the defaults dictionary to get a dict with the object's keys
647 //overriding the default keys
648 prefWithDefaultsDict = [[defaultPrefs mutableCopy] autorelease];
649 [(NSMutableDictionary *)prefWithDefaultsDict addEntriesFromDictionary:userPrefs];
651 //With no defaults, just use the userPrefs
652 prefWithDefaultsDict = userPrefs;
655 //And cache the result; the cache should be updated or cleared when the dictionary changes
656 [activeDefaultsCache setObject:prefWithDefaultsDict forKey:cacheKey];
659 return(prefWithDefaultsDict);
663 * @brief Write preference changes back to the cache and to disk
665 * @param prefDict The user-specified preferences (not including defaults)
666 * @param group The group
667 * @param object The object, or nil for global
669 - (void)updateCachedPreferences:(NSMutableDictionary *)prefDict forGroup:(NSString *)group object:(AIListObject *)object
672 NSString *path = [object pathToPreferences];
673 NSString *uniqueID = [object internalObjectID];
674 NSString *cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
676 //Store to our object pref cache
677 [objectPrefCache setObject:prefDict forKey:cacheKey];
679 /* Retain and autorelease the current prefDict so it remains viable for this run loop, since code should be
680 * able to depend upon a retrieved prefDict remaining usable without bracketing a set of
681 * -[AIPreferenceController setPreference:forKey:group:] calls with retain/release. */
682 [[[objectPrefWithDefaultsCache objectForKey:group] retain] autorelease];
684 //Clear the cache so the union of defaults and preferences will be recalculated on next call
685 [objectPrefWithDefaultsCache removeObjectForKey:cacheKey];
687 //Save the preference change immediately (Probably not the best idea?)
688 [prefDict writeToPath:[userDirectory stringByAppendingPathComponent:path]
689 withName:[uniqueID safeFilenameString]];
692 /* Retain and autorelease the current prefDict so it remains viable for this run loop, since code should be
693 * able to depend upon a retrieved prefDict remaining usable without bracketing a set of
694 * -[AIPreferenceController setPreference:forKey:group:] calls with retain/release. */
695 [[[prefWithDefaultsCache objectForKey:group] retain] autorelease];
697 //Clear the cache so the union of defaults and preferences will be recalculated on next call
698 [prefWithDefaultsCache removeObjectForKey:group];
700 //Save the preference change immediately (Probably not the best idea?)
701 [prefDict writeToPath:userDirectory withName:group];
706 //Default download locaiton --------------------------------------------------------------------------------------------
707 #pragma mark Default download location
709 * @brief Get the default download location
711 * This will use an Adium-specific preference if set, or the systemwide download location if not
713 * @result A full path to the download location
715 - (NSString *) userPreferredDownloadFolder
717 NSString *userPreferredDownloadFolder;
719 userPreferredDownloadFolder = [self preferenceForKey:@"UserPreferredDownloadFolder"
720 group:PREF_GROUP_GENERAL];
722 if(!userPreferredDownloadFolder){
723 OSStatus err = noErr;
724 ICInstance inst = NULL;
726 long length = kICFileSpecHeaderSize;
730 memset( path, 0, 1024 ); //clear path's memory range
732 if((err = ICStart(&inst, 'AdiM')) == noErr){
733 ICGetPref( inst, kICDownloadFolder, NULL, &folder, &length );
736 if(((err = FSpMakeFSRef(&folder.fss, &ref)) == noErr) &&
737 ((err = FSRefMakePath(&ref, (unsigned char *)path, 1024)) == noErr) &&
738 ((path != NULL) && (strlen(path) > 0))){
739 userPreferredDownloadFolder = [NSString stringWithUTF8String:path];
744 if(!userPreferredDownloadFolder){
745 userPreferredDownloadFolder = @"~/Desktop";
748 userPreferredDownloadFolder = [userPreferredDownloadFolder stringByExpandingTildeInPath];
750 /* If we can't write to the specified folder, fall back to the desktop and then to the home directory;
751 * if neither are writable the user has worse problems then an IM download to worry about.
753 if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
754 NSString *originalFolder = userPreferredDownloadFolder;
756 userPreferredDownloadFolder = [@"~/Desktop" stringByExpandingTildeInPath];
758 if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
759 userPreferredDownloadFolder = NSHomeDirectory();
762 NSLog(@"Could not obtain write access for %@; defaulting to %@",
764 userPreferredDownloadFolder);
767 return userPreferredDownloadFolder;
771 * @brief Set the location Adium should use for saving files
773 * @param A path to an existing folder
775 - (void)setUserPreferredDownloadFolder:(NSString *)path
777 [self setPreference:[path stringByAbbreviatingWithTildeInPath]
778 forKey:@"UserPreferredDownloadFolder"
779 group:PREF_GROUP_GENERAL];