23
23
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
25
25
<title>NSUserDefaults class reference</title>
26
$Date: 2002/03/13 09:58:43 $ $Revision: 1.77 $
26
$Date: 2005/02/22 14:06:28 $ $Revision: 1.118 $
30
#include <base/preface.h>
30
#include "GNUstepBase/preface.h"
31
31
#include <sys/stat.h>
32
32
#include <sys/types.h>
35
#include <Foundation/NSUserDefaults.h>
36
#include <Foundation/NSFileManager.h>
37
#include <Foundation/NSPathUtilities.h>
38
#include <Foundation/NSDictionary.h>
39
#include <Foundation/NSArray.h>
40
#include <Foundation/NSDate.h>
41
#include <Foundation/NSUtilities.h>
42
#include <Foundation/NSArchiver.h>
43
#include <Foundation/NSException.h>
44
#include <Foundation/NSNotification.h>
45
#include <Foundation/NSTimer.h>
46
#include <Foundation/NSProcessInfo.h>
47
#include <Foundation/NSRunLoop.h>
48
#include <Foundation/NSBundle.h>
49
#include <Foundation/NSValue.h>
50
#include <Foundation/NSLock.h>
51
#include <base/GSLocale.h>
35
#include "Foundation/NSUserDefaults.h"
36
#include "Foundation/NSArchiver.h"
37
#include "Foundation/NSArray.h"
38
#include "Foundation/NSBundle.h"
39
#include "Foundation/NSDate.h"
40
#include "Foundation/NSDictionary.h"
41
#include "Foundation/NSDistributedLock.h"
42
#include "Foundation/NSException.h"
43
#include "Foundation/NSFileManager.h"
44
#include "Foundation/NSLock.h"
45
#include "Foundation/NSNotification.h"
46
#include "Foundation/NSPathUtilities.h"
47
#include "Foundation/NSProcessInfo.h"
48
#include "Foundation/NSRunLoop.h"
49
#include "Foundation/NSSet.h"
50
#include "Foundation/NSThread.h"
51
#include "Foundation/NSTimer.h"
52
#include "Foundation/NSUtilities.h"
53
#include "Foundation/NSValue.h"
54
#include "Foundation/NSDebug.h"
55
#include "GNUstepBase/GSLocale.h"
56
#include "GNUstepBase/GSLock.h"
53
62
#include "GSPrivate.h"
357
399
NSEnumerator *enumerator;
402
* Calling +standardUserDefaults and +userLanguages is horribly interrelated.
403
* Messing with the order of assignments and calls within these methods can
404
* break things very easily ... take care.
406
* If +standardUserDefaults is called first, it sets up initial information
407
* in sharedDefaults then calls +userLanguages to get language information.
408
* +userLanguages then calls +standardUserDefaults to read NSLanguages
409
* and returns the language information.
410
* +standardUserDefaults then sets up language domains and loads localisation
411
* information before returning.
413
* If +userLanguages is called first, it calls +standardUserDefaults
414
* to obtain the NSLanguages array.
415
* +standardUserDefaults loads basic information initialising the
416
* sharedDefaults variable, then calls back to +userLanguages to set up
418
* +userLanguages calls +standardUserDefaults again to get NSLanguages.
419
* +standardUserDefaults returns the partially initialised sharedDefaults.
420
* +userLanguages uses this to create and return the user languages.
421
* +standardUserDefaults uses the languages to update the search list
422
* and load in localisation information then returns sharedDefaults.
423
* +userLanguages uses this to rebuild language information and return it.
359
426
[classLock lock];
360
if (setSharedDefaults)
429
* NB. The use of the setSharedDefaults flag ensures that a recursive
430
* call to this method is quietly suppressed ... so we get a more
431
* manageable problem.
433
if (setSharedDefaults == YES)
362
435
RETAIN(sharedDefaults);
363
436
[classLock unlock];
364
437
return AUTORELEASE(sharedDefaults);
366
439
setSharedDefaults = YES;
368
* Get the user languages *before* setting up sharedDefaults, to avoid
369
* the userLanguages method trying to look up languages in a partially
370
* constructed user defaults object.
372
uL = [[self class] userLanguages];
373
441
// Create new sharedDefaults (NOTE: Not added to the autorelease pool!)
374
442
sharedDefaults = [[self alloc] init];
375
443
if (sharedDefaults == nil)
439
543
+ (NSArray*) userLanguages
441
545
NSArray *currLang = nil;
547
NSString *locale = nil;
550
* Calling +standardUserDefaults and +userLanguages is horribly interrelated.
551
* Messing with the order of assignments and calls within these methods can
552
* break things very easily ... take care.
554
* If +standardUserDefaults is called first, it sets up initial information
555
* in sharedDefaults then calls +userLanguages to get language information.
556
* +userLanguages then calls +standardUserDefaults to read NSLanguages
557
* and returns the language information.
558
* +standardUserDefaults then sets up language domains and loads localisation
559
* information before returning.
561
* If +userLanguages is called first, it calls +standardUserDefaults
562
* to obtain the NSLanguages array.
563
* +standardUserDefaults loads basic information initialising the
564
* sharedDefaults variable, then calls back to +userLanguages to set up
566
* +userLanguages calls +standardUserDefaults again to get NSLanguages.
567
* +standardUserDefaults returns the partially initialised sharedDefaults.
568
* +userLanguages uses this to create and return the user languages.
569
* +standardUserDefaults uses the languages to update the search list
570
* and load in localisation information then returns sharedDefaults.
571
* +userLanguages uses this to rebuild language information and return it.
576
locale = GSSetLocale(LC_MESSAGES, nil);
444
579
[classLock lock];
445
if (userLanguages != nil)
580
if (invalidatedLanguages == YES)
447
RETAIN(userLanguages);
449
return AUTORELEASE(userLanguages);
582
invalidatedLanguages = NO;
583
DESTROY(userLanguages);
451
userLanguages = RETAIN([NSMutableArray arrayWithCapacity: 5]);
452
locale = GSSetLocale(@"");
453
if (sharedDefaults == nil)
585
if (userLanguages == nil)
455
/* Create our own defaults to get "NSLanguages" since sharedDefaults
457
NSUserDefaults *tempDefaults;
459
tempDefaults = [[self alloc] init];
460
if (tempDefaults != nil)
587
currLang = [[NSUserDefaults standardUserDefaults]
588
stringArrayForKey: @"NSLanguages"];
590
userLanguages = [[NSMutableArray alloc] initWithCapacity: 5];
592
if (currLang == nil && locale != nil && GSLanguageFromLocale(locale))
462
NSMutableArray *sList;
465
* Can't use the standard method to set up a search list,
466
* it would cause mutual recursion as it includes languages.
468
sList = [[NSMutableArray alloc] initWithCapacity: 4];
469
[sList addObject: NSArgumentDomain];
470
[sList addObject: processName];
471
[sList addObject: NSGlobalDomain];
472
[sList addObject: NSRegistrationDomain];
473
[tempDefaults setSearchList: sList];
475
currLang = [tempDefaults stringArrayForKey: @"NSLanguages"];
476
AUTORELEASE(tempDefaults);
594
currLang = [NSArray arrayWithObject: GSLanguageFromLocale(locale)];
482
= [[self standardUserDefaults] stringArrayForKey: @"NSLanguages"];
484
if (currLang == nil && locale != 0 && GSLanguageFromLocale(locale))
486
currLang = [NSArray arrayWithObject: GSLanguageFromLocale(locale)];
489
if (currLang == nil && locale != 0)
491
/* Check for language as the first part of the locale string */
492
NSRange under = [locale rangeOfString: @"_"];
494
currLang = [NSArray arrayWithObject:
495
[locale substringToIndex: under.location]];
597
if (currLang == nil && locale != nil)
599
/* Check for language as the first part of the locale string */
600
NSRange under = [locale rangeOfString: @"_"];
602
currLang = [NSArray arrayWithObject:
603
[locale substringToIndex: under.location]];
500
const char *env_list;
503
env_list = getenv("LANGUAGES");
506
env = [NSStringClass stringWithCString: env_list];
507
currLang = [env componentsSeparatedByString: @";"];
513
if ([currLang containsObject: @""] == YES)
515
NSMutableArray *a = [currLang mutableCopy];
517
[a removeObject: @""];
518
currLang = (NSArray*)AUTORELEASE(a);
520
[userLanguages addObjectsFromArray: currLang];
523
/* Check if "English" is included. We do this to make sure all the
524
required language constants are set somewhere if they aren't set
525
in the default language */
526
if ([userLanguages containsObject: @"English"] == NO)
528
[userLanguages addObject: @"English"];
530
RETAIN(userLanguages);
608
const char *env_list;
611
env_list = getenv("LANGUAGES");
614
env = [NSStringClass stringWithCString: env_list];
615
currLang = [env componentsSeparatedByString: @";"];
621
if ([currLang containsObject: @""] == YES)
623
NSMutableArray *a = [currLang mutableCopy];
625
[a removeObject: @""];
626
currLang = (NSArray*)AUTORELEASE(a);
628
[userLanguages addObjectsFromArray: currLang];
631
/* Check if "English" is included. We do this to make sure all the
632
required language constants are set somewhere if they aren't set
633
in the default language */
634
if ([userLanguages containsObject: @"English"] == NO)
636
[userLanguages addObject: @"English"];
639
result = RETAIN(userLanguages);
531
640
[classLock unlock];
532
return AUTORELEASE(userLanguages);
641
return AUTORELEASE(result);
704
810
processName = RETAIN([[NSProcessInfo processInfo] processName]);
707
if (path != nil && [path isEqual: @""] == NO)
709
_defaultsDatabase = [path copy];
813
if (path == nil || [path isEqual: @""] == YES)
815
path = pathForUser(NSUserName());
817
path = [path stringByStandardizingPath];
818
_defaultsDatabase = [path copy];
819
path = [path stringByDeletingLastPathComponent];
820
if ([mgr isWritableFileAtPath: path] == NO)
822
NSWarnMLog(@"Path '%@' is not writable - making user defaults for '%@' "
823
@" read-only\n", path, _defaultsDatabase);
826
else if ([mgr fileExistsAtPath: path isDirectory: &flag] == NO && flag == NO)
828
NSWarnMLog(@"Path '%@' is not an accessible directory - making user "
829
@"defaults for '%@' read-only\n", path, _defaultsDatabase);
831
else if ([mgr fileExistsAtPath: _defaultsDatabase] == YES
832
&& [mgr isReadableFileAtPath: _defaultsDatabase] == NO)
834
NSWarnMLog(@"Path '%@' is not readable - making user defaults blank\n",
713
_defaultsDatabase = [pathForUser(NSUserName()) copy];
840
* Only create the file lock if we can update the file ...
841
* if we can't the absence of the lock tells us we must be
844
_fileLock = [[NSDistributedLock alloc] initWithPath:
845
[_defaultsDatabase stringByAppendingPathExtension: @"lck"]];
847
_lock = [GSLazyRecursiveLock new];
716
849
// Create an empty search list
717
850
_searchList = [[NSMutableArray alloc] initWithCapacity: 10];
975
1135
- (void) setInteger: (int)value forKey: (NSString*)defaultName
979
sprintf(buf,"%d",value);
980
[self setObject: [NSStringClass stringWithCString: buf] forKey: defaultName];
1137
NSNumber *n = [NSNumberClass numberWithInt: value];
1139
[self setObject: n forKey: defaultName];
1142
static BOOL isPlistObject(id o)
1144
if ([o isKindOfClass: NSStringClass] == YES)
1148
if ([o isKindOfClass: NSDataClass] == YES)
1152
if ([o isKindOfClass: NSDateClass] == YES)
1156
if ([o isKindOfClass: NSNumberClass] == YES)
1160
if ([o isKindOfClass: NSArrayClass] == YES)
1162
NSEnumerator *e = [o objectEnumerator];
1165
while ((tmp = [e nextObject]) != nil)
1167
if (isPlistObject(tmp) == NO)
1174
if ([o isKindOfClass: NSDictionaryClass] == YES)
1176
NSEnumerator *e = [o keyEnumerator];
1179
while ((tmp = [e nextObject]) != nil)
1181
if (isPlistObject(tmp) == NO)
1185
tmp = [o objectForKey: tmp];
1186
if (isPlistObject(tmp) == NO)
985
* Sets an object value for defaultName in the application domain.
986
* <br />Causes a NSUserDefaultsDidChangeNotification to be posted
1197
* Sets an object value for defaultName in the application domain.<br />
1198
* The defaultName must be a non-empty string.<br />
1199
* The value must be an instance of one of the [NSString-propertyList]
1201
* <p>Causes a NSUserDefaultsDidChangeNotification to be posted
987
1202
* if this is the first change to a persistent-domain since the
988
1203
* last -synchronize.
1205
* If value is nil, this is equivalent to the -removeObjectForKey: method.
990
1207
- (void) setObject: (id)value forKey: (NSString*)defaultName
992
if (value && defaultName && ([defaultName length] > 0))
994
NSMutableDictionary *dict;
998
obj = [_persDomains objectForKey: processName];
999
if ([obj isKindOfClass: NSMutableDictionaryClass] == YES)
1005
dict = [obj mutableCopy];
1006
[_persDomains setObject: dict forKey: processName];
1009
[dict setObject: value forKey: defaultName];
1010
[self __changePersistentDomain: processName];
1209
NSMutableDictionary *dict;
1214
[self removeObjectForKey: defaultName];
1216
if ([defaultName isKindOfClass: [NSString class]] == NO
1217
|| [defaultName length] == 0)
1219
[NSException raise: NSInvalidArgumentException
1220
format: @"attempt to set object with bad key (%@)", defaultName];
1222
if (isPlistObject(value) == NO)
1224
[NSException raise: NSInvalidArgumentException
1225
format: @"attempt to set non property list object for key (%@)",
1230
obj = [_persDomains objectForKey: processName];
1231
if ([obj isKindOfClass: NSMutableDictionaryClass] == YES)
1237
dict = [obj mutableCopy];
1238
[_persDomains setObject: dict forKey: processName];
1241
[dict setObject: value forKey: defaultName];
1242
[self __changePersistentDomain: processName];
1454
wasLocked = isLocked;
1455
if (isLocked == NO && _fileLock != nil)
1457
while ([_fileLock tryLock] == NO)
1459
CREATE_AUTORELEASE_POOL(arp);
1463
lockDate = [_fileLock lockDate];
1464
when = [NSDateClass dateWithTimeIntervalSinceNow: 0.1];
1467
* In case we have tried and failed to break the lock,
1468
* we give up after a while ... 16 seconds should give
1469
* us three lock breaks if we do them at 5 second
1472
if ([when timeIntervalSinceDate: started] > 16.0)
1474
NSLog(@"Failed to lock user defaults database even after "
1475
@"breaking old locks!");
1481
* If lockDate is nil, we should be able to lock again ... but we
1482
* wait a little anyway ... so that in the case of a locking
1483
* problem we do an idle wait rather than a busy one.
1485
if (lockDate != nil && [when timeIntervalSinceDate: lockDate] > 5.0)
1487
[_fileLock breakLock];
1491
[NSThread sleepUntilDate: when];
1499
* Re-fetch database attributes in cased they changed while obtaining lock.
1501
attr = [mgr fileAttributesAtPath: _defaultsDatabase
1212
1504
DESTROY(_dictionaryRep);
1505
if (self == sharedDefaults) invalidatedLanguages = YES;
1214
1507
// Read the persistent data from the stored database
1217
unsigned long desired;
1218
unsigned long attributes;
1220
1510
newDict = [[NSMutableDictionaryClass allocWithZone: [self zone]]
1221
initWithContentsOfFile: _defaultsDatabase];
1511
initWithCapacity: 1];
1512
if (_fileLock != nil)
1514
NSLog(@"Creating defaults database file %@", _defaultsDatabase);
1515
[newDict writeToFile: _defaultsDatabase atomically: YES];
1516
attr = [mgr fileAttributesAtPath: _defaultsDatabase
1522
if ([mgr isReadableFileAtPath: _defaultsDatabase] == NO)
1528
newDict = [[NSMutableDictionaryClass allocWithZone: [self zone]]
1529
initWithContentsOfFile: _defaultsDatabase];
1222
1531
if (newDict == nil)
1224
NSLog(@"Unable to load defaults from '%@'", _defaultsDatabase);
1533
if (_fileLock == nil)
1536
* Running with no readable user defaults ... but we were
1537
* initialised that way (possibly on a read-only filesystem)
1538
* so we just continue as best we can.
1540
newDict = [[NSMutableDictionaryClass allocWithZone: [self zone]]
1541
initWithCapacity: 4];
1546
* The defaults system has become unreadable singe we started...
1547
* probably a severe error of some sort
1549
NSLog(@"Unable to load defaults from '%@'", _defaultsDatabase);
1550
if (wasLocked == NO)
1229
attributes = [attr filePosixPermissions];
1230
// We enforce the permission mode 0600 on the defaults database
1562
* We enforce the permission mode 0600 on the defaults database
1564
attributes = [attr filePosixPermissions];
1231
1565
#if !(defined(S_IRUSR) && defined(S_IWUSR))
1234
desired = (S_IRUSR|S_IWUSR);
1568
desired = (S_IRUSR|S_IWUSR);
1236
if (attributes != desired)
1238
NSMutableDictionary *enforced_attributes;
1239
NSNumber *permissions;
1241
enforced_attributes = [NSMutableDictionary dictionaryWithDictionary:
1242
[mgr fileAttributesAtPath: _defaultsDatabase traverseLink: YES]];
1244
permissions = [NSNumber numberWithUnsignedLong: desired];
1245
[enforced_attributes setObject: permissions
1246
forKey: NSFilePosixPermissions];
1248
[mgr changeFileAttributes: enforced_attributes
1249
atPath: _defaultsDatabase];
1570
if (attributes != desired)
1254
unsigned long desired;
1255
NSNumber *permissions;
1257
// We enforce the permission mode 0600 on the defaults database
1258
#if !(defined(S_IRUSR) && defined(S_IWUSR))
1261
desired = (S_IRUSR|S_IWUSR);
1263
permissions = [NSNumber numberWithUnsignedLong: desired];
1264
attr = [NSDictionary dictionaryWithObjectsAndKeys:
1265
NSUserName(), NSFileOwnerAccountName,
1266
permissions, NSFilePosixPermissions,
1268
NSLog(@"Creating defaults database file %@", _defaultsDatabase);
1269
[mgr createFileAtPath: _defaultsDatabase
1272
newDict = [[NSMutableDictionaryClass allocWithZone: [self zone]]
1273
initWithCapacity: 1];
1274
[newDict writeToFile: _defaultsDatabase atomically: YES];
1572
NSMutableDictionary *enforced_attributes;
1573
NSNumber *permissions;
1575
enforced_attributes = [NSMutableDictionary dictionaryWithDictionary:
1576
[mgr fileAttributesAtPath: _defaultsDatabase traverseLink: YES]];
1578
permissions = [NSNumberClass numberWithUnsignedLong: desired];
1579
[enforced_attributes setObject: permissions
1580
forKey: NSFilePosixPermissions];
1582
[mgr changeFileAttributes: enforced_attributes
1583
atPath: _defaultsDatabase];
1277
if (_changedDomains)
1586
if (_changedDomains != nil)
1278
1587
{ // Synchronize both dictionaries
1279
1588
NSEnumerator *enumerator = [_changedDomains objectEnumerator];
1589
NSString *domainName;
1590
NSDictionary *domain;
1284
nextImp = [enumerator methodForSelector: nextObjectSel];
1285
pImp = [_persDomains methodForSelector: objectForKeySel];
1286
while ((obj = (*nextImp)(enumerator, nextObjectSel)) != nil)
1592
DESTROY(_changedDomains); // Retained by enumerator.
1593
while ((domainName = [enumerator nextObject]) != nil)
1288
dict = (*pImp)(_persDomains, objectForKeySel, obj);
1289
if (dict) // Domain was added or changed
1595
domain = [_persDomains objectForKey: domainName];
1596
if (domain != nil) // Domain was added or changed
1291
[newDict setObject: dict forKey: obj];
1598
[newDict setObject: domain forKey: domainName];
1293
else // Domain was removed
1600
else // Domain was removed
1295
[newDict removeObjectForKey: obj];
1602
[newDict removeObjectForKey: domainName];
1298
1605
RELEASE(_persDomains);
1299
1606
_persDomains = newDict;
1301
if (![_persDomains writeToFile: _defaultsDatabase atomically: YES])
1607
// Save the changes unless we are in read-only mode.
1608
if (_fileLock != nil)
1610
if (![_persDomains writeToFile: _defaultsDatabase atomically: YES])
1612
if (wasLocked == NO)
1306
ASSIGN(_lastSync, [NSDate date]);
1621
ASSIGN(_lastSync, [NSDateClass date]);
1310
ASSIGN(_lastSync, [NSDate date]);
1625
ASSIGN(_lastSync, [NSDateClass date]);
1311
1626
if ([_persDomains isEqual: newDict] == NO)
1313
1628
RELEASE(_persDomains);
1449
1787
[_tempDomains setObject: regDefs forKey: NSRegistrationDomain];
1451
1789
DESTROY(_dictionaryRep);
1790
if (self == sharedDefaults) invalidatedLanguages = YES;
1452
1791
[regDefs addEntriesFromDictionary: newVals];
1453
1792
[_lock unlock];
1796
* Removes the named domain from the serach list of the receiver.<br />
1797
* Suites may be added using the -addSuiteNamed: method.
1799
- (void) removeSuiteNamed: (NSString*)aName
1803
[NSException raise: NSInvalidArgumentException
1804
format: @"attempt to remove suite with nil name"];
1807
DESTROY(_dictionaryRep);
1808
if (self == sharedDefaults) invalidatedLanguages = YES;
1809
[_searchList removeObject: aName];
1456
1813
/*************************************************************************
1457
1814
*** Accessing the User Defaults database
1458
1815
*************************************************************************/
1459
- (void) __createStandardSearchList
1462
NSEnumerator *enumerator;
1466
// Note: The search list should exist!
1468
// 1. NSArgumentDomain
1469
[_searchList addObject: NSArgumentDomain];
1472
[_searchList addObject: processName];
1474
// 3. NSGlobalDomain
1475
[_searchList addObject: NSGlobalDomain];
1477
// 4. User's preferred languages
1478
uL = [[self class] userLanguages];
1479
enumerator = [uL objectEnumerator];
1480
while ((object = [enumerator nextObject]))
1482
[_searchList addObject: object];
1485
// 5. NSRegistrationDomain
1486
[_searchList addObject: NSRegistrationDomain];
1492
1817
- (NSDictionary*) __createArgumentDictionary