3 from __future__ import with_statement
14 import util.coroutines as coroutines
15 import util.misc as util_misc
18 _moduleLogger = logging.getLogger("gvoice.conversations")
21 class Conversations(object):
23 def __init__(self, getter):
24 self._get_raw_conversations = getter
25 self._conversations = {}
27 self.updateSignalHandler = coroutines.CoTee()
31 return repr(self._get_raw_conversations.__name__)
34 assert not self._conversations
36 with open(path, "rb") as f:
37 fileVersion, fileBuild, convs = pickle.load(f)
38 except (pickle.PickleError, IOError):
39 _moduleLogger.exception("While loading for %s" % self._name)
42 if fileVersion == constants.__version__ and fileBuild == constants.__build__:
43 self._conversations = convs
46 "%s Skipping cache due to version mismatch (%s-%s)" % (self._name, fileVersion, fileBuild)
51 dataToDump = (constants.__version__, constants.__build__, self._conversations)
52 with open(path, "wb") as f:
53 pickle.dump(dataToDump, f, pickle.HIGHEST_PROTOCOL)
54 except (pickle.PickleError, IOError):
55 _moduleLogger.exception("While saving for %s" % self._name)
57 def update(self, force=False):
58 if not force and self._conversations:
61 oldConversationIds = set(self._conversations.iterkeys())
63 updateConversationIds = set()
64 conversations = list(self._get_raw_conversations())
66 for conversation in conversations:
67 key = util_misc.normalize_number(conversation.number)
69 mergedConversations = self._conversations[key]
71 mergedConversations = MergedConversations()
72 self._conversations[key] = mergedConversations
75 mergedConversations.append_conversation(conversation)
76 isConversationUpdated = True
77 except RuntimeError, e:
79 _moduleLogger.debug("%s Skipping conversation for %r because '%s'" % (self._name, key, e))
80 isConversationUpdated = False
82 if isConversationUpdated:
83 updateConversationIds.add(key)
85 if updateConversationIds:
86 message = (self, updateConversationIds, )
87 self.updateSignalHandler.stage.send(message)
89 def get_conversations(self):
90 return self._conversations.iterkeys()
92 def get_conversation(self, key):
93 return self._conversations[key]
95 def clear_conversation(self, key):
97 del self._conversations[key]
99 _moduleLogger.info("%s Conversation never existed for %r" % (self._name, key, ))
102 self._conversations.clear()
105 class MergedConversations(object):
108 self._conversations = []
110 def append_conversation(self, newConversation):
111 self._validate(newConversation)
113 for similarConversation in self._find_related_conversation(newConversation.id):
114 self._update_previous_related_conversation(similarConversation, newConversation)
115 self._remove_repeats(similarConversation, newConversation)
118 if newConversation.messages:
119 newConversation.isRead = False
121 newConversation.isRead = True
122 self._conversations.append(newConversation)
126 selfDict["conversations"] = [conv.to_dict() for conv in self._conversations]
130 def conversations(self):
131 return self._conversations
133 def _validate(self, newConversation):
134 if not self._conversations:
137 for constantField in ("number", ):
138 assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
139 getattr(self._conversations[0], constantField),
140 getattr(newConversation, constantField),
143 if newConversation.time <= self._conversations[-1].time:
144 raise RuntimeError("Conversations got out of order")
146 def _find_related_conversation(self, convId):
147 similarConversations = (
149 for conversation in self._conversations
150 if conversation.id == convId
152 return similarConversations
154 def _update_previous_related_conversation(self, relatedConversation, newConversation):
155 for commonField in ("isSpam", "isTrash", "isArchived"):
156 newValue = getattr(newConversation, commonField)
157 setattr(relatedConversation, commonField, newValue)
159 def _remove_repeats(self, relatedConversation, newConversation):
160 newConversationMessages = newConversation.messages
161 newConversation.messages = [
163 for newMessage in newConversationMessages
164 if newMessage not in relatedConversation.messages
166 _moduleLogger.debug("Found %d new messages in conversation %s (%d/%d)" % (
167 len(newConversationMessages) - len(newConversation.messages),
169 len(newConversation.messages),
170 len(newConversationMessages),
172 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"