Package Gnumed :: Package pycommon :: Module gmHooks |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmHooks

      1  """GNUmed hooks framework.
      2
      3  This module provides convenience functions and definitions
      4  for accessing the GNUmed hooks framework.
      5
      6  This framework calls the script
      7
      8          ~/.gnumed/scripts/hook_script.py
      9
     10  at various times during client execution. The script must
     11  contain a function
     12
     13  def run_script(hook=None):
     14          pass
     15
     16  which accepts a single argument <hook>. That argument will
     17  contain the hook that is being activated.
     18  """
     19  # ========================================================================
     20  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>"
     21  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     22
     23  # stdlib
     24  import os
     25  import sys
     26  import stat
     27  import logging
     28
     29
     30  # GNUmed libs
     31  if __name__ == '__main__':
     32          sys.path.insert(0, '../../')
     33  from Gnumed.pycommon import gmDispatcher
     34  from Gnumed.pycommon import gmTools
     35
     36
     37  _log = logging.getLogger('gm.hook')
     38  # ========================================================================
     39  known_hooks = [
     40          u'post_patient_activation',
     41          u'post_person_creation',
     42
     43          u'shutdown-post-GUI',
     44          u'startup-after-GUI-init',
     45          u'startup-before-GUI',
     46
     47          u'request_user_attention',
     48          u'app_activated_startup',
     49          u'app_activated',
     50          u'app_deactivated',
     51
     52          u'after_substance_intake_modified',
     53          u'after_test_result_modified',
     54          u'after_soap_modified',
     55          u'after_code_link_modified',
     56
     57          u'after_new_doc_created',
     58          u'before_print_doc',
     59          u'before_fax_doc',
     60          u'before_mail_doc',
     61          u'before_print_doc_part',
     62          u'before_fax_doc_part',
     63          u'before_mail_doc_part',
     64          u'before_external_doc_access',
     65
     66          u'db_maintenance_warning'
     67  ]
     68
     69  _log.debug('known hooks:')
     70  for hook in known_hooks:
     71          _log.debug(hook)
     72
     73  # ========================================================================
     74  hook_module = None
     75
     76 -def import_hook_module(reimport=False):
     77
     78          global hook_module
     79          if not reimport:
     80                  if hook_module is not None:
     81                          return True
     82
     83          # hardcoding path and script name allows us to
     84          # not need configuration for it, the environment
     85          # can always be detected at runtime (workplace etc)
     86          script_name = 'hook_script.py'
     87          script_path = os.path.expanduser(os.path.join('~', '.gnumed', 'scripts'))
     88          full_script = os.path.join(script_path, script_name)
     89
     90          if not os.access(full_script, os.F_OK):
     91                  _log.warning('creating default hook script')
     92                  f = open(full_script, 'w')
     93                  f.write("""
     94  # known hooks:
     95  #  %s
     96
     97  def run_script(hook=None):
     98          pass
     99  """ % '#  '.join(known_hooks))
    100                  f.close()
    101                  os.chmod(full_script, 384)
    102
    103          if os.path.islink(full_script):
    104                  gmDispatcher.send (
    105                          signal = 'statustext',
    106                          msg = _('Script must not be a link: [%s].') % full_script
    107                  )
    108                  return False
    109
    110          if not os.access(full_script, os.R_OK):
    111                  gmDispatcher.send (
    112                          signal = 'statustext',
    113                          msg = _('Script must be readable by the calling user: [%s].') % full_script
    114                  )
    115                  return False
    116
    117          script_stat_val = os.stat(full_script)
    118          _log.debug('hook script stat(): %s', script_stat_val)
    119          script_perms = stat.S_IMODE(script_stat_val.st_mode)
    120          _log.debug('hook script mode: %s (oktal: %s)', script_perms, oct(script_perms))
    121          if script_perms != 384:                         # octal 0600
    122                  if os.name in ['nt']:
    123                          _log.warning('this platform does not support os.stat() file permission checking')
    124                  else:
    125                          gmDispatcher.send (
    126                                  signal = 'statustext',
    127                                  msg = _('Script must be readable by the calling user only (permissions "0600"): [%s].') % full_script
    128                          )
    129                          return False
    130
    131          try:
    132                  tmp = gmTools.import_module_from_directory(script_path, script_name)
    133          except StandardError:
    134                  _log.exception('cannot import hook script')
    135                  return False
    136
    137          hook_module = tmp
    138  #       if reimport:
    139  #               reload(tmp)                     # this has well-known shortcomings !
    140
    141          _log.info('hook script: %s', full_script)
    142          return True
    143  # ========================================================================
    144  __current_hook_stack = []
    145
    146 -def run_hook_script(hook=None):
    147          # NOTE: this just *might* be a huge security hole
    148
    149          _log.info('told to pull hook [%s]', hook)
    150
    151          if hook not in known_hooks:
    152                  raise ValueError('run_hook_script(): unknown hook [%s]' % hook)
    153
    154          if not import_hook_module(reimport = False):
    155                  _log.debug('cannot import hook module, not pulling hook')
    156                  return False
    157
    158          if hook in __current_hook_stack:
    159                  _log.error('hook-code cycle detected, aborting')
    160                  _log.error('current hook stack: %s', __current_hook_stack)
    161                  return False
    162
    163          __current_hook_stack.append(hook)
    164
    165          try:
    166                  hook_module.run_script(hook = hook)
    167          except StandardError:
    168                  _log.exception('error running hook script for [%s]', hook)
    169                  gmDispatcher.send (
    170                          signal = u'statustext',
    171                          msg = _('Error running hook [%s] script.') % hook,
    172                          beep = True
    173                  )
    174                  if __current_hook_stack[-1] != hook:
    175                          _log.error('hook nesting errror detected')
    176                          _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1])
    177                          _log.error('current hook stack: %s', __current_hook_stack)
    178                  else:
    179                          __current_hook_stack.pop()
    180                  return False
    181
    182          if __current_hook_stack[-1] != hook:
    183                  _log.error('hook nesting errror detected')
    184                  _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1])
    185                  _log.error('current hook stack: %s', __current_hook_stack)
    186          else:
    187                  __current_hook_stack.pop()
    188
    189          return True
    190  # ========================================================================
    191  if __name__ == '__main__':
    192
    193          run_hook_script(hook = 'shutdown-post-GUI')
    194          run_hook_script(hook = 'invalid hook')
    195
    196  # ========================================================================
    197