]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/AgenorUserDefaults.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1117 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: @"'"
197                                       withString:@"''"]];
198 #else
199   NSData *serializedDefaultsData;
200   NSString *error;
201
202   error = nil;
203   serializedDefaultsData
204     = [NSPropertyListSerialization dataFromPropertyList: values
205                                    format: NSPropertyListOpenStepFormat
206                                    errorDescription: &error];
207
208   if (error)
209     {
210       sql = nil;
211       [error release];
212     }
213   else
214     {
215       serializedDefaults = [[NSString alloc] initWithData: serializedDefaultsData
216                                              encoding: NSUTF8StringEncoding];
217
218       sql = [NSString stringWithFormat: (@"INSERT INTO %@"
219                                          @"            (%@, %@)"
220                                          @"     VALUES ('%@', '%@')"),
221                       [[self tableURL] gcsTableName], uidColumnName, fieldName,
222                       [self uid],
223                       [serializedDefaults stringByReplacingString:@"'" withString:@"''"]];
224       [serializedDefaults release];
225     }
226 #endif
227
228   return sql;
229 }
230
231 - (NSString *) generateSQLForUpdate
232 {
233   NSMutableString *sql;
234   NSString *serializedDefaults;
235
236 #if LIB_FOUNDATION_LIBRARY
237   serializedDefaults = [values stringRepresentation];
238
239   sql = [NSString stringWithFormat: (@"UPDATE %@"
240                                      @"     SET %@ = '%@'"
241                                      @"   WHERE %@ = '%@'"),
242                   [[self tableURL] gcsTableName],
243                   fieldName,
244                   [serializedDefaults stringByReplacingString:@"'" withString:@"''"],
245                   uidColumnName, [self uid]];
246 #else
247   NSData *serializedDefaultsData;
248   NSString *error;
249
250   error = nil;
251   serializedDefaultsData
252     = [NSPropertyListSerialization dataFromPropertyList: values
253                                    format: NSPropertyListOpenStepFormat
254                                    errorDescription: &error];
255   if (error)
256     {
257       sql = nil;
258       [error release];
259     }
260   else
261     {
262       serializedDefaults = [[NSString alloc] initWithData: serializedDefaultsData
263                                              encoding: NSUTF8StringEncoding];
264
265       sql = [NSString stringWithFormat: (@"UPDATE %@"
266                                          @"     SET %@ = '%@'"
267                                          @"   WHERE %@ = '%@'"),
268                       [[self tableURL] gcsTableName],
269                       fieldName,
270                       [serializedDefaults stringByReplacingString: @"'"
271                                           withString: @"''"],
272                       uidColumnName, [self uid]];
273       [serializedDefaults release];
274     }
275 #endif
276
277   return sql;
278 }
279
280 - (BOOL) primaryStoreProfile
281 {
282   GCSChannelManager *cm;
283   EOAdaptorChannel *channel;
284   NSException *ex;
285   NSString *sql;
286   BOOL rc;
287
288   rc = NO;
289   
290   cm = [GCSChannelManager defaultChannelManager];
291   sql = ((defFlags.isNew)
292          ? [self generateSQLForInsert] 
293          : [self generateSQLForUpdate]);
294   if (sql)
295     {
296       channel = [cm acquireOpenChannelForURL: [self tableURL]];
297       if (channel)
298         {
299           ex = [channel evaluateExpressionX:sql];
300           if (ex)
301             [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
302           else
303             {
304               if ([[channel adaptorContext] hasOpenTransaction])
305                 {
306                   ex = [channel evaluateExpressionX: @"COMMIT TRANSACTION"];
307                   if (ex)
308                     [self errorWithFormat:@"could not commit transaction for update: %@", ex];
309                   else
310                     rc = YES;
311                 }
312               else
313                 rc = YES;
314               
315               defFlags.modified = NO;
316               defFlags.isNew = NO;
317             }
318           
319           [cm releaseChannel: channel];
320         }
321       else
322         [self errorWithFormat: @"failed to acquire channel for URL: %@", 
323               [self tableURL]];
324     }
325   else
326     [self errorWithFormat: @"failed to generate SQL for storing defaults"];
327
328   return rc;
329 }
330
331 - (BOOL) fetchProfile
332 {
333   return (values || [self primaryFetchProfile]);
334 }
335
336 - (NSString *) jsonRepresentation
337 {
338   [self fetchProfile];
339
340   return [values jsonRepresentation];
341 }
342
343 /* value access */
344
345 - (void) setObject: (id) value
346             forKey: (NSString *) key
347
348   id old;
349   
350   if (![self fetchProfile])
351     return;
352
353   /* check whether the value is actually modified */
354   if (!defFlags.modified)
355     {
356       old = [values objectForKey: key];
357       if (old == value || [old isEqual: value]) /* value didn't change */
358         return;
359   
360       /* we need to this because our typed accessors convert to strings */
361       // TODO: especially problematic with bools
362       if ([value isKindOfClass: [NSString class]]) {
363         if (![old isKindOfClass: [NSString class]])
364           if ([[old description] isEqualToString: value])
365             return;
366       }
367     }
368
369   /* set in hash and mark as modified */
370   [values setObject: value forKey: key];
371
372   defFlags.modified = YES;
373 }
374
375 - (id) objectForKey: (NSString *) key
376 {
377   id value;
378   
379   if (![self fetchProfile])
380     value = nil;
381   else
382     value = [values objectForKey: key];
383
384   return value;
385 }
386
387 - (void) removeObjectForKey: (NSString *) key
388 {
389   [self setObject: nil forKey: key];
390 }
391
392 /* saving changes */
393
394 - (BOOL) synchronize
395 {
396 //   if (!defFlags.modified) /* was not modified */
397 //     return YES;
398   
399   /* ensure fetched data (more or less guaranteed by modified!=0) */
400   if (![self fetchProfile])
401     return NO;
402
403   /* store */
404   if (![self primaryStoreProfile])
405     {
406       [self primaryFetchProfile];
407       return NO;
408     }
409
410   /* refetch */
411   return [self primaryFetchProfile];
412 }
413
414 - (void) flush
415 {
416   [values release];
417   [lastFetch release];
418   values = nil;
419   lastFetch = nil;
420   defFlags.modified = NO;
421   defFlags.isNew = NO;
422 }
423
424 /* typed accessors */
425
426 - (NSArray *) arrayForKey: (NSString *) key
427 {
428   return [self objectForKey: key];
429 }
430
431 - (NSDictionary *) dictionaryForKey: (NSString *) key
432 {
433   return [self objectForKey: key];
434 }
435
436 - (NSData *) dataForKey: (NSString *) key
437 {
438   return [self objectForKey: key];
439 }
440
441 - (NSString *) stringForKey: (NSString *) key
442 {
443   return [self objectForKey: key];
444 }
445
446 - (BOOL) boolForKey: (NSString *) key
447 {
448   return [[self objectForKey: key] boolValue];
449 }
450
451 - (float) floatForKey: (NSString *) key
452 {
453   return [[self objectForKey: key] floatValue];
454 }
455
456 - (int) integerForKey: (NSString *) key
457 {
458   return [[self objectForKey: key] intValue];
459 }
460
461 - (void) setBool: (BOOL) value
462           forKey: (NSString *) key
463 {
464   // TODO: need special support here for int-DB fields
465   [self setObject: [NSNumber numberWithBool: value]
466         forKey: key];
467 }
468
469 - (void) setFloat: (float) value
470            forKey: (NSString *) key
471 {
472   [self setObject: [NSNumber numberWithFloat: value]
473         forKey: key];
474 }
475
476 - (void) setInteger: (int) value
477              forKey: (NSString *) key
478 {
479   [self setObject: [NSNumber numberWithInt: value]
480         forKey: key];
481 }
482
483 @end /* AgenorUserDefaults */