Getting click to call working and adding GoogleVoice support to the UI
[gc-dialer] / src / gc_backend.py
index c600713..e1573db 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-# GC Dialer - Front end for Google's Grand Central service.
+# DialCentral - Front end for Google's Grand Central service.
 # Copyright (C) 2008  Eric Warnke ericew AT gmail DOT com
 # 
 # This library is free software; you can redistribute it and/or
@@ -18,7 +18,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
 """
-Grandcentral Dialer backend code
+Grandcentral backend code
 """
 
 
@@ -31,6 +31,11 @@ import warnings
 
 from browser_emu import MozillaEmulator
 
+import socket
+
+
+socket.setdefaulttimeout(5)
+
 
 class GCDialer(object):
        """
@@ -46,8 +51,7 @@ class GCDialer(object):
        _inboxRe = re.compile(r"""<td>.*?(voicemail|received|missed|call return).*?</td>\s+<td>\s+<font size="2">\s+(.*?)\s+&nbsp;\|&nbsp;\s+<a href="/mobile/contacts/.*?">(.*?)\s?</a>\s+<br/>\s+(.*?)\s?<a href=""", re.S)
        _contactsRe = re.compile(r"""<a href="/mobile/contacts/detail/(\d+)">(.*?)</a>""", re.S)
        _contactsNextRe = re.compile(r""".*<a href="/mobile/contacts(\?page=\d+)">Next</a>""", re.S)
-       _contactDetailGroupRe   = re.compile(r"""Group:\s*(\w*)""", re.S)
-       _contactDetailPhoneRe   = re.compile(r"""(\w+):[0-9\-\(\) \t]*?<a href="/mobile/calls/click_to_call\?destno=(\d+).*?">call</a>""", re.S)
+       _contactDetailPhoneRe = re.compile(r"""(\w+):[0-9\-\(\) \t]*?<a href="/mobile/calls/click_to_call\?destno=(\d+).*?">call</a>""", re.S)
 
        _validateRe = re.compile("^[0-9]{10,}$")
 
@@ -61,11 +65,9 @@ class GCDialer(object):
 
        def __init__(self, cookieFile = None):
                # Important items in this function are the setup of the browser emulation and cookie file
-               self._msg = ""
-
                self._browser = MozillaEmulator(None, 0)
                if cookieFile is None:
-                       cookieFile = os.path.join(os.path.expanduser("~"), ".gc_dialer_cookies.txt")
+                       cookieFile = os.path.join(os.path.expanduser("~"), ".gc_cookies.txt")
                self._browser.cookies.filename = cookieFile
                if os.path.isfile(cookieFile):
                        self._browser.cookies.load()
@@ -75,6 +77,8 @@ class GCDialer(object):
                self._callbackNumbers = {}
                self._lastAuthed = 0.0
 
+               self.__contacts = None
+
        def is_authed(self, force = False):
                """
                Attempts to detect a current session and pull the auth token ( a_t ) from the page.
@@ -82,14 +86,13 @@ class GCDialer(object):
                @returns If authenticated
                """
 
-               if time.time() - self._lastAuthed < 60 and not force:
+               if (time.time() - self._lastAuthed) < 60 and not force:
                        return True
 
                try:
                        forwardSelectionPage = self._browser.download(GCDialer._forwardselectURL)
                except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._forwardselectURL, UserWarning, 2)
-                       return False
+                       raise RuntimeError("%s is not accesible" % GCDialer._forwardselectURL)
 
                self._browser.cookies.save()
                if GCDialer._isLoginPageRe.search(forwardSelectionPage) is None:
@@ -112,8 +115,7 @@ class GCDialer(object):
                try:
                        loginSuccessOrFailurePage = self._browser.download(GCDialer._loginURL, loginPostData)
                except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._loginURL, UserWarning, 2)
-                       return False
+                       raise RuntimeError("%s is not accesible" % GCDialer._loginURL)
 
                return self.is_authed()
 
@@ -122,20 +124,19 @@ class GCDialer(object):
                self._browser.cookies.clear()
                self._browser.cookies.save()
 
+               self.clear_caches()
+
        def dial(self, number):
                """
                This is the main function responsible for initating the callback
                """
-               self._msg = ""
-
                # If the number is not valid throw exception
                if not self.is_valid_syntax(number):
                        raise ValueError('number is not valid')
 
                # No point if we don't have the magic cookie
                if not self.is_authed():
-                       self._msg = "Not authenticated"
-                       return False
+                       raise RuntimeError("Not Authenticated")
 
                # Strip leading 1 from 11 digit dialing
                if len(number) == 11 and number[0] == 1:
@@ -148,20 +149,15 @@ class GCDialer(object):
                                {'Referer' : 'http://www.grandcentral.com/mobile/messages'}
                        )
                except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._clicktocallURL, UserWarning, 2)
-                       return False
+                       raise RuntimeError("%s is not accesible" % GCDialer._clicktocallURL)
 
-               if GCDialer._gcDialingStrRe.search(callSuccessPage) is not None:
-                       return True
-               else:
-                       self._msg = "Grand Central returned an error"
-                       return False
+               if GCDialer._gcDialingStrRe.search(callSuccessPage) is None:
+                       raise RuntimeError("Grand Central returned an error")
 
-               self._msg = "Unknown Error"
-               return False
+               return True
 
        def clear_caches(self):
-               pass
+               self.__contacts = None
 
        def is_valid_syntax(self, number):
                """
@@ -226,8 +222,7 @@ class GCDialer(object):
                try:
                        callbackSetPage = self._browser.download(GCDialer._setforwardURL, callbackPostData)
                except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._setforwardURL, UserWarning, 2)
-                       return False
+                       raise RuntimeError("%s is not accesible" % GCDialer._setforwardURL)
 
                self._browser.cookies.save()
                return True
@@ -248,8 +243,7 @@ class GCDialer(object):
                try:
                        recentCallsPage = self._browser.download(GCDialer._inboxallURL)
                except urllib2.URLError, e:
-                       warnings.warn("%s is not accesible" % GCDialer._inboxallURL, UserWarning, 2)
-                       return
+                       raise RuntimeError("%s is not accesible" % GCDialer._inboxallURL)
 
                for match in self._inboxRe.finditer(recentCallsPage):
                        phoneNumber = match.group(4)
@@ -263,12 +257,12 @@ class GCDialer(object):
                @returns Iterable of (Address Book Factory, Book Id, Book Name)
                """
                yield self, "", ""
-       
+
        def open_addressbook(self, bookId):
                return self
 
        @staticmethod
-       def factory_short_name():
+       def contact_source_short_name(contactId):
                return "GC"
 
        @staticmethod
@@ -279,24 +273,39 @@ class GCDialer(object):
                """
                @returns Iterable of (contact id, contact name)
                """
-               contactsPagesUrls = [GCDialer._contactsURL]
-               for contactsPageUrl in contactsPagesUrls:
-                       contactsPage = self._browser.download(contactsPageUrl)
-                       for contact_match in self._contactsRe.finditer(contactsPage):
-                               contactId = contact_match.group(1)
-                               contactName = contact_match.group(2)
-                               yield contactId, contactName
-
-                       next_match = self._contactsNextRe.match(contactsPage)
-                       if next_match is not None:
-                               newContactsPageUrl = self._contactsURL + next_match.group(1)
-                               contactsPagesUrls.append(newContactsPageUrl)
-       
+               if self.__contacts is None:
+                       self.__contacts = []
+
+                       contactsPagesUrls = [GCDialer._contactsURL]
+                       for contactsPageUrl in contactsPagesUrls:
+                               try:
+                                       contactsPage = self._browser.download(contactsPageUrl)
+                               except urllib2.URLError, e:
+                                       raise RuntimeError("%s is not accesible" % contactsPageUrl)
+                               for contact_match in self._contactsRe.finditer(contactsPage):
+                                       contactId = contact_match.group(1)
+                                       contactName = contact_match.group(2)
+                                       contact = contactId, contactName
+                                       self.__contacts.append(contact)
+                                       yield contact
+
+                               next_match = self._contactsNextRe.match(contactsPage)
+                               if next_match is not None:
+                                       newContactsPageUrl = self._contactsURL + next_match.group(1)
+                                       contactsPagesUrls.append(newContactsPageUrl)
+               else:
+                       for contact in self.__contacts:
+                               yield contact
+
        def get_contact_details(self, contactId):
                """
                @returns Iterable of (Phone Type, Phone Number)
                """
-               detailPage = self._browser.download(GCDialer._contactDetailURL + '/' + contactId)
+               try:
+                       detailPage = self._browser.download(GCDialer._contactDetailURL + '/' + contactId)
+               except urllib2.URLError, e:
+                       raise RuntimeError("%s is not accesible" % GCDialer._contactDetailURL)
+
                for detail_match in self._contactDetailPhoneRe.finditer(detailPage):
                        phoneType = detail_match.group(1)
                        phoneNumber = detail_match.group(2)