diff --git a/OpenChange/GNUmakefile b/OpenChange/GNUmakefile index 7054f8998..91b75ec0e 100644 --- a/OpenChange/GNUmakefile +++ b/OpenChange/GNUmakefile @@ -80,6 +80,7 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreCalendarContext.m \ MAPIStoreCalendarFolder.m \ MAPIStoreCalendarMessage.m \ + MAPIStoreCalendarEmbeddedMessage.m \ MAPIStoreCalendarMessageTable.m \ MAPIStoreRecurrenceUtils.m \ \ @@ -114,11 +115,13 @@ $(SOGOBACKEND)_OBJC_FILES += \ NSString+MAPIStore.m \ NSValue+MAPIStore.m \ \ - EOBitmaskQualifier.m \ + iCalEvent+MAPIStore.m \ \ GCSSpecialQueries+OpenChange.m\ \ - EOQualifier+MAPI.m + EOQualifier+MAPI.m \ + \ + EOBitmaskQualifier.m $(SOGOBACKEND)_RESOURCE_FILES += \ diff --git a/OpenChange/MAPIStoreAppointmentWrapper.m b/OpenChange/MAPIStoreAppointmentWrapper.m index e813287c8..c6c707694 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.m +++ b/OpenChange/MAPIStoreAppointmentWrapper.m @@ -1,6 +1,6 @@ /* MAPIStoreAppointmentWrapper.m - this file is part of SOGo * - * Copyright (C) 2011 Inverse inc + * Copyright (C) 2011, 2012 Inverse inc * * Author: Wolfgang Sourdeau * @@ -26,6 +26,7 @@ #import #import #import +#import #import #import #import @@ -432,7 +433,7 @@ static NSCharacterSet *hexCharacterSet = nil; } - (int) getPidTagIconIndex: (void **) data // TODO - inMemCtx: (TALLOC_CTX *) memCtx + inMemCtx: (TALLOC_CTX *) memCtx { uint32_t longValue; @@ -855,6 +856,31 @@ static NSCharacterSet *hexCharacterSet = nil; inMemCtx: memCtx]; } +/* sender representing */ +- (int) getPidTagSentRepresentingEmailAddress: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + return [self getPidTagSenderEmailAddress: data inMemCtx: memCtx]; +} + +- (int) getPidTagSentRepresentingAddressType: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + return [self getSMTPAddrType: data inMemCtx: memCtx]; +} + +- (int) getPidTagSentRepresentingName: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + return [self getPidTagSenderName: data inMemCtx: memCtx]; +} + +- (int) getPidTagSentRepresentingEntryId: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + return [self getPidTagSenderEntryId: data inMemCtx: memCtx]; +} + /* attendee */ - (int) getPidTagReceivedByEmailAddress: (void **) data inMemCtx: (TALLOC_CTX *) memCtx @@ -1057,14 +1083,6 @@ static NSCharacterSet *hexCharacterSet = nil; return rc; } -- (int) getPidLidIsRecurring: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx -{ - *data = MAPIBoolValue (memCtx, [event isRecurrent]); - - return MAPISTORE_SUCCESS; -} - - (int) getPidLidRecurring: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { @@ -1073,15 +1091,57 @@ static NSCharacterSet *hexCharacterSet = nil; return MAPISTORE_SUCCESS; } -- (struct SBinary_short *) _computeAppointmentRecurInMemCtx: (TALLOC_CTX *) memCtx +- (int) getPidLidIsRecurring: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + *data = MAPIBoolValue (memCtx, + [event isRecurrent] + || ([event recurrenceId] != nil)); + return MAPISTORE_SUCCESS; +} + +- (int) getPidLidIsException: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + *data = MAPIBoolValue (memCtx, [event recurrenceId] != nil); + + return MAPISTORE_SUCCESS; +} + +- (void) _fillExceptionInfo: (struct ExceptionInfo *) exceptionInfo + withException: (iCalEvent *) exceptionEvent +{ + memset (exceptionInfo, 0, sizeof (struct ExceptionInfo)); + exceptionInfo->OverrideFlags = 0; + exceptionInfo->StartDateTime = [[exceptionEvent startDate] asMinutesSince1601]; + exceptionInfo->EndDateTime = [[exceptionEvent endDate] asMinutesSince1601]; + exceptionInfo->OriginalStartDate = [[[exceptionEvent recurrenceId] hour: 0 + minute: 0 + second: 0] + asMinutesSince1601]; +} + +- (void) _fillExtendedException: (struct ExtendedException *) extendedException + withException: (iCalEvent *) exceptionEvent +{ + memset (extendedException, 0, sizeof (struct ExtendedException)); + extendedException->ChangeHighlight.Size = sizeof (uint32_t); + extendedException->ChangeHighlight.Value = 1 << 31 | 1 << 30; +} + +// - (struct SBinary_short *) _computeAppointmentRecurInMemCtx: (TALLOC_CTX *) memCtx +- (struct Binary_r *) _computeAppointmentRecurInMemCtx: (TALLOC_CTX *) memCtx { struct AppointmentRecurrencePattern *arp; struct Binary_r *bin; - struct SBinary_short *sBin; + // struct SBinary_short *sBin; NSCalendarDate *firstStartDate; iCalRecurrenceRule *rule; NSUInteger startMinutes; + NSArray *events, *exceptions; + iCalEvent *exceptionEvent; + NSUInteger count, max; rule = [[event recurrenceRules] objectAtIndex: 0]; @@ -1090,7 +1150,7 @@ static NSCharacterSet *hexCharacterSet = nil; { [firstStartDate setTimeZone: timeZone]; - arp = talloc_zero (memCtx, struct AppointmentRecurrencePattern); + arp = talloc_zero (NULL, struct AppointmentRecurrencePattern); [rule fillRecurrencePattern: &arp->RecurrencePattern withEvent: event inTimeZone: timeZone @@ -1105,17 +1165,31 @@ static NSCharacterSet *hexCharacterSet = nil; + (NSUInteger) ([event durationAsTimeInterval] / 60)); - arp->ExceptionCount = 0; + events = [[event parent] events]; + exceptions + = [events subarrayWithRange: NSMakeRange (1, [events count] - 1)]; + max = [exceptions count]; + arp->ExceptionCount = max; + arp->ExceptionInfo = talloc_array (memCtx, struct ExceptionInfo, max); + arp->ExtendedException = talloc_array (memCtx, struct ExtendedException, max); + for (count = 0; count < max; count++) + { + exceptionEvent = [exceptions objectAtIndex: count]; + [self _fillExceptionInfo: arp->ExceptionInfo + count + withException: exceptionEvent]; + [self _fillExtendedException: arp->ExtendedException + count + withException: exceptionEvent]; + } arp->ReservedBlock1Size = 0; + arp->ReservedBlock2Size = 0; /* Currently ignored in property.idl: arp->ReservedBlock2Size = 0; */ - /* convert struct to blob */ - sBin = talloc_zero (memCtx, struct SBinary_short); - bin = set_AppointmentRecurrencePattern (sBin, arp); - sBin->cb = bin->cb; - sBin->lpb = bin->lpb; + // sBin = talloc_zero (memCtx, struct SBinary_short); + bin = set_AppointmentRecurrencePattern (memCtx, arp); + // sBin->cb = bin->cb; + // sBin->lpb = bin->lpb; talloc_free (arp); // DEBUG(5, ("To client:\n")); @@ -1124,10 +1198,11 @@ static NSCharacterSet *hexCharacterSet = nil; else { [self errorWithFormat: @"no first occurrence found in rule: %@", rule]; - sBin = NULL; + // bin = NULL; + bin = NULL; } - return sBin; + return bin; } - (int) getPidLidAppointmentRecur: (void **) data diff --git a/OpenChange/MAPIStoreCalendarAttachment.h b/OpenChange/MAPIStoreCalendarAttachment.h index d95c3f560..c97f752d5 100644 --- a/OpenChange/MAPIStoreCalendarAttachment.h +++ b/OpenChange/MAPIStoreCalendarAttachment.h @@ -25,7 +25,15 @@ #import "MAPIStoreAttachment.h" -@interface MAPIStoreCalendarAttachment : MAPIStoreAttachment +@class iCalEvent; + +@interface MAPIStoreCalendarAttachment : MAPIStoreAttachment +{ + iCalEvent *event; +} + +- (void) setEvent: (iCalEvent *) newEvent; +- (iCalEvent *) event; @end diff --git a/OpenChange/MAPIStoreCalendarAttachment.m b/OpenChange/MAPIStoreCalendarAttachment.m index 1ba40ac17..a6d7848a2 100644 --- a/OpenChange/MAPIStoreCalendarAttachment.m +++ b/OpenChange/MAPIStoreCalendarAttachment.m @@ -20,9 +20,14 @@ * Boston, MA 02111-1307, USA. */ -#import "MAPIStoreTypes.h" +#import -#import "MAPIStoreEmbeddedMessage.h" +#import +#import + +#import "iCalEvent+MAPIStore.h" +#import "MAPIStoreTypes.h" +#import "MAPIStoreCalendarEmbeddedMessage.h" #import "MAPIStoreCalendarAttachment.h" @@ -34,6 +39,32 @@ @implementation MAPIStoreCalendarAttachment +- (id) init +{ + if ((self = [super init])) + { + event = nil; + } + + return self; +} + +- (void) dealloc +{ + [event release]; + [super dealloc]; +} + +- (void) setEvent: (iCalEvent *) newEvent +{ + ASSIGN (event, newEvent); +} + +- (iCalEvent *) event +{ + return event; +} + - (int) getPidTagAttachmentHidden: (void **) data inMemCtx: (TALLOC_CTX *) localMemCtx { @@ -53,7 +84,7 @@ - (int) getPidTagAttachMethod: (void **) data inMemCtx: (TALLOC_CTX *) localMemCtx { - *data = MAPILongValue (localMemCtx, 0x00000005); /* afEmbeddedMessage */ + *data = MAPILongValue (localMemCtx, afEmbeddedMessage); return MAPISTORE_SUCCESS; } @@ -63,21 +94,24 @@ // case PidTagExceptionReplaceTime: /* subclasses */ -- (MAPIStoreEmbeddedMessage *) openEmbeddedMessage +- (MAPIStoreCalendarEmbeddedMessage *) openEmbeddedMessage { - MAPIStoreEmbeddedMessage *msg; + MAPIStoreCalendarEmbeddedMessage *msg; - // if (isNew) - msg = nil; - // else - // msg = nil; + msg = [MAPIStoreCalendarEmbeddedMessage + mapiStoreObjectInContainer: self]; return msg; } -- (MAPIStoreEmbeddedMessage *) createEmbeddedMessage +- (MAPIStoreCalendarEmbeddedMessage *) createEmbeddedMessage { - return [MAPIStoreEmbeddedMessage embeddedMessageWithAttachment: self]; + MAPIStoreCalendarEmbeddedMessage *msg; + + msg = [self openEmbeddedMessage]; + [msg setIsNew: YES]; + + return msg; } @end diff --git a/OpenChange/MAPIStoreCalendarEmbeddedMessage.h b/OpenChange/MAPIStoreCalendarEmbeddedMessage.h new file mode 100644 index 000000000..3e27c575c --- /dev/null +++ b/OpenChange/MAPIStoreCalendarEmbeddedMessage.h @@ -0,0 +1,34 @@ +/* MAPIStoreCalendarEmbeddedMessage.h - this file is part of SOGo + * + * Copyright (C) 2012 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef MAPISTORECALENDAREMBEDDEDMESSAGE_H +#define MAPISTORECALENDAREMBEDDEDMESSAGE_H + +#import "MAPIStoreEmbeddedMessage.h" + +@class MAPIStoreAppointmentWrapper; + +@interface MAPIStoreCalendarEmbeddedMessage : MAPIStoreEmbeddedMessage + +@end + +#endif /* MAPISTORECALENDAREMBEDDEDMESSAGE_H */ diff --git a/OpenChange/MAPIStoreCalendarEmbeddedMessage.m b/OpenChange/MAPIStoreCalendarEmbeddedMessage.m new file mode 100644 index 000000000..361b10d6f --- /dev/null +++ b/OpenChange/MAPIStoreCalendarEmbeddedMessage.m @@ -0,0 +1,162 @@ +/* MAPIStoreCalendarEmbeddedMessage.m - this file is part of SOGo + * + * Copyright (C) 2012 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include + +#import +#import + +#import + +#import +#import "iCalEvent+MAPIStore.h" + +#import "MAPIStoreAppointmentWrapper.h" +#import "MAPIStoreCalendarAttachment.h" +#import "MAPIStoreContext.h" +#import "MAPIStoreUserContext.h" +#import "MAPIStoreTypes.h" +#import "NSObject+MAPIStore.h" + +#import "MAPIStoreCalendarEmbeddedMessage.h" + +#include + +@implementation MAPIStoreCalendarEmbeddedMessage + +- (id) initInContainer: (id) newContainer +{ + MAPIStoreContext *context; + MAPIStoreUserContext *userContext; + MAPIStoreAppointmentWrapper *appointmentWrapper; + + if ((self = [super initInContainer: newContainer])) + { + context = [self context]; + userContext = [self userContext]; + appointmentWrapper + = [MAPIStoreAppointmentWrapper + wrapperWithICalEvent: [newContainer event] + andUser: [userContext sogoUser] + andSenderEmail: nil + inTimeZone: [userContext timeZone] + withConnectionInfo: [context connectionInfo]]; + [self addProxy: appointmentWrapper]; + } + + return self; +} + +- (NSDate *) creationTime +{ + return [[container event] created]; +} + +- (NSDate *) lastModificationTime +{ + return [[container event] lastModified]; +} + +- (void) getMessageData: (struct mapistore_message **) dataPtr + inMemCtx: (TALLOC_CTX *) memCtx +{ + struct mapistore_message *msgData; + + [super getMessageData: &msgData inMemCtx: memCtx]; + + /* HACK: we know the first (and only) proxy is our appointment wrapper + instance, but this might not always be true */ + [[proxies objectAtIndex: 0] fillMessageData: msgData + inMemCtx: memCtx]; + *dataPtr = msgData; +} + +- (int) getPidTagMessageClass: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + *data = talloc_strdup (memCtx, "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"); + + return MAPISTORE_SUCCESS; +} + +- (int) getPidTagProcessed: (void **) data inMemCtx: (TALLOC_CTX *) memCtx +{ + return [self getYes: data inMemCtx: memCtx]; +} + +- (int) getPidTagResponseRequested: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + return [self getYes: data inMemCtx: memCtx]; +} + +- (void) save +{ +// (gdb) po embeddedMessage->properties +// 2442592320 = "2012-07-11 22:30:00 +0000"; +// 2448359488 = "2012-07-11 22:30:00 +0000"; +// 2442723392 = "2012-07-11 22:30:00 +0000"; +// 2442068032 = "2012-07-11 22:30:00 +0000"; +// 2441740352 = "2012-07-11 23:00:00 +0000"; +// 131083 = 1; 2442330115 = 2; +// 235339779 = 9; +// 6291520 = "2012-07-11 16:00:00 +0000"; +// 2442526784 = "2012-07-11 23:00:00 +0000"; +// 2818059 = 0; +// 1703967 = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"; +// 3538947 = 0; +// 1071513603 = 28591; 805830720 = "2012-07-10 16:42:00 +0000"; +// 2485977346 = <02013000 02001500 45006100 73007400 +// 65007200 6e002000 53007400 61006e00 64006100 72006400 20005400 69006d00 +// 65000200 02013e00 0000d607 00000000 00000000 00000000 00002c01 00000000 +// 0000c4ff ffff0000 0a000000 05000200 00000000 00000000 04000000 01000200 +// 00000000 00000201 3e000200 d7070000 00000000 00000000 00000000 2c010000 +// 00000000 c4ffffff 00000b00 00000100 02000000 00000000 00000300 00000200 +// 02000000 00000000>; 2454257728 = "2012-07-11 16:00:00 +0000"; 2442985475 = +// 118330; 1507331 = 1; 805765184 = "2012-07-09 18:32:00 +0000"; 2442657856 = +// "2012-07-11 23:00:00 +0000"; 2443051039 = "11.0"; 236912651 = 1; 2485911810 = +// <02013000 02001500 45006100 73007400 65007200 6e002000 53007400 61006e00 +// 64006100 72006400 20005400 69006d00 65000200 02013e00 0000d607 00000000 +// 00000000 00000000 00002c01 00000000 0000c4ff ffff0000 0a000000 05000200 +// 00000000 00000000 04000000 01000200 00000000 00000201 3e000200 d7070000 +// 00000000 00000000 00000000 2c010000 00000000 c4ffffff 00000b00 00000100 +// 02000000 00000000 00000300 00000200 02000000 00000000>; 2441543683 = 30; +// 2442068032 = "2012-07-11 22:30:00 +0000"; +// 1073348639 = "OpenChange User"; +// 806027522 = <2d64f6f5 89a59243 992d29d1 49173b3a>; 6357056 = "2012-07-11 +// 16:30:00 +0000"; +// */ + +// // 0x92490040 = 2454257728 + +// } + + SOGoUser *activeUser; + + activeUser = [[self context] activeUser]; + + [[container event] updateFromMAPIProperties: properties + inUserContext: [self userContext] + withActiveUser: activeUser]; +} + +@end diff --git a/OpenChange/MAPIStoreCalendarMessage.h b/OpenChange/MAPIStoreCalendarMessage.h index ff942e6cf..a98149ac2 100644 --- a/OpenChange/MAPIStoreCalendarMessage.h +++ b/OpenChange/MAPIStoreCalendarMessage.h @@ -25,9 +25,15 @@ #import "MAPIStoreGCSMessage.h" +@class iCalCalendar; +@class iCalEvent; @class MAPIStoreAppointmentWrapper; @interface MAPIStoreCalendarMessage : MAPIStoreGCSMessage +{ + iCalCalendar *calendar; + iCalEvent *masterEvent; +} @end diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index 904887098..2ad110bbb 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -49,6 +49,7 @@ #import #import +#import "iCalEvent+MAPIStore.h" #import "MAPIStoreAppointmentWrapper.h" #import "MAPIStoreCalendarAttachment.h" #import "MAPIStoreCalendarFolder.h" @@ -103,32 +104,63 @@ return MAPISTORE_SUCCESS; } +- (id) init +{ + if ((self = [super init])) + { + calendar = nil; + masterEvent = nil; + } + + return self; +} + - (id) initWithSOGoObject: (id) newSOGoObject inContainer: (MAPIStoreObject *) newFolder { - iCalEvent *event; MAPIStoreContext *context; MAPIStoreUserContext *userContext; + iCalCalendar *origCalendar; MAPIStoreAppointmentWrapper *appointmentWrapper; - + if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newFolder])) { - event = [sogoObject component: NO secure: YES]; + if ([newSOGoObject isNew]) + { + ASSIGN (calendar, [iCalCalendar groupWithTag: @"vcalendar"]); + [calendar setVersion: @"2.0"]; + [calendar setProdID: @"-//Inverse inc.//OpenChange+SOGo//EN"]; + masterEvent = [iCalEvent groupWithTag: @"vevent"]; + [calendar addChild: masterEvent]; + [masterEvent setCreated: [NSCalendarDate date]]; + } + else + { + origCalendar = [sogoObject calendar: YES secure: YES]; + calendar = [origCalendar mutableCopy]; + masterEvent = [[calendar events] objectAtIndex: 0]; + } context = [self context]; userContext = [self userContext]; appointmentWrapper - = [MAPIStoreAppointmentWrapper wrapperWithICalEvent: event + = [MAPIStoreAppointmentWrapper wrapperWithICalEvent: masterEvent andUser: [userContext sogoUser] andSenderEmail: nil inTimeZone: [userContext timeZone] withConnectionInfo: [context connectionInfo]]; [self addProxy: appointmentWrapper]; } - + return self; } +- (void) dealloc +{ + [calendar release]; + [super dealloc]; +} + /* getters */ - (int) getPidLidFInvited: (void **) data inMemCtx: (TALLOC_CTX *) memCtx @@ -177,6 +209,7 @@ instance, but this might not always be true */ [[proxies objectAtIndex: 0] fillMessageData: msgData inMemCtx: memCtx]; + *dataPtr = msgData; } @@ -209,28 +242,28 @@ // - (int) getPidTagReceivedByAddressType: (void **) data // inMemCtx: (TALLOC_CTX *) memCtx // { -// return [[self appointmentWrapper] getPidTagReceivedByAddressType: data +// return [appointmentWrapper getPidTagReceivedByAddressType: data // inMemCtx: memCtx]; // } // - (int) getPidTagReceivedByEmailAddress: (void **) data // inMemCtx: (TALLOC_CTX *) memCtx // { -// return [[self appointmentWrapper] getPidTagReceivedByEmailAddress: data +// return [appointmentWrapper getPidTagReceivedByEmailAddress: data // inMemCtx: memCtx]; // } // - (int) getPidTagReceivedByName: (void **) data // inMemCtx: (TALLOC_CTX *) memCtx // { -// return [[self appointmentWrapper] getPidTagReceivedByName: data +// return [appointmentWrapper getPidTagReceivedByName: data // inMemCtx: memCtx]; // } // - (int) getPidTagReceivedByEntryId: (void **) data // inMemCtx: (TALLOC_CTX *) memCtx // { -// return [[self appointmentWrapper] getPidTagReceivedByEntryId: data +// return [appointmentWrapper getPidTagReceivedByEntryId: data // inMemCtx: memCtx]; // } @@ -265,28 +298,6 @@ return [self getYes: data inMemCtx: memCtx]; } -/* attendee */ -- (void) _setupRecurrenceInCalendar: (iCalCalendar *) calendar - withEvent: (iCalEvent *) event - fromData: (NSData *) mapiRecurrenceData -{ - struct Binary_r *blob; - struct AppointmentRecurrencePattern *pattern; - NSMutableArray *otherEvents; - - /* cleanup */ - otherEvents = [[calendar events] mutableCopy]; - [otherEvents removeObject: event]; - [calendar removeChildren: otherEvents]; - [otherEvents release]; - - blob = [mapiRecurrenceData asBinaryInMemCtx: NULL]; - pattern = get_AppointmentRecurrencePattern (blob, blob); - [calendar setupRecurrenceWithMasterEntity: event - fromRecurrencePattern: &pattern->RecurrencePattern]; - talloc_free (blob); -} - - (NSString *) _uidFromGlobalObjectId { NSData *objectId; @@ -296,7 +307,8 @@ /* NOTE: we only handle the generic case at the moment, see MAPIStoreAppointmentWrapper */ - objectId = [properties objectForKey: MAPIPropertyKey (PidLidGlobalObjectId)]; + objectId = [properties + objectForKey: MAPIPropertyKey (PidLidGlobalObjectId)]; if (objectId) { length = [objectId length]; @@ -360,50 +372,6 @@ ASSIGN (sogoObject, newObject); } -- (void) _setupAlarmDataInEvent: (iCalEvent *) newEvent -{ - NSArray *alarms; - iCalAlarm *currentAlarm, *alarm = nil; - iCalTrigger *trigger; - NSNumber *delta; - NSString *action; - NSUInteger count, max; - - /* find and remove first display alarm */ - alarms = [newEvent alarms]; - max = [alarms count]; - for (count = 0; !alarm && count < max; count++) - { - currentAlarm = [alarms objectAtIndex: count]; - action = [[currentAlarm action] lowercaseString]; - if (!action || [action isEqualToString: @"display"]) - alarm = currentAlarm; - } - - if (alarm) - [newEvent removeChild: alarm]; - - if ([[properties objectForKey: MAPIPropertyKey (PidLidReminderSet)] - boolValue]) - { - delta - = [properties objectForKey: MAPIPropertyKey (PidLidReminderDelta)]; - if (delta) - { - alarm = [iCalAlarm new]; - [alarm setAction: @"DISPLAY"]; - trigger = [iCalTrigger elementWithTag: @"trigger"]; - [trigger setValueType: @"DURATION"]; - [trigger - setSingleValue: [NSString stringWithFormat: @"-PT%@M", delta] - forKey: @""]; - [alarm setTrigger: trigger]; - [newEvent addToAlarms: alarm]; - [alarm release]; - } - } -} - - (BOOL) subscriberCanReadMessage { NSArray *roles; @@ -429,20 +397,9 @@ return rc; } -- (void) save { - iCalCalendar *vCalendar; - BOOL isAllDay; - iCalDateTime *start, *end; - iCalTimeZone *tz; - NSCalendarDate *now; - NSString *uid, *content, *tzName, *priority, *newParticipationStatus = nil; iCalEvent *newEvent; // iCalPerson *userPerson; - NSUInteger responseStatus = 0; - NSInteger tzOffset; - SOGoUser *activeUser, *ownerUser; - id value; if (isNew) { @@ -450,397 +407,31 @@ if (uid) { /* Hack required because of what's explained in oxocal 3.1.4.7.1: - basically, Outlook creates a copy of the event and then removes the - old instance. We perform a trickery to avoid performing those - operations in the backend, in a way that enables us to recover the - initial instance and act solely on it. */ + basically, Outlook creates a copy of the event and then removes + the old instance. We perform a trickery to avoid performing those + operations in the backend, in a way that enables us to recover + the initial instance and act solely on it. */ [self _fixupAppointmentObjectWithUID: uid]; } else uid = [SOGoObject globallyUniqueObjectId]; + [masterEvent setUid: uid]; + [sogoObject setNameInContainer: + [NSString stringWithFormat: @"%@.ics", uid]]; } - [self logWithFormat: @"-save, event props:"]; + // [self logWithFormat: @"-save, event props:"]; // MAPIStoreDumpMessageProperties (newProperties); - now = [NSCalendarDate date]; + // now = [NSCalendarDate date]; - content = [sogoObject contentAsString]; - if (![content length]) - { - newEvent = [sogoObject component: YES secure: NO]; - vCalendar = [newEvent parent]; - [vCalendar setProdID: @"-//Inverse inc.//OpenChange+SOGo//EN"]; - [newEvent setCreated: now]; - // CREATED = PidTagCreationTime - value = [properties objectForKey: MAPIPropertyKey (PidTagCreationTime)]; - if (value) - [newEvent setCreated: value]; - [newEvent setUid: uid]; - content = [vCalendar versitString]; - } + activeUser = [[self context] activeUser]; + [masterEvent updateFromMAPIProperties: properties + inUserContext: [self userContext] + withActiveUser: activeUser]; + [sogoObject updateContentWithCalendar: calendar + fromRequest: nil]; - vCalendar = [iCalCalendar parseSingleFromSource: content]; - newEvent = [[vCalendar events] objectAtIndex: 0]; - - // DTSTAMP = PidLidOwnerCriticalChange or PidLidAttendeeCriticalChange - value = [properties objectForKey: MAPIPropertyKey (PidLidOwnerCriticalChange)]; - if (!value || [value isNever]) - value = now; - [newEvent setTimeStampAsDate: value]; - - // LAST-MODIFIED = PidTagLastModificationTime - value = [properties objectForKey: MAPIPropertyKey (PidTagLastModificationTime)]; - if (!value) - value = now; - [newEvent setLastModified: value]; - - // summary - value = [properties - objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)]; - if (value) - [newEvent setSummary: value]; - - // Location - value = [properties objectForKey: MAPIPropertyKey (PidLidLocation)]; - if (value) - [newEvent setLocation: value]; - - isAllDay = [newEvent isAllDay]; - value = [properties - objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)]; - if (value) - isAllDay = [value boolValue]; - if (!isAllDay) - { - tzName = [[[self userContext] timeZone] name]; - tz = [iCalTimeZone timeZoneForName: tzName]; - [vCalendar addTimeZone: tz]; - } - else - tz = nil; - - // start - value = [properties objectForKey: MAPIPropertyKey (PR_START_DATE)]; - if (!value) - value = [properties - objectForKey: MAPIPropertyKey (PidLidAppointmentStartWhole)]; - if (value) - { - start = (iCalDateTime *) [newEvent uniqueChildWithTag: @"dtstart"]; - [start setTimeZone: tz]; - if (isAllDay) - { - [start setDate: value]; - [start setTimeZone: nil]; - } - else - { - tzOffset = [[value timeZone] secondsFromGMTForDate: value]; - value = [value dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: tzOffset]; - [start setDateTime: value]; - } - } - - /* end */ - value = [properties objectForKey: MAPIPropertyKey (PR_END_DATE)]; - if (!value) - value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentEndWhole)]; - if (value) - { - end = (iCalDateTime *) [newEvent uniqueChildWithTag: @"dtend"]; - [end setTimeZone: tz]; - if (isAllDay) - { - [end setDate: value]; - [end setTimeZone: nil]; - } - else - { - tzOffset = [[value timeZone] secondsFromGMTForDate: value]; - value = [value dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: tzOffset]; - [end setDateTime: value]; - } - } - - /* priority */ - value = [properties objectForKey: MAPIPropertyKey(PR_IMPORTANCE)]; - if (value) - { - switch ([value intValue]) - { - case 0: // IMPORTANCE_LOW - priority = @"9"; - break; - case 2: // IMPORTANCE_HIGH - priority = @"1"; - break; - default: // IMPORTANCE_NORMAL - priority = @"5"; - } - } - else - priority = @"0"; // None - [newEvent setPriority: priority]; - - /* show time as free/busy/tentative/out of office. Possible values are: - 0x00000000 - olFree - 0x00000001 - olTentative - 0x00000002 - olBusy - 0x00000003 - olOutOfOffice */ - value = [properties objectForKey: MAPIPropertyKey(PidLidBusyStatus)]; - if (value) - { - switch ([value intValue]) - { - case 0: - [newEvent setTransparency: @"TRANSPARENT"]; - break; - case 1: - case 2: - case 3: - default: - [newEvent setTransparency: @"OPAQUE"]; - } - } - - /* Comment */ - value = [properties objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)]; - if (!value) - { - value = [properties objectForKey: MAPIPropertyKey (PR_HTML)]; - if (value) - { - value = [[NSString alloc] initWithData: value - encoding: NSUTF8StringEncoding]; - [value autorelease]; - value = [value htmlToText]; - } - } - if (value) - { - if ([value length] == 0 || [value isEqualToString: @"\\n"]) - value = nil; - [newEvent setComment: value]; - } - - /* recurrence */ - value = [properties - objectForKey: MAPIPropertyKey (PidLidAppointmentRecur)]; - if (value) - [self _setupRecurrenceInCalendar: vCalendar - withEvent: newEvent - fromData: value]; - - /* alarm */ - [self _setupAlarmDataInEvent: newEvent]; - - // Organizer - value = [properties objectForKey: @"recipients"]; - if (value) - { - NSArray *recipients; - NSDictionary *dict; - NSString *orgEmail, *sentBy, *attEmail; - iCalPerson *person; - iCalPersonPartStat newPartStat; - NSNumber *flags, *trackStatus; - int i, effective; - BOOL organizerIsSet = NO; - - [newEvent setOrganizer: nil]; - [newEvent removeAllAttendees]; - - recipients = [value objectForKey: @"to"]; - effective = 0; - for (i = 0; i < [recipients count]; i++) - { - dict = [recipients objectAtIndex: i]; - person = [iCalPerson new]; - [person setCn: [dict objectForKey: @"fullName"]]; - attEmail = [dict objectForKey: @"email"]; - [person setEmail: attEmail]; - - flags = [dict objectForKey: MAPIPropertyKey (PR_RECIPIENT_FLAGS)]; - if (!flags) - { - [self logWithFormat: - @"no recipient flags specified: skipping recipient"]; - continue; - } - - if (([flags unsignedIntValue] & 0x0002)) /* recipOrganizer */ - { - [newEvent setOrganizer: person]; - organizerIsSet = YES; - [self logWithFormat: @"organizer set via recipient flags"]; - } - else - { - BOOL isOrganizer = NO; - - // /* Work-around: it happens that Outlook still passes the - // organizer as a recipient, maybe because of a feature - // documented in a pre-mesozoic PDF still buried in a - // cavern... In that case we remove it, and we keep the - // number of effective recipients in "effective". If the - // total is 0, we remove the "ORGANIZER" too. */ - // if ([attEmail isEqualToString: orgEmail]) - // { - // [self logWithFormat: - // @"avoiding setting organizer as recipient"]; - // continue; - // } - - trackStatus = [dict objectForKey: MAPIPropertyKey (PidTagRecipientTrackStatus)]; - if (trackStatus) - { - /* FIXME: we should provide a data converter between OL - partstats and SOGo */ - switch ([trackStatus unsignedIntValue]) - { - case 0x01: /* respOrganized */ - isOrganizer = YES; - break; - case 0x02: /* respTentative */ - newPartStat = iCalPersonPartStatTentative; - break; - case 0x03: /* respAccepted */ - newPartStat = iCalPersonPartStatAccepted; - break; - case 0x04: /* respDeclined */ - newPartStat = iCalPersonPartStatDeclined; - break; - default: - newPartStat = iCalPersonPartStatNeedsAction; - } - - if (isOrganizer) - { - [newEvent setOrganizer: person]; - organizerIsSet = YES; - [self logWithFormat: @"organizer set via track status"]; - } - else - { - [person setParticipationStatus: newPartStat]; - [person setRsvp: @"TRUE"]; - [person setRole: @"REQ-PARTICIPANT"]; - [newEvent addToAttendees: person]; - effective++; - } - } - else - [self errorWithFormat: @"skipped recipient due" - @" to missing track status"]; - } - - [person release]; - } - - if (effective == 0) /* See work-around above */ - [newEvent setOrganizer: nil]; - else - { - // SEQUENCE = PidLidAppointmentSequence - value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentSequence)]; - if (value) - [newEvent setSequence: value]; - - ownerUser = [[self userContext] sogoUser]; - if (organizerIsSet) - { - /* We must reset the participation status to the value - obtained from PidLidResponseStatus as the value in - PidTagRecipientTrackStatus is not correct. Note (hack): - the method used here requires that the user directory - from LDAP and Samba matches perfectly. This can be solved - more appropriately by making use of the sender - properties... */ - person = [newEvent userAsAttendee: ownerUser]; - if (person) - { - value - = [properties objectForKey: MAPIPropertyKey (PidLidResponseStatus)]; - if (value) - responseStatus = [value unsignedLongValue]; - - /* FIXME: we should provide a data converter between OL partstats and - SOGo */ - switch (responseStatus) - { - case 0x02: /* respTentative */ - newPartStat = iCalPersonPartStatTentative; - break; - case 0x03: /* respAccepted */ - newPartStat = iCalPersonPartStatAccepted; - break; - case 0x04: /* respDeclined */ - newPartStat = iCalPersonPartStatDeclined; - break; - default: - newPartStat = iCalPersonPartStatNeedsAction; - } - [person setParticipationStatus: newPartStat]; - newParticipationStatus = [person partStatWithDefault]; - - value = [properties objectForKey: MAPIPropertyKey (PidLidAttendeeCriticalChange)]; - if (value && ![value isNever]) - [newEvent setTimeStampAsDate: value]; - // if (newPartStat // != iCalPersonPartStatUndefined - // ) - // { - // // iCalPerson *participant; - - // // participant = [newEvent userAsAttendee: ownerUser]; - // // [participant setParticipationStatus: newPartStat]; - // // [sogoObject saveComponent: newEvent]; - - // [sogoObject changeParticipationStatus: newPartStat - // withDelegate: nil]; - // // [[self context] tearDownRequest]; - // } - // // }1005 - - // // else - // // { - } - } - else - { - [self errorWithFormat: @"organizer was not set although a" - @" recipient list was specified"]; - /* We must set the organizer preliminarily here because, unlike what - the doc states, Outlook does not always pass the real organizer - in the recipients list. */ - dict = [ownerUser primaryIdentity]; - person = [iCalPerson new]; - [person setCn: [dict objectForKey: @"fullName"]]; - orgEmail = [dict objectForKey: @"email"]; - [person setEmail: orgEmail]; - - activeUser = [[self context] activeUser]; - if (![activeUser isEqual: ownerUser]) - { - dict = [activeUser primaryIdentity]; - sentBy = [NSString stringWithFormat: @"mailto:%@", - [dict objectForKey: @"email"]]; - [person setSentBy: sentBy]; - } - [newEvent setOrganizer: person]; - [person release]; - } - } - } - - [sogoObject saveComponent: newEvent]; - if (newParticipationStatus) - [sogoObject changeParticipationStatus: newParticipationStatus - withDelegate: nil]; [self updateVersions]; } diff --git a/OpenChange/iCalEvent+MAPIStore.h b/OpenChange/iCalEvent+MAPIStore.h new file mode 100644 index 000000000..171813e2b --- /dev/null +++ b/OpenChange/iCalEvent+MAPIStore.h @@ -0,0 +1,41 @@ +/* iCalEvent+MAPIStore.h - this file is part of SOGo + * + * Copyright (C) 2012 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef ICALEVENT_MAPISTORE_H +#define ICALEVENT_MAPISTORE_H + +#import + +@class MAPIStoreUserContext; +@class NSDictionary; +@class NSString; +@class SOGoUser; + +@interface iCalEvent (MAPIStoreProperties) + +- (void) updateFromMAPIProperties: (NSDictionary *) properties + inUserContext: (MAPIStoreUserContext *) userContext + withActiveUser: (SOGoUser *) activeUser; + +@end + +#endif /* ICALEVENT_MAPISTORE_H */ diff --git a/OpenChange/iCalEvent+MAPIStore.m b/OpenChange/iCalEvent+MAPIStore.m new file mode 100644 index 000000000..82785046a --- /dev/null +++ b/OpenChange/iCalEvent+MAPIStore.m @@ -0,0 +1,523 @@ +/* iCalEvent+MAPIStore.m - this file is part of SOGo + * + * Copyright (C) 2012 Inverse inc + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "MAPIStoreAppointmentWrapper.h" +#import "MAPIStoreCalendarAttachment.h" +#import "MAPIStoreCalendarFolder.h" +#import "MAPIStoreContext.h" +#import "MAPIStoreMapping.h" +#import "MAPIStoreRecurrenceUtils.h" +#import "MAPIStoreTypes.h" +#import "MAPIStoreUserContext.h" +#import "NSDate+MAPIStore.h" +#import "NSData+MAPIStore.h" +#import "NSObject+MAPIStore.h" +#import "NSString+MAPIStore.h" +#import "NSValue+MAPIStore.h" + +#import "MAPIStoreCalendarMessage.h" + +#undef DEBUG +#include +#include +#include +#include +#include +#include +#include + +#import "iCalEvent+MAPIStore.h" + +@implementation iCalEvent (MAPIStoreProperties) + +- (void) _setupEventRecurrence: (NSData *) mapiRecurrenceData +{ + struct Binary_r *blob; + struct AppointmentRecurrencePattern *pattern; + + blob = [mapiRecurrenceData asBinaryInMemCtx: NULL]; + pattern = get_AppointmentRecurrencePattern (blob, blob); + [(iCalCalendar *) parent + setupRecurrenceWithMasterEntity: self + fromRecurrencePattern: &pattern->RecurrencePattern]; + talloc_free (blob); +} + +- (void) _setupEventAlarmFromProperties: (NSDictionary *) properties +{ + NSArray *alarms; + iCalAlarm *currentAlarm, *alarm = nil; + iCalTrigger *trigger; + NSNumber *delta; + NSString *action; + NSUInteger count, max; + + /* find and remove first display alarm */ + alarms = [self alarms]; + max = [alarms count]; + for (count = 0; !alarm && count < max; count++) + { + currentAlarm = [alarms objectAtIndex: count]; + action = [[currentAlarm action] lowercaseString]; + if (!action || [action isEqualToString: @"display"]) + alarm = currentAlarm; + } + + if (alarm) + [self removeChild: alarm]; + + if ([[properties objectForKey: MAPIPropertyKey (PidLidReminderSet)] + boolValue]) + { + delta + = [properties objectForKey: MAPIPropertyKey (PidLidReminderDelta)]; + if (delta) + { + alarm = [iCalAlarm new]; + [alarm setAction: @"DISPLAY"]; + trigger = [iCalTrigger elementWithTag: @"trigger"]; + [trigger setValueType: @"DURATION"]; + [trigger + setSingleValue: [NSString stringWithFormat: @"-PT%@M", delta] + forKey: @""]; + [alarm setTrigger: trigger]; + [self addToAlarms: alarm]; + [alarm release]; + } + } +} + +- (void) updateFromMAPIProperties: (NSDictionary *) properties + inUserContext: (MAPIStoreUserContext *) userContext + withActiveUser: (SOGoUser *) activeUser +{ + BOOL isAllDay; + iCalDateTime *start, *end; + iCalTimeZone *tz; + NSTimeZone *userTimeZone; + NSString *priority; + NSUInteger responseStatus = 0; + NSInteger tzOffset; + SOGoUser *ownerUser; + id value; + + // value = [properties objectForKey: MAPIPropertyKey (PidTagMessageClass)]; + // if (value) + // isException = [value isEqualToString: @"IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}"]; + // else + // isException = NO; + + userTimeZone = [userContext timeZone]; + + /* CREATED */ + value = [properties objectForKey: MAPIPropertyKey (PidTagCreationTime)]; + if (value) + [self setCreated: value]; + + // LAST-MODIFIED = PidTagLastModificationTime + value = [properties objectForKey: MAPIPropertyKey (PidTagLastModificationTime)]; + if (value) + [self setLastModified: value]; + + /* DTSTAMP = PidLidOwnerCriticalChange or PidLidAttendeeCriticalChange */ + value = [properties objectForKey: MAPIPropertyKey (PidLidOwnerCriticalChange)]; + if (value) + [self setTimeStampAsDate: value]; + + /* SUMMARY */ + value = [properties + objectForKey: MAPIPropertyKey (PR_NORMALIZED_SUBJECT_UNICODE)]; + if (value) + [self setSummary: value]; + + // Location + value = [properties objectForKey: MAPIPropertyKey (PidLidLocation)]; + if (value) + [self setLocation: value]; + + isAllDay = [self isAllDay]; + value = [properties + objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)]; + if (value) + isAllDay = [value boolValue]; + if (!isAllDay) + { + tz = [iCalTimeZone timeZoneForName: [userTimeZone name]]; + [(iCalCalendar *) parent addTimeZone: tz]; + } + else + tz = nil; + + // recurrence-id + value + = [properties objectForKey: MAPIPropertyKey (PidLidExceptionReplaceTime)]; + if (value) + { + if (!isAllDay) + { + tzOffset = [userTimeZone secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: tzOffset]; + } + [self setRecurrenceId: value]; + } + + // start + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentStartWhole)]; + if (!value) + value = [properties objectForKey: MAPIPropertyKey (PR_START_DATE)]; + if (value) + { + start = (iCalDateTime *) [self uniqueChildWithTag: @"dtstart"]; + [start setTimeZone: tz]; + if (isAllDay) + { + [start setDate: value]; + [start setTimeZone: nil]; + } + else + { + tzOffset = [userTimeZone secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: tzOffset]; + [start setDateTime: value]; + } + } + + /* end */ + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentEndWhole)]; + if (!value) + value = [properties objectForKey: MAPIPropertyKey (PR_END_DATE)]; + if (value) + { + end = (iCalDateTime *) [self uniqueChildWithTag: @"dtend"]; + [end setTimeZone: tz]; + if (isAllDay) + { + [end setDate: value]; + [end setTimeZone: nil]; + } + else + { + tzOffset = [[value timeZone] secondsFromGMTForDate: value]; + value = [value dateByAddingYears: 0 months: 0 days: 0 + hours: 0 minutes: 0 + seconds: tzOffset]; + [end setDateTime: value]; + } + } + + /* priority */ + value = [properties objectForKey: MAPIPropertyKey(PR_IMPORTANCE)]; + if (value) + { + switch ([value intValue]) + { + case 0: // IMPORTANCE_LOW + priority = @"9"; + break; + case 2: // IMPORTANCE_HIGH + priority = @"1"; + break; + default: // IMPORTANCE_NORMAL + priority = @"5"; + } + } + else + priority = @"0"; // None + [self setPriority: priority]; + + /* show time as free/busy/tentative/out of office. Possible values are: + 0x00000000 - olFree + 0x00000001 - olTentative + 0x00000002 - olBusy + 0x00000003 - olOutOfOffice */ + value = [properties objectForKey: MAPIPropertyKey(PidLidBusyStatus)]; + if (value) + { + switch ([value intValue]) + { + case 0: + [self setTransparency: @"TRANSPARENT"]; + break; + case 1: + case 2: + case 3: + default: + [self setTransparency: @"OPAQUE"]; + } + } + + /* Comment */ + value = [properties objectForKey: MAPIPropertyKey (PR_BODY_UNICODE)]; + if (!value) + { + value = [properties objectForKey: MAPIPropertyKey (PR_HTML)]; + if (value) + { + value = [[NSString alloc] initWithData: value + encoding: NSUTF8StringEncoding]; + [value autorelease]; + value = [value htmlToText]; + } + } + if (value) + { + if ([value length] == 0 || [value isEqualToString: @"\\n"]) + value = nil; + [self setComment: value]; + } + + /* recurrence */ + value = [properties + objectForKey: MAPIPropertyKey (PidLidAppointmentRecur)]; + if (value) + [self _setupEventRecurrence: value]; + + /* alarm */ + [self _setupEventAlarmFromProperties: properties]; + + // Organizer + value = [properties objectForKey: @"recipients"]; + if (value) + { + NSArray *recipients; + NSDictionary *dict; + NSString *orgEmail, *sentBy, *attEmail; + iCalPerson *person; + iCalPersonPartStat newPartStat; + NSNumber *flags, *trackStatus; + int i, effective; + BOOL organizerIsSet = NO; + + [self setOrganizer: nil]; + [self removeAllAttendees]; + + recipients = [value objectForKey: @"to"]; + effective = 0; + for (i = 0; i < [recipients count]; i++) + { + dict = [recipients objectAtIndex: i]; + person = [iCalPerson new]; + [person setCn: [dict objectForKey: @"fullName"]]; + attEmail = [dict objectForKey: @"email"]; + [person setEmail: attEmail]; + + flags = [dict objectForKey: MAPIPropertyKey (PR_RECIPIENT_FLAGS)]; + if (!flags) + { + [self logWithFormat: + @"no recipient flags specified: skipping recipient"]; + continue; + } + + if (([flags unsignedIntValue] & 0x0002)) /* recipOrganizer */ + { + [self setOrganizer: person]; + organizerIsSet = YES; + [self logWithFormat: @"organizer set via recipient flags"]; + } + else + { + BOOL isOrganizer = NO; + + // /* Work-around: it happens that Outlook still passes the + // organizer as a recipient, maybe because of a feature + // documented in a pre-mesozoic PDF still buried in a + // cavern... In that case we remove it, and we keep the + // number of effective recipients in "effective". If the + // total is 0, we remove the "ORGANIZER" too. */ + // if ([attEmail isEqualToString: orgEmail]) + // { + // [self logWithFormat: + // @"avoiding setting organizer as recipient"]; + // continue; + // } + + trackStatus = [dict objectForKey: MAPIPropertyKey (PidTagRecipientTrackStatus)]; + if (trackStatus) + { + /* FIXME: we should provide a data converter between OL + partstats and SOGo */ + switch ([trackStatus unsignedIntValue]) + { + case 0x01: /* respOrganized */ + isOrganizer = YES; + break; + case 0x02: /* respTentative */ + newPartStat = iCalPersonPartStatTentative; + break; + case 0x03: /* respAccepted */ + newPartStat = iCalPersonPartStatAccepted; + break; + case 0x04: /* respDeclined */ + newPartStat = iCalPersonPartStatDeclined; + break; + default: + newPartStat = iCalPersonPartStatNeedsAction; + } + + if (isOrganizer) + { + [self setOrganizer: person]; + organizerIsSet = YES; + [self logWithFormat: @"organizer set via track status"]; + } + else + { + [person setParticipationStatus: newPartStat]; + [person setRsvp: @"TRUE"]; + [person setRole: @"REQ-PARTICIPANT"]; + [self addToAttendees: person]; + effective++; + } + } + else + [self errorWithFormat: @"skipped recipient due" + @" to missing track status"]; + } + + [person release]; + } + + if (effective == 0) /* See work-around above */ + [self setOrganizer: nil]; + else + { + // SEQUENCE = PidLidAppointmentSequence + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentSequence)]; + if (value) + [self setSequence: value]; + + ownerUser = [userContext sogoUser]; + if (organizerIsSet) + { + /* We must reset the participation status to the value + obtained from PidLidResponseStatus as the value in + PidTagRecipientTrackStatus is not correct. Note (hack): + the method used here requires that the user directory + from LDAP and Samba matches perfectly. This can be solved + more appropriately by making use of the sender + properties... */ + person = [self userAsAttendee: ownerUser]; + if (person) + { + value + = [properties objectForKey: MAPIPropertyKey (PidLidResponseStatus)]; + if (value) + responseStatus = [value unsignedLongValue]; + + /* FIXME: we should provide a data converter between OL partstats and + SOGo */ + switch (responseStatus) + { + case 0x02: /* respTentative */ + newPartStat = iCalPersonPartStatTentative; + break; + case 0x03: /* respAccepted */ + newPartStat = iCalPersonPartStatAccepted; + break; + case 0x04: /* respDeclined */ + newPartStat = iCalPersonPartStatDeclined; + break; + default: + newPartStat = iCalPersonPartStatNeedsAction; + } + [person setParticipationStatus: newPartStat]; + + value = [properties objectForKey: MAPIPropertyKey (PidLidAttendeeCriticalChange)]; + if (value && ![value isNever]) + [self setTimeStampAsDate: value]; + // if (newPartStat // != iCalPersonPartStatUndefined + // ) + // { + // // iCalPerson *participant; + + // // participant = [self userAsAttendee: ownerUser]; + // // [participant setParticipationStatus: newPartStat]; + // // [sogoObject saveComponent: self]; + + // [sogoObject changeParticipationStatus: newPartStat + // withDelegate: nil]; + // // [[self context] tearDownRequest]; + // } + // // }1005 + + // // else + // // { + } + } + else + { + [self errorWithFormat: @"organizer was not set although a" + @" recipient list was specified"]; + /* We must set the organizer preliminarily here because, unlike what + the doc states, Outlook does not always pass the real organizer + in the recipients list. */ + dict = [ownerUser primaryIdentity]; + person = [iCalPerson new]; + [person setCn: [dict objectForKey: @"fullName"]]; + orgEmail = [dict objectForKey: @"email"]; + [person setEmail: orgEmail]; + + if (![activeUser isEqual: ownerUser]) + { + dict = [activeUser primaryIdentity]; + sentBy = [NSString stringWithFormat: @"mailto:%@", + [dict objectForKey: @"email"]]; + [person setSentBy: sentBy]; + } + [self setOrganizer: person]; + [person release]; + } + } + } +} + +@end