]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoFolder.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1163 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / SOGo / SOGoFolder.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #import <unistd.h>
23 #import <stdlib.h>
24
25 #import <Foundation/NSArray.h>
26 #import <Foundation/NSDate.h>
27 #import <Foundation/NSDictionary.h>
28 #import <Foundation/NSException.h>
29 #import <Foundation/NSKeyValueCoding.h>
30 #import <Foundation/NSURL.h>
31
32 #import <NGObjWeb/SoObject.h>
33 #import <NGObjWeb/SoObject+SoDAV.h>
34 #import <NGObjWeb/SoSelectorInvocation.h>
35 #import <NGObjWeb/WOContext+SoObjects.h>
36 #import <NGExtensions/NSNull+misc.h>
37 #import <NGExtensions/NSObject+Logs.h>
38 #import <EOControl/EOQualifier.h>
39 #import <GDLAccess/EOAdaptorChannel.h>
40 #import <GDLContentStore/GCSFolderManager.h>
41 #import <GDLContentStore/GCSFolder.h>
42 #import <GDLContentStore/GCSFolderType.h>
43 #import <SaxObjC/XMLNamespaces.h>
44
45 #import "NSArray+Utilities.h"
46 #import "NSString+Utilities.h"
47
48 #import "SOGoPermissions.h"
49 #import "SOGoUser.h"
50
51 #import "SOGoFolder.h"
52
53 static NSString *defaultUserID = @"<default>";
54
55 @implementation SOGoFolder
56
57 + (int) version
58 {
59   return [super version] + 0 /* v0 */;
60 }
61
62 + (void) initialize
63 {
64   NSAssert2([super version] == 0,
65             @"invalid superclass (%@) version %i !",
66             NSStringFromClass([self superclass]), [super version]);
67 }
68
69 + (NSString *) globallyUniqueObjectId
70 {
71   /*
72     4C08AE1A-A808-11D8-AC5A-000393BBAFF6
73     SOGo-Web-28273-18283-288182
74     printf( "%x", *(int *) &f);
75   */
76   static int pid = 0;
77   static int sequence = 0;
78   static float rndm = 0;
79   float f;
80
81   if (pid == 0)
82     { /* break if we fork ;-) */
83       pid = getpid();
84       rndm = random();
85     }
86   sequence++;
87   f = [[NSDate date] timeIntervalSince1970];
88   return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X",
89                    pid, *(int *)&f, sequence++, random];
90 }
91
92 - (id) init
93 {
94   if ((self = [super init]))
95     {
96       ocsPath = nil;
97       ocsFolder = nil;
98       aclCache = [NSMutableDictionary new];
99     }
100
101   return self;
102 }
103
104 - (void) dealloc
105 {
106   [ocsFolder release];
107   [ocsPath release];
108   [aclCache release];
109   [super dealloc];
110 }
111
112 /* accessors */
113
114 - (BOOL) isFolderish
115 {
116   return YES;
117 }
118
119 - (void) setOCSPath: (NSString *) _path
120 {
121   if ([ocsPath isEqualToString:_path])
122     return;
123   
124   if (ocsPath)
125     [self warnWithFormat:@"GCS path is already set! '%@'", _path];
126   
127   ASSIGNCOPY(ocsPath, _path);
128 }
129
130 - (NSString *) ocsPath
131 {
132   return ocsPath;
133 }
134
135 - (GCSFolderManager *) folderManager
136 {
137   static GCSFolderManager *folderManager = nil;
138
139   if (!folderManager)
140     folderManager = [GCSFolderManager defaultFolderManager];
141
142   return folderManager;
143 }
144
145 - (GCSFolder *) ocsFolderForPath: (NSString *) _path
146 {
147   return [[self folderManager] folderAtPath: _path];
148 }
149
150 - (BOOL) folderIsMandatory
151 {
152   [self subclassResponsibility: _cmd];
153
154   return NO;
155 }
156
157 - (GCSFolder *) ocsFolder
158 {
159   GCSFolder *folder;
160   NSString *userLogin;
161
162   if (!ocsFolder)
163     {
164       ocsFolder = [self ocsFolderForPath: [self ocsPath]];
165       userLogin = [[context activeUser] login];
166       if (!ocsFolder
167           && [userLogin isEqualToString: [self ownerInContext: context]]
168           && [self folderIsMandatory]
169           && [self create])
170         ocsFolder = [self ocsFolderForPath: [self ocsPath]];
171       [ocsFolder retain];
172     }
173
174   if ([ocsFolder isNotNull])
175     folder = ocsFolder;
176   else
177     folder = nil;
178
179   return folder;
180 }
181
182 - (NSString *) folderType
183 {
184   return @"";
185 }
186
187 - (BOOL) create
188 {
189   NSException *result;
190
191   result = [[self folderManager] createFolderOfType: [self folderType]
192                                  atPath: ocsPath];
193
194   return (result == nil);
195 }
196
197 - (NSException *) delete
198 {
199   return [[self folderManager] deleteFolderAtPath: ocsPath];
200 }
201
202 - (NSArray *) fetchContentObjectNames
203 {
204   NSArray *fields, *records;
205   
206   fields = [NSArray arrayWithObject: @"c_name"];
207   records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
208   if (![records isNotNull])
209     {
210       [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
211       return nil;
212     }
213   if ([records isKindOfClass: [NSException class]])
214     return records;
215   return [records objectsForKey: @"c_name"];
216 }
217
218 - (BOOL) nameExistsInFolder: (NSString *) objectName
219 {
220   NSArray *fields, *records;
221   EOQualifier *qualifier;
222
223   qualifier
224     = [EOQualifier qualifierWithQualifierFormat:
225                      [NSString stringWithFormat: @"c_name='%@'", objectName]];
226
227   fields = [NSArray arrayWithObject: @"c_name"];
228   records = [[self ocsFolder] fetchFields: fields
229                               matchingQualifier: qualifier];
230   return (records
231           && ![records isKindOfClass:[NSException class]]
232           && [records count] > 0);
233 }
234
235 - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
236 {
237   NSDictionary *files;
238   
239   files = [[self ocsFolder] fetchContentsOfAllFiles];
240   if (![files isNotNull])
241     {
242       [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
243       return nil;
244     }
245   if ([files isKindOfClass:[NSException class]])
246     return files;
247   return files;
248 }
249
250 /* reflection */
251
252 - (NSString *) defaultFilenameExtension
253 {
254   /* 
255      Override to add an extension to a filename
256      
257      Note: be careful with that, needs to be consistent with object lookup!
258   */
259   return nil;
260 }
261
262 - (NSArray *) davResourceType
263 {
264   NSArray *rType, *groupDavCollection;
265
266   if ([self respondsToSelector: @selector (groupDavResourceType)])
267     {
268       groupDavCollection
269         = [NSArray arrayWithObjects: [self groupDavResourceType],
270                    XMLNS_GROUPDAV, nil];
271       rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
272                        nil];
273     }
274   else
275     rType = [NSArray arrayWithObject: @"collection"];
276
277   return rType;
278 }
279
280 - (NSString *) davContentType
281 {
282   return @"httpd/unix-directory";
283 }
284
285 - (NSArray *) toOneRelationshipKeys
286 {
287   /* toOneRelationshipKeys are the 'files' contained in a folder */
288   NSMutableArray *ma;
289   NSArray  *names;
290   NSString *name, *ext;
291   unsigned i, count;
292   NSRange  r;
293
294   names = [self fetchContentObjectNames];
295   count = [names count];
296   ext = [self defaultFilenameExtension];
297   if (count && [ext length] > 0)
298     {
299       ma = [NSMutableArray arrayWithCapacity: count];
300       for (i = 0; i < count; i++)
301         {
302           name = [names objectAtIndex: i];
303           r = [name rangeOfString: @"."];
304           if (r.length == 0)
305             name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
306           [ma addObject: name];
307         }
308
309       names = ma;
310     }
311
312   return names;
313 }
314
315 /* acls as a container */
316
317 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
318 {
319   EOQualifier *qualifier;
320   NSString *qs;
321   NSArray *records;
322
323   qs = [NSString stringWithFormat: @"c_object = '/%@'",
324                  [objectPathArray componentsJoinedByString: @"/"]];
325   qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
326   records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
327
328   return [records valueForKey: @"c_uid"];
329 }
330
331 - (NSArray *) _fetchAclsForUser: (NSString *) uid
332                 forObjectAtPath: (NSString *) objectPath
333 {
334   EOQualifier *qualifier;
335   NSArray *records;
336   NSMutableArray *acls;
337   NSString *qs;
338
339   qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
340                  objectPath, uid];
341   qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
342   records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
343
344   acls = [NSMutableArray array];
345   if ([records count] > 0)
346     {
347       [acls addObject: SOGoRole_AuthorizedSubscriber];
348       [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
349     }
350
351   return acls;
352 }
353
354 - (void) _cacheRoles: (NSArray *) roles
355              forUser: (NSString *) uid
356      forObjectAtPath: (NSString *) objectPath
357 {
358   NSMutableDictionary *aclsForObject;
359
360   aclsForObject = [aclCache objectForKey: objectPath];
361   if (!aclsForObject)
362     {
363       aclsForObject = [NSMutableDictionary dictionary];
364       [aclCache setObject: aclsForObject
365                 forKey: objectPath];
366     }
367   if (roles)
368     [aclsForObject setObject: roles forKey: uid];
369   else
370     [aclsForObject removeObjectForKey: uid];
371 }
372
373 - (NSArray *) aclsForUser: (NSString *) uid
374           forObjectAtPath: (NSArray *) objectPathArray
375 {
376   NSArray *acls;
377   NSString *objectPath;
378   NSDictionary *aclsForObject;
379
380   objectPath = [objectPathArray componentsJoinedByString: @"/"];
381   aclsForObject = [aclCache objectForKey: objectPath];
382   if (aclsForObject)
383     acls = [aclsForObject objectForKey: uid];
384   else
385     acls = nil;
386   if (!acls)
387     {
388       acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
389       [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
390     }
391
392   if (!([acls count] || [uid isEqualToString: defaultUserID]))
393     acls = [self aclsForUser: defaultUserID
394                  forObjectAtPath: objectPathArray];
395
396   return acls;
397 }
398
399 - (void) removeAclsForUsers: (NSArray *) users
400             forObjectAtPath: (NSArray *) objectPathArray
401 {
402   EOQualifier *qualifier;
403   NSString *uids, *qs, *objectPath;
404   NSMutableDictionary *aclsForObject;
405
406   if ([users count] > 0)
407     {
408       objectPath = [objectPathArray componentsJoinedByString: @"/"];
409       aclsForObject = [aclCache objectForKey: objectPath];
410       if (aclsForObject)
411         [aclsForObject removeObjectsForKeys: users];
412       uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
413       qs = [NSString
414              stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
415              objectPath, uids];
416       qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
417       [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
418     }
419 }
420
421 - (void) _commitRoles: (NSArray *) roles
422                forUID: (NSString *) uid
423             forObject: (NSString *) objectPath
424 {
425   EOAdaptorChannel *channel;
426   GCSFolder *folder;
427   NSEnumerator *userRoles;
428   NSString *SQL, *currentRole;
429
430   folder = [self ocsFolder];
431   channel = [folder acquireAclChannel];
432   userRoles = [roles objectEnumerator];
433   currentRole = [userRoles nextObject];
434   while (currentRole)
435     {
436       SQL = [NSString stringWithFormat: @"INSERT INTO %@"
437                       @" (c_object, c_uid, c_role)"
438                       @" VALUES ('/%@', '%@', '%@')",
439                       [folder aclTableName],
440                       objectPath, uid, currentRole];
441       [channel evaluateExpressionX: SQL];
442       currentRole = [userRoles nextObject];
443     }
444
445   [folder releaseChannel: channel];
446 }
447
448 - (void) setRoles: (NSArray *) roles
449           forUser: (NSString *) uid
450   forObjectAtPath: (NSArray *) objectPathArray
451 {
452   NSString *objectPath;
453   NSMutableArray *newRoles;
454
455   [self removeAclsForUsers: [NSArray arrayWithObject: uid]
456         forObjectAtPath: objectPathArray];
457
458   newRoles = [NSMutableArray arrayWithArray: roles];
459   [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
460   [newRoles removeObject: SOGoRole_None];
461   objectPath = [objectPathArray componentsJoinedByString: @"/"];
462   [self _cacheRoles: newRoles forUser: uid
463         forObjectAtPath: objectPath];
464   if (![newRoles count])
465     [newRoles addObject: SOGoRole_None];
466
467   [self _commitRoles: newRoles forUID: uid forObject: objectPath];
468 }
469
470 /* acls */
471 - (NSArray *) aclUsers
472 {
473   return [self aclUsersForObjectAtPath: [self pathArrayToSoObject]];
474 }
475
476 - (NSArray *) aclsForUser: (NSString *) uid
477 {
478   NSMutableArray *acls;
479   NSArray *ownAcls, *containerAcls;
480
481   acls = [NSMutableArray array];
482   ownAcls = [self aclsForUser: uid
483                   forObjectAtPath: [self pathArrayToSoObject]];
484   [acls addObjectsFromArray: ownAcls];
485   if ([container respondsToSelector: @selector (aclsForUser:)])
486     {
487       containerAcls = [container aclsForUser: uid];
488       if ([containerAcls count] > 0)
489         {
490           if ([containerAcls containsObject: SOGoRole_ObjectReader])
491             [acls addObject: SOGoRole_ObjectViewer];
492 #warning this should be checked
493           if ([containerAcls containsObject: SOGoRole_ObjectEraser])
494             [acls addObject: SOGoRole_ObjectEraser];
495         }
496     }
497
498   return acls;
499 }
500
501 - (void) setRoles: (NSArray *) roles
502           forUser: (NSString *) uid
503 {
504   return [self setRoles: roles
505                forUser: uid
506                forObjectAtPath: [self pathArrayToSoObject]];
507 }
508
509 - (void) removeAclsForUsers: (NSArray *) users
510 {
511   return [self removeAclsForUsers: users
512                forObjectAtPath: [self pathArrayToSoObject]];
513 }
514
515 - (NSString *) defaultUserID
516 {
517   return defaultUserID;
518 }
519
520 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
521 {
522   return [[self soURL] absoluteString];
523 }
524
525 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
526 {
527   return [[self davURL] absoluteString];
528 }
529
530 - (id) lookupName: (NSString *) lookupName
531         inContext: (id) localContext
532           acquire: (BOOL) acquire
533 {
534   id obj;
535   NSArray *davNamespaces;
536   NSDictionary *davInvocation;
537   NSString *objcMethod;
538
539   obj = [super lookupName: lookupName inContext: localContext
540                acquire: acquire];
541   if (!obj)
542     {
543       davNamespaces = [self davNamespaces];
544       if ([davNamespaces count] > 0)
545         {
546           davInvocation = [lookupName asDavInvocation];
547           if (davInvocation
548               && [davNamespaces
549                    containsObject: [davInvocation objectForKey: @"ns"]])
550             {
551               objcMethod = [[davInvocation objectForKey: @"method"]
552                              davMethodToObjC];
553               obj = [[SoSelectorInvocation alloc]
554                       initWithSelectorNamed:
555                         [NSString stringWithFormat: @"%@:", objcMethod]
556                       addContextParameter: YES];
557               [obj autorelease];
558             }
559         }
560     }
561
562   return obj;
563 }
564
565 /* WebDAV */
566
567 - (NSArray *) davNamespaces
568 {
569   return nil;
570 }
571
572 - (BOOL) davIsCollection
573 {
574   return [self isFolderish];
575 }
576
577 /* folder type */
578
579 - (NSString *) outlookFolderClass
580 {
581   [self subclassResponsibility: _cmd];
582
583   return nil;
584 }
585
586 /* description */
587
588 - (void) appendAttributesToDescription: (NSMutableString *) _ms
589 {
590   [super appendAttributesToDescription:_ms];
591   
592   [_ms appendFormat:@" ocs=%@", [self ocsPath]];
593 }
594
595 - (NSString *) loggingPrefix
596 {
597   return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
598                    self, NSStringFromClass([self class]),
599                    [self nameInContainer]];
600 }
601
602 @end /* SOGoFolder */