1
2
3 __doc__ = """GNUmed client launcher.
4
5 This is the launcher for the GNUmed GUI client. It takes
6 care of all the pre- and post-GUI runtime environment setup.
7
8 --quiet
9 Be extra quiet and show only _real_ errors in the log.
10 --debug
11 Pre-set the [debug mode] checkbox in the login dialog to
12 increase verbosity in the log file. Useful for, well, debugging :-)
13 --slave
14 Pre-set the [enable remote control] checkbox in the login
15 dialog to enable the XML-RPC remote control feature.
16 --hipaa
17 Enable HIPAA functionality which has user impact.
18 --profile=<file>
19 Activate profiling and write profile data to <file>.
20 --text-domain=<text domain>
21 Set this to change the name of the language file to be loaded.
22 Note, this does not change the directory the file is searched in,
23 only the name of the file where messages are loaded from. The
24 standard textdomain is, of course, "gnumed.mo".
25 --log-file=<file>
26 Use this to change the name of the log file.
27 See gmLog2.py to find out where the standard log file would
28 end up.
29 --conf-file=<file>
30 Use configuration file <file> instead of searching for it in
31 standard locations.
32 --lang-gettext=<language>
33 Explicitly set the language to use in gettext translation. The very
34 same effect can be achieved by setting the environment variable $LANG
35 from a launcher script.
36 --override-schema-check
37 Continue loading the client even if the database schema version
38 and the client software version cannot be verified to be compatible.
39 --skip-update-check
40 Skip checking for client updates. This is useful during development
41 and when the update check URL is unavailable (down).
42 --local-import
43 Adjust the PYTHONPATH such that GNUmed can be run from a local source tree.
44 --ui=<ui type>
45 Start an alternative UI. Defaults to wxPython if not specified.
46 Valid values: chweb (CherryPy), wxp (wxPython), web (ProxiedWeb)
47 --version, -V
48 Show version information.
49 --help, -h, or -?
50 Show this help.
51 """
52
53 __version__ = "$Revision: 1.169 $"
54 __author__ = "H. Herb <hherb@gnumed.net>, K. Hilbert <Karsten.Hilbert@gmx.net>, I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
55 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
56
57
58 import sys
59 import os
60 import platform
61 import logging
62 import signal
63 import os.path
64 import shutil
65 import stat
66 import codecs
67
68
69
70 if __name__ != "__main__":
71 print "GNUmed startup: This is not intended to be imported as a module !"
72 print "-----------------------------------------------------------------"
73 print __doc__
74 sys.exit(1)
75
76
77
78 if os.name in ['posix'] and os.geteuid() == 0:
79 print """
80 GNUmed startup: GNUmed should not be run as root.
81 -------------------------------------------------
82
83 Running GNUmed as <root> can potentially put all
84 your medical data at risk. It is strongly advised
85 against. Please run GNUmed as a non-root user.
86 """
87 sys.exit(1)
88
89
90
91 current_client_version = u'GIT HEAD'
92
93 current_client_branch = u'GIT tree'
94
95 _log = None
96 _cfg = None
97 _old_sig_term = None
98 _known_short_options = u'h?V'
99 _known_long_options = [
100 u'debug',
101 u'slave',
102 u'skip-update-check',
103 u'profile=',
104 u'text-domain=',
105 u'log-file=',
106 u'conf-file=',
107 u'lang-gettext=',
108 u'ui=',
109 u'override-schema-check',
110 u'local-import',
111 u'help',
112 u'version',
113 u'hipaa'
114 ]
115
116 _known_ui_types = [
117 u'web',
118 u'wxp',
119 u'chweb'
120 ]
121
122 import_error_sermon = """
123 GNUmed startup: Cannot load GNUmed Python modules !
124 ---------------------------------------------------
125 CRITICAL ERROR: Program halted.
126
127 Please make sure you have:
128
129 1) the required third-party Python modules installed
130 2) the GNUmed Python modules linked or installed into site-packages/
131 (if you do not run from a CVS tree the installer should have taken care of that)
132 3) your PYTHONPATH environment variable set up correctly
133
134 sys.path is currently set to:
135
136 %s
137
138 If you are running from a copy of the CVS tree make sure you
139 did run gnumed/check-prerequisites.sh with good results.
140
141 If you still encounter errors after checking the above
142 requirements please ask on the mailing list.
143 """
144
145
146 missing_cli_config_file = u"""
147 GNUmed startup: Missing configuration file.
148 -------------------------------------------
149
150 You explicitly specified a configuration file
151 on the command line:
152
153 --conf-file=%s
154
155 The file does not exist, however.
156 """
157
158
159 no_config_files = u"""
160 GNUmed startup: Missing configuration files.
161 --------------------------------------------
162
163 None of the below candidate configuration
164 files could be found:
165
166 %s
167
168 Cannot run GNUmed without any of them.
169 """
170
171
172
174
175 if not u'--local-import' in sys.argv:
176 return
177
178 local_python_base_dir = os.path.dirname (
179 os.path.abspath(os.path.join(sys.argv[0], '..'))
180 )
181 print "Running from local source tree (%s) ..." % local_python_base_dir
182
183
184
185 link_name = os.path.join(local_python_base_dir, 'Gnumed')
186 if not os.path.exists(link_name):
187 real_dir = os.path.join(local_python_base_dir, 'client')
188 print "Creating module import symlink ..."
189 print ' real dir:', real_dir
190 print ' link:', link_name
191 os.symlink(real_dir, link_name)
192
193 print "Adjusting PYTHONPATH ..."
194 sys.path.insert(0, local_python_base_dir)
195
197
198 local_repo_path = os.path.expanduser(os.path.join (
199 '~',
200 '.gnumed',
201 'local_code',
202 str(current_client_branch)
203 ))
204 local_wxGladeWidgets_path = os.path.join(local_repo_path, 'Gnumed', 'wxGladeWidgets')
205
206 if not os.path.exists(local_wxGladeWidgets_path):
207 _log.debug('[%s] not found', local_wxGladeWidgets_path)
208 _log.info('local wxGlade widgets repository not available')
209 return
210
211 _log.info('local wxGlade widgets repository found:')
212 _log.info(local_wxGladeWidgets_path)
213
214 if not os.access(local_wxGladeWidgets_path, os.R_OK):
215 _log.error('invalid repo: no read access')
216 return
217
218 all_entries = os.listdir(os.path.join(local_repo_path, 'Gnumed'))
219 _log.debug('repo base contains: %s', all_entries)
220 all_entries.remove('wxGladeWidgets')
221 try:
222 all_entries.remove('__init__.py')
223 except ValueError:
224 _log.error('invalid repo: lacking __init__.py')
225 return
226 try:
227 all_entries.remove('__init__.pyc')
228 except ValueError:
229 pass
230
231 if len(all_entries) > 0:
232 _log.error('insecure repo: additional files or directories found')
233 return
234
235
236 stat_val = os.stat(local_wxGladeWidgets_path)
237 _log.debug('repo stat(): %s', stat_val)
238 perms = stat.S_IMODE(stat_val.st_mode)
239 _log.debug('repo permissions: %s (octal: %s)', perms, oct(perms))
240 if perms != 448:
241 if os.name in ['nt']:
242 _log.warning('this platform does not support os.stat() permission checking')
243 else:
244 _log.error('insecure repo: permissions not 0600')
245 return
246
247 print "Activating local wxGlade widgets repository (%s) ..." % local_wxGladeWidgets_path
248 sys.path.insert(0, local_repo_path)
249 _log.debug('sys.path with repo:')
250 _log.debug(sys.path)
251
263
265 _log.info(u'Starting up as main module (%s).', __version__)
266 _log.info(u'GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
267 _log.info(u'Platform: %s', platform.uname())
268 _log.info(u'Python %s on %s (%s)', sys.version, sys.platform, os.name)
269 try:
270 import lsb_release
271 _log.info(u'%s' % lsb_release.get_distro_information())
272 except ImportError:
273 pass
274 _log.info('process environment:')
275 for key, val in os.environ.items():
276 _log.info(u' %s: %s', (u'${%s}' % key).rjust(30), val)
277
282
284 from Gnumed.pycommon import gmCfg2
285
286 global _cfg
287 _cfg = gmCfg2.gmCfgData()
288 _cfg.add_cli (
289 short_options = _known_short_options,
290 long_options = _known_long_options
291 )
292
293 val = _cfg.get(option = '--debug', source_order = [('cli', 'return')])
294 if val is None:
295 val = False
296 _cfg.set_option (
297 option = u'debug',
298 value = val
299 )
300
301 val = _cfg.get(option = '--slave', source_order = [('cli', 'return')])
302 if val is None:
303 val = False
304 _cfg.set_option (
305 option = u'slave',
306 value = val
307 )
308
309 val = _cfg.get(option = '--skip-update-check', source_order = [('cli', 'return')])
310 if val is None:
311 val = False
312 _cfg.set_option (
313 option = u'skip-update-check',
314 value = val
315 )
316
317 val = _cfg.get(option = '--hipaa', source_order = [('cli', 'return')])
318 if val is None:
319 val = False
320 _cfg.set_option (
321 option = u'hipaa',
322 value = val
323 )
324
325 val = _cfg.get(option = '--local-import', source_order = [('cli', 'return')])
326 if val is None:
327 val = False
328 _cfg.set_option (
329 option = u'local-import',
330 value = val
331 )
332
333 _cfg.set_option (
334 option = u'client_version',
335 value = current_client_version
336 )
337
338 _cfg.set_option (
339 option = u'client_branch',
340 value = current_client_branch
341 )
342
343
345 _log.critical('SIGTERM (SIG%s) received, shutting down ...' % signum)
346 gmLog2.flush()
347 print 'GNUmed: SIGTERM (SIG%s) received, shutting down ...' % signum
348 if frame is not None:
349 print '%s::%s@%s' % (frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno)
350
351
352
353 if _old_sig_term in [None, signal.SIG_IGN]:
354 sys.exit(signal.SIGTERM)
355 else:
356 _old_sig_term(signum, frame)
357
361
372
374 src = [(u'cli', u'return')]
375
376 help_requested = (
377 _cfg.get(option = u'--help', source_order = src) or
378 _cfg.get(option = u'-h', source_order = src) or
379 _cfg.get(option = u'-?', source_order = src)
380 )
381
382 if help_requested:
383 print _(
384 'Help requested\n'
385 '--------------'
386 )
387 print __doc__
388 sys.exit(0)
389
408
409
411 """Create needed paths in user home directory."""
412
413 gnumed_DIR_README_TEXT = u"""GNUmed Electronic Medical Record
414
415 %s/
416
417 This directory should only ever contain files which the
418 user will come into direct contact with while using the
419 application (say, by selecting a file from the file system,
420 as when selecting document parts from files). You can create
421 subdirectories here as you see fit for the purpose.
422
423 This directory will also serve as the default directory when
424 GNUmed asks the user to select a directory for storing a
425 file.
426
427 Any files which are NOT intended for direct user interaction
428 but must be configured to live at a known location (say,
429 inter-application data exchange files) should be put under
430 the hidden directory "%s/".""" % (
431 os.path.expanduser(os.path.join('~', 'gnumed')),
432 os.path.expanduser(os.path.join('~', '.gnumed'))
433 )
434
435 gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')))
436 gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'spellcheck')))
437 gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'error_logs')))
438 gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed')))
439
440 README = codecs.open(os.path.expanduser(os.path.join('~', 'gnumed', '00_README')), 'wb', 'utf8')
441 README.write(gnumed_DIR_README_TEXT)
442 README.close()
443
444 paths = gmTools.gmPaths(app_name = u'gnumed')
445
446 open(os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed.conf')), 'a+').close()
447
450
452 """Detect and setup access to GNUmed config file.
453
454 Parts of this will have limited value due to
455 wxPython not yet being available.
456 """
457
458 enc = gmI18N.get_encoding()
459 paths = gmTools.gmPaths(app_name = u'gnumed')
460
461 candidates = [
462
463 [u'workbase', os.path.join(paths.working_dir, 'gnumed.conf')],
464
465 [u'system', os.path.join(paths.system_config_dir, 'gnumed-client.conf')],
466
467 [u'user', os.path.join(paths.user_config_dir, 'gnumed.conf')],
468
469 [u'local', os.path.join(paths.local_base_dir, 'gnumed.conf')]
470 ]
471
472 explicit_fname = _cfg.get(option = u'--conf-file', source_order = [(u'cli', u'return')])
473 if explicit_fname is None:
474 candidates.append([u'explicit', None])
475 else:
476 candidates.append([u'explicit', explicit_fname])
477
478 for candidate in candidates:
479 _cfg.add_file_source (
480 source = candidate[0],
481 file = candidate[1],
482 encoding = enc
483 )
484
485
486 if explicit_fname is not None:
487 if _cfg.source_files['explicit'] is None:
488 _log.error('--conf-file argument does not exist')
489 sys.exit(missing_cli_config_file % explicit_fname)
490
491
492 found_any_file = False
493 for f in _cfg.source_files.values():
494 if f is not None:
495 found_any_file = True
496 break
497 if not found_any_file:
498 _log.error('no config file found at all')
499 sys.exit(no_config_files % '\n '.join(candidates))
500
501
502 fname = u'mime_type2file_extension.conf'
503 _cfg.add_file_source (
504 source = u'user-mime',
505 file = os.path.join(paths.user_config_dir, fname),
506 encoding = enc
507 )
508 _cfg.add_file_source (
509 source = u'system-mime',
510 file = os.path.join(paths.system_config_dir, fname),
511 encoding = enc
512 )
513
515 global ui_type
516
517 ui_type = _cfg.get(option = u'--ui', source_order = [(u'cli', u'return')])
518
519 if ui_type in [True, False, None]:
520 ui_type = 'wxp'
521
522 ui_type = ui_type.strip()
523
524 if ui_type not in _known_ui_types:
525 _log.error('unknown UI type: %s', ui_type)
526 _log.debug('known UI types: %s', str(_known_ui_types))
527 print "GNUmed startup: Unknown UI type (%s). Defaulting to wxPython client." % ui_type
528 ui_type = 'wxp'
529
530 _log.debug('UI type: %s', ui_type)
531
533
534 db_version = gmPG2.map_client_branch2required_db_version[current_client_branch]
535 _log.info('client expects database version [%s]', db_version)
536 _cfg.set_option (
537 option = u'database_version',
538 value = db_version
539 )
540
541
542 timezone = _cfg.get (
543 group = u'backend',
544 option = 'client timezone',
545 source_order = [
546 ('explicit', 'return'),
547 ('workbase', 'return'),
548 ('local', 'return'),
549 ('user', 'return'),
550 ('system', 'return')
551 ]
552 )
553 if timezone is not None:
554 gmPG2.set_default_client_timezone(timezone)
555
558
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585 logging.raiseExceptions = False
586
597
598
599
600 setup_python_path()
601 setup_logging()
602 log_startup_info()
603 setup_console_exception_handler()
604 setup_cli()
605 setup_signal_handlers()
606 setup_local_repo_path()
607
608 from Gnumed.pycommon import gmI18N, gmTools, gmDateTime, gmHooks
609 setup_locale()
610 handle_help_request()
611 handle_version_request()
612 setup_paths_and_files()
613 setup_date_time()
614 setup_cfg()
615 setup_ui_type()
616
617 from Gnumed.pycommon import gmPG2
618 if ui_type in [u'web']:
619 gmPG2.auto_request_login_params = False
620 setup_backend()
621
622
623 gmHooks.run_hook_script(hook = u'startup-before-GUI')
624
625 if ui_type == u'wxp':
626 from Gnumed.wxpython import gmGuiMain
627 profile_file = _cfg.get(option = u'--profile', source_order = [(u'cli', u'return')])
628 if profile_file is not None:
629 _log.info('writing profiling data into %s', profile_file)
630 import profile
631 profile.run('gmGuiMain.main()', profile_file)
632 else:
633 gmGuiMain.main()
634 elif ui_type == u'web':
635 from Gnumed.proxiedpyjamas import gmWebGuiServer
636 gmWebGuiServer.main()
637
638 elif ui_type == u'chweb':
639 from Gnumed.CherryPy import gmGuiWeb
640 gmGuiWeb.main()
641
642 gmHooks.run_hook_script(hook = u'shutdown-post-GUI')
643
644 shutdown_backend()
645 shutdown_tmp_dir()
646 _log.info('Normally shutting down as main module.')
647 shutdown_logging()
648
649
650