mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
297 lines
8.6 KiB
Objective-C
297 lines
8.6 KiB
Objective-C
/* SOGoSQLUserProfile.m - this file is part of SOGo
|
|
*
|
|
* Copyright (C) 2009-2017 Inverse inc.
|
|
*
|
|
* This file is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This file is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#import <NGExtensions/NSObject+Logs.h>
|
|
#import <NGExtensions/NSNull+misc.h>
|
|
|
|
#import <GDLContentStore/GCSChannelManager.h>
|
|
#import <GDLContentStore/NSURL+GCS.h>
|
|
#import <GDLAccess/EOAdaptor.h>
|
|
#import <GDLAccess/EOAdaptorChannel.h>
|
|
#import <GDLAccess/EOAdaptorContext.h>
|
|
#import <GDLAccess/EOAttribute.h>
|
|
#import <SOGo/SOGoCache.h>
|
|
|
|
#import "SOGoSystemDefaults.h"
|
|
|
|
#import "SOGoSQLUserProfile.h"
|
|
|
|
static NSURL *tableURL = nil;
|
|
static NSString *uidColumnName = @"c_uid";
|
|
static EOAttribute *textColumn = nil;
|
|
static const NSString *kCDefaultsLenKey = @"kCDefaultsLenKey";
|
|
|
|
@implementation SOGoSQLUserProfile
|
|
|
|
+ (void) initialize
|
|
{
|
|
NSDictionary *description;
|
|
NSString *profileURL;
|
|
SOGoSystemDefaults *sd;
|
|
|
|
if (!tableURL)
|
|
{
|
|
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
|
profileURL = [sd profileURL];
|
|
if (profileURL)
|
|
tableURL = [[NSURL alloc] initWithString: profileURL];
|
|
}
|
|
|
|
if (!textColumn)
|
|
{
|
|
#warning This is a hack for providing an EOAttribute definition \
|
|
that is compatible with all the backends that we support
|
|
/* TODO: ... We should make use of EOModel for the profile tables */
|
|
description = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
@"c_textfield", @"columnName",
|
|
@"VARCHAR", @"externalType",
|
|
nil];
|
|
textColumn = [EOAttribute attributeFromPropertyList: description];
|
|
[textColumn retain];
|
|
}
|
|
}
|
|
|
|
- (id) init
|
|
{
|
|
if ((self = [super init]))
|
|
{
|
|
fieldName = nil;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
[fieldName release];
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void) setProfileType: (SOGoUserProfileType) newProfileType
|
|
{
|
|
if (newProfileType == SOGoUserProfileTypeDefaults)
|
|
ASSIGN (fieldName, @"c_defaults");
|
|
else if (newProfileType == SOGoUserProfileTypeSettings)
|
|
ASSIGN (fieldName, @"c_settings");
|
|
else
|
|
[self errorWithFormat: @"unknown profile value: %d", newProfileType];
|
|
|
|
[super setProfileType: newProfileType];
|
|
}
|
|
|
|
- (NSString *) fetchJSONProfileFromDB
|
|
{
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *channel;
|
|
NSDictionary *row;
|
|
NSException *ex;
|
|
NSString *sql, *result;
|
|
id value;
|
|
NSArray *attrs;
|
|
|
|
value = nil;
|
|
result = nil;
|
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
channel = [cm acquireOpenChannelForURL: tableURL];
|
|
if (channel)
|
|
{
|
|
/* generate SQL */
|
|
defFlags.ready = YES;
|
|
sql = [NSString stringWithFormat: @"SELECT %@ FROM %@ WHERE %@ = '%@'",
|
|
fieldName, [tableURL gcsTableName],
|
|
uidColumnName, [self uid]];
|
|
/* run SQL */
|
|
ex = [channel evaluateExpressionX: sql];
|
|
if (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];
|
|
[channel cancelFetch];
|
|
|
|
/* the isNew flag depends on the presence of the row in the
|
|
database rather than on the existence of the value. */
|
|
defFlags.isNew = (row == nil);
|
|
|
|
value = [row objectForKey: fieldName];
|
|
if (![value isNotNull])
|
|
value = nil; /* we discard any NSNull instance */
|
|
|
|
if (value && [value isKindOfClass: [NSData class]]) {
|
|
result = [NSString stringWithUTF8String: [value bytes]];
|
|
} else if (value && [value isKindOfClass: [NSString class]]) {
|
|
result = value;
|
|
}
|
|
}
|
|
|
|
[cm releaseChannel: channel];
|
|
}
|
|
else
|
|
{
|
|
defFlags.ready = NO;
|
|
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
|
tableURL];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSString *) generateSQLForInsert: (NSString *) jsonRepresentation
|
|
{
|
|
NSString *sql;
|
|
|
|
if ([jsonRepresentation length])
|
|
sql = [NSString stringWithFormat: (@"INSERT INTO %@"
|
|
@" (%@, %@)"
|
|
@" VALUES ('%@', %@)"),
|
|
[tableURL gcsTableName], uidColumnName, fieldName,
|
|
[self uid],
|
|
jsonRepresentation];
|
|
else
|
|
sql = nil;
|
|
|
|
return sql;
|
|
}
|
|
|
|
- (NSString *) generateSQLForUpdate: (NSString *) jsonRepresentation
|
|
{
|
|
NSString *sql;
|
|
|
|
if ([jsonRepresentation length])
|
|
sql = [NSString stringWithFormat: (@"UPDATE %@"
|
|
@" SET %@ = %@"
|
|
@" WHERE %@ = '%@'"),
|
|
[tableURL gcsTableName],
|
|
fieldName,
|
|
jsonRepresentation,
|
|
uidColumnName, [self uid]];
|
|
else
|
|
sql = nil;
|
|
|
|
return sql;
|
|
}
|
|
|
|
- (BOOL) storeJSONProfileInDB: (NSString *) jsonRepresentation
|
|
{
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *channel;
|
|
EOAdaptorContext *context;
|
|
NSException *ex;
|
|
NSString *sql, *formattedValue;
|
|
BOOL rc;
|
|
|
|
rc = NO;
|
|
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
channel = [cm acquireOpenChannelForURL: tableURL];
|
|
if (channel)
|
|
{
|
|
context = [channel adaptorContext];
|
|
if ([context beginTransaction])
|
|
{
|
|
formattedValue = [[context adaptor] formatValue: jsonRepresentation
|
|
forAttribute: textColumn];
|
|
sql = ((defFlags.isNew)
|
|
? [self generateSQLForInsert: formattedValue]
|
|
: [self generateSQLForUpdate: formattedValue]);
|
|
|
|
defFlags.ready = YES;
|
|
ex = [channel evaluateExpressionX:sql];
|
|
if (ex)
|
|
{
|
|
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
|
|
[context rollbackTransaction];
|
|
}
|
|
else
|
|
{
|
|
rc = YES;
|
|
defFlags.modified = NO;
|
|
defFlags.isNew = NO;
|
|
[context commitTransaction];
|
|
}
|
|
[cm releaseChannel: channel];
|
|
}
|
|
else
|
|
{
|
|
defFlags.ready = NO;
|
|
[cm releaseChannel: channel immediately: YES];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
defFlags.ready = NO;
|
|
[self errorWithFormat: @"failed to acquire channel for URL: %@",
|
|
tableURL];
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
- (unsigned long long) getCDefaultsSize {
|
|
unsigned long long r;
|
|
NSString *sql;
|
|
NSException *ex;
|
|
GCSChannelManager *cm;
|
|
EOAdaptorChannel *channel;
|
|
EOAdaptorContext *context;
|
|
NSArray *attrs;
|
|
NSDictionary *infos;
|
|
SOGoCache *cache;
|
|
NSNumberFormatter *formatter;
|
|
|
|
r = 65535;
|
|
|
|
cache = [SOGoCache sharedCache];
|
|
if ([cache valueForKey:kCDefaultsLenKey]) {
|
|
formatter = [[NSNumberFormatter alloc] init];
|
|
formatter.numberStyle = NSNumberFormatterDecimalStyle;
|
|
r = [[formatter numberFromString: [cache valueForKey:kCDefaultsLenKey]] unsignedLongLongValue];
|
|
[formatter release];
|
|
} else {
|
|
cm = [GCSChannelManager defaultChannelManager];
|
|
channel = [cm acquireOpenChannelForURL: tableURL];
|
|
sql = [NSString stringWithFormat: @"select character_octet_length as CHARACTER_MAXIMUM_LENGTH from information_schema.columns where table_name = '%@' AND column_name = 'c_defaults'", [tableURL gcsTableName]];
|
|
ex = [channel evaluateExpressionX: sql];
|
|
|
|
if (!ex) {
|
|
attrs = [channel describeResults: NO];
|
|
infos = [channel fetchAttributes: attrs withZone: NULL];
|
|
[cm releaseChannel: channel immediately: YES];
|
|
if (infos && [infos objectForKey:@"CHARACTER_MAXIMUM_LENGTH"]) {
|
|
r = [[infos objectForKey:@"CHARACTER_MAXIMUM_LENGTH"] longLongValue];
|
|
} else if (infos && [infos objectForKey:@"character_maximum_length"]) { // PGSQL case
|
|
r = [[infos objectForKey:@"character_maximum_length"] longLongValue];
|
|
}
|
|
}
|
|
|
|
[cache setValue: [[NSNumber numberWithUnsignedLongLong: r] stringValue] forKey: kCDefaultsLenKey];
|
|
}
|
|
|
|
|
|
return r;
|
|
}
|
|
|
|
@end
|