From 2f2605dfcc5c7443387b7f4b53cbb0644d36595e Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Thu, 28 Jul 2011 00:55:50 +0000 Subject: [PATCH] Monotone-Parent: ec3d19f268090d56a33692cdc116485c488fd4d6 Monotone-Revision: 6b4e2f16b2a1ac3bbbb8f854763b7deb70bd8a46 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2011-07-28T00:55:50 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 21 +++ OpenChange/MAPIStoreCalendarFolder.m | 1 + OpenChange/MAPIStoreContactsFolder.m | 1 + OpenChange/MAPIStoreGCSFolder.h | 10 ++ OpenChange/MAPIStoreGCSFolder.m | 223 +++++++++++++++++++++++++- OpenChange/MAPIStoreGCSMessage.m | 17 ++ OpenChange/MAPIStoreGCSMessageTable.m | 110 +++++++------ OpenChange/MAPIStoreTasksFolder.m | 2 +- 8 files changed, 332 insertions(+), 53 deletions(-) diff --git a/ChangeLog b/ChangeLog index f9b0f8c5d..3898f89e0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,26 @@ 2011-07-27 Wolfgang Sourdeau + * OpenChange/MAPIStoreGCSMessageTable.m + (-evaluatePropertyRestriction:intoQualifier:): now handles + PR_CHANGE_NUM using the new methods below. + + * OpenChange/MAPIStoreGCSMessage.m (-objectVersion): overriden + method to make use of -[MAPIStoreGCSFolder + changeNumberForMessageWithKey:]. + + * OpenChange/MAPIStoreGCSFolder.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. + (-lastModifiedFromMessageChangeNumber:): new helper method that returns + the corresponding c_lastmodified from a MAPI change number. + (-changeNumberForMessageWithKey:): new helper method that returns the + version number associated with a message. + * OpenChange/MAPIStoreMailMessageTable.m (-evaluatePropertyRestriction:intoQualifier:): now handles PR_CHANGE_NUM using the new methods below. diff --git a/OpenChange/MAPIStoreCalendarFolder.m b/OpenChange/MAPIStoreCalendarFolder.m index 8b6eb771b..a3018646e 100644 --- a/OpenChange/MAPIStoreCalendarFolder.m +++ b/OpenChange/MAPIStoreCalendarFolder.m @@ -83,6 +83,7 @@ static Class MAPIStoreCalendarMessageK; - (MAPIStoreMessageTable *) messageTable { + [self synchroniseCache]; return [MAPIStoreCalendarMessageTable tableForContainer: self]; } diff --git a/OpenChange/MAPIStoreContactsFolder.m b/OpenChange/MAPIStoreContactsFolder.m index df6f5ed36..a687508f5 100644 --- a/OpenChange/MAPIStoreContactsFolder.m +++ b/OpenChange/MAPIStoreContactsFolder.m @@ -85,6 +85,7 @@ static Class MAPIStoreContactsMessageK; - (MAPIStoreMessageTable *) messageTable { + [self synchroniseCache]; return [MAPIStoreContactsMessageTable tableForContainer: self]; } diff --git a/OpenChange/MAPIStoreGCSFolder.h b/OpenChange/MAPIStoreGCSFolder.h index fdc42679f..ed26fd29f 100644 --- a/OpenChange/MAPIStoreGCSFolder.h +++ b/OpenChange/MAPIStoreGCSFolder.h @@ -26,8 +26,18 @@ #import "MAPIStoreFolder.h" @class NSCalendarDate; +@class NSNumber; +@class NSString; @interface MAPIStoreGCSFolder : MAPIStoreFolder +{ + SOGoMAPIFSMessage *versionsMessage; +} + +/* synchronisation */ +- (BOOL) synchroniseCache; +- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSNumber *) changeNum; +- (NSNumber *) changeNumberForMessageWithKey: (NSString *) messageKey; /* subclasses */ - (EOQualifier *) componentQualifier; diff --git a/OpenChange/MAPIStoreGCSFolder.m b/OpenChange/MAPIStoreGCSFolder.m index a8323d69b..7ad627ebb 100644 --- a/OpenChange/MAPIStoreGCSFolder.m +++ b/OpenChange/MAPIStoreGCSFolder.m @@ -20,24 +20,61 @@ * Boston, MA 02111-1307, USA. */ -#import #import +#import +#import #import #import +#import #import #import #import +#import "MAPIStoreContext.h" +#import "MAPIStoreTypes.h" #import "NSDate+MAPIStore.h" +#import "SOGoMAPIFSMessage.h" #import "MAPIStoreGCSFolder.h" -#import "MAPIStoreTypes.h" #undef DEBUG #include @implementation MAPIStoreGCSFolder +- (id) initWithURL: (NSURL *) newURL + inContext: (MAPIStoreContext *) newContext +{ + if ((self = [super initWithURL: newURL + inContext: newContext])) + { + ASSIGN (versionsMessage, + [SOGoMAPIFSMessage objectWithName: @"versions.plist" + inContainer: propsFolder]); + } + + return self; +} + +- (id) initWithSOGoObject: (id) newSOGoObject + inContainer: (MAPIStoreObject *) newContainer +{ + if ((self = [super initWithSOGoObject: newSOGoObject inContainer: newContainer])) + { + ASSIGN (versionsMessage, + [SOGoMAPIFSMessage objectWithName: @"versions.plist" + inContainer: propsFolder]); + } + + return self; +} + +- (void) dealloc +{ + [versionsMessage release]; + [super dealloc]; +} + - (NSArray *) messageKeysMatchingQualifier: (EOQualifier *) qualifier andSortOrderings: (NSArray *) sortOrderings { @@ -79,7 +116,187 @@ - (NSDate *) lastMessageModificationTime { - return [[sogoObject ocsFolder] lastModificationDate]; + 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; + ... + } +} +*/ + +- (BOOL) synchroniseCache +{ + BOOL rc = YES, foundChange = NO; + uint64_t newChangeNum; + NSNumber *ti, *changeNumber, *lastModificationDate, *cName, *cVersion, *cLastModified; + EOFetchSpecification *fs; + EOQualifier *searchQualifier, *fetchQualifier; + NSUInteger count, max; + NSArray *fetchResults; + NSDictionary *result; + NSMutableDictionary *currentProperties, *messages, *mapping, *messageEntry; + NSCalendarDate *now; + GCSFolder *ocsFolder; + static NSArray *fields = nil; + static EOSortOrdering *sortOrdering = nil; + + if (!fields) + fields = [[NSArray alloc] + initWithObjects: @"c_name", @"c_version", @"c_lastmodified", + nil]; + + if (!sortOrdering) + { + sortOrdering = [EOSortOrdering sortOrderingWithKey: @"c_lastmodified" + selector: EOCompareAscending]; + [sortOrdering retain]; + } + + now = [NSCalendarDate date]; + + 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]; + } + + lastModificationDate = [currentProperties objectForKey: @"SyncLastModificationDate"]; + if (lastModificationDate) + { + searchQualifier = [[EOKeyValueQualifier alloc] + initWithKey: @"c_lastmodified" + operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo + value: lastModificationDate]; + fetchQualifier = [[EOAndQualifier alloc] + initWithQualifiers: + searchQualifier, [self componentQualifier], nil]; + [fetchQualifier autorelease]; + [searchQualifier release]; + } + else + fetchQualifier = [self componentQualifier]; + + ocsFolder = [sogoObject ocsFolder]; + fs = [EOFetchSpecification + fetchSpecificationWithEntityName: [ocsFolder folderName] + qualifier: fetchQualifier + sortOrderings: [NSArray arrayWithObject: sortOrdering]]; + fetchResults = [ocsFolder fetchFields: fields fetchSpecification: fs]; + max = [fetchResults count]; + if (max > 0) + { + for (count = 0; count < max; count++) + { + result = [fetchResults objectAtIndex: count]; + cName = [result objectForKey: @"c_name"]; + cVersion = [result objectForKey: @"c_version"]; + cLastModified = [result objectForKey: @"c_lastmodified"]; + + messageEntry = [messages objectForKey: cName]; + if (!messageEntry) + { + messageEntry = [NSMutableDictionary new]; + [messages setObject: messageEntry forKey: cName]; + [messageEntry release]; + } + if (![[messageEntry objectForKey: @"c_version"] + isEqual: cVersion]) + { + foundChange = YES; + + newChangeNum = [[self context] getNewChangeNumber]; + changeNumber = [NSNumber numberWithUnsignedLongLong: newChangeNum]; + + [messageEntry setObject: cLastModified forKey: @"c_lastmodified"]; + [messageEntry setObject: cVersion forKey: @"c_version"]; + [messageEntry setObject: changeNumber forKey: @"version"]; + + [mapping setObject: cLastModified forKey: changeNumber]; + + if (!lastModificationDate + || ([lastModificationDate compare: cLastModified] + == NSOrderedAscending)) + lastModificationDate = cLastModified; + } + } + + if (foundChange) + { + ti = [NSNumber numberWithDouble: [now timeIntervalSince1970]]; + [currentProperties setObject: ti + forKey: @"SyncLastSynchronisationDate"]; + [currentProperties setObject: lastModificationDate + forKey: @"SyncLastModificationDate"]; + [versionsMessage appendProperties: currentProperties]; + [versionsMessage save]; + } + } + + return rc; +} + +- (NSNumber *) lastModifiedFromMessageChangeNumber: (NSNumber *) changeNum +{ + NSDictionary *mapping; + NSNumber *modseq; + + mapping = [[versionsMessage properties] objectForKey: @"VersionMapping"]; + modseq = [mapping objectForKey: changeNum]; + + return modseq; +} + +- (NSNumber *) changeNumberForMessageWithKey: (NSString *) messageKey +{ + NSDictionary *messages; + NSNumber *changeNumber; + + messages = [[versionsMessage properties] objectForKey: @"Messages"]; + changeNumber = [[messages objectForKey: messageKey] + objectForKey: @"version"]; + + return changeNumber; } /* subclasses */ diff --git a/OpenChange/MAPIStoreGCSMessage.m b/OpenChange/MAPIStoreGCSMessage.m index 0f51e2bc6..e6e5216b5 100644 --- a/OpenChange/MAPIStoreGCSMessage.m +++ b/OpenChange/MAPIStoreGCSMessage.m @@ -20,8 +20,10 @@ * Boston, MA 02111-1307, USA. */ +#import #import +#import "MAPIStoreGCSFolder.h" #import "MAPIStoreTypes.h" #import "MAPIStoreGCSMessage.h" @@ -41,4 +43,19 @@ return [sogoObject lastModified]; } +- (uint64_t) objectVersion +{ + uint64_t version = 0xffffffffffffffffLL; + NSNumber *changeNumber; + + changeNumber = [(MAPIStoreGCSFolder *) container + changeNumberForMessageWithKey: [self nameInContainer]]; + if (changeNumber) + version = [changeNumber unsignedLongLongValue] >> 16; + else + abort (); + + return version; +} + @end diff --git a/OpenChange/MAPIStoreGCSMessageTable.m b/OpenChange/MAPIStoreGCSMessageTable.m index 13d44264c..7e2baa0c9 100644 --- a/OpenChange/MAPIStoreGCSMessageTable.m +++ b/OpenChange/MAPIStoreGCSMessageTable.m @@ -36,8 +36,8 @@ #import #import "MAPIStoreTypes.h" +#import "MAPIStoreGCSFolder.h" #import "NSAutoreleasePool+MAPIStore.h" -#import "MAPIStoreFolder.h" #import "MAPIStoreGCSMessageTable.h" @@ -46,20 +46,10 @@ @implementation MAPIStoreGCSMessageTable -- (id) init +- (void) cleanupCaches { - if ((self = [super init])) - { - sortOrderings = nil; - } - - return self; -} - -- (void) dealloc -{ - [sortOrderings release]; - [super dealloc]; + [(MAPIStoreGCSFolder *) container synchroniseCache]; + [super cleanupCaches]; } - (struct mapi_SPropertyRestriction *) _fixedDatePropertyRestriction: (struct mapi_SPropertyRestriction *) res @@ -92,45 +82,67 @@ SEL operator; id value; NSString *property; + NSNumber *lastModified; MAPIRestrictionState rc; - property = [self backendIdentifierForProperty: res->ulPropTag]; - if (property) + if (res->ulPropTag == PR_CHANGE_NUM) { - if (res->relop >= 0 && res->relop < 7) - operator = operators[res->relop]; - else - { - operator = NULL; - [NSException raise: @"MAPIStoreRestrictionException" - format: @"unhandled operator type number %d", res->relop]; - } - - if ((res->ulPropTag & 0xffff) == PT_SYSTIME) - { - res = [self _fixedDatePropertyRestriction: res]; - NSAutoreleaseTallocPointer (res); - } - value = NSObjectFromMAPISPropValue (&res->lpProp); - if ((res->ulPropTag & 0xffff) == PT_UNICODE) + lastModified = [(MAPIStoreGCSFolder *) + container lastModifiedFromMessageChangeNumber: value]; + [self logWithFormat: @"change number from oxcfxics: %.16lx", [value unsignedLongLongValue]]; + [self logWithFormat: @" c_lastmodified: %@", lastModified]; + if (lastModified) { - property = [NSString stringWithFormat: @"UPPER(%@)", property]; - value = [value uppercaseString]; + *qualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_lastmodified" + operatorSelector: EOQualifierOperatorGreaterThanOrEqualTo + value: lastModified]; + [*qualifier autorelease]; + rc = MAPIRestrictionStateNeedsEval; } - - *qualifier = [[EOKeyValueQualifier alloc] initWithKey: property - operatorSelector: operator - value: value]; - [*qualifier autorelease]; - - rc = MAPIRestrictionStateNeedsEval; + else + rc = MAPIRestrictionStateAlwaysTrue; } else { - [self warnUnhandledProperty: res->ulPropTag - inFunction: __FUNCTION__]; - rc = MAPIRestrictionStateAlwaysFalse; + property = [self backendIdentifierForProperty: res->ulPropTag]; + if (property) + { + if (res->relop >= 0 && res->relop < 7) + operator = operators[res->relop]; + else + { + operator = NULL; + [NSException raise: @"MAPIStoreRestrictionException" + format: @"unhandled operator type number %d", res->relop]; + } + + if ((res->ulPropTag & 0xffff) == PT_SYSTIME) + { + res = [self _fixedDatePropertyRestriction: res]; + NSAutoreleaseTallocPointer (res); + } + + value = NSObjectFromMAPISPropValue (&res->lpProp); + if ((res->ulPropTag & 0xffff) == PT_UNICODE) + { + property = [NSString stringWithFormat: @"UPPER(%@)", property]; + value = [value uppercaseString]; + } + + *qualifier = [[EOKeyValueQualifier alloc] initWithKey: property + operatorSelector: operator + value: value]; + [*qualifier autorelease]; + + rc = MAPIRestrictionStateNeedsEval; + } + else + { + [self warnUnhandledProperty: res->ulPropTag + inFunction: __FUNCTION__]; + rc = MAPIRestrictionStateAlwaysFalse; + } } return rc; @@ -147,9 +159,9 @@ - (EOSortOrdering *) _sortOrderingFromSortOrder: (struct SSortOrder *) sortOrder { - EOSortOrdering *newSortOrdering; + EOSortOrdering *newSortOrdering = nil; NSString *sortIdentifier; - SEL orderSelector; + SEL orderSelector = NULL; const char *propName; sortIdentifier = [self sortIdentifierForProperty: sortOrder->ulPropTag]; @@ -182,12 +194,12 @@ @"TABLE_SORT_MAXIMUM_CATEGORY is not handled"]; } } - newSortOrdering = [EOSortOrdering sortOrderingWithKey: sortIdentifier - selector: orderSelector]; + if (orderSelector) + newSortOrdering = [EOSortOrdering sortOrderingWithKey: sortIdentifier + selector: orderSelector]; } else { - newSortOrdering = nil; propName = get_proptag_name (sortOrder->ulPropTag); if (!propName) propName = ""; diff --git a/OpenChange/MAPIStoreTasksFolder.m b/OpenChange/MAPIStoreTasksFolder.m index 511ccf80b..8d26790c1 100644 --- a/OpenChange/MAPIStoreTasksFolder.m +++ b/OpenChange/MAPIStoreTasksFolder.m @@ -83,6 +83,7 @@ static Class MAPIStoreTasksMessageK; - (MAPIStoreMessageTable *) messageTable { + [self synchroniseCache]; return [MAPIStoreTasksMessageTable tableForContainer: self]; } @@ -90,7 +91,6 @@ static Class MAPIStoreTasksMessageK; { static EOQualifier *componentQualifier = nil; - /* TODO: we need to support vlist as well */ if (!componentQualifier) componentQualifier = [[EOKeyValueQualifier alloc] initWithKey: @"c_component"