Bug fixes, code cleanup, debugging help all working together to make it so that I...
[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                                 _moduleLogger.info("Skipping conversation for %r because '%s'" % (key, e))
42                                 isConversationUpdated = False
43
44                         if isConversationUpdated:
45                                 updateConversationIds.add(key)
46
47                 if updateConversationIds:
48                         message = (self, updateConversationIds, )
49                         self.updateSignalHandler.stage.send(message)
50
51         def get_conversations(self):
52                 return self._conversations.iterkeys()
53
54         def get_conversation(self, key):
55                 return self._conversations[key]
56
57         def clear_conversation(self, key):
58                 try:
59                         del self._conversations[key]
60                 except KeyError:
61                         _moduleLogger.info("Conversation never existed for %r" % (key,))
62
63         def clear_all(self):
64                 self._conversations.clear()
65
66
67 class MergedConversations(object):
68
69         def __init__(self):
70                 self._conversations = []
71
72         def append_conversation(self, newConversation):
73                 self._validate(newConversation)
74                 for similarConversation in self._find_related_conversation(newConversation.id):
75                         self._update_previous_related_conversation(similarConversation, newConversation)
76                         self._remove_repeats(similarConversation, newConversation)
77                 self._conversations.append(newConversation)
78
79         @property
80         def conversations(self):
81                 return self._conversations
82
83         def _validate(self, newConversation):
84                 if not self._conversations:
85                         return
86
87                 for constantField in ("contactId", "number"):
88                         assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
89                                 getattr(self._conversations[0], constantField),
90                                 getattr(newConversation, constantField),
91                         )
92
93                 if newConversation.time <= self._conversations[-1].time:
94                         raise RuntimeError("Conversations got out of order")
95
96         def _find_related_conversation(self, convId):
97                 similarConversations = (
98                         conversation
99                         for conversation in self._conversations
100                         if conversation.id == convId
101                 )
102                 return similarConversations
103
104         def _update_previous_related_conversation(self, relatedConversation, newConversation):
105                 for commonField in ("isRead", "isSpam", "isTrash", "isArchived"):
106                         newValue = getattr(newConversation, commonField)
107                         setattr(relatedConversation, commonField, newValue)
108
109         def _remove_repeats(self, relatedConversation, newConversation):
110                 newConversationMessages = newConversation.messages
111                 newConversation.messages = [
112                         newMessage
113                         for newMessage in newConversationMessages
114                         if newMessage not in relatedConversation.messages
115                 ]
116                 _moduleLogger.info("Found %d new messages in conversation %s (%d/%d)" % (
117                         len(newConversationMessages) - len(newConversation.messages),
118                         newConversation.id,
119                         len(newConversation.messages),
120                         len(newConversationMessages),
121                 ))
122                 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"