60
58
return make_message(message_id, message.get_payload(), headers, hidden)
61
def threaded_messages(messages):
65
for message in messages:
66
if message.get('replies') is None:
67
threads[message['message_id']] = [message]
70
pending.append(message)
71
for message in pending:
72
threads[message['replies']].append(message)
73
return threads.values()
77
"""A memory-backed message store."""
79
def __init__(self, messages):
81
self.messages = messages
84
def is_multipart(message):
85
return isinstance(message['body'], list)
87
def get_messages(self, archive_id, query_string):
88
"""Return matching messages.
90
:param archive_id: The archive to retrieve from.
91
:param query_string: Contains 'parameters', which is a JSON-format
92
string describing parameters.
94
query = parse_qs(query_string)
95
parameters = simplejson.loads(query['parameters'][0])
96
order = parameters.get('order')
97
messages = self.messages[archive_id]
99
if order not in SUPPORTED_ORDERS:
100
raise UnsupportedOrder
101
elif order.startswith('thread_'):
102
threaded = threaded_messages(messages)
104
if order == 'thread_subject':
105
threaded.sort(key=lambda t: t[0]['subject'])
106
if order == 'thread_oldest':
107
threaded.sort(key=lambda t: min(m['date'] for m in t))
108
if order == 'thread_newest':
109
threaded.sort(key=lambda t: max(m['date'] for m in t))
110
for thread in threaded:
111
messages.extend(thread)
113
messages.sort(key=lambda m: m[order])
114
display_type = parameters.get('display_type', 'all')
115
if display_type not in SUPPORTED_DISPLAY_TYPES:
116
raise UnsupportedDisplayType
117
if 'date_range' in parameters:
119
start_date, end_date = parameters['date_range'].split('..')
120
if not start_date or not end_date:
121
raise UnparsableDateRange
123
raise UnparsableDateRange
125
for message in messages:
126
if (not parameters['include_hidden'] and message['hidden']):
128
if ('message_ids' in parameters
129
and message['message_id'] not in parameters['message_ids']):
131
if ('date_range' in parameters
132
and (message['date'] < start_date
133
or message['date'] > end_date)):
135
message = dict(message)
136
if 'headers' in parameters:
138
(k, v) for k, v in message['headers'].iteritems()
139
if k in parameters['headers'])
140
message['headers'] = headers
141
if display_type == 'headers-only':
143
elif display_type == 'text-only' and self.is_multipart(message):
145
part.get_payload() for part in message['body']
146
if part.get_content_type() == 'text/plain']
147
message['body'] = '\n\n'.join(text_parts)
148
elif display_type == 'all' and self.is_multipart(message):
149
parts = [str(part.get_payload()) for part in message['body']]
150
message['body'] = '\n\n'.join(parts)
151
max_body = parameters.get('max_body_length')
152
if max_body is not None and display_type != 'headers-only':
153
message['body'] = message['body'][:max_body]
154
new_messages.append(message)
155
messages = new_messages
156
limit = parameters.get('limit', 100)
157
memo = parameters.get('memo')
158
message_id_indices = dict(
159
(m['message_id'], idx) for idx, m in enumerate(messages))
163
start = message_id_indices[memo.encode('rot13')]
165
previous_memo = messages[start - 1]['message_id'].encode('rot13')
168
end = min(start + limit, len(messages))
169
if end < len(messages):
170
next_memo = messages[end]['message_id'].encode('rot13')
173
messages = messages[start:end]
176
'messages': messages,
177
'next_memo': next_memo,
178
'previous_memo': previous_memo
63
183
class ForkedFakeService:
64
184
"""A Grackle service fake, as a ContextManager."""
66
def __init__(self, port, message_archives=None, write_logs=False):
186
def __init__(self, port, messages=None, write_logs=False):
69
189
:param port: The tcp port to use.
70
:param message_archives: A dict of lists of dicts representing
71
archives of messages. The outer dict represents the archive,
72
the list represents the list of messages for that archive.
190
:param messages: A dict of lists of dicts representing messages. The
191
outer dict represents the archive, the list represents the list of
192
messages for that archive.
73
193
:param write_logs: If true, log messages will be written to stdout.
77
if message_archives is None:
78
self.message_archives = {}
80
self.message_archives = message_archives
200
self.messages = messages
81
201
self.read_end, self.write_end = os.pipe()
82
202
self.write_logs = write_logs
85
def from_client(client, message_archives=None):
205
def from_client(client, messages=None):
86
206
"""Instantiate a ForkedFakeService from the client.
88
208
:param port: The client to provide service for.
89
:param message_archives: A dict of lists of dicts representing
90
archives of messages. The outer dict represents the archive,
91
the list represents the list of messages for that archive.
209
:param messages: A dict of lists of dicts representing messages. The
210
outer dict represents the archive, the list represents the list of
211
messages for that archive.
93
return ForkedFakeService(client.port, message_archives)
213
return ForkedFakeService(client.port, messages)
95
215
def is_ready(self):
96
216
"""Tell the parent process that the server is ready for writes."""
437
565
self.assertEqual(['bar', 'naf', 'qux'], ids)
439
567
def test_date_range_unparsabledaterange(self):
440
client = GrackleClient('localhost', 8449)
568
client = GrackleClient('localhost', 8448)
441
569
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
442
570
with ForkedFakeService.from_client(client, archive):
443
571
with ExpectedException(UnparsableDateRange, ''):
444
572
client.get_messages('baz', date_range='2012-01-01')
446
574
def test_date_range_unparsabledaterange_missing_part(self):
447
client = GrackleClient('localhost', 8450)
575
client = GrackleClient('localhost', 8448)
448
576
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
449
577
with ForkedFakeService.from_client(client, archive):
450
578
with ExpectedException(UnparsableDateRange, ''):
451
579
client.get_messages('baz', date_range='2012-01-01..')
453
def test_date_range_unparsabledaterange_extra_part(self):
454
client = GrackleClient('localhost', 8451)
455
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
456
with ForkedFakeService.from_client(client, archive):
457
with ExpectedException(UnparsableDateRange, ''):
458
client.get_messages('baz', date_range='2012-01..12-02..12-03')