2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
22 #include "SOGoContentObject.h"
23 #include "SOGoFolder.h"
25 #include <GDLContentStore/GCSFolder.h>
27 @interface SOGoContentObject(ETag)
28 - (NSArray *)parseETagList:(NSString *)_c;
31 @implementation SOGoContentObject
33 // TODO: check superclass version
36 [self->content release];
37 [self->ocsPath release];
44 [self->content release]; self->content = nil;
54 - (void)setOCSPath:(NSString *)_path {
55 if ([self->ocsPath isEqualToString:_path])
59 [self warnWithFormat:@"GCS path is already set! '%@'", _path];
61 ASSIGNCOPY(self->ocsPath, _path);
64 - (NSString *)ocsPath {
65 if (self->ocsPath == nil) {
68 if ((p = [self ocsPathOfContainer]) != nil) {
69 if (![p hasSuffix:@"/"]) p = [p stringByAppendingString:@"/"];
70 p = [p stringByAppendingString:[self nameInContainer]];
71 self->ocsPath = [p copy];
77 - (NSString *)ocsPathOfContainer {
78 if (![[self container] respondsToSelector:@selector(ocsPath)])
81 return [[self container] ocsPath];
84 - (GCSFolder *)ocsFolder {
85 if (![[self container] respondsToSelector:@selector(ocsFolder)])
88 return [[self container] ocsFolder];
93 - (NSString *)contentAsString {
96 if (self->content != nil)
99 if ((folder = [self ocsFolder]) == nil) {
100 [self errorWithFormat:@"Did not find folder of content object."];
104 self->content = [[folder fetchContentWithName:[self nameInContainer]] copy];
105 return self->content;
108 - (NSException *)saveContentString:(NSString *)_str
109 baseVersion:(unsigned int)_baseVersion
111 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
115 if ((folder = [self ocsFolder]) == nil) {
116 [self errorWithFormat:@"Did not find folder of content object."];
120 ex = [folder writeContent:_str toName:[self nameInContainer]
121 baseVersion:_baseVersion];
123 [self errorWithFormat:@"write failed: %@", ex];
128 - (NSException *)saveContentString:(NSString *)_str {
129 return [self saveContentString:_str baseVersion:0 /* don't check */];
132 - (NSException *)delete {
133 /* Note: "iCal multifolder saves" are implemented in the apt subclass! */
137 // TODO: add precondition check? (or add DELETEAction?)
139 if ((folder = [self ocsFolder]) == nil) {
140 [self errorWithFormat:@"Did not find folder of content object."];
144 if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
145 [self errorWithFormat:@"delete failed: %@", ex];
153 - (id)PUTAction:(WOContext *)_ctx {
156 unsigned int baseVersion;
160 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
165 /* check whether its a request to the 'special' 'new' location */
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
171 A correct client would loop until it has a name which doesn't not
172 yet exist (by using if-none-match).
175 tmp = [[self nameInContainer] stringByDeletingPathExtension];
176 if ([tmp isEqualToString:@"new"]) {
177 tmp = [[[self container] class] globallyUniqueObjectId];
180 [self debugWithFormat:
181 @"reassigned a new location for special new-location: %@", tmp];
183 /* kinda dangerous */
184 ASSIGNCOPY(self->nameInContainer, tmp);
185 ASSIGN(self->ocsPath, nil);
188 /* determine base version from etag in if-match header */
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
194 (between the check and the update a change could have been done)
196 tmp = [rq headerForKey:@"if-match"];
197 tmp = [self parseETagList:tmp];
199 if ([tmp count] > 0) {
200 if ([tmp count] > 1) {
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
206 [self warnWithFormat:
207 @"Got multiple if-match etags from client, only attempting to "
208 @"save with the first: %@", tmp];
211 etag = [tmp objectAtIndex:0];
213 baseVersion = ([etag length] > 0)
214 ? [etag unsignedIntValue]
215 : 0 /* 0 means 'do not check' */;
219 if ((error = [self saveContentString:[rq contentAsString]
220 baseVersion:baseVersion]) != nil)
225 // TODO: this should be automatic in the SoDispatcher if we return nil?
226 [[_ctx response] setStatus:201 /* Created */];
228 if ((etag = [self davEntityTag]) != nil)
229 [[_ctx response] setHeader:etag forKey:@"etag"];
232 [[_ctx response] setHeader:[self baseURLInContext:_ctx]
236 return [_ctx response];
242 // TODO: cache tag in ivar? => if you do, remember to flush after PUT
246 if ((folder = [self ocsFolder]) == nil) {
247 [self errorWithFormat:@"Did not find folder of content object."];
251 sprintf(buf, "\"gcs%08d\"",
252 [[folder versionOfContentWithName:[self nameInContainer]]
254 return [NSString stringWithCString:buf];
259 - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
263 Note: even for new objects we won't get a new name but a preinstantiated
264 object representing the new one.
267 @"TODO: move not implemented:\n target: %@\n new name: %@",
269 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
270 reason:@"this object cannot be copied via WebDAV"];
273 - (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
277 Note: even for new objects we won't get a new name but a preinstantiated
278 object representing the new one.
281 @"TODO: copy not implemented:\n target: %@\n new name: %@",
283 return [NSException exceptionWithHTTPStatus:405 /* not allowed */
284 reason:@"this object cannot be copied via WebDAV"];
287 - (BOOL)davIsCollection {
288 return [self isFolderish];
293 - (NSString *)outlookMessageClass {
299 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
300 [super appendAttributesToDescription:_ms];
302 [_ms appendFormat:@" ocs=%@", [self ocsPath]];
305 @end /* SOGoContentObject */