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 print "Running from local source tree ..."
179
180 local_python_base_dir = os.path.dirname (
181 os.path.abspath(os.path.join(sys.argv[0], '..'))
182 )
183
184
185
186 link_name = os.path.join(local_python_base_dir, 'Gnumed')
187 if not os.path.exists(link_name):
188 real_dir = os.path.join(local_python_base_dir, 'client')
189 print "Creating module import symlink ..."
190 print ' real dir:', real_dir
191 print ' link:', link_name
192 os.symlink(real_dir, link_name)
193
194 print "Adjusting PYTHONPATH ..."
195 sys.path.insert(0, local_python_base_dir)
196
198
199 local_repo_path = os.path.expanduser(os.path.join (
200 '~',
201 '.gnumed',
202 'local_code',
203 str(current_client_branch)
204 ))
205 local_wxGladeWidgets_path = os.path.join(local_repo_path, 'Gnumed', 'wxGladeWidgets')
206
207 if not os.path.exists(local_wxGladeWidgets_path):
208 _log.debug('[%s] not found', local_wxGladeWidgets_path)
209 _log.info('local wxGlade widgets repository not available')
210 return
211
212 _log.info('local wxGlade widgets repository found:')
213 _log.info(local_wxGladeWidgets_path)
214
215 if not os.access(local_wxGladeWidgets_path, os.R_OK):
216 _log.error('invalid repo: no read access')
217 return
218
219 all_entries = os.listdir(os.path.join(local_repo_path, 'Gnumed'))
220 _log.debug('repo base contains: %s', all_entries)
221 all_entries.remove('wxGladeWidgets')
222 try:
223 all_entries.remove('__init__.py')
224 except ValueError:
225 _log.error('invalid repo: lacking __init__.py')
226 return
227 try:
228 all_entries.remove('__init__.pyc')
229 except ValueError:
230 pass
231
232 if len(all_entries) > 0:
233 _log.error('insecure repo: additional files or directories found')
234 return
235
236
237 stat_val = os.stat(local_wxGladeWidgets_path)
238 _log.debug('repo stat(): %s', stat_val)
239 perms = stat.S_IMODE(stat_val.st_mode)
240 _log.debug('repo permissions: %s (octal: %s)', perms, oct(perms))
241 if perms != 448:
242 if os.name in ['nt']:
243 _log.warning('this platform does not support os.stat() permission checking')
244 else:
245 _log.error('insecure repo: permissions not 0600')
246 return
247
248 print "Activating local wxGlade widgets repository ..."
249 sys.path.insert(0, local_repo_path)
250 _log.debug('sys.path with repo:')
251 _log.debug(sys.path)
252
264
266 _log.info(u'Starting up as main module (%s).', __version__)
267 _log.info(u'GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
268 _log.info(u'Platform: %s', platform.uname())
269 _log.info(u'Python %s on %s (%s)', sys.version, sys.platform, os.name)
270 try:
271 import lsb_release
272 _log.info(u'%s' % lsb_release.get_distro_information())
273 except ImportError:
274 pass
275 _log.info('process environment:')
276 for key, val in os.environ.items():
277 _log.info(u' %s: %s', (u'${%s}' % key).rjust(30), val)
278
283
285 from Gnumed.pycommon import gmCfg2
286
287 global _cfg
288 _cfg = gmCfg2.gmCfgData()
289 _cfg.add_cli (
290 short_options = _known_short_options,
291 long_options = _known_long_options
292 )
293
294 val = _cfg.get(option = '--debug', source_order = [('cli', 'return')])
295 if val is None:
296 val = False
297 _cfg.set_option (
298 option = u'debug',
299 value = val
300 )
301
302 val = _cfg.get(option = '--slave', source_order = [('cli', 'return')])
303 if val is None:
304 val = False
305 _cfg.set_option (
306 option = u'slave',
307 value = val
308 )
309
310 val = _cfg.get(option = '--skip-update-check', source_order = [('cli', 'return')])
311 if val is None:
312 val = False
313 _cfg.set_option (
314 option = u'skip-update-check',
315 value = val
316 )
317
318 val = _cfg.get(option = '--hipaa', source_order = [('cli', 'return')])
319 if val is None:
320 val = False
321 _cfg.set_option (
322 option = u'hipaa',
323 value = val
324 )
325
326 val = _cfg.get(option = '--local-import', source_order = [('cli', 'return')])
327 if val is None:
328 val = False
329 _cfg.set_option (
330 option = u'local-import',
331 value = val
332 )
333
334 _cfg.set_option (
335 option = u'client_version',
336 value = current_client_version
337 )
338
339 _cfg.set_option (
340 option = u'client_branch',
341 value = current_client_branch
342 )
343
344
346 _log.critical('SIGTERM (SIG%s) received, shutting down ...' % signum)
347 gmLog2.flush()
348 print 'GNUmed: SIGTERM (SIG%s) received, shutting down ...' % signum
349 if frame is not None:
350 print '%s::%s@%s' % (frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno)
351
352
353
354 if _old_sig_term in [None, signal.SIG_IGN]:
355 sys.exit(signal.SIGTERM)
356 else:
357 _old_sig_term(signum, frame)
358
362
373
375 src = [(u'cli', u'return')]
376
377 help_requested = (
378 _cfg.get(option = u'--help', source_order = src) or
379 _cfg.get(option = u'-h', source_order = src) or
380 _cfg.get(option = u'-?', source_order = src)
381 )
382
383 if help_requested:
384 print _(
385 'Help requested\n'
386 '--------------'
387 )
388 print __doc__
389 sys.exit(0)
390
409
410
412 """Create needed paths in user home directory."""
413
414 gnumed_DIR_README_TEXT = u"""GNUmed Electronic Medical Record
415
416 %s/
417
418 This directory should only ever contain file which the user
419 will come into direct contact with during using the
420 application (say, by selecting a file from the file system,
421 as when selecting document parts from files). You can create
422 subdirectories here as you see fit for the purpose.
423
424 This directory will also serve as the default directory when
425 GNUmed asks the user to select a directory for storing a
426 file.
427
428 Any files which are NOT intended for direct user interaction
429 but must be configured to live at a known location (say,
430 inter-application data exchange files) should be put under
431 the hidden directory "%s/".""" % (
432 os.path.expanduser(os.path.join('~', 'gnumed')),
433 os.path.expanduser(os.path.join('~', '.gnumed'))
434 )
435
436 gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')))
437 gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'spellcheck')))
438 gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'error_logs')))
439 gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed')))
440
441 README = codecs.open(os.path.expanduser(os.path.join('~', 'gnumed', '00_README')), 'wb', 'utf8')
442 README.write(gnumed_DIR_README_TEXT)
443 README.close()
444
445 paths = gmTools.gmPaths(app_name = u'gnumed')
446
447 open(os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed.conf')), 'a+').close()
448
451
453 """Detect and setup access to GNUmed config file.
454
455 Parts of this will have limited value due to
456 wxPython not yet being available.
457 """
458
459 enc = gmI18N.get_encoding()
460 paths = gmTools.gmPaths(app_name = u'gnumed')
461
462 candidates = [
463
464 [u'workbase', os.path.join(paths.working_dir, 'gnumed.conf')],
465
466 [u'system', os.path.join(paths.system_config_dir, 'gnumed-client.conf')],
467
468 [u'user', os.path.join(paths.user_config_dir, 'gnumed.conf')],
469
470 [u'local', os.path.join(paths.local_base_dir, 'gnumed.conf')]
471 ]
472
473 explicit_fname = _cfg.get(option = u'--conf-file', source_order = [(u'cli', u'return')])
474 if explicit_fname is None:
475 candidates.append([u'explicit', None])
476 else:
477 candidates.append([u'explicit', explicit_fname])
478
479 for candidate in candidates:
480 _cfg.add_file_source (
481 source = candidate[0],
482 file = candidate[1],
483 encoding = enc
484 )
485
486
487 if explicit_fname is not None:
488 if _cfg.source_files['explicit'] is None:
489 _log.error('--conf-file argument does not exist')
490 sys.exit(missing_cli_config_file % explicit_fname)
491
492
493 found_any_file = False
494 for f in _cfg.source_files.values():
495 if f is not None:
496 found_any_file = True
497 break
498 if not found_any_file:
499 _log.error('no config file found at all')
500 sys.exit(no_config_files % '\n '.join(candidates))
501
502
503 fname = u'mime_type2file_extension.conf'
504 _cfg.add_file_source (
505 source = u'user-mime',
506 file = os.path.join(paths.user_config_dir, fname),
507 encoding = enc
508 )
509 _cfg.add_file_source (
510 source = u'system-mime',
511 file = os.path.join(paths.system_config_dir, fname),
512 encoding = enc
513 )
514
516 global ui_type
517
518 ui_type = _cfg.get(option = u'--ui', source_order = [(u'cli', u'return')])
519
520 if ui_type in [True, False, None]:
521 ui_type = 'wxp'
522
523 ui_type = ui_type.strip()
524
525 if ui_type not in _known_ui_types:
526 _log.error('unknown UI type: %s', ui_type)
527 _log.debug('known UI types: %s', str(_known_ui_types))
528 print "GNUmed startup: Unknown UI type (%s). Defaulting to wxPython client." % ui_type
529 ui_type = 'wxp'
530
531 _log.debug('UI type: %s', ui_type)
532
534
535 db_version = gmPG2.map_client_branch2required_db_version[current_client_branch]
536 _log.info('client expects database version [%s]', db_version)
537 _cfg.set_option (
538 option = u'database_version',
539 value = db_version
540 )
541
542
543 timezone = _cfg.get (
544 group = u'backend',
545 option = 'client timezone',
546 source_order = [
547 ('explicit', 'return'),
548 ('workbase', 'return'),
549 ('local', 'return'),
550 ('user', 'return'),
551 ('system', 'return')
552 ]
553 )
554 if timezone is not None:
555 gmPG2.set_default_client_timezone(timezone)
556
559
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
586 logging.raiseExceptions = False
587
598
599
600
601 setup_python_path()
602 setup_logging()
603 log_startup_info()
604 setup_console_exception_handler()
605 setup_cli()
606 setup_signal_handlers()
607 setup_local_repo_path()
608
609 from Gnumed.pycommon import gmI18N, gmTools, gmDateTime, gmHooks
610 setup_locale()
611 handle_help_request()
612 handle_version_request()
613 setup_paths_and_files()
614 setup_date_time()
615 setup_cfg()
616 setup_ui_type()
617
618 from Gnumed.pycommon import gmPG2
619 if ui_type in [u'web']:
620 gmPG2.auto_request_login_params = False
621 setup_backend()
622
623
624 gmHooks.run_hook_script(hook = u'startup-before-GUI')
625
626 if ui_type == u'wxp':
627 from Gnumed.wxpython import gmGuiMain
628 profile_file = _cfg.get(option = u'--profile', source_order = [(u'cli', u'return')])
629 if profile_file is not None:
630 _log.info('writing profiling data into %s', profile_file)
631 import profile
632 profile.run('gmGuiMain.main()', profile_file)
633 else:
634 gmGuiMain.main()
635 elif ui_type == u'web':
636 from Gnumed.proxiedpyjamas import gmWebGuiServer
637 gmWebGuiServer.main()
638
639 elif ui_type == u'chweb':
640 from Gnumed.CherryPy import gmGuiWeb
641 gmGuiWeb.main()
642
643 gmHooks.run_hook_script(hook = u'shutdown-post-GUI')
644
645 shutdown_backend()
646 shutdown_tmp_dir()
647 _log.info('Normally shutting down as main module.')
648 shutdown_logging()
649
650
651