# -*- coding: utf-8 *-
# vi:si:et:sw=4:sts=4:ts=4
##
## Copyright (C) 2005-2013 Async Open Source <http://www.async.com.br>
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program 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 General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##
##
""" Stoq shell routines"""
import logging
import os
import sys
import glib
# FIXME: We can import whatever we want here, but don't import anything
# significant, it's good to maintain lazy loaded things during startup
from stoqlib.exceptions import StoqlibError
from stoqlib.lib.translation import stoqlib_gettext as _
log = logging.getLogger(__name__)
_shell = None
PRIVACY_STRING = _(
"One of the new features of Stoq 1.0 is support for online "
"services. Features using the online services include automatic "
"bug report and update notifications. More services are under development."
"To be able to provide a better service and properly identify the user "
"we will collect the CNPJ of the primary branch and the ip address.\n\n"
"<b>We will not disclose the collected information and we are committed "
"to keeping your privacy intact.</b>")
[docs]class ShellDatabaseConnection(object):
"""Sets up a database connection
- Connects to a database
- Telling why if it failed
- Runs database wizard if needed
- Runs schema migration
- Activates plugins
- Sets up main branch
"""
def __init__(self, options):
self._options = options
self._config = None
self._ran_wizard = False
[docs] def connect(self):
self._load_configuration()
self._maybe_run_first_time_wizard()
self._try_connect()
self._post_connect()
def _load_configuration(self):
from stoqlib.lib.configparser import StoqConfig
log.debug('reading configuration')
self._config = StoqConfig()
if self._options.filename:
self._config.load(self._options.filename)
else:
self._config.load_default()
def _maybe_run_first_time_wizard(self):
from stoqlib.gui.base.dialogs import run_dialog
from stoq.gui.config import FirstTimeConfigWizard
config_file = self._config.get_filename()
if self._options.wizard or not os.path.exists(config_file):
run_dialog(FirstTimeConfigWizard, None, self._options)
self._ran_wizard = True
if self._config.get('Database', 'enable_production') == 'True':
run_dialog(FirstTimeConfigWizard, None, self._options, self._config)
self._ran_wizard = True
def _get_password(self):
import binascii
configdir = self._config.get_config_directory()
filename = os.path.join(configdir, 'data')
if not os.path.exists(filename):
return
data = open(filename).read()
return binascii.a2b_base64(data)
def _try_connect(self):
from stoqlib.lib.message import error
try:
store_uri = self._config.get_settings().get_store_uri()
except Exception:
type, value, trace = sys.exc_info()
error(_("Could not open the database config file"),
_("Invalid config file settings, got error '%s', "
"of type '%s'") % (value, type))
from stoqlib.database.exceptions import PostgreSQLError
from stoqlib.database.runtime import get_default_store
from stoqlib.exceptions import DatabaseError
from stoqlib.lib.pgpass import write_pg_pass
from stoq.lib.startup import setup
# XXX: progress dialog for connecting (if it takes more than
# 2 seconds) or creating the database
log.debug('calling setup()')
try:
setup(self._config, self._options, register_station=False,
check_schema=False, load_plugins=False)
# the setup call above is not really trying to connect (since
# register_station, check_schema and load_plugins are all False).
# Try to really connect here.
get_default_store()
except (StoqlibError, PostgreSQLError) as e:
log.debug('Connection failed.')
error(_('Could not connect to the database'),
'error=%s uri=%s' % (str(e), store_uri))
except DatabaseError:
log.debug('Connection failed. Tring to setup .pgpass')
# This is probably a missing password configuration. Setup the
# pgpass file and try again.
try:
password = self._get_password()
if not password:
# There is no password stored in data file. Abort
raise
from stoqlib.database.settings import db_settings
write_pg_pass(db_settings.dbname, db_settings.address,
db_settings.port, db_settings.username, password)
# Now that there is a pg_pass file, try to connect again
get_default_store()
except DatabaseError as e:
log.debug('Connection failed again.')
error(_('Could not connect to the database'),
'error=%s uri=%s' % (str(e), store_uri))
def _post_connect(self):
self._check_schema_migration()
self._check_branch()
self._activate_plugins()
def _check_schema_migration(self):
from stoqlib.lib.message import error
from stoqlib.database.migration import needs_schema_update
from stoqlib.exceptions import DatabaseInconsistency
if needs_schema_update():
self._run_update_wizard()
from stoqlib.database.migration import StoqlibSchemaMigration
migration = StoqlibSchemaMigration()
try:
migration.check()
except DatabaseInconsistency as e:
error(_('The database version differs from your installed '
'version.'), str(e))
def _activate_plugins(self):
from stoqlib.lib.pluginmanager import get_plugin_manager
manager = get_plugin_manager()
manager.activate_installed_plugins()
def _check_branch(self):
from stoqlib.database.runtime import (get_default_store, new_store,
get_current_station,
set_current_branch_station)
from stoqlib.domain.person import Company
from stoqlib.lib.parameters import sysparam
from stoqlib.lib.message import info
default_store = get_default_store()
compaines = default_store.find(Company)
if (compaines.count() == 0 or
not sysparam.has_object('MAIN_COMPANY')):
from stoqlib.gui.base.dialogs import run_dialog
from stoqlib.gui.dialogs.branchdialog import BranchDialog
if self._ran_wizard:
info(_("You need to register a company before start using Stoq"))
else:
info(_("Could not find a company. You'll need to register one "
"before start using Stoq"))
store = new_store()
person = run_dialog(BranchDialog, None, store)
if not person:
raise SystemExit
branch = person.branch
sysparam.set_object(store, 'MAIN_COMPANY', branch)
current_station = get_current_station(store)
if current_station is not None:
current_station.branch = branch
store.commit()
store.close()
set_current_branch_station(default_store, station_name=None)
def _run_update_wizard(self):
from stoqlib.gui.base.dialogs import run_dialog
from stoq.gui.update import SchemaUpdateWizard
retval = run_dialog(SchemaUpdateWizard, None)
if not retval:
raise SystemExit()
[docs]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 new_store
from stoqlib.lib.parameters import sysparam
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.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.set_bool(store, 'ONLINE_SERVICES', 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.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()
log.debug("Stoq gtk.main")
import gtk
gtk.main_quit()
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
import gtk
from stoqlib.gui.dialogs.crashreportdialog import show_dialog
show_dialog(gtk.main_quit)
gtk.main()
#
# Public API
#
[docs] 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)
[docs] 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
[docs] 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
[docs] 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)
[docs] 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._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(unicode(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()
log.debug("Entering main loop")
self._bootstrap.entered_main = True
import gtk
gtk.main()
log.info("Leaving main loop")
[docs] 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)
[docs]def get_shell():
return _shell