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  import io 
 29   
 30   
 31  # GNUmed libs 
 32  if __name__ == '__main__': 
 33          sys.path.insert(0, '../../') 
 34  from Gnumed.pycommon import gmDispatcher 
 35  from Gnumed.pycommon import gmTools 
 36   
 37   
 38  _log = logging.getLogger('gm.hook') 
 39  # ======================================================================== 
 40  known_hooks = [ 
 41          'post_patient_activation', 
 42          'post_person_creation', 
 43   
 44          'after_waiting_list_modified', 
 45   
 46          'shutdown-post-GUI', 
 47          'startup-after-GUI-init', 
 48          'startup-before-GUI', 
 49   
 50          'request_user_attention', 
 51          'app_activated_startup', 
 52          'app_activated', 
 53          'app_deactivated', 
 54   
 55          'after_substance_intake_modified', 
 56          'after_test_result_modified', 
 57          'after_soap_modified', 
 58          'after_code_link_modified', 
 59   
 60          'after_new_doc_created', 
 61          'before_print_doc', 
 62          'before_fax_doc', 
 63          'before_mail_doc', 
 64          'before_print_doc_part', 
 65          'before_fax_doc_part', 
 66          'before_mail_doc_part', 
 67          'before_external_doc_access', 
 68   
 69          'db_maintenance_warning' 
 70  ] 
 71   
 72  _log.debug('known hooks:') 
 73  for hook in known_hooks: 
 74          _log.debug(hook) 
 75   
 76  # ======================================================================== 
 77  hook_module = None 
 78   
79 -def import_hook_module(reimport=False):
80 81 global hook_module 82 if not reimport: 83 if hook_module is not None: 84 return True 85 86 # hardcoding path and script name allows us to 87 # not need configuration for it, the environment 88 # can always be detected at runtime (workplace etc) 89 script_name = 'hook_script.py' 90 script_path = os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')) 91 full_script = os.path.join(script_path, script_name) 92 93 if not os.access(full_script, os.F_OK): 94 _log.warning('creating default hook script') 95 f = io.open(full_script, mode = 'wt', encoding = 'utf8') 96 f.write(""" 97 # known hooks: 98 # %s 99 100 def run_script(hook=None): 101 pass 102 """ % '# '.join(known_hooks)) 103 f.close() 104 os.chmod(full_script, 384) 105 106 if os.path.islink(full_script): 107 gmDispatcher.send ( 108 signal = 'statustext', 109 msg = _('Script must not be a link: [%s].') % full_script 110 ) 111 return False 112 113 if not os.access(full_script, os.R_OK): 114 gmDispatcher.send ( 115 signal = 'statustext', 116 msg = _('Script must be readable by the calling user: [%s].') % full_script 117 ) 118 return False 119 120 script_stat_val = os.stat(full_script) 121 _log.debug('hook script stat(): %s', script_stat_val) 122 script_perms = stat.S_IMODE(script_stat_val.st_mode) 123 _log.debug('hook script mode: %s (oktal: %s)', script_perms, oct(script_perms)) 124 if script_perms != 384: # octal 0600 125 if os.name in ['nt']: 126 _log.warning('this platform does not support os.stat() file permission checking') 127 else: 128 gmDispatcher.send ( 129 signal = 'statustext', 130 msg = _('Script must be readable by the calling user only (permissions "0600"): [%s].') % full_script 131 ) 132 return False 133 134 try: 135 tmp = gmTools.import_module_from_directory(script_path, script_name) 136 except Exception: 137 _log.exception('cannot import hook script') 138 return False 139 140 hook_module = tmp 141 # if reimport: 142 # imp.reload(tmp) # this has well-known shortcomings ! 143 144 _log.info('hook script: %s', full_script) 145 return True
146 147 # ======================================================================== 148 __current_hook_stack = [] 149
150 -def run_hook_script(hook=None):
151 # NOTE: this just *might* be a huge security hole 152 153 _log.info('told to pull hook [%s]', hook) 154 155 if hook not in known_hooks: 156 raise ValueError('run_hook_script(): unknown hook [%s]' % hook) 157 158 if not import_hook_module(reimport = False): 159 _log.debug('cannot import hook module, not pulling hook') 160 return False 161 162 if hook in __current_hook_stack: 163 _log.error('hook-code cycle detected, aborting') 164 _log.error('current hook stack: %s', __current_hook_stack) 165 return False 166 167 __current_hook_stack.append(hook) 168 169 try: 170 hook_module.run_script(hook = hook) 171 except Exception: 172 _log.exception('error running hook script for [%s]', hook) 173 gmDispatcher.send ( 174 signal = 'statustext', 175 msg = _('Error running hook [%s] script.') % hook, 176 beep = True 177 ) 178 if __current_hook_stack[-1] != hook: 179 _log.error('hook nesting errror detected') 180 _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1]) 181 _log.error('current hook stack: %s', __current_hook_stack) 182 else: 183 __current_hook_stack.pop() 184 return False 185 186 if __current_hook_stack[-1] != hook: 187 _log.error('hook nesting errror detected') 188 _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1]) 189 _log.error('current hook stack: %s', __current_hook_stack) 190 else: 191 __current_hook_stack.pop() 192 193 return True
194 195 # ======================================================================== 196 if __name__ == '__main__': 197 198 if len(sys.argv) < 2: 199 sys.exit() 200 201 if sys.argv[1] != 'test': 202 sys.exit() 203 204 run_hook_script(hook = 'shutdown-post-GUI') 205 run_hook_script(hook = 'invalid hook') 206 207 # ======================================================================== 208