Added missing recurrences support, improved MeetingResponse, added WindowSize support.
Also more bug fixes regarding event invitations, and ServerId handling for calendar objects.pull/17/head
parent
4d1fdb33f5
commit
9218c7f253
|
@ -49,6 +49,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#import <NGCards/iCalCalendar.h>
|
||||
#import <NGCards/iCalEntityObject.h>
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalPerson.h>
|
||||
#import <NGCards/iCalToDo.h>
|
||||
#import <NGCards/NGVCard.h>
|
||||
|
||||
|
@ -89,6 +90,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
#import <Mailer/SOGoMailAccount.h>
|
||||
#import <Mailer/SOGoMailAccounts.h>
|
||||
#import <Mailer/SOGoMailFolder.h>
|
||||
#import <Mailer/SOGoMailObject.h>
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
|
@ -187,20 +189,20 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// invitation email and choses "Add to calendar" BEFORE actually syncing the calendar. That would
|
||||
// create a duplicate on the server.
|
||||
if ([allValues objectForKey: @"UID"])
|
||||
serverId = [NSString stringWithFormat: @"%@.ics", [allValues objectForKey: @"UID"]];
|
||||
serverId = [allValues objectForKey: @"UID"];
|
||||
else
|
||||
serverId = [NSString stringWithFormat: @"%@.ics", [theCollection globallyUniqueObjectId]];
|
||||
serverId = [theCollection globallyUniqueObjectId];
|
||||
|
||||
[allValues setObject: [[[context activeUser] userDefaults] timeZone] forKey: @"SOGoUserTimeZone"];
|
||||
|
||||
sogoObject = [theCollection lookupName: serverId
|
||||
sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
// If object isn't found, we 'create' a new one
|
||||
if ([sogoObject isKindOfClass: [NSException class]])
|
||||
{
|
||||
sogoObject = [[SOGoAppointmentObject alloc] initWithName: serverId
|
||||
sogoObject = [[SOGoAppointmentObject alloc] initWithName: [serverId sanitizedServerIdWithType: theFolderType]
|
||||
inContainer: theCollection];
|
||||
o = [sogoObject component: YES secure: NO];
|
||||
}
|
||||
|
@ -224,7 +226,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
default:
|
||||
{
|
||||
// FIXME
|
||||
continue;
|
||||
//continue;
|
||||
NSLog(@"BLARG!");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,7 +306,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
allChanges = [[(id)[aChange getElementsByTagName: @"ApplicationData"] lastObject] applicationData];
|
||||
|
||||
// Fetch the object and apply the changes
|
||||
sogoObject = [theCollection lookupName: serverId
|
||||
sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
|
@ -387,7 +391,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
serverId = [[(id)[aDelete getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
|
||||
sogoObject = [theCollection lookupName: serverId
|
||||
sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
|
@ -412,7 +416,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
||||
|
||||
o = [theCollection lookupName: serverId
|
||||
o = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
|
||||
|
@ -432,13 +436,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
//
|
||||
- (void) processSyncGetChanges: (id <DOMElement>) theDocumentElement
|
||||
inCollection: (id) theCollection
|
||||
withWindowSize: (unsigned int) theWindowSize
|
||||
withSyncKey: (NSString *) theSyncKey
|
||||
withFolderType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
||||
withFilterType: (NSCalendarDate *) theFilterType
|
||||
inBuffer: (NSMutableString *) theBuffer
|
||||
lastServerKey: (NSString **) theLastServerKey
|
||||
|
||||
{
|
||||
NSMutableString *s;
|
||||
int i;
|
||||
|
||||
BOOL more_available;
|
||||
int i, max;
|
||||
|
||||
//
|
||||
// No changes in the collection - 2.2.2.19.1.1 Empty Sync Request.
|
||||
|
@ -448,6 +457,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
return;
|
||||
|
||||
s = [NSMutableString string];
|
||||
more_available = NO;
|
||||
|
||||
switch (theFolderType)
|
||||
{
|
||||
|
@ -473,7 +483,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
|
||||
for (i = 0; i < [allComponents count]; i++)
|
||||
// Check for the WindowSize
|
||||
max = [allComponents count];
|
||||
|
||||
// Disabled for now for GCS folders.
|
||||
// if (max > theWindowSize)
|
||||
// {
|
||||
// max = theWindowSize;
|
||||
// more_available = YES;
|
||||
// }
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
component = [allComponents objectAtIndex: i];
|
||||
deleted = [[component objectForKey: @"c_deleted"] intValue];
|
||||
|
@ -481,7 +501,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
|
||||
continue;
|
||||
|
||||
uid = [component objectForKey: @"c_name"];
|
||||
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
|
@ -496,15 +516,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if ([[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
|
||||
updated = NO;
|
||||
|
||||
if (updated)
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
else
|
||||
[s appendString: @"<Add xmlns=\"AirSync:\">"];
|
||||
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
|
||||
sogoObject = [theCollection lookupName: uid
|
||||
sogoObject = [theCollection lookupName: [uid sanitizedServerIdWithType: theFolderType]
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
|
@ -513,6 +525,40 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
else
|
||||
componentObject = [sogoObject component: NO secure: NO];
|
||||
|
||||
|
||||
//
|
||||
// We do NOT synchronize NEW events that are in fact, invitations
|
||||
// to events. This is due to the fact that Outlook 2013 creates
|
||||
// "phantom" events in the calendar that are mapped to invitations mails.
|
||||
// If we synchronize these events too, it'll interfere with the whole thing
|
||||
// and prevent Outlook from properly calling MeetingResponse.
|
||||
//
|
||||
if (!updated && theFolderType == ActiveSyncEventFolder)
|
||||
{
|
||||
iCalPersonPartStat partstat;
|
||||
iCalPerson *attendee;
|
||||
NSString *email;
|
||||
|
||||
email = [[[context activeUser] allEmails] objectAtIndex: 0];
|
||||
attendee = [componentObject findAttendeeWithEmail: email];
|
||||
|
||||
if (attendee)
|
||||
{
|
||||
partstat = [attendee participationStatus];
|
||||
|
||||
if (partstat == iCalPersonPartStatNeedsAction)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
||||
else
|
||||
[s appendString: @"<Add xmlns=\"AirSync:\">"];
|
||||
|
||||
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
|
||||
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
||||
|
||||
[s appendString: [componentObject activeSyncRepresentation]];
|
||||
|
||||
[s appendString: @"</ApplicationData>"];
|
||||
|
@ -535,7 +581,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType];
|
||||
|
||||
for (i = 0; i < [allMessages count]; i++)
|
||||
// Check for the WindowSize.
|
||||
// FIXME: we should eventually check for modseq and slice the maximum
|
||||
// amount of messages returned to ensure we don't have the same
|
||||
// modseq accross contiguous boundaries
|
||||
max = [allMessages count];
|
||||
if (max > theWindowSize)
|
||||
{
|
||||
max = theWindowSize;
|
||||
more_available = YES;
|
||||
}
|
||||
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
aMessage = [allMessages objectAtIndex: i];
|
||||
|
||||
|
@ -571,6 +628,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
if (more_available)
|
||||
{
|
||||
*theLastServerKey = uid;
|
||||
}
|
||||
}
|
||||
break;
|
||||
} // switch (folderType) ...
|
||||
|
@ -580,6 +643,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[theBuffer appendString: @"<Commands>"];
|
||||
[theBuffer appendString: s];
|
||||
[theBuffer appendString: @"</Commands>"];
|
||||
|
||||
if (more_available)
|
||||
[theBuffer appendString: @"<MoreAvailable/>"];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,12 +729,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
inBuffer: (NSMutableString *) theBuffer
|
||||
changeDetected: (BOOL *) changeDetected
|
||||
{
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType;
|
||||
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *lastServerKey;
|
||||
SOGoMicrosoftActiveSyncFolderType folderType;
|
||||
id collection, value;
|
||||
|
||||
NSMutableString *changeBuffer, *commandsBuffer;
|
||||
BOOL getChanges, first_sync;
|
||||
unsigned int windowSize;
|
||||
|
||||
changeBuffer = [NSMutableString string];
|
||||
commandsBuffer = [NSMutableString string];
|
||||
|
@ -679,6 +746,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
||||
|
||||
// We check for a window size, default to 100 if not specfied or out of bounds
|
||||
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
||||
|
||||
if (windowSize == 0 || windowSize > 512)
|
||||
windowSize = 100;
|
||||
|
||||
lastServerKey = nil;
|
||||
|
||||
// From the documention, if GetChanges is missing, we must assume it's a YES.
|
||||
// See http://msdn.microsoft.com/en-us/library/gg675447(v=exchg.80).aspx for all details.
|
||||
value = [theDocumentElement getElementsByTagName: @"GetChanges"];
|
||||
|
@ -712,10 +787,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{
|
||||
[self processSyncGetChanges: theDocumentElement
|
||||
inCollection: collection
|
||||
withWindowSize: windowSize
|
||||
withSyncKey: syncKey
|
||||
withFolderType: folderType
|
||||
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
|
||||
inBuffer: changeBuffer];
|
||||
inBuffer: changeBuffer
|
||||
lastServerKey: &lastServerKey];
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -745,7 +822,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
// let's regenerate our SyncKey based on the collection tag.
|
||||
if ([changeBuffer length] || [commandsBuffer length])
|
||||
{
|
||||
davCollectionTag = [collection davCollectionTag];
|
||||
if (lastServerKey)
|
||||
davCollectionTag = [collection davCollectionTagFromId: lastServerKey];
|
||||
else
|
||||
davCollectionTag = [collection davCollectionTag];
|
||||
|
||||
*changeDetected = YES;
|
||||
}
|
||||
|
||||
|
|
|
@ -702,7 +702,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[s appendString: @"<Properties>"];
|
||||
|
||||
[s appendFormat: @"<ContentType xmlns=\"AirSyncBase:\">%@/%@</ContentType>", [[currentBodyPart partInfo] objectForKey: @"type"], [[currentBodyPart partInfo] objectForKey: @"subtype"]];
|
||||
[s appendFormat: @"<Data>%@</Data>", [[[currentBodyPart fetchBLOB] stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]];
|
||||
[s appendFormat: @"<Data>%@</Data>", [[currentBodyPart fetchBLOB] activeSyncRepresentation]];
|
||||
|
||||
[s appendString: @"</Properties>"];
|
||||
[s appendString: @"</Fetch>"];
|
||||
|
@ -735,7 +735,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
- (void) processMeetingResponse: (id <DOMElement>) theDocumentElement
|
||||
inResponse: (WOResponse *) theResponse
|
||||
{
|
||||
NSString *realCollectionId, *requestId, *participationStatus;
|
||||
NSString *realCollectionId, *requestId, *participationStatus, *calendarId;
|
||||
SOGoAppointmentObject *appointmentObject;
|
||||
SOGoMailObject *mailObject;
|
||||
NSMutableString *s;
|
||||
NSData *d;
|
||||
|
@ -750,34 +751,75 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
status = 1;
|
||||
|
||||
realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType];
|
||||
collection = [self collectionFromId: realCollectionId type: ActiveSyncMailFolder];
|
||||
|
||||
// 1 -> accepted, 2 -> tentative, 3 -> declined
|
||||
userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue];
|
||||
requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue];
|
||||
appointmentObject = nil;
|
||||
calendarId = nil;
|
||||
|
||||
// Outlook 2013 calls MeetingResponse on the calendar folder! We have
|
||||
// no way of handling as we can't retrieve the email (using the id found
|
||||
// in requestId) in any mail folder! If that happens, let's simply
|
||||
// assume it comes from the INBOX. This should be generally safe as people
|
||||
// will answer email invitations as they receive them on their INBOX.
|
||||
// Note that the mail should also still be there as MeetingResponse is
|
||||
// called *before* MoveItems.
|
||||
//
|
||||
// We fetch the calendar information based on the email (requestId) in the user's INBOX (or elsewhere)
|
||||
//
|
||||
// FIXME: that won't work too well for external invitations...
|
||||
mailObject = [collection lookupName: requestId
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
if (![mailObject isKindOfClass: [NSException class]])
|
||||
// Apple iOS will also call MeetingResponse on the calendar folder when the
|
||||
// user accepts/declines the meeting from the Calendar application. Before
|
||||
// falling back on INBOX, we first check if we can find the event in the
|
||||
// personal calendar.
|
||||
if (folderType == ActiveSyncEventFolder)
|
||||
{
|
||||
SOGoAppointmentObject *appointmentObject;
|
||||
iCalCalendar *calendar;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [mailObject calendarFromIMIPMessage];
|
||||
event = [[calendar events] lastObject];
|
||||
|
||||
// Fetch the SOGoAppointmentObject
|
||||
collection = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]]
|
||||
appointmentObject = [collection lookupName: [requestId sanitizedServerIdWithType: ActiveSyncEventFolder]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
calendarId = requestId;
|
||||
|
||||
// Object not found, let's fallback on the INBOX folder
|
||||
if ([appointmentObject isKindOfClass: [NSException class]])
|
||||
{
|
||||
folderType = ActiveSyncMailFolder;
|
||||
realCollectionId = @"INBOX";
|
||||
appointmentObject = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the appointment object from the mail message
|
||||
if (!appointmentObject)
|
||||
{
|
||||
collection = [self collectionFromId: realCollectionId type: folderType];
|
||||
|
||||
//
|
||||
// We fetch the calendar information based on the email (requestId) in the user's INBOX (or elsewhere)
|
||||
//
|
||||
// FIXME: that won't work too well for external invitations...
|
||||
mailObject = [collection lookupName: requestId
|
||||
inContext: context
|
||||
acquire: 0];
|
||||
|
||||
if (![mailObject isKindOfClass: [NSException class]])
|
||||
{
|
||||
iCalCalendar *calendar;
|
||||
iCalEvent *event;
|
||||
|
||||
calendar = [mailObject calendarFromIMIPMessage];
|
||||
event = [[calendar events] lastObject];
|
||||
calendarId = [event uid];
|
||||
|
||||
// Fetch the SOGoAppointmentObject
|
||||
collection = [[context activeUser] personalCalendarFolderInContext: context];
|
||||
appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]]
|
||||
inContext: context
|
||||
acquire: NO];
|
||||
}
|
||||
}
|
||||
|
||||
if (appointmentObject &&
|
||||
calendarId &&
|
||||
(![appointmentObject isKindOfClass: [NSException class]]))
|
||||
{
|
||||
// 1 -> accepted, 2 -> tentative, 3 -> declined
|
||||
if (userResponse == 1)
|
||||
participationStatus = @"ACCEPTED";
|
||||
else if (userResponse == 2)
|
||||
|
@ -785,29 +827,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
else
|
||||
participationStatus = @"DECLINED";
|
||||
|
||||
if (![appointmentObject isKindOfClass: [NSException class]])
|
||||
{
|
||||
[appointmentObject changeParticipationStatus: participationStatus
|
||||
withDelegate: nil];
|
||||
[appointmentObject changeParticipationStatus: participationStatus
|
||||
withDelegate: nil];
|
||||
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
|
||||
[s appendString: @"<Result>"];
|
||||
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
|
||||
[s appendFormat: @"<CalendarId>%@</CalendarId>", [event uid]];
|
||||
[s appendFormat: @"<Status>%d</Status>", status];
|
||||
[s appendString: @"</Result>"];
|
||||
[s appendString: @"</MeetingResponse>"];
|
||||
[s appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
||||
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
||||
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
|
||||
[s appendString: @"<Result>"];
|
||||
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
|
||||
[s appendFormat: @"<CalendarId>%@</CalendarId>", calendarId];
|
||||
[s appendFormat: @"<Status>%d</Status>", status];
|
||||
[s appendString: @"</Result>"];
|
||||
[s appendString: @"</MeetingResponse>"];
|
||||
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
||||
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
else
|
||||
{
|
||||
[theResponse setStatus: 500];
|
||||
}
|
||||
[theResponse setContent: d];
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1042,7 +1077,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
[s appendString: @"<Recipient>"];
|
||||
[s appendFormat: @"<Type>%d</Type>", 1];
|
||||
[s appendFormat: @"<DisplayName>%@</DisplayName>", [user cn]];
|
||||
[s appendFormat: @"<EmailAddress>%@</EmailAddress>", [user systemEmail]];
|
||||
[s appendFormat: @"<EmailAddress>%@</EmailAddress>", [[user allEmails] objectAtIndex: 0]];
|
||||
|
||||
// Freebusy structure: http://msdn.microsoft.com/en-us/library/gg663493(v=exchg.80).aspx
|
||||
[s appendString: @"<Availability>"];
|
||||
|
|
|
@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#import <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#import <NGCards/iCalCalendar.h>
|
||||
|
@ -48,9 +49,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
|
||||
#include "iCalTimeZone+ActiveSync.h"
|
||||
#include "NSData+ActiveSync.h"
|
||||
#include "NSDate+ActiveSync.h"
|
||||
#include "NSString+ActiveSync.h"
|
||||
|
||||
#include "../SoObjects/Appointments/iCalPerson+SOGo.h"
|
||||
#include "../SoObjects/Mailer/NSString+Mail.h"
|
||||
#include "../SoObjects/Mailer/SOGoMailBodyPart.h"
|
||||
|
||||
|
@ -133,39 +136,37 @@ struct GlobalObjectId {
|
|||
//
|
||||
// For debugging purposes...
|
||||
//
|
||||
// - (NSString *) _uidFromGlobalObjectId: (NSData *) objectId
|
||||
// {
|
||||
// NSString *uid;
|
||||
- (NSString *) _uidFromGlobalObjectId: (NSData *) objectId
|
||||
{
|
||||
NSString *uid;
|
||||
|
||||
// struct GlobalObjectId *newGlobalId;
|
||||
// NSUInteger length;
|
||||
// uint8_t *bytes;
|
||||
struct GlobalObjectId *newGlobalId;
|
||||
NSUInteger length;
|
||||
uint8_t *bytes;
|
||||
|
||||
// length = [objectId length];
|
||||
// uid = nil;
|
||||
length = [objectId length];
|
||||
uid = nil;
|
||||
|
||||
// bytes = malloc(length*sizeof(uint8_t));
|
||||
// [objectId getBytes: bytes length: length];
|
||||
bytes = malloc(length*sizeof(uint8_t));
|
||||
[objectId getBytes: bytes length: length];
|
||||
|
||||
// newGlobalId = (struct GlobalObjectId *)bytes;
|
||||
newGlobalId = (struct GlobalObjectId *)bytes;
|
||||
|
||||
// // We must take the offset (dataPrefix) into account
|
||||
// uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSASCIIStringEncoding];
|
||||
// free(bytes);
|
||||
|
||||
// return AUTORELEASE(uid);
|
||||
// }
|
||||
// We must take the offset (dataPrefix) into account
|
||||
uid = [[NSString alloc] initWithBytes: newGlobalId->Data+12 length: newGlobalId->Size-12 encoding: NSASCIIStringEncoding];
|
||||
free(bytes);
|
||||
|
||||
return AUTORELEASE(uid);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSString *) _emailAddressesFrom: (NSArray *) enveloppeAddresses
|
||||
{
|
||||
NSMutableArray *addresses;
|
||||
NSString *rc;
|
||||
NGImap4EnvelopeAddress *address;
|
||||
NSString *email;
|
||||
NSMutableArray *addresses;
|
||||
NSString *email, *rc;
|
||||
int i, max;
|
||||
|
||||
rc = nil;
|
||||
|
@ -177,7 +178,7 @@ struct GlobalObjectId {
|
|||
for (i = 0; i < max; i++)
|
||||
{
|
||||
address = [enveloppeAddresses objectAtIndex: i];
|
||||
email = [address email];
|
||||
email = [NSString stringWithFormat: @"\"%@\" <%@>", [address personalName], [address baseEMail]];
|
||||
|
||||
if (email)
|
||||
[addresses addObject: email];
|
||||
|
@ -317,10 +318,19 @@ struct GlobalObjectId {
|
|||
|
||||
if (bodyPart)
|
||||
{
|
||||
iCalCalendar *calendar;
|
||||
NSData *calendarData;
|
||||
|
||||
calendarData = [bodyPart fetchBLOB];
|
||||
return [iCalCalendar parseSingleFromSource: calendarData];
|
||||
calendar = nil;
|
||||
|
||||
NS_DURING
|
||||
calendar = [iCalCalendar parseSingleFromSource: calendarData];
|
||||
NS_HANDLER
|
||||
calendar = nil;
|
||||
NS_ENDHANDLER
|
||||
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,24 +354,16 @@ struct GlobalObjectId {
|
|||
|
||||
s = [NSMutableString string];
|
||||
|
||||
// From
|
||||
value = [self _emailAddressesFrom: [[self envelope] from]];
|
||||
if (value)
|
||||
[s appendFormat: @"<From xmlns=\"Email:\">%@</From>", [value activeSyncRepresentation]];
|
||||
|
||||
// To - "The value of this element contains one or more e-mail addresses.
|
||||
// If there are multiple e-mail addresses, they are separated by commas."
|
||||
value = [self _emailAddressesFrom: [[self envelope] to]];
|
||||
if (value)
|
||||
[s appendFormat: @"<To xmlns=\"Email:\">%@</To>", [value activeSyncRepresentation]];
|
||||
|
||||
// DisplayTo
|
||||
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">\"%@\"</DisplayTo>", [[context activeUser] login]];
|
||||
|
||||
// Cc - same syntax as the To field
|
||||
value = [self _emailAddressesFrom: [[self envelope] cc]];
|
||||
// From
|
||||
value = [self _emailAddressesFrom: [[self envelope] from]];
|
||||
if (value)
|
||||
[s appendFormat: @"<Cc xmlns=\"Email:\">%@</Cc>", [value activeSyncRepresentation]];
|
||||
[s appendFormat: @"<From xmlns=\"Email:\">%@</From>", [value activeSyncRepresentation]];
|
||||
|
||||
// Subject
|
||||
value = [self decodedSubject];
|
||||
|
@ -376,52 +378,97 @@ struct GlobalObjectId {
|
|||
if (value)
|
||||
[s appendFormat: @"<DateReceived xmlns=\"Email:\">%@</DateReceived>", [value activeSyncRepresentationWithoutSeparators]];
|
||||
|
||||
// DisplayTo
|
||||
[s appendFormat: @"<DisplayTo xmlns=\"Email:\">%@</DisplayTo>", [[context activeUser] login]];
|
||||
|
||||
// Cc - same syntax as the To field
|
||||
value = [self _emailAddressesFrom: [[self envelope] cc]];
|
||||
if (value)
|
||||
[s appendFormat: @"<Cc xmlns=\"Email:\">%@</Cc>", [value activeSyncRepresentation]];
|
||||
|
||||
// Importance - FIXME
|
||||
[s appendFormat: @"<Importance xmlns=\"Email:\">%@</Importance>", @"1"];
|
||||
|
||||
// Read
|
||||
[s appendFormat: @"<Read xmlns=\"Email:\">%d</Read>", ([self read] ? 1 : 0)];
|
||||
|
||||
|
||||
// We handle MeetingRequest
|
||||
calendar = [self calendarFromIMIPMessage];
|
||||
|
||||
if (calendar)
|
||||
{
|
||||
NSString *method, *className;
|
||||
iCalPerson *attendee;
|
||||
iCalTimeZone *tz;
|
||||
iCalEvent *event;
|
||||
|
||||
iCalPersonPartStat partstat;
|
||||
int v;
|
||||
|
||||
event = [[calendar events] lastObject];
|
||||
method = [[event parent] method];
|
||||
|
||||
attendee = [event findAttendeeWithEmail: [[[context activeUser] allEmails] objectAtIndex: 0]];
|
||||
partstat = [attendee participationStatus];
|
||||
|
||||
// We generate the correct MessageClass
|
||||
if ([method isEqualToString: @"REQUEST"])
|
||||
className = @"IPM.Schedule.Meeting.Request";
|
||||
else if ([method isEqualToString: @"REPLY"])
|
||||
{
|
||||
switch (partstat)
|
||||
{
|
||||
case iCalPersonPartStatAccepted:
|
||||
className = @"IPM.Schedule.Meeting.Resp.Pos";
|
||||
break;
|
||||
case iCalPersonPartStatDeclined:
|
||||
className = @"IPM.Schedule.Meeting.Resp.Neg";
|
||||
break;
|
||||
case iCalPersonPartStatTentative:
|
||||
className = @"IPM.Schedule.Meeting.Resp.Tent";
|
||||
break;
|
||||
default:
|
||||
className = @"IPM.Appointment";
|
||||
NSLog(@"unhandled part stat");
|
||||
}
|
||||
}
|
||||
else if ([method isEqualToString: @"COUNTER"])
|
||||
className = @"IPM.Schedule.Meeting.Resp.Tent";
|
||||
else if ([method isEqualToString: @"CANCEL"])
|
||||
className = @"IPM.Schedule.Meeting.Cancelled";
|
||||
else
|
||||
className = @"IPM.Appointment";
|
||||
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", className];
|
||||
|
||||
[s appendString: @"<MeetingRequest xmlns=\"Email:\">"];
|
||||
|
||||
[s appendFormat: @"<AllDayEvent xmlns=\"Email:\">%d</AllDayEvent>", ([event isAllDay] ? 1 : 0)];
|
||||
|
||||
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
||||
if ([event startDate])
|
||||
[s appendFormat: @"<StartTime xmlns=\"Email:\">%@</StartTime>", [[event startDate] activeSyncRepresentationWithoutSeparators]];
|
||||
|
||||
if ([event timeStampAsDate])
|
||||
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event timeStampAsDate] activeSyncRepresentationWithoutSeparators]];
|
||||
else if ([event created])
|
||||
[s appendFormat: @"<DTStamp xmlns=\"Email:\">%@</DTStamp>", [[event created] activeSyncRepresentationWithoutSeparators]];
|
||||
|
||||
// StartTime -- http://msdn.microsoft.com/en-us/library/ee157132(v=exchg.80).aspx
|
||||
if ([event startDate])
|
||||
[s appendFormat: @"<StartTime xmlns=\"Email:\">%@</StartTime>", [[event startDate] activeSyncRepresentationWithoutSeparators]];
|
||||
|
||||
// EndTime -- http://msdn.microsoft.com/en-us/library/ee157945(v=exchg.80).aspx
|
||||
if ([event endDate])
|
||||
[s appendFormat: @"<EndTime xmlns=\"Email:\">%@</EndTime>", [[event endDate] activeSyncRepresentationWithoutSeparators]];
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
if (!tz)
|
||||
tz = [iCalTimeZone timeZoneForName: @"Europe/London"];
|
||||
|
||||
[s appendFormat: @"<TimeZone xmlns=\"Email:\">%@</TimeZone>", [[tz activeSyncRepresentation] stringByReplacingString: @"\n" withString: @""]];;
|
||||
|
||||
[s appendFormat: @"<InstanceType xmlns=\"Email:\">%d</InstanceType>", 0];
|
||||
[s appendFormat: @"<Organizer xmlns=\"Email:\">%@</Organizer>", [[event organizer] rfc822Email]];
|
||||
[s appendFormat: @"<ResponseRequested xmlns=\"Email:\">%d</ResponseRequested>", 1];
|
||||
|
||||
// Location
|
||||
if ([[event location] length])
|
||||
[s appendFormat: @"<Location xmlns=\"Email:\">%@</Location>", [[event location] activeSyncRepresentation]];
|
||||
|
||||
[s appendFormat: @"<Organizer xmlns=\"Email:\">%@</Organizer>", [[[event organizer] mailAddress] activeSyncRepresentation]];
|
||||
|
||||
// This will trigger the SendMail command. We set it to no for email invitations as
|
||||
// SOGo will send emails when MeetingResponse is called.
|
||||
[s appendFormat: @"<ResponseRequested xmlns=\"Email:\">%d</ResponseRequested>", 0];
|
||||
|
||||
// Sensitivity
|
||||
if ([[event accessClass] isEqualToString: @"PRIVATE"])
|
||||
|
@ -433,23 +480,33 @@ struct GlobalObjectId {
|
|||
|
||||
[s appendFormat: @"<Sensitivity xmlns=\"Email:\">%d</Sensitivity>", v];
|
||||
|
||||
[s appendFormat: @"<BusyStatus xmlns=\"Email:\">%d</BusyStatus>", 2];
|
||||
|
||||
// Timezone
|
||||
tz = [(iCalDateTime *)[event firstChildWithTag: @"dtstart"] timeZone];
|
||||
|
||||
if (!tz)
|
||||
tz = [iCalTimeZone timeZoneForName: @"Europe/London"];
|
||||
|
||||
[s appendFormat: @"<TimeZone xmlns=\"Email:\">%@</TimeZone>", [tz activeSyncRepresentation]];
|
||||
|
||||
|
||||
// We disallow new time proposals
|
||||
[s appendFormat: @"<DisallowNewTimeProposal xmlns=\"Email:\">%d</DisallowNewTimeProposal>", 1];
|
||||
|
||||
// We set the right message type - we must set AS version to 14.1 for this
|
||||
//[s appendFormat: @"<MeetingMessageType xmlns=\"Email2:\">%d</MeetingMessageType>", 1];
|
||||
|
||||
// From http://blogs.msdn.com/b/exchangedev/archive/2011/07/22/working-with-meeting-requests-in-exchange-activesync.aspx:
|
||||
//
|
||||
// "Clients that need to determine whether the GlobalObjId element for a meeting request corresponds to an existing Calendar
|
||||
// object in the Calendar folder have to convert the GlobalObjId element value to a UID element value to make the comparison."
|
||||
//
|
||||
globalObjId = [self _computeGlobalObjectIdFromEvent: event];
|
||||
[s appendFormat: @"<GlobalObjId xmlns=\"Email:\">%@</GlobalObjId>", [[globalObjId stringByEncodingBase64] stringByReplacingString: @"\n" withString: @""]];
|
||||
[s appendFormat: @"<GlobalObjId xmlns=\"Email:\">%@</GlobalObjId>", [globalObjId activeSyncRepresentation]];
|
||||
|
||||
// We set the right message type - we must set AS version to 14.1 for this
|
||||
[s appendFormat: @"<MeetingMessageType xmlns=\"Email2:\">%d</MeetingMessageType>", 1];
|
||||
[s appendString: @"</MeetingRequest>"];
|
||||
|
||||
// MesssageClass and ContentClass
|
||||
[s appendFormat: @"<MessageClass xmlns=\"Email:\">%@</MessageClass>", @"IPM.Schedule.Meeting.Request"];
|
||||
// ContentClass
|
||||
[s appendFormat: @"<ContentClass xmlns=\"Email:\">%@</ContentClass>", @"urn:content-classes:calendarmessage"];
|
||||
}
|
||||
else
|
||||
|
@ -476,19 +533,29 @@ struct GlobalObjectId {
|
|||
if (d)
|
||||
{
|
||||
NSString *content;
|
||||
int len;
|
||||
int len, truncated;
|
||||
|
||||
content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding];
|
||||
|
||||
// FIXME: This is a hack. We should normally avoid doing this as we might get
|
||||
// broken encodings. We should rather tell that the data was truncated and expect
|
||||
// a ItemOperations call to download the whole base64 encoding multipart.
|
||||
if (!content)
|
||||
content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding];
|
||||
|
||||
AUTORELEASE(content);
|
||||
|
||||
content = [content activeSyncRepresentation];
|
||||
truncated = 0;
|
||||
|
||||
len = [content length];
|
||||
|
||||
[s appendString: @"<Body xmlns=\"AirSyncBase:\">"];
|
||||
[s appendFormat: @"<Type>%d</Type>", preferredBodyType];
|
||||
[s appendFormat: @"<EstimatedDataSize>%d</EstimatedDataSize>", len];
|
||||
[s appendFormat: @"<Truncated>%d</Truncated>", 0];
|
||||
[s appendFormat: @"<Data>%@</Data>", content];
|
||||
if (!truncated)
|
||||
[s appendFormat: @"<Data>%@</Data>", content];
|
||||
[s appendString: @"</Body>"];
|
||||
}
|
||||
|
||||
|
@ -556,7 +623,6 @@ struct GlobalObjectId {
|
|||
[self addFlags: @"\\Flagged"];
|
||||
else
|
||||
[self removeFlags: @"\\Flagged"];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
s = [NSMutableString string];
|
||||
|
||||
[s appendFormat: @"<AllDayEvent xmlns=\"Calendar:\">%d</AllDayEvent>", ([self isAllDay] ? 1 : 0)];
|
||||
|
||||
// DTStamp -- http://msdn.microsoft.com/en-us/library/ee219470(v=exchg.80).aspx
|
||||
if ([self timeStampAsDate])
|
||||
[s appendFormat: @"<DTStamp xmlns=\"Calendar:\">%@</DTStamp>", [[self timeStampAsDate] activeSyncRepresentationWithoutSeparators]];
|
||||
|
@ -83,18 +85,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
if (!tz)
|
||||
tz = [iCalTimeZone timeZoneForName: @"Europe/London"];
|
||||
|
||||
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [[tz activeSyncRepresentation] stringByReplacingString: @"\n" withString: @""]];;
|
||||
[s appendFormat: @"<TimeZone xmlns=\"Calendar:\">%@</TimeZone>", [tz activeSyncRepresentation]];
|
||||
|
||||
// Organizer
|
||||
// Organizer and other invitations related properties
|
||||
if ((organizer = [self organizer]))
|
||||
{
|
||||
o = [organizer rfc822Email];
|
||||
if ([o length])
|
||||
[s appendFormat: @"<Organizer_Email xmlns=\"Calendar:\">%@</Organizer_Email>", o];
|
||||
{
|
||||
[s appendFormat: @"<Organizer_Email xmlns=\"Calendar:\">%@</Organizer_Email>", o];
|
||||
|
||||
o = [organizer cn];
|
||||
if ([o length])
|
||||
[s appendFormat: @"<Organizer_Name xmlns=\"Calendar:\">%@</Organizer_Name>", o];
|
||||
o = [organizer cn];
|
||||
if ([o length])
|
||||
[s appendFormat: @"<Organizer_Name xmlns=\"Calendar:\">%@</Organizer_Name>", o];
|
||||
|
||||
|
||||
// This depends on the 'NEEDS-ACTION' parameter.
|
||||
// This will trigger the SendMail command
|
||||
[s appendFormat: @"<ResponseRequested xmlns=\"Calendar:\">%d</ResponseRequested>", 1];
|
||||
[s appendFormat: @"<ResponseType xmlns=\"Calendar:\">%d</ResponseType>", 5];
|
||||
[s appendFormat: @"<MeetingStatus xmlns=\"Calendar:\">%d</MeetingStatus>", 3];
|
||||
[s appendFormat: @"<DisallowNewTimeProposal xmlns=\"Calendar:\">%d</DisallowNewTimeProposal>", 1];
|
||||
|
||||
// BusyStatus -- http://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx
|
||||
[s appendFormat: @"<BusyStatus xmlns=\"Calendar:\">%d</BusyStatus>", 2];
|
||||
}
|
||||
}
|
||||
|
||||
// Attendees
|
||||
|
@ -168,9 +183,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
[s appendFormat: @"<Sensitivity xmlns=\"Calendar:\">%d</Sensitivity>", v];
|
||||
|
||||
// BusyStatus -- http://msdn.microsoft.com/en-us/library/ee202290(v=exchg.80).aspx
|
||||
[s appendFormat: @"<BusyStatus xmlns=\"Calendar:\">%d</BusyStatus>", 0];
|
||||
|
||||
// Reminder -- http://msdn.microsoft.com/en-us/library/ee219691(v=exchg.80).aspx
|
||||
// TODO
|
||||
|
||||
|
|
|
@ -189,6 +189,55 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
return s;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (void) _setByDayFromValues: (NSDictionary *) theValues
|
||||
{
|
||||
NSString *day;
|
||||
id o;
|
||||
|
||||
unsigned int day_of_week;
|
||||
int i, week_of_month;
|
||||
|
||||
o = [theValues objectForKey: @"Recurrence_DayOfWeek"];
|
||||
|
||||
// The documentation says WeekOfMonth must be between 1 and 5. The value
|
||||
// 5 means "last week of month"
|
||||
week_of_month = [[theValues objectForKey: @"Recurrence_WeekOfMonth"] intValue];
|
||||
|
||||
if (week_of_month > 4)
|
||||
week_of_month = -1;
|
||||
|
||||
// We find the correct day of the week
|
||||
day_of_week = [o intValue];
|
||||
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
if ((1<<i) == day_of_week)
|
||||
{
|
||||
day_of_week = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
day = [self iCalRepresentationForWeekDay: i];
|
||||
|
||||
[self setSingleValue: [NSString stringWithFormat: @"%d%@",
|
||||
week_of_month, day]
|
||||
forKey: @"byday"];
|
||||
}
|
||||
|
||||
- (void) _setByMonthFromValues: (NSDictionary *) theValues
|
||||
{
|
||||
unsigned int month_of_year;
|
||||
|
||||
month_of_year = [[theValues objectForKey: @"Recurrence_MonthOfYear"] intValue];
|
||||
|
||||
[self setSingleValue: [NSString stringWithFormat: @"%d", month_of_year]
|
||||
forKey: @"bymonth"];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
@ -200,29 +249,95 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
recurrenceType = [[theValues objectForKey: @"Recurrence_Type"] intValue];
|
||||
|
||||
[self setInterval: @"1"];
|
||||
if ((o = [theValues objectForKey: @"Recurrence_Interval"]))
|
||||
{
|
||||
[self setRepeatInterval: [o intValue]];
|
||||
}
|
||||
else
|
||||
[self setRepeatInterval: 1];
|
||||
|
||||
switch (recurrenceType)
|
||||
{
|
||||
//
|
||||
// Daily
|
||||
//
|
||||
case 0:
|
||||
[self setFrequency: iCalRecurrenceFrequenceDaily];
|
||||
if ((o = [theValues objectForKey: @"Recurrence_Interval"]))
|
||||
|
||||
|
||||
// Every weekday
|
||||
if ((o = [theValues objectForKey: @"Recurrence_DayOfWeek"]))
|
||||
{
|
||||
[self setRepeatInterval: [o intValue]];
|
||||
[self setByDayMask: [iCalByDayMask byDayMaskWithWeekDays]];
|
||||
}
|
||||
break;
|
||||
//
|
||||
// Weekly
|
||||
//
|
||||
case 1:
|
||||
[self setFrequency: iCalRecurrenceFrequenceWeekly];
|
||||
|
||||
// 42 == Every Monday, Wednesday and Friday, for example
|
||||
if ((o = [theValues objectForKey: @"Recurrence_DayOfWeek"]))
|
||||
{
|
||||
iCalWeekOccurrences days;
|
||||
unsigned int i, v;
|
||||
|
||||
memset(days, 0, 7 * sizeof(iCalWeekOccurrence));
|
||||
v = [o intValue];
|
||||
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
if (v & (1<<i))
|
||||
days[i] = iCalWeekOccurrenceAll;
|
||||
}
|
||||
|
||||
[self setByDayMask: [iCalByDayMask byDayMaskWithDays: days]];
|
||||
}
|
||||
break;
|
||||
//
|
||||
// Montly
|
||||
//
|
||||
case 2:
|
||||
case 3:
|
||||
[self setFrequency: iCalRecurrenceFrequenceMonthly];
|
||||
|
||||
// The 5th of every X month(s)
|
||||
if ((o = [theValues objectForKey: @"Recurrence_DayOfMonth"]))
|
||||
{
|
||||
[self setValues: [NSArray arrayWithObject: o]
|
||||
atIndex: 0 forKey: @"bymonthday"];
|
||||
}
|
||||
// The 3rd Thursay every X month(s)
|
||||
else if ((o = [theValues objectForKey: @"Recurrence_DayOfWeek"]))
|
||||
{
|
||||
[self _setByDayFromValues: theValues];
|
||||
}
|
||||
|
||||
break;
|
||||
//
|
||||
// Yearly
|
||||
//
|
||||
case 5:
|
||||
case 6:
|
||||
default:
|
||||
[self setFrequency: iCalRecurrenceFrequenceYearly];
|
||||
|
||||
// On April 19th
|
||||
if ((o = [theValues objectForKey: @"Recurrence_DayOfMonth"]))
|
||||
{
|
||||
[self setValues: [NSArray arrayWithObject: o] atIndex: 0
|
||||
forKey: @"bymonthday"];
|
||||
|
||||
[self _setByMonthFromValues: theValues];
|
||||
}
|
||||
else
|
||||
{
|
||||
// On the Second Wednesday of April
|
||||
[self _setByDayFromValues: theValues];
|
||||
[self _setByMonthFromValues: theValues];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
#import <NGExtensions/NGBase64Coding.h>
|
||||
|
||||
#include "NSData+ActiveSync.h"
|
||||
|
||||
struct SYSTEMTIME {
|
||||
uint16_t wYear;
|
||||
uint16_t wMonth;
|
||||
|
@ -157,7 +159,7 @@ struct SYSTEMTIME {
|
|||
[bytes appendBytes: &stDaylightDate length: 16];
|
||||
[bytes appendBytes: &lDaylightBias length: 4];
|
||||
|
||||
return [bytes stringByEncodingBase64];
|
||||
return [bytes activeSyncRepresentation];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in New Issue