Package Gnumed :: Module gnumed |
[frames] | no frames]

# Source Code for Module Gnumed.gnumed

      1  #!/usr/bin/env python
      2
      3  __doc__ = """GNUmed client launcher.
      4
      5  This is the launcher for the GNUmed GUI client. It takes
      6  care of all the pre- and post-GUI runtime environment setup.
      7
      8  --quiet
      9   Be extra quiet and show only _real_ errors in the log.
     10  --debug
     11   Pre-set the [debug mode] checkbox in the login dialog to
     12   increase verbosity in the log file. Useful for, well, debugging :-)
     13  --slave
     14   Pre-set the [enable remote control] checkbox in the login
     15   dialog to enable the XML-RPC remote control feature.
     16  --hipaa
     17   Enable HIPAA functionality which has user impact.
     18  --profile=<file>
     19   Activate profiling and write profile data to <file>.
     20  --text-domain=<text domain>
     21   Set this to change the name of the language file to be loaded.
     22   Note, this does not change the directory the file is searched in,
     23   only the name of the file where messages are loaded from. The
     24   standard textdomain is, of course, "gnumed.mo".
     25  --log-file=<file>
     26   Use this to change the name of the log file.
     27   See gmLog2.py to find out where the standard log file would
     28   end up.
     29  --conf-file=<file>
     30   Use configuration file <file> instead of searching for it in
     31   standard locations.
     32  --lang-gettext=<language>
     33   Explicitly set the language to use in gettext translation. The very
     34   same effect can be achieved by setting the environment variable $LANG
     35   from a launcher script.
     36  --override-schema-check
     37   Continue loading the client even if the database schema version
     38   and the client software version cannot be verified to be compatible.
     39  --skip-update-check
     40   Skip checking for client updates. This is useful during development
     41   and when the update check URL is unavailable (down).
     42  --local-import
     43   Adjust the PYTHONPATH such that GNUmed can be run from a local source tree.
     44  --ui=<ui type>
     45   Start an alternative UI. Defaults to wxPython if not specified.
     46   Valid values: chweb (CherryPy), wxp (wxPython), web (ProxiedWeb)
     47  --version, -V
     48   Show version information.
     49  --help, -h, or -?
     50   Show this help.
     51  """
     52  #==========================================================
     53  __version__ = "$Revision: 1.169 $"
     54  __author__  = "H. Herb <hherb@gnumed.net>, K. Hilbert <Karsten.Hilbert@gmx.net>, I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
     55  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     56
     57  # standard library
     58  import sys
     59  import os
     60  import platform
     61  import logging
     62  import signal
     63  import os.path
     64  import shutil
     65  import stat
     66  import codecs
     67
     68
     69  # do not run as module
     70  if __name__ != "__main__":
     71          print "GNUmed startup: This is not intended to be imported as a module !"
     72          print "-----------------------------------------------------------------"
     73          print __doc__
     74          sys.exit(1)
     75
     76
     77  # do not run as root
     78  if os.name in ['posix'] and os.geteuid() == 0:
     79          print """
     80  GNUmed startup: GNUmed should not be run as root.
     81  -------------------------------------------------
     82
     83  Running GNUmed as <root> can potentially put all
     84  your medical data at risk. It is strongly advised
     85  against. Please run GNUmed as a non-root user.
     86  """
     87          sys.exit(1)
     88
     89  #----------------------------------------------------------
     90  #current_client_version = u'0.7.rc1'
     91  current_client_version = u'GIT HEAD'
     92  #current_client_branch = u'0.7'
     93  current_client_branch = u'GIT tree'
     94
     95  _log = None
     96  _cfg = None
     97  _old_sig_term = None
     98  _known_short_options = u'h?V'
     99  _known_long_options = [
    100          u'debug',
    101          u'slave',
    102          u'skip-update-check',
    103          u'profile=',
    104          u'text-domain=',
    105          u'log-file=',
    106          u'conf-file=',
    107          u'lang-gettext=',
    108          u'ui=',
    109          u'override-schema-check',
    110          u'local-import',
    111          u'help',
    112          u'version',
    113          u'hipaa'
    114  ]
    115
    116  _known_ui_types = [
    117          u'web',
    118          u'wxp',
    119          u'chweb'
    120  ]
    121
    122  import_error_sermon = """
    123  GNUmed startup: Cannot load GNUmed Python modules !
    124  ---------------------------------------------------
    125  CRITICAL ERROR: Program halted.
    126
    127  Please make sure you have:
    128
    129   1) the required third-party Python modules installed
    130   2) the GNUmed Python modules linked or installed into site-packages/
    131      (if you do not run from a CVS tree the installer should have taken care of that)
    132   3) your PYTHONPATH environment variable set up correctly
    133
    134  sys.path is currently set to:
    135
    136   %s
    137
    138  If you are running from a copy of the CVS tree make sure you
    139  did run gnumed/check-prerequisites.sh with good results.
    140
    141  If you still encounter errors after checking the above
    142  requirements please ask on the mailing list.
    143  """
    144
    145
    146  missing_cli_config_file = u"""
    147  GNUmed startup: Missing configuration file.
    148  -------------------------------------------
    149
    150  You explicitly specified a configuration file
    151  on the command line:
    152
    153          --conf-file=%s
    154
    155  The file does not exist, however.
    156  """
    157
    158
    159  no_config_files = u"""
    160  GNUmed startup: Missing configuration files.
    161  --------------------------------------------
    162
    163  None of the below candidate configuration
    164  files could be found:
    165
    166   %s
    167
    168  Cannot run GNUmed without any of them.
    169  """
    170  #==========================================================
    171  # convenience functions
    172  #==========================================================
    173 -def setup_python_path():
    174
    175          if not u'--local-import' in sys.argv:
    176                  return
    177
    178          local_python_base_dir = os.path.dirname (
    179                  os.path.abspath(os.path.join(sys.argv[0], '..'))
    180          )
    181          print "Running from local source tree (%s) ..." % local_python_base_dir
    182
    183          # does the path exist at all, physically ?
    184          # (*broken* links are reported as False)
    185          link_name = os.path.join(local_python_base_dir, 'Gnumed')
    186          if not os.path.exists(link_name):
    187                  real_dir = os.path.join(local_python_base_dir, 'client')
    188                  print "Creating module import symlink ..."
    189                  print ' real dir:', real_dir
    190                  print '     link:', link_name
    191                  os.symlink(real_dir, link_name)
    192
    193          print "Adjusting PYTHONPATH ..."
    194          sys.path.insert(0, local_python_base_dir)
    195  #==========================================================
    196 -def setup_local_repo_path():
    197
    198          local_repo_path = os.path.expanduser(os.path.join (
    199                  '~',
    200                  '.gnumed',
    201                  'local_code',
    202                  str(current_client_branch)
    203          ))
    204          local_wxGladeWidgets_path = os.path.join(local_repo_path, 'Gnumed', 'wxGladeWidgets')
    205
    206          if not os.path.exists(local_wxGladeWidgets_path):
    207                  _log.debug('[%s] not found', local_wxGladeWidgets_path)
    208                  _log.info('local wxGlade widgets repository not available')
    209                  return
    210
    211          _log.info('local wxGlade widgets repository found:')
    212          _log.info(local_wxGladeWidgets_path)
    213
    214          if not os.access(local_wxGladeWidgets_path, os.R_OK):
    215                  _log.error('invalid repo: no read access')
    216                  return
    217
    218          all_entries = os.listdir(os.path.join(local_repo_path, 'Gnumed'))
    219          _log.debug('repo base contains: %s', all_entries)
    220          all_entries.remove('wxGladeWidgets')
    221          try:
    222                  all_entries.remove('__init__.py')
    223          except ValueError:
    224                  _log.error('invalid repo: lacking __init__.py')
    225                  return
    226          try:
    227                  all_entries.remove('__init__.pyc')
    228          except ValueError:
    229                  pass
    230
    231          if len(all_entries) > 0:
    232                  _log.error('insecure repo: additional files or directories found')
    233                  return
    234
    235          # repo must be 0700 (rwx------)
    236          stat_val = os.stat(local_wxGladeWidgets_path)
    237          _log.debug('repo stat(): %s', stat_val)
    238          perms = stat.S_IMODE(stat_val.st_mode)
    239          _log.debug('repo permissions: %s (octal: %s)', perms, oct(perms))
    240          if perms != 448:                                # octal 0700
    241                  if os.name in ['nt']:
    242                          _log.warning('this platform does not support os.stat() permission checking')
    243                  else:
    244                          _log.error('insecure repo: permissions not 0600')
    245                          return
    246
    247          print "Activating local wxGlade widgets repository (%s) ..." % local_wxGladeWidgets_path
    248          sys.path.insert(0, local_repo_path)
    249          _log.debug('sys.path with repo:')
    250          _log.debug(sys.path)
    251  #==========================================================
    252 -def setup_logging():
    253          try:
    254                  from Gnumed.pycommon import gmLog2 as _gmLog2
    255          except ImportError:
    256                  sys.exit(import_error_sermon % '\n '.join(sys.path))
    257
    258          global gmLog2
    259          gmLog2 = _gmLog2
    260
    261          global _log
    262          _log = logging.getLogger('gm.launcher')
    263  #==========================================================
    264 -def log_startup_info():
    265          _log.info(u'Starting up as main module (%s).', __version__)
    266          _log.info(u'GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
    267          _log.info(u'Platform: %s', platform.uname())
    268          _log.info(u'Python %s on %s (%s)', sys.version, sys.platform, os.name)
    269          try:
    270                  import lsb_release
    271                  _log.info(u'%s' % lsb_release.get_distro_information())
    272          except ImportError:
    273                  pass
    274          _log.info('process environment:')
    275          for key, val in os.environ.items():
    276                  _log.info(u' %s: %s', (u'${%s}' % key).rjust(30), val)
    277  #==========================================================
    278 -def setup_console_exception_handler():
    279          from Gnumed.pycommon.gmTools import handle_uncaught_exception_console
    280
    281          sys.excepthook = handle_uncaught_exception_console
    282  #==========================================================
    283 -def setup_cli():
    284          from Gnumed.pycommon import gmCfg2
    285
    286          global _cfg
    287          _cfg = gmCfg2.gmCfgData()
    288          _cfg.add_cli (
    289                  short_options = _known_short_options,
    290                  long_options = _known_long_options
    291          )
    292
    293          val = _cfg.get(option = '--debug', source_order = [('cli', 'return')])
    294          if val is None:
    295                  val = False
    296          _cfg.set_option (
    297                  option = u'debug',
    298                  value = val
    299          )
    300
    301          val = _cfg.get(option = '--slave', source_order = [('cli', 'return')])
    302          if val is None:
    303                  val = False
    304          _cfg.set_option (
    305                  option = u'slave',
    306                  value = val
    307          )
    308
    309          val = _cfg.get(option = '--skip-update-check', source_order = [('cli', 'return')])
    310          if val is None:
    311                  val = False
    312          _cfg.set_option (
    313                  option = u'skip-update-check',
    314                  value = val
    315          )
    316
    317          val = _cfg.get(option = '--hipaa', source_order = [('cli', 'return')])
    318          if val is None:
    319                  val = False
    320          _cfg.set_option (
    321                  option = u'hipaa',
    322                  value = val
    323          )
    324
    325          val = _cfg.get(option = '--local-import', source_order = [('cli', 'return')])
    326          if val is None:
    327                  val = False
    328          _cfg.set_option (
    329                  option = u'local-import',
    330                  value = val
    331          )
    332
    333          _cfg.set_option (
    334                  option = u'client_version',
    335                  value = current_client_version
    336          )
    337
    338          _cfg.set_option (
    339                  option = u'client_branch',
    340                  value = current_client_branch
    341          )
    342
    343  #==========================================================
    344 -def handle_sig_term(signum, frame):
    345          _log.critical('SIGTERM (SIG%s) received, shutting down ...' % signum)
    346          gmLog2.flush()
    347          print 'GNUmed: SIGTERM (SIG%s) received, shutting down ...' % signum
    348          if frame is not None:
    349                  print '%s::%s@%s' % (frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno)
    350
    351          # FIXME: need to do something useful here
    352
    353          if _old_sig_term in [None, signal.SIG_IGN]:
    354                  sys.exit(signal.SIGTERM)
    355          else:
    356                  _old_sig_term(signum, frame)
    357  #----------------------------------------------------------
    358 -def setup_signal_handlers():
    359          global _old_sig_term
    360          old_sig_term = signal.signal(signal.SIGTERM, handle_sig_term)
    361  #==========================================================
    362 -def setup_locale():
    363          gmI18N.activate_locale()
    364
    365          td = _cfg.get(option = '--text-domain', source_order = [('cli', 'return')])
    366          l =  _cfg.get(option = '--lang-gettext', source_order = [('cli', 'return')])
    367          gmI18N.install_domain(domain = td, language = l, prefer_local_catalog = _cfg.get(option = u'local-import'))
    368
    369          # make sure we re-get the default encoding
    370          # in case it changed
    371          gmLog2.set_string_encoding()
    372  #==========================================================
    373 -def handle_help_request():
    374          src = [(u'cli', u'return')]
    375
    376          help_requested = (
    377                  _cfg.get(option = u'--help', source_order = src) or
    378                  _cfg.get(option = u'-h', source_order = src) or
    379                  _cfg.get(option = u'-?', source_order = src)
    380          )
    381
    382          if help_requested:
    383                  print _(
    384                          'Help requested\n'
    385                          '--------------'
    386                  )
    387                  print __doc__
    388                  sys.exit(0)
    389  #==========================================================
    390 -def handle_version_request():
    391          src = [(u'cli', u'return')]
    392
    393          version_requested = (
    394                  _cfg.get(option = u'--version', source_order = src) or
    395                  _cfg.get(option = u'-V', source_order = src)
    396          )
    397
    398          if version_requested:
    399
    400                  from Gnumed.pycommon.gmPG2 import map_client_branch2required_db_version, known_schema_hashes
    401
    402                  print 'GNUmed version information'
    403                  print '--------------------------'
    404                  print 'client     : %s on branch [%s]' % (current_client_version, current_client_branch)
    405                  print 'database   : %s' % map_client_branch2required_db_version[current_client_branch]
    406                  print 'schema hash: %s' % known_schema_hashes[map_client_branch2required_db_version[current_client_branch]]
    407                  sys.exit(0)
    408
    409  #==========================================================
    410 -def setup_paths_and_files():
    411          """Create needed paths in user home directory."""
    412
    413          gnumed_DIR_README_TEXT = u"""GNUmed Electronic Medical Record
    414
    415          %s/
    416
    417  This directory should only ever contain files which the
    418  user will come into direct contact with while using the
    419  application (say, by selecting a file from the file system,
    420  as when selecting document parts from files). You can create
    421  subdirectories here as you see fit for the purpose.
    422
    423  This directory will also serve as the default directory when
    424  GNUmed asks the user to select a directory for storing a
    425  file.
    426
    427  Any files which are NOT intended for direct user interaction
    428  but must be configured to live at a known location (say,
    429  inter-application data exchange files) should be put under
    430  the hidden directory "%s/".""" % (
    431                  os.path.expanduser(os.path.join('~', 'gnumed')),
    432                  os.path.expanduser(os.path.join('~', '.gnumed'))
    433          )
    434
    435          gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')))
    436          gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'spellcheck')))
    437          gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'error_logs')))
    438          gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed')))
    439
    440          README = codecs.open(os.path.expanduser(os.path.join('~', 'gnumed', '00_README')), 'wb', 'utf8')
    441          README.write(gnumed_DIR_README_TEXT)
    442          README.close()
    443
    444          paths = gmTools.gmPaths(app_name = u'gnumed')
    445
    446          open(os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed.conf')), 'a+').close()
    447  #==========================================================
    448 -def setup_date_time():
    449          gmDateTime.init()
    450  #==========================================================
    451 -def setup_cfg():
    452          """Detect and setup access to GNUmed config file.
    453
    454          Parts of this will have limited value due to
    455          wxPython not yet being available.
    456          """
    457
    458          enc = gmI18N.get_encoding()
    459          paths = gmTools.gmPaths(app_name = u'gnumed')
    460
    461          candidates = [
    462                  # the current working dir
    463                  [u'workbase', os.path.join(paths.working_dir, 'gnumed.conf')],
    464                  # /etc/gnumed/
    465                  [u'system', os.path.join(paths.system_config_dir, 'gnumed-client.conf')],
    466                  # ~/.gnumed/
    467                  [u'user', os.path.join(paths.user_config_dir, 'gnumed.conf')],
    468                  # CVS/tgz tree .../gnumed/client/ (IOW a local installation)
    469                  [u'local', os.path.join(paths.local_base_dir, 'gnumed.conf')]
    470          ]
    471          # --conf-file=
    472          explicit_fname = _cfg.get(option = u'--conf-file', source_order = [(u'cli', u'return')])
    473          if explicit_fname is None:
    474                  candidates.append([u'explicit', None])
    475          else:
    476                  candidates.append([u'explicit', explicit_fname])
    477
    478          for candidate in candidates:
    479                  _cfg.add_file_source (
    480                          source = candidate[0],
    481                          file = candidate[1],
    482                          encoding = enc
    483                  )
    484
    485          # --conf-file given but does not actually exist ?
    486          if explicit_fname is not None:
    487                  if _cfg.source_files['explicit'] is None:
    488                          _log.error('--conf-file argument does not exist')
    489                          sys.exit(missing_cli_config_file % explicit_fname)
    490
    491          # any config file found at all ?
    492          found_any_file = False
    493          for f in _cfg.source_files.values():
    494                  if f is not None:
    495                          found_any_file = True
    496                          break
    497          if not found_any_file:
    498                  _log.error('no config file found at all')
    499                  sys.exit(no_config_files % '\n '.join(candidates))
    500
    501          # mime type handling sources
    502          fname = u'mime_type2file_extension.conf'
    503          _cfg.add_file_source (
    504                  source = u'user-mime',
    505                  file = os.path.join(paths.user_config_dir, fname),
    506                  encoding = enc
    507          )
    508          _cfg.add_file_source (
    509                  source = u'system-mime',
    510                  file = os.path.join(paths.system_config_dir, fname),
    511                  encoding = enc
    512          )
    513  #==========================================================
    514 -def setup_ui_type():
    515          global ui_type
    516
    517          ui_type = _cfg.get(option = u'--ui', source_order = [(u'cli', u'return')])
    518
    519          if ui_type in [True, False, None]:
    520                  ui_type = 'wxp'
    521
    522          ui_type = ui_type.strip()
    523
    524          if ui_type not in _known_ui_types:
    525                  _log.error('unknown UI type: %s', ui_type)
    526                  _log.debug('known UI types: %s', str(_known_ui_types))
    527                  print "GNUmed startup: Unknown UI type (%s). Defaulting to wxPython client." % ui_type
    528                  ui_type = 'wxp'
    529
    530          _log.debug('UI type: %s', ui_type)
    531  #==========================================================
    532 -def setup_backend():
    533
    534          db_version = gmPG2.map_client_branch2required_db_version[current_client_branch]
    535          _log.info('client expects database version [%s]', db_version)
    536          _cfg.set_option (
    537                  option = u'database_version',
    538                  value = db_version
    539          )
    540
    541          # set up database connection timezone
    542          timezone = _cfg.get (
    543                  group = u'backend',
    544                  option = 'client timezone',
    545                  source_order = [
    546                          ('explicit', 'return'),
    547                          ('workbase', 'return'),
    548                          ('local', 'return'),
    549                          ('user', 'return'),
    550                          ('system', 'return')
    551                  ]
    552          )
    553          if timezone is not None:
    554                  gmPG2.set_default_client_timezone(timezone)
    555  #==========================================================
    556 -def shutdown_backend():
    557          gmPG2.shutdown()
    558  #==========================================================
    559 -def shutdown_logging():
    560
    561  #       if _cfg.get(option = u'debug'):
    562  #               import types
    563
    564  #               def get_refcounts():
    565  #                       refcount = {}
    566  #                       # collect all classes
    567  #                       for module in sys.modules.values():
    568  #                               for sym in dir(module):
    569  #                                       obj = getattr(module, sym)
    570  #                                       if type(obj) is types.ClassType:
    571  #                                               refcount[obj] = sys.getrefcount(obj)
    572  #                       # sort by refcount
    573  #                       pairs = map(lambda x: (x[1],x[0]), refcount.items())
    574  #                       pairs.sort()
    575  #                       pairs.reverse()
    576  #                       return pairs
    577
    578  #               rcfile = open('./gm-refcount.lst', 'wb')
    579  #               for refcount, class_ in get_refcounts():
    580  #                       if not class_.__name__.startswith('wx'):
    581  #                               rcfile.write('%10d %s\n' % (refcount, class_.__name__))
    582  #               rcfile.close()
    583
    584          # do not choke on Windows
    585          logging.raiseExceptions = False
    586  #==========================================================
    587 -def shutdown_tmp_dir():
    588
    589          tmp_dir = gmTools.gmPaths().tmp_dir
    590
    591          if _cfg.get(option = u'debug'):
    592                  _log.debug('not removing tmp dir (--debug mode): %s', tmp_dir)
    593                  return
    594
    595          _log.warning('removing tmp dir: %s', tmp_dir)
    596          shutil.rmtree(tmp_dir, True)
    597  #==========================================================
    598  # main - launch the GNUmed wxPython GUI client
    599  #----------------------------------------------------------
    600  setup_python_path()
    601  setup_logging()
    602  log_startup_info()
    603  setup_console_exception_handler()
    604  setup_cli()
    605  setup_signal_handlers()
    606  setup_local_repo_path()
    607
    608  from Gnumed.pycommon import gmI18N, gmTools, gmDateTime, gmHooks
    609  setup_locale()
    610  handle_help_request()
    611  handle_version_request()
    612  setup_paths_and_files()
    613  setup_date_time()
    614  setup_cfg()
    615  setup_ui_type()
    616
    617  from Gnumed.pycommon import gmPG2
    618  if ui_type in [u'web']:
    619          gmPG2.auto_request_login_params = False
    620  setup_backend()
    621
    622
    623  gmHooks.run_hook_script(hook = u'startup-before-GUI')
    624
    625  if ui_type == u'wxp':
    626          from Gnumed.wxpython import gmGuiMain
    627          profile_file = _cfg.get(option = u'--profile', source_order = [(u'cli', u'return')])
    628          if profile_file is not None:
    629                  _log.info('writing profiling data into %s', profile_file)
    630                  import profile
    631                  profile.run('gmGuiMain.main()', profile_file)
    632          else:
    633                  gmGuiMain.main()
    634  elif ui_type == u'web':
    635          from Gnumed.proxiedpyjamas import gmWebGuiServer
    636          gmWebGuiServer.main()
    637
    638  elif ui_type == u'chweb':
    639          from Gnumed.CherryPy import gmGuiWeb
    640          gmGuiWeb.main()
    641
    642  gmHooks.run_hook_script(hook = u'shutdown-post-GUI')
    643
    644  shutdown_backend()
    645  shutdown_tmp_dir()
    646  _log.info('Normally shutting down as main module.')
    647  shutdown_logging()
    648
    649  #==========================================================
    650