#include "AgenorUserDefaults.h"
#include <GDLContentStore/GCSChannelManager.h>
+#include <GDLContentStore/NSURL+GCS.h>
+#include <GDLAccess/EOAdaptorChannel.h>
#include "common.h"
@implementation AgenorUserDefaults
+static NSString *uidColumnName = @"uid";
+
- (id)initWithTableURL:(NSURL *)_url uid:(NSString *)_uid {
if ((self = [super init])) {
if (_url == nil || [_uid length] < 1) {
}
- (void)dealloc {
- [self->parent release];
- [self->url release];
- [self->uid release];
+ [self->lastFetch release];
+ [self->parent release];
+ [self->url release];
+ [self->uid release];
[super dealloc];
}
/* operation */
-- (BOOL)fill {
+- (BOOL)primaryFetchProfile {
GCSChannelManager *cm;
EOAdaptorChannel *channel;
+ NSDictionary *row;
+ NSException *ex;
+ NSString *sql;
+ NSArray *attrs;
+ NSMutableArray *fields;
cm = [GCSChannelManager defaultChannelManager];
if ((channel = [cm acquireOpenChannelForURL:[self tableURL]]) == nil) {
[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 */];
+
+ fields = [[attrs valueForKey:@"name"] mutableCopy];
+ [fields removeObject:uidColumnName];
+ ASSIGNCOPY(self->fieldNames, fields);
+ [fields release]; fields =nil;
+
+ /* fetch values */
+
+ row = [channel fetchAttributes:attrs withZone:NULL];
+ [channel cancelFetch];
- [self logWithFormat:@"do work on: %@", channel];
+ /* remember values */
+
+ [self->values release]; self->values = nil;
+ if (row != nil)
+ self->values = [row mutableCopy];
+ else
+ self->values = [[NSMutableDictionary alloc] initWithCapacity:8];
+ [self->values removeObjectForKey:uidColumnName];
+
+ ASSIGN(self->lastFetch, [NSCalendarDate date]);
+ self->defFlags.modified = 0;
[cm releaseChannel:channel];
return YES;
}
+- (BOOL)fetchProfile {
+ if (self->values != nil)
+ return YES;
+
+ return [self primaryFetchProfile];
+}
+
+- (NSArray *)primaryDefaultNames {
+ if (![self fetchProfile])
+ return nil;
+
+ return self->fieldNames;
+}
+
/* value access */
- (void)setObject:(id)_value forKey:(NSString *)_key {
- [self logWithFormat:@"TODO(%s): %@", __PRETTY_FUNCTION__, _key];
+ 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;
+
+ /* 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;
}
+
- (id)objectForKey:(NSString *)_key {
- [self logWithFormat:@"TODO(%s): %@", __PRETTY_FUNCTION__, _key];
- [self fill];
- return nil;
+ 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;
}
- (void)removeObjectForKey:(NSString *)_key {
[self setObject:nil forKey:_key];
}
+/* saving changes */
+
+- (BOOL)synchronize {
+ if (!self->defFlags.modified) /* was not modified */
+ return YES;
+
+ [self logWithFormat:@"TODO: sync!"];
+ return NO;
+}
+
/* typed accessors */
- (NSArray *)arrayForKey:(NSString *)_key {
}
- (BOOL)boolForKey:(NSString *)_key {
+ // TODO: need special support here for int-DB fields
id obj;
if ((obj = [self objectForKey:_key]) == nil)
}
- (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 {
#include "common.h"
static void usage(NSArray *args) {
- fprintf(stderr, "usage: %s <uid> read|write <key> [<value>]\n",
+ fprintf(stderr, "usage: %s <uid> read|write|info [<key>] [<value>]\n",
[[args objectAtIndex:0] cString]);
}
static void doRead(NSUserDefaults *defaults, NSString *key) {
id value;
-
- if ((value = [defaults objectForKey:key]) == nil) {
+
+ if (key == nil) {
+ NSArray *defNames;
+ unsigned i, count;
+
+ defNames = [defaults valueForKey:@"primaryDefaultNames"];
+ if ((count = [defNames count]) == 0) {
+ fprintf(stderr, "There are no keys in the Agenor profile!\n");
+ return;
+ }
+
+ for (i = 0; i < count; i++) {
+ printf("%s: %s\n",
+ [[defNames objectAtIndex:i] cString],
+ [[[defaults objectForKey:[defNames objectAtIndex:i]]
+ description] cString]);
+ }
+ }
+ else if ((value = [defaults objectForKey:key]) == nil) {
fprintf(stderr, "There is no key '%s' in the Agenor profile!\n",
[key cString]);
- return;
}
-
- printf("%s\n", [[value description] cString]);
+ else
+ printf("%s\n", [[value description] cString]);
}
static void doWrite(NSUserDefaults *defaults, NSString *key, NSString *value) {
key = nil;
value = nil;
- if ([op isEqualToString:@"write"] || [op isEqualToString:@"read"]) {
- if ([args count] < 4) {
- usage(args);
- return;
- }
+ if ([args count] > 3)
key = [args objectAtIndex:3];
- }
if ([op isEqualToString:@"write"]) {
if ([args count] < 5) {