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