diff --git a/ChangeLog b/ChangeLog index 1cfc2103b..e5aab5532 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,15 +1,37 @@ +2009-10-08 Wolfgang Sourdeau + + * Tests/webdavlib.py (MailDAVMailQuery._initSort): now handles the + "order" attribute from the "ascending" parameter. + + * Tests/test-maildav.py (DAVMailCollectionTest._testSort): added a + boolean "ascending" parameter that defaults to True. + (DAVMailCollectionTest.testREPORTMailQueryFilters): method + replacing the filter part of testREPORTMailQuery. + (DAVMailCollectionTest.testREPORTMailQuerySort): method + replacing the sort part of testREPORTMailQuery. + + * SoObjects/Mailer/SOGoMailFolder.m (_sortElementIsAscending:): + new method that determines whether the sort ordering is ascending + or not. + (_sortOrderingsFromSortElement:): new method relacing + "_mailSortingFromSortElement:", that returns an array of + EOSortOrdering instances based on the "sort" element passed as + parameter. + 2009-10-07 Cyril Robert - * SoObjects/Mailer/EOQualifier+MailDAV.m (buildQualifierFromFilters:): Now - returns an EOQualifier, fixed qualifier formats and constructor, refactored - to regroup code. - * Tests/test-maildav.py (testREPORTMailQuery): Disabled a few tests that - don't pass for now. + * SoObjects/Mailer/EOQualifier+MailDAV.m + (buildQualifierFromFilters:): Now returns an EOQualifier, fixed + qualifier formats and constructor, refactored to regroup code. + * Tests/test-maildav.py (testREPORTMailQuery): Disabled a few + tests that don't pass for now. * SoObjects/Mailer/EOQualifier+MailDAV.m (buildQualifierFromFilters:): Removed, everything is now done in (qualifierFromMailDAVMailFilters:). - (qualifierFromMailDAVMailFilters:): Removed all parentheses in qualifier. - * SoObjects/Mailer/EOQualifier+MailDAV.m (qualifierFromMailDAVMailFilters:): - Fixed segfault when there are no filters. + (qualifierFromMailDAVMailFilters:): Removed all parentheses in + qualifier. + * SoObjects/Mailer/EOQualifier+MailDAV.m + (qualifierFromMailDAVMailFilters:): Fixed segfault when there are + no filters. 2009-10-07 Francis Lachapelle diff --git a/SoObjects/Mailer/SOGoMailFolder.m b/SoObjects/Mailer/SOGoMailFolder.m index a09815dbd..62703ac82 100644 --- a/SoObjects/Mailer/SOGoMailFolder.m +++ b/SoObjects/Mailer/SOGoMailFolder.m @@ -38,6 +38,8 @@ #import #import +#import + #import #import @@ -1151,84 +1153,108 @@ static NSString *spoolFolder = nil; return davIMAPFieldsTable; } -- (NSDictionary *) _davIMAPFieldsForProperties: (NSArray *) properties +- (BOOL) _sortElementIsAscending: (DOMElement *) sortElement { - NSMutableDictionary *davIMAPFields; - NSDictionary *davIMAPFieldsTable; - NSString *imapField, *property; - unsigned int count, max; - - davIMAPFieldsTable = [self davIMAPFieldsTable]; - - max = [properties count]; - davIMAPFields = [NSMutableDictionary dictionaryWithCapacity: max]; - for (count = 0; count < max; count++) - { - property = [properties objectAtIndex: count]; - imapField = [davIMAPFieldsTable objectForKey: property]; - if (imapField) - [davIMAPFields setObject: imapField forKey: property]; - else - [self errorWithFormat: @"DAV property '%@' has no matching IMAP field," - @" response could be incomplete", property]; - } - - return davIMAPFields; -} - -- (NSDictionary *) parseDAVRequestedProperties: (DOMElement *) propElement -{ - NSArray *properties; - NSDictionary *imapFieldsTable; - - properties = [propElement flatPropertyNameOfSubElements]; - imapFieldsTable = [self _davIMAPFieldsForProperties: properties]; - - return imapFieldsTable; -} - -- (NSString *) _mailSortingFromSortElement: (DOMElement *) sortElement -{ - NSArray *imapFields; NSString *davReverseAttr; - NSMutableString *imapSortCriteria; + BOOL orderIsAscending; - imapSortCriteria = [NSMutableString string]; + orderIsAscending = YES; - imapFields = [[self parseDAVRequestedProperties: sortElement] allValues]; - davReverseAttr = [[sortElement attribute: @"order"] uppercaseString]; + davReverseAttr = [sortElement attribute: @"order"]; if ([davReverseAttr isEqualToString: @"descending"]) - [imapSortCriteria appendString: @"REVERSE "]; + orderIsAscending = NO; else if ([davReverseAttr length] && ![davReverseAttr isEqualToString: @"ascending"]) [self errorWithFormat: @"unrecognized sort order: '%@'", davReverseAttr]; - [imapSortCriteria - appendString: [imapFields componentsJoinedByString: @" "]]; - return imapSortCriteria; + return orderIsAscending; +} + +- (NSArray *) _sortOrderingsFromSortElement: (DOMElement *) sortElement +{ + NSArray *davSortCriterias; + NSMutableArray *sortOrderings; + SEL sortOrderingOrder; + NSString *davSortVerb, *imapSortVerb; + EOSortOrdering *currentOrdering; + static NSMutableDictionary *criteriasMap = nil; + int count, max; + + if (!criteriasMap) + { + criteriasMap = [NSMutableDictionary new]; + [criteriasMap setObject: @"ARRIVAL" + forKey: @"{urn:schemas:mailheader:}received"]; + [criteriasMap setObject: @"DATE" + forKey: @"{urn:schemas:mailheader:}date"]; + [criteriasMap setObject: @"FROM" + forKey: @"{urn:schemas:mailheader:}from"]; + [criteriasMap setObject: @"TO" + forKey: @"{urn:schemas:mailheader:}to"]; + [criteriasMap setObject: @"CC" + forKey: @"{urn:schemas:mailheader:}cc"]; + [criteriasMap setObject: @"SUBJECT" + forKey: @"{DAV:}displayname"]; + [criteriasMap setObject: @"SUBJECT" + forKey: @"{urn:schemas:mailheader:}subject"]; + [criteriasMap setObject: @"SIZE" + forKey: @"{DAV:}getcontentlength"]; + } + + sortOrderings = [NSMutableArray array]; + + if ([self _sortElementIsAscending: sortElement]) + sortOrderingOrder = EOCompareAscending; + else + sortOrderingOrder = EOCompareDescending; + + davSortCriterias = [sortElement flatPropertyNameOfSubElements]; + max = [davSortCriterias count]; + for (count = 0; count < max; count++) + { + davSortVerb = [davSortCriterias objectAtIndex : count]; + imapSortVerb = [criteriasMap objectForKey: davSortVerb]; + if (imapSortVerb) + { + currentOrdering + = [EOSortOrdering sortOrderingWithKey: imapSortVerb + selector: sortOrderingOrder]; + [sortOrderings addObject: currentOrdering]; + } + else + [self errorWithFormat: @"unrecognized sort key: '%@'", davSortVerb]; + } + + return sortOrderings; } - (NSArray *) _fetchMessageProperties: (NSDictionary *) properties matchingQualifier: (EOQualifier *) searchQualifier - andSorting: (NSString *) sorting + andSortOrderings: (NSArray *) sortOrderings { NGImap4Client *client; NSDictionary *response; NSArray *messages; + NSString *resultKey; client = [[self imap4Connection] client]; [imap4 selectFolder: [self imap4URL]]; - if ([sorting length]) - response = [client sort: sorting - qualifier: searchQualifier - encoding: @"UTF-8"]; + if ([sortOrderings count]) + { + response = [client sort: sortOrderings qualifier: searchQualifier + encoding: @"UTF-8"]; + resultKey = @"sort"; + } else - response = [client searchWithQualifier: searchQualifier]; + { + response = [client searchWithQualifier: searchQualifier]; + resultKey = @"search"; + } if ([[response objectForKey: @"result"] boolValue]) - messages = [response objectForKey: @"search"]; + messages = [response objectForKey: resultKey]; else messages = nil; @@ -1278,7 +1304,7 @@ static NSString *spoolFolder = nil; } messageUrl = [NSString stringWithFormat: @"%@%@.eml", - [self davURL], messageId]; + [self davURL], messageId]; [propstats addObject: davElementWithContent (@"href", XMLNS_WEBDAV, messageUrl)]; @@ -1325,20 +1351,60 @@ static NSString *spoolFolder = nil; } davString = [davElementWithContent (@"multistatus", XMLNS_WEBDAV, all) - asWebDavStringWithNamespaces: nil]; + asWebDavStringWithNamespaces: nil]; [response appendContentString: davString]; NSZoneFree (NULL, selectors); } +- (NSDictionary *) _davIMAPFieldsForProperties: (NSArray *) properties +{ + NSMutableDictionary *davIMAPFields; + NSDictionary *davIMAPFieldsTable; + NSString *imapField, *property; + unsigned int count, max; + + davIMAPFieldsTable = [self davIMAPFieldsTable]; + + max = [properties count]; + davIMAPFields = [NSMutableDictionary dictionaryWithCapacity: max]; + for (count = 0; count < max; count++) + { + property = [properties objectAtIndex: count]; + imapField = [davIMAPFieldsTable objectForKey: property]; + if (imapField) + [davIMAPFields setObject: imapField forKey: property]; + else + [self errorWithFormat: @"DAV property '%@' has no matching IMAP field," + @" response could be incomplete", property]; + } + + return davIMAPFields; +} + +- (NSDictionary *) parseDAVRequestedProperties: (DOMElement *) propElement +{ + NSArray *properties; + NSDictionary *imapFieldsTable; + + properties = [propElement flatPropertyNameOfSubElements]; + imapFieldsTable = [self _davIMAPFieldsForProperties: properties]; + + return imapFieldsTable; +} + +/* TODO: + - populate only required keys in returned SOGoMailObject rather that + fetching the whole envelope and stuff + - use EOSortOrdering rather than an NSString + */ - (id) davMailQuery: (id) queryContext { WOResponse *r; id document; DOMElement *documentElement, *propElement, *filterElement, *sortElement; NSDictionary *properties; - NSArray *messages; + NSArray *messages, *sortOrderings; EOQualifier *searchQualifier; - NSString *sorting; r = [context response]; [r setContentEncoding: NSUTF8StringEncoding]; @@ -1358,11 +1424,11 @@ static NSString *spoolFolder = nil; qualifierFromMailDAVMailFilters: filterElement]; sortElement = [documentElement firstElementWithTag: @"sort" inNamespace: XMLNS_INVERSEDAV]; - sorting = [self _mailSortingFromSortElement: sortElement]; + sortOrderings = [self _sortOrderingsFromSortElement: sortElement]; messages = [self _fetchMessageProperties: properties matchingQualifier: searchQualifier - andSorting: sorting]; + andSortOrderings: sortOrderings]; [r setStatus: 207]; [r appendContentString: @"\n"]; [self _appendProperties: [properties allKeys] diff --git a/Tests/test-maildav.py b/Tests/test-maildav.py index 72647fff3..bb8db8df4 100755 --- a/Tests/test-maildav.py +++ b/Tests/test-maildav.py @@ -7,6 +7,9 @@ import unittest import webdavlib import time +# TODO +# add test with multiple sort criterias + def fetchUserEmail(login): client = webdavlib.WebDAVClient(hostname, port, username, password) @@ -330,14 +333,14 @@ class DAVMailCollectionTest(unittest.TestCase): "filter %s:\n\tunexpected amount of refs: %d" % (filter[0], received_count)) - def _testSort(self, sortOrder): + def _testSort(self, sortOrder, ascending = True): expected_hrefs = sortOrder[1] expected_count = len(expected_hrefs) received_count = 0 url = "%sfolder%s" % (self.resource, "test-dav-mail") query = webdavlib.MailDAVMailQuery(url, ["displayname"], - None, sortOrder[0]) + None, sortOrder[0], ascending) self.client.execute(query) self.assertEquals(query.response["status"], 207, "sortOrder %s:\n\tunexpected status: %d" @@ -348,18 +351,18 @@ class DAVMailCollectionTest(unittest.TestCase): for response_node in response_nodes: href_node = query.xpath_evaluate("D:href", response_node)[0] href = href_node.childNodes[0].nodeValue - received_count = received_count + 1 self.assertEquals(expected_hrefs[received_count], href, "sortOrder %s:\n\tunexpected href: %s (expecting: %s)" % (sortOrder[0], href, expected_hrefs[received_count])) + received_count = received_count + 1 self.assertEquals(expected_count, received_count, "sortOrder %s:\n\tunexpected amount of refs: %d" % (sortOrder[0], received_count)) - def testREPORTMailQuery(self): - """mail-query""" + def testREPORTMailQueryFilters(self): + """mail-query filters""" self._deleteCollection("test-dav-mail") self._makeCollection("test-dav-mail") @@ -549,6 +552,7 @@ class DAVMailCollectionTest(unittest.TestCase): "not": "true" }}, [ msg2Loc, msg3Loc ])) + print "message flags are not handled yet" ## 1. test filter: answered # ANSWERED, UNANSWERED ## 1. test filter: draft @@ -579,33 +583,57 @@ class DAVMailCollectionTest(unittest.TestCase): [])) self._testFilters(filters) + self._deleteCollection("test-dav-mail") + + def testREPORTMailQuerySort(self): + """mail-query sort""" + self._deleteCollection("test-dav-mail") + self._makeCollection("test-dav-mail") + + msg1Loc = self._putMessage(self.client, "test-dav-mail", message1) + parsed = webdavlib.HTTPUnparsedURL(msg1Loc) + msg1Path = parsed.path + msg2Loc = self._putMessage(self.client, "test-dav-mail", message2) + parsed = webdavlib.HTTPUnparsedURL(msg2Loc) + msg2Path = parsed.path + msg3Loc = self._putMessage(self.client, "test-dav-mail", message3) + parsed = webdavlib.HTTPUnparsedURL(msg3Loc) + msg3Path = parsed.path + # 1. test sort: (receive-date) ARRIVAL self._testSort(([ "{urn:schemas:mailheader:}received" ], - [ msg2Loc, msg3Loc, msg1Loc ])) + [ msg1Loc, msg2Loc, msg3Loc ])) # 1. test sort: (date) DATE self._testSort(([ "{urn:schemas:mailheader:}date" ], + [ msg2Loc, msg1Loc, msg3Loc ])) + + # 1. test sort: FROM + self._testSort(([ "{urn:schemas:mailheader:}from" ], + [ msg1Loc, msg2Loc, msg3Loc ])) + + # 1. test sort: TO + self._testSort(([ "{urn:schemas:mailheader:}to" ], [ msg1Loc, msg2Loc, msg3Loc ])) # 1. test sort: CC self._testSort(([ "{urn:schemas:mailheader:}cc" ], [ msg3Loc, msg1Loc, msg2Loc ])) - # 1. test sort: FROM - self._testSort(([ "{urn:schemas:mailheader:}from" ], - [ msg1Loc, msg2Loc, msg3Loc ])) + # 1. test sort: SUBJECT + self._testSort(([ "{DAV:}displayname" ], + [ msg3Loc, msg1Loc, msg2Loc ])) + self._testSort(([ "{urn:schemas:mailheader:}subject" ], + [ msg3Loc, msg1Loc, msg2Loc ])) # 1. test sort: SIZE self._testSort(([ "{DAV:}getcontentlength" ], [ msg3Loc, msg1Loc, msg2Loc ])) - # 1. test sort: SUBJECT - self._testSort(([ "{urn:schemas:mailheader:}displayName" ], - [ msg3Loc, msg1Loc, msg2Loc ])) - - # 1. test sort: TO - self._testSort(([ "{urn:schemas:mailheader:}to" ], - [ msg1Loc, msg2Loc, msg3Loc ])) + # 1. test sort: REVERSE CC + self._testSort(([ "{urn:schemas:mailheader:}cc" ], + [ msg2Loc, msg1Loc, msg3Loc ]), + False) self._deleteCollection("test-dav-mail") @@ -629,11 +657,15 @@ class DAVMailCollectionTest(unittest.TestCase): ("{urn:schemas:httpmail:}textdescription", "" % message1, 0), ("{urn:schemas:httpmail:}unreadcount", None, 0), - ("{urn:schemas:mailheader:}cc","2message1cc@cyril.dev, user10@cyril.dev", 0), - ("{urn:schemas:mailheader:}date", "Mon, 28 Sep 2009 11:42:14 GMT", 0), - ("{urn:schemas:mailheader:}from", "Cyril ", 0), + ("{urn:schemas:mailheader:}cc", + "2message1cc@cyril.dev, user10@cyril.dev", 0), + ("{urn:schemas:mailheader:}date", + "Mon, 28 Sep 2009 11:42:14 GMT", 0), + ("{urn:schemas:mailheader:}from", + "Cyril ", 0), ("{urn:schemas:mailheader:}in-reply-to", None, 0), - ("{urn:schemas:mailheader:}message-id","<4AC1F29sept6.5060801@cyril.dev>", 0), + ("{urn:schemas:mailheader:}message-id", + "<4AC1F29sept6.5060801@cyril.dev>", 0), ("{urn:schemas:mailheader:}received", message1_received, 0), ("{urn:schemas:mailheader:}references", "<4AC3BF1B.3010806@inverse.ca>", 0), diff --git a/Tests/webdavlib.py b/Tests/webdavlib.py index 78843ae2d..6d834d1d2 100644 --- a/Tests/webdavlib.py +++ b/Tests/webdavlib.py @@ -315,7 +315,8 @@ class WebDAVSyncQuery(WebDAVREPORT): self._initProperties(properties) class MailDAVMailQuery(WebDAVREPORT): - def __init__(self, url, properties, filters = None, sort = None): + def __init__(self, url, properties, filters = None, + sort = None, ascending = True): WebDAVQuery.__init__(self, url) mailquery_tag = self.ns_mgr.register("mail-query", xmlns_inversedav) @@ -327,7 +328,7 @@ class MailDAVMailQuery(WebDAVREPORT): self._initFilters(filters) if sort is not None and len(sort) > 0: - self._initSort(sort) + self._initSort(sort, ascending) def _initFilters(self, filters): mailfilter_tag = self.ns_mgr.register("mail-filters", @@ -341,9 +342,13 @@ class MailDAVMailQuery(WebDAVREPORT): filters[filterk]) mailfilter_node.append(filter_node) - def _initSort(self, sort): + def _initSort(self, sort, ascending): sort_tag = self.ns_mgr.register("sort", xmlns_inversedav) - sort_node = _WD_XMLTreeElement(sort_tag) + if ascending: + sort_attrs = { "order": "ascending" } + else: + sort_attrs = { "order": "descending" } + sort_node = _WD_XMLTreeElement(sort_tag, sort_attrs) self.top_node.append(sort_node) for item in sort: