--- /dev/null
+#!/usr/bin/python
+
+
+import logging
+
+import util.coroutines as coroutines
+
+import backend
+
+
+_moduleLogger = logging.getLogger("gvoice.conversations")
+
+
+class Conversations(object):
+
+ def __init__(self, backend):
+ self._backend = backend
+ self._conversations = {}
+
+ self.updateSignalHandler = coroutines.CoTee()
+ self.update()
+
+ def update(self, force=False):
+ if not force and self._conversations:
+ return
+
+ oldConversationIds = set(self._conversations.iterkeys())
+
+ updateConversationIds = set()
+ messages = self._backend.get_messages()
+ sortedMessages = backend.sort_messages(messages)
+ for messageData in sortedMessages:
+ key = messageData["contactId"], messageData["number"]
+ try:
+ conversation = self._conversations[key]
+ isNewConversation = False
+ except KeyError:
+ conversation = Conversation(self._backend, messageData)
+ self._conversations[key] = conversation
+ isNewConversation = True
+
+ if isNewConversation:
+ # @todo see if this has issues with a user marking a item as unread/unarchive?
+ isConversationUpdated = True
+ else:
+ isConversationUpdated = conversation.merge_conversation(messageData)
+
+ if isConversationUpdated:
+ updateConversationIds.add(key)
+
+ if updateConversationIds:
+ message = (self, updateConversationIds, )
+ self.updateSignalHandler.stage.send(message)
+
+ def get_conversations(self):
+ return self._conversations.iterkeys()
+
+ def get_conversation(self, key):
+ return self._conversations[key]
+
+
+class Conversation(object):
+
+ def __init__(self, backend, data):
+ self._backend = backend
+ self._data = dict((key, value) for (key, value) in data.iteritems())
+
+ # confirm we have a list
+ self._data["messageParts"] = list(
+ self._append_time(message, self._data["time"])
+ for message in self._data["messageParts"]
+ )
+
+ def __getitem__(self, key):
+ return self._data[key]
+
+ def merge_conversation(self, moreData):
+ """
+ @returns True if there was content to merge (new messages arrived
+ rather than being a duplicate)
+
+ @warning This assumes merges are done in chronological order
+ """
+ for constantField in ("contactId", "number"):
+ assert self._data[constantField] == moreData[constantField], "Constant field changed, soemthing is seriously messed up: %r v %r" % (self._data, moreData)
+
+ if moreData["time"] < self._data["time"]:
+ # If its older, assuming it has nothing new to report
+ return False
+
+ for preferredMoreField in ("id", "name", "time", "relTime", "prettyNumber", "location"):
+ preferredFieldValue = moreData[preferredMoreField]
+ if preferredFieldValue:
+ self._data[preferredMoreField] = preferredFieldValue
+
+ messageAppended = False
+
+ messageParts = self._data["messageParts"]
+ for message in moreData["messageParts"]:
+ messageWithTimestamp = self._append_time(message, moreData["time"])
+ if messageWithTimestamp not in messageParts:
+ messageParts.append(messageWithTimestamp)
+ messageAppended = True
+
+ return messageAppended
+
+ @staticmethod
+ def _append_time(message, exactWhen):
+ whoFrom, message, when = message
+ return whoFrom, message, when, exactWhen
--- /dev/null
+from __future__ import with_statement
+
+import datetime
+import logging
+
+import sys
+sys.path.append("../src")
+
+import util.coroutines as coroutines
+
+import gvoice
+
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+class MockBackend(object):
+
+ def __init__(self, conversationsData):
+ self.conversationsData = conversationsData
+
+ def get_messages(self):
+ return self.conversationsData
+
+
+def generate_update_callback(callbackData):
+
+ @coroutines.func_sink
+ @coroutines.expand_positional
+ def callback(conversations, updatedIds):
+ callbackData.append((conversations, updatedIds))
+
+ return callback
+
+
+def test_no_conversations():
+ callbackData = []
+ callback = generate_update_callback(callbackData)
+
+ backend = MockBackend([])
+ conversings = gvoice.conversations.Conversations(backend)
+ conversings.updateSignalHandler.register_sink(callback)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update()
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update(force=True)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ contacts = list(conversings.get_conversations())
+ assert len(contacts) == 0
+
+
+def test_a_conversation():
+ callbackData = []
+ callback = generate_update_callback(callbackData)
+
+ backend = MockBackend([
+ {
+ "id": "conv1",
+ "contactId": "con1",
+ "name": "Con Man",
+ "time": datetime.datetime(2000, 1, 1),
+ "relTime": "Sometime back",
+ "prettyNumber": "(555) 555-1224",
+ "number": "5555551224",
+ "location": "",
+ "messageParts": [
+ ("Innocent Man", "Body of Message", "Forever ago")
+ ],
+ },
+ ])
+ conversings = gvoice.conversations.Conversations(backend)
+ conversings.updateSignalHandler.register_sink(callback)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ cons = list(conversings.get_conversations())
+ assert len(cons) == 1
+ assert cons[0] == ("con1", "5555551224"), cons
+
+ conversings.update()
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update(force=True)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+
+def test_adding_a_conversation():
+ callbackData = []
+ callback = generate_update_callback(callbackData)
+
+ backend = MockBackend([
+ {
+ "id": "conv1",
+ "contactId": "con1",
+ "name": "Con Man",
+ "time": datetime.datetime(2000, 1, 1),
+ "relTime": "Sometime back",
+ "prettyNumber": "(555) 555-1224",
+ "number": "5555551224",
+ "location": "",
+ "messageParts": [
+ ("Innocent Man", "Body of Message", "Forever ago")
+ ],
+ },
+ ])
+ conversings = gvoice.conversations.Conversations(backend)
+ conversings.updateSignalHandler.register_sink(callback)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ cons = list(conversings.get_conversations())
+ assert len(cons) == 1
+ assert cons[0] == ("con1", "5555551224"), cons
+
+ conversings.update()
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update(force=True)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ backend.conversationsData.append(
+ {
+ "id": "conv2",
+ "contactId": "con2",
+ "name": "Pretty Man",
+ "time": datetime.datetime(2003, 1, 1),
+ "relTime": "Somewhere over the rainbow",
+ "prettyNumber": "(555) 555-2244",
+ "number": "5555552244",
+ "location": "",
+ "messageParts": [
+ ("Con Man", "Body of Message somewhere", "Maybe")
+ ],
+ },
+ )
+
+ conversings.update()
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update(force=True)
+ assert len(callbackData) == 1, "%r" % callbackData
+ idsOnly = callbackData[0][1]
+ assert ("con2", "5555552244") in idsOnly, idsOnly
+
+ cons = list(conversings.get_conversations())
+ assert len(cons) == 2
+ assert ("con1", "5555551224") in cons, cons
+ assert ("con2", "5555552244") in cons, cons
+
+
+def test_merging_a_conversation():
+ callbackData = []
+ callback = generate_update_callback(callbackData)
+
+ backend = MockBackend([
+ {
+ "id": "conv1",
+ "contactId": "con1",
+ "name": "Con Man",
+ "time": datetime.datetime(2000, 1, 1),
+ "relTime": "Sometime back",
+ "prettyNumber": "(555) 555-1224",
+ "number": "5555551224",
+ "location": "",
+ "messageParts": [
+ ("Innocent Man", "Body of Message", "Forever ago")
+ ],
+ },
+ ])
+ conversings = gvoice.conversations.Conversations(backend)
+ conversings.updateSignalHandler.register_sink(callback)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ cons = list(conversings.get_conversations())
+ assert len(cons) == 1
+ assert cons[0] == ("con1", "5555551224"), cons
+
+ conversings.update()
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update(force=True)
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ backend.conversationsData.append(
+ {
+ "id": "conv1",
+ "contactId": "con1",
+ "name": "Con Man",
+ "time": datetime.datetime(2003, 1, 1),
+ "relTime": "Sometime back",
+ "prettyNumber": "(555) 555-1224",
+ "number": "5555551224",
+ "location": "",
+ "messageParts": [
+ ("Innocent Man", "Mwahahaah", "somewhat closer")
+ ],
+ },
+ )
+
+ conversings.update()
+ assert len(callbackData) == 0, "%r" % callbackData
+
+ conversings.update(force=True)
+ assert len(callbackData) == 1, "%r" % callbackData
+ idsOnly = callbackData[0][1]
+ assert ("con1", "5555551224") in idsOnly, idsOnly
+ convseration = conversings.get_conversation(idsOnly.pop())
+ assert len(convseration["messageParts"]) == 2, convseration["messageParts"]