Exemple #1
0
class Shell(object):
    """The main application shell
    - bootstraps via ShellBootstrap
    - connects to the database via ShellDatabaseConnection
    - handles login
    - runs applications
    """

    def __init__(self, bootstrap, options, initial=True):
        global _shell
        _shell = self
        self._appname = None
        self._bootstrap = bootstrap
        self._dbconn = ShellDatabaseConnection(options=options)
        self._blocked_apps = []
        self._hidden_apps = []
        self._login = None
        self._options = options
        self._user = None

        app = Gtk.Application.get_default()
        if not app:
            app = Gtk.Application()
            Gtk.Application.set_default(app)
        self._app = app
        self._app.connect('activate', self._on_app__activate)
        self.windows = []

    #
    # Private
    #

    def _do_login(self):
        from stoqlib.exceptions import LoginError
        from stoqlib.gui.utils.login import LoginHelper
        from stoqlib.lib.message import error

        self._login = LoginHelper(username=self._options.login_username)
        try:
            if not self.login():
                return False
        except LoginError as e:
            error(str(e))
            return False
        self._check_param_online_services()
        self._maybe_show_welcome_dialog()
        return True

    def _check_param_online_services(self):
        from stoqlib.database.runtime import new_store
        from stoqlib.lib.parameters import sysparam
        from gi.repository import Gtk

        if sysparam.get_bool('ONLINE_SERVICES') is None:
            from kiwi.ui.dialogs import HIGAlertDialog
            # FIXME: All of this is to avoid having to set markup as the default
            #        in kiwi/ui/dialogs:HIGAlertDialog.set_details, after 1.0
            #        this can be simplified when we fix so that all descriptions
            #        sent to these dialogs are properly escaped
            dialog = HIGAlertDialog(
                parent=None,
                flags=Gtk.DialogFlags.MODAL,
                type=Gtk.MessageType.WARNING)
            dialog.add_button(_("Not right now"), Gtk.ResponseType.NO)
            dialog.add_button(_("Enable online services"), Gtk.ResponseType.YES)

            dialog.set_primary(_('Do you want to enable Stoq online services?'))
            dialog.set_details(PRIVACY_STRING, use_markup=True)
            dialog.set_default_response(Gtk.ResponseType.YES)
            response = dialog.run()
            dialog.destroy()
            store = new_store()
            sysparam.set_bool(store, 'ONLINE_SERVICES', response == Gtk.ResponseType.YES)
            store.commit()
            store.close()

    def _maybe_show_welcome_dialog(self):
        from stoqlib.api import api
        if not api.user_settings.get('show-welcome-dialog', True):
            return
        api.user_settings.set('show-welcome-dialog', False)

        from stoq.gui.welcomedialog import WelcomeDialog
        from stoqlib.gui.base.dialogs import run_dialog
        run_dialog(WelcomeDialog)

    def _maybe_correct_demo_position(self, shell_window):
        # Possibly correct window position (livecd workaround for small
        # screens)
        from stoqlib.lib.parameters import sysparam
        from stoqlib.lib.pluginmanager import get_plugin_manager
        manager = get_plugin_manager()
        if (sysparam.get_bool('DEMO_MODE') and
                manager.is_active(u'ecf')):
            pos = shell_window.toplevel.get_position()
            if pos[0] < 220:
                shell_window.toplevel.move(220, pos[1])

    def _maybe_schedule_idle_logout(self):
        # Verify if the user will use automatic logout.
        from stoqlib.lib.parameters import sysparam
        minutes = sysparam.get_int('AUTOMATIC_LOGOUT')
        # If user defined 0 minutes, ignore automatic logout.
        if minutes != 0:
            seconds = minutes * 60
            GLib.timeout_add_seconds(5, self._verify_idle_logout, seconds)

    def _verify_idle_logout(self, seconds):
        # This is called once every 10 seconds
        from stoqlib.gui.utils.idle import get_idle_seconds
        if get_idle_seconds() > seconds:
            return self._idle_logout()
        # Call us again in 10 seconds
        return True

    def _idle_logout(self):
        # Before performing logout, verify that the currently opened window
        # is modal.
        from kiwi.component import get_utility
        from stoqlib.gui.base.dialogs import has_modal_window
        from stoqlib.lib.interfaces import ICookieFile
        # If not a modal window, logout.
        # Otherwise returns True to continue checking the automatic logout.
        if not has_modal_window():
            log.debug('Automatic logout')
            get_utility(ICookieFile).clear()
            self.quit(restart=True)
        return True

    def _logout(self):
        from stoqlib.database.runtime import (get_current_user,
                                              get_default_store)
        log.debug('Logging out the current user')
        try:
            user = get_current_user(get_default_store())
            if user:
                user.logout()
        except StoqlibError:
            pass

    def _terminate(self, restart=False, app=None):
        log.info("Terminating Stoq")

        # This removes all temporary files created when calling
        # get_resource_filename() that extract files to the file system
        import pkg_resources
        pkg_resources.cleanup_resources()

        log.debug('Stopping deamon')
        from stoqlib.lib.daemonutils import stop_daemon
        stop_daemon()

        # Finally, go out of the reactor and show possible crash reports
        log.debug("Show some crash reports")
        self._show_crash_reports()

        # Make sure that no connection is left open (specially on Windows)
        try:
            from stoqlib.database.runtime import get_default_store
            get_default_store().close()
        except Exception:
            pass

        if restart:
            from stoqlib.lib.process import Process
            log.info('Restarting Stoq')
            args = [sys.argv[0], '--no-splash-screen']
            if app is not None:
                args.append(app)
            Process(args)

        # os._exit() forces a quit without running atexit handlers
        # and does not block on any running threads
        # FIXME: This is the wrong solution, we should figure out why there
        #        are any running threads/processes at this point
        log.debug("Terminating by calling os._exit()")
        os._exit(0)

        raise AssertionError("Should never happen")

    def _show_crash_reports(self):
        from stoqlib.lib.crashreport import has_tracebacks
        if not has_tracebacks():
            return
        if 'STOQ_DISABLE_CRASHREPORT' in os.environ:
            return
        from gi.repository import Gtk
        from stoqlib.gui.dialogs.crashreportdialog import show_dialog
        show_dialog(Gtk.main_quit)
        Gtk.main()

    #
    # Public API
    #

    def login(self):
        """
        Do a login
        @param try_cookie: Try to use a cookie if one is available
        @returns: True if login succeed, otherwise false
        """
        from stoqlib.exceptions import LoginError
        from stoqlib.lib.message import info
        user = self._login.cookie_login()

        if not user:
            try:
                user = self._login.validate_user()
            except LoginError as e:
                info(str(e))

        if user:
            self._user = user
        return bool(user)

    def get_current_app_name(self):
        """
        Get the name of the currently running application
        @returns: the name
        @rtype: str
        """
        if not self.windows:
            return ''
        app = self.windows[0].current_app
        if not app:
            return ''
        return app.app_name

    def create_window(self):
        """
        Creates a new shell window.

        Note that it will not contain any applications and it will be hidden.

        :returns: the shell_window
        """
        from stoq.gui.shell.shellwindow import ShellWindow
        from stoqlib.database.runtime import get_default_store
        shell_window = ShellWindow(self._options,
                                   shell=self,
                                   store=get_default_store(),
                                   app=self._app)
        self._app.add_window(shell_window.toplevel)
        self.windows.append(shell_window)

        self._maybe_correct_demo_position(shell_window)

        return shell_window

    def close_window(self, shell_window):
        """
        Close a currently open window
        :param ShellWindow shell_window: the shell_window
        """
        shell_window.close()
        self.windows.remove(shell_window)

    def main(self, appname, action_name=None):
        """
        Start the shell.
        This will:
        - connect to the database
        - login the current user
        - create a new window
        - run the launcher/application selector app
        - run a mainloop

        This will only exit when the complete stoq application
        is shutdown.

        :param appname: name of the application to run
        :param action_name: action to activate or ``None``
        """
        self._appname = appname
        self._action_name = action_name

        self._bootstrap.entered_main = True

        log.debug("Entering main loop")
        self._app.run()
        log.info("Leaving main loop")

    def quit(self, restart=False, app=None):
        """
        Quit the shell and exit the application.
        This will save user settings and then forcefully terminate
        the application

        :param restart: if ``True`` restart after terminating
        :param str app: if not ``None``, name of the application to restart
        """
        from stoqlib.api import api
        self._logout()

        # Write user settings to disk, this obviously only happens when
        # termination the complete stoq application
        log.debug("Flushing user settings")
        api.user_settings.flush()

        self._terminate(restart=restart, app=app)

    #
    # Callbacks
    #

    def _on_app__activate(self, app):
        appname = self._appname
        action_name = self._action_name
        self._dbconn.connect()
        if not self._do_login():
            raise SystemExit
        if appname is None:
            appname = u'launcher'
        shell_window = self.create_window()
        app = shell_window.run_application(str(appname))
        shell_window.show()

        if action_name is not None:
            action = getattr(app, action_name, None)
            if action is not None:
                action.activate()

        self._maybe_schedule_idle_logout()
Exemple #2
0
class Shell(object):
    """The main application shell
    - bootstraps via ShellBootstrap
    - connects to the database via ShellDatabaseConnection
    - handles login
    - runs applications
    """
    def __init__(self, bootstrap, options, initial=True):
        global _shell
        _shell = self
        self._appname = None
        self._bootstrap = bootstrap
        self._dbconn = ShellDatabaseConnection(options=options)
        self._blocked_apps = []
        self._hidden_apps = []
        self._login = None
        self._options = options
        self._user = None
        self.windows = []

    #
    # Private
    #

    def _do_login(self):
        from stoqlib.exceptions import LoginError
        from stoqlib.gui.utils.login import LoginHelper
        from stoqlib.lib.message import error

        self._login = LoginHelper(username=self._options.login_username)
        try:
            if not self.login():
                return False
        except LoginError as e:
            error(str(e))
            return False
        self._check_param_online_services()
        self._maybe_show_welcome_dialog()
        return True

    def _check_param_online_services(self):
        from stoqlib.database.runtime import get_default_store, new_store
        from stoqlib.lib.parameters import sysparam
        import gtk

        sparam = sysparam(get_default_store())
        if sparam.ONLINE_SERVICES is None:
            from kiwi.ui.dialogs import HIGAlertDialog
            # FIXME: All of this is to avoid having to set markup as the default
            #        in kiwi/ui/dialogs:HIGAlertDialog.set_details, after 1.0
            #        this can be simplified when we fix so that all descriptions
            #        sent to these dialogs are properly escaped
            dialog = HIGAlertDialog(
                parent=None,
                flags=gtk.DIALOG_MODAL,
                type=gtk.MESSAGE_WARNING)
            dialog.add_button(_("Not right now"), gtk.RESPONSE_NO)
            dialog.add_button(_("Enable online services"), gtk.RESPONSE_YES)

            dialog.set_primary(_('Do you want to enable Stoq online services?'))
            dialog.set_details(PRIVACY_STRING, use_markup=True)
            dialog.set_default_response(gtk.RESPONSE_YES)
            response = dialog.run()
            dialog.destroy()
            store = new_store()
            sysparam(store).ONLINE_SERVICES = int(bool(response == gtk.RESPONSE_YES))
            store.commit()
            store.close()

    def _maybe_show_welcome_dialog(self):
        from stoqlib.api import api
        if not api.user_settings.get('show-welcome-dialog', True):
            return
        api.user_settings.set('show-welcome-dialog', False)

        from stoq.gui.welcomedialog import WelcomeDialog
        from stoqlib.gui.base.dialogs import run_dialog
        run_dialog(WelcomeDialog)

    def _maybe_correct_demo_position(self, shell_window):
        # Possibly correct window position (livecd workaround for small
        # screens)
        from stoqlib.database.runtime import get_default_store
        from stoqlib.lib.parameters import sysparam
        from stoqlib.lib.pluginmanager import get_plugin_manager
        manager = get_plugin_manager()
        if (sysparam(get_default_store()).DEMO_MODE and
            manager.is_active(u'ecf')):
            pos = shell_window.toplevel.get_position()
            if pos[0] < 220:
                shell_window.toplevel.move(220, pos[1])

    def _logout(self):
        from stoqlib.database.runtime import (get_current_user,
                                              get_default_store)
        log.debug('Logging out the current user')
        try:
            user = get_current_user(get_default_store())
            if user:
                user.logout()
        except StoqlibError:
            pass

    @inlineCallbacks
    def _terminate(self, restart=False, app=None):
        log.info("Terminating Stoq")

        # This removes all temporary files created when calling
        # get_resource_filename() that extract files to the file system
        import pkg_resources
        pkg_resources.cleanup_resources()

        log.debug('Stopping deamon')
        from stoqlib.lib.daemonutils import stop_daemon
        stop_daemon()

        # Finally, go out of the reactor and show possible crash reports
        yield self._quit_reactor_and_maybe_show_crashreports()

        if restart:
            from stoqlib.lib.process import Process
            log.info('Restarting Stoq')
            args = [sys.argv[0], '--no-splash-screen']
            if app is not None:
                args.append(app)
            Process(args)

        # os._exit() forces a quit without running atexit handlers
        # and does not block on any running threads
        # FIXME: This is the wrong solution, we should figure out why there
        #        are any running threads/processes at this point
        log.debug("Terminating by calling os._exit()")
        os._exit(0)

        raise AssertionError("Should never happen")

    def _show_crash_reports(self):
        from stoqlib.lib.crashreport import has_tracebacks
        if not has_tracebacks():
            return succeed(None)
        if 'STOQ_DISABLE_CRASHREPORT' in os.environ:
            return succeed(None)
        from stoqlib.gui.dialogs.crashreportdialog import show_dialog
        return show_dialog()

    @inlineCallbacks
    def _quit_reactor_and_maybe_show_crashreports(self):
        log.debug("Show some crash reports")
        yield self._show_crash_reports()
        log.debug("Shutdown reactor")
        from twisted.internet import reactor
        reactor.stop()

    #
    # Public API
    #

    def login(self):
        """
        Do a login
        @param try_cookie: Try to use a cookie if one is available
        @returns: True if login succeed, otherwise false
        """
        from stoqlib.exceptions import LoginError
        from stoqlib.lib.message import info
        user = self._login.cookie_login()

        if not user:
            try:
                user = self._login.validate_user()
            except LoginError as e:
                info(str(e))

        if user:
            self._user = user
        return bool(user)

    def get_current_app_name(self):
        """
        Get the name of the currently running application
        @returns: the name
        @rtype: str
        """
        if not self.windows:
            return ''
        return self.windows[0].current_app.app_name

    def create_window(self):
        """
        Creates a new shell window.

        Note that it will not contain any applications and it will be hidden.

        :returns: the shell_window
        """
        from stoq.gui.shell.shellwindow import ShellWindow
        from stoqlib.database.runtime import get_default_store
        shell_window = ShellWindow(self._options,
                                   shell=self,
                                   store=get_default_store())
        self.windows.append(shell_window)

        self._maybe_correct_demo_position(shell_window)

        return shell_window

    def close_window(self, shell_window):
        """
        Close a currently open window
        :param ShellWindow shell_window: the shell_window
        """
        shell_window.close()
        self.windows.remove(shell_window)

    def main(self, appname):
        """
        Start the shell.
        This will:
        - connect to the database
        - login the current user
        - create a new window
        - run the launcher/application selector app
        - run a mainloop

        This will only exit when the complete stoq application
        is shutdown.

        :param appname: name of the application to run
        """
        self._dbconn.connect()
        if not self._do_login():
            raise SystemExit
        if appname is None:
            appname = u'launcher'
        shell_window = self.create_window()
        shell_window.run_application(unicode(appname))
        shell_window.show()

        log.debug("Entering reactor")
        self._bootstrap.entered_main = True
        from twisted.internet import reactor
        reactor.run()
        log.info("Leaving reactor")

    def quit(self, restart=False, app=None):
        """
        Quit the shell and exit the application.
        This will save user settings and then forcefully terminate
        the application

        :param restart: if ``True`` restart after terminating
        :param str app: if not ``None``, name of the application to restart
        """
        from stoqlib.api import api
        self._logout()

        # Write user settings to disk, this obviously only happens when
        # termination the complete stoq application
        log.debug("Flushing user settings")
        api.user_settings.flush()

        self._terminate(restart=restart, app=app)