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