- (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;
}
static NSArray *OtherKeyWords = nil;
static BOOL debugOn = NO;
-- (void)_initImap4SearchCategory {
+static void _initImap4SearchCategory(void) {
NSUserDefaults *ud;
if (FlagKeyWords) return;
userInfo:nil];
}
-- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction {
- return nil;
-}
- (BOOL)isImap4NotQualifier {
return 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)
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 <search-key1> <search-key2>
+ 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;
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 {
- (NSException *)appendToImap4SearchString:(NSMutableString *)search
insertNot:(BOOL)insertNot
{
+ // TODO: this needs to get reworked
/* returns exception on fail */
NSString *lkey;
id lvalue;
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;
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: %@",
@"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) */