Bump version to 0.8.5.
[gnumed:gnumed-fedora.git] / gnumed / gnumed / client / gnumed.py
1 #!/usr/bin/env python
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 (details at http://www.gnu.org)"
56
57 # standard library
58 import sys, os, os.path, signal, logging, platform
59
60
61 # do not run as module
62 if __name__ != "__main__":
63         print "GNUmed startup: This is not intended to be imported as a module !"
64         print "-----------------------------------------------------------------"
65         print __doc__
66         sys.exit(1)
67
68
69 # do not run as root
70 if os.name in ['posix'] and os.geteuid() == 0:
71         print """
72 GNUmed startup: GNUmed should not be run as root.
73 -------------------------------------------------
74
75 Running GNUmed as <root> can potentially put all
76 your medical data at risk. It is strongly advised
77 against. Please run GNUmed as a non-root user.
78 """
79         sys.exit(1)
80
81 #----------------------------------------------------------
82 current_client_version = u'0.8.5'
83 current_client_branch = u'0.8'
84
85 _log = None
86 _cfg = None
87 _old_sig_term = None
88 _known_short_options = u'h?V'
89 _known_long_options = [
90         u'debug',
91         u'slave',
92         u'skip-update-check',
93         u'profile=',
94         u'text-domain=',
95         u'log-file=',
96         u'conf-file=',
97         u'lang-gettext=',
98         u'ui=',
99         u'override-schema-check',
100         u'local-import',
101         u'help',
102         u'version',
103         u'hipaa'
104 ]
105
106 _known_ui_types = [
107         u'web',
108         u'wxp',
109         u'chweb'
110 ]
111
112 import_error_sermon = """
113 GNUmed startup: Cannot load GNUmed Python modules !
114 ---------------------------------------------------
115 CRITICAL ERROR: Program halted.
116
117 Please make sure you have:
118
119  1) the required third-party Python modules installed
120  2) the GNUmed Python modules linked or installed into site-packages/
121     (if you do not run from a CVS tree the installer should have taken care of that)
122  3) your PYTHONPATH environment variable set up correctly
123
124 sys.path is currently set to:
125
126  %s
127
128 If you are running from a copy of the CVS tree make sure you
129 did run gnumed/check-prerequisites.sh with good results.
130
131 If you still encounter errors after checking the above
132 requirements please ask on the mailing list.
133 """
134
135
136 missing_cli_config_file = u"""
137 GNUmed startup: Missing configuration file.
138 -------------------------------------------
139
140 You explicitly specified a configuration file
141 on the command line:
142
143         --conf-file=%s
144
145 The file does not exist, however.
146 """
147
148
149 no_config_files = u"""
150 GNUmed startup: Missing configuration files.
151 --------------------------------------------
152
153 None of the below candidate configuration
154 files could be found:
155
156  %s
157
158 Cannot run GNUmed without any of them.
159 """
160 #==========================================================
161 # convenience functions
162 #==========================================================
163 def setup_python_path():
164
165         if not u'--local-import' in sys.argv:
166                 return
167
168         print "GNUmed startup: Running from local source tree."
169         print "-----------------------------------------------"
170
171         local_python_base_dir = os.path.dirname (
172                 os.path.abspath(os.path.join(sys.argv[0], '..'))
173         )
174
175         # does the path exist at all, physically ?
176         # (*broken* links are reported as False)
177         link_name = os.path.join(local_python_base_dir, 'Gnumed')
178         if not os.path.exists(link_name):
179                 real_dir = os.path.join(local_python_base_dir, 'client')
180                 print "Creating module import symlink ..."
181                 print ' real dir:', real_dir
182                 print '     link:', link_name
183                 os.symlink(real_dir, link_name)
184
185         print "Adjusting PYTHONPATH ..."
186         sys.path.insert(0, local_python_base_dir)
187 #==========================================================
188 def setup_logging():
189         try:
190                 from Gnumed.pycommon import gmLog2 as _gmLog2
191         except ImportError:
192                 sys.exit(import_error_sermon % '\n '.join(sys.path))
193
194         global gmLog2
195         gmLog2 = _gmLog2
196
197         global _log
198         _log = logging.getLogger('gm.launcher')
199 #==========================================================
200 def log_startup_info():
201         _log.info('Starting up as main module (%s).', __version__)
202         _log.info('GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
203         _log.info('Platform: %s', platform.uname())
204         _log.info('Python %s on %s (%s)', sys.version, sys.platform, os.name)
205         try:
206                 import lsb_release
207                 _log.info('%s' % lsb_release.get_distro_information())
208         except ImportError:
209                 pass
210 #==========================================================
211 def setup_console_exception_handler():
212         from Gnumed.pycommon.gmTools import handle_uncaught_exception_console
213
214         sys.excepthook = handle_uncaught_exception_console
215 #==========================================================
216 def setup_cli():
217         from Gnumed.pycommon import gmCfg2
218
219         global _cfg
220         _cfg = gmCfg2.gmCfgData()
221         _cfg.add_cli (
222                 short_options = _known_short_options,
223                 long_options = _known_long_options
224         )
225
226         val = _cfg.get(option = '--debug', source_order = [('cli', 'return')])
227         if val is None:
228                 val = False
229         _cfg.set_option (
230                 option = u'debug',
231                 value = val
232         )
233
234         val = _cfg.get(option = '--slave', source_order = [('cli', 'return')])
235         if val is None:
236                 val = False
237         _cfg.set_option (
238                 option = u'slave',
239                 value = val
240         )
241
242         val = _cfg.get(option = '--skip-update-check', source_order = [('cli', 'return')])
243         if val is None:
244                 val = False
245         _cfg.set_option (
246                 option = u'skip-update-check',
247                 value = val
248         )
249
250         val = _cfg.get(option = '--hipaa', source_order = [('cli', 'return')])
251         if val is None:
252                 val = False
253         _cfg.set_option (
254                 option = u'hipaa',
255                 value = val
256         )
257
258         val = _cfg.get(option = '--local-import', source_order = [('cli', 'return')])
259         if val is None:
260                 val = False
261         _cfg.set_option (
262                 option = u'local-import',
263                 value = val
264         )
265
266         _cfg.set_option (
267                 option = u'client_version',
268                 value = current_client_version
269         )
270
271         _cfg.set_option (
272                 option = u'client_branch',
273                 value = current_client_branch
274         )
275
276 #==========================================================
277 def handle_sig_term(signum, frame):
278         _log.critical('SIGTERM (SIG%s) received, shutting down ...' % signum)
279         gmLog2.flush()
280         print 'GNUmed: SIGTERM (SIG%s) received, shutting down ...' % signum
281         if frame is not None:
282                 print '%s::%s@%s' % (frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno)
283
284         # FIXME: need to do something useful here
285
286         if _old_sig_term in [None, signal.SIG_IGN]:
287                 sys.exit(signal.SIGTERM)
288         else:
289                 _old_sig_term(signum, frame)
290 #----------------------------------------------------------
291 def setup_signal_handlers():
292         global _old_sig_term
293         old_sig_term = signal.signal(signal.SIGTERM, handle_sig_term)
294 #==========================================================
295 def setup_locale():
296         gmI18N.activate_locale()
297
298         td = _cfg.get(option = '--text-domain', source_order = [('cli', 'return')])
299         l =  _cfg.get(option = '--lang-gettext', source_order = [('cli', 'return')])
300         gmI18N.install_domain(domain = td, language = l, prefer_local_catalog = _cfg.get(option = u'local-import'))
301
302         # make sure we re-get the default encoding
303         # in case it changed
304         gmLog2.set_string_encoding()
305 #==========================================================
306 def handle_help_request():
307         src = [(u'cli', u'return')]
308
309         help_requested = (
310                 _cfg.get(option = u'--help', source_order = src) or
311                 _cfg.get(option = u'-h', source_order = src) or
312                 _cfg.get(option = u'-?', source_order = src)
313         )
314
315         if help_requested:
316                 print _(
317                         'Help requested\n'
318                         '--------------'
319                 )
320                 print __doc__
321                 sys.exit(0)
322 #==========================================================
323 def handle_version_request():
324         src = [(u'cli', u'return')]
325
326         version_requested = (
327                 _cfg.get(option = u'--version', source_order = src) or
328                 _cfg.get(option = u'-V', source_order = src)
329         )
330
331         if version_requested:
332
333                 from Gnumed.pycommon.gmPG2 import map_client_branch2required_db_version, known_schema_hashes
334
335                 print 'GNUmed version information'
336                 print '--------------------------'
337                 print 'client     : %s on branch [%s]' % (current_client_version, current_client_branch)
338                 print 'database   : %s' % map_client_branch2required_db_version[current_client_branch]
339                 print 'schema hash: %s' % known_schema_hashes[map_client_branch2required_db_version[current_client_branch]]
340                 sys.exit(0)
341
342 #==========================================================
343 def setup_paths_and_files():
344         """Create needed paths in user home directory."""
345
346         gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')))
347         gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'spellcheck')))
348         gmTools.mkdir(os.path.expanduser(os.path.join('~', '.gnumed', 'tmp')))
349         gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'docs')))
350         gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'xDT')))
351         gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed', 'export', 'EMR')))
352         gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed', 'xDT')))
353         gmTools.mkdir(os.path.expanduser(os.path.join('~', 'gnumed', 'logs')))
354
355         paths = gmTools.gmPaths(app_name = u'gnumed')
356
357         open(os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed.conf')), 'a+').close()
358 #==========================================================
359 def setup_date_time():
360         gmDateTime.init()
361 #==========================================================
362 def setup_cfg():
363         """Detect and setup access to GNUmed config file.
364
365         Parts of this will have limited value due to
366         wxPython not yet being available.
367         """
368
369         enc = gmI18N.get_encoding()
370         paths = gmTools.gmPaths(app_name = u'gnumed')
371
372         candidates = [
373                 # the current working dir
374                 [u'workbase', os.path.join(paths.working_dir, 'gnumed.conf')],
375                 # /etc/gnumed/
376                 [u'system', os.path.join(paths.system_config_dir, 'gnumed-client.conf')],
377                 # ~/.gnumed/
378                 [u'user', os.path.join(paths.user_config_dir, 'gnumed.conf')],
379                 # CVS/tgz tree .../gnumed/client/ (IOW a local installation)
380                 [u'local', os.path.join(paths.local_base_dir, 'gnumed.conf')]
381         ]
382         # --conf-file=
383         explicit_fname = _cfg.get(option = u'--conf-file', source_order = [(u'cli', u'return')])
384         if explicit_fname is None:
385                 candidates.append([u'explicit', None])
386         else:
387                 candidates.append([u'explicit', explicit_fname])
388
389         for candidate in candidates:
390                 _cfg.add_file_source (
391                         source = candidate[0],
392                         file = candidate[1],
393                         encoding = enc
394                 )
395
396         # --conf-file given but does not actually exist ?
397         if explicit_fname is not None:
398                 if _cfg.source_files['explicit'] is None:
399                         _log.error('--conf-file argument does not exist')
400                         sys.exit(missing_cli_config_file % explicit_fname)
401
402         # any config file found at all ?
403         found_any_file = False
404         for f in _cfg.source_files.values():
405                 if f is not None:
406                         found_any_file = True
407                         break
408         if not found_any_file:
409                 _log.error('no config file found at all')
410                 sys.exit(no_config_files % '\n '.join(candidates))
411
412         # mime type handling sources
413         fname = u'mime_type2file_extension.conf'
414         _cfg.add_file_source (
415                 source = u'user-mime',
416                 file = os.path.join(paths.user_config_dir, fname),
417                 encoding = enc
418         )
419         _cfg.add_file_source (
420                 source = u'system-mime',
421                 file = os.path.join(paths.system_config_dir, fname),
422                 encoding = enc
423         )
424 #==========================================================
425 def setup_ui_type():
426         global ui_type
427
428         ui_type = _cfg.get(option = u'--ui', source_order = [(u'cli', u'return')])
429
430         if ui_type in [True, False, None]:
431                 ui_type = 'wxp'
432
433         ui_type = ui_type.strip()
434
435         if ui_type not in _known_ui_types:
436                 _log.error('unknown UI type: %s', ui_type)
437                 _log.debug('known UI types: %s', str(_known_ui_types))
438                 print "GNUmed startup: Unknown UI type (%s). Defaulting to wxPython client." % ui_type
439                 ui_type = 'wxp'
440
441         _log.debug('UI type: %s', ui_type)
442 #==========================================================
443 def setup_backend():
444         _log.info('client expects database version [%s]', gmPG2.map_client_branch2required_db_version[current_client_branch])
445
446         # set up database connection timezone
447         timezone = _cfg.get (
448                 group = u'backend',
449                 option = 'client timezone',
450                 source_order = [
451                         ('explicit', 'return'),
452                         ('workbase', 'return'),
453                         ('local', 'return'),
454                         ('user', 'return'),
455                         ('system', 'return')
456                 ]
457         )
458         if timezone is not None:
459                 gmPG2.set_default_client_timezone(timezone)
460 #==========================================================
461 def shutdown_backend():
462         gmPG2.shutdown()
463 #==========================================================
464 def shutdown_logging():
465
466 #       if _cfg.get(option = u'debug'):
467 #               import types
468
469 #               def get_refcounts():
470 #                       refcount = {}
471 #                       # collect all classes
472 #                       for module in sys.modules.values():
473 #                               for sym in dir(module):
474 #                                       obj = getattr(module, sym)
475 #                                       if type(obj) is types.ClassType:
476 #                                               refcount[obj] = sys.getrefcount(obj)
477 #                       # sort by refcount
478 #                       pairs = map(lambda x: (x[1],x[0]), refcount.items())
479 #                       pairs.sort()
480 #                       pairs.reverse()
481 #                       return pairs
482
483 #               rcfile = open('./gm-refcount.lst', 'wb')
484 #               for refcount, class_ in get_refcounts():
485 #                       if not class_.__name__.startswith('wx'):
486 #                               rcfile.write('%10d %s\n' % (refcount, class_.__name__))
487 #               rcfile.close()
488
489         # do not choke on Windows
490         logging.raiseExceptions = False
491
492 #==========================================================
493 # main - launch the GNUmed wxPython GUI client
494 #----------------------------------------------------------
495 setup_python_path()
496 setup_logging()
497 log_startup_info()
498 setup_console_exception_handler()
499 setup_cli()
500 setup_signal_handlers()
501
502 from Gnumed.pycommon import gmI18N, gmTools, gmDateTime, gmHooks
503 setup_locale()
504 handle_help_request()
505 handle_version_request()
506 setup_paths_and_files()
507 setup_date_time()
508 setup_cfg()
509 setup_ui_type()
510
511 from Gnumed.pycommon import gmPG2
512 if ui_type in [u'web']:
513         gmPG2.auto_request_login_params = False
514 setup_backend()
515
516
517 gmHooks.run_hook_script(hook = u'startup-before-GUI')
518
519 if ui_type == u'wxp':
520         from Gnumed.wxpython import gmGuiMain
521         profile_file = _cfg.get(option = u'--profile', source_order = [(u'cli', u'return')])
522         if profile_file is not None:
523                 _log.info('writing profiling data into %s', profile_file)
524                 import profile
525                 profile.run('gmGuiMain.main()', profile_file)
526         else:
527                 gmGuiMain.main()
528 elif ui_type == u'web':
529         from Gnumed.ProxiedWeb import gmWebGuiServer
530         gmWebGuiServer.main()
531
532 elif ui_type == u'chweb':
533         from Gnumed.CherryPy import gmGuiWeb
534         gmGuiWeb.main()
535
536 gmHooks.run_hook_script(hook = u'shutdown-post-GUI')
537
538 shutdown_backend()
539 _log.info('Normally shutting down as main module.')
540 shutdown_logging()
541
542 #==========================================================