1/* SOGoDefaultsSource.m - this file is part of SOGo
2 *
3 * Copyright (C) 2009 Inverse inc.
4 *
5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6 *
7 * This file is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This file is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; see the file COPYING.  If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23#import <Foundation/NSArray.h>
24#import <Foundation/NSData.h>
25#import <Foundation/NSDictionary.h>
26#import <Foundation/NSException.h>
27#import <Foundation/NSString.h>
28#import <Foundation/NSValue.h>
29
30#import <NGExtensions/NSObject+Logs.h>
31
32#define NEEDS_DEFAULTS_SOURCE_INTERNAL 1
33#import "SOGoDefaultsSource.h"
34
35NSString *SOGoDefaultsSourceInvalidSource = @"SOGoDefaultsSourceInvalidSource";
36NSString *SOGoDefaultsSourceUnmutableSource = @"SOGoDefaultsSourceUnmutableSource";
37
38static Class NSArrayKlass = Nil;
39static Class NSDataKlass = Nil;
40static Class NSDictionaryKlass = Nil;
41static Class NSStringKlass = Nil;
42
43@implementation SOGoDefaultsSource
44
45+ (void) initialize
46{
47  if (!NSArrayKlass)
48    NSArrayKlass = [NSArray class];
49  if (!NSDataKlass)
50    NSDataKlass = [NSData class];
51  if (!NSDictionaryKlass)
52    NSDictionaryKlass = [NSDictionary class];
53  if (!NSStringKlass)
54    NSStringKlass = [NSString class];
55}
56
57+ (id) defaultsSourceWithSource: (id) newSource
58                andParentSource: (SOGoDefaultsSource *) newParentSource
59{
60  SOGoDefaultsSource *sogoDefaultsSource;
61
62  sogoDefaultsSource = [self new];
63  [sogoDefaultsSource autorelease];
64  [sogoDefaultsSource setSource: newSource];
65  [sogoDefaultsSource setParentSource: newParentSource];
66
67  if ([sogoDefaultsSource migrate])
68    [sogoDefaultsSource synchronize];
69
70  return sogoDefaultsSource;
71}
72
73- (id) init
74{
75  if ((self = [super init]))
76    {
77      source = nil;
78      parentSource = nil;
79      isMutable = NO;
80    }
81
82  return self;
83}
84
85- (void) dealloc
86{
87  [source release];
88  [parentSource release];
89  [super dealloc];
90}
91
92- (void) setSource: (id) newSource
93{
94  if ([newSource respondsToSelector: @selector (objectForKey:)])
95    {
96      ASSIGN (source, newSource);
97      isMutable = [source respondsToSelector: @selector (setObject:forKey:)];
98    }
99  else
100    [NSException raise: SOGoDefaultsSourceInvalidSource
101                format: @"UserDefaults source '%@'"
102                 @" does not respond to 'object:forKey:'", newSource];
103}
104
105- (id) source
106{
107  return source;
108}
109
110- (void) setParentSource: (SOGoDefaultsSource *) newParentSource
111{
112  ASSIGN (parentSource, newParentSource);
113}
114
115- (void) setObject: (id) value
116	    forKey: (NSString *) key
117{
118  if (isMutable)
119    [source setObject: value forKey: key];
120  else
121    [NSException raise: SOGoDefaultsSourceUnmutableSource
122                format: @"UserDefaults source '%@' is not mutable", source];
123}
124
125- (id) objectForKey: (NSString *) objectKey
126{
127  id objectForKey;
128
129  objectForKey = [source objectForKey: objectKey];
130  if (!objectForKey)
131    objectForKey = [parentSource objectForKey: objectKey];
132
133  return objectForKey;
134}
135
136- (void) removeObjectForKey: (NSString *) key
137{
138  [source removeObjectForKey: key];
139}
140
141- (void) setBool: (BOOL) value
142	  forKey: (NSString *) key
143{
144  [self setInteger: (value ? 1: 0) forKey: key];
145}
146
147- (BOOL) boolForKey: (NSString *) key
148{
149  id boolForKey;
150  BOOL value;
151
152  boolForKey = [self objectForKey: key];
153  if (boolForKey)
154    {
155      if ([boolForKey respondsToSelector: @selector (boolValue)])
156        value = [boolForKey boolValue];
157      else
158        {
159          [self warnWithFormat: @"expected a boolean for '%@' (ignored)",
160                key];
161          value = NO;
162        }
163    }
164  else
165    value = NO;
166
167  return value;
168}
169
170- (void) setFloat: (float) value
171	   forKey: (NSString *) key
172{
173  [self setObject: [NSNumber numberWithFloat: value]
174	forKey: key];
175}
176
177- (float) floatForKey: (NSString *) key
178{
179  id floatForKey;
180  float value;
181
182  floatForKey = [self objectForKey: key];
183  if (floatForKey)
184    {
185      if ([floatForKey respondsToSelector: @selector (floatValue)])
186        value = [floatForKey floatValue];
187      else
188        {
189          [self warnWithFormat: @"expected a float for '%@' (ignored)",
190                key];
191          value = 0.0;
192        }
193    }
194  else
195    value = 0.0;
196
197  return value;
198}
199
200- (void) setInteger: (int) value
201	     forKey: (NSString *) key
202{
203  [self setObject: [NSString stringWithFormat: @"%d", value]
204	forKey: key];
205}
206
207- (int) integerForKey: (NSString *) key
208{
209  id intForKey;
210  int value;
211
212  intForKey = [self objectForKey: key];
213  if (intForKey)
214    {
215      if ([intForKey respondsToSelector: @selector (intValue)])
216        value = [intForKey intValue];
217      else
218        {
219          [self warnWithFormat: @"expected an integer for '%@' (ignored)",
220                key];
221          value = 0;
222        }
223    }
224  else
225    value = 0;
226
227  return value;
228}
229
230- (NSData *) dataForKey: (NSString *) key
231{
232  NSData *dataForKey;
233
234  dataForKey = [self objectForKey: key];
235  if (dataForKey && ![dataForKey isKindOfClass: NSDataKlass])
236    {
237      [self warnWithFormat: @"expected an NSData for '%@' (ignored)",
238            key];
239      dataForKey = nil;
240    }
241
242  return dataForKey;
243}
244
245- (NSString *) stringForKey: (NSString *) key
246{
247  NSString *stringForKey;
248
249  stringForKey = [self objectForKey: key];
250  if (stringForKey && ![stringForKey isKindOfClass: NSStringKlass])
251    {
252      [self warnWithFormat: @"expected an NSString for '%@' (ignored)",
253            key];
254      stringForKey = nil;
255    }
256
257  return stringForKey;
258}
259
260- (NSDictionary *) dictionaryForKey: (NSString *) key
261{
262  NSDictionary *dictionaryForKey;
263
264  /* Dictionaries are a special case for which we don't allow searches in the
265     parent source. Each level can thus have its own set of dictionary
266     values. */
267  dictionaryForKey = [source objectForKey: key];
268  if (dictionaryForKey
269      && ![dictionaryForKey isKindOfClass: NSDictionaryKlass])
270    {
271      [self warnWithFormat: @"expected an NSDictionary for '%@' (ignored)",
272            key];
273      dictionaryForKey = nil;
274    }
275
276  return dictionaryForKey;
277}
278
279- (NSArray *) arrayForKey: (NSString *) key
280{
281  NSArray *arrayForKey;
282
283  arrayForKey = [self objectForKey: key];
284  if (arrayForKey && ![arrayForKey isKindOfClass: NSArrayKlass])
285    {
286      [self warnWithFormat: @"expected an NSArray for '%@' (ignored)",
287            key];
288      arrayForKey = nil;
289    }
290
291  return arrayForKey;
292}
293
294- (NSArray *) stringArrayForKey: (NSString *) key
295{
296  NSArray *stringArray;
297  int count, max;
298
299  stringArray = [self arrayForKey: key];
300  max = [stringArray count];
301  for (count = 0; stringArray && count < max; count++)
302    if (![[stringArray objectAtIndex: count] isKindOfClass: NSStringKlass])
303      {
304        [self warnWithFormat: @"expected string values in array for '%@'"
305              @", value %d is not a string (ignored)",
306              key, count];
307        stringArray = nil;
308      }
309
310  return stringArray;
311}
312
313- (BOOL) migrate
314{
315  return NO;
316}
317
318- (BOOL) migrateOldDefaultsWithDictionary: (NSDictionary *) migratedKeys
319{
320  NSArray *allKeys;
321  id currentValue;
322  NSString *oldName, *newName;
323  int count, max;
324  BOOL requireSync;
325
326  requireSync = NO;
327
328  allKeys = [migratedKeys allKeys];
329  max = [allKeys count];
330  for (count = 0; count < max; count++)
331    {
332      oldName = [allKeys objectAtIndex: count];
333      currentValue = [source objectForKey: oldName];
334      if (currentValue)
335        {
336          newName = [migratedKeys objectForKey: oldName];
337          requireSync = YES;
338          [source setObject: currentValue forKey: newName];
339          [source removeObjectForKey: oldName];
340          [self warnWithFormat: @"defaults key '%@' was renamed to '%@'",
341                oldName, newName];
342        }
343    }
344
345  return requireSync;
346}
347
348- (BOOL) synchronize
349{
350  BOOL rc;
351
352  if ([source respondsToSelector: @selector (synchronize)])
353    rc = [source synchronize];
354  else
355    {
356      [self errorWithFormat: @"current source cannot synchronize defaults"];
357      rc = NO;
358    }
359
360  return rc;
361}
362
363@end
364