(fix) numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)

Conflicts:

	NEWS
pull/110/head
Ludovic Marcotte 2015-10-14 09:21:32 -04:00
parent 4002da9323
commit bfa3cf379c
9 changed files with 378 additions and 94 deletions

View File

@ -112,6 +112,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher (Sync) @implementation SOGoActiveSyncDispatcher (Sync)
- (void) _setOrUnsetSyncInProgress: (BOOL) set
invalidate: (BOOL) invalidate
{
SOGoCacheGCSObject *o;
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded];
if (set)
if (invalidate)
[[o properties] setObject: [NSCalendarDate date] forKey: @"InvalidateSyncInProgress"];
else
[[o properties] setObject: [NSCalendarDate date] forKey: @"SyncInProgress"];
else
{
[[o properties] removeObjectForKey: @"SyncInProgress"];
[[o properties] removeObjectForKey: @"InvalidateSyncInProgress"];
}
[o save];
}
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata - (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
forKey: (NSString *) theFolderKey forKey: (NSString *) theFolderKey
{ {
@ -405,7 +429,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[sogoObject saveComponent: o]; [sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; [syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
} }
break; break;
case ActiveSyncEventFolder: case ActiveSyncEventFolder:
@ -416,14 +439,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[sogoObject saveComponent: o]; [sogoObject saveComponent: o];
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; [syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
} }
break; break;
case ActiveSyncMailFolder: case ActiveSyncMailFolder:
default: default:
{ {
NSDictionary *result; NSDictionary *result;
NSString *modseq; NSNumber *modseq;
[sogoObject takeActiveSyncValues: allChanges inContext: context]; [sogoObject takeActiveSyncValues: allChanges inContext: context];
@ -431,11 +453,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"]; modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
if (modseq) if (modseq)
[syncCache setObject: modseq forKey: serverId]; [syncCache setObject: [modseq stringValue] forKey: serverId];
} }
} }
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
[theBuffer appendString: @"<Change>"]; [theBuffer appendString: @"<Change>"];
@ -578,36 +600,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
withFilterType: (NSCalendarDate *) theFilterType withFilterType: (NSCalendarDate *) theFilterType
inBuffer: (NSMutableString *) theBuffer inBuffer: (NSMutableString *) theBuffer
lastServerKey: (NSString **) theLastServerKey lastServerKey: (NSString **) theLastServerKey
{ {
NSMutableDictionary *folderMetadata, *dateCache, *syncCache; NSMutableDictionary *folderMetadata, *dateCache, *syncCache;
NSString *davCollectionTagToStore; NSString *davCollectionTagToStore;
NSAutoreleasePool *pool; NSAutoreleasePool *pool;
NSMutableString *s; NSMutableString *s;
BOOL more_available; BOOL cleanup_needed, more_available;
int i, max; int i, max;
s = [NSMutableString string]; s = [NSMutableString string];
cleanup_needed = more_available = NO;
more_available = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
// If this is a new sync operation, DateCache and SyncCache needs to be deleted // If this is a new sync operation, DateCache and SyncCache need to be deleted
if ([theSyncKey isEqualToString: @"-1"]) if ([theSyncKey isEqualToString: @"-1"])
{ {
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
} }
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
{
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
// We need to cleanup this mess.
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login]];
cleanup_needed = YES;
}
syncCache = [folderMetadata objectForKey: @"SyncCache"]; syncCache = [folderMetadata objectForKey: @"SyncCache"];
dateCache = [folderMetadata objectForKey: @"DateCache"]; dateCache = [folderMetadata objectForKey: @"DateCache"];
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) && if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
!([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize (cleanup_needed ||
!([theSyncKey isEqualToString: @"-1"]) && // new sync operation ( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
theFilterType) !([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
theFilterType
)
{ {
NSArray *allKeys; NSArray *allKeys;
NSString *key; NSString *key;
@ -623,14 +652,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending) if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
{ {
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"]; if ([syncCache objectForKey:key])
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key]; {
[s appendString: @"</SoftDelete>"]; if (debugOn)
[self logWithFormat: @"EAS - SoftDelete %@", key];
[syncCache removeObjectForKey: key]; [s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
[dateCache removeObjectForKey: key]; [s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
[s appendString: @"</SoftDelete>"];
[syncCache removeObjectForKey: key];
//[dateCache removeObjectForKey: key];
softdelete_count++; softdelete_count++;
}
else if (cleanup_needed)
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
// With this we make sure that a SoftDelete is set again on next sync.
[syncCache setObject: @"0" forKey: key];
}
else
{
if (debugOn)
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
// Now we are save to remove the dateCache entry.
[dateCache removeObjectForKey: key];
}
} }
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize)) if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
@ -657,8 +708,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length])) if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]))
return; return;
more_available = NO;
davCollectionTagToStore = [theCollection davCollectionTag]; davCollectionTagToStore = [theCollection davCollectionTag];
@ -687,7 +736,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
initialLoadInProgress = NO; initialLoadInProgress = NO;
if ([theSyncKey isEqualToString: @"-1"]) if ([theSyncKey isEqualToString: @"-1"])
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"]; [folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
if ([folderMetadata objectForKey: @"InitialLoadSequence"]) if ([folderMetadata objectForKey: @"InitialLoadSequence"])
{ {
@ -865,11 +914,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOGoMailObject *mailObject; SOGoMailObject *mailObject;
NSArray *allMessages, *a; NSArray *allMessages, *a;
NSString *lastSequence, *firstUidAdded;
int j, k, return_count, highestmodseq; int j, k, return_count, highestmodseq;
BOOL found_in_cache, initialLoadInProgress; BOOL found_in_cache, initialLoadInProgress;
initialLoadInProgress = NO; initialLoadInProgress = NO;
found_in_cache = NO;
lastSequence = nil;
firstUidAdded = nil;
if ([theSyncKey isEqualToString: @"-1"]) if ([theSyncKey isEqualToString: @"-1"])
{ {
@ -900,7 +953,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (i = 0; i < max; i++) for (i = 0; i < max; i++)
{ {
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject] [allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]]; sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
} }
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache]; sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
@ -909,15 +962,82 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[allCacheObjects sortUsingSelector: @selector(compareSequence:)]; [allCacheObjects sortUsingSelector: @selector(compareSequence:)];
//NSLog(@"sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]); if (debugOn)
//NSLog(@"allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]); {
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
}
lastCacheObject = [sortedBySequence lastObject]; lastCacheObject = [sortedBySequence lastObject];
//
// Cleanup the mess
//
if (cleanup_needed)
{
NSMutableArray *sortedByUID;
int uidnextFromCache;
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
[sortedByUID sortUsingSelector: @selector(compareUID:)];
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
uidnextFromCache = [[a objectAtIndex: 0] intValue];
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
// Remove all entries from cache beginning with the first uid added by the last sync request.
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
}
RELEASE(sortedByUID);
for (j = 0; j < [allCacheObjects count]; j++)
{
// Update the modseq in cache, sence othersie, it would be identical to the modseq from server
//and we would skip the cache when generating the response.
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
}
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
{
if (debugOn)
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
}
}
}
if ([folderMetadata objectForKey: @"MoreAvailable"] && lastCacheObject) if (!cleanup_needed &&
[folderMetadata objectForKey: @"MoreAvailable"] &&
lastCacheObject &&
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
{ {
for (j = 0; j < [allCacheObjects count]; j++) for (j = 0; j < [allCacheObjects count]; j++)
{ {
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
([[allCacheObjects objectAtIndex: j] sequence] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
{
// We need to continue with adds or deletes from here.
found_in_cache = YES;
j--;
break;
}
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]]) if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
{ {
// Found out where we're at, let's continue from there... // Found out where we're at, let's continue from there...
@ -932,12 +1052,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (found_in_cache) if (found_in_cache)
k = j+1; k = j+1;
else else
{ j = k = 0;
k = 0;
j = 0; if (debugOn)
} [self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
//NSLog(@"found in cache: %d k = %d", found_in_cache, k);
return_count = 0; return_count = 0;
@ -948,23 +1066,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Check for the WindowSize and slice accordingly // Check for the WindowSize and slice accordingly
if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize)) if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
{ {
NSString *lastSequence;
more_available = YES; more_available = YES;
if (!firstUidAdded)
{
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
firstUidAdded = [a objectAtIndex: 0];
RETAIN(firstUidAdded);
}
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]); lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", [aCacheObject uid], lastSequence]; //RETAIN(lastSequence);
//NSLog(@"Reached windowSize - lastUID will be: %@", *theLastServerKey); *theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUidAdded, lastSequence];
if (debugOn)
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
DESTROY(pool); DESTROY(pool);
break; break;
} }
aCacheObject = [allCacheObjects objectAtIndex: k]; aCacheObject = [allCacheObjects objectAtIndex: k];
// If found in cache, it's either a Change or a Delete if (debugOn)
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
// If found in cache, it's either a Change or a Delete operation.
if ([syncCache objectForKey: [aCacheObject uid]]) if ([syncCache objectForKey: [aCacheObject uid]])
{ {
if ([[aCacheObject sequence] isEqual: [NSNull null]]) if ([[aCacheObject sequence] isEqual: [NSNull null]])
{ {
if (debugOn)
[self logWithFormat: @"EAS - DELETE!"];
// Deleted // Deleted
[s appendString: @"<Delete xmlns=\"AirSync:\">"]; [s appendString: @"<Delete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]]; [s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
@ -982,9 +1115,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mailObject = [theCollection lookupName: [aCacheObject uid] mailObject = [theCollection lookupName: [aCacheObject uid]
inContext: context inContext: context
acquire: 0]; acquire: 0];
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]]) if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
{ {
if (debugOn)
[self logWithFormat: @"EAS - CHANGE!"];
[s appendString: @"<Change xmlns=\"AirSync:\">"]; [s appendString: @"<Change xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]]; [s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"]; [s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
@ -1000,6 +1136,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
} }
else else
{ {
if (debugOn)
[self logWithFormat: @"EAS - ADD!"];
// Added // Added
if (![[aCacheObject sequence] isEqual: [NSNull null]]) if (![[aCacheObject sequence] isEqual: [NSNull null]])
{ {
@ -1032,11 +1171,22 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]]; [syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]]; [dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
// Save the frist UID we add. We will use it for the synckey late.
if (!firstUidAdded)
{
firstUidAdded = [aCacheObject uid];
RETAIN(firstUidAdded);
if (debugOn)
[self logWithFormat: @"EAS - first uid added %@", firstUidAdded];
}
return_count++; return_count++;
} }
else else
{ {
//NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]); if (debugOn)
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
} }
} }
@ -1045,16 +1195,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (more_available) if (more_available)
{ {
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; //[folderMetadata setObject: lastSequence forKey: @"MoreAvailable"];
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"]; [folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
//RELEASE(lastSequence);
} }
else else
{ {
[folderMetadata removeObjectForKey: @"MoreAvailable"]; [folderMetadata removeObjectForKey: @"MoreAvailable"];
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
if (firstUidAdded)
{
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
[folderMetadata setObject: [[NSString alloc] initWithFormat: @"%@-%@", firstUidAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
}
else
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
} }
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
RELEASE(firstUidAdded);
RELEASE(*theLastServerKey); RELEASE(*theLastServerKey);
} // default: } // default:
@ -1153,15 +1314,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
changeDetected: (BOOL *) changeDetected changeDetected: (BOOL *) changeDetected
maxSyncResponseSize: (int) theMaxSyncResponseSize maxSyncResponseSize: (int) theMaxSyncResponseSize
{ {
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache; NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *lastServerKey, *syncKeyInCache, *folderKey;
SOGoMicrosoftActiveSyncFolderType folderType;
id collection, value;
NSMutableString *changeBuffer, *commandsBuffer;
BOOL getChanges, first_sync;
unsigned int windowSize, v, status;
NSMutableDictionary *folderMetadata, *folderOptions; NSMutableDictionary *folderMetadata, *folderOptions;
NSMutableString *changeBuffer, *commandsBuffer;
id collection, value;
SOGoMicrosoftActiveSyncFolderType folderType;
unsigned int windowSize, v, status;
BOOL getChanges, first_sync;
changeBuffer = [NSMutableString string]; changeBuffer = [NSMutableString string];
commandsBuffer = [NSMutableString string]; commandsBuffer = [NSMutableString string];
@ -1184,7 +1345,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//[theBuffer appendString: @"</Collection>"]; //[theBuffer appendString: @"</Collection>"];
return; return;
} }
//
// First check if we have any concurrent Sync requests going on for this device.
// If we do and we are still within our maximumSyncInterval, we let our EAS
// device know to retry.
//
folderKey = [self _getNameInCache: collection withType: folderType];
folderMetadata = [self _folderMetadataForKey: folderKey];
// We check for a window size, default to 100 if not specfied or out of bounds // We check for a window size, default to 100 if not specfied or out of bounds
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue]; windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
@ -1209,8 +1378,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
first_sync = NO; first_sync = NO;
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]];
if ([syncKey isEqualToString: @"0"]) if ([syncKey isEqualToString: @"0"])
{ {
davCollectionTag = @"-1"; davCollectionTag = @"-1";
@ -1265,7 +1432,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
{ {
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil]; folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", bodyPreferenceType, @"BodyPreferenceType", nil];
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"]; [folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: collection withType: folderType]]; [self _setFolderMetadata: folderMetadata forKey: folderKey];
} }
} }
@ -1311,7 +1478,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
lastServerKey: &lastServerKey]; lastServerKey: &lastServerKey];
} }
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: collection withType: folderType]]; folderMetadata = [self _folderMetadataForKey: folderKey];
// If we got any changes or if we have applied any commands // If we got any changes or if we have applied any commands
// let's regenerate our SyncKey based on the collection tag. // let's regenerate our SyncKey based on the collection tag.
@ -1473,16 +1640,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSArray *allCollections; NSArray *allCollections;
NSData *d; NSData *d;
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize; int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep;
BOOL changeDetected; BOOL changeDetected;
changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
// We initialize our output buffer // We initialize our output buffer
output = [[NSMutableString alloc] init]; output = [[NSMutableString alloc] init];
defaults = [SOGoSystemDefaults sharedSystemDefaults];
defaultInterval = [defaults maximumSyncInterval];
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"]; [output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"]; [output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[output appendString: @"<Sync xmlns=\"AirSync:\">"]; [output appendString: @"<Sync xmlns=\"AirSync:\">"];
@ -1497,12 +1663,53 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[output appendString: @"</Sync>"]; [output appendString: @"</Sync>"];
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
[theResponse setContent: d]; [theResponse setContent: d];
RELEASE(output);
return; return;
} }
if ([[self globalMetadataForDevice] objectForKey: @"SyncInProgress"])
{
NSCalendarDate *syncStartDate;
// An other sync is going on right now. Lets break it and wait
// until the other process clears it up.
[self _setOrUnsetSyncInProgress: YES invalidate: YES];
total_sleep = 0;
syncStartDate = [[self globalMetadataForDevice] objectForKey: @"SyncInProgress"];
while ([[self globalMetadataForDevice] objectForKey: @"SyncInProgress"])
{
// Don't go into a heartbeat loop.
heartbeatInterval = 0;
// We waited too long. Return a fatal error to the client.
if (abs([syncStartDate timeIntervalSinceNow]) > defaultInterval)
{
if (debugOn)
[self logWithFormat: @"EAS - We waited too long. syncStartDate: %@ %@", syncStartDate, [self globalMetadataForDevice]];
[theResponse setStatus: 503];
[self _setOrUnsetSyncInProgress: NO invalidate: NO];
RELEASE(output);
return;
}
if (debugOn)
[self logWithFormat: @"EAS - globalMetadataForDevice %@", [self globalMetadataForDevice]];
[self logWithFormat: @"Sync in progress for device %@ (login: %@). Lock will expire in %d seconds", [context objectForKey: @"DeviceId"], [[context activeUser] login], defaultInterval-total_sleep];
sleep(5);
total_sleep += 5;
}
}
// No lock or broke the existing one, lets grab it and proceed
[self _setOrUnsetSyncInProgress: YES invalidate: NO];
defaults = [SOGoSystemDefaults sharedSystemDefaults]; changeDetected = NO;
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue]; heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
defaultInterval = [defaults maximumSyncInterval];
internalInterval = [defaults internalSyncInterval]; internalInterval = [defaults internalSyncInterval];
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response. // If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
@ -1512,11 +1719,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// We check to see if our heartbeat interval falls into the supported ranges. // We check to see if our heartbeat interval falls into the supported ranges.
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1) if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
{ {
int limit;
// Interval is too long, inform the client. // Interval is too long, inform the client.
heartbeatInterval = defaultInterval; heartbeatInterval = defaultInterval;
// Outlook doesn't like this... // When Status = 14, the Wait interval is specified in minutes while
//[output appendFormat: @"<Limit>%d</Limit>", defaultInterval]; // defaultInterval is specifed in seconds. Adjust accordinlgy.
limit = defaultInterval/60;
if (limit < 1) limit = 1;
if (limit > 59) limit = 59;
//[output appendFormat: @"<Limit>%d</Limit>", limit];
//[output appendFormat: @"<Status>%d</Status>", 14]; //[output appendFormat: @"<Status>%d</Status>", 14];
} }
@ -1532,7 +1744,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
for (j = 0; j < [allCollections count]; j++) for (j = 0; j < [allCollections count]; j++)
{ {
aCollection = [allCollections objectAtIndex: j]; aCollection = [allCollections objectAtIndex: j];
[self processSyncCollection: aCollection [self processSyncCollection: aCollection
inBuffer: s inBuffer: s
changeDetected: &changeDetected changeDetected: &changeDetected
@ -1544,13 +1756,46 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (changeDetected) if (changeDetected)
{ {
[self logWithFormat: @"Change detected, we push the content."]; // Don't return a response if an other Sync is waiting and this is a heartbeat request.
if ([[self globalMetadataForDevice] objectForKey: @"InvalidateSyncInProgress"] && heartbeatInterval > 1)
{
if (debugOn)
[self logWithFormat: @"EAS - Heartbeat stopped - discard response %@", [self globalMetadataForDevice]];
[theResponse setStatus: 503];
[self _setOrUnsetSyncInProgress: NO invalidate: NO];
RELEASE(output);
return;
}
[self logWithFormat: @"Change detected during Sync, we push the content."];
break; break;
} }
else if (heartbeatInterval > 1) else if (heartbeatInterval > 1)
{ {
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval]; total_sleep = 0;
sleep(internalInterval);
while (total_sleep < internalInterval)
{
// We check if we must break the current synchronization since an other Sync
// has just arrived.
if ([[self globalMetadataForDevice] objectForKey: @"InvalidateSyncInProgress"])
{
if (debugOn)
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
// Make sure we end the heardbeat-loop.
heartbeatInterval = internalInterval = 1;
break;
}
else
{
[self logWithFormat: @"Sleeping %d seconds while detecting changes in Sync...", internalInterval-total_sleep];
sleep(5);
total_sleep += 5;
}
}
} }
else else
{ {
@ -1558,8 +1803,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
} }
} }
//
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0 oterwise send an empty response. // Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
// otherwise send an empty response.
//
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"]) if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
{ {
// We always return the last generated response. // We always return the last generated response.
@ -1577,6 +1824,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Avoid overloading the autorelease pool here, as Sync command can // Avoid overloading the autorelease pool here, as Sync command can
// generate fairly large responses. // generate fairly large responses.
RELEASE(output); RELEASE(output);
[self _setOrUnsetSyncInProgress: NO invalidate: NO];
} }
@end @end

View File

@ -1,6 +1,6 @@
/* /*
Copyright (c) 2014, Inverse inc. Copyright (c) 2014-2015, Inverse inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "SOGoActiveSyncConstants.h" #include "SOGoActiveSyncConstants.h"
@class NSException; @class NSException;
@class NSMutableDictionary;
@class NSURL; @class NSURL;
@interface SOGoActiveSyncDispatcher : NSObject @interface SOGoActiveSyncDispatcher : NSObject
@ -39,8 +40,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
NSURL *folderTableURL; NSURL *folderTableURL;
NSDictionary *imapFolderGUIDS; NSDictionary *imapFolderGUIDS;
id context; id context;
BOOL debugOn;
} }
- (NSMutableDictionary *) globalMetadataForDevice;
- (id) collectionFromId: (NSString *) theCollectionId - (id) collectionFromId: (NSString *) theCollectionId
type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; type: (SOGoMicrosoftActiveSyncFolderType) theFolderType;

View File

@ -143,8 +143,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@implementation SOGoActiveSyncDispatcher @implementation SOGoActiveSyncDispatcher
static BOOL debugOn = NO;
- (id) init - (id) init
{ {
[super init]; [super init];
@ -171,16 +169,16 @@ static BOOL debugOn = NO;
[o setTableUrl: [self folderTableURL]]; [o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded]; [o reloadIfNeeded];
[[o properties] removeAllObjects]; [[o properties] setObject: theSyncKey
[[o properties] addEntriesFromDictionary: [NSDictionary dictionaryWithObject: theSyncKey forKey: @"FolderSyncKey"]]; forKey: @"FolderSyncKey"];
[o save]; [o save];
} }
- (NSMutableDictionary *) _globalMetadataForDevice - (NSMutableDictionary *) globalMetadataForDevice
{ {
SOGoCacheGCSObject *o; SOGoCacheGCSObject *o;
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil]; o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
[o setObjectType: ActiveSyncGlobalCacheObject]; [o setObjectType: ActiveSyncGlobalCacheObject];
[o setTableUrl: [self folderTableURL]]; [o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded]; [o reloadIfNeeded];
@ -204,7 +202,7 @@ static BOOL debugOn = NO;
if (theFilter) if (theFilter)
{ {
o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil]; o = [SOGoCacheGCSObject objectWithName: [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theCollectionId] inContainer: nil];
[o setObjectType: ActiveSyncGlobalCacheObject]; [o setObjectType: ActiveSyncFolderCacheObject];
[o setTableUrl: [self folderTableURL]]; [o setTableUrl: [self folderTableURL]];
[o reloadIfNeeded]; [o reloadIfNeeded];
@ -716,7 +714,7 @@ static BOOL debugOn = NO;
BOOL first_sync; BOOL first_sync;
sm = [SoSecurityManager sharedSecurityManager]; sm = [SoSecurityManager sharedSecurityManager];
metadata = [self _globalMetadataForDevice]; metadata = [self globalMetadataForDevice];
syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
s = [NSMutableString string]; s = [NSMutableString string];
@ -2033,13 +2031,13 @@ static BOOL debugOn = NO;
if ([foldersWithChanges count]) if ([foldersWithChanges count])
{ {
[self logWithFormat: @"Change detected, we push the content."]; [self logWithFormat: @"Change detected using Ping, we let the EAS client know to send a Sync."];
status = 2; status = 2;
break; break;
} }
else else
{ {
[self logWithFormat: @"Sleeping %d seconds while detecting changes...", internalInterval]; [self logWithFormat: @"Sleeping %d seconds while detecting changes in Ping...", internalInterval];
sleep(internalInterval); sleep(internalInterval);
} }
} }

View File

@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@interface SOGoSyncCacheObject : NSObject @interface SOGoSyncCacheObject : NSObject
{ {
@public
id _uid; id _uid;
id _sequence; id _sequence;
} }

View File

@ -32,9 +32,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Foundation/NSNull.h> #import <Foundation/NSNull.h>
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
static Class NSNullK;
@implementation SOGoSyncCacheObject @implementation SOGoSyncCacheObject
+ (void) initialize
{
NSNullK = [NSNull class];
}
- (id) init - (id) init
{ {
if ((self = [super init])) if ((self = [super init]))
@ -46,14 +54,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
return self; return self;
} }
+ (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence; + (id) syncCacheObjectWithUID: (id) theUID sequence: (id) theSequence
{ {
id o; id o;
o = [[self alloc] init]; o = [[self alloc] init];
[o setUID: theUID]; [o setUID: [NSNumber numberWithInt: [theUID intValue]]];
[o setSequence: theSequence]; [o setSequence: ([theSequence isKindOfClass: NSNullK] ? theSequence : [NSNumber numberWithInt: [theSequence intValue]])];
return [o autorelease]; return [o autorelease];
} }
@ -67,7 +75,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) uid - (id) uid
{ {
return _uid; return [_uid description];
} }
- (void) setUID: (id) theUID - (void) setUID: (id) theUID
@ -77,7 +85,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (id) sequence - (id) sequence
{ {
return _sequence; return ([_sequence isKindOfClass: NSNullK] ? _sequence : [_sequence description]);
} }
- (void) setSequence: (id) theSequence - (void) setSequence: (id) theSequence
@ -88,7 +96,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject - (NSComparisonResult) compareUID: (SOGoSyncCacheObject *) theSyncCacheObject
{ {
return [[self uid] compare: [theSyncCacheObject uid]]; return [self->_uid compare: theSyncCacheObject->_uid];
} }
// //
@ -97,21 +105,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// //
- (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject - (NSComparisonResult) compareSequence: (SOGoSyncCacheObject *) theSyncCacheObject
{ {
if ([[self sequence] isEqual: [NSNull null]] && if ([self->_sequence isEqual: [NSNull null]] &&
[[theSyncCacheObject sequence] isEqual: [NSNull null]]) [theSyncCacheObject->_sequence isEqual: [NSNull null]])
return [self compareUID: theSyncCacheObject]; return [self compareUID: theSyncCacheObject];
if (![[self sequence] isEqual: [NSNull null]] && [[theSyncCacheObject sequence] isEqual: [NSNull null]]) if (![self->_sequence isEqual: [NSNull null]] && [theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedDescending; return NSOrderedDescending;
if ([[self sequence] isEqual: [NSNull null]] && ![[theSyncCacheObject sequence] isEqual: [NSNull null]]) if ([self->_sequence isEqual: [NSNull null]] && ![theSyncCacheObject->_sequence isEqual: [NSNull null]])
return NSOrderedAscending; return NSOrderedAscending;
// Must check this here, to avoid comparing NSNull objects // Must check this here, to avoid comparing NSNull objects
if ([[self sequence] compare: [theSyncCacheObject sequence]] == NSOrderedSame) if ([self->_sequence compare: theSyncCacheObject->_sequence] == NSOrderedSame)
return [self compareUID: theSyncCacheObject]; return [self compareUID: theSyncCacheObject];
return [[self sequence] compare: [theSyncCacheObject sequence]]; return [self->_sequence compare: theSyncCacheObject->_sequence];
} }
- (NSString *) description - (NSString *) description
@ -120,3 +128,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
} }
@end @end

10
NEWS
View File

@ -1,3 +1,13 @@
2.3.3 (2015-mm-dd)
------------------
New features
Enhancements
Bug fixes
- numerous EAS fixes when connections are dropped before the EAS client receives the response (#3058, #2849)
2.3.2 (2015-09-16) 2.3.2 (2015-09-16)
------------------ ------------------

View File

@ -2106,7 +2106,7 @@ _compareFetchResultsByMODSEQ (id entry1, id entry2, void *data)
// * 5 FETCH (UID 559 MODSEQ (17)) // * 5 FETCH (UID 559 MODSEQ (17))
// * 6 FETCH (UID 560 MODSEQ (18)) // * 6 FETCH (UID 560 MODSEQ (18))
// * 7 FETCH (UID 561 MODSEQ (19)) // * 7 FETCH (UID 561 MODSEQ (19))
//
// //
// fetchUIDsOfVanishedItems .. // fetchUIDsOfVanishedItems ..
// //

View File

@ -56,6 +56,8 @@ typedef enum {
- (void) reloadIfNeeded; - (void) reloadIfNeeded;
- (void) save; - (void) save;
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache;
/* accessors */ /* accessors */
- (NSMutableString *) path; /* full filename */ - (NSMutableString *) path; /* full filename */

View File

@ -103,11 +103,22 @@ static EOAttribute *textColumn = nil;
} }
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer + (id) objectWithName: (NSString *) key inContainer: (id) theContainer
{
return [self objectWithName: key
inContainer: theContainer
useCache: YES];
}
+ (id) objectWithName: (NSString *) key inContainer: (id) theContainer useCache: (BOOL) useCache
{ {
SOGoCache *cache; SOGoCache *cache;
id o; id o;
cache = [SOGoCache sharedCache]; cache = [SOGoCache sharedCache];
if (!useCache)
[cache unregisterObjectWithName: key inContainer: theContainer];
o = [cache objectNamed: key inContainer: theContainer]; o = [cache objectNamed: key inContainer: theContainer];
if (!o) if (!o)