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  # ======================================================================== 
 41  known_hooks = [ 
 42          'post_patient_activation', 
 43          'post_person_creation', 
 44   
 45          'after_waiting_list_modified', 
 46   
 47          'shutdown-post-GUI', 
 48          'startup-after-GUI-init', 
 49          'startup-before-GUI', 
 50   
 51          'request_user_attention', 
 52          'app_activated_startup', 
 53          'app_activated', 
 54          'app_deactivated', 
 55   
 56          'after_substance_intake_modified', 
 57          'after_test_result_modified', 
 58          'after_soap_modified', 
 59          'after_code_link_modified', 
 60   
 61          'after_new_doc_created', 
 62          'before_print_doc', 
 63          'before_fax_doc', 
 64          'before_mail_doc', 
 65          'before_print_doc_part', 
 66          'before_fax_doc_part', 
 67          'before_mail_doc_part', 
 68          'before_external_doc_access', 
 69   
 70          'db_maintenance_warning' 
 71  ] 
 72   
 73   
 74  README_pat_dir = """Directory for data files containing the current patient. 
 75   
 76  Whenever the patient is changed GNUmed will export 
 77  formatted demographics into files in this directory 
 78  for use by 3rd party software. 
 79   
 80  Currently exported formats: 
 81   
 82  GDT, VCF (vcard), MCF (mecard), XML (linuxmednews)""" 
 83   
 84   
 85  HOOK_SCRIPT_EXAMPLE = """#!/usr/bin/python3 
 86  # -*- coding: utf-8 -*- 
 87  # 
 88  #=========================================================================== 
 89  # 
 90  # Example script to be run off GNUmed hooks. 
 91  # 
 92  # It can print a message to stdout whenever any hook is invoked. 
 93  # 
 94  # Copy this file to ~/.gnumed/scripts/hook_script.py and modify as needed. 
 95  # 
 96  # Known hooks: 
 97  # 
 98  #       %s 
 99  # 
100  #=========================================================================== 
101  # SPDX-License-Identifier: GPL-2.0-or-later 
102  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
103  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
104   
105   
106  import os 
107   
108   
109  from Gnumed.pycommon import gmWorkerThread 
110  from Gnumed.pycommon import gmTools 
111   
112  from Gnumed.business import gmPerson 
113   
114  from Gnumed.wxpython import gmGuiHelpers 
115  from Gnumed.wxpython import gmPatSearchWidgets 
116   
117   
118  PAT_DIR = os.path.expanduser(os.path.join('~', '.gnumed', 'current_patient')) 
119  README_pat_dir = \"\"\"%s 
120  \"\"\" 
121  CURR_PAT = None 
122   
123  #=========================================================================== 
124  def on_startup_after_GUI_init(): 
125          # examine external patient sources 
126          gmPatSearchWidgets.get_person_from_external_sources(search_immediately = False, activate_immediately = True) 
127   
128   
129  def request_user_attention(): 
130          # signal user to look at GNUmed 
131          gmGuiHelpers.gm_show_info(_('Hey, GNUmed wants you to take a look at it !')) 
132   
133   
134  #def on_app_activated_startup(): 
135          #pass 
136   
137   
138  def on_app_activated(): 
139          # might want to look at external sources again 
140          gmPatSearchWidgets.get_person_from_external_sources(search_immediately = False, activate_immediately = True) 
141   
142   
143  def _export_patient_demographics(): 
144          if CURR_PAT is None: 
145                  return 
146   
147          fname = os.path.join(PAT_DIR, 'patient') 
148          CURR_PAT.export_as_gdt(filename = fname + '.gdt', encoding = 'cp850') 
149          CURR_PAT.export_as_xml_linuxmednews(filename = fname + '.xml') 
150          CURR_PAT.export_as_vcard(filename = fname + '.vcf') 
151          CURR_PAT.export_as_mecard(filename = fname + '.mcf') 
152   
153   
154  #def on_app_deactivated(): 
155  def on_post_patient_activation(): 
156          # might want to export the active patient into an xDT file 
157          global CURR_PAT 
158          curr_pat = gmPerson.gmCurrentPatient() 
159          if not curr_pat.connected: 
160                  CURR_PAT = None 
161                  return 
162   
163          CURR_PAT = curr_pat 
164          gmWorkerThread.execute_in_worker_thread ( 
165                  payload_function = _export_patient_demographics, 
166                  worker_name = 'current_patient_demographics_exporter' 
167          ) 
168   
169   
170  #=========================================================================== 
171  gmTools.mkdir(PAT_DIR) 
172  gmTools.create_directory_description_file(directory = PAT_DIR, readme = README_pat_dir) 
173   
174  # main entry point 
175  def run_script(hook=None): 
176   
177          if hook is None: 
178                  hook = _('no hook specified, please report bug') 
179                  print('hook_script.py::run_script():', hook) 
180   
181          #print('GNUmed invoked the hook:', hook) 
182   
183          # a few examples: 
184   
185          #if hook == 'startup-after-GUI-init': 
186          #       on_startup_after_GUI_init() 
187   
188          #if hook == 'request_user_attention': 
189          #       on_request_user_attention() 
190   
191          #if hook == 'app_activated_startup': 
192          #       on_app_activated_startup() 
193   
194          #if hook == 'app_activated': 
195          #       on_app_activated() 
196   
197          #if hook == 'app_deactivated': 
198          #       on_app_deactivated() 
199   
200          if hook == 'post_patient_activation': 
201                  on_post_patient_activation() 
202   
203          return 
204  """ % ( 
205          '\n#\t'.join(known_hooks), 
206          README_pat_dir 
207  ) 
208   
209   
210  # hardcoding path and script name allows us to not 
211  # need configuration for it, the environment can 
212  # always be detected at runtime (workplace etc) 
213  HOOK_SCRIPT_NAME = 'hook_script.py' 
214  HOOK_SCRIPT_DIR = os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')) 
215  HOOK_SCRIPT_FULL_NAME = os.path.join(HOOK_SCRIPT_DIR, HOOK_SCRIPT_NAME) 
216   
217   
218  README_hook_dir = """Directory for scripts called from hooks at client runtime. 
219   
220  Known hooks: 
221   
222          %s 
223   
224  You can use <%s.example> as a script template. 
225  """ % ( 
226          '\n\t'.join(known_hooks), 
227          HOOK_SCRIPT_NAME 
228  ) 
229   
230   
231  # ======================================================================== 
232 -def setup_hook_dir():
233 _log.debug('known hooks:') 234 for hook in known_hooks: 235 _log.debug(hook) 236 gmTools.mkdir(HOOK_SCRIPT_DIR) 237 gmTools.create_directory_description_file(directory = HOOK_SCRIPT_DIR, readme = README_hook_dir) 238 # create hook script example/template 239 example_name = os.path.join(HOOK_SCRIPT_DIR, HOOK_SCRIPT_NAME + '.example') 240 example = open(example_name, mode = 'wt', encoding = 'utf8') 241 example.write(HOOK_SCRIPT_EXAMPLE) 242 example.close() 243 os.chmod(example_name, 384)
244 245 # ======================================================================== 246 hook_module = None 247
248 -def import_hook_module(reimport=False):
249 250 global hook_module 251 if not reimport: 252 if hook_module is not None: 253 return True 254 255 if not os.access(HOOK_SCRIPT_FULL_NAME, os.F_OK): 256 _log.warning('creating default hook script') 257 f = io.open(HOOK_SCRIPT_FULL_NAME, mode = 'wt', encoding = 'utf8') 258 f.write(""" 259 # known hooks: 260 # %s 261 262 def run_script(hook=None): 263 pass 264 """ % '\n#\t'.join(known_hooks)) 265 f.close() 266 os.chmod(HOOK_SCRIPT_FULL_NAME, 384) 267 268 if os.path.islink(HOOK_SCRIPT_FULL_NAME): 269 gmDispatcher.send ( 270 signal = 'statustext', 271 msg = _('Script must not be a link: [%s].') % HOOK_SCRIPT_FULL_NAME 272 ) 273 return False 274 275 if not os.access(HOOK_SCRIPT_FULL_NAME, os.R_OK): 276 gmDispatcher.send ( 277 signal = 'statustext', 278 msg = _('Script must be readable by the calling user: [%s].') % HOOK_SCRIPT_FULL_NAME 279 ) 280 return False 281 282 script_stat_val = os.stat(HOOK_SCRIPT_FULL_NAME) 283 _log.debug('hook script stat(): %s', script_stat_val) 284 script_perms = stat.S_IMODE(script_stat_val.st_mode) 285 _log.debug('hook script mode: %s (oktal: %s)', script_perms, oct(script_perms)) 286 if script_perms != 384: # octal 0600 287 if os.name in ['nt']: 288 _log.warning('this platform does not support os.stat() file permission checking') 289 else: 290 gmDispatcher.send ( 291 signal = 'statustext', 292 msg = _('Script must be readable by the calling user only (permissions "0600"): [%s].') % HOOK_SCRIPT_FULL_NAME 293 ) 294 return False 295 296 try: 297 tmp = gmTools.import_module_from_directory(HOOK_SCRIPT_DIR, HOOK_SCRIPT_NAME) 298 except Exception: 299 _log.exception('cannot import hook script') 300 return False 301 302 hook_module = tmp 303 # if reimport: 304 # imp.reload(tmp) # this has well-known shortcomings ! 305 306 _log.info('hook script: %s', HOOK_SCRIPT_FULL_NAME) 307 return True
308 309 # ======================================================================== 310 __current_hook_stack = [] 311
312 -def run_hook_script(hook=None):
313 # NOTE: this just *might* be a huge security hole 314 315 _log.info('told to pull hook [%s]', hook) 316 317 if hook not in known_hooks: 318 raise ValueError('run_hook_script(): unknown hook [%s]' % hook) 319 320 if not import_hook_module(reimport = False): 321 _log.debug('cannot import hook module, not pulling hook') 322 return False 323 324 if hook in __current_hook_stack: 325 _log.error('hook-code cycle detected, aborting') 326 _log.error('current hook stack: %s', __current_hook_stack) 327 return False 328 329 __current_hook_stack.append(hook) 330 331 try: 332 hook_module.run_script(hook = hook) 333 except Exception: 334 _log.exception('error running hook script for [%s]', hook) 335 gmDispatcher.send ( 336 signal = 'statustext', 337 msg = _('Error running hook [%s] script.') % hook, 338 beep = True 339 ) 340 if __current_hook_stack[-1] != hook: 341 _log.error('hook nesting error detected') 342 _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1]) 343 _log.error('current hook stack: %s', __current_hook_stack) 344 else: 345 __current_hook_stack.pop() 346 return False 347 348 if __current_hook_stack[-1] != hook: 349 _log.error('hook nesting error detected') 350 _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1]) 351 _log.error('current hook stack: %s', __current_hook_stack) 352 else: 353 __current_hook_stack.pop() 354 355 return True
356 357 # ======================================================================== 358 359 setup_hook_dir() 360 361 if __name__ == '__main__': 362 363 if len(sys.argv) < 2: 364 sys.exit() 365 366 if sys.argv[1] != 'test': 367 sys.exit() 368 369 run_hook_script(hook = 'shutdown-post-GUI') 370 run_hook_script(hook = 'post_patient_activation') 371 run_hook_script(hook = 'invalid hook') 372 373 # ======================================================================== 374