diff --git a/ChangeLog b/ChangeLog index c1764a07d..8904490af 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,12 @@ 2007-03-27 Wolfgang Sourdeau + * SoObjects/SOGo/AgenorUserDefaults.m: rewrote a big part of the + module to bind the userdefaults to a property list contained in a + specified field of the sogo_user_profile table. + ([AgenorUserDefaults + -initWithTableURL:tableURLuid:userIDfieldName:defaultsFieldName]): + added a "fieldName" parameter. + * UI/Common/UIxFolderActions.m: new module implementing web actions common to all GCS-based folders. diff --git a/SoObjects/SOGo/AgenorUserDefaults.h b/SoObjects/SOGo/AgenorUserDefaults.h index f984fc91f..7bdbca786 100644 --- a/SoObjects/SOGo/AgenorUserDefaults.h +++ b/SoObjects/SOGo/AgenorUserDefaults.h @@ -34,51 +34,54 @@ @class NSString, NSURL, NSUserDefaults, NSArray, NSDictionary, NSData; @class NSCalendarDate, NSMutableDictionary; -@interface AgenorUserDefaults : NSObject +@interface AgenorUserDefaults : NSObject { NSUserDefaults *parent; NSURL *url; NSString *uid; + NSString *fieldName; - NSArray *fieldNames; - NSDictionary *attributes; + NSArray *fieldNames; NSMutableDictionary *values; - NSCalendarDate *lastFetch; + NSCalendarDate *lastFetch; - struct { - int modified:1; - int isNew:1; - int reserved:30; + struct + { + int modified: 1; + int isNew: 1; + int reserved: 30; } defFlags; } -- (id)initWithTableURL:(NSURL *)_url uid:(NSString *)_uid; +- (id) initWithTableURL: (NSURL *) url + uid: (NSString *) uid + fieldName: (NSString *) fieldName; /* value access */ -- (void)setObject:(id)_value forKey:(NSString *)_key; -- (id)objectForKey:(NSString *)_key; -- (void)removeObjectForKey:(NSString *)_key; +- (void) setObject: (id) value + forKey: (NSString *) key; +- (id) objectForKey: (NSString *) key; +- (void) removeObjectForKey: (NSString *) key; /* typed accessors */ -- (NSArray *)arrayForKey:(NSString *)_key; -- (NSDictionary *)dictionaryForKey:(NSString *)_key; -- (NSData *)dataForKey:(NSString *)_key; -- (NSArray *)stringArrayForKey:(NSString *)_key; -- (NSString *)stringForKey:(NSString *)_key; -- (BOOL)boolForKey:(NSString *)_key; -- (float)floatForKey:(NSString *)_key; -- (int)integerForKey:(NSString *)_key; +- (NSArray *) arrayForKey: (NSString *)key; +- (NSDictionary *) dictionaryForKey: (NSString *)key; +- (NSData *) dataForKey: (NSString *)key; +- (NSString *) stringForKey: (NSString *)key; +- (BOOL) boolForKey: (NSString *) key; +- (float) floatForKey: (NSString *) key; +- (int) integerForKey: (NSString *) key; -- (void)setBool:(BOOL)value forKey:(NSString *)_key; -- (void)setFloat:(float)value forKey:(NSString *)_key; -- (void)setInteger:(int)value forKey:(NSString *)_key; +- (void) setBool: (BOOL) value forKey: (NSString *) key; +- (void) setFloat: (float) value forKey: (NSString *) key; +- (void) setInteger: (int) value forKey: (NSString *) key; /* saving changes */ -- (BOOL)synchronize; +- (BOOL) synchronize; @end -#endif /* __AgenorUserDefaults_H_ */ +#endif /* __AgenorUserDefaults_H__ */ diff --git a/SoObjects/SOGo/AgenorUserDefaults.m b/SoObjects/SOGo/AgenorUserDefaults.m index 9b97cc65a..974c06140 100644 --- a/SoObjects/SOGo/AgenorUserDefaults.m +++ b/SoObjects/SOGo/AgenorUserDefaults.m @@ -19,454 +19,407 @@ 02111-1307, USA. */ -#include "AgenorUserDefaults.h" -#include -#include -#include -#include -#include -#include "common.h" +#import +#import +#import +#import +#import +#import + +#import "common.h" +#import "AgenorUserDefaults.h" @implementation AgenorUserDefaults static NSString *uidColumnName = @"uid"; -- (id)initWithTableURL:(NSURL *)_url uid:(NSString *)_uid { - if ((self = [super init])) { - if (_url == nil || [_uid length] < 1) { - [self errorWithFormat:@"tried to create AgenorUserDefaults w/o args!"]; - [self release]; - return nil; +- (id) initWithTableURL: (NSURL *) tableURL + uid: (NSString *) userID + fieldName: (NSString *) defaultsFieldName +{ + if ((self = [super init])) + { + if (tableURL && [userID length] > 0 + && [defaultsFieldName length] > 0) + { + parent = [[NSUserDefaults standardUserDefaults] retain]; + fieldName = [defaultsFieldName copy]; + url = [tableURL copy]; + uid = [userID copy]; + } + else + { + [self errorWithFormat: @"missing arguments"]; + [self release]; + self = nil; + } } - self->parent = [[NSUserDefaults standardUserDefaults] retain]; - self->url = [_url copy]; - self->uid = [_uid copy]; - } return self; } -- (id)init { - return [self initWithTableURL:nil uid:nil]; + +- (id) init +{ + [self release]; + + return nil; } -- (void)dealloc { - [self->attributes release]; - [self->lastFetch release]; - [self->parent release]; - [self->url release]; - [self->uid release]; +- (void) dealloc +{ + [values release]; + [lastFetch release]; + [parent release]; + [url release]; + [uid release]; + [fieldName release]; [super dealloc]; } /* accessors */ -- (NSURL *)tableURL { - return self->url; -} -- (NSString *)uid { - return self->uid; +- (NSURL *) tableURL +{ + return url; } -- (NSUserDefaults *)parentDefaults { - return self->parent; +- (NSString *) uid +{ + return uid; +} + +- (NSString *) fieldName +{ + return fieldName; +} + +- (NSUserDefaults *) parentDefaults +{ + return parent; } /* operation */ -- (void)_loadAttributes:(NSArray *)_attrs { - NSMutableArray *fields; - NSMutableDictionary *attrmap; - unsigned i, count; - - fields = [[NSMutableArray alloc] initWithCapacity:16]; - attrmap = [[NSMutableDictionary alloc] initWithCapacity:16]; - for (i = 0, count = [_attrs count]; i < count; i++) { - EOAttribute *attr; - NSString *name; - - attr = [_attrs objectAtIndex:i]; - name = [attr valueForKey:@"name"]; - [attrmap setObject:attr forKey:name]; - - if (![name isEqual:uidColumnName]) - [fields addObject:name]; - } - - ASSIGNCOPY(self->fieldNames, fields); - ASSIGNCOPY(self->attributes, attrmap); - [attrmap release]; - [fields release]; -} - -- (BOOL)primaryFetchProfile { +- (BOOL) primaryFetchProfile +{ GCSChannelManager *cm; - EOAdaptorChannel *channel; - NSDictionary *row; - NSException *ex; - NSString *sql; - NSArray *attrs; + EOAdaptorChannel *channel; + NSDictionary *row; + NSException *ex; + NSString *sql; + NSArray *attrs; + BOOL rc; + + rc = NO; cm = [GCSChannelManager defaultChannelManager]; - if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) { + channel = [cm acquireOpenChannelForURL: [self tableURL]]; + if (channel) + { + /* generate SQL */ + sql = [NSString stringWithFormat: (@"SELECT %@" + @" FROM %@" + @" WHERE %@ = '%@'"), + fieldName, [[self tableURL] gcsTableName], + uidColumnName, [self uid]]; + + /* run SQL */ + + ex = [channel evaluateExpressionX: sql]; + if (ex) + { + [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex]; + values = [NSMutableDictionary new]; + } + else + { + /* fetch schema */ + attrs = [channel describeResults: NO /* don't beautify */]; + + /* fetch values */ + row = [channel fetchAttributes: attrs withZone: NULL]; + defFlags.isNew = (row == nil); + [channel cancelFetch]; + + /* remember values */ + [values release]; + values = [[row objectForKey: fieldName] propertyList]; + if (values) + [values retain]; + else + values = [NSMutableDictionary new]; + + ASSIGN(lastFetch, [NSCalendarDate date]); + defFlags.modified = NO; + rc = YES; + } + + [cm releaseChannel:channel]; + } + else [self errorWithFormat:@"failed to acquire channel for URL: %@", [self tableURL]]; - return NO; - } - - /* generate SQL */ - - sql = [[self tableURL] gcsTableName]; - sql = [@"SELECT * FROM " stringByAppendingString:sql]; - sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", - uidColumnName, [self uid]]; - - /* run SQL */ - - if ((ex = [channel evaluateExpressionX:sql]) != nil) { - [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex]; - [cm releaseChannel:channel]; - return NO; - } - /* fetch schema */ - - attrs = [channel describeResults:NO /* don't beautify */]; - [self _loadAttributes:attrs]; - - /* fetch values */ - - row = [channel fetchAttributes:attrs withZone:NULL]; - self->defFlags.isNew = (row != nil) ? 0 : 1; - [channel cancelFetch]; - - /* remember values */ - - [self->values release]; self->values = nil; - self->values = (row != nil) - ? [row mutableCopy] - : [[NSMutableDictionary alloc] initWithCapacity:8]; - [self->values removeObjectForKey:uidColumnName]; - - ASSIGN(self->lastFetch, [NSCalendarDate date]); - self->defFlags.modified = 0; - - [cm releaseChannel:channel]; - return YES; + return rc; } -- (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute { - NSString *s; - - if (![_value isNotNull]) - return @"NULL"; - - if ([[_attribute externalType] hasPrefix:@"int"]) - return [_value stringValue]; - - s = [_value stringValue]; - s = [s stringByReplacingString:@"'" withString:@"''"]; - s = [[@"'" stringByAppendingString:s] stringByAppendingString:@"'"]; - return s; -} - -- (NSString *)generateSQLForInsert { +- (NSString *) generateSQLForInsert +{ NSMutableString *sql; - unsigned i, count; - - if ([self->values count] == 0) - return nil; - - sql = [NSMutableString stringWithCapacity:2048]; - - [sql appendString:@"INSERT INTO "]; - [sql appendString:[[self tableURL] gcsTableName]]; - [sql appendString:@" ( uid"]; + NSString *serializedDefaults, *error; + NSData *serializedDefaultsData; + + serializedDefaultsData + = [NSPropertyListSerialization dataFromPropertyList: values + format: NSPropertyListOpenStepFormat + errorDescription: &error]; + if (error) + sql = nil; + else + { + serializedDefaults = [[NSString alloc] initWithData: serializedDefaultsData + encoding: NSUTF8StringEncoding]; + + sql = [NSString stringWithFormat: (@"INSERT INTO %@" + @" (%@, %@)" + @" VALUES ('%@', '%@')"), + [[self tableURL] gcsTableName], uidColumnName, fieldName, + [self uid], + [serializedDefaults stringByReplacingString:@"'" withString:@"''"]]; + [serializedDefaults release]; + } - for (i = 0, count = [self->fieldNames count]; i < count; i++) { - EOAttribute *attr; - - attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]]; - [sql appendString:@", "]; - [sql appendString:[attr columnName]]; - } - - [sql appendString:@") VALUES ("]; - - [sql appendString:@"'"]; - [sql appendString:[self uid]]; // TODO: escaping necessary? - [sql appendString:@"'"]; - - for (i = 0, count = [self->fieldNames count]; i < count; i++) { - EOAttribute *attr; - id value; - - attr = [self->attributes objectForKey:[self->fieldNames objectAtIndex:i]]; - value = [self->values objectForKey:[self->fieldNames objectAtIndex:i]]; - - [sql appendString:@", "]; - [sql appendString:[self formatValue:value forAttribute:attr]]; - } - - [sql appendString:@")"]; return sql; } -- (NSString *)generateSQLForUpdate { +- (NSString *) generateSQLForUpdate +{ NSMutableString *sql; - unsigned i, count; - - if ([self->values count] == 0) - return nil; - - sql = [NSMutableString stringWithCapacity:2048]; - - [sql appendString:@"UPDATE "]; - [sql appendString:[[self tableURL] gcsTableName]]; - [sql appendString:@" SET "]; - - for (i = 0, count = [self->fieldNames count]; i < count; i++) { - EOAttribute *attr; - NSString *name; - id value; - - name = [self->fieldNames objectAtIndex:i]; - value = [self->values objectForKey:name]; - attr = [self->attributes objectForKey:name]; - - if (i != 0) [sql appendString:@", "]; - [sql appendString:[attr columnName]]; - [sql appendString:@" = "]; - [sql appendString:[self formatValue:value forAttribute:attr]]; - } - - [sql appendString:@" WHERE "]; - [sql appendString:uidColumnName]; - [sql appendString:@" = '"]; - [sql appendString:[self uid]]; - [sql appendString:@"'"]; + NSString *serializedDefaults, *error; + NSData *serializedDefaultsData; + + error = nil; + serializedDefaultsData + = [NSPropertyListSerialization dataFromPropertyList: values + format: NSPropertyListOpenStepFormat + errorDescription: &error]; + if (error) + { + sql = nil; + [error release]; + } + else + { + serializedDefaults = [[NSString alloc] initWithData: serializedDefaultsData + encoding: NSUTF8StringEncoding]; + + sql = [NSString stringWithFormat: (@"UPDATE %@" + @" SET %@ = '%@'" + @" WHERE %@ = '%@'"), + [[self tableURL] gcsTableName], + fieldName, + [serializedDefaults stringByReplacingString:@"'" withString:@"''"], + uidColumnName, [self uid]]; + [serializedDefaults release]; + } + return sql; } -- (BOOL)primaryStoreProfile { +- (BOOL) primaryStoreProfile +{ GCSChannelManager *cm; - EOAdaptorChannel *channel; - NSException *ex; - NSString *sql; + EOAdaptorChannel *channel; + NSException *ex; + NSString *sql; + BOOL rc; + + rc = NO; cm = [GCSChannelManager defaultChannelManager]; - if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) { - [self errorWithFormat:@"failed to acquire channel for URL: %@", - [self tableURL]]; - return NO; - } - - /* run SQL */ - - sql = self->defFlags.isNew - ? [self generateSQLForInsert] - : [self generateSQLForUpdate]; - if ((ex = [channel evaluateExpressionX:sql]) != nil) { - [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex]; - [cm releaseChannel:channel]; - return NO; - } - - /* commit */ - - ex = nil; - if ([[channel adaptorContext] hasOpenTransaction]) - ex = [channel evaluateExpressionX:@"COMMIT TRANSACTION"]; - - [cm releaseChannel:channel]; - - if (ex != nil) { - [self errorWithFormat:@"could not commit transaction for update: %@", ex]; - return NO; - } - - self->defFlags.modified = 0; - self->defFlags.isNew = 0; - return YES; + sql = ((defFlags.isNew) + ? [self generateSQLForInsert] + : [self generateSQLForUpdate]); + if (sql) + { + channel = [cm acquireOpenChannelForURL: [self tableURL]]; + if (channel) + { + ex = [channel evaluateExpressionX:sql]; + if (ex) + [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; + else + { + if ([[channel adaptorContext] hasOpenTransaction]) + { + ex = [channel evaluateExpressionX: @"COMMIT TRANSACTION"]; + if (ex) + [self errorWithFormat:@"could not commit transaction for update: %@", ex]; + else + rc = YES; + } + else + rc = YES; + + defFlags.modified = NO; + defFlags.isNew = NO; + } + + [cm releaseChannel: channel]; + } + else + [self errorWithFormat: @"failed to acquire channel for URL: %@", + [self tableURL]]; + } + else + [self errorWithFormat: @"failed to generate SQL for storing defaults"]; + + return rc; } - -- (BOOL)fetchProfile { - if (self->values != nil) - return YES; - - return [self primaryFetchProfile]; -} - -- (NSArray *)primaryDefaultNames { - if (![self fetchProfile]) - return nil; - - return self->fieldNames; +- (BOOL) fetchProfile +{ + return (values || [self primaryFetchProfile]); } /* value access */ -- (void)setObject:(id)_value forKey:(NSString *)_key { +- (void) setObject: (id) value + forKey: (NSString *) key +{ + id old; + if (![self fetchProfile]) return; - - if (![self->fieldNames containsObject:_key]) { - [self errorWithFormat:@"tried to write key: '%@'", _key]; - return; - } - - /* check whether the value is actually modified */ - if (!self->defFlags.modified) { - id old; - old = [self->values objectForKey:_key]; - if (old == _value || [old isEqual:_value]) /* value didn't change */ - return; + /* check whether the value is actually modified */ + if (!defFlags.modified) + { + old = [values objectForKey: key]; + if (old == value || [old isEqual: value]) /* value didn't change */ + return; - /* we need to this because our typed accessors convert to strings */ - // TODO: especially problematic with bools - if ([_value isKindOfClass:[NSString class]]) { - if (![old isKindOfClass:[NSString class]]) - if ([[old description] isEqualToString:_value]) - return; + /* we need to this because our typed accessors convert to strings */ + // TODO: especially problematic with bools + if ([value isKindOfClass: [NSString class]]) { + if (![old isKindOfClass: [NSString class]]) + if ([[old description] isEqualToString: value]) + return; + } } - } - + /* set in hash and mark as modified */ - [self->values setObject:(_value ? _value : [NSNull null]) forKey:_key]; - self->defFlags.modified = 1; + [values setObject: value forKey: key]; + + defFlags.modified = YES; } -- (id)objectForKey:(NSString *)_key { +- (id) objectForKey: (NSString *) key +{ id value; if (![self fetchProfile]) - return nil; - - if (![self->fieldNames containsObject:_key]) - return [self->parent objectForKey:_key]; - - value = [self->values objectForKey:_key]; - return [value isNotNull] ? value : nil; + value = nil; + else + value = [values objectForKey: key]; + + return value; } -- (void)removeObjectForKey:(NSString *)_key { - [self setObject:nil forKey:_key]; +- (void) removeObjectForKey: (NSString *) key +{ + [self setObject: nil forKey: key]; } /* saving changes */ -- (BOOL)synchronize { - if (!self->defFlags.modified) /* was not modified */ - return YES; +- (BOOL) synchronize +{ +// if (!defFlags.modified) /* was not modified */ +// return YES; /* ensure fetched data (more or less guaranteed by modified!=0) */ if (![self fetchProfile]) return NO; /* store */ - if (![self primaryStoreProfile]) { - [self primaryFetchProfile]; - return NO; - } - + if (![self primaryStoreProfile]) + { + [self primaryFetchProfile]; + return NO; + } + /* refetch */ return [self primaryFetchProfile]; } -- (void)flush { - [self->values release]; self->values = nil; - [self->fieldNames release]; self->fieldNames = nil; - [self->attributes release]; self->attributes = nil; - [self->lastFetch release]; self->lastFetch = nil; - self->defFlags.modified = 0; - self->defFlags.isNew = 0; +- (void) flush +{ + [values release]; + [lastFetch release]; + values = nil; + lastFetch = nil; + defFlags.modified = NO; + defFlags.isNew = NO; } /* typed accessors */ -- (NSArray *)arrayForKey:(NSString *)_key { - id obj = [self objectForKey:_key]; - return [obj isKindOfClass:[NSArray class]] ? obj : nil; +- (NSArray *) arrayForKey: (NSString *) key +{ + return [self objectForKey: key]; } -- (NSDictionary *)dictionaryForKey:(NSString *)_key { - id obj = [self objectForKey:_key]; - return [obj isKindOfClass:[NSDictionary class]] ? obj : nil; +- (NSDictionary *) dictionaryForKey: (NSString *) key +{ + return [self objectForKey: key]; } -- (NSData *)dataForKey:(NSString *)_key { - id obj = [self objectForKey:_key]; - return [obj isKindOfClass:[NSData class]] ? obj : nil; +- (NSData *) dataForKey: (NSString *) key +{ + return [self objectForKey: key]; } -- (NSArray *)stringArrayForKey:(NSString *)_key { - id obj = [self objectForKey:_key]; - int n; - Class strClass = [NSString class]; - - if (![obj isKindOfClass:[NSArray class]]) - return nil; - - for (n = [obj count]-1; n >= 0; n--) { - if (![[obj objectAtIndex:n] isKindOfClass:strClass]) - return nil; - } - return obj; +- (NSString *) stringForKey: (NSString *) key +{ + return [self objectForKey: key]; } -- (NSString *)stringForKey:(NSString *)_key { - id obj = [self objectForKey:_key]; - return [obj isKindOfClass:[NSString class]] ? obj : nil; +- (BOOL) boolForKey: (NSString *) key +{ + return [[self objectForKey: key] boolValue]; } -- (BOOL)boolForKey:(NSString *)_key { +- (float) floatForKey: (NSString *) key +{ + return [[self objectForKey: key] floatValue]; +} + +- (int) integerForKey: (NSString *) key +{ + return [[self objectForKey: key] intValue]; +} + +- (void) setBool: (BOOL) value + forKey: (NSString *) key +{ // TODO: need special support here for int-DB fields - id obj; - - if ((obj = [self objectForKey:_key]) == nil) - return NO; - if ([obj isKindOfClass:[NSString class]]) { - if ([obj compare:@"YES" options:NSCaseInsensitiveSearch] == NSOrderedSame) - return YES; - } - if ([obj respondsToSelector:@selector(intValue)]) - return [obj intValue] ? YES : NO; - return NO; + [self setObject: [NSNumber numberWithBool: value] + forKey: key]; } -- (float)floatForKey:(NSString *)_key { - id obj = [self stringForKey:_key]; - return (obj != nil) ? [obj floatValue] : 0.0; -} -- (int)integerForKey:(NSString *)_key { - id obj = [self stringForKey:_key]; - return (obj != nil) ? [obj intValue] : 0; +- (void) setFloat: (float) value + forKey: (NSString *) key +{ + [self setObject: [NSNumber numberWithFloat: value] + forKey: key]; } -- (void)setBool:(BOOL)value forKey:(NSString *)_key { - // TODO: need special support here for int-DB fields - [self setObject:(value ? @"YES" : @"NO") forKey:_key]; -} -- (void)setFloat:(float)value forKey:(NSString *)_key { - [self setObject:[NSString stringWithFormat:@"%f", value] forKey:_key]; -} -- (void)setInteger:(int)value forKey:(NSString *)_key { - [self setObject:[NSString stringWithFormat:@"%d", value] forKey:_key]; -} - -/* description */ - -- (NSString *)description { - NSMutableString *ms; - - ms = [NSMutableString stringWithCapacity:16]; - [ms appendFormat:@"<0x%08X[%@]>", self, NSStringFromClass([self class])]; - [ms appendFormat:@" uid=%@", self->uid]; - [ms appendFormat:@" url=%@", [self->url absoluteString]]; - [ms appendFormat:@" parent=0x%08X", self->parent]; - [ms appendString:@">"]; - return ms; +- (void) setInteger: (int) value + forKey: (NSString *) key +{ + [self setObject: [NSNumber numberWithInt: value] + forKey: key]; } @end /* AgenorUserDefaults */