3 # @bug Its inconsistent as to whether messages from contacts come as from the
4 # contact or just a raw number
5 # @bug False positives on startup. Luckily the object path for the channel is
6 # unique, so can use that to cache some of the data out to file
10 import util.coroutines as coroutines
11 import util.misc as util_misc
14 _moduleLogger = logging.getLogger("gvoice.conversations")
17 class Conversations(object):
19 def __init__(self, getter):
20 self._get_raw_conversations = getter
21 self._conversations = {}
23 self.updateSignalHandler = coroutines.CoTee()
25 def update(self, force=False):
26 if not force and self._conversations:
29 oldConversationIds = set(self._conversations.iterkeys())
31 updateConversationIds = set()
32 conversations = list(self._get_raw_conversations())
34 for conversation in conversations:
35 key = conversation.contactId, util_misc.strip_number(conversation.number)
37 mergedConversations = self._conversations[key]
39 mergedConversations = MergedConversations()
40 self._conversations[key] = mergedConversations
43 mergedConversations.append_conversation(conversation)
44 isConversationUpdated = True
45 except RuntimeError, e:
47 _moduleLogger.info("Skipping conversation for %r because '%s'" % (key, e))
48 isConversationUpdated = False
50 if isConversationUpdated:
51 updateConversationIds.add(key)
53 if updateConversationIds:
54 message = (self, updateConversationIds, )
55 self.updateSignalHandler.stage.send(message)
57 def get_conversations(self):
58 return self._conversations.iterkeys()
60 def get_conversation(self, key):
61 return self._conversations[key]
63 def clear_conversation(self, key):
65 del self._conversations[key]
67 _moduleLogger.info("Conversation never existed for %r" % (key,))
70 self._conversations.clear()
73 class MergedConversations(object):
76 self._conversations = []
78 def append_conversation(self, newConversation):
79 self._validate(newConversation)
80 for similarConversation in self._find_related_conversation(newConversation.id):
81 self._update_previous_related_conversation(similarConversation, newConversation)
82 self._remove_repeats(similarConversation, newConversation)
83 self._conversations.append(newConversation)
86 def conversations(self):
87 return self._conversations
89 def _validate(self, newConversation):
90 if not self._conversations:
93 for constantField in ("contactId", "number"):
94 assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
95 getattr(self._conversations[0], constantField),
96 getattr(newConversation, constantField),
99 if newConversation.time <= self._conversations[-1].time:
100 raise RuntimeError("Conversations got out of order")
102 def _find_related_conversation(self, convId):
103 similarConversations = (
105 for conversation in self._conversations
106 if conversation.id == convId
108 return similarConversations
110 def _update_previous_related_conversation(self, relatedConversation, newConversation):
111 for commonField in ("isRead", "isSpam", "isTrash", "isArchived"):
112 newValue = getattr(newConversation, commonField)
113 setattr(relatedConversation, commonField, newValue)
115 def _remove_repeats(self, relatedConversation, newConversation):
116 newConversationMessages = newConversation.messages
117 newConversation.messages = [
119 for newMessage in newConversationMessages
120 if newMessage not in relatedConversation.messages
122 _moduleLogger.debug("Found %d new messages in conversation %s (%d/%d)" % (
123 len(newConversationMessages) - len(newConversation.messages),
125 len(newConversation.messages),
126 len(newConversationMessages),
128 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"