3 from __future__ import with_statement
13 import util.coroutines as coroutines
14 import util.misc as util_misc
17 _moduleLogger = logging.getLogger("gvoice.conversations")
20 class Conversations(object):
22 def __init__(self, getter):
23 self._get_raw_conversations = getter
24 self._conversations = {}
26 self.updateSignalHandler = coroutines.CoTee()
30 return repr(self._get_raw_conversations.__name__)
33 assert not self._conversations
35 with open(path, "rb") as f:
36 self._conversations = pickle.load(f)
37 except (pickle.PickleError, IOError):
38 _moduleLogger.exception("While loading for %s" % self._name)
42 with open(path, "wb") as f:
43 pickle.dump(self._conversations, f, pickle.HIGHEST_PROTOCOL)
44 except (pickle.PickleError, IOError):
45 _moduleLogger.exception("While saving for %s" % self._name)
47 def update(self, force=False):
48 if not force and self._conversations:
51 oldConversationIds = set(self._conversations.iterkeys())
53 updateConversationIds = set()
54 conversations = list(self._get_raw_conversations())
56 for conversation in conversations:
57 key = util_misc.normalize_number(conversation.number)
59 mergedConversations = self._conversations[key]
61 mergedConversations = MergedConversations()
62 self._conversations[key] = mergedConversations
65 mergedConversations.append_conversation(conversation)
66 isConversationUpdated = True
67 except RuntimeError, e:
69 _moduleLogger.debug("%s Skipping conversation for %r because '%s'" % (self._name, key, e))
70 isConversationUpdated = False
72 if isConversationUpdated:
73 updateConversationIds.add(key)
75 if updateConversationIds:
76 message = (self, updateConversationIds, )
77 self.updateSignalHandler.stage.send(message)
79 def get_conversations(self):
80 return self._conversations.iterkeys()
82 def get_conversation(self, key):
83 return self._conversations[key]
85 def clear_conversation(self, key):
87 del self._conversations[key]
89 _moduleLogger.info("%s Conversation never existed for %r" % (self._name, key, ))
92 self._conversations.clear()
95 class MergedConversations(object):
98 self._conversations = []
100 def append_conversation(self, newConversation):
101 self._validate(newConversation)
102 for similarConversation in self._find_related_conversation(newConversation.id):
103 self._update_previous_related_conversation(similarConversation, newConversation)
104 self._remove_repeats(similarConversation, newConversation)
105 self._conversations.append(newConversation)
109 selfDict["conversations"] = [conv.to_dict() for conv in self._conversations]
113 def conversations(self):
114 return self._conversations
116 def _validate(self, newConversation):
117 if not self._conversations:
120 for constantField in ("number", ):
121 assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
122 getattr(self._conversations[0], constantField),
123 getattr(newConversation, constantField),
126 if newConversation.time <= self._conversations[-1].time:
127 raise RuntimeError("Conversations got out of order")
129 def _find_related_conversation(self, convId):
130 similarConversations = (
132 for conversation in self._conversations
133 if conversation.id == convId
135 return similarConversations
137 def _update_previous_related_conversation(self, relatedConversation, newConversation):
138 for commonField in ("isRead", "isSpam", "isTrash", "isArchived"):
139 newValue = getattr(newConversation, commonField)
140 setattr(relatedConversation, commonField, newValue)
142 def _remove_repeats(self, relatedConversation, newConversation):
143 newConversationMessages = newConversation.messages
144 newConversation.messages = [
146 for newMessage in newConversationMessages
147 if newMessage not in relatedConversation.messages
149 _moduleLogger.debug("Found %d new messages in conversation %s (%d/%d)" % (
150 len(newConversationMessages) - len(newConversation.messages),
152 len(newConversation.messages),
153 len(newConversationMessages),
155 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"