From: Ed Page Date: Sun, 20 Dec 2009 03:54:19 +0000 (-0600) Subject: Massive reworking of messages to make debugging easier along with some code cleaning... X-Git-Url: http://git.maemo.org/git/?p=theonering;a=commitdiff_plain;h=6c40cd8e38418993c6efcbaf51d0c57bae81d268 Massive reworking of messages to make debugging easier along with some code cleaning including separating/creating of hand tests --- diff --git a/hand_tests/conv.py b/hand_tests/conv.py new file mode 100755 index 0000000..8655396 --- /dev/null +++ b/hand_tests/conv.py @@ -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() diff --git a/hand_tests/generic.py b/hand_tests/generic.py index 2e69a5d..a6e4d0b 100755 --- a/hand_tests/generic.py +++ b/hand_tests/generic.py @@ -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 index 0000000..a254d7e --- /dev/null +++ b/hand_tests/gv.py @@ -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() diff --git a/hand_tests/sm.py b/hand_tests/sm.py index f3c1e3b..0fac9da 100755 --- a/hand_tests/sm.py +++ b/hand_tests/sm.py @@ -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] diff --git a/src/channel/text.py b/src/channel/text.py index 050b6a8..22e840d 100644 --- a/src/channel/text.py +++ b/src/channel/text.py @@ -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 diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index c4203b0..97acd2f 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -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) diff --git a/src/gvoice/backend.py b/src/gvoice/backend.py index b2b01a3..251cfa5 100755 --- a/src/gvoice/backend.py +++ b/src/gvoice/backend.py @@ -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": "%s", - "med2": "%s", - "high": "%s", - } - 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 = [ - "%s: %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() diff --git a/src/gvoice/conversations.py b/src/gvoice/conversations.py index 43514c0..10b11e0 100644 --- a/src/gvoice/conversations.py +++ b/src/gvoice/conversations.py @@ -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" diff --git a/src/gvoice/session.py b/src/gvoice/session.py index 19ffbe0..1f52bf9 100644 --- a/src/gvoice/session.py +++ b/src/gvoice/session.py @@ -35,7 +35,6 @@ class Session(object): self._stateMachine.start() def logout(self): - self._loggedIn = False self._stateMachine.stop() self._backend.logout() diff --git a/src/gvoice/state_machine.py b/src/gvoice/state_machine.py index b24633f..2fb1d01 100644 --- a/src/gvoice/state_machine.py +++ b/src/gvoice/state_machine.py @@ -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 diff --git a/src/util/linux.py b/src/util/linux.py index 43cbe2d..4837f2a 100644 --- a/src/util/linux.py +++ b/src/util/linux.py @@ -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')