]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoContentObject.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1071 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / SOGo / SOGoContentObject.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 <GDLContentStore/GCSFolder.h>
23
24 #import "common.h"
25 #import "SOGoFolder.h"
26 #import "SOGoUser.h"
27 #import "SOGoPermissions.h"
28 #import "SOGoContentObject.h"
29
30 @interface SOGoContentObject(ETag)
31 - (NSArray *)parseETagList:(NSString *)_c;
32 @end
33
34 @implementation SOGoContentObject
35
36 // TODO: check superclass version
37
38 - (void)dealloc {
39   [content release];
40   [ocsPath release];
41   [super dealloc];
42 }
43
44 /* notifications */
45
46 - (void)sleep {
47   [content release]; content = nil;
48   [super sleep];
49 }
50
51 /* accessors */
52
53 - (BOOL)isFolderish {
54   return NO;
55 }
56
57 - (void)setOCSPath:(NSString *)_path {
58   if ([ocsPath isEqualToString:_path])
59     return;
60   
61   if (ocsPath)
62     [self warnWithFormat:@"GCS path is already set! '%@'", _path];
63   
64   ASSIGNCOPY(ocsPath, _path);
65 }
66
67 - (NSString *) ocsPath
68 {
69   NSString *p;
70     
71   if (!ocsPath)
72     {
73       p = [self ocsPathOfContainer];
74       if (p)
75         {
76           if (![p hasSuffix:@"/"])
77             p = [p stringByAppendingString: @"/"];
78           ocsPath = [p stringByAppendingString: [self nameInContainer]];
79           [ocsPath retain];
80         }
81     }
82
83   return ocsPath;
84 }
85
86 - (NSString *)ocsPathOfContainer {
87   if (![[self container] respondsToSelector:@selector(ocsPath)])
88     return nil;
89
90   return [[self container] ocsPath];
91 }
92
93 - (GCSFolder *) ocsFolder
94 {
95   return [container ocsFolder];
96 }
97
98 /* content */
99
100 - (NSString *) contentAsString
101 {
102   if (!content)
103     {
104       content = [[self ocsFolder] fetchContentWithName: nameInContainer];
105       [content retain];
106     }
107
108   return content;
109 }
110
111 - (NSException *) saveContentString: (NSString *) _str
112                         baseVersion: (unsigned int) _baseVersion
113 {
114   /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
115   GCSFolder   *folder;
116   NSException *ex;
117   
118   if ((folder = [self ocsFolder]) == nil) {
119     [self errorWithFormat:@"Did not find folder of content object."];
120     return nil;
121   }
122   
123   ex = [folder writeContent:_str toName:[self nameInContainer]
124                baseVersion:_baseVersion];
125   if (ex != nil) {
126     [self errorWithFormat:@"write failed: %@", ex];
127     return ex;
128   }
129   return nil;
130 }
131 - (NSException *)saveContentString:(NSString *)_str {
132   return [self saveContentString:_str baseVersion:0 /* don't check */];
133 }
134
135 - (NSException *)delete {
136   /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
137   GCSFolder   *folder;
138   NSException *ex;
139   
140   // TODO: add precondition check? (or add DELETEAction?)
141   
142   if ((folder = [self ocsFolder]) == nil) {
143     [self errorWithFormat:@"Did not find folder of content object."];
144     return nil;
145   }
146   
147   if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
148     [self errorWithFormat:@"delete failed: %@", ex];
149     return ex;
150   }
151   return nil;
152 }
153
154 /* actions */
155
156 - (id)PUTAction:(WOContext *)_ctx {
157   WORequest    *rq;
158   NSException  *error;
159   unsigned int baseVersion;
160   id           etag, tmp;
161   BOOL         needsLocation;
162   
163   if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
164     return error;
165   
166   rq = [_ctx request];
167   
168   /* check whether its a request to the 'special' 'new' location */
169   /*
170     Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as
171           object creations and will assign a server side identifier. Most
172           current GroupDAV clients rely on this behaviour, so we reproduce it
173           here.
174           A correct client would loop until it has a name which doesn't not
175           yet exist (by using if-none-match).
176   */
177   needsLocation = NO;
178   tmp = [[self nameInContainer] stringByDeletingPathExtension];
179   if ([tmp isEqualToString:@"new"]) {
180     tmp = [[[self container] class] globallyUniqueObjectId];
181     needsLocation = YES;
182     
183     [self debugWithFormat:
184             @"reassigned a new location for special new-location: %@", tmp];
185     
186     /* kinda dangerous */
187     ASSIGNCOPY(nameInContainer, tmp);
188     ASSIGN(ocsPath, nil);
189   }
190   
191   /* determine base version from etag in if-match header */
192   /*
193     Note: The -matchesRequestConditionInContext: already checks whether the
194           etag matches and returns an HTTP exception in case it doesn't.
195           We retrieve the etag again here to _ensure_ a transactionally save
196           commit.
197           (between the check and the update a change could have been done)
198   */
199   tmp  = [rq headerForKey:@"if-match"];
200   tmp  = [self parseETagList:tmp];
201   etag = nil;
202   if ([tmp count] > 0) {
203     if ([tmp count] > 1) {
204       /*
205         Note: we would have to attempt a save for _each_ of the etags being
206               passed in! In practice most WebDAV clients submit exactly one
207               etag.
208       */
209       [self warnWithFormat:
210               @"Got multiple if-match etags from client, only attempting to "
211               @"save with the first: %@", tmp];
212     }
213     
214     etag = [tmp objectAtIndex:0];
215   }
216   baseVersion = ([etag length] > 0)
217     ? [etag unsignedIntValue]
218     : 0 /* 0 means 'do not check' */;
219   
220   /* attempt a save */
221
222   if ((error = [self saveContentString: [rq contentAsString]
223                      baseVersion: baseVersion]) != nil)
224     return error;
225   
226   /* setup response */
227   
228   // TODO: this should be automatic in the SoDispatcher if we return nil?
229   [[_ctx response] setStatus:201 /* Created */];
230   
231   if ((etag = [self davEntityTag]) != nil)
232     [[_ctx response] setHeader:etag forKey:@"etag"];
233   
234   if (needsLocation) {
235     [[_ctx response] setHeader:[self baseURLInContext:_ctx] 
236                      forKey:@"location"];
237   }
238   
239   return [_ctx response];
240 }
241
242 /* E-Tags */
243
244 - (id) davEntityTag
245 {
246   // TODO: cache tag in ivar? => if you do, remember to flush after PUT
247   GCSFolder *folder;
248   char buf[64];
249   NSString *entityTag;
250   NSNumber *versionValue;
251   
252   folder = [self ocsFolder];
253   if (folder)
254     {
255       versionValue = [folder versionOfContentWithName: [self nameInContainer]];
256       sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]);
257       entityTag = [NSString stringWithCString: buf];
258     }
259   else
260     {
261       [self errorWithFormat:@"Did not find folder of content object."];
262       entityTag = nil;
263     }
264
265   return entityTag;
266 }
267
268 /* WebDAV */
269
270 - (NSException *) davMoveToTargetObject: (id) _target
271                                 newName: (NSString *) _name
272                               inContext: (id) _ctx
273 {
274   /*
275     Note: even for new objects we won't get a new name but a preinstantiated
276           object representing the new one.
277   */
278   [self logWithFormat:
279           @"TODO: move not implemented:\n  target:  %@\n  new name: %@",
280           _target, _name];
281   return [NSException exceptionWithHTTPStatus:405 /* not allowed */
282                       reason:@"this object cannot be copied via WebDAV"];
283 }
284
285 - (NSException *) davCopyToTargetObject: (id)_target
286                                 newName: (NSString *) _name
287                               inContext: (id) _ctx
288 {
289   /*
290     Note: even for new objects we won't get a new name but a preinstantiated
291           object representing the new one.
292   */
293   [self logWithFormat:
294           @"TODO: copy not implemented:\n  target:  %@\n  new name: %@",
295           _target, _name];
296   return [NSException exceptionWithHTTPStatus:405 /* not allowed */
297                       reason:@"this object cannot be copied via WebDAV"];
298 }
299
300 - (BOOL)davIsCollection {
301   return [self isFolderish];
302 }
303
304 /* acls */
305
306 - (NSArray *) aclUsers
307 {
308   return [container aclUsersForObjectAtPath: [self pathArrayToSoObject]];
309 }
310
311 - (NSArray *) aclsForUser: (NSString *) uid
312 {
313   NSMutableArray *acls;
314   NSArray *ownAcls, *containerAcls;
315
316   acls = [NSMutableArray array];
317   ownAcls = [container aclsForUser: uid
318                        forObjectAtPath: [self pathArrayToSoObject]];
319   [acls addObjectsFromArray: ownAcls];
320   containerAcls = [container aclsForUser: uid];
321   if ([containerAcls count] > 0)
322     {
323       if ([containerAcls containsObject: SOGoRole_ObjectCreator])
324         [acls addObject: SOGoRole_ObjectCreator];
325       if ([containerAcls containsObject: SOGoRole_ObjectEraser])
326         [acls addObject: SOGoRole_ObjectEraser];
327     }
328
329   return acls;
330 }
331
332 - (void) setRoles: (NSArray *) roles
333           forUser: (NSString *) uid
334 {
335   return [container setRoles: roles
336                     forUser: uid
337                     forObjectAtPath: [self pathArrayToSoObject]];
338 }
339
340 - (void) removeAclsForUsers: (NSArray *) users
341 {
342   return [container removeAclsForUsers: users
343                     forObjectAtPath: [self pathArrayToSoObject]];
344 }
345
346 - (NSString *) defaultUserID
347 {
348   return @"<default>";
349 }
350
351 /* message type */
352
353 - (NSString *) outlookMessageClass
354 {
355   return nil;
356 }
357
358 /* description */
359
360 - (void) appendAttributesToDescription: (NSMutableString *) _ms
361 {
362   [super appendAttributesToDescription:_ms];
363   
364   [_ms appendFormat:@" ocs=%@", [self ocsPath]];
365 }
366
367 @end /* SOGoContentObject */