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"
# import < Foundation / NSArray . h >
2014-12-04 17:27:10 +01:00
# import < Foundation / NSAutoreleasePool . h >
2014-01-10 20:12:53 +01:00
# import < Foundation / NSCalendarDate . h >
2014-05-15 21:03:24 +02:00
# import < Foundation / NSNull . h >
2014-01-10 20:12:53 +01:00
# import < Foundation / NSProcessInfo . h >
2014-11-14 15:13:14 +01:00
# import < Foundation / NSSortDescriptor . h >
2014-01-10 20:12:53 +01:00
# import < Foundation / NSTimeZone . h >
# import < Foundation / NSURL . h >
2014-05-15 21:03:24 +02:00
# import < Foundation / NSValue . h >
2014-01-10 20:12:53 +01:00
# import < NGObjWeb / NSException + HTTP . h >
# import < NGObjWeb / SoApplication . h >
# import < NGObjWeb / SoObject . h >
# import < NGObjWeb / WOContext . h >
# import < NGObjWeb / WOContext + SoObjects . h >
# import < NGObjWeb / WOCookie . h >
# import < NGObjWeb / WODirectAction . h >
# import < NGObjWeb / WORequest . h >
# import < NGObjWeb / WOResponse . h >
2014-02-03 16:24:33 +01:00
# import < NGCards / iCalCalendar . h >
2014-01-10 20:12:53 +01:00
# import < NGCards / iCalEntityObject . h >
# import < NGCards / iCalEvent . h >
2014-02-17 14:46:05 +01:00
# import < NGCards / iCalPerson . h >
2014-01-10 20:12:53 +01:00
# import < NGCards / iCalToDo . h >
# import < NGCards / NGVCard . h >
# 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 < DOM / DOMElement . h >
# import < DOM / DOMProtocols . h >
# import < EOControl / EOQualifier . h >
# import < SOGo / NSArray + DAV . h >
2014-03-19 16:31:54 +01:00
# import < SOGo / SOGoCache . h >
2014-01-10 20:12:53 +01:00
# import < SOGo / NSDictionary + DAV . h >
# import < SOGo / SOGoDAVAuthenticator . h >
# import < SOGo / SOGoDomainDefaults . h >
# import < SOGo / SOGoMailer . h >
2014-02-11 02:16:43 +01:00
# import < SOGo / SOGoSystemDefaults . h >
2014-01-10 20:12:53 +01:00
# import < SOGo / SOGoUser . h >
# import < SOGo / SOGoUserSettings . h >
2014-05-15 21:03:24 +02:00
# import < SOGo / SOGoCacheGCSObject . h >
2014-01-10 20:12:53 +01:00
# import < Appointments / SOGoAppointmentObject . h >
# import < Appointments / SOGoAppointmentFolder . h >
# import < Appointments / SOGoAppointmentFolders . h >
# import < Appointments / SOGoTaskObject . h >
# import < Contacts / SOGoContactGCSEntry . h >
# import < Contacts / SOGoContactGCSFolder . h >
# import < Contacts / SOGoContactFolders . h >
# import < Contacts / SOGoContactSourceFolder . h >
# import < Mailer / SOGoMailAccount . h >
# import < Mailer / SOGoMailAccounts . h >
2014-02-17 14:46:05 +01:00
# import < Mailer / SOGoMailFolder . h >
2014-01-10 20:12:53 +01:00
# import < Mailer / SOGoMailObject . h >
# import < Foundation / NSObject . h >
# import < Foundation / NSString . h >
# 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 "SOGoActiveSyncConstants.h"
# include "SOGoMailObject+ActiveSync.h"
2014-05-15 21:03:24 +02:00
# include "SOGoSyncCacheObject.h"
2014-01-10 20:12:53 +01:00
2014-02-04 17:19:33 +01:00
# include < unistd . h >
2014-01-10 20:12:53 +01:00
@ implementation SOGoActiveSyncDispatcher ( Sync )
2015-10-14 15:21:32 +02:00
- ( void ) _setOrUnsetSyncInProgress : ( BOOL ) set
invalidate : ( BOOL ) invalidate
{
SOGoCacheGCSObject * o ;
o = [ SOGoCacheGCSObject objectWithName : [ context objectForKey : @ "DeviceId" ] inContainer : nil useCache : NO ] ;
[ o setObjectType : ActiveSyncGlobalCacheObject ] ;
[ o setTableUrl : [ self folderTableURL ] ] ;
[ o reloadIfNeeded ] ;
if ( set )
if ( invalidate )
[ [ o properties ] setObject : [ NSCalendarDate date ] forKey : @ "InvalidateSyncInProgress" ] ;
else
[ [ o properties ] setObject : [ NSCalendarDate date ] forKey : @ "SyncInProgress" ] ;
else
{
[ [ o properties ] removeObjectForKey : @ "SyncInProgress" ] ;
[ [ o properties ] removeObjectForKey : @ "InvalidateSyncInProgress" ] ;
}
[ o save ] ;
}
2014-05-15 21:03:24 +02:00
- ( void ) _setFolderMetadata : ( NSDictionary * ) theFolderMetadata
forKey : ( NSString * ) theFolderKey
{
SOGoCacheGCSObject * o ;
2014-12-04 17:27:10 +01:00
NSDictionary * values ;
2014-05-15 21:03:24 +02:00
NSString * key ;
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" ] ;
2014-06-25 21:05:25 +02:00
[ [ o properties ] removeObjectForKey : @ "MoreAvailable" ] ;
2015-03-30 15:49:44 +02:00
[ [ o properties ] removeObjectForKey : @ "BodyPreferenceType" ] ;
2014-12-08 16:25:37 +01:00
[ [ o properties ] removeObjectForKey : @ "SuccessfulMoveItemsOps" ] ;
2015-09-09 16:20:31 +02:00
[ [ o properties ] removeObjectForKey : @ "InitialLoadSequence" ] ;
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 ;
}
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
inBuffer : ( NSMutableString * ) theBuffer
{
2014-11-14 15:13:14 +01:00
NSMutableDictionary * folderMetadata , * dateCache , * syncCache , * allValues ;
2014-01-10 20:12:53 +01:00
NSString * clientId , * serverId ;
2014-01-10 22:48:39 +01:00
NSArray * additions ;
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" ] ;
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
2014-01-10 21:29:57 +01:00
clientId = [ [ ( id ) [ anAddition getElementsByTagName : @ "ClientId" ] lastObject ] textValue ] ;
2014-01-10 22:48:39 +01:00
allValues = [ NSMutableDictionary dictionaryWithDictionary : [ [ ( id ) [ anAddition getElementsByTagName : @ "ApplicationData" ] lastObject ] applicationData ] ] ;
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 ] ;
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 .
if ( [ allValues objectForKey : @ "UID" ] )
2014-02-17 14:46:05 +01:00
serverId = [ allValues objectForKey : @ "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 ] ;
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 ] ;
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 :
{
2015-01-22 19:31:31 +01:00
// FIXME - what to do ?
[ self errorWithFormat : @ "Fatal error occured - tried to call -processSyncAddCommand: ... on a mail folder. We abort." ] ;
2014-02-17 14:46:05 +01:00
abort ( ) ;
2014-01-10 20:12:53 +01:00
}
}
2014-02-17 16:01:44 +01:00
[ o takeActiveSyncValues : allValues inContext : context ] ;
2014-02-11 02:16:43 +01:00
[ sogoObject setIsNew : is_new ] ;
2014-01-10 20:12:53 +01:00
[ sogoObject saveComponent : o ] ;
// Everything is fine , lets generate our response
[ theBuffer appendString : @ "<Add>" ] ;
[ theBuffer appendFormat : @ "<ClientId>%@</ClientId>" , clientId ] ;
[ theBuffer appendFormat : @ "<ServerId>%@</ServerId>" , serverId ] ;
[ theBuffer appendFormat : @ "<Status>%d</Status>" , 1 ] ;
[ theBuffer appendString : @ "</Add>" ] ;
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" ] ;
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
2014-11-14 15:13:14 +01:00
[ self _setFolderMetadata : folderMetadata forKey : [ self _getNameInCache : theCollection withType : theFolderType ] ] ;
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 > 36 C5 -52 B36280 -1 -27 B38F40 . 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
inBuffer : ( NSMutableString * ) theBuffer
{
NSDictionary * allChanges ;
NSString * serverId ;
NSArray * changes ;
id aChange , o , sogoObject ;
2015-07-22 15:46:06 +02:00
NSMutableDictionary * folderMetadata , * syncCache ;
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" ] ;
2014-01-10 20:12:53 +01:00
for ( i = 0 ; i < [ changes count ] ; i + + )
{
aChange = [ changes objectAtIndex : i ] ;
serverId = [ [ ( id ) [ aChange getElementsByTagName : @ "ServerId" ] lastObject ] textValue ] ;
allChanges = [ [ ( id ) [ aChange getElementsByTagName : @ "ApplicationData" ] lastObject ] applicationData ] ;
// 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 ] ] )
{
// FIXME - return status = = 8
continue ;
}
2014-01-10 20:12:53 +01:00
switch ( theFolderType )
{
case ActiveSyncContactFolder :
{
o = [ sogoObject vCard ] ;
2014-02-17 16:01:44 +01:00
[ o takeActiveSyncValues : allChanges inContext : context ] ;
2014-01-10 20:12:53 +01:00
[ sogoObject saveComponent : o ] ;
2015-07-22 15:46:06 +02:00
[ syncCache setObject : [ NSString stringWithFormat : @ "%f" , [ [ sogoObject lastModified ] timeIntervalSince1970 ] ] forKey : serverId ] ;
2014-01-10 20:12:53 +01:00
}
break ;
case ActiveSyncEventFolder :
case ActiveSyncTaskFolder :
{
o = [ sogoObject component : NO secure : NO ] ;
2014-02-17 16:01:44 +01:00
[ o takeActiveSyncValues : allChanges inContext : context ] ;
2014-01-10 20:12:53 +01:00
[ sogoObject saveComponent : o ] ;
2015-07-22 15:46:06 +02:00
[ syncCache setObject : [ NSString stringWithFormat : @ "%f" , [ [ sogoObject lastModified ] timeIntervalSince1970 ] ] forKey : serverId ] ;
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
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" ] ;
if ( modseq )
2015-10-14 15:21:32 +02: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>" ] ;
[ theBuffer appendFormat : @ "<ServerId>%@</ServerId>" , serverId ] ;
[ theBuffer appendFormat : @ "<Status>%d</Status>" , 1 ] ;
[ 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 > 2 CB5 -52 B36080 -1 -1 C1D0240 . ics < / ServerId >
// < / Delete >
// < / Commands >
// < / Collection >
// < / Collections >
// < / Sync >
//
- ( void ) processSyncDeleteCommand : ( id < DOMElement > ) theDocumentElement
inCollection : ( id ) theCollection
withType : ( SOGoMicrosoftActiveSyncFolderType ) theFolderType
inBuffer : ( NSMutableString * ) theBuffer
{
2015-06-22 15:19:54 +02:00
id aDelete , sogoObject , value ;
2014-01-10 20:12:53 +01:00
NSArray * deletions ;
NSString * serverId ;
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 ] )
{
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 ] ;
serverId = [ [ ( id ) [ aDelete getElementsByTagName : @ "ServerId" ] lastObject ] textValue ] ;
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-22 15:19:54 +02:00
{
// FIXME : handle errors here
2015-06-29 19:49:41 +02:00
if ( deletesAsMoves && theFolderType = = ActiveSyncMailFolder )
2015-06-22 15:19:54 +02:00
[ ( SOGoMailFolder * ) [ sogoObject container ] deleteUIDs : [ NSArray arrayWithObjects : serverId , nil ] useTrashFolder : & useTrash inContext : context ] ;
else
[ sogoObject delete ] ;
}
2014-10-29 16:13:18 +01:00
[ theBuffer appendString : @ "<Delete>" ] ;
[ theBuffer appendFormat : @ "<ServerId>%@</ServerId>" , serverId ] ;
[ theBuffer appendFormat : @ "<Status>%d</Status>" , 1 ] ;
[ theBuffer appendString : @ "</Delete>" ] ;
2014-11-14 15:13:14 +01:00
// update syncCache
NSMutableDictionary * folderMetadata , * dateCache , * syncCache ;
folderMetadata = [ self _folderMetadataForKey : [ self _getNameInCache : theCollection withType : theFolderType ] ] ;
syncCache = [ folderMetadata objectForKey : @ "SyncCache" ] ;
dateCache = [ folderMetadata objectForKey : @ "DateCache" ] ;
[ syncCache removeObjectForKey : serverId ] ;
[ dateCache removeObjectForKey : serverId ] ;
[ self _setFolderMetadata : folderMetadata forKey : [ self _getNameInCache : theCollection withType : theFolderType ] ] ;
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 ] ;
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
2014-01-10 20:12:53 +01:00
{
2014-06-25 21:05:25 +02:00
NSMutableDictionary * folderMetadata , * dateCache , * syncCache ;
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" ] ;
}
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 .
[ self logWithFormat : @ "Cache cleanup needed for device %@ - user: %@" , [ context objectForKey : @ "DeviceId" ] , [ [ context activeUser ] login ] ] ;
cleanup_needed = YES ;
}
2014-11-14 15:13:14 +01:00
syncCache = [ folderMetadata objectForKey : @ "SyncCache" ] ;
dateCache = [ folderMetadata objectForKey : @ "DateCache" ] ;
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 ] ;
}
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 .
//
2014-06-25 21:05:25 +02:00
if ( [ theSyncKey isEqualToString : [ theCollection davCollectionTag ] ] && ! ( [ s length ] ) )
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 ;
NSString * uid , * component_name ;
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" ] ;
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 ] ;
2014-11-14 15:13:14 +01:00
return_count = 0 ;
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 ] ;
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 : \ ">" ] ;
[ s appendFormat : @ "<ServerId xmlns=\" AirSync : \ ">%@</ServerId>" , uid ] ;
[ 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-11-14 15:13:14 +01:00
return_count + + ;
2014-12-22 17:50:51 +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
componentObject = [ sogoObject component : NO secure : NO ] ;
//
// We do NOT synchronize NEW events that are in fact , invitations
// to events . This is due to the fact that Outlook 2013 creates
// "phantom" events in the calendar that are mapped to invitations mails .
// If we synchronize these events too , it ' ll interfere with the whole thing
// and prevent Outlook from properly calling MeetingResponse .
//
if ( ! updated && theFolderType = = ActiveSyncEventFolder )
{
iCalPersonPartStat partstat ;
iCalPerson * attendee ;
NSString * email ;
email = [ [ [ context activeUser ] allEmails ] objectAtIndex : 0 ] ;
attendee = [ componentObject findAttendeeWithEmail : email ] ;
if ( attendee )
{
partstat = [ attendee participationStatus ] ;
if ( partstat = = iCalPersonPartStatNeedsAction )
2015-01-08 21:56:16 +01:00
{
DESTROY ( pool ) ;
continue ;
}
2014-02-17 14:46:05 +01:00
}
}
2014-11-14 15:13:14 +01:00
[ syncCache setObject : [ component objectForKey : @ "c_lastmodified" ] forKey : uid ] ;
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 ] ;
}
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
2014-01-22 17:02:12 +01:00
[ s appendFormat : @ "<ServerId xmlns=\" AirSync : \ ">%@</ServerId>" , uid ] ;
[ 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-14 15:21:32 +02:00
NSString * lastSequence , * 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 ;
lastSequence = nil ;
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" ] ;
}
else
{
a = [ theSyncKey componentsSeparatedByString : @ "-" ] ;
highestmodseq = [ [ a objectAtIndex : 1 ] intValue ] ;
}
if ( [ folderMetadata objectForKey : @ "InitialLoadSequence" ] )
{
if ( highestmodseq < [ [ folderMetadata objectForKey : @ "InitialLoadSequence" ] intValue ] )
initialLoadInProgress = YES ;
else
[ folderMetadata removeObjectForKey : @ "InitialLoadSequence" ] ;
}
2014-03-06 20:16:08 +01:00
2015-09-09 16:20:31 +02: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 + + )
{
if ( debugOn )
[ self logWithFormat : @ "EAS - Cache cleanup: ADD %d" , j ] ;
[ syncCache removeObjectForKey : [ NSString stringWithFormat : @ "%d" , j ] ] ;
[ dateCache removeObjectForKey : [ NSString stringWithFormat : @ "%d" , j ] ] ;
}
RELEASE ( sortedByUID ) ;
for ( j = 0 ; j < [ allCacheObjects count ] ; j + + )
{
// Update the modseq in cache , sence othersie , it would be identical to the modseq from server
// and we would skip the cache when generating the response .
if ( [ syncCache objectForKey : [ [ allCacheObjects objectAtIndex : j ] uid ] ] && ! [ [ [ allCacheObjects objectAtIndex : j ] sequence ] isEqual : [ NSNull null ] ] )
{
if ( debugOn )
[ self logWithFormat : @ "EAS - Cache cleanup: CHANGE %@" , [ [ allCacheObjects objectAtIndex : j ] uid ] ] ;
[ syncCache setObject : @ "0" forKey : [ [ allCacheObjects objectAtIndex : j ] uid ] ] ;
}
else if ( [ [ [ allCacheObjects objectAtIndex : j ] sequence ] isEqual : [ NSNull null ] ] )
{
if ( debugOn )
[ self logWithFormat : @ "EAS - Cache cleanup: DELETE %@" , [ [ allCacheObjects objectAtIndex : j ] uid ] ] ;
// For deletes we have to recreate a cache entry to have the < Delete > included in the response .
[ syncCache setObject : @ "0" forKey : [ [ allCacheObjects objectAtIndex : j ] uid ] ] ;
}
}
}
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 ] ] ) ||
( [ [ allCacheObjects objectAtIndex : j ] sequence ] && ! [ syncCache objectForKey : [ [ allCacheObjects objectAtIndex : j ] uid ] ] ) )
{
// We need to continue with adds or deletes from here .
found_in _cache = YES ;
j - - ;
break ;
}
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 ;
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
{
2014-05-15 21:03:24 +02:00
more_available = YES ;
2015-10-14 15:21:32 +02:00
if ( ! firstUidAdded )
{
a = [ davCollectionTagToStore componentsSeparatedByString : @ "-" ] ;
firstUidAdded = [ a objectAtIndex : 0 ] ;
RETAIN ( firstUidAdded ) ;
}
2015-09-09 16:20:31 +02:00
lastSequence = ( [ [ aCacheObject sequence ] isEqual : [ NSNull null ] ] ? [ NSString stringWithFormat : @ "%d" , highestmodseq ] : [ aCacheObject sequence ] ) ;
2015-10-14 15:21:32 +02:00
// RETAIN ( lastSequence ) ;
* theLastServerKey = [ [ NSString alloc ] initWithFormat : @ "%@-%@" , firstUidAdded , lastSequence ] ;
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>" ] ;
[ 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 .
if ( ! firstUidAdded )
{
firstUidAdded = [ aCacheObject uid ] ;
RETAIN ( firstUidAdded ) ;
if ( debugOn )
[ self logWithFormat : @ "EAS - first uid added %@" , firstUidAdded ] ;
}
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 : lastSequence forKey : @ "MoreAvailable" ] ;
[ folderMetadata setObject : [ NSNumber numberWithInt : YES ] forKey : @ "MoreAvailable" ] ;
2014-06-10 17:04:27 +02:00
[ folderMetadata setObject : * theLastServerKey forKey : @ "SyncKey" ] ;
2015-10-14 15:21:32 +02:00
// RELEASE ( lastSequence ) ;
2014-06-10 17:04:27 +02:00
}
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
if ( firstUidAdded )
{
a = [ davCollectionTagToStore componentsSeparatedByString : @ "-" ] ;
[ folderMetadata setObject : [ [ NSString alloc ] initWithFormat : @ "%@-%@" , firstUidAdded , [ a objectAtIndex : 1 ] ] forKey : @ "SyncKey" ] ;
}
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
RELEASE ( firstUidAdded ) ;
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
processed : ( BOOL * ) processed
{
id < DOMNodeList > aCommandDetails ;
id < DOMElement > aCommand , element ;
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 + + )
{
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
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
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
}
}
}
}
}
//
//
//
- ( 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
{
2015-10-14 15:21:32 +02:00
NSString * collectionId , * realCollectionId , * syncKey , * davCollectionTag , * bodyPreferenceType , * mimeSupport , * lastServerKey , * syncKeyInCache , * folderKey ;
NSMutableDictionary * folderMetadata , * folderOptions ;
2014-01-28 19:51:21 +01:00
NSMutableString * changeBuffer , * commandsBuffer ;
2015-10-14 15:21:32 +02:00
id collection , value ;
SOGoMicrosoftActiveSyncFolderType folderType ;
2014-11-14 15:13:14 +01:00
unsigned int windowSize , v , status ;
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 ] ;
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 ;
if ( [ syncKey isEqualToString : @ "0" ] )
{
davCollectionTag = @ "-1" ;
first_sync = YES ;
2014-02-04 17:19:33 +01:00
* changeDetected = YES ;
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 )
2015-03-30 15:49:44 +02:00
{
bodyPreferenceType = [ [ folderMetadata objectForKey : @ "FolderOptions" ] objectForKey : @ "BodyPreferenceType" ] ;
// By default , send MIME mails . See #3146 for details .
if ( ! bodyPreferenceType )
bodyPreferenceType = @ "4" ;
}
else
{
mimeSupport = [ [ ( id ) [ theDocumentElement getElementsByTagName : @ "MIMESupport" ] lastObject ] textValue ] ;
if ( ! mimeSupport )
mimeSupport = [ [ folderMetadata objectForKey : @ "FolderOptions" ] objectForKey : @ "MIMESupport" ] ;
if ( ! mimeSupport )
mimeSupport = @ "0" ;
if ( [ mimeSupport isEqualToString : @ "1" ] && [ bodyPreferenceType isEqualToString : @ "4" ] )
bodyPreferenceType = @ "2" ;
else if ( [ mimeSupport isEqualToString : @ "2" ] && [ bodyPreferenceType isEqualToString : @ "4" ] )
bodyPreferenceType = @ "4" ;
else if ( [ mimeSupport isEqualToString : @ "0" ] && [ bodyPreferenceType isEqualToString : @ "4" ] )
bodyPreferenceType = @ "2" ;
// 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 ] )
{
folderOptions = [ [ NSDictionary alloc ] initWithObjectsAndKeys : mimeSupport , @ "MIMESupport" , bodyPreferenceType , @ "BodyPreferenceType" , nil ] ;
[ folderMetadata setObject : folderOptions forKey : @ "FolderOptions" ] ;
2015-10-14 15:21:32 +02:00
[ self _setFolderMetadata : folderMetadata forKey : folderKey ] ;
2015-03-30 15:49:44 +02:00
}
}
2014-01-10 20:12:53 +01:00
[ context setObject : bodyPreferenceType forKey : @ "BodyPreferenceType" ] ;
//
// We process the commands from the request
//
if ( ! first_sync )
{
NSMutableString * s ;
BOOL processed ;
s = [ NSMutableString string ] ;
processed = NO ;
[ self processSyncCommands : theDocumentElement
inCollection : collection
withType : folderType
inBuffer : s
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 ] )
2014-01-28 19:51:21 +01:00
[ commandsBuffer appendFormat : @ "<Responses>%@</Responses>" , s ] ;
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
lastServerKey : & lastServerKey ] ;
}
2015-10-14 15:21:32 +02:00
folderMetadata = [ self _folderMetadataForKey : folderKey ] ;
2015-01-28 21:03:49 +01:00
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 / 040 b254e - f47e -4 cc1 - a397 -6 d8393cdb819 / 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>" ] ;
}
//
// 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 -09 T19 : 00 : 00.000 Z < / UTCDueDate >
// < Complete xmlns = "Tasks:" > 0 < / Complete >
// < ReminderSet xmlns = "Tasks:" > 0 < / ReminderSet >
// < DueDate xmlns = "Tasks:" > 2013 -12 -09 T19 : 00 : 00.000 Z < / 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 ;
2014-02-11 02:16:43 +01:00
NSArray * allCollections ;
2014-01-10 20:12:53 +01:00
NSData * d ;
2015-10-14 15:21:32 +02:00
int i , j , defaultInterval , heartbeatInterval , internalInterval , maxSyncResponseSize , total_sleep ;
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 ] ;
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
if ( [ [ self globalMetadataForDevice ] objectForKey : @ "SyncInProgress" ] )
{
NSCalendarDate * syncStartDate ;
// An other sync is going on right now . Lets break it and wait
// until the other process clears it up .
[ self _setOrUnsetSyncInProgress : YES invalidate : YES ] ;
total_sleep = 0 ;
syncStartDate = [ [ self globalMetadataForDevice ] objectForKey : @ "SyncInProgress" ] ;
while ( [ [ self globalMetadataForDevice ] objectForKey : @ "SyncInProgress" ] )
{
// Don ' t go into a heartbeat loop .
heartbeatInterval = 0 ;
// We waited too long . Return a fatal error to the client .
if ( abs ( [ syncStartDate timeIntervalSinceNow ] ) > defaultInterval )
{
if ( debugOn )
[ self logWithFormat : @ "EAS - We waited too long. syncStartDate: %@ %@" , syncStartDate , [ self globalMetadataForDevice ] ] ;
[ theResponse setStatus : 503 ] ;
[ self _setOrUnsetSyncInProgress : NO invalidate : NO ] ;
RELEASE ( output ) ;
return ;
}
if ( debugOn )
[ self logWithFormat : @ "EAS - globalMetadataForDevice %@" , [ self globalMetadataForDevice ] ] ;
[ self logWithFormat : @ "Sync in progress for device %@ (login: %@). Lock will expire in %d seconds" , [ context objectForKey : @ "DeviceId" ] , [ [ context activeUser ] login ] , defaultInterval - total_sleep ] ;
sleep ( 5 ) ;
total_sleep + = 5 ;
}
}
// No lock or broke the existing one , lets grab it and proceed
[ self _setOrUnsetSyncInProgress : YES invalidate : NO ] ;
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 ] ;
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 ] ;
}
[ output appendString : @ "<Collections>" ] ;
2014-01-13 22:24:15 +01:00
allCollections = ( id ) [ theDocumentElement getElementsByTagName : @ "Collection" ] ;
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
{
2014-02-04 17:19:33 +01:00
s = [ NSMutableString string ] ;
for ( j = 0 ; j < [ allCollections count ] ; j + + )
{
aCollection = [ allCollections objectAtIndex : j ] ;
2015-10-14 15:21:32 +02:00
2014-12-22 17:50:51 +01:00
[ self processSyncCollection : aCollection
inBuffer : s
changeDetected : & changeDetected
maxSyncResponseSize : maxSyncResponseSize ] ;
2015-05-04 15:11:52 +02:00
if ( maxSyncResponseSize > 0 && [ s length ] >= maxSyncResponseSize )
break ;
2014-02-04 17:19:33 +01:00
}
2014-01-10 20:12:53 +01:00
2014-02-04 17:19:33 +01:00
if ( changeDetected )
{
2015-10-14 15:21:32 +02:00
// Don ' t return a response if an other Sync is waiting and this is a heartbeat request .
if ( [ [ self globalMetadataForDevice ] objectForKey : @ "InvalidateSyncInProgress" ] && heartbeatInterval > 1 )
{
if ( debugOn )
[ self logWithFormat : @ "EAS - Heartbeat stopped - discard response %@" , [ self globalMetadataForDevice ] ] ;
[ theResponse setStatus : 503 ] ;
[ self _setOrUnsetSyncInProgress : NO invalidate : NO ] ;
RELEASE ( output ) ;
return ;
}
[ self logWithFormat : @ "Change detected during Sync, we push the content." ] ;
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 ;
while ( total_sleep < internalInterval )
{
// We check if we must break the current synchronization since an other Sync
// has just arrived .
if ( [ [ self globalMetadataForDevice ] objectForKey : @ "InvalidateSyncInProgress" ] )
{
if ( debugOn )
[ self logWithFormat : @ "EAS - Heartbeat stopped %@" , [ self globalMetadataForDevice ] ] ;
// Make sure we end the heardbeat - loop .
heartbeatInterval = internalInterval = 1 ;
break ;
}
else
{
[ self logWithFormat : @ "Sleeping %d seconds while detecting changes in Sync..." , internalInterval - total_sleep ] ;
sleep ( 5 ) ;
total_sleep + = 5 ;
}
}
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
{
// We always return the last generated response .
// If we only return < Sync > < Collections / > < / Sync > ,
// iOS powered devices will simply crash .
[ output appendString : s ] ;
[ 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 ) ;
2015-10-14 15:21:32 +02:00
[ self _setOrUnsetSyncInProgress : NO invalidate : NO ] ;
2014-01-10 20:12:53 +01:00
}
@ end