]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/AgenorUserDefaults.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1015 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / SOGo / AgenorUserDefaults.m
1 /*
2   Copyright (C) 2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
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>
28 #include "common.h"
29
30 @implementation AgenorUserDefaults
31
32 static NSString *uidColumnName = @"uid";
33
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!"];
38       [self release];
39       return nil;
40     }
41
42     self->parent = [[NSUserDefaults standardUserDefaults] retain];
43     self->url = [_url copy];
44     self->uid = [_uid copy];
45   }
46   return self;
47 }
48 - (id)init {
49   return [self initWithTableURL:nil uid:nil];
50 }
51
52 - (void)dealloc {
53   [self->attributes release];
54   [self->lastFetch  release];
55   [self->parent     release];
56   [self->url        release];
57   [self->uid        release];
58   [super dealloc];
59 }
60
61 /* accessors */
62
63 - (NSURL *)tableURL {
64   return self->url;
65 }
66 - (NSString *)uid {
67   return self->uid;
68 }
69
70 - (NSUserDefaults *)parentDefaults {
71   return self->parent;
72 }
73
74 /* operation */
75
76 - (void)_loadAttributes:(NSArray *)_attrs {
77   NSMutableArray      *fields;
78   NSMutableDictionary *attrmap;
79   unsigned i, count;
80   
81   fields  = [[NSMutableArray      alloc] initWithCapacity:16];
82   attrmap = [[NSMutableDictionary alloc] initWithCapacity:16];
83   for (i = 0, count = [_attrs count]; i < count; i++) {
84     EOAttribute *attr;
85     NSString    *name;
86     
87     attr = [_attrs objectAtIndex:i];
88     name = [attr valueForKey:@"name"];
89     [attrmap setObject:attr forKey:name];
90     
91     if (![name isEqual:uidColumnName]) 
92       [fields  addObject:name];
93   }
94   
95   ASSIGNCOPY(self->fieldNames, fields);
96   ASSIGNCOPY(self->attributes, attrmap);
97   [attrmap release];
98   [fields  release];
99 }
100
101 - (BOOL)primaryFetchProfile {
102   GCSChannelManager *cm;
103   EOAdaptorChannel  *channel;
104   NSDictionary      *row;
105   NSException       *ex;
106   NSString          *sql;
107   NSArray           *attrs;
108   
109   cm = [GCSChannelManager defaultChannelManager];
110   if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
111     [self errorWithFormat:@"failed to acquire channel for URL: %@", 
112           [self tableURL]];
113     return NO;
114   }
115   
116   /* generate SQL */
117   
118   sql = [[self tableURL] gcsTableName];
119   sql = [@"SELECT * FROM " stringByAppendingString:sql];
120   sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", 
121              uidColumnName, [self uid]];
122   
123   /* run SQL */
124   
125   if ((ex = [channel evaluateExpressionX:sql]) != nil) {
126     [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
127     [cm releaseChannel:channel];
128     return NO;
129   }
130
131   /* fetch schema */
132   
133   attrs = [channel describeResults:NO /* don't beautify */];
134   [self _loadAttributes:attrs];
135   
136   /* fetch values */
137   
138   row = [channel fetchAttributes:attrs withZone:NULL];
139   self->defFlags.isNew = (row != nil) ? 0 : 1;
140   [channel cancelFetch];
141   
142   /* remember values */
143   
144   [self->values release]; self->values = nil;
145   self->values = (row != nil)
146     ? [row mutableCopy]
147     : [[NSMutableDictionary alloc] initWithCapacity:8];
148   [self->values removeObjectForKey:uidColumnName];
149   
150   ASSIGN(self->lastFetch, [NSCalendarDate date]);
151   self->defFlags.modified = 0;
152   
153   [cm releaseChannel:channel];
154   return YES;
155 }
156
157 - (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute {
158   NSString *s;
159   
160   if (![_value isNotNull])
161     return @"NULL";
162   
163   if ([[_attribute externalType] hasPrefix:@"int"])
164     return [_value stringValue];
165   
166   s = [_value stringValue];
167   s = [s stringByReplacingString:@"'" withString:@"''"];
168   s = [[@"'" stringByAppendingString:s] stringByAppendingString:@"'"];
169   return s;
170 }
171
172 - (NSString *)generateSQLForInsert {
173   NSMutableString *sql;
174   unsigned i, count;
175   
176   if ([self->values count] == 0)
177     return nil;
178   
179   sql = [NSMutableString stringWithCapacity:2048];
180   
181   [sql appendString:@"INSERT INTO "];
182   [sql appendString:[[self tableURL] gcsTableName]];
183   [sql appendString:@" ( uid"];
184
185   for (i = 0, count = [self->fieldNames count]; i < count; i++) {
186     EOAttribute *attr;
187     
188     attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]];
189     [sql appendString:@", "];
190     [sql appendString:[attr columnName]];
191   }
192   
193   [sql appendString:@") VALUES ("];
194   
195   [sql appendString:@"'"];
196   [sql appendString:[self uid]]; // TODO: escaping necessary?
197   [sql appendString:@"'"];
198   
199   for (i = 0, count = [self->fieldNames count]; i < count; i++) {
200     EOAttribute *attr;
201     id value;
202     
203     attr  = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]];
204     value = [self->values objectForKey:[self->fieldNames objectAtIndex:i]];
205     
206     [sql appendString:@", "];
207     [sql appendString:[self formatValue:value forAttribute:attr]];
208   }
209   
210   [sql appendString:@")"];
211   return sql;
212 }
213
214 - (NSString *)generateSQLForUpdate {
215   NSMutableString *sql;
216   unsigned i, count;
217   
218   if ([self->values count] == 0)
219     return nil;
220   
221   sql = [NSMutableString stringWithCapacity:2048];
222   
223   [sql appendString:@"UPDATE "];
224   [sql appendString:[[self tableURL] gcsTableName]];
225   [sql appendString:@" SET "];
226   
227   for (i = 0, count = [self->fieldNames count]; i < count; i++) {
228     EOAttribute *attr;
229     NSString    *name;
230     id value;
231     
232     name  = [self->fieldNames objectAtIndex:i];
233     value = [self->values objectForKey:name];
234     attr  = [self->attributes objectForKey:name];
235     
236     if (i != 0) [sql appendString:@", "];
237     [sql appendString:[attr columnName]];
238     [sql appendString:@" = "];
239     [sql appendString:[self formatValue:value forAttribute:attr]];
240   }
241   
242   [sql appendString:@" WHERE "];
243   [sql appendString:uidColumnName];
244   [sql appendString:@" = '"];
245   [sql appendString:[self uid]];
246   [sql appendString:@"'"];
247   return sql;
248 }
249
250 - (BOOL)primaryStoreProfile {
251   GCSChannelManager *cm;
252   EOAdaptorChannel  *channel;
253   NSException       *ex;
254   NSString          *sql;
255   
256   cm = [GCSChannelManager defaultChannelManager];
257   if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
258     [self errorWithFormat:@"failed to acquire channel for URL: %@", 
259           [self tableURL]];
260     return NO;
261   }
262   
263   /* run SQL */
264   
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];
271     return NO;
272   }
273   
274   /* commit */
275   
276   ex = nil;
277   if ([[channel adaptorContext] hasOpenTransaction])
278     ex = [channel evaluateExpressionX:@"COMMIT TRANSACTION"];
279   
280   [cm releaseChannel:channel];
281   
282   if (ex != nil) {
283     [self errorWithFormat:@"could not commit transaction for update: %@", ex];
284     return NO;
285   }
286   
287   self->defFlags.modified = 0;
288   self->defFlags.isNew    = 0;
289   return YES;
290 }
291
292
293 - (BOOL)fetchProfile {
294   if (self->values != nil)
295     return YES;
296   
297   return [self primaryFetchProfile];
298 }
299
300 - (NSArray *)primaryDefaultNames {
301   if (![self fetchProfile])
302     return nil;
303   
304   return self->fieldNames;
305 }
306
307 /* value access */
308
309 - (void)setObject:(id)_value forKey:(NSString *)_key {
310   if (![self fetchProfile])
311     return;
312   
313   if (![self->fieldNames containsObject:_key]) {
314     [self errorWithFormat:@"tried to write key: '%@'", _key];
315     return;
316   }
317   
318   /* check whether the value is actually modified */
319   if (!self->defFlags.modified) {
320     id old;
321
322     old = [self->values objectForKey:_key];
323     if (old == _value || [old isEqual:_value]) /* value didn't change */
324       return;
325   
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])
331         return;
332     }
333   }
334   
335   /* set in hash and mark as modified */
336   [self->values setObject:(_value ? _value : [NSNull null])  forKey:_key];
337   self->defFlags.modified = 1;
338 }
339
340 - (id)objectForKey:(NSString *)_key {
341   id value;
342   
343   if (![self fetchProfile])
344     return nil;
345   
346   if (![self->fieldNames containsObject:_key])
347     return [self->parent objectForKey:_key];
348   
349   value = [self->values objectForKey:_key];
350   return [value isNotNull] ? value : nil;
351 }
352
353 - (void)removeObjectForKey:(NSString *)_key {
354   [self setObject:nil forKey:_key];
355 }
356
357 /* saving changes */
358
359 - (BOOL)synchronize {
360   if (!self->defFlags.modified) /* was not modified */
361     return YES;
362   
363   /* ensure fetched data (more or less guaranteed by modified!=0) */
364   if (![self fetchProfile])
365     return NO;
366   
367   /* store */
368   if (![self primaryStoreProfile]) {
369     [self primaryFetchProfile];
370     return NO;
371   }
372   
373   /* refetch */
374   return [self primaryFetchProfile];
375 }
376
377 - (void)flush {
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;
384 }
385
386 /* typed accessors */
387
388 - (NSArray *)arrayForKey:(NSString *)_key {
389   id obj = [self objectForKey:_key];
390   return [obj isKindOfClass:[NSArray class]] ? obj : nil;
391 }
392
393 - (NSDictionary *)dictionaryForKey:(NSString *)_key {
394   id obj = [self objectForKey:_key];
395   return [obj isKindOfClass:[NSDictionary class]] ? obj : nil;
396 }
397
398 - (NSData *)dataForKey:(NSString *)_key {
399   id obj = [self objectForKey:_key];
400   return [obj isKindOfClass:[NSData class]] ? obj : nil;
401 }
402
403 - (NSArray *)stringArrayForKey:(NSString *)_key {
404   id obj = [self objectForKey:_key];
405   int n;
406   Class strClass = [NSString class];
407   
408   if (![obj isKindOfClass:[NSArray class]])
409     return nil;
410         
411   for (n = [obj count]-1; n >= 0; n--) {
412     if (![[obj objectAtIndex:n] isKindOfClass:strClass])
413       return nil;
414   }
415   return obj;
416 }
417
418 - (NSString *)stringForKey:(NSString *)_key {
419   id obj = [self objectForKey:_key];
420   return [obj isKindOfClass:[NSString class]] ? obj : nil;
421 }
422
423 - (BOOL)boolForKey:(NSString *)_key {
424   // TODO: need special support here for int-DB fields
425   id obj;
426   
427   if ((obj = [self objectForKey:_key]) == nil)
428     return NO;
429   if ([obj isKindOfClass:[NSString class]]) {
430     if ([obj compare:@"YES" options:NSCaseInsensitiveSearch] == NSOrderedSame)
431       return YES;
432   }
433   if ([obj respondsToSelector:@selector(intValue)])
434     return [obj intValue] ? YES : NO;
435   return NO;
436 }
437
438 - (float)floatForKey:(NSString *)_key {
439   id obj = [self stringForKey:_key];
440   return (obj != nil) ? [obj floatValue] : 0.0;
441 }
442 - (int)integerForKey:(NSString *)_key {
443   id obj = [self stringForKey:_key];
444   return (obj != nil) ? [obj intValue] : 0;
445 }
446
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];
450 }
451 - (void)setFloat:(float)value forKey:(NSString *)_key {
452   [self setObject:[NSString stringWithFormat:@"%f", value] forKey:_key];
453 }
454 - (void)setInteger:(int)value forKey:(NSString *)_key {
455   [self setObject:[NSString stringWithFormat:@"%d", value] forKey:_key];
456 }
457
458 /* description */
459
460 - (NSString *)description {
461   NSMutableString *ms;
462   
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:@">"];
469   return ms;
470 }
471
472 @end /* AgenorUserDefaults */