c8f623a84873705d8b40a5a830ce2ea95eb68481
[theonering] / src / gvoice / conversations.py
1 #!/usr/bin/python
2
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
7
8 import logging
9
10 import util.coroutines as coroutines
11 import util.misc as util_misc
12
13
14 _moduleLogger = logging.getLogger("gvoice.conversations")
15
16
17 class Conversations(object):
18
19         def __init__(self, getter):
20                 self._get_raw_conversations = getter
21                 self._conversations = {}
22
23                 self.updateSignalHandler = coroutines.CoTee()
24
25         def update(self, force=False):
26                 if not force and self._conversations:
27                         return
28
29                 oldConversationIds = set(self._conversations.iterkeys())
30
31                 updateConversationIds = set()
32                 conversations = list(self._get_raw_conversations())
33                 conversations.sort()
34                 for conversation in conversations:
35                         key = conversation.contactId, util_misc.strip_number(conversation.number)
36                         try:
37                                 mergedConversations = self._conversations[key]
38                         except KeyError:
39                                 mergedConversations = MergedConversations()
40                                 self._conversations[key] = mergedConversations
41
42                         try:
43                                 mergedConversations.append_conversation(conversation)
44                                 isConversationUpdated = True
45                         except RuntimeError, e:
46                                 if False:
47                                         _moduleLogger.info("Skipping conversation for %r because '%s'" % (key, e))
48                                 isConversationUpdated = False
49
50                         if isConversationUpdated:
51                                 updateConversationIds.add(key)
52
53                 if updateConversationIds:
54                         message = (self, updateConversationIds, )
55                         self.updateSignalHandler.stage.send(message)
56
57         def get_conversations(self):
58                 return self._conversations.iterkeys()
59
60         def get_conversation(self, key):
61                 return self._conversations[key]
62
63         def clear_conversation(self, key):
64                 try:
65                         del self._conversations[key]
66                 except KeyError:
67                         _moduleLogger.info("Conversation never existed for %r" % (key,))
68
69         def clear_all(self):
70                 self._conversations.clear()
71
72
73 class MergedConversations(object):
74
75         def __init__(self):
76                 self._conversations = []
77
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)
84
85         @property
86         def conversations(self):
87                 return self._conversations
88
89         def _validate(self, newConversation):
90                 if not self._conversations:
91                         return
92
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),
97                         )
98
99                 if newConversation.time <= self._conversations[-1].time:
100                         raise RuntimeError("Conversations got out of order")
101
102         def _find_related_conversation(self, convId):
103                 similarConversations = (
104                         conversation
105                         for conversation in self._conversations
106                         if conversation.id == convId
107                 )
108                 return similarConversations
109
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)
114
115         def _remove_repeats(self, relatedConversation, newConversation):
116                 newConversationMessages = newConversation.messages
117                 newConversation.messages = [
118                         newMessage
119                         for newMessage in newConversationMessages
120                         if newMessage not in relatedConversation.messages
121                 ]
122                 _moduleLogger.debug("Found %d new messages in conversation %s (%d/%d)" % (
123                         len(newConversationMessages) - len(newConversation.messages),
124                         newConversation.id,
125                         len(newConversation.messages),
126                         len(newConversationMessages),
127                 ))
128                 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"