diff --git a/ChangeLog b/ChangeLog index e7c4c7ca5..f9b0f8c5d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,27 @@ 2011-07-27 Wolfgang Sourdeau + * OpenChange/MAPIStoreMailMessageTable.m + (-evaluatePropertyRestriction:intoQualifier:): now handles + PR_CHANGE_NUM using the new methods below. + + * OpenChange/MAPIStoreMailMessage.m (-objectVersion): overriden + method to make use of -[MAPIStoreMailFolder + changeNumberForMessageUID:]. + + * OpenChange/MAPIStoreMailFolder.m (-initWithURL:inContext:) + (initWithSOGoObject:inContainer:): initialise the new + "versionsMessage" ivar, pointing to a SOGoMAPIFSMessage object + that includes versioning information. + (-lastMessageModificationTime): return the date value stored in + "versionsMessage". + (-synchroniseCache): new method that updates "versionsMessage" + differentually with versioning data from the server. + (-modseqFromMessageChangeNumber:): new helper method that returns + the corresponding IMAP "modseq" from a MAPI change number. + (-messageUIDFromMessageKey:): new helper method. + (-changeNumberForMessageUID:): new helper method that returns the + version number associated with a message. + * OpenChange/MAPIStoreTable.m (-loggingPrefix): overriden method in order to get more useful informations. diff --git a/OpenChange/MAPIStoreMailFolder.h b/OpenChange/MAPIStoreMailFolder.h index 12440cb4e..d992d565b 100644 --- a/OpenChange/MAPIStoreMailFolder.h +++ b/OpenChange/MAPIStoreMailFolder.h @@ -25,6 +25,8 @@ #import "MAPIStoreFolder.h" +@class NSNumber; + @class WOContext; @class SOGoMailAccount; @@ -35,6 +37,7 @@ @interface MAPIStoreMailFolder : MAPIStoreFolder { MAPIStoreMailMessageTable *messageTable; + SOGoMAPIFSMessage *versionsMessage; } /* subclasses */ @@ -42,6 +45,12 @@ inContext: (WOContext *) woContext; +/* synchronisation & versioning */ +- (BOOL) synchroniseCache; +- (NSNumber *) modseqFromMessageChangeNumber: (NSNumber *) changeNum; +- (NSNumber *) messageUIDFromMessageKey: (NSString *) messageKey; +- (NSNumber *) changeNumberForMessageUID: (NSNumber *) messageUid; + @end @interface MAPIStoreInboxFolder : MAPIStoreMailFolder diff --git a/OpenChange/MAPIStoreMailFolder.m b/OpenChange/MAPIStoreMailFolder.m index c2bc55294..84df178ad 100644 --- a/OpenChange/MAPIStoreMailFolder.m +++ b/OpenChange/MAPIStoreMailFolder.m @@ -20,12 +20,16 @@ * Boston, MA 02111-1307, USA. */ +#include + #import #import +#import #import #import #import #import +#import #import #import #import @@ -38,12 +42,14 @@ #import #import "MAPIApplication.h" +#import "MAPIStoreAppointmentWrapper.h" #import "MAPIStoreContext.h" #import "MAPIStoreDraftsMessage.h" #import "MAPIStoreMailMessage.h" #import "MAPIStoreMailMessageTable.h" #import "MAPIStoreTypes.h" #import "NSString+MAPIStore.h" +#import "SOGoMAPIFSMessage.h" #import "MAPIStoreMailFolder.h" @@ -61,6 +67,7 @@ static Class SOGoMailFolderK; { MAPIStoreMailMessageK = [MAPIStoreMailMessage class]; SOGoMailFolderK = [SOGoMailFolder class]; + [MAPIStoreAppointmentWrapper class]; } - (id) initWithURL: (NSURL *) newURL @@ -103,7 +110,27 @@ static Class SOGoMailFolderK; currentContainer = [currentContainer container]; } - [self logWithFormat: @"sogoObject: %@", sogoObject]; + ASSIGN (versionsMessage, + [SOGoMAPIFSMessage objectWithName: @"versions.plist" + inContainer: propsFolder]); + } + + return self; +} + +- (id) initWithSOGoObject: (id) newSOGoObject + inContainer: (MAPIStoreObject *) newContainer +{ + NSURL *propsURL; + NSString *urlString; + + if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer])) + { + urlString = [[self url] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; + propsURL = [NSURL URLWithString: urlString]; + ASSIGN (versionsMessage, + [SOGoMAPIFSMessage objectWithName: @"versions.plist" + inContainer: propsFolder]); } return self; @@ -111,6 +138,7 @@ static Class SOGoMailFolderK; - (void) dealloc { + [versionsMessage release]; [messageTable release]; [super dealloc]; } @@ -126,7 +154,10 @@ static Class SOGoMailFolderK; - (MAPIStoreMessageTable *) messageTable { if (!messageTable) - ASSIGN (messageTable, [MAPIStoreMailMessageTable tableForContainer: self]); + { + [self synchroniseCache]; + ASSIGN (messageTable, [MAPIStoreMailMessageTable tableForContainer: self]); + } return messageTable; } @@ -276,7 +307,198 @@ static Class SOGoMailFolderK; - (NSDate *) lastMessageModificationTime { - return [sogoObject mostRecentMessageDate]; + NSNumber *ti; + NSDate *value = nil; + + ti = [[versionsMessage properties] + objectForKey: @"SyncLastSynchronisationDate"]; + if (ti) + value = [NSDate dateWithTimeIntervalSince1970: [ti doubleValue]]; + else + value = [NSDate date]; + + [self logWithFormat: @"lastMessageModificationTime: %@", value]; + + return value; +} + +/* synchronisation */ + +/* Tree: +{ + SyncLastModseq = x; + SyncLastSynchronisationDate = x; ** not updated until something changed + Messages = { + MessageKey = { + Version = x; + Modseq = x; + Deleted = b; + }; + ... + }; + VersionMapping = { + Version = MessageKey; + ... + } +} +*/ + +static NSComparisonResult +_compareFetchResultsByMODSEQ (id entry1, id entry2, void *data) +{ + static NSNumber *zeroNumber = nil; + NSNumber *modseq1, *modseq2; + + if (!zeroNumber) + zeroNumber = [NSNumber numberWithUnsignedLongLong: 0]; + + modseq1 = [entry1 objectForKey: @"modseq"]; + if (!modseq1) + modseq1 = zeroNumber; + modseq2 = [entry2 objectForKey: @"modseq"]; + if (!modseq2) + modseq2 = zeroNumber; + + return [modseq1 compare: modseq2]; +} + +- (BOOL) synchroniseCache +{ + BOOL rc = YES; + uint64_t newChangeNum; + NSNumber *ti, *changeNumber, *modseq, *lastModseq, *nextModseq, *uid; + EOQualifier *searchQualifier; + NSArray *uids; + NSUInteger count, max; + NSArray *fetchResults; + NSDictionary *result; + NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry; + NSCalendarDate *now; + + now = [NSCalendarDate date]; + [now setTimeZone: utcTZ]; + + currentProperties = [[versionsMessage properties] mutableCopy]; + if (!currentProperties) + currentProperties = [NSMutableDictionary new]; + [currentProperties autorelease]; + messages = [currentProperties objectForKey: @"Messages"]; + if (!messages) + { + messages = [NSMutableDictionary new]; + [currentProperties setObject: messages forKey: @"Messages"]; + [messages release]; + } + mapping = [currentProperties objectForKey: @"VersionMapping"]; + if (!mapping) + { + mapping = [NSMutableDictionary new]; + [currentProperties setObject: mapping forKey: @"VersionMapping"]; + [mapping release]; + } + + lastModseq = [currentProperties objectForKey: @"SyncLastModseq"]; + if (lastModseq) + { + nextModseq = [NSNumber numberWithUnsignedLongLong: + [lastModseq unsignedLongLongValue] + 1]; + searchQualifier = [[EOKeyValueQualifier alloc] + initWithKey: @"modseq" + operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo + value: nextModseq]; + [searchQualifier autorelease]; + } + else + searchQualifier = [self nonDeletedQualifier]; + + uids = [sogoObject fetchUIDsMatchingQualifier: searchQualifier + sortOrdering: nil]; + max = [uids count]; + if (max > 0) + { + fetchResults + = [(NSDictionary *) [sogoObject fetchUIDs: uids + parts: [NSArray arrayWithObject: @"modseq"]] + objectForKey: @"fetch"]; + + /* NOTE: we sort items manually because Cyrus does not properly sort + entries with a MODSEQ of 0 */ + fetchResults + = [fetchResults sortedArrayUsingFunction: _compareFetchResultsByMODSEQ + context: NULL]; + for (count = 0; count < max; count++) + { + result = [fetchResults objectAtIndex: count]; + uid = [result objectForKey: @"uid"]; + modseq = [result objectForKey: @"modseq"]; + [self logWithFormat: @"uid '%@' has modseq '%@'", uid, modseq]; + newChangeNum = [[self context] getNewChangeNumber]; + changeNumber = [NSNumber numberWithUnsignedLongLong: newChangeNum]; + + messageEntry = [NSMutableDictionary new]; + [messages setObject: messageEntry forKey: uid]; + [messageEntry release]; + + [messageEntry setObject: modseq forKey: @"modseq"]; + [messageEntry setObject: changeNumber forKey: @"version"]; + + [mapping setObject: modseq forKey: changeNumber]; + + if (!lastModseq + || ([lastModseq compare: modseq] == NSOrderedAscending)) + lastModseq = modseq; + } + + ti = [NSNumber numberWithDouble: [now timeIntervalSince1970]]; + [currentProperties setObject: ti + forKey: @"SyncLastSynchronisationDate"]; + [currentProperties setObject: lastModseq forKey: @"SyncLastModseq"]; + [versionsMessage appendProperties: currentProperties]; + [versionsMessage save]; + } + + return rc; +} + +- (NSNumber *) modseqFromMessageChangeNumber: (NSNumber *) changeNum +{ + NSDictionary *mapping; + NSNumber *modseq; + + mapping = [[versionsMessage properties] objectForKey: @"VersionMapping"]; + modseq = [mapping objectForKey: changeNum]; + + return modseq; +} + +- (NSNumber *) messageUIDFromMessageKey: (NSString *) messageKey +{ + NSNumber *messageUid; + NSString *uidString; + NSRange dotRange; + + dotRange = [messageKey rangeOfString: @".eml"]; + if (dotRange.location != NSNotFound) + { + uidString = [messageKey substringToIndex: dotRange.location]; + messageUid = [NSNumber numberWithInt: [uidString intValue]]; + } + else + messageUid = nil; + + return messageUid; +} + +- (NSNumber *) changeNumberForMessageUID: (NSNumber *) messageUid +{ + NSDictionary *messages; + NSNumber *changeNumber; + + messages = [[versionsMessage properties] objectForKey: @"Messages"]; + changeNumber = [[messages objectForKey: messageUid] + objectForKey: @"version"]; + + return changeNumber; } @end diff --git a/OpenChange/MAPIStoreMailMessage.m b/OpenChange/MAPIStoreMailMessage.m index 36df4785c..1127037ac 100644 --- a/OpenChange/MAPIStoreMailMessage.m +++ b/OpenChange/MAPIStoreMailMessage.m @@ -266,6 +266,28 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) return appointmentWrapper; } +- (uint64_t) objectVersion +{ + uint64_t version = 0xffffffffffffffffLL; + NSNumber *uid, *changeNumber; + + uid = [(MAPIStoreMailFolder *) + container messageUIDFromMessageKey: [self nameInContainer]]; + if (uid) + { + changeNumber = [(MAPIStoreMailFolder *) + container changeNumberForMessageUID: uid]; + if (changeNumber) + version = [changeNumber unsignedLongLongValue] >> 16; + else + abort (); + } + else + abort (); + + return version; +} + - (int) getPrIconIndex: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { @@ -1135,8 +1157,8 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) } } -- (NSArray *) attachmentsKeysMatchingQualifier: (EOQualifier *) qualifier - andSortOrderings: (NSArray *) sortOrderings +- (NSArray *) attachmentKeysMatchingQualifier: (EOQualifier *) qualifier + andSortOrderings: (NSArray *) sortOrderings { [self _fetchAttachmentPartsInBodyInfo: [sogoObject bodyStructure] withPrefix: @""]; diff --git a/OpenChange/MAPIStoreMailMessageTable.m b/OpenChange/MAPIStoreMailMessageTable.m index d21c5c2c1..c68bbdf63 100644 --- a/OpenChange/MAPIStoreMailMessageTable.m +++ b/OpenChange/MAPIStoreMailMessageTable.m @@ -72,6 +72,12 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK; return self; } +- (void) cleanupCaches +{ + [(MAPIStoreMailFolder *) container synchroniseCache]; + [super cleanupCaches]; +} + - (NSString *) backendIdentifierForProperty: (enum MAPITAGS) property { static NSMutableDictionary *knownProperties = nil; @@ -97,6 +103,7 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK; { MAPIRestrictionState rc; id value; + NSNumber *modseq; value = NSObjectFromMAPISPropValue (&res->lpProp); switch ((uint32_t) res->ulPropTag) @@ -143,6 +150,25 @@ static Class MAPIStoreMailMessageK, NSDataK, NSStringK; case PR_CONVERSATION_KEY: rc = MAPIRestrictionStateAlwaysFalse; break; + + case PR_CHANGE_NUM: + { + modseq = [(MAPIStoreMailFolder *) + container modseqFromMessageChangeNumber: value]; + [self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]]; + [self logWithFormat: @" modseq: %.16lx", [modseq unsignedLongLongValue]]; + if (modseq) + modseq = [NSNumber numberWithUnsignedLongLong: + [modseq unsignedLongLongValue] + 1]; + else + modseq = [NSNumber numberWithUnsignedLongLong: 0]; + *qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"MODSEQ" + operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo + value: modseq]; + [*qualifier autorelease]; + rc = MAPIRestrictionStateNeedsEval; + } + break; default: rc = [super evaluatePropertyRestriction: res intoQualifier: qualifier];