Merged [15040]: Trying some magic: 5 seconds after the last unreachable host is repor...
[adiumx.git] / Source / AIPreferenceController.m
blobfa7f914936b5e0f6d78d8700dfdfaa6219040829
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
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;
39 @end
41 /*!
42  * @class AIPreferenceController
43  * @brief Preference Controller
44  *
45  * Handles loading and saving preferences, default preferences, and preference changed notifications
46  */
47 @implementation AIPreferenceController
49 /*!
50  * @brief Initialize
51  */
52 - (void)initController
54     //
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;
69         //
70         userDirectory = [[[adium loginController] userDirectory] retain];
71         
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]];
75         
76         //Register our default preferences
77     [self registerDefaults:[NSDictionary dictionaryNamed:PREFS_DEFAULT_PREFS forClass:[self class]] forGroup:PREF_GROUP_GENERAL];
80 /*!
81  * @brief Finish initialization
82  *
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.
85  */
86 - (void)finishIniting
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
95                                                                                                                  target:self
96                                                                                                 settingSelector:@selector(setImage:)
97                                                                                                         itemContent:[NSImage imageNamed:@"pref-general"
98                                                                                                                                                    forClass:[self class]]
99                                                                                                                  action:@selector(showPreferenceWindow:)
100                                                                                                                    menu:nil];
101     [[adium toolbarController] registerToolbarItem:toolbarItem forToolbarType:@"General"];
105  * @brief Begin closing
106  * 
107  * We must close the preference window before plugins and the other controllers are unloaded.
108  */
109 - (void)beginClosing
111     [AIPreferenceWindowController closePreferenceWindow];
115  * @brief Close
116  */
117 - (void)closeController
119     //Preferences are (always) saved as they're modified, so there's no need to save them here.
123  * @brief Deallocate
124  */
125 - (void)dealloc
127     [delayedNotificationGroups release]; delayedNotificationGroups = nil;
128     [paneArray release]; paneArray = nil;
129     [prefCache release]; prefCache = nil;
130         [objectPrefCache release]; objectPrefCache = nil;
131     [super dealloc];
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.
139  */
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;
146         
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);
152         
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];
156     }
160 //Preference Window ----------------------------------------------------------------------------------------------------
161 #pragma mark Preference Window
163  * @brief Show the preference window
164  */
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
176  */
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
187  */
188 - (void)openPreferencesToAdvancedPane:(NSString *)paneName
190         [AIPreferenceWindowController openPreferenceWindowToAdvancedPane:paneName];
194  * @brief Add a view to the preferences
195  */
196 - (void)addPreferencePane:(AIPreferencePane *)inPane
198     [paneArray addObject:inPane];
202  * @brief Returns all currently available preference panes
203  */
204 - (NSArray *)paneArray
206     return(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
219  */
220 - (void)registerPreferenceObserver:(id)observer forGroup:(NSString *)group
222         NSMutableArray  *groupObservers;
223         
224         NSParameterAssert([observer respondsToSelector:@selector(preferencesChangedForGroup:key:object:preferenceDict:firstTime:)]);
225         
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];
231         }
233         //Add our new observer
234 #ifdef TRACK_PREFERENCE_OBSERVERS
235         NSLog(@"adding observer: %@", observer);
236 #endif
237         [groupObservers addObject:[NSValue valueWithNonretainedObject:observer]];
238         
239         //Blanket change notification for initialization
240         [observer preferencesChangedForGroup:group
241                                                                          key:nil
242                                                                   object:nil
243                                                   preferenceDict:[self cachedPreferencesWithDefaultsForGroup:group object:nil]
244                                                            firstTime:YES];
248  * @brief Unregister a preference observer
249  */
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]);
259 #endif
260                 [observerArray removeObject:observerValue];
261 #ifdef TRACK_PREFERENCE_OBSERVERS
262                 NSLog(@"...removed");
263 #endif
264         }
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.
276  * @param key The key
277  * @param group The group
278  * @param object The object, or nil if global
279  */
280 - (void)informObserversOfChangedKey:(NSString *)key inGroup:(NSString *)group object:(AIListObject *)object
282         if(!object && preferenceChangeDelays > 0){
283         [delayedNotificationGroups addObject:group];
284     }else{
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);
293 #endif
295                         [observer preferencesChangedForGroup:group
296                                                                                          key:key
297                                                                                   object:object
298                                                                   preferenceDict:preferenceDict
299                                                                            firstTime:NO];
300                 }
301     }
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.
309  */
310 - (void)delayPreferenceChangedNotifications:(BOOL)inDelay
312         if(inDelay){
313                 preferenceChangeDelays++;
314         }else{
315                 preferenceChangeDelays--;
316         }
317         
318         //If changes are no longer delayed, save and notify of all preferences modified while delayed
319     if(!preferenceChangeDelays){
320                 NSEnumerator    *enumerator = [delayedNotificationGroups objectEnumerator];
321                 NSString        *group;
322                 
323                 [[adium contactController] delayListObjectNotifications];
325                 while(group = [enumerator nextObject]){
326                         [self informObserversOfChangedKey:nil inGroup:group object:nil];
327                 }
329                 [[adium contactController] endListObjectNotificationsDelay];
330                 
331                 [delayedNotificationGroups removeAllObjects];
332     }
335     
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
346  */
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:
356  */
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];
363         BOOL                            changed = YES;
365     //Set the new value
366     if(value != nil){
367         [prefDict setObject:value forKey:key];
368     }else{
369                 if([prefDict objectForKey:key]){
370                         [prefDict removeObjectForKey:key];
371                 }else{
372                         changed = NO;
373                 }
374     }
376         //Update the preference cache with our changes
377         if(changed){
378                 [self updateCachedPreferences:prefDict forGroup:group object:object];
379                 [self informObserversOfChangedKey:key inGroup:group object:object];
380         }
384 //Retrieving Preferences ----------------------------------------------------------------
385 #pragma mark Retrieving Preferences
387  * @brief Retrieve a preference
388  */
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.
398  */
399 - (id)_noDefaultsPreferenceForKey:(NSString *)key group:(NSString *)group object:(AIListObject *)object
401         id      result = [[self cachedPreferencesForGroup:group object:object] objectForKey:key];
402         
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]]);
406         }else{
407                 //If we have no object (either were passed no object initially or got here recursively) use defaults if necessary
408                 return(result);
409         }
413  * @brief Retrieve an object specific default preference with inheritance
414  */
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];
419         
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]]);
423         }else{
424                 //If we have no object (either were passed no object initially or got here recursively) use defaults if necessary
425                 return(result);
426         }       
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.
434  */
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];
439         
440         //If no result, try defaults
441         if(!result) result = [self defaultPreferenceForKey:key group:group object:object];
442         
443         return(result);
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.
451  */
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];
456         
457         return(result);
461  * @brief Retrieve all the preferences in a group
463  * @result A dictionary of preferences for the group, including default values as appropriate
464  */
465 - (NSDictionary *)preferencesForGroup:(NSString *)group
467         
468     return([self cachedPreferencesWithDefaultsForGroup:group object:nil]);
471 //Defaults -------------------------------------------------------------------------------------------------------------
472 #pragma mark Defaults
474  * @brief Register a dictionary of defaults.
475  */
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.
482  */
483 - (void)registerDefaults:(NSDictionary *)defaultDict forGroup:(NSString *)group object:(AIListObject *)object
485         NSMutableDictionary     *targetDefaultsDict;
486         NSMutableDictionary     *activeDefaultsCache;
487         NSMutableDictionary *actualDefaultsDict;
488         NSString                        *cacheKey;      
489         
490         //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
491         if(object){
492                 NSString        *path = [object pathToPreferences];
493                 NSString        *uniqueID = [object internalObjectID];
494                 cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
495                 activeDefaultsCache = objectPrefWithDefaultsCache;
496                 targetDefaultsDict = objectDefaults;
497                 
498         }else{
499                 cacheKey = group;
500                 activeDefaultsCache = prefWithDefaultsCache;
501                 targetDefaultsDict = defaults;
502                 
503         }
504         
505         actualDefaultsDict = [targetDefaultsDict objectForKey:cacheKey];
506         if(!actualDefaultsDict) actualDefaultsDict = [NSMutableDictionary dictionary];
507         
508         [actualDefaultsDict addEntriesFromDictionary:defaultDict];
509         [targetDefaultsDict setObject:actualDefaultsDict
510                                                    forKey:cacheKey];
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.
520  */
521 - (void)resetPreferencesInPane:(AIPreferencePane *)preferencePane
523         NSDictionary    *allDefaults, *groupDefaults;
524         NSEnumerator    *enumerator, *keyEnumerator;
525         NSString                *group, *key;
526         
527         [self delayPreferenceChangedNotifications:YES];
528         
529         //Get the restorable prefs dictionary of the pref pane
530         allDefaults = [preferencePane restorablePreferences];
531         
532         //They keys are preference groups, run through all of them
533         enumerator = [allDefaults keyEnumerator];
534         while(group = [enumerator nextObject]){
535                 
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]
541                                                                                                  forKey:key
542                                                                                                   group:group];
543                 }
544         }
545         
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
558  */
559 - (NSMutableDictionary *)cachedPreferencesForGroup:(NSString *)group object:(AIListObject *)object
561         NSMutableDictionary     *prefDict;
562         
563         //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
564         if(object){
565                 NSString        *path = [object pathToPreferences];
566                 NSString        *uniqueID = [object internalObjectID];
567                 NSString        *cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
568                 
569                 if(!(prefDict = [objectPrefCache objectForKey:cacheKey])){
570                         prefDict = [NSMutableDictionary dictionaryAtPath:[userDirectory stringByAppendingPathComponent:path]
571                                                                                                         withName:[uniqueID safeFilenameString]
572                                                                                                           create:YES];
573                         [objectPrefCache setObject:prefDict forKey:cacheKey];
574                 }
576         }else{
577                 if(!(prefDict = [prefCache objectForKey:group])){
578                         prefDict = [NSMutableDictionary dictionaryAtPath:userDirectory
579                                                                                                         withName:group
580                                                                                                           create:YES];
581                         [prefCache setObject:prefDict forKey:group];
582                 }
583         }
584         
585         return(prefDict);
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
593  */
594 - (NSDictionary *)cachedDefaultsForGroup:(NSString *)group object:(AIListObject *)object
596         NSDictionary            *sourceDefaultsDict;
597         NSString                        *cacheKey;
599         //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
600         if(object){
601                 NSString        *path = [object pathToPreferences];
602                 NSString        *uniqueID = [object internalObjectID];
603                 cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
604                 sourceDefaultsDict = objectDefaults;
605                 
606         }else{
607                 cacheKey = group;
608                 sourceDefaultsDict = defaults;
609         }
610         
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
619  */
620 - (NSDictionary *)cachedPreferencesWithDefaultsForGroup:(NSString *)group object:(AIListObject *)object
622         NSDictionary            *prefWithDefaultsDict;
623         NSMutableDictionary     *activeDefaultsCache;
624         NSDictionary            *sourceDefaultsDict;
625         NSString                        *cacheKey;
626         
627         //Object specific preferences are stored by path and objectID, while regular preferences are stored by group.
628         if(object){
629                 NSString        *path = [object pathToPreferences];
630                 NSString        *uniqueID = [object internalObjectID];
631                 cacheKey = [NSString stringWithFormat:@"%@:%@", path, uniqueID];
632                 activeDefaultsCache = objectPrefWithDefaultsCache;
633                 sourceDefaultsDict = objectDefaults;
635         }else{
636                 cacheKey = group;
637                 activeDefaultsCache = prefWithDefaultsCache;
638                 sourceDefaultsDict = defaults;
640         }
641         
642         if(!(prefWithDefaultsDict = [activeDefaultsCache objectForKey:cacheKey])){              
643                 NSDictionary    *userPrefs = [self cachedPreferencesForGroup:group object:object];
644                 NSDictionary    *defaultPrefs = [sourceDefaultsDict objectForKey:cacheKey];
645                 if(defaultPrefs){
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];
650                 }else{
651                         //With no defaults, just use the userPrefs
652                         prefWithDefaultsDict = userPrefs;
653                 }
654                 
655                 //And cache the result; the cache should be updated or cleared when the dictionary changes
656                 [activeDefaultsCache setObject:prefWithDefaultsDict forKey:cacheKey];
657         }
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
668  */
669 - (void)updateCachedPreferences:(NSMutableDictionary *)prefDict forGroup:(NSString *)group object:(AIListObject *)object
671         if(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];
678                 
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]];
691         }else{
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];
696                 
697                 //Clear the cache so the union of defaults and preferences will be recalculated on next call
698                 [prefWithDefaultsCache removeObjectForKey:group];
699                 
700                 //Save the preference change immediately (Probably not the best idea?)
701                 [prefDict writeToPath:userDirectory withName:group];
702                 
703         }
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
714  */
715 - (NSString *) userPreferredDownloadFolder
717         NSString        *userPreferredDownloadFolder;
718         
719         userPreferredDownloadFolder = [self preferenceForKey:@"UserPreferredDownloadFolder"
720                                                                                                    group:PREF_GROUP_GENERAL];
721         
722         if(!userPreferredDownloadFolder){
723                 OSStatus                err = noErr;
724                 ICInstance              inst = NULL;
725                 ICFileSpec              folder;
726                 long                    length = kICFileSpecHeaderSize;
727                 FSRef                   ref;
728                 char                    path[1024];
729                 
730                 memset( path, 0, 1024 ); //clear path's memory range
731                 
732                 if((err = ICStart(&inst, 'AdiM')) == noErr){
733                         ICGetPref( inst, kICDownloadFolder, NULL, &folder, &length );
734                         ICStop( inst );
735                         
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];
740                         }
741                 }
742         }
743         
744         if(!userPreferredDownloadFolder){
745                 userPreferredDownloadFolder = @"~/Desktop";
746         }
748         userPreferredDownloadFolder = [userPreferredDownloadFolder stringByExpandingTildeInPath];
749         
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.
752          */
753         if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
754                 NSString *originalFolder = userPreferredDownloadFolder;
756                 userPreferredDownloadFolder = [@"~/Desktop" stringByExpandingTildeInPath];
758                 if (![[NSFileManager defaultManager] isWritableFileAtPath:userPreferredDownloadFolder]) {
759                         userPreferredDownloadFolder = NSHomeDirectory();
760                 }
762                 NSLog(@"Could not obtain write access for %@; defaulting to %@",
763                           originalFolder,
764                           userPreferredDownloadFolder);
765         }
767         return userPreferredDownloadFolder;
771  * @brief Set the location Adium should use for saving files
773  * @param A path to an existing folder
774  */
775 - (void)setUserPreferredDownloadFolder:(NSString *)path
777         [self setPreference:[path stringByAbbreviatingWithTildeInPath]
778                                  forKey:@"UserPreferredDownloadFolder"
779                                   group:PREF_GROUP_GENERAL];
783 @end