diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 49cc21cee..3102e92e8 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -123,7 +123,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [o setTableUrl: [self folderTableURL]]; [o reloadIfNeeded]; - [[o properties] removeAllObjects]; + [[o properties] removeObjectForKey: @"SyncCache"]; + [[o properties] removeObjectForKey: @"DateCache"]; + [[o properties] addEntriesFromDictionary: theFolderMetadata]; [o save]; } @@ -627,21 +629,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sequence: [[[allMessages objectAtIndex: i] allValues] lastObject]]]; } - // If it's a new Sync operation, ignore anything we might have - // in our preferences. + // If it's a new Sync operation, DateCache and SyncCache need to be deleted + // but GUID stored by folderSync shouldn't be touched + folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]]; if ([theSyncKey isEqualToString: @"-1"]) { - folderMetadata = [NSMutableDictionary dictionary]; - [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"SyncCache"]; [folderMetadata setObject: [NSMutableDictionary dictionary] forKey: @"DateCache"]; - - // TODO - Generate GUID - //[folderMetadata setObject: @"FOO-BAR-BAZ" forKey: @"GUID"]; } - else - folderMetadata = [self _folderMetadataForKey: [theCollection nameInContainer]]; - + // Check whether GUID in cache is equal to the GUID from imap - this is to avoid cache corruptions if a folder has been renamed and a new folder + // with the same name has been created but folderSync has not yet updated the cache + if (!([[theCollection nameInContainer] isEqualToString: + [NSString stringWithFormat: @"folder%@", [self globallyUniqueIDToIMAPFolderName: [folderMetadata objectForKey: @"GUID"] type: theFolderType]]])) + { + NSLog(@"GUID mismatch don't sync now!"); + return; + } + syncCache = [folderMetadata objectForKey: @"SyncCache"]; dateCache = [folderMetadata objectForKey: @"DateCache"]; @@ -711,6 +715,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; [syncCache removeObjectForKey: [aCacheObject uid]]; + [dateCache removeObjectForKey: [aCacheObject uid]]; } else { @@ -773,13 +778,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //NSLog(@"skipping old deleted UID: %@", [aCacheObject uid]); } } + } - + if (more_available) [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; else [folderMetadata removeObjectForKey: @"MoreAvailable"]; + [self _setFolderMetadata: folderMetadata forKey: [theCollection nameInContainer]]; } // default: @@ -890,6 +897,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; collection = [self collectionFromId: realCollectionId type: folderType]; syncKey = davCollectionTag = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; @@ -988,6 +996,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. davCollectionTag = [collection davCollectionTag]; } + // Generate the response buffer [theBuffer appendString: @""]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.h b/ActiveSync/SOGoActiveSyncDispatcher.h index eda6a3dbf..d72073ad1 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.h +++ b/ActiveSync/SOGoActiveSyncDispatcher.h @@ -43,6 +43,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (id) collectionFromId: (NSString *) theCollectionId type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; +- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate + type: (SOGoMicrosoftActiveSyncFolderType) theFolderType; + - (NSException *) dispatchRequest: (id) theRequest inResponse: (id) theResponse context: (id) theContext; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 47ebec555..63e4b54ee 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -129,7 +129,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [super init]; folderTableURL = nil; - return self; } @@ -165,6 +164,30 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. return [o properties]; } +- (id) globallyUniqueIDToIMAPFolderName: (NSString *) theIdToTranslate + type: (SOGoMicrosoftActiveSyncFolderType) theFolderType +{ + if (theFolderType == ActiveSyncMailFolder) + { + SOGoMailAccounts *accountsFolder; + SOGoMailAccount *accountFolder; + SOGoUserFolder *userFolder; + NSDictionary *imapGUIDs; + + userFolder = [[context activeUser] homeFolderInContext: context]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + // Get the GUID of the IMAP folder + imapGUIDs = [accountFolder imapFolderGUIDs]; + + return [[imapGUIDs allKeysForObject: theIdToTranslate] objectAtIndex: 0]; + } + + return theIdToTranslate; +} + + // // // @@ -198,7 +221,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. userFolder = [[context activeUser] homeFolderInContext: context]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; currentFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; - + collection = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@", theCollectionId] inContext: context acquire: NO]; @@ -251,20 +274,45 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. inContext: context acquire: NO]; else - newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@/%@", [[parentId stringByUnescapingURL] substringFromIndex: 5], - [displayName stringByEncodingImap4FolderName]] - inContext: context - acquire: NO]; - + { + parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: ActiveSyncMailFolder]; + newFolder = [currentFolder lookupName: [NSString stringWithFormat: @"folder%@/%@", [parentId stringByEncodingImap4FolderName], + [displayName stringByEncodingImap4FolderName]] + inContext: context + acquire: NO]; + } + // FIXME // handle exists (status == 2) // handle right synckey if ([newFolder create]) { + SOGoMailAccount *accountFolder; + NSDictionary *imapGUIDs; + SOGoCacheGCSObject *o; + NSString *key; + nameInContainer = [newFolder nameInContainer]; // We strip the "folder" prefix nameInContainer = [nameInContainer substringFromIndex: 6]; + + // save new guid into cache + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + + // update GUID in cache + imapGUIDs = [accountFolder imapFolderGUIDs]; + + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], nameInContainer ]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + nameInContainer =[imapGUIDs objectForKey: nameInContainer]; + + [[o properties ] setObject: nameInContainer forKey: @"GUID"]; + [o save]; + nameInContainer = [[NSString stringWithFormat: @"mail/%@", nameInContainer] stringByEscapingURL]; } else @@ -345,9 +393,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSString *serverId; SOGoMicrosoftActiveSyncFolderType folderType; - serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType]; userFolder = [[context activeUser] homeFolderInContext: context]; accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; @@ -408,6 +456,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. int status; serverId = [[[(id)[theDocumentElement getElementsByTagName: @"ServerId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + serverId = [self globallyUniqueIDToIMAPFolderName: serverId type: folderType]; parentId = [[(id)[theDocumentElement getElementsByTagName: @"ParentId"] lastObject] textValue]; displayName = [[(id)[theDocumentElement getElementsByTagName: @"DisplayName"] lastObject] textValue]; @@ -427,7 +476,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } else { - error = [folderToUpdate renameTo: [NSString stringWithFormat: @"%@/%@", [[parentId stringByUnescapingURL] substringFromIndex: 5], + parentId = [self globallyUniqueIDToIMAPFolderName: [[parentId stringByUnescapingURL] substringFromIndex: 5] type: folderType]; + error = [folderToUpdate renameTo: [NSString stringWithFormat: @"%@/%@", [parentId stringByEncodingImap4FolderName], [displayName stringByEncodingImap4FolderName]]]; } @@ -445,7 +495,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // See http://msdn.microsoft.com/en-us/library/gg675615(v=exchg.80).aspx // we return '9' - we force a FolderSync - status = 9; + status = 1; [self _setFolderSyncKey: syncKey]; @@ -481,11 +531,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) processFolderSync: (id ) theDocumentElement inResponse: (WOResponse *) theResponse { + NSMutableDictionary *metadata; NSMutableString *s; NSString *syncKey; NSData *d; - + BOOL first_sync; int status; @@ -512,34 +563,107 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [s appendString: @""]; [s appendString: @""]; [s appendFormat: @"%d%@", status, syncKey]; - - // Initial sync, let's return the complete folder list - if (first_sync) + + if (status == 1) { SOGoMailAccounts *accountsFolder; SOGoMailAccount *accountFolder; SOGoUserFolder *userFolder; id currentFolder; - NSDictionary *folderMetadata; - NSArray *allFoldersMetadata; - NSString *name, *serverId, *parentId; + NSString *key, *cKey, *nkey, *name, *serverId, *parentId; + NSDictionary *folderMetadata, *imapGUIDs; + NSArray *allFoldersMetadata, *allKeys; + NSMutableDictionary *cachedGUIDs; + NSMutableString *commands; + SOGoCacheGCSObject *o; + + int i, type, command_count; - int i, type; - userFolder = [[context activeUser] homeFolderInContext: context]; - accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; - accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; + accountsFolder = [userFolder lookupName: @"Mail" inContext: context acquire: NO]; + accountFolder = [accountsFolder lookupName: @"0" inContext: context acquire: NO]; allFoldersMetadata = [accountFolder allFoldersMetadata]; + + // Get GUIDs of folder (IMAP) + // e.g. {INBOX = "sogo_73c_192bd57b_d8" + imapGUIDs = [accountFolder imapFolderGUIDs]; - // See 2.2.3.170.3 Type (FolderSync) - http://msdn.microsoft.com/en-us/library/gg650877(v=exchg.80).aspx - [s appendFormat: @"%d", [allFoldersMetadata count]+3]; + cachedGUIDs = [NSMutableDictionary dictionary]; + + // No need to read cached folder infos during first sync. Otherwise, pull it from the database. + // e.g. {"sogo_73c_192bd57b_d8" = INBOX} - guid = foldername for easy reverse lookup with imapGUIDs + if (!first_sync) + { + NSArray *foldersInCache; + NSString *folderName; + + // get the list of folder stored in cache + key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], @"0"]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + foldersInCache = [o folderList: [context objectForKey: @"DeviceId"] newerThanVersion: -1]; + + // get guids of folders stored in cache + for (i = 0; i < [foldersInCache count]; i++) + { + folderName = [foldersInCache objectAtIndex: i]; + key = [folderName substringFromIndex: 1]; + + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + if ([[o properties ] objectForKey: @"GUID"]) + [cachedGUIDs setObject: [key substringFromIndex: [key rangeOfString: @"+"].location+7] + forKey: [[o properties] objectForKey: @"GUID"]]; + } + } + + // Handle folders that have been deleted over IMAP + command_count = 0; + commands = [NSMutableString string]; + allKeys = [cachedGUIDs allKeys]; + for (i = 0; i < [allKeys count]; i++) + { + cKey = [allKeys objectAtIndex: i]; + + if (![imapGUIDs allKeysForObject: cKey]) + { + // Delete folders cache content to avoid stale data if a new folder gets created with the same name + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: cKey]]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + // Only send a delete command if GUID is found + if ([[o properties] objectForKey: @"GUID"]) + { + [commands appendFormat: @"%@", [[NSString stringWithFormat: @"mail/%@", [[o properties ] objectForKey: @"GUID"]] stringByEscapingURL] ]; + command_count++; + } + + [[o properties] removeAllObjects]; + [o save]; + } + } + + // Handle addition and changes for (i = 0; i < [allFoldersMetadata count]; i++) { folderMetadata = [allFoldersMetadata objectAtIndex: i]; - serverId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"path"]]; + + // No GUID -> no sync + if (!([imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]])) + continue; + + serverId = [NSString stringWithFormat: @"mail/%@", [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]; name = [folderMetadata objectForKey: @"displayName"]; if ([name hasPrefix: @"/"]) @@ -547,49 +671,105 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([name hasSuffix: @"/"]) name = [name substringToIndex: [name length]-1]; - + type = [[folderMetadata objectForKey: @"type"] activeSyncFolderType]; - parentId = @"0"; - + if ([folderMetadata objectForKey: @"parent"]) { - parentId = [NSString stringWithFormat: @"mail%@", [folderMetadata objectForKey: @"parent"]]; + parentId = [NSString stringWithFormat: @"mail/%@", [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"parent"] substringFromIndex: 1]]]; name = [[name pathComponents] lastObject]; } - - [s appendFormat: @"%@%@%d%@", - [serverId stringByEscapingURL], - [parentId stringByEscapingURL], - type, - [name activeSyncRepresentationInContext: context]]; + + // Decide between add and change + if ([cachedGUIDs objectForKey: [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]) + { + // Search GUID to check name change in cache (diff between IMAP and cache) + if ((![[[folderMetadata objectForKey: @"path"] substringFromIndex: 1] isEqualToString: [imapGUIDs objectForKey: [cachedGUIDs objectForKey: + [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]])) + { + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [cachedGUIDs objectForKey: + [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]]]; + nkey = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1] ]; + + if (![key isEqualToString: nkey]) + { + [commands appendFormat: @"%@%@%d%@", + [serverId stringByEscapingURL], + [parentId stringByEscapingURL], + type, + [name activeSyncRepresentationInContext: context]]; + + + // Change path in cache + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + [o changePathTo: [NSString stringWithFormat: @"/%@", nkey]]; // ?? why is '/' prefix needed - problem in changePathTo? + + command_count++; + } + } + } + else + { + [commands appendFormat: @"%@%@%d%@", + [serverId stringByEscapingURL], + [parentId stringByEscapingURL], + type, + [name activeSyncRepresentationInContext: context]]; + + // Store folder's GUID in cache + key = [NSString stringWithFormat: @"%@+folder%@", [context objectForKey: @"DeviceId"], [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]]; + o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; + [o setObjectType: ActiveSyncFolderCacheObject]; + [o setTableUrl: [self folderTableURL]]; + [o reloadIfNeeded]; + + [[o properties ] setObject: [imapGUIDs objectForKey: [[folderMetadata objectForKey: @"path"] substringFromIndex: 1]] forKey: @"GUID"]; + [o save]; + + command_count++; + } } - - // We add the personal calendar - events - // FIXME: add all calendars - currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; - name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]]; - [s appendFormat: @"%@%@%d%@", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; - - // We add the personal calendar - tasks - // FIXME: add all calendars - currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; - name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]]; - [s appendFormat: @"%@%@%d%@", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; - // We add the personal address book - // FIXME: add all address books - currentFolder = [[context activeUser] personalContactsFolderInContext: context]; - name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]]; - [s appendFormat: @"%@%@%d%@", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + if (first_sync) + [s appendFormat: @"%d", command_count+3]; + else + [s appendFormat: @"%d", command_count]; + + if (command_count > 0) + [s appendFormat: @"%@", commands]; + + if (first_sync) + { + // We add the personal calendar - events + // FIXME: add all calendars + currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; + name = [NSString stringWithFormat: @"vevent/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 8, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + + // We add the personal calendar - tasks + // FIXME: add all calendars + currentFolder = [[context activeUser] personalCalendarFolderInContext: context]; + name = [NSString stringWithFormat: @"vtodo/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 7, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + + // We add the personal address book + // FIXME: add all address books + currentFolder = [[context activeUser] personalContactsFolderInContext: context]; + name = [NSString stringWithFormat: @"vcard/%@", [currentFolder nameInContainer]]; + [s appendFormat: @"%@%@%d%@", name, @"0", 9, [[currentFolder displayName] activeSyncRepresentationInContext: context]]; + } } - + [s appendString: @""]; - + d = [[s dataUsingEncoding: NSUTF8StringEncoding] xml2wbxml]; - + [theResponse setContent: d]; -} +} // // From: http://msdn.microsoft.com/en-us/library/ee157980(v=exchg.80).aspx : @@ -636,6 +816,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. collectionId = [[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue]; realCollectionId = [collectionId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; currentCollection = [self collectionFromId: realCollectionId type: folderType]; // @@ -710,6 +891,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fileReference = [[[(id)[theDocumentElement getElementsByTagName: @"FileReference"] lastObject] textValue] stringByUnescapingURL]; realCollectionId = [fileReference realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; if (folderType == ActiveSyncMailFolder) { @@ -803,6 +985,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. status = 1; realCollectionId = [[[(id)[theDocumentElement getElementsByTagName: @"CollectionId"] lastObject] textValue] realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; userResponse = [[[(id)[theDocumentElement getElementsByTagName: @"UserResponse"] lastObject] textValue] intValue]; requestId = [[(id)[theDocumentElement getElementsByTagName: @"RequestId"] lastObject] textValue]; appointmentObject = nil; @@ -953,6 +1136,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSDictionary *response; NSString *v; + srcFolderId = [self globallyUniqueIDToIMAPFolderName: srcFolderId type: srcFolderType]; + dstFolderId = [self globallyUniqueIDToIMAPFolderName: dstFolderId type: dstFolderType]; + currentCollection = [self collectionFromId: srcFolderId type: srcFolderType]; client = [[currentCollection imap4Connection] client]; @@ -1496,6 +1682,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. folderId = [[(id)[theDocumentElement getElementsByTagName: @"FolderId"] lastObject] textValue]; itemId = [[(id)[theDocumentElement getElementsByTagName: @"ItemId"] lastObject] textValue]; realCollectionId = [folderId realCollectionIdWithFolderType: &folderType]; + realCollectionId = [self globallyUniqueIDToIMAPFolderName: realCollectionId type: folderType]; value = [theDocumentElement getElementsByTagName: @"ReplaceMime"]; diff --git a/NEWS b/NEWS index b7988e9aa..64bb8d524 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ Enhancements - updated French translation - added more sycned contact properties when using ActiveSync (#2775) - now possible to configure the default subscribed resource name using SOGoSubscriptionFolderFormat + - now handle server-side folder updates using ActiveSync (#2688) Bug fixes - fixed saved HTML content of draft when attaching a file diff --git a/SoObjects/Mailer/SOGoMailAccount.h b/SoObjects/Mailer/SOGoMailAccount.h index 2efc05fc1..7bff26e6f 100644 --- a/SoObjects/Mailer/SOGoMailAccount.h +++ b/SoObjects/Mailer/SOGoMailAccount.h @@ -84,6 +84,8 @@ typedef enum { - (NSArray *) allFolderPaths; - (NSArray *) allFoldersMetadata; +- (NSDictionary *) imapFolderGUIDs; + - (BOOL) isInDraftsFolder; /* special folders */ diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index 5f9a67c49..c4429b599 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -60,6 +60,8 @@ #import "SOGoUser+Mailer.h" #import "SOGoMailAccount.h" +#import + #define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav" @@ -659,6 +661,71 @@ static NSString *inboxFolderName = @"INBOX"; return password; } + +- (NSDictionary *) imapFolderGUIDs +{ + NSDictionary *result, *nresult, *folderData; + NSMutableDictionary *folders; + NGImap4Client *client; + SOGoUserDefaults *ud; + NSArray *folderList; + NSEnumerator *e; + NSString *guid; + id object; + + BOOL subscribedOnly; + + ud = [[context activeUser] userDefaults]; + subscribedOnly = [ud mailShowSubscribedFoldersOnly]; + + folderList = [[self imap4Connection] allFoldersForURL: [self imap4URL] + onlySubscribedFolders: subscribedOnly]; + + folders = [NSMutableDictionary dictionary]; + + client = [[self imap4Connection] client]; + result = [client annotation: @"*" entryName: @"/comment" attributeName: @"value.priv"]; + + if (![[result objectForKey: @"result"] boolValue]) + { + NSLog(@"IMAP annotation call failed."); + return folders; + } + + e = [folderList objectEnumerator]; + + while (object = [e nextObject]) + { + guid = [[[[result objectForKey: @"FolderList"] objectForKey: [object substringFromIndex: 1]] objectForKey: @"/comment"] objectForKey: @"value.priv"]; + + if (!guid) + { + guid = [[NSProcessInfo processInfo] globallyUniqueString]; + nresult = [client annotation: [object substringFromIndex: 1] entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid]; + + if (![[nresult objectForKey: @"result"] boolValue]) + { + // need to implement X-GUID query for Dovecot - this requires modification in SOPE to support following command: + // 1 list "" "*" return (status (x-guid)) -> this would avoid firing a command per folder to IMAP + //guid = [NSString stringWithFormat: @"%@-%@", [object substringFromIndex: 1], @"xxx" ]; + nresult = [client status: [object substringFromIndex: 1] flags: [NSArray arrayWithObject: @"x-guid"]]; + guid = [nresult objectForKey: @"x-guid"]; + if (!guid) + { + guid = [NSString stringWithFormat: @"%@", [object substringFromIndex: 1]]; + } + NSLog(@"tfu setannotation failed: %@", nresult ); + NSLog(@"tfu uniqueid folderid: %@", guid ); + } + } + + [folders setObject: guid forKey: [object substringFromIndex: 1]]; + } + + return folders; +} + + /* name lookup */ - (id) lookupName: (NSString *) _key diff --git a/SoObjects/SOGo/SOGoCacheGCSObject.h b/SoObjects/SOGo/SOGoCacheGCSObject.h index 8d26a499a..90f7d1ba1 100644 --- a/SoObjects/SOGo/SOGoCacheGCSObject.h +++ b/SoObjects/SOGo/SOGoCacheGCSObject.h @@ -67,6 +67,9 @@ typedef enum { - (NSDictionary *) lookupRecord: (NSString *) path newerThanVersion: (NSInteger) startVersion; +- (NSArray *) folderList: (NSString *) deviceId + newerThanVersion: (NSInteger) startVersion; + - (void) setObjectType: (SOGoCacheObjectType) newObjectType; - (SOGoCacheObjectType) objectType; /* message, fai, folder */ diff --git a/SoObjects/SOGo/SOGoCacheGCSObject.m b/SoObjects/SOGo/SOGoCacheGCSObject.m index bb2e6907b..03269d9d7 100644 --- a/SoObjects/SOGo/SOGoCacheGCSObject.m +++ b/SoObjects/SOGo/SOGoCacheGCSObject.m @@ -112,7 +112,7 @@ static EOAttribute *textColumn = nil; tableUrl = [container tableUrl]; [tableUrl retain]; if (!tableUrl) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"table url is not set for object '%@'", self]; } @@ -169,7 +169,7 @@ static EOAttribute *textColumn = nil; path = [NSMutableString stringWithFormat: @"/%@", nameInContainer]; if ([path rangeOfString: @"//"].location != NSNotFound) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"object path has not been properly set for" " folder '%@' (%@)", self, path]; @@ -190,7 +190,7 @@ static EOAttribute *textColumn = nil; - (NSCalendarDate *) creationDate { if (!initialized) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"record has not been initialized: %@", self]; return creationDate; @@ -199,7 +199,7 @@ static EOAttribute *textColumn = nil; - (NSCalendarDate *) lastModified { if (!initialized) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"record has not been initialized: %@", self]; return lastModified; @@ -350,7 +350,7 @@ static EOAttribute *textColumn = nil; EOAdaptor *adaptor; if ([path hasSuffix: @"/"]) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"path ends with a slash: %@", path]; tableName = [self tableName]; @@ -375,6 +375,47 @@ static EOAttribute *textColumn = nil; return record; } +// get a list of all folders +- (NSArray *) folderList: (NSString *) deviceId + newerThanVersion: (NSInteger) startVersion +{ + NSMutableArray *recordsOut; + NSArray *records; + NSString *tableName, *pathValue; + NSMutableString *sql; + EOAdaptor *adaptor; + NSUInteger count, max; + + if ([deviceId hasSuffix: @"/"]) + [NSException raise: @"SOGoCacheIOException" + format: @"path ends with a slash: %@", deviceId]; + + tableName = [self tableName]; + adaptor = [self tableChannelAdaptor]; + pathValue = [adaptor formatValue: [NSString stringWithFormat: @"/%@+folder%", deviceId] + forAttribute: textColumn]; + + /* query */ + sql = [NSMutableString stringWithFormat: + @"SELECT * FROM %@ WHERE c_path LIKE %@ AND c_deleted <> 1", + tableName, pathValue]; + if (startVersion > -1) + [sql appendFormat: @" AND c_version > %d", startVersion]; + + /* execution */ + records = [self performSQLQuery: sql]; + + max = [records count]; + recordsOut = [[NSMutableArray alloc] init]; + for (count = 0; count < max; count++) + { + [recordsOut addObject: [[records objectAtIndex: count] objectForKey: @"c_path"]]; + } + + return recordsOut; +} + + - (void) reloadIfNeeded { /* if object is uninitialized: reload without condition, otherwise, load if @@ -428,7 +469,7 @@ static EOAttribute *textColumn = nil; NSException *result; if (!initialized) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"record has not been initialized: %@", self]; cm = [GCSChannelManager defaultChannelManager]; @@ -447,7 +488,7 @@ static EOAttribute *textColumn = nil; lastModifiedValue = (NSInteger) [lastModified timeIntervalSince1970]; if (objectType == -1) - [NSException raise: @"MAPIStoreIOException" + [NSException raise: @"SOGoCacheIOException" format: @"object type has not been set for object '%@'", self];