CoreAudio/PublicUtility/CASettingsStorage.cpp
/* |
File: CASettingsStorage.cpp |
Abstract: CASettingsStorage.h |
Version: 1.1 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
*/ |
//================================================================================================== |
// Includes |
//================================================================================================== |
// Self Include |
#include "CASettingsStorage.h" |
// PublicUtility Includes |
#include "CAAutoDisposer.h" |
#include "CACFArray.h" |
#include "CACFData.h" |
#include "CACFDictionary.h" |
#include "CACFDistributedNotification.h" |
#include "CACFNumber.h" |
// Stamdard Library Includes |
#include <string.h> |
#include <sys/fcntl.h> |
//================================================================================================== |
// CASettingsStorage |
//================================================================================================== |
CASettingsStorage::CASettingsStorage(const char* inSettingsFilePath, mode_t inSettingsFileAccessMode, CFPropertyListFormat inSettingsCacheFormat, bool inIsSingleProcessOnly, bool inIsReadOnly) |
: |
mSettingsFilePath(NULL), |
mSettingsFileAccessMode(inSettingsFileAccessMode), |
mSettingsCache(CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)), |
mSettingsCacheFormat(inSettingsCacheFormat), |
mSettingsCacheTime(), |
mSettingsCacheForceRefresh(true), |
mIsSingleProcessOnly(inIsSingleProcessOnly), |
mIsReadOnly(inIsReadOnly) |
{ |
size_t theLength = strlen(inSettingsFilePath); |
mSettingsFilePath = new char[theLength + 2]; |
strlcpy(mSettingsFilePath, inSettingsFilePath, theLength + 2); |
mSettingsCacheTime.tv_sec = 0; |
mSettingsCacheTime.tv_nsec = 0; |
mSettingsCacheForceRefresh = true; |
} |
CASettingsStorage::~CASettingsStorage() |
{ |
delete[] mSettingsFilePath; |
if(mSettingsCache != NULL) |
{ |
CFRelease(mSettingsCache); |
} |
} |
UInt32 CASettingsStorage::GetNumberKeys() const |
{ |
// make sure our cache is up to date |
const_cast<CASettingsStorage*>(this)->RefreshSettings(); |
return ToUInt32(CFDictionaryGetCount(mSettingsCache)); |
} |
void CASettingsStorage::GetKeys(UInt32 inNumberKeys, UInt32& outNumberKeys, CFStringRef* outKeys) const |
{ |
// make sure our cache is up to date |
const_cast<CASettingsStorage*>(this)->RefreshSettings(); |
CFDictionaryGetKeysAndValues(mSettingsCache, reinterpret_cast<const void**>(outKeys), NULL); |
outNumberKeys = inNumberKeys; |
} |
void CASettingsStorage::CopyBoolValue(CFStringRef inKey, bool& outValue, bool inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// bools can be made from either CFBooleans or CFNumbers |
if(CFGetTypeID(theValue) == CFBooleanGetTypeID()) |
{ |
// get the return value from the CF object |
outValue = CFBooleanGetValue(static_cast<CFBooleanRef>(theValue)); |
} |
else if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the numeric value |
SInt32 theNumericValue = 0; |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &theNumericValue); |
// non-zero indicates true |
outValue = theNumericValue != 0; |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopySInt32Value(CFStringRef inKey, SInt32& outValue, SInt32 inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the return value from the CF object |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue); |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopyUInt32Value(CFStringRef inKey, UInt32& outValue, UInt32 inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the return value from the CF object |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt32Type, &outValue); |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopySInt64Value(CFStringRef inKey, SInt64& outValue, SInt64 inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the return value from the CF object |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &outValue); |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopyUInt64Value(CFStringRef inKey, UInt64& outValue, UInt64 inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the return value from the CF object |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberSInt64Type, &outValue); |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopyFloat32Value(CFStringRef inKey, Float32& outValue, Float32 inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the return value from the CF object |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberFloat32Type, &outValue); |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopyFloat64Value(CFStringRef inKey, Float64& outValue, Float64 inDefaultValue) const |
{ |
// initialize the return value |
outValue = inDefaultValue; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, NULL); |
// for this type, NULL is an invalid value |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// get the return value from the CF object |
CFNumberGetValue(static_cast<CFNumberRef>(theValue), kCFNumberFloat64Type, &outValue); |
} |
// release the value since we aren't returning it |
CFRelease(theValue); |
} |
} |
void CASettingsStorage::CopyNumberValue(CFStringRef inKey, CFNumberRef& outValue, CFNumberRef inDefaultValue) const |
{ |
// initialize the return value |
outValue = NULL; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, inDefaultValue); |
// for this type, NULL is a valid value, but requires less work |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFNumberGetTypeID()) |
{ |
// set the return value to the CF object we are returning |
outValue = static_cast<CFNumberRef>(theValue); |
} |
else |
{ |
// release the value since we aren't returning it |
CFRelease(theValue); |
// set the return value to the default value |
outValue = inDefaultValue; |
// and retain it |
CFRetain(outValue); |
} |
} |
} |
void CASettingsStorage::CopyStringValue(CFStringRef inKey, CFStringRef& outValue, CFStringRef inDefaultValue) const |
{ |
// initialize the return value |
outValue = NULL; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, inDefaultValue); |
// for this type, NULL is a valid value, but requires less work |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFStringGetTypeID()) |
{ |
// set the return value to the CF object we are returning |
outValue = static_cast<CFStringRef>(theValue); |
} |
else |
{ |
// release the value since we aren't returning it |
CFRelease(theValue); |
// set the return value to the default value |
outValue = inDefaultValue; |
// and retain it |
CFRetain(outValue); |
} |
} |
} |
void CASettingsStorage::CopyArrayValue(CFStringRef inKey, CFArrayRef& outValue, CFArrayRef inDefaultValue) const |
{ |
// initialize the return value |
outValue = NULL; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, inDefaultValue); |
// for this type, NULL is a valid value, but requires less work |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFArrayGetTypeID()) |
{ |
// set the return value to the CF object we are returning |
outValue = static_cast<CFArrayRef>(theValue); |
} |
else |
{ |
// release the value since we aren't returning it |
CFRelease(theValue); |
// set the return value to the default value |
outValue = inDefaultValue; |
// and retain it |
CFRetain(outValue); |
} |
} |
} |
void CASettingsStorage::CopyDictionaryValue(CFStringRef inKey, CFDictionaryRef& outValue, CFDictionaryRef inDefaultValue) const |
{ |
// initialize the return value |
outValue = NULL; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, inDefaultValue); |
// for this type, NULL is a valid value, but requires less work |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFDictionaryGetTypeID()) |
{ |
// set the return value to the CF object we are returning |
outValue = static_cast<CFDictionaryRef>(theValue); |
} |
else |
{ |
// release the value since we aren't returning it |
CFRelease(theValue); |
// set the return value to the default value |
outValue = inDefaultValue; |
// and retain it |
CFRetain(outValue); |
} |
} |
} |
void CASettingsStorage::CopyDataValue(CFStringRef inKey, CFDataRef& outValue, CFDataRef inDefaultValue) const |
{ |
// initialize the return value |
outValue = NULL; |
// get the raw value |
CFTypeRef theValue = NULL; |
CopyCFTypeValue(inKey, theValue, inDefaultValue); |
// for this type, NULL is a valid value, but requires less work |
if(theValue != NULL) |
{ |
// make sure we are dealing with the right kind of CF object |
if(CFGetTypeID(theValue) == CFDataGetTypeID()) |
{ |
// set the return value to the CF object we are returning |
outValue = static_cast<CFDataRef>(theValue); |
} |
else |
{ |
// release the value since we aren't returning it |
CFRelease(theValue); |
// set the return value to the default value |
outValue = inDefaultValue; |
// and retain it |
CFRetain(outValue); |
} |
} |
} |
void CASettingsStorage::CopyCFTypeValue(CFStringRef inKey, CFTypeRef& outValue, CFTypeRef inDefaultValue) const |
{ |
// make sure our cache is up to date |
const_cast<CASettingsStorage*>(this)->RefreshSettings(); |
// check to see if we have a value for the given key |
if(!CFDictionaryGetValueIfPresent(mSettingsCache, inKey, &outValue)) |
{ |
// the key wasn't in the cache, so return the default value |
outValue = inDefaultValue; |
} |
// make sure we retain the return value |
if(outValue != NULL) |
{ |
CFRetain(outValue); |
} |
} |
void CASettingsStorage::SetSInt32Value(CFStringRef inKey, SInt32 inValue) |
{ |
CACFNumber theValue(inValue); |
SetCFTypeValue(inKey, theValue.GetCFNumber()); |
} |
void CASettingsStorage::SetUInt32Value(CFStringRef inKey, UInt32 inValue) |
{ |
CACFNumber theValue(inValue); |
SetCFTypeValue(inKey, theValue.GetCFNumber()); |
} |
void CASettingsStorage::SetSInt64Value(CFStringRef inKey, SInt64 inValue) |
{ |
CACFNumber theValue(inValue); |
SetCFTypeValue(inKey, theValue.GetCFNumber()); |
} |
void CASettingsStorage::SetUInt64Value(CFStringRef inKey, UInt64 inValue) |
{ |
CACFNumber theValue(inValue); |
SetCFTypeValue(inKey, theValue.GetCFNumber()); |
} |
void CASettingsStorage::SetFloat32Value(CFStringRef inKey, Float32 inValue) |
{ |
CACFNumber theValue(inValue); |
SetCFTypeValue(inKey, theValue.GetCFNumber()); |
} |
void CASettingsStorage::SetFloat64Value(CFStringRef inKey, Float64 inValue) |
{ |
CACFNumber theValue(inValue); |
SetCFTypeValue(inKey, theValue.GetCFNumber()); |
} |
void CASettingsStorage::SetNumberValue(CFStringRef inKey, CFNumberRef inValue) |
{ |
SetCFTypeValue(inKey, inValue); |
} |
void CASettingsStorage::SetStringValue(CFStringRef inKey, CFStringRef inValue) |
{ |
SetCFTypeValue(inKey, inValue); |
} |
void CASettingsStorage::SetArrayValue(CFStringRef inKey, CFArrayRef inValue) |
{ |
SetCFTypeValue(inKey, inValue); |
} |
void CASettingsStorage::SetDictionaryValue(CFStringRef inKey, CFDictionaryRef inValue) |
{ |
SetCFTypeValue(inKey, inValue); |
} |
void CASettingsStorage::SetDataValue(CFStringRef inKey, CFDataRef inValue) |
{ |
SetCFTypeValue(inKey, inValue); |
} |
void CASettingsStorage::SetCFTypeValue(CFStringRef inKey, CFTypeRef inValue) |
{ |
// make sure our cache is up to date |
RefreshSettings(); |
// add the new key/value to the dictionary |
CFDictionarySetValue(mSettingsCache, inKey, inValue); |
// write the settings to the file |
SaveSettings(); |
} |
void CASettingsStorage::RemoveValue(CFStringRef inKey) |
{ |
// make sure our cache is up to date |
RefreshSettings(); |
// remove the given key |
CFDictionaryRemoveValue(mSettingsCache, inKey); |
// write the settings to the file |
SaveSettings(); |
} |
void CASettingsStorage::RemoveAllValues() |
{ |
// make sure our cache is up to date |
RefreshSettings(); |
// remove the given key |
CFDictionaryRemoveAllValues(mSettingsCache); |
// write the settings to the file |
SaveSettings(); |
} |
void CASettingsStorage::SendNotification(CFStringRef inName, CFDictionaryRef inData, bool inPostToAllSessions) const |
{ |
CACFDistributedNotification::PostNotification(inName, inData, inPostToAllSessions); |
} |
void CASettingsStorage::ForceRefresh() |
{ |
mSettingsCacheForceRefresh = true; |
} |
inline bool operator<(const struct timespec& inX, const struct timespec& inY) |
{ |
return ((inX.tv_sec < inY.tv_sec) || ((inX.tv_sec == inY.tv_sec) && (inX.tv_nsec < inY.tv_nsec))); |
} |
void CASettingsStorage::RefreshSettings() |
{ |
// if this storage is only supporting a single process, there is no need to hit the disk unless |
// required to by it being the first time or if the refresh is specifically forced for some reason |
if(!mIsSingleProcessOnly || (mSettingsCache == NULL) || ((mSettingsCacheTime.tv_sec == 0) && (mSettingsCacheTime.tv_nsec == 0)) || mSettingsCacheForceRefresh) |
{ |
// first, we need to stat the file to check the mod date, this has the side effect of also |
// telling us if the file exisits |
struct stat theFileInfo; |
int theStatError = stat(mSettingsFilePath, &theFileInfo); |
// we use this boolean to make error recovery easier since we need a case for when there's no file anyway |
bool theSettingsWereCached = false; |
bool theSettingsNeedSaving = true; |
if(theStatError == 0) |
{ |
// stat says there is something there, only have to do work if we either don't have a cache or the cache is out of date |
if((mSettingsCache == NULL) || (mSettingsCacheTime < theFileInfo.st_mtimespec) || mSettingsCacheForceRefresh) |
{ |
// open the file |
FILE* theFile = fopen(mSettingsFilePath, "r"); |
if(theFile != NULL) |
{ |
// lock the file (this call blocks until the lock is taken) |
int theError = flock(fileno(theFile), LOCK_EX); |
if(theError == 0) |
{ |
// get the length of the file |
fseek(theFile, 0, SEEK_END); |
size_t theFileLength = static_cast<size_t>(ftell(theFile)); |
fseek(theFile, 0, SEEK_SET); |
if(theFileLength > 0) |
{ |
// allocate a block of memory to hold the data in the file |
CAAutoFree<Byte> theRawFileData(theFileLength); |
// read all the data in |
fread(static_cast<Byte*>(theRawFileData), theFileLength, 1, theFile); |
// release the lock |
flock(fileno(theFile), LOCK_UN); |
// put it into a CFData object |
CACFData theRawFileDataCFData(static_cast<Byte*>(theRawFileData), static_cast<UInt32>(theFileLength)); |
// get rid of the existing cache |
if(mSettingsCache != NULL) |
{ |
CFRelease(mSettingsCache); |
mSettingsCache = NULL; |
} |
// parse the data as a property list |
mSettingsCache = (CFMutableDictionaryRef)CFPropertyListCreateWithData(NULL, theRawFileDataCFData.GetCFData(), kCFPropertyListMutableContainersAndLeaves, NULL, NULL); |
// check to be sure we parsed a plist out of the file |
if(mSettingsCache != NULL) |
{ |
// save the date of the cache |
mSettingsCacheTime = theFileInfo.st_mtimespec; |
// mark that we're done |
theSettingsWereCached = true; |
theSettingsNeedSaving = false; |
} |
} |
} |
// close the file |
fclose(theFile); |
mSettingsCacheForceRefresh = false; |
} |
} |
else |
{ |
// nothing to do since the file was older than the cached data |
theSettingsNeedSaving = false; |
theSettingsWereCached = true; |
} |
} |
// if there was a failure, we need to clean up |
if((theStatError != 0) || theSettingsNeedSaving || !theSettingsWereCached) |
{ |
// we get here if either there isn't a file or something wacky happenned while parsing it |
// so, make sure we have a valid cache dictionary |
if(mSettingsCache == NULL) |
{ |
mSettingsCache = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
} |
mSettingsCacheTime.tv_sec = 0; |
mSettingsCacheTime.tv_nsec = 0; |
if((theStatError != 0) || theSettingsNeedSaving) |
{ |
SaveSettings(); |
} |
} |
} |
} |
void CASettingsStorage::SaveSettings() |
{ |
if(!mIsReadOnly && (mSettingsCache != NULL)) |
{ |
// make a CFData that contains the new settings |
CACFData theNewRawPrefsCFData(CFPropertyListCreateData(NULL, mSettingsCache, mSettingsCacheFormat, 0, NULL), true); |
// open the file for writing |
FILE* theFile = fopen(mSettingsFilePath, "w+"); |
if(theFile != NULL) |
{ |
// lock the file (this call blocks until the lock is taken) |
int theError = flock(fileno(theFile), LOCK_EX); |
if(theError == 0) |
{ |
// set the file access mode if necessary |
if(mSettingsFileAccessMode != 0) |
{ |
fchmod(fileno(theFile), mSettingsFileAccessMode); |
} |
// write the data |
fwrite(theNewRawPrefsCFData.GetDataPtr(), theNewRawPrefsCFData.GetSize(), 1, theFile); |
// flush the file to be sure it is all on disk |
fflush(theFile); |
// release the lock |
flock(fileno(theFile), LOCK_UN); |
// close the file |
fclose(theFile); |
// stat the file to get the mod date |
struct stat theFileInfo; |
stat(mSettingsFilePath, &theFileInfo); |
// save the mod date |
mSettingsCacheTime = theFileInfo.st_mtimespec; |
} |
else |
{ |
// close the file |
fclose(theFile); |
} |
} |
} |
} |
Copyright © 2014 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2014-07-08