]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoFolder.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1173 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 <Foundation/NSArray.h>
23 #import <Foundation/NSDate.h>
24 #import <Foundation/NSDictionary.h>
25 #import <Foundation/NSException.h>
26 #import <Foundation/NSKeyValueCoding.h>
27 #import <Foundation/NSURL.h>
28
29 #import <NGObjWeb/NSException+HTTP.h>
30 #import <NGObjWeb/SoObject.h>
31 #import <NGObjWeb/SoObject+SoDAV.h>
32 #import <NGObjWeb/SoSelectorInvocation.h>
33 #import <NGObjWeb/WOContext+SoObjects.h>
34 #import <NGExtensions/NSNull+misc.h>
35 #import <NGExtensions/NSObject+Logs.h>
36 #import <EOControl/EOQualifier.h>
37 #import <GDLAccess/EOAdaptorChannel.h>
38 #import <GDLContentStore/GCSChannelManager.h>
39 #import <GDLContentStore/GCSFolderManager.h>
40 #import <GDLContentStore/GCSFolder.h>
41 #import <GDLContentStore/GCSFolderType.h>
42 #import <GDLContentStore/NSURL+GCS.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 + (id) folderWithSubscriptionReference: (NSString *) reference
70                            inContainer: (id) aContainer
71 {
72   id newFolder;
73   NSArray *elements, *pathElements;
74   NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName;
75
76   elements = [reference componentsSeparatedByString: @":"];
77   owner = [elements objectAtIndex: 0];
78   objectPath = [elements objectAtIndex: 1];
79   pathElements = [objectPath componentsSeparatedByString: @"/"];
80   if ([pathElements count] > 1)
81     ocsName = [pathElements objectAtIndex: 1];
82   else
83     ocsName = @"personal";
84
85   ocsPath = [NSString stringWithFormat: @"/Users/%@/%@/%@",
86                       owner, [pathElements objectAtIndex: 0], ocsName];
87   folderName = [NSString stringWithFormat: @"%@_%@", owner, ocsName];
88   newFolder = [[self alloc] initWithName: folderName
89                             inContainer: aContainer];
90   [newFolder setOCSPath: ocsPath];
91   [newFolder setOwner: owner];
92
93   return newFolder;
94 }
95
96 - (id) init
97 {
98   if ((self = [super init]))
99     {
100       displayName = nil;
101       ocsPath = nil;
102       ocsFolder = nil;
103       aclCache = [NSMutableDictionary new];
104     }
105
106   return self;
107 }
108
109 - (void) dealloc
110 {
111   [ocsFolder release];
112   [ocsPath release];
113   [aclCache release];
114   [displayName release];
115   [super dealloc];
116 }
117
118 /* accessors */
119
120 - (BOOL) isFolderish
121 {
122   return YES;
123 }
124
125 - (void) setOCSPath: (NSString *) _path
126 {
127   if (![ocsPath isEqualToString:_path])
128     {
129       if (ocsPath)
130         [self warnWithFormat: @"GCS path is already set! '%@'", _path];
131       ASSIGN (ocsPath, _path);
132     }
133 }
134
135 - (NSString *) ocsPath
136 {
137   return ocsPath;
138 }
139
140 - (GCSFolderManager *) folderManager
141 {
142   static GCSFolderManager *folderManager = nil;
143
144   if (!folderManager)
145     folderManager = [GCSFolderManager defaultFolderManager];
146
147   return folderManager;
148 }
149
150 - (GCSFolder *) ocsFolderForPath: (NSString *) _path
151 {
152   return [[self folderManager] folderAtPath: _path];
153 }
154
155 - (BOOL) folderIsMandatory
156 {
157   return [nameInContainer isEqualToString: @"personal"];
158 }
159
160 - (void) _setDisplayNameFromRow: (NSDictionary *) row
161 {
162   NSString *currentLogin, *ownerLogin;
163   NSDictionary *ownerIdentity;
164
165   displayName
166     = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]];
167   currentLogin = [[context activeUser] login];
168   ownerLogin = [self ownerInContext: context];
169   if (![currentLogin isEqualToString: ownerLogin])
170     {
171       ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil]
172                         primaryIdentity];
173       [displayName appendFormat: @" (%@ <%@>)",
174                    [ownerIdentity objectForKey: @"fullName"],
175                    [ownerIdentity objectForKey: @"email"]];
176     }
177 }
178
179 - (void) _fetchDisplayName
180 {
181   GCSChannelManager *cm;
182   EOAdaptorChannel *fc;
183   NSURL *folderLocation;
184   NSString *sql;
185   NSArray *attrs;
186   NSDictionary *row;
187
188   cm = [GCSChannelManager defaultChannelManager];
189   folderLocation
190     = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
191   fc = [cm acquireOpenChannelForURL: folderLocation];
192   if (fc)
193     {
194       sql
195         = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
196                                        @" WHERE c_path = '%@'"),
197                     [folderLocation gcsTableName], ocsPath];
198       [fc evaluateExpressionX: sql];
199       attrs = [fc describeResults: NO];
200       row = [fc fetchAttributes: attrs withZone: NULL];
201       if (row)
202         [self _setDisplayNameFromRow: row];
203       [fc cancelFetch];
204       [cm releaseChannel: fc];
205     }
206 }
207
208 - (void) setDisplayName: (NSString *) newDisplayName
209 {
210   ASSIGN (displayName, newDisplayName);
211 }
212
213 - (NSString *) displayName
214 {
215   if (!displayName)
216     [self _fetchDisplayName];
217
218   return displayName;
219 }
220
221 - (NSString *) davDisplayName
222 {
223   return [self displayName];
224 }
225
226 - (GCSFolder *) ocsFolder
227 {
228   GCSFolder *folder;
229   NSString *userLogin;
230
231   if (!ocsFolder)
232     {
233       ocsFolder = [self ocsFolderForPath: [self ocsPath]];
234       userLogin = [[context activeUser] login];
235       if (!ocsFolder
236           && [userLogin isEqualToString: [self ownerInContext: context]]
237           && [self folderIsMandatory]
238           && [self create])
239         ocsFolder = [self ocsFolderForPath: [self ocsPath]];
240       [ocsFolder retain];
241     }
242
243   if ([ocsFolder isNotNull])
244     folder = ocsFolder;
245   else
246     folder = nil;
247
248   return folder;
249 }
250
251 - (NSString *) folderType
252 {
253   return @"";
254 }
255
256 - (BOOL) create
257 {
258   NSException *result;
259
260 //   [self dieHard];
261   result = [[self folderManager] createFolderOfType: [self folderType]
262                                  withName: displayName
263                                  atPath: ocsPath];
264
265   return (result == nil);
266 }
267
268 - (NSException *) delete
269 {
270   NSException *error;
271
272   if ([nameInContainer isEqualToString: @"personal"])
273     error = [NSException exceptionWithHTTPStatus: 403
274                          reason: @"folder 'personal' cannot be deleted"];
275   else
276     error = [[self folderManager] deleteFolderAtPath: ocsPath];
277
278   return error;
279 }
280
281 - (void) renameTo: (NSString *) newName
282 {
283   GCSChannelManager *cm;
284   EOAdaptorChannel *fc;
285   NSURL *folderLocation;
286   NSString *sql;
287
288   [displayName release];
289   displayName = nil;
290
291   cm = [GCSChannelManager defaultChannelManager];
292   folderLocation
293     = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
294   fc = [cm acquireOpenChannelForURL: folderLocation];
295   if (fc)
296     {
297       sql
298         = [NSString stringWithFormat: (@"UPDATE %@ SET c_foldername = '%@'"
299                                        @" WHERE c_path = '%@'"),
300                     [folderLocation gcsTableName], newName, ocsPath];
301       [fc evaluateExpressionX: sql];
302       [cm releaseChannel: fc];
303 //       sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", 
304 //                  uidColumnName, [self uid]];
305     }
306 }
307
308 - (NSArray *) fetchContentObjectNames
309 {
310   NSArray *fields, *records;
311   
312   fields = [NSArray arrayWithObject: @"c_name"];
313   records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
314   if (![records isNotNull])
315     {
316       [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
317       return nil;
318     }
319   if ([records isKindOfClass: [NSException class]])
320     return records;
321   return [records objectsForKey: @"c_name"];
322 }
323
324 - (BOOL) nameExistsInFolder: (NSString *) objectName
325 {
326   NSArray *fields, *records;
327   EOQualifier *qualifier;
328
329   qualifier
330     = [EOQualifier qualifierWithQualifierFormat:
331                      [NSString stringWithFormat: @"c_name='%@'", objectName]];
332
333   fields = [NSArray arrayWithObject: @"c_name"];
334   records = [[self ocsFolder] fetchFields: fields
335                               matchingQualifier: qualifier];
336   return (records
337           && ![records isKindOfClass:[NSException class]]
338           && [records count] > 0);
339 }
340
341 - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
342 {
343   NSDictionary *files;
344   
345   files = [[self ocsFolder] fetchContentsOfAllFiles];
346   if (![files isNotNull])
347     {
348       [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
349       return nil;
350     }
351   if ([files isKindOfClass:[NSException class]])
352     return files;
353   return files;
354 }
355
356 /* reflection */
357
358 - (NSString *) defaultFilenameExtension
359 {
360   /* 
361      Override to add an extension to a filename
362      
363      Note: be careful with that, needs to be consistent with object lookup!
364   */
365   return nil;
366 }
367
368 - (NSArray *) davResourceType
369 {
370   NSArray *rType, *groupDavCollection;
371
372   if ([self respondsToSelector: @selector (groupDavResourceType)])
373     {
374       groupDavCollection
375         = [NSArray arrayWithObjects: [self groupDavResourceType],
376                    XMLNS_GROUPDAV, nil];
377       rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
378                        nil];
379     }
380   else
381     rType = [NSArray arrayWithObject: @"collection"];
382
383   return rType;
384 }
385
386 - (NSString *) davContentType
387 {
388   return @"httpd/unix-directory";
389 }
390
391 - (NSArray *) toOneRelationshipKeys
392 {
393   /* toOneRelationshipKeys are the 'files' contained in a folder */
394   NSMutableArray *ma;
395   NSArray  *names;
396   NSString *name, *ext;
397   unsigned i, count;
398   NSRange  r;
399
400   names = [self fetchContentObjectNames];
401   count = [names count];
402   ext = [self defaultFilenameExtension];
403   if (count && [ext length] > 0)
404     {
405       ma = [NSMutableArray arrayWithCapacity: count];
406       for (i = 0; i < count; i++)
407         {
408           name = [names objectAtIndex: i];
409           r = [name rangeOfString: @"."];
410           if (r.length == 0)
411             name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
412           [ma addObject: name];
413         }
414
415       names = ma;
416     }
417
418   return names;
419 }
420
421 /* acls as a container */
422
423 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
424 {
425   EOQualifier *qualifier;
426   NSString *qs;
427   NSArray *records;
428
429   qs = [NSString stringWithFormat: @"c_object = '/%@'",
430                  [objectPathArray componentsJoinedByString: @"/"]];
431   qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
432   records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
433
434   return [records valueForKey: @"c_uid"];
435 }
436
437 - (NSArray *) _fetchAclsForUser: (NSString *) uid
438                 forObjectAtPath: (NSString *) objectPath
439 {
440   EOQualifier *qualifier;
441   NSArray *records;
442   NSMutableArray *acls;
443   NSString *qs;
444
445   qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
446                  objectPath, uid];
447   qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
448   records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
449
450   acls = [NSMutableArray array];
451   if ([records count] > 0)
452     {
453       [acls addObject: SOGoRole_AuthorizedSubscriber];
454       [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
455     }
456
457   return acls;
458 }
459
460 - (void) _cacheRoles: (NSArray *) roles
461              forUser: (NSString *) uid
462      forObjectAtPath: (NSString *) objectPath
463 {
464   NSMutableDictionary *aclsForObject;
465
466   aclsForObject = [aclCache objectForKey: objectPath];
467   if (!aclsForObject)
468     {
469       aclsForObject = [NSMutableDictionary dictionary];
470       [aclCache setObject: aclsForObject
471                 forKey: objectPath];
472     }
473   if (roles)
474     [aclsForObject setObject: roles forKey: uid];
475   else
476     [aclsForObject removeObjectForKey: uid];
477 }
478
479 - (NSArray *) aclsForUser: (NSString *) uid
480           forObjectAtPath: (NSArray *) objectPathArray
481 {
482   NSArray *acls;
483   NSString *objectPath;
484   NSDictionary *aclsForObject;
485
486   objectPath = [objectPathArray componentsJoinedByString: @"/"];
487   aclsForObject = [aclCache objectForKey: objectPath];
488   if (aclsForObject)
489     acls = [aclsForObject objectForKey: uid];
490   else
491     acls = nil;
492   if (!acls)
493     {
494       acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
495       [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
496     }
497
498   if (!([acls count] || [uid isEqualToString: defaultUserID]))
499     acls = [self aclsForUser: defaultUserID
500                  forObjectAtPath: objectPathArray];
501
502   return acls;
503 }
504
505 - (void) removeAclsForUsers: (NSArray *) users
506             forObjectAtPath: (NSArray *) objectPathArray
507 {
508   EOQualifier *qualifier;
509   NSString *uids, *qs, *objectPath;
510   NSMutableDictionary *aclsForObject;
511
512   if ([users count] > 0)
513     {
514       objectPath = [objectPathArray componentsJoinedByString: @"/"];
515       aclsForObject = [aclCache objectForKey: objectPath];
516       if (aclsForObject)
517         [aclsForObject removeObjectsForKeys: users];
518       uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
519       qs = [NSString
520              stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
521              objectPath, uids];
522       qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
523       [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
524     }
525 }
526
527 - (void) _commitRoles: (NSArray *) roles
528                forUID: (NSString *) uid
529             forObject: (NSString *) objectPath
530 {
531   EOAdaptorChannel *channel;
532   GCSFolder *folder;
533   NSEnumerator *userRoles;
534   NSString *SQL, *currentRole;
535
536   folder = [self ocsFolder];
537   channel = [folder acquireAclChannel];
538   userRoles = [roles objectEnumerator];
539   currentRole = [userRoles nextObject];
540   while (currentRole)
541     {
542       SQL = [NSString stringWithFormat: @"INSERT INTO %@"
543                       @" (c_object, c_uid, c_role)"
544                       @" VALUES ('/%@', '%@', '%@')",
545                       [folder aclTableName],
546                       objectPath, uid, currentRole];
547       [channel evaluateExpressionX: SQL];
548       currentRole = [userRoles nextObject];
549     }
550
551   [folder releaseChannel: channel];
552 }
553
554 - (void) setRoles: (NSArray *) roles
555           forUser: (NSString *) uid
556   forObjectAtPath: (NSArray *) objectPathArray
557 {
558   NSString *objectPath;
559   NSMutableArray *newRoles;
560
561   [self removeAclsForUsers: [NSArray arrayWithObject: uid]
562         forObjectAtPath: objectPathArray];
563
564   newRoles = [NSMutableArray arrayWithArray: roles];
565   [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
566   [newRoles removeObject: SOGoRole_None];
567   objectPath = [objectPathArray componentsJoinedByString: @"/"];
568   [self _cacheRoles: newRoles forUser: uid
569         forObjectAtPath: objectPath];
570   if (![newRoles count])
571     [newRoles addObject: SOGoRole_None];
572
573   [self _commitRoles: newRoles forUID: uid forObject: objectPath];
574 }
575
576 /* acls */
577 - (NSArray *) aclUsers
578 {
579   return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
580 }
581
582 - (NSArray *) aclsForUser: (NSString *) uid
583 {
584   NSMutableArray *acls;
585   NSArray *ownAcls, *containerAcls;
586
587   acls = [NSMutableArray array];
588   ownAcls = [self aclsForUser: uid
589                   forObjectAtPath: [self pathArrayToSOGoObject]];
590   [acls addObjectsFromArray: ownAcls];
591   if ([container respondsToSelector: @selector (aclsForUser:)])
592     {
593       containerAcls = [container aclsForUser: uid];
594       if ([containerAcls count] > 0)
595         {
596           if ([containerAcls containsObject: SOGoRole_ObjectReader])
597             [acls addObject: SOGoRole_ObjectViewer];
598 #warning this should be checked
599           if ([containerAcls containsObject: SOGoRole_ObjectEraser])
600             [acls addObject: SOGoRole_ObjectEraser];
601         }
602     }
603
604   return acls;
605 }
606
607 - (void) setRoles: (NSArray *) roles
608           forUser: (NSString *) uid
609 {
610   return [self setRoles: roles
611                forUser: uid
612                forObjectAtPath: [self pathArrayToSOGoObject]];
613 }
614
615 - (void) removeAclsForUsers: (NSArray *) users
616 {
617   return [self removeAclsForUsers: users
618                forObjectAtPath: [self pathArrayToSOGoObject]];
619 }
620
621 - (NSString *) defaultUserID
622 {
623   return defaultUserID;
624 }
625
626 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
627 {
628   return [[self soURL] absoluteString];
629 }
630
631 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
632 {
633   return [[self davURL] absoluteString];
634 }
635
636 - (id) lookupName: (NSString *) lookupName
637         inContext: (id) localContext
638           acquire: (BOOL) acquire
639 {
640   id obj;
641   NSArray *davNamespaces;
642   NSDictionary *davInvocation;
643   NSString *objcMethod;
644
645   obj = [super lookupName: lookupName inContext: localContext
646                acquire: acquire];
647   if (!obj)
648     {
649       davNamespaces = [self davNamespaces];
650       if ([davNamespaces count] > 0)
651         {
652           davInvocation = [lookupName asDavInvocation];
653           if (davInvocation
654               && [davNamespaces
655                    containsObject: [davInvocation objectForKey: @"ns"]])
656             {
657               objcMethod = [[davInvocation objectForKey: @"method"]
658                              davMethodToObjC];
659               obj = [[SoSelectorInvocation alloc]
660                       initWithSelectorNamed:
661                         [NSString stringWithFormat: @"%@:", objcMethod]
662                       addContextParameter: YES];
663               [obj autorelease];
664             }
665         }
666     }
667
668   return obj;
669 }
670
671 /* WebDAV */
672
673 - (NSArray *) davNamespaces
674 {
675   return nil;
676 }
677
678 - (BOOL) davIsCollection
679 {
680   return [self isFolderish];
681 }
682
683 /* folder type */
684
685 - (NSString *) outlookFolderClass
686 {
687   [self subclassResponsibility: _cmd];
688
689   return nil;
690 }
691
692 /* description */
693
694 - (void) appendAttributesToDescription: (NSMutableString *) _ms
695 {
696   [super appendAttributesToDescription:_ms];
697   
698   [_ms appendFormat:@" ocs=%@", [self ocsPath]];
699 }
700
701 - (NSString *) loggingPrefix
702 {
703   return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
704                    self, NSStringFromClass([self class]),
705                    [self nameInContainer]];
706 }
707
708 @end /* SOGoFolder */