]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoContentObject.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1026 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 <SOGo/SOGoUser.h>
25
26 #import "common.h"
27 #import "SOGoFolder.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   [self->content release];
40   [self->ocsPath release];
41   [super dealloc];
42 }
43
44 /* notifications */
45
46 - (void)sleep {
47   [self->content release]; self->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 ([self->ocsPath isEqualToString:_path])
59     return;
60   
61   if (self->ocsPath)
62     [self warnWithFormat:@"GCS path is already set! '%@'", _path];
63   
64   ASSIGNCOPY(self->ocsPath, _path);
65 }
66
67 - (NSString *)ocsPath {
68   if (self->ocsPath == nil) {
69     NSString *p;
70     
71     if ((p = [self ocsPathOfContainer]) != nil) {
72       if (![p hasSuffix:@"/"]) p = [p stringByAppendingString:@"/"];
73       p = [p stringByAppendingString:[self nameInContainer]];
74       self->ocsPath = [p copy];
75     }
76   }
77   return self->ocsPath;
78 }
79
80 - (NSString *)ocsPathOfContainer {
81   if (![[self container] respondsToSelector:@selector(ocsPath)])
82     return nil;
83
84   return [[self container] ocsPath];
85 }
86
87 - (GCSFolder *)ocsFolder {
88   if (![[self container] respondsToSelector:@selector(ocsFolder)])
89     return nil;
90   
91   return [[self container] ocsFolder];
92 }
93
94 /* content */
95
96 - (NSString *)contentAsString {
97   GCSFolder *folder;
98
99   if (self->content != nil)
100     return self->content;
101   
102   if ((folder = [self ocsFolder]) == nil) {
103     [self errorWithFormat:@"Did not find folder of content object."];
104     return nil;
105   }
106   
107   self->content = [[folder fetchContentWithName:[self nameInContainer]] copy];
108   return self->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(self->nameInContainer, tmp);
188     ASSIGN(self->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 /* security */
243 - (NSArray *) rolesOfUser: (NSString *) login
244                 inContext: (WOContext *) context
245 {
246   NSMutableArray *sogoRoles;
247   SOGoUser *user;
248
249   sogoRoles = [NSMutableArray new];
250   [sogoRoles autorelease];
251
252   if (![container nameExistsInFolder: nameInContainer])
253     {
254       user = [[SOGoUser alloc] initWithLogin: login roles: nil];
255       [sogoRoles addObjectsFromArray: [user rolesForObject: container
256                                             inContext: context]];
257       [user release];
258     }
259
260   return sogoRoles;
261 }
262
263 /* E-Tags */
264
265 - (id)davEntityTag {
266   // TODO: cache tag in ivar? => if you do, remember to flush after PUT
267   GCSFolder *folder;
268   char buf[64];
269   
270   if ((folder = [self ocsFolder]) == nil) {
271     [self errorWithFormat:@"Did not find folder of content object."];
272     return nil;
273   }
274   
275   sprintf(buf, "\"gcs%08d\"",
276           [[folder versionOfContentWithName:[self nameInContainer]]
277             unsignedIntValue]);
278   return [NSString stringWithCString:buf];
279 }
280
281 /* WebDAV */
282
283 - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
284   inContext:(id)_ctx
285 {
286   /*
287     Note: even for new objects we won't get a new name but a preinstantiated
288           object representing the new one.
289   */
290   [self logWithFormat:
291           @"TODO: move not implemented:\n  target:  %@\n  new name: %@",
292           _target, _name];
293   return [NSException exceptionWithHTTPStatus:405 /* not allowed */
294                       reason:@"this object cannot be copied via WebDAV"];
295 }
296
297 - (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
298   inContext:(id)_ctx
299 {
300   /*
301     Note: even for new objects we won't get a new name but a preinstantiated
302           object representing the new one.
303   */
304   [self logWithFormat:
305           @"TODO: copy not implemented:\n  target:  %@\n  new name: %@",
306           _target, _name];
307   return [NSException exceptionWithHTTPStatus:405 /* not allowed */
308                       reason:@"this object cannot be copied via WebDAV"];
309 }
310
311 - (BOOL)davIsCollection {
312   return [self isFolderish];
313 }
314
315 /* message type */
316
317 - (NSString *)outlookMessageClass {
318   return nil;
319 }
320
321 /* description */
322
323 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
324   [super appendAttributesToDescription:_ms];
325   
326   [_ms appendFormat:@" ocs=%@", [self ocsPath]];
327 }
328
329 @end /* SOGoContentObject */