diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index f8beacbb1..8808523eb 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -59,6 +59,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import +#import #include "iCalEvent+ActiveSync.h" #include "iCalToDo+ActiveSync.h" @@ -123,7 +124,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSNumber *processIdentifier, *processIdentifierInCache; SOGoCacheGCSObject *o; NSDictionary *values; - NSString *key; + NSString *key, *pkey; + NSArray *a; if ([theFolderKey hasPrefix: @"folder"]) key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]]; @@ -134,7 +136,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key]; // Don't update the cache if another request is processing the same collection. - if (!([processIdentifierInCache isEqual: processIdentifier])) + // I case of a merged folder we have to check personal folder's lock. + a = [key componentsSeparatedByString: @"/"]; + pkey = [NSString stringWithFormat: @"%@/personal", [a objectAtIndex:0]]; + + if (!([processIdentifierInCache isEqual: processIdentifier] || [[[self globalMetadataForDevice] objectForKey: pkey] isEqual: processIdentifier])) { if (debugOn) [self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier]; @@ -153,13 +159,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [[o properties] removeObjectForKey: @"SyncKey"]; [[o properties] removeObjectForKey: @"SyncCache"]; [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] removeObjectForKey: @"UidCache"]; [[o properties] removeObjectForKey: @"MoreAvailable"]; + [[o properties] removeObjectForKey: @"FolderPermissions"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"SupportedElements"]; [[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; [[o properties] removeObjectForKey: @"InitialLoadSequence"]; [[o properties] removeObjectForKey: @"FirstIdInCache"]; [[o properties] removeObjectForKey: @"LastIdInCache"]; + [[o properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[o properties] removeObjectForKey: @"MergedFolder"]; + [[o properties] removeObjectForKey: @"CleanoutDate"]; [[o properties] addEntriesFromDictionary: values]; [o save]; @@ -247,8 +258,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType inBuffer: (NSMutableString *) theBuffer { - NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *allValues; - NSString *clientId, *serverId; + NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache, *allValues; + NSString *clientId, *serverId, *easId; NSArray *additions; id anAddition, sogoObject, o; @@ -326,23 +337,54 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [sogoObject setIsNew: is_new]; [sogoObject saveComponent: o]; - // Everything is fine, lets generate our response - [theBuffer appendString: @""]; - [theBuffer appendFormat: @"%@", clientId]; - [theBuffer appendFormat: @"%@", serverId]; - [theBuffer appendFormat: @"%d", 1]; - [theBuffer appendString: @""]; - // Update syncCache folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; syncCache = [folderMetadata objectForKey: @"SyncCache"]; dateCache = [folderMetadata objectForKey: @"DateCache"]; + uidCache = [folderMetadata objectForKey: @"UidCache"]; + + if (uidCache) + { + if (is_new && [serverId length] > 64) + { + easId = [theCollection globallyUniqueObjectId]; + [uidCache setObject: easId forKey: serverId]; + if (debugOn) + [self logWithFormat: @"EAS - Generated new easId: %@ for serverId: %@", easId, serverId]; + } + else + { + easId = [uidCache objectForKey: serverId]; + if (easId) + { + if (debugOn) + [self logWithFormat: @"EAS - Reuse easId: %@ for serverId: %@", easId, serverId]; + } + else + { + if (debugOn) + [self logWithFormat: @"EAS - Use original serverId: %@ %@", serverId, easId]; + + easId = serverId; + } + } + } + else + easId = serverId; [syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; [dateCache setObject: [NSCalendarDate date] forKey: serverId]; [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; + + // Everything is fine, lets generate our response + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", clientId]; + [theBuffer appendFormat: @"%@", easId]; + [theBuffer appendFormat: @"%d", 1]; + [theBuffer appendString: @""]; + } } } @@ -389,10 +431,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inBuffer: (NSMutableString *) theBuffer { NSDictionary *allChanges; - NSString *serverId; - NSArray *changes; + NSString *serverId, *easId, *origServerId, *mergedFolder; + NSArray *changes, *a; id aChange, o, sogoObject; - NSMutableDictionary *folderMetadata, *syncCache; + NSMutableDictionary *folderMetadata, *syncCache, *uidCache; int i; @@ -402,14 +444,50 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; syncCache = [folderMetadata objectForKey: @"SyncCache"]; + uidCache = [folderMetadata objectForKey: @"UidCache"]; for (i = 0; i < [changes count]; i++) { aChange = [changes objectAtIndex: i]; - serverId = [[(id)[aChange getElementsByTagName: @"ServerId"] lastObject] textValue]; + origServerId = [[(id)[aChange getElementsByTagName: @"ServerId"] lastObject] textValue]; + easId = origServerId; + + a = [origServerId componentsSeparatedByString: @"{+}"]; + if ([a count] > 1) + { + easId = [a objectAtIndex: 1]; + mergedFolder = [a objectAtIndex: 0]; + + // Make sure that the change goes to the target folder and not to personal. + if ([[theCollection nameInContainer] isEqualToString: @"personal"]) + { + if (debugOn) + [self logWithFormat: @"EAS - Change - Target folder %@, easId %@", mergedFolder, easId]; + + [self processSyncChangeCommand: theDocumentElement + inCollection: [self collectionFromId: mergedFolder type: theFolderType] + withType: theFolderType + inBuffer: theBuffer]; + + continue; + } + } + + if (debugOn) + [self logWithFormat: @"EAS - Change - Process change for folder %@ easId %@", [theCollection nameInContainer],easId]; + allChanges = [[(id)[aChange getElementsByTagName: @"ApplicationData"] lastObject] applicationData]; + if (uidCache && (serverId = [[uidCache allKeysForObject: easId] objectAtIndex: 0])) + { + if (debugOn) + [self logWithFormat: @"EAS - Found serverId: %@ for easId: %@", serverId, easId]; + + } + else + serverId = easId; + // Fetch the object and apply the changes sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] inContext: context @@ -418,7 +496,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Object was removed inbetween sync/commands? if ([sogoObject isKindOfClass: [NSException class]]) { - // FIXME - return status == 8 + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", origServerId]; + [theBuffer appendFormat: @"%d", 8]; + [theBuffer appendString: @""]; continue; } @@ -474,7 +555,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theBuffer appendString: @""]; - [theBuffer appendFormat: @"%@", serverId]; + [theBuffer appendFormat: @"%@", origServerId]; [theBuffer appendFormat: @"%d", 1]; [theBuffer appendString: @""]; } @@ -513,8 +594,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { id aDelete, sogoObject, value; - NSArray *deletions; - NSString *serverId; + NSArray *deletions, *a; + NSString *serverId, *easId, *origServerId, *mergedFolder; BOOL deletesAsMoves, useTrash; int i; @@ -523,6 +604,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([deletions count]) { + NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache; + folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; + + syncCache = [folderMetadata objectForKey: @"SyncCache"]; + dateCache = [folderMetadata objectForKey: @"DateCache"]; + uidCache = [folderMetadata objectForKey: @"UidCache"]; + // From the documention, if DeletesAsMoves is missing, we must assume it's a YES. // See https://msdn.microsoft.com/en-us/library/gg675480(v=exchg.80).aspx for all details. value = [theDocumentElement getElementsByTagName: @"DeletesAsMoves"]; @@ -536,8 +624,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { aDelete = [deletions objectAtIndex: i]; - serverId = [[(id)[aDelete getElementsByTagName: @"ServerId"] lastObject] textValue]; + origServerId = [[(id)[aDelete getElementsByTagName: @"ServerId"] lastObject] textValue]; + easId = origServerId; + + a = [origServerId componentsSeparatedByString: @"{+}"]; + if ([a count] > 1) + { + easId = [a objectAtIndex: 1]; + mergedFolder = [a objectAtIndex: 0]; + + // Make sure that the delete goes to the target folder and not to personal. + if ([[theCollection nameInContainer] isEqualToString: @"personal"]) + { + if (debugOn) + [self logWithFormat: @"EAS - Delete - Target folder %@, easId %@", mergedFolder, easId]; + + [self processSyncDeleteCommand: theDocumentElement + inCollection: [self collectionFromId: mergedFolder type: theFolderType] + withType: theFolderType + inBuffer: theBuffer]; + + continue; + } + } + + if (debugOn) + [self logWithFormat: @"EAS - Delete - Process delete for folder %@ easId %@", [theCollection nameInContainer],easId]; + if (uidCache && (serverId = [[uidCache allKeysForObject: easId] objectAtIndex: 0])) + { + if (debugOn) + [self logWithFormat: @"EAS - Found serverId: %@ for easId: %@", serverId, easId]; + } + else + serverId = easId; + sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType] inContext: context acquire: NO]; @@ -554,24 +675,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } else [sogoObject delete]; - } - [theBuffer appendString: @""]; - [theBuffer appendFormat: @"%@", serverId]; - [theBuffer appendFormat: @"%d", 1]; - [theBuffer appendString: @""]; + [syncCache removeObjectForKey: serverId]; + [dateCache removeObjectForKey: serverId]; + //[uidCache removeObjectForKey: serverId]; + [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; - // update syncCache - NSMutableDictionary *folderMetadata, *dateCache, *syncCache; - folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; - - syncCache = [folderMetadata objectForKey: @"SyncCache"]; - dateCache = [folderMetadata objectForKey: @"DateCache"]; - - [syncCache removeObjectForKey: serverId]; - [dateCache removeObjectForKey: serverId]; - - [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", origServerId]; + [theBuffer appendFormat: @"%d", 1]; + [theBuffer appendString: @""]; + } } } } @@ -624,8 +738,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inBuffer: (NSMutableString *) theBuffer lastServerKey: (NSString **) theLastServerKey defaultInterval: (unsigned int) theDefaultInterval + mergeFolders: (BOOL) theMergeFolder { - NSMutableDictionary *folderMetadata, *dateCache, *syncCache; + NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache; NSString *davCollectionTagToStore; NSAutoreleasePool *pool; NSMutableString *s; @@ -643,6 +758,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; + if (theFolderType != ActiveSyncMailFolder) + { + [folderMetadata setObject: [NSCalendarDate date] forKey: @"CleanoutDate"]; + [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"UidCache"]; + } } else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]])) { @@ -654,6 +774,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. syncCache = [folderMetadata objectForKey: @"SyncCache"]; dateCache = [folderMetadata objectForKey: @"DateCache"]; + uidCache = [folderMetadata objectForKey: @"UidCache"]; + + if (!cleanup_needed && -[[folderMetadata objectForKey: @"CleanoutDate"] timeIntervalSinceNow] > 3600) + { + NSArray *allKeys; + NSString *key; + + if (debugOn) + [self logWithFormat: @"EAS - Cleanout UidCache of folder %@", [theCollection nameInContainer]]; + + allKeys = [uidCache allKeys]; + for (i = 0; i < [allKeys count]; i++) + { + key = [allKeys objectAtIndex: i]; + if (![syncCache objectForKey:key] && ![syncCache objectForKey:key]) + { + if (debugOn) + [self logWithFormat: @"EAS - Delete UidCache entry %@", key]; + + [uidCache removeObjectForKey: key]; + } + } + + [folderMetadata setObject: [NSCalendarDate date] forKey: @"CleanoutDate"]; + [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; + } if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) && (cleanup_needed || @@ -705,6 +851,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Now we are save to remove the dateCache entry. [dateCache removeObjectForKey: key]; + //[uidCache removeObjectForKey: key]; } } @@ -743,7 +890,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. case ActiveSyncTaskFolder: { id sogoObject, componentObject; - NSString *uid, *component_name; + NSString *uid, *easId, *component_name; NSDictionary *component; NSArray *allComponents; @@ -822,7 +969,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [syncCache removeObjectForKey: uid]; [dateCache removeObjectForKey: uid]; } - else + else { if (debugOn) [self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid]; @@ -863,13 +1010,49 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType]; + + if (uidCache) + { + easId = [uidCache objectForKey: uid]; + + if (!easId) + { + if ([uid length] > 64) + { + easId = [theCollection globallyUniqueObjectId]; + [uidCache setObject: easId forKey: uid]; + + if (debugOn) + [self logWithFormat: @"EAS - Generated new easId: %@ for serverId: %@", easId, uid]; + } + else + { + if (debugOn) + [self logWithFormat: @"EAS - Use original serverId: %@ %@", uid, easId]; + + easId = uid; + } + } + else + { + if (debugOn) + [self logWithFormat: @"EAS - Reuse easId: %@ for serverId: %@", easId, uid]; + } + } + else + easId = uid; if (deleted) { if ([syncCache objectForKey: uid]) { [s appendString: @""]; - [s appendFormat: @"%@", uid]; + + if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder) + [s appendFormat: @"%@{+}%@", [theCollection nameInContainer], easId]; + else + [s appendFormat: @"%@", easId]; + [s appendString: @""]; [syncCache removeObjectForKey: uid]; @@ -922,7 +1105,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. else [s appendString: @""]; - [s appendFormat: @"%@", uid]; + if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder) + [s appendFormat: @"%@{+}%@", [theCollection nameInContainer], easId]; + else + [s appendFormat: @"%@", easId]; + [s appendString: @""]; [s appendString: [componentObject activeSyncRepresentationInContext: context]]; @@ -1459,6 +1646,27 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. folderKey = [self _getNameInCache: collection withType: folderType]; folderMetadata = [self _folderMetadataForKey: folderKey]; + if (![folderMetadata count]) + { + if (debugOn) + [self logWithFormat: @"EAS - processSyncCollection: no folderMetadata found: %@", [collection nameInContainer]]; + + // We request foldersync to initialize the missing metadata. + // We skip root-folders for shared mailboxes (shared and shared/jdoe@example.com). + if (![collection isKindOfClass: [SOGoMailNamespace class]]) + { + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", syncKey]; + [theBuffer appendFormat: @"%@", collectionId]; + [theBuffer appendFormat: @"%d", 12]; + [theBuffer appendString: @""]; + + *changeDetected = YES; + } + + return; + } + // We check for a window size, default to 100 if not specfied or out of bounds windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue]; @@ -1639,11 +1847,215 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] inBuffer: changeBuffer lastServerKey: &lastServerKey - defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]]; + defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval] + mergeFolders: NO]; } folderMetadata = [self _folderMetadataForKey: folderKey]; + // We only check for changes in other folders if we got nothing back from personal folder and + // MergedFolder is set (sogo-tool manage-eas mergeVCard). + if (getChanges && !first_sync && + ([[collection nameInContainer] isEqualToString: @"personal"]) && + ([folderMetadata objectForKey: @"MergedFolder"])) + { + NSArray *unsortedArray; + NSMutableDictionary *mergedFolderMetadata, *mergedFoldersSyncKeys; + NSString *mergedFolderSyncKey, *component_name, *mfLastServerKey; + id mfCollection; + BOOL cacheUpdateNeeded; + + mfLastServerKey = nil; + cacheUpdateNeeded=NO; + + if (folderType == ActiveSyncContactFolder) + component_name = @"vcard"; + else if (folderType == ActiveSyncEventFolder) + component_name = @"vevent"; + else + component_name = @"vtodo"; + + mergedFoldersSyncKeys = [folderMetadata objectForKey: @"MergedFoldersSyncKeys"]; + + // Initialize if MergedFoldersSyncKeys doesn't exists. + // We user MergedFoldersSyncKeys to store the SyncKey for merged folders: + // MergedFoldersSyncKeys = { 1447166580 = {"vcard/34B2-543AB980-D-DB9ECF0" = 1447157519; "vcard/5DE4-5640BC80-3-37E3EE00" = 1447166074; } + // 1447166638 = {"vcard/34B2-543AB980-D-DB9ECF0" = 1447157519; "vcard/5DE4-5640BC80-3-37E3EE00" = 1447166074; } } + if (!mergedFoldersSyncKeys || [syncKey isEqualToString: @"-1"]) + { + [folderMetadata setObject: [NSMutableDictionary dictionaryWithObject: [NSMutableDictionary dictionary] forKey: syncKey] + forKey: @"MergedFoldersSyncKeys"]; + mergedFoldersSyncKeys = [folderMetadata objectForKey: @"MergedFoldersSyncKeys"]; + + cacheUpdateNeeded=YES; + } + + // Copy the MergedFoldersSyncKeys entry. Later we update this entry with new SyncKeys if there are changes. + if (![syncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]) + { + [mergedFoldersSyncKeys setObject: [mergedFoldersSyncKeys objectForKey: syncKey] forKey: [folderMetadata objectForKey: @"SyncKey"]]; + cacheUpdateNeeded=YES; + } + + // We only keep 5 entries in MergedFoldersSyncKeys. + unsortedArray = [mergedFoldersSyncKeys allKeys]; + if ([unsortedArray count] > 5) + [mergedFoldersSyncKeys removeObjectForKey: [[unsortedArray sortedArrayUsingSelector:@selector(compare:)] objectAtIndex:0]]; + + // Don't check for changes in other folders if personal folder has changes. + if (![changeBuffer length]) + { + NSArray *foldersInCache; + SOGoCacheGCSObject *o; + NSString *folderName, *realCollectionId; + NSArray *a, *folderNames; + SOGoMicrosoftActiveSyncFolderType mergedFolderType; + + o = [SOGoCacheGCSObject objectWithName: @"0" inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: folderTableURL]; + + foldersInCache = [o cacheEntriesForDeviceId: [context objectForKey: @"DeviceId"] newerThanVersion: -1]; + + // Add folders to MergedFoldersSyncKeys. + for (i = 0; i < [foldersInCache count]; i++) + { + a = [[foldersInCache objectAtIndex: i] componentsSeparatedByString: @"+"]; + folderName = [a objectAtIndex: 1]; + + if (![folderName hasPrefix: component_name] || [folderName hasSuffix: @"/personal"]) + continue; + + mergedFolderSyncKey = [[mergedFoldersSyncKeys objectForKey: [folderMetadata objectForKey: @"SyncKey"]] objectForKey: folderName ]; + + if (!mergedFolderSyncKey) + { + realCollectionId = [folderName realCollectionIdWithFolderType: &mergedFolderType]; + mfCollection = [self collectionFromId: realCollectionId type: mergedFolderType]; + + // Cache-entry still exists but folder doesn't exists or synchronize flag is not set. + // We ignore the folder and wait for foldersync to do the cleanup. + if (!(mfCollection && [mfCollection synchronize])) + { + if (debugOn) + [self logWithFormat: @"EAS - Folder %@ not found. Ignoring ...", folderName]; + + continue; + } + + mergedFolderMetadata = [self _folderMetadataForKey: folderName]; + + if (debugOn) + [self logWithFormat: @"EAS - Add folder %@ for merging into personal folder", folderName]; + + // Initialize MergedFoldersSyncKeys entry. e.g. "vcard/34B2-543AB980-D-DB9ECF0" = -1 + [[mergedFoldersSyncKeys objectForKey: [folderMetadata objectForKey: @"SyncKey"]] setObject: @"-1" forKey: folderName]; + cacheUpdateNeeded = YES; + + // Flag folder as a merged folder. + if (![[mergedFolderMetadata objectForKey: @"MergedFolder"] isEqualToString: @"2"]) + { + [mergedFolderMetadata setObject: @"1" forKey: @"MergedFolder"]; + [self _setFolderMetadata: mergedFolderMetadata forKey: folderName]; + } + } + } + + // Check merged folders for changes. + folderNames = [[mergedFoldersSyncKeys objectForKey: [folderMetadata objectForKey: @"SyncKey"]] allKeys]; + for (i = 0; i < [folderNames count]; i++) + { + folderName = [folderNames objectAtIndex: i]; + realCollectionId = [folderName realCollectionIdWithFolderType: &mergedFolderType]; + mfCollection = [self collectionFromId: realCollectionId type: mergedFolderType]; + + if (!(mfCollection && [mfCollection synchronize])) + { + if (debugOn) + [self logWithFormat: @"EAS - Folder %@ not found. Reset peronal folder to cleanup", folderName]; + + davCollectionTag = @"0"; + *changeDetected = YES; + status = 3; + + // Reset personal folder - Cleanup metadata of personal folder. + [folderMetadata removeObjectForKey: @"SyncKey"]; + [folderMetadata removeObjectForKey: @"SyncCache"]; + [folderMetadata removeObjectForKey: @"DateCache"]; + [folderMetadata removeObjectForKey: @"UidCache"]; + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + [folderMetadata removeObjectForKey: @"CleanoutDate"]; + + [self _setFolderMetadata: folderMetadata forKey: folderKey]; + break; + } + + if (debugOn) + [self logWithFormat: @"EAS - Merging folder %@ into personal folder", folderName]; + + mergedFolderSyncKey = [[mergedFoldersSyncKeys objectForKey: [folderMetadata objectForKey: @"SyncKey"]] objectForKey: folderName ]; + + [self processSyncGetChanges: theDocumentElement + inCollection: mfCollection + withWindowSize: windowSize + withMaxSyncResponseSize: theMaxSyncResponseSize + withSyncKey: mergedFolderSyncKey + withFolderType: folderType + withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]] + inBuffer: changeBuffer + lastServerKey: &mfLastServerKey + defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval] + mergeFolders: YES]; + + mergedFolderMetadata = [self _folderMetadataForKey: folderName]; + + if ([changeBuffer length] || [commandsBuffer length]) + { + if (mfLastServerKey) + mergedFolderSyncKey = mfLastServerKey; + else + { + mergedFolderSyncKey = [mergedFolderMetadata objectForKey: @"SyncKey"]; + + if (!mergedFolderSyncKey) + mergedFolderSyncKey = [mfCollection davCollectionTag]; + } + + *changeDetected = YES; + } + else + { + // Make sure that client is updated with the right syncKey. - This keeps vtodo's and vevent's syncKey in sync. + syncKeyInCache = [mergedFolderMetadata objectForKey: @"SyncKey"]; + if (syncKeyInCache && !([mergedFolderSyncKey isEqualToString:syncKeyInCache]) && !first_sync) + { + mergedFolderSyncKey = syncKeyInCache; + *changeDetected = YES; + } + } + + // Update MergedFoldersSyncKeys with new SyncKey. + [[mergedFoldersSyncKeys objectForKey: [folderMetadata objectForKey: @"SyncKey"]] setObject: mergedFolderSyncKey forKey: folderName]; + + if (*changeDetected) + { + // Set MoreAvailable to make sure we come back and check remaining folders. + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + + break; + } + else if ([folderMetadata objectForKey: @"MoreAvailable"]) + { + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + cacheUpdateNeeded = YES; + } + } + } // end if + + if (*changeDetected || cacheUpdateNeeded) + [self _setFolderMetadata: folderMetadata forKey: folderKey]; + } + // If we got any changes or if we have applied any commands // let's regenerate our SyncKey based on the collection tag. if ([changeBuffer length] || [commandsBuffer length]) diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index d301a7109..a4b18aeb1 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -81,6 +81,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -98,6 +99,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import @@ -134,6 +136,9 @@ void handle_eas_terminate(int signum) - (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey; - (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata forKey: (NSString *) theFolderKey; +- (void) _setOrUnsetSyncRequest: (BOOL) set + collections: (NSArray *) collections; + @end @@ -856,7 +861,7 @@ void handle_eas_terminate(int signum) if ([key rangeOfString: @"+folder" options: NSCaseInsensitiveSearch].location != NSNotFound) [cachedGUIDs setObject: [NSString stringWithFormat: @"folder%@", [[o properties] objectForKey: @"displayName"]] // e.g. CDB648DDBC5040F8AC90792383DBBBAA+folderINBOX forKey: [key substringFromIndex: [key rangeOfString: @"+"].location+1]]; - else + else [cachedGUIDs setObject: [key substringFromIndex: [key rangeOfString: @"+"].location+1] // e.g. CDB648DDBC5040F8AC90792383DBBBAA+vcard/personal forKey: [key substringFromIndex: [key rangeOfString: @"+"].location+1]]; } @@ -899,12 +904,26 @@ void handle_eas_terminate(int signum) if ([cKey rangeOfString: @"/"].location != NSNotFound) currentFolder = [[[[context activeUser] homeFolderInContext: context] lookupName: folderType inContext: context acquire: NO] lookupName: [cKey substringFromIndex: [cKey rangeOfString: @"/"].location+1] inContext: context acquire: NO]; + else + currentFolder = nil; // We skip personal GCS folders - we always want to synchronize these if ([currentFolder isKindOfClass: [SOGoGCSFolder class]] && [[currentFolder nameInContainer] isEqualToString: @"personal"]) continue; + // We remove the folder from device but keep the cache entry, + // otherwise the user would see duplication objects, one in personal folder and one in the merged folder. + // MergedFolder=1 - Folder need to be removed from device. + // MergedFolder=2 - Folder has been removed from device; i.e. has been sent already. + if ([[[o properties] objectForKey: @"MergedFolder"] isEqualToString: @"1"]) + { + [commands appendFormat: @"%@", [cKey stringByEscapingURL] ]; + command_count++; + [[o properties] setObject: @"2" forKey: @"MergedFolder"]; + [o save]; + } + // Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions if (!currentFolder || ![currentFolder synchronize] || @@ -915,10 +934,16 @@ void handle_eas_terminate(int signum) onObject: currentFolder inContext: context]) { - [commands appendFormat: @"%@", [cKey stringByEscapingURL] ]; - command_count++; + // Don't send a delete when MergedFoler is set, we have done it above. + // Windows Phones don't like when a -folder is sent twice. + if (![[[o properties] objectForKey: @"MergedFolder"] isEqualToString: @"2"]) + { + [commands appendFormat: @"%@", [cKey stringByEscapingURL] ]; + command_count++; + } [o destroy]; } + } } } @@ -1010,6 +1035,7 @@ void handle_eas_terminate(int signum) [[o properties] removeObjectForKey: @"SyncKey"]; [[o properties] removeObjectForKey: @"SyncCache"]; [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] removeObjectForKey: @"UidCache"]; [[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"SupportedElements"]; @@ -1017,6 +1043,9 @@ void handle_eas_terminate(int signum) [[o properties] removeObjectForKey: @"InitialLoadSequence"]; [[o properties] removeObjectForKey: @"FirstIdInCache"]; [[o properties] removeObjectForKey: @"LastIdInCache"]; + [[o properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[o properties] removeObjectForKey: @"MergedFolder"]; + [[o properties] removeObjectForKey: @"CleanoutDate"]; [o save]; @@ -1075,7 +1104,7 @@ void handle_eas_terminate(int signum) operation = @"Add"; else if (![[[o properties ] objectForKey: @"displayName"] isEqualToString: [[folders objectAtIndex:fi] displayName]]) operation = @"Update"; - else + else operation = nil; if (operation) @@ -1111,6 +1140,7 @@ void handle_eas_terminate(int signum) [[o properties] removeObjectForKey: @"SyncKey"]; [[o properties] removeObjectForKey: @"SyncCache"]; [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] removeObjectForKey: @"UidCache"]; [[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"SupportedElements"]; @@ -1118,6 +1148,9 @@ void handle_eas_terminate(int signum) [[o properties] removeObjectForKey: @"InitialLoadSequence"]; [[o properties] removeObjectForKey: @"FirstIdInCache"]; [[o properties] removeObjectForKey: @"LastIdInCache"]; + [[o properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[o properties] removeObjectForKey: @"MergedFolder"]; + [[o properties] removeObjectForKey: @"CleanoutDate"]; } [o save]; @@ -1138,6 +1171,7 @@ void handle_eas_terminate(int signum) [[o properties] removeObjectForKey: @"SyncKey"]; [[o properties] removeObjectForKey: @"SyncCache"]; [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] removeObjectForKey: @"UidCache"]; [[o properties] removeObjectForKey: @"MoreAvailable"]; [[o properties] removeObjectForKey: @"BodyPreferenceType"]; [[o properties] removeObjectForKey: @"SupportedElements"]; @@ -1145,6 +1179,9 @@ void handle_eas_terminate(int signum) [[o properties] removeObjectForKey: @"InitialLoadSequence"]; [[o properties] removeObjectForKey: @"FirstIdInCache"]; [[o properties] removeObjectForKey: @"LastIdInCache"]; + [[o properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[o properties] removeObjectForKey: @"MergedFolder"]; + [[o properties] removeObjectForKey: @"CleanoutDate"]; } [o save]; @@ -1612,10 +1649,11 @@ void handle_eas_terminate(int signum) - (void) processMeetingResponse: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { - NSString *realCollectionId, *requestId, *participationStatus, *calendarId; + NSString *realCollectionId, *requestId, *easRequestId, *participationStatus, *calendarId; SOGoAppointmentObject *appointmentObject; SOGoMailObject *mailObject; - NSMutableString *s; + NSMutableDictionary *uidCache, *folderMetadata; + NSMutableString *s, *nameInCache; NSData *d; id collection; @@ -1630,7 +1668,7 @@ void handle_eas_terminate(int signum) realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue]; - requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue]; + easRequestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue]; appointmentObject = nil; calendarId = nil; @@ -1649,10 +1687,35 @@ void handle_eas_terminate(int signum) if (folderType == ActiveSyncEventFolder) { collection = [[context activeUser] personalCalendarFolderInContext: context]; + + nameInCache = [NSString stringWithFormat: @"vevent/%@", [collection nameInContainer]]; + folderMetadata = [self _folderMetadataForKey: nameInCache]; + + uidCache = [folderMetadata objectForKey: @"UidCache"]; + if (uidCache) + { + requestId = [[uidCache allKeysForObject: easRequestId] objectAtIndex: 0]; + + if (requestId) + { + if (debugOn) + [self logWithFormat: @"EAS - Found requestId: %@ for easRequestId: %@", requestId, easRequestId]; + } + else + { + if (debugOn) + [self logWithFormat: @"EAS - Use original requestId: %@", easRequestId]; + + requestId = easRequestId; + } + } + else + requestId = easRequestId; + appointmentObject = [collection lookupName: [requestId sanitizedServerIdWithType: ActiveSyncEventFolder] inContext: context acquire: NO]; - calendarId = requestId; + calendarId = easRequestId; // Object not found, let's fallback on the INBOX folder if ([appointmentObject isKindOfClass: [NSException class]]) @@ -1672,7 +1735,7 @@ void handle_eas_terminate(int signum) // 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 + mailObject = [collection lookupName: easRequestId inContext: context acquire: 0]; @@ -1687,6 +1750,12 @@ void handle_eas_terminate(int signum) // Fetch the SOGoAppointmentObject collection = [[context activeUser] personalCalendarFolderInContext: context]; + nameInCache = [NSString stringWithFormat: @"vevent/%@", [collection nameInContainer]]; + + [self _setOrUnsetSyncRequest: YES collections: [NSArray arrayWithObject: nameInCache]]; + folderMetadata = [self _folderMetadataForKey: nameInCache]; + uidCache = [folderMetadata objectForKey: @"UidCache"]; + appointmentObject = [collection lookupName: [NSString stringWithFormat: @"%@.ics", [event uid]] inContext: context acquire: NO]; @@ -1698,6 +1767,27 @@ void handle_eas_terminate(int signum) inContainer: collection]; [appointmentObject saveComponent: event]; } + + if (uidCache && [calendarId length] > 64) + { + calendarId = [uidCache objectForKey: [event uid]]; + + if (![calendarId length]) + { + calendarId = [collection globallyUniqueObjectId]; + [uidCache setObject: calendarId forKey: [event uid]]; + + [self _setFolderMetadata: folderMetadata forKey: nameInCache]; + + if (debugOn) + [self logWithFormat: @"EAS - Generated new calendarId: %@ for serverId: %@", calendarId, [event uid]]; + } + else + { + if (debugOn) + [self logWithFormat: @"EAS - Reuse calendarId: %@ for serverId: %@", calendarId, [event uid]]; + } + } } } @@ -1721,7 +1811,7 @@ void handle_eas_terminate(int signum) [s appendString: @""]; [s appendString: @""]; [s appendString: @""]; - [s appendFormat: @"%@", requestId]; + [s appendFormat: @"%@", easRequestId]; [s appendFormat: @"%@", calendarId]; [s appendFormat: @"%d", status]; [s appendString: @""]; @@ -1737,7 +1827,7 @@ void handle_eas_terminate(int signum) [s appendString: @""]; [s appendString: @""]; [s appendString: @""]; - [s appendFormat: @"%@", requestId]; + [s appendFormat: @"%@", easRequestId]; [s appendFormat: @"%d", 2]; [s appendString: @""]; [s appendString: @""]; @@ -1762,8 +1852,8 @@ void handle_eas_terminate(int signum) - (void) processMoveItems: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { - NSString *srcMessageId, *srcFolderId, *dstFolderId, *dstMessageId, *nameInCache, *currentFolder; - NSMutableDictionary *folderMetadata, *prevSuccessfulMoveItemsOps, *newSuccessfulMoveItemsOps; + NSString *srcMessageId, *srcFolderId, *dstFolderId, *dstMessageId, *srcNameInCache, *dstNameInCache, *currentSrcFolder, *currentDstFolder; + NSMutableDictionary *srcFolderMetadata, *dstFolderMetadata, *prevSuccessfulMoveItemsOps, *newSuccessfulMoveItemsOps; SOGoMicrosoftActiveSyncFolderType srcFolderType, dstFolderType; id aMoveOperation; NSArray *moveOperations; @@ -1772,12 +1862,17 @@ void handle_eas_terminate(int signum) NSData *d; int i; + currentSrcFolder = nil; + currentDstFolder = nil; + moveOperations = (id)[theDocumentElement getElementsByTagName: @"Move"]; newSuccessfulMoveItemsOps = [NSMutableDictionary dictionary]; prevSuccessfulMoveItemsOps = nil; - folderMetadata = nil; - currentFolder = nil; + srcFolderMetadata = nil; + dstFolderMetadata = nil; + currentSrcFolder = nil; + currentDstFolder = nil; s = [NSMutableString string]; @@ -1793,18 +1888,33 @@ void handle_eas_terminate(int signum) srcFolderId = [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] realCollectionIdWithFolderType: &srcFolderType]; dstFolderId = [[[(id)[aMoveOperation getElementsByTagName: @"DstFldId"] lastObject] textValue] realCollectionIdWithFolderType: &dstFolderType]; + [self _setOrUnsetSyncRequest: YES collections: [NSArray arrayWithObjects: + [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL], + [[[(id)[aMoveOperation getElementsByTagName: @"DstFldId"] lastObject] textValue] stringByUnescapingURL], nil ]]; + if (srcFolderType == ActiveSyncMailFolder) - nameInCache = [NSString stringWithFormat: @"folder%@", [[[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL] substringFromIndex: 5]]; + srcNameInCache = [NSString stringWithFormat: @"folder%@", [[[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL] substringFromIndex: 5]]; else - nameInCache = [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL]; + srcNameInCache = [[[(id)[aMoveOperation getElementsByTagName: @"SrcFldId"] lastObject] textValue] stringByUnescapingURL]; - if (![nameInCache isEqualToString: currentFolder]) + if (![srcNameInCache isEqualToString: currentSrcFolder]) { - folderMetadata = [self _folderMetadataForKey: nameInCache]; - prevSuccessfulMoveItemsOps = [folderMetadata objectForKey: @"SuccessfulMoveItemsOps"]; - currentFolder = nameInCache; + srcFolderMetadata = [self _folderMetadataForKey: srcNameInCache]; + prevSuccessfulMoveItemsOps = [srcFolderMetadata objectForKey: @"SuccessfulMoveItemsOps"]; + currentSrcFolder = srcNameInCache; } - + + if (dstFolderType == ActiveSyncMailFolder) + dstNameInCache = [NSString stringWithFormat: @"folder%@", [[[[(id)[aMoveOperation getElementsByTagName: @"DstFldId"] lastObject] textValue] stringByUnescapingURL] substringFromIndex: 5]]; + else + dstNameInCache = [[[(id)[aMoveOperation getElementsByTagName: @"DstFldId"] lastObject] textValue] stringByUnescapingURL]; + + if (![dstNameInCache isEqualToString: currentDstFolder]) + { + dstFolderMetadata = [self _folderMetadataForKey: dstNameInCache]; + currentDstFolder = dstNameInCache; + } + [s appendString: @""]; if (srcFolderType == ActiveSyncMailFolder && dstFolderType == ActiveSyncMailFolder) @@ -1909,14 +2019,44 @@ void handle_eas_terminate(int signum) { id srcCollection, dstCollection, srcSogoObject, dstSogoObject; NSArray *elements; - NSString *newUID; + NSString *newUID, *origSrcMessageId; + NSMutableDictionary *srcUidCache, *dstUidCache, *dstSyncCache; NSException *ex; unsigned int count, max; srcCollection = [self collectionFromId: srcFolderId type: srcFolderType]; + + if ([srcCollection isKindOfClass: [NSException class]]) + { + [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%d", 1]; + continue; + } + dstCollection = [self collectionFromId: dstFolderId type: srcFolderType]; - + + if ([dstCollection isKindOfClass: [NSException class]]) + { + [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%d", 2]; + continue; + } + + origSrcMessageId = srcMessageId; + srcUidCache = [srcFolderMetadata objectForKey: @"UidCache"]; + dstUidCache = [dstFolderMetadata objectForKey: @"UidCache"]; + dstSyncCache = [dstFolderMetadata objectForKey: @"SyncCache"]; + + if (srcUidCache) + { + srcMessageId = [[srcUidCache allKeysForObject: origSrcMessageId] objectAtIndex: 0]; + if (debugOn) + [self logWithFormat: @"EAS - Found serverId: %@ for easId: %@", srcMessageId, origSrcMessageId]; + } + else + srcMessageId = origSrcMessageId; + srcSogoObject = [srcCollection lookupName: [srcMessageId sanitizedServerIdWithType: srcFolderType] inContext: context acquire: NO]; @@ -1924,7 +2064,8 @@ void handle_eas_terminate(int signum) sm = [SoSecurityManager sharedSecurityManager]; if (![sm validatePermission: SoPerm_DeleteObjects onObject: srcCollection - inContext: context]) + inContext: context] && + ![srcSogoObject isKindOfClass: [NSException class]]) { if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles onObject: dstCollection @@ -1942,7 +2083,18 @@ void handle_eas_terminate(int signum) if (!ex) { ex = [srcSogoObject delete]; - [s appendFormat: @"%@", srcMessageId]; + + if (dstUidCache) + { + [dstUidCache setObject: newUID forKey: newUID]; + + if (debugOn) + [self logWithFormat: @"EAS - Saved new easId: %@ for serverId: %@", newUID, newUID]; + } + + [dstSyncCache setObject: [dstFolderMetadata objectForKey: @"SyncKey"] forKey: newUID]; + + [s appendFormat: @"%@", origSrcMessageId]; [s appendFormat: @"%@", newUID]; [s appendFormat: @"%d", 3]; @@ -1954,36 +2106,45 @@ void handle_eas_terminate(int signum) if ([prevSuccessfulMoveItemsOps objectForKey: srcMessageId]) { // Move failed but we can recover the dstMessageId from previous request - [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%@", origSrcMessageId]; [s appendFormat: @"%@", [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] ]; [s appendFormat: @"%d", 3]; [newSuccessfulMoveItemsOps setObject: [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] forKey: srcMessageId]; + + if (dstUidCache) + { + [dstUidCache setObject: newUID forKey: newUID]; + + if (debugOn) + [self logWithFormat: @"EAS - Saved new easId: %@ for serverId: %@", newUID, newUID]; + } } else { - [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%@", origSrcMessageId]; [s appendFormat: @"%d", 1]; } } } - else + else { - [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%@", origSrcMessageId]; [s appendFormat: @"%d", 2]; } } else { - [s appendFormat: @"%@", srcMessageId]; + [s appendFormat: @"%@", origSrcMessageId]; [s appendFormat: @"%d", 1]; } } [s appendString: @""]; - [folderMetadata removeObjectForKey: @"SuccessfulMoveItemsOps"]; - [folderMetadata setObject: newSuccessfulMoveItemsOps forKey: @"SuccessfulMoveItemsOps"]; - [self _setFolderMetadata: folderMetadata forKey: nameInCache]; + [srcFolderMetadata removeObjectForKey: @"SuccessfulMoveItemsOps"]; + [srcFolderMetadata setObject: newSuccessfulMoveItemsOps forKey: @"SuccessfulMoveItemsOps"]; + [self _setFolderMetadata: srcFolderMetadata forKey: srcNameInCache]; + [self _setFolderMetadata: dstFolderMetadata forKey: dstNameInCache]; } [s appendString: @""]; @@ -2161,7 +2322,7 @@ void handle_eas_terminate(int signum) if (folderType == ActiveSyncMailFolder) folderMetadata = [self _folderMetadataForKey: [NSString stringWithFormat: @"folder%@", [[collectionId stringByUnescapingURL] substringFromIndex:5]]]; - else + else folderMetadata = [self _folderMetadataForKey: [collectionId stringByUnescapingURL]]; collection = [self collectionFromId: realCollectionId type: folderType]; @@ -3215,7 +3376,7 @@ void handle_eas_terminate(int signum) textPart = apart; } } - else + else { if ([[[part contentType] type] isEqualToString: @"text"] && [[[part contentType] subType] isEqualToString: @"html"]) htmlPart = part; @@ -3248,7 +3409,7 @@ void handle_eas_terminate(int signum) charset = [[htmlPart contentType] valueOfParameter: @"charset"]; isHTML = YES; } - else + else { bodyFromSmartForward = [textPart body]; charset = [[textPart contentType] valueOfParameter: @"charset"]; @@ -3565,7 +3726,7 @@ void handle_eas_terminate(int signum) if (([cmdName rangeOfString: @"ItemOperations" options: NSCaseInsensitiveSearch].location != NSNotFound) && ([[theRequest headerForKey: @"MS-ASAcceptMultiPart"] isEqualToString:@"T"] || [[theRequest uri] acceptsMultiPart])) [theResponse setHeader: @"application/vnd.ms-sync.multipart" forKey: @"Content-Type"]; - else + else [theResponse setHeader: @"application/vnd.ms-sync.wbxml" forKey: @"Content-Type"]; [self performSelector: aSelector withObject: documentElement withObject: theResponse]; diff --git a/Tools/SOGoToolManageEAS.m b/Tools/SOGoToolManageEAS.m index 0cb16bbd2..2c59f72ba 100644 --- a/Tools/SOGoToolManageEAS.m +++ b/Tools/SOGoToolManageEAS.m @@ -20,6 +20,8 @@ #import #import +#import +#import #import @@ -39,6 +41,8 @@ typedef enum ManageEASListFolders = 2, ManageEASResetDevice = 3, ManageEASRestFolder = 4, + ManageEASVCard = 5, + ManageEASVEvent = 6, } SOGoManageEASCommand; @interface SOGoToolManageEAS : SOGoTool @@ -46,6 +50,8 @@ typedef enum @implementation SOGoToolManageEAS +NSURL *folderTableURL; + + (void) initialize { } @@ -60,15 +66,58 @@ typedef enum return @"manage EAS folders"; } +- (void) _setOrUnsetSyncRequest: (BOOL) set + collections: (NSArray *) collections +{ + SOGoCacheGCSObject *o; + NSNumber *processIdentifier; + NSString *key; + NSArray *a; + int i; + + processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]]; + + o = [SOGoCacheGCSObject objectWithName: [[[collections objectAtIndex: 0] componentsSeparatedByString: @"+"] objectAtIndex: 0] inContainer: nil useCache: NO]; + [o setObjectType: ActiveSyncGlobalCacheObject]; + [o setTableUrl: folderTableURL]; + [o reloadIfNeeded]; + + if (set) + { + [[o properties] setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"SyncRequest"]; + + for (i = 0; i < [collections count]; i++) + { + a = [[collections objectAtIndex: i] componentsSeparatedByString: @"+"]; + key = [NSString stringWithFormat: @"SyncRequest+%@", [a objectAtIndex: 1]]; + [[o properties] setObject: processIdentifier forKey: key]; + } + } + else + { + [[o properties] removeObjectForKey: @"SyncRequest"]; + for (i = 0; i < [collections count]; i++) + { + a = [[collections objectAtIndex: i] componentsSeparatedByString: @"+"]; + key = [NSString stringWithFormat: @"SyncRequest+%@", [a objectAtIndex: 1]]; + [[o properties] removeObjectForKey: key]; + } + } + + [o save]; +} + - (void) usage { - fprintf (stderr, "manage-eas listdevices|resetdevice|resetfolder user \n\n" + fprintf (stderr, "manage-eas listdevices|resetdevice|resetfolder|mergevcard|mergevevent user \n\n" " user the user of whom to reset the whole device or a single folder\n" " Examples:\n" " sogo-tool manage-eas listdevices janedoe\n" " sogo-tool manage-eas listfolders janedoe androidc316986417\n" " sogo-tool manage-eas resetdevice janedoe androidc316986417\n" - " sogo-tool manage-eas resetfolder janedow androidc316986417+folderlala-dada-sasa_7a13_1a2386e0_e\n") ; + " sogo-tool manage-eas resetfolder janedow androidc316986417+folderlala-dada-sasa_7a13_1a2386e0_e\n" + " sogo-tool manage-eas mergevcard janedow androidc316986417 YES\n" + " sogo-tool manage-eas mergevevent janedow androidc316986417 YES\n") ; } @@ -84,6 +133,10 @@ typedef enum return ManageEASResetDevice; else if ([theString caseInsensitiveCompare: @"resetfolder"] == NSOrderedSame) return ManageEASRestFolder; + else if ([theString caseInsensitiveCompare: @"mergevcard"] == NSOrderedSame) + return ManageEASVCard; + else if ([theString caseInsensitiveCompare: @"mergevevent"] == NSOrderedSame) + return ManageEASVEvent; } return ManageEASUnknown; @@ -94,7 +147,6 @@ typedef enum NSString *urlString, *deviceId, *userId; NSMutableString *ocFSTableName; SOGoCacheGCSObject *oc, *foc; - NSURL *folderTableURL; NSMutableArray *parts; NSArray *entries; id cacheEntry; @@ -156,6 +208,7 @@ typedef enum } rc = YES; + break; case ManageEASListFolders: @@ -182,6 +235,8 @@ typedef enum [foc reloadIfNeeded]; fprintf(stdout, " Folder Name: %s\n\n", [[[foc properties] objectForKey: @"displayName"] UTF8String]); + if ([[foc properties] objectForKey: @"MergedFolder"]) + fprintf(stdout, " MergedFolder = YES\n\n"); if (verbose) fprintf(stdout, " metadata Name: %s\n\n", [[[foc properties] description] UTF8String]); @@ -214,7 +269,7 @@ typedef enum NSMutableString *sql; - sql = [NSMutableString stringWithFormat: @"DELETE FROM %@ WHERE c_path like '/%@'", [oc tableName], deviceId]; + sql = [NSMutableString stringWithFormat: @"DELETE FROM %@ WHERE c_path like '/%@%'", [oc tableName], deviceId]; [oc performBatchSQLQueries: [NSArray arrayWithObject: sql]]; rc = YES; @@ -247,16 +302,138 @@ typedef enum fprintf(stderr, "ERROR: Folder with ID \"%s\" not found\n", [deviceId UTF8String]); return rc; } - [[oc properties] removeObjectForKey: @"SyncKey"]; - [[oc properties] removeObjectForKey: @"SyncCache"]; - [[oc properties] removeObjectForKey: @"DateCache"]; - [[oc properties] removeObjectForKey: @"MoreAvailable"]; - [[oc properties] removeObjectForKey: @"FirstIdInCache"]; - [[oc properties] removeObjectForKey: @"LastIdInCache"]; - [oc save]; + if ((![deviceId hasSuffix: @"/personal"]) && [[oc properties] objectForKey: @"MergedFolder"]) + { + fprintf(stderr, "ERROR: MergedFolder = true; only personal folder can be reset"); + return rc; + } + else + { + [self _setOrUnsetSyncRequest: YES collections: [NSArray arrayWithObject: deviceId]]; + + [[oc properties] removeObjectForKey: @"SyncKey"]; + [[oc properties] removeObjectForKey: @"SyncCache"]; + [[oc properties] removeObjectForKey: @"DateCache"]; + [[oc properties] removeObjectForKey: @"UidCache"]; + [[oc properties] removeObjectForKey: @"MoreAvailable"]; + [[oc properties] removeObjectForKey: @"BodyPreferenceType"]; + [[oc properties] removeObjectForKey: @"SupportedElements"]; + [[oc properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; + [[oc properties] removeObjectForKey: @"InitialLoadSequence"]; + [[oc properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[oc properties] removeObjectForKey: @"CleanoutDate"]; + + [oc save]; + rc = YES; + } + } + else + { + fprintf(stderr, "\nERROR: folderId not specified\n\n"); + } + + break; + + case ManageEASVCard: + case ManageEASVEvent: + if (max > 3) + { + NSString *folderType; + + if (cmd == ManageEASVCard) + folderType = @"vcard"; + else + folderType = @"vevent"; + + /* value specified on command line */ + deviceId = [sanitizedArguments objectAtIndex: 2]; + + oc = [SOGoCacheGCSObject objectWithName: @"0" inContainer: nil]; + [oc setObjectType: ActiveSyncFolderCacheObject]; + + [oc setTableUrl: folderTableURL]; + entries = [oc cacheEntriesForDeviceId: deviceId newerThanVersion: -1]; + + vtodo: + + for (i = 0; i < [entries count]; i++) + { + cacheEntry = [entries objectAtIndex: i]; + + if ([[cacheEntry substringFromIndex: 1] hasPrefix: [NSString stringWithFormat: @"%@+%@/", deviceId, folderType]]) + { + fprintf(stdout,"Folder Key: %s\n", [[cacheEntry substringFromIndex: 1] UTF8String]); + + foc = [SOGoCacheGCSObject objectWithName: [cacheEntry substringFromIndex: 1] inContainer: nil]; + [foc setObjectType: ActiveSyncFolderCacheObject]; + [foc setTableUrl: folderTableURL]; + + [foc reloadIfNeeded]; + + if ([foc isNew]) + continue; + + [self _setOrUnsetSyncRequest: YES collections: [NSArray arrayWithObject: [cacheEntry substringFromIndex: 1]]]; + + if (![[cacheEntry substringFromIndex: 1] hasPrefix: [NSString stringWithFormat: @"%@+%@/personal", deviceId, folderType]] && + [[sanitizedArguments objectAtIndex: 3] isEqualToString: @"NO"] && + [[[foc properties] objectForKey: @"MergedFolder"] isEqualToString: @"2"]) + { + [foc destroy]; + continue; + } + else if ([[sanitizedArguments objectAtIndex: 3] isEqualToString: @"NO"]) + { + [[foc properties] removeObjectForKey: @"SyncKey"]; + [[foc properties] removeObjectForKey: @"SyncCache"]; + [[foc properties] removeObjectForKey: @"DateCache"]; + [[foc properties] removeObjectForKey: @"UidCache"]; + [[foc properties] removeObjectForKey: @"MoreAvailable"]; + [[foc properties] removeObjectForKey: @"BodyPreferenceType"]; + [[foc properties] removeObjectForKey: @"SupportedElements"]; + [[foc properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; + [[foc properties] removeObjectForKey: @"InitialLoadSequence"]; + [[foc properties] removeObjectForKey: @"FirstIdInCache"]; + [[foc properties] removeObjectForKey: @"LastIdInCache"]; + [[foc properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[foc properties] removeObjectForKey: @"CleanoutDate"]; + + [[foc properties] removeObjectForKey: @"MergedFolder"]; + } + else if ([[sanitizedArguments objectAtIndex: 3] isEqualToString: @"YES"] && ![[foc properties] objectForKey: @"MergedFolder"]) + { + if (![[cacheEntry substringFromIndex: 1] hasPrefix: [NSString stringWithFormat: @"%@+%@/personal", deviceId, folderType]]) + { + [[foc properties] removeObjectForKey: @"SyncKey"]; + [[foc properties] removeObjectForKey: @"SyncCache"]; + [[foc properties] removeObjectForKey: @"DateCache"]; + [[foc properties] removeObjectForKey: @"UidCache"]; + [[foc properties] removeObjectForKey: @"MoreAvailable"]; + [[foc properties] removeObjectForKey: @"BodyPreferenceType"]; + [[foc properties] removeObjectForKey: @"SupportedElements"]; + [[foc properties] removeObjectForKey: @"SuccessfulMoveItemsOps"]; + [[foc properties] removeObjectForKey: @"InitialLoadSequence"]; + [[foc properties] removeObjectForKey: @"FirstIdInCache"]; + [[foc properties] removeObjectForKey: @"LastIdInCache"]; + [[foc properties] removeObjectForKey: @"MergedFoldersSyncKeys"]; + [[foc properties] removeObjectForKey: @"CleanoutDate"]; + } + + [[foc properties] setObject: @"1" forKey: @"MergedFolder"]; + } + + [foc save]; + } + } + + if (cmd == ManageEASVEvent && [folderType isEqualToString: @"vevent"]) + { + folderType = @"vtodo"; + goto vtodo; + } + rc = YES; - } else {