1/* SOGoSQLUserProfile.m - this file is part of SOGo 2 * 3 * Copyright (C) 2009 Inverse inc. 4 * 5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca> 6 * 7 * This file is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2, or (at your option) 10 * any later version. 11 * 12 * This file is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; see the file COPYING. If not, write to 19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 20 * Boston, MA 02111-1307, USA. 21 */ 22 23#import <NGExtensions/NSObject+Logs.h> 24#import <NGExtensions/NSNull+misc.h> 25 26#import <GDLContentStore/GCSChannelManager.h> 27#import <GDLContentStore/NSURL+GCS.h> 28#import <GDLAccess/EOAdaptor.h> 29#import <GDLAccess/EOAdaptorChannel.h> 30#import <GDLAccess/EOAdaptorContext.h> 31#import <GDLAccess/EOAttribute.h> 32 33#import "SOGoSystemDefaults.h" 34 35#import "SOGoSQLUserProfile.h" 36 37static NSURL *tableURL = nil; 38static NSString *uidColumnName = @"c_uid"; 39static EOAttribute *textColumn = nil; 40 41@implementation SOGoSQLUserProfile 42 43+ (void) initialize 44{ 45 NSDictionary *description; 46 NSString *profileURL; 47 SOGoSystemDefaults *sd; 48 49 if (!tableURL) 50 { 51 sd = [SOGoSystemDefaults sharedSystemDefaults]; 52 profileURL = [sd profileURL]; 53 if (profileURL) 54 tableURL = [[NSURL alloc] initWithString: profileURL]; 55 } 56 57 if (!textColumn) 58 { 59 #warning This is a hack for providing an EOAttribute definition \ 60 that is compatible with all the backends that we support 61 /* TODO: ... We should make use of EOModel for the profile tables */ 62 description = [NSDictionary dictionaryWithObjectsAndKeys: 63 @"c_textfield", @"columnName", 64 @"VARCHAR", @"externalType", 65 nil]; 66 textColumn = [EOAttribute attributeFromPropertyList: description]; 67 [textColumn retain]; 68 } 69} 70 71- (id) init 72{ 73 if ((self = [super init])) 74 { 75 fieldName = nil; 76 } 77 78 return self; 79} 80 81- (void) dealloc 82{ 83 [fieldName release]; 84 [super dealloc]; 85} 86 87- (void) setProfileType: (SOGoUserProfileType) newProfileType 88{ 89 if (newProfileType == SOGoUserProfileTypeDefaults) 90 ASSIGN (fieldName, @"c_defaults"); 91 else if (newProfileType == SOGoUserProfileTypeSettings) 92 ASSIGN (fieldName, @"c_settings"); 93 else 94 [self errorWithFormat: @"unknown profile value: %d", newProfileType]; 95 96 [super setProfileType: newProfileType]; 97} 98 99- (NSString *) fetchJSONProfileFromDB 100{ 101 GCSChannelManager *cm; 102 EOAdaptorChannel *channel; 103 NSDictionary *row; 104 NSException *ex; 105 NSString *sql, *value; 106 NSArray *attrs; 107 108 value = nil; 109 110 cm = [GCSChannelManager defaultChannelManager]; 111 channel = [cm acquireOpenChannelForURL: tableURL]; 112 if (channel) 113 { 114 /* generate SQL */ 115 defFlags.ready = YES; 116 sql = [NSString stringWithFormat: @"SELECT %@ FROM %@ WHERE %@ = '%@'", 117 fieldName, [tableURL gcsTableName], 118 uidColumnName, [self uid]]; 119 /* run SQL */ 120 ex = [channel evaluateExpressionX: sql]; 121 if (ex) 122 [self errorWithFormat:@"could not run SQL '%@': %@", sql, ex]; 123 else 124 { 125 /* fetch schema */ 126 attrs = [channel describeResults: NO /* don't beautify */]; 127 128 /* fetch values */ 129 row = [channel fetchAttributes: attrs withZone: NULL]; 130 [channel cancelFetch]; 131 132 /* the isNew flag depends on the presence of the row in the 133 database rather than on the existence of the value. */ 134 defFlags.isNew = (row == nil); 135 136 value = [row objectForKey: fieldName]; 137 if (![value isNotNull]) 138 value = nil; /* we discard any NSNull instance */ 139 } 140 141 [cm releaseChannel: channel]; 142 } 143 else 144 { 145 defFlags.ready = NO; 146 [self errorWithFormat:@"failed to acquire channel for URL: %@", 147 tableURL]; 148 } 149 150 return value; 151} 152 153- (NSString *) generateSQLForInsert: (NSString *) jsonRepresentation 154{ 155 NSString *sql; 156 157 if ([jsonRepresentation length]) 158 sql = [NSString stringWithFormat: (@"INSERT INTO %@" 159 @" (%@, %@)" 160 @" VALUES ('%@', %@)"), 161 [tableURL gcsTableName], uidColumnName, fieldName, 162 [self uid], 163 jsonRepresentation]; 164 else 165 sql = nil; 166 167 return sql; 168} 169 170- (NSString *) generateSQLForUpdate: (NSString *) jsonRepresentation 171{ 172 NSString *sql; 173 174 if ([jsonRepresentation length]) 175 sql = [NSString stringWithFormat: (@"UPDATE %@" 176 @" SET %@ = %@" 177 @" WHERE %@ = '%@'"), 178 [tableURL gcsTableName], 179 fieldName, 180 jsonRepresentation, 181 uidColumnName, [self uid]]; 182 else 183 sql = nil; 184 185 return sql; 186} 187 188- (BOOL) storeJSONProfileInDB: (NSString *) jsonRepresentation 189{ 190 GCSChannelManager *cm; 191 EOAdaptorChannel *channel; 192 EOAdaptorContext *context; 193 NSException *ex; 194 NSString *sql, *formattedValue; 195 BOOL rc; 196 197 rc = NO; 198 199 cm = [GCSChannelManager defaultChannelManager]; 200 channel = [cm acquireOpenChannelForURL: tableURL]; 201 if (channel) 202 { 203 context = [channel adaptorContext]; 204 if ([context beginTransaction]) 205 { 206 formattedValue = [[context adaptor] formatValue: jsonRepresentation 207 forAttribute: textColumn]; 208 sql = ((defFlags.isNew) 209 ? [self generateSQLForInsert: formattedValue] 210 : [self generateSQLForUpdate: formattedValue]); 211 212 defFlags.ready = YES; 213 ex = [channel evaluateExpressionX:sql]; 214 if (ex) 215 { 216 [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; 217 [context rollbackTransaction]; 218 } 219 else 220 { 221 rc = YES; 222 defFlags.modified = NO; 223 defFlags.isNew = NO; 224 [context commitTransaction]; 225 } 226 [cm releaseChannel: channel]; 227 } 228 else 229 { 230 defFlags.ready = NO; 231 [cm releaseChannel: channel immediately: YES]; 232 } 233 } 234 else 235 { 236 defFlags.ready = NO; 237 [self errorWithFormat: @"failed to acquire channel for URL: %@", 238 tableURL]; 239 } 240 241 return rc; 242} 243 244@end 245