API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPUserDefaults.j
Go to the documentation of this file.
1 /*
2  * CPUserDefaults.j
3  * Foundation
4  *
5  * Created by Nicholas Small.
6  * Copyright 2010, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @class CPCookie
25 
26 @global CPApp
27 
28 CPArgumentDomain = @"CPArgumentDomain";
29 CPApplicationDomain = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleIdentifier"] || @"CPApplicationDomain";
30 CPGlobalDomain = @"CPGlobalDomain";
31 CPLocaleDomain = @"CPLocaleDomain";
32 CPRegistrationDomain = @"CPRegistrationDomain";
33 
34 CPUserDefaultsDidChangeNotification = @"CPUserDefaultsDidChangeNotification";
35 
36 var StandardUserDefaults;
37 
54 @implementation CPUserDefaults : CPObject
55 {
56  CPDictionary _domains;
57  CPDictionary _stores;
58 
59  CPDictionary _searchList;
60  BOOL _searchListNeedsReload;
61 }
62 
66 + (id)standardUserDefaults
67 {
68  if (!StandardUserDefaults)
69  StandardUserDefaults = [[CPUserDefaults alloc] init];
70 
71  return StandardUserDefaults;
72 }
73 
78 + (void)resetStandardUserDefaults
79 {
80  if (StandardUserDefaults)
81  [StandardUserDefaults synchronize];
82 
83  StandardUserDefaults = nil;
84 }
85 
86 /*
87  @ignore
88 */
89 - (id)init
90 {
91  self = [super init];
92 
93  if (self)
94  {
95  _domains = @{};
96  [self _setupArgumentsDomain];
97 
98  var defaultStore = [CPUserDefaultsLocalStore supportsLocalStorage] ? CPUserDefaultsLocalStore : CPUserDefaultsCookieStore;
99 
100  _stores = @{};
101  [self setPersistentStoreClass:defaultStore forDomain:CPGlobalDomain reloadData:YES];
102  [self setPersistentStoreClass:defaultStore forDomain:CPApplicationDomain reloadData:YES];
103  }
104 
105  return self;
106 }
107 
108 /*
109  @ignore
110 */
111 - (void)_setupArgumentsDomain
112 {
113  var args = [CPApp namedArguments],
114  keys = [args allKeys],
115  count = [keys count],
116  i = 0;
117 
118  for (; i < count; i++)
119  {
120  var key = keys[i];
121  [self setObject:[args objectForKey:key] forKey:key inDomain:CPArgumentDomain];
122  }
123 }
124 
129 - (id)objectForKey:(CPString)aKey
130 {
131  if (_searchListNeedsReload)
132  [self _reloadSearchList];
133 
134  return [_searchList objectForKey:aKey];
135 }
136 
140 - (void)setObject:(id)anObject forKey:(CPString)aKey
141 {
142  [self setObject:anObject forKey:aKey inDomain:CPApplicationDomain];
143 }
144 
150 - (id)objectForKey:(CPString)aKey inDomain:(CPString)aDomain
151 {
152  var domain = [_domains objectForKey:aDomain];
153 
154  if (!domain)
155  return nil;
156 
157  return [domain objectForKey:aKey];
158 }
159 
164 - (void)setObject:(id)anObject forKey:(CPString)aKey inDomain:(CPString)aDomain
165 {
166  if (!aKey || !aDomain)
167  return;
168 
169  var domain = [_domains objectForKey:aDomain];
170  if (!domain)
171  {
172  domain = @{};
173  [_domains setObject:domain forKey:aDomain];
174  }
175 
176  [domain setObject:anObject forKey:aKey];
177  _searchListNeedsReload = YES;
178  [self domainDidChange:aDomain];
179 }
180 
185 - (void)removeObjectForKey:(CPString)aKey
186 {
187  [self removeObjectForKey:aKey inDomain:CPApplicationDomain];
188 }
189 
193 - (void)removeObjectForKey:(CPString)aKey inDomain:(CPString)aDomain
194 {
195  if (!aKey || !aDomain)
196  return;
197 
198  var domain = [_domains objectForKey:aDomain];
199  if (!domain)
200  return;
201 
202  [domain removeObjectForKey:aKey];
203  _searchListNeedsReload = YES;
204  [self domainDidChange:aDomain];
205 }
206 
215 - (void)registerDefaults:(CPDictionary)aDictionary
216 {
217  var keys = [aDictionary allKeys],
218  count = [keys count],
219  i = 0;
220 
221  for (; i < count; i++)
222  {
223  var key = keys[i];
224  [self setObject:[aDictionary objectForKey:key] forKey:key inDomain:CPRegistrationDomain];
225  }
226 }
227 
234 - (void)registerDefaultsFromContentsOfFile:(CPURL)aURL
235 {
237  data = [CPData dataWithRawString:[contents rawString]],
238  plist = [data plistObject];
239 
240  [self registerDefaults:plist];
241 }
242 
243 /*
244  @ignore
245 */
246 - (void)_reloadSearchList
247 {
248  _searchListNeedsReload = NO;
249 
250  var dicts = [CPRegistrationDomain, CPGlobalDomain, CPApplicationDomain, CPArgumentDomain],
251  count = [dicts count],
252  i = 0;
253 
254  _searchList = @{};
255 
256  for (; i < count; i++)
257  {
258  var domain = [_domains objectForKey:dicts[i]];
259  if (!domain)
260  continue;
261 
262  var keys = [domain allKeys],
263  keysCount = [keys count],
264  j = 0;
265 
266  for (; j < keysCount; j++)
267  {
268  var key = keys[j];
269  [_searchList setObject:[domain objectForKey:key] forKey:key];
270  }
271  }
272 }
273 
274 // Synchronization
275 
279 - (CPArray)volatileDomainNames
280 {
281  return [CPArgumentDomain, CPLocaleDomain, CPRegistrationDomain];
282 }
283 
287 - (CPArray)persistentDomainNames
288 {
289  return [CPGlobalDomain, CPApplicationDomain];
290 }
291 
295 - (CPUserDefaultsStore)persistentStoreForDomain:(CPString)aDomain
296 {
297  return [_stores objectForKey:aDomain];
298 }
299 
308 - (CPUserDefaultsStore)setPersistentStoreClass:(Class)aStoreClass forDomain:(CPString)aDomain reloadData:(BOOL)aFlag
309 {
310  var currentStore = [_stores objectForKey:aDomain];
311  if (currentStore && [currentStore class] === aStoreClass)
312  return currentStore;
313 
314  var store = [[aStoreClass alloc] init];
315  [store setDomain:aDomain];
316  [_stores setObject:store forKey:aDomain];
317 
318  if (aFlag)
319  [self reloadDataFromStoreForDomain:aDomain];
320 
321  return store;
322 }
323 
327 - (void)reloadDataFromStoreForDomain:(CPString)aDomain
328 {
329  var data = [[self persistentStoreForDomain:aDomain] data],
330  domain = data ? [CPKeyedUnarchiver unarchiveObjectWithData:data] : nil;
331 
332  if (domain === nil)
333  [_domains removeObjectForKey:aDomain];
334  else
335  [_domains setObject:domain forKey:aDomain];
336 
337  _searchListNeedsReload = YES;
338 }
339 
343 - (void)domainDidChange:(CPString)aDomain
344 {
345  if (aDomain === CPGlobalDomain || aDomain === CPApplicationDomain)
346  [[CPRunLoop currentRunLoop] performSelector:@selector(synchronize) target:self argument:nil order:0 modes:[CPDefaultRunLoopMode]];
347 
348  [[CPNotificationCenter defaultCenter] postNotificationName:CPUserDefaultsDidChangeNotification object:self];
349 }
350 
354 - (void)synchronize
355 {
356  var globalDomain = [_domains objectForKey:CPGlobalDomain];
357  if (globalDomain)
358  {
359  var data = [CPKeyedArchiver archivedDataWithRootObject:globalDomain];
360  [[self persistentStoreForDomain:CPGlobalDomain] setData:data];
361  }
362 
363  var appDomain = [_domains objectForKey:CPApplicationDomain];
364  if (appDomain)
365  {
366  var data = [CPKeyedArchiver archivedDataWithRootObject:appDomain];
367  [[self persistentStoreForDomain:CPApplicationDomain] setData:data];
368  }
369 }
370 
371 #pragma mark Getting Default Values
372 
376 - (CPArray)arrayForKey:(CPString)aKey
377 {
378  var value = [self objectForKey:aKey];
379  if ([value isKindOfClass:CPArray])
380  return value;
381 
382  return nil;
383 }
384 
388 - (BOOL)boolForKey:(CPString)aKey
389 {
390  var value = [self objectForKey:aKey];
391  if ([value respondsToSelector:@selector(boolValue)])
392  return [value boolValue];
393 
394  return NO;
395 }
396 
397 
401 - (CPData)dataForKey:(CPString)aKey
402 {
403  var value = [self objectForKey:aKey];
404  if ([value isKindOfClass:CPData])
405  return value;
406 
407  return nil;
408 }
409 
413 - (CPDictionary)dictionaryForKey:(CPString)aKey
414 {
415  var value = [self objectForKey:aKey];
416  if ([value isKindOfClass:CPDictionary])
417  return value;
418 
419  return nil;
420 }
421 
425 - (float)floatForKey:(CPString)aKey
426 {
427  var value = [self objectForKey:aKey];
428  if (value === nil)
429  return 0;
430 
431  if ([value respondsToSelector:@selector(floatValue)])
432  value = [value floatValue];
433 
434  return parseFloat(value);
435 }
436 
440 - (int)integerForKey:(CPString)aKey
441 {
442  var value = [self objectForKey:aKey];
443  if (value === nil)
444  return 0;
445 
446  if ([value respondsToSelector:@selector(intValue)])
447  value = [value intValue];
448 
449  return parseInt(value);
450 }
451 
455 - (double)doubleForKey:(CPString)aKey
456 {
457  return [self floatForKey:aKey];
458 }
459 
463 - (CPString)stringForKey:(CPString)aKey
464 {
465  var value = [self objectForKey:aKey];
466 
467  if ([value isKindOfClass:CPString])
468  return value;
469 
470  else if ([value respondsToSelector:@selector(stringValue)])
471  return [value stringValue];
472 
473  return nil;
474 }
475 
479 - (CPArray)stringArrayForKey:(CPString)aKey
480 {
481  var value = [self objectForKey:aKey];
482  if (![value isKindOfClass:CPArray])
483  return nil;
484 
485  for (var i = 0, count = [value count]; i < count; i++)
486  if (![value[i] isKindOfClass:CPString])
487  return nil;
488 
489  return value;
490 }
491 
495 - (CPURL)URLForKey:(CPString)aKey
496 {
497  var value = [self objectForKey:aKey];
498  if ([value isKindOfClass:CPURL])
499  return value;
500 
501  if ([value isKindOfClass:CPString])
502  return [CPURL URLWithString:value];
503 
504  return nil;
505 }
506 
507 #pragma mark Setting Default Values
508 
513 - (void)setBool:(BOOL)aValue forKey:(CPString)aKey
514 {
515  if ([aValue respondsToSelector:@selector(boolValue)])
516  [self setObject:[aValue boolValue] forKey:aKey];
517 }
518 
523 - (void)setFloat:(float)aValue forKey:(CPString)aKey
524 {
525  if ([aValue respondsToSelector:@selector(aValue)])
526  aValue = [aValue floatValue];
527 
528  [self setObject:parseFloat(aValue) forKey:aKey];
529 }
530 
534 - (void)setDouble:(double)aValue forKey:(CPString)aKey
535 {
536  [self setFloat:aValue forKey:aKey];
537 }
538 
543 - (void)setInteger:(int)aValue forKey:(CPString)aKey
544 {
545  if ([aValue respondsToSelector:@selector(intValue)])
546  aValue = [aValue intValue];
547 
548  [self setObject:parseInt(aValue) forKey:aKey];
549 }
550 
555 - (void)setURL:(CPURL)aValue forKey:(CPString)aKey
556 {
557  if ([aValue isKindOfClass:CPString])
558  aValue = [CPURL URLWithString:aValue];
559 
560  [self setObject:aValue forKey:aKey];
561 }
562 
563 @end
564 
565 @implementation CPUserDefaultsStore : CPObject
566 {
567  CPString _domain;
568 }
569 
570 - (CPData)data
571 {
572  _CPRaiseInvalidAbstractInvocation(self, _cmd);
573  return nil;
574 }
575 
576 - (void)setData:(CPData)aData
577 {
578  _CPRaiseInvalidAbstractInvocation(self, _cmd);
579 }
580 
581 @end
582 
584 {
585  CPCookie _cookie;
586 }
587 
588 - (void)setDomain:(CPString)aDomain
589 {
590  if (_domain === aDomain)
591  return;
592 
593  _domain = aDomain;
594 
595  _cookie = [[CPCookie alloc] initWithName:_domain];
596 }
597 
598 - (CPData)data
599 {
600  var result = [_cookie value];
601  if (!result || [result length] < 1)
602  return nil;
603 
604  return [CPData dataWithRawString:decodeURIComponent(result)];
605 }
606 
607 - (void)setData:(CPData)aData
608 {
609  [_cookie setValue:encodeURIComponent([aData rawString]) expires:[CPDate distantFuture] domain:window.location.href.hostname];
610 }
611 
612 @end
613 
614 var CPUserDefaultsLocalStoreTestKey = "9961800812587769-Cappuccino-Storage-Test";
616 {
617  id __doxygen__;
618 }
619 
620 + (BOOL)supportsLocalStorage
621 {
622  if (!window.localStorage)
623  return NO;
624 
625  try
626  {
627  // Just because localStorage exists does not mean it works. In particular it might be disabled
628  // as it is when Safari's private browsing mode is active.
629  localStorage.setItem(CPUserDefaultsLocalStoreTestKey, "1");
630  if (localStorage.getItem(CPUserDefaultsLocalStoreTestKey) != "1")
631  return NO;
632  localStorage.removeItem(CPUserDefaultsLocalStoreTestKey);
633  }
634  catch (e)
635  {
636  return NO;
637  }
638  return YES;
639 }
640 
641 - (id)init
642 {
643  if (![[self class] supportsLocalStorage])
644  {
645  [CPException raise:@"UnsupportedFeature" reason:@"Browser does not support localStorage for CPUserDefaultsLocalStore"];
646  return self = nil;
647  }
648 
649  return self = [super init];
650 }
651 
652 - (CPData)data
653 {
654  var result = localStorage.getItem(_domain);
655  if (!result || [result length] < 1)
656  return nil;
657 
658  return [CPData dataWithRawString:decodeURIComponent(result)];
659 }
660 
661 - (void)setData:(CPData)aData
662 {
663  try
664  {
665  localStorage.setItem(_domain, encodeURIComponent([aData rawString]));
666  }
667  catch (e)
668  {
669  CPLog.warn("Unable to write to local storage: " + e);
670  }
671 }
672 
673 @end
674 
675 @implementation CPUserDefaultsStore (CPSynthesizedAccessors)
676 
680 - (CPString)domain
681 {
682  return _domain;
683 }
684 
688 - (void)setDomain:(CPString)aValue
689 {
690  _domain = aValue;
691 }
692 
693 @end