Separating voicemail / texts into their own state machines
[theonering] / src / gvoice / conversations.py
1 #!/usr/bin/python
2
3
4 import logging
5
6 import util.coroutines as coroutines
7 import util.misc as util_misc
8
9
10 _moduleLogger = logging.getLogger("gvoice.conversations")
11
12
13 class Conversations(object):
14
15         def __init__(self, getter):
16                 self._get_raw_conversations = getter
17                 self._conversations = {}
18
19                 self.updateSignalHandler = coroutines.CoTee()
20
21         def update(self, force=False):
22                 if not force and self._conversations:
23                         return
24
25                 oldConversationIds = set(self._conversations.iterkeys())
26
27                 updateConversationIds = set()
28                 conversations = list(self._get_raw_conversations())
29                 conversations.sort()
30                 for conversation in conversations:
31                         key = conversation.contactId, util_misc.strip_number(conversation.number)
32                         try:
33                                 mergedConversations = self._conversations[key]
34                         except KeyError:
35                                 mergedConversations = MergedConversations()
36                                 self._conversations[key] = mergedConversations
37
38                         try:
39                                 mergedConversations.append_conversation(conversation)
40                                 isConversationUpdated = True
41                         except RuntimeError, e:
42                                 if False:
43                                         _moduleLogger.info("Skipping conversation for %r because '%s'" % (key, e))
44                                 isConversationUpdated = False
45
46                         if isConversationUpdated:
47                                 updateConversationIds.add(key)
48
49                 if updateConversationIds:
50                         message = (self, updateConversationIds, )
51                         self.updateSignalHandler.stage.send(message)
52
53         def get_conversations(self):
54                 return self._conversations.iterkeys()
55
56         def get_conversation(self, key):
57                 return self._conversations[key]
58
59         def clear_conversation(self, key):
60                 try:
61                         del self._conversations[key]
62                 except KeyError:
63                         _moduleLogger.info("Conversation never existed for %r" % (key,))
64
65         def clear_all(self):
66                 self._conversations.clear()
67
68
69 class MergedConversations(object):
70
71         def __init__(self):
72                 self._conversations = []
73
74         def append_conversation(self, newConversation):
75                 self._validate(newConversation)
76                 for similarConversation in self._find_related_conversation(newConversation.id):
77                         self._update_previous_related_conversation(similarConversation, newConversation)
78                         self._remove_repeats(similarConversation, newConversation)
79                 self._conversations.append(newConversation)
80
81         @property
82         def conversations(self):
83                 return self._conversations
84
85         def _validate(self, newConversation):
86                 if not self._conversations:
87                         return
88
89                 for constantField in ("contactId", "number"):
90                         assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
91                                 getattr(self._conversations[0], constantField),
92                                 getattr(newConversation, constantField),
93                         )
94
95                 if newConversation.time <= self._conversations[-1].time:
96                         raise RuntimeError("Conversations got out of order")
97
98         def _find_related_conversation(self, convId):
99                 similarConversations = (
100                         conversation
101                         for conversation in self._conversations
102                         if conversation.id == convId
103                 )
104                 return similarConversations
105
106         def _update_previous_related_conversation(self, relatedConversation, newConversation):
107                 for commonField in ("isRead", "isSpam", "isTrash", "isArchived"):
108                         newValue = getattr(newConversation, commonField)
109                         setattr(relatedConversation, commonField, newValue)
110
111         def _remove_repeats(self, relatedConversation, newConversation):
112                 newConversationMessages = newConversation.messages
113                 newConversation.messages = [
114                         newMessage
115                         for newMessage in newConversationMessages
116                         if newMessage not in relatedConversation.messages
117                 ]
118                 _moduleLogger.info("Found %d new messages in conversation %s (%d/%d)" % (
119                         len(newConversationMessages) - len(newConversation.messages),
120                         newConversation.id,
121                         len(newConversation.messages),
122                         len(newConversationMessages),
123                 ))
124                 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"