809d0fc76e72b1f7a9a8a7dfe041083147f2af1e
[theonering] / src / gvoice / conversations.py
1 #!/usr/bin/python
2
3 from __future__ import with_statement
4
5 import logging
6
7 try:
8         import cPickle
9         pickle = cPickle
10 except ImportError:
11         import pickle
12
13 import util.coroutines as coroutines
14 import util.misc as util_misc
15
16
17 _moduleLogger = logging.getLogger("gvoice.conversations")
18
19
20 class Conversations(object):
21
22         def __init__(self, getter):
23                 self._get_raw_conversations = getter
24                 self._conversations = {}
25
26                 self.updateSignalHandler = coroutines.CoTee()
27
28         @property
29         def _name(self):
30                 return repr(self._get_raw_conversations.__name__)
31
32         def load(self, path):
33                 assert not self._conversations
34                 try:
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)
39
40         def save(self, path):
41                 try:
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)
46
47         def update(self, force=False):
48                 if not force and self._conversations:
49                         return
50
51                 oldConversationIds = set(self._conversations.iterkeys())
52
53                 updateConversationIds = set()
54                 conversations = list(self._get_raw_conversations())
55                 conversations.sort()
56                 for conversation in conversations:
57                         key = util_misc.normalize_number(conversation.number)
58                         try:
59                                 mergedConversations = self._conversations[key]
60                         except KeyError:
61                                 mergedConversations = MergedConversations()
62                                 self._conversations[key] = mergedConversations
63
64                         try:
65                                 mergedConversations.append_conversation(conversation)
66                                 isConversationUpdated = True
67                         except RuntimeError, e:
68                                 if False:
69                                         _moduleLogger.debug("%s Skipping conversation for %r because '%s'" % (self._name, key, e))
70                                 isConversationUpdated = False
71
72                         if isConversationUpdated:
73                                 updateConversationIds.add(key)
74
75                 if updateConversationIds:
76                         message = (self, updateConversationIds, )
77                         self.updateSignalHandler.stage.send(message)
78
79         def get_conversations(self):
80                 return self._conversations.iterkeys()
81
82         def get_conversation(self, key):
83                 return self._conversations[key]
84
85         def clear_conversation(self, key):
86                 try:
87                         del self._conversations[key]
88                 except KeyError:
89                         _moduleLogger.info("%s Conversation never existed for %r" % (self._name, key, ))
90
91         def clear_all(self):
92                 self._conversations.clear()
93
94
95 class MergedConversations(object):
96
97         def __init__(self):
98                 self._conversations = []
99
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)
106
107         def to_dict(self):
108                 selfDict = {}
109                 selfDict["conversations"] = [conv.to_dict() for conv in self._conversations]
110                 return selfDict
111
112         @property
113         def conversations(self):
114                 return self._conversations
115
116         def _validate(self, newConversation):
117                 if not self._conversations:
118                         return
119
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),
124                         )
125
126                 if newConversation.time <= self._conversations[-1].time:
127                         raise RuntimeError("Conversations got out of order")
128
129         def _find_related_conversation(self, convId):
130                 similarConversations = (
131                         conversation
132                         for conversation in self._conversations
133                         if conversation.id == convId
134                 )
135                 return similarConversations
136
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)
141
142         def _remove_repeats(self, relatedConversation, newConversation):
143                 newConversationMessages = newConversation.messages
144                 newConversation.messages = [
145                         newMessage
146                         for newMessage in newConversationMessages
147                         if newMessage not in relatedConversation.messages
148                 ]
149                 _moduleLogger.debug("Found %d new messages in conversation %s (%d/%d)" % (
150                         len(newConversationMessages) - len(newConversation.messages),
151                         newConversation.id,
152                         len(newConversation.messages),
153                         len(newConversationMessages),
154                 ))
155                 assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"