mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-03-06 23:51:23 +00:00
Monotone-Parent: 9f8af75f69269845084162a8844a51cf065ae8fc
Monotone-Revision: 54e08d70ed3b12e1a8bb0a68c590698753bb3e02 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2009-11-19T17:08:47 Monotone-Branch: ca.inverse.sogo
This commit is contained in:
23
ChangeLog
23
ChangeLog
@@ -1,5 +1,28 @@
|
||||
2009-11-19 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* SoObjects/SOGo/SOGoUser.m (-userDefaults, -userSettings): the
|
||||
cache handling has now been put in the SOGoUserDefaults class.
|
||||
|
||||
* SoObjects/SOGo/SOGoUserDefaults.m (-primaryFetchProfile): split
|
||||
method to separate the cache data and the db data. We know invoke
|
||||
the "jsonRepresentation" method, which is the real data accessor
|
||||
now.
|
||||
(-primaryStoreProfile): user profile data is now serialized as a
|
||||
JSON string in order to avoid constant conversions between the
|
||||
plist and the json formats, from and to the cache.
|
||||
(-jsonRepresentation): we now handle the cache data from here, as
|
||||
it made more sense and reduces the code size in SOGoUser.m.
|
||||
(-fetchProfile): now a void method.
|
||||
|
||||
* SoObjects/SOGo/SOGoCache.m (+initialize): the memcached host can
|
||||
now be configured using the "SOGoMemCachedHost" user default. The
|
||||
method now contains initialization code for global vars that used
|
||||
to be in -init instead.
|
||||
(-userSettingsForLogin:,-userDefaultsForLogin:,-userAttributesForLogin:):
|
||||
refactoring: those accessors now only return an NSString
|
||||
associated with their respective key. There is no longer any
|
||||
dictionary de-/serialization occuring in SOGoCache.
|
||||
|
||||
* SoObjects/SOGo/SOGoParentFolder.m (-subFolders): we raise the
|
||||
exceptions we get only if the current request is a PROPFIND. This
|
||||
avoids a useless restart of SOGo when the database is down during
|
||||
|
||||
@@ -61,13 +61,17 @@
|
||||
withName: (NSString *) userName;
|
||||
- (id) userNamed: (NSString *) name;
|
||||
|
||||
- (NSMutableDictionary *) userAttributesForLogin: (NSString *) theLogin;
|
||||
- (NSDictionary *) userDefaultsForLogin: (NSString *) theLogin;
|
||||
- (NSDictionary *) userSettingsForLogin: (NSString *) theLogin;
|
||||
- (void) setUserAttributes: (NSString *) attributes
|
||||
forLogin: (NSString *) login;
|
||||
- (NSString *) userAttributesForLogin: (NSString *) theLogin;
|
||||
|
||||
- (void) cacheValues: (NSDictionary *) theAttributes
|
||||
ofType: (NSString *) theType
|
||||
forLogin: (NSString *) theLogin;
|
||||
- (void) setUserDefaults: (NSString *) attributes
|
||||
forLogin: (NSString *) login;
|
||||
- (NSString *) userDefaultsForLogin: (NSString *) theLogin;
|
||||
|
||||
- (void) setUserSettings: (NSString *) attributes
|
||||
forLogin: (NSString *) login;
|
||||
- (NSString *) userSettingsForLogin: (NSString *) theLogin;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSEnumerator.h>
|
||||
#import <Foundation/NSLock.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import <Foundation/NSTimer.h>
|
||||
@@ -47,14 +47,11 @@
|
||||
|
||||
#import "SOGoCache.h"
|
||||
|
||||
#import "NSDictionary+BSJSONAdditions.h"
|
||||
|
||||
// We define the default value for cleaning up cached
|
||||
// users' preferences. This value should be relatively
|
||||
// high to avoid useless database calls.
|
||||
static NSTimeInterval cleanupInterval = 300;
|
||||
|
||||
static NSString *memcachedServerName = @"localhost";
|
||||
static NSTimeInterval cleanupInterval = 0;
|
||||
static NSString *memcachedServerName;
|
||||
|
||||
#if defined(THREADSAFE)
|
||||
static NSLock *lock;
|
||||
@@ -62,12 +59,32 @@ static NSLock *lock;
|
||||
|
||||
@implementation SOGoCache
|
||||
|
||||
#if defined(THREADSAFE)
|
||||
+ (void) initialize
|
||||
{
|
||||
NSString *cleanupSetting;
|
||||
NSUserDefaults *ud;
|
||||
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
// We fire our timer that will cleanup cache entries
|
||||
cleanupSetting = [ud objectForKey: @"SOGoCacheCleanupInterval"];
|
||||
if (cleanupSetting && [cleanupSetting doubleValue] > 0.0)
|
||||
cleanupInterval = [cleanupSetting doubleValue];
|
||||
if (cleanupInterval == 0.0)
|
||||
cleanupInterval = 300;
|
||||
|
||||
[self logWithFormat: @"Cache cleanup interval set every %f seconds for memcached",
|
||||
cleanupInterval];
|
||||
|
||||
ASSIGN (memcachedServerName, [ud stringForKey: @"SOGoMemCachedHost"]);
|
||||
if (!memcachedServerName)
|
||||
memcachedServerName = @"localhost";
|
||||
[self logWithFormat: @"Using host '%@' as memcached server",
|
||||
memcachedServerName];
|
||||
|
||||
#if defined(THREADSAFE)
|
||||
lock = [NSLock new];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSTimeInterval) cleanupInterval
|
||||
{
|
||||
@@ -82,7 +99,7 @@ static NSLock *lock;
|
||||
[lock lock];
|
||||
#endif
|
||||
if (!sharedCache)
|
||||
sharedCache = [[self alloc] init];
|
||||
sharedCache = [self new];
|
||||
#if defined(THREADSAFE)
|
||||
[lock unlock];
|
||||
#endif
|
||||
@@ -110,34 +127,28 @@ static NSLock *lock;
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
NSString *cleanupSetting;
|
||||
memcached_return error;
|
||||
|
||||
cache = [[NSMutableDictionary alloc] init];
|
||||
users = [[NSMutableDictionary alloc] init];
|
||||
cache = [NSMutableDictionary new];
|
||||
users = [NSMutableDictionary new];
|
||||
|
||||
// localCache is used to avoid going all the time to the memcached server during
|
||||
// each request. We'll cache the value we got from memcached for the duration
|
||||
// of the current request - which is good enough for pretty much all caces. We
|
||||
// surely don't want to get new defaults/settings during the _same_ requests, it
|
||||
// could produce relatively strange behaviors
|
||||
localCache = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// We fire our timer that will cleanup cache entries
|
||||
cleanupSetting = [[NSUserDefaults standardUserDefaults]
|
||||
objectForKey: @"SOGoCacheCleanupInterval"];
|
||||
|
||||
if (cleanupSetting && [cleanupSetting doubleValue] > 0.0)
|
||||
cleanupInterval = [cleanupSetting doubleValue];
|
||||
|
||||
[self logWithFormat: @"Cache cleanup interval set every %f seconds for memcached",
|
||||
cleanupInterval];
|
||||
// localCache is used to avoid going all the time to the memcached
|
||||
// server during each request. We'll cache the value we got from
|
||||
// memcached for the duration of the current request - which is good
|
||||
// enough for pretty much all cases. We surely don't want to get new
|
||||
// defaults/settings during the _same_ requests, it could produce
|
||||
// relatively strange behaviors
|
||||
localCache = [NSMutableDictionary new];
|
||||
|
||||
handle = memcached_create(NULL);
|
||||
|
||||
if (handle)
|
||||
{
|
||||
servers = memcached_server_list_append(NULL, [memcachedServerName UTF8String], 11211, &error);
|
||||
#warning We could also make the port number configurable and even make use \
|
||||
of NGNetUtilities for that.
|
||||
servers
|
||||
= memcached_server_list_append(NULL,
|
||||
[memcachedServerName UTF8String],
|
||||
11211, &error);
|
||||
error = memcached_server_push(handle, servers);
|
||||
}
|
||||
}
|
||||
@@ -242,99 +253,110 @@ static NSLock *lock;
|
||||
// For non-blocking cache method, see memcached_behavior_set and MEMCACHED_BEHAVIOR_NO_BLOCK
|
||||
// memcached is thread-safe so no need to lock here.
|
||||
//
|
||||
- (void) cacheValues: (NSDictionary *) theAttributes
|
||||
ofType: (NSString *) theType
|
||||
forLogin: (NSString *) theLogin
|
||||
- (void) _cacheValues: (NSString *) theAttributes
|
||||
ofType: (NSString *) theType
|
||||
forLogin: (NSString *) theLogin
|
||||
{
|
||||
memcached_return error;
|
||||
const char *key, *value;
|
||||
unsigned int len, vlen;
|
||||
NSString *keyName;
|
||||
NSData *key, *value;
|
||||
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
key = [[NSString stringWithFormat: @"%@+%@", theLogin, theType] UTF8String];
|
||||
len = strlen(key);
|
||||
|
||||
value = [[theAttributes jsonStringValue] UTF8String];
|
||||
vlen = strlen(value);
|
||||
|
||||
error = memcached_set(handle, key, len, value, vlen, cleanupInterval, 0);
|
||||
|
||||
if (error != MEMCACHED_SUCCESS)
|
||||
[self logWithFormat: @"memcached error: unable to cache values with subtype %@ for user %@", theType, theLogin];
|
||||
//else
|
||||
//[self logWithFormat: @"memcached: cached values (%s) with subtype %@ for user %@", value, theType, theLogin];
|
||||
}
|
||||
|
||||
- (NSDictionary *) _valuesOfType: (NSString *) theType
|
||||
forLogin: (NSString *) theLogin
|
||||
{
|
||||
NSDictionary *d;
|
||||
NSString *k;
|
||||
|
||||
const char *key;
|
||||
unsigned int len;
|
||||
|
||||
if (!handle)
|
||||
return nil;
|
||||
|
||||
k = [NSString stringWithFormat: @"%@+%@", theLogin, theType];
|
||||
key = [k UTF8String];
|
||||
len = strlen(key);
|
||||
|
||||
d = [localCache objectForKey: k];
|
||||
|
||||
if (!d)
|
||||
keyName = [NSString stringWithFormat: @"%@+%@", theLogin, theType];
|
||||
if (handle)
|
||||
{
|
||||
memcached_return rc;
|
||||
unsigned int flags;
|
||||
size_t vlen;
|
||||
char *s;
|
||||
key = [keyName dataUsingEncoding: NSUTF8StringEncoding];
|
||||
value = [theAttributes dataUsingEncoding: NSUTF8StringEncoding];
|
||||
error = memcached_set(handle,
|
||||
[key bytes], [key length],
|
||||
[value bytes], [value length],
|
||||
cleanupInterval, 0);
|
||||
|
||||
s = memcached_get(handle, key, len, &vlen, &flags, &rc);
|
||||
|
||||
if (rc == MEMCACHED_SUCCESS && s)
|
||||
{
|
||||
NSString *v;
|
||||
|
||||
v = [NSString stringWithUTF8String: s];
|
||||
d = [NSDictionary dictionaryWithJSONString: v];
|
||||
|
||||
// Cache the value in our localCache
|
||||
if (d)
|
||||
[localCache setObject: d forKey: k];
|
||||
else
|
||||
[self errorWithFormat: @"Unable to convert (%@) to a JSON string for type: %@ and login: %@", v, theType, theLogin];
|
||||
|
||||
free(s);
|
||||
}
|
||||
if (error != MEMCACHED_SUCCESS)
|
||||
[self logWithFormat: @"memcached error: unable to cache values with subtype '%@' for user '%@'", theType, theLogin];
|
||||
//else
|
||||
//[self logWithFormat: @"memcached: cached values (%s) with subtype %@
|
||||
//for user %@", value, theType, theLogin];
|
||||
}
|
||||
|
||||
return d;
|
||||
else
|
||||
[self errorWithFormat: @"attempting to cache value for key '%@' while"
|
||||
" no handle exists", keyName];
|
||||
}
|
||||
|
||||
|
||||
- (NSMutableDictionary *) userAttributesForLogin: (NSString *) theLogin
|
||||
- (NSString *) _valuesOfType: (NSString *) theType
|
||||
forLogin: (NSString *) theLogin
|
||||
{
|
||||
id o;
|
||||
NSString *valueString, *keyName;
|
||||
NSData *key;
|
||||
char *value;
|
||||
size_t vlen;
|
||||
memcached_return rc;
|
||||
unsigned int flags;
|
||||
|
||||
o = [self _valuesOfType: @"attributes" forLogin: theLogin];
|
||||
valueString = nil;
|
||||
|
||||
if (o)
|
||||
return [NSMutableDictionary dictionaryWithDictionary: o];
|
||||
if (handle)
|
||||
{
|
||||
keyName = [NSString stringWithFormat: @"%@+%@", theLogin, theType];
|
||||
valueString = [localCache objectForKey: keyName];
|
||||
if (!valueString)
|
||||
{
|
||||
key = [keyName dataUsingEncoding: NSUTF8StringEncoding];
|
||||
value = memcached_get (handle, [key bytes], [key length],
|
||||
&vlen, &flags, &rc);
|
||||
if (rc == MEMCACHED_SUCCESS && value)
|
||||
{
|
||||
valueString
|
||||
= [[NSString alloc] initWithBytesNoCopy: value
|
||||
length: vlen
|
||||
encoding: NSUTF8StringEncoding
|
||||
freeWhenDone: YES];
|
||||
[valueString autorelease];
|
||||
// Cache the value in our localCache
|
||||
[localCache setObject: valueString forKey: keyName];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"attempting to retrieved cached value for key"
|
||||
@" '%@' while no handle exists", keyName];
|
||||
|
||||
return nil;
|
||||
return valueString;
|
||||
}
|
||||
|
||||
- (NSDictionary *) userDefaultsForLogin: (NSString *) theLogin
|
||||
- (void) setUserAttributes: (NSString *) theAttributes
|
||||
forLogin: (NSString *) login
|
||||
{
|
||||
return [self _valuesOfType: @"defaults" forLogin: theLogin];
|
||||
[self _cacheValues: theAttributes ofType: @"attributes"
|
||||
forLogin: login];
|
||||
}
|
||||
|
||||
- (NSDictionary *) userSettingsForLogin: (NSString *) theLogin
|
||||
- (NSString *) userAttributesForLogin: (NSString *) theLogin
|
||||
{
|
||||
return [self _valuesOfType: @"settings" forLogin: theLogin];
|
||||
return [self _valuesOfType: @"attributes" forLogin: theLogin];
|
||||
}
|
||||
|
||||
- (void) setUserDefaults: (NSString *) theAttributes
|
||||
forLogin: (NSString *) login
|
||||
{
|
||||
[self _cacheValues: theAttributes ofType: @"defaults"
|
||||
forLogin: login];
|
||||
}
|
||||
|
||||
- (NSString *) userDefaultsForLogin: (NSString *) theLogin
|
||||
{
|
||||
return [self _valuesOfType: @"defaults" forLogin: theLogin];
|
||||
}
|
||||
|
||||
- (void) setUserSettings: (NSString *) theAttributes
|
||||
forLogin: (NSString *) login
|
||||
{
|
||||
[self _cacheValues: theAttributes ofType: @"settings"
|
||||
forLogin: login];
|
||||
}
|
||||
|
||||
- (NSString *) userSettingsForLogin: (NSString *) theLogin
|
||||
{
|
||||
return [self _valuesOfType: @"settings" forLogin: theLogin];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "SOGoUserManager.h"
|
||||
#import "NSArray+Utilities.h"
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoDateFormatter.h"
|
||||
@@ -45,6 +44,7 @@
|
||||
#import "SOGoPermissions.h"
|
||||
#import "SOGoUserDefaults.h"
|
||||
#import "SOGoUserFolder.h"
|
||||
#import "SOGoUserManager.h"
|
||||
|
||||
#import "../../Main/SOGo.h"
|
||||
|
||||
@@ -487,50 +487,35 @@ _timeValue (NSString *key)
|
||||
|
||||
- (NSUserDefaults *) userDefaults
|
||||
{
|
||||
NSDictionary *values;
|
||||
|
||||
if (!_defaults)
|
||||
if (!_defaults)
|
||||
{
|
||||
_defaults = [self primaryUserDefaults];
|
||||
if (_defaults)
|
||||
{
|
||||
values = [[SOGoCache sharedCache] userDefaultsForLogin: login];
|
||||
[_defaults fetchProfile];
|
||||
if ([_defaults values])
|
||||
{
|
||||
BOOL b;
|
||||
b = NO;
|
||||
|
||||
if (values)
|
||||
{
|
||||
[_defaults setValues: values];
|
||||
if (![[_defaults stringForKey: @"MessageCheck"] length])
|
||||
{
|
||||
[_defaults setObject: defaultMessageCheck forKey: @"MessageCheck"];
|
||||
b = YES;
|
||||
}
|
||||
if (![[_defaults stringForKey: @"TimeZone"] length])
|
||||
{
|
||||
[_defaults setObject: [serverTimeZone name] forKey: @"TimeZone"];
|
||||
b = YES;
|
||||
}
|
||||
|
||||
if (b)
|
||||
[_defaults synchronize];
|
||||
|
||||
|
||||
// See explanation in -language
|
||||
[self invalidateLanguage];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_defaults fetchProfile];
|
||||
values = [_defaults values];
|
||||
|
||||
if (values)
|
||||
{
|
||||
BOOL b;
|
||||
|
||||
b = NO;
|
||||
|
||||
if (![[_defaults stringForKey: @"MessageCheck"] length])
|
||||
{
|
||||
[_defaults setObject: defaultMessageCheck forKey: @"MessageCheck"];
|
||||
b = YES;
|
||||
}
|
||||
if (![[_defaults stringForKey: @"TimeZone"] length])
|
||||
{
|
||||
[_defaults setObject: [serverTimeZone name] forKey: @"TimeZone"];
|
||||
b = YES;
|
||||
}
|
||||
|
||||
if (b)
|
||||
[_defaults synchronize];
|
||||
|
||||
[[SOGoCache sharedCache] cacheValues: [_defaults values] ofType: @"defaults" forLogin: login];
|
||||
}
|
||||
}
|
||||
|
||||
// See explanation in -language
|
||||
[self invalidateLanguage];
|
||||
}
|
||||
}
|
||||
//else
|
||||
@@ -541,29 +526,12 @@ _timeValue (NSString *key)
|
||||
|
||||
- (NSUserDefaults *) userSettings
|
||||
{
|
||||
NSDictionary *values;
|
||||
|
||||
if (!_settings)
|
||||
{
|
||||
_settings = [self primaryUserSettings];
|
||||
if (_settings)
|
||||
{
|
||||
values = [[SOGoCache sharedCache] userSettingsForLogin: login];
|
||||
|
||||
if (values)
|
||||
{
|
||||
[_settings setValues: values];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_settings fetchProfile];
|
||||
values = [_settings values];
|
||||
|
||||
if (values)
|
||||
{
|
||||
[[SOGoCache sharedCache] cacheValues: values ofType: @"settings" forLogin: login];
|
||||
}
|
||||
}
|
||||
[_settings fetchProfile];
|
||||
|
||||
// See explanation in -language
|
||||
[self invalidateLanguage];
|
||||
@@ -585,9 +553,9 @@ _timeValue (NSString *key)
|
||||
if (![language length])
|
||||
{
|
||||
language = [[self userDefaults] stringForKey: @"Language"];
|
||||
// This is a workaround until we handle the connection errors to the db in a
|
||||
// better way. It enables us to avoid retrieving the userDefaults too
|
||||
// many times when the DB is down, causing a huge delay.
|
||||
// This is a workaround until we handle the connection errors to the db
|
||||
// in a better way. It enables us to avoid retrieving the userDefaults
|
||||
// too many times when the DB is down, causing a huge delay.
|
||||
if (![language length])
|
||||
language = [SOGoUser language];
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
- (NSString *) jsonRepresentation;
|
||||
|
||||
- (BOOL) fetchProfile;
|
||||
- (void) fetchProfile;
|
||||
|
||||
/* saving changes */
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#import <GDLAccess/EOAttribute.h>
|
||||
|
||||
#import "NSObject+Utilities.h"
|
||||
#import "NSString+Utilities.h"
|
||||
|
||||
#import "NSDictionary+BSJSONAdditions.h"
|
||||
#import "SOGoUserDefaults.h"
|
||||
@@ -53,11 +54,11 @@ static NSString *uidColumnName = @"c_uid";
|
||||
if (theURL && [theUID length] > 0
|
||||
&& [theFieldName length] > 0)
|
||||
{
|
||||
fieldName = [theFieldName copy];
|
||||
url = [theURL copy];
|
||||
uid = [theUID copy];
|
||||
defFlags.ready = YES;
|
||||
defFlags.isNew = YES;
|
||||
ASSIGN (fieldName, theFieldName);
|
||||
ASSIGN (url, theURL);
|
||||
ASSIGN (uid, theUID);
|
||||
defFlags.ready = NO;
|
||||
defFlags.isNew = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -98,142 +99,193 @@ static NSString *uidColumnName = @"c_uid";
|
||||
|
||||
/* operation */
|
||||
|
||||
- (BOOL) primaryFetchProfile
|
||||
- (NSString *) _fetchJSONProfileFromDB
|
||||
{
|
||||
GCSChannelManager *cm;
|
||||
EOAdaptorChannel *channel;
|
||||
NSDictionary *row;
|
||||
NSDictionary *row, *plist;
|
||||
NSException *ex;
|
||||
NSString *sql, *value, *error;
|
||||
NSArray *attrs;
|
||||
BOOL rc;
|
||||
NSData *plistData;
|
||||
|
||||
rc = NO;
|
||||
|
||||
value = nil;
|
||||
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: [self tableURL]];
|
||||
if (channel)
|
||||
{
|
||||
/* generate SQL */
|
||||
defFlags.ready = YES;
|
||||
sql = [NSString stringWithFormat: (@"SELECT %@"
|
||||
@" FROM %@"
|
||||
@" WHERE %@ = '%@'"),
|
||||
fieldName, [[self tableURL] gcsTableName],
|
||||
uidColumnName, [self uid]];
|
||||
|
||||
values = [[NSMutableDictionary alloc] init];
|
||||
|
||||
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];
|
||||
[self errorWithFormat:@"could not run SQL '%@': %@", sql, ex];
|
||||
else
|
||||
{
|
||||
/* fetch schema */
|
||||
attrs = [channel describeResults: NO /* don't beautify */];
|
||||
|
||||
/* fetch values */
|
||||
row = [channel fetchAttributes: attrs withZone: NULL];
|
||||
defFlags.isNew = !row;
|
||||
[channel cancelFetch];
|
||||
|
||||
/* remember values */
|
||||
value = [row objectForKey: fieldName];
|
||||
if ([value isNotNull])
|
||||
{
|
||||
id v;
|
||||
|
||||
value = [value stringByReplacingString: @"''"
|
||||
withString: @"'"];
|
||||
value = [value stringByReplacingString: @"\\\\"
|
||||
withString: @"\\"];
|
||||
plistData = [value dataUsingEncoding: NSUTF8StringEncoding];
|
||||
v = [NSPropertyListSerialization propertyListFromData: plistData
|
||||
mutabilityOption: NSPropertyListMutableContainers
|
||||
format: NULL
|
||||
errorDescription: &error];
|
||||
if ([v isKindOfClass: [NSMutableDictionary class]])
|
||||
[values addEntriesFromDictionary: v];
|
||||
}
|
||||
|
||||
defFlags.modified = NO;
|
||||
rc = YES;
|
||||
}
|
||||
{
|
||||
/* fetch schema */
|
||||
attrs = [channel describeResults: NO /* don't beautify */];
|
||||
|
||||
/* fetch values */
|
||||
row = [channel fetchAttributes: attrs withZone: NULL];
|
||||
[channel cancelFetch];
|
||||
|
||||
[cm releaseChannel:channel];
|
||||
value = [row objectForKey: fieldName];
|
||||
if ([value isNotNull])
|
||||
{
|
||||
defFlags.isNew = NO;
|
||||
#warning The result is supposed to be unescaped, why re-unescaping it here ?
|
||||
value = [value stringByReplacingString: @"''" withString: @"'"];
|
||||
value = [value stringByReplacingString: @"\\\\" withString: @"\\"];
|
||||
if (![value isJSONString])
|
||||
{
|
||||
plistData = [value dataUsingEncoding: NSUTF8StringEncoding];
|
||||
plist = [NSPropertyListSerialization propertyListFromData: plistData
|
||||
mutabilityOption: NSPropertyListMutableContainers
|
||||
format: NULL
|
||||
errorDescription: &error];
|
||||
if (plist)
|
||||
{
|
||||
[self logWithFormat: @"database value for '%@'"
|
||||
@" (uid: '%@') is a plist", fieldName, uid];
|
||||
value = [plist jsonStringValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"failed to parse property list value"
|
||||
@" (error: %@): %@", error, value];
|
||||
value = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
defFlags.isNew = YES;
|
||||
value = nil; /* we discard any NSNull instance */
|
||||
}
|
||||
}
|
||||
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
else
|
||||
{
|
||||
defFlags.ready = NO;
|
||||
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
||||
[self tableURL]];
|
||||
[self tableURL]];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (NSString *) jsonRepresentation
|
||||
{
|
||||
SOGoCache *cache;
|
||||
NSString *jsonValue;
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
if ([fieldName isEqualToString: @"c_defaults"])
|
||||
jsonValue = [cache userDefaultsForLogin: uid];
|
||||
else
|
||||
jsonValue = [cache userSettingsForLogin: uid];
|
||||
if ([jsonValue length])
|
||||
{
|
||||
defFlags.ready = YES;
|
||||
defFlags.isNew = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonValue = [self _fetchJSONProfileFromDB];
|
||||
if ([jsonValue length])
|
||||
{
|
||||
defFlags.isNew = NO;
|
||||
if ([fieldName isEqualToString: @"c_defaults"])
|
||||
[cache setUserDefaults: jsonValue forLogin: uid];
|
||||
else
|
||||
[cache setUserSettings: jsonValue forLogin: uid];
|
||||
}
|
||||
else
|
||||
{
|
||||
defFlags.isNew = YES;
|
||||
jsonValue = @"{}";
|
||||
}
|
||||
}
|
||||
|
||||
return jsonValue;
|
||||
}
|
||||
|
||||
- (void) primaryFetchProfile
|
||||
{
|
||||
NSString *jsonValue;
|
||||
|
||||
defFlags.modified = NO;
|
||||
[values release];
|
||||
jsonValue = [self jsonRepresentation];
|
||||
values = [NSDictionary dictionaryWithJSONString: jsonValue];
|
||||
if (values)
|
||||
[values retain];
|
||||
else
|
||||
[self errorWithFormat: @"failure parsing json string: '%@'", jsonValue];
|
||||
}
|
||||
|
||||
- (BOOL) _isReadyOrRetry
|
||||
{
|
||||
BOOL rc;
|
||||
|
||||
if (defFlags.ready)
|
||||
rc = YES;
|
||||
else
|
||||
{
|
||||
[self primaryFetchProfile];
|
||||
rc = defFlags.ready;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (NSString *) _serializedDefaults
|
||||
- (NSString *) _sqlJsonRepresentation: (NSString *) jsonRepresentation
|
||||
{
|
||||
NSMutableString *serializedDefaults;
|
||||
NSData *serializedDefaultsData;
|
||||
NSString *error;
|
||||
NSMutableString *sql;
|
||||
|
||||
error = nil;
|
||||
serializedDefaultsData
|
||||
= [NSPropertyListSerialization dataFromPropertyList: values
|
||||
format: NSPropertyListOpenStepFormat
|
||||
errorDescription: &error];
|
||||
if (error)
|
||||
{
|
||||
[self errorWithFormat: @"serializing the defaults: %@", error];
|
||||
serializedDefaults = nil;
|
||||
[error release];
|
||||
}
|
||||
else
|
||||
{
|
||||
serializedDefaults
|
||||
= [[NSMutableString alloc] initWithData: serializedDefaultsData
|
||||
encoding: NSUTF8StringEncoding];
|
||||
[serializedDefaults autorelease];
|
||||
[serializedDefaults replaceString: @"\\" withString: @"\\\\"];
|
||||
[serializedDefaults replaceString: @"'" withString: @"''"];
|
||||
}
|
||||
sql = [jsonRepresentation mutableCopy];
|
||||
[sql autorelease];
|
||||
[sql replaceString: @"\\\\" withString: @"\\"];
|
||||
[sql replaceString: @"'" withString: @"''"];
|
||||
|
||||
return serializedDefaults;
|
||||
return sql;
|
||||
}
|
||||
|
||||
- (NSString *) generateSQLForInsert
|
||||
- (NSString *) generateSQLForInsert: (NSString *) jsonRepresentation
|
||||
{
|
||||
NSString *sql, *serializedDefaults;
|
||||
NSString *sql;
|
||||
|
||||
serializedDefaults = [self _serializedDefaults];
|
||||
if (serializedDefaults)
|
||||
if ([jsonRepresentation length])
|
||||
sql = [NSString stringWithFormat: (@"INSERT INTO %@"
|
||||
@" (%@, %@)"
|
||||
@" VALUES ('%@', '%@')"),
|
||||
[[self tableURL] gcsTableName], uidColumnName, fieldName,
|
||||
[self uid], serializedDefaults];
|
||||
@" (%@, %@)"
|
||||
@" VALUES ('%@', '%@')"),
|
||||
[[self tableURL] gcsTableName], uidColumnName, fieldName,
|
||||
[self uid],
|
||||
[self _sqlJsonRepresentation: jsonRepresentation]];
|
||||
else
|
||||
sql = nil;
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
- (NSString *) generateSQLForUpdate
|
||||
- (NSString *) generateSQLForUpdate: (NSString *) jsonRepresentation
|
||||
{
|
||||
NSString *sql, *serializedDefaults;
|
||||
NSString *sql;
|
||||
|
||||
serializedDefaults = [self _serializedDefaults];
|
||||
if (serializedDefaults)
|
||||
if ([jsonRepresentation length])
|
||||
sql = [NSString stringWithFormat: (@"UPDATE %@"
|
||||
@" SET %@ = '%@'"
|
||||
@" WHERE %@ = '%@'"),
|
||||
[[self tableURL] gcsTableName],
|
||||
fieldName,
|
||||
serializedDefaults,
|
||||
uidColumnName, [self uid]];
|
||||
@" SET %@ = '%@'"
|
||||
@" WHERE %@ = '%@'"),
|
||||
[[self tableURL] gcsTableName],
|
||||
fieldName,
|
||||
[self _sqlJsonRepresentation: jsonRepresentation],
|
||||
uidColumnName, [self uid]];
|
||||
else
|
||||
sql = nil;
|
||||
|
||||
@@ -245,136 +297,135 @@ static NSString *uidColumnName = @"c_uid";
|
||||
GCSChannelManager *cm;
|
||||
EOAdaptorChannel *channel;
|
||||
NSException *ex;
|
||||
NSString *sql;
|
||||
NSString *sql, *jsonRepresentation;
|
||||
SOGoCache *cache;
|
||||
BOOL rc;
|
||||
|
||||
rc = NO;
|
||||
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
sql = ((defFlags.isNew)
|
||||
? [self generateSQLForInsert]
|
||||
: [self generateSQLForUpdate]);
|
||||
if (sql)
|
||||
|
||||
jsonRepresentation = [values jsonStringValue];
|
||||
if (jsonRepresentation)
|
||||
{
|
||||
sql = ((defFlags.isNew)
|
||||
? [self generateSQLForInsert: jsonRepresentation]
|
||||
: [self generateSQLForUpdate: jsonRepresentation]);
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: [self tableURL]];
|
||||
if (channel)
|
||||
{
|
||||
defFlags.ready = YES;
|
||||
[[channel adaptorContext] beginTransaction];
|
||||
ex = [channel evaluateExpressionX:sql];
|
||||
if (ex)
|
||||
{
|
||||
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
||||
[[channel adaptorContext] rollbackTransaction];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([[channel adaptorContext] commitTransaction])
|
||||
{
|
||||
rc = YES;
|
||||
}
|
||||
|
||||
defFlags.modified = NO;
|
||||
defFlags.isNew = NO;
|
||||
}
|
||||
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
{
|
||||
if ([[channel adaptorContext] beginTransaction])
|
||||
{
|
||||
defFlags.ready = YES;
|
||||
ex = [channel evaluateExpressionX:sql];
|
||||
if (ex)
|
||||
{
|
||||
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
||||
[[channel adaptorContext] rollbackTransaction];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([[channel adaptorContext] commitTransaction])
|
||||
{
|
||||
cache = [SOGoCache sharedCache];
|
||||
if ([fieldName isEqualToString: @"c_defaults"])
|
||||
[cache setUserDefaults: jsonRepresentation
|
||||
forLogin: uid];
|
||||
else
|
||||
[cache setUserSettings: jsonRepresentation
|
||||
forLogin: uid];
|
||||
}
|
||||
defFlags.modified = NO;
|
||||
defFlags.isNew = NO;
|
||||
}
|
||||
[cm releaseChannel: channel];
|
||||
}
|
||||
else
|
||||
{
|
||||
defFlags.ready = NO;
|
||||
[cm releaseChannel: channel immediately: YES];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
defFlags.ready = NO;
|
||||
[self errorWithFormat: @"failed to acquire channel for URL: %@",
|
||||
[self tableURL]];
|
||||
}
|
||||
{
|
||||
defFlags.ready = NO;
|
||||
[self errorWithFormat: @"failed to acquire channel for URL: %@",
|
||||
[self tableURL]];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"failed to generate SQL for storing defaults"];
|
||||
|
||||
if (rc)
|
||||
[[SOGoCache sharedCache] cacheValues: values
|
||||
ofType: ([fieldName isEqualToString: @"c_defaults"] ? @"defaults" : @"settings")
|
||||
forLogin: uid];
|
||||
[self errorWithFormat: @"Unable to convert (%@) to a JSON string for"
|
||||
@" type: %@ and login: %@", values, fieldName, uid];
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) fetchProfile
|
||||
- (void) fetchProfile
|
||||
{
|
||||
return (values || [self primaryFetchProfile]);
|
||||
}
|
||||
|
||||
- (NSString *) jsonRepresentation
|
||||
{
|
||||
NSString *jsonRep;
|
||||
|
||||
[self fetchProfile];
|
||||
if (values)
|
||||
jsonRep = [values jsonStringValue];
|
||||
else
|
||||
jsonRep = @"{}";
|
||||
|
||||
return jsonRep;
|
||||
if (!values)
|
||||
[self primaryFetchProfile];
|
||||
}
|
||||
|
||||
/* value access */
|
||||
- (void) setValues: (NSDictionary *) theValues
|
||||
{
|
||||
[values release];
|
||||
|
||||
values = [[NSMutableDictionary alloc] init];
|
||||
[values addEntriesFromDictionary: theValues];
|
||||
defFlags.modified = NO;
|
||||
defFlags.isNew = NO;
|
||||
if ([self _isReadyOrRetry])
|
||||
{
|
||||
[values release];
|
||||
values = [[NSMutableDictionary alloc] init];
|
||||
[values addEntriesFromDictionary: theValues];
|
||||
defFlags.modified = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *) values
|
||||
{
|
||||
return values;
|
||||
NSDictionary *returnValues;
|
||||
|
||||
if ([self _isReadyOrRetry])
|
||||
returnValues = values;
|
||||
else
|
||||
returnValues = nil;
|
||||
|
||||
return returnValues;
|
||||
}
|
||||
|
||||
- (void) setObject: (id) value
|
||||
forKey: (NSString *) key
|
||||
{
|
||||
id old;
|
||||
|
||||
if (!defFlags.ready || ![self fetchProfile])
|
||||
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;
|
||||
}
|
||||
if ([self _isReadyOrRetry])
|
||||
{
|
||||
/* 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;
|
||||
|
||||
#warning Note that this work-around only works for first-level objects.
|
||||
/* 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 */
|
||||
if (value)
|
||||
[values setObject: value forKey: key];
|
||||
else
|
||||
[values removeObjectForKey: key];
|
||||
|
||||
defFlags.modified = YES;
|
||||
}
|
||||
|
||||
/* set in hash and mark as modified */
|
||||
if (value)
|
||||
[values setObject: value forKey: key];
|
||||
else
|
||||
[values removeObjectForKey: key];
|
||||
|
||||
defFlags.modified = YES;
|
||||
}
|
||||
|
||||
- (id) objectForKey: (NSString *) key
|
||||
{
|
||||
id value;
|
||||
|
||||
if (!defFlags.ready || ![self fetchProfile])
|
||||
value = nil;
|
||||
else
|
||||
value = [values objectForKey: key];
|
||||
|
||||
return value;
|
||||
return [[self values] objectForKey: key];
|
||||
}
|
||||
|
||||
- (void) removeObjectForKey: (NSString *) key
|
||||
@@ -388,9 +439,10 @@ static NSString *uidColumnName = @"c_uid";
|
||||
{
|
||||
// if (!defFlags.modified) /* was not modified */
|
||||
// return YES;
|
||||
|
||||
|
||||
/* ensure fetched data (more or less guaranteed by modified!=0) */
|
||||
if (![self fetchProfile])
|
||||
[self fetchProfile];
|
||||
if (!values)
|
||||
return NO;
|
||||
|
||||
/* store */
|
||||
@@ -401,7 +453,9 @@ static NSString *uidColumnName = @"c_uid";
|
||||
}
|
||||
|
||||
/* refetch */
|
||||
return [self primaryFetchProfile];
|
||||
[self primaryFetchProfile];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/* typed accessors */
|
||||
|
||||
@@ -29,14 +29,15 @@
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#include "NSArray+Utilities.h"
|
||||
#include "SOGoSource.h"
|
||||
#include "SOGoUserManager.h"
|
||||
#include "SOGoCache.h"
|
||||
#include "SOGoSource.h"
|
||||
#import "NSDictionary+BSJSONAdditions.h"
|
||||
#import "NSArray+Utilities.h"
|
||||
#import "SOGoSource.h"
|
||||
#import "SOGoUserManager.h"
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoSource.h"
|
||||
|
||||
#include "LDAPSource.h"
|
||||
#include "SQLSource.h"
|
||||
#import "LDAPSource.h"
|
||||
#import "SQLSource.h"
|
||||
|
||||
static NSString *defaultMailDomain = nil;
|
||||
static NSString *LDAPContactInfoAttribute = nil;
|
||||
@@ -344,14 +345,15 @@ static NSLock *lock = nil;
|
||||
andPassword: (NSString *) password
|
||||
{
|
||||
NSMutableDictionary *currentUser;
|
||||
NSString *dictPassword;
|
||||
NSString *dictPassword, *jsonUser;
|
||||
BOOL checkOK;
|
||||
|
||||
#if defined(THREADSAFE)
|
||||
[lock lock];
|
||||
#endif
|
||||
|
||||
currentUser = [[SOGoCache sharedCache] userAttributesForLogin: login];
|
||||
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: login];
|
||||
currentUser = [NSMutableDictionary dictionaryWithJSONString: jsonUser];
|
||||
dictPassword = [currentUser objectForKey: @"password"];
|
||||
if (currentUser && dictPassword)
|
||||
checkOK = ([dictPassword isEqualToString: password]);
|
||||
@@ -363,12 +365,15 @@ static NSLock *lock = nil;
|
||||
currentUser = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
// It's important to cache the password here as we might have cached the user's entry
|
||||
// in -contactInfosForUserWithUIDorEmail: and if we don't set the password and recache the
|
||||
// entry, the password would never be cached for the user unless its entry expires from
|
||||
// memcached's internal cache.
|
||||
// It's important to cache the password here as we might have cached the
|
||||
// user's entry in -contactInfosForUserWithUIDorEmail: and if we don't
|
||||
// set the password and recache the entry, the password would never be
|
||||
// cached for the user unless its entry expires from memcached's
|
||||
// internal cache.
|
||||
[currentUser setObject: password forKey: @"password"];
|
||||
[[SOGoCache sharedCache] cacheValues: currentUser ofType: @"attributes" forLogin: login];
|
||||
[[SOGoCache sharedCache]
|
||||
setUserAttributes: [currentUser jsonStringValue]
|
||||
forLogin: login];
|
||||
}
|
||||
else
|
||||
checkOK = NO;
|
||||
@@ -469,29 +474,23 @@ static NSLock *lock = nil;
|
||||
NSEnumerator *emails;
|
||||
NSString *key;
|
||||
|
||||
#if defined(THREADSAFE)
|
||||
[lock lock];
|
||||
#endif
|
||||
key = [newUser objectForKey: @"c_uid"];
|
||||
if (key)
|
||||
{
|
||||
[[SOGoCache sharedCache] cacheValues: newUser ofType: @"attributes" forLogin: key];
|
||||
}
|
||||
[[SOGoCache sharedCache]
|
||||
setUserAttributes: [newUser jsonStringValue]
|
||||
forLogin: key];
|
||||
|
||||
emails = [[newUser objectForKey: @"emails"] objectEnumerator];
|
||||
while ((key = [emails nextObject]))
|
||||
{
|
||||
[[SOGoCache sharedCache] cacheValues: newUser ofType: @"attributes" forLogin: key];
|
||||
}
|
||||
#if defined(THREADSAFE)
|
||||
[lock unlock];
|
||||
#endif
|
||||
[[SOGoCache sharedCache]
|
||||
setUserAttributes: [newUser jsonStringValue]
|
||||
forLogin: key];
|
||||
}
|
||||
|
||||
- (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid
|
||||
{
|
||||
NSMutableDictionary *currentUser, *contactInfos;
|
||||
NSString *aUID;
|
||||
NSString *aUID, *jsonUser;
|
||||
BOOL newUser;
|
||||
|
||||
if ([uid length] > 0)
|
||||
@@ -499,7 +498,8 @@ static NSLock *lock = nil;
|
||||
// Remove the "@" prefix used to identified groups in the ACL tables.
|
||||
aUID = [uid hasPrefix: @"@"] ? [uid substringFromIndex: 1] : uid;
|
||||
contactInfos = [NSMutableDictionary dictionary];
|
||||
currentUser = [[SOGoCache sharedCache] userAttributesForLogin: aUID];
|
||||
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: aUID];
|
||||
currentUser = [NSDictionary dictionaryWithJSONString: jsonUser];
|
||||
#if defined(THREADSAFE)
|
||||
[lock lock];
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user