From: Ed Page Date: Fri, 16 Jul 2010 01:47:06 +0000 (-0500) Subject: Merging in files from skeleton X-Git-Url: http://git.maemo.org/git/?p=gc-dialer;a=commitdiff_plain;h=5f523a1060b32b25030113b74024da4bc7bf5f2d Merging in files from skeleton --- 5f523a1060b32b25030113b74024da4bc7bf5f2d diff --cc src/constants.py index b2b9662,8be32c8..d41e7cd --- a/src/constants.py +++ b/src/constants.py @@@ -1,12 -1,10 +1,12 @@@ import os -__pretty_app_name__ = "REPLACEME" -__app_name__ = "REPLACEME" -__version__ = "0.1.0" +__pretty_app_name__ = "DialCentral" +__app_name__ = "dialcentral" +__version__ = "1.1.8" __build__ = 0 __app_magic__ = 0xdeadbeef - _data_path_ = os.path.join(os.path.expanduser("~"), ".dialcentral") + _data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__) _user_settings_ = "%s/settings.ini" % _data_path_ +_custom_notifier_settings_ = "%s/notifier.ini" % _data_path_ - _user_logpath_ = "%s/dialcentral.log" % _data_path_ + _user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__) +_notifier_logpath_ = "%s/notifier.log" % _data_path_ diff --cc src/dc_glade.py index aa32707,0000000..14ffb02 mode 100755,000000..100755 --- a/src/dc_glade.py +++ b/src/dc_glade.py @@@ -1,1008 -1,0 +1,1006 @@@ +#!/usr/bin/env python + +""" +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 +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +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 +""" + + +from __future__ import with_statement + +import sys +import gc +import os +import threading +import base64 +import ConfigParser +import itertools +import shutil +import logging + +import gtk +import gtk.glade + +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 Exception: + 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): + + _glade_files = [ + os.path.join(os.path.dirname(__file__), "dialcentral.glade"), + os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"), + '/usr/lib/dialcentral/dialcentral.glade', + ] + + KEYPAD_TAB = 0 + RECENT_TAB = 1 + MESSAGES_TAB = 2 + CONTACTS_TAB = 3 + ACCOUNT_TAB = 4 + + NULL_BACKEND = 0 + # 1 Was GrandCentral support so the gap was maintained for compatibility + GV_BACKEND = 2 + 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.GV_BACKEND + self._phoneBackends = None + self._dialpads = None + self._accountViews = None + self._messagesViews = 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): + self._widgetTree = gtk.glade.XML(path) + break + else: + 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") + errorBox = self._widgetTree.get_widget("errorEventBox") + errorDescription = self._widgetTree.get_widget("errorDescription") + errorClose = self._widgetTree.get_widget("errorClose") + self._errorDisplay = gtk_toolbox.ErrorDisplay(errorBox, errorDescription, errorClose) + self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree) + self._smsEntryWindow = None + + self._isFullScreen = False + self.__isPortrait = False + 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", + "callbackSelectButton", + "minutesEntryButton", + "clearcookies", + "phoneTypeSelection", + ): + button = self._widgetTree.get_widget(buttonName) + assert button is not None, buttonName + hildonize.set_button_thumb_selectable(button) + + 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("Refresh") + button.connect("clicked", self._on_menu_refresh) + menu.append(button) + + menu.show_all() + + 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") + - hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__) ++ hildonize.set_application_name("%s" % constants.__pretty_app_name__) + + self._window.connect("destroy", self._on_close) + self._window.set_default_size(800, 300) + self._window.show_all() + + self._loginSink = gtk_toolbox.threaded_stage( + gtk_toolbox.comap( + self._attempt_login, + gtk_toolbox.null_sink(), + ) + ) + + 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: + 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._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._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 + try: + try: + import osso + 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) + self._deviceState = osso.DeviceState(self._osso) + self._deviceState.set_device_state_callback(self._on_device_state_change, 0) + else: + _moduleLogger.warning("No device state support") + + try: + import alarm_handler + 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 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 + + try: + import conic + except (ImportError, OSError): + conic = None + self._connection = None + if conic is not None: + self._connection = conic.Connection() + self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__) + self._connection.request_connection(conic.CONNECT_FLAG_NONE) + else: + _moduleLogger.warning("No connection support") + except Exception, e: + with gtk_toolbox.gtk_lock(): + self._errorDisplay.push_exception() + + # Setup costly backends + try: + from backends import gv_backend + from backends import file_backend + import gv_views + from backends import merge_backend + + with gtk_toolbox.gtk_lock(): + self._smsEntryWindow = gv_views.SmsEntryWindow(self._widgetTree, self._window, self._app) + try: + os.makedirs(constants._data_path_) + except OSError, e: + if e.errno != 17: + raise + gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt") + + self._phoneBackends.update({ + self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath), + }) + with gtk_toolbox.gtk_lock(): + unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay) + self._dialpads.update({ + self.GV_BACKEND: unifiedDialpad, + }) + self._accountViews.update({ + self.GV_BACKEND: gv_views.AccountInfo( + self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay + ), + }) + self._accountViews[self.GV_BACKEND].save_everything = self._save_settings + self._historyViews.update({ + self.GV_BACKEND: gv_views.CallHistoryView( + self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay + ), + }) + self._messagesViews.update({ + self.GV_BACKEND: gv_views.MessagesView( + self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay + ), + }) + self._contactsViews.update({ + self.GV_BACKEND: gv_views.ContactsView( + self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay + ), + }) + + fileBackend = file_backend.FilesystemAddressBookFactory(self._fsContactsPath) + + self._smsEntryWindow.send_sms = self._on_sms_clicked + self._smsEntryWindow.dial = self._on_dial_clicked + self._dialpads[self.GV_BACKEND].add_contact = self._add_contact + self._dialpads[self.GV_BACKEND].dial = self._on_dial_clicked + self._historyViews[self.GV_BACKEND].add_contact = self._add_contact + self._messagesViews[self.GV_BACKEND].add_contact = self._add_contact + self._contactsViews[self.GV_BACKEND].add_contact = self._add_contact + + 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_about_activate": self._on_about_activate, + } + 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 = [ + self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text() + for pageIndex in xrange(self._notebook.get_n_pages()) + ] + self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook) + self._notebookTapHandler.enable() + self._notebookTapHandler.on_tap = self._reset_tab_refresh + self._notebookTapHandler.on_hold = self._on_tab_refresh + self._notebookTapHandler.on_holding = self._set_tab_refresh + self._notebookTapHandler.on_cancel = self._reset_tab_refresh + + config = ConfigParser.SafeConfigParser() + config.read(constants._user_settings_) + with gtk_toolbox.gtk_lock(): + self.load_settings(config) + except Exception, e: + with gtk_toolbox.gtk_lock(): + self._errorDisplay.push_exception() + finally: + self._initDone = True + self._spawn_attempt_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 self._initDone, "Attempting login before app is fully loaded" + + serviceId = self.NULL_BACKEND + loggedIn = False + 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 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() + + with gtk_toolbox.gtk_lock(): + self._change_loggedin_status(serviceId) + 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 refresh_session(self): + """ + @note Thread agnostic + """ + assert self._initDone, "Attempting login before app is fully loaded" + + loggedIn = False + if not loggedIn: + loggedIn = self._login_by_cookie() + if not loggedIn: + loggedIn = self._login_by_settings() + + if not loggedIn: + raise RuntimeError("Login Failed") + + def _login_by_cookie(self): + """ + @note Thread agnostic + """ + 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: + _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 + _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId]) + return loggedIn + + def _login_by_user(self): + """ + @note This must be run outside of the UI lock + """ + loggedIn, (username, password) = False, self._credentials + tmpServiceId = self.GV_BACKEND + while not loggedIn: + with gtk_toolbox.gtk_lock(): + credentials = self._credentialsDialog.request_credentials( + defaultCredentials = self._credentials + ) + 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 + _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 _add_contact(self, *args, **kwds): + self._smsEntryWindow.add_contact(*args, **kwds) + + def _change_loggedin_status(self, newStatus): + oldStatus = self._selectedBackendId + if oldStatus == newStatus: + return + + _moduleLogger.debug("Changing from %s to %s" % (oldStatus, newStatus)) + self._dialpads[oldStatus].disable() + self._accountViews[oldStatus].disable() + self._historyViews[oldStatus].disable() + self._messagesViews[oldStatus].disable() + self._contactsViews[oldStatus].disable() + + self._dialpads[newStatus].enable() + self._accountViews[newStatus].enable() + self._historyViews[newStatus].enable() + self._messagesViews[newStatus].enable() + self._contactsViews[newStatus].enable() + + self._selectedBackendId = newStatus + + self._accountViews[self._selectedBackendId].update() + self._refresh_active_tab() + self._refresh_orientation() + + def load_settings(self, config): + """ + @note UI Thread + """ + try: + 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)) + ) + creds = ( + base64.b64decode(blob) + for blob in blobs + ) + self._credentials = tuple(creds) + + 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() + + isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait") + if isPortrait ^ self.__isPortrait: + if isPortrait: + orientation = gtk.ORIENTATION_VERTICAL + else: + orientation = gtk.ORIENTATION_HORIZONTAL + self.set_orientation(orientation) + 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, + ), + ) + + for backendId, view in itertools.chain( + self._dialpads.iteritems(), + self._accountViews.iteritems(), + self._messagesViews.iteritems(), + self._historyViews.iteritems(), + self._contactsViews.iteritems(), + ): + sectionName = "%s - %s" % (backendId, view.name()) + try: + view.load_settings(config, sectionName) + 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(backend)) + config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait)) + 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) + config.add_section("alarm") + if self._alarmHandler is not None: + self._alarmHandler.save_settings(config, "alarm") + + for backendId, view in itertools.chain( + self._dialpads.iteritems(), + self._accountViews.iteritems(), + self._messagesViews.iteritems(), + self._historyViews.iteritems(), + self._contactsViews.iteritems(), + ): + sectionName = "%s - %s" % (backendId, view.name()) + config.add_section(sectionName) + view.save_settings(config, sectionName) + + def _save_settings(self): + """ + @note Thread Agnostic + """ + config = ConfigParser.SafeConfigParser() + self.save_settings(config) + with open(constants._user_settings_, "wb") as configFile: + config.write(configFile) + + def _refresh_active_tab(self): + pageIndex = self._notebook.get_current_page() + if pageIndex == self.CONTACTS_TAB: + self._contactsViews[self._selectedBackendId].update(force=True) + elif pageIndex == self.RECENT_TAB: + self._historyViews[self._selectedBackendId].update(force=True) + elif pageIndex == self.MESSAGES_TAB: + self._messagesViews[self._selectedBackendId].update(force=True) + + if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB): + if self._ledHandler is not None: + self._ledHandler.off() + + def set_orientation(self, orientation): + if orientation == gtk.ORIENTATION_VERTICAL: + hildonize.window_to_portrait(self._window) + self._notebook.set_property("tab-pos", gtk.POS_BOTTOM) + self.__isPortrait = True + elif orientation == gtk.ORIENTATION_HORIZONTAL: + hildonize.window_to_landscape(self._window) + self._notebook.set_property("tab-pos", gtk.POS_LEFT) + self.__isPortrait = False + else: + raise NotImplementedError(orientation) + + def get_orientation(self): + return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL + + def _toggle_rotate(self): + if self.__isPortrait: + self.set_orientation(gtk.ORIENTATION_HORIZONTAL) + else: + self.set_orientation(gtk.ORIENTATION_VERTICAL) + + def _refresh_orientation(self): + """ + Mostly meant to be used when switching backends + """ + if self.__isPortrait: + self.set_orientation(gtk.ORIENTATION_VERTICAL) + else: + self.set_orientation(gtk.ORIENTATION_HORIZONTAL) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_close(self, *args, **kwds): + try: + 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() + + 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. + For system_inactivity, we have no background tasks to pause + + @note Hildon specific + """ + 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() + except Exception, e: + self._errorDisplay.push_exception() + + def _on_connection_change(self, connection, event, magicIdentifier): + """ + @note Hildon specific + """ + 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 + """ + 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 + """ + 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.o and event.get_state() & gtk.gdk.CONTROL_MASK: + self._toggle_rotate() + return True + elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK: + self._refresh_active_tab() + elif event.keyval == gtk.keysyms.i and event.get_state() & gtk.gdk.CONTROL_MASK: + self._import_contacts() + except Exception, e: + self._errorDisplay.push_exception() + + def _on_clearcookies_clicked(self, *args): + 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): + 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): + 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): + 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): + try: + self._refresh_active_tab() + self._reset_tab_refresh() + except Exception, e: + self._errorDisplay.push_exception() + return False + + def _on_sms_clicked(self, numbers, message): + try: + assert numbers, "No number specified" + assert message, "Empty message" + self.refresh_session() + 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 GoogleVoice is not working, please try again" + ) + return + + 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() + except Exception, e: + self._errorDisplay.push_exception() + + def _on_dial_clicked(self, number): + try: + assert number, "No number to call" + self.refresh_session() + 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 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].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() + + def _import_contacts(self): + 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) + + def _on_menu_refresh(self, *args): + try: + self._refresh_active_tab() + except Exception, e: + self._errorDisplay.push_exception() + + def _on_paste(self, *args): + 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): + 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(): + import doctest + + failureCount, testCount = doctest.testmod() + if not failureCount: + print "Tests Successful" + sys.exit(0) + else: + sys.exit(1) + + +def run_dialpad(): + gtk.gdk.threads_init() + - if hildonize.IS_HILDON_SUPPORTED: - gtk.set_application_name(constants.__pretty_app_name__) + handle = Dialcentral() + if not PROFILE_STARTUP: + gtk.main() + + +class DummyOptions(object): + + def __init__(self): + self.test = False + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + try: + 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 = [] + + if commandOptions.test: + run_doctest() + else: + run_dialpad() + finally: + logging.shutdown() diff --cc support/builddeb.py index 6805808,d12305a..eb99ebc --- a/support/builddeb.py +++ b/support/builddeb.py @@@ -37,13 -29,10 +37,13 @@@ __changelog__ = "" __postinstall__ = """#!/bin/sh -e gtk-update-icon-cache -f /usr/share/icons/hicolor - rm -f ~/.dialcentral/notifier.log - rm -f ~/.dialcentral/dialcentral.log - """ + rm -f ~/.%(name)s/%(name)s.log ++rm -f ~/.%(name)s/notifier.log + """ % {"name": constants.__app_name__} __preremove__ = """#!/bin/sh -e + +python /usr/lib/dialcentral/alarm_handler.py -d || true """ @@@ -77,8 -66,7 +77,7 @@@ def build_package(distribution) p = py2deb.Py2deb(__appname__) p.prettyName = constants.__pretty_app_name__ p.description = __description__ - p.bugTracker = "REPLACEME" + p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=Dialcentral" - p.upgradeDescription = __changelog__.split("\n\n", 1)[0] p.author = __author__ p.mail = __email__ p.license = "lgpl" @@@ -109,13 -97,13 +108,13 @@@ p.postinstall = __postinstall__ p.preremove = __preremove__ p.icon = { - "debian": "REPLACEME", - "diablo": "REPLACEME", - "fremantle": "REPLACEME", # Fremantle natively uses 48x48 + "debian": "26x26-dialcentral.png", + "diablo": "26x26-dialcentral.png", + "fremantle": "64x64-dialcentral.png", # Fremantle natively uses 48x48 }[distribution] - p["/usr/bin"] = [ "dialcentral.py" ] - for relPath, files in unflatten_files(find_files(".")).iteritems(): - fullPath = "/usr/lib/dialcentral" - p["/opt/%s/bin" % constants.__appname__] = [ "%s.py" % constants.__appname__ ] ++ p["/opt/%s/bin" % __appname__] = [ "%s.py" % __appname__ ] + for relPath, files in unflatten_files(find_files("src", ".")).iteritems(): - fullPath = "/opt/%s/lib" % constants.__appname__ ++ fullPath = "/opt/%s/lib" % __appname__ if relPath: fullPath += os.sep+relPath p[fullPath] = list( diff --cc support/dialcentral.desktop index ce28c88,0000000..c5ec2f3 mode 100644,000000..100644 --- a/support/dialcentral.desktop +++ b/support/dialcentral.desktop @@@ -1,7 -1,0 +1,7 @@@ +[Desktop Entry] +Encoding=UTF-8 +Version=1.0 +Type=Application +Name=DialCentral - Exec=/usr/bin/run-standalone.sh /usr/bin/dialcentral.py ++Exec=/usr/bin/run-standalone.sh /opt/dialcentral/bin/dialcentral.py +Icon=dialcentral