From: Ed Page Date: Fri, 18 Dec 2009 05:18:11 +0000 (-0600) Subject: Lots more bug fixes X-Git-Url: http://git.maemo.org/git/?p=theonering;a=commitdiff_plain;h=cd56d4ab7a3beaa59e8ef4a5d6c2624419038fee Lots more bug fixes I switched how I implemented the addressbook so I don't feel bad about a separate thread for the state machine. Had strange issues with using a better contact source, so hacks are in place. I switched the state machine away from threads because I consistent issues with the thread being inactive as long as the main thread was active. As all this got implemented, I found weird object binding issues. For soem reason, self wasn't binding for the callbacks but instead I was getting the other side's object. I put in some hacks to work around this issue --- diff --git a/src/channel/contact_list.py b/src/channel/contact_list.py index a66e4cd..f82c090 100644 --- a/src/channel/contact_list.py +++ b/src/channel/contact_list.py @@ -31,8 +31,13 @@ class AllContactsListChannel(AbstractListChannel): def __init__(self, connection, h): AbstractListChannel.__init__(self, connection, h) + self._callback = coroutines.func_sink( + coroutines.expand_positional( + self._on_contacts_refreshed + ) + ) self._session.addressbook.updateSignalHandler.register_sink( - self._on_contacts_refreshed + self._callback ) self.GroupFlagsChanged(0, 0) @@ -45,11 +50,9 @@ class AllContactsListChannel(AbstractListChannel): telepathy.server.ChannelTypeContactList.Close(self) self.remove_from_connection() self._session.addressbook.updateSignalHandler.unregister_sink( - self._on_contacts_refreshed + self._callback ) - @coroutines.func_sink - @coroutines.expand_positional @gobject_utils.async @gtk_toolbox.log_exception(_moduleLogger) def _on_contacts_refreshed(self, addressbook, added, removed, changed): diff --git a/src/channel/text.py b/src/channel/text.py index b9f6fe5..050b6a8 100644 --- a/src/channel/text.py +++ b/src/channel/text.py @@ -24,8 +24,13 @@ class TextChannel(telepathy.server.ChannelTypeText): self._otherHandle = h + self._callback = coroutines.func_sink( + coroutines.expand_positional( + self._on_conversations_updated + ) + ) self._conn.session.conversations.updateSignalHandler.register_sink( - self._on_conversations_updated + self._callback ) # The only reason there should be anything in the conversation is if @@ -53,7 +58,7 @@ class TextChannel(telepathy.server.ChannelTypeText): self._conn.session.conversations.clear_conversation(self._contactKey) self._conn.session.conversations.updateSignalHandler.unregister_sink( - self._on_conversations_updated + self._callback ) finally: telepathy.server.ChannelTypeText.Close(self) @@ -64,11 +69,9 @@ class TextChannel(telepathy.server.ChannelTypeText): contactKey = self._otherHandle.contactID, self._otherHandle.phoneNumber return contactKey - @coroutines.func_sink - @coroutines.expand_positional @gobject_utils.async @gtk_toolbox.log_exception(_moduleLogger) - def _on_conversations_updated(self, conversationIds): + def _on_conversations_updated(self, conv, conversationIds): if self._contactKey not in conversationIds: return _moduleLogger.info("Incoming messages from %r for existing conversation" % (self._contactKey, )) @@ -84,11 +87,11 @@ class TextChannel(telepathy.server.ChannelTypeText): self._report_new_message(formattedMessage) def _filter_seen_messages(self, messages): - return ( + 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) @@ -98,9 +101,8 @@ class TextChannel(telepathy.server.ChannelTypeText): timestamp = int(time.time()) type = telepathy.CHANNEL_TEXT_MESSAGE_TYPE_NORMAL - message = message.content _moduleLogger.info("Received message from User %r" % self._otherHandle) - self.Received(id, timestamp, self._otherHandle, type, 0, message) + self.Received(currentReceivedId, timestamp, self._otherHandle, type, 0, message) self._nextRecievedId += 1 diff --git a/src/connection.py b/src/connection.py index fa87b31..69ed50c 100644 --- a/src/connection.py +++ b/src/connection.py @@ -64,6 +64,11 @@ class TheOneRingConnection( self.set_self_handle(handle.create_handle(self, 'connection')) + self._callback = coroutines.func_sink( + coroutines.expand_positional( + self._on_conversations_updated + ) + ) _moduleLogger.info("Connection to the account %s created" % account) except Exception, e: _moduleLogger.exception("Failed to create Connection") @@ -97,7 +102,7 @@ class TheOneRingConnection( ) try: self.session.conversations.updateSignalHandler.register_sink( - self._on_conversations_updated + self._callback ) self.session.login(*self._credentials) self.session.backend.set_callback_number(self._callbackNumber) @@ -129,7 +134,7 @@ class TheOneRingConnection( _moduleLogger.info("Disconnecting") try: self.session.conversations.updateSignalHandler.unregister_sink( - self._on_conversations_updated + self._callback ) self._channelManager.close() self.session.logout() @@ -205,17 +210,15 @@ class TheOneRingConnection( h = handle.create_handle(self, 'contact', requestedContactId, requestedContactNumber) return h - @coroutines.func_sink - @coroutines.expand_positional @gobject_utils.async @gtk_toolbox.log_exception(_moduleLogger) - def _on_conversations_updated(self, conversationIds): + def _on_conversations_updated(self, conv, conversationIds): # @todo get conversations update running # @todo test conversatiuons _moduleLogger.info("Incoming messages from: %r" % (conversationIds, )) channelManager = self._channelManager for contactId, phoneNumber in conversationIds: - h = self._create_contact_handle(contactId, phoneNumber) + h = handle.create_handle(self, 'contact', contactId, phoneNumber) # if its new, __init__ will take care of things # if its old, its own update will take care of it - channel = channelManager.channel_for_text(handle) + channel = channelManager.channel_for_text(h) diff --git a/src/gvoice/addressbook.py b/src/gvoice/addressbook.py index 4684ed1..f9f713b 100644 --- a/src/gvoice/addressbook.py +++ b/src/gvoice/addressbook.py @@ -56,8 +56,13 @@ class Addressbook(object): if self._contacts: return contacts = self._backend.get_contacts() - for contactId, contactName in contacts: - self._contacts[contactId] = (contactName, []) + for contactId, contactDetails in contacts: + contactName = contactDetails["name"] + contactNumbers = [ + (numberDetails.get("phoneType", "Mobile"), numberDetails["phoneNumber"]) + for numberDetails in contactDetails["numbers"] + ] + self._contacts[contactId] = (contactName, contactNumbers) def _populate_contact_details(self, contactId): if self._get_contact_details(contactId): diff --git a/src/gvoice/backend.py b/src/gvoice/backend.py index 435ebb8..ad3a20e 100755 --- a/src/gvoice/backend.py +++ b/src/gvoice/backend.py @@ -98,7 +98,8 @@ class GVoiceBackend(object): self._downloadVoicemailURL = SECURE_URL_BASE + "media/send_voicemail/" self._XML_SEARCH_URL = SECURE_URL_BASE + "inbox/search/" - self._XML_ACCOUNT_URL = SECURE_URL_BASE + "inbox/contacts/" + self._XML_ACCOUNT_URL = SECURE_URL_BASE + "contacts/" + self._XML_CONTACTS_URL = "http://www.google.com/voice/inbox/search/contact" self._XML_RECENT_URL = SECURE_URL_BASE + "inbox/recent/" self.XML_FEEDS = ( @@ -117,18 +118,12 @@ class GVoiceBackend(object): self._XML_RECEIVED_URL = SECURE_URL_BASE + "inbox/recent/received/" self._XML_MISSED_URL = SECURE_URL_BASE + "inbox/recent/missed/" - self._contactsURL = SECURE_MOBILE_URL_BASE + "contacts" - self._contactDetailURL = SECURE_MOBILE_URL_BASE + "contact" - self._galxRe = re.compile(r"""""", re.MULTILINE | re.DOTALL) self._tokenRe = re.compile(r"""""") self._accountNumRe = re.compile(r"""(.{14})""") self._callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)\s*$""", re.M) - self._contactsRe = re.compile(r"""(.*?)""", re.S) - self._contactsNextRe = re.compile(r""".*Next.*?""", re.S) - self._contactDetailPhoneRe = re.compile(r"""([0-9+\-\(\) \t]+?)\((\w+)\)""", re.S) - + self._contactsBodyRe = re.compile(r"""gcData\s*=\s*({.*?});""", re.MULTILINE | re.DOTALL) self._seperateVoicemailsRegex = re.compile(r"""^\s*
""", re.MULTILINE | re.DOTALL) self._exactVoicemailTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE) self._relativeVoicemailTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE) @@ -385,30 +380,13 @@ class GVoiceBackend(object): """ @returns Iterable of (contact id, contact name) """ - contactsPagesUrls = [self._contactsURL] - for contactsPageUrl in contactsPagesUrls: - contactsPage = self._get_page(contactsPageUrl) - for contact_match in self._contactsRe.finditer(contactsPage): - contactId = contact_match.group(1) - contactName = saxutils.unescape(contact_match.group(2)) - contact = contactId, contactName - yield contact - - next_match = self._contactsNextRe.match(contactsPage) - if next_match is not None: - newContactsPageUrl = self._contactsURL + next_match.group(1) - contactsPagesUrls.append(newContactsPageUrl) - - def get_contact_details(self, contactId): - """ - @returns Iterable of (Phone Type, Phone Number) - """ - detailPage = self._get_page(self._contactDetailURL + '/' + contactId) - - for detail_match in self._contactDetailPhoneRe.finditer(detailPage): - phoneNumber = detail_match.group(1) - phoneType = saxutils.unescape(detail_match.group(2)) - yield (phoneType, phoneNumber) + page = self._get_page(self._XML_CONTACTS_URL) + contactsBody = self._contactsBodyRe.search(page) + if contactsBody is None: + raise RuntimeError("Could not extract contact information") + accountData = _fake_parse_json(contactsBody.group(1)) + for contactId, contactDetails in accountData["contacts"].iteritems(): + yield contactId, contactDetails def get_messages(self): voicemailPage = self._get_page(self._XML_VOICEMAIL_URL) @@ -799,7 +777,7 @@ def test_backend(username, password): if not backend.is_authed(): print "Login?: ", backend.login(username, password) print "Authenticated: ", backend.is_authed() - print "Is Dnd: ", backend.is_dnd() + #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) @@ -809,7 +787,7 @@ def test_backend(username, password): #print "Account: ", backend.get_account_number() #print "Callback: ", backend.get_callback_number() #print "All Callback: ", - #import pprint + import pprint #pprint.pprint(backend.get_callback_numbers()) #print "Recent: " @@ -819,10 +797,9 @@ def test_backend(username, password): # pprint.pprint(decorate_recent(data)) #pprint.pprint(list(backend.get_recent())) - #print "Contacts: ", - #for contact in backend.get_contacts(): - # print contact - # pprint.pprint(list(backend.get_contact_details(contact[0]))) + print "Contacts: ", + for contact in backend.get_contacts(): + pprint.pprint(contact) #print "Messages: ", #for message in backend.get_messages(): @@ -848,9 +825,9 @@ def grab_debug_info(username, password): ("token", backend._tokenURL), ("login", backend._loginURL), ("isdnd", backend._isDndURL), - ("contacts", backend._contactsURL), - ("account", backend._XML_ACCOUNT_URL), + ("contacts", backend._XML_CONTACTS_URL), + ("voicemail", backend._XML_VOICEMAIL_URL), ("sms", backend._XML_SMS_URL), @@ -894,19 +871,19 @@ def grab_debug_info(username, password): try: page = browser.download(url) except StandardError, e: - print e.message + print str(e) continue print "\tWriting to file" with open("loggedin_%s.txt" % name, "w") as f: f.write(page) # Cookies - browser.cookies.save() + browser.save_cookies() print "\tWriting cookies to file" with open("cookies.txt", "w") as f: f.writelines( "%s: %s\n" % (c.name, c.value) - for c in browser.cookies + for c in browser._cookies ) diff --git a/src/gvoice/browser_emu.py b/src/gvoice/browser_emu.py index 108fb5c..d574e6a 100644 --- a/src/gvoice/browser_emu.py +++ b/src/gvoice/browser_emu.py @@ -115,13 +115,14 @@ class MozillaEmulator(object): return openerdirector return self._read(openerdirector, trycount) - except urllib2.URLError: + except urllib2.URLError, e: + _moduleLogger.info("%s: %s" % (e, url)) cnt += 1 if (-1 < trycount) and (trycount < cnt): raise # Retry :-) - _moduleLogger.info("MozillaEmulator: urllib2.URLError, retryting %d" % cnt) + _moduleLogger.info("MozillaEmulator: urllib2.URLError, retrying %d" % cnt) def _build_opener(self, url, postdata = None, extraheaders = None, forbidRedirect = False): if extraheaders is None: diff --git a/src/gvoice/state_machine.py b/src/gvoice/state_machine.py index 8e81cb7..b24633f 100644 --- a/src/gvoice/state_machine.py +++ b/src/gvoice/state_machine.py @@ -6,13 +6,14 @@ """ import Queue -import threading import logging import gobject import util.algorithms as algorithms +import util.go_utils as gobject_utils import util.coroutines as coroutines +import gtk_toolbox _moduleLogger = logging.getLogger("gvoice.state_machine") @@ -49,25 +50,31 @@ class StateMachine(object): self._initItems = initItems self._updateItems = updateItems - self._actions = Queue.Queue() self._state = self.STATE_ACTIVE self._timeoutId = None - self._thread = None self._currentPeriod = self._INITIAL_ACTIVE_PERIOD self._set_initial_period() + self._callback = coroutines.func_sink( + coroutines.expand_positional( + self._request_reset_timers + ) + ) + + @gobject_utils.async + @gtk_toolbox.log_exception(_moduleLogger) def start(self): - assert self._thread is None - self._thread = threading.Thread(target=self._run) - self._thread.setDaemon(self._IS_DAEMON) - self._thread.start() + _moduleLogger.info("Starting State Machine") + for item in self._initItems: + try: + item.update() + except Exception: + _moduleLogger.exception("Initial update failed for %r" % item) + self._schedule_update() def stop(self): - if self._thread is not None: - self._actions.put(self._ACTION_STOP) - self._thread = None - else: - _moduleLogger.info("Stopping an already stopped state machine") + _moduleLogger.info("Stopping an already stopped state machine") + self._stop_update() def set_state(self, state): self._state = state @@ -77,55 +84,20 @@ class StateMachine(object): return self._state def reset_timers(self): - self._actions.put(self._ACTION_RESET) + self._reset_timers() - @coroutines.func_sink - def request_reset_timers(self, args): - self.reset_timers() + @property + def request_reset_timers(self): + return self._callback - def _run(self): - logging.basicConfig(level=logging.DEBUG) - _moduleLogger.info("Starting State Machine") - for item in self._initItems: - try: - item.update() - except Exception: - _moduleLogger.exception("Initial update failed for %r" % item) - - # empty the task queue - actions = list(algorithms.itr_available(self._actions, initiallyBlock = False)) - self._schedule_update() - if len(self._updateItems) == 0: - self.stop() - - while True: - # block till we get a task, or get all the tasks if we were late - actions = list(algorithms.itr_available(self._actions, initiallyBlock = True)) - - if self._ACTION_STOP in actions: - _moduleLogger.info("Requested to stop") - self._stop_update() - break - elif self._ACTION_RESET in actions: - _moduleLogger.info("Reseting timers") - self._reset_timers() - elif self._ACTION_UPDATE in actions: - _moduleLogger.info("Update") - for item in self._updateItems: - try: - item.update(force=True) - except Exception: - _moduleLogger.exception("Update failed for %r" % item) - self._schedule_update() + @gobject_utils.async + @gtk_toolbox.log_exception(_moduleLogger) + def _request_reset_timers(self, *args): + self.reset_timers() def _set_initial_period(self): self._currentPeriod = self._INITIAL_ACTIVE_PERIOD / 2 # We will double it later - def _reset_timers(self): - self._stop_update() - self._set_initial_period() - self._schedule_update() - def _schedule_update(self): nextTimeout = self._calculate_step(self._state, self._currentPeriod) nextTimeout = int(nextTimeout) @@ -139,8 +111,19 @@ class StateMachine(object): gobject.source_remove(self._timeoutId) self._timeoutId = None + def _reset_timers(self): + self._stop_update() + self._set_initial_period() + self._schedule_update() + def _on_timeout(self): - self._actions.put(self._ACTION_UPDATE) + _moduleLogger.info("Update") + for item in self._updateItems: + try: + item.update(force=True) + except Exception: + _moduleLogger.exception("Update failed for %r" % item) + self._schedule_update() return False # do not continue @classmethod diff --git a/tests/test_addressbook.py b/tests/test_addressbook.py index 318126d..0702c1b 100644 --- a/tests/test_addressbook.py +++ b/tests/test_addressbook.py @@ -20,13 +20,10 @@ class MockBackend(object): def get_contacts(self): return ( - (i, contactData["name"]) + (i, contactData) for (i, contactData) in enumerate(self.contactsData) ) - def get_contact_details(self, contactId): - return self.contactsData[contactId]["details"] - def generate_update_callback(callbackData): @@ -64,7 +61,7 @@ def test_one_contact_no_details(): backend = MockBackend([ { "name": "One", - "details": [], + "numbers": [], }, ]) book = gvoice.addressbook.Addressbook(backend) @@ -95,9 +92,6 @@ def test_one_contact_no_details(): name = book.get_contact_name(id) assert name == backend.contactsData[id]["name"] - contactDetails = list(book.get_contact_details(id)) - assert len(contactDetails) == 0 - def test_one_contact_with_details(): callbackData = [] @@ -106,7 +100,10 @@ def test_one_contact_with_details(): backend = MockBackend([ { "name": "One", - "details": [("Type A", "123"), ("Type B", "456"), ("Type C", "789")], + "numbers": [ + {"phoneType": "Type A", "phoneNumber": "123"}, + {"phoneType": "Type B", "phoneNumber": "456"}, + {"phoneType": "Type C", "phoneNumber": "789"}], }, ]) book = gvoice.addressbook.Addressbook(backend) @@ -137,16 +134,6 @@ def test_one_contact_with_details(): name = book.get_contact_name(id) assert name == backend.contactsData[id]["name"] - contactDetails = list(book.get_contact_details(id)) - print "%r" % contactDetails - assert len(contactDetails) == 3 - assert contactDetails[0][0] == "Type A" - assert contactDetails[0][1] == "123" - assert contactDetails[1][0] == "Type B" - assert contactDetails[1][1] == "456" - assert contactDetails[2][0] == "Type C" - assert contactDetails[2][1] == "789" - def test_adding_a_contact(): callbackData = [] @@ -155,7 +142,7 @@ def test_adding_a_contact(): backend = MockBackend([ { "name": "One", - "details": [], + "numbers": [], }, ]) book = gvoice.addressbook.Addressbook(backend) @@ -174,7 +161,7 @@ def test_adding_a_contact(): backend.contactsData.append({ "name": "Two", - "details": [], + "numbers": [], }) book.update() @@ -198,7 +185,7 @@ def test_removing_a_contact(): backend = MockBackend([ { "name": "One", - "details": [], + "numbers": [], }, ]) book = gvoice.addressbook.Addressbook(backend)