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 'fingerprint_db',
343 'generate_man_page'
344 ]
345
346
347 import_error_sermon = """
348 GNUmed startup: Cannot load GNUmed Python modules !
349 ---------------------------------------------------
350 CRITICAL ERROR: Program halted.
351
352 Please make sure you have:
353
354 1) the required third-party Python modules installed
355 2) the GNUmed Python modules linked or installed into site-packages/
356 (if you do not run from a CVS tree the installer should have taken care of that)
357 3) your PYTHONPATH environment variable set up correctly
358
359 <sys.path> is currently set to:
360
361 %s
362
363 If you are running from a copy of the CVS tree make sure you
364 did run gnumed/check-prerequisites.sh with good results.
365
366 If you still encounter errors after checking the above
367 requirements please ask on the mailing list.
368 """
369
370
371 missing_cli_config_file = """
372 GNUmed startup: Missing configuration file.
373 -------------------------------------------
374
375 You explicitly specified a configuration file
376 on the command line:
377
378 --conf-file=%s
379
380 The file does not exist, however.
381 """
382
383
384 no_config_files = """
385 GNUmed startup: Missing configuration files.
386 --------------------------------------------
387
388 None of the below candidate configuration
389 files could be found:
390
391 %s
392
393 Cannot run GNUmed without any of them.
394 """
395
396
397
398
400 import ctypes
401 csl = ctypes.windll.kernel32.CreateSymbolicLinkW
402 csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
403 csl.restype = ctypes.c_ubyte
404 if os.path.isdir(source):
405 flags = 1
406 else:
407 flags = 0
408 ret_code = csl(link_name, source.replace('/', '\\'), flags)
409 if ret_code == 0:
410 raise ctypes.WinError()
411 return ret_code
412
413
414
415
417 if target is None:
418 faulthandler.enable()
419 _pre_log_buffer.append('<faulthandler> enabled, target = [console]: %s' % faulthandler)
420 return
421 _pre_log_buffer.append('<faulthandler> enabled, target = [%s]: %s' % (target, faulthandler))
422 faulthandler.enable(file = target)
423
424
426 print_lines = []
427 try:
428 sys.stdout.reconfigure(errors = 'surrogateescape')
429 sys.stderr.reconfigure(errors = 'surrogateescape')
430 _pre_log_buffer.append('stdout/stderr reconfigured to use <surrogateescape> for encoding errors')
431 return
432 except AttributeError:
433 line = 'cannot reconfigure sys.stdout/stderr to use <errors="surrogateescape"> (needs Python 3.7+)'
434 _pre_log_buffer.append(line)
435 print_lines.append(line)
436 try:
437 _pre_log_buffer.append('sys.stdout/stderr default to "${PYTHONIOENCODING}=%s"' % os.environ['PYTHONIOENCODING'])
438 return
439 except KeyError:
440 lines = [
441 '${PYTHONIOENCODING} is not set up, use <PYTHONIOENCODING=utf-8:surrogateescape> in the shell (for Python < 3.7)',
442 'console encoding errors may occur'
443 ]
444 for line in lines:
445 print_lines.append(line)
446 _pre_log_buffer.append(line)
447 for line in print_lines:
448 print('GNUmed startup:', line)
449
450
452
453 if not '--local-import' in sys.argv:
454 _pre_log_buffer.append('running against systemwide install')
455 return
456
457 local_python_import_dir = os.path.dirname (
458 os.path.abspath(os.path.join(sys.argv[0], '..'))
459 )
460 print("Running from local source tree (%s) ..." % local_python_import_dir)
461 _pre_log_buffer.append("running from local source tree: %s" % local_python_import_dir)
462
463
464
465 link_name = os.path.join(local_python_import_dir, 'Gnumed')
466 if os.path.exists(link_name):
467 _pre_log_buffer.append('local module import dir symlink exists: %s' % link_name)
468 else:
469 real_dir = os.path.join(local_python_import_dir, 'client')
470 print('Creating local module import symlink ...')
471 print(' real dir:', real_dir)
472 print(' link:', link_name)
473 try:
474 os.symlink(real_dir, link_name)
475 except AttributeError:
476 _pre_log_buffer.append('Windows does not have os.symlink(), resorting to ctypes')
477 result = _symlink_windows(real_dir, link_name)
478 _pre_log_buffer.append('ctypes.windll.kernel32.CreateSymbolicLinkW() exit code: %s', result)
479 _pre_log_buffer.append('created local module import dir symlink: link [%s] => dir [%s]' % (link_name, real_dir))
480
481 sys.path.insert(0, local_python_import_dir)
482 _pre_log_buffer.append('sys.path with local module import base dir prepended: %s' % sys.path)
483
484
486
487 local_repo_path = os.path.expanduser(os.path.join (
488 '~',
489 '.gnumed',
490 'local_code',
491 str(current_client_branch)
492 ))
493 local_wxGladeWidgets_path = os.path.join(local_repo_path, 'Gnumed', 'wxGladeWidgets')
494
495 if not os.path.exists(local_wxGladeWidgets_path):
496 _log.debug('[%s] not found', local_wxGladeWidgets_path)
497 _log.info('local wxGlade widgets repository not available')
498 return
499
500 _log.info('local wxGlade widgets repository found:')
501 _log.info(local_wxGladeWidgets_path)
502
503 if not os.access(local_wxGladeWidgets_path, os.R_OK):
504 _log.error('invalid repo: no read access')
505 return
506
507 all_entries = os.listdir(os.path.join(local_repo_path, 'Gnumed'))
508 _log.debug('repo base contains: %s', all_entries)
509 all_entries.remove('wxGladeWidgets')
510 try:
511 all_entries.remove('__init__.py')
512 except ValueError:
513 _log.error('invalid repo: lacking __init__.py')
514 return
515 try:
516 all_entries.remove('__init__.pyc')
517 except ValueError:
518 pass
519
520 if len(all_entries) > 0:
521 _log.error('insecure repo: additional files or directories found')
522 return
523
524
525 stat_val = os.stat(local_wxGladeWidgets_path)
526 _log.debug('repo stat(): %s', stat_val)
527 perms = stat.S_IMODE(stat_val.st_mode)
528 _log.debug('repo permissions: %s (octal: %s)', perms, oct(perms))
529 if perms != 448:
530 if os.name in ['nt']:
531 _log.warning('this platform does not support os.stat() permission checking')
532 else:
533 _log.error('insecure repo: permissions not 0600')
534 return
535
536 print("Activating local wxGlade widgets repository (%s) ..." % local_wxGladeWidgets_path)
537 sys.path.insert(0, local_repo_path)
538 _log.debug('sys.path with repo:')
539 _log.debug(sys.path)
540
541
557
558
560 global _pre_log_buffer
561 if len(_pre_log_buffer) > 0:
562 _log.info('early startup log buffer:')
563 for line in _pre_log_buffer:
564 _log.info(' ' + line)
565 del _pre_log_buffer
566 _log.info('GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
567 _log.info('Platform: %s', platform.uname())
568 _log.info(('Python %s on %s (%s)' % (sys.version, sys.platform, os.name)).replace('\n', '<\\n>'))
569 try:
570 import lsb_release
571 _log.info('lsb_release: %s', lsb_release.get_distro_information())
572 except ImportError:
573 pass
574 _log.info('module <sys> info:')
575 attrs2skip = ['__doc__', 'copyright', '__name__', '__spec__']
576 for attr_name in dir(sys):
577 if attr_name in attrs2skip:
578 continue
579 if attr_name.startswith('set'):
580 continue
581 attr = getattr(sys, attr_name)
582 if not attr_name.startswith('get'):
583 _log.info('%s: %s', attr_name.rjust(30), attr)
584 continue
585 if callable(attr):
586 try:
587 _log.info('%s: %s', attr_name.rjust(30), attr())
588 except Exception:
589 _log.exception('%s: <cannot log>', attr_name.rjust(30))
590 continue
591 _log.info('module <platform> info:')
592 attrs2skip = ['__doc__', '__copyright__', '__name__', '__spec__', '__cached__', '__builtins__']
593 for attr_name in dir(platform):
594 if attr_name in attrs2skip:
595 continue
596 if attr_name.startswith('set'):
597 continue
598 attr = getattr(platform, attr_name)
599 if callable(attr):
600 if attr_name.startswith('_'):
601 _log.info('%s: %s', attr_name.rjust(30), attr)
602 continue
603 try:
604 _log.info('%s: %s', attr_name.rjust(30), attr())
605 except Exception:
606 _log.exception('%s: <cannot log>', attr_name.rjust(30))
607 continue
608 _log.info('%s: %s', attr_name.rjust(30), attr)
609 continue
610 _log.info('module <os> info:')
611 for n in os.confstr_names:
612 _log.info('%s: %s', ('confstr[%s]' % n).rjust(40), os.confstr(n))
613 for n in os.sysconf_names:
614 try:
615 _log.info('%s: %s', ('sysconf[%s]' % n).rjust(40), os.sysconf(n))
616 except Exception:
617 _log.exception('%s: <invalid> ??', ('sysconf[%s]' % n).rjust(30))
618 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']
619 for attr_name in os_attrs:
620 attr = getattr(os, attr_name)
621 if callable(attr):
622 _log.info('%s: %s', attr_name.rjust(40), attr())
623 continue
624 _log.info('%s: %s', attr_name.rjust(40), attr)
625 _log.info('process environment:')
626 for key, val in os.environ.items():
627 _log.info(' %s: %s' % (('${%s}' % key).rjust(40), val))
628 import sysconfig
629 _log.info('module <sysconfig> info:')
630 _log.info(' platform [%s] -- python version [%s]', sysconfig.get_platform(), sysconfig.get_python_version())
631 _log.info(' sysconfig.get_paths():')
632 paths = sysconfig.get_paths()
633 for path in paths:
634 _log.info('%s: %s', path.rjust(40), paths[path])
635 _log.info(' sysconfig.get_config_vars():')
636 conf_vars = sysconfig.get_config_vars()
637 for var in conf_vars:
638 _log.info('%s: %s', var.rjust(45), conf_vars[var])
639
640
645
646
648 from Gnumed.pycommon import gmCfg2
649
650 global _cfg
651 _cfg = gmCfg2.gmCfgData()
652 _cfg.add_cli (
653 short_options = _known_short_options,
654 long_options = _known_long_options
655 )
656
657 val = _cfg.get(option = '--debug', source_order = [('cli', 'return')])
658 if val is None:
659 val = False
660 _cfg.set_option (
661 option = 'debug',
662 value = val
663 )
664
665 val = _cfg.get(option = '--slave', source_order = [('cli', 'return')])
666 if val is None:
667 val = False
668 _cfg.set_option (
669 option = 'slave',
670 value = val
671 )
672
673 val = _cfg.get(option = '--skip-update-check', source_order = [('cli', 'return')])
674 if val is None:
675 val = False
676 _cfg.set_option (
677 option = 'skip-update-check',
678 value = val
679 )
680
681 val = _cfg.get(option = '--hipaa', source_order = [('cli', 'return')])
682 if val is None:
683 val = False
684 _cfg.set_option (
685 option = 'hipaa',
686 value = val
687 )
688
689 val = _cfg.get(option = '--local-import', source_order = [('cli', 'return')])
690 if val is None:
691 val = False
692 _cfg.set_option (
693 option = 'local-import',
694 value = val
695 )
696
697 _cfg.set_option (
698 option = 'client_version',
699 value = current_client_version
700 )
701
702 _cfg.set_option (
703 option = 'client_branch',
704 value = current_client_branch
705 )
706
707
709 _log.critical('SIGTERM (SIG%s) received, shutting down ...' % signum)
710 gmLog2.flush()
711 print('GNUmed: SIGTERM (SIG%s) received, shutting down ...' % signum)
712 if frame is not None:
713 print('%s::%s@%s' % (frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno))
714
715
716
717 if _old_sig_term in [None, signal.SIG_IGN]:
718 sys.exit(1)
719 else:
720 _old_sig_term(signum, frame)
721
722
726
727
734
735
736
737
738
739
741 src = [('cli', 'return')]
742
743 help_requested = (
744 _cfg.get(option = '--help', source_order = src) or
745 _cfg.get(option = '-h', source_order = src) or
746 _cfg.get(option = '-?', source_order = src)
747 )
748
749 if help_requested:
750 input('\nHit <ENTER> to display commandline help\n')
751 if platform.system() == 'Windows':
752 for line in MANPAGE.split('\n'):
753 print(regex.sub('^\.\w+\s*', '', line, count = 1))
754 sys.exit(0)
755
756 handle, man_page_fname = tempfile.mkstemp(text = True, suffix = '.1')
757 man_page_file = open(man_page_fname, mode = 'wt', encoding = 'utf8')
758 man_page_file.write(MANPAGE % datetime.date.today().strftime('%x'))
759 man_page_file.close()
760 os.system('man %s' % man_page_fname)
761 sys.exit(0)
762
763
784
785
787 """Create needed paths in user home directory."""
788
789 gmd_dir = os.path.expanduser(os.path.join('~', 'gnumed'))
790 dot_gmd_dir = os.path.expanduser(os.path.join('~', '.gnumed'))
791 readme = """GNUmed Electronic Medical Record
792
793 %s/
794
795 This directory should only ever contain files which the
796 user will come into direct contact with while using the
797 application (say, by selecting a file from the file system,
798 as when selecting document parts from files). You can create
799 subdirectories here as you see fit for the purpose.
800
801 This directory will also serve as the default directory when
802 GNUmed asks the user to select a directory for storing a
803 file.
804
805 Any files which are NOT intended for direct user interaction
806 but must be configured to live at a known location (say,
807 inter-application data exchange files) should be put under
808 the hidden directory:
809 "%s/"
810 """ % (gmd_dir, dot_gmd_dir)
811 gmTools.mkdir(gmd_dir)
812 gmTools.create_directory_description_file(directory = gmd_dir, readme = readme)
813
814 gmTools.remove_file(os.path.join(gmd_dir, '00_README'))
815
816 gmTools.mkdir(os.path.expanduser(os.path.join(dot_gmd_dir, 'spellcheck')))
817 err_dir = os.path.expanduser(os.path.join(dot_gmd_dir, 'error_logs'))
818 gmTools.mkdir(err_dir)
819 readme = """This directory should be used for files not intended for user
820 interaction at the file system level (file selection dialogs,
821 file browsers) such as inter-application data exchange files
822 which need to live at a known location."""
823 gmTools.create_directory_description_file(directory = dot_gmd_dir, readme = readme)
824 gmTools.create_directory_description_file (
825 directory = err_dir,
826 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.'
827 )
828
829
830 paths = gmTools.gmPaths(app_name = 'gnumed')
831 print("Temp dir:", paths.tmp_dir)
832
833 open(os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed.conf')), mode = 'a+t').close()
834
835 logfile_link = os.path.join(paths.tmp_dir, 'zzz-gnumed.log')
836 gmTools.mklink (gmLog2._logfile.name, logfile_link, overwrite = False)
837
838
841
842
844 """Detect and setup access to GNUmed config file.
845
846 Parts of this will have limited value due to
847 wxPython not yet being available.
848 """
849
850 enc = gmI18N.get_encoding()
851 paths = gmTools.gmPaths(app_name = 'gnumed')
852
853 candidates = [
854
855 ['workbase', os.path.join(paths.working_dir, 'gnumed.conf')],
856
857 ['system', os.path.join(paths.system_config_dir, 'gnumed-client.conf')],
858
859 ['user', os.path.join(paths.user_config_dir, 'gnumed.conf')],
860
861 ['local', os.path.join(paths.local_base_dir, 'gnumed.conf')]
862 ]
863
864 explicit_fname = _cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
865 if explicit_fname is None:
866 candidates.append(['explicit', None])
867 else:
868 candidates.append(['explicit', explicit_fname])
869
870 for candidate in candidates:
871 _cfg.add_file_source (
872 source = candidate[0],
873 file = candidate[1],
874 encoding = enc
875 )
876
877
878 if explicit_fname is not None:
879 if _cfg.source_files['explicit'] is None:
880 _log.error('--conf-file argument does not exist')
881 print(missing_cli_config_file % explicit_fname)
882 sys.exit(1)
883
884
885 found_any_file = False
886 for f in _cfg.source_files.values():
887 if f is not None:
888 found_any_file = True
889 break
890 if not found_any_file:
891 _log.error('no config file found at all')
892 print(no_config_files % '\n '.join(candidates))
893 sys.exit(1)
894
895
896 fname = 'mime_type2file_extension.conf'
897 _cfg.add_file_source (
898 source = 'user-mime',
899 file = os.path.join(paths.user_config_dir, fname),
900 encoding = enc
901 )
902 _cfg.add_file_source (
903 source = 'system-mime',
904 file = os.path.join(paths.system_config_dir, fname),
905 encoding = enc
906 )
907
908
910
911 db_version = gmPG2.map_client_branch2required_db_version[current_client_branch]
912 _log.info('client expects database version [%s]', db_version)
913 _cfg.set_option (
914 option = 'database_version',
915 value = db_version
916 )
917
918
919 timezone = _cfg.get (
920 group = 'backend',
921 option = 'client timezone',
922 source_order = [
923 ('explicit', 'return'),
924 ('workbase', 'return'),
925 ('local', 'return'),
926 ('user', 'return'),
927 ('system', 'return')
928 ]
929 )
930
931
932
933
943
944
946 try:
947 import urwid
948 except ModuleNotFoundError:
949 _log.exception('cannot import <urwid>')
950 return 1
951
952 from Gnumed.urwid import gmTuiMain
953 gmTuiMain.main()
954 return 0
955
956
958
959 from Gnumed.wxpython import gmGuiMain
960 profile_file = _cfg.get(option = '--profile', source_order = [('cli', 'return')])
961 if profile_file is None:
962 gmGuiMain.main()
963 else:
964 _log.info('writing profiling data into %s', profile_file)
965 import profile
966 profile.run('gmGuiMain.main()', profile_file)
967
968 return 0
969
970
1101
1102
1103
1104
1107
1108
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135 logging.raiseExceptions = False
1136
1137
1148
1149
1150
1151
1152
1153 random.seed()
1154
1155
1156 setup_fault_handler(target = None)
1157 setup_console_encoding()
1158 setup_python_path()
1159 setup_logging()
1160 log_startup_info()
1161 setup_console_exception_handler()
1162 setup_cli()
1163 setup_signal_handlers()
1164 setup_local_repo_path()
1165
1166 from Gnumed.pycommon import gmI18N
1167 from Gnumed.pycommon import gmTools
1168 from Gnumed.pycommon import gmDateTime
1169
1170 setup_locale()
1171 handle_help_request()
1172 handle_version_request()
1173 setup_paths_and_files()
1174 setup_date_time()
1175 setup_cfg()
1176
1177 from Gnumed.pycommon import gmPG2
1178 from Gnumed.pycommon import gmConnectionPool
1179 setup_backend_environment()
1180
1181
1182 exit_code = run_tool()
1183 if exit_code is None:
1184 from Gnumed.pycommon import gmHooks
1185 exit_code = run_ui()
1186
1187
1188 shutdown_backend()
1189 shutdown_tmp_dir()
1190 _log.info('Normally shutting down as main module.')
1191 shutdown_logging()
1192
1193 sys.exit(exit_code)
1194
1195
1196