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