API 0.9.5
Foundation/CPUserDefaults.j
Go to the documentation of this file.
00001 /*
00002  * CPUserDefaults.j
00003  * Foundation
00004  *
00005  * Created by Nicholas Small.
00006  * Copyright 2010, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 CPArgumentDomain        = @"CPArgumentDomain";
00026 CPApplicationDomain     = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleIdentifier"] || @"CPApplicationDomain";
00027 CPGlobalDomain          = @"CPGlobalDomain";
00028 CPLocaleDomain          = @"CPLocaleDomain";
00029 CPRegistrationDomain    = @"CPRegistrationDomain";
00030 
00031 CPUserDefaultsDidChangeNotification = @"CPUserDefaultsDidChangeNotification";
00032 
00033 var StandardUserDefaults;
00034 
00051 @implementation CPUserDefaults : CPObject
00052 {
00053     CPDictionary            _domains;
00054     CPDictionary            _stores;
00055 
00056     CPDictionary            _searchList;
00057     BOOL                    _searchListNeedsReload;
00058 }
00059 
00063 + (id)standardUserDefaults
00064 {
00065     if (!StandardUserDefaults)
00066         StandardUserDefaults = [[CPUserDefaults alloc] init];
00067 
00068     return StandardUserDefaults;
00069 }
00070 
00075 + (void)resetStandardUserDefaults
00076 {
00077     if (StandardUserDefaults)
00078         [StandardUserDefaults synchronize];
00079 
00080     StandardUserDefaults = nil;
00081 }
00082 
00083 /*
00084     @ignore
00085 */
00086 - (id)init
00087 {
00088     self = [super init];
00089 
00090     if (self)
00091     {
00092         _domains = [CPDictionary dictionary];
00093         [self _setupArgumentsDomain];
00094 
00095         var defaultStore = [CPUserDefaultsLocalStore supportsLocalStorage] ? CPUserDefaultsLocalStore : CPUserDefaultsCookieStore;
00096 
00097         _stores = [CPDictionary dictionary];
00098         [self setPersistentStoreClass:defaultStore forDomain:CPGlobalDomain reloadData:YES];
00099         [self setPersistentStoreClass:defaultStore forDomain:CPApplicationDomain reloadData:YES];
00100     }
00101 
00102     return self;
00103 }
00104 
00105 /*
00106     @ignore
00107 */
00108 - (void)_setupArgumentsDomain
00109 {
00110     var args = [CPApp namedArguments],
00111         keys = [args allKeys],
00112         count = [keys count],
00113         i = 0;
00114 
00115     for (; i < count; i++)
00116     {
00117         var key = keys[i];
00118         [self setObject:[args objectForKey:key] forKey:key inDomain:CPArgumentDomain];
00119     }
00120 }
00121 
00126 - (id)objectForKey:(CPString)aKey
00127 {
00128     if (_searchListNeedsReload)
00129         [self _reloadSearchList];
00130 
00131     return [_searchList objectForKey:aKey];
00132 }
00133 
00137 - (void)setObject:(id)anObject forKey:(CPString)aKey
00138 {
00139     [self setObject:anObject forKey:aKey inDomain:CPApplicationDomain];
00140 }
00141 
00147 - (id)objectForKey:(CPString)aKey inDomain:(CPString)aDomain
00148 {
00149     var domain = [_domains objectForKey:aDomain];
00150 
00151     if (!domain)
00152         return nil;
00153 
00154     return [domain objectForKey:aKey];
00155 }
00156 
00161 - (void)setObject:(id)anObject forKey:(CPString)aKey inDomain:(CPString)aDomain
00162 {
00163     if (!aKey || !aDomain)
00164         return;
00165 
00166     var domain = [_domains objectForKey:aDomain];
00167     if (!domain)
00168     {
00169         domain = [CPDictionary dictionary];
00170         [_domains setObject:domain forKey:aDomain];
00171     }
00172 
00173     [domain setObject:anObject forKey:aKey];
00174     _searchListNeedsReload = YES;
00175     [self domainDidChange:aDomain];
00176 }
00177 
00182 - (void)removeObjectForKey:(CPString)aKey
00183 {
00184     [self removeObjectForKey:aKey inDomain:CPApplicationDomain];
00185 }
00186 
00190 - (void)removeObjectForKey:(CPString)aKey inDomain:(CPString)aDomain
00191 {
00192     if (!aKey || !aDomain)
00193         return;
00194 
00195     var domain = [_domains objectForKey:aDomain];
00196     if (!domain)
00197         return;
00198 
00199     [domain removeObjectForKey:aKey];
00200     _searchListNeedsReload = YES;
00201     [self domainDidChange:aDomain];
00202 }
00203 
00212 - (void)registerDefaults:(CPDictionary)aDictionary
00213 {
00214     var keys = [aDictionary allKeys],
00215         count = [keys count],
00216         i = 0;
00217 
00218     for (; i < count; i++)
00219     {
00220         var key = keys[i];
00221         [self setObject:[aDictionary objectForKey:key] forKey:key inDomain:CPRegistrationDomain];
00222     }
00223 }
00224 
00231 - (void)registerDefaultsFromContentsOfFile:(CPURL)aURL
00232 {
00233     var contents = [CPURLConnection sendSynchronousRequest:[CPURLRequest requestWithURL:aURL] returningResponse:nil],
00234         data = [CPData dataWithRawString:[contents rawString]],
00235         plist = [data plistObject];
00236 
00237     [self registerDefaults:plist];
00238 }
00239 
00240 /*
00241     @ignore
00242 */
00243 - (void)_reloadSearchList
00244 {
00245     _searchListNeedsReload = NO;
00246 
00247     var dicts = [CPRegistrationDomain, CPGlobalDomain, CPApplicationDomain, CPArgumentDomain],
00248         count = [dicts count],
00249         i = 0;
00250 
00251     _searchList = [CPDictionary dictionary];
00252 
00253     for (; i < count; i++)
00254     {
00255         var domain = [_domains objectForKey:dicts[i]];
00256         if (!domain)
00257             continue;
00258 
00259         var keys = [domain allKeys],
00260             keysCount = [keys count],
00261             j = 0;
00262 
00263         for (; j < keysCount; j++)
00264         {
00265             var key = keys[j];
00266             [_searchList setObject:[domain objectForKey:key] forKey:key];
00267         }
00268     }
00269 }
00270 
00271 // Synchronization
00272 
00276 - (CPArray)volatileDomainNames
00277 {
00278     return [CPArgumentDomain, CPLocaleDomain, CPRegistrationDomain];
00279 }
00280 
00284 - (CPArray)persistentDomainNames
00285 {
00286     return [CPGlobalDomain, CPApplicationDomain];
00287 }
00288 
00292 - (CPUserDefaultsStore)persistentStoreForDomain:(CPString)aDomain
00293 {
00294     return [_stores objectForKey:aDomain];
00295 }
00296 
00305 - (CPUserDefaultsStore)setPersistentStoreClass:(Class)aStoreClass forDomain:(CPString)aDomain reloadData:(BOOL)aFlag
00306 {
00307     var currentStore = [_stores objectForKey:aDomain];
00308     if (currentStore && [currentStore class] === aStoreClass)
00309         return currentStore;
00310 
00311     var store = [[aStoreClass alloc] init];
00312     [store setDomain:aDomain];
00313     [_stores setObject:store forKey:aDomain];
00314 
00315     if (aFlag)
00316         [self reloadDataFromStoreForDomain:aDomain];
00317 
00318     return store;
00319 }
00320 
00324 - (void)reloadDataFromStoreForDomain:(CPString)aDomain
00325 {
00326     var data = [[self persistentStoreForDomain:aDomain] data],
00327         domain = data ? [CPKeyedUnarchiver unarchiveObjectWithData:data] : nil;
00328 
00329     [_domains setObject:domain forKey:aDomain];
00330 
00331     _searchListNeedsReload = YES;
00332 }
00333 
00337 - (void)domainDidChange:(CPString)aDomain
00338 {
00339     if (aDomain === CPGlobalDomain || aDomain === CPApplicationDomain)
00340         [[CPRunLoop currentRunLoop] performSelector:@selector(synchronize) target:self argument:nil order:0 modes:[CPDefaultRunLoopMode]];
00341 
00342     [[CPNotificationCenter defaultCenter] postNotificationName:CPUserDefaultsDidChangeNotification object:self];
00343 }
00344 
00348 - (void)synchronize
00349 {
00350     var globalDomain = [_domains objectForKey:CPGlobalDomain];
00351     if (globalDomain)
00352     {
00353         var data = [CPKeyedArchiver archivedDataWithRootObject:globalDomain];
00354         [[self persistentStoreForDomain:CPGlobalDomain] setData:data];
00355     }
00356 
00357     var appDomain = [_domains objectForKey:CPApplicationDomain];
00358     if (appDomain)
00359     {
00360         var data = [CPKeyedArchiver archivedDataWithRootObject:appDomain];
00361         [[self persistentStoreForDomain:CPApplicationDomain] setData:data];
00362     }
00363 }
00364 
00365 #pragma mark Getting Default Values
00366 
00370 - (CPArray)arrayForKey:(CPString)aKey
00371 {
00372     var value = [self objectForKey:aKey];
00373     if ([value isKindOfClass:CPArray])
00374         return value;
00375 
00376     return nil;
00377 }
00378 
00382 - (BOOL)boolForKey:(CPString)aKey
00383 {
00384     var value = [self objectForKey:aKey];
00385     if ([value respondsToSelector:@selector(boolValue)])
00386         return [value boolValue];
00387 
00388     return NO;
00389 }
00390 
00391 
00395 - (CPData)dataForKey:(CPString)aKey
00396 {
00397     var value = [self objectForKey:aKey];
00398     if ([value isKindOfClass:CPData])
00399         return value;
00400 
00401     return nil;
00402 }
00403 
00407 - (CPDictionary)dictionaryForKey:(CPString)aKey
00408 {
00409     var value = [self objectForKey:aKey];
00410     if ([value isKindOfClass:CPDictionary])
00411         return value;
00412 
00413     return nil;
00414 }
00415 
00419 - (float)floatForKey:(CPString)aKey
00420 {
00421     var value = [self objectForKey:aKey];
00422     if (value === nil)
00423         return 0;
00424 
00425     if ([value respondsToSelector:@selector(floatValue)])
00426         value = [value floatValue];
00427 
00428     return parseFloat(value);
00429 }
00430 
00434 - (int)integerForKey:(CPString)aKey
00435 {
00436     var value = [self objectForKey:aKey];
00437     if (value === nil)
00438         return 0;
00439 
00440     if ([value respondsToSelector:@selector(intValue)])
00441         value = [value intValue];
00442 
00443     return parseInt(value);
00444 }
00445 
00449 - (double)doubleForKey:(CPString)aKey
00450 {
00451     return [self floatForKey:aKey];
00452 }
00453 
00457 - (CPString)stringForKey:(CPString)aKey
00458 {
00459     var value = [self objectForKey:aKey];
00460 
00461     if ([value isKindOfClass:CPString])
00462         return value;
00463 
00464     else if ([value respondsToSelector:@selector(stringValue)])
00465         return [value stringValue];
00466 
00467     return nil;
00468 }
00469 
00473 - (CPArray)stringArrayForKey:(CPString)aKey
00474 {
00475     var value = [self objectForKey:aKey];
00476     if (![value isKindOfClass:CPArray])
00477         return nil;
00478 
00479     for (var i = 0, count = [value count]; i < count; i++)
00480         if (![value[i] isKindOfClass:CPString])
00481             return nil;
00482 
00483     return value;
00484 }
00485 
00489 - (CPURL)URLForKey:(CPString)aKey
00490 {
00491     var value = [self objectForKey:aKey];
00492     if ([value isKindOfClass:CPURL])
00493         return value;
00494 
00495     if ([value isKindOfClass:CPString])
00496         return [CPURL URLWithString:value];
00497 
00498     return nil;
00499 }
00500 
00501 #pragma mark Setting Default Values
00502 
00507 - (void)setBool:(BOOL)aValue forKey:(CPString)aKey
00508 {
00509     if ([aValue respondsToSelector:@selector(boolValue)])
00510         [self setObject:[aValue boolValue] forKey:aKey];
00511 }
00512 
00517 - (void)setFloat:(float)aValue forKey:(CPString)aKey
00518 {
00519     if ([aValue respondsToSelector:@selector(aValue)])
00520         aValue = [aValue floatValue];
00521 
00522     [self setObject:parseFloat(aValue) forKey:aKey];
00523 }
00524 
00528 - (void)setDouble:(double)aValue forKey:(CPString)aKey
00529 {
00530     [self setFloat:aValue forKey:aKey];
00531 }
00532 
00537 - (void)setInteger:(int)aValue forKey:(CPString)aKey
00538 {
00539     if ([aValue respondsToSelector:@selector(intValue)])
00540         aValue = [aValue intValue];
00541 
00542     [self setObject:parseInt(aValue) forKey:aKey];
00543 }
00544 
00549 - (void)setURL:(CPURL)aValue forKey:(CPString)aKey
00550 {
00551     if ([aValue isKindOfClass:CPString])
00552         aValue = [CPURL URLWithString:aValue];
00553 
00554     [self setObject:aValue forKey:aKey];
00555 }
00556 
00557 @end
00558 
00559 @implementation CPUserDefaultsStore : CPObject
00560 {
00561     CPString    _domain;
00562 }
00563 
00564 - (CPData)data
00565 {
00566     _CPRaiseInvalidAbstractInvocation(self, _cmd);
00567     return nil;
00568 }
00569 
00570 - (void)setData:(CPData)aData
00571 {
00572     _CPRaiseInvalidAbstractInvocation(self, _cmd);
00573 }
00574 
00575 @end
00576 
00577 @implementation CPUserDefaultsCookieStore : CPUserDefaultsStore
00578 {
00579     CPCookie    _cookie;
00580 }
00581 
00582 - (void)setDomain:(CPString)aDomain
00583 {
00584     if (_domain === aDomain)
00585         return;
00586 
00587     _domain = aDomain;
00588 
00589     _cookie = [[CPCookie alloc] initWithName:_domain];
00590 }
00591 
00592 - (CPData)data
00593 {
00594     var result = [_cookie value];
00595     if (!result || [result length] < 1)
00596         return nil;
00597 
00598     return [CPData dataWithRawString:decodeURIComponent(result)];
00599 }
00600 
00601 - (void)setData:(CPData)aData
00602 {
00603     [_cookie setValue:encodeURIComponent([aData rawString]) expires:[CPDate distantFuture] domain:window.location.href.hostname];
00604 }
00605 
00606 @end
00607 
00608 var CPUserDefaultsLocalStoreTestKey = "9961800812587769-Cappuccino-Storage-Test";
00609 @implementation CPUserDefaultsLocalStore : CPUserDefaultsStore
00610 {
00611     id __doxygen__;
00612 }
00613 
00614 + (BOOL)supportsLocalStorage
00615 {
00616     if (!window.localStorage)
00617         return NO;
00618 
00619     try
00620     {
00621         // Just because localStorage exists does not mean it works. In particular it might be disabled
00622         // as it is when Safari's private browsing mode is active.
00623         localStorage.setItem(CPUserDefaultsLocalStoreTestKey, "1");
00624         if (localStorage.getItem(CPUserDefaultsLocalStoreTestKey) != "1")
00625             return NO;
00626         localStorage.removeItem(CPUserDefaultsLocalStoreTestKey);
00627     }
00628     catch (e)
00629     {
00630         return NO;
00631     }
00632     return YES;
00633 }
00634 
00635 - (id)init
00636 {
00637     if (![[self class] supportsLocalStorage])
00638     {
00639         [CPException raise:@"UnsupportedFeature" reason:@"Browser does not support localStorage for CPUserDefaultsLocalStore"];
00640         return self = nil;
00641     }
00642 
00643     return self = [super init];
00644 }
00645 
00646 - (CPData)data
00647 {
00648     var result = localStorage.getItem(_domain);
00649     if (!result || [result length] < 1)
00650         return nil;
00651 
00652     return [CPData dataWithRawString:decodeURIComponent(result)];
00653 }
00654 
00655 - (void)setData:(CPData)aData
00656 {
00657     try
00658     {
00659         localStorage.setItem(_domain, encodeURIComponent([aData rawString]));
00660     }
00661     catch (e)
00662     {
00663         CPLog.warn("Unable to write to local storage: " + e);
00664     }
00665 }
00666 
00667 @end
00668 
00669 @implementation CPUserDefaultsStore (CPSynthesizedAccessors)
00670 
00674 - (CPString)domain
00675 {
00676     return _domain;
00677 }
00678 
00682 - (void)setDomain:(CPString)aValue
00683 {
00684     _domain = aValue;
00685 }
00686 
00687 @end
 All Classes Files Functions Variables Defines