4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 @todo Add "login failed" and "attempting login" notifications
25 from __future__ import with_statement
44 _moduleLogger = logging.getLogger("dc_glade")
47 def getmtime_nothrow(path):
49 return os.path.getmtime(path)
54 def display_error_message(msg):
55 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
57 def close(dialog, response):
59 error_dialog.connect("response", close)
63 class Dialcentral(object):
66 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
67 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
68 '/usr/lib/dialcentral/dialcentral.glade',
79 BACKENDS = (NULL_BACKEND, GV_BACKEND)
82 self._initDone = False
83 self._connection = None
85 self._clipboard = gtk.clipboard_get()
87 self._credentials = ("", "")
88 self._selectedBackendId = self.NULL_BACKEND
89 self._defaultBackendId = self.GV_BACKEND
90 self._phoneBackends = None
92 self._accountViews = None
93 self._messagesViews = None
94 self._recentViews = None
95 self._contactsViews = None
96 self._alarmHandler = None
97 self._ledHandler = None
98 self._originalCurrentLabels = []
100 for path in self._glade_files:
101 if os.path.isfile(path):
102 self._widgetTree = gtk.glade.XML(path)
105 display_error_message("Cannot find dialcentral.glade")
109 self._window = self._widgetTree.get_widget("mainWindow")
110 self._notebook = self._widgetTree.get_widget("notebook")
111 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
112 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
114 self._isFullScreen = False
115 self._app = hildonize.get_app_class()()
116 self._window = hildonize.hildonize_window(self._app, self._window)
117 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
118 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
120 for scrollingWidget in (
121 'recent_scrolledwindow',
122 'message_scrolledwindow',
123 'contacts_scrolledwindow',
124 "phoneSelectionMessages_scrolledwindow",
125 "smsMessages_scrolledwindow",
127 hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
128 for scrollingWidget in (
129 "phonetypes_scrolledwindow",
130 "smsMessage_scrolledEntry",
132 hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
134 replacementButtons = [gtk.Button("Test")]
135 menu = hildonize.hildonize_menu(
137 self._widgetTree.get_widget("dialpad_menubar"),
141 self._window.connect("key-press-event", self._on_key_press)
142 self._window.connect("window-state-event", self._on_window_state_change)
143 if not hildonize.IS_HILDON_SUPPORTED:
144 _moduleLogger.warning("No hildonization support")
146 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
148 self._window.connect("destroy", self._on_close)
149 self._window.set_default_size(800, 300)
150 self._window.show_all()
152 self._loginSink = gtk_toolbox.threaded_stage(
155 gtk_toolbox.null_sink(),
159 backgroundSetup = threading.Thread(target=self._idle_setup)
160 backgroundSetup.setDaemon(True)
161 backgroundSetup.start()
163 def _idle_setup(self):
165 If something can be done after the UI loads, push it here so it's not blocking the UI
167 # Barebones UI handlers
172 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
173 with gtk_toolbox.gtk_lock():
174 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
175 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
176 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
177 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
178 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
180 self._dialpads[self._selectedBackendId].enable()
181 self._accountViews[self._selectedBackendId].enable()
182 self._recentViews[self._selectedBackendId].enable()
183 self._messagesViews[self._selectedBackendId].enable()
184 self._contactsViews[self._selectedBackendId].enable()
186 with gtk_toolbox.gtk_lock():
187 self._errorDisplay.push_exception()
189 # Setup maemo specifics
193 except (ImportError, OSError):
197 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
198 device = osso.DeviceState(self._osso)
199 device.set_device_state_callback(self._on_device_state_change, 0)
201 _moduleLogger.warning("No device state support")
205 self._alarmHandler = alarm_handler.AlarmHandler()
206 except (ImportError, OSError):
209 with gtk_toolbox.gtk_lock():
210 self._errorDisplay.push_exception()
212 _moduleLogger.warning("No notification support")
213 if hildonize.IS_HILDON_SUPPORTED:
216 self._ledHandler = led_handler.LedHandler()
218 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
219 self._ledHandler = None
221 self._ledHandler = None
225 except (ImportError, OSError):
227 self._connection = None
228 if conic is not None:
229 self._connection = conic.Connection()
230 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
231 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
233 _moduleLogger.warning("No connection support")
235 with gtk_toolbox.gtk_lock():
236 self._errorDisplay.push_exception()
238 # Setup costly backends
245 os.makedirs(constants._data_path_)
249 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
251 self._phoneBackends.update({
252 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
254 with gtk_toolbox.gtk_lock():
255 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
256 self._dialpads.update({
257 self.GV_BACKEND: unifiedDialpad,
259 self._accountViews.update({
260 self.GV_BACKEND: gv_views.AccountInfo(
261 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
264 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
265 self._recentViews.update({
266 self.GV_BACKEND: gv_views.RecentCallsView(
267 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
270 self._messagesViews.update({
271 self.GV_BACKEND: gv_views.MessagesView(
272 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
275 self._contactsViews.update({
276 self.GV_BACKEND: gv_views.ContactsView(
277 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
281 fsContactsPath = os.path.join(constants._data_path_, "contacts")
282 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
284 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
285 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
286 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
287 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
290 self._phoneBackends[self.GV_BACKEND],
293 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
294 self._contactsViews[self.GV_BACKEND].append(mergedBook)
295 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
296 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
299 "on_paste": self._on_paste,
300 "on_refresh": self._on_menu_refresh,
301 "on_clearcookies_clicked": self._on_clearcookies_clicked,
302 "on_about_activate": self._on_about_activate,
304 if hildonize.GTK_MENU_USED:
305 self._widgetTree.signal_autoconnect(callbackMapping)
306 self._notebook.connect("switch-page", self._on_notebook_switch_page)
307 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
309 with gtk_toolbox.gtk_lock():
310 self._originalCurrentLabels = [
311 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
312 for pageIndex in xrange(self._notebook.get_n_pages())
314 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
315 self._notebookTapHandler.enable()
316 self._notebookTapHandler.on_tap = self._reset_tab_refresh
317 self._notebookTapHandler.on_hold = self._on_tab_refresh
318 self._notebookTapHandler.on_holding = self._set_tab_refresh
319 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
321 self._initDone = True
323 config = ConfigParser.SafeConfigParser()
324 config.read(constants._user_settings_)
325 with gtk_toolbox.gtk_lock():
326 self.load_settings(config)
328 with gtk_toolbox.gtk_lock():
329 self._errorDisplay.push_exception()
331 self._spawn_attempt_login(2)
333 def _spawn_attempt_login(self, *args):
334 self._loginSink.send(args)
336 def _attempt_login(self, numOfAttempts = 10, force = False):
338 @note This must be run outside of the UI lock
341 assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
342 assert self._initDone, "Attempting login before app is fully loaded"
344 serviceId = self.NULL_BACKEND
348 self.refresh_session()
349 serviceId = self._defaultBackendId
352 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
355 loggedIn, serviceId = self._login_by_user(numOfAttempts)
357 with gtk_toolbox.gtk_lock():
358 self._change_loggedin_status(serviceId)
360 hildonize.show_information_banner(self._window, "Logged In")
362 with gtk_toolbox.gtk_lock():
363 self._errorDisplay.push_exception()
365 def refresh_session(self):
367 @note Thread agnostic
369 assert self._initDone, "Attempting login before app is fully loaded"
373 loggedIn = self._login_by_cookie()
375 loggedIn = self._login_by_settings()
378 raise RuntimeError("Login Failed")
380 def _login_by_cookie(self):
382 @note Thread agnostic
384 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
386 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
389 def _login_by_settings(self):
391 @note Thread agnostic
393 username, password = self._credentials
394 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
396 self._credentials = username, password
397 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
400 def _login_by_user(self, numOfAttempts):
402 @note This must be run outside of the UI lock
404 loggedIn, (username, password) = False, self._credentials
405 tmpServiceId = self.GV_BACKEND
406 for attemptCount in xrange(numOfAttempts):
409 with gtk_toolbox.gtk_lock():
410 credentials = self._credentialsDialog.request_credentials(
411 defaultCredentials = self._credentials
413 if not self._phoneBackends[tmpServiceId].get_callback_number():
414 # subtle reminder to the users to configure things
415 self._notebook.set_current_page(self.ACCOUNT_TAB)
416 username, password = credentials
417 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
420 serviceId = tmpServiceId
421 self._credentials = username, password
422 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
424 serviceId = self.NULL_BACKEND
425 self._notebook.set_current_page(self.ACCOUNT_TAB)
427 return loggedIn, serviceId
429 def _select_action(self, action, number, message):
430 self.refresh_session()
431 if action == "select":
432 self._dialpads[self._selectedBackendId].set_number(number)
433 self._notebook.set_current_page(self.KEYPAD_TAB)
434 elif action == "dial":
435 self._on_dial_clicked(number)
436 elif action == "sms":
437 self._on_sms_clicked(number, message)
439 assert False, "Unknown action: %s" % action
441 def _change_loggedin_status(self, newStatus):
442 oldStatus = self._selectedBackendId
443 if oldStatus == newStatus:
446 self._dialpads[oldStatus].disable()
447 self._accountViews[oldStatus].disable()
448 self._recentViews[oldStatus].disable()
449 self._messagesViews[oldStatus].disable()
450 self._contactsViews[oldStatus].disable()
452 self._dialpads[newStatus].enable()
453 self._accountViews[newStatus].enable()
454 self._recentViews[newStatus].enable()
455 self._messagesViews[newStatus].enable()
456 self._contactsViews[newStatus].enable()
458 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
459 self._phoneBackends[self._selectedBackendId].set_sane_callback()
461 self._selectedBackendId = newStatus
463 self._accountViews[self._selectedBackendId].update()
464 self._refresh_active_tab()
466 def load_settings(self, config):
471 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
473 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
474 for i in xrange(len(self._credentials))
477 base64.b64decode(blob)
480 self._credentials = tuple(creds)
482 if self._alarmHandler is not None:
483 self._alarmHandler.load_settings(config, "alarm")
484 except ConfigParser.NoOptionError, e:
485 _moduleLogger.exception(
486 "Settings file %s is missing section %s" % (
487 constants._user_settings_,
491 except ConfigParser.NoSectionError, e:
492 _moduleLogger.exception(
493 "Settings file %s is missing section %s" % (
494 constants._user_settings_,
499 for backendId, view in itertools.chain(
500 self._dialpads.iteritems(),
501 self._accountViews.iteritems(),
502 self._messagesViews.iteritems(),
503 self._recentViews.iteritems(),
504 self._contactsViews.iteritems(),
506 sectionName = "%s - %s" % (backendId, view.name())
508 view.load_settings(config, sectionName)
509 except ConfigParser.NoOptionError, e:
510 _moduleLogger.exception(
511 "Settings file %s is missing section %s" % (
512 constants._user_settings_,
516 except ConfigParser.NoSectionError, e:
517 _moduleLogger.exception(
518 "Settings file %s is missing section %s" % (
519 constants._user_settings_,
525 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
526 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
527 hildonize.window_to_landscape(self._window)
528 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
529 hildonize.window_to_portrait(self._window)
530 except ConfigParser.NoOptionError, e:
531 _moduleLogger.exception(
532 "Settings file %s is missing section %s" % (
533 constants._user_settings_,
537 except ConfigParser.NoSectionError, e:
538 _moduleLogger.exception(
539 "Settings file %s is missing section %s" % (
540 constants._user_settings_,
545 def save_settings(self, config):
547 @note Thread Agnostic
549 config.add_section(constants.__pretty_app_name__)
550 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
551 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
552 for i, value in enumerate(self._credentials):
553 blob = base64.b64encode(value)
554 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
555 config.add_section("alarm")
556 if self._alarmHandler is not None:
557 self._alarmHandler.save_settings(config, "alarm")
559 for backendId, view in itertools.chain(
560 self._dialpads.iteritems(),
561 self._accountViews.iteritems(),
562 self._messagesViews.iteritems(),
563 self._recentViews.iteritems(),
564 self._contactsViews.iteritems(),
566 sectionName = "%s - %s" % (backendId, view.name())
567 config.add_section(sectionName)
568 view.save_settings(config, sectionName)
570 def _save_settings(self):
572 @note Thread Agnostic
574 config = ConfigParser.SafeConfigParser()
575 self.save_settings(config)
576 with open(constants._user_settings_, "wb") as configFile:
577 config.write(configFile)
579 def _refresh_active_tab(self):
580 pageIndex = self._notebook.get_current_page()
581 if pageIndex == self.CONTACTS_TAB:
582 self._contactsViews[self._selectedBackendId].update(force=True)
583 elif pageIndex == self.RECENT_TAB:
584 self._recentViews[self._selectedBackendId].update(force=True)
585 elif pageIndex == self.MESSAGES_TAB:
586 self._messagesViews[self._selectedBackendId].update(force=True)
588 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
589 if self._ledHandler is not None:
590 self._ledHandler.off()
592 def _on_close(self, *args, **kwds):
594 if self._osso is not None:
598 self._save_settings()
602 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
604 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
605 For system_inactivity, we have no background tasks to pause
607 @note Hildon specific
611 for backendId in self.BACKENDS:
612 self._phoneBackends[backendId].clear_caches()
613 self._contactsViews[self._selectedBackendId].clear_caches()
616 if save_unsaved_data or shutdown:
617 self._save_settings()
619 self._errorDisplay.push_exception()
621 def _on_connection_change(self, connection, event, magicIdentifier):
623 @note Hildon specific
628 status = event.get_status()
629 error = event.get_error()
630 iap_id = event.get_iap_id()
631 bearer = event.get_bearer_type()
633 if status == conic.STATUS_CONNECTED:
635 self._spawn_attempt_login(2)
636 elif status == conic.STATUS_DISCONNECTED:
638 self._defaultBackendId = self._selectedBackendId
639 self._change_loggedin_status(self.NULL_BACKEND)
641 self._errorDisplay.push_exception()
643 def _on_window_state_change(self, widget, event, *args):
645 @note Hildon specific
648 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
649 self._isFullScreen = True
651 self._isFullScreen = False
653 self._errorDisplay.push_exception()
655 def _on_key_press(self, widget, event, *args):
657 @note Hildon specific
661 event.keyval == gtk.keysyms.F6 or
662 event.keyval == gtk.keysyms.Return and event.get_state() & gtk.gdk.CONTROL_MASK
664 if self._isFullScreen:
665 self._window.unfullscreen()
667 self._window.fullscreen()
669 self._errorDisplay.push_exception()
671 def _on_clearcookies_clicked(self, *args):
673 self._phoneBackends[self._selectedBackendId].logout()
674 self._accountViews[self._selectedBackendId].clear()
675 self._recentViews[self._selectedBackendId].clear()
676 self._messagesViews[self._selectedBackendId].clear()
677 self._contactsViews[self._selectedBackendId].clear()
678 self._change_loggedin_status(self.NULL_BACKEND)
680 self._spawn_attempt_login(2, True)
682 self._errorDisplay.push_exception()
684 def _on_notebook_switch_page(self, notebook, page, pageIndex):
686 self._reset_tab_refresh()
688 didRecentUpdate = False
689 didMessagesUpdate = False
691 if pageIndex == self.RECENT_TAB:
692 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
693 elif pageIndex == self.MESSAGES_TAB:
694 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
695 elif pageIndex == self.CONTACTS_TAB:
696 self._contactsViews[self._selectedBackendId].update()
697 elif pageIndex == self.ACCOUNT_TAB:
698 self._accountViews[self._selectedBackendId].update()
700 if didRecentUpdate or didMessagesUpdate:
701 if self._ledHandler is not None:
702 self._ledHandler.off()
704 self._errorDisplay.push_exception()
706 def _set_tab_refresh(self, *args):
708 pageIndex = self._notebook.get_current_page()
709 child = self._notebook.get_nth_page(pageIndex)
710 self._notebook.get_tab_label(child).set_text("Refresh?")
712 self._errorDisplay.push_exception()
715 def _reset_tab_refresh(self, *args):
717 pageIndex = self._notebook.get_current_page()
718 child = self._notebook.get_nth_page(pageIndex)
719 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
721 self._errorDisplay.push_exception()
724 def _on_tab_refresh(self, *args):
726 self._refresh_active_tab()
727 self._reset_tab_refresh()
729 self._errorDisplay.push_exception()
732 def _on_sms_clicked(self, number, message):
734 assert number, "No number specified"
735 assert message, "Empty message"
737 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
740 self._errorDisplay.push_exception()
744 self._errorDisplay.push_message(
745 "Backend link with GoogleVoice is not working, please try again"
751 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
752 hildonize.show_information_banner(self._window, "Sending to %s" % number)
755 self._errorDisplay.push_exception()
758 self._dialpads[self._selectedBackendId].clear()
760 self._errorDisplay.push_exception()
762 def _on_dial_clicked(self, number):
764 assert number, "No number to call"
766 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
769 self._errorDisplay.push_exception()
773 self._errorDisplay.push_message(
774 "Backend link with GoogleVoice is not working, please try again"
780 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
781 self._phoneBackends[self._selectedBackendId].dial(number)
782 hildonize.show_information_banner(self._window, "Calling %s" % number)
785 self._errorDisplay.push_exception()
788 self._dialpads[self._selectedBackendId].clear()
790 self._errorDisplay.push_exception()
792 def _on_menu_refresh(self, *args):
794 self._refresh_active_tab()
796 self._errorDisplay.push_exception()
798 def _on_paste(self, *args):
800 contents = self._clipboard.wait_for_text()
801 if contents is not None:
802 self._dialpads[self._selectedBackendId].set_number(contents)
804 self._errorDisplay.push_exception()
806 def _on_about_activate(self, *args):
808 dlg = gtk.AboutDialog()
809 dlg.set_name(constants.__pretty_app_name__)
810 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
811 dlg.set_copyright("Copyright 2008 - LGPL")
812 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
813 dlg.set_website("http://gc-dialer.garage.maemo.org/")
814 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
818 self._errorDisplay.push_exception()
824 failureCount, testCount = doctest.testmod()
826 print "Tests Successful"
833 _lock_file = os.path.join(constants._data_path_, ".lock")
834 #with gtk_toolbox.flock(_lock_file, 0):
835 gtk.gdk.threads_init()
837 if hildonize.IS_HILDON_SUPPORTED:
838 gtk.set_application_name(constants.__pretty_app_name__)
839 handle = Dialcentral()
843 class DummyOptions(object):
849 if __name__ == "__main__":
850 logging.basicConfig(level=logging.DEBUG)
852 if len(sys.argv) > 1:
858 if optparse is not None:
859 parser = optparse.OptionParser()
860 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
861 (commandOptions, commandArgs) = parser.parse_args()
863 commandOptions = DummyOptions()
866 if commandOptions.test: