mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-06-02 00:49:45 +00:00
see ChangeLog
Monotone-Revision: 9054022ef1ca8aeba6e34842d27d9b94ce002b89 Monotone-Author: dev-unix.inverse.qc.ca Monotone-Date: 2006-06-15T19:34:10 Monotone-Branch: ca.inverse.sogo
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
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 */
|
||||
Reference in New Issue
Block a user