From b2ff72c896387ff995034b50a17447dc8eb0d74f Mon Sep 17 00:00:00 2001 From: helge Date: Sat, 12 Feb 2005 20:39:08 +0000 Subject: [PATCH] fixed to qualifier handling git-svn-id: http://svn.opengroupware.org/SOPE/trunk@553 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- sope-mime/ChangeLog | 3 + sope-mime/NGImap4/ChangeLog | 3 + sope-mime/NGImap4/EOQualifier+IMAPAdditions.m | 311 ++++++++++++------ sope-mime/NGImap4/NGImap4Client.m | 2 +- sope-mime/Version | 2 +- 5 files changed, 223 insertions(+), 98 deletions(-) diff --git a/sope-mime/ChangeLog b/sope-mime/ChangeLog index a7453572..aeaf6f9f 100644 --- a/sope-mime/ChangeLog +++ b/sope-mime/ChangeLog @@ -1,5 +1,8 @@ 2005-02-12 Helge Hess + * NGImap4: reworked qualifier generation, fixed handling of OR + qualifiers (v4.5.213) + * NGImap4: improved copying, cleaned up sort ordering (v4.5.212) 2005-02-08 Helge Hess diff --git a/sope-mime/NGImap4/ChangeLog b/sope-mime/NGImap4/ChangeLog index bcbc6932..672d3273 100644 --- a/sope-mime/NGImap4/ChangeLog +++ b/sope-mime/NGImap4/ChangeLog @@ -1,5 +1,8 @@ 2005-02-12 Helge Hess + * EOQualifier+IMAPAdditions.m: more reworks in qualifier generation, + changed handling of spaces, fixed handling of OR qualifiers + * EOQualifier+IMAPAdditions.m: allow contains: qualifier operator for key searches diff --git a/sope-mime/NGImap4/EOQualifier+IMAPAdditions.m b/sope-mime/NGImap4/EOQualifier+IMAPAdditions.m index 401d4c02..9563b221 100644 --- a/sope-mime/NGImap4/EOQualifier+IMAPAdditions.m +++ b/sope-mime/NGImap4/EOQualifier+IMAPAdditions.m @@ -29,12 +29,13 @@ - (NSException *)appendToImap4SearchString:(NSMutableString *)_search insertNot:(BOOL)_insertNot; +- (NSException *)appendToImap4SearchString:(NSMutableString *)_search; @end @implementation EOQualifier(IMAPAdditions) -- (BOOL)isImap4UnseenQualifier { +- (BOOL)isImap4UnseenQualifier { /* a special key/value qualifier */ return NO; } @@ -44,7 +45,7 @@ static NSArray *FlagKeyWords = nil; static NSArray *OtherKeyWords = nil; static BOOL debugOn = NO; -- (void)_initImap4SearchCategory { +static void _initImap4SearchCategory(void) { NSUserDefaults *ud; if (FlagKeyWords) return; @@ -68,9 +69,6 @@ static BOOL debugOn = NO; userInfo:nil]; } -- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction { - return nil; -} - (BOOL)isImap4NotQualifier { return NO; } @@ -88,44 +86,69 @@ static BOOL debugOn = NO; } - (id)imap4SearchString { /* returns exception on fail */ - // TODO: split up method - BOOL disjunction = NO; /* OR */ - NSEnumerator *quals; - id qualifier; - NSMutableString *search; + [self logWithFormat:@"ERROR(%s): subclass %@ must overide this method!", + __PRETTY_FUNCTION__, [self class]]; + return nil; +} + +@end /* EOQualifier(IMAPAdditions) */ + + +@implementation EOAndQualifier(IMAPAdditions) + +- (NSException *)appendToImap4SearchString:(NSMutableString *)_search { + NSArray *quals; + unsigned i, lCount; - [self _initImap4SearchCategory]; + quals = [self qualifiers]; - if ([self isImap4UnseenQualifier]) { + if ((lCount = [quals count]) == 0) /* no subqualifiers */ + return nil; + if (lCount == 1) { + // TODO: use appendToImap4SearchString? + [_search appendString:[[quals objectAtIndex:0] imap4SearchString]]; + return nil; + } + + for (i = 0; i < lCount; i++) { + EOQualifier *qualifier; + NSException *error; + + qualifier = [quals objectAtIndex:i]; if (debugOn) - [self logWithFormat:@"is unseen: %@ (%@)", self, [self class]]; - return @" unseen"; + [self logWithFormat:@" append subqualifier: %@", qualifier]; + + [_search appendString:(i == 0) ? @"(" : @" ("]; + if ((error = [qualifier appendToImap4SearchString:_search])) + return error; + [_search appendString:@")"]; } - if (debugOn) - [self logWithFormat:@"generate IMAP4 expression for qualifier: %@", self]; + return nil /* no error */; +} + +- (id)imap4SearchString { /* returns exception on fail */ + NSMutableString *search; + NSException *error; + unsigned lCount; - search = [NSMutableString stringWithCapacity:256]; - quals = nil; + _initImap4SearchCategory(); - if ((quals = [self subqualifiersForImap4SearchString:&disjunction]) == nil) { - if (debugOn) - [self logWithFormat:@" got no subqualifiers .."]; - - return (id)[self invalidImap4SearchQualifier:@"unexpected qualifier 1"]; + if (debugOn) { + [self logWithFormat: + @"generate IMAP4 expression for AND qualifier: %@", self]; } - if (disjunction) - [search appendString:@" or"]; + if ((lCount = [[self qualifiers] count]) == 0) /* no subqualifiers */ + return nil; + if (lCount == 1) + return [[[self qualifiers] objectAtIndex:0] imap4SearchString]; - while ((qualifier = [quals nextObject]) != nil) { - NSException *error; - - if (debugOn) - [self logWithFormat:@" append subqualifier: %@", qualifier]; - - if ((error = [qualifier appendToImap4SearchString:search])) - return error; + search = [NSMutableString stringWithCapacity:lCount * 3]; + + if ((error = [self appendToImap4SearchString:search]) != nil) { + if (debugOn) [self logWithFormat:@" error: %@", error]; + return error; } if (debugOn) @@ -134,32 +157,75 @@ static BOOL debugOn = NO; return search; } -@end /* EOQualifier(IMAPAdditions) */ +@end /* EOAndQualifier(IMAPAdditions) */ -@implementation EOAndQualifier(IMAPAdditions) -- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction { - if (_isDisjunction) *_isDisjunction = NO; - return [[self qualifiers] objectEnumerator]; -} +@implementation EOOrQualifier(IMAPAdditions) -@end /* EOAndQualifier(IMAPAdditions) */ +- (NSException *)appendToImap4SearchString:(NSMutableString *)_search { + // TODO: move generation to this method + id s; + + s = [self imap4SearchString]; + if ([s isKindOfClass:[NSException class]]) + return s; + + [_search appendString:s]; + return nil; +} -@implementation EOOrQualifier(IMAPAdditions) +- (id)imap4SearchString { /* returns exception on fail */ + NSArray *quals; + NSMutableString *search; + unsigned i, lCount; + NSException *error; + + _initImap4SearchCategory(); + + if (debugOn) { + [self logWithFormat: + @"generate IMAP4 expression for or-qualifier: %@", self]; + } + + quals = [self qualifiers]; -- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction { - if (_isDisjunction) *_isDisjunction = YES; - return [[self qualifiers] objectEnumerator]; + if ((lCount = [quals count]) == 0) /* no subqualifiers */ + return nil; + if (lCount == 1) + return [[quals objectAtIndex:0] imap4SearchString]; + + search = [NSMutableString stringWithCapacity:lCount * 32]; + + /* + Note: or queries are specified as: + OR + so we need to wrap more ORs in multiple "OR" IMAP4 expressions + eg: "OR (OR (subject "abc") (subject "nbc")) from "duck"" + */ + + if ((error = [[quals objectAtIndex:0] appendToImap4SearchString:search])) + return error; + + for (i = 1; i < lCount; i++) { + EOQualifier *qualifier; + + qualifier = [quals objectAtIndex:i]; + [search insertString:@"OR (" atIndex:0]; + [search appendString:@") ("]; + if ((error = [qualifier appendToImap4SearchString:search])) + return error; + [search appendString:@")"]; + } + + if (debugOn) + [self logWithFormat:@" generated: '%@'", search]; + return search; } @end /* EOOrQualifier(IMAPAdditions) */ -@implementation EOKeyValueQualifier(IMAPAdditions) -- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction { - if (_isDisjunction) *_isDisjunction = NO; - return [[NSArray arrayWithObject:self] objectEnumerator]; -} +@implementation EOKeyValueQualifier(IMAPAdditions) - (BOOL)isImap4KeyValueQualifier { return YES; @@ -182,31 +248,32 @@ static BOOL debugOn = NO; lvalue = [self value]; lselector = [self selector]; - if (sel_eq(lselector, EOQualifierOperatorEqual)) { - lvalue = [NSArray arrayWithObject:lvalue]; - } - else if (!sel_eq(lselector, EOQualifierOperatorContains)) { - return [self invalidImap4SearchQualifier: - @"unexpected EOKeyValueQualifier selector"]; - } - if (![lvalue isKindOfClass:[NSArray class]]) { - return [self invalidImap4SearchQualifier: - @"expected an array in contains-qualifier"]; - } - enumerator = [lvalue objectEnumerator]; - while ((lvalue = [enumerator nextObject])) { - lvalue = [lvalue lowercaseString]; + if (sel_eq(lselector, EOQualifierOperatorEqual)) { + lvalue = [NSArray arrayWithObject:lvalue]; + } + else if (!sel_eq(lselector, EOQualifierOperatorContains)) { + return [self invalidImap4SearchQualifier: + @"unexpected EOKeyValueQualifier selector"]; + } + if (![lvalue isKindOfClass:[NSArray class]]) { + return [self invalidImap4SearchQualifier: + @"expected an array in contains-qualifier"]; + } + + enumerator = [lvalue objectEnumerator]; + while ((lvalue = [enumerator nextObject]) != nil) { + lvalue = [lvalue lowercaseString]; - if ([FlagKeyWords containsObject:lvalue]) { - [search appendString:insertNot ? @" not " : @" "]; - [search appendString:lvalue]; - } - else { - return [self invalidImap4SearchQualifier: - @"unexpected keyword for EOKeyValueQualifier"]; - } - } - return nil; + if ([FlagKeyWords containsObject:lvalue]) { + if (insertNot) [search appendString:@"not "]; + [search appendString:lvalue]; + } + else { + return [self invalidImap4SearchQualifier: + @"unexpected keyword for EOKeyValueQualifier"]; + } + } + return nil; } - (NSString *)imap4OperatorForDateComparisonSelector:(SEL)lselector { @@ -223,6 +290,7 @@ static BOOL debugOn = NO; - (NSException *)appendToImap4SearchString:(NSMutableString *)search insertNot:(BOOL)insertNot { + // TODO: this needs to get reworked /* returns exception on fail */ NSString *lkey; id lvalue; @@ -233,13 +301,14 @@ static BOOL debugOn = NO; lselector = [self selector]; if ([lkey isEqualToString:@"flags"]) { + /* NOTE: special "not" processing! */ return [self appendFlagsCheckToImap4SearchString:search insertNot:insertNot]; } /* not a flag */ if (insertNot) - [search appendString:@" not"]; + [search appendString:@"not "]; if ([lkey isEqualToString:@"date"]) { NSString *s; @@ -252,30 +321,46 @@ static BOOL debugOn = NO; if ((s = [self imap4OperatorForDateComparisonSelector:lselector]) == nil) return [self invalidImap4SearchQualifier:@"unexpected selector"]; + // TODO: operator created but NOT added? + // TODO: much faster without descriptionWithCalendarFormat:?! s = [lvalue descriptionWithCalendarFormat:@"%d-%b-%Y"]; [search appendString:s]; + return nil; } - else if ([lkey isEqualToString:@"uid"]) { + + if ([lkey isEqualToString:@"uid"]) { if (!sel_eq(lselector, EOQualifierOperatorEqual)) return [self invalidImap4SearchQualifier:@"unexpected qualifier 2"]; - [search appendString:@" uid "]; + [search appendString:@"uid "]; [search appendString:[lvalue stringValue]]; + return nil; } - else if ([lkey isEqualToString:@"size"]) { + + if ([lkey isEqualToString:@"size"]) { if (sel_eq(lselector, EOQualifierOperatorGreaterThan)) - [search appendString:@" larger "]; + [search appendString:@"larger "]; else if (sel_eq(lselector, EOQualifierOperatorLessThan)) - [search appendString:@" smaller "]; + [search appendString:@"smaller "]; else return [self invalidImap4SearchQualifier:@"unexpected qualifier 3"]; [search appendString:[lvalue stringValue]]; + return nil; } - else if ([OtherKeyWords containsObject:lkey]) { + + if ([OtherKeyWords containsObject:lkey]) { // TODO: actually most keywords only allow for contains! Eg "subject abc" // is a contains query, not an equal query! + /* + RFC 3501: + In all search keys that use strings, a message matches the key if + the string is a substring of the field. The matching is + case-insensitive. + + Would be: "a caseInsensitiveLike: '*ABC*'" + */ if (!sel_eq(lselector, EOQualifierOperatorEqual) && !sel_eq(lselector, EOQualifierOperatorContains)) { [self logWithFormat:@"IMAP4 generation: got: %@, allowed: %@", @@ -286,40 +371,74 @@ static BOOL debugOn = NO; @"OtherKeyWords)"]; } - [search appendString:@" "]; - [search appendString:lkey]; - [search appendString:@" \""]; - [search appendString:[lvalue stringValue]]; - [search appendString:@"\""]; - } - else { - if (!sel_eq(lselector, EOQualifierOperatorEqual)) - return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"]; - - [search appendString:@" header "]; [search appendString:lkey]; [search appendString:@" \""]; [search appendString:[lvalue stringValue]]; [search appendString:@"\""]; + return nil; } + + + if (!sel_eq(lselector, EOQualifierOperatorEqual)) + return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"]; + + [search appendString:@"header "]; + [search appendString:lkey]; + [search appendString:@" \""]; + [search appendString:[lvalue stringValue]]; + [search appendString:@"\""]; return nil; } +- (id)imap4SearchString { /* returns exception on fail */ + NSMutableString *search; + NSException *error; + + _initImap4SearchCategory(); + + if ([self isImap4UnseenQualifier]) { + if (debugOn) + [self logWithFormat:@"is unseen: %@ (%@)", self, [self class]]; + return @"unseen"; + } + + search = [NSMutableString stringWithCapacity:256]; + + if ((error = [self appendToImap4SearchString:search])) + return error; + + return search; +} + @end /* EOKeyValueQualifier(IMAPAdditions) */ -@implementation EONotQualifier(IMAPAdditions) -- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction { - if (_isDisjunction) *_isDisjunction = NO; - return [[NSArray arrayWithObject:self] objectEnumerator]; -} +@implementation EONotQualifier(IMAPAdditions) - (BOOL)isImap4NotQualifier { return YES; } - (NSException *)appendToImap4SearchString:(NSMutableString *)_search { + /* + TODO: we do this because the key/value qualifier can generate multiple + queries + */ return [[self qualifier] appendToImap4SearchString:_search insertNot:YES]; } +- (id)imap4SearchString { /* returns exception on fail */ + NSMutableString *search; + NSException *error; + + _initImap4SearchCategory(); + + search = [NSMutableString stringWithCapacity:256]; + + if ((error = [self appendToImap4SearchString:search])) + return error; + + return search; +} + @end /* EONotQualifier(IMAPAdditions) */ diff --git a/sope-mime/NGImap4/NGImap4Client.m b/sope-mime/NGImap4/NGImap4Client.m index b5668913..3995c095 100644 --- a/sope-mime/NGImap4/NGImap4Client.m +++ b/sope-mime/NGImap4/NGImap4Client.m @@ -966,7 +966,7 @@ static BOOL ImapDebugEnabled = NO; qualifier:_qualifier]; return nil; } - return result; + return [@" " stringByAppendingString:result]; } - (NSDictionary *)threadBySubject:(BOOL)_bySubject diff --git a/sope-mime/Version b/sope-mime/Version index 892cc325..f1a8c404 100644 --- a/sope-mime/Version +++ b/sope-mime/Version @@ -2,6 +2,6 @@ MAJOR_VERSION:=4 MINOR_VERSION:=5 -SUBMINOR_VERSION:=212 +SUBMINOR_VERSION:=213 # v4.2.149 requires libNGStreams v4.2.34 -- 2.39.5