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.info("%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)
108 def conversations(self):
109 return self._conversations
111 def _validate(self, newConversation):
112 if not self._conversations:
115 for constantField in ("number", ):
116 assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
117 getattr(self._conversations[0], constantField),
118 getattr(newConversation, constantField),
121 if newConversation.time <= self._conversations[-1].time:
122 raise RuntimeError("Conversations got out of order")
124 def _find_related_conversation(self, convId):
125 similarConversations = (
127 for conversation in self._conversations
128 if conversation.id == convId
130 return similarConversations
132 def _update_previous_related_conversation(self, relatedConversation, newConversation):
133 for commonField in ("isRead", "isSpam", "isTrash", "isArchived"):
134 newValue = getattr(newConversation, commonField)
135 setattr(relatedConversation, commonField, newValue)
137 def _remove_repeats(self, relatedConversation, newConversation):
138 newConversationMessages = newConversation.messages
139 newConversation.messages = [
141 for newMessage in newConversationMessages
142 if newMessage not in relatedConversation.messages
144 _moduleLogger.debug("%s Found %d new messages in conversation %s (%d/%d)" % (
146 len(newConversationMessages) - len(newConversation.messages),
148 len(newConversation.messages),
149 len(newConversationMessages),
151 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"