2 Copyright (C) 2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "AgenorUserDefaults.h"
23 #include <GDLContentStore/GCSChannelManager.h>
24 #include <GDLContentStore/NSURL+GCS.h>
25 #include <GDLAccess/EOAdaptorChannel.h>
26 #include <GDLAccess/EOAdaptorContext.h>
27 #include <GDLAccess/EOAttribute.h>
30 @implementation AgenorUserDefaults
32 static NSString *uidColumnName = @"uid";
34 - (id)initWithTableURL:(NSURL *)_url uid:(NSString *)_uid {
35 if ((self = [super init])) {
36 if (_url == nil || [_uid length] < 1) {
37 [self errorWithFormat:@"tried to create AgenorUserDefaults w/o args!"];
42 self->parent = [[NSUserDefaults standardUserDefaults] retain];
43 self->url = [_url copy];
44 self->uid = [_uid copy];
49 return [self initWithTableURL:nil uid:nil];
53 [self->attributes release];
54 [self->lastFetch release];
55 [self->parent release];
70 - (NSUserDefaults *)parentDefaults {
76 - (void)_loadAttributes:(NSArray *)_attrs {
77 NSMutableArray *fields;
78 NSMutableDictionary *attrmap;
81 fields = [[NSMutableArray alloc] initWithCapacity:16];
82 attrmap = [[NSMutableDictionary alloc] initWithCapacity:16];
83 for (i = 0, count = [_attrs count]; i < count; i++) {
87 attr = [_attrs objectAtIndex:i];
88 name = [attr valueForKey:@"name"];
89 [attrmap setObject:attr forKey:name];
91 if (![name isEqual:uidColumnName])
92 [fields addObject:name];
95 ASSIGNCOPY(self->fieldNames, fields);
96 ASSIGNCOPY(self->attributes, attrmap);
101 - (BOOL)primaryFetchProfile {
102 GCSChannelManager *cm;
103 EOAdaptorChannel *channel;
109 cm = [GCSChannelManager defaultChannelManager];
110 if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
111 [self errorWithFormat:@"failed to acquire channel for URL: %@",
118 sql = [[self tableURL] gcsTableName];
119 sql = [@"SELECT * FROM " stringByAppendingString:sql];
120 sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'",
121 uidColumnName, [self uid]];
125 if ((ex = [channel evaluateExpressionX:sql]) != nil) {
126 [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
127 [cm releaseChannel:channel];
133 attrs = [channel describeResults:NO /* don't beautify */];
134 [self _loadAttributes:attrs];
138 row = [channel fetchAttributes:attrs withZone:NULL];
139 self->defFlags.isNew = (row != nil) ? 0 : 1;
140 [channel cancelFetch];
142 /* remember values */
144 [self->values release]; self->values = nil;
145 self->values = (row != nil)
147 : [[NSMutableDictionary alloc] initWithCapacity:8];
148 [self->values removeObjectForKey:uidColumnName];
150 ASSIGN(self->lastFetch, [NSCalendarDate date]);
151 self->defFlags.modified = 0;
153 [cm releaseChannel:channel];
157 - (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute {
160 if (![_value isNotNull])
163 if ([[_attribute externalType] hasPrefix:@"int"])
164 return [_value stringValue];
166 s = [_value stringValue];
167 s = [s stringByReplacingString:@"'" withString:@"''"];
168 s = [[@"'" stringByAppendingString:s] stringByAppendingString:@"'"];
172 - (NSString *)generateSQLForInsert {
173 NSMutableString *sql;
176 if ([self->values count] == 0)
179 sql = [NSMutableString stringWithCapacity:2048];
181 [sql appendString:@"INSERT INTO "];
182 [sql appendString:[[self tableURL] gcsTableName]];
183 [sql appendString:@" ( uid"];
185 for (i = 0, count = [self->fieldNames count]; i < count; i++) {
188 attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]];
189 [sql appendString:@", "];
190 [sql appendString:[attr columnName]];
193 [sql appendString:@") VALUES ("];
195 [sql appendString:@"'"];
196 [sql appendString:[self uid]]; // TODO: escaping necessary?
197 [sql appendString:@"'"];
199 for (i = 0, count = [self->fieldNames count]; i < count; i++) {
203 attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]];
204 value = [self->values objectForKey:[self->fieldNames objectAtIndex:i]];
206 [sql appendString:@", "];
207 [sql appendString:[self formatValue:value forAttribute:attr]];
210 [sql appendString:@")"];
214 - (NSString *)generateSQLForUpdate {
215 NSMutableString *sql;
218 if ([self->values count] == 0)
221 sql = [NSMutableString stringWithCapacity:2048];
223 [sql appendString:@"UPDATE "];
224 [sql appendString:[[self tableURL] gcsTableName]];
225 [sql appendString:@" SET "];
227 for (i = 0, count = [self->fieldNames count]; i < count; i++) {
232 name = [self->fieldNames objectAtIndex:i];
233 value = [self->values objectForKey:name];
234 attr = [self->attributes objectForKey:name];
236 if (i != 0) [sql appendString:@", "];
237 [sql appendString:[attr columnName]];
238 [sql appendString:@" = "];
239 [sql appendString:[self formatValue:value forAttribute:attr]];
242 [sql appendString:@" WHERE "];
243 [sql appendString:uidColumnName];
244 [sql appendString:@" = '"];
245 [sql appendString:[self uid]];
246 [sql appendString:@"'"];
250 - (BOOL)primaryStoreProfile {
251 GCSChannelManager *cm;
252 EOAdaptorChannel *channel;
256 cm = [GCSChannelManager defaultChannelManager];
257 if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
258 [self errorWithFormat:@"failed to acquire channel for URL: %@",
265 sql = self->defFlags.isNew
266 ? [self generateSQLForInsert]
267 : [self generateSQLForUpdate];
268 if ((ex = [channel evaluateExpressionX:sql]) != nil) {
269 [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
270 [cm releaseChannel:channel];
277 if ([[channel adaptorContext] hasOpenTransaction])
278 ex = [channel evaluateExpressionX:@"COMMIT TRANSACTION"];
280 [cm releaseChannel:channel];
283 [self errorWithFormat:@"could not commit transaction for update: %@", ex];
287 self->defFlags.modified = 0;
288 self->defFlags.isNew = 0;
293 - (BOOL)fetchProfile {
294 if (self->values != nil)
297 return [self primaryFetchProfile];
300 - (NSArray *)primaryDefaultNames {
301 if (![self fetchProfile])
304 return self->fieldNames;
309 - (void)setObject:(id)_value forKey:(NSString *)_key {
310 if (![self fetchProfile])
313 if (![self->fieldNames containsObject:_key]) {
314 [self errorWithFormat:@"tried to write key: '%@'", _key];
318 /* check whether the value is actually modified */
319 if (!self->defFlags.modified) {
322 old = [self->values objectForKey:_key];
323 if (old == _value || [old isEqual:_value]) /* value didn't change */
326 /* we need to this because our typed accessors convert to strings */
327 // TODO: especially problematic with bools
328 if ([_value isKindOfClass:[NSString class]]) {
329 if (![old isKindOfClass:[NSString class]])
330 if ([[old description] isEqualToString:_value])
335 /* set in hash and mark as modified */
336 [self->values setObject:(_value ? _value : [NSNull null]) forKey:_key];
337 self->defFlags.modified = 1;
340 - (id)objectForKey:(NSString *)_key {
343 if (![self fetchProfile])
346 if (![self->fieldNames containsObject:_key])
347 return [self->parent objectForKey:_key];
349 value = [self->values objectForKey:_key];
350 return [value isNotNull] ? value : nil;
353 - (void)removeObjectForKey:(NSString *)_key {
354 [self setObject:nil forKey:_key];
359 - (BOOL)synchronize {
360 if (!self->defFlags.modified) /* was not modified */
363 /* ensure fetched data (more or less guaranteed by modified!=0) */
364 if (![self fetchProfile])
368 if (![self primaryStoreProfile]) {
369 [self primaryFetchProfile];
374 return [self primaryFetchProfile];
378 [self->values release]; self->values = nil;
379 [self->fieldNames release]; self->fieldNames = nil;
380 [self->attributes release]; self->attributes = nil;
381 [self->lastFetch release]; self->lastFetch = nil;
382 self->defFlags.modified = 0;
383 self->defFlags.isNew = 0;
386 /* typed accessors */
388 - (NSArray *)arrayForKey:(NSString *)_key {
389 id obj = [self objectForKey:_key];
390 return [obj isKindOfClass:[NSArray class]] ? obj : nil;
393 - (NSDictionary *)dictionaryForKey:(NSString *)_key {
394 id obj = [self objectForKey:_key];
395 return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
398 - (NSData *)dataForKey:(NSString *)_key {
399 id obj = [self objectForKey:_key];
400 return [obj isKindOfClass:[NSData class]] ? obj : nil;
403 - (NSArray *)stringArrayForKey:(NSString *)_key {
404 id obj = [self objectForKey:_key];
406 Class strClass = [NSString class];
408 if (![obj isKindOfClass:[NSArray class]])
411 for (n = [obj count]-1; n >= 0; n--) {
412 if (![[obj objectAtIndex:n] isKindOfClass:strClass])
418 - (NSString *)stringForKey:(NSString *)_key {
419 id obj = [self objectForKey:_key];
420 return [obj isKindOfClass:[NSString class]] ? obj : nil;
423 - (BOOL)boolForKey:(NSString *)_key {
424 // TODO: need special support here for int-DB fields
427 if ((obj = [self objectForKey:_key]) == nil)
429 if ([obj isKindOfClass:[NSString class]]) {
430 if ([obj compare:@"YES" options:NSCaseInsensitiveSearch] == NSOrderedSame)
433 if ([obj respondsToSelector:@selector(intValue)])
434 return [obj intValue] ? YES : NO;
438 - (float)floatForKey:(NSString *)_key {
439 id obj = [self stringForKey:_key];
440 return (obj != nil) ? [obj floatValue] : 0.0;
442 - (int)integerForKey:(NSString *)_key {
443 id obj = [self stringForKey:_key];
444 return (obj != nil) ? [obj intValue] : 0;
447 - (void)setBool:(BOOL)value forKey:(NSString *)_key {
448 // TODO: need special support here for int-DB fields
449 [self setObject:(value ? @"YES" : @"NO") forKey:_key];
451 - (void)setFloat:(float)value forKey:(NSString *)_key {
452 [self setObject:[NSString stringWithFormat:@"%f", value] forKey:_key];
454 - (void)setInteger:(int)value forKey:(NSString *)_key {
455 [self setObject:[NSString stringWithFormat:@"%d", value] forKey:_key];
460 - (NSString *)description {
463 ms = [NSMutableString stringWithCapacity:16];
464 [ms appendFormat:@"<0x%08X[%@]>", self, NSStringFromClass([self class])];
465 [ms appendFormat:@" uid=%@", self->uid];
466 [ms appendFormat:@" url=%@", [self->url absoluteString]];
467 [ms appendFormat:@" parent=0x%08X", self->parent];
468 [ms appendString:@">"];
472 @end /* AgenorUserDefaults */