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
24 import os
25 import sys
26 import stat
27 import logging
28
29
30
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
77
78 global hook_module
79 if not reimport:
80 if hook_module is not None:
81 return True
82
83
84
85
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:
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
139
140
141 _log.info('hook script: %s', full_script)
142 return True
143
144 __current_hook_stack = []
145
147
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