Added EAS folder merge support and fixed long GUID issue (fixes #3460)

pull/218/head
Ludovic Marcotte 2016-07-21 14:06:11 -04:00
parent 8dfe399c02
commit 440acec143
3 changed files with 842 additions and 92 deletions

View File

@ -59,6 +59,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Contacts/SOGoContactGCSEntry.h>
#import <Mailer/SOGoMailFolder.h>
#import <Mailer/SOGoMailNamespace.h>
#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: @"<Add>"];
[theBuffer appendFormat: @"<ClientId>%@</ClientId>", clientId];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Add>"];
// 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: @"<Add>"];
[theBuffer appendFormat: @"<ClientId>%@</ClientId>", clientId];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", easId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Add>"];
}
}
}
@ -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: @"<Change>"];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
[theBuffer appendFormat: @"<Status>%d</Status>", 8];
[theBuffer appendString: @"</Change>"];
continue;
}
@ -474,7 +555,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[theBuffer appendString: @"<Change>"];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Change>"];
}
@ -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: @"<Delete>"];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Delete>"];
[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: @"<Delete>"];
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
[theBuffer appendString: @"</Delete>"];
}
}
}
}
@ -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: @"<Delete xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder)
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@{+}%@</ServerId>", [theCollection nameInContainer], easId];
else
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", easId];
[s appendString: @"</Delete>"];
[syncCache removeObjectForKey: uid];
@ -922,7 +1105,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
else
[s appendString: @"<Add xmlns=\"AirSync:\">"];
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", uid];
if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder)
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@{+}%@</ServerId>", [theCollection nameInContainer], easId];
else
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", easId];
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
[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: @"<Collection>"];
[theBuffer appendFormat: @"<SyncKey>%@</SyncKey>", syncKey];
[theBuffer appendFormat: @"<CollectionId>%@</CollectionId>", collectionId];
[theBuffer appendFormat: @"<Status>%d</Status>", 12];
[theBuffer appendString: @"</Collection>"];
*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])

View File

@ -81,6 +81,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <SOGo/NSString+Utilities.h>
#import <SOGo/WORequest+SOGo.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <Appointments/SOGoAppointmentFolder.h>
#import <Appointments/SOGoAppointmentFolders.h>
@ -98,6 +99,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <Mailer/SOGoMailObject+Draft.h>
#import <Mailer/SOGoSentFolder.h>
#import <Mailer/NSString+Mail.h>
#import <Mailer/SOGoSentFolder.h>
#import <Foundation/NSString.h>
@ -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. <Delete> has been sent already.
if ([[[o properties] objectForKey: @"MergedFolder"] isEqualToString: @"1"])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [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: @"<Delete><ServerId>%@</ServerId></Delete>", [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 <Delete>-folder is sent twice.
if (![[[o properties] objectForKey: @"MergedFolder"] isEqualToString: @"2"])
{
[commands appendFormat: @"<Delete><ServerId>%@</ServerId></Delete>", [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 <DOMElement>) 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: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
[s appendString: @"<Result>"];
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
[s appendFormat: @"<RequestId>%@</RequestId>", easRequestId];
[s appendFormat: @"<CalendarId>%@</CalendarId>", calendarId];
[s appendFormat: @"<Status>%d</Status>", status];
[s appendString: @"</Result>"];
@ -1737,7 +1827,7 @@ void handle_eas_terminate(int signum)
[s appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
[s appendString: @"<MeetingResponse xmlns=\"MeetingResponse:\">"];
[s appendString: @"<Result>"];
[s appendFormat: @"<RequestId>%@</RequestId>", requestId];
[s appendFormat: @"<RequestId>%@</RequestId>", easRequestId];
[s appendFormat: @"<Status>%d</Status>", 2];
[s appendString: @"</Result>"];
[s appendString: @"</MeetingResponse>"];
@ -1762,8 +1852,8 @@ void handle_eas_terminate(int signum)
- (void) processMoveItems: (id <DOMElement>) 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 <DOMElement> 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: @"<Response>"];
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: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
[s appendFormat: @"<Status>%d</Status>", 1];
continue;
}
dstCollection = [self collectionFromId: dstFolderId type: srcFolderType];
if ([dstCollection isKindOfClass: [NSException class]])
{
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
[s appendFormat: @"<Status>%d</Status>", 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: @"<SrcMsgId>%@</SrcMsgId>", 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: @"<SrcMsgId>%@</SrcMsgId>", origSrcMessageId];
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", newUID];
[s appendFormat: @"<Status>%d</Status>", 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: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", origSrcMessageId];
[s appendFormat: @"<DstMsgId>%@</DstMsgId>", [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] ];
[s appendFormat: @"<Status>%d</Status>", 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: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", origSrcMessageId];
[s appendFormat: @"<Status>%d</Status>", 1];
}
}
}
else
else
{
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", origSrcMessageId];
[s appendFormat: @"<Status>%d</Status>", 2];
}
}
else
{
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", srcMessageId];
[s appendFormat: @"<SrcMsgId>%@</SrcMsgId>", origSrcMessageId];
[s appendFormat: @"<Status>%d</Status>", 1];
}
}
[s appendString: @"</Response>"];
[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: @"</MoveItems>"];
@ -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];

View File

@ -20,6 +20,8 @@
#import <Foundation/NSDictionary.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSProcessInfo.h>
#import <NGObjWeb/WOContext+SoObjects.h>
@ -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 <devinceId | folderId> \n\n"
fprintf (stderr, "manage-eas listdevices|resetdevice|resetfolder|mergevcard|mergevevent user <devinceId | folderId> <YES | NO>\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
{