1
2
3 __doc__ = MANPAGE = """.\\" ========================================================
4 .\\" SPDX-License-Identifier: GPL-2.0-or-later
5 .\\" ========================================================
6
7 .TH GNUmed 1 "%s" "Manual for GNUmed"
8
9 .SH NAME
10 .B GNUmed
11 - an electronic MEDICAL record software for GP offices
12
13 This is not fully featured yet. Use at your own risk.
14 You have been warned.
15
16 .SH SYNOPSIS
17 .B gnumed
18 .RB [--quiet|debug]
19 .RB [--slave]
20 .RB [--text-domain=TEXTDOMAIN]
21 .RB [--log-file=FILE]
22 .RB [--conf-file=FILE]
23 .RB [--profile=FILE]
24 .RB [--lang-gettext=LANGUAGE]
25 .RB [--tool=TOOL]
26 .RB [--override-schema-check]
27 .RB [--skip-update-check]
28 .RB [--local-import]
29 .RB [--help]
30 .RB [--version]
31 .RB [-V]
32 .RB [-h|?]
33
34
35 .SH DESCRIPTION
36 .B GNUmed
37 is a solution for keeping safe and medically sound electronic
38 records on a patient's health. It primarily focuses on GP
39 offices. It is released under the GPL.
40
41 GNUmed is written in Python with wxPython/wxWindows. Data is
42 stored in a PostgreSQL database. Multiple clients can work
43 with the same database at the same time.
44
45 .SH OPTIONS
46 .PP
47 .TP
48 .B \--quiet
49 Be extra quiet and show only _real_ errors in the log.
50 .TP
51 .B \--debug
52 Pre-set the [debug mode] checkbox in the login dialog
53 which controls increased verbosity in the log file.
54
55 Useful for, well, debugging :-) Slower, too.
56 .TP
57 .B \--slave
58 Pre-set the [enable remote control] checkbox in the login
59 dialog to enable the XML-RPC remote control feature.
60 .TP
61 .B \--hipaa
62 Enable HIPAA features with user workflow impact.
63
64 Those features which do not affect the workflow of the user
65 are permanently enabled.
66 .TP
67 .B \--log-file=FILE
68 Use this to change the name of the log file. At startup
69 GNUmed will tell you the name of the log file in use.
70 .TP
71 .B \--conf-file=FILE
72 Use configuration file FILE instead of searching for it in standard locations.
73 .TP
74 .B \--profile=FILE
75 Activate profiling and write profile data to file FILE.
76 .TP
77 .B \--lang-gettext=LANGUAGE
78 Explicitly set the language to use in gettext translation. The very
79 same effect can be achieved by setting the environment variable $LANG
80 from a launcher script.
81 .TP
82 .B \--text-domain=TEXTDOMAIN
83 Set this to change the name of the language file to be loaded.
84 Note, this does not change the directory the file is searched in,
85 only the name of the file where messages are loaded from. The
86 standard textdomain is, of course, "gnumed.mo". You need only
87 specify the base name of the file without the .mo extension.
88 .TP
89 .B \--tool=TOOL
90 Run the named TOOL instead of a GUI.
91
92 Currently implemented tools:
93
94 check_enc_epi_xref: Cross-check that foreign keys values in any given row of any table carrying both of fk_episode and fk_encounter do point to episodes and encounters, respectively, of the very same patient.
95
96 export_pat_emr_structure: Export the EMR structure (issues and episodes) of a patient into a text file.
97
98 check_mimetypes_in_archive: Show mimetypes and related information of all document parts in the archive.
99
100 read_all_rows_of_table: Check readability of all rows of a given table.
101 .TP
102 .B \--override-schema-check
103 Continue loading the client even if the database schema
104 version and the client software version cannot be verified
105 to be compatible.
106 .TP
107 .B \--skip-update-check
108 Skip checking for client updates. This is useful during
109 development or when the update check URL is unavailable.
110 .TP
111 .B \--local-import
112 At startup adjust the PYTHONPATH such that the GNUmed client is
113 run from a local copy of the source tree (say an unpacked tarball
114 or a GIT repo) rather than from a proper system-wide installation.
115 .TP
116 .B \--version, -V
117 Show version information about the GNUmed client and the
118 database it needs.
119 .TP
120 .B \--help, -h, or -?
121 Show this help.
122
123
124 .SH CONFIGURATION
125 .PP
126 .TP
127 .B Client startup and shutdown (OS level)
128
129 A shell script /usr/bin/gnumed is used to startup the client.
130 It checks whether the systemwide configuration file
131
132 /etc/gnumed/gnumed-client.conf
133
134 exists. It then executes the following scripts (in that
135 order) if found:
136
137 /etc/gnumed/gnumed-startup-local.sh
138
139 ~/.gnumed/scripts/gnumed-startup-local.sh
140
141 When the client terminates it will execute the following
142 scripts in order if they exist:
143
144 /etc/gnumed/gnumed-shutdown-local.sh
145
146 ~/.gnumed/scripts/gnumed-shutdown-local.sh
147
148 .PP
149 .TP
150 .B wxPython client startup
151
152 The gnumed.py script checks for INI style configuration files
153 and fails if it does not find any. The files are searched for
154 in the following order and extend/overwrite each others
155 options:
156
157 in the current working directory (cwd)
158
159 ./gumed.conf
160
161 in the systemwide configuration directory
162
163 /etc/gnumed/gnumed-client.conf
164
165 in the home directory
166
167 ~/.gnumed/gnumed.conf
168
169 in a local git tree or unpacked tarball
170
171 .../gnumed/client/gnumed.conf
172
173 explicitly given by CLI option
174
175 --conf-file=<CONF FILE>
176
177 .PP
178 .TP
179 .B client/system interfacing
180
181 .B ~/.gnumed/gnumed-xsanerc.conf
182
183 (requires XSane > v0.992)
184
185 When GNUmed invokes XSane for scanning it passes along this file (via =--xsane-rc=). This way a custom XSane configuration can be used with GNUmed. If the file doesn't exist it will be created from ~/.sane/xsane/xsanerc on the first call to XSane.
186
187 When you configure XSane after calling it from GNUmed your changes will be stored in the GNUmed-specific XSane configuration file and will not affect your usual XSane settings.
188
189 .B mime_type2file_extension.conf
190
191 (searched for in ~/.gnumed/ and /etc/gnumed/, in that order)
192
193 GNUmed will use these files to map mime types to file extensions if need be.
194
195 The file must contain a group [extensions] under which there can be one option per mime type specifying the extension to use on files of said type. Set the value to the raw extension only, omitting the ".", like so:
196
197 .nf
198 [extensions]
199 image/x-bmp = bmp
200
201
202 .SH EXIT STATUS
203 .TP
204 > 0: some error occurred while the GUI client was run
205 .TP
206 0: normal termination of the client
207 .TP
208 < 0: some error occurred while trying to run a console tool
209 .TP
210 -1: an unknown console tool was requested
211 .TP
212 < -1: an error occurred while a console tool was run
213 .TP
214 -999: hard abort of the client
215
216
217 .SH ENVIRONMENT
218 .TP
219 .B LANG, LC_MESSAGES, etc.
220 See gettext(1) for how the various locale related environment variables work.
221
222
223 .SH OTHER FILES AND DIRECTORIES
224 .PP
225 .TP
226 .B ~/.gnumed/gnumed.log
227 The default log file.
228 .TP
229 .B gnumed-client.tmpfiles.d.conf
230 Integration with systemd-tmpfiles(8).
231 .TP
232 .B gnumed-completion.bash
233 Integration with BASH completions.
234
235
236 .SH SEE ALSO
237 .PP
238 .TP
239 .B https://www.gnumed.[de|org]
240 Online documenation.
241 .TP
242 .B http://savannah.gnu.org/projects/gnumed
243 Mailing list home
244 .TP
245 .B https://github.org/ncqgm/gnumed
246 Source code repository (Git)
247 .TP
248 .B /usr/share/doc/gnumed/
249 Local documentation
250 .TP
251 .B man -k gm-*
252 List man pages on gm-* commands.
253 .TP
254 .B gettext(1)
255
256
257 .SH BUGS
258
259 A lot of functionality is still missing. To make up for
260 that, there's bugs here and there for you to report :-)
261
262 Use at your own risk. You have been warned. Take proper backups !
263 """
264
265
266
267 __author__ = "H. Herb <hherb@gnumed.net>, K. Hilbert <Karsten.Hilbert@gmx.net>, I. Haywood <i.haywood@ugrad.unimelb.edu.au>"
268 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
269
270
271
272 import sys
273 import os
274 import platform
275 import tempfile
276 import faulthandler
277 import random
278 import logging
279 import datetime
280 import signal
281 import os.path
282 import shutil
283 import stat
284 import re as regex
285
286
287
288 if __name__ != "__main__":
289 print("GNUmed startup: This is not intended to be imported as a module !")
290 print("-----------------------------------------------------------------")
291 sys.exit(1)
292
293
294
295 if os.name in ['posix'] and os.geteuid() == 0:
296 print("""
297 GNUmed startup: GNUmed should not be run as root.
298 -------------------------------------------------
299
300 Running GNUmed as <root> can potentially put all
301 your medical data at risk. It is strongly advised
302 against. Please run GNUmed as a non-root user.
303 """)
304 sys.exit(1)
305
306
307
308
309 current_client_version = 'head'
310 current_client_branch = 'master'
311
312
313 _log = None
314 _pre_log_buffer = []
315 _cfg = None
316 _old_sig_term = None
317 _known_short_options = 'h?V'
318 _known_long_options = [
319 'debug',
320 'slave',
321 'skip-update-check',
322 'profile=',
323 'text-domain=',
324 'log-file=',
325 'conf-file=',
326 'lang-gettext=',
327 'override-schema-check',
328 'local-import',
329 'help',
330 'version',
331 'hipaa',
332 'wxp=',
333 'tool=',
334 'tui'
335 ]
336
337 _known_tools = [
338 'check_enc_epi_xref',
339 'export_pat_emr_structure',
340 'check_mimetypes_in_archive',
341 'read_all_rows_of_table',
342 'generate_man_page'
343 ]
344
345
346 import_error_sermon = """
347 GNUmed startup: Cannot load GNUmed Python modules !
348 ---------------------------------------------------
349 CRITICAL ERROR: Program halted.
350
351 Please make sure you have:
352
353 1) the required third-party Python modules installed
354 2) the GNUmed Python modules linked or installed into site-packages/
355 (if you do not run from a CVS tree the installer should have taken care of that)
356 3) your PYTHONPATH environment variable set up correctly
357
358 <sys.path> is currently set to:
359
360 %s
361
362 If you are running from a copy of the CVS tree make sure you
363 did run gnumed/check-prerequisites.sh with good results.
364
365 If you still encounter errors after checking the above
366 requirements please ask on the mailing list.
367 """
368
369
370 missing_cli_config_file = """
371 GNUmed startup: Missing configuration file.
372 -------------------------------------------
373
374 You explicitly specified a configuration file
375 on the command line:
376
377 --conf-file=%s
378
379 The file does not exist, however.
380 """
381
382
383 no_config_files = """
384 GNUmed startup: Missing configuration files.
385 --------------------------------------------
386
387 None of the below candidate configuration
388 files could be found:
389
390 %s
391
392 Cannot run GNUmed without any of them.
393 """
394
395
396
397
399 import ctypes
400 csl = ctypes.windll.kernel32.CreateSymbolicLinkW
401 csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
402 csl.restype = ctypes.c_ubyte
403 if os.path.isdir(source):
404 flags = 1
405 else:
406 flags = 0
407 ret_code = csl(link_name, source.replace('/', '\\'), flags)
408 if ret_code == 0:
409 raise ctypes.WinError()
410 return ret_code
411
412
413
414
416 if target is None:
417 faulthandler.enable()
418 _pre_log_buffer.append('<faulthandler> enabled, target = [console]: %s' % faulthandler)
419 return
420 _pre_log_buffer.append('<faulthandler> enabled, target = [%s]: %s' % (target, faulthandler))
421 faulthandler.enable(file = target)
422
423
425 print_lines = []
426 try:
427 sys.stdout.reconfigure(errors = 'surrogateescape')
428 sys.stderr.reconfigure(errors = 'surrogateescape')
429 _pre_log_buffer.append('stdout/stderr reconfigured to use <surrogateescape> for encoding errors')
430 return
431 except AttributeError:
432 line = 'cannot reconfigure sys.stdout/stderr to use <errors="surrogateescape"> (needs Python 3.7+)'
433 _pre_log_buffer.append(line)
434 print_lines.append(line)
435 try:
436 _pre_log_buffer.append('sys.stdout/stderr default to "${PYTHONIOENCODING}=%s"' % os.environ['PYTHONIOENCODING'])
437 return
438 except KeyError:
439 lines = [
440 '${PYTHONIOENCODING} is not set up, use <PYTHONIOENCODING=utf-8:surrogateescape> in the shell (for Python < 3.7)',
441 'console encoding errors may occur'
442 ]
443 for line in lines:
444 print_lines.append(line)
445 _pre_log_buffer.append(line)
446 for line in print_lines:
447 print('GNUmed startup:', line)
448
449
451
452 if not '--local-import' in sys.argv:
453 _pre_log_buffer.append('running against systemwide install')
454 return
455
456 local_python_import_dir = os.path.dirname (
457 os.path.abspath(os.path.join(sys.argv[0], '..'))
458 )
459 print("Running from local source tree (%s) ..." % local_python_import_dir)
460 _pre_log_buffer.append("running from local source tree: %s" % local_python_import_dir)
461
462
463
464 link_name = os.path.join(local_python_import_dir, 'Gnumed')
465 if os.path.exists(link_name):
466 _pre_log_buffer.append('local module import dir symlink exists: %s' % link_name)
467 else:
468 real_dir = os.path.join(local_python_import_dir, 'client')
469 print('Creating local module import symlink ...')
470 print(' real dir:', real_dir)
471 print(' link:', link_name)
472 try:
473 os.symlink(real_dir, link_name)
474 except AttributeError:
475 _pre_log_buffer.append('Windows does not have os.symlink(), resorting to ctypes')
476 result = _symlink_windows(real_dir, link_name)
477 _pre_log_buffer.append('ctypes.windll.kernel32.CreateSymbolicLinkW() exit code: %s', result)
478 _pre_log_buffer.append('created local module import dir symlink: link [%s] => dir [%s]' % (link_name, real_dir))
479
480 sys.path.insert(0, local_python_import_dir)
481 _pre_log_buffer.append('sys.path with local module import base dir prepended: %s' % sys.path)
482
483
485
486 local_repo_path = os.path.expanduser(os.path.join (
487 '~',
488 '.gnumed',
489 'local_code',
490 str(current_client_branch)
491 ))
492 local_wxGladeWidgets_path = os.path.join(local_repo_path, 'Gnumed', 'wxGladeWidgets')
493
494 if not os.path.exists(local_wxGladeWidgets_path):
495 _log.debug('[%s] not found', local_wxGladeWidgets_path)
496 _log.info('local wxGlade widgets repository not available')
497 return
498
499 _log.info('local wxGlade widgets repository found:')
500 _log.info(local_wxGladeWidgets_path)
501
502 if not os.access(local_wxGladeWidgets_path, os.R_OK):
503 _log.error('invalid repo: no read access')
504 return
505
506 all_entries = os.listdir(os.path.join(local_repo_path, 'Gnumed'))
507 _log.debug('repo base contains: %s', all_entries)
508 all_entries.remove('wxGladeWidgets')
509 try:
510 all_entries.remove('__init__.py')
511 except ValueError:
512 _log.error('invalid repo: lacking __init__.py')
513 return
514 try:
515 all_entries.remove('__init__.pyc')
516 except ValueError:
517 pass
518
519 if len(all_entries) > 0:
520 _log.error('insecure repo: additional files or directories found')
521 return
522
523
524 stat_val = os.stat(local_wxGladeWidgets_path)
525 _log.debug('repo stat(): %s', stat_val)
526 perms = stat.S_IMODE(stat_val.st_mode)
527 _log.debug('repo permissions: %s (octal: %s)', perms, oct(perms))
528 if perms != 448:
529 if os.name in ['nt']:
530 _log.warning('this platform does not support os.stat() permission checking')
531 else:
532 _log.error('insecure repo: permissions not 0600')
533 return
534
535 print("Activating local wxGlade widgets repository (%s) ..." % local_wxGladeWidgets_path)
536 sys.path.insert(0, local_repo_path)
537 _log.debug('sys.path with repo:')
538 _log.debug(sys.path)
539
540
556
557
559 global _pre_log_buffer
560 if len(_pre_log_buffer) > 0:
561 _log.info('early startup log buffer:')
562 for line in _pre_log_buffer:
563 _log.info(' ' + line)
564 del _pre_log_buffer
565 _log.info('GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
566 _log.info('Platform: %s', platform.uname())
567 _log.info(('Python %s on %s (%s)' % (sys.version, sys.platform, os.name)).replace('\n', '<\\n>'))
568 try:
569 import lsb_release
570 _log.info('lsb_release: %s', lsb_release.get_distro_information())
571 except ImportError:
572 pass
573 _log.info('module <sys> info:')
574 attrs2skip = ['__doc__', 'copyright', '__name__', '__spec__']
575 for attr_name in dir(sys):
576 if attr_name in attrs2skip:
577 continue
578 if attr_name.startswith('set'):
579 continue
580 attr = getattr(sys, attr_name)
581 if not attr_name.startswith('get'):
582 _log.info('%s: %s', attr_name.rjust(30), attr)
583 continue
584 if callable(attr):
585 try:
586 _log.info('%s: %s', attr_name.rjust(30), attr())
587 except Exception:
588 _log.exception('%s: <cannot log>', attr_name.rjust(30))
589 continue
590 _log.info('module <platform> info:')
591 attrs2skip = ['__doc__', '__copyright__', '__name__', '__spec__', '__cached__', '__builtins__']
592 for attr_name in dir(platform):
593 if attr_name in attrs2skip:
594 continue
595 if attr_name.startswith('set'):
596 continue
597 attr = getattr(platform, attr_name)
598 if callable(attr):
599 if attr_name.startswith('_'):
600 _log.info('%s: %s', attr_name.rjust(30), attr)
601 continue
602 try:
603 _log.info('%s: %s', attr_name.rjust(30), attr())
604 except Exception:
605 _log.exception('%s: <cannot log>', attr_name.rjust(30))
606 continue
607 _log.info('%s: %s', attr_name.rjust(30), attr)
608 continue
609 _log.info('module <os> info:')
610 for n in os.confstr_names:
611 _log.info('%s: %s', ('confstr[%s]' % n).rjust(40), os.confstr(n))
612 for n in os.sysconf_names:
613 try:
614 _log.info('%s: %s', ('sysconf[%s]' % n).rjust(40), os.sysconf(n))
615 except Exception:
616 _log.exception('%s: <invalid> ??', ('sysconf[%s]' % n).rjust(30))
617 os_attrs = ['name', 'ctermid', 'getcwd', 'get_exec_path', 'getegid', 'geteuid', 'getgid', 'getgroups', 'getlogin', 'getpgrp', 'getpid', 'getppid', 'getresuid', 'getresgid', 'getuid', 'supports_bytes_environ', 'uname', 'get_terminal_size', 'pathconf_names', 'times', 'cpu_count', 'curdir', 'pardir', 'sep', 'altsep', 'extsep', 'pathsep', 'defpath', 'linesep', 'devnull']
618 for attr_name in os_attrs:
619 attr = getattr(os, attr_name)
620 if callable(attr):
621 _log.info('%s: %s', attr_name.rjust(40), attr())
622 continue
623 _log.info('%s: %s', attr_name.rjust(40), attr)
624 _log.info('process environment:')
625 for key, val in os.environ.items():
626 _log.info(' %s: %s' % (('${%s}' % key).rjust(40), val))
627 import sysconfig
628 _log.info('module <sysconfig> info:')
629 _log.info(' platform [%s] -- python version [%s]', sysconfig.get_platform(), sysconfig.get_python_version())
630 _log.info(' sysconfig.get_paths():')
631 paths = sysconfig.get_paths()
632 for path in paths:
633 _log.info('%s: %s', path.rjust(40), paths[path])
634 _log.info(' sysconfig.get_config_vars():')
635 conf_vars = sysconfig.get_config_vars()
636 for var in conf_vars:
637 _log.info('%s: %s', var.rjust(45), conf_vars[var])
638
639
644
645
647 from Gnumed.pycommon import gmCfg2
648
649 global _cfg
650 _cfg = gmCfg2.gmCfgData()
651 _cfg.add_cli (
652 short_options = _known_short_options,
653 long_options = _known_long_options
654 )
655
656 val = _cfg.get(option = '--debug', source_order = [('cli', 'return')])
657 if val is None:
658 val = False
659 _cfg.set_option (
660 option = 'debug',
661 value = val
662 )
663
664 val = _cfg.get(option = '--slave', source_order = [('cli', 'return')])
665 if val is None:
666 val = False
667 _cfg.set_option (
668 option = 'slave',
669 value = val
670 )
671
672 val = _cfg.get(option = '--skip-update-check', source_order = [('cli', 'return')])
673 if val is None:
674 val = False
675 _cfg.set_option (
676 option = 'skip-update-check',
677 value = val
678 )
679
680 val = _cfg.get(option = '--hipaa', source_order = [('cli', 'return')])
681 if val is None:
682 val = False
683 _cfg.set_option (
684 option = 'hipaa',
685 value = val
686 )
687
688 val = _cfg.get(option = '--local-import', source_order = [('cli', 'return')])
689 if val is None:
690 val = False
691 _cfg.set_option (
692 option = 'local-import',
693 value = val
694 )
695
696 _cfg.set_option (
697 option = 'client_version',
698 value = current_client_version
699 )
700
701 _cfg.set_option (
702 option = 'client_branch',
703 value = current_client_branch
704 )
705
706
708 _log.critical('SIGTERM (SIG%s) received, shutting down ...' % signum)
709 gmLog2.flush()
710 print('GNUmed: SIGTERM (SIG%s) received, shutting down ...' % signum)
711 if frame is not None:
712 print('%s::%s@%s' % (frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno))
713
714
715
716 if _old_sig_term in [None, signal.SIG_IGN]:
717 sys.exit(1)
718 else:
719 _old_sig_term(signum, frame)
720
721
725
726
733
734
735
736
737
738
740 src = [('cli', 'return')]
741
742 help_requested = (
743 _cfg.get(option = '--help', source_order = src) or
744 _cfg.get(option = '-h', source_order = src) or
745 _cfg.get(option = '-?', source_order = src)
746 )
747
748 if help_requested:
749 input('\nHit <ENTER> to display commandline help\n')
750 if platform.system() == 'Windows':
751 for line in MANPAGE.split('\n'):
752 print(regex.sub('^\.\w+\s*', '', line, count = 1))
753 sys.exit(0)
754
755 handle, man_page_fname = tempfile.mkstemp(text = True, suffix = '.1')
756 man_page_file = open(man_page_fname, mode = 'wt', encoding = 'utf8')
757 man_page_file.write(MANPAGE % datetime.date.today().strftime('%x'))
758 man_page_file.close()
759 os.system('man %s' % man_page_fname)
760 sys.exit(0)
761
762
783
784
786 """Create needed paths in user home directory."""
787
788 gmd_dir = os.path.expanduser(os.path.join('~', 'gnumed'))
789 dot_gmd_dir = os.path.expanduser(os.path.join('~', '.gnumed'))
790 readme = """GNUmed Electronic Medical Record
791
792 %s/
793
794 This directory should only ever contain files which the
795 user will come into direct contact with while using the
796 application (say, by selecting a file from the file system,
797 as when selecting document parts from files). You can create
798 subdirectories here as you see fit for the purpose.
799
800 This directory will also serve as the default directory when
801 GNUmed asks the user to select a directory for storing a
802 file.
803
804 Any files which are NOT intended for direct user interaction
805 but must be configured to live at a known location (say,
806 inter-application data exchange files) should be put under
807 the hidden directory:
808 "%s/"
809 """ % (gmd_dir, dot_gmd_dir)
810 gmTools.mkdir(gmd_dir)
811 gmTools.create_directory_description_file(directory = gmd_dir, readme = readme)
812
813 gmTools.remove_file(os.path.join(gmd_dir, '00_README'))
814
815 gmTools.mkdir(os.path.expanduser(os.path.join(dot_gmd_dir, 'spellcheck')))
816 err_dir = os.path.expanduser(os.path.join(dot_gmd_dir, 'error_logs'))
817 gmTools.mkdir(err_dir)
818 readme = """This directory should be used for files not intended for user
819 interaction at the file system level (file selection dialogs,
820 file browsers) such as inter-application data exchange files
821 which need to live at a known location."""
822 gmTools.create_directory_description_file(directory = dot_gmd_dir, readme = readme)
823 gmTools.create_directory_description_file (
824 directory = err_dir,
825 readme = 'Whenever an unhandled exception is detected a copy of the log file is placed here.\n\nThis directory is subject to systemd-tmpfiles cleaning.'
826 )
827
828
829 paths = gmTools.gmPaths(app_name = 'gnumed')
830 print("Temp dir:", paths.tmp_dir)
831
832 open(os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed.conf')), mode = 'a+t').close()
833
834 logfile_link = os.path.join(paths.tmp_dir, 'zzz-gnumed.log')
835 gmTools.mklink (gmLog2._logfile.name, logfile_link, overwrite = False)
836
837
840
841
843 """Detect and setup access to GNUmed config file.
844
845 Parts of this will have limited value due to
846 wxPython not yet being available.
847 """
848
849 enc = gmI18N.get_encoding()
850 paths = gmTools.gmPaths(app_name = 'gnumed')
851
852 candidates = [
853
854 ['workbase', os.path.join(paths.working_dir, 'gnumed.conf')],
855
856 ['system', os.path.join(paths.system_config_dir, 'gnumed-client.conf')],
857
858 ['user', os.path.join(paths.user_config_dir, 'gnumed.conf')],
859
860 ['local', os.path.join(paths.local_base_dir, 'gnumed.conf')]
861 ]
862
863 explicit_fname = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
864 if explicit_fname is None:
865 candidates.append(['explicit', None])
866 else:
867 candidates.append(['explicit', explicit_fname])
868
869 for candidate in candidates:
870 _cfg.add_file_source (
871 source = candidate[0],
872 file = candidate[1],
873 encoding = enc
874 )
875
876
877 if explicit_fname is not None:
878 if _cfg.source_files['explicit'] is None:
879 _log.error('--conf-file argument does not exist')
880 print(missing_cli_config_file % explicit_fname)
881 sys.exit(1)
882
883
884 found_any_file = False
885 for f in _cfg.source_files.values():
886 if f is not None:
887 found_any_file = True
888 break
889 if not found_any_file:
890 _log.error('no config file found at all')
891 print(no_config_files % '\n '.join(candidates))
892 sys.exit(1)
893
894
895 fname = 'mime_type2file_extension.conf'
896 _cfg.add_file_source (
897 source = 'user-mime',
898 file = os.path.join(paths.user_config_dir, fname),
899 encoding = enc
900 )
901 _cfg.add_file_source (
902 source = 'system-mime',
903 file = os.path.join(paths.system_config_dir, fname),
904 encoding = enc
905 )
906
907
909
910 db_version = gmPG2.map_client_branch2required_db_version[current_client_branch]
911 _log.info('client expects database version [%s]', db_version)
912 _cfg.set_option (
913 option = 'database_version',
914 value = db_version
915 )
916
917
918 timezone = _cfg.get (
919 group = 'backend',
920 option = 'client timezone',
921 source_order = [
922 ('explicit', 'return'),
923 ('workbase', 'return'),
924 ('local', 'return'),
925 ('user', 'return'),
926 ('system', 'return')
927 ]
928 )
929
930
931
932
942
943
945 try:
946 import urwid
947 except ModuleNotFoundError:
948 _log.exception('cannot import <urwid>')
949 return 1
950
951 from Gnumed.urwid import gmTuiMain
952 gmTuiMain.main()
953 return 0
954
955
957
958 from Gnumed.wxpython import gmGuiMain
959 profile_file = _cfg.get(option = '--profile', source_order = [('cli', 'return')])
960 if profile_file is None:
961 gmGuiMain.main()
962 else:
963 _log.info('writing profiling data into %s', profile_file)
964 import profile
965 profile.run('gmGuiMain.main()', profile_file)
966
967 return 0
968
969
1092
1093
1094
1095
1098
1099
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126 logging.raiseExceptions = False
1127
1128
1139
1140
1141
1142
1143
1144 random.seed()
1145
1146
1147 setup_fault_handler(target = None)
1148 setup_console_encoding()
1149 setup_python_path()
1150 setup_logging()
1151 log_startup_info()
1152 setup_console_exception_handler()
1153 setup_cli()
1154 setup_signal_handlers()
1155 setup_local_repo_path()
1156
1157 from Gnumed.pycommon import gmI18N
1158 from Gnumed.pycommon import gmTools
1159 from Gnumed.pycommon import gmDateTime
1160
1161 setup_locale()
1162 handle_help_request()
1163 handle_version_request()
1164 setup_paths_and_files()
1165 setup_date_time()
1166 setup_cfg()
1167
1168 from Gnumed.pycommon import gmPG2
1169 from Gnumed.pycommon import gmConnectionPool
1170 setup_backend_environment()
1171
1172
1173 exit_code = run_tool()
1174 if exit_code is None:
1175 from Gnumed.pycommon import gmHooks
1176 exit_code = run_ui()
1177
1178
1179 shutdown_backend()
1180 shutdown_tmp_dir()
1181 _log.info('Normally shutting down as main module.')
1182 shutdown_logging()
1183
1184 sys.exit(exit_code)
1185
1186
1187