]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/AgenorUserDefaults.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1087 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 #import <Foundation/NSPropertyList.h>
23 #import <Foundation/NSUserDefaults.h>
24 #import <Foundation/NSValue.h>
25
26 #import <NGExtensions/NSNull+misc.h>
27 #import <NGExtensions/NSObject+Logs.h>
28
29 #import <GDLContentStore/GCSChannelManager.h>
30 #import <GDLContentStore/NSURL+GCS.h>
31 #import <GDLAccess/EOAdaptorChannel.h>
32 #import <GDLAccess/EOAdaptorContext.h>
33 #import <GDLAccess/EOAttribute.h>
34
35 #import "NSObject+Utilities.h"
36
37 #import "AgenorUserDefaults.h"
38
39 @implementation AgenorUserDefaults
40
41 static NSString *uidColumnName = @"uid";
42
43 - (id) initWithTableURL: (NSURL *) tableURL
44                     uid: (NSString *) userID
45               fieldName: (NSString *) defaultsFieldName
46 {
47   if ((self = [super init]))
48     {
49       if (tableURL && [userID length] > 0
50           && [defaultsFieldName length] > 0)
51         {
52           parent = [[NSUserDefaults standardUserDefaults] retain];
53           fieldName = [defaultsFieldName copy];
54           url = [tableURL copy];
55           uid = [userID copy];
56         }
57       else
58         {
59           [self errorWithFormat: @"missing arguments"];
60           [self release];
61           self = nil;
62         }
63     }
64
65   return self;
66 }
67
68 - (id) init
69 {
70   [self release];
71
72   return nil;
73 }
74
75 - (void) dealloc
76 {
77   [values release];
78   [lastFetch release];
79   [parent release];
80   [url release];
81   [uid release];
82   [fieldName release];
83   [super dealloc];
84 }
85
86 /* accessors */
87
88 - (NSURL *) tableURL
89 {
90   return url;
91 }
92
93 - (NSString *) uid
94 {
95   return uid;
96 }
97
98 - (NSString *) fieldName
99 {
100   return fieldName;
101 }
102
103 - (NSUserDefaults *) parentDefaults
104 {
105   return parent;
106 }
107
108 /* operation */
109
110 - (BOOL) primaryFetchProfile
111 {
112   GCSChannelManager *cm;
113   EOAdaptorChannel *channel;
114   NSDictionary *row;
115   NSException *ex;
116   NSString *sql, *value;
117   NSArray *attrs;
118   BOOL rc;
119 #if LIB_FOUNDATION_LIBRARY
120   NSData *plistData;
121 #endif
122
123   rc = NO;
124   
125   cm = [GCSChannelManager defaultChannelManager];
126   channel = [cm acquireOpenChannelForURL: [self tableURL]];
127   if (channel)
128     {
129       /* generate SQL */
130       sql = [NSString stringWithFormat: (@"SELECT %@"
131                                          @"  FROM %@"
132                                          @" WHERE %@ = '%@'"),
133                       fieldName, [[self tableURL] gcsTableName],
134                       uidColumnName, [self uid]];
135       
136       [values release];
137       values = [NSMutableDictionary new];
138
139       /* run SQL */
140
141       ex = [channel evaluateExpressionX: sql];
142       if (ex)
143         [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
144       else
145         {
146           /* fetch schema */
147           attrs = [channel describeResults: NO /* don't beautify */];
148   
149           /* fetch values */
150           row = [channel fetchAttributes: attrs withZone: NULL];
151           defFlags.isNew = (row == nil);
152           [channel cancelFetch];
153   
154           /* remember values */
155           value = [row objectForKey: fieldName];
156           if ([value isNotNull])
157             {
158 #if LIB_FOUNDATION_LIBRARY
159               plistData = [value dataUsingEncoding: NSUTF8StringEncoding];
160               [values setDictionary: [NSDeserializer
161                                        deserializePropertyListFromData: plistData
162                                        mutableContainers: YES]];
163
164 #else
165               [values setDictionary: [value propertyList]];
166 #endif
167             }
168
169           ASSIGN (lastFetch, [NSCalendarDate date]);
170           defFlags.modified = NO;
171           rc = YES;
172         }
173
174       [cm releaseChannel:channel];
175     }
176   else
177     [self errorWithFormat:@"failed to acquire channel for URL: %@", 
178           [self tableURL]];
179
180   return rc;
181 }
182
183 - (NSString *) generateSQLForInsert
184 {
185   NSMutableString *sql;
186   NSString *serializedDefaults;
187
188 #if LIB_FOUNDATION_LIBRARY
189   serializedDefaults = [values stringRepresentation];
190
191   sql = [NSString stringWithFormat: (@"INSERT INTO %@"
192                                      @"            (%@, %@)"
193                                      @"     VALUES ('%@', '%@')"),
194                   [[self tableURL] gcsTableName], uidColumnName, fieldName,
195                   [self uid],
196                   [serializedDefaults stringByReplacingString:@"'" withString:@"''"]];
197 #else
198   NSData *serializedDefaultsData;
199   NSString *error;
200
201   serializedDefaultsData
202     = [NSPropertyListSerialization dataFromPropertyList: values
203                                    format: NSPropertyListOpenStepFormat
204                                    errorDescription: &error];
205
206   if (error)
207     sql = nil;
208   else
209     {
210       serializedDefaults = [[NSString alloc] initWithData: serializedDefaultsData
211                                              encoding: NSUTF8StringEncoding];
212
213       sql = [NSString stringWithFormat: (@"INSERT INTO %@"
214                                          @"            (%@, %@)"
215                                          @"     VALUES ('%@', '%@')"),
216                       [[self tableURL] gcsTableName], uidColumnName, fieldName,
217                       [self uid],
218                       [serializedDefaults stringByReplacingString:@"'" withString:@"''"]];
219       [serializedDefaults release];
220     }
221 #endif
222
223   return sql;
224 }
225
226 - (NSString *) generateSQLForUpdate
227 {
228   NSMutableString *sql;
229   NSString *serializedDefaults;
230
231 #if LIB_FOUNDATION_LIBRARY
232   serializedDefaults = [values stringRepresentation];
233
234   sql = [NSString stringWithFormat: (@"UPDATE %@"
235                                      @"     SET %@ = '%@'"
236                                      @"   WHERE %@ = '%@'"),
237                   [[self tableURL] gcsTableName],
238                   fieldName,
239                   [serializedDefaults stringByReplacingString:@"'" withString:@"''"],
240                   uidColumnName, [self uid]];
241 #else
242   NSData *serializedDefaultsData;
243   NSString *error;
244
245   serializedDefaultsData
246     = [NSPropertyListSerialization dataFromPropertyList: values
247                                    format: NSPropertyListOpenStepFormat
248                                    errorDescription: &error];
249   error = nil;
250   if (error)
251     {
252       sql = nil;
253       [error release];
254     }
255   else
256     {
257       serializedDefaults = [[NSString alloc] initWithData: serializedDefaultsData
258                                              encoding: NSUTF8StringEncoding];
259
260       sql = [NSString stringWithFormat: (@"UPDATE %@"
261                                          @"     SET %@ = '%@'"
262                                          @"   WHERE %@ = '%@'"),
263                       [[self tableURL] gcsTableName],
264                       fieldName,
265                       [serializedDefaults stringByReplacingString:@"'" withString:@"''"],
266                       uidColumnName, [self uid]];
267       [serializedDefaults release];
268     }
269 #endif
270
271   return sql;
272 }
273
274 - (BOOL) primaryStoreProfile
275 {
276   GCSChannelManager *cm;
277   EOAdaptorChannel *channel;
278   NSException *ex;
279   NSString *sql;
280   BOOL rc;
281
282   rc = NO;
283   
284   cm = [GCSChannelManager defaultChannelManager];
285   sql = ((defFlags.isNew)
286          ? [self generateSQLForInsert] 
287          : [self generateSQLForUpdate]);
288   if (sql)
289     {
290       channel = [cm acquireOpenChannelForURL: [self tableURL]];
291       if (channel)
292         {
293           ex = [channel evaluateExpressionX:sql];
294           if (ex)
295             [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
296           else
297             {
298               if ([[channel adaptorContext] hasOpenTransaction])
299                 {
300                   ex = [channel evaluateExpressionX: @"COMMIT TRANSACTION"];
301                   if (ex)
302                     [self errorWithFormat:@"could not commit transaction for update: %@", ex];
303                   else
304                     rc = YES;
305                 }
306               else
307                 rc = YES;
308               
309               defFlags.modified = NO;
310               defFlags.isNew = NO;
311             }
312           
313           [cm releaseChannel: channel];
314         }
315       else
316         [self errorWithFormat: @"failed to acquire channel for URL: %@", 
317               [self tableURL]];
318     }
319   else
320     [self errorWithFormat: @"failed to generate SQL for storing defaults"];
321
322   return rc;
323 }
324
325 - (BOOL) fetchProfile
326 {
327   return (values || [self primaryFetchProfile]);
328 }
329
330 - (NSString *) jsonRepresentation
331 {
332   [self fetchProfile];
333
334   return [values jsonRepresentation];
335 }
336
337 /* value access */
338
339 - (void) setObject: (id) value
340             forKey: (NSString *) key
341
342   id old;
343   
344   if (![self fetchProfile])
345     return;
346
347   /* check whether the value is actually modified */
348   if (!defFlags.modified)
349     {
350       old = [values objectForKey: key];
351       if (old == value || [old isEqual: value]) /* value didn't change */
352         return;
353   
354       /* we need to this because our typed accessors convert to strings */
355       // TODO: especially problematic with bools
356       if ([value isKindOfClass: [NSString class]]) {
357         if (![old isKindOfClass: [NSString class]])
358           if ([[old description] isEqualToString: value])
359             return;
360       }
361     }
362
363   /* set in hash and mark as modified */
364   [values setObject: value forKey: key];
365
366   defFlags.modified = YES;
367 }
368
369 - (id) objectForKey: (NSString *) key
370 {
371   id value;
372   
373   if (![self fetchProfile])
374     value = nil;
375   else
376     value = [values objectForKey: key];
377
378   return value;
379 }
380
381 - (void) removeObjectForKey: (NSString *) key
382 {
383   [self setObject: nil forKey: key];
384 }
385
386 /* saving changes */
387
388 - (BOOL) synchronize
389 {
390 //   if (!defFlags.modified) /* was not modified */
391 //     return YES;
392   
393   /* ensure fetched data (more or less guaranteed by modified!=0) */
394   if (![self fetchProfile])
395     return NO;
396   
397   /* store */
398   if (![self primaryStoreProfile])
399     {
400       [self primaryFetchProfile];
401       return NO;
402     }
403
404   /* refetch */
405   return [self primaryFetchProfile];
406 }
407
408 - (void) flush
409 {
410   [values release];
411   [lastFetch release];
412   values = nil;
413   lastFetch = nil;
414   defFlags.modified = NO;
415   defFlags.isNew = NO;
416 }
417
418 /* typed accessors */
419
420 - (NSArray *) arrayForKey: (NSString *) key
421 {
422   return [self objectForKey: key];
423 }
424
425 - (NSDictionary *) dictionaryForKey: (NSString *) key
426 {
427   return [self objectForKey: key];
428 }
429
430 - (NSData *) dataForKey: (NSString *) key
431 {
432   return [self objectForKey: key];
433 }
434
435 - (NSString *) stringForKey: (NSString *) key
436 {
437   return [self objectForKey: key];
438 }
439
440 - (BOOL) boolForKey: (NSString *) key
441 {
442   return [[self objectForKey: key] boolValue];
443 }
444
445 - (float) floatForKey: (NSString *) key
446 {
447   return [[self objectForKey: key] floatValue];
448 }
449
450 - (int) integerForKey: (NSString *) key
451 {
452   return [[self objectForKey: key] intValue];
453 }
454
455 - (void) setBool: (BOOL) value
456           forKey: (NSString *) key
457 {
458   // TODO: need special support here for int-DB fields
459   [self setObject: [NSNumber numberWithBool: value]
460         forKey: key];
461 }
462
463 - (void) setFloat: (float) value
464            forKey: (NSString *) key
465 {
466   [self setObject: [NSNumber numberWithFloat: value]
467         forKey: key];
468 }
469
470 - (void) setInteger: (int) value
471              forKey: (NSString *) key
472 {
473   [self setObject: [NSNumber numberWithInt: value]
474         forKey: key];
475 }
476
477 @end /* AgenorUserDefaults */