License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+@bug Completely broken on Maemo
+@todo Add storing of credentials like DoneIt
@todo Add logging support to make debugging issues for people a lot easier
"""
import gc
import os
import threading
+import base64
+import ConfigParser
+import itertools
import warnings
import traceback
return 0
+def display_error_message(msg):
+ error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
+
+ def close(dialog, response):
+ dialog.destroy()
+ error_dialog.connect("response", close)
+ error_dialog.run()
+
+
class Dialcentral(object):
__pretty_app_name__ = "DialCentral"
BACKENDS = (NULL_BACKEND, GC_BACKEND, GV_BACKEND)
_data_path = os.path.join(os.path.expanduser("~"), ".dialcentral")
+ _user_settings = "%s/settings.ini" % _data_path
def __init__(self):
+ self._initDone = False
self._connection = None
self._osso = None
self._clipboard = gtk.clipboard_get()
self._deviceIsOnline = True
+ self._credentials = ("", "")
self._selectedBackendId = self.NULL_BACKEND
self._defaultBackendId = self.GC_BACKEND
self._phoneBackends = None
self._widgetTree = gtk.glade.XML(path)
break
else:
- self.display_error_message("Cannot find dialcentral.glade")
+ display_error_message("Cannot find dialcentral.glade")
gtk.main_quit()
return
self._window = self._widgetTree.get_widget("mainWindow")
self._notebook = self._widgetTree.get_widget("notebook")
self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
- self._credentials = gtk_toolbox.LoginWindow(self._widgetTree)
+ self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
self._app = None
self._isFullScreen = False
self._widgetTree.signal_autoconnect(callbackMapping)
if self._window:
- self._window.connect("destroy", gtk.main_quit)
+ self._window.connect("destroy", self._on_close)
self._window.show_all()
self._window.set_default_size(800, 300)
}
self._widgetTree.signal_autoconnect(callbackMapping)
+ self._initDone = True
+
+ config = ConfigParser.SafeConfigParser()
+ config.read(self._user_settings)
+ with gtk_toolbox.gtk_lock():
+ self.load_settings(config)
+
self.attempt_login(2)
return False
if self._phoneBackends[self._defaultBackendId].is_authed():
serviceId = self._defaultBackendId
loggedIn = True
+ username, password = self._credentials
for x in xrange(numOfAttempts):
if loggedIn:
break
self.GV_BACKEND: "Google Voice",
self.GC_BACKEND: "Grand Central",
}
- credentials = self._credentials.request_credentials_from(availableServices)
+ credentials = self._credentialsDialog.request_credentials_from(
+ availableServices, defaultCredentials = self._credentials
+ )
serviceId, username, password = credentials
loggedIn = self._phoneBackends[serviceId].login(username, password)
self._errorDisplay.push_exception_with_lock(e)
with gtk_toolbox.gtk_lock():
- if not loggedIn:
+ if loggedIn:
+ self._credentials = username, password
+ else:
self._errorDisplay.push_message("Login Failed")
self._change_loggedin_status(serviceId if loggedIn else self.NULL_BACKEND)
return loggedIn
- def display_error_message(self, msg):
- error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
-
- def close(dialog, response, editor):
- editor.about_dialog = None
- dialog.destroy()
- error_dialog.connect("response", close, self)
- error_dialog.run()
-
def _on_close(self, *args, **kwds):
- if self._osso is not None:
- self._osso.close()
- gtk.main_quit()
+ try:
+ if self._osso is not None:
+ self._osso.close()
+
+ if self._initDone:
+ self._save_settings()
+ finally:
+ gtk.main_quit()
def _change_loggedin_status(self, newStatus):
oldStatus = self._selectedBackendId
self._selectedBackendId = newStatus
+ def load_settings(self, config):
+ """
+ @note UI Thread
+ """
+ self._defaultBackendId = int(config.get(self.__pretty_app_name__, "active"))
+ blobs = (
+ config.get(self.__pretty_app_name__, "bin_blob_%i" % i)
+ for i in xrange(len(self._credentials))
+ )
+ creds = (
+ base64.b64decode(blob)
+ for blob in blobs
+ )
+ self._credentials = tuple(creds)
+ for backendId, view in itertools.chain(
+ self._dialpads.iteritems(),
+ self._accountViews.iteritems(),
+ self._messagesViews.iteritems(),
+ self._recentViews.iteritems(),
+ self._contactsViews.iteritems(),
+ ):
+ sectionName = "%s - %s" % (backendId, view.name())
+ view.load_settings(config, sectionName)
+
+ def save_settings(self, config):
+ """
+ @note Thread Agnostic
+ """
+ config.add_section(self.__pretty_app_name__)
+ config.set(self.__pretty_app_name__, "active", str(self._selectedBackendId))
+ for i, value in enumerate(self._credentials):
+ blob = base64.b64encode(value)
+ config.set(self.__pretty_app_name__, "bin_blob_%i" % i, blob)
+ for backendId, view in itertools.chain(
+ self._dialpads.iteritems(),
+ self._accountViews.iteritems(),
+ self._messagesViews.iteritems(),
+ self._recentViews.iteritems(),
+ self._contactsViews.iteritems(),
+ ):
+ sectionName = "%s - %s" % (backendId, view.name())
+ config.add_section(sectionName)
+ view.save_settings(config, sectionName)
+
def _guess_preferred_backend(self, backendAndCookiePaths):
modTimeAndPath = [
(getmtime_nothrow(path), backendId, path)
modTimeAndPath.sort()
return modTimeAndPath[-1][1]
+ def _save_settings(self):
+ """
+ @note Thread Agnostic
+ """
+ config = ConfigParser.SafeConfigParser()
+ self.save_settings(config)
+ with open(self._user_settings, "wb") as configFile:
+ config.write(configFile)
+
def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
"""
For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
self._contactsViews[self._selectedBackendId].clear_caches()
gc.collect()
+ if save_unsaved_data or shutdown:
+ self._save_settings()
+
def _on_connection_change(self, connection, event, magicIdentifier):
"""
@note Hildon specific
from __future__ import with_statement
import threading
-import time
import warnings
import gobject
def clear(self):
self.set_number("")
+ @staticmethod
+ def name():
+ return "Dialpad"
+
+ def load_settings(self, config, section):
+ pass
+
+ def save_settings(self, config, section):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
def _on_dial_clicked(self, widget):
self.dial(self.get_number())
self._callbackCombo = widgetTree.get_widget("callbackcombo")
self._onCallbackentryChangedId = 0
+ self._defaultCallback = ""
+
def enable(self):
assert self._backend.is_authed()
self._accountViewNumberDisplay.set_use_markup(True)
self._accountViewNumberDisplay.set_label("<span size='23000' weight='bold'>%s</span>" % (number))
def update(self, force = False):
- self.populate_callback_combo()
+ self._populate_callback_combo()
self.set_account_number(self._backend.get_account_number())
def clear(self):
self._callbackCombo.get_child().set_text("")
self.set_account_number("")
- def populate_callback_combo(self):
+ @staticmethod
+ def name():
+ return "Account Info"
+
+ def load_settings(self, config, section):
+ self._defaultCallback = config.get(section, "callback")
+
+ def save_settings(self, config, section):
+ """
+ @note Thread Agnostic
+ """
+ callback = self.get_selected_callback_number()
+ config.set(section, "callback", callback)
+
+ def _populate_callback_combo(self):
self._callbackList.clear()
try:
callbackNumbers = self._backend.get_callback_numbers()
try:
callbackNumber = self._backend.get_callback_number()
except RuntimeError, e:
- self._errorDisplay.push_exception(e)
- return
+ callbackNumber = self._defaultCallback
self._callbackCombo.get_child().set_text(make_pretty(callbackNumber))
def _on_callbackentry_changed(self, *args):
self._errorDisplay = errorDisplay
self._backend = backend
- self._recenttime = 0.0
+ self._isPopulated = False
self._recentmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
self._recentview = widgetTree.get_widget("recentview")
self._recentviewselection = None
raise NotImplementedError
def update(self, force = False):
- if not force and (time.time() - self._recenttime) < 300:
+ if not force and self._isPopulated:
return
backgroundPopulate = threading.Thread(target=self._idly_populate_recentview)
backgroundPopulate.setDaemon(True)
backgroundPopulate.start()
def clear(self):
- self._recenttime = 0.0
+ self._isPopulated = False
self._recentmodel.clear()
+ @staticmethod
+ def name():
+ return "Recent Calls"
+
+ def load_settings(self, config, section):
+ pass
+
+ def save_settings(self, config, section):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
def _idly_populate_recentview(self):
- self._recenttime = time.time()
+ self._isPopulated = True
self._recentmodel.clear()
try:
recentItems = self._backend.get_recent()
except RuntimeError, e:
self._errorDisplay.push_exception_with_lock(e)
- self._recenttime = 0.0
+ self._isPopulated = False
recentItems = []
for personsName, phoneNumber, date, action in recentItems:
self._errorDisplay = errorDisplay
self._backend = backend
- self._messagetime = 0.0
+ self._isPopulated = False
self._messagemodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
self._messageview = widgetTree.get_widget("messages_view")
self._messageviewselection = None
raise NotImplementedError
def update(self, force = False):
- if not force and (time.time() - self._messagetime) < 300:
+ if not force and self._isPopulated:
return
backgroundPopulate = threading.Thread(target=self._idly_populate_messageview)
backgroundPopulate.setDaemon(True)
backgroundPopulate.start()
def clear(self):
- self._messagetime = 0.0
+ self._isPopulated = False
self._messagemodel.clear()
+ @staticmethod
+ def name():
+ return "Messages"
+
+ def load_settings(self, config, section):
+ pass
+
+ def save_settings(self, config, section):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
def _idly_populate_messageview(self):
- self._messagetime = time.time()
+ self._isPopulated = True
self._messagemodel.clear()
try:
messageItems = self._backend.get_messages()
except RuntimeError, e:
self._errorDisplay.push_exception_with_lock(e)
- self._messagetime = 0.0
+ self._isPopulated = False
messageItems = []
for header, number, relativeDate, message in messageItems:
self._booksList = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
self._booksSelectionBox = widgetTree.get_widget("addressbook_combo")
- self._contactstime = 0.0
+ self._isPopulated = False
self._contactsmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
self._contactsviewselection = None
self._contactsview = widgetTree.get_widget("contactsview")
def open_addressbook(self, bookFactoryId, bookId):
self._addressBook = self._addressBookFactories[bookFactoryId].open_addressbook(bookId)
- self._contactstime = 0
- backgroundPopulate = threading.Thread(target=self._idly_populate_contactsview)
- backgroundPopulate.setDaemon(True)
- backgroundPopulate.start()
+ self.update(force=True)
def update(self, force = False):
- if not force and (time.time() - self._contactstime) < 300:
+ if not force and self._isPopulated:
return
backgroundPopulate = threading.Thread(target=self._idly_populate_contactsview)
backgroundPopulate.setDaemon(True)
backgroundPopulate.start()
def clear(self):
- self._contactstime = 0.0
+ self._isPopulated = False
self._contactsmodel.clear()
def clear_caches(self):
def extend(self, books):
self._addressBookFactories.extend(books)
+ @staticmethod
+ def name():
+ return "Contacts"
+
+ def load_settings(self, config, section):
+ pass
+
+ def save_settings(self, config, section):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
def _idly_populate_contactsview(self):
- #@todo Add a lock so only one code path can be in here at a time
+ self._isPopulated = True
self.clear()
# completely disable updating the treeview while we populate the data
contacts = addressBook.get_contacts()
except RuntimeError, e:
contacts = []
- self._contactstime = 0.0
+ self._isPopulated = False
self._errorDisplay.push_exception_with_lock(e)
for contactId, contactName in contacts:
contactType = (addressBook.contact_source_short_name(contactId), )
contactDetails = self._addressBook.get_contact_details(contactId)
except RuntimeError, e:
contactDetails = []
- self._contactstime = 0.0
self._errorDisplay.push_exception(e)
contactPhoneNumbers = [phoneNumber for phoneNumber in contactDetails]
return username, password
- def request_credentials_from(self, services, parentWindow = None):
+ def request_credentials_from(self,
+ services,
+ parentWindow = None,
+ defaultCredentials = ("", "")
+ ):
"""
@note UI Thread
"""
self._serviceCombo.set_active(0)
self._serviceCombo.show()
+ self._usernameEntry.set_text(defaultCredentials[0])
+ self._passwordEntry.set_text(defaultCredentials[1])
+
try:
self._dialog.set_transient_for(parentWindow)
self._dialog.set_default_response(gtk.RESPONSE_OK)
username = self._usernameEntry.get_text()
password = self._passwordEntry.get_text()
- self._passwordEntry.set_text("")
finally:
self._dialog.hide()
def disable(self):
self._dialButton.set_sensitive(True)
+ @staticmethod
+ def name():
+ return "Dialpad"
+
+ def load_settings(self, config, sectionName):
+ pass
+
+ def save_settings(self, config, sectionName):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
class AccountInfo(object):
def clear():
pass
+ @staticmethod
+ def name():
+ return "Account Info"
+
+ def load_settings(self, config, sectionName):
+ pass
+
+ def save_settings(self, config, sectionName):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
class RecentCallsView(object):
def clear():
pass
+ @staticmethod
+ def name():
+ return "Recent Calls"
+
+ def load_settings(self, config, sectionName):
+ pass
+
+ def save_settings(self, config, sectionName):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
class MessagesView(object):
def clear():
pass
+ @staticmethod
+ def name():
+ return "Messages"
+
+ def load_settings(self, config, sectionName):
+ pass
+
+ def save_settings(self, config, sectionName):
+ """
+ @note Thread Agnostic
+ """
+ pass
+
class ContactsView(object):
@staticmethod
def clear():
pass
+
+ @staticmethod
+ def name():
+ return "Contacts"
+
+ def load_settings(self, config, sectionName):
+ pass
+
+ def save_settings(self, config, sectionName):
+ """
+ @note Thread Agnostic
+ """
+ pass