diff --git a/ActiveSync/GNUmakefile b/ActiveSync/GNUmakefile index 012ffabd6..65d91f338 100644 --- a/ActiveSync/GNUmakefile +++ b/ActiveSync/GNUmakefile @@ -12,6 +12,7 @@ ActiveSync_OBJC_FILES = \ iCalEvent+ActiveSync.m \ iCalTimeZone+ActiveSync.m \ iCalToDo+ActiveSync.m \ + NSCalendarDate+ActiveSync.m \ NSData+ActiveSync.m \ NSDate+ActiveSync.m \ NGDOMElement+ActiveSync.m \ diff --git a/ActiveSync/NSCalendarDate+ActiveSync.h b/ActiveSync/NSCalendarDate+ActiveSync.h new file mode 100644 index 000000000..b8deed527 --- /dev/null +++ b/ActiveSync/NSCalendarDate+ActiveSync.h @@ -0,0 +1,43 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __NSCALENDARDATEACTIVESYNC_H__ +#define __NSCALENDARDATEACTIVESYNC_H__ + +#import + +@class NSString; + +@interface NSCalendarDate (ActiveSync) + ++ (NSCalendarDate *) dateFromFilterType: (NSString *) theFilterType; + +@end + +#endif diff --git a/ActiveSync/NSCalendarDate+ActiveSync.m b/ActiveSync/NSCalendarDate+ActiveSync.m new file mode 100644 index 000000000..e28a95c57 --- /dev/null +++ b/ActiveSync/NSCalendarDate+ActiveSync.m @@ -0,0 +1,87 @@ +/* + +Copyright (c) 2014, Inverse inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Inverse inc. nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#import "NSCalendarDate+ActiveSync.h" + +#import +#import + +#define ONE_DAY 86400 + +@implementation NSCalendarDate (ActiveSync) + +// +// See http://msdn.microsoft.com/en-us/library/gg709713(v=exchg.80).aspx for available types +// ++ (NSCalendarDate *) dateFromFilterType: (NSString *) theFilterType +{ + NSCalendarDate *d; + + d = [NSCalendarDate calendarDate]; + + if (d) + { + int value; + + switch ([theFilterType intValue]) + { + case 1: + value = ONE_DAY; + break; + case 2: + value = 3 * ONE_DAY; + break; + case 3: + value = 7 * ONE_DAY; + break; + case 4: + value = 14 * ONE_DAY; + break; + case 5: + value = 30 * ONE_DAY; + break; + case 6: + value = 90 * ONE_DAY; + break; + case 7: + value = 180 * ONE_DAY; + break; + case 0: + case 8: + default: + return nil; + } + + return [d initWithTimeIntervalSinceNow: -value]; + } + + return d; +} + +@end diff --git a/ActiveSync/NSData+ActiveSync.m b/ActiveSync/NSData+ActiveSync.m index 3c6601fbf..670474240 100644 --- a/ActiveSync/NSData+ActiveSync.m +++ b/ActiveSync/NSData+ActiveSync.m @@ -35,7 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#define WBXMLDEBUG 0 +#define WBXMLDEBUG 1 @implementation NSData (ActiveSync) diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 6377e68e4..41398c71c 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -98,6 +98,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "iCalToDo+ActiveSync.h" #include "NGDOMElement+ActiveSync.h" #include "NGVCard+ActiveSync.h" +#include "NSCalendarDate+ActiveSync.h" #include "NSDate+ActiveSync.h" #include "NSData+ActiveSync.h" #include "NSString+ActiveSync.h" @@ -447,7 +448,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inCollection: (id) theCollection withSyncKey: (NSString *) theSyncKey withFolderType: (SOGoMicrosoftActiveSyncFolderType) theFolderType - withFilterType: (NSDate *) theFilterType + withFilterType: (NSCalendarDate *) theFilterType inBuffer: (NSMutableString *) theBuffer { int i; @@ -463,97 +464,37 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. switch (theFolderType) { + // Handle all the GCS components case ActiveSyncContactFolder: - { - NSArray *allContacts; - NGVCard *card; - id contact; - - allContacts = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey]; - - for (i = 0; i < [allContacts count]; i++) - { - contact = [theCollection lookupName: [[allContacts objectAtIndex: i] objectForKey: @"c_name"] - inContext: context - acquire: NO]; - - if (![[[allContacts objectAtIndex: i] objectForKey: @"c_component"] isEqualToString: @"vcard"]) - continue; - - // FIXME: we skip list right now - if ([contact respondsToSelector: @selector (vCard)]) - { - card = [contact vCard]; - - [theBuffer appendString: @""]; - [theBuffer appendFormat: @"%@", [contact nameInContainer]]; - [theBuffer appendString: @""]; - - [theBuffer appendString: [card activeSyncRepresentation]]; - - [theBuffer appendString: @""]; - [theBuffer appendString: @""]; - } - } - } - break; case ActiveSyncEventFolder: - { - NSArray *allEvents; - NSDictionary *d; - id eventObject; - - allEvents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey]; - - for (i = 0; i < [allEvents count]; i++) - { - NSString *serverId; - iCalEvent *event; - - d = [allEvents objectAtIndex: i]; - - if (![[d objectForKey: @"c_component"] isEqualToString: @"vevent"]) - continue; - - serverId = [d objectForKey: @"c_name"]; - - [theBuffer appendString: @""]; - [theBuffer appendFormat: @"%@", serverId]; - [theBuffer appendString: @""]; - - eventObject = [theCollection lookupName: serverId inContext: self->context acquire: 0]; - - event = [eventObject component: NO secure: NO]; - - [theBuffer appendString: [event activeSyncRepresentation]]; - - [theBuffer appendString: @""]; - [theBuffer appendString: @""]; - - } // for (i = 0; i < [allEvents count]; i++) - } - break; case ActiveSyncTaskFolder: { - NSArray *allTasks; - NSDictionary *task; - id taskObject; - - allTasks = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey]; - - for (i = 0; i < [allTasks count]; i++) - - { - int deleted; - - task = [allTasks objectAtIndex: i]; - deleted = [[task objectForKey: @"c_deleted"] intValue]; + id sogoObject, componentObject; + NSString *uid, *component_name; + NSDictionary *component; + NSArray *allComponents; - if (!deleted && ![[task objectForKey: @"c_component"] isEqualToString: @"vtodo"]) + BOOL updated; + int deleted; + + if (theFolderType == ActiveSyncContactFolder) + component_name = @"vcard"; + else if (theFolderType == ActiveSyncEventFolder) + component_name = @"vevent"; + else + component_name = @"vtodo"; + + allComponents = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; + + for (i = 0; i < [allComponents count]; i++) + { + component = [allComponents objectAtIndex: i]; + deleted = [[component objectForKey: @"c_deleted"] intValue]; + + if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name]) continue; - NSString *uid; - uid = [task objectForKey: @"c_name"]; + uid = [component objectForKey: @"c_name"]; if (deleted) { @@ -563,12 +504,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } else { - iCalToDo *todo; - BOOL updated; - updated = YES; - if ([[task objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue]) + if ([[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue]) updated = NO; if (updated) @@ -579,11 +517,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theBuffer appendFormat: @"%@", uid]; [theBuffer appendString: @""]; - taskObject = [theCollection lookupName: uid inContext: self->context acquire: 0]; + sogoObject = [theCollection lookupName: uid + inContext: context + acquire: 0]; - todo = [taskObject component: NO secure: NO]; + if (theFolderType == ActiveSyncContactFolder) + componentObject = [sogoObject vCard]; + else + componentObject = [sogoObject component: NO secure: NO]; - [theBuffer appendString: [todo activeSyncRepresentation]]; + [theBuffer appendString: [componentObject activeSyncRepresentation]]; [theBuffer appendString: @""]; @@ -598,12 +541,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. case ActiveSyncMailFolder: default: { + SOGoMailObject *mailObject; + NSString *uid, *command; NSDictionary *aMessage; NSArray *allMessages; - NSString *uid, *command; - SOGoMailObject *mailObject; - allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey]; + allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; for (i = 0; i < [allMessages count]; i++) { @@ -779,7 +722,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inCollection: collection withSyncKey: syncKey withFolderType: folderType - withFilterType: [NSDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] + withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] inBuffer: theBuffer]; } diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 553e52b8b..ce3793ec7 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -104,7 +104,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "iCalToDo+ActiveSync.h" #include "NGMimeMessage+ActiveSync.h" #include "NGVCard+ActiveSync.h" +#include "NSCalendarDate+ActiveSync.h" #include "NSData+ActiveSync.h" +#include "NSDate+ActiveSync.h" #include "NSString+ActiveSync.h" #include "SOGoActiveSyncConstants.h" #include "SOGoMailObject+ActiveSync.h" @@ -516,12 +518,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) processGetItemEstimate: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { + EOQualifier *notDeletedQualifier, *sinceDateQualifier; + NSString *collectionId, *realCollectionId; + id currentFolder, currentCollection; SOGoMailAccounts *accountsFolder; SOGoUserFolder *userFolder; - id currentFolder, currentCollection; - - NSString *collectionId, *realCollectionId; + EOAndQualifier *qualifier; + NSCalendarDate *filter; NSMutableString *s; + NSArray *uids; NSData *d; SOGoMicrosoftActiveSyncFolderType folderType; @@ -541,13 +546,29 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. currentCollection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", realCollectionId] inContext: context acquire: NO]; - - // FIXME: we ignore FilterType for now - NSArray *uids = [currentCollection fetchUIDsMatchingQualifier: [EOQualifier qualifierWithQualifierFormat: - @"(not (flags = %@))", - @"deleted"] - sortOrdering: @"REVERSE ARRIVAL" - threaded: NO]; + // + // For IMAP, we simply build a request like this: + // + // . UID SORT (SUBJECT) UTF-8 SINCE 1-Jan-2014 NOT DELETED + // * SORT 124576 124577 124579 124578 + // . OK Completed (4 msgs in 0.000 secs) + // + filter = [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]; + + notDeletedQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(not (flags = %@))", + @"deleted"]; + sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(DATE >= %@)", filter]; + + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: notDeletedQualifier, sinceDateQualifier, + nil]; + AUTORELEASE(qualifier); + + uids = [currentCollection fetchUIDsMatchingQualifier: qualifier + sortOrdering: @"REVERSE ARRIVAL" + threaded: NO]; [s appendString: @""]; diff --git a/SoObjects/Mailer/SOGoMailFolder.h b/SoObjects/Mailer/SOGoMailFolder.h index 2c9855bbe..cff0c1b9b 100644 --- a/SoObjects/Mailer/SOGoMailFolder.h +++ b/SoObjects/Mailer/SOGoMailFolder.h @@ -96,6 +96,10 @@ - (NSString *) davCollectionTag; +- (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties + matchingSyncToken: (NSString *) syncToken + fromDate: (NSCalendarDate *) theStartDate; + /* flags */ - (NSException *) addFlagsToAllMessages: (id) _f; diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index 76fb70073..904ac770b 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -1992,6 +1992,7 @@ static NSString *defaultUserID = @"anyone"; // - (NSArray *) syncTokenFieldsWithProperties: (NSArray *) theProperties matchingSyncToken: (NSString *) theSyncToken + fromDate: (NSCalendarDate *) theStartDate { EOQualifier *searchQualifier; NSMutableArray *allTokens; @@ -2001,13 +2002,21 @@ static NSString *defaultUserID = @"anyone"; int uidnext, highestmodseq, i; allTokens = [NSMutableArray array]; - a = [theSyncToken componentsSeparatedByString: @"-"]; - uidnext = [[a objectAtIndex: 0] intValue]; - highestmodseq = [[a objectAtIndex: 1] intValue]; + if ([theSyncToken isEqualToString: @"-1"]) + { + uidnext = highestmodseq = 0; + } + else + { + a = [theSyncToken componentsSeparatedByString: @"-"]; + uidnext = [[a objectAtIndex: 0] intValue]; + highestmodseq = [[a objectAtIndex: 1] intValue]; + } + // We first make sure QRESYNC is enabled [[self imap4Connection] enableExtensions: [NSArray arrayWithObject: @"QRESYNC"]]; - + // We fetch new messages and modified messages if (highestmodseq) @@ -2031,6 +2040,17 @@ static NSString *defaultUserID = @"anyone"; searchQualifier = [self _nonDeletedQualifier]; } + if (theStartDate) + { + EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"(DATE >= %@)", theStartDate]; + + searchQualifier = [[EOAndQualifier alloc] initWithQualifiers: searchQualifier, sinceDateQualifier, + nil]; + [searchQualifier autorelease]; + } + + // we fetch modified or added uids uids = [self fetchUIDsMatchingQualifier: searchQualifier sortOrdering: nil]; diff --git a/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h index 7b23b0a52..ec6795bd2 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.h +++ b/SoObjects/SOGo/SOGoGCSFolder.h @@ -126,7 +126,9 @@ - (NSString *) davCollectionTag; - (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties - matchingSyncToken: (NSString *) syncToken; + matchingSyncToken: (NSString *) syncToken + fromDate: (NSCalendarDate *) theStartDate; + /* multiget helper */ - (WOResponse *) performMultigetInContext: (WOContext *) queryContext diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index 99a92ce40..668a23fea 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -1101,6 +1101,7 @@ static NSArray *childRecordFields = nil; // - (NSArray *) syncTokenFieldsWithProperties: (NSDictionary *) properties matchingSyncToken: (NSString *) syncToken + fromDate: (NSCalendarDate *) theStartDate { /* TODO: - validation: @@ -1124,8 +1125,20 @@ static NSArray *childRecordFields = nil; if ([syncToken length]) { syncTokenInt = [syncToken intValue]; + qualifier = [EOQualifier qualifierWithQualifierFormat: @"c_lastmodified > %d", syncTokenInt]; + + if (theStartDate) + { + EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"c_creationdate > %d", (int)[theStartDate timeIntervalSince1970]]; + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, qualifier, + nil]; + [qualifier autorelease]; + } + mRecords = [NSMutableArray arrayWithArray: [self _fetchFields: fields withQualifier: qualifier ignoreDeleted: YES]]; @@ -1145,10 +1158,24 @@ static NSArray *childRecordFields = nil; qualifier = [EOQualifier qualifierWithQualifierFormat: filter]; else qualifier = nil; - records = [self _fetchFields: fields withQualifier: qualifier + + if (theStartDate) + { + EOQualifier *sinceDateQualifier = [EOQualifier qualifierWithQualifierFormat: + @"c_creationdate > %d", (int)[theStartDate timeIntervalSince1970]]; + + qualifier = [[EOAndQualifier alloc] initWithQualifiers: sinceDateQualifier, qualifier, + nil]; + [qualifier autorelease]; + } + + records = [self _fetchFields: fields + withQualifier: qualifier ignoreDeleted: YES]; } + + return records; } @@ -1407,7 +1434,8 @@ static NSArray *childRecordFields = nil; firstElementWithTag: @"prop" inNamespace: XMLNS_WEBDAV]; properties = [self parseDAVRequestedProperties: propElement]; records = [self syncTokenFieldsWithProperties: properties - matchingSyncToken: syncToken]; + matchingSyncToken: syncToken + fromDate: nil]; [self _appendComponentProperties: [properties allKeys] fromRecords: records matchingSyncToken: [syncToken intValue]