Skip to content

Commit

Permalink
start workign on a message-based tracker
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Good committed May 22, 2015
1 parent 861b4c2 commit 6220937
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 8 deletions.
66 changes: 66 additions & 0 deletions pymap/msgtracker.py
@@ -0,0 +1,66 @@
# Copyright (c) 2014 Ian C. Good
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

import asyncio

__all__ = ['MessageTracker']


class MessageTracker(object):

def __init__(self):
super().__init__()
self.messages = {}
self.exists = 0
self.recent = 0

@asyncio.coroutine
def update(self, messages):
"""Given a list of messages, return a new tracker and a dictionary of
external updates that need to be returned in untagged FETCH responses.
:param list messages: :class:`~pymap.interfaces.MessageInterface
objects fetched from the backend.
:returns: Two-tuple of the :class:`MessageTracker` object and details
about what has changed since the last :meth:`.update`.
"""
ret = {}
if self.exists != len(messages):
ret['exists'] = self.exists = len(messages)
uid_flags = {msg.uid: (msg.seq, (yield from msg.fetch_flags()))
for msg in messages}
recent = len([True for _, flags in uid_flags.values()
if br'\Recent' in flags])
if self.recent != recent:
ret['recent'] = self.recent = recent
before_uids = frozenset(self.messages.keys())
after_uids = frozenset(uid_flags.keys())
ret['expunge'] = [self.messages[uid][0]
for uid in (before_uids - after_uids)]
ret['flags'] = []
for uid in before_uids & after_uids:
before_flags = frozenset(self.messages[uid][1])
after_flags = frozenset(uid_flags[uid][1])
if before_flags != after_flags:
seq = uid_flags[uid][0]
ret['flags'].append((seq, after_flags))
return ret
18 changes: 10 additions & 8 deletions pymap/state.py
Expand Up @@ -61,6 +61,7 @@ def __init__(self, transport, backend):
self.backend = backend
self.session = None
self.selected = None
self.tracker = None
self.capability = Capability([])

@asyncio.coroutine
Expand Down Expand Up @@ -113,7 +114,7 @@ def do_select(self, cmd):
except MailboxNotFound:
return ResponseNo(cmd.tag, b'Mailbox does not exist.')
self.selected = mbx
yield from mbx.poll()
self.selected_tacker = yield from mbx.init()
code, data = self._get_mailbox_response_data(mbx)
resp = ResponseOk(cmd.tag, b'Selected mailbox.', code)
for data_part in data:
Expand All @@ -127,7 +128,7 @@ def do_examine(self, cmd):
except MailboxNotFound:
return ResponseNo(cmd.tag, b'Mailbox does not exist.')
self.selected = mbx
yield from mbx.poll()
self.selected_tacker = yield from mbx.init()
code, data = self._get_mailbox_response_data(mbx, True)
resp = ResponseOk(cmd.tag, b'Examined mailbox.', code)
for data_part in data:
Expand Down Expand Up @@ -176,7 +177,7 @@ def do_status(self, cmd):
mbx = yield from self.session.get_mailbox(cmd.mailbox)
except MailboxNotFound:
return ResponseNo(cmd.tag, b'Mailbox does not exist.')
yield from mbx.poll()
yield from mbx.init()
resp = ResponseOk(cmd.tag, b'STATUS completed.')
status_list = List([])
for status_item in cmd.status_list:
Expand Down Expand Up @@ -254,6 +255,7 @@ def do_close(self, cmd):
except MailboxReadOnly:
pass
self.selected = None
self.tracker = None
return ResponseOk(cmd.tag, b'CLOSE completed.')

@asyncio.coroutine
Expand Down Expand Up @@ -373,18 +375,18 @@ def do_logout(self, cmd):
@asyncio.coroutine
def _check_mailbox_updates(self, cmd, resp):
send_expunge = not getattr(cmd, 'no_expunge_response', False)
updates = yield from self.selected.poll()
self.tracker, updates = yield from self.selected.poll(self.tracker)
if 'exists' in updates:
resp.add_data(ExistsResponse(updates['exists']))
if 'recent' in updates:
resp.add_data(RecentResponse(updates['recent']))
if 'expunge' in updates and send_expunge:
for seq in updates['expunge']:
resp.add_data(ExpungeResponse(seq))
if 'fetch' in updates:
for msg in updates['fetch']:
fetch_data = yield from msg.fetch([self.flags_attr])
resp.add_data(FetchResponse(msg.seq, fetch_data))
if 'flags' in updates:
for seq, flags in updates['flags']:
fetch_data = {self.flags_attr: List(flags)}
resp.add_data(FetchResponse(seq, fetch_data))

@asyncio.coroutine
def do_command(self, cmd):
Expand Down

0 comments on commit 6220937

Please sign in to comment.