]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoGCSFolder.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1244 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / SOGo / SOGoGCSFolder.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/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>
45
46 #import "NSArray+Utilities.h"
47 #import "NSString+Utilities.h"
48
49 #import "SOGoContentObject.h"
50 #import "SOGoPermissions.h"
51 #import "SOGoUser.h"
52
53 #import "SOGoGCSFolder.h"
54
55 static NSString *defaultUserID = @"<default>";
56
57 @implementation SOGoGCSFolder
58
59 + (id) folderWithSubscriptionReference: (NSString *) reference
60                            inContainer: (id) aContainer
61 {
62   id newFolder;
63   NSArray *elements, *pathElements;
64   NSString *ocsPath, *objectPath, *owner, *ocsName, *folderName;
65
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];
72   else
73     ocsName = @"personal";
74
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];
82
83   return newFolder;
84 }
85
86 - (id) init
87 {
88   if ((self = [super init]))
89     {
90       displayName = nil;
91       ocsPath = nil;
92       ocsFolder = nil;
93       aclCache = [NSMutableDictionary new];
94     }
95
96   return self;
97 }
98
99 - (void) dealloc
100 {
101   [ocsFolder release];
102   [ocsPath release];
103   [aclCache release];
104   [displayName release];
105   [super dealloc];
106 }
107
108 /* accessors */
109
110 - (void) _setDisplayNameFromRow: (NSDictionary *) row
111 {
112   NSString *currentLogin, *ownerLogin;
113   NSDictionary *ownerIdentity;
114
115   displayName
116     = [NSMutableString stringWithString: [row objectForKey: @"c_foldername"]];
117   currentLogin = [[context activeUser] login];
118   ownerLogin = [self ownerInContext: context];
119   if (![currentLogin isEqualToString: ownerLogin])
120     {
121       ownerIdentity = [[SOGoUser userWithLogin: ownerLogin roles: nil]
122                         primaryIdentity];
123       [displayName appendFormat: @" (%@ <%@>)",
124                    [ownerIdentity objectForKey: @"fullName"],
125                    [ownerIdentity objectForKey: @"email"]];
126     }
127   [displayName retain];
128 }
129
130 - (void) _fetchDisplayName
131 {
132   GCSChannelManager *cm;
133   EOAdaptorChannel *fc;
134   NSURL *folderLocation;
135   NSString *sql;
136   NSArray *attrs;
137   NSDictionary *row;
138
139   cm = [GCSChannelManager defaultChannelManager];
140   folderLocation
141     = [[GCSFolderManager defaultFolderManager] folderInfoLocation];
142   fc = [cm acquireOpenChannelForURL: folderLocation];
143   if (fc)
144     {
145       sql
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];
152       if (row)
153         [self _setDisplayNameFromRow: row];
154       [fc cancelFetch];
155       [cm releaseChannel: fc];
156     }
157 }
158
159 - (NSString *) displayName
160 {
161   if (!displayName)
162     [self _fetchDisplayName];
163
164   return displayName;
165 }
166
167 - (void) setOCSPath: (NSString *) _path
168 {
169   if (![ocsPath isEqualToString:_path])
170     {
171       if (ocsPath)
172         [self warnWithFormat: @"GCS path is already set! '%@'", _path];
173       ASSIGN (ocsPath, _path);
174     }
175 }
176
177 - (NSString *) ocsPath
178 {
179   return ocsPath;
180 }
181
182 - (GCSFolderManager *) folderManager
183 {
184   static GCSFolderManager *folderManager = nil;
185
186   if (!folderManager)
187     folderManager = [GCSFolderManager defaultFolderManager];
188
189   return folderManager;
190 }
191
192 - (GCSFolder *) ocsFolderForPath: (NSString *) _path
193 {
194   return [[self folderManager] folderAtPath: _path];
195 }
196
197 - (BOOL) folderIsMandatory
198 {
199   return [nameInContainer isEqualToString: @"personal"];
200 }
201
202 - (NSString *) davDisplayName
203 {
204   return [self displayName];
205 }
206
207 - (GCSFolder *) ocsFolder
208 {
209   GCSFolder *folder;
210   NSString *userLogin;
211
212   if (!ocsFolder)
213     {
214       ocsFolder = [self ocsFolderForPath: [self ocsPath]];
215       userLogin = [[context activeUser] login];
216       if (!ocsFolder
217           && [userLogin isEqualToString: [self ownerInContext: context]]
218           && [self folderIsMandatory]
219           && [self create])
220         ocsFolder = [self ocsFolderForPath: [self ocsPath]];
221       [ocsFolder retain];
222     }
223
224   if ([ocsFolder isNotNull])
225     folder = ocsFolder;
226   else
227     folder = nil;
228
229   return folder;
230 }
231
232 - (void) sendFolderAdvisoryTemplate: (NSString *) template
233 {
234   NSString *pageName;
235   SOGoUser *user;
236   SOGoFolderAdvisory *page;
237
238   user = [context activeUser];
239   pageName = [NSString stringWithFormat: @"SOGoFolder%@%@Advisory",
240                        [user language], template];
241
242   page = [[WOApplication application] pageWithName: pageName
243                                       inContext: context];
244   [page setFolderObject: self];
245   [page setRecipientUID: [user login]];
246   [page send];
247 }
248
249
250 //   if (!result) [self sendFolderAdvisoryTemplate: @"Addition"];
251
252 - (BOOL) create
253 {
254   NSException *result;
255
256   result = [[self folderManager] createFolderOfType: [self folderType]
257                                  withName: displayName
258                                  atPath: ocsPath];
259
260   return (result == nil);
261 }
262
263 - (NSException *) delete
264 {
265   NSException *error;
266
267   // We just fetch our displayName since our table will use it!
268   [self displayName];
269   
270   if ([nameInContainer isEqualToString: @"personal"])
271     error = [NSException exceptionWithHTTPStatus: 403
272                          reason: @"folder 'personal' cannot be deleted"];
273   else
274     error = [[self folderManager] deleteFolderAtPath: ocsPath];
275
276   return error;
277 }
278
279 //   if (!error) [self sendFolderAdvisoryTemplate: @"Removal"];
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 - (void) deleteEntriesWithIds: (NSArray *) ids
342 {
343   unsigned int count, max;
344   NSString *currentID;
345   SOGoContentObject *deleteObject;
346
347   max = [ids count];
348   for (count = 0; count < max; count++)
349     {
350       currentID = [ids objectAtIndex: count];
351       deleteObject = [self lookupName: currentID
352                            inContext: context acquire: NO];
353       if (![deleteObject isKindOfClass: [NSException class]])
354         [deleteObject delete];
355     }
356 }
357
358 - (NSDictionary *) fetchContentStringsAndNamesOfAllObjects
359 {
360   NSDictionary *files;
361   
362   files = [[self ocsFolder] fetchContentsOfAllFiles];
363   if (![files isNotNull])
364     {
365       [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__];
366       return nil;
367     }
368   if ([files isKindOfClass:[NSException class]])
369     return files;
370   return files;
371 }
372
373 /* reflection */
374
375 - (NSString *) defaultFilenameExtension
376 {
377   /* 
378      Override to add an extension to a filename
379      
380      Note: be careful with that, needs to be consistent with object lookup!
381   */
382   return nil;
383 }
384
385 - (NSArray *) davResourceType
386 {
387   NSArray *rType, *groupDavCollection;
388
389   if ([self respondsToSelector: @selector (groupDavResourceType)])
390     {
391       groupDavCollection
392         = [NSArray arrayWithObjects: [self groupDavResourceType],
393                    XMLNS_GROUPDAV, nil];
394       rType = [NSArray arrayWithObjects: @"collection", groupDavCollection,
395                        nil];
396     }
397   else
398     rType = [NSArray arrayWithObject: @"collection"];
399
400   return rType;
401 }
402
403 - (NSArray *) toOneRelationshipKeys
404 {
405   /* toOneRelationshipKeys are the 'files' contained in a folder */
406   NSMutableArray *ma;
407   NSArray  *names;
408   NSString *name, *ext;
409   unsigned i, count;
410   NSRange  r;
411
412   names = [self fetchContentObjectNames];
413   count = [names count];
414   ext = [self defaultFilenameExtension];
415   if (count && [ext length] > 0)
416     {
417       ma = [NSMutableArray arrayWithCapacity: count];
418       for (i = 0; i < count; i++)
419         {
420           name = [names objectAtIndex: i];
421           r = [name rangeOfString: @"."];
422           if (r.length == 0)
423             name = [NSMutableString stringWithFormat: @"%@.%@", name, ext];
424           [ma addObject: name];
425         }
426
427       names = ma;
428     }
429
430   return names;
431 }
432
433 /* acls as a container */
434
435 - (NSArray *) aclUsersForObjectAtPath: (NSArray *) objectPathArray;
436 {
437   EOQualifier *qualifier;
438   NSString *qs;
439   NSArray *records;
440
441   qs = [NSString stringWithFormat: @"c_object = '/%@'",
442                  [objectPathArray componentsJoinedByString: @"/"]];
443   qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
444   records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
445
446   return [records valueForKey: @"c_uid"];
447 }
448
449 - (NSArray *) _fetchAclsForUser: (NSString *) uid
450                 forObjectAtPath: (NSString *) objectPath
451 {
452   EOQualifier *qualifier;
453   NSArray *records;
454   NSMutableArray *acls;
455   NSString *qs;
456
457   qs = [NSString stringWithFormat: @"(c_object = '/%@') AND (c_uid = '%@')",
458                  objectPath, uid];
459   qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
460   records = [[self ocsFolder] fetchAclMatchingQualifier: qualifier];
461
462   acls = [NSMutableArray array];
463   if ([records count] > 0)
464     {
465       [acls addObject: SOGoRole_AuthorizedSubscriber];
466       [acls addObjectsFromArray: [records valueForKey: @"c_role"]];
467     }
468
469   return acls;
470 }
471
472 - (void) _cacheRoles: (NSArray *) roles
473              forUser: (NSString *) uid
474      forObjectAtPath: (NSString *) objectPath
475 {
476   NSMutableDictionary *aclsForObject;
477
478   aclsForObject = [aclCache objectForKey: objectPath];
479   if (!aclsForObject)
480     {
481       aclsForObject = [NSMutableDictionary dictionary];
482       [aclCache setObject: aclsForObject
483                 forKey: objectPath];
484     }
485   if (roles)
486     [aclsForObject setObject: roles forKey: uid];
487   else
488     [aclsForObject removeObjectForKey: uid];
489 }
490
491 - (NSArray *) aclsForUser: (NSString *) uid
492           forObjectAtPath: (NSArray *) objectPathArray
493 {
494   NSArray *acls;
495   NSString *objectPath;
496   NSDictionary *aclsForObject;
497
498   objectPath = [objectPathArray componentsJoinedByString: @"/"];
499   aclsForObject = [aclCache objectForKey: objectPath];
500   if (aclsForObject)
501     acls = [aclsForObject objectForKey: uid];
502   else
503     acls = nil;
504   if (!acls)
505     {
506       acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
507       [self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
508     }
509
510   if (!([acls count] || [uid isEqualToString: defaultUserID]))
511     acls = [self aclsForUser: defaultUserID
512                  forObjectAtPath: objectPathArray];
513
514   return acls;
515 }
516
517 - (void) removeAclsForUsers: (NSArray *) users
518             forObjectAtPath: (NSArray *) objectPathArray
519 {
520   EOQualifier *qualifier;
521   NSString *uids, *qs, *objectPath;
522   NSMutableDictionary *aclsForObject;
523
524   if ([users count] > 0)
525     {
526       objectPath = [objectPathArray componentsJoinedByString: @"/"];
527       aclsForObject = [aclCache objectForKey: objectPath];
528       if (aclsForObject)
529         [aclsForObject removeObjectsForKeys: users];
530       uids = [users componentsJoinedByString: @"') OR (c_uid = '"];
531       qs = [NSString
532              stringWithFormat: @"(c_object = '/%@') AND ((c_uid = '%@'))",
533              objectPath, uids];
534       qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
535       [[self ocsFolder] deleteAclMatchingQualifier: qualifier];
536     }
537 }
538
539 - (void) _commitRoles: (NSArray *) roles
540                forUID: (NSString *) uid
541             forObject: (NSString *) objectPath
542 {
543   EOAdaptorChannel *channel;
544   GCSFolder *folder;
545   NSEnumerator *userRoles;
546   NSString *SQL, *currentRole;
547
548   folder = [self ocsFolder];
549   channel = [folder acquireAclChannel];
550   userRoles = [roles objectEnumerator];
551   currentRole = [userRoles nextObject];
552   while (currentRole)
553     {
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];
561     }
562
563   [folder releaseChannel: channel];
564 }
565
566 - (void) setRoles: (NSArray *) roles
567           forUser: (NSString *) uid
568   forObjectAtPath: (NSArray *) objectPathArray
569 {
570   NSString *objectPath;
571   NSMutableArray *newRoles;
572
573   [self removeAclsForUsers: [NSArray arrayWithObject: uid]
574         forObjectAtPath: objectPathArray];
575
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];
584
585   [self _commitRoles: newRoles forUID: uid forObject: objectPath];
586 }
587
588 /* acls */
589 - (NSArray *) aclUsers
590 {
591   return [self aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
592 }
593
594 - (NSArray *) aclsForUser: (NSString *) uid
595 {
596   NSMutableArray *acls;
597   NSArray *ownAcls, *containerAcls;
598
599   acls = [NSMutableArray array];
600   ownAcls = [self aclsForUser: uid
601                   forObjectAtPath: [self pathArrayToSOGoObject]];
602   [acls addObjectsFromArray: ownAcls];
603   if ([container respondsToSelector: @selector (aclsForUser:)])
604     {
605       containerAcls = [container aclsForUser: uid];
606       if ([containerAcls count] > 0)
607         {
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];
613         }
614     }
615
616   return acls;
617 }
618
619 - (void) setRoles: (NSArray *) roles
620           forUser: (NSString *) uid
621 {
622   return [self setRoles: roles
623                forUser: uid
624                forObjectAtPath: [self pathArrayToSOGoObject]];
625 }
626
627 - (void) removeAclsForUsers: (NSArray *) users
628 {
629   return [self removeAclsForUsers: users
630                forObjectAtPath: [self pathArrayToSOGoObject]];
631 }
632
633 - (NSString *) defaultUserID
634 {
635   return defaultUserID;
636 }
637
638 - (NSComparisonResult) _compareByOrigin: (SOGoFolder *) otherFolder
639 {
640   NSArray *thisElements, *otherElements;
641   unsigned thisCount, otherCount;
642   NSComparisonResult comparison;
643
644   thisElements = [nameInContainer componentsSeparatedByString: @"_"];
645   otherElements = [[otherFolder nameInContainer]
646                     componentsSeparatedByString: @"_"];
647   thisCount = [thisElements count];
648   otherCount = [otherElements count];
649   if (thisCount == otherCount)
650     {
651       if (thisCount == 1)
652         comparison = NSOrderedSame;
653       else
654         comparison = [[thisElements objectAtIndex: 0]
655                        compare: [otherElements objectAtIndex: 0]];
656     }
657   else
658     {
659       if (thisCount > otherCount)
660         comparison = NSOrderedDescending;
661       else
662         comparison = NSOrderedAscending;
663     }
664
665   return comparison;
666 }
667
668 - (NSComparisonResult) _compareByNameInContainer: (SOGoFolder *) otherFolder
669 {
670   NSString *otherName;
671   NSComparisonResult comparison;
672
673   otherName = [otherFolder nameInContainer];
674   if ([nameInContainer hasSuffix: @"personal"])
675     {
676       if ([otherName hasSuffix: @"personal"])
677         comparison = [nameInContainer compare: otherName];
678       else
679         comparison = NSOrderedAscending;
680     }
681   else
682     {
683       if ([otherName hasSuffix: @"personal"])
684         comparison = NSOrderedDescending;
685       else
686         comparison = NSOrderedSame;
687     }
688
689   return comparison;
690 }
691
692 - (NSComparisonResult) compare: (SOGoGCSFolder *) otherFolder
693 {
694   NSComparisonResult comparison;
695
696   comparison = [self _compareByOrigin: otherFolder];
697   if (comparison == NSOrderedSame)
698     {
699       comparison = [self _compareByNameInContainer: otherFolder];
700       if (comparison == NSOrderedSame)
701         comparison
702           = [[self displayName]
703               localizedCaseInsensitiveCompare: [otherFolder displayName]];
704     }
705
706   return comparison;
707 }
708
709 /* description */
710
711 - (void) appendAttributesToDescription: (NSMutableString *) _ms
712 {
713   [super appendAttributesToDescription:_ms];
714   
715   [_ms appendFormat:@" ocs=%@", [self ocsPath]];
716 }
717
718 @end /* SOGoFolder */