adium/adium
Clone
Summary
Browse
Changes
Graph
Updated Sparkle to 1.17.0, to fix a problem when copying broken symlinks.
adium-1.5.10.4
2017-04-23, Thijs Alkemade
5883c460b8cb
Updated Sparkle to 1.17.0, to fix a problem when copying broken symlinks.
/*
* Adium is the legal property of its developers, whose names are listed in the copyright file included
* with this source distribution.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import "AIPreferenceContainer.h"
#import "AIPreferenceController.h"
#import <Adium/AIListObject.h>
#import <Adium/AILoginControllerProtocol.h>
#import <AIUtilities/AIDictionaryAdditions.h>
#import <AIUtilities/AIStringAdditions.h>
#import <Adium/AIAccount.h>
@interface
AIPreferenceContainer
()
-
(
id
)
initForGroup
:
(
NSString
*
)
inGroup
object
:
(
AIListObject
*
)
inObject
;
-
(
void
)
save
;
@property
(
readonly
,
nonatomic
)
NSMutableDictionary
*
prefs
;
-
(
void
)
loadGlobalPrefs
;
//Lazily sets up our pref dict if needed
-
(
void
)
setPrefValue:
(
id
)
val
forKey:
(
id
)
key
;
@end
#define SAVE_OBJECT_PREFS_DELAY 10.0
/* XXX Remove me */
#ifdef DEBUG_BUILD
#define PREFERENCE_CONTAINER_DEBUG
#endif
static
NSMutableDictionary
*
objectPrefs
=
nil
;
static
NSTimer
*
timer_savingOfObjectCache
=
nil
;
static
NSMutableDictionary
*
accountPrefs
=
nil
;
static
NSTimer
*
timer_savingOfAccountCache
=
nil
;
/*!
* @brief Preference Container
*
* A single AIPreferenceContainer instance provides read/write access preferences to a specific preference group, either
* for the global preferences or for a specific object.
*
* All contacts share a single plist on-disk, loaded into a single mutable dictionary in-memory, objectPrefs.
* All accounts share a single plist on-disk, loaded into a single mutable dictionary in-memory, accountPrefs.
* These global dictionaries provide per-object preference dictionaries, keyed by the object's internalObjectID.
*
* Individual instances of AIPreferenceContainer make use of this shared store. Saving of changes is batched for all changes made during a
* SAVE_OBJECT_PREFS_DELAY interval across all instances of AIPreferenceContainer for a given global dictionary. Because creating
* the data representation of a large dictionary and writing it out can be time-consuming (certainly less than a second, but still long
* enough to cause a perceptible delay for a user actively typing or interacting with Adium), saving is performed on a thread.
*/
@implementation
AIPreferenceContainer
+
(
AIPreferenceContainer
*
)
preferenceContainerForGroup:
(
NSString
*
)
inGroup
object:
(
AIListObject
*
)
inObject
{
return
[[[
self
alloc
]
initForGroup
:
inGroup
object
:
inObject
]
autorelease
];
}
+
(
void
)
preferenceControllerWillClose
{
//If a save of the object prefs is pending, perform it immediately since we are quitting
if
(
timer_savingOfObjectCache
)
{
[
objectPrefs
writeToPath
:
[
adium
.
loginController
userDirectory
]
withName
:
@"ByObjectPrefs"
];
/* There's no guarantee that 'will close' is called in the same run loop as the actual program termination.
* We've done our final save, though; don't let the timer fire again.
*/
[
timer_savingOfObjectCache
invalidate
];
[
timer_savingOfObjectCache
release
];
timer_savingOfObjectCache
=
nil
;
}
//If a save of the account prefs is pending, perform it immediately since we are quitting
if
(
timer_savingOfAccountCache
)
{
[
accountPrefs
writeToPath
:
[
adium
.
loginController
userDirectory
]
withName
:
@"AccountPrefs"
];
/* There's no guarantee that 'will close' is called in the same run loop as the actual program termination.
* We've done our final save, though; don't let the timer fire again.
*/
[
timer_savingOfAccountCache
invalidate
];
[
timer_savingOfAccountCache
release
];
timer_savingOfObjectCache
=
nil
;
}
}
-
(
id
)
initForGroup:
(
NSString
*
)
inGroup
object:
(
AIListObject
*
)
inObject
{
if
((
self
=
[
super
init
]))
{
group
=
[
inGroup
retain
];
object
=
[
inObject
retain
];
if
(
object
)
{
if
([
object
isKindOfClass
:
[
AIAccount
class
]])
{
myGlobalPrefs
=
&
accountPrefs
;
myTimerForSavingGlobalPrefs
=
&
timer_savingOfAccountCache
;
globalPrefsName
=
@"AccountPrefs"
;
}
else
{
myGlobalPrefs
=
&
objectPrefs
;
myTimerForSavingGlobalPrefs
=
&
timer_savingOfObjectCache
;
globalPrefsName
=
@"ByObjectPrefs"
;
}
}
}
return
self
;
}
-
(
void
)
dealloc
{
[
defaults
release
];
defaults
=
nil
;
[
group
release
];
[
object
release
];
[
globalPrefsName
release
];
globalPrefsName
=
nil
;
[
super
dealloc
];
}
+
(
BOOL
)
automaticallyNotifiesObserversForKey:
(
NSString
*
)
theKey
{
return
NO
;
}
#pragma mark Defaults
@synthesize
defaults
;
/*!
* @brief Register defaults
*
* These defaults will be added to any existing defaults; if there is overlap between keys, the new key-value pair will be used.
*/
-
(
void
)
registerDefaults:
(
NSDictionary
*
)
inDefaults
{
if
(
!
defaults
)
defaults
=
[[
NSMutableDictionary
alloc
]
init
];
[
defaults
addEntriesFromDictionary
:
inDefaults
];
//Clear the cached defaults dictionary so it will be recreated as needed
[
prefsWithDefaults
release
];
prefsWithDefaults
=
nil
;
}
#pragma mark Get and set
-
(
void
)
loadGlobalPrefs
{
NSAssert
(
*
myGlobalPrefs
==
nil
,
@"Attempting to load global prefs when they're already loaded"
);
NSString
*
objectPrefsPath
=
[[
adium
.
loginController
.
userDirectory
stringByAppendingPathComponent
:
globalPrefsName
]
stringByAppendingPathExtension
:
@"plist"
];
NSString
*
errorString
=
nil
;
NSError
*
error
=
nil
;
NSData
*
data
=
[
NSData
dataWithContentsOfFile
:
objectPrefsPath
options
:
NSUncachedRead
error
:
&
error
];
if
(
error
)
{
NSLog
(
@"Error reading data for preferences file %@: %@ (%@ %ld: %@)"
,
objectPrefsPath
,
error
,
[
error
domain
],
(
long
)[
error
code
],
[
error
userInfo
]);
AILogWithSignature
(
@"Error reading data for preferences file %@: %@ (%@ %i: %@)"
,
objectPrefsPath
,
error
,
[
error
domain
],
[
error
code
],
[
error
userInfo
]);
if
([[
NSFileManager
defaultManager
]
fileExistsAtPath
:
objectPrefsPath
])
{
while
(
!
data
)
{
AILogWithSignature
(
@"Preferences file %@'s attributes: %@. Reattempting to read the file..."
,
globalPrefsName
,
[[
NSFileManager
defaultManager
]
attributesOfItemAtPath
:
objectPrefsPath
error
:
NULL
]);
data
=
[
NSData
dataWithContentsOfFile
:
objectPrefsPath
options
:
NSUncachedRead
error
:
&
error
];
if
(
error
)
{
AILogWithSignature
(
@"Error reading data for preferences file %@: %@ (%@ %i: %@)"
,
objectPrefsPath
,
error
,
[
error
domain
],
[
error
code
],
[
error
userInfo
]);
}
}
}
}
//We want to load a mutable dictioanry of mutable dictionaries.
if
(
data
)
{
*
myGlobalPrefs
=
[[
NSPropertyListSerialization
propertyListFromData
:
data
mutabilityOption
:
NSPropertyListMutableContainers
format
:
NULL
errorDescription
:
&
errorString
]
retain
];
}
/* Log any error */
if
(
errorString
)
{
NSLog
(
@"Error reading preferences file %@: %@"
,
objectPrefsPath
,
errorString
);
AILogWithSignature
(
@"Error reading preferences file %@: %@"
,
objectPrefsPath
,
errorString
);
}
#ifdef PREFERENCE_CONTAINER_DEBUG
AILogWithSignature
(
@"I read in %@ with %i items"
,
globalPrefsName
,
[
*
myGlobalPrefs
count
]);
#endif
/* If we don't get a dictionary, create a new one */
if
(
!*
myGlobalPrefs
)
{
/* This wouldn't be an error if this were a new Adium installation; the below is temporary debug logging. */
NSLog
(
@"WARNING: Unable to parse preference file %@ (data was %@)"
,
objectPrefsPath
,
data
);
AILogWithSignature
(
@"WARNING: Unable to parse preference file %@ (data was %@)"
,
objectPrefsPath
,
data
);
*
myGlobalPrefs
=
[[
NSMutableDictionary
alloc
]
init
];
}
}
-
(
void
)
setPrefValue:
(
id
)
value
forKey:
(
id
)
key
{
NSAssert
([
NSThread
currentThread
]
==
[
NSThread
mainThread
],
@"AIPreferenceContainer is not threadsafe! Don't set prefs from non-main threads"
);
NSMutableDictionary
*
prefDict
=
self
.
prefs
;
if
(
object
&&
!
prefDict
)
{
//For compatibility with having loaded individual object prefs from previous version of Adium, we key by the safe filename string
NSString
*
globalPrefsKey
=
[
object
.
internalObjectID
safeFilenameString
];
prefs
=
[[
NSMutableDictionary
alloc
]
init
];
[
*
myGlobalPrefs
setObject
:
prefs
forKey
:
globalPrefsKey
];
}
[
self
.
prefs
setValue
:
value
forKey
:
key
];
}
/*!
* @brief Return a dictionary of our preferences, loading it from disk as needed
*/
-
(
NSMutableDictionary
*
)
prefs
{
if
(
!
prefs
)
{
NSString
*
userDirectory
=
adium
.
loginController
.
userDirectory
;
if
(
object
)
{
if
(
!
(
*
myGlobalPrefs
))
[
self
loadGlobalPrefs
];
//For compatibility with having loaded individual object prefs from previous version of Adium, we key by the safe filename string
NSString
*
globalPrefsKey
=
[
object
.
internalObjectID
safeFilenameString
];
prefs
=
[[
*
myGlobalPrefs
objectForKey
:
globalPrefsKey
]
retain
];
}
else
{
prefs
=
[[
NSMutableDictionary
dictionaryAtPath
:
userDirectory
withName
:
group
create
:
YES
]
retain
];
}
}
return
prefs
;
}
/*!
* @brief Return a dictionary of preferences and defaults, appropriately merged together
*/
-
(
NSDictionary
*
)
dictionary
{
if
(
!
prefsWithDefaults
)
{
//Add our own preferences to the defaults dictionary to get a dict with the set keys overriding the default keys
if
(
defaults
)
{
prefsWithDefaults
=
[
defaults
mutableCopy
];
NSDictionary
*
prefDict
=
self
.
prefs
;
if
(
prefDict
)
[
prefsWithDefaults
addEntriesFromDictionary
:
prefDict
];
}
else
{
prefsWithDefaults
=
[
self
.
prefs
retain
];
}
}
return
prefsWithDefaults
;
}
/*!
* @brief Set value for key
*
* This sets and saves a preference for the given key
*/
-
(
void
)
setValue:
(
id
)
value
forKey:
(
NSString
*
)
key
{
BOOL
valueChanged
=
YES
;
/* Comparing pointers, numbers, and strings is far cheapear than writing out to disk;
* check to see if we don't need to change anything at all. However, we still want to post notifications
* for observers that we were set.
*/
id
oldValue
=
[
self
valueForKey
:
key
];
if
((
!
value
&&
!
oldValue
)
||
(
value
&&
oldValue
&&
[
value
isEqual
:
oldValue
]))
valueChanged
=
NO
;
[
self
willChangeValueForKey
:
key
];
if
(
valueChanged
)
{
//Clear the cached defaults dictionary so it will be recreated as needed
if
(
value
)
[
prefsWithDefaults
setValue
:
value
forKey
:
key
];
else
{
[
prefsWithDefaults
autorelease
];
prefsWithDefaults
=
nil
;
}
[
self
setPrefValue
:
value
forKey
:
key
];
}
[
self
didChangeValueForKey
:
key
];
//Now tell the preference controller
if
(
!
preferenceChangeDelays
)
{
[
adium
.
preferenceController
informObserversOfChangedKey
:
key
inGroup
:
group
object
:
object
];
if
(
valueChanged
)
[
self
save
];
}
}
-
(
id
)
valueForKey:
(
NSString
*
)
key
{
return
[[
self
dictionary
]
valueForKey
:
key
];
}
/*!
* @brief Get a preference, possibly ignoring the defaults
*
* @param key The key
* @param ignoreDefaults If YES, the preferences are accessed diretly, without including the default values
*/
-
(
id
)
valueForKey:
(
NSString
*
)
key
ignoringDefaults:
(
BOOL
)
ignoreDefaults
{
if
(
ignoreDefaults
)
return
[
self
.
prefs
valueForKey
:
key
];
else
return
[
self
valueForKey
:
key
];
}
-
(
id
)
defaultValueForKey:
(
NSString
*
)
key
{
return
[[
self
defaults
]
valueForKey
:
key
];
}
/*!
* @brief Set all preferences for this group
*
* All existing preferences are removed for this group; the passed dictionary becomes the new preferences
*/
-
(
void
)
setPreferences:
(
NSDictionary
*
)
inPreferences
{
[
self
setPreferenceChangedNotificationsEnabled
:
NO
];
[
self
setValuesForKeysWithDictionary
:
inPreferences
];
[
self
setPreferenceChangedNotificationsEnabled
:
YES
];
}
-
(
void
)
setPreferenceChangedNotificationsEnabled:
(
BOOL
)
inEnabled
{
if
(
inEnabled
)
preferenceChangeDelays
--
;
else
preferenceChangeDelays
++
;
if
(
preferenceChangeDelays
==
0
)
{
[
adium
.
preferenceController
informObserversOfChangedKey
:
nil
inGroup
:
group
object
:
object
];
[
self
save
];
}
}
#pragma mark Saving
-
(
void
)
performObjectPrefsSave:
(
NSTimer
*
)
inTimer
{
NSDictionary
*
immutablePrefsToWrite
=
[[[
NSDictionary
alloc
]
initWithDictionary
:
inTimer
.
userInfo
copyItems
:
YES
]
autorelease
];
/* Data verification */
#ifdef PREFERENCE_CONTAINER_DEBUG
// {
// NSData *data = [NSData dataWithContentsOfFile:[adium.loginController.userDirectory stringByAppendingPathComponent:[globalPrefsName stringByAppendingPathExtension:@"plist"]]];
// NSString *errorString = nil;
// NSDictionary *theDict = [NSPropertyListSerialization propertyListFromData:data
// mutabilityOption:NSPropertyListMutableContainers
// format:NULL
// errorDescription:&errorString];
// if (theDict && [theDict count] > 0 && [immutablePrefsToWrite count] == 0)
// {
// NSLog(@"Writing out an empty ByObjectPrefs when we have an existing non-empty one!");
// *((int*)0xdeadbeef) = 42;
// }
// }
#endif
#warning figure this out
if
([
immutablePrefsToWrite
count
]
>
0
)
{
[
immutablePrefsToWrite
asyncWriteToPath
:
adium
.
loginController
.
userDirectory
withName
:
globalPrefsName
];
}
else
{
NSLog
(
@"Attempted to write an empty ByObject Prefs. Uh oh!"
);
*
((
int
*
)
0xdeadbeef
)
=
42
;
}
if
(
inTimer
==
timer_savingOfObjectCache
)
{
[
timer_savingOfObjectCache
release
];
timer_savingOfObjectCache
=
nil
;
}
else
if
(
inTimer
==
timer_savingOfAccountCache
)
{
[
timer_savingOfAccountCache
release
];
timer_savingOfAccountCache
=
nil
;
}
}
/*!
* @brief Save to disk
*/
-
(
void
)
save
{
if
(
object
)
{
//For an object's pref changes, batch all changes in a SAVE_OBJECT_PREFS_DELAY second period. We'll force an immediate save if Adium quits.
if
(
*
myTimerForSavingGlobalPrefs
)
{
[
*
myTimerForSavingGlobalPrefs
setFireDate
:
[
NSDate
dateWithTimeIntervalSinceNow
:
SAVE_OBJECT_PREFS_DELAY
]];
}
else
{
#ifdef PREFERENCE_CONTAINER_DEBUG
// This shouldn't be happening at all.
if
(
!*
myGlobalPrefs
)
{
NSLog
(
@"Attempted to detach to save for %@ [%@], but info was nil."
,
self
,
globalPrefsName
);
AILogWithSignature
(
@"Attempted to detach to save for %@ [%@], but info was nil."
,
self
,
globalPrefsName
);
}
#endif
*
myTimerForSavingGlobalPrefs
=
[[
NSTimer
scheduledTimerWithTimeInterval
:
SAVE_OBJECT_PREFS_DELAY
target
:
self
selector
:
@selector
(
performObjectPrefsSave
:
)
userInfo
:
*
myGlobalPrefs
repeats
:
NO
]
retain
];
}
}
else
{
//Save the preference change immediately
[
self
.
prefs
writeToPath
:
adium
.
loginController
.
userDirectory
withName
:
group
];
}
}
-
(
void
)
setGroup:
(
NSString
*
)
inGroup
{
if
(
group
!=
inGroup
)
{
[
group
release
];
group
=
[
inGroup
retain
];
}
}
#pragma mark Debug
-
(
NSString
*
)
description
{
return
[
NSString
stringWithFormat
:
@"<%@ %p: Group %@, object %@>"
,
NSStringFromClass
([
self
class
]),
self
,
group
,
object
];
}
@end