]> err.no Git - scalable-opengroupware.org/commitdiff
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1244 d1b88da0-ebda-0310...
authorwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Wed, 7 Nov 2007 18:28:22 +0000 (18:28 +0000)
committerwolfgang <wolfgang@d1b88da0-ebda-0310-925b-ed51d893ca5b>
Wed, 7 Nov 2007 18:28:22 +0000 (18:28 +0000)
SoObjects/SOGo/SOGoGCSFolder.h [new file with mode: 0644]
SoObjects/SOGo/SOGoGCSFolder.m [new file with mode: 0644]

diff --git a/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h
new file mode 100644 (file)
index 0000000..9e33145
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#ifndef __SOGo_SOGoGCSFolder_H__
+#define __SOGo_SOGoGCSFolder_H__
+
+#import "SOGoFolder.h"
+
+@class NSArray;
+@class NSDictionary;
+@class NSMutableDictionary;
+@class NSString;
+
+@class GCSFolder;
+
+/*
+  SOGoGCSFolder
+  
+  A common superclass for folders stored in GCS. Already deals with all GCS
+  folder specific things.
+  
+  Important: folders should NOT retain the context! Otherwise you might get
+             cyclic references.
+*/
+
+@interface SOGoGCSFolder : SOGoFolder
+{
+  NSString *ocsPath;
+  GCSFolder *ocsFolder;
+  NSMutableDictionary *aclCache;
+}
+
++ (id) folderWithSubscriptionReference: (NSString *) reference
+                          inContainer: (id) aContainer;
+
+/* accessors */
+
+- (void) setOCSPath: (NSString *)_Path;
+- (NSString *) ocsPath;
+
+- (GCSFolder *) ocsFolderForPath: (NSString *)_path;
+- (GCSFolder *) ocsFolder;
+
+/* lower level fetches */
+- (BOOL) nameExistsInFolder: (NSString *) objectName;
+
+- (void) deleteEntriesWithIds: (NSArray *) ids;
+
+- (NSArray *) fetchContentObjectNames;
+- (NSDictionary *) fetchContentStringsAndNamesOfAllObjects;
+
+/* folder type */
+
+- (BOOL) folderIsMandatory;
+
+/* sorting */
+- (NSComparisonResult) compare: (SOGoGCSFolder *) otherFolder;
+
+- (BOOL) create;
+- (NSException *) delete;
+- (void) renameTo: (NSString *) newName;
+
+/* acls as a container */
+- (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
+- (NSArray *) aclsForUser: (NSString *) uid
+          forObjectAtPath: (NSArray *) objectPathArray;
+- (void) setRoles: (NSArray *) roles
+          forUser: (NSString *) uid
+  forObjectAtPath: (NSArray *) objectPathArray;
+- (void) removeAclsForUsers: (NSArray *) users
+            forObjectAtPath: (NSArray *) objectPathArray;
+
+@end
+
+@interface SOGoGCSFolder (GroupDAVExtensions)
+
+- (NSString *) groupDavResourceType;
+
+@end
+
+#endif /* __SOGo_SOGoGCSFolder_H__ */
diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m
new file mode 100644 (file)
index 0000000..2caaa68
--- /dev/null
@@ -0,0 +1,718 @@
+/*
+  Copyright (C) 2004-2005 SKYRIX Software AG
+
+  This file is part of OpenGroupware.org.
+
+  OGo is free software; you can redistribute it and/or modify it under
+  the terms of the GNU Lesser General Public License as published by the
+  Free Software Foundation; either version 2, or (at your option) any
+  later version.
+
+  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+  License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#import <Foundation/NSArray.h>
+#import <Foundation/NSDate.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSException.h>
+#import <Foundation/NSKeyValueCoding.h>
+#import <Foundation/NSURL.h>
+
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGObjWeb/SoObject.h>
+#import <NGObjWeb/SoObject+SoDAV.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
+#import <NGObjWeb/WOApplication.h>
+#import <NGExtensions/NSNull+misc.h>
+#import <NGExtensions/NSObject+Logs.h>
+#import <EOControl/EOQualifier.h>
+#import <GDLAccess/EOAdaptorChannel.h>
+#import <GDLContentStore/GCSChannelManager.h>
+#import <GDLContentStore/GCSFolderManager.h>
+#import <GDLContentStore/GCSFolder.h>
+#import <GDLContentStore/GCSFolderType.h>
+#import <GDLContentStore/NSURL+GCS.h>
+#import <SaxObjC/XMLNamespaces.h>
+#import <UI/SOGoUI/SOGoFolderAdvisory.h>
+
+#import "NSArray+Utilities.h"
+#import "NSString+Utilities.h"
+
+#import "SOGoContentObject.h"
+#import "SOGoPermissions.h"
+#import "SOGoUser.h"
+
+#import "SOGoGCSFolder.h"
+
+static NSString *defaultUserID = @"<default>";
+
+@implementation SOGoGCSFolder
+
++ (id) folderWithSubscriptionReference: (NSString *) reference
+                          inContainer: (id) aContainer
+{
+  id newFolder;
+  NSArray *elements, *pathElements;
+  NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName;
+
+  elements = [reference componentsSeparatedByString: @":"];
+  owner = [elements objectAtIndex: 0];
+  objectPath = [elements objectAtIndex: 1];
+  pathElements = [objectPath componentsSeparatedByString: @"/"];
+  if ([pathElements count] > 1)
+    ocsName = [pathElements objectAtIndex: 1];
+  else
+    ocsName = @"personal";
+
+  ocsPath = [NSString stringWithFormat: @"/Users/%@/%@/%@",
+                     owner, [pathElements objectAtIndex: 0], ocsName];
+  folderName = [NSString stringWithFormat: @"%@_%@", owner, ocsName];
+  newFolder = [[self alloc] initWithName: folderName
+                           inContainer: aContainer];
+  [newFolder setOCSPath: ocsPath];
+  [newFolder setOwner: owner];
+
+  return newFolder;
+}
+
+- (id) init
+{
+  if ((self = [super init]))
+    {
+      displayName = nil;
+      ocsPath = nil;
+      ocsFolder = nil;
+      aclCache = [NSMutableDictionary new];
+    }
+
+  return self;
+}
+
+- (void) dealloc
+{
+  [ocsFolder release];
+  [ocsPath release];
+  [aclCache release];
+  [displayName release];
+  [super dealloc];
+}
+
+/* accessors */
+
+- (void) _setDisplayNameFromRow: (NSDictionary *) row
+{
+  NSString *currentLogin, *ownerLogin;
+  NSDictionary *ownerIdentity;
+
+  displayName
+    = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]];
+  currentLogin = [[context activeUser] login];
+  ownerLogin = [self ownerInContext: context];
+  if (![currentLogin isEqualToString: ownerLogin])
+    {
+      ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil]
+                       primaryIdentity];
+      [displayName appendFormat: @" (%@ <%@>)",
+                  [ownerIdentity objectForKey: @"fullName"],
+                  [ownerIdentity objectForKey: @"email"]];
+    }
+  [displayName retain];
+}
+
+- (void) _fetchDisplayName
+{
+  GCSChannelManager *cm;
+  EOAdaptorChannel *fc;
+  NSURL *folderLocation;
+  NSString *sql;
+  NSArray *attrs;
+  NSDictionary *row;
+
+  cm = [GCSChannelManager defaultChannelManager];
+  folderLocation
+    = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
+  fc = [cm acquireOpenChannelForURL: folderLocation];
+  if (fc)
+    {
+      sql
+       = [NSString stringWithFormat: (@"SELECT c_foldername FROM %@"
+                                      @" WHERE c_path = '%@'"),
+                   [folderLocation gcsTableName], ocsPath];
+      [fc evaluateExpressionX: sql];
+      attrs = [fc describeResults: NO];
+      row = [fc fetchAttributes: attrs withZone: NULL];
+      if (row)
+       [self _setDisplayNameFromRow: row];
+      [fc cancelFetch];
+      [cm releaseChannel: fc];
+    }
+}
+
+- (NSString *) displayName
+{
+  if (!displayName)
+    [self _fetchDisplayName];
+
+  return displayName;
+}
+
+- (void) setOCSPath: (NSString *) _path
+{
+  if (![ocsPath isEqualToString:_path])
+    {
+      if (ocsPath)
+       [self warnWithFormat: @"GCS path is already set! '%@'", _path];
+      ASSIGN (ocsPath, _path);
+    }
+}
+
+- (NSString *) ocsPath
+{
+  return ocsPath;
+}
+
+- (GCSFolderManager *) folderManager
+{
+  static GCSFolderManager *folderManager = nil;
+
+  if (!folderManager)
+    folderManager = [GCSFolderManager defaultFolderManager];
+
+  return folderManager;
+}
+
+- (GCSFolder *) ocsFolderForPath: (NSString *) _path
+{
+  return [[self folderManager] folderAtPath: _path];
+}
+
+- (BOOL) folderIsMandatory
+{
+  return [nameInContainer isEqualToString: @"personal"];
+}
+
+- (NSString *) davDisplayName
+{
+  return [self displayName];
+}
+
+- (GCSFolder *) ocsFolder
+{
+  GCSFolder *folder;
+  NSString *userLogin;
+
+  if (!ocsFolder)
+    {
+      ocsFolder = [self ocsFolderForPath: [self ocsPath]];
+      userLogin = [[context activeUser] login];
+      if (!ocsFolder
+         && [userLogin isEqualToString: [self ownerInContext: context]]
+         && [self folderIsMandatory]
+         && [self create])
+       ocsFolder = [self ocsFolderForPath: [self ocsPath]];
+      [ocsFolder retain];
+    }
+
+  if ([ocsFolder isNotNull])
+    folder = ocsFolder;
+  else
+    folder = nil;
+
+  return folder;
+}
+
+- (void) sendFolderAdvisoryTemplate: (NSString *) template
+{
+  NSString *pageName;
+  SOGoUser *user;
+  SOGoFolderAdvisory *page;
+
+  user = [context activeUser];
+  pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory",
+                      [user language], template];
+
+  page = [[WOApplication application] pageWithName: pageName
+                                     inContext: context];
+  [page setFolderObject: self];
+  [page setRecipientUID: [user login]];
+  [page send];
+}
+
+
+//   if (!result) [self sendFolderAdvisoryTemplate: @"Addition"];
+
+- (BOOL) create
+{
+  NSException *result;
+
+  result = [[self folderManager] createFolderOfType: [self folderType]
+                                withName: displayName
+                                 atPath: ocsPath];
+
+  return (result == nil);
+}
+
+- (NSException *) delete
+{
+  NSException *error;
+
+  // We just fetch our displayName since our table will use it!
+  [self displayName];
+  
+  if ([nameInContainer isEqualToString: @"personal"])
+    error = [NSException exceptionWithHTTPStatus: 403
+                        reason: @"folder 'personal' cannot be deleted"];
+  else
+    error = [[self folderManager] deleteFolderAtPath: ocsPath];
+
+  return error;
+}
+
+//   if (!error) [self sendFolderAdvisoryTemplate: @"Removal"];
+
+- (void) renameTo: (NSString *) newName
+{
+  GCSChannelManager *cm;
+  EOAdaptorChannel *fc;
+  NSURL *folderLocation;
+  NSString *sql;
+
+  [displayName release];
+  displayName = nil;
+
+  cm = [GCSChannelManager defaultChannelManager];
+  folderLocation
+    = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
+  fc = [cm acquireOpenChannelForURL: folderLocation];
+  if (fc)
+    {
+      sql
+       = [NSString stringWithFormat: (@"UPDATE %@ SET c_foldername = '%@'"
+                                      @" WHERE c_path = '%@'"),
+                   [folderLocation gcsTableName], newName, ocsPath];
+      [fc evaluateExpressionX: sql];
+      [cm releaseChannel: fc];
+//       sql = [sql stringByAppendingFormat:@" WHERE %@ = '%@'", 
+//                  uidColumnName, [self uid]];
+    }
+}
+
+- (NSArray *) fetchContentObjectNames
+{
+  NSArray *fields, *records;
+  
+  fields = [NSArray arrayWithObject: @"c_name"];
+  records = [[self ocsFolder] fetchFields:fields matchingQualifier:nil];
+  if (![records isNotNull])
+    {
+      [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
+      return nil;
+    }
+  if ([records isKindOfClass: [NSException class]])
+    return records;
+  return [records objectsForKey: @"c_name"];
+}
+
+- (BOOL) nameExistsInFolder: (NSString *) objectName
+{
+  NSArray *fields, *records;
+  EOQualifier *qualifier;
+
+  qualifier
+    = [EOQualifier qualifierWithQualifierFormat:
+                     [NSString stringWithFormat: @"c_name='%@'", objectName]];
+
+  fields = [NSArray arrayWithObject: @"c_name"];
+  records = [[self ocsFolder] fetchFields: fields
+                              matchingQualifier: qualifier];
+  return (records
+          && ![records isKindOfClass:[NSException class]]
+          && [records count] > 0);
+}
+
+- (void) deleteEntriesWithIds: (NSArray *) ids
+{
+  unsigned int count, max;
+  NSString *currentID;
+  SOGoContentObject *deleteObject;
+
+  max = [ids count];
+  for (count = 0; count < max; count++)
+    {
+      currentID = [ids objectAtIndex: count];
+      deleteObject = [self lookupName: currentID
+                          inContext: context acquire: NO];
+      if (![deleteObject isKindOfClass: [NSException class]])
+       [deleteObject delete];
+    }
+}
+
+- (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
+{
+  NSDictionary *files;
+  
+  files = [[self ocsFolder] fetchContentsOfAllFiles];
+  if (![files isNotNull])
+    {
+      [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
+      return nil;
+    }
+  if ([files isKindOfClass:[NSException class]])
+    return files;
+  return files;
+}
+
+/* reflection */
+
+- (NSString *) defaultFilenameExtension
+{
+  /* 
+     Override to add an extension to a filename
+     
+     Note: be careful with that, needs to be consistent with object lookup!
+  */
+  return nil;
+}
+
+- (NSArray *) davResourceType
+{
+  NSArray *rType, *groupDavCollection;
+
+  if ([self respondsToSelector: @selector (groupDavResourceType)])
+    {
+      groupDavCollection
+       = [NSArray arrayWithObjects: [self groupDavResourceType],
+                  XMLNS_GROUPDAV, nil];
+      rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
+                      nil];
+    }
+  else
+    rType = [NSArray arrayWithObject: @"collection"];
+
+  return rType;
+}
+
+- (NSArray *) toOneRelationshipKeys
+{
+  /* toOneRelationshipKeys are the 'files' contained in a folder */
+  NSMutableArray *ma;
+  NSArray  *names;
+  NSString *name, *ext;
+  unsigned i, count;
+  NSRange  r;
+
+  names = [self fetchContentObjectNames];
+  count = [names count];
+  ext = [self defaultFilenameExtension];
+  if (count && [ext length] > 0)
+    {
+      ma = [NSMutableArray arrayWithCapacity: count];
+      for (i = 0; i < count; i++)
+        {
+          name = [names objectAtIndex: i];
+          r = [name rangeOfString: @"."];
+          if (r.length == 0)
+           name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
+          [ma addObject: name];
+        }
+
+      names = ma;
+    }
+
+  return names;
+}
+
+/* acls as a container */
+
+- (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
+{
+  EOQualifier *qualifier;
+  NSString *qs;
+  NSArray *records;
+
+  qs = [NSString stringWithFormat: @"c_object = '/%@'",
+                [objectPathArray componentsJoinedByString: @"/"]];
+  qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
+  records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
+
+  return [records valueForKey: @"c_uid"];
+}
+
+- (NSArray *) _fetchAclsForUser: (NSString *) uid
+               forObjectAtPath: (NSString *) objectPath
+{
+  EOQualifier *qualifier;
+  NSArray *records;
+  NSMutableArray *acls;
+  NSString *qs;
+
+  qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
+                objectPath, uid];
+  qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
+  records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
+
+  acls = [NSMutableArray array];
+  if ([records count] > 0)
+    {
+      [acls addObject: SOGoRole_AuthorizedSubscriber];
+      [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
+    }
+
+  return acls;
+}
+
+- (void) _cacheRoles: (NSArray *) roles
+            forUser: (NSString *) uid
+     forObjectAtPath: (NSString *) objectPath
+{
+  NSMutableDictionary *aclsForObject;
+
+  aclsForObject = [aclCache objectForKey: objectPath];
+  if (!aclsForObject)
+    {
+      aclsForObject = [NSMutableDictionary dictionary];
+      [aclCache setObject: aclsForObject
+               forKey: objectPath];
+    }
+  if (roles)
+    [aclsForObject setObject: roles forKey: uid];
+  else
+    [aclsForObject removeObjectForKey: uid];
+}
+
+- (NSArray *) aclsForUser: (NSString *) uid
+          forObjectAtPath: (NSArray *) objectPathArray
+{
+  NSArray *acls;
+  NSString *objectPath;
+  NSDictionary *aclsForObject;
+
+  objectPath = [objectPathArray componentsJoinedByString: @"/"];
+  aclsForObject = [aclCache objectForKey: objectPath];
+  if (aclsForObject)
+    acls = [aclsForObject objectForKey: uid];
+  else
+    acls = nil;
+  if (!acls)
+    {
+      acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
+      [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
+    }
+
+  if (!([acls count] || [uid isEqualToString: defaultUserID]))
+    acls = [self aclsForUser: defaultUserID
+                forObjectAtPath: objectPathArray];
+
+  return acls;
+}
+
+- (void) removeAclsForUsers: (NSArray *) users
+            forObjectAtPath: (NSArray *) objectPathArray
+{
+  EOQualifier *qualifier;
+  NSString *uids, *qs, *objectPath;
+  NSMutableDictionary *aclsForObject;
+
+  if ([users count] > 0)
+    {
+      objectPath = [objectPathArray componentsJoinedByString: @"/"];
+      aclsForObject = [aclCache objectForKey: objectPath];
+      if (aclsForObject)
+       [aclsForObject removeObjectsForKeys: users];
+      uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
+      qs = [NSString
+            stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
+            objectPath, uids];
+      qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
+      [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
+    }
+}
+
+- (void) _commitRoles: (NSArray *) roles
+              forUID: (NSString *) uid
+           forObject: (NSString *) objectPath
+{
+  EOAdaptorChannel *channel;
+  GCSFolder *folder;
+  NSEnumerator *userRoles;
+  NSString *SQL, *currentRole;
+
+  folder = [self ocsFolder];
+  channel = [folder acquireAclChannel];
+  userRoles = [roles objectEnumerator];
+  currentRole = [userRoles nextObject];
+  while (currentRole)
+    {
+      SQL = [NSString stringWithFormat: @"INSERT INTO %@"
+                     @" (c_object, c_uid, c_role)"
+                     @" VALUES ('/%@', '%@', '%@')",
+                     [folder aclTableName],
+                     objectPath, uid, currentRole];
+      [channel evaluateExpressionX: SQL];
+      currentRole = [userRoles nextObject];
+    }
+
+  [folder releaseChannel: channel];
+}
+
+- (void) setRoles: (NSArray *) roles
+          forUser: (NSString *) uid
+  forObjectAtPath: (NSArray *) objectPathArray
+{
+  NSString *objectPath;
+  NSMutableArray *newRoles;
+
+  [self removeAclsForUsers: [NSArray arrayWithObject: uid]
+        forObjectAtPath: objectPathArray];
+
+  newRoles = [NSMutableArray arrayWithArray: roles];
+  [newRoles removeObject: SOGoRole_AuthorizedSubscriber];
+  [newRoles removeObject: SOGoRole_None];
+  objectPath = [objectPathArray componentsJoinedByString: @"/"];
+  [self _cacheRoles: newRoles forUser: uid
+       forObjectAtPath: objectPath];
+  if (![newRoles count])
+    [newRoles addObject: SOGoRole_None];
+
+  [self _commitRoles: newRoles forUID: uid forObject: objectPath];
+}
+
+/* acls */
+- (NSArray *) aclUsers
+{
+  return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
+}
+
+- (NSArray *) aclsForUser: (NSString *) uid
+{
+  NSMutableArray *acls;
+  NSArray *ownAcls, *containerAcls;
+
+  acls = [NSMutableArray array];
+  ownAcls = [self aclsForUser: uid
+                 forObjectAtPath: [self pathArrayToSOGoObject]];
+  [acls addObjectsFromArray: ownAcls];
+  if ([container respondsToSelector: @selector (aclsForUser:)])
+    {
+      containerAcls = [container aclsForUser: uid];
+      if ([containerAcls count] > 0)
+       {
+         if ([containerAcls containsObject: SOGoRole_ObjectReader])
+           [acls addObject: SOGoRole_ObjectViewer];
+#warning this should be checked
+         if ([containerAcls containsObject: SOGoRole_ObjectEraser])
+           [acls addObject: SOGoRole_ObjectEraser];
+       }
+    }
+
+  return acls;
+}
+
+- (void) setRoles: (NSArray *) roles
+          forUser: (NSString *) uid
+{
+  return [self setRoles: roles
+               forUser: uid
+               forObjectAtPath: [self pathArrayToSOGoObject]];
+}
+
+- (void) removeAclsForUsers: (NSArray *) users
+{
+  return [self removeAclsForUsers: users
+               forObjectAtPath: [self pathArrayToSOGoObject]];
+}
+
+- (NSString *) defaultUserID
+{
+  return defaultUserID;
+}
+
+- (NSComparisonResult) _compareByOrigin: (SOGoFolder *) otherFolder
+{
+  NSArray *thisElements, *otherElements;
+  unsigned thisCount, otherCount;
+  NSComparisonResult comparison;
+
+  thisElements = [nameInContainer componentsSeparatedByString: @"_"];
+  otherElements = [[otherFolder nameInContainer]
+                   componentsSeparatedByString: @"_"];
+  thisCount = [thisElements count];
+  otherCount = [otherElements count];
+  if (thisCount == otherCount)
+    {
+      if (thisCount == 1)
+       comparison = NSOrderedSame;
+      else
+       comparison = [[thisElements objectAtIndex: 0]
+                      compare: [otherElements objectAtIndex: 0]];
+    }
+  else
+    {
+      if (thisCount > otherCount)
+       comparison = NSOrderedDescending;
+      else
+       comparison = NSOrderedAscending;
+    }
+
+  return comparison;
+}
+
+- (NSComparisonResult) _compareByNameInContainer: (SOGoFolder *) otherFolder
+{
+  NSString *otherName;
+  NSComparisonResult comparison;
+
+  otherName = [otherFolder nameInContainer];
+  if ([nameInContainer hasSuffix: @"personal"])
+    {
+      if ([otherName hasSuffix: @"personal"])
+       comparison = [nameInContainer compare: otherName];
+      else
+       comparison = NSOrderedAscending;
+    }
+  else
+    {
+      if ([otherName hasSuffix: @"personal"])
+       comparison = NSOrderedDescending;
+      else
+       comparison = NSOrderedSame;
+    }
+
+  return comparison;
+}
+
+- (NSComparisonResult) compare: (SOGoGCSFolder *) otherFolder
+{
+  NSComparisonResult comparison;
+
+  comparison = [self _compareByOrigin: otherFolder];
+  if (comparison == NSOrderedSame)
+    {
+      comparison = [self _compareByNameInContainer: otherFolder];
+      if (comparison == NSOrderedSame)
+       comparison
+         = [[self displayName]
+             localizedCaseInsensitiveCompare: [otherFolder displayName]];
+    }
+
+  return comparison;
+}
+
+/* description */
+
+- (void) appendAttributesToDescription: (NSMutableString *) _ms
+{
+  [super appendAttributesToDescription:_ms];
+  
+  [_ms appendFormat:@" ocs=%@", [self ocsPath]];
+}
+
+@end /* SOGoFolder */