1/* SOGoCacheGCSFolder.m - this file is part of SOGo
2 *
3 * Copyright (C) 2012-2014 Inverse inc.
4 *
5 * This file is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; see the file COPYING.  If not, write to
17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21#import <Foundation/NSArray.h>
22#import <Foundation/NSData.h>
23#import <Foundation/NSDictionary.h>
24#import <Foundation/NSException.h>
25#import <Foundation/NSPropertyList.h>
26#import <Foundation/NSString.h>
27#import <Foundation/NSValue.h>
28#import <Foundation/NSURL.h>
29
30#import <NGExtensions/NSObject+Logs.h>
31#import <NGExtensions/NSNull+misc.h>
32#import <GDLAccess/EOAdaptorChannel.h>
33#import <GDLContentStore/GCSChannelManager.h>
34
35#import <SOGo/NSArray+Utilities.h>
36#import <SOGo/NSString+Utilities.h>
37#import <SOGo/SOGoDomainDefaults.h>
38#import <SOGo/SOGoUser.h>
39#import "EOQualifier+SOGoCacheObject.h"
40#import "GCSSpecialQueries+SOGoCacheObject.h"
41
42#import "SOGoCacheGCSFolder.h"
43
44#undef DEBUG
45//#include <stdbool.h>
46//#include <talloc.h>
47//#include <util/time.h>
48//#include <mapistore/mapistore.h>
49//#include <mapistore/mapistore_errors.h>
50//#include <libmapiproxy.h>
51//#include <param.h>
52
53Class SOGoCacheGCSObjectK = Nil;
54
55@implementation SOGoCacheGCSFolder
56
57+ (void) initialize
58{
59  SOGoCacheGCSObjectK = [SOGoCacheGCSObject class];
60}
61
62- (id) init
63{
64  if ((self = [super init]))
65    {
66      pathPrefix = nil;
67    }
68
69  return self;
70}
71
72- (id) initWithName: (NSString *) name inContainer: (id) newContainer
73{
74  if ((self = [super initWithName: name inContainer: newContainer]))
75    {
76      objectType = MAPIFolderCacheObject;
77      aclMessage = [SOGoCacheGCSObject objectWithName: @"permissions"
78                                        inContainer: self];
79      [aclMessage setObjectType: MAPIInternalCacheObject];
80      [aclMessage retain];
81    }
82
83  return self;
84}
85
86- (void) dealloc
87{
88  [aclMessage release];
89  [pathPrefix release];
90  [super dealloc];
91}
92
93- (BOOL) isFolderish
94{
95  return YES;
96}
97
98- (void) setPathPrefix: (NSString *) newPathPrefix
99{
100  ASSIGN (pathPrefix, newPathPrefix);
101}
102
103- (NSMutableString *) pathForChild: (NSString *) childName
104{
105  NSMutableString *path;
106
107  path = [self path];
108  [path appendFormat: @"/%@", childName];
109
110  return path;
111}
112
113- (NSMutableString *) path
114{
115  NSMutableString *path;
116
117  path = [super path];
118  if (pathPrefix)
119    [path insertString: pathPrefix atIndex: 0];
120
121  return path;
122}
123
124// - (SOGoMAPIDBMessage *) newMessage
125// {
126//   NSString *newFilename;
127
128//   newFilename = [NSString stringWithFormat: @"%@.plist",
129//                           [SOGoObject globallyUniqueObjectId]];
130
131//   return [SOGoMAPIDBMessage objectWithName: filename inContainer: self];
132// }
133
134- (NSArray *) childKeysOfType: (SOGoCacheObjectType) type
135               includeDeleted: (BOOL) includeDeleted
136            matchingQualifier: (EOQualifier *) qualifier
137             andSortOrderings: (NSArray *) sortOrderings
138{
139  NSMutableArray *childKeys;
140  NSMutableString *sql// , *qualifierClause
141    ;
142  NSString *childPathPrefix, *childPath, *childKey;
143  NSMutableArray *whereClause;
144  NSArray *records;
145  NSDictionary *record;
146  NSUInteger childPathPrefixLen, count, max;
147  SOGoCacheGCSObject *currentChild;
148
149  /* query construction */
150  sql = [NSMutableString stringWithCapacity: 256];
151  [sql appendFormat: @"SELECT * FROM %@", [self tableName]];
152
153  whereClause = [NSMutableArray arrayWithCapacity: 2];
154  [whereClause addObject: [NSString stringWithFormat: @"c_parent_path = '%@'",
155                                    [self path]]];
156  [whereClause addObject: [NSString stringWithFormat: @"c_type = %d", type]];
157  if (!includeDeleted)
158    [whereClause addObject: @"c_deleted = 0"];
159
160  [sql appendFormat: @" WHERE %@",
161       [whereClause componentsJoinedByString: @" AND "]];
162
163  childPathPrefix = [NSString stringWithFormat: @"%@/", [self path]];
164
165  /* results */
166  records = [self performSQLQuery: sql];
167  if (records)
168    {
169      max = [records count];
170      childKeys = [NSMutableArray arrayWithCapacity: max];
171      childPathPrefixLen = [childPathPrefix length];
172      for (count = 0; count < max; count++)
173        {
174          record = [records objectAtIndex: count];
175          childPath = [record objectForKey: @"c_path"];
176          childKey = [childPath substringFromIndex: childPathPrefixLen];
177          if ([childKey rangeOfString: @"/"].location == NSNotFound)
178            {
179              if (qualifier)
180                {
181                  currentChild = [SOGoCacheGCSObject objectWithName: childKey
182                                                      inContainer: self];
183                  [currentChild setupFromRecord: record];
184                  if ([qualifier evaluateSOGoMAPIDBObject: currentChild])
185                    [childKeys addObject: childKey];
186                }
187              else
188                [childKeys addObject: childKey];
189            }
190        }
191    }
192  else
193    childKeys = nil;
194
195  return childKeys;
196}
197
198- (NSArray *) toManyRelationshipKeys
199{
200  return [self childKeysOfType: MAPIFolderCacheObject
201                includeDeleted: NO
202             matchingQualifier: nil
203              andSortOrderings: nil];
204}
205
206- (NSArray *) toOneRelationshipKeys
207{
208  return [self childKeysOfType: MAPIMessageCacheObject
209                includeDeleted: NO
210             matchingQualifier: nil
211              andSortOrderings: nil];
212}
213
214- (void) setNameInContainer: (NSString *) newName
215{
216  NSMutableString *sql;
217  NSString *oldPath, *newPath, *path, *parentPath;
218  NSMutableArray *queries;
219  NSArray *records;
220  NSDictionary *record;
221  NSUInteger count, max;
222
223  /* change the paths in children records */
224  if (nameInContainer)
225    oldPath = [self path];
226
227  [super setNameInContainer: newName];
228
229  if (nameInContainer)
230    {
231      newPath = [self path];
232
233      sql = [NSMutableString stringWithFormat:
234                               @"SELECT c_path, c_parent_path FROM %@"
235                             @" WHERE c_path LIKE '%@/%%'",
236                             [self tableName], oldPath];
237      records = [self performSQLQuery: sql];
238      max = [records count];
239      queries = [NSMutableArray arrayWithCapacity: max + 1];
240      if (max > 0)
241        {
242          for (count = 0; count < max; count++)
243            {
244              record = [records objectAtIndex: count];
245              path = [record objectForKey: @"c_path"];
246              sql = [NSMutableString stringWithFormat: @"UPDATE %@"
247                                     @" SET c_path = '%@'",
248                                     [self tableName],
249                        [path stringByReplacingPrefix: oldPath
250                                           withPrefix: newPath]];
251              parentPath = [record objectForKey: @"c_parent_path"];
252              if ([parentPath isNotNull])
253                [sql appendFormat: @", c_parent_path = '%@'",
254                     [parentPath stringByReplacingPrefix: oldPath
255                                              withPrefix: newPath]];
256              [sql appendFormat: @" WHERE c_path = '%@'", path];
257              [queries addObject: sql];
258            }
259          [self performBatchSQLQueries: queries];
260        }
261    }
262}
263
264- (void) changePathTo: (NSString *) newPath
265{
266  NSMutableString *sql// , *qualifierClause
267    ;
268  NSString *oldPath, *oldPathAsPrefix, *path, *parentPath;
269  NSMutableArray *queries;
270  NSArray *records;
271  NSDictionary *record;
272  NSUInteger count, max;
273
274  /* change the paths in children records */
275  oldPath = [self path];
276  oldPathAsPrefix = [NSString stringWithFormat: @"%@/", oldPath];
277
278  sql = [NSMutableString stringWithFormat:
279                           @"SELECT c_path, c_parent_path FROM %@"
280                         @" WHERE c_path LIKE '%@%%'",
281                         [self tableName], oldPathAsPrefix];
282  records = [self performSQLQuery: sql];
283  max = [records count];
284  queries = [NSMutableArray arrayWithCapacity: max + 1];
285  if (max > 0)
286    {
287      for (count = 0; count < max; count++)
288        {
289          record = [records objectAtIndex: count];
290          path = [record objectForKey: @"c_path"];
291          sql = [NSMutableString stringWithFormat: @"UPDATE %@"
292                                 @" SET c_path = '%@'",
293                                 [self tableName],
294                                 [path stringByReplacingPrefix: oldPath
295                                       withPrefix: newPath]];
296          parentPath = [record objectForKey: @"c_parent_path"];
297          if ([parentPath isNotNull])
298            [sql appendFormat: @", c_parent_path = '%@'",
299                 [parentPath stringByReplacingPrefix: oldPath
300                             withPrefix: newPath]];
301          [sql appendFormat: @" WHERE c_path = '%@'", path];
302          [queries addObject: sql];
303        }
304      [self performBatchSQLQueries: queries];
305    }
306
307  /* change the path in this folder record */
308  [super changePathTo: newPath];
309}
310
311- (void) changePathTo: (NSString *) newPath intoNewContainer: (id) newContainer
312{
313  [self changePathTo: newPath];
314  container = newContainer;
315  if ([self doesRetainContainer])
316    [container retain];
317}
318
319// - (NSArray *) toOneRelationshipKeysMatchingQualifier: (EOQualifier *) qualifier
320//                                     andSortOrderings: (NSArray *) sortOrderings
321// {
322//   NSArray *allKeys;
323//   NSMutableArray *keys;
324//   NSUInteger count, max;
325//   NSString *messageKey;
326//   SOGoMAPIDBMessage *message;
327
328//   if (sortOrderings)
329//     [self warnWithFormat: @"sorting is not handled yet"];
330
331//   allKeys = [self toOneRelationshipKeys];
332//   if (qualifier)
333//     {
334//       [self logWithFormat: @"%s: getting restricted FAI keys", __PRETTY_FUNCTION__];
335//       max = [allKeys count];
336//       keys = [NSMutableArray arrayWithCapacity: max];
337//       for (count = 0; count < max; count++)
338//         {
339//           messageKey = [allKeys objectAtIndex: count];
340//           message = [self lookupName: messageKey
341//                            inContext: nil
342//                              acquire: NO];
343//           if ([qualifier evaluateMAPIVolatileMessage: message])
344//             [keys addObject: messageKey];
345// 	}
346//     }
347//   else
348//     keys = (NSMutableArray *) allKeys;
349
350//   return keys;
351// }
352
353- (id) lookupName: (NSString *) childName
354	inContext: (WOContext *) woContext
355	  acquire: (BOOL) acquire
356{
357  id object;
358  Class objectClass;
359  NSString *childPath;
360  NSDictionary *record;
361
362  childPath = [self pathForChild: childName];
363  record = [self lookupRecord: childPath newerThanVersion: -1];
364  if (record)
365    {
366      if ([[record objectForKey: @"c_type"] intValue] == MAPIFolderCacheObject)
367        objectClass = object_getClass(self);
368      else
369        objectClass = SOGoCacheGCSObjectK;
370
371      object = [objectClass objectWithName: childName
372                               inContainer: self];
373      [object setupFromRecord: record];
374    }
375  else
376    object = nil;
377
378  return object;
379}
380
381- (id) lookupFolder: (NSString *) folderName
382          inContext: (WOContext *) woContext
383{
384  id object;
385
386  object = [SOGoCacheGCSFolder objectWithName: folderName
387                                inContainer: self];
388  [object reloadIfNeeded];
389
390  return object;
391}
392
393// - (id) _fileAttributeForKey: (NSString *) key
394// {
395//   NSDictionary *attributes;
396
397//   attributes = [[NSFileManager defaultManager]
398//                    fileAttributesAtPath: directory
399//                            traverseLink: NO];
400
401//   return [attributes objectForKey: key];
402// }
403
404// - (NSCalendarDate *) creationTime
405// {
406//   return [self _fileAttributeForKey: NSFileCreationDate];
407// }
408
409// - (NSCalendarDate *) lastModificationTime
410// {
411//   return [self _fileAttributeForKey: NSFileModificationDate];
412// }
413
414/* acl */
415- (NSString *) defaultUserID
416{
417  return @"default";
418}
419
420- (NSMutableDictionary *) _aclEntries
421{
422  NSMutableDictionary *aclEntries;
423
424  [aclMessage reloadIfNeeded];
425  aclEntries = [aclMessage properties];
426  if (![aclEntries objectForKey: @"users"])
427    [aclEntries setObject: [NSMutableArray array] forKey: @"users"];
428  if (![aclEntries objectForKey: @"entries"])
429    [aclEntries setObject: [NSMutableDictionary dictionary]
430                   forKey: @"entries"];
431
432  return aclEntries;
433}
434
435- (void) addUserInAcls: (NSString *) user
436{
437  NSMutableDictionary *acl;
438  NSMutableArray *users;
439
440  acl = [self _aclEntries];
441  users = [acl objectForKey: @"users"];
442  [users addObjectUniquely: user];
443  [aclMessage save];
444}
445
446- (void) removeAclsForUsers: (NSArray *) oldUsers
447{
448  NSDictionary *acl;
449  NSMutableDictionary *entries;
450  NSMutableArray *users;
451
452  acl = [self _aclEntries];
453  entries = [acl objectForKey: @"entries"];
454  [entries removeObjectsForKeys: oldUsers];
455  users = [acl objectForKey: @"users"];
456  [users removeObjectsInArray: oldUsers];
457  [aclMessage save];
458}
459
460- (NSArray *) aclUsers
461{
462  return [[self _aclEntries] objectForKey: @"users"];
463}
464
465- (NSArray *) aclsForUser: (NSString *) uid
466{
467  NSDictionary *entries;
468
469  entries = [[self _aclEntries] objectForKey: @"entries"];
470
471  return [entries objectForKey: uid];
472}
473
474- (void) setRoles: (NSArray *) roles
475          forUser: (NSString *) uid
476{
477  NSMutableDictionary *acl;
478  NSMutableDictionary *entries;
479
480  acl = [self _aclEntries];
481  entries = [acl objectForKey: @"entries"];
482  [entries setObject: roles forKey: uid];
483  [aclMessage save];
484}
485
486@end
487