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