X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=blobdiff_plain;f=src%2Fdc_glade.py;h=2c1563e1c2fe00667501420cc7a3fcf5f51a7a28;hp=94d54065491e52d96691a082f2129e9804994968;hb=0320d6d2086ce89fe554d8f902531f5b00dbec94;hpb=bc887e08a3cd90c75426d3b0635af82c4df116a1 diff --git a/src/dc_glade.py b/src/dc_glade.py index 94d5406..2c1563e 100755 --- a/src/dc_glade.py +++ b/src/dc_glade.py @@ -1,7 +1,7 @@ #!/usr/bin/python2.5 """ -DialCentral - Front end for Google's Grand Central service. +DialCentral - Front end for Google's GoogleVoice service. Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com This library is free software; you can redistribute it and/or @@ -17,18 +17,6 @@ Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public 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 Crashes when switching contact lists, see http://talk.maemo.org/showpost.php?p=312920&postcount=176 -@bug Refeshing SMS a lot, then go to contacts and send a message, see http://talk.maemo.org/showpost.php?p=312920&postcount=176 -@bug Can't send sms from dialpad, see http://talk.maemo.org/showpost.php?p=312922&postcount=177 -@bug Sending an sms from contacts gave an error -@bug Getting into a bad state on connection loss, see http://talk.maemo.org/showpost.php?p=312912&postcount=175 - -@todo Figure out how to integrate with the Maemo contacts app -@bug Session timeouts are bad, possible solutions: - @li For every X minutes, if logged in, attempt login - @li Restructure so code handling login/dial/sms is beneath everything else and login attempts are made if those fail -@todo Add logging support to make debugging issues for people a lot easier """ @@ -41,24 +29,25 @@ import threading import base64 import ConfigParser import itertools -import warnings +import shutil +import logging import gtk import gtk.glade -try: - import hildon -except ImportError: - hildon = None - import constants +import hildonize import gtk_toolbox +_moduleLogger = logging.getLogger("dc_glade") +PROFILE_STARTUP = False + + def getmtime_nothrow(path): try: return os.path.getmtime(path) - except StandardError: + except Exception: return 0 @@ -86,28 +75,30 @@ class Dialcentral(object): ACCOUNT_TAB = 4 NULL_BACKEND = 0 - GC_BACKEND = 1 + # 1 Was GrandCentral support so the gap was maintained for compatibility GV_BACKEND = 2 - BACKENDS = (NULL_BACKEND, GC_BACKEND, GV_BACKEND) + BACKENDS = (NULL_BACKEND, GV_BACKEND) def __init__(self): self._initDone = False self._connection = None self._osso = None + self._deviceState = None self._clipboard = gtk.clipboard_get() self._credentials = ("", "") self._selectedBackendId = self.NULL_BACKEND - self._defaultBackendId = self.GC_BACKEND + self._defaultBackendId = self.GV_BACKEND self._phoneBackends = None self._dialpads = None self._accountViews = None self._messagesViews = None - self._recentViews = None + self._historyViews = None self._contactsViews = None self._alarmHandler = None self._ledHandler = None self._originalCurrentLabels = [] + self._fsContactsPath = os.path.join(constants._data_path_, "contacts") for path in self._glade_files: if os.path.isfile(path): @@ -123,45 +114,68 @@ class Dialcentral(object): self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree) self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree) - self._app = None self._isFullScreen = False - if hildon is not None: - self._app = hildon.Program() - oldWindow = self._window - self._window = hildon.Window() - oldWindow.get_child().reparent(self._window) - self._app.add_window(self._window) + self._app = hildonize.get_app_class()() + self._window = hildonize.hildonize_window(self._app, self._window) + hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry")) + hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry")) + + for scrollingWidgetName in ( + 'history_scrolledwindow', + 'message_scrolledwindow', + 'contacts_scrolledwindow', + "smsMessages_scrolledwindow", + ): + scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName) + assert scrollingWidget is not None, scrollingWidgetName + hildonize.hildonize_scrollwindow(scrollingWidget) + for scrollingWidgetName in ( + "smsMessage_scrolledEntry", + ): + scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName) + assert scrollingWidget is not None, scrollingWidgetName + hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget) + + for buttonName in ( + "back", + "addressbookSelectButton", + "sendSmsButton", + "dialButton", + "cancelSmsButton", + "callbackSelectButton", + "minutesEntryButton", + "clearcookies", + "phoneTypeSelection", + ): + button = self._widgetTree.get_widget(buttonName) + assert button is not None, buttonName + hildonize.set_button_thumb_selectable(button) - try: - self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7) - self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29)) - self._widgetTree.get_widget("callbackcombo").get_child().set_property('hildon-input-mode', (1 << 4)) - except TypeError, e: - warnings.warn(e.message) - hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('recent_scrolledwindow'), True) - hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('message_scrolledwindow'), True) - hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('contacts_scrolledwindow'), True) - - gtkMenu = self._widgetTree.get_widget("dialpad_menubar") - menu = gtk.Menu() - for child in gtkMenu.get_children(): - child.reparent(menu) - self._window.set_menu(menu) - gtkMenu.destroy() - - self._window.connect("key-press-event", self._on_key_press) - self._window.connect("window-state-event", self._on_window_state_change) - else: - pass # warnings.warn("No Hildon", UserWarning, 2) + menu = hildonize.hildonize_menu( + self._window, + self._widgetTree.get_widget("dialpad_menubar"), + ) + if not hildonize.GTK_MENU_USED: + button = gtk.Button("New Login") + button.connect("clicked", self._on_clearcookies_clicked) + menu.append(button) + + button = gtk.Button("Import Contacts") + button.connect("clicked", self._on_contact_import) + menu.append(button) + + button = gtk.Button("Refresh") + button.connect("clicked", self._on_menu_refresh) + menu.append(button) + + menu.show_all() - # If under hildon, rely on the application name being shown - if hildon is None: - self._window.set_title("%s" % constants.__pretty_app_name__) + self._window.connect("key-press-event", self._on_key_press) + self._window.connect("window-state-event", self._on_window_state_change) + if not hildonize.IS_HILDON_SUPPORTED: + _moduleLogger.warning("No hildonization support") - callbackMapping = { - "on_dialpad_quit": self._on_close, - } - self._widgetTree.signal_autoconnect(callbackMapping) + hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__) self._window.connect("destroy", self._on_close) self._window.set_default_size(800, 300) @@ -169,68 +183,86 @@ class Dialcentral(object): self._loginSink = gtk_toolbox.threaded_stage( gtk_toolbox.comap( - self.attempt_login, + self._attempt_login, gtk_toolbox.null_sink(), ) ) - backgroundSetup = threading.Thread(target=self._idle_setup) - backgroundSetup.setDaemon(True) - backgroundSetup.start() + if not PROFILE_STARTUP: + backgroundSetup = threading.Thread(target=self._idle_setup) + backgroundSetup.setDaemon(True) + backgroundSetup.start() + else: + self._idle_setup() def _idle_setup(self): """ If something can be done after the UI loads, push it here so it's not blocking the UI """ + # Barebones UI handlers try: - # Barebones UI handlers - import null_backend + from backends import null_backend import null_views self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()} with gtk_toolbox.gtk_lock(): self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)} self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)} - self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)} + self._historyViews = {self.NULL_BACKEND: null_views.CallHistoryView(self._widgetTree)} self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)} self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)} self._dialpads[self._selectedBackendId].enable() self._accountViews[self._selectedBackendId].enable() - self._recentViews[self._selectedBackendId].enable() + self._historyViews[self._selectedBackendId].enable() self._messagesViews[self._selectedBackendId].enable() self._contactsViews[self._selectedBackendId].enable() + except Exception, e: + with gtk_toolbox.gtk_lock(): + self._errorDisplay.push_exception() - # Setup maemo specifics + # Setup maemo specifics + try: try: import osso - except ImportError: + except (ImportError, OSError): osso = None self._osso = None + self._deviceState = None if osso is not None: self._osso = osso.Context(constants.__app_name__, constants.__version__, False) - device = osso.DeviceState(self._osso) - device.set_device_state_callback(self._on_device_state_change, 0) + self._deviceState = osso.DeviceState(self._osso) + self._deviceState.set_device_state_callback(self._on_device_state_change, 0) else: - pass # warnings.warn("No OSSO", UserWarning, 2) + _moduleLogger.warning("No device state support") try: import alarm_handler - self._alarmHandler = alarm_handler.AlarmHandler() - except ImportError: + if alarm_handler.AlarmHandler is not alarm_handler._NoneAlarmHandler: + self._alarmHandler = alarm_handler.AlarmHandler() + else: + self._alarmHandler = None + except (ImportError, OSError): alarm_handler = None except Exception: with gtk_toolbox.gtk_lock(): self._errorDisplay.push_exception() alarm_handler = None - if hildon is not None: - import led_handler - self._ledHandler = led_handler.LedHandler() + if alarm_handler is None: + _moduleLogger.warning("No notification support") + if hildonize.IS_HILDON_SUPPORTED: + try: + import led_handler + self._ledHandler = led_handler.LedHandler() + except Exception, e: + _moduleLogger.exception('LED Handling failed: "%s"' % str(e)) + self._ledHandler = None + else: + self._ledHandler = None - # Setup maemo specifics try: import conic - except ImportError: + except (ImportError, OSError): conic = None self._connection = None if conic is not None: @@ -238,98 +270,82 @@ class Dialcentral(object): self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__) self._connection.request_connection(conic.CONNECT_FLAG_NONE) else: - pass # warnings.warn("No Internet Connectivity API ", UserWarning) + _moduleLogger.warning("No connection support") + except Exception, e: + with gtk_toolbox.gtk_lock(): + self._errorDisplay.push_exception() - # Setup costly backends - import gv_backend - import gc_backend - import file_backend - import evo_backend - import gc_views + # Setup costly backends + try: + from backends import gv_backend + from backends import file_backend + import gv_views + from backends import merge_backend try: os.makedirs(constants._data_path_) except OSError, e: if e.errno != 17: raise - gcCookiePath = os.path.join(constants._data_path_, "gc_cookies.txt") gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt") - self._defaultBackendId = self._guess_preferred_backend(( - (self.GC_BACKEND, gcCookiePath), - (self.GV_BACKEND, gvCookiePath), - )) self._phoneBackends.update({ - self.GC_BACKEND: gc_backend.GCDialer(gcCookiePath), self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath), }) with gtk_toolbox.gtk_lock(): - unifiedDialpad = gc_views.Dialpad(self._widgetTree, self._errorDisplay) - unifiedDialpad.set_number("") + unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay) self._dialpads.update({ - self.GC_BACKEND: unifiedDialpad, self.GV_BACKEND: unifiedDialpad, }) self._accountViews.update({ - self.GC_BACKEND: gc_views.AccountInfo( - self._widgetTree, self._phoneBackends[self.GC_BACKEND], None, self._errorDisplay - ), - self.GV_BACKEND: gc_views.AccountInfo( + self.GV_BACKEND: gv_views.AccountInfo( self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay ), }) - self._accountViews[self.GC_BACKEND].save_everything = lambda *args: None self._accountViews[self.GV_BACKEND].save_everything = self._save_settings - self._recentViews.update({ - self.GC_BACKEND: gc_views.RecentCallsView( - self._widgetTree, self._phoneBackends[self.GC_BACKEND], self._errorDisplay - ), - self.GV_BACKEND: gc_views.RecentCallsView( + self._historyViews.update({ + self.GV_BACKEND: gv_views.CallHistoryView( self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay ), }) self._messagesViews.update({ - self.GC_BACKEND: null_views.MessagesView(self._widgetTree), - self.GV_BACKEND: gc_views.MessagesView( + self.GV_BACKEND: gv_views.MessagesView( self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay ), }) self._contactsViews.update({ - self.GC_BACKEND: gc_views.ContactsView( - self._widgetTree, self._phoneBackends[self.GC_BACKEND], self._errorDisplay - ), - self.GV_BACKEND: gc_views.ContactsView( + self.GV_BACKEND: gv_views.ContactsView( self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay ), }) - evoBackend = evo_backend.EvolutionAddressBook() - fsContactsPath = os.path.join(constants._data_path_, "contacts") - fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath) - for backendId in (self.GV_BACKEND, self.GC_BACKEND): - self._dialpads[backendId].number_selected = self._select_action - self._recentViews[backendId].number_selected = self._select_action - self._messagesViews[backendId].number_selected = self._select_action - self._contactsViews[backendId].number_selected = self._select_action - - addressBooks = [ - self._phoneBackends[backendId], - evoBackend, - fileBackend, - ] - mergedBook = gc_views.MergedAddressBook(addressBooks, gc_views.MergedAddressBook.advanced_lastname_sorter) - self._contactsViews[backendId].append(mergedBook) - self._contactsViews[backendId].extend(addressBooks) - self._contactsViews[backendId].open_addressbook(*self._contactsViews[backendId].get_addressbooks().next()[0][0:2]) + fileBackend = file_backend.FilesystemAddressBookFactory(self._fsContactsPath) + + self._dialpads[self.GV_BACKEND].number_selected = self._select_action + self._historyViews[self.GV_BACKEND].number_selected = self._select_action + self._messagesViews[self.GV_BACKEND].number_selected = self._select_action + self._contactsViews[self.GV_BACKEND].number_selected = self._select_action + + addressBooks = [ + self._phoneBackends[self.GV_BACKEND], + fileBackend, + ] + mergedBook = merge_backend.MergedAddressBook(addressBooks, merge_backend.MergedAddressBook.basic_firtname_sorter) + self._contactsViews[self.GV_BACKEND].append(mergedBook) + self._contactsViews[self.GV_BACKEND].extend(addressBooks) + self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2]) callbackMapping = { "on_paste": self._on_paste, "on_refresh": self._on_menu_refresh, "on_clearcookies_clicked": self._on_clearcookies_clicked, - "on_notebook_switch_page": self._on_notebook_switch_page, "on_about_activate": self._on_about_activate, + "on_import": self._on_contact_import, } - self._widgetTree.signal_autoconnect(callbackMapping) + if hildonize.GTK_MENU_USED: + self._widgetTree.signal_autoconnect(callbackMapping) + self._notebook.connect("switch-page", self._on_notebook_switch_page) + self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked) with gtk_toolbox.gtk_lock(): self._originalCurrentLabels = [ @@ -343,50 +359,59 @@ class Dialcentral(object): self._notebookTapHandler.on_holding = self._set_tab_refresh self._notebookTapHandler.on_cancel = self._reset_tab_refresh - self._initDone = True - config = ConfigParser.SafeConfigParser() config.read(constants._user_settings_) with gtk_toolbox.gtk_lock(): self.load_settings(config) - - self._spawn_attempt_login(2) except Exception, e: with gtk_toolbox.gtk_lock(): self._errorDisplay.push_exception() + finally: + self._initDone = True + self._spawn_attempt_login() - def attempt_login(self, numOfAttempts = 10, force = False): - """ - @todo Handle user notification better like attempting to login and failed login + def _spawn_attempt_login(self, *args): + self._loginSink.send(args) + def _attempt_login(self, force = False): + """ @note This must be run outside of the UI lock """ try: - assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts" assert self._initDone, "Attempting login before app is fully loaded" serviceId = self.NULL_BACKEND loggedIn = False - if not force: + if not force and self._defaultBackendId != self.NULL_BACKEND: + with gtk_toolbox.gtk_lock(): + banner = hildonize.show_busy_banner_start(self._window, "Logging In...") try: self.refresh_session() serviceId = self._defaultBackendId loggedIn = True - except StandardError, e: - warnings.warn('Session refresh failed with the following message "%s"' % e.message, UserWarning, 2) + except Exception, e: + _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e)) + finally: + with gtk_toolbox.gtk_lock(): + hildonize.show_busy_banner_end(banner) if not loggedIn: - loggedIn, serviceId = self._login_by_user(numOfAttempts) + loggedIn, serviceId = self._login_by_user() with gtk_toolbox.gtk_lock(): self._change_loggedin_status(serviceId) - except StandardError, e: + if loggedIn: + hildonize.show_information_banner(self._window, "Logged In") + else: + hildonize.show_information_banner(self._window, "Login Failed") + if not self._phoneBackends[self._defaultBackendId].get_callback_number(): + # subtle reminder to the users to configure things + self._notebook.set_current_page(self.ACCOUNT_TAB) + + except Exception, e: with gtk_toolbox.gtk_lock(): self._errorDisplay.push_exception() - def _spawn_attempt_login(self, *args): - self._loginSink.send(args) - def refresh_session(self): """ @note Thread agnostic @@ -406,69 +431,74 @@ class Dialcentral(object): """ @note Thread agnostic """ - loggedIn = self._phoneBackends[self._defaultBackendId].is_authed() + loggedIn = False + + isQuickLoginPossible = self._phoneBackends[self._defaultBackendId].is_quick_login_possible() + if self._credentials != ("", "") and isQuickLoginPossible: + if not loggedIn: + loggedIn = self._phoneBackends[self._defaultBackendId].is_authed() + if loggedIn: - warnings.warn( - "Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId], - UserWarning, 2 - ) + _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId]) + else: + # If the cookies are bad, scratch them completely + self._phoneBackends[self._defaultBackendId].logout() + return loggedIn def _login_by_settings(self): """ @note Thread agnostic """ + if self._credentials == ("", ""): + # Don't bother with the settings if they are blank + return False + username, password = self._credentials loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password) if loggedIn: self._credentials = username, password - warnings.warn( - "Logged into %r through settings" % self._phoneBackends[self._defaultBackendId], - UserWarning, 2 - ) + _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId]) return loggedIn - def _login_by_user(self, numOfAttempts): + def _login_by_user(self): """ @note This must be run outside of the UI lock """ loggedIn, (username, password) = False, self._credentials - tmpServiceId = self.NULL_BACKEND - for attemptCount in xrange(numOfAttempts): - if loggedIn: - break - availableServices = ( - (self.GV_BACKEND, "Google Voice"), - (self.GC_BACKEND, "Grand Central"), - ) + tmpServiceId = self.GV_BACKEND + while not loggedIn: with gtk_toolbox.gtk_lock(): - credentials = self._credentialsDialog.request_credentials_from( - availableServices, defaultCredentials = self._credentials + credentials = self._credentialsDialog.request_credentials( + defaultCredentials = self._credentials ) - tmpServiceId, username, password = credentials - loggedIn = self._phoneBackends[tmpServiceId].login(username, password) + banner = hildonize.show_busy_banner_start(self._window, "Logging In...") + try: + username, password = credentials + loggedIn = self._phoneBackends[tmpServiceId].login(username, password) + finally: + with gtk_toolbox.gtk_lock(): + hildonize.show_busy_banner_end(banner) if loggedIn: serviceId = tmpServiceId self._credentials = username, password - warnings.warn( - "Logged into %r through user request" % self._phoneBackends[serviceId], - UserWarning, 2 - ) + _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId]) else: + # Hint to the user that they are not logged in serviceId = self.NULL_BACKEND + self._notebook.set_current_page(self.ACCOUNT_TAB) return loggedIn, serviceId - def _select_action(self, action, number, message): + def _select_action(self, action, numbers, message): self.refresh_session() - if action == "select": - self._dialpads[self._selectedBackendId].set_number(number) - self._notebook.set_current_page(self.KEYPAD_TAB) - elif action == "dial": + if action == "dial": + assert len(numbers) == 1 + number = numbers[0] self._on_dial_clicked(number) elif action == "sms": - self._on_sms_clicked(number, message) + self._on_sms_clicked(numbers, message) else: assert False, "Unknown action: %s" % action @@ -479,28 +509,30 @@ class Dialcentral(object): self._dialpads[oldStatus].disable() self._accountViews[oldStatus].disable() - self._recentViews[oldStatus].disable() + self._historyViews[oldStatus].disable() self._messagesViews[oldStatus].disable() self._contactsViews[oldStatus].disable() self._dialpads[newStatus].enable() self._accountViews[newStatus].enable() - self._recentViews[newStatus].enable() + self._historyViews[newStatus].enable() self._messagesViews[newStatus].enable() self._contactsViews[newStatus].enable() - if self._phoneBackends[self._selectedBackendId].get_callback_number() is None: - self._phoneBackends[self._selectedBackendId].set_sane_callback() - self._accountViews[self._selectedBackendId].update() - self._selectedBackendId = newStatus + self._accountViews[self._selectedBackendId].update() + self._refresh_active_tab() + def load_settings(self, config): """ @note UI Thread """ try: - self._defaultBackendId = int(config.get(constants.__pretty_app_name__, "active")) + if not PROFILE_STARTUP: + self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active") + else: + self._defaultBackendId = self.NULL_BACKEND blobs = ( config.get(constants.__pretty_app_name__, "bin_blob_%i" % i) for i in xrange(len(self._credentials)) @@ -513,56 +545,86 @@ class Dialcentral(object): if self._alarmHandler is not None: self._alarmHandler.load_settings(config, "alarm") + + isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen") + if isFullscreen: + self._window.fullscreen() except ConfigParser.NoOptionError, e: - warnings.warn( + _moduleLogger.exception( "Settings file %s is missing section %s" % ( constants._user_settings_, e.section, ), - stacklevel=2 ) except ConfigParser.NoSectionError, e: - warnings.warn( + _moduleLogger.exception( "Settings file %s is missing section %s" % ( constants._user_settings_, e.section, ), - stacklevel=2 ) for backendId, view in itertools.chain( self._dialpads.iteritems(), self._accountViews.iteritems(), self._messagesViews.iteritems(), - self._recentViews.iteritems(), + self._historyViews.iteritems(), self._contactsViews.iteritems(), ): sectionName = "%s - %s" % (backendId, view.name()) try: view.load_settings(config, sectionName) except ConfigParser.NoOptionError, e: - warnings.warn( + _moduleLogger.exception( "Settings file %s is missing section %s" % ( constants._user_settings_, e.section, ), - stacklevel=2 ) except ConfigParser.NoSectionError, e: - warnings.warn( + _moduleLogger.exception( "Settings file %s is missing section %s" % ( constants._user_settings_, e.section, ), - stacklevel=2 ) + try: + previousOrientation = config.getint(constants.__pretty_app_name__, "orientation") + if previousOrientation == gtk.ORIENTATION_HORIZONTAL: + hildonize.window_to_landscape(self._window) + elif previousOrientation == gtk.ORIENTATION_VERTICAL: + hildonize.window_to_portrait(self._window) + except ConfigParser.NoOptionError, e: + _moduleLogger.exception( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + ) + except ConfigParser.NoSectionError, e: + _moduleLogger.exception( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + ) + def save_settings(self, config): """ @note Thread Agnostic """ + # Because we now only support GVoice, if there are user credentials, + # always assume its using the GVoice backend + if self._credentials[0] and self._credentials[1]: + backend = self.GV_BACKEND + else: + backend = self.NULL_BACKEND + config.add_section(constants.__pretty_app_name__) - config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId)) + config.set(constants.__pretty_app_name__, "active", str(backend)) + config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation()))) + config.set(constants.__pretty_app_name__, "fullscreen", str(self._isFullScreen)) for i, value in enumerate(self._credentials): blob = base64.b64encode(value) config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob) @@ -574,21 +636,13 @@ class Dialcentral(object): self._dialpads.iteritems(), self._accountViews.iteritems(), self._messagesViews.iteritems(), - self._recentViews.iteritems(), + self._historyViews.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) - for backendId, path in backendAndCookiePaths - ] - modTimeAndPath.sort() - return modTimeAndPath[-1][1] - def _save_settings(self): """ @note Thread Agnostic @@ -603,7 +657,7 @@ class Dialcentral(object): if pageIndex == self.CONTACTS_TAB: self._contactsViews[self._selectedBackendId].update(force=True) elif pageIndex == self.RECENT_TAB: - self._recentViews[self._selectedBackendId].update(force=True) + self._historyViews[self._selectedBackendId].update(force=True) elif pageIndex == self.MESSAGES_TAB: self._messagesViews[self._selectedBackendId].update(force=True) @@ -611,13 +665,20 @@ class Dialcentral(object): if self._ledHandler is not None: self._ledHandler.off() + @gtk_toolbox.log_exception(_moduleLogger) def _on_close(self, *args, **kwds): try: - if self._osso is not None: - self._osso.close() - if self._initDone: self._save_settings() + + try: + self._deviceState.close() + except AttributeError: + pass # Either None or close was removed (in Fremantle) + try: + self._osso.close() + except AttributeError: + pass # Either None or close was removed (in Fremantle) finally: gtk.main_quit() @@ -628,172 +689,252 @@ class Dialcentral(object): @note Hildon specific """ - if memory_low: - for backendId in self.BACKENDS: - self._phoneBackends[backendId].clear_caches() - self._contactsViews[self._selectedBackendId].clear_caches() - gc.collect() + try: + if memory_low: + for backendId in self.BACKENDS: + self._phoneBackends[backendId].clear_caches() + self._contactsViews[self._selectedBackendId].clear_caches() + gc.collect() - if save_unsaved_data or shutdown: - self._save_settings() + if save_unsaved_data or shutdown: + self._save_settings() + except Exception, e: + self._errorDisplay.push_exception() def _on_connection_change(self, connection, event, magicIdentifier): """ @note Hildon specific """ - import conic - - status = event.get_status() - error = event.get_error() - iap_id = event.get_iap_id() - bearer = event.get_bearer_type() - - if status == conic.STATUS_CONNECTED: - if self._initDone: - self._spawn_attempt_login(2) - elif status == conic.STATUS_DISCONNECTED: - if self._initDone: - self._defaultBackendId = self._selectedBackendId - self._change_loggedin_status(self.NULL_BACKEND) + try: + import conic + + status = event.get_status() + error = event.get_error() + iap_id = event.get_iap_id() + bearer = event.get_bearer_type() + + if status == conic.STATUS_CONNECTED: + if self._initDone: + self._spawn_attempt_login() + elif status == conic.STATUS_DISCONNECTED: + if self._initDone: + self._defaultBackendId = self._selectedBackendId + self._change_loggedin_status(self.NULL_BACKEND) + except Exception, e: + self._errorDisplay.push_exception() def _on_window_state_change(self, widget, event, *args): """ @note Hildon specific """ - if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: - self._isFullScreen = True - else: - self._isFullScreen = False + try: + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: + self._isFullScreen = True + else: + self._isFullScreen = False + except Exception, e: + self._errorDisplay.push_exception() def _on_key_press(self, widget, event, *args): """ @note Hildon specific """ - if event.keyval == gtk.keysyms.F6: - if self._isFullScreen: - self._window.unfullscreen() - else: - self._window.fullscreen() + RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter) + try: + if ( + event.keyval == gtk.keysyms.F6 or + event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK + ): + if self._isFullScreen: + self._window.unfullscreen() + else: + self._window.fullscreen() + elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK: + with open(constants._user_logpath_, "r") as f: + logLines = f.xreadlines() + log = "".join(logLines) + self._clipboard.set_text(str(log)) + elif ( + event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and + event.get_state() & gtk.gdk.CONTROL_MASK + ): + self._window.destroy() + elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK: + self._refresh_active_tab() + except Exception, e: + self._errorDisplay.push_exception() def _on_clearcookies_clicked(self, *args): - self._phoneBackends[self._selectedBackendId].logout() - self._accountViews[self._selectedBackendId].clear() - self._recentViews[self._selectedBackendId].clear() - self._messagesViews[self._selectedBackendId].clear() - self._contactsViews[self._selectedBackendId].clear() - self._change_loggedin_status(self.NULL_BACKEND) - - self._spawn_attempt_login(2, True) + try: + self._phoneBackends[self._selectedBackendId].logout() + self._accountViews[self._selectedBackendId].clear() + self._historyViews[self._selectedBackendId].clear() + self._messagesViews[self._selectedBackendId].clear() + self._contactsViews[self._selectedBackendId].clear() + self._change_loggedin_status(self.NULL_BACKEND) + + self._spawn_attempt_login(True) + except Exception, e: + self._errorDisplay.push_exception() def _on_notebook_switch_page(self, notebook, page, pageIndex): - self._reset_tab_refresh() - - didRecentUpdate = False - didMessagesUpdate = False - - if pageIndex == self.RECENT_TAB: - didRecentUpdate = self._recentViews[self._selectedBackendId].update() - elif pageIndex == self.MESSAGES_TAB: - didMessagesUpdate = self._messagesViews[self._selectedBackendId].update() - elif pageIndex == self.CONTACTS_TAB: - self._contactsViews[self._selectedBackendId].update() - elif pageIndex == self.ACCOUNT_TAB: - self._accountViews[self._selectedBackendId].update() - - if didRecentUpdate or didMessagesUpdate: - if self._ledHandler is not None: - self._ledHandler.off() + try: + self._reset_tab_refresh() + + didRecentUpdate = False + didMessagesUpdate = False + + if pageIndex == self.RECENT_TAB: + didRecentUpdate = self._historyViews[self._selectedBackendId].update() + elif pageIndex == self.MESSAGES_TAB: + didMessagesUpdate = self._messagesViews[self._selectedBackendId].update() + elif pageIndex == self.CONTACTS_TAB: + self._contactsViews[self._selectedBackendId].update() + elif pageIndex == self.ACCOUNT_TAB: + self._accountViews[self._selectedBackendId].update() + + if didRecentUpdate or didMessagesUpdate: + if self._ledHandler is not None: + self._ledHandler.off() + except Exception, e: + self._errorDisplay.push_exception() def _set_tab_refresh(self, *args): - pageIndex = self._notebook.get_current_page() - child = self._notebook.get_nth_page(pageIndex) - self._notebook.get_tab_label(child).set_text("Refresh?") + try: + pageIndex = self._notebook.get_current_page() + child = self._notebook.get_nth_page(pageIndex) + self._notebook.get_tab_label(child).set_text("Refresh?") + except Exception, e: + self._errorDisplay.push_exception() return False def _reset_tab_refresh(self, *args): - pageIndex = self._notebook.get_current_page() - child = self._notebook.get_nth_page(pageIndex) - self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex]) + try: + pageIndex = self._notebook.get_current_page() + child = self._notebook.get_nth_page(pageIndex) + self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex]) + except Exception, e: + self._errorDisplay.push_exception() return False def _on_tab_refresh(self, *args): - self._refresh_active_tab() - self._reset_tab_refresh() + try: + self._refresh_active_tab() + self._reset_tab_refresh() + except Exception, e: + self._errorDisplay.push_exception() return False - def _on_sms_clicked(self, number, message): - assert number, "No number specified" - assert message, "Empty message" + def _on_sms_clicked(self, numbers, message): try: - loggedIn = self._phoneBackends[self._selectedBackendId].is_authed() - except StandardError, e: - loggedIn = False - self._errorDisplay.push_exception() - return + assert numbers, "No number specified" + assert message, "Empty message" + try: + loggedIn = self._phoneBackends[self._selectedBackendId].is_authed() + except Exception, e: + loggedIn = False + self._errorDisplay.push_exception() + return - if not loggedIn: - self._errorDisplay.push_message( - "Backend link with grandcentral is not working, please try again" - ) - return + if not loggedIn: + self._errorDisplay.push_message( + "Backend link with GoogleVoice is not working, please try again" + ) + return - dialed = False - try: - self._phoneBackends[self._selectedBackendId].send_sms(number, message) - dialed = True - except StandardError, e: - self._errorDisplay.push_exception() - except ValueError, e: - self._errorDisplay.push_exception() + dialed = False + try: + self._phoneBackends[self._selectedBackendId].send_sms(numbers, message) + hildonize.show_information_banner(self._window, "Sending to %s" % ", ".join(numbers)) + _moduleLogger.info("Sending SMS to %r" % numbers) + dialed = True + except Exception, e: + self._errorDisplay.push_exception() - if dialed: - self._dialpads[self._selectedBackendId].clear() + if dialed: + self._dialpads[self._selectedBackendId].clear() + except Exception, e: + self._errorDisplay.push_exception() def _on_dial_clicked(self, number): - assert number, "No number to call" try: - loggedIn = self._phoneBackends[self._selectedBackendId].is_authed() - except StandardError, e: - loggedIn = False - self._errorDisplay.push_exception() - return + assert number, "No number to call" + try: + loggedIn = self._phoneBackends[self._selectedBackendId].is_authed() + except Exception, e: + loggedIn = False + self._errorDisplay.push_exception() + return - if not loggedIn: - self._errorDisplay.push_message( - "Backend link with grandcentral is not working, please try again" - ) - return + if not loggedIn: + self._errorDisplay.push_message( + "Backend link with GoogleVoice is not working, please try again" + ) + return - dialed = False - try: - assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified" - self._phoneBackends[self._selectedBackendId].dial(number) - dialed = True - except StandardError, e: - self._errorDisplay.push_exception() - except ValueError, e: + dialed = False + try: + assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified" + self._phoneBackends[self._selectedBackendId].call(number) + hildonize.show_information_banner(self._window, "Calling %s" % number) + _moduleLogger.info("Calling %s" % number) + dialed = True + except Exception, e: + self._errorDisplay.push_exception() + + if dialed: + self._dialpads[self._selectedBackendId].clear() + except Exception, e: self._errorDisplay.push_exception() - if dialed: - self._dialpads[self._selectedBackendId].clear() + def _on_contact_import(self, *args): + try: + csvFilter = gtk.FileFilter() + csvFilter.set_name("Contacts") + csvFilter.add_pattern("*.csv") + importFileChooser = gtk.FileChooserDialog( + title="Contacts", + parent=self._window, + ) + importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + + importFileChooser.set_property("filter", csvFilter) + userResponse = importFileChooser.run() + importFileChooser.hide() + if userResponse == gtk.RESPONSE_OK: + filename = importFileChooser.get_filename() + shutil.copy2(filename, self._fsContactsPath) + except Exception, e: + self._errorDisplay.push_exception() def _on_menu_refresh(self, *args): - self._refresh_active_tab() + try: + self._refresh_active_tab() + except Exception, e: + self._errorDisplay.push_exception() def _on_paste(self, *args): - contents = self._clipboard.wait_for_text() - self._dialpads[self._selectedBackendId].set_number(contents) + try: + contents = self._clipboard.wait_for_text() + if contents is not None: + self._dialpads[self._selectedBackendId].set_number(contents) + except Exception, e: + self._errorDisplay.push_exception() def _on_about_activate(self, *args): - dlg = gtk.AboutDialog() - dlg.set_name(constants.__pretty_app_name__) - dlg.set_version(constants.__version__) - dlg.set_copyright("Copyright 2008 - LGPL") - dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice/Grandcentral account. This application is not affiliated with Google in any way") - dlg.set_website("http://gc-dialer.garage.maemo.org/") - dlg.set_authors(["", "Eric Warnke ", "Ed Page "]) - dlg.run() - dlg.destroy() + try: + dlg = gtk.AboutDialog() + dlg.set_name(constants.__pretty_app_name__) + dlg.set_version("%s-%d" % (constants.__version__, constants.__build__)) + dlg.set_copyright("Copyright 2008 - LGPL") + dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way") + dlg.set_website("http://gc-dialer.garage.maemo.org/") + dlg.set_authors(["", "Eric Warnke ", "Ed Page "]) + dlg.run() + dlg.destroy() + except Exception, e: + self._errorDisplay.push_exception() def run_doctest(): @@ -812,10 +953,11 @@ def run_dialpad(): #with gtk_toolbox.flock(_lock_file, 0): gtk.gdk.threads_init() - if hildon is not None: + if hildonize.IS_HILDON_SUPPORTED: gtk.set_application_name(constants.__pretty_app_name__) handle = Dialcentral() - gtk.main() + if not PROFILE_STARTUP: + gtk.main() class DummyOptions(object): @@ -825,21 +967,25 @@ class DummyOptions(object): if __name__ == "__main__": - if len(sys.argv) > 1: - try: - import optparse - except ImportError: - optparse = None - - if optparse is not None: - parser = optparse.OptionParser() - parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests") - (commandOptions, commandArgs) = parser.parse_args() - else: - commandOptions = DummyOptions() - commandArgs = [] + logging.basicConfig(level=logging.DEBUG) + try: + if len(sys.argv) > 1: + try: + import optparse + except ImportError: + optparse = None - if commandOptions.test: - run_doctest() - else: - run_dialpad() + if optparse is not None: + parser = optparse.OptionParser() + parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests") + (commandOptions, commandArgs) = parser.parse_args() + else: + commandOptions = DummyOptions() + commandArgs = [] + + if commandOptions.test: + run_doctest() + else: + run_dialpad() + finally: + logging.shutdown()