2014-01-10 20:12:53 +01:00
|
|
|
/*
|
|
|
|
|
|
|
|
Copyright (c) 2014, Inverse inc.
|
|
|
|
All rights reserved.
|
|
|
|
|
2014-01-13 17:46:32 +01:00
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
|
|
notice, this list of conditions and the following disclaimer in the
|
|
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
* Neither the name of the Inverse inc. nor the
|
|
|
|
names of its contributors may be used to endorse or promote products
|
|
|
|
derived from this software without specific prior written permission.
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
*/
|
|
|
|
#import "SOGoActiveSyncDispatcher+Sync.h"
|
|
|
|
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <Foundation/NSAutoreleasePool.h>
|
|
|
|
#import <Foundation/NSNull.h>
|
|
|
|
#import <Foundation/NSProcessInfo.h>
|
|
|
|
#import <Foundation/NSSortDescriptor.h>
|
|
|
|
#import <Foundation/NSValue.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#import <NGObjWeb/NSException+HTTP.h>
|
|
|
|
#import <NGObjWeb/WOContext+SoObjects.h>
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <NGObjWeb/WORequest.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#import <NGExtensions/NSCalendarDate+misc.h>
|
2015-01-22 19:31:31 +01:00
|
|
|
#import <NGExtensions/NSObject+Logs.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <NGExtensions/NSString+misc.h>
|
|
|
|
|
|
|
|
#import <NGImap4/NSString+Imap4.h>
|
|
|
|
|
|
|
|
#import <SOGo/NSArray+DAV.h>
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <SOGo/SOGoCache.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
#import <SOGo/NSDictionary+DAV.h>
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <SOGo/SOGoSystemDefaults.h>
|
|
|
|
#import <SOGo/SOGoUser.h>
|
|
|
|
#import <SOGo/SOGoCacheGCSObject.h>
|
2016-11-21 16:45:27 +01:00
|
|
|
#import <SOGo/SOGoPermissions.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2017-04-11 16:13:56 +02:00
|
|
|
#import <NGCards/iCalCalendar.h>
|
|
|
|
|
2016-01-22 19:35:02 +01:00
|
|
|
#import <Appointments/iCalEntityObject+SOGo.h>
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <Appointments/SOGoAppointmentObject.h>
|
|
|
|
#import <Appointments/SOGoTaskObject.h>
|
2017-04-11 16:13:56 +02:00
|
|
|
#import <Appointments/SOGoAppointmentFolder.h>
|
2016-02-13 22:11:52 +01:00
|
|
|
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <Contacts/SOGoContactGCSEntry.h>
|
2016-02-13 22:11:52 +01:00
|
|
|
|
2016-02-17 00:39:58 +01:00
|
|
|
#import <Mailer/SOGoMailFolder.h>
|
2016-07-21 20:06:11 +02:00
|
|
|
#import <Mailer/SOGoMailNamespace.h>
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
#include "iCalEvent+ActiveSync.h"
|
|
|
|
#include "iCalToDo+ActiveSync.h"
|
|
|
|
#include "NGDOMElement+ActiveSync.h"
|
|
|
|
#include "NGVCard+ActiveSync.h"
|
2014-01-20 16:13:16 +01:00
|
|
|
#include "NSCalendarDate+ActiveSync.h"
|
2014-01-16 21:13:09 +01:00
|
|
|
#include "NSDate+ActiveSync.h"
|
2014-01-10 20:12:53 +01:00
|
|
|
#include "NSData+ActiveSync.h"
|
|
|
|
#include "NSString+ActiveSync.h"
|
|
|
|
#include "SOGoMailObject+ActiveSync.h"
|
2016-02-17 00:39:58 +01:00
|
|
|
#include "SOGoSyncCacheObject.h"
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2016-02-17 00:39:58 +01:00
|
|
|
#include <unistd.h>
|
2014-02-04 17:19:33 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
@implementation SOGoActiveSyncDispatcher (Sync)
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
- (void) _setOrUnsetSyncRequest: (BOOL) set
|
2016-09-22 17:42:28 +02:00
|
|
|
collections: (NSArray *) collections
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
SOGoCacheGCSObject *o;
|
2015-10-20 14:48:39 +02:00
|
|
|
NSNumber *processIdentifier;
|
2016-07-25 12:17:17 +02:00
|
|
|
NSString *key, *collectionId;;
|
2015-10-20 14:48:39 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: [context objectForKey: @"DeviceId"] inContainer: nil useCache: NO];
|
|
|
|
[o setObjectType: ActiveSyncGlobalCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
if (set)
|
2015-10-20 14:48:39 +02:00
|
|
|
{
|
|
|
|
RELEASE(syncRequest);
|
|
|
|
syncRequest = [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]];
|
|
|
|
RETAIN(syncRequest);
|
|
|
|
|
|
|
|
[[o properties] setObject: syncRequest forKey: @"SyncRequest"];
|
|
|
|
|
|
|
|
for (i = 0; i < [collections count]; i++)
|
|
|
|
{
|
2016-07-25 12:17:17 +02:00
|
|
|
if ([[collections objectAtIndex: i] isKindOfClass: [NSString class]])
|
|
|
|
collectionId = [collections objectAtIndex: i];
|
|
|
|
else
|
|
|
|
collectionId = [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL];
|
|
|
|
|
|
|
|
key = [NSString stringWithFormat: @"SyncRequest+%@", collectionId];
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
[[o properties] setObject: processIdentifier forKey: key];
|
|
|
|
}
|
|
|
|
}
|
2015-10-14 15:21:32 +02:00
|
|
|
else
|
|
|
|
{
|
2015-10-20 14:48:39 +02:00
|
|
|
[[o properties] removeObjectForKey: @"SyncRequest"];
|
|
|
|
for (i = 0; i < [collections count]; i++)
|
|
|
|
{
|
2016-07-25 12:17:17 +02:00
|
|
|
if ([[collections objectAtIndex: i] isKindOfClass: [NSString class]])
|
|
|
|
collectionId = [collections objectAtIndex: i];
|
|
|
|
else
|
|
|
|
collectionId = [[[(id)[[collections objectAtIndex: i] getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL];
|
|
|
|
|
|
|
|
key = [NSString stringWithFormat: @"SyncRequest+%@", collectionId];
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
[[o properties] removeObjectForKey: key];
|
|
|
|
}
|
2015-10-14 15:21:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
[o save];
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
- (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata
|
|
|
|
forKey: (NSString *) theFolderKey
|
|
|
|
{
|
2015-10-20 14:48:39 +02:00
|
|
|
NSNumber *processIdentifier, *processIdentifierInCache;
|
2014-05-15 21:03:24 +02:00
|
|
|
SOGoCacheGCSObject *o;
|
2014-12-04 17:27:10 +01:00
|
|
|
NSDictionary *values;
|
2016-07-21 20:06:11 +02:00
|
|
|
NSString *key, *pkey;
|
|
|
|
NSArray *a;
|
2014-05-15 21:03:24 +02:00
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
if ([theFolderKey hasPrefix: @"folder"])
|
|
|
|
key = [NSString stringWithFormat: @"SyncRequest+mail/%@", [theFolderKey substringFromIndex: 6]];
|
|
|
|
else
|
|
|
|
key = [NSString stringWithFormat: @"SyncRequest+%@", theFolderKey];
|
|
|
|
|
|
|
|
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
|
|
|
processIdentifierInCache = [[self globalMetadataForDevice] objectForKey: key];
|
|
|
|
|
|
|
|
// Don't update the cache if another request is processing the same collection.
|
2018-06-04 20:47:55 +02:00
|
|
|
// In case of a merged folder we have to check personal folder's lock.
|
2016-07-21 20:06:11 +02:00
|
|
|
a = [key componentsSeparatedByString: @"/"];
|
|
|
|
pkey = [NSString stringWithFormat: @"%@/personal", [a objectAtIndex:0]];
|
|
|
|
|
|
|
|
if (!([processIdentifierInCache isEqual: processIdentifier] || [[[self globalMetadataForDevice] objectForKey: pkey] isEqual: processIdentifier]))
|
2015-10-20 14:48:39 +02:00
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - We lost our lock - discard folder cache update %@ %@ <> %@", key, processIdentifierInCache, processIdentifier];
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
2014-12-04 17:27:10 +01:00
|
|
|
values = [theFolderMetadata copy];
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
2014-12-04 17:27:10 +01:00
|
|
|
//[o reloadIfNeeded];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
[[o properties] removeObjectForKey: @"SyncKey"];
|
2014-05-27 20:44:57 +02:00
|
|
|
[[o properties] removeObjectForKey: @"SyncCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"DateCache"];
|
2016-07-21 20:06:11 +02:00
|
|
|
[[o properties] removeObjectForKey: @"UidCache"];
|
2014-06-25 21:05:25 +02:00
|
|
|
[[o properties] removeObjectForKey: @"MoreAvailable"];
|
2016-07-21 20:06:11 +02:00
|
|
|
[[o properties] removeObjectForKey: @"FolderPermissions"];
|
2015-03-30 15:49:44 +02:00
|
|
|
[[o properties] removeObjectForKey: @"BodyPreferenceType"];
|
2015-10-26 15:15:35 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SupportedElements"];
|
2014-12-08 16:25:37 +01:00
|
|
|
[[o properties] removeObjectForKey: @"SuccessfulMoveItemsOps"];
|
2015-09-09 16:20:31 +02:00
|
|
|
[[o properties] removeObjectForKey: @"InitialLoadSequence"];
|
2016-03-28 14:43:56 +02:00
|
|
|
[[o properties] removeObjectForKey: @"FirstIdInCache"];
|
|
|
|
[[o properties] removeObjectForKey: @"LastIdInCache"];
|
2016-07-21 20:06:11 +02:00
|
|
|
[[o properties] removeObjectForKey: @"MergedFoldersSyncKeys"];
|
|
|
|
[[o properties] removeObjectForKey: @"MergedFolder"];
|
|
|
|
[[o properties] removeObjectForKey: @"CleanoutDate"];
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
[[o properties] addEntriesFromDictionary: values];
|
2014-05-15 21:03:24 +02:00
|
|
|
[o save];
|
2014-12-04 17:27:10 +01:00
|
|
|
[values release];
|
2014-05-15 21:03:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSMutableDictionary *) _folderMetadataForKey: (NSString *) theFolderKey
|
|
|
|
{
|
|
|
|
SOGoCacheGCSObject *o;
|
|
|
|
NSString *key;
|
|
|
|
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], theFolderKey];
|
|
|
|
|
|
|
|
o = [SOGoCacheGCSObject objectWithName: key inContainer: nil];
|
|
|
|
[o setObjectType: ActiveSyncFolderCacheObject];
|
|
|
|
[o setTableUrl: [self folderTableURL]];
|
|
|
|
[o reloadIfNeeded];
|
|
|
|
|
|
|
|
return [o properties];
|
|
|
|
}
|
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
- (NSString *) _getNameInCache: (id) theCollection withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
|
|
|
{
|
|
|
|
NSString *nameInCache;
|
|
|
|
|
|
|
|
if (theFolderType == ActiveSyncMailFolder)
|
2015-09-09 16:12:32 +02:00
|
|
|
nameInCache = [imapFolderGUIDS objectForKey: [theCollection nameInContainer]];
|
2014-11-14 15:13:14 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
NSString *component_name;
|
|
|
|
if (theFolderType == ActiveSyncContactFolder)
|
|
|
|
component_name = @"vcard";
|
|
|
|
else if (theFolderType == ActiveSyncEventFolder)
|
|
|
|
component_name = @"vevent";
|
|
|
|
else
|
|
|
|
component_name = @"vtodo";
|
|
|
|
|
2015-09-09 16:12:32 +02:00
|
|
|
nameInCache = [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]];
|
2014-11-14 15:13:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nameInCache;
|
|
|
|
}
|
|
|
|
|
2017-04-11 16:13:56 +02:00
|
|
|
- (void) _removeAllAlarmsFromCalendar: (iCalCalendar *) theCalendar
|
|
|
|
{
|
|
|
|
NSArray *allComponents;
|
|
|
|
iCalEntityObject *currentComponent;
|
|
|
|
NSUInteger count, max;
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Remove all alarms"];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2017-04-11 16:13:56 +02:00
|
|
|
allComponents = [theCalendar allObjects];
|
|
|
|
|
|
|
|
max = [allComponents count];
|
|
|
|
for (count = 0; count < max; count++)
|
|
|
|
{
|
|
|
|
currentComponent = [allComponents objectAtIndex: count];
|
|
|
|
if ([currentComponent isKindOfClass: [iCalEvent class]] ||
|
|
|
|
[currentComponent isKindOfClass: [iCalToDo class]])
|
|
|
|
[currentComponent removeAllAlarms];
|
|
|
|
}
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Sync xmlns="AirSync:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey>1388757902</SyncKey>
|
|
|
|
// <CollectionId>vcard/personal</CollectionId>
|
|
|
|
// <GetChanges/>
|
|
|
|
// <WindowSize>25</WindowSize>
|
|
|
|
// <Options>
|
|
|
|
// <BodyPreference xmlns="AirSyncBase:">
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <TruncationSize>32768</TruncationSize>
|
|
|
|
// </BodyPreference>
|
|
|
|
// </Options>
|
|
|
|
// <Commands>
|
|
|
|
// <Add>
|
|
|
|
// <ClientId>16</ClientId>
|
|
|
|
// <ApplicationData>
|
|
|
|
// <Body xmlns="AirSyncBase:">
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <Data/>
|
|
|
|
// </Body>
|
|
|
|
// <CompanyName xmlns="Contacts:">Goo Inc.</CompanyName>
|
|
|
|
// <Email1Address xmlns="Contacts:">annie@broccoli.com</Email1Address>
|
|
|
|
// <FileAs xmlns="Contacts:">Broccoli, Annie</FileAs>
|
|
|
|
// <FirstName xmlns="Contacts:">Annie</FirstName>
|
|
|
|
// <LastName xmlns="Contacts:">Broccoli</LastName>
|
|
|
|
// <Picture xmlns="Contacts:"/>
|
|
|
|
// </ApplicationData>
|
|
|
|
// </Add>
|
|
|
|
// </Commands>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </Sync>
|
|
|
|
//
|
|
|
|
- (void) processSyncAddCommand: (id <DOMElement>) theDocumentElement
|
|
|
|
inCollection: (id) theCollection
|
|
|
|
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: (NSMutableArray *) objectsToTouch
|
2014-01-10 20:12:53 +01:00
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
|
|
|
{
|
2016-07-21 20:06:11 +02:00
|
|
|
NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache, *allValues;
|
|
|
|
NSString *clientId, *serverId, *easId;
|
2016-11-21 16:45:27 +01:00
|
|
|
NSArray *additions, *roles;
|
2014-01-10 22:48:39 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
id anAddition, sogoObject, o;
|
2014-02-11 02:16:43 +01:00
|
|
|
BOOL is_new;
|
2014-01-10 20:12:53 +01:00
|
|
|
int i;
|
|
|
|
|
|
|
|
additions = (id)[theDocumentElement getElementsByTagName: @"Add"];
|
2017-01-23 14:53:41 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
if ([additions count])
|
|
|
|
{
|
|
|
|
for (i = 0; i < [additions count]; i++)
|
|
|
|
{
|
|
|
|
anAddition = [additions objectAtIndex: i];
|
2014-02-11 02:16:43 +01:00
|
|
|
is_new = YES;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2018-02-20 17:15:46 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
FIXME
|
|
|
|
<Add>
|
|
|
|
<ClientId>338</ClientId>
|
|
|
|
<ApplicationData>
|
|
|
|
<To xmlns="Email:"><foot@bar.com></To>
|
|
|
|
<Cc xmlns="Email:"/>
|
|
|
|
<Subject xmlns="Email:">test</Subject>
|
|
|
|
<Reply-To xmlns="Email:">foo@bar.com</Reply-To>
|
|
|
|
<Importance xmlns="Email:">1</Importance>
|
|
|
|
<Read xmlns="Email:">1</Read>
|
|
|
|
<Attachments xmlns="AirSyncBase:">
|
|
|
|
<Add>
|
|
|
|
<ClientId>152-ab557915-8451-49a7-a9c6-a9ac153021ad</ClientId>
|
|
|
|
|
|
|
|
|
|
|
|
-> lastObject returns the ClientId in Attachments element -> try with objectAtIndex: 0 -> is this correct?
|
|
|
|
*/
|
|
|
|
//clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] lastObject] textValue];
|
|
|
|
clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] objectAtIndex: 0] textValue];
|
|
|
|
|
2014-01-10 22:48:39 +01:00
|
|
|
allValues = [NSMutableDictionary dictionaryWithDictionary: [[(id)[anAddition getElementsByTagName: @"ApplicationData"] lastObject] applicationData]];
|
2018-02-20 17:15:46 +01:00
|
|
|
|
|
|
|
// FIXME: ignore the <Add> elements of Attachemnts - above (id)[theDocumentElement getElementsByTagName: @"Add"]; return any <Add> elements instead of only the direct childs of the <commands> element ..
|
|
|
|
if (![allValues count])
|
|
|
|
continue;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
switch (theFolderType)
|
|
|
|
{
|
|
|
|
case ActiveSyncContactFolder:
|
|
|
|
{
|
|
|
|
serverId = [NSString stringWithFormat: @"%@.vcf", [theCollection globallyUniqueObjectId]];
|
|
|
|
sogoObject = [[SOGoContactGCSEntry alloc] initWithName: serverId
|
|
|
|
inContainer: theCollection];
|
2016-11-21 16:45:27 +01:00
|
|
|
[sogoObject autorelease];
|
2014-01-10 20:12:53 +01:00
|
|
|
o = [sogoObject vCard];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncEventFolder:
|
|
|
|
{
|
2014-02-11 02:16:43 +01:00
|
|
|
// Before adding a new appointment, we check if one is already present with the same UID. If that's
|
|
|
|
// the case, let's just update it. This can happen if for example, an iOS based device receives the
|
|
|
|
// invitation email and choses "Add to calendar" BEFORE actually syncing the calendar. That would
|
|
|
|
// create a duplicate on the server.
|
2017-06-01 22:07:34 +02:00
|
|
|
if ([allValues objectForKey: ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 16.0) ? @"ClientUid" : @"UID"])
|
|
|
|
serverId = [allValues objectForKey: ([[context objectForKey: @"ASProtocolVersion"] floatValue] >= 16.0) ? @"ClientUid" : @"UID"];
|
2014-02-11 02:16:43 +01:00
|
|
|
else
|
2014-02-17 14:46:05 +01:00
|
|
|
serverId = [theCollection globallyUniqueObjectId];
|
2014-02-17 16:01:44 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
2014-02-11 02:16:43 +01:00
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
// If object isn't found, we 'create' a new one
|
|
|
|
if ([sogoObject isKindOfClass: [NSException class]])
|
|
|
|
{
|
2014-02-17 14:46:05 +01:00
|
|
|
sogoObject = [[SOGoAppointmentObject alloc] initWithName: [serverId sanitizedServerIdWithType: theFolderType]
|
2014-02-11 02:16:43 +01:00
|
|
|
inContainer: theCollection];
|
2016-11-21 16:45:27 +01:00
|
|
|
[sogoObject autorelease];
|
2014-02-11 02:16:43 +01:00
|
|
|
o = [sogoObject component: YES secure: NO];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
o = [sogoObject component: NO secure: NO];
|
|
|
|
is_new = NO;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncTaskFolder:
|
|
|
|
{
|
|
|
|
serverId = [NSString stringWithFormat: @"%@.ics", [theCollection globallyUniqueObjectId]];
|
|
|
|
sogoObject = [[SOGoTaskObject alloc] initWithName: serverId
|
|
|
|
inContainer: theCollection];
|
2016-11-21 16:45:27 +01:00
|
|
|
[sogoObject autorelease];
|
2015-07-22 15:46:06 +02:00
|
|
|
o = [sogoObject component: YES secure: NO];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncMailFolder:
|
|
|
|
default:
|
|
|
|
{
|
2018-02-20 17:15:46 +01:00
|
|
|
// Support Draft Mail/SMS to Exchange eMail sync.
|
2017-06-01 22:07:34 +02:00
|
|
|
NSString *serverId;
|
2018-02-20 17:15:46 +01:00
|
|
|
NSMutableString *s;
|
|
|
|
NSDictionary *result;
|
|
|
|
NSNumber *modseq;
|
|
|
|
|
|
|
|
serverId = nil;
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
2017-06-01 22:07:34 +02:00
|
|
|
sogoObject = [SOGoMailObject objectWithName: @"Mail" inContainer: theCollection];
|
2018-02-20 17:15:46 +01:00
|
|
|
serverId = [sogoObject storeMail: allValues inBuffer: s inContext: context];
|
2017-06-01 22:07:34 +02:00
|
|
|
if (serverId)
|
|
|
|
{
|
2018-02-20 17:15:46 +01:00
|
|
|
sogoObject = [theCollection lookupName: serverId inContext: context acquire: 0];
|
|
|
|
[sogoObject takeActiveSyncValues: allValues inContext: context];
|
|
|
|
|
2017-06-01 22:07:34 +02:00
|
|
|
// 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];
|
2018-02-20 17:15:46 +01:00
|
|
|
[theBuffer appendString: s];
|
2017-06-01 22:07:34 +02:00
|
|
|
[theBuffer appendString: @"</Add>"];
|
|
|
|
|
2018-02-20 17:15:46 +01:00
|
|
|
|
2017-06-01 22:07:34 +02:00
|
|
|
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
|
|
|
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
2018-02-20 17:15:46 +01:00
|
|
|
|
|
|
|
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
|
|
|
|
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
|
|
|
|
|
|
|
[syncCache setObject: [modseq stringValue] forKey: serverId];
|
|
|
|
|
2017-06-01 22:07:34 +02:00
|
|
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// FIXME - what to do?
|
|
|
|
[self errorWithFormat: @"Fatal error occured - tried to call -processSyncAddCommand: ... on a mail folder. We abort."];
|
|
|
|
abort();
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
2016-11-21 16:45:27 +01:00
|
|
|
|
|
|
|
roles = [theCollection aclsForUser: [[context activeUser] login]];
|
|
|
|
|
|
|
|
if (![roles containsObject: SOGoRole_ObjectCreator] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
|
|
|
{
|
|
|
|
// This will trigger a delete-command to remove the component without proper permission.
|
|
|
|
// FIXME: ultimately, we need to find a better way to create a phantom object in the user's calendar
|
|
|
|
// and delete it to keep the transactional process in shape. We could also create a deleted one right way
|
|
|
|
// We strip any attendees from the values as we don't want to send bogus invitation emails
|
|
|
|
[allValues removeObjectForKey: @"Attendees"];
|
|
|
|
[o takeActiveSyncValues: allValues inContext: context];
|
|
|
|
[sogoObject setIsNew: YES];
|
|
|
|
[sogoObject saveComponent: o];
|
|
|
|
[sogoObject delete];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[o takeActiveSyncValues: allValues inContext: context];
|
|
|
|
[sogoObject setIsNew: is_new];
|
2016-12-21 16:47:33 +01:00
|
|
|
|
|
|
|
if (theFolderType == ActiveSyncEventFolder)
|
|
|
|
[sogoObject saveComponent: o force: YES];
|
|
|
|
else
|
|
|
|
[sogoObject saveComponent: o];
|
|
|
|
|
2016-11-21 16:45:27 +01:00
|
|
|
}
|
|
|
|
|
2017-01-23 14:53:41 +01:00
|
|
|
if ([sogoObject isKindOfClass: [SOGoAppointmentObject class]] && [sogoObject resourceHasAutoAccepted])
|
|
|
|
[objectsToTouch addObject: sogoObject];
|
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
// Update syncCache
|
|
|
|
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
|
|
|
|
|
|
|
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
|
|
|
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
2016-07-21 20:06:11 +02:00
|
|
|
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;
|
2015-07-22 15:46:06 +02:00
|
|
|
|
|
|
|
[syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
2014-11-14 15:13:14 +01:00
|
|
|
[dateCache setObject: [NSCalendarDate date] forKey: serverId];
|
2015-07-22 15:46:06 +02:00
|
|
|
|
2016-11-21 16:45:27 +01:00
|
|
|
// make sure to pickup the delete immediately if we don't have proper permission to add
|
|
|
|
if (![roles containsObject: SOGoRole_ObjectCreator] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
|
|
|
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
2016-07-21 20:06:11 +02:00
|
|
|
|
|
|
|
// 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>"];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Sync xmlns="AirSync:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey>1387546048</SyncKey>
|
|
|
|
// <CollectionId>vtodo/personal</CollectionId>
|
|
|
|
// <GetChanges/>
|
|
|
|
// <WindowSize>25</WindowSize>
|
|
|
|
// <Options>
|
|
|
|
// <BodyPreference xmlns="AirSyncBase:">
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <TruncationSize>32768</TruncationSize>
|
|
|
|
// </BodyPreference>
|
|
|
|
// </Options>
|
|
|
|
// <Commands>
|
|
|
|
// <Change>
|
|
|
|
// <ServerId>36C5-52B36280-1-27B38F40.ics</ServerId>
|
|
|
|
// <ApplicationData>
|
|
|
|
// <Body xmlns="AirSyncBase:">
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <Data/>
|
|
|
|
// </Body>
|
|
|
|
// <Subject xmlns="Tasks:">foobar1</Subject>
|
|
|
|
// <Importance xmlns="Tasks:">1</Importance>
|
|
|
|
// <Complete xmlns="Tasks:">0</Complete>
|
|
|
|
// <Sensitivity xmlns="Tasks:">0</Sensitivity>
|
|
|
|
// <ReminderSet xmlns="Tasks:">0</ReminderSet>
|
|
|
|
// </ApplicationData>
|
|
|
|
// </Change>
|
|
|
|
// </Commands>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </Sync>
|
|
|
|
//
|
|
|
|
- (void) processSyncChangeCommand: (id <DOMElement>) theDocumentElement
|
|
|
|
inCollection: (id) theCollection
|
|
|
|
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: (NSMutableArray *) objectsToTouch
|
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
|
|
|
NSDictionary *allChanges;
|
2016-07-21 20:06:11 +02:00
|
|
|
NSString *serverId, *easId, *origServerId, *mergedFolder;
|
2016-11-21 16:45:27 +01:00
|
|
|
NSArray *changes, *a, *roles;
|
2014-01-10 20:12:53 +01:00
|
|
|
id aChange, o, sogoObject;
|
2016-07-21 20:06:11 +02:00
|
|
|
NSMutableDictionary *folderMetadata, *syncCache, *uidCache;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
changes = (id)[theDocumentElement getElementsByTagName: @"Change"];
|
|
|
|
|
|
|
|
if ([changes count])
|
|
|
|
{
|
2015-07-22 15:46:06 +02:00
|
|
|
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
|
|
|
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
2016-07-21 20:06:11 +02:00
|
|
|
uidCache = [folderMetadata objectForKey: @"UidCache"];
|
2015-07-22 15:46:06 +02:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
for (i = 0; i < [changes count]; i++)
|
|
|
|
{
|
|
|
|
aChange = [changes objectAtIndex: i];
|
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
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
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: objectsToTouch
|
2016-07-21 20:06:11 +02:00
|
|
|
inBuffer: theBuffer];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Change - Process change for folder %@ easId %@", [theCollection nameInContainer],easId];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
allChanges = [[(id)[aChange getElementsByTagName: @"ApplicationData"] lastObject] applicationData];
|
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
if (uidCache && (serverId = [[uidCache allKeysForObject: easId] objectAtIndex: 0]))
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Found serverId: %@ for easId: %@", serverId, easId];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
serverId = easId;
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
// Fetch the object and apply the changes
|
2014-02-17 14:46:05 +01:00
|
|
|
sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
2014-01-10 20:12:53 +01:00
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
2014-01-13 22:24:15 +01:00
|
|
|
// Object was removed inbetween sync/commands?
|
|
|
|
if ([sogoObject isKindOfClass: [NSException class]])
|
|
|
|
{
|
2016-07-21 20:06:11 +02:00
|
|
|
[theBuffer appendString: @"<Change>"];
|
|
|
|
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
|
|
|
|
[theBuffer appendFormat: @"<Status>%d</Status>", 8];
|
|
|
|
[theBuffer appendString: @"</Change>"];
|
2014-01-13 22:24:15 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
switch (theFolderType)
|
|
|
|
{
|
|
|
|
case ActiveSyncContactFolder:
|
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
roles = [sogoObject aclsForUser:[[context activeUser] login]];
|
|
|
|
o = [sogoObject vCard];
|
|
|
|
|
|
|
|
// Don't update the component without proper permission
|
|
|
|
if (([roles containsObject: SOGoRole_ObjectEditor] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]))
|
|
|
|
{
|
|
|
|
[o takeActiveSyncValues: allChanges inContext: context];
|
|
|
|
[sogoObject saveComponent: o];
|
|
|
|
|
|
|
|
if ([syncCache objectForKey: serverId])
|
|
|
|
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
|
|
|
}
|
|
|
|
// Trigger a change-command to override client changes since we don't have permissions
|
|
|
|
else
|
|
|
|
[sogoObject touch];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncEventFolder:
|
|
|
|
case ActiveSyncTaskFolder:
|
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
roles = [sogoObject aclsForUser:[[context activeUser] login]];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
o = [sogoObject component: NO secure: NO];
|
2016-01-22 19:35:02 +01:00
|
|
|
|
|
|
|
if (theFolderType == ActiveSyncEventFolder &&
|
|
|
|
[(iCalEvent *)o userIsAttendee: [context activeUser]])
|
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
// Don't update the component without proper permission
|
|
|
|
if ([roles containsObject: SOGoCalendarRole_ComponentResponder] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
|
|
|
{
|
|
|
|
[o changeParticipationStatus: allChanges inContext: context component: sogoObject];
|
|
|
|
|
|
|
|
if ([syncCache objectForKey: serverId])
|
|
|
|
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
|
|
|
}
|
|
|
|
// Trigger a change-command to override client changes since we don't have permissions
|
|
|
|
else
|
|
|
|
[sogoObject touch];
|
2016-01-22 19:35:02 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
// Don't update the component without proper permission
|
|
|
|
if ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
|
|
|
{
|
|
|
|
[o takeActiveSyncValues: allChanges inContext: context];
|
2016-12-21 16:47:33 +01:00
|
|
|
|
|
|
|
if (theFolderType == ActiveSyncEventFolder)
|
2017-01-23 14:53:41 +01:00
|
|
|
{
|
|
|
|
[sogoObject saveComponent: o force: YES];
|
|
|
|
if ([sogoObject resourceHasAutoAccepted])
|
|
|
|
[objectsToTouch addObject: sogoObject];
|
|
|
|
}
|
2016-12-21 16:47:33 +01:00
|
|
|
else
|
|
|
|
[sogoObject saveComponent: o];
|
2016-11-21 16:45:27 +01:00
|
|
|
|
|
|
|
if ([syncCache objectForKey: serverId])
|
|
|
|
[syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId];
|
|
|
|
}
|
|
|
|
// Trigger a change-command to override client changes since we don't have permissions
|
|
|
|
else
|
|
|
|
[sogoObject touch];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncMailFolder:
|
|
|
|
default:
|
|
|
|
{
|
2015-07-22 15:46:06 +02:00
|
|
|
NSDictionary *result;
|
2015-10-14 15:21:32 +02:00
|
|
|
NSNumber *modseq;
|
2015-07-22 15:46:06 +02:00
|
|
|
|
2018-02-20 17:15:46 +01:00
|
|
|
// Process an update to a Draft Mail.
|
|
|
|
if ([allChanges objectForKey: @"Body"])
|
|
|
|
{
|
|
|
|
NSString *serverId;
|
|
|
|
NSMutableString *s;
|
|
|
|
|
|
|
|
serverId = nil;
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
serverId = [sogoObject storeMail: allChanges inBuffer: s inContext: context];
|
|
|
|
if (serverId)
|
|
|
|
{
|
|
|
|
// we delete the original email - next sync will update the client with the new mail
|
|
|
|
[sogoObject delete];
|
|
|
|
sogoObject = [theCollection lookupName: serverId inContext: context acquire: 0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-17 16:01:44 +01:00
|
|
|
[sogoObject takeActiveSyncValues: allChanges inContext: context];
|
2015-07-22 15:46:06 +02:00
|
|
|
|
|
|
|
result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]];
|
|
|
|
modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"];
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
if (modseq && [syncCache objectForKey: serverId])
|
2016-11-21 16:45:27 +01:00
|
|
|
[syncCache setObject: [modseq stringValue] forKey: serverId];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
2015-07-22 15:46:06 +02:00
|
|
|
|
2014-10-29 16:13:18 +01:00
|
|
|
[theBuffer appendString: @"<Change>"];
|
2016-07-21 20:06:11 +02:00
|
|
|
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
|
2018-02-20 17:15:46 +01:00
|
|
|
|
|
|
|
// A body element is sent only for draft mails - status 8 will delete the mail on the client - the next sync update fetch the new mail
|
|
|
|
if ([allChanges objectForKey: @"Body"] && theFolderType == ActiveSyncMailFolder)
|
|
|
|
[theBuffer appendFormat: @"<Status>%d</Status>", 8];
|
|
|
|
else
|
|
|
|
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
|
2014-10-29 16:13:18 +01:00
|
|
|
[theBuffer appendString: @"</Change>"];
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Sync xmlns="AirSync:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey>1388764784</SyncKey>
|
|
|
|
// <CollectionId>vtodo/personal</CollectionId>
|
|
|
|
// <GetChanges/>
|
|
|
|
// <WindowSize>25</WindowSize>
|
|
|
|
// <Options>
|
|
|
|
// <BodyPreference xmlns="AirSyncBase:">
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <TruncationSize>32768</TruncationSize>
|
|
|
|
// </BodyPreference>
|
|
|
|
// </Options>
|
|
|
|
// <Commands>
|
|
|
|
// <Delete>
|
|
|
|
// <ServerId>2CB5-52B36080-1-1C1D0240.ics</ServerId>
|
|
|
|
// </Delete>
|
|
|
|
// </Commands>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </Sync>
|
|
|
|
//
|
|
|
|
- (void) processSyncDeleteCommand: (id <DOMElement>) theDocumentElement
|
|
|
|
inCollection: (id) theCollection
|
|
|
|
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
|
|
|
{
|
2016-07-21 20:06:11 +02:00
|
|
|
NSString *serverId, *easId, *origServerId, *mergedFolder;
|
2016-11-21 16:45:27 +01:00
|
|
|
NSArray *deletions, *a, *roles;
|
|
|
|
id aDelete, sogoObject, value;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-06-22 15:19:54 +02:00
|
|
|
BOOL deletesAsMoves, useTrash;
|
2014-01-10 20:12:53 +01:00
|
|
|
int i;
|
|
|
|
|
|
|
|
deletions = (id)[theDocumentElement getElementsByTagName: @"Delete"];
|
|
|
|
|
|
|
|
if ([deletions count])
|
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
NSMutableDictionary *folderMetadata, *uidCache;
|
2016-07-21 20:06:11 +02:00
|
|
|
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
|
|
|
|
|
|
|
uidCache = [folderMetadata objectForKey: @"UidCache"];
|
|
|
|
|
2015-06-22 15:19:54 +02:00
|
|
|
// 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"];
|
|
|
|
deletesAsMoves = YES;
|
|
|
|
useTrash = YES;
|
|
|
|
|
|
|
|
if ([value count] && [[[value lastObject] textValue] length])
|
|
|
|
deletesAsMoves = [[[value lastObject] textValue] boolValue];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
for (i = 0; i < [deletions count]; i++)
|
|
|
|
{
|
|
|
|
aDelete = [deletions objectAtIndex: i];
|
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
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];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
if (uidCache && (serverId = [[uidCache allKeysForObject: easId] objectAtIndex: 0]))
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Found serverId: %@ for easId: %@", serverId, easId];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
serverId = easId;
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
sogoObject = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
2014-01-10 20:12:53 +01:00
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
2014-03-11 18:21:05 +01:00
|
|
|
if (![sogoObject isKindOfClass: [NSException class]])
|
2015-06-29 19:49:41 +02:00
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
// Remove the cache entry. If the delete fails due to permission issues we do a fake update to trigger
|
|
|
|
// an add-command.
|
|
|
|
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]];
|
|
|
|
|
2015-06-29 19:49:41 +02:00
|
|
|
// FIXME: handle errors here
|
|
|
|
if (deletesAsMoves && theFolderType == ActiveSyncMailFolder)
|
2016-11-21 16:45:27 +01:00
|
|
|
{
|
2018-03-14 18:21:49 +01:00
|
|
|
[(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil]
|
|
|
|
useTrashFolder: &useTrash
|
|
|
|
inContext: context];
|
2016-11-21 16:45:27 +01:00
|
|
|
}
|
|
|
|
else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder || theFolderType == ActiveSyncContactFolder)
|
2016-01-14 20:49:22 +01:00
|
|
|
{
|
2016-11-21 16:45:27 +01:00
|
|
|
roles = [theCollection aclsForUser:[[context activeUser] login]];
|
|
|
|
|
|
|
|
// We check ACLs on the collection and not on the SOGo object itself, as the add/delete rights are on the collection itself
|
2016-11-24 21:49:10 +01:00
|
|
|
if (![roles containsObject: SOGoRole_ObjectEraser] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
2016-11-21 16:45:27 +01:00
|
|
|
{
|
|
|
|
// This will trigger an add-command to re-add the component to the client
|
|
|
|
[sogoObject touch];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!(theFolderType == ActiveSyncContactFolder))
|
|
|
|
[sogoObject prepareDelete];
|
|
|
|
[sogoObject delete];
|
|
|
|
}
|
2016-01-14 20:49:22 +01:00
|
|
|
}
|
2015-06-29 19:49:41 +02:00
|
|
|
else
|
|
|
|
[sogoObject delete];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
[theBuffer appendString: @"<Delete>"];
|
|
|
|
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", origServerId];
|
|
|
|
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[theBuffer appendString: @"</Delete>"];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// <Fetch>
|
|
|
|
// <ServerId>91</ServerId>
|
|
|
|
// </Fetch>
|
|
|
|
//
|
|
|
|
- (void) processSyncFetchCommand: (id <DOMElement>) theDocumentElement
|
|
|
|
inCollection: (id) theCollection
|
|
|
|
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
|
|
|
{
|
|
|
|
NSString *serverId;
|
|
|
|
id o;
|
|
|
|
|
|
|
|
serverId = [[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue];
|
|
|
|
|
2016-03-09 14:49:17 +01:00
|
|
|
// https://msdn.microsoft.com/en-us/library/gg675490%28v=exchg.80%29.aspx
|
|
|
|
// The fetch element is used to request the application data of an item that was truncated in a synchronization response from the server.
|
|
|
|
// The complete item is then returned to the client in a server response.
|
|
|
|
[context setObject: @"8" forKey: @"MIMETruncation"];
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
o = [theCollection lookupName: [serverId sanitizedServerIdWithType: theFolderType]
|
2014-01-10 20:12:53 +01:00
|
|
|
inContext: context
|
|
|
|
acquire: NO];
|
|
|
|
|
|
|
|
// FIXME - error handling
|
|
|
|
[theBuffer appendString: @"<Fetch>"];
|
|
|
|
[theBuffer appendFormat: @"<ServerId>%@</ServerId>", serverId];
|
|
|
|
[theBuffer appendFormat: @"<Status>%d</Status>", 1];
|
|
|
|
[theBuffer appendString: @"<ApplicationData>"];
|
2014-02-17 16:01:44 +01:00
|
|
|
[theBuffer appendString: [o activeSyncRepresentationInContext: context]];
|
2014-01-10 20:12:53 +01:00
|
|
|
[theBuffer appendString: @"</ApplicationData>"];
|
|
|
|
[theBuffer appendString: @"</Fetch>"];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// The method handles <GetChanges/>
|
|
|
|
//
|
|
|
|
- (void) processSyncGetChanges: (id <DOMElement>) theDocumentElement
|
|
|
|
inCollection: (id) theCollection
|
2014-02-17 14:46:05 +01:00
|
|
|
withWindowSize: (unsigned int) theWindowSize
|
2014-12-22 17:50:51 +01:00
|
|
|
withMaxSyncResponseSize: (unsigned int) theMaxSyncResponseSize
|
2014-01-10 20:12:53 +01:00
|
|
|
withSyncKey: (NSString *) theSyncKey
|
2014-01-16 21:13:09 +01:00
|
|
|
withFolderType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
2014-01-20 16:13:16 +01:00
|
|
|
withFilterType: (NSCalendarDate *) theFilterType
|
2014-01-10 20:12:53 +01:00
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
2014-02-17 14:46:05 +01:00
|
|
|
lastServerKey: (NSString **) theLastServerKey
|
2015-10-20 14:48:39 +02:00
|
|
|
defaultInterval: (unsigned int) theDefaultInterval
|
2016-07-21 20:06:11 +02:00
|
|
|
mergeFolders: (BOOL) theMergeFolder
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2016-07-21 20:06:11 +02:00
|
|
|
NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache;
|
2015-03-23 22:23:29 +01:00
|
|
|
NSString *davCollectionTagToStore;
|
2014-12-04 17:27:10 +01:00
|
|
|
NSAutoreleasePool *pool;
|
2014-01-22 17:02:12 +01:00
|
|
|
NSMutableString *s;
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
BOOL cleanup_needed, more_available;
|
2014-02-17 14:46:05 +01:00
|
|
|
int i, max;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-06-25 21:05:25 +02:00
|
|
|
s = [NSMutableString string];
|
2015-10-14 15:21:32 +02:00
|
|
|
cleanup_needed = more_available = NO;
|
2014-06-25 21:05:25 +02:00
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
// If this is a new sync operation, DateCache and SyncCache need to be deleted
|
2014-11-14 15:13:14 +01:00
|
|
|
if ([theSyncKey isEqualToString: @"-1"])
|
|
|
|
{
|
|
|
|
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"];
|
|
|
|
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"];
|
2016-07-21 20:06:11 +02:00
|
|
|
if (theFolderType != ActiveSyncMailFolder)
|
|
|
|
{
|
|
|
|
[folderMetadata setObject: [NSCalendarDate date] forKey: @"CleanoutDate"];
|
|
|
|
[folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"UidCache"];
|
|
|
|
}
|
2014-11-14 15:13:14 +01:00
|
|
|
}
|
2015-10-14 15:21:32 +02:00
|
|
|
else if ([folderMetadata objectForKey: @"SyncKey"] && !([theSyncKey isEqualToString: [folderMetadata objectForKey: @"SyncKey"]]))
|
|
|
|
{
|
|
|
|
// The syncKey received from the client doesn't match the syncKey we have in cache - client might have missed a response.
|
|
|
|
// We need to cleanup this mess.
|
2015-10-20 14:48:39 +02:00
|
|
|
[self logWithFormat: @"Cache cleanup needed for device %@ - user: %@ syncKey: %@ cache: %@", [context objectForKey: @"DeviceId"], [[context activeUser] login], theSyncKey, [folderMetadata objectForKey: @"SyncKey"]];
|
2015-10-14 15:21:32 +02:00
|
|
|
cleanup_needed = YES;
|
|
|
|
}
|
2014-11-14 15:13:14 +01:00
|
|
|
|
|
|
|
syncCache = [folderMetadata objectForKey: @"SyncCache"];
|
|
|
|
dateCache = [folderMetadata objectForKey: @"DateCache"];
|
2016-07-21 20:06:11 +02:00
|
|
|
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]];
|
|
|
|
}
|
2014-11-14 15:13:14 +01:00
|
|
|
|
|
|
|
if ((theFolderType == ActiveSyncMailFolder || theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) &&
|
2015-10-14 15:21:32 +02:00
|
|
|
(cleanup_needed ||
|
|
|
|
( !([folderMetadata objectForKey: @"MoreAvailable"]) && // previous sync operation reached the windowSize or maximumSyncReponseSize
|
|
|
|
!([folderMetadata objectForKey: @"InitialLoadSequence"]))) &&
|
|
|
|
theFilterType
|
|
|
|
)
|
2014-06-25 21:05:25 +02:00
|
|
|
{
|
|
|
|
NSArray *allKeys;
|
|
|
|
NSString *key;
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2014-06-25 21:05:25 +02:00
|
|
|
int softdelete_count;
|
|
|
|
|
|
|
|
softdelete_count = 0;
|
|
|
|
|
|
|
|
allKeys = [dateCache allKeys];
|
|
|
|
for (i = 0; i < [allKeys count]; i++)
|
|
|
|
{
|
|
|
|
key = [allKeys objectAtIndex: i];
|
|
|
|
|
|
|
|
if ([[dateCache objectForKey:key] compare: theFilterType] == NSOrderedAscending)
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
if ([syncCache objectForKey:key])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - SoftDelete %@", key];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
[s appendString: @"<SoftDelete xmlns=\"AirSync:\">"];
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", key];
|
|
|
|
[s appendString: @"</SoftDelete>"];
|
|
|
|
|
|
|
|
[syncCache removeObjectForKey: key];
|
|
|
|
//[dateCache removeObjectForKey: key];
|
2014-06-25 21:05:25 +02:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
softdelete_count++;
|
|
|
|
}
|
|
|
|
else if (cleanup_needed)
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - SoftDelete cleanup %@", key];
|
|
|
|
|
|
|
|
// With this we make sure that a SoftDelete is set again on next sync.
|
|
|
|
[syncCache setObject: @"0" forKey: key];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - SoftDelete final delete %@", key];
|
|
|
|
|
|
|
|
// Now we are save to remove the dateCache entry.
|
|
|
|
[dateCache removeObjectForKey: key];
|
2016-07-21 20:06:11 +02:00
|
|
|
//[uidCache removeObjectForKey: key];
|
2015-10-14 15:21:32 +02:00
|
|
|
}
|
2014-06-25 21:05:25 +02:00
|
|
|
}
|
|
|
|
|
2014-12-22 17:50:51 +01:00
|
|
|
if (softdelete_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
2014-06-25 21:05:25 +02:00
|
|
|
{
|
|
|
|
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
2014-11-14 15:13:14 +01:00
|
|
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
2014-06-25 21:05:25 +02:00
|
|
|
|
|
|
|
more_available = YES;
|
|
|
|
*theLastServerKey = theSyncKey;
|
|
|
|
|
|
|
|
// Since WindowSize is reached don't even try to add more to the response, let's just
|
|
|
|
// jump to the end and return the response immediately
|
|
|
|
goto return_response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
2014-11-14 15:13:14 +01:00
|
|
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
2014-06-25 21:05:25 +02:00
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
//
|
|
|
|
// No changes in the collection - 2.2.2.19.1.1 Empty Sync Request.
|
|
|
|
// We check this and we don't generate any commands if we don't have to.
|
|
|
|
//
|
2016-03-28 14:43:56 +02:00
|
|
|
if ([theSyncKey isEqualToString: [theCollection davCollectionTag]] && !([s length]) && ![context objectForKey: @"FilterTypeChanged"] && ![folderMetadata objectForKey: @"InitialLoadSequence"])
|
2014-01-10 20:12:53 +01:00
|
|
|
return;
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2015-03-23 22:23:29 +01:00
|
|
|
davCollectionTagToStore = [theCollection davCollectionTag];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
switch (theFolderType)
|
|
|
|
{
|
2014-01-20 16:13:16 +01:00
|
|
|
// Handle all the GCS components
|
2014-01-10 20:12:53 +01:00
|
|
|
case ActiveSyncContactFolder:
|
|
|
|
case ActiveSyncEventFolder:
|
|
|
|
case ActiveSyncTaskFolder:
|
|
|
|
{
|
2014-01-20 16:13:16 +01:00
|
|
|
id sogoObject, componentObject;
|
2016-07-21 20:06:11 +02:00
|
|
|
NSString *uid, *easId, *component_name;
|
2014-01-20 16:13:16 +01:00
|
|
|
NSDictionary *component;
|
|
|
|
NSArray *allComponents;
|
|
|
|
|
2015-09-09 16:20:31 +02:00
|
|
|
BOOL updated, initialLoadInProgress;
|
2014-11-14 15:13:14 +01:00
|
|
|
int deleted, return_count;
|
2014-01-20 16:13:16 +01:00
|
|
|
|
|
|
|
if (theFolderType == ActiveSyncContactFolder)
|
|
|
|
component_name = @"vcard";
|
|
|
|
else if (theFolderType == ActiveSyncEventFolder)
|
|
|
|
component_name = @"vevent";
|
|
|
|
else
|
|
|
|
component_name = @"vtodo";
|
|
|
|
|
2015-09-09 16:20:31 +02:00
|
|
|
initialLoadInProgress = NO;
|
|
|
|
|
|
|
|
if ([theSyncKey isEqualToString: @"-1"])
|
2015-10-14 15:21:32 +02:00
|
|
|
[folderMetadata setObject: davCollectionTagToStore forKey: @"InitialLoadSequence"];
|
2016-03-28 14:43:56 +02:00
|
|
|
else if ([context objectForKey: @"FilterTypeChanged"])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - FilterTypeChanged!"];
|
|
|
|
|
|
|
|
theSyncKey = @"-1";
|
|
|
|
}
|
2015-09-09 16:20:31 +02:00
|
|
|
|
|
|
|
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
|
|
|
{
|
|
|
|
if ([theSyncKey intValue] < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
|
|
|
initialLoadInProgress = YES;
|
|
|
|
else
|
|
|
|
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
|
|
|
}
|
|
|
|
|
2015-09-17 21:58:38 +02:00
|
|
|
allComponents = [theCollection syncTokenFieldsWithProperties: nil
|
|
|
|
matchingSyncToken: theSyncKey
|
|
|
|
fromDate: theFilterType
|
|
|
|
initialLoad: initialLoadInProgress];
|
2015-09-17 22:02:21 +02:00
|
|
|
allComponents = [allComponents sortedArrayUsingDescriptors: [NSArray arrayWithObject: [[[NSSortDescriptor alloc] initWithKey: @"c_lastmodified" ascending: YES] autorelease]]];
|
2014-12-04 17:27:10 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
// Check for the WindowSize
|
|
|
|
max = [allComponents count];
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
//
|
|
|
|
// Cleanup the mess
|
|
|
|
//
|
|
|
|
if (cleanup_needed)
|
|
|
|
{
|
|
|
|
|
|
|
|
for (i = 0; i < max; i++)
|
|
|
|
{
|
|
|
|
component = [allComponents objectAtIndex: i];
|
|
|
|
deleted = [[component objectForKey: @"c_deleted"] intValue];
|
|
|
|
|
|
|
|
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
|
|
|
|
|
|
|
|
if (deleted)
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", uid];
|
|
|
|
|
|
|
|
// For deletes we have to recreate a cache entry to make sure the delete is sent again.
|
|
|
|
[syncCache setObject: @"0" forKey: uid];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ([syncCache objectForKey: uid] && [[component objectForKey: @"c_creationdate"] intValue] > [theSyncKey intValue])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: ADD %@", uid];
|
|
|
|
|
|
|
|
// Cleanup the cache to make sure the add is sent again.
|
|
|
|
[syncCache removeObjectForKey: uid];
|
|
|
|
[dateCache removeObjectForKey: uid];
|
|
|
|
}
|
2016-07-21 20:06:11 +02:00
|
|
|
else
|
2015-10-20 14:48:39 +02:00
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", uid];
|
|
|
|
|
|
|
|
// Update cache entry to make sure the change is sent again.
|
|
|
|
[syncCache setObject: @"0" forKey: uid];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-21 16:45:27 +01:00
|
|
|
} // if (cleanup_needed) ...
|
2015-10-20 14:48:39 +02:00
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
return_count = 0;
|
2016-06-06 19:28:42 +02:00
|
|
|
component = nil;
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
for (i = 0; i < max; i++)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-12-04 17:27:10 +01:00
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
// Check for the WindowSize and slice accordingly
|
2014-12-22 17:50:51 +01:00
|
|
|
if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
2014-11-14 15:13:14 +01:00
|
|
|
{
|
|
|
|
more_available = YES;
|
|
|
|
|
|
|
|
// -1 to make sure that we miss no event in case there are more with the same c_lastmodified
|
2014-12-04 17:27:10 +01:00
|
|
|
*theLastServerKey = [[NSString alloc] initWithFormat: @"%d", [[component objectForKey: @"c_lastmodified"] intValue] - 1];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
DESTROY(pool);
|
2014-11-14 15:13:14 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-01-20 16:13:16 +01:00
|
|
|
component = [allComponents objectAtIndex: i];
|
|
|
|
deleted = [[component objectForKey: @"c_deleted"] intValue];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-01-20 16:13:16 +01:00
|
|
|
if (!deleted && ![[component objectForKey: @"c_component"] isEqualToString: component_name])
|
2014-12-22 17:50:51 +01:00
|
|
|
{
|
|
|
|
DESTROY(pool);
|
|
|
|
continue;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
uid = [[component objectForKey: @"c_name"] sanitizedServerIdWithType: theFolderType];
|
2016-07-21 20:06:11 +02:00
|
|
|
|
|
|
|
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;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if (deleted)
|
|
|
|
{
|
2014-11-14 15:13:14 +01:00
|
|
|
if ([syncCache objectForKey: uid])
|
|
|
|
{
|
|
|
|
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
2016-07-21 20:06:11 +02:00
|
|
|
|
|
|
|
if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder)
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@{+}%@</ServerId>", [theCollection nameInContainer], easId];
|
|
|
|
else
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", easId];
|
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
[s appendString: @"</Delete>"];
|
|
|
|
|
|
|
|
[syncCache removeObjectForKey: uid];
|
|
|
|
[dateCache removeObjectForKey: uid];
|
|
|
|
return_count++;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
updated = YES;
|
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
if (![syncCache objectForKey: uid])
|
2014-01-10 20:12:53 +01:00
|
|
|
updated = NO;
|
2014-11-14 15:13:14 +01:00
|
|
|
else if ([[component objectForKey: @"c_lastmodified"] intValue] == [[syncCache objectForKey: uid] intValue])
|
2014-12-22 17:50:51 +01:00
|
|
|
{
|
|
|
|
DESTROY(pool);
|
|
|
|
continue;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
sogoObject = [theCollection lookupName: [uid sanitizedServerIdWithType: theFolderType]
|
|
|
|
inContext: context
|
|
|
|
acquire: 0];
|
|
|
|
|
|
|
|
if (theFolderType == ActiveSyncContactFolder)
|
|
|
|
componentObject = [sogoObject vCard];
|
|
|
|
else
|
2016-11-21 16:45:27 +01:00
|
|
|
componentObject = [sogoObject component: NO secure: YES];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
|
|
|
[syncCache setObject: [component objectForKey: @"c_lastmodified"] forKey: uid];
|
2014-12-22 18:39:58 +01:00
|
|
|
|
2016-11-21 16:45:27 +01:00
|
|
|
// We have got a null componentObject, i.e. we have no permission to view the calendar entry.
|
|
|
|
// For updated calendar entries we have to remove it from the client and for new entries we just ignore them.
|
|
|
|
if (!componentObject)
|
|
|
|
{
|
|
|
|
if (updated)
|
|
|
|
{
|
|
|
|
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
|
|
|
|
|
|
|
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];
|
|
|
|
[dateCache removeObjectForKey: uid];
|
|
|
|
return_count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
DESTROY(pool);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-12-22 18:39:58 +01:00
|
|
|
// No need to set dateCache for Contacts
|
|
|
|
if ((theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder))
|
|
|
|
{
|
|
|
|
NSCalendarDate *d;
|
|
|
|
|
|
|
|
if ([[component objectForKey: @"c_cycleenddate"] intValue])
|
|
|
|
d = [NSCalendarDate dateWithTimeIntervalSince1970: [[component objectForKey: @"c_cycleenddate"] intValue]];
|
|
|
|
else if ([[component objectForKey: @"c_enddate"] intValue])
|
|
|
|
d = [NSCalendarDate dateWithTimeIntervalSince1970: [[component objectForKey: @"c_enddate"] intValue]];
|
|
|
|
else
|
|
|
|
d = [NSCalendarDate distantFuture];
|
|
|
|
|
|
|
|
[dateCache setObject: d forKey: uid];
|
2017-04-11 16:13:56 +02:00
|
|
|
|
|
|
|
if (!([theCollection showCalendarAlarms]))
|
|
|
|
[self _removeAllAlarmsFromCalendar: [componentObject parent]];
|
2014-12-22 18:39:58 +01:00
|
|
|
}
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
if (updated)
|
2014-01-22 17:02:12 +01:00
|
|
|
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
2014-01-10 20:12:53 +01:00
|
|
|
else
|
2014-12-22 18:39:58 +01:00
|
|
|
[s appendString: @"<Add xmlns=\"AirSync:\">"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder)
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@{+}%@</ServerId>", [theCollection nameInContainer], easId];
|
|
|
|
else
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", easId];
|
|
|
|
|
2014-01-22 17:02:12 +01:00
|
|
|
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-17 16:01:44 +01:00
|
|
|
[s appendString: [componentObject activeSyncRepresentationInContext: context]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-01-22 17:02:12 +01:00
|
|
|
[s appendString: @"</ApplicationData>"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
if (updated)
|
2014-01-22 17:02:12 +01:00
|
|
|
[s appendString: @"</Change>"];
|
2014-01-10 20:12:53 +01:00
|
|
|
else
|
2014-01-22 17:02:12 +01:00
|
|
|
[s appendString: @"</Add>"];
|
2014-11-14 15:13:14 +01:00
|
|
|
|
|
|
|
return_count++;
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2015-01-07 15:29:31 +01:00
|
|
|
|
|
|
|
DESTROY(pool);
|
2015-01-08 21:56:16 +01:00
|
|
|
} // for (i = 0; i < max; i++) ...
|
2014-06-10 17:04:27 +02:00
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
if (more_available)
|
|
|
|
{
|
|
|
|
[folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"];
|
|
|
|
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
2015-03-23 22:23:29 +01:00
|
|
|
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
2014-11-14 15:13:14 +01:00
|
|
|
}
|
|
|
|
|
2014-06-10 17:04:27 +02:00
|
|
|
[self _setFolderMetadata: folderMetadata
|
|
|
|
forKey: [NSString stringWithFormat: @"%@/%@", component_name, [theCollection nameInContainer]]];
|
2014-12-04 17:27:10 +01:00
|
|
|
|
|
|
|
RELEASE(*theLastServerKey);
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ActiveSyncMailFolder:
|
|
|
|
default:
|
|
|
|
{
|
2014-05-15 21:03:24 +02:00
|
|
|
SOGoSyncCacheObject *lastCacheObject, *aCacheObject;
|
|
|
|
NSMutableArray *allCacheObjects, *sortedBySequence;
|
|
|
|
|
2014-01-20 16:13:16 +01:00
|
|
|
SOGoMailObject *mailObject;
|
2015-09-09 16:20:31 +02:00
|
|
|
NSArray *allMessages, *a;
|
2015-10-20 14:48:39 +02:00
|
|
|
NSString *firstUIDAdded;
|
2015-09-09 16:20:31 +02:00
|
|
|
|
|
|
|
int j, k, return_count, highestmodseq;
|
|
|
|
BOOL found_in_cache, initialLoadInProgress;
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
initialLoadInProgress = NO;
|
|
|
|
found_in_cache = NO;
|
2015-10-20 14:48:39 +02:00
|
|
|
firstUIDAdded = nil;
|
2014-03-06 20:16:08 +01:00
|
|
|
|
2015-09-09 16:20:31 +02:00
|
|
|
if ([theSyncKey isEqualToString: @"-1"])
|
|
|
|
{
|
|
|
|
highestmodseq = 0;
|
|
|
|
|
|
|
|
a = [[theCollection davCollectionTag] componentsSeparatedByString: @"-"];
|
|
|
|
[folderMetadata setObject: [a objectAtIndex: 1] forKey: @"InitialLoadSequence"];
|
|
|
|
}
|
2016-03-28 14:43:56 +02:00
|
|
|
else if ([context objectForKey: @"FilterTypeChanged"])
|
|
|
|
{
|
|
|
|
NSMutableArray *sortedByUID;
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - FilterTypeChanged!"];
|
|
|
|
|
|
|
|
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
|
|
|
|
|
|
|
if ([sortedByUID count])
|
|
|
|
{
|
|
|
|
[sortedByUID sortUsingSelector: @selector(compareUID:)];
|
|
|
|
|
|
|
|
// Save first and last uid in cache. We don't need to touch this range in case a cleanup is required.
|
|
|
|
[folderMetadata setObject: [[sortedByUID objectAtIndex: 0] uid] forKey: @"FirstIdInCache"];
|
|
|
|
[folderMetadata setObject: [[sortedByUID lastObject] uid] forKey: @"LastIdInCache"];
|
|
|
|
|
|
|
|
[folderMetadata setObject: [[sortedByUID lastObject] sequence] forKey: @"InitialLoadSequence"];
|
|
|
|
}
|
|
|
|
|
|
|
|
theSyncKey = @"-1";
|
2016-06-06 19:28:42 +02:00
|
|
|
highestmodseq = 0;
|
2016-03-28 14:43:56 +02:00
|
|
|
|
|
|
|
RELEASE(sortedByUID);
|
|
|
|
}
|
2015-09-09 16:20:31 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
a = [theSyncKey componentsSeparatedByString: @"-"];
|
|
|
|
highestmodseq = [[a objectAtIndex: 1] intValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([folderMetadata objectForKey: @"InitialLoadSequence"])
|
|
|
|
{
|
|
|
|
if (highestmodseq < [[folderMetadata objectForKey: @"InitialLoadSequence"] intValue])
|
|
|
|
initialLoadInProgress = YES;
|
|
|
|
else
|
2016-03-28 14:43:56 +02:00
|
|
|
{
|
|
|
|
[folderMetadata removeObjectForKey: @"FirstIdInCache"];
|
|
|
|
[folderMetadata removeObjectForKey: @"LastIdInCache"];
|
|
|
|
[folderMetadata removeObjectForKey: @"InitialLoadSequence"];
|
|
|
|
}
|
2015-09-09 16:20:31 +02:00
|
|
|
}
|
2014-03-06 20:16:08 +01:00
|
|
|
|
2018-03-14 18:21:49 +01:00
|
|
|
allMessages = [theCollection syncTokenFieldsWithProperties: nil
|
|
|
|
matchingSyncToken: theSyncKey
|
|
|
|
fromDate: theFilterType
|
|
|
|
initialLoad: initialLoadInProgress];
|
2014-02-17 14:46:05 +01:00
|
|
|
max = [allMessages count];
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
allCacheObjects = [NSMutableArray array];
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
for (i = 0; i < max; i++)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2014-05-15 21:03:24 +02:00
|
|
|
[allCacheObjects addObject: [SOGoSyncCacheObject syncCacheObjectWithUID: [[[allMessages objectAtIndex: i] allKeys] lastObject]
|
2015-10-14 15:21:32 +02:00
|
|
|
sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]];
|
2014-05-15 21:03:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sortedBySequence = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
|
|
|
[sortedBySequence sortUsingSelector: @selector(compareSequence:)];
|
|
|
|
[sortedBySequence autorelease];
|
|
|
|
|
|
|
|
[allCacheObjects sortUsingSelector: @selector(compareSequence:)];
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
{
|
|
|
|
[self logWithFormat: @"EAS - sortedBySequence (%d) - lastObject: %@", [sortedBySequence count], [sortedBySequence lastObject]];
|
|
|
|
[self logWithFormat: @"EAS - allCacheObjects (%d) - lastObject: %@", [allCacheObjects count], [allCacheObjects lastObject]];
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
|
|
|
|
lastCacheObject = [sortedBySequence lastObject];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// Cleanup the mess
|
|
|
|
//
|
|
|
|
if (cleanup_needed)
|
|
|
|
{
|
|
|
|
NSMutableArray *sortedByUID;
|
|
|
|
int uidnextFromCache;
|
|
|
|
|
|
|
|
sortedByUID = [[NSMutableArray alloc] initWithDictionary: syncCache];
|
|
|
|
[sortedByUID sortUsingSelector: @selector(compareUID:)];
|
|
|
|
|
|
|
|
// Get the uid from SyncKey in cache. The uid is the first uid added to cache by the last sync request.
|
|
|
|
a = [[folderMetadata objectForKey: @"SyncKey"] componentsSeparatedByString: @"-"];
|
|
|
|
uidnextFromCache = [[a objectAtIndex: 0] intValue];
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: from uid: %d to uid: %d", uidnextFromCache, [[[sortedByUID lastObject] uid] intValue]];
|
|
|
|
|
|
|
|
// Remove all entries from cache beginning with the first uid added by the last sync request.
|
|
|
|
for (j = uidnextFromCache; j <= [[[sortedByUID lastObject] uid] intValue]; j++)
|
|
|
|
{
|
2016-03-28 14:43:56 +02:00
|
|
|
if ([folderMetadata objectForKey: @"FirstIdInCache"])
|
|
|
|
{
|
|
|
|
if (j >= [[folderMetadata objectForKey: @"FirstIdInCache"] intValue] && j <= [[folderMetadata objectForKey: @"LastIdInCache"] intValue])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanupa: ignore (a) %d", j];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: ADD %d", j];
|
|
|
|
|
|
|
|
[syncCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
|
|
|
[dateCache removeObjectForKey: [NSString stringWithFormat:@"%d", j]];
|
|
|
|
}
|
|
|
|
|
|
|
|
RELEASE(sortedByUID);
|
|
|
|
|
|
|
|
for (j = 0; j < [allCacheObjects count]; j++)
|
|
|
|
{
|
2016-03-28 14:43:56 +02:00
|
|
|
if ([folderMetadata objectForKey: @"FirstIdInCache"])
|
|
|
|
{
|
|
|
|
if ([[[allCacheObjects objectAtIndex: j] uid] intValue] >= [[folderMetadata objectForKey: @"FirstIdInCache"] intValue] &&
|
|
|
|
[[[allCacheObjects objectAtIndex: j] uid] intValue] <= [[folderMetadata objectForKey: @"LastIdInCache"] intValue])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanupa: ignore (c) %@", [[allCacheObjects objectAtIndex: j] uid]];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the modseq in cache, since otherwise, it would be identical to the modseq from server
|
|
|
|
// and we would skip the cache when generating the response.
|
2015-10-14 15:21:32 +02:00
|
|
|
if ([syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]] && ![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: CHANGE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
|
|
|
|
|
|
|
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
|
|
|
}
|
|
|
|
else if ([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Cache cleanup: DELETE %@", [[allCacheObjects objectAtIndex: j] uid]];
|
|
|
|
|
|
|
|
// For deletes we have to recreate a cache entry to have the <Delete> included in the response.
|
|
|
|
[syncCache setObject: @"0" forKey:[[allCacheObjects objectAtIndex: j] uid]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
if (!cleanup_needed &&
|
|
|
|
[folderMetadata objectForKey: @"MoreAvailable"] &&
|
|
|
|
lastCacheObject &&
|
|
|
|
!([[lastCacheObject sequence] isEqual: @"0"])) // Sequence 0 is set during cache cleanup.
|
2014-05-15 21:03:24 +02:00
|
|
|
{
|
|
|
|
for (j = 0; j < [allCacheObjects count]; j++)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
if (([[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && [syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]) ||
|
2015-10-20 14:48:39 +02:00
|
|
|
(![[[allCacheObjects objectAtIndex: j] sequence] isEqual: [NSNull null]] && ![syncCache objectForKey: [[allCacheObjects objectAtIndex: j] uid]]))
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
// We need to continue with adds or deletes from here.
|
|
|
|
found_in_cache = YES;
|
|
|
|
j--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
if ([[lastCacheObject uid] isEqual: [[allCacheObjects objectAtIndex: j] uid]])
|
|
|
|
{
|
|
|
|
// Found out where we're at, let's continue from there...
|
|
|
|
found_in_cache = YES;
|
|
|
|
break;
|
|
|
|
}
|
2014-03-06 20:16:08 +01:00
|
|
|
}
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
else
|
|
|
|
found_in_cache = NO;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
if (found_in_cache)
|
|
|
|
k = j+1;
|
|
|
|
else
|
2015-10-14 15:21:32 +02:00
|
|
|
j = k = 0;
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - found in cache: %d k = %d", found_in_cache, k];
|
2014-05-15 21:03:24 +02:00
|
|
|
|
|
|
|
return_count = 0;
|
2016-06-06 19:28:42 +02:00
|
|
|
aCacheObject = nil;
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
for (; k < [allCacheObjects count]; k++)
|
|
|
|
{
|
2014-12-04 17:27:10 +01:00
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
// Check for the WindowSize and slice accordingly
|
2014-12-22 17:50:51 +01:00
|
|
|
if (return_count >= theWindowSize || (theMaxSyncResponseSize > 0 && [s length] >= theMaxSyncResponseSize))
|
2014-03-19 16:31:54 +01:00
|
|
|
{
|
2015-10-20 14:48:39 +02:00
|
|
|
NSString *lastSequence;
|
2014-05-15 21:03:24 +02:00
|
|
|
more_available = YES;
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
if (!firstUIDAdded)
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
2015-10-20 14:48:39 +02:00
|
|
|
firstUIDAdded = [a objectAtIndex: 0];
|
|
|
|
RETAIN(firstUIDAdded);
|
2015-10-14 15:21:32 +02:00
|
|
|
}
|
2015-09-09 16:20:31 +02:00
|
|
|
lastSequence = ([[aCacheObject sequence] isEqual: [NSNull null]] ? [NSString stringWithFormat:@"%d", highestmodseq] : [aCacheObject sequence]);
|
2015-10-20 14:48:39 +02:00
|
|
|
*theLastServerKey = [[NSString alloc] initWithFormat: @"%@-%@", firstUIDAdded, lastSequence];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Reached windowSize - lastUID will be: %@", *theLastServerKey];
|
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
DESTROY(pool);
|
2014-05-15 21:03:24 +02:00
|
|
|
break;
|
2014-03-19 16:31:54 +01:00
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
aCacheObject = [allCacheObjects objectAtIndex: k];
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Dealing with cacheObject: %@", aCacheObject];
|
|
|
|
|
|
|
|
// If found in cache, it's either a Change or a Delete operation.
|
2014-05-15 21:03:24 +02:00
|
|
|
if ([syncCache objectForKey: [aCacheObject uid]])
|
|
|
|
{
|
|
|
|
if ([[aCacheObject sequence] isEqual: [NSNull null]])
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - DELETE!"];
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
// Deleted
|
|
|
|
[s appendString: @"<Delete xmlns=\"AirSync:\">"];
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
|
|
|
[s appendString: @"</Delete>"];
|
|
|
|
|
|
|
|
[syncCache removeObjectForKey: [aCacheObject uid]];
|
2014-05-27 20:44:57 +02:00
|
|
|
[dateCache removeObjectForKey: [aCacheObject uid]];
|
2015-07-22 15:46:06 +02:00
|
|
|
|
|
|
|
return_count++;
|
2014-05-15 21:03:24 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Changed
|
|
|
|
outlook_hack:
|
|
|
|
mailObject = [theCollection lookupName: [aCacheObject uid]
|
|
|
|
inContext: context
|
|
|
|
acquire: 0];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
2015-07-22 15:46:06 +02:00
|
|
|
if (![[aCacheObject sequence] isEqual: [syncCache objectForKey: [aCacheObject uid]]])
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - CHANGE!"];
|
|
|
|
|
2015-07-22 15:46:06 +02:00
|
|
|
[s appendString: @"<Change xmlns=\"AirSync:\">"];
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
|
|
|
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
|
|
|
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
|
|
|
|
[s appendString: @"</ApplicationData>"];
|
|
|
|
[s appendString: @"</Change>"];
|
|
|
|
|
|
|
|
return_count++;
|
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
|
|
|
}
|
|
|
|
}
|
2014-03-06 20:16:08 +01:00
|
|
|
else
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - ADD!"];
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
// Added
|
|
|
|
if (![[aCacheObject sequence] isEqual: [NSNull null]])
|
|
|
|
{
|
|
|
|
NSString *key;
|
|
|
|
|
|
|
|
// We check for Outlook stupidity to avoid creating duplicates - see the comment
|
|
|
|
// in SOGoActiveSyncDispatcher.m: -processMoveItems:inResponse: for more details.
|
|
|
|
key = [NSString stringWithFormat: @"%@+%@+%@+%@",
|
|
|
|
[[context activeUser] login],
|
|
|
|
[context objectForKey: @"DeviceType"],
|
|
|
|
[theCollection displayName],
|
|
|
|
[aCacheObject uid]];
|
|
|
|
|
|
|
|
if ([[SOGoCache sharedCache] valueForKey: key])
|
|
|
|
{
|
|
|
|
[[SOGoCache sharedCache] removeValueForKey: key];
|
|
|
|
goto outlook_hack;
|
|
|
|
}
|
|
|
|
|
|
|
|
mailObject = [theCollection lookupName: [aCacheObject uid]
|
|
|
|
inContext: context
|
|
|
|
acquire: 0];
|
|
|
|
|
|
|
|
[s appendString: @"<Add xmlns=\"AirSync:\">"];
|
|
|
|
[s appendFormat: @"<ServerId xmlns=\"AirSync:\">%@</ServerId>", [aCacheObject uid]];
|
|
|
|
[s appendString: @"<ApplicationData xmlns=\"AirSync:\">"];
|
|
|
|
[s appendString: [mailObject activeSyncRepresentationInContext: context]];
|
|
|
|
[s appendString: @"</ApplicationData>"];
|
|
|
|
[s appendString: @"</Add>"];
|
2017-04-11 16:13:56 +02:00
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
[syncCache setObject: [aCacheObject sequence] forKey: [aCacheObject uid]];
|
|
|
|
[dateCache setObject: [NSCalendarDate date] forKey: [aCacheObject uid]];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
|
|
|
// Save the frist UID we add. We will use it for the synckey late.
|
2015-10-20 14:48:39 +02:00
|
|
|
if (!firstUIDAdded)
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
2015-10-20 14:48:39 +02:00
|
|
|
firstUIDAdded = [aCacheObject uid];
|
|
|
|
RETAIN(firstUIDAdded);
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - first uid added %@", firstUIDAdded];
|
2015-10-14 15:21:32 +02:00
|
|
|
}
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
return_count++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - skipping old deleted UID: %@", [aCacheObject uid]];
|
2014-05-15 21:03:24 +02:00
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
DESTROY(pool);
|
|
|
|
} // for (; k < ...)
|
2014-05-27 20:44:57 +02:00
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
if (more_available)
|
2014-06-10 17:04:27 +02:00
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
[folderMetadata setObject: [NSNumber numberWithInt: YES] forKey: @"MoreAvailable"];
|
2014-06-10 17:04:27 +02:00
|
|
|
[folderMetadata setObject: *theLastServerKey forKey: @"SyncKey"];
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
else
|
2014-06-10 17:04:27 +02:00
|
|
|
{
|
|
|
|
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
if (firstUIDAdded)
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
a = [davCollectionTagToStore componentsSeparatedByString: @"-"];
|
2016-03-28 21:10:12 +02:00
|
|
|
[folderMetadata setObject: [NSString stringWithFormat: @"%@-%@", firstUIDAdded, [a objectAtIndex: 1]] forKey: @"SyncKey"];
|
2015-10-20 14:48:39 +02:00
|
|
|
RELEASE(firstUIDAdded);
|
2015-10-14 15:21:32 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
[folderMetadata setObject: davCollectionTagToStore forKey: @"SyncKey"];
|
2014-06-10 17:04:27 +02:00
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
[self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
2014-12-04 17:27:10 +01:00
|
|
|
RELEASE(*theLastServerKey);
|
|
|
|
|
2014-05-15 21:03:24 +02:00
|
|
|
} // default:
|
2014-01-10 20:12:53 +01:00
|
|
|
break;
|
|
|
|
} // switch (folderType) ...
|
|
|
|
|
2014-06-25 21:05:25 +02:00
|
|
|
return_response:
|
|
|
|
|
2014-01-22 17:02:12 +01:00
|
|
|
if ([s length])
|
|
|
|
{
|
|
|
|
[theBuffer appendString: @"<Commands>"];
|
|
|
|
[theBuffer appendString: s];
|
|
|
|
[theBuffer appendString: @"</Commands>"];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// We have something like this:
|
|
|
|
//
|
|
|
|
// <Commands>
|
|
|
|
// <Fetch>
|
|
|
|
// <ServerId>91</ServerId>
|
|
|
|
// </Fetch>
|
|
|
|
// </Commands>
|
|
|
|
//
|
|
|
|
- (void) processSyncCommands: (id <DOMElement>) theDocumentElement
|
|
|
|
inCollection: (id) theCollection
|
|
|
|
withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType
|
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: (NSMutableArray *) objectsToTouch
|
2014-01-10 20:12:53 +01:00
|
|
|
processed: (BOOL *) processed
|
|
|
|
{
|
|
|
|
id <DOMElement> aCommand, element;
|
2016-02-05 16:03:56 +01:00
|
|
|
id <DOMNodeList> aCommandDetails;
|
|
|
|
NSAutoreleasePool *pool;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSArray *allCommands;
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
allCommands = (id)[theDocumentElement getElementsByTagName: @"Commands"];
|
|
|
|
|
|
|
|
for (i = 0; i < [allCommands count]; i++)
|
|
|
|
{
|
|
|
|
aCommand = [allCommands objectAtIndex: i];
|
|
|
|
aCommandDetails = [aCommand childNodes];
|
|
|
|
|
|
|
|
for (j = 0; j < [(id)aCommandDetails count]; j++)
|
|
|
|
{
|
2016-02-05 16:03:56 +01:00
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
2014-01-10 20:12:53 +01:00
|
|
|
element = [aCommandDetails objectAtIndex: j];
|
|
|
|
|
|
|
|
if ([element nodeType] == DOM_ELEMENT_NODE)
|
|
|
|
{
|
|
|
|
if ([[element tagName] isEqualToString: @"Add"])
|
|
|
|
{
|
|
|
|
// Add
|
2015-01-15 17:55:04 +01:00
|
|
|
[self processSyncAddCommand: element
|
2014-01-10 20:12:53 +01:00
|
|
|
inCollection: theCollection
|
|
|
|
withType: theFolderType
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: objectsToTouch
|
2014-01-10 20:12:53 +01:00
|
|
|
inBuffer: theBuffer];
|
|
|
|
*processed = YES;
|
|
|
|
}
|
|
|
|
else if ([[element tagName] isEqualToString: @"Change"])
|
|
|
|
{
|
|
|
|
// Change
|
2015-01-15 17:55:04 +01:00
|
|
|
[self processSyncChangeCommand: element
|
2014-01-10 20:12:53 +01:00
|
|
|
inCollection: theCollection
|
|
|
|
withType: theFolderType
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: objectsToTouch
|
2014-01-10 20:12:53 +01:00
|
|
|
inBuffer: theBuffer];
|
|
|
|
*processed = YES;
|
|
|
|
}
|
|
|
|
else if ([[element tagName] isEqualToString: @"Delete"])
|
|
|
|
{
|
|
|
|
// Delete
|
2015-01-15 17:55:04 +01:00
|
|
|
[self processSyncDeleteCommand: element
|
2014-01-10 20:12:53 +01:00
|
|
|
inCollection: theCollection
|
|
|
|
withType: theFolderType
|
|
|
|
inBuffer: theBuffer];
|
2014-10-29 16:13:18 +01:00
|
|
|
*processed = YES;
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
else if ([[element tagName] isEqualToString: @"Fetch"])
|
|
|
|
{
|
|
|
|
// Fetch
|
2015-01-15 17:55:04 +01:00
|
|
|
[self processSyncFetchCommand: element
|
2014-01-10 20:12:53 +01:00
|
|
|
inCollection: theCollection
|
|
|
|
withType: theFolderType
|
|
|
|
inBuffer: theBuffer];
|
2014-01-14 16:09:10 +01:00
|
|
|
*processed = YES;
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
2016-02-05 16:03:56 +01:00
|
|
|
DESTROY(pool);
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) processSyncCollection: (id <DOMElement>) theDocumentElement
|
|
|
|
inBuffer: (NSMutableString *) theBuffer
|
2014-02-04 17:19:33 +01:00
|
|
|
changeDetected: (BOOL *) changeDetected
|
2014-12-22 17:50:51 +01:00
|
|
|
maxSyncResponseSize: (int) theMaxSyncResponseSize
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2016-03-28 14:43:56 +02:00
|
|
|
NSString *collectionId, *realCollectionId, *syncKey, *davCollectionTag, *bodyPreferenceType, *mimeSupport, *mimeTruncation, *filterType, *lastServerKey, *syncKeyInCache, *folderKey;
|
2015-10-14 15:21:32 +02:00
|
|
|
NSMutableDictionary *folderMetadata, *folderOptions;
|
2017-01-23 14:53:41 +01:00
|
|
|
NSMutableArray *supportedElements, *supportedElementNames, *objectsToTouch;
|
2014-01-28 19:51:21 +01:00
|
|
|
NSMutableString *changeBuffer, *commandsBuffer;
|
2015-10-14 15:21:32 +02:00
|
|
|
id collection, value;
|
|
|
|
|
|
|
|
SOGoMicrosoftActiveSyncFolderType folderType;
|
2015-10-26 15:15:35 +01:00
|
|
|
unsigned int windowSize, v, status, i;
|
2015-10-14 15:21:32 +02:00
|
|
|
BOOL getChanges, first_sync;
|
|
|
|
|
2014-01-28 19:51:21 +01:00
|
|
|
changeBuffer = [NSMutableString string];
|
|
|
|
commandsBuffer = [NSMutableString string];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue];
|
|
|
|
realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType];
|
2014-05-27 20:44:57 +02:00
|
|
|
realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType];
|
2014-01-10 20:12:53 +01:00
|
|
|
collection = [self collectionFromId: realCollectionId type: folderType];
|
|
|
|
|
2014-01-28 19:51:21 +01:00
|
|
|
syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
|
|
|
if (collection == nil)
|
|
|
|
{
|
|
|
|
// Collection not found - next folderSync will do the cleanup
|
|
|
|
//NSLog(@"Sync Collection not found %@ %@", collectionId, realCollectionId);
|
2014-11-26 21:27:36 +01:00
|
|
|
//Outlook doesn't like following response
|
|
|
|
//[theBuffer appendString: @"<Collection>"];
|
|
|
|
//[theBuffer appendFormat: @"<SyncKey>%@</SyncKey>", syncKey];
|
|
|
|
//[theBuffer appendFormat: @"<CollectionId>%@</CollectionId>", collectionId];
|
|
|
|
//[theBuffer appendFormat: @"<Status>%d</Status>", 8];
|
|
|
|
//[theBuffer appendString: @"</Collection>"];
|
2014-10-29 19:20:03 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-10-14 15:21:32 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// First check if we have any concurrent Sync requests going on for this device.
|
|
|
|
// If we do and we are still within our maximumSyncInterval, we let our EAS
|
|
|
|
// device know to retry.
|
|
|
|
//
|
|
|
|
folderKey = [self _getNameInCache: collection withType: folderType];
|
|
|
|
folderMetadata = [self _folderMetadataForKey: folderKey];
|
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-02-17 14:46:05 +01:00
|
|
|
// We check for a window size, default to 100 if not specfied or out of bounds
|
|
|
|
windowSize = [[[(id)[theDocumentElement getElementsByTagName: @"WindowSize"] lastObject] textValue] intValue];
|
|
|
|
|
|
|
|
if (windowSize == 0 || windowSize > 512)
|
|
|
|
windowSize = 100;
|
2014-04-10 02:12:19 +02:00
|
|
|
|
|
|
|
// We check if we must overwrite the windowSize with a system preference. This can be useful
|
|
|
|
// if the user population has large mailboxes and slow connectivity
|
|
|
|
if ((v = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncWindowSize]))
|
|
|
|
windowSize = v;
|
2014-02-17 14:46:05 +01:00
|
|
|
|
|
|
|
lastServerKey = nil;
|
2014-11-14 15:13:14 +01:00
|
|
|
status = 1;
|
2014-02-17 14:46:05 +01:00
|
|
|
|
2014-01-14 17:41:26 +01:00
|
|
|
// From the documention, if GetChanges is missing, we must assume it's a YES.
|
|
|
|
// See http://msdn.microsoft.com/en-us/library/gg675447(v=exchg.80).aspx for all details.
|
|
|
|
value = [theDocumentElement getElementsByTagName: @"GetChanges"];
|
|
|
|
getChanges = YES;
|
|
|
|
|
2014-01-16 21:13:09 +01:00
|
|
|
if ([value count] && [[[value lastObject] textValue] length])
|
2014-01-14 17:41:26 +01:00
|
|
|
getChanges = [[[value lastObject] textValue] boolValue];
|
2014-01-16 21:13:09 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
first_sync = NO;
|
|
|
|
|
2016-11-21 16:45:27 +01:00
|
|
|
// We check if the folder permissions have changed since the last sync. If so, we simply clear the cached data
|
|
|
|
// and a "full sync" will be issued by the EAS client as it'll repull all elements from the collection.
|
|
|
|
// We don't need to check for contact folder. Either it has View_object or it will be removed by FolderSync.
|
|
|
|
if ((folderType == ActiveSyncEventFolder || folderType == ActiveSyncTaskFolder) && ![[collection ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
|
|
|
{
|
|
|
|
NSArray *folderRoles, *folderRolesInCache;
|
|
|
|
|
|
|
|
folderRoles = [collection aclsForUser: [[context activeUser] login]];
|
|
|
|
folderRolesInCache = [folderMetadata objectForKey: @"FolderPermissions"];
|
|
|
|
if (![folderRoles isEqualToArray: folderRolesInCache])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Folder permission change: %@ (%@ <> %@). Refresh folder", [collection nameInContainer], folderRoles, folderRolesInCache];
|
|
|
|
|
|
|
|
// Cleanup metadata
|
|
|
|
[folderMetadata removeObjectForKey: @"SyncKey"];
|
|
|
|
[folderMetadata removeObjectForKey: @"SyncCache"];
|
|
|
|
[folderMetadata removeObjectForKey: @"DateCache"];
|
|
|
|
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
|
|
|
|
|
|
|
[folderMetadata setObject: folderRoles forKey: @"FolderPermissions"];
|
|
|
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
if ([syncKey isEqualToString: @"0"])
|
|
|
|
{
|
|
|
|
davCollectionTag = @"-1";
|
|
|
|
first_sync = YES;
|
2014-02-04 17:19:33 +01:00
|
|
|
*changeDetected = YES;
|
2015-10-26 15:15:35 +01:00
|
|
|
|
|
|
|
supportedElementNames = [[[NSMutableArray alloc] init] autorelease];
|
|
|
|
value = [theDocumentElement getElementsByTagName: @"Supported"];
|
|
|
|
|
|
|
|
if ([value count])
|
|
|
|
{
|
|
|
|
supportedElements = (id)[[value lastObject] childNodes];
|
|
|
|
|
|
|
|
if ([supportedElements count])
|
|
|
|
{
|
|
|
|
for (i = 0; i < [supportedElements count]; i++)
|
|
|
|
{
|
|
|
|
if ([[supportedElements objectAtIndex: i] nodeType] == DOM_ELEMENT_NODE)
|
|
|
|
[supportedElementNames addObject: [[supportedElements objectAtIndex: i] tagName]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[folderMetadata setObject: supportedElementNames forKey: @"SupportedElements"];
|
|
|
|
|
|
|
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
|
|
|
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - %d %@: supportedElements saved: %@", [supportedElements count], [collection nameInContainer], supportedElementNames];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2015-03-30 15:49:44 +02:00
|
|
|
else if ((![syncKey isEqualToString: @"-1"]) && !([folderMetadata objectForKey: @"SyncCache"]))
|
2014-11-14 15:13:14 +01:00
|
|
|
{
|
|
|
|
//NSLog(@"Reset folder: %@", [collection nameInContainer]);
|
|
|
|
davCollectionTag = @"0";
|
|
|
|
first_sync = YES;
|
|
|
|
*changeDetected = YES;
|
|
|
|
|
2015-03-30 15:49:44 +02:00
|
|
|
if (!([folderMetadata objectForKey: @"displayName"]))
|
2014-12-08 16:45:34 +01:00
|
|
|
status = 12; // need folderSync
|
2014-11-14 15:13:14 +01:00
|
|
|
else
|
|
|
|
status = 3; // do a complete resync
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// We check our sync preferences and we stash them
|
|
|
|
bodyPreferenceType = [[(id)[[(id)[theDocumentElement getElementsByTagName: @"BodyPreference"] lastObject] getElementsByTagName: @"Type"] lastObject] textValue];
|
|
|
|
|
|
|
|
if (!bodyPreferenceType)
|
2016-02-15 22:04:18 +01:00
|
|
|
{
|
|
|
|
bodyPreferenceType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"];
|
|
|
|
|
|
|
|
if (!bodyPreferenceType)
|
2019-10-30 18:44:13 +01:00
|
|
|
bodyPreferenceType = @"1";
|
2015-03-30 15:49:44 +02:00
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
|
|
|
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
|
2016-03-28 14:43:56 +02:00
|
|
|
filterType = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"FilterType"];
|
2015-10-15 21:31:46 +02:00
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
if (!mimeSupport)
|
|
|
|
mimeSupport = @"1";
|
2015-10-15 21:31:46 +02:00
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
if (!mimeTruncation)
|
|
|
|
mimeTruncation = @"8";
|
2016-03-28 14:43:56 +02:00
|
|
|
|
|
|
|
if (!filterType)
|
|
|
|
filterType = @"0";
|
2016-02-15 22:04:18 +01:00
|
|
|
}
|
2015-03-30 15:49:44 +02:00
|
|
|
else
|
2016-02-15 22:04:18 +01:00
|
|
|
{
|
|
|
|
mimeSupport = [[(id)[theDocumentElement getElementsByTagName: @"MIMESupport"] lastObject] textValue];
|
|
|
|
mimeTruncation = [[(id)[theDocumentElement getElementsByTagName: @"MIMETruncation"] lastObject] textValue];
|
2016-03-28 14:43:56 +02:00
|
|
|
filterType = [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue];
|
2015-03-30 15:49:44 +02:00
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
if (!mimeSupport)
|
2015-03-30 15:49:44 +02:00
|
|
|
mimeSupport = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"];
|
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
if (!mimeSupport)
|
2015-03-30 15:49:44 +02:00
|
|
|
mimeSupport = @"0";
|
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
if (!mimeTruncation)
|
|
|
|
mimeTruncation = [[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"];
|
|
|
|
|
|
|
|
if (!mimeTruncation)
|
|
|
|
mimeTruncation = @"8";
|
|
|
|
|
2016-03-28 14:43:56 +02:00
|
|
|
if (!filterType)
|
|
|
|
filterType = @"0";
|
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
if ([mimeSupport isEqualToString: @"1"] && [bodyPreferenceType isEqualToString: @"4"])
|
2015-03-30 15:49:44 +02:00
|
|
|
bodyPreferenceType = @"2";
|
2016-02-15 22:04:18 +01:00
|
|
|
else if ([mimeSupport isEqualToString: @"2"] && [bodyPreferenceType isEqualToString: @"4"])
|
2015-03-30 15:49:44 +02:00
|
|
|
bodyPreferenceType = @"4";
|
2016-02-15 22:04:18 +01:00
|
|
|
else if ([mimeSupport isEqualToString: @"0"] && [bodyPreferenceType isEqualToString: @"4"])
|
2015-03-30 15:49:44 +02:00
|
|
|
bodyPreferenceType = @"2";
|
|
|
|
|
2016-02-15 22:04:18 +01:00
|
|
|
// Avoid writing to cache if there is nothing to change.
|
|
|
|
if (![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"BodyPreferenceType"] isEqualToString: bodyPreferenceType] ||
|
|
|
|
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMESupport"] isEqualToString: mimeSupport] ||
|
2016-03-28 14:43:56 +02:00
|
|
|
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"MIMETruncation"] isEqualToString: mimeTruncation] ||
|
|
|
|
![[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"FilterType"] isEqualToString: filterType])
|
2016-02-15 22:04:18 +01:00
|
|
|
{
|
2016-03-28 14:43:56 +02:00
|
|
|
if ((([filterType intValue] != 0) && [[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"FilterType"] intValue] < [filterType intValue]) ||
|
|
|
|
(([filterType intValue] == 0) && ([[[folderMetadata objectForKey: @"FolderOptions"] objectForKey: @"FilterType"] intValue] > [filterType intValue])))
|
|
|
|
{
|
|
|
|
[context setObject: [NSNumber numberWithBool: YES] forKey: @"FilterTypeChanged"];
|
|
|
|
}
|
|
|
|
|
|
|
|
folderOptions = [[NSDictionary alloc] initWithObjectsAndKeys: mimeSupport, @"MIMESupport", mimeTruncation, @"MIMETruncation", filterType, @"FilterType", bodyPreferenceType, @"BodyPreferenceType", nil];
|
2016-02-15 22:04:18 +01:00
|
|
|
[folderMetadata setObject: folderOptions forKey: @"FolderOptions"];
|
|
|
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
|
|
|
}
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
[context setObject: bodyPreferenceType forKey: @"BodyPreferenceType"];
|
2015-10-15 21:31:46 +02:00
|
|
|
[context setObject: mimeSupport forKey: @"MIMESupport"];
|
2016-02-15 22:04:18 +01:00
|
|
|
[context setObject: mimeTruncation forKey: @"MIMETruncation"];
|
2015-10-26 15:15:35 +01:00
|
|
|
[context setObject: [folderMetadata objectForKey: @"SupportedElements"] forKey: @"SupportedElements"];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// We process the commands from the request
|
|
|
|
//
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch = [NSMutableArray array];
|
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
if (!first_sync)
|
|
|
|
{
|
|
|
|
NSMutableString *s;
|
|
|
|
BOOL processed;
|
|
|
|
|
|
|
|
s = [NSMutableString string];
|
|
|
|
processed = NO;
|
|
|
|
|
|
|
|
[self processSyncCommands: theDocumentElement
|
|
|
|
inCollection: collection
|
|
|
|
withType: folderType
|
|
|
|
inBuffer: s
|
2017-01-23 14:53:41 +01:00
|
|
|
objectsToTouch: objectsToTouch
|
2014-01-10 20:12:53 +01:00
|
|
|
processed: &processed];
|
|
|
|
|
2015-07-22 15:46:06 +02:00
|
|
|
// Windows phones don't like empty Responses tags - such as: <Responses></Responses>.
|
|
|
|
// We only generate this tag when there is a response
|
2014-10-16 15:08:50 +02:00
|
|
|
if (processed && [s length])
|
2016-11-21 16:45:27 +01:00
|
|
|
{
|
|
|
|
[commandsBuffer appendFormat: @"<Responses>%@</Responses>", s];
|
|
|
|
getChanges = NO;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
2015-07-22 15:46:06 +02:00
|
|
|
|
|
|
|
|
|
|
|
// We generate the commands, if any, for the response. We might also have
|
|
|
|
// generated some in processSyncCommand:inResponse: as we could have
|
|
|
|
// received a Fetch command
|
|
|
|
if (getChanges && !first_sync)
|
|
|
|
{
|
|
|
|
[self processSyncGetChanges: theDocumentElement
|
|
|
|
inCollection: collection
|
|
|
|
withWindowSize: windowSize
|
|
|
|
withMaxSyncResponseSize: theMaxSyncResponseSize
|
|
|
|
withSyncKey: syncKey
|
|
|
|
withFolderType: folderType
|
|
|
|
withFilterType: [NSCalendarDate dateFromFilterType: [[(id)[theDocumentElement getElementsByTagName: @"FilterType"] lastObject] textValue]]
|
|
|
|
inBuffer: changeBuffer
|
2015-10-20 14:48:39 +02:00
|
|
|
lastServerKey: &lastServerKey
|
2016-07-21 20:06:11 +02:00
|
|
|
defaultInterval: [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncInterval]
|
|
|
|
mergeFolders: NO];
|
2015-07-22 15:46:06 +02:00
|
|
|
}
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
folderMetadata = [self _folderMetadataForKey: folderKey];
|
2015-01-28 21:03:49 +01:00
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
// 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)
|
2016-11-21 16:45:27 +01:00
|
|
|
[self logWithFormat: @"EAS - Folder %@ not found. Reset personal folder to cleanup", folderName];
|
2016-07-21 20:06:11 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-11-21 16:45:27 +01:00
|
|
|
// Here we don't need to check for contact folder. Either it has View_object or it will be removed by folersync.
|
|
|
|
if ((folderType == ActiveSyncEventFolder || folderType == ActiveSyncTaskFolder) && ![[mfCollection ownerInContext: context] isEqualToString: [[context activeUser] login]])
|
|
|
|
{
|
|
|
|
NSArray *folderRoles, *folderRolesInCache;
|
|
|
|
|
|
|
|
folderRoles = [mfCollection aclsForUser: [[context activeUser] login]];
|
|
|
|
folderRolesInCache = [folderMetadata objectForKey: @"FolderPermissions"];
|
|
|
|
|
|
|
|
if (![folderRoles isEqualToArray: folderRolesInCache])
|
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Folder permission change: %@ (%@ <> %@). Refresh folder", [collection nameInContainer], folderRoles, folderRolesInCache];
|
|
|
|
|
|
|
|
davCollectionTag = @"0";
|
|
|
|
*changeDetected = YES;
|
|
|
|
|
|
|
|
status = 3;
|
|
|
|
// Cleanup metadata
|
|
|
|
[folderMetadata removeObjectForKey: @"SyncKey"];
|
|
|
|
[folderMetadata removeObjectForKey: @"SyncCache"];
|
|
|
|
[folderMetadata removeObjectForKey: @"DateCache"];
|
|
|
|
[folderMetadata removeObjectForKey: @"MoreAvailable"];
|
|
|
|
|
|
|
|
[folderMetadata setObject: folderRoles forKey: @"FolderPermissions"];
|
|
|
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-21 20:06:11 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2016-11-21 16:45:27 +01:00
|
|
|
} // if (![changeBuffer length]) ...
|
2016-07-21 20:06:11 +02:00
|
|
|
|
|
|
|
if (*changeDetected || cacheUpdateNeeded)
|
|
|
|
[self _setFolderMetadata: folderMetadata forKey: folderKey];
|
|
|
|
}
|
|
|
|
|
2014-01-28 19:51:21 +01:00
|
|
|
// 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])
|
2014-02-04 17:19:33 +01:00
|
|
|
{
|
2014-02-17 14:46:05 +01:00
|
|
|
if (lastServerKey)
|
2014-05-15 21:03:24 +02:00
|
|
|
davCollectionTag = lastServerKey;
|
2014-10-29 19:20:03 +01:00
|
|
|
else
|
|
|
|
{
|
2014-11-14 15:13:14 +01:00
|
|
|
// Use the SyncKey saved by processSyncGetChanges - if processSyncGetChanges is not called (because of getChanges=false)
|
|
|
|
// SyncKey has the value of the previous sync operation.
|
2015-01-28 21:03:49 +01:00
|
|
|
davCollectionTag = [folderMetadata objectForKey: @"SyncKey"];
|
2014-10-29 19:20:03 +01:00
|
|
|
|
2014-11-14 15:13:14 +01:00
|
|
|
if (!davCollectionTag)
|
2014-10-29 19:20:03 +01:00
|
|
|
davCollectionTag = [collection davCollectionTag];
|
|
|
|
}
|
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
*changeDetected = YES;
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
else
|
|
|
|
{
|
2015-01-28 21:03:49 +01:00
|
|
|
// Make sure that client is updated with the right syncKey. - This keeps vtodo's and vevent's syncKey in sync.
|
|
|
|
syncKeyInCache = [folderMetadata objectForKey: @"SyncKey"];
|
2015-07-22 15:46:06 +02:00
|
|
|
if (syncKeyInCache && !([davCollectionTag isEqualToString:syncKeyInCache]) && !first_sync)
|
2015-01-28 21:03:49 +01:00
|
|
|
{
|
|
|
|
davCollectionTag = syncKeyInCache;
|
|
|
|
*changeDetected = YES;
|
|
|
|
}
|
2014-05-15 21:03:24 +02:00
|
|
|
}
|
2014-01-28 19:51:21 +01:00
|
|
|
|
|
|
|
// Generate the response buffer
|
|
|
|
[theBuffer appendString: @"<Collection>"];
|
|
|
|
|
|
|
|
if (folderType == ActiveSyncMailFolder)
|
|
|
|
[theBuffer appendString: @"<Class>Email</Class>"];
|
|
|
|
else if (folderType == ActiveSyncContactFolder)
|
|
|
|
[theBuffer appendString: @"<Class>Contacts</Class>"];
|
|
|
|
else if (folderType == ActiveSyncEventFolder)
|
|
|
|
[theBuffer appendString: @"<Class>Calendar</Class>"];
|
|
|
|
else if (folderType == ActiveSyncTaskFolder)
|
|
|
|
[theBuffer appendString: @"<Class>Tasks</Class>"];
|
|
|
|
|
|
|
|
[theBuffer appendFormat: @"<SyncKey>%@</SyncKey>", davCollectionTag];
|
|
|
|
[theBuffer appendFormat: @"<CollectionId>%@</CollectionId>", collectionId];
|
2014-11-14 15:13:14 +01:00
|
|
|
[theBuffer appendFormat: @"<Status>%d</Status>", status];
|
|
|
|
|
|
|
|
// MoreAvailable breaks Windows Mobile devices if not between <Status> and <Commands>
|
|
|
|
// https://social.msdn.microsoft.com/Forums/en-US/040b254e-f47e-4cc1-a397-6d8393cdb819/airsyncmoreavailable-breaks-windows-mobile-devices-what-am-i-doing-wrong?forum=os_exchangeprotocols
|
2015-01-28 21:03:49 +01:00
|
|
|
if ([folderMetadata objectForKey: @"MoreAvailable"])
|
2014-11-14 15:13:14 +01:00
|
|
|
[theBuffer appendString: @"<MoreAvailable/>"];
|
2014-01-28 19:51:21 +01:00
|
|
|
|
|
|
|
[theBuffer appendString: commandsBuffer];
|
2014-10-29 16:13:18 +01:00
|
|
|
[theBuffer appendString: changeBuffer];
|
2014-01-28 19:51:21 +01:00
|
|
|
|
2014-01-10 20:12:53 +01:00
|
|
|
[theBuffer appendString: @"</Collection>"];
|
2017-01-23 14:53:41 +01:00
|
|
|
|
|
|
|
// We touch objects (likely only SOGoAppointmentObjects for now) that might have been changed
|
|
|
|
// by the server and that we want the EAS client to re-pull. For example, that's the case for
|
|
|
|
// auto-accepted events by resources
|
|
|
|
for (i = 0; i < [objectsToTouch count]; i++)
|
|
|
|
{
|
|
|
|
sleep(1);
|
|
|
|
[[objectsToTouch objectAtIndex: i] touch];
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Initial folder sync:
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Sync xmlns="AirSync:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey>0</SyncKey>
|
|
|
|
// <CollectionId>folderINBOX</CollectionId>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </Sync>
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Following this will be a GetItemEstimate call. Following our response to the GetItemEstimate, we'll
|
|
|
|
// have a new Sync call like this:
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Sync xmlns="AirSync:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey>1</SyncKey>
|
|
|
|
// <CollectionId>folderINBOX</CollectionId>
|
|
|
|
// <DeletesAsMoves>1</DeletesAsMoves>
|
|
|
|
// <GetChanges/>
|
|
|
|
// <WindowSize>50</WindowSize>
|
|
|
|
// <Options>
|
|
|
|
// <FilterType>5</FilterType> -- http://msdn.microsoft.com/en-us/library/gg709713(v=exchg.80).aspx
|
|
|
|
// <BodyPreference xmlns="AirSyncBase:"> -- http://msdn.microsoft.com/en-us/library/ee218197(v=exchg.80).aspx
|
|
|
|
// <Type>2</Type> --
|
|
|
|
// <TruncationSize>51200</TruncationSize>
|
|
|
|
// </BodyPreference>
|
|
|
|
// <BodyPreference xmlns="AirSyncBase:">
|
|
|
|
// <Type>4</Type>
|
|
|
|
// </BodyPreference>
|
|
|
|
// </Options>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </Sync>
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// When adding a new task, we might have something like this:
|
|
|
|
//
|
|
|
|
// <?xml version="1.0"?>
|
|
|
|
// <!DOCTYPE ActiveSync PUBLIC "-//MICROSOFT//DTD ActiveSync//EN" "http://www.microsoft.com/">
|
|
|
|
// <Sync xmlns="AirSync:">
|
|
|
|
// <Collections>
|
|
|
|
// <Collection>
|
|
|
|
// <SyncKey>1</SyncKey>
|
|
|
|
// <CollectionId>personal</CollectionId>
|
|
|
|
// <DeletesAsMoves/>
|
|
|
|
// <GetChanges/> -- http://msdn.microsoft.com/en-us/library/gg675447(v=exchg.80).aspx
|
|
|
|
// <WindowSize>5</WindowSize> -- http://msdn.microsoft.com/en-us/library/gg650865(v=exchg.80).aspx
|
|
|
|
// <Options>
|
|
|
|
// <BodyPreference xmlns="AirSyncBase:"> -- http://msdn.microsoft.com/en-us/library/ee218197(v=exchg.80).aspx
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <TruncationSize>400000</TruncationSize>
|
|
|
|
// </BodyPreference>
|
|
|
|
// </Options>
|
|
|
|
// <Commands>
|
|
|
|
// <Add>
|
|
|
|
// <ClientId>new_task_1386614771261</ClientId>
|
|
|
|
// <ApplicationData>
|
|
|
|
// <Body xmlns="AirSyncBase:">
|
|
|
|
// <Type>1</Type>
|
|
|
|
// <EstimatedDataSize>6</EstimatedDataSize>
|
|
|
|
// <Data>tomate</Data>
|
|
|
|
// </Body>
|
|
|
|
// <Subject xmlns="Tasks:">test 1</Subject>
|
|
|
|
// <Importance xmlns="Tasks:">1</Importance>
|
|
|
|
// <UTCDueDate xmlns="Tasks:">2013-12-09T19:00:00.000Z</UTCDueDate>
|
|
|
|
// <Complete xmlns="Tasks:">0</Complete>
|
|
|
|
// <ReminderSet xmlns="Tasks:">0</ReminderSet>
|
|
|
|
// <DueDate xmlns="Tasks:">2013-12-09T19:00:00.000Z</DueDate>
|
|
|
|
// </ApplicationData>
|
|
|
|
// </Add>
|
|
|
|
// </Commands>
|
|
|
|
// </Collection>
|
|
|
|
// </Collections>
|
|
|
|
// </Sync>
|
|
|
|
//
|
|
|
|
// The algorithm here is pretty simple:
|
|
|
|
//
|
|
|
|
// 1. extract the list of collections
|
|
|
|
// 2. for each collection
|
|
|
|
// 2.1. extract the metadata (id, synckey, etc.)
|
|
|
|
// 2.2. extract the list of commands
|
|
|
|
// 2.3. for each command
|
|
|
|
// 2.3.1 process the command (add/change/delete/fetch)
|
|
|
|
// 2.3.2 build a response during the processsing
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (void) processSync: (id <DOMElement>) theDocumentElement
|
|
|
|
inResponse: (WOResponse *) theResponse
|
|
|
|
{
|
2014-02-11 02:16:43 +01:00
|
|
|
SOGoSystemDefaults *defaults;
|
2014-01-10 20:12:53 +01:00
|
|
|
id <DOMElement> aCollection;
|
2014-02-04 17:19:33 +01:00
|
|
|
NSMutableString *output, *s;
|
2015-10-20 14:48:39 +02:00
|
|
|
NSMutableDictionary *globalMetadata;
|
|
|
|
NSNumber *syncRequestInCache, *processIdentifier;
|
|
|
|
NSString *key;
|
2014-02-11 02:16:43 +01:00
|
|
|
NSArray *allCollections;
|
2014-01-10 20:12:53 +01:00
|
|
|
NSData *d;
|
|
|
|
|
2016-03-30 20:31:57 +02:00
|
|
|
int i, j, defaultInterval, heartbeatInterval, internalInterval, maxSyncResponseSize, total_sleep, sleepInterval;
|
2014-02-04 17:19:33 +01:00
|
|
|
BOOL changeDetected;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
|
|
|
// We initialize our output buffer
|
2014-12-04 17:27:10 +01:00
|
|
|
output = [[NSMutableString alloc] init];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
defaults = [SOGoSystemDefaults sharedSystemDefaults];
|
|
|
|
defaultInterval = [defaults maximumSyncInterval];
|
2015-10-20 14:48:39 +02:00
|
|
|
processIdentifier = [NSNumber numberWithInt: [[NSProcessInfo processInfo] processIdentifier]];
|
|
|
|
|
|
|
|
allCollections = (id)[theDocumentElement getElementsByTagName: @"Collection"];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
[output appendString: @"<?xml version=\"1.0\" encoding=\"utf-8\"?>"];
|
|
|
|
[output appendString: @"<!DOCTYPE ActiveSync PUBLIC \"-//MICROSOFT//DTD ActiveSync//EN\" \"http://www.microsoft.com/\">"];
|
|
|
|
[output appendString: @"<Sync xmlns=\"AirSync:\">"];
|
2014-12-22 14:36:55 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// We don't support yet empty Sync requests. See: http://msdn.microsoft.com/en-us/library/ee203280(v=exchg.80).aspx
|
|
|
|
// We return '13' - see http://msdn.microsoft.com/en-us/library/gg675457(v=exchg.80).aspx
|
|
|
|
//
|
|
|
|
if (!theDocumentElement || [[(id)[theDocumentElement getElementsByTagName: @"Partial"] lastObject] textValue])
|
|
|
|
{
|
|
|
|
[output appendString: @"<Status>13</Status>"];
|
|
|
|
[output appendString: @"</Sync>"];
|
|
|
|
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
[theResponse setContent: d];
|
2015-10-14 15:21:32 +02:00
|
|
|
RELEASE(output);
|
2014-12-22 14:36:55 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-10-14 15:21:32 +02:00
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
// Let other requests know about the collections we are dealing with.
|
|
|
|
[self _setOrUnsetSyncRequest: YES collections: allCollections];
|
2014-02-11 02:16:43 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
changeDetected = NO;
|
|
|
|
maxSyncResponseSize = [[SOGoSystemDefaults sharedSystemDefaults] maximumSyncResponseSize];
|
2014-02-04 17:19:33 +01:00
|
|
|
heartbeatInterval = [[[(id)[theDocumentElement getElementsByTagName: @"HeartbeatInterval"] lastObject] textValue] intValue];
|
2014-02-11 02:16:43 +01:00
|
|
|
internalInterval = [defaults internalSyncInterval];
|
2016-03-30 20:31:57 +02:00
|
|
|
sleepInterval = (internalInterval < 5) ? internalInterval : 5;
|
2014-02-04 17:19:33 +01:00
|
|
|
|
2015-01-28 21:03:49 +01:00
|
|
|
// If the request doesn't contain "HeartbeatInterval" there is no reason to delay the response.
|
|
|
|
if (heartbeatInterval == 0)
|
|
|
|
heartbeatInterval = internalInterval = 1;
|
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
// We check to see if our heartbeat interval falls into the supported ranges.
|
2014-02-11 02:16:43 +01:00
|
|
|
if (heartbeatInterval > defaultInterval || heartbeatInterval < 1)
|
2014-02-04 17:19:33 +01:00
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
int limit;
|
2014-02-04 17:19:33 +01:00
|
|
|
// Interval is too long, inform the client.
|
2014-02-11 02:16:43 +01:00
|
|
|
heartbeatInterval = defaultInterval;
|
2014-02-04 17:19:33 +01:00
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
// When Status = 14, the Wait interval is specified in minutes while
|
|
|
|
// defaultInterval is specifed in seconds. Adjust accordinlgy.
|
|
|
|
limit = defaultInterval/60;
|
|
|
|
if (limit < 1) limit = 1;
|
|
|
|
if (limit > 59) limit = 59;
|
|
|
|
//[output appendFormat: @"<Limit>%d</Limit>", limit];
|
2014-02-04 17:19:33 +01:00
|
|
|
//[output appendFormat: @"<Status>%d</Status>", 14];
|
|
|
|
}
|
|
|
|
|
2016-06-06 19:28:42 +02:00
|
|
|
s = nil;
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2014-02-04 17:19:33 +01:00
|
|
|
// We enter our loop detection change
|
2015-01-28 21:03:49 +01:00
|
|
|
for (i = 0; i < (heartbeatInterval/internalInterval); i++)
|
2014-01-10 20:12:53 +01:00
|
|
|
{
|
2018-03-15 21:01:50 +01:00
|
|
|
// Terminate the process if we need to
|
2016-04-07 20:22:17 +02:00
|
|
|
if ([self easShouldTerminate])
|
2016-03-29 16:32:10 +02:00
|
|
|
break;
|
|
|
|
|
2018-03-15 21:01:50 +01:00
|
|
|
// We first check of any of the collections we want to sync are already
|
|
|
|
// in an other sync process. If that's the case, we do not do anything
|
|
|
|
// and we return immediately. So we'll let the other sync process terminate
|
2014-02-04 17:19:33 +01:00
|
|
|
for (j = 0; j < [allCollections count]; j++)
|
|
|
|
{
|
|
|
|
aCollection = [allCollections objectAtIndex: j];
|
2015-10-20 14:48:39 +02:00
|
|
|
globalMetadata = [self globalMetadataForDevice];
|
2018-03-15 21:01:50 +01:00
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
key = [NSString stringWithFormat: @"SyncRequest+%@", [[[(id)[aCollection getElementsByTagName: @"CollectionId"] lastObject] textValue] stringByUnescapingURL]];
|
2014-01-10 20:12:53 +01:00
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
if (!([[globalMetadata objectForKey: key] isEqual: processIdentifier]))
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
if (debugOn)
|
2015-10-20 14:48:39 +02:00
|
|
|
[self logWithFormat: @"EAS - Discard response %@", [self globalMetadataForDevice]];
|
2015-10-14 15:21:32 +02:00
|
|
|
|
2018-03-15 21:01:50 +01:00
|
|
|
[output appendString: @"<Status>13</Status>"];
|
|
|
|
[output appendString: @"</Sync>"];
|
|
|
|
d = [[output dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml];
|
|
|
|
[theResponse setContent: d];
|
2015-10-14 15:21:32 +02:00
|
|
|
RELEASE(output);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-03-15 21:01:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// We're good to go to sync the collections
|
|
|
|
s = [NSMutableString string];
|
|
|
|
|
|
|
|
for (j = 0; j < [allCollections count]; j++)
|
|
|
|
{
|
|
|
|
aCollection = [allCollections objectAtIndex: j];
|
|
|
|
|
|
|
|
[self processSyncCollection: aCollection
|
|
|
|
inBuffer: s
|
|
|
|
changeDetected: &changeDetected
|
|
|
|
maxSyncResponseSize: maxSyncResponseSize];
|
|
|
|
|
2015-10-20 14:48:39 +02:00
|
|
|
if ((maxSyncResponseSize > 0 && [s length] >= maxSyncResponseSize))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changeDetected)
|
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
[self logWithFormat: @"Change detected during Sync, we push the content."];
|
2014-02-04 17:19:33 +01:00
|
|
|
break;
|
|
|
|
}
|
2015-01-28 21:03:49 +01:00
|
|
|
else if (heartbeatInterval > 1)
|
2014-02-04 17:19:33 +01:00
|
|
|
{
|
2015-10-14 15:21:32 +02:00
|
|
|
total_sleep = 0;
|
|
|
|
|
2016-04-07 20:22:17 +02:00
|
|
|
while (![self easShouldTerminate] && total_sleep < internalInterval)
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
// We check if we must break the current synchronization since an other Sync
|
|
|
|
// has just arrived.
|
2015-10-20 14:48:39 +02:00
|
|
|
syncRequestInCache = [[self globalMetadataForDevice] objectForKey: @"SyncRequest"];
|
|
|
|
if (!([syncRequest isEqualToNumber: syncRequestInCache]))
|
2015-10-14 15:21:32 +02:00
|
|
|
{
|
|
|
|
if (debugOn)
|
|
|
|
[self logWithFormat: @"EAS - Heartbeat stopped %@", [self globalMetadataForDevice]];
|
|
|
|
|
|
|
|
// Make sure we end the heardbeat-loop.
|
|
|
|
heartbeatInterval = internalInterval = 1;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-09-29 19:00:48 +02:00
|
|
|
int t;
|
|
|
|
|
2017-09-22 16:36:01 +02:00
|
|
|
[self logWithFormat: @"Sleeping %d seconds while detecting changes for user %@ in Sync...", internalInterval-total_sleep, [[context activeUser] login]];
|
2016-09-29 19:00:48 +02:00
|
|
|
|
|
|
|
for (t = 0; t < sleepInterval; t++)
|
|
|
|
{
|
|
|
|
if ([self easShouldTerminate])
|
|
|
|
break;
|
|
|
|
sleep(1);
|
|
|
|
}
|
2016-03-30 20:31:57 +02:00
|
|
|
total_sleep += sleepInterval;
|
2015-10-14 15:21:32 +02:00
|
|
|
}
|
|
|
|
}
|
2014-02-04 17:19:33 +01:00
|
|
|
}
|
2015-01-28 21:03:49 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
2015-10-14 15:21:32 +02:00
|
|
|
//
|
|
|
|
// Only send a response if there are changes or MS-ASProtocolVersion is either 2.5 or 12.0,
|
|
|
|
// otherwise send an empty response.
|
|
|
|
//
|
2015-02-26 23:48:06 +01:00
|
|
|
if (changeDetected || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"] || [[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"12.0"])
|
2014-12-22 14:36:55 +01:00
|
|
|
{
|
2020-01-08 19:04:42 +01:00
|
|
|
[output appendString: @"<Collections>"];
|
|
|
|
|
2014-12-22 14:36:55 +01:00
|
|
|
// We always return the last generated response.
|
|
|
|
// If we only return <Sync><Collections/></Sync>,
|
|
|
|
// iOS powered devices will simply crash.
|
2016-06-06 19:28:42 +02:00
|
|
|
if (s)
|
|
|
|
[output appendString: s];
|
2014-12-22 14:36:55 +01:00
|
|
|
|
|
|
|
[output appendString: @"</Collections></Sync>"];
|
2014-02-17 17:28:06 +01:00
|
|
|
|
2014-12-22 14:36:55 +01:00
|
|
|
d = [output dataUsingEncoding: NSUTF8StringEncoding];
|
|
|
|
d = [d xml2wbxml];
|
|
|
|
[theResponse setContent: d];
|
|
|
|
}
|
2014-12-04 17:27:10 +01:00
|
|
|
|
|
|
|
// Avoid overloading the autorelease pool here, as Sync command can
|
|
|
|
// generate fairly large responses.
|
|
|
|
RELEASE(output);
|
2014-01-10 20:12:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|