4 DialCentral - Front end for Google's Grand Central 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 @bug When switching to tab while logging in, it doesn't refresh once logged int
25 from __future__ import with_statement
44 def getmtime_nothrow(path):
46 return os.path.getmtime(path)
51 def display_error_message(msg):
52 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
54 def close(dialog, response):
56 error_dialog.connect("response", close)
60 class Dialcentral(object):
63 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
64 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
65 '/usr/lib/dialcentral/dialcentral.glade',
76 BACKENDS = (NULL_BACKEND, GV_BACKEND)
79 self._initDone = False
80 self._connection = None
82 self._clipboard = gtk.clipboard_get()
84 self._credentials = ("", "")
85 self._selectedBackendId = self.NULL_BACKEND
86 self._defaultBackendId = self.GV_BACKEND
87 self._phoneBackends = None
89 self._accountViews = None
90 self._messagesViews = None
91 self._recentViews = None
92 self._contactsViews = None
93 self._alarmHandler = None
94 self._ledHandler = None
95 self._originalCurrentLabels = []
97 for path in self._glade_files:
98 if os.path.isfile(path):
99 self._widgetTree = gtk.glade.XML(path)
102 display_error_message("Cannot find dialcentral.glade")
106 self._window = self._widgetTree.get_widget("mainWindow")
107 self._notebook = self._widgetTree.get_widget("notebook")
108 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
109 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
111 self._isFullScreen = False
112 self._app = hildonize.get_app_class()()
113 self._window = hildonize.hildonize_window(self._app, self._window)
114 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
115 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
116 hildonize.hildonize_combo_entry(self._widgetTree.get_widget("callbackcombo").get_child())
118 for scrollingWidget in (
119 'recent_scrolledwindow',
120 'message_scrolledwindow',
121 'contacts_scrolledwindow',
122 "phoneSelectionMessages_scrolledwindow",
123 "smsMessages_scrolledwindow",
125 hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
126 for scrollingWidget in (
127 "phonetypes_scrolledwindow",
128 "smsMessage_scrolledEntry",
130 hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
132 replacementButtons = [gtk.Button("Test")]
133 menu = hildonize.hildonize_menu(
135 self._widgetTree.get_widget("dialpad_menubar"),
139 if hildonize.IS_HILDON_SUPPORTED:
140 self._window.connect("key-press-event", self._on_key_press)
141 self._window.connect("window-state-event", self._on_window_state_change)
143 logging.warning("No hildonization support")
145 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
147 self._window.connect("destroy", self._on_close)
148 self._window.set_default_size(800, 300)
149 self._window.show_all()
151 self._loginSink = gtk_toolbox.threaded_stage(
154 gtk_toolbox.null_sink(),
158 backgroundSetup = threading.Thread(target=self._idle_setup)
159 backgroundSetup.setDaemon(True)
160 backgroundSetup.start()
162 def _idle_setup(self):
164 If something can be done after the UI loads, push it here so it's not blocking the UI
166 # Barebones UI handlers
171 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
172 with gtk_toolbox.gtk_lock():
173 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
174 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
175 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
176 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
177 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
179 self._dialpads[self._selectedBackendId].enable()
180 self._accountViews[self._selectedBackendId].enable()
181 self._recentViews[self._selectedBackendId].enable()
182 self._messagesViews[self._selectedBackendId].enable()
183 self._contactsViews[self._selectedBackendId].enable()
185 with gtk_toolbox.gtk_lock():
186 self._errorDisplay.push_exception()
188 # Setup maemo specifics
192 except (ImportError, OSError):
196 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
197 device = osso.DeviceState(self._osso)
198 device.set_device_state_callback(self._on_device_state_change, 0)
200 logging.warning("No device state support")
204 self._alarmHandler = alarm_handler.AlarmHandler()
205 except (ImportError, OSError):
208 with gtk_toolbox.gtk_lock():
209 self._errorDisplay.push_exception()
211 logging.warning("No notification support")
212 if hildonize.IS_HILDON_SUPPORTED:
215 self._ledHandler = led_handler.LedHandler()
217 logging.exception('LED Handling failed: "%s"' % str(e))
218 self._ledHandler = None
220 self._ledHandler = None
224 except (ImportError, OSError):
226 self._connection = None
227 if conic is not None:
228 self._connection = conic.Connection()
229 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
230 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
232 logging.warning("No connection support")
234 with gtk_toolbox.gtk_lock():
235 self._errorDisplay.push_exception()
237 # Setup costly backends
244 os.makedirs(constants._data_path_)
248 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
250 self._phoneBackends.update({
251 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
253 with gtk_toolbox.gtk_lock():
254 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
255 self._dialpads.update({
256 self.GV_BACKEND: unifiedDialpad,
258 self._accountViews.update({
259 self.GV_BACKEND: gv_views.AccountInfo(
260 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
263 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
264 self._recentViews.update({
265 self.GV_BACKEND: gv_views.RecentCallsView(
266 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
269 self._messagesViews.update({
270 self.GV_BACKEND: gv_views.MessagesView(
271 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
274 self._contactsViews.update({
275 self.GV_BACKEND: gv_views.ContactsView(
276 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
280 fsContactsPath = os.path.join(constants._data_path_, "contacts")
281 fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
283 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
284 self._recentViews[self.GV_BACKEND].number_selected = self._select_action
285 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
286 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
289 self._phoneBackends[self.GV_BACKEND],
292 mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
293 self._contactsViews[self.GV_BACKEND].append(mergedBook)
294 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
295 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
298 "on_paste": self._on_paste,
299 "on_refresh": self._on_menu_refresh,
300 "on_rotate": self._on_menu_rotate,
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 attempt_login(self, numOfAttempts = 10, force = False):
335 @todo Handle user notification better like attempting to login and failed login
337 @note This must be run outside of the UI lock
340 assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
341 assert self._initDone, "Attempting login before app is fully loaded"
343 serviceId = self.NULL_BACKEND
347 self.refresh_session()
348 serviceId = self._defaultBackendId
351 logging.exception('Session refresh failed with the following message "%s"' % str(e))
354 loggedIn, serviceId = self._login_by_user(numOfAttempts)
356 with gtk_toolbox.gtk_lock():
357 self._change_loggedin_status(serviceId)
359 with gtk_toolbox.gtk_lock():
360 self._errorDisplay.push_exception()
362 def _spawn_attempt_login(self, *args):
363 self._loginSink.send(args)
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 logging.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 logging.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 logging.info("Logged into %r through user request" % self._phoneBackends[serviceId])
424 serviceId = self.NULL_BACKEND
426 return loggedIn, serviceId
428 def _select_action(self, action, number, message):
429 self.refresh_session()
430 if action == "select":
431 self._dialpads[self._selectedBackendId].set_number(number)
432 self._notebook.set_current_page(self.KEYPAD_TAB)
433 elif action == "dial":
434 self._on_dial_clicked(number)
435 elif action == "sms":
436 self._on_sms_clicked(number, message)
438 assert False, "Unknown action: %s" % action
440 def _change_loggedin_status(self, newStatus):
441 oldStatus = self._selectedBackendId
442 if oldStatus == newStatus:
445 self._dialpads[oldStatus].disable()
446 self._accountViews[oldStatus].disable()
447 self._recentViews[oldStatus].disable()
448 self._messagesViews[oldStatus].disable()
449 self._contactsViews[oldStatus].disable()
451 self._dialpads[newStatus].enable()
452 self._accountViews[newStatus].enable()
453 self._recentViews[newStatus].enable()
454 self._messagesViews[newStatus].enable()
455 self._contactsViews[newStatus].enable()
457 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
458 self._phoneBackends[self._selectedBackendId].set_sane_callback()
459 self._accountViews[self._selectedBackendId].update()
460 self._refresh_active_tab()
462 self._selectedBackendId = newStatus
464 def load_settings(self, config):
469 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
471 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
472 for i in xrange(len(self._credentials))
475 base64.b64decode(blob)
478 self._credentials = tuple(creds)
480 if self._alarmHandler is not None:
481 self._alarmHandler.load_settings(config, "alarm")
482 except ConfigParser.NoOptionError, e:
484 "Settings file %s is missing section %s" % (
485 constants._user_settings_,
489 except ConfigParser.NoSectionError, e:
491 "Settings file %s is missing section %s" % (
492 constants._user_settings_,
497 for backendId, view in itertools.chain(
498 self._dialpads.iteritems(),
499 self._accountViews.iteritems(),
500 self._messagesViews.iteritems(),
501 self._recentViews.iteritems(),
502 self._contactsViews.iteritems(),
504 sectionName = "%s - %s" % (backendId, view.name())
506 view.load_settings(config, sectionName)
507 except ConfigParser.NoOptionError, e:
509 "Settings file %s is missing section %s" % (
510 constants._user_settings_,
514 except ConfigParser.NoSectionError, e:
516 "Settings file %s is missing section %s" % (
517 constants._user_settings_,
523 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
524 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
525 hildonize.window_to_landscape(self._window)
526 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
527 hildonize.window_to_portrait(self._window)
528 except ConfigParser.NoOptionError, e:
530 "Settings file %s is missing section %s" % (
531 constants._user_settings_,
535 except ConfigParser.NoSectionError, e:
537 "Settings file %s is missing section %s" % (
538 constants._user_settings_,
543 def save_settings(self, config):
545 @note Thread Agnostic
547 config.add_section(constants.__pretty_app_name__)
548 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
549 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
550 for i, value in enumerate(self._credentials):
551 blob = base64.b64encode(value)
552 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
553 config.add_section("alarm")
554 if self._alarmHandler is not None:
555 self._alarmHandler.save_settings(config, "alarm")
557 for backendId, view in itertools.chain(
558 self._dialpads.iteritems(),
559 self._accountViews.iteritems(),
560 self._messagesViews.iteritems(),
561 self._recentViews.iteritems(),
562 self._contactsViews.iteritems(),
564 sectionName = "%s - %s" % (backendId, view.name())
565 config.add_section(sectionName)
566 view.save_settings(config, sectionName)
568 def _save_settings(self):
570 @note Thread Agnostic
572 config = ConfigParser.SafeConfigParser()
573 self.save_settings(config)
574 with open(constants._user_settings_, "wb") as configFile:
575 config.write(configFile)
577 def _refresh_active_tab(self):
578 pageIndex = self._notebook.get_current_page()
579 if pageIndex == self.CONTACTS_TAB:
580 self._contactsViews[self._selectedBackendId].update(force=True)
581 elif pageIndex == self.RECENT_TAB:
582 self._recentViews[self._selectedBackendId].update(force=True)
583 elif pageIndex == self.MESSAGES_TAB:
584 self._messagesViews[self._selectedBackendId].update(force=True)
586 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
587 if self._ledHandler is not None:
588 self._ledHandler.off()
590 def _on_close(self, *args, **kwds):
592 if self._osso is not None:
596 self._save_settings()
600 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
602 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
603 For system_inactivity, we have no background tasks to pause
605 @note Hildon specific
609 for backendId in self.BACKENDS:
610 self._phoneBackends[backendId].clear_caches()
611 self._contactsViews[self._selectedBackendId].clear_caches()
614 if save_unsaved_data or shutdown:
615 self._save_settings()
617 self._errorDisplay.push_exception()
619 def _on_connection_change(self, connection, event, magicIdentifier):
621 @note Hildon specific
626 status = event.get_status()
627 error = event.get_error()
628 iap_id = event.get_iap_id()
629 bearer = event.get_bearer_type()
631 if status == conic.STATUS_CONNECTED:
633 self._spawn_attempt_login(2)
634 elif status == conic.STATUS_DISCONNECTED:
636 self._defaultBackendId = self._selectedBackendId
637 self._change_loggedin_status(self.NULL_BACKEND)
639 self._errorDisplay.push_exception()
641 def _on_window_state_change(self, widget, event, *args):
643 @note Hildon specific
646 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
647 self._isFullScreen = True
649 self._isFullScreen = False
651 self._errorDisplay.push_exception()
653 def _on_key_press(self, widget, event, *args):
655 @note Hildon specific
658 if event.keyval == gtk.keysyms.F6:
659 if self._isFullScreen:
660 self._window.unfullscreen()
662 self._window.fullscreen()
664 self._errorDisplay.push_exception()
666 def _on_clearcookies_clicked(self, *args):
668 self._phoneBackends[self._selectedBackendId].logout()
669 self._accountViews[self._selectedBackendId].clear()
670 self._recentViews[self._selectedBackendId].clear()
671 self._messagesViews[self._selectedBackendId].clear()
672 self._contactsViews[self._selectedBackendId].clear()
673 self._change_loggedin_status(self.NULL_BACKEND)
675 self._spawn_attempt_login(2, True)
677 self._errorDisplay.push_exception()
679 def _on_notebook_switch_page(self, notebook, page, pageIndex):
681 self._reset_tab_refresh()
683 didRecentUpdate = False
684 didMessagesUpdate = False
686 if pageIndex == self.RECENT_TAB:
687 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
688 elif pageIndex == self.MESSAGES_TAB:
689 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
690 elif pageIndex == self.CONTACTS_TAB:
691 self._contactsViews[self._selectedBackendId].update()
692 elif pageIndex == self.ACCOUNT_TAB:
693 self._accountViews[self._selectedBackendId].update()
695 if didRecentUpdate or didMessagesUpdate:
696 if self._ledHandler is not None:
697 self._ledHandler.off()
699 self._errorDisplay.push_exception()
701 def _set_tab_refresh(self, *args):
703 pageIndex = self._notebook.get_current_page()
704 child = self._notebook.get_nth_page(pageIndex)
705 self._notebook.get_tab_label(child).set_text("Refresh?")
707 self._errorDisplay.push_exception()
710 def _reset_tab_refresh(self, *args):
712 pageIndex = self._notebook.get_current_page()
713 child = self._notebook.get_nth_page(pageIndex)
714 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
716 self._errorDisplay.push_exception()
719 def _on_tab_refresh(self, *args):
721 self._refresh_active_tab()
722 self._reset_tab_refresh()
724 self._errorDisplay.push_exception()
727 def _on_sms_clicked(self, number, message):
729 assert number, "No number specified"
730 assert message, "Empty message"
732 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
735 self._errorDisplay.push_exception()
739 self._errorDisplay.push_message(
740 "Backend link with grandcentral is not working, please try again"
746 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
749 self._errorDisplay.push_exception()
752 self._dialpads[self._selectedBackendId].clear()
754 self._errorDisplay.push_exception()
756 def _on_dial_clicked(self, number):
758 assert number, "No number to call"
760 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
763 self._errorDisplay.push_exception()
767 self._errorDisplay.push_message(
768 "Backend link with grandcentral is not working, please try again"
774 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
775 self._phoneBackends[self._selectedBackendId].dial(number)
778 self._errorDisplay.push_exception()
781 self._dialpads[self._selectedBackendId].clear()
783 self._errorDisplay.push_exception()
785 def _on_menu_refresh(self, *args):
787 self._refresh_active_tab()
789 self._errorDisplay.push_exception()
791 def _on_menu_rotate(self, *args):
793 orientation = gtk_toolbox.get_screen_orientation()
794 if orientation == gtk.ORIENTATION_HORIZONTAL:
795 hildonize.window_to_portrait(self._window)
796 elif orientation == gtk.ORIENTATION_VERTICAL:
797 hildonize.window_to_landscape(self._window)
799 self._errorDisplay.push_exception()
801 def _on_paste(self, *args):
803 contents = self._clipboard.wait_for_text()
804 if contents is not None:
805 self._dialpads[self._selectedBackendId].set_number(contents)
807 self._errorDisplay.push_exception()
809 def _on_about_activate(self, *args):
811 dlg = gtk.AboutDialog()
812 dlg.set_name(constants.__pretty_app_name__)
813 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
814 dlg.set_copyright("Copyright 2008 - LGPL")
815 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")
816 dlg.set_website("http://gc-dialer.garage.maemo.org/")
817 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
821 self._errorDisplay.push_exception()
827 failureCount, testCount = doctest.testmod()
829 print "Tests Successful"
836 _lock_file = os.path.join(constants._data_path_, ".lock")
837 #with gtk_toolbox.flock(_lock_file, 0):
838 gtk.gdk.threads_init()
840 if hildonize.IS_HILDON_SUPPORTED:
841 gtk.set_application_name(constants.__pretty_app_name__)
842 handle = Dialcentral()
846 class DummyOptions(object):
852 if __name__ == "__main__":
853 logging.basicConfig(level=logging.DEBUG)
855 if len(sys.argv) > 1:
861 if optparse is not None:
862 parser = optparse.OptionParser()
863 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
864 (commandOptions, commandArgs) = parser.parse_args()
866 commandOptions = DummyOptions()
869 if commandOptions.test: