From 8c26a74bf5afcc80178ecb149f2d8936d9d008d7 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 22 Mar 2011 21:28:16 +0000 Subject: [PATCH] Monotone-Parent: 8c4a5e6241dde8edac2d02e1b2dda9528b69ddb6 Monotone-Revision: b4cd488f57719689e0d2bed481d82df8e68fcf46 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-03-22T21:28:16 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 11 + OpenChange/MAPIStoreCalendarMessage.h | 7 +- OpenChange/MAPIStoreCalendarMessage.m | 336 ++++++++++++++++++++++++-- 3 files changed, 327 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index c56ee31c7..ab00d0d08 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,16 @@ 2011-03-22 Wolfgang Sourdeau + * OpenChange/MAPIStoreCalendarMessage.m (-init): new method to + initialize the attachment ivars. + (-ownerTimeZone): new method with common code. + (-getProperty:withTag): (PR_ICON_INDEX) return a more precise icon depending on + the recurring nature of the event as well on its number of + attendees. (PidLidAppointmentStartWhole, + PidLidAppointmentEndWhole) removed properties. (PidLidRecurring, + PidLidIsRecurring), returns YES when recurring. + (PidLidAppointmentRecur) property handles property, by using + -_computeAppointmentRecur and other submethods. + * OpenChange/NSCalendarDate+MAPIStore.m (-asMinutesSince1601): corollary to +dateFromMinutesSince1601. diff --git a/OpenChange/MAPIStoreCalendarMessage.h b/OpenChange/MAPIStoreCalendarMessage.h index 32d409722..7b1e2de38 100644 --- a/OpenChange/MAPIStoreCalendarMessage.h +++ b/OpenChange/MAPIStoreCalendarMessage.h @@ -25,12 +25,11 @@ #import "MAPIStoreGCSMessage.h" -#define minutesPerHour 60 -#define hoursPerDay 24 -#define minutesPerDay (minutesPerHour * hoursPerDay) +#define SOGoMinutesPerHour 60 +#define SOGoHoursPerDay 24 +#define SOGoMinutesPerDay (SOGoMinutesPerHour * SOGoHoursPerDay) @interface MAPIStoreCalendarMessage : MAPIStoreGCSMessage - @end #endif /* MAPISTORECALENDARMESSAGE_H */ diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index eb8b101d9..ca8f99490 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -28,8 +28,10 @@ #import #import #import +#import #import #import +#import #import #import #import @@ -43,6 +45,8 @@ #import "MAPIStoreContext.h" #import "MAPIStoreTypes.h" +#import "MAPIStoreAttachment.h" +#import "MAPIStoreAttachmentTable.h" #import "NSCalendarDate+MAPIStore.h" #import "NSData+MAPIStore.h" #import "NSString+MAPIStore.h" @@ -57,8 +61,36 @@ #include #include +// extern void ndr_print_AppointmentRecurrencePattern(struct ndr_print *ndr, const char *name, const struct AppointmentRecurrencePattern *r); + @implementation MAPIStoreCalendarMessage +- (id) init +{ + if ((self = [super init])) + { + attachmentKeys = [NSMutableArray new]; + attachmentParts = [NSMutableDictionary new]; + } + + return self; +} + +- (NSTimeZone *) ownerTimeZone +{ + NSString *owner; + SOGoUserDefaults *ud; + NSTimeZone *tz; + WOContext *woContext; + + woContext = [[self context] woContext]; + owner = [sogoObject ownerInContext: woContext]; + ud = [[SOGoUser userWithLogin: owner] userDefaults]; + tz = [ud timeZone]; + + return tz; +} + - (void) _setupRecurrenceInCalendar: (iCalCalendar *) calendar withMasterEvent: (iCalEvent *) vEvent fromData: (NSData *) mapiRecurrenceData @@ -93,11 +125,14 @@ blob = [mapiRecurrenceData asBinaryInMemCtx: memCtx]; pattern = get_AppointmentRecurrencePattern (memCtx, blob); + // DEBUG(5, ("From client:\n")); + // NDR_PRINT_DEBUG(AppointmentRecurrencePattern, pattern); + memset (&dayMaskDays, 0, sizeof (iCalWeekOccurrences)); if (pattern->RecurrencePattern.PatternType == PatternType_Day) { [rule setFrequency: iCalRecurrenceFrequenceDaily]; - [rule setRepeatInterval: pattern->RecurrencePattern.Period / minutesPerDay]; + [rule setRepeatInterval: pattern->RecurrencePattern.Period / SOGoMinutesPerDay]; } else if (pattern->RecurrencePattern.PatternType == PatternType_Week) { @@ -235,11 +270,227 @@ talloc_free (blob); } +static void +_fillRecurrencePattern (struct RecurrencePattern *rp, + NSCalendarDate *startDate, NSCalendarDate *endDate, + iCalRecurrenceRule *rule) +{ + iCalRecurrenceFrequency freq; + iCalByDayMask *byDayMask; + NSString *byMonthDay, *bySetPos; + NSCalendarDate *untilDate, *beginOfWeek, *minimumDate, *moduloDate, *midnight; + iCalWeekOccurrences *days; + NSInteger dayOfWeek, repeatInterval, repeatCount, count, firstOccurrence; + uint32_t nbrMonths, mask; + + rp->ReaderVersion = 0x3004; + rp->WriterVersion = 0x3004; + + rp->StartDate = [[startDate beginOfDay] asMinutesSince1601]; + + untilDate = [rule untilDate]; + if (untilDate) + { + rp->EndDate = [untilDate asMinutesSince1601]; + rp->EndType = END_AFTER_DATE; + } + else + { + repeatCount = [rule repeatCount]; + if (repeatCount > 0) + { + rp->EndDate = [endDate asMinutesSince1601]; + rp->OccurrenceCount = repeatCount; + rp->EndType = END_AFTER_N_OCCURRENCES; + } + else + { + rp->EndDate = 0x5ae980df; + rp->EndType = END_NEVER_END; + } + } + + freq = [rule frequency]; + repeatInterval = [rule repeatInterval]; + if (freq == iCalRecurrenceFrequenceDaily) + { + rp->RecurFrequency = RecurFrequency_Daily; + rp->PatternType = PatternType_Day; + rp->Period = repeatInterval * SOGoMinutesPerDay; + rp->FirstDateTime = rp->StartDate % rp->Period; + } + else if (freq == iCalRecurrenceFrequenceWeekly) + { + rp->RecurFrequency = RecurFrequency_Weekly; + rp->PatternType = PatternType_Week; + rp->Period = repeatInterval; + mask = 0; + byDayMask = [rule byDayMask]; + for (count = 0; count < 7; count++) + if ([byDayMask occursOnDay: count]) + mask |= 1 << count; + rp->PatternTypeSpecific.WeekRecurrencePattern = mask; + + /* FirstDateTime */ + dayOfWeek = [startDate dayOfWeek]; + if (dayOfWeek) + beginOfWeek = [startDate dateByAddingYears: 0 months: 0 + days: -dayOfWeek + hours: 0 minutes: 0 + seconds: 0]; + else + beginOfWeek = startDate; + rp->FirstDateTime = ([[beginOfWeek beginOfDay] asMinutesSince1601] + % (repeatInterval * 10080)); + } + else + { + if (freq == iCalRecurrenceFrequenceMonthly) + { + rp->RecurFrequency = RecurFrequency_Monthly; + rp->Period = repeatInterval; + } + else if (freq == iCalRecurrenceFrequenceYearly) + { + rp->RecurFrequency = RecurFrequency_Yearly; + rp->Period = 12; + if (repeatInterval != 1) + [rule errorWithFormat: + @"yearly interval '%d' cannot be converted", + repeatInterval]; + } + else + [rule errorWithFormat: @"frequency '%d' cannot be converted", freq]; + + /* FirstDateTime */ + midnight = [[startDate firstDayOfMonth] beginOfDay]; + minimumDate = [NSCalendarDate dateFromMinutesSince1601: 0]; + nbrMonths = (([midnight yearOfCommonEra] + - [minimumDate yearOfCommonEra]) * 12 + + [midnight monthOfYear] - 1); + moduloDate = [minimumDate dateByAddingYears: 0 + months: (nbrMonths % rp->Period) + days: 0 hours: 0 minutes: 0 + seconds: 0]; + rp->FirstDateTime = [moduloDate asMinutesSince1601]; + + byMonthDay = [[rule byMonthDay] objectAtIndex: 0]; + if (byMonthDay) + { + if ([byMonthDay intValue] < 0) + { + /* This means we cannot handle values of BYMONTHDAY that are < + -7. */ + rp->PatternType = PatternType_MonthNth; + rp->PatternTypeSpecific.MonthRecurrencePattern.WeekRecurrencePattern = 0x7f; + rp->PatternTypeSpecific.MonthRecurrencePattern.N = RecurrenceN_Last; + } + else + { + rp->PatternType = PatternType_Month; + rp->PatternTypeSpecific.Day = [byMonthDay intValue]; + } + } + else + { + rp->PatternType = PatternType_MonthNth; + byDayMask = [rule byDayMask]; + days = [byDayMask weekDayOccurrences]; + mask = 0; + for (count = 0; count < 7; count++) + if (days[0][count]) + mask |= 1 << count; + if (mask) + { + rp->PatternTypeSpecific.MonthRecurrencePattern.WeekRecurrencePattern = mask; + bySetPos = [rule namedValue: @"bysetpos"]; + if ([bySetPos length]) + rp->PatternTypeSpecific.MonthRecurrencePattern.N + = ([bySetPos hasPrefix: @"-"] + ? RecurrenceN_Last : [bySetPos intValue]); + else + { + firstOccurrence = [byDayMask firstOccurrence]; + if (firstOccurrence) + rp->PatternTypeSpecific.MonthRecurrencePattern.N + = ((firstOccurrence > -1) + ? firstOccurrence : RecurrenceN_Last); + } + } + else + [rule errorWithFormat: @"rule for an event that never occurs"]; + } + } +} + +static void +_fillAppointmentRecurrencePattern (struct AppointmentRecurrencePattern *arp, + NSCalendarDate *startDate, NSTimeInterval duration, + NSCalendarDate * endDate, iCalRecurrenceRule * rule) +{ + uint32_t startMinutes; + + _fillRecurrencePattern (&arp->RecurrencePattern, startDate, endDate, rule); + arp->ReaderVersion2 = 0x00003006; + arp->WriterVersion2 = 0x00003009; + + startMinutes = ([startDate hourOfDay] * 60 + [startDate minuteOfHour]); + arp->StartTimeOffset = startMinutes; + arp->EndTimeOffset = startMinutes + (uint32_t) (duration / 60); + + arp->ExceptionCount = 0; + arp->ReservedBlock1Size = 0; + + /* Currently ignored in property.idl: + arp->ReservedBlock2Size = 0; */ +} + +- (struct SBinary_short *) _computeAppointmentRecur +{ + struct AppointmentRecurrencePattern *arp; + struct Binary_r *bin; + struct SBinary_short *sBin; + NSCalendarDate *firstStartDate; + iCalEvent *vEvent; + iCalRecurrenceRule *rule; + + vEvent = [sogoObject component: NO secure: NO]; + rule = [[vEvent recurrenceRules] objectAtIndex: 0]; + + firstStartDate = [vEvent firstRecurrenceStartDate]; + if (firstStartDate) + { + [firstStartDate setTimeZone: [self ownerTimeZone]]; + + arp = talloc_zero (memCtx, struct AppointmentRecurrencePattern); + _fillAppointmentRecurrencePattern (arp, firstStartDate, + [vEvent durationAsTimeInterval], + [vEvent lastPossibleRecurrenceStartDate], + rule); + sBin = talloc_zero (memCtx, struct SBinary_short); + bin = set_AppointmentRecurrencePattern (sBin, arp); + sBin->cb = bin->cb; + sBin->lpb = bin->lpb; + talloc_free (arp); + + // DEBUG(5, ("To client:\n")); + // NDR_PRINT_DEBUG (AppointmentRecurrencePattern, arp); + } + else + { + [self errorWithFormat: @"no first occurrence found in rule: %@", rule]; + sBin = NULL; + } + + return sBin; +} + - (enum MAPISTATUS) getProperty: (void **) data withTag: (enum MAPITAGS) propTag { NSTimeInterval timeValue; id event; + uint32_t longValue; int rc; rc = MAPI_E_SUCCESS; @@ -251,19 +502,19 @@ // *longValue = 0x00000402 for meeting // *longValue = 0x00000403 for recurring meeting // *longValue = 0x00000404 for invitation - *data = MAPILongValue (memCtx, 0x00000400); + + event = [sogoObject component: NO secure: NO]; + longValue = 0x0400; + if ([event isRecurrent]) + longValue |= 0x0001; + if ([[event attendees] count] > 0) + longValue |= 0x0002; + + *data = MAPILongValue (memCtx, longValue); break; case PR_MESSAGE_CLASS_UNICODE: *data = talloc_strdup(memCtx, "IPM.Appointment"); break; - case PidLidAppointmentStartWhole: // DTSTART - event = [sogoObject component: NO secure: NO]; - *data = [[event startDate] asFileTimeInMemCtx: memCtx]; - break; - case PidLidAppointmentEndWhole: // DTEND - event = [sogoObject component: NO secure: NO]; - *data = [[event endDate] asFileTimeInMemCtx: memCtx]; - break; case PidLidAppointmentDuration: event = [sogoObject component: NO secure: NO]; timeValue = [[event endDate] timeIntervalSinceDate: [event startDate]]; @@ -276,9 +527,6 @@ case PidLidBusyStatus: // TODO *data = MAPILongValue (memCtx, 0x02); break; - case PidLidRecurring: // TODO - *data = MAPIBoolValue (memCtx, NO); - break; // case 0x82410003: // TODO // *data = MAPILongValue (memCtx, 0); @@ -320,6 +568,17 @@ } break; + + /* Recurrence */ + case PidLidIsRecurring: + case PidLidRecurring: + event = [sogoObject component: NO secure: NO]; + *data = MAPIBoolValue (memCtx, [event isRecurrent]); + break; + case PidLidAppointmentRecur: + *data = [self _computeAppointmentRecur]; + break; + // case PidLidTimeZoneStruct: // case PR_VD_NAME_UNICODE: // *data = talloc_strdup(memCtx, "PR_VD_NAME_UNICODE"); @@ -391,15 +650,14 @@ - (void) save { + WOContext *woContext; iCalCalendar *vCalendar; iCalEvent *vEvent; - id value; - SOGoUserDefaults *ud; - NSCalendarDate *now; - iCalTimeZone *tz; - NSString *owner, *content; iCalDateTime *start, *end; - WOContext *woContext; + iCalTimeZone *tz; + NSCalendarDate *now; + NSString *content, *tzName; + id value; [self logWithFormat: @"-save, event props:"]; // MAPIStoreDumpMessageProperties (newProperties); @@ -427,10 +685,8 @@ if (value) [vEvent setLocation: value]; - woContext = [[self context] woContext]; - owner = [sogoObject ownerInContext: woContext]; - ud = [[SOGoUser userWithLogin: owner] userDefaults]; - tz = [iCalTimeZone timeZoneForName: [ud timeZoneName]]; + tzName = [[self ownerTimeZone] name]; + tz = [iCalTimeZone timeZoneForName: tzName]; [vCalendar addTimeZone: tz]; // start @@ -472,6 +728,7 @@ iCalPerson *person; int i; + woContext = [[self context] woContext]; dict = [[woContext activeUser] primaryIdentity]; person = [iCalPerson new]; [person setCn: [dict objectForKey: @"fullName"]]; @@ -511,4 +768,37 @@ [sogoObject saveComponent: vEvent]; } +/* TODO: those are stubs meant to prevent OpenChange from crashing when a + recurring event is open */ +- (NSArray *) childKeysMatchingQualifier: (EOQualifier *) qualifier + andSortOrderings: (NSArray *) sortOrderings +{ + /* TODO: Here we should return recurrence exceptions */ + return attachmentKeys; +} + +- (id) lookupChild: (NSString *) childKey +{ + return [attachmentParts objectForKey: childKey]; +} + +- (MAPIStoreAttachmentTable *) attachmentTable +{ + return [MAPIStoreAttachmentTable tableForContainer: self]; +} + +- (MAPIStoreAttachment *) createAttachment +{ + MAPIStoreAttachment *newAttachment; + + newAttachment = [MAPIStoreAttachment new]; + [newAttachment setAID: 0]; + [attachmentParts setObject: newAttachment + forKey: @"0"]; + [attachmentKeys addObject: @"0"]; + [newAttachment release]; + + return newAttachment; +} + @end