2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
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>
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>
45 #import "NSArray+Utilities.h"
46 #import "NSString+Utilities.h"
48 #import "SOGoContentObject.h"
49 #import "SOGoPermissions.h"
52 #import "SOGoFolder.h"
54 static NSString *defaultUserID = @"<default>";
56 @implementation SOGoFolder
60 return [super version] + 0 /* v0 */;
65 NSAssert2([super version] == 0,
66 @"invalid superclass (%@) version %i !",
67 NSStringFromClass([self superclass]), [super version]);
70 + (id) folderWithSubscriptionReference: (NSString *) reference
71 inContainer: (id) aContainer
74 NSArray *elements, *pathElements;
75 NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName;
77 elements = [reference componentsSeparatedByString: @":"];
78 owner = [elements objectAtIndex: 0];
79 objectPath = [elements objectAtIndex: 1];
80 pathElements = [objectPath componentsSeparatedByString: @"/"];
81 if ([pathElements count] > 1)
82 ocsName = [pathElements objectAtIndex: 1];
84 ocsName = @"personal";
86 ocsPath = [NSString stringWithFormat: @"/Users/%@/%@/%@",
87 owner, [pathElements objectAtIndex: 0], ocsName];
88 folderName = [NSString stringWithFormat: @"%@_%@", owner, ocsName];
89 newFolder = [[self alloc] initWithName: folderName
90 inContainer: aContainer];
91 [newFolder setOCSPath: ocsPath];
92 [newFolder setOwner: owner];
99 if ((self = [super init]))
104 aclCache = [NSMutableDictionary new];
115 [displayName release];
126 - (void) setOCSPath: (NSString *) _path
128 if (![ocsPath isEqualToString:_path])
131 [self warnWithFormat: @"GCS path is already set! '%@'", _path];
132 ASSIGN (ocsPath, _path);
136 - (NSString *) ocsPath
141 - (GCSFolderManager *) folderManager
143 static GCSFolderManager *folderManager = nil;
146 folderManager = [GCSFolderManager defaultFolderManager];
148 return folderManager;
151 - (GCSFolder *) ocsFolderForPath: (NSString *) _path
153 return [[self folderManager] folderAtPath: _path];
156 - (BOOL) folderIsMandatory
158 return [nameInContainer isEqualToString: @"personal"];
161 - (void) _setDisplayNameFromRow: (NSDictionary *) row
163 NSString *currentLogin, *ownerLogin;
164 NSDictionary *ownerIdentity;
167 = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]];
168 currentLogin = [[context activeUser] login];
169 ownerLogin = [self ownerInContext: context];
170 if (![currentLogin isEqualToString: ownerLogin])
172 ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil]
174 [displayName appendFormat: @" (%@ <%@>)",
175 [ownerIdentity objectForKey: @"fullName"],
176 [ownerIdentity objectForKey: @"email"]];
178 [displayName retain];
181 - (void) _fetchDisplayName
183 GCSChannelManager *cm;
184 EOAdaptorChannel *fc;
185 NSURL *folderLocation;
190 cm = [GCSChannelManager defaultChannelManager];
192 = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
193 fc = [cm acquireOpenChannelForURL: folderLocation];
197 = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
198 @" WHERE c_path = '%@'"),
199 [folderLocation gcsTableName], ocsPath];
200 [fc evaluateExpressionX: sql];
201 attrs = [fc describeResults: NO];
202 row = [fc fetchAttributes: attrs withZone: NULL];
204 [self _setDisplayNameFromRow: row];
206 [cm releaseChannel: fc];
210 - (void) setDisplayName: (NSString *) newDisplayName
212 ASSIGN (displayName, newDisplayName);
215 - (NSString *) displayName
218 [self _fetchDisplayName];
223 - (NSString *) davDisplayName
225 return [self displayName];
228 - (GCSFolder *) ocsFolder
235 ocsFolder = [self ocsFolderForPath: [self ocsPath]];
236 userLogin = [[context activeUser] login];
238 && [userLogin isEqualToString: [self ownerInContext: context]]
239 && [self folderIsMandatory]
241 ocsFolder = [self ocsFolderForPath: [self ocsPath]];
245 if ([ocsFolder isNotNull])
253 - (NSString *) folderType
263 result = [[self folderManager] createFolderOfType: [self folderType]
264 withName: displayName
267 return (result == nil);
270 - (NSException *) delete
274 if ([nameInContainer isEqualToString: @"personal"])
275 error = [NSException exceptionWithHTTPStatus: 403
276 reason: @"folder 'personal' cannot be deleted"];
278 error = [[self folderManager] deleteFolderAtPath: ocsPath];
283 - (void) renameTo: (NSString *) newName
285 GCSChannelManager *cm;
286 EOAdaptorChannel *fc;
287 NSURL *folderLocation;
290 [displayName release];
293 cm = [GCSChannelManager defaultChannelManager];
295 = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
296 fc = [cm acquireOpenChannelForURL: folderLocation];
300 = [NSString stringWithFormat: (@"UPDATE %@ SET c_foldername = '%@'"
301 @" WHERE c_path = '%@'"),
302 [folderLocation gcsTableName], newName, ocsPath];
303 [fc evaluateExpressionX: sql];
304 [cm releaseChannel: fc];
305 // sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'",
306 // uidColumnName, [self uid]];
310 - (NSArray *) fetchContentObjectNames
312 NSArray *fields, *records;
314 fields = [NSArray arrayWithObject: @"c_name"];
315 records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
316 if (![records isNotNull])
318 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
321 if ([records isKindOfClass: [NSException class]])
323 return [records objectsForKey: @"c_name"];
326 - (BOOL) nameExistsInFolder: (NSString *) objectName
328 NSArray *fields, *records;
329 EOQualifier *qualifier;
332 = [EOQualifier qualifierWithQualifierFormat:
333 [NSString stringWithFormat: @"c_name='%@'", objectName]];
335 fields = [NSArray arrayWithObject: @"c_name"];
336 records = [[self ocsFolder] fetchFields: fields
337 matchingQualifier: qualifier];
339 && ![records isKindOfClass:[NSException class]]
340 && [records count] > 0);
343 - (void) deleteEntriesWithIds: (NSArray *) ids
345 unsigned int count, max;
347 SOGoContentObject *deleteObject;
350 for (count = 0; count < max; count++)
352 currentID = [ids objectAtIndex: count];
353 deleteObject = [self lookupName: currentID
354 inContext: context acquire: NO];
355 [deleteObject delete];
359 - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
363 files = [[self ocsFolder] fetchContentsOfAllFiles];
364 if (![files isNotNull])
366 [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
369 if ([files isKindOfClass:[NSException class]])
376 - (NSString *) defaultFilenameExtension
379 Override to add an extension to a filename
381 Note: be careful with that, needs to be consistent with object lookup!
386 - (NSArray *) davResourceType
388 NSArray *rType, *groupDavCollection;
390 if ([self respondsToSelector: @selector (groupDavResourceType)])
393 = [NSArray arrayWithObjects: [self groupDavResourceType],
394 XMLNS_GROUPDAV, nil];
395 rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
399 rType = [NSArray arrayWithObject: @"collection"];
404 - (NSString *) davContentType
406 return @"httpd/unix-directory";
409 - (NSArray *) toOneRelationshipKeys
411 /* toOneRelationshipKeys are the 'files' contained in a folder */
414 NSString *name, *ext;
418 names = [self fetchContentObjectNames];
419 count = [names count];
420 ext = [self defaultFilenameExtension];
421 if (count && [ext length] > 0)
423 ma = [NSMutableArray arrayWithCapacity: count];
424 for (i = 0; i < count; i++)
426 name = [names objectAtIndex: i];
427 r = [name rangeOfString: @"."];
429 name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
430 [ma addObject: name];
439 /* acls as a container */
441 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
443 EOQualifier *qualifier;
447 qs = [NSString stringWithFormat: @"c_object = '/%@'",
448 [objectPathArray componentsJoinedByString: @"/"]];
449 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
450 records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
452 return [records valueForKey: @"c_uid"];
455 - (NSArray *) _fetchAclsForUser: (NSString *) uid
456 forObjectAtPath: (NSString *) objectPath
458 EOQualifier *qualifier;
460 NSMutableArray *acls;
463 qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
465 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
466 records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
468 acls = [NSMutableArray array];
469 if ([records count] > 0)
471 [acls addObject: SOGoRole_AuthorizedSubscriber];
472 [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
478 - (void) _cacheRoles: (NSArray *) roles
479 forUser: (NSString *) uid
480 forObjectAtPath: (NSString *) objectPath
482 NSMutableDictionary *aclsForObject;
484 aclsForObject = [aclCache objectForKey: objectPath];
487 aclsForObject = [NSMutableDictionary dictionary];
488 [aclCache setObject: aclsForObject
492 [aclsForObject setObject: roles forKey: uid];
494 [aclsForObject removeObjectForKey: uid];
497 - (NSArray *) aclsForUser: (NSString *) uid
498 forObjectAtPath: (NSArray *) objectPathArray
501 NSString *objectPath;
502 NSDictionary *aclsForObject;
504 objectPath = [objectPathArray componentsJoinedByString: @"/"];
505 aclsForObject = [aclCache objectForKey: objectPath];
507 acls = [aclsForObject objectForKey: uid];
512 acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
513 [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
516 if (!([acls count] || [uid isEqualToString: defaultUserID]))
517 acls = [self aclsForUser: defaultUserID
518 forObjectAtPath: objectPathArray];
523 - (void) removeAclsForUsers: (NSArray *) users
524 forObjectAtPath: (NSArray *) objectPathArray
526 EOQualifier *qualifier;
527 NSString *uids, *qs, *objectPath;
528 NSMutableDictionary *aclsForObject;
530 if ([users count] > 0)
532 objectPath = [objectPathArray componentsJoinedByString: @"/"];
533 aclsForObject = [aclCache objectForKey: objectPath];
535 [aclsForObject removeObjectsForKeys: users];
536 uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
538 stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
540 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
541 [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
545 - (void) _commitRoles: (NSArray *) roles
546 forUID: (NSString *) uid
547 forObject: (NSString *) objectPath
549 EOAdaptorChannel *channel;
551 NSEnumerator *userRoles;
552 NSString *SQL, *currentRole;
554 folder = [self ocsFolder];
555 channel = [folder acquireAclChannel];
556 userRoles = [roles objectEnumerator];
557 currentRole = [userRoles nextObject];
560 SQL = [NSString stringWithFormat: @"INSERT INTO %@"
561 @" (c_object, c_uid, c_role)"
562 @" VALUES ('/%@', '%@', '%@')",
563 [folder aclTableName],
564 objectPath, uid, currentRole];
565 [channel evaluateExpressionX: SQL];
566 currentRole = [userRoles nextObject];
569 [folder releaseChannel: channel];
572 - (void) setRoles: (NSArray *) roles
573 forUser: (NSString *) uid
574 forObjectAtPath: (NSArray *) objectPathArray
576 NSString *objectPath;
577 NSMutableArray *newRoles;
579 [self removeAclsForUsers: [NSArray arrayWithObject: uid]
580 forObjectAtPath: objectPathArray];
582 newRoles = [NSMutableArray arrayWithArray: roles];
583 [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
584 [newRoles removeObject: SOGoRole_None];
585 objectPath = [objectPathArray componentsJoinedByString: @"/"];
586 [self _cacheRoles: newRoles forUser: uid
587 forObjectAtPath: objectPath];
588 if (![newRoles count])
589 [newRoles addObject: SOGoRole_None];
591 [self _commitRoles: newRoles forUID: uid forObject: objectPath];
595 - (NSArray *) aclUsers
597 return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
600 - (NSArray *) aclsForUser: (NSString *) uid
602 NSMutableArray *acls;
603 NSArray *ownAcls, *containerAcls;
605 acls = [NSMutableArray array];
606 ownAcls = [self aclsForUser: uid
607 forObjectAtPath: [self pathArrayToSOGoObject]];
608 [acls addObjectsFromArray: ownAcls];
609 if ([container respondsToSelector: @selector (aclsForUser:)])
611 containerAcls = [container aclsForUser: uid];
612 if ([containerAcls count] > 0)
614 if ([containerAcls containsObject: SOGoRole_ObjectReader])
615 [acls addObject: SOGoRole_ObjectViewer];
616 #warning this should be checked
617 if ([containerAcls containsObject: SOGoRole_ObjectEraser])
618 [acls addObject: SOGoRole_ObjectEraser];
625 - (void) setRoles: (NSArray *) roles
626 forUser: (NSString *) uid
628 return [self setRoles: roles
630 forObjectAtPath: [self pathArrayToSOGoObject]];
633 - (void) removeAclsForUsers: (NSArray *) users
635 return [self removeAclsForUsers: users
636 forObjectAtPath: [self pathArrayToSOGoObject]];
639 - (NSString *) defaultUserID
641 return defaultUserID;
644 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
646 return [[self soURL] absoluteString];
649 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
651 return [[self davURL] absoluteString];
654 - (id) lookupName: (NSString *) lookupName
655 inContext: (id) localContext
656 acquire: (BOOL) acquire
659 NSArray *davNamespaces;
660 NSDictionary *davInvocation;
661 NSString *objcMethod;
663 obj = [super lookupName: lookupName inContext: localContext
667 davNamespaces = [self davNamespaces];
668 if ([davNamespaces count] > 0)
670 davInvocation = [lookupName asDavInvocation];
673 containsObject: [davInvocation objectForKey: @"ns"]])
675 objcMethod = [[davInvocation objectForKey: @"method"]
677 obj = [[SoSelectorInvocation alloc]
678 initWithSelectorNamed:
679 [NSString stringWithFormat: @"%@:", objcMethod]
680 addContextParameter: YES];
689 - (NSComparisonResult) _compareByOrigin: (SOGoFolder *) otherFolder
691 NSArray *thisElements, *otherElements;
692 unsigned thisCount, otherCount;
693 NSComparisonResult comparison;
695 thisElements = [nameInContainer componentsSeparatedByString: @"_"];
696 otherElements = [[otherFolder nameInContainer]
697 componentsSeparatedByString: @"_"];
698 thisCount = [thisElements count];
699 otherCount = [otherElements count];
700 if (thisCount == otherCount)
703 comparison = NSOrderedSame;
705 comparison = [[thisElements objectAtIndex: 0]
706 compare: [otherElements objectAtIndex: 0]];
710 if (thisCount > otherCount)
711 comparison = NSOrderedDescending;
713 comparison = NSOrderedAscending;
719 - (NSComparisonResult) _compareByNameInContainer: (SOGoFolder *) otherFolder
722 NSComparisonResult comparison;
724 otherName = [otherFolder nameInContainer];
725 if ([nameInContainer hasSuffix: @"personal"])
727 if ([otherName hasSuffix: @"personal"])
728 comparison = [nameInContainer compare: otherName];
730 comparison = NSOrderedAscending;
734 if ([otherName hasSuffix: @"personal"])
735 comparison = NSOrderedDescending;
737 comparison = NSOrderedSame;
743 - (NSComparisonResult) compare: (SOGoFolder *) otherFolder
745 NSComparisonResult comparison;
747 comparison = [self _compareByOrigin: otherFolder];
748 if (comparison == NSOrderedSame)
750 comparison = [self _compareByNameInContainer: otherFolder];
751 if (comparison == NSOrderedSame)
753 = [[self displayName]
754 localizedCaseInsensitiveCompare: [otherFolder displayName]];
762 - (NSArray *) davNamespaces
767 - (BOOL) davIsCollection
769 return [self isFolderish];
774 - (NSString *) outlookFolderClass
776 [self subclassResponsibility: _cmd];
783 - (void) appendAttributesToDescription: (NSMutableString *) _ms
785 [super appendAttributesToDescription:_ms];
787 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
790 - (NSString *) loggingPrefix
792 return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
793 self, NSStringFromClass([self class]),
794 [self nameInContainer]];
797 @end /* SOGoFolder */