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