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 import io
29
30
31
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
80
81 global hook_module
82 if not reimport:
83 if hook_module is not None:
84 return True
85
86
87
88
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:
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
142
143
144 _log.info('hook script: %s', full_script)
145 return True
146
147
148 __current_hook_stack = []
149
151
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