From c426afd7f23a39382e3a3449164190d526ce28cc Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Thu, 15 May 2014 15:03:24 -0400 Subject: [PATCH] New cache subsystem for ActiveSync. --- ActiveSync/GNUmakefile | 6 +- ActiveSync/NSArray+SyncCache.h | 49 ++++ ActiveSync/NSArray+SyncCache.m | 84 ++++++ ActiveSync/SOGoActiveSyncDispatcher+Sync.m | 289 +++++++++++++++------ ActiveSync/SOGoActiveSyncDispatcher.h | 5 + ActiveSync/SOGoActiveSyncDispatcher.m | 120 ++++++++- ActiveSync/SOGoSyncCacheObject.h | 55 ++++ ActiveSync/SOGoSyncCacheObject.m | 122 +++++++++ SoObjects/SOGo/SOGoCacheGCSObject.h | 4 +- SoObjects/SOGo/SOGoCacheObject.m | 2 +- 10 files changed, 638 insertions(+), 98 deletions(-) create mode 100644 ActiveSync/NSArray+SyncCache.h create mode 100644 ActiveSync/NSArray+SyncCache.m create mode 100644 ActiveSync/SOGoSyncCacheObject.h create mode 100644 ActiveSync/SOGoSyncCacheObject.m diff --git a/ActiveSync/GNUmakefile b/ActiveSync/GNUmakefile index 969580670..926e02725 100644 --- a/ActiveSync/GNUmakefile +++ b/ActiveSync/GNUmakefile @@ -14,16 +14,18 @@ ActiveSync_OBJC_FILES = \ iCalRecurrenceRule+ActiveSync.m \ iCalTimeZone+ActiveSync.m \ iCalToDo+ActiveSync.m \ - NSCalendarDate+ActiveSync.m \ + NSCalendarDate+ActiveSync.m \ NSData+ActiveSync.m \ NSDate+ActiveSync.m \ NGDOMElement+ActiveSync.m \ NGMimeMessage+ActiveSync.m \ NGVCard+ActiveSync.m \ + NSArray+SyncCache.m \ NSString+ActiveSync.m \ SOGoActiveSyncDispatcher.m \ SOGoActiveSyncDispatcher+Sync.m \ - SOGoMailObject+ActiveSync.m \ + SOGoMailObject+ActiveSync.m \ + SOGoSyncCacheObject.m \ SoObjectWebDAVDispatcher+ActiveSync.m ActiveSync_RESOURCE_FILES += \ diff --git a/ActiveSync/NSArray+SyncCache.h b/ActiveSync/NSArray+SyncCache.h new file mode 100644 index 000000000..8eebd2475 --- /dev/null +++ b/ActiveSync/NSArray+SyncCache.h @@ -0,0 +1,49 @@ +/* + +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 __NSARRAYSYNCCACHE_H__ +#define __NSARRAYSYNCCACHE_H__ + +#import + +@class NSDictionary; + +@interface NSMutableArray (SyncCache) + +- (id) initWithDictionary: (NSDictionary *) theDictionary; + +@end + +@interface NSArray (SyncCache) + +- (NSDictionary *) dictionaryValue; + +@end + +#endif diff --git a/ActiveSync/NSArray+SyncCache.m b/ActiveSync/NSArray+SyncCache.m new file mode 100644 index 000000000..c499f2e23 --- /dev/null +++ b/ActiveSync/NSArray+SyncCache.m @@ -0,0 +1,84 @@ +/* + +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 "NSArray+SyncCache.h" + +#import + +#include "SOGoSyncCacheObject.h" + +@implementation NSMutableArray (SyncCache) + +- (id) initWithDictionary: (NSDictionary *) theDictionary +{ + SOGoSyncCacheObject *o; + NSArray *allKeys; + id key; + int i; + + self = [self initWithCapacity: [theDictionary count]]; + + allKeys = [theDictionary allKeys]; + + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + o = [SOGoSyncCacheObject syncCacheObjectWithUID: key + sequence: [theDictionary objectForKey: key]]; + [self addObject: o]; + } + + return self; +} + +@end + +// +// +// +@implementation NSArray (SyncCache) + +- (NSDictionary *) dictionaryValue +{ + NSMutableDictionary *d; + SOGoSyncCacheObject *o; + int i; + + d = [NSMutableDictionary dictionary]; + + for (i = 0; i < [self count]; i++) + { + o = [self objectAtIndex: i]; + [d setObject: [o sequence] forKey: [o uid]]; + } + + return d; +} + +@end diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 396c2668a..49cc21cee 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -32,9 +32,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import +#import #import #import #import +#import #import #import @@ -72,6 +74,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -101,11 +104,46 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "NSString+ActiveSync.h" #include "SOGoActiveSyncConstants.h" #include "SOGoMailObject+ActiveSync.h" +#include "SOGoSyncCacheObject.h" #include @implementation SOGoActiveSyncDispatcher (Sync) +- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata + forKey: (NSString *) theFolderKey +{ + SOGoCacheGCSObject *o; + NSString *key; + + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey]; + + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + [[o properties] removeAllObjects]; + [[o properties] addEntriesFromDictionary: theFolderMetadata]; + [o save]; +} + +- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey +{ + SOGoCacheGCSObject *o; + NSString *key; + + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey]; + + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + return [o properties]; +} + + // // // @@ -423,7 +461,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theBuffer appendString: @""]; } - // // The method handles // @@ -450,6 +487,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return; s = [NSMutableString string]; + more_available = NO; switch (theFolderType) @@ -567,106 +605,184 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. case ActiveSyncMailFolder: default: { - NSMutableArray *addedOrChangedMessages; - NSString *uid, *command, *key; + NSMutableDictionary *syncCache, *dateCache, *folderMetadata; + SOGoSyncCacheObject *lastCacheObject, *aCacheObject; + NSMutableArray *allCacheObjects, *sortedBySequence; + SOGoMailObject *mailObject; - NSDictionary *aMessage; NSArray *allMessages; - int deleted_count; + + int j, k, return_count; + BOOL found_in_cache; + allMessages = [theCollection syncTokenFieldsWithProperties: nil matchingSyncToken: theSyncKey fromDate: theFilterType]; - addedOrChangedMessages = [NSMutableArray array]; - deleted_count = 0; - - // 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]; - // We first check the number of deleted messages we have - // We do NOT honor the window size here as it seems to be - // impossible to get the modseq of an expunged message so - // we can't iterate in the list of deleted messages. - for (i = 0; i < max; i++) - { - aMessage = [allMessages objectAtIndex: i]; - - uid = [[[aMessage allKeys] lastObject] stringValue]; - command = [[aMessage allValues] lastObject]; - - if ([command isEqualToString: @"deleted"]) - { - [s appendString: @""]; - [s appendFormat: @"%@", uid]; - [s appendString: @""]; - deleted_count++; - } - else - { - [addedOrChangedMessages addObject: aMessage]; - } - } - - // We then "pad" with our added/changed messages. We ALWAYS - // at least return one if available - max = [addedOrChangedMessages count]; + allCacheObjects = [NSMutableArray array]; for (i = 0; i < max; i++) { - aMessage = [addedOrChangedMessages objectAtIndex: i]; + [allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject] + sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]]; + } + + // If it's a new Sync operation, ignore anything we might have + // in our preferences. + if ([theSyncKey isEqualToString: @"-1"]) + { + folderMetadata = [NSMutableDictionary dictionary]; - uid = [[[aMessage allKeys] lastObject] stringValue]; - command = [[aMessage allValues] lastObject]; + [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; + [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; - // We check for Outlook stupidity to avoid creating duplicates - see the comment - // in SOGoActiveSyncDispatcher.m: -processMoveItems:inResponse: for more details. - key = [NSString stringWithFormat: @"%@+%@+%@+%@", - [[context activeUser] login], - [context objectForKey: @"DeviceType"], - [theCollection displayName], - uid]; - - if ([[SOGoCache sharedCache] valueForKey: key]) + // TODO - Generate GUID + //[folderMetadata setObject: @"FOO-BAR-BAZ" forKey: @"GUID"]; + } + else + folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]]; + + syncCache = [folderMetadata objectForKey: @"SyncCache"]; + dateCache = [folderMetadata objectForKey: @"DateCache"]; + + sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache]; + [sortedBySequence sortUsingSelector: @selector(compareSequence:)]; + [sortedBySequence autorelease]; + + [allCacheObjects sortUsingSelector: @selector(compareSequence:)]; + + //NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]); + //NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]); + + lastCacheObject = [sortedBySequence lastObject]; + + if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject) + { + for (j = 0; j < [allCacheObjects count]; j++) { - [[SOGoCache sharedCache] removeValueForKey: key]; - command = @"changed"; + if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]]) + { + // Found out where we're at, let's continue from there... + found_in_cache = YES; + break; + } } - - if ([command isEqualToString: @"added"]) - [s appendString: @""]; - else - [s appendString: @""]; - - mailObject = [theCollection lookupName: uid - inContext: context - acquire: 0]; - - [s appendFormat: @"%@", uid]; - [s appendString: @""]; - [s appendString: [mailObject activeSyncRepresentationInContext: context]]; - [s appendString: @""]; - - if ([command isEqualToString: @"added"]) - [s appendString: @""]; - else - [s appendString: @""]; - - - // We check if we must stop padding - if (i+1+deleted_count > theWindowSize) + } + else + found_in_cache = NO; + + + if (found_in_cache) + k = j+1; + else + { + k = 0; + j = 0; + } + + //NSLog(@"found in cache: %d k = %d", found_in_cache, k); + + return_count = 0; + + for (; k < [allCacheObjects count]; k++) + { + // Check for the WindowSize and slice accordingly + if (return_count >= theWindowSize) { + NSString *lastSequence; more_available = YES; + + lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? @"1" : [aCacheObject sequence]); + *theLastServerKey = [NSString stringWithFormat: @"%@-%@", [aCacheObject uid], lastSequence]; + //NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey); break; } + + aCacheObject = [allCacheObjects objectAtIndex: k]; + + // If found in cache, it's either a Change or a Delete + if ([syncCache objectForKey: [aCacheObject uid]]) + { + if ([[aCacheObject sequence] isEqual: [NSNull null]]) + { + // Deleted + [s appendString: @""]; + [s appendFormat: @"%@", [aCacheObject uid]]; + [s appendString: @""]; + + [syncCache removeObjectForKey: [aCacheObject uid]]; + } + else + { + // Changed + outlook_hack: + mailObject = [theCollection lookupName: [aCacheObject uid] + inContext: context + acquire: 0]; + + [s appendString: @""]; + [s appendFormat: @"%@", [aCacheObject uid]]; + [s appendString: @""]; + [s appendString: [mailObject activeSyncRepresentationInContext: context]]; + [s appendString: @""]; + [s appendString: @""]; + + [syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]]; + } + + return_count++; + } + else + { + // Added + if (![[aCacheObject sequence] isEqual: [NSNull null]]) + { + NSString *key; + + // We check for Outlook stupidity to avoid creating duplicates - see the comment + // in SOGoActiveSyncDispatcher.m: -processMoveItems:inResponse: for more details. + key = [NSString stringWithFormat: @"%@+%@+%@+%@", + [[context activeUser] login], + [context objectForKey: @"DeviceType"], + [theCollection displayName], + [aCacheObject uid]]; + + if ([[SOGoCache sharedCache] valueForKey: key]) + { + [[SOGoCache sharedCache] removeValueForKey: key]; + goto outlook_hack; + } + + mailObject = [theCollection lookupName: [aCacheObject uid] + inContext: context + acquire: 0]; + + [s appendString: @""]; + [s appendFormat: @"%@", [aCacheObject uid]]; + [s appendString: @""]; + [s appendString: [mailObject activeSyncRepresentationInContext: context]]; + [s appendString: @""]; + [s appendString: @""]; + + [syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]]; + [dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]]; + return_count++; + } + else + { + //NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]); + } + } } - - // + if (more_available) - { - *theLastServerKey = uid; - } - } + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + else + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + + [self _setFolderMetadata: folderMetadata + forKey: [theCollection nameInContainer]]; + } // default: break; } // switch (folderType) ... @@ -860,12 +976,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([changeBuffer length] || [commandsBuffer length]) { if (lastServerKey) - davCollectionTag = [collection davCollectionTagFromId: lastServerKey]; - else + davCollectionTag = lastServerKey; + else if (![[self _folderMetadataForKey: [collection nameInContainer]] objectForKey: @"MoreAvailable"]) davCollectionTag = [collection davCollectionTag]; *changeDetected = YES; } + else + { + if (folderType == ActiveSyncMailFolder && [syncKey isEqualToString: @"-1"]) + davCollectionTag = [collection davCollectionTag]; + } // Generate the response buffer [theBuffer appendString: @""]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.h b/ActiveSync/SOGoActiveSyncDispatcher.h index 0c28ebced..eda6a3dbf 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.h +++ b/ActiveSync/SOGoActiveSyncDispatcher.h @@ -32,9 +32,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SOGoActiveSyncConstants.h" @class NSException; +@class NSURL; @interface SOGoActiveSyncDispatcher : NSObject { + NSURL *folderTableURL; id context; } @@ -45,4 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inResponse: (id) theResponse context: (id) theContext; +- (NSURL *) folderTableURL; +- (void) ensureFolderTableExists; + @end diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index b1b8e4eeb..331654e8c 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -77,6 +77,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import #import @@ -85,6 +86,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import +#import #import #import @@ -115,22 +118,51 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "SOGoActiveSyncConstants.h" #include "SOGoMailObject+ActiveSync.h" +#import + #include @implementation SOGoActiveSyncDispatcher +- (id) init +{ + [super init]; + + folderTableURL = nil; + + return self; +} + +- (void) dealloc +{ + RELEASE(folderTableURL); + [super dealloc]; +} + - (void) _setFolderSyncKey: (NSString *) theSyncKey { - NSMutableDictionary *metadata; - - metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]]; - - [metadata setObject: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"SyncKey"] forKey: @"FolderSync"]; + SOGoCacheGCSObject *o; - [[[context activeUser] userSettings] setMicrosoftActiveSyncMetadata: metadata - forDevice: [context objectForKey: @"DeviceId"]]; + o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + [[o properties] removeAllObjects]; + [[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]]; + [o save]; +} - [[[context activeUser] userSettings] synchronize]; +- (NSMutableDictionary *) _globalMetadataForDevice +{ + SOGoCacheGCSObject *o; + + o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + return [o properties]; } // @@ -417,6 +449,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [self _setFolderSyncKey: syncKey]; + // FIXME - TODO: We *MUST* update the path in our cache! See -changePathTo in SOGoCacheGCSObject + s = [NSMutableString string]; [s appendString: @""]; [s appendString: @""]; @@ -455,7 +489,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BOOL first_sync; int status; - metadata = [[[context activeUser] userSettings] microsoftActiveSyncMetadataForDevice: [context objectForKey: @"DeviceId"]]; + metadata = [self _globalMetadataForDevice]; syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; s = [NSMutableString string]; @@ -467,7 +501,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. first_sync = YES; syncKey = @"1"; } - else if (![syncKey isEqualToString: [[metadata objectForKey: @"FolderSync"] objectForKey: @"SyncKey"]]) + else if (![syncKey isEqualToString: [metadata objectForKey: @"FolderSyncKey"]]) { // Synchronization key mismatch or invalid synchronization key status = 9; @@ -1588,6 +1622,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cmdName = [[theRequest uri] command]; + // We make sure our cache table exists + [self ensureFolderTableExists]; + // // If the MS-ASProtocolVersion header is set to "12.1", the body of the SendMail request is // is a "message/rfc822" payload - otherwise, it's a WBXML blob. @@ -1659,4 +1696,67 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return nil; } +- (NSURL *) folderTableURL +{ + NSString *urlString, *ocFSTableName; + NSMutableArray *parts; + SOGoUser *user; + + if (!folderTableURL) + { + user = [context activeUser]; + urlString = [[user domainDefaults] folderInfoURL]; + parts = [[urlString componentsSeparatedByString: @"/"] + mutableCopy]; + [parts autorelease]; + if ([parts count] == 5) + { + /* If "OCSFolderInfoURL" is properly configured, we must have 5 + parts in this url. */ + ocFSTableName = [NSString stringWithFormat: @"sogo_cache_folder_%@", + [[[context activeUser] loginInDomain] asCSSIdentifier]]; + [parts replaceObjectAtIndex: 4 withObject: ocFSTableName]; + folderTableURL + = [NSURL URLWithString: [parts componentsJoinedByString: @"/"]]; + [folderTableURL retain]; + } + else + [NSException raise: @"MAPIStoreIOException" + format: @"'OCSFolderInfoURL' is not set"]; + } + + return folderTableURL; +} + +- (void) ensureFolderTableExists +{ + GCSChannelManager *cm; + EOAdaptorChannel *channel; + NSString *tableName, *query; + GCSSpecialQueries *queries; + + [self folderTableURL]; + + cm = [GCSChannelManager defaultChannelManager]; + channel = [cm acquireOpenChannelForURL: folderTableURL]; + + /* FIXME: make use of [EOChannelAdaptor describeTableNames] instead */ + tableName = [[folderTableURL path] lastPathComponent]; + if ([channel evaluateExpressionX: + [NSString stringWithFormat: @"SELECT count(*) FROM %@", + tableName]]) + { + queries = [channel specialQueries]; + query = [queries createSOGoCacheGCSFolderTableWithName: tableName]; + if ([channel evaluateExpressionX: query]) + [NSException raise: @"MAPIStoreIOException" + format: @"could not create special table '%@'", tableName]; + } + else + [channel cancelFetch]; + + + [cm releaseChannel: channel]; +} + @end diff --git a/ActiveSync/SOGoSyncCacheObject.h b/ActiveSync/SOGoSyncCacheObject.h new file mode 100644 index 000000000..c6ba61068 --- /dev/null +++ b/ActiveSync/SOGoSyncCacheObject.h @@ -0,0 +1,55 @@ +/* + +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 __SOGOSYNCCACHEOBJECT_H__ +#define __SOGOSYNCCACHEOBJECT_H__ + +#import + +@class NSNull; + +@interface SOGoSyncCacheObject : NSObject +{ + id _uid; + id _sequence; +} + ++ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence; +- (id) uid; +- (void) setUID: (id) theUID; +- (id) sequence; +- (void) setSequence: (id) theSequence; + +- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject; +- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject; + +@end + +#endif diff --git a/ActiveSync/SOGoSyncCacheObject.m b/ActiveSync/SOGoSyncCacheObject.m new file mode 100644 index 000000000..ab973348d --- /dev/null +++ b/ActiveSync/SOGoSyncCacheObject.m @@ -0,0 +1,122 @@ +/* + +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. + +*/ + +#include "SOGoSyncCacheObject.h" + +#import +#import + +@implementation SOGoSyncCacheObject + +- (id) init +{ + if ((self = [super init])) + { + _uid = nil; + _sequence = nil; + } + + return self; +} + ++ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence; +{ + id o; + + o = [[self alloc] init]; + + [o setUID: theUID]; + [o setSequence: theSequence]; + + return o; +} + +- (void) dealloc +{ + RELEASE(_uid); + RELEASE(_sequence); + [super dealloc]; +} + +- (id) uid +{ + return _uid; +} + +- (void) setUID: (id) theUID +{ + ASSIGN(_uid, theUID); +} + +- (id) sequence +{ + return _sequence; +} + +- (void) setSequence: (id) theSequence +{ + ASSIGN(_sequence, theSequence); +} + + +- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject +{ + return [[self uid] compare: [theSyncCacheObject uid]]; +} + +// +// We might get NSNull values here, so if both are NSNull instances, +// we sort by UID. If both sequences are equal, we also sort by UID. +// +- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject +{ + if ([[self sequence] isEqual: [NSNull null]] && + [[theSyncCacheObject sequence] isEqual: [NSNull null]]) + return [self compareUID: theSyncCacheObject]; + + if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]]) + return NSOrderedDescending; + + if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]]) + return NSOrderedAscending; + + // Must check this here, to avoid comparing NSNull objects + if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame) + return [self compareUID: theSyncCacheObject]; + + return [[self sequence] compare: [theSyncCacheObject sequence]]; +} + +- (NSString *) description +{ + return [NSString stringWithFormat: @"%@-%@", _uid, _sequence]; +} + +@end diff --git a/SoObjects/SOGo/SOGoCacheGCSObject.h b/SoObjects/SOGo/SOGoCacheGCSObject.h index cc7584198..8d26a499a 100644 --- a/SoObjects/SOGo/SOGoCacheGCSObject.h +++ b/SoObjects/SOGo/SOGoCacheGCSObject.h @@ -35,7 +35,9 @@ typedef enum { MAPIFolderCacheObject = 1, MAPIMessageCacheObject = 2, MAPIFAICacheObject = 3, - MAPIInternalCacheObject = 99 /* object = property list */ + MAPIInternalCacheObject = 99, /* object = property list */ + ActiveSyncGlobalCacheObject = 200, + ActiveSyncFolderCacheObject = 201 } SOGoCacheObjectType; @interface SOGoCacheGCSObject : SOGoCacheObject diff --git a/SoObjects/SOGo/SOGoCacheObject.m b/SoObjects/SOGo/SOGoCacheObject.m index 6842e148a..877437ab5 100644 --- a/SoObjects/SOGo/SOGoCacheObject.m +++ b/SoObjects/SOGo/SOGoCacheObject.m @@ -18,8 +18,8 @@ * Boston, MA 02111-1307, USA. */ -#import #import +#import #import "SOGoCacheObject.h"