f18c764ffa
Monotone-Revision: 9054022ef1ca8aeba6e34842d27d9b94ce002b89 Monotone-Author: dev-unix.inverse.qc.ca Monotone-Date: 2006-06-15T19:34:10 Monotone-Branch: ca.inverse.sogo
306 lines
8 KiB
Objective-C
306 lines
8 KiB
Objective-C
/*
|
|
Copyright (C) 2004-2005 SKYRIX Software AG
|
|
|
|
This file is part of OpenGroupware.org.
|
|
|
|
OGo is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
Free Software Foundation; either version 2, or (at your option) any
|
|
later version.
|
|
|
|
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with OGo; see the file COPYING. If not, write to the
|
|
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
*/
|
|
|
|
#include "SOGoContentObject.h"
|
|
#include "SOGoFolder.h"
|
|
#include "common.h"
|
|
#include <GDLContentStore/GCSFolder.h>
|
|
|
|
@interface SOGoContentObject(ETag)
|
|
- (NSArray *)parseETagList:(NSString *)_c;
|
|
@end
|
|
|
|
@implementation SOGoContentObject
|
|
|
|
// TODO: check superclass version
|
|
|
|
- (void)dealloc {
|
|
[self->content release];
|
|
[self->ocsPath release];
|
|
[super dealloc];
|
|
}
|
|
|
|
/* notifications */
|
|
|
|
- (void)sleep {
|
|
[self->content release]; self->content = nil;
|
|
[super sleep];
|
|
}
|
|
|
|
/* accessors */
|
|
|
|
- (BOOL)isFolderish {
|
|
return NO;
|
|
}
|
|
|
|
- (void)setOCSPath:(NSString *)_path {
|
|
if ([self->ocsPath isEqualToString:_path])
|
|
return;
|
|
|
|
if (self->ocsPath)
|
|
[self warnWithFormat:@"GCS path is already set! '%@'", _path];
|
|
|
|
ASSIGNCOPY(self->ocsPath, _path);
|
|
}
|
|
|
|
- (NSString *)ocsPath {
|
|
if (self->ocsPath == nil) {
|
|
NSString *p;
|
|
|
|
if ((p = [self ocsPathOfContainer]) != nil) {
|
|
if (![p hasSuffix:@"/"]) p = [p stringByAppendingString:@"/"];
|
|
p = [p stringByAppendingString:[self nameInContainer]];
|
|
self->ocsPath = [p copy];
|
|
}
|
|
}
|
|
return self->ocsPath;
|
|
}
|
|
|
|
- (NSString *)ocsPathOfContainer {
|
|
if (![[self container] respondsToSelector:@selector(ocsPath)])
|
|
return nil;
|
|
|
|
return [[self container] ocsPath];
|
|
}
|
|
|
|
- (GCSFolder *)ocsFolder {
|
|
if (![[self container] respondsToSelector:@selector(ocsFolder)])
|
|
return nil;
|
|
|
|
return [[self container] ocsFolder];
|
|
}
|
|
|
|
/* content */
|
|
|
|
- (NSString *)contentAsString {
|
|
GCSFolder *folder;
|
|
|
|
if (self->content != nil)
|
|
return self->content;
|
|
|
|
if ((folder = [self ocsFolder]) == nil) {
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
return nil;
|
|
}
|
|
|
|
self->content = [[folder fetchContentWithName:[self nameInContainer]] copy];
|
|
return self->content;
|
|
}
|
|
|
|
- (NSException *)saveContentString:(NSString *)_str
|
|
baseVersion:(unsigned int)_baseVersion
|
|
{
|
|
/* Note: "iCal multifolder saves" are implemented in the apt subclass! */
|
|
GCSFolder *folder;
|
|
NSException *ex;
|
|
|
|
if ((folder = [self ocsFolder]) == nil) {
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
return nil;
|
|
}
|
|
|
|
ex = [folder writeContent:_str toName:[self nameInContainer]
|
|
baseVersion:_baseVersion];
|
|
if (ex != nil) {
|
|
[self errorWithFormat:@"write failed: %@", ex];
|
|
return ex;
|
|
}
|
|
return nil;
|
|
}
|
|
- (NSException *)saveContentString:(NSString *)_str {
|
|
return [self saveContentString:_str baseVersion:0 /* don't check */];
|
|
}
|
|
|
|
- (NSException *)delete {
|
|
/* Note: "iCal multifolder saves" are implemented in the apt subclass! */
|
|
GCSFolder *folder;
|
|
NSException *ex;
|
|
|
|
// TODO: add precondition check? (or add DELETEAction?)
|
|
|
|
if ((folder = [self ocsFolder]) == nil) {
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
return nil;
|
|
}
|
|
|
|
if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
|
|
[self errorWithFormat:@"delete failed: %@", ex];
|
|
return ex;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
/* actions */
|
|
|
|
- (id)PUTAction:(WOContext *)_ctx {
|
|
WORequest *rq;
|
|
NSException *error;
|
|
unsigned int baseVersion;
|
|
id etag, tmp;
|
|
BOOL needsLocation;
|
|
|
|
if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
|
|
return error;
|
|
|
|
rq = [_ctx request];
|
|
|
|
/* check whether its a request to the 'special' 'new' location */
|
|
/*
|
|
Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as
|
|
object creations and will assign a server side identifier. Most
|
|
current GroupDAV clients rely on this behaviour, so we reproduce it
|
|
here.
|
|
A correct client would loop until it has a name which doesn't not
|
|
yet exist (by using if-none-match).
|
|
*/
|
|
needsLocation = NO;
|
|
tmp = [[self nameInContainer] stringByDeletingPathExtension];
|
|
if ([tmp isEqualToString:@"new"]) {
|
|
tmp = [[[self container] class] globallyUniqueObjectId];
|
|
needsLocation = YES;
|
|
|
|
[self debugWithFormat:
|
|
@"reassigned a new location for special new-location: %@", tmp];
|
|
|
|
/* kinda dangerous */
|
|
ASSIGNCOPY(self->nameInContainer, tmp);
|
|
ASSIGN(self->ocsPath, nil);
|
|
}
|
|
|
|
/* determine base version from etag in if-match header */
|
|
/*
|
|
Note: The -matchesRequestConditionInContext: already checks whether the
|
|
etag matches and returns an HTTP exception in case it doesn't.
|
|
We retrieve the etag again here to _ensure_ a transactionally save
|
|
commit.
|
|
(between the check and the update a change could have been done)
|
|
*/
|
|
tmp = [rq headerForKey:@"if-match"];
|
|
tmp = [self parseETagList:tmp];
|
|
etag = nil;
|
|
if ([tmp count] > 0) {
|
|
if ([tmp count] > 1) {
|
|
/*
|
|
Note: we would have to attempt a save for _each_ of the etags being
|
|
passed in! In practice most WebDAV clients submit exactly one
|
|
etag.
|
|
*/
|
|
[self warnWithFormat:
|
|
@"Got multiple if-match etags from client, only attempting to "
|
|
@"save with the first: %@", tmp];
|
|
}
|
|
|
|
etag = [tmp objectAtIndex:0];
|
|
}
|
|
baseVersion = ([etag length] > 0)
|
|
? [etag unsignedIntValue]
|
|
: 0 /* 0 means 'do not check' */;
|
|
|
|
/* attempt a save */
|
|
|
|
if ((error = [self saveContentString:[rq contentAsString]
|
|
baseVersion:baseVersion]) != nil)
|
|
return error;
|
|
|
|
/* setup response */
|
|
|
|
// TODO: this should be automatic in the SoDispatcher if we return nil?
|
|
[[_ctx response] setStatus:201 /* Created */];
|
|
|
|
if ((etag = [self davEntityTag]) != nil)
|
|
[[_ctx response] setHeader:etag forKey:@"etag"];
|
|
|
|
if (needsLocation) {
|
|
[[_ctx response] setHeader:[self baseURLInContext:_ctx]
|
|
forKey:@"location"];
|
|
}
|
|
|
|
return [_ctx response];
|
|
}
|
|
|
|
/* E-Tags */
|
|
|
|
- (id)davEntityTag {
|
|
// TODO: cache tag in ivar? => if you do, remember to flush after PUT
|
|
GCSFolder *folder;
|
|
char buf[64];
|
|
|
|
if ((folder = [self ocsFolder]) == nil) {
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
return nil;
|
|
}
|
|
|
|
sprintf(buf, "\"gcs%08d\"",
|
|
[[folder versionOfContentWithName:[self nameInContainer]]
|
|
unsignedIntValue]);
|
|
return [NSString stringWithCString:buf];
|
|
}
|
|
|
|
/* WebDAV */
|
|
|
|
- (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name
|
|
inContext:(id)_ctx
|
|
{
|
|
/*
|
|
Note: even for new objects we won't get a new name but a preinstantiated
|
|
object representing the new one.
|
|
*/
|
|
[self logWithFormat:
|
|
@"TODO: move not implemented:\n target: %@\n new name: %@",
|
|
_target, _name];
|
|
return [NSException exceptionWithHTTPStatus:405 /* not allowed */
|
|
reason:@"this object cannot be copied via WebDAV"];
|
|
}
|
|
|
|
- (NSException *)davCopyToTargetObject:(id)_target newName:(NSString *)_name
|
|
inContext:(id)_ctx
|
|
{
|
|
/*
|
|
Note: even for new objects we won't get a new name but a preinstantiated
|
|
object representing the new one.
|
|
*/
|
|
[self logWithFormat:
|
|
@"TODO: copy not implemented:\n target: %@\n new name: %@",
|
|
_target, _name];
|
|
return [NSException exceptionWithHTTPStatus:405 /* not allowed */
|
|
reason:@"this object cannot be copied via WebDAV"];
|
|
}
|
|
|
|
- (BOOL)davIsCollection {
|
|
return [self isFolderish];
|
|
}
|
|
|
|
/* message type */
|
|
|
|
- (NSString *)outlookMessageClass {
|
|
return nil;
|
|
}
|
|
|
|
/* description */
|
|
|
|
- (void)appendAttributesToDescription:(NSMutableString *)_ms {
|
|
[super appendAttributesToDescription:_ms];
|
|
|
|
[_ms appendFormat:@" ocs=%@", [self ocsPath]];
|
|
}
|
|
|
|
@end /* SOGoContentObject */
|