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/WOContext+SoObjects.h>
33 #import <NGObjWeb/WOApplication.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 #import <UI/SOGoUI/SOGoFolderAdvisory.h>
46 #import "NSArray+Utilities.h"
47 #import "NSString+Utilities.h"
49 #import "SOGoContentObject.h"
50 #import "SOGoPermissions.h"
53 #import "SOGoGCSFolder.h"
55 static NSString *defaultUserID = @"<default>";
57 @implementation SOGoGCSFolder
59 + (id) folderWithSubscriptionReference: (NSString *) reference
60 inContainer: (id) aContainer
63 NSArray *elements, *pathElements;
64 NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName;
66 elements = [reference componentsSeparatedByString: @":"];
67 owner = [elements objectAtIndex: 0];
68 objectPath = [elements objectAtIndex: 1];
69 pathElements = [objectPath componentsSeparatedByString: @"/"];
70 if ([pathElements count] > 1)
71 ocsName = [pathElements objectAtIndex: 1];
73 ocsName = @"personal";
75 ocsPath = [NSString stringWithFormat: @"/Users/%@/%@/%@",
76 owner, [pathElements objectAtIndex: 0], ocsName];
77 folderName = [NSString stringWithFormat: @"%@_%@", owner, ocsName];
78 newFolder = [[self alloc] initWithName: folderName
79 inContainer: aContainer];
80 [newFolder setOCSPath: ocsPath];
81 [newFolder setOwner: owner];
88 if ((self = [super init]))
93 aclCache = [NSMutableDictionary new];
104 [displayName release];
110 - (void) _setDisplayNameFromRow: (NSDictionary *) row
112 NSString *currentLogin, *ownerLogin;
113 NSDictionary *ownerIdentity;
116 = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]];
117 currentLogin = [[context activeUser] login];
118 ownerLogin = [self ownerInContext: context];
119 if (![currentLogin isEqualToString: ownerLogin])
121 ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil]
123 [displayName appendFormat: @" (%@ <%@>)",
124 [ownerIdentity objectForKey: @"fullName"],
125 [ownerIdentity objectForKey: @"email"]];
127 [displayName retain];
130 - (void) _fetchDisplayName
132 GCSChannelManager *cm;
133 EOAdaptorChannel *fc;
134 NSURL *folderLocation;
139 cm = [GCSChannelManager defaultChannelManager];
141 = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
142 fc = [cm acquireOpenChannelForURL: folderLocation];
146 = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
147 @" WHERE c_path = '%@'"),
148 [folderLocation gcsTableName], ocsPath];
149 [fc evaluateExpressionX: sql];
150 attrs = [fc describeResults: NO];
151 row = [fc fetchAttributes: attrs withZone: NULL];
153 [self _setDisplayNameFromRow: row];
155 [cm releaseChannel: fc];
159 - (NSString *) displayName
162 [self _fetchDisplayName];
167 - (void) setOCSPath: (NSString *) _path
169 if (![ocsPath isEqualToString:_path])
172 [self warnWithFormat: @"GCS path is already set! '%@'", _path];
173 ASSIGN (ocsPath, _path);
177 - (NSString *) ocsPath
182 - (GCSFolderManager *) folderManager
184 static GCSFolderManager *folderManager = nil;
187 folderManager = [GCSFolderManager defaultFolderManager];
189 return folderManager;
192 - (GCSFolder *) ocsFolderForPath: (NSString *) _path
194 return [[self folderManager] folderAtPath: _path];
197 - (BOOL) folderIsMandatory
199 return [nameInContainer isEqualToString: @"personal"];
202 - (NSString *) davDisplayName
204 return [self displayName];
207 - (GCSFolder *) ocsFolder
214 ocsFolder = [self ocsFolderForPath: [self ocsPath]];
215 userLogin = [[context activeUser] login];
217 && [userLogin isEqualToString: [self ownerInContext: context]]
218 && [self folderIsMandatory]
220 ocsFolder = [self ocsFolderForPath: [self ocsPath]];
224 if ([ocsFolder isNotNull])
232 - (void) sendFolderAdvisoryTemplate: (NSString *) template
236 SOGoFolderAdvisory *page;
238 user = [context activeUser];
239 pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory",
240 [user language], template];
242 page = [[WOApplication application] pageWithName: pageName
244 [page setFolderObject: self];
245 [page setRecipientUID: [user login]];
250 // if (!result) [self sendFolderAdvisoryTemplate: @"Addition"];
256 result = [[self folderManager] createFolderOfType: [self folderType]
257 withName: displayName
260 return (result == nil);
263 - (NSException *) delete
267 // We just fetch our displayName since our table will use it!
270 if ([nameInContainer isEqualToString: @"personal"])
271 error = [NSException exceptionWithHTTPStatus: 403
272 reason: @"folder 'personal' cannot be deleted"];
274 error = [[self folderManager] deleteFolderAtPath: ocsPath];
279 // if (!error) [self sendFolderAdvisoryTemplate: @"Removal"];
281 - (void) renameTo: (NSString *) newName
283 GCSChannelManager *cm;
284 EOAdaptorChannel *fc;
285 NSURL *folderLocation;
288 [displayName release];
291 cm = [GCSChannelManager defaultChannelManager];
293 = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
294 fc = [cm acquireOpenChannelForURL: folderLocation];
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]];
308 - (NSArray *) fetchContentObjectNames
310 NSArray *fields, *records;
312 fields = [NSArray arrayWithObject: @"c_name"];
313 records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
314 if (![records isNotNull])
316 [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
319 if ([records isKindOfClass: [NSException class]])
321 return [records objectsForKey: @"c_name"];
324 - (BOOL) nameExistsInFolder: (NSString *) objectName
326 NSArray *fields, *records;
327 EOQualifier *qualifier;
330 = [EOQualifier qualifierWithQualifierFormat:
331 [NSString stringWithFormat: @"c_name='%@'", objectName]];
333 fields = [NSArray arrayWithObject: @"c_name"];
334 records = [[self ocsFolder] fetchFields: fields
335 matchingQualifier: qualifier];
337 && ![records isKindOfClass:[NSException class]]
338 && [records count] > 0);
341 - (void) deleteEntriesWithIds: (NSArray *) ids
343 unsigned int count, max;
345 SOGoContentObject *deleteObject;
348 for (count = 0; count < max; count++)
350 currentID = [ids objectAtIndex: count];
351 deleteObject = [self lookupName: currentID
352 inContext: context acquire: NO];
353 if (![deleteObject isKindOfClass: [NSException class]])
354 [deleteObject delete];
358 - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
362 files = [[self ocsFolder] fetchContentsOfAllFiles];
363 if (![files isNotNull])
365 [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
368 if ([files isKindOfClass:[NSException class]])
375 - (NSString *) defaultFilenameExtension
378 Override to add an extension to a filename
380 Note: be careful with that, needs to be consistent with object lookup!
385 - (NSArray *) davResourceType
387 NSArray *rType, *groupDavCollection;
389 if ([self respondsToSelector: @selector (groupDavResourceType)])
392 = [NSArray arrayWithObjects: [self groupDavResourceType],
393 XMLNS_GROUPDAV, nil];
394 rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
398 rType = [NSArray arrayWithObject: @"collection"];
403 - (NSArray *) toOneRelationshipKeys
405 /* toOneRelationshipKeys are the 'files' contained in a folder */
408 NSString *name, *ext;
412 names = [self fetchContentObjectNames];
413 count = [names count];
414 ext = [self defaultFilenameExtension];
415 if (count && [ext length] > 0)
417 ma = [NSMutableArray arrayWithCapacity: count];
418 for (i = 0; i < count; i++)
420 name = [names objectAtIndex: i];
421 r = [name rangeOfString: @"."];
423 name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
424 [ma addObject: name];
433 /* acls as a container */
435 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
437 EOQualifier *qualifier;
441 qs = [NSString stringWithFormat: @"c_object = '/%@'",
442 [objectPathArray componentsJoinedByString: @"/"]];
443 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
444 records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
446 return [records valueForKey: @"c_uid"];
449 - (NSArray *) _fetchAclsForUser: (NSString *) uid
450 forObjectAtPath: (NSString *) objectPath
452 EOQualifier *qualifier;
454 NSMutableArray *acls;
457 qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
459 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
460 records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
462 acls = [NSMutableArray array];
463 if ([records count] > 0)
465 [acls addObject: SOGoRole_AuthorizedSubscriber];
466 [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
472 - (void) _cacheRoles: (NSArray *) roles
473 forUser: (NSString *) uid
474 forObjectAtPath: (NSString *) objectPath
476 NSMutableDictionary *aclsForObject;
478 aclsForObject = [aclCache objectForKey: objectPath];
481 aclsForObject = [NSMutableDictionary dictionary];
482 [aclCache setObject: aclsForObject
486 [aclsForObject setObject: roles forKey: uid];
488 [aclsForObject removeObjectForKey: uid];
491 - (NSArray *) aclsForUser: (NSString *) uid
492 forObjectAtPath: (NSArray *) objectPathArray
495 NSString *objectPath;
496 NSDictionary *aclsForObject;
498 objectPath = [objectPathArray componentsJoinedByString: @"/"];
499 aclsForObject = [aclCache objectForKey: objectPath];
501 acls = [aclsForObject objectForKey: uid];
506 acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
507 [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
510 if (!([acls count] || [uid isEqualToString: defaultUserID]))
511 acls = [self aclsForUser: defaultUserID
512 forObjectAtPath: objectPathArray];
517 - (void) removeAclsForUsers: (NSArray *) users
518 forObjectAtPath: (NSArray *) objectPathArray
520 EOQualifier *qualifier;
521 NSString *uids, *qs, *objectPath;
522 NSMutableDictionary *aclsForObject;
524 if ([users count] > 0)
526 objectPath = [objectPathArray componentsJoinedByString: @"/"];
527 aclsForObject = [aclCache objectForKey: objectPath];
529 [aclsForObject removeObjectsForKeys: users];
530 uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
532 stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
534 qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
535 [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
539 - (void) _commitRoles: (NSArray *) roles
540 forUID: (NSString *) uid
541 forObject: (NSString *) objectPath
543 EOAdaptorChannel *channel;
545 NSEnumerator *userRoles;
546 NSString *SQL, *currentRole;
548 folder = [self ocsFolder];
549 channel = [folder acquireAclChannel];
550 userRoles = [roles objectEnumerator];
551 currentRole = [userRoles nextObject];
554 SQL = [NSString stringWithFormat: @"INSERT INTO %@"
555 @" (c_object, c_uid, c_role)"
556 @" VALUES ('/%@', '%@', '%@')",
557 [folder aclTableName],
558 objectPath, uid, currentRole];
559 [channel evaluateExpressionX: SQL];
560 currentRole = [userRoles nextObject];
563 [folder releaseChannel: channel];
566 - (void) setRoles: (NSArray *) roles
567 forUser: (NSString *) uid
568 forObjectAtPath: (NSArray *) objectPathArray
570 NSString *objectPath;
571 NSMutableArray *newRoles;
573 [self removeAclsForUsers: [NSArray arrayWithObject: uid]
574 forObjectAtPath: objectPathArray];
576 newRoles = [NSMutableArray arrayWithArray: roles];
577 [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
578 [newRoles removeObject: SOGoRole_None];
579 objectPath = [objectPathArray componentsJoinedByString: @"/"];
580 [self _cacheRoles: newRoles forUser: uid
581 forObjectAtPath: objectPath];
582 if (![newRoles count])
583 [newRoles addObject: SOGoRole_None];
585 [self _commitRoles: newRoles forUID: uid forObject: objectPath];
589 - (NSArray *) aclUsers
591 return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
594 - (NSArray *) aclsForUser: (NSString *) uid
596 NSMutableArray *acls;
597 NSArray *ownAcls, *containerAcls;
599 acls = [NSMutableArray array];
600 ownAcls = [self aclsForUser: uid
601 forObjectAtPath: [self pathArrayToSOGoObject]];
602 [acls addObjectsFromArray: ownAcls];
603 if ([container respondsToSelector: @selector (aclsForUser:)])
605 containerAcls = [container aclsForUser: uid];
606 if ([containerAcls count] > 0)
608 if ([containerAcls containsObject: SOGoRole_ObjectReader])
609 [acls addObject: SOGoRole_ObjectViewer];
610 #warning this should be checked
611 if ([containerAcls containsObject: SOGoRole_ObjectEraser])
612 [acls addObject: SOGoRole_ObjectEraser];
619 - (void) setRoles: (NSArray *) roles
620 forUser: (NSString *) uid
622 return [self setRoles: roles
624 forObjectAtPath: [self pathArrayToSOGoObject]];
627 - (void) removeAclsForUsers: (NSArray *) users
629 return [self removeAclsForUsers: users
630 forObjectAtPath: [self pathArrayToSOGoObject]];
633 - (NSString *) defaultUserID
635 return defaultUserID;
638 - (NSComparisonResult) _compareByOrigin: (SOGoFolder *) otherFolder
640 NSArray *thisElements, *otherElements;
641 unsigned thisCount, otherCount;
642 NSComparisonResult comparison;
644 thisElements = [nameInContainer componentsSeparatedByString: @"_"];
645 otherElements = [[otherFolder nameInContainer]
646 componentsSeparatedByString: @"_"];
647 thisCount = [thisElements count];
648 otherCount = [otherElements count];
649 if (thisCount == otherCount)
652 comparison = NSOrderedSame;
654 comparison = [[thisElements objectAtIndex: 0]
655 compare: [otherElements objectAtIndex: 0]];
659 if (thisCount > otherCount)
660 comparison = NSOrderedDescending;
662 comparison = NSOrderedAscending;
668 - (NSComparisonResult) _compareByNameInContainer: (SOGoFolder *) otherFolder
671 NSComparisonResult comparison;
673 otherName = [otherFolder nameInContainer];
674 if ([nameInContainer hasSuffix: @"personal"])
676 if ([otherName hasSuffix: @"personal"])
677 comparison = [nameInContainer compare: otherName];
679 comparison = NSOrderedAscending;
683 if ([otherName hasSuffix: @"personal"])
684 comparison = NSOrderedDescending;
686 comparison = NSOrderedSame;
692 - (NSComparisonResult) compare: (SOGoGCSFolder *) otherFolder
694 NSComparisonResult comparison;
696 comparison = [self _compareByOrigin: otherFolder];
697 if (comparison == NSOrderedSame)
699 comparison = [self _compareByNameInContainer: otherFolder];
700 if (comparison == NSOrderedSame)
702 = [[self displayName]
703 localizedCaseInsensitiveCompare: [otherFolder displayName]];
711 - (void) appendAttributesToDescription: (NSMutableString *) _ms
713 [super appendAttributesToDescription:_ms];
715 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
718 @end /* SOGoFolder */