From: Ed Page Date: Fri, 13 Nov 2009 03:30:18 +0000 (-0600) Subject: Updating from Dialcentral X-Git-Url: http://git.maemo.org/git/?p=theonering;a=commitdiff_plain;h=58f796243ce76a2629f169b943d2e0f89a877999 Updating from Dialcentral --- diff --git a/Makefile b/Makefile index a2c7d28..c461fc8 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ test: $(OBJ) package: $(OBJ) rm -Rf $(BUILD_PATH) + mkdir -p $(BUILD_PATH)/generic cp $(SOURCE_PATH)/constants.py $(BUILD_PATH)/generic $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; ) @@ -49,6 +50,7 @@ package: $(OBJ) cp support/builddeb.py $(BUILD_PATH)/generic cp support/py2deb.py $(BUILD_PATH)/generic cp support/fake_py2deb.py $(BUILD_PATH)/generic + mkdir -p $(BUILD_PATH)/chinook cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/chinook cd $(BUILD_PATH)/chinook ; python builddeb.py chinook diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py index a74dea6..9ef914f 100644 --- a/src/gtk_toolbox.py +++ b/src/gtk_toolbox.py @@ -219,3 +219,19 @@ def safecall(f, errorDisplay=None, default=None, exception=Exception): errorDisplay.push_exception(e) return default return _safecall + + +def log_exception(logger): + + def log_exception_decorator(func): + + @functools.wraps(func) + def wrapper(*args, **kwds): + try: + return func(*args, **kwds) + except Exception: + logger.exception(func.__name__) + + return wrapper + + return log_exception_decorator \ No newline at end of file diff --git a/src/gvoice/backend.py b/src/gvoice/backend.py index 41d66a8..9bada99 100755 --- a/src/gvoice/backend.py +++ b/src/gvoice/backend.py @@ -25,6 +25,7 @@ Resources http://posttopic.com/topic/google-voice-add-on-development """ +from __future__ import with_statement import os import re @@ -38,20 +39,20 @@ from xml.sax import saxutils from xml.etree import ElementTree -import browser_emu - try: import simplejson except ImportError: simplejson = None +import browser_emu + _moduleLogger = logging.getLogger("gvoice.backend") -_TRUE_REGEX = re.compile("true") -_FALSE_REGEX = re.compile("false") def safe_eval(s): + _TRUE_REGEX = re.compile("true") + _FALSE_REGEX = re.compile("false") s = _TRUE_REGEX.sub("True", s) s = _FALSE_REGEX.sub("False", s) return eval(s, {}, {}) @@ -123,6 +124,48 @@ class GVoiceBackend(object): self._callbackNumber = "" self._callbackNumbers = {} + # Suprisingly, moving all of these from class to self sped up startup time + + self._validateRe = re.compile("^[0-9]{10,}$") + + self._forwardURL = "https://www.google.com/voice/mobile/phones" + self._tokenURL = "http://www.google.com/voice/m" + self._loginURL = "https://www.google.com/accounts/ServiceLoginAuth" + 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._gvDialingStrRe = re.compile("This may take a few seconds", re.M) + self._clicktocallURL = "https://www.google.com/voice/m/sendcall" + self._sendSmsURL = "https://www.google.com/voice/m/sendsms" + + self._recentCallsURL = "https://www.google.com/voice/inbox/recent/" + self._placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/" + self._receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/" + self._missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/" + + self._contactsRe = re.compile(r"""(.*?)""", re.S) + self._contactsNextRe = re.compile(r""".*Next.*?""", re.S) + self._contactsURL = "https://www.google.com/voice/mobile/contacts" + self._contactDetailPhoneRe = re.compile(r"""([0-9+\-\(\) \t]+?)\((\w+)\)""", re.S) + self._contactDetailURL = "https://www.google.com/voice/mobile/contact" + + self._voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/" + self._smsURL = "https://www.google.com/voice/inbox/recent/sms/" + self._seperateVoicemailsRegex = re.compile(r"""^\s*
""", re.MULTILINE | re.DOTALL) + self._exactVoicemailTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE) + self._relativeVoicemailTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE) + self._voicemailNameRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) + self._voicemailNumberRegex = re.compile(r"""""", re.MULTILINE) + self._prettyVoicemailNumberRegex = re.compile(r"""(.*?)""", re.MULTILINE) + self._voicemailLocationRegex = re.compile(r""".*?(.*?)""", re.MULTILINE) + self._messagesContactID = re.compile(r""".*?\s*?(.*?)""", re.MULTILINE) + self._voicemailMessageRegex = re.compile(r"""((.*?)|(.*?))""", re.MULTILINE) + self._smsFromRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) + self._smsTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) + self._smsTextRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) + def is_authed(self, force = False): """ Attempts to detect a current session @@ -143,15 +186,7 @@ class GVoiceBackend(object): self._lastAuthed = time.time() return True - _tokenURL = "http://www.google.com/voice/m" - _loginURL = "https://www.google.com/accounts/ServiceLoginAuth" - _galxRe = re.compile(r"""""", re.MULTILINE | re.DOTALL) - - def login(self, username, password): - """ - Attempt to login to GoogleVoice - @returns Whether login was successful or not - """ + def _get_token(self): try: tokenPage = self._browser.download(self._tokenURL) except urllib2.URLError, e: @@ -163,7 +198,9 @@ class GVoiceBackend(object): else: galxToken = "" _moduleLogger.debug("Could not grab GALX token") + return galxToken + def _login(self, username, password, token): loginPostData = urllib.urlencode({ 'Email' : username, 'Passwd' : password, @@ -171,7 +208,7 @@ class GVoiceBackend(object): "ltmpl": "mobile", "btmpl": "mobile", "PersistentCookie": "yes", - "GALX": galxToken, + "GALX": token, "continue": self._forwardURL, }) @@ -180,12 +217,27 @@ class GVoiceBackend(object): except urllib2.URLError, e: _moduleLogger.exception("Translating error: %s" % str(e)) raise NetworkError("%s is not accesible" % self._loginURL) + return loginSuccessOrFailurePage + + def login(self, username, password): + """ + Attempt to login to GoogleVoice + @returns Whether login was successful or not + """ + self.logout() + galxToken = self._get_token() + loginSuccessOrFailurePage = self._login(username, password, galxToken) try: self._grab_account_info(loginSuccessOrFailurePage) except Exception, e: - _moduleLogger.exception(str(e)) - return False + # Retry in case the redirect failed + # luckily is_authed does everything we need for a retry + loggedIn = self.is_authed(True) + if not loggedIn: + _moduleLogger.exception(str(e)) + return False + _moduleLogger.info("Redirection failed on initial login attempt, auto-corrected for this") self._browser.cookies.save() self._lastAuthed = time.time() @@ -196,9 +248,6 @@ class GVoiceBackend(object): self._browser.cookies.clear() self._browser.cookies.save() - _gvDialingStrRe = re.compile("This may take a few seconds", re.M) - _clicktocallURL = "https://www.google.com/voice/m/sendcall" - def dial(self, number): """ This is the main function responsible for initating the callback @@ -223,8 +272,6 @@ class GVoiceBackend(object): return True - _sendSmsURL = "https://www.google.com/voice/m/sendsms" - def send_sms(self, number, message): number = self._send_validation(number) try: @@ -245,8 +292,6 @@ class GVoiceBackend(object): return True - _validateRe = re.compile("^[0-9]{10,}$") - def is_valid_syntax(self, number): """ @returns If This number be called ( syntax validation only ) @@ -268,8 +313,6 @@ class GVoiceBackend(object): return {} return self._callbackNumbers - _setforwardURL = "https://www.google.com//voice/m/setphone" - def set_callback_number(self, callbacknumber): """ Set the number that GoogleVoice calls @@ -284,11 +327,6 @@ class GVoiceBackend(object): """ return self._callbackNumber - _recentCallsURL = "https://www.google.com/voice/inbox/recent/" - _placedCallsURL = "https://www.google.com/voice/inbox/recent/placed/" - _receivedCallsURL = "https://www.google.com/voice/inbox/recent/received/" - _missedCallsURL = "https://www.google.com/voice/inbox/recent/missed/" - def get_recent(self): """ @returns Iterable of (personsName, phoneNumber, exact date, relative date, action) @@ -310,10 +348,6 @@ class GVoiceBackend(object): recentCallData["action"] = action yield recentCallData - _contactsRe = re.compile(r"""(.*?)""", re.S) - _contactsNextRe = re.compile(r""".*Next.*?""", re.S) - _contactsURL = "https://www.google.com/voice/mobile/contacts" - def get_contacts(self): """ @returns Iterable of (contact id, contact name) @@ -336,9 +370,6 @@ class GVoiceBackend(object): newContactsPageUrl = self._contactsURL + next_match.group(1) contactsPagesUrls.append(newContactsPageUrl) - _contactDetailPhoneRe = re.compile(r"""([0-9+\-\(\) \t]+?)\((\w+)\)""", re.S) - _contactDetailURL = "https://www.google.com/voice/mobile/contact" - def get_contact_details(self, contactId): """ @returns Iterable of (Phone Type, Phone Number) @@ -354,9 +385,6 @@ class GVoiceBackend(object): phoneType = saxutils.unescape(detail_match.group(2)) yield (phoneType, phoneNumber) - _voicemailURL = "https://www.google.com/voice/inbox/recent/voicemail/" - _smsURL = "https://www.google.com/voice/inbox/recent/sms/" - def get_messages(self): try: voicemailPage = self._browser.download(self._voicemailURL) @@ -364,8 +392,10 @@ class GVoiceBackend(object): _moduleLogger.exception("Translating error: %s" % str(e)) raise NetworkError("%s is not accesible" % self._voicemailURL) voicemailHtml = self._grab_html(voicemailPage) + voicemailJson = self._grab_json(voicemailPage) parsedVoicemail = self._parse_voicemail(voicemailHtml) - decoratedVoicemails = self._decorate_voicemail(parsedVoicemail) + voicemails = self._merge_messages(parsedVoicemail, voicemailJson) + decoratedVoicemails = self._decorate_voicemail(voicemails) try: smsPage = self._browser.download(self._smsURL) @@ -373,8 +403,10 @@ class GVoiceBackend(object): _moduleLogger.exception("Translating error: %s" % str(e)) raise NetworkError("%s is not accesible" % self._smsURL) smsHtml = self._grab_html(smsPage) + smsJson = self._grab_json(smsPage) parsedSms = self._parse_sms(smsHtml) - decoratedSms = self._decorate_sms(parsedSms) + smss = self._merge_messages(parsedSms, smsJson) + decoratedSms = self._decorate_sms(smss) allMessages = itertools.chain(decoratedVoicemails, decoratedSms) return allMessages @@ -392,11 +424,6 @@ class GVoiceBackend(object): flatHtml = htmlElement.text return flatHtml - _tokenRe = re.compile(r"""""") - _accountNumRe = re.compile(r"""(.{14})
""") - _callbackRe = re.compile(r"""\s+(.*?):\s*(.*?)\s*$""", re.M) - _forwardURL = "https://www.google.com/voice/mobile/phones" - def _grab_account_info(self, page): tokenGroup = self._tokenRe.search(page) if tokenGroup is None: @@ -428,18 +455,6 @@ class GVoiceBackend(object): number = number[1:] return number - _seperateVoicemailsRegex = re.compile(r"""^\s*
""", re.MULTILINE | re.DOTALL) - _exactVoicemailTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE) - _relativeVoicemailTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE) - _voicemailNameRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) - _voicemailNumberRegex = re.compile(r"""""", re.MULTILINE) - _prettyVoicemailNumberRegex = re.compile(r"""(.*?)""", re.MULTILINE) - _voicemailLocationRegex = re.compile(r""".*?(.*?)""", re.MULTILINE) - _messagesContactID = re.compile(r""".*?\s*?(.*?)""", re.MULTILINE) - #_voicemailMessageRegex = re.compile(r"""(.*?)""", re.MULTILINE) - #_voicemailMessageRegex = re.compile(r"""(.*?)""", re.MULTILINE) - _voicemailMessageRegex = re.compile(r"""((.*?)|(.*?))""", re.MULTILINE) - @staticmethod def _interpret_voicemail_regex(group): quality, content, number = group.group(2), group.group(3), group.group(4) @@ -484,6 +499,7 @@ class GVoiceBackend(object): "number": number, "location": location, "messageParts": messageParts, + "type": "Voicemail", } def _decorate_voicemail(self, parsedVoicemails): @@ -504,10 +520,6 @@ class GVoiceBackend(object): voicemailData["messageParts"] = ((whoFrom, message, when), ) yield voicemailData - _smsFromRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) - _smsTimeRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) - _smsTextRegex = re.compile(r"""(.*?)""", re.MULTILINE | re.DOTALL) - def _parse_sms(self, smsHtml): splitSms = self._seperateVoicemailsRegex.split(smsHtml) for messageId, messageHtml in itergroup(splitSms[1:], 2): @@ -545,11 +557,23 @@ class GVoiceBackend(object): "number": number, "location": "", "messageParts": messageParts, + "type": "Texts", } def _decorate_sms(self, parsedTexts): return parsedTexts + @staticmethod + def _merge_messages(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"] + yield message + def set_sane_callback(backend): """ @@ -673,7 +697,83 @@ def test_backend(username, password): return backend +def grab_debug_info(username, password): + cookieFile = os.path.join(".", "raw_cookies.txt") + try: + os.remove(cookieFile) + except OSError: + pass + + backend = GVoiceBackend(cookieFile) + browser = backend._browser + + _TEST_WEBPAGES = [ + ("forward", backend._forwardURL), + ("token", backend._tokenURL), + ("login", backend._loginURL), + ("contacts", backend._contactsURL), + + ("voicemail", backend._voicemailURL), + ("sms", backend._smsURL), + + ("recent", backend._recentCallsURL), + ("placed", backend._placedCallsURL), + ("recieved", backend._receivedCallsURL), + ("missed", backend._missedCallsURL), + ] + + # Get Pages + print "Grabbing pre-login pages" + for name, url in _TEST_WEBPAGES: + try: + page = browser.download(url) + except StandardError, e: + print e.message + continue + print "\tWriting to file" + with open("not_loggedin_%s.txt" % name, "w") as f: + f.write(page) + + # Login + print "Attempting login" + galxToken = backend._get_token() + loginSuccessOrFailurePage = backend._login(username, password, galxToken) + with open("loggingin.txt", "w") as f: + print "\tWriting to file" + f.write(loginSuccessOrFailurePage) + try: + backend._grab_account_info(loginSuccessOrFailurePage) + except Exception: + # Retry in case the redirect failed + # luckily is_authed does everything we need for a retry + loggedIn = backend.is_authed(True) + if not loggedIn: + raise + + # Get Pages + print "Grabbing post-login pages" + for name, url in _TEST_WEBPAGES: + try: + page = browser.download(url) + except StandardError, e: + print e.message + continue + print "\tWriting to file" + with open("loggedin_%s.txt" % name, "w") as f: + f.write(page) + + # Cookies + browser.cookies.save() + 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 + ) + + if __name__ == "__main__": import sys logging.basicConfig(level=logging.DEBUG) - test_backend(sys.argv[1], sys.argv[2]) + #test_backend(sys.argv[1], sys.argv[2]) + grab_debug_info(sys.argv[1], sys.argv[2]) diff --git a/support/builddeb.py b/support/builddeb.py index cabcaab..0ce60ea 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -1,9 +1,5 @@ #!/usr/bin/python2.5 -""" -@bug In update desrcription stuff -""" - import os import sys @@ -16,7 +12,10 @@ import constants __appname__ = constants.__app_name__ -__description__ = "Google Voice Communication Plugin" +__description__ = """Google Voice Communication Plugin +. +Homepage: http://theonering.garage.maemo.org +""" __author__ = "Ed Page" __email__ = "eopage@byu.net" __version__ = constants.__version__ @@ -60,7 +59,9 @@ def build_package(distribution): py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution] p = py2deb.Py2deb(__appname__) + p.prettyName = constants.__pretty_app_name__ p.description = __description__ + p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=theonering" p.upgradeDescription = __changelog__.split("\n\n", 1)[0] p.author = __author__ p.mail = __email__ diff --git a/support/py2deb.py b/support/py2deb.py index a3cbc32..6018f91 100644 --- a/support/py2deb.py +++ b/support/py2deb.py @@ -501,8 +501,10 @@ class Py2deb(object): mail = author+"@"+socket.gethostname() self.name = name + self.prettyName = "" self.description = description self.upgradeDescription = "" + self.bugTracker = "" self.license = license self.depends = depends self.recommends = "" @@ -753,8 +755,16 @@ FILES : "Description: %(description)s", ] + if self.prettyName: + prettyName = "XSBC-Maemo-Display-Name: %s" % self.prettyName.strip() + specificParagraphFields.append("\n ".join(prettyName.split("\n"))) + + if self.bugTracker: + bugTracker = "XSBC-Bugtracker: %s" % self.bugTracker.strip() + specificParagraphFields.append("\n ".join(bugTracker.split("\n"))) + if self.upgradeDescription: - upgradeDescription = "XB-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip() + upgradeDescription = "XSBC-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip() specificParagraphFields.append("\n ".join(upgradeDescription.split("\n"))) if self.icon: @@ -769,7 +779,7 @@ FILES : if i % 60 == 0: uueIconLines.append("") uueIconLines[-1] += c - uueIconLines[0:0] = ("XB-Maemo-Icon-26:", ) + uueIconLines[0:0] = ("XSBC-Maemo-Icon-26:", ) specificParagraphFields.append("\n ".join(uueIconLines)) generalParagraph = "\n".join(generalParagraphFields)