Massive reworking of messages to make debugging easier along with some code cleaning...
authorEd Page <eopage@byu.net>
Sun, 20 Dec 2009 03:54:19 +0000 (21:54 -0600)
committerEd Page <eopage@byu.net>
Sun, 20 Dec 2009 03:54:19 +0000 (21:54 -0600)
hand_tests/conv.py [new file with mode: 0755]
hand_tests/generic.py
hand_tests/gv.py [new file with mode: 0755]
hand_tests/sm.py
src/channel/text.py
src/gtk_toolbox.py
src/gvoice/backend.py
src/gvoice/conversations.py
src/gvoice/session.py
src/gvoice/state_machine.py
src/util/linux.py

diff --git a/hand_tests/conv.py b/hand_tests/conv.py
new file mode 100755 (executable)
index 0000000..8655396
--- /dev/null
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+
+import sys
+sys.path.insert(0,"../src")
+import pprint
+import logging
+
+import gvoice.backend as backend
+import gvoice.conversations as conversations
+
+
+def main():
+       logging.basicConfig(level=logging.DEBUG)
+
+       args = sys.argv
+       username = args[1]
+       password = args[2]
+
+       b = backend.GVoiceBackend()
+       b.login(username, password)
+
+       c = conversations.Conversations(b)
+
+       c.update(force=True)
+       for key in c.get_conversations():
+               print "="*50
+               print key
+               for conv in c.get_conversation(key).conversations:
+                       pprint.pprint(conv.to_dict())
+
+
+if __name__ == "__main__":
+       main()
index 2e69a5d..a6e4d0b 100755 (executable)
@@ -501,12 +501,12 @@ if __name__ == '__main__':
                                lastAction.append_action(sps)
                                lastAction = sps
 
-               if True:
+               if False:
                        sl = Sleep(10 * 1000)
                        lastAction.append_action(sl)
                        lastAction = sl
 
-               if True:
+               if False:
                        rclh = RequestHandle(con, telepathy.HANDLE_TYPE_LIST, ["subscribe"])
                        lastAction.append_action(rclh)
                        lastAction = rclh
diff --git a/hand_tests/gv.py b/hand_tests/gv.py
new file mode 100755 (executable)
index 0000000..a254d7e
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+
+import sys
+sys.path.insert(0,"../src")
+import pprint
+import logging
+
+import gvoice.backend as backend
+
+
+def main():
+       logging.basicConfig(level=logging.DEBUG)
+
+       args = sys.argv
+       username = args[1]
+       password = args[2]
+
+       b = backend.GVoiceBackend()
+       if False:
+               print "Authenticated: ", b.is_authed()
+               if not b.is_authed():
+                       print "Login?: ", b.login(username, password)
+               print "Authenticated: ", b.is_authed()
+       else:
+               b.login(username, password)
+
+       if False:
+               print "Is Dnd: ", b.is_dnd()
+               print "Setting Dnd", b.set_dnd(True)
+               print "Is Dnd: ", b.is_dnd()
+               print "Setting Dnd", b.set_dnd(False)
+               print "Is Dnd: ", b.is_dnd()
+
+       if False:
+               print "Token: ", b._token
+               print "Account: ", b.get_account_number()
+               print "Callback: ", b.get_callback_number()
+               print "All Callback: ",
+               pprint.pprint(b.get_callback_numbers())
+
+       if False:
+               print "Recent: "
+               for data in b.get_recent():
+                       pprint.pprint(data)
+
+       if False:
+               print "Contacts: ",
+               for contact in b.get_contacts():
+                       pprint.pprint(contact)
+
+       if True:
+               print "Messages: ",
+               for message in b.get_conversations():
+                       pprint.pprint(message.to_dict())
+
+       return b
+
+
+if __name__ == "__main__":
+       main()
index f3c1e3b..0fac9da 100755 (executable)
@@ -3,6 +3,7 @@
 import threading
 import datetime
 import time
+import logging
 
 import gtk
 
@@ -31,6 +32,7 @@ def loop(state):
 
 
 def main():
+       logging.basicConfig(level=logging.DEBUG)
        startTime = datetime.datetime.now()
 
        state = [True]
index 050b6a8..22e840d 100644 (file)
@@ -36,11 +36,11 @@ class TextChannel(telepathy.server.ChannelTypeText):
                # The only reason there should be anything in the conversation is if
                # its new, so report it all
                try:
-                       conversation = self._conn.session.conversations.get_conversation(self._contactKey)
+                       mergedConversations = self._conn.session.conversations.get_conversation(self._contactKey)
                except KeyError:
                        pass
                else:
-                       self._report_conversation(conversation)
+                       self._report_conversation(mergedConversations)
 
        @gtk_toolbox.log_exception(_moduleLogger)
        def Send(self, messageType, text):
@@ -75,26 +75,49 @@ class TextChannel(telepathy.server.ChannelTypeText):
                if self._contactKey not in conversationIds:
                        return
                _moduleLogger.info("Incoming messages from %r for existing conversation" % (self._contactKey, ))
-               conversation = self._conn.session.conversations.get_conversation(self._contactKey)
-               self._report_conversation(conversation)
-
-       def _report_conversation(self, conversation):
-               # @bug? Check if messages sent need to be filtered out
-               completeMessageHistory = conversation["messageParts"]
-               messages = self._filter_seen_messages(completeMessageHistory)
-               self._lastMessageTimestamp = messages[-1][0]
-               formattedMessage = self._format_messages(messages)
-               self._report_new_message(formattedMessage)
-
-       def _filter_seen_messages(self, messages):
-               return [
-                       message
-                       for message in messages
-                       if self._lastMessageTimestamp < message[0]
-               ]
-
-       def _format_messages(self, messages):
-               return "\n".join(message[1] for message in messages)
+               mergedConversations = self._conn.session.conversations.get_conversation(self._contactKey)
+               self._report_conversation(mergedConversations)
+
+       def _report_conversation(self, mergedConversations):
+               newConversations = mergedConversations.conversations
+               newConversations = list(newConversations)
+               lenMerged = len(newConversations)
+               newConversations = self._filter_out_reported(newConversations)
+               newConversations = list(newConversations)
+               lenUnreported = len(newConversations)
+               newConversations = self._filter_out_read(newConversations)
+               newConversations = list(newConversations)
+               lenUnread = len(newConversations)
+               if not newConversations:
+                       _moduleLogger.info(
+                               "New messages for %r have already been read externally" % (self._contactKey, )
+                       )
+                       return
+               _moduleLogger.debug("%s, %s, %s" % (lenMerged, lenUnreported, lenUnread))
+               self._lastMessageTimestamp = newConversations[-1].time
+
+               for newConversation in newConversations:
+                       for newMessage in newConversations.messages:
+                               if newMessage.name != "Me:":
+                                       formattedMessage = self._format_message(newMessage)
+                                       self._report_new_message(formattedMessage)
+
+       def _filter_out_reported(self, conversations):
+               return (
+                       conversation
+                       for conversation in conversations
+                       if self._lastMessageTimestamp < conversation.time
+               )
+
+       def _filter_out_read(self, conversations):
+               return (
+                       conversation
+                       for conversation in conversations
+                       if not conversation.isRead and not conversation.isArchived
+               )
+
+       def _format_message(self, message):
+               return " ".join(part.text.strip() for part in message.body)
 
        def _report_new_message(self, message):
                currentReceivedId = self._nextRecievedId
index c4203b0..97acd2f 100644 (file)
@@ -213,7 +213,7 @@ def safecall(f, errorDisplay=None, default=None, exception=Exception):
        '''
        def _safecall(*args, **argv):
                try:
-                       return f(*args,**argv)
+                       return f(*args, **argv)
                except exception, e:
                        if errorDisplay is not None:
                                errorDisplay.push_exception(e)
index b2b01a3..251cfa5 100755 (executable)
@@ -35,7 +35,7 @@ import time
 import datetime
 import itertools
 import logging
-from xml.sax import saxutils
+import inspect
 
 from xml.etree import ElementTree
 
@@ -55,6 +55,90 @@ class NetworkError(RuntimeError):
        pass
 
 
+class MessageText(object):
+
+       ACCURACY_LOW = "med1"
+       ACCURACY_MEDIUM = "med2"
+       ACCURACY_HIGH = "high"
+
+       def __init__(self):
+               self.accuracy = None
+               self.text = None
+
+       def __str__(self):
+               return self.text
+
+       def to_dict(self):
+               return to_dict(self)
+
+       def __eq__(self, other):
+               return self.accuracy == other.accuracy and self.text == other.text
+
+
+class Message(object):
+
+       def __init__(self):
+               self.whoFrom = None
+               self.body = None
+               self.when = None
+
+       def __str__(self):
+               return "%s (%s): %s" % (
+                       self.whoFrom,
+                       self.when,
+                       "".join(str(part) for part in self.body)
+               )
+
+       def to_dict(self):
+               selfDict = to_dict(self)
+               selfDict["body"] = [text.to_dict() for text in self.body] if self.body is not None else None
+               return selfDict
+
+       def __eq__(self, other):
+               return self.whoFrom == other.whoFrom and self.when == other.when and self.body == other.body
+
+
+class Conversation(object):
+
+       TYPE_VOICEMAIL = "Voicemail"
+       TYPE_SMS = "SMS"
+
+       def __init__(self):
+               self.type = None
+               self.id = None
+               self.contactId = None
+               self.name = None
+               self.location = None
+               self.prettyNumber = None
+               self.number = None
+
+               self.time = None
+               self.relTime = None
+               self.messages = None
+               self.isRead = None
+               self.isSpam = None
+               self.isTrash = None
+               self.isArchived = None
+
+       def __cmp__(self, other):
+               cmpValue = cmp(self.contactId, other.contactId)
+               if cmpValue != 0:
+                       return cmpValue
+
+               cmpValue = cmp(self.time, other.time)
+               if cmpValue != 0:
+                       return cmpValue
+
+               cmpValue = cmp(self.id, other.id)
+               if cmpValue != 0:
+                       return cmpValue
+
+       def to_dict(self):
+               selfDict = to_dict(self)
+               selfDict["messages"] = [message.to_dict() for message in self.messages] if self.messages is not None else None
+               return selfDict
+
+
 class GVoiceBackend(object):
        """
        This class encapsulates all of the knowledge necessary to interact with the GoogleVoice servers
@@ -96,6 +180,8 @@ class GVoiceBackend(object):
                self._setDndURL = "https://www.google.com/voice/m/savednd"
 
                self._downloadVoicemailURL = SECURE_URL_BASE + "media/send_voicemail/"
+               self._markAsReadURL = SECURE_URL_BASE + "m/mark"
+               self._archiveMessageURL = SECURE_URL_BASE + "m/archive"
 
                self._XML_SEARCH_URL = SECURE_URL_BASE + "inbox/search/"
                self._XML_ACCOUNT_URL = SECURE_URL_BASE + "contacts/"
@@ -235,10 +321,9 @@ class GVoiceBackend(object):
        def set_dnd(self, doNotDisturb):
                dndPostData = {
                        "doNotDisturb": 1 if doNotDisturb else 0,
-                       "_rnr_se": self._token,
                }
 
-               dndPage = self._get_page(self._setDndURL, dndPostData)
+               dndPage = self._get_page_with_token(self._setDndURL, dndPostData)
 
        def call(self, outgoingNumber):
                """
@@ -372,7 +457,7 @@ class GVoiceBackend(object):
                        flatXml = self._get_page(url)
 
                        allRecentHtml = self._grab_html(flatXml)
-                       allRecentData = self._parse_voicemail(allRecentHtml)
+                       allRecentData = self._parse_history(allRecentHtml)
                        for recentCallData in allRecentData:
                                recentCallData["action"] = action
                                yield recentCallData
@@ -391,23 +476,37 @@ class GVoiceBackend(object):
                        if contactId != "0":
                                yield contactId, contactDetails
 
-       def get_messages(self):
+       def get_conversations(self):
                voicemailPage = self._get_page(self._XML_VOICEMAIL_URL)
                voicemailHtml = self._grab_html(voicemailPage)
                voicemailJson = self._grab_json(voicemailPage)
                parsedVoicemail = self._parse_voicemail(voicemailHtml)
-               voicemails = self._merge_messages(parsedVoicemail, voicemailJson)
-               decoratedVoicemails = self._decorate_voicemail(voicemails)
+               voicemails = self._merge_conversation_sources(parsedVoicemail, voicemailJson)
 
                smsPage = self._get_page(self._XML_SMS_URL)
                smsHtml = self._grab_html(smsPage)
                smsJson = self._grab_json(smsPage)
                parsedSms = self._parse_sms(smsHtml)
-               smss = self._merge_messages(parsedSms, smsJson)
+               smss = self._merge_conversation_sources(parsedSms, smsJson)
                decoratedSms = self._decorate_sms(smss)
 
-               allMessages = itertools.chain(decoratedVoicemails, decoratedSms)
-               return allMessages
+               allConversations = itertools.chain(voicemails, decoratedSms)
+               return allConversations
+
+       def mark_message(self, messageId, asRead):
+               postData = {
+                       "read": 1 if asRead else 0,
+                       "id": messageId,
+               }
+
+               markPage = self._get_page(self._markAsReadURL, postData)
+
+       def archive_message(self, messageId):
+               postData = {
+                       "id": messageId,
+               }
+
+               markPage = self._get_page(self._archiveMessageURL, postData)
 
        def _grab_json(self, flatXml):
                xmlTree = ElementTree.fromstring(flatXml)
@@ -453,16 +552,8 @@ class GVoiceBackend(object):
                        number = number[1:]
                return number
 
-       @staticmethod
-       def _interpret_voicemail_regex(group):
-               quality, content, number = group.group(2), group.group(3), group.group(4)
-               if quality is not None and content is not None:
-                       return quality, content
-               elif number is not None:
-                       return "high", number
-
-       def _parse_voicemail(self, voicemailHtml):
-               splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
+       def _parse_history(self, historyHtml):
+               splitVoicemail = self._seperateVoicemailsRegex.split(historyHtml)
                for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
                        exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
                        exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
@@ -481,12 +572,6 @@ class GVoiceBackend(object):
                        contactIdGroup = self._messagesContactIDRegex.search(messageHtml)
                        contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
 
-                       messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
-                       messageParts = (
-                               self._interpret_voicemail_regex(group)
-                               for group in messageGroups
-                       ) if messageGroups else ()
-
                        yield {
                                "id": messageId.strip(),
                                "contactId": contactId,
@@ -496,45 +581,93 @@ class GVoiceBackend(object):
                                "prettyNumber": prettyNumber,
                                "number": number,
                                "location": location,
-                               "messageParts": messageParts,
-                               "type": "Voicemail",
                        }
 
-       def _decorate_voicemail(self, parsedVoicemails):
-               messagePartFormat = {
-                       "med1": "<i>%s</i>",
-                       "med2": "%s",
-                       "high": "<b>%s</b>",
-               }
-               for voicemailData in parsedVoicemails:
-                       message = " ".join((
-                               messagePartFormat[quality] % part
-                               for (quality, part) in voicemailData["messageParts"]
-                       )).strip()
-                       if not message:
-                               message = "No Transcription"
-                       whoFrom = voicemailData["name"]
-                       when = voicemailData["time"]
-                       voicemailData["messageParts"] = ((whoFrom, message, when), )
-                       yield voicemailData
+       @staticmethod
+       def _interpret_voicemail_regex(group):
+               quality, content, number = group.group(2), group.group(3), group.group(4)
+               text = MessageText()
+               if quality is not None and content is not None:
+                       text.accuracy = quality
+                       text.text = content
+                       return text
+               elif number is not None:
+                       text.accuracy = MessageText.ACCURACY_HIGH
+                       text.text = number
+                       return text
+
+       def _parse_voicemail(self, voicemailHtml):
+               splitVoicemail = self._seperateVoicemailsRegex.split(voicemailHtml)
+               for messageId, messageHtml in itergroup(splitVoicemail[1:], 2):
+                       conv = Conversation()
+                       conv.type = Conversation.TYPE_VOICEMAIL
+                       conv.id = messageId.strip()
+
+                       exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
+                       exactTimeText = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
+                       conv.time = datetime.datetime.strptime(exactTimeText, "%m/%d/%y %I:%M %p")
+                       relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
+                       conv.relTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
+                       locationGroup = self._voicemailLocationRegex.search(messageHtml)
+                       conv.location = locationGroup.group(1).strip() if locationGroup else ""
+
+                       nameGroup = self._voicemailNameRegex.search(messageHtml)
+                       conv.name = nameGroup.group(1).strip() if nameGroup else ""
+                       numberGroup = self._voicemailNumberRegex.search(messageHtml)
+                       conv.number = numberGroup.group(1).strip() if numberGroup else ""
+                       prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
+                       conv.prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
+                       contactIdGroup = self._messagesContactIDRegex.search(messageHtml)
+                       conv.contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
+
+                       messageGroups = self._voicemailMessageRegex.finditer(messageHtml)
+                       messageParts = [
+                               self._interpret_voicemail_regex(group)
+                               for group in messageGroups
+                       ] if messageGroups else ((MessageText.ACCURACY_LOW, "No Transcription"), )
+                       message = Message()
+                       message.body = messageParts
+                       message.whoFrom = conv.name
+                       message.when = conv.time.strftime("%I:%M %p")
+                       conv.messages = (message, )
+
+                       yield conv
+
+       @staticmethod
+       def _interpret_sms_message_parts(fromPart, textPart, timePart):
+               text = MessageText()
+               text.accuracy = MessageText.ACCURACY_MEDIUM
+               text.text = textPart
+
+               message = Message()
+               message.body = (text, )
+               message.whoFrom = fromPart
+               message.when = timePart
+
+               return message
 
        def _parse_sms(self, smsHtml):
                splitSms = self._seperateVoicemailsRegex.split(smsHtml)
                for messageId, messageHtml in itergroup(splitSms[1:], 2):
+                       conv = Conversation()
+                       conv.type = Conversation.TYPE_SMS
+                       conv.id = messageId.strip()
+
                        exactTimeGroup = self._exactVoicemailTimeRegex.search(messageHtml)
-                       exactTime = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
-                       exactTime = datetime.datetime.strptime(exactTime, "%m/%d/%y %I:%M %p")
+                       exactTimeText = exactTimeGroup.group(1).strip() if exactTimeGroup else ""
+                       conv.time = datetime.datetime.strptime(exactTimeText, "%m/%d/%y %I:%M %p")
                        relativeTimeGroup = self._relativeVoicemailTimeRegex.search(messageHtml)
-                       relativeTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
+                       conv.relTime = relativeTimeGroup.group(1).strip() if relativeTimeGroup else ""
+                       conv.location = ""
 
                        nameGroup = self._voicemailNameRegex.search(messageHtml)
-                       name = nameGroup.group(1).strip() if nameGroup else ""
+                       conv.name = nameGroup.group(1).strip() if nameGroup else ""
                        numberGroup = self._voicemailNumberRegex.search(messageHtml)
-                       number = numberGroup.group(1).strip() if numberGroup else ""
+                       conv.number = numberGroup.group(1).strip() if numberGroup else ""
                        prettyNumberGroup = self._prettyVoicemailNumberRegex.search(messageHtml)
-                       prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
+                       conv.prettyNumber = prettyNumberGroup.group(1).strip() if prettyNumberGroup else ""
                        contactIdGroup = self._messagesContactIDRegex.search(messageHtml)
-                       contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
+                       conv.contactId = contactIdGroup.group(1).strip() if contactIdGroup else ""
 
                        fromGroups = self._smsFromRegex.finditer(messageHtml)
                        fromParts = (group.group(1).strip() for group in fromGroups)
@@ -544,32 +677,22 @@ class GVoiceBackend(object):
                        timeParts = (group.group(1).strip() for group in timeGroups)
 
                        messageParts = itertools.izip(fromParts, textParts, timeParts)
+                       messages = [self._interpret_sms_message_parts(*parts) for parts in messageParts]
+                       conv.messages = messages
 
-                       yield {
-                               "id": messageId.strip(),
-                               "contactId": contactId,
-                               "name": name,
-                               "time": exactTime,
-                               "relTime": relativeTime,
-                               "prettyNumber": prettyNumber,
-                               "number": number,
-                               "location": "",
-                               "messageParts": messageParts,
-                               "type": "Texts",
-                       }
+                       yield conv
 
        def _decorate_sms(self, parsedTexts):
                return parsedTexts
 
        @staticmethod
-       def _merge_messages(parsedMessages, json):
+       def _merge_conversation_sources(parsedMessages, json):
                for message in parsedMessages:
-                       id = message["id"]
-                       jsonItem = json["messages"][id]
-                       message["isRead"] = jsonItem["isRead"]
-                       message["isSpam"] = jsonItem["isSpam"]
-                       message["isTrash"] = jsonItem["isTrash"]
-                       message["isArchived"] = "inbox" not in jsonItem["labels"]
+                       jsonItem = json["messages"][message.id]
+                       message.isRead = jsonItem["isRead"]
+                       message.isSpam = jsonItem["isSpam"]
+                       message.isTrash = jsonItem["isTrash"]
+                       message.isArchived = "inbox" not in jsonItem["labels"]
                        yield message
 
        def _get_page(self, url, data = None, refererUrl = None):
@@ -715,102 +838,13 @@ def set_sane_callback(backend):
                        return
 
 
-def sort_messages(allMessages):
-       sortableAllMessages = [
-               (message["time"], message)
-               for message in allMessages
-       ]
-       sortableAllMessages.sort(reverse=True)
-       return (
-               message
-               for (exactTime, message) in sortableAllMessages
-       )
+def _is_not_special(name):
+       return not name.startswith("_") and name[0].lower() == name[0] and "_" not in name
 
 
-def decorate_recent(recentCallData):
-       """
-       @returns (personsName, phoneNumber, date, action)
-       """
-       contactId = recentCallData["contactId"]
-       if recentCallData["name"]:
-               header = recentCallData["name"]
-       elif recentCallData["prettyNumber"]:
-               header = recentCallData["prettyNumber"]
-       elif recentCallData["location"]:
-               header = recentCallData["location"]
-       else:
-               header = "Unknown"
-
-       number = recentCallData["number"]
-       relTime = recentCallData["relTime"]
-       action = recentCallData["action"]
-       return contactId, header, number, relTime, action
-
-
-def decorate_message(messageData):
-       contactId = messageData["contactId"]
-       exactTime = messageData["time"]
-       if messageData["name"]:
-               header = messageData["name"]
-       elif messageData["prettyNumber"]:
-               header = messageData["prettyNumber"]
-       else:
-               header = "Unknown"
-       number = messageData["number"]
-       relativeTime = messageData["relTime"]
-
-       messageParts = list(messageData["messageParts"])
-       if len(messageParts) == 0:
-               messages = ("No Transcription", )
-       elif len(messageParts) == 1:
-               messages = (messageParts[0][1], )
-       else:
-               messages = [
-                       "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
-                       for messagePart in messageParts
-               ]
-
-       decoratedResults = contactId, header, number, relativeTime, messages
-       return decoratedResults
-
-
-def test_backend(username, password):
-       backend = GVoiceBackend()
-       print "Authenticated: ", backend.is_authed()
-       if not backend.is_authed():
-               print "Login?: ", backend.login(username, password)
-       print "Authenticated: ", backend.is_authed()
-       #print "Is Dnd: ", backend.is_dnd()
-       #print "Setting Dnd", backend.set_dnd(True)
-       #print "Is Dnd: ", backend.is_dnd()
-       #print "Setting Dnd", backend.set_dnd(False)
-       #print "Is Dnd: ", backend.is_dnd()
-
-       #print "Token: ", backend._token
-       #print "Account: ", backend.get_account_number()
-       #print "Callback: ", backend.get_callback_number()
-       #print "All Callback: ",
-       import pprint
-       #pprint.pprint(backend.get_callback_numbers())
-
-       #print "Recent: "
-       #for data in backend.get_recent():
-       #       pprint.pprint(data)
-       #for data in sort_messages(backend.get_recent()):
-       #       pprint.pprint(decorate_recent(data))
-       #pprint.pprint(list(backend.get_recent()))
-
-       print "Contacts: ",
-       for contact in backend.get_contacts():
-               pprint.pprint(contact)
-
-       #print "Messages: ",
-       #for message in backend.get_messages():
-       #       pprint.pprint(message)
-       #for message in sort_messages(backend.get_messages()):
-       #       pprint.pprint(decorate_message(message))
-
-       return backend
+def to_dict(obj):
+       members = inspect.getmembers(obj)
+       return dict((name, value) for (name, value) in members if _is_not_special(name))
 
 
 def grab_debug_info(username, password):
@@ -890,10 +924,16 @@ def grab_debug_info(username, password):
                )
 
 
-if __name__ == "__main__":
+def main():
        import sys
        logging.basicConfig(level=logging.DEBUG)
-       if True:
-               grab_debug_info(sys.argv[1], sys.argv[2])
-       else:
-               test_backend(sys.argv[1], sys.argv[2])
+       args = sys.argv
+       if 3 <= len(args):
+               username = args[1]
+               password = args[2]
+
+       grab_debug_info(username, password)
+
+
+if __name__ == "__main__":
+       main()
index 43514c0..10b11e0 100644 (file)
@@ -5,8 +5,6 @@ import logging
 
 import util.coroutines as coroutines
 
-import backend
-
 
 _moduleLogger = logging.getLogger("gvoice.conversations")
 
@@ -26,23 +24,21 @@ class Conversations(object):
                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"]
+               conversations = list(self._backend.get_conversations())
+               conversations.sort()
+               for conversation in conversations:
+                       key = conversation.contactId, conversation.number
                        try:
-                               conversation = self._conversations[key]
-                               isNewConversation = False
+                               mergedConversations = self._conversations[key]
                        except KeyError:
-                               conversation = Conversation(self._backend, messageData)
-                               self._conversations[key] = conversation
-                               isNewConversation = True
+                               mergedConversations = MergedConversations()
+                               self._conversations[key] = mergedConversations
 
-                       if isNewConversation:
-                               # @todo see if this has issues with a user marking a item as unread/unarchive?
+                       try:
+                               mergedConversations.append_conversation(conversation)
                                isConversationUpdated = True
-                       else:
-                               isConversationUpdated = conversation.merge_conversation(messageData)
+                       except RuntimeError:
+                               isConversationUpdated = False
 
                        if isConversationUpdated:
                                updateConversationIds.add(key)
@@ -67,54 +63,48 @@ class Conversations(object):
                self._conversations.clear()
 
 
-class Conversation(object):
+class MergedConversations(object):
 
-       def __init__(self, backend, data):
-               self._backend = backend
-               self._data = dict((key, value) for (key, value) in data.iteritems())
+       def __init__(self):
+               self._conversations = []
 
-               # confirm we have a list
-               self._data["messageParts"] = list(
-                       self._append_time(message, self._data["time"])
-                       for message in self._data["messageParts"]
-               )
+       def append_conversation(self, newConversation):
+               self._validate(newConversation)
+               self._remove_repeats(newConversation)
+               self._conversations.append(newConversation)
 
-       def __getitem__(self, key):
-               return self._data[key]
+       @property
+       def conversations(self):
+               return self._conversations
 
-       def merge_conversation(self, moreData):
-               """
-               @returns True if there was content to merge (new messages arrived
-               rather than being a duplicate)
+       def _validate(self, newConversation):
+               if not self._conversations:
+                       return
 
-               @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
-
-               # @todo Handle No Transcription voicemails
-               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
-               messageParts.sort()
-
-               return messageAppended
-
-       @staticmethod
-       def _append_time(message, exactWhen):
-               whoFrom, message, when = message
-               return exactWhen, whoFrom, message, when
+                       assert getattr(self._conversations[0], constantField) == getattr(newConversation, constantField), "Constant field changed, soemthing is seriously messed up: %r v %r" % (
+                               getattr(self._conversations[0], constantField),
+                               getattr(newConversation, constantField),
+                       )
+
+               if newConversation.time <= self._conversations[-1].time:
+                       raise RuntimeError("Conversations got out of order")
+
+       def _remove_repeats(self, newConversation):
+               similarConversations = [
+                       conversation
+                       for conversation in self._conversations
+                       if conversation.id == newConversation.id
+               ]
+
+               for similarConversation in similarConversations:
+                       for commonField in ("isRead", "isSpam", "isTrash", "isArchived"):
+                               newValue = getattr(newConversation, commonField)
+                               setattr(similarConversation, commonField, newValue)
+
+                       newConversation.messages = [
+                               newMessage
+                               for newMessage in newConversation.messages
+                               if newMessage not in similarConversation.messages
+                       ]
+                       assert 0 < len(newConversation.messages), "Everything shouldn't have been removed"
index 19ffbe0..1f52bf9 100644 (file)
@@ -35,7 +35,6 @@ class Session(object):
                self._stateMachine.start()
 
        def logout(self):
-               self._loggedIn = False
                self._stateMachine.stop()
                self._backend.logout()
 
index b24633f..2fb1d01 100644 (file)
@@ -5,12 +5,10 @@
 @todo Look into supporting more states that have a different F and MAX
 """
 
-import Queue
 import logging
 
 import gobject
 
-import util.algorithms as algorithms
 import util.go_utils as gobject_utils
 import util.coroutines as coroutines
 import gtk_toolbox
index 43cbe2d..4837f2a 100644 (file)
@@ -6,8 +6,8 @@ import logging
 
 def set_process_name(name):
        try: # change process name for killall
-          import ctypes
-          libc = ctypes.CDLL('libc.so.6')
-          libc.prctl(15, name, 0, 0, 0)
+               import ctypes
+               libc = ctypes.CDLL('libc.so.6')
+               libc.prctl(15, name, 0, 0, 0)
        except Exception, e:
-          logging.warning('Unable to set processName: %s" % e')
+               logging.warning('Unable to set processName: %s" % e')