def export_patients_as_xdt(base_path=None): path = gmTools.get_unique_filename ( prefix = u'gm-export-', suffix = u'', tmp_dir = base_path ) path = os.path.splitext(path)[0] gmTools.mkdir(path) for ID in gmPerson.get_person_IDs(): _log.info(u'exporting patient #%s', ID) identity = gmPerson.cPerson(aPK_obj = ID) _log.info(u'identity: %s', identity) filename = gmTools.get_unique_filename ( prefix = u'gm_exp-%s-' % identity.dirname, suffix = u'.xdt', tmp_dir = path ) _log.info(u'file: %s', filename) identity.export_as_gdt ( filename = filename, encoding = u'utf8' #encoding = u'iso-8859-15' )
def download_data_pack_old(url, target_dir=None): if target_dir is None: target_dir = gmTools.get_unique_filename(prefix='gm-dl-') _log.debug('downloading [%s]', url) _log.debug('unpacking into [%s]', target_dir) gmTools.mkdir(directory=target_dir) # FIXME: rewrite to use urllib.urlretrieve() and paths = gmTools.gmPaths() local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', 'gm-download_data') candidates = [ u'gm-download_data', u'gm-download_data.bat', local_script, u'gm-download_data.bat' ] args = u' %s %s' % (url, target_dir) success = gmShellAPI.run_first_available_in_shell(binaries=candidates, args=args, blocking=True, run_last_one_anyway=True) if success: return True, target_dir _log.error('download failed') return False, None
def download_data_pack_old(url, target_dir=None): if target_dir is None: target_dir = gmTools.get_unique_filename(prefix = 'gm-dl-') _log.debug('downloading [%s]', url) _log.debug('unpacking into [%s]', target_dir) gmTools.mkdir(directory = target_dir) # FIXME: rewrite to use urllib.urlretrieve() and paths = gmTools.gmPaths() local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', 'gm-download_data') candidates = [u'gm-download_data', u'gm-download_data.bat', local_script, u'gm-download_data.bat'] args = u' %s %s' % (url, target_dir) success = gmShellAPI.run_first_available_in_shell ( binaries = candidates, args = args, blocking = True, run_last_one_anyway = True ) if success: return True, target_dir _log.error('download failed') return False, None
def setup_hook_dir(): _log.debug('known hooks:') for hook in known_hooks: _log.debug(hook) hook_dir = os.path.expanduser(os.path.join('~', '.gnumed', 'scripts')) gmTools.mkdir(hook_dir) gmTools.create_directory_description_file(directory = hook_dir, readme = README_hook_dir)
def setup_hook_dir(): _log.debug('known hooks:') for hook in known_hooks: _log.debug(hook) gmTools.mkdir(HOOK_SCRIPT_DIR) gmTools.create_directory_description_file(directory=HOOK_SCRIPT_DIR, readme=README_hook_dir) # create hook script example/template example_name = os.path.join(HOOK_SCRIPT_DIR, HOOK_SCRIPT_NAME + '.example') example = open(example_name, mode='wt', encoding='utf8') example.write(HOOK_SCRIPT_EXAMPLE) example.close() os.chmod(example_name, 384)
def unzip_data_pack(filename=None): unzip_dir = os.path.splitext(filename)[0] _log.debug('unzipping data pack into [%s]', unzip_dir) gmTools.mkdir(unzip_dir) try: data_pack = zipfile.ZipFile(filename, 'r') except (zipfile.BadZipfile): _log.exception('cannot unzip data pack [%s]', filename) gmLog2.log_stack_trace() return None data_pack.extractall(unzip_dir) return unzip_dir
def setup_hook_dir(): _old_path = os.path.join(gmTools.gmPaths().home_dir, '.gnumed', 'scripts') if os.path.isdir(_old_path): print('obsolete: [%s], use [%s]' %(_old_path, HOOK_SCRIPT_DIR)) _log.debug('obsolete: %s', _old_path) _log.debug('known hooks:') for hook in known_hooks: _log.debug(hook) gmTools.mkdir(HOOK_SCRIPT_DIR) gmTools.create_directory_description_file(directory = HOOK_SCRIPT_DIR, readme = README_hook_dir) # create hook script example/template example_name = os.path.join(HOOK_SCRIPT_DIR, HOOK_SCRIPT_NAME + '.example') example = open(example_name, mode = 'wt', encoding = 'utf8') example.write(HOOK_SCRIPT_EXAMPLE) example.close() os.chmod(example_name, 384)
def dump_items_to_disk(self, base_dir=None, items=None): if items is None: items = self.items if len(items) == 0: return None if base_dir is None: from Gnumed.business.gmPerson import cPatient pat = cPatient(aPK_obj=self.__pk_identity) base_dir = gmTools.mk_sandbox_dir(prefix=u'exp-%s-' % pat.dirname) _log.debug('dumping export items to: %s', base_dir) gmTools.mkdir(base_dir) for item in items: item.save_to_file(directory=base_dir) return base_dir
def extract_HL7_from_XML_CDATA(filename, xml_path, target_dir=None): _log.debug('extracting HL7 from CDATA of <%s> nodes in XML file [%s]', xml_path, filename) # sanity checks/setup try: open(filename).close() orig_dir = os.path.split(filename)[0] work_filename = gmTools.get_unique_filename(prefix = 'gm-x2h-%s-' % gmTools.fname_stem(filename), suffix = '.hl7') if target_dir is None: target_dir = os.path.join(orig_dir, 'HL7') done_dir = os.path.join(orig_dir, 'done') else: done_dir = os.path.join(target_dir, 'done') _log.debug('target dir: %s', target_dir) gmTools.mkdir(target_dir) gmTools.mkdir(done_dir) except Exception: _log.exception('cannot setup unwrapping environment') return None hl7_xml = pyxml.ElementTree() try: hl7_xml.parse(filename) except pyxml.ParseError: _log.exception('cannot parse [%s]' % filename) return None nodes = hl7_xml.findall(xml_path) if len(nodes) == 0: _log.debug('no nodes found for data extraction') return None _log.debug('unwrapping HL7 from XML into [%s]', work_filename) hl7_file = io.open(work_filename, mode = 'wt', encoding = 'utf8', newline = '') # universal newlines acceptance but no translation on output for node in nodes: # hl7_file.write(node.text.rstrip() + HL7_EOL) hl7_file.write(node.text + '') # trick to make node.text unicode hl7_file.close() target_fname = os.path.join(target_dir, os.path.split(work_filename)[1]) shutil.copy(work_filename, target_dir) shutil.move(filename, done_dir) return target_fname
def export_patients_as_xdt(base_path=None): path = gmTools.get_unique_filename(prefix='gm-export-', suffix='', tmp_dir=base_path) path = os.path.splitext(path)[0] gmTools.mkdir(path) for ID in gmPerson.get_person_IDs(): _log.info('exporting patient #%s', ID) identity = gmPerson.cPerson(aPK_obj=ID) _log.info('identity: %s', identity) filename = gmTools.get_unique_filename(prefix='gm_exp-%s-' % identity.subdir_name, suffix='.xdt', tmp_dir=path) _log.info('file: %s', filename) identity.export_as_gdt(filename=filename, encoding='utf8' #encoding = u'iso-8859-15' )
def export(self, base_dir=None, items=None, with_metadata=True): if items is None: items_found = self.items if len(items) == 0: return None from Gnumed.business.gmPerson import cPatient pat = cPatient(aPK_obj = self.__pk_identity) if base_dir is None: base_dir = gmTools.mk_sandbox_dir(prefix = u'exp-%s-' % pat.dirname) _log.debug('base dir: %s', base_dir) doc_dir = os.path.join(base_dir, r'documents') gmTools.mkdir(doc_dir) mugshot = pat.document_folder.latest_mugshot if mugshot is None: mugshot_url = u'documents/no-such-file.png' mugshot_alt = _('no patient photograph available') mugshot_title = u'' else: mugshot_url = mugshot.export_to_file(directory = doc_dir) mugshot_alt =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') mugshot_title = gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') # index.html idx_fname = os.path.join(base_dir, u'index.html') idx_file = io.open(idx_fname, mode = u'wt', encoding = u'utf8') # header browse_dicomdir = u'' existing_files = os.listdir(base_dir) if u'DICOMDIR' in existing_files: browse_dicomdir = u' <li><a href="./DICOMDIR">browse DICOMDIR</a></li>' idx_file.write(_html_start % { u'html_title_header': _('Patient data for'), u'html_title_patient': gmTools.html_escape_string(pat['description_gender'] + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')), u'title': _('Patient data export'), u'pat_name': gmTools.html_escape_string(pat['description_gender']), u'pat_dob': gmTools.html_escape_string(_(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')), u'mugshot_url': mugshot_url, u'mugshot_alt': mugshot_alt, u'mugshot_title': mugshot_title, u'docs_title': _(u'Documents'), u'browse_root': _(u'browse storage medium'), u'browse_docs': _(u'browse documents area'), u'browse_dicomdir': browse_dicomdir }) # middle for item in items: item_path = item.export_to_file(directory = doc_dir) item_fname = os.path.split(item_path)[1] idx_file.write(_html_list_item % ( item_fname, gmTools.html_escape_string(item['description']) )) # footer _cfg = gmCfg2.gmCfgData() idx_file.write(_html_end % ( gmTools.html_escape_string(gmDateTime.pydt_strftime(format = '%Y %B %d', encoding = u'utf8')), gmTools.html_escape_string(_cfg.get(option = u'client_version')) )) idx_file.close() # autorun.inf autorun_fname = os.path.join(base_dir, u'autorun.inf') autorun_file = io.open(autorun_fname, mode = u'wt', encoding = u'utf8') autorun_file.write(_autorun_inf % ( (pat['description_gender'] + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')).strip(), _('Browse patient data') )) autorun_file.close() # cd.inf cd_inf_fname = os.path.join(base_dir, u'cd.inf') cd_inf_file = io.open(cd_inf_fname, mode = u'wt', encoding = u'utf8') cd_inf_file.write(_cd_inf % ( pat['lastnames'], pat['firstnames'], gmTools.coalesce(pat['gender'], u'?'), pat.get_formatted_dob('%Y-%m-%d'), gmDateTime.pydt_strftime(format = '%Y-%m-%d', encoding = u'utf8'), pat.ID, _cfg.get(option = u'client_version'), u' / '.join([ u'%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in pat.gender_list ]) )) cd_inf_file.close() # README readme_fname = os.path.join(base_dir, u'README') readme_file = io.open(readme_fname, mode = u'wt', encoding = u'utf8') readme_file.write(_README % ( pat['description_gender'] + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d') )) readme_file.close() # patient demographics as GDT/XML/VCF pat.export_as_gdt(filename = os.path.join(base_dir, u'patient.gdt')) pat.export_as_xml_linuxmednews(filename = os.path.join(base_dir, u'patient.xml')) pat.export_as_vcard(filename = os.path.join(base_dir, u'patient.vcf')) return base_dir
def create_encrypted_zip_archive_from_dir(source_dir: str, comment: str = None, overwrite: bool = True, passphrase: str = None, verbose: bool = False) -> bool: """Create encrypted archive of a directory. The encrypted archive file will always be named "datawrapper.zip" for confidentiality reasons. If callers want another name they will have to shutil.move() the zip file themselves. This archive will be compressed and AES256 encrypted with the given passphrase. Therefore, the result will not decrypt with earlier versions of unzip software. On Windows, 7z oder WinZip are needed. The zip format does not support header encryption thereby allowing attackers to gain knowledge of patient details by observing the names of files and directories inside the encrypted archive. To reduce that attack surface, GNUmed will create _another_ zip archive inside "datawrapper.zip", which eventually wraps up the patient data as "data.zip". That archive is not compressed and not encrypted, and can thus be unpacked with any old unzipper. Note that GNUmed does NOT remember the passphrase for you. You will have to take care of that yourself, and possibly also safely hand over the passphrase to any receivers of the zip archive. Args: source_dir: the directory to archive and encrypt comment: included as a file containing the comment overwrite: remove preexisting archive before creation, avoiding *updating* of same, and thereby including unintended data passphrase: minimum length of 5 if given Returns: True / False / None (other error) """ assert (source_dir is not None), '<source_dir> must not be <None>' if len(passphrase) < 5: _log.error('<passphrase> must be at least 5 characters/signs/digits') return None gmLog2.add_word2hide(passphrase) source_dir = os.path.abspath(source_dir) if not os.path.isdir(source_dir): _log.error('<source_dir> does not exist or is not a directory: %s', source_dir) return False for cmd in ['7z', '7z.exe']: found, binary = gmShellAPI.detect_external_binary(binary=cmd) if found: break if not found: _log.warning('no 7z binary found') return None sandbox_dir = gmTools.mk_sandbox_dir() archive_path_inner = os.path.join(sandbox_dir, 'data') if not gmTools.mkdir(archive_path_inner): _log.error('cannot create scratch space for inner achive: %s', archive_path_inner) archive_fname_inner = 'data.zip' archive_name_inner = os.path.join(archive_path_inner, archive_fname_inner) archive_path_outer = gmTools.gmPaths().tmp_dir archive_fname_outer = 'datawrapper.zip' archive_name_outer = os.path.join(archive_path_outer, archive_fname_outer) # remove existing archives so they don't get *updated* rather than newly created if overwrite: if not gmTools.remove_file(archive_name_inner, force=True): _log.error('cannot remove existing archive [%s]', archive_name_inner) return False if not gmTools.remove_file(archive_name_outer, force=True): _log.error('cannot remove existing archive [%s]', archive_name_outer) return False # 7z does not support ZIP comments so create a text file holding the comment if comment is not None: tmp, fname = os.path.split(source_dir.rstrip(os.sep)) comment_filename = os.path.join(sandbox_dir, '000-%s-comment.txt' % fname) with open(comment_filename, mode='wt', encoding='utf8', errors='replace') as comment_file: comment_file.write(comment) # create inner (data) archive: uncompressed, unencrypted, similar to a tar archive args = [ binary, 'a', # create archive '-sas', # be smart about archive name extension '-bd', # no progress indicator '-mx0', # no compression (only store files) '-mcu=on', # UTF8 filenames '-l', # store content of links, not links '-scsUTF-8', # console charset '-tzip' # force ZIP format ] if verbose: args.append('-bb3') args.append('-bt') else: args.append('-bb1') args.append(archive_name_inner) args.append(source_dir) if comment is not None: args.append(comment_filename) success, exit_code, stdout = gmShellAPI.run_process(cmd_line=args, encoding='utf8', verbose=verbose) if not success: _log.error('cannot create inner archive') return None # create "decompress instructions" file instructions_filename = os.path.join( archive_path_inner, '000-on_Windows-open_with-WinZip_or_7z_tools') open(instructions_filename, mode='wt').close() # create outer (wrapper) archive: compressed, encrypted args = [ binary, 'a', # create archive '-sas', # be smart about archive name extension '-bd', # no progress indicator '-mx9', # best available zip compression ratio '-mcu=on', # UTF8 filenames '-l', # store content of links, not links '-scsUTF-8', # console charset '-tzip', # force ZIP format '-mem=AES256', # force useful encryption '-p%s' % passphrase # set passphrase ] if verbose: args.append('-bb3') args.append('-bt') else: args.append('-bb1') args.append(archive_name_outer) args.append(archive_path_inner) success, exit_code, stdout = gmShellAPI.run_process(cmd_line=args, encoding='utf8', verbose=verbose) if success: return archive_name_outer _log.error('cannot create outer archive') return None
def print_generic_document(parent=None, jobtype:str=None, episode=None): """Call LibreOffice Writer with a generated (fake) ODT file. Once Writer is closed, the ODT is imported if different from its initial content. Note: The file passed to LO _must_ reside in a directory the parents of which are all R-X by the user or else LO will throw up its arms in despair and fall back to the user's home dir upon saving. So, /tmp/user/$UID/some-file.odt will *not* work (because .../user/... is "drwx--x--x root.root") while /home/$USER/some/dir/some-file.odt does. """ sandbox = os.path.join(gmTools.gmPaths().user_tmp_dir, 'libreoffice') gmTools.mkdir(sandbox) fpath = gmTools.get_unique_filename(suffix = '.txt', tmp_dir = sandbox) doc_file = open(fpath, mode = 'wt') doc_file.write(__ODT_FILE_PREAMBLE) doc_file.write(_('Today: %s') % gmDateTime.pydt_now_here().strftime('%c')) doc_file.write('\n\n') prax = gmPraxis.gmCurrentPraxisBranch() doc_file.write('Praxis:\n') doc_file.write(prax.format()) doc_file.write('\n') doc_file.write('Praxis branch:\n') doc_file.write('\n'.join(prax.org_unit.format ( with_address = True, with_org = True, with_comms = True ))) doc_file.write('\n\n') pat = gmPerson.gmCurrentPatient(gmPerson.cPatient(12)) if pat.connected: doc_file.write('Patient:\n') doc_file.write(pat.get_description_gender()) doc_file.write('\n\n') for adr in pat.get_addresses(): doc_file.write(adr.format(single_line = False, verbose = True, show_type = True)) doc_file.write('\n\n') for chan in pat.get_comm_channels(): doc_file.werite(chan.format()) doc_file.write('\n\n') doc_file.write('Provider:\n') doc_file.write('\n'.join(gmStaff.gmCurrentProvider().get_staff().format())) doc_file.write('\n\n-----------------------------------------------------------------------------\n\n') doc_file.close() # convert txt -> odt success, ret_code, stdout = gmShellAPI.run_process ( cmd_line = [ 'lowriter', '--convert-to', 'odt', '--outdir', os.path.split(fpath)[0], fpath ], verbose = True ) if success: fpath = gmTools.fname_stem_with_path(fpath) + '.odt' else: _log.warning('cannot convert .txt to .odt') md5_before = gmTools.file2md5(fpath) gmShellAPI.run_process(cmd_line = ['lowriter', fpath], verbose = True) md5_after = gmTools.file2md5(fpath) if md5_before == md5_after: gmDispatcher.send(signal = 'statustext', msg = _('Document not modified. Discarding.'), beep = False) return if not pat.connected: shutil.move(fpath, gmTools.gmPaths().user_work_dir) gmDispatcher.send(signal = 'statustext', msg = _('No patient. Moved file into %s') % gmTools.gmPaths().user_work_dir, beep = False) return pat.export_area.add_file ( filename = fpath, hint = _('Generic letter, written at %s') % gmDateTime.pydt_now_here().strftime('%Y %b %d %H:%M') ) gmDispatcher.send(signal = 'display_widget', name = 'gmExportAreaPlugin')
def stage_single_PID_hl7_file(filename, source=None, encoding='utf8'): """Multi-step processing of HL7 files. - input must be single-MSH / single-PID / normalized HL7 - imports into clin.incoming_data_unmatched - needs write permissions in dir_of(filename) - moves PID files which were successfully staged into dir_of(filename)/done/PID/ """ local_log_name = gmTools.get_unique_filename ( prefix = gmTools.fname_stem(filename) + '-', suffix = '.stage.log' ) local_logger = logging.FileHandler(local_log_name) local_logger.setLevel(logging.DEBUG) root_logger = logging.getLogger('') root_logger.addHandler(local_logger) _log.info(u'staging [%s] as unmatched incoming HL7%s', filename, gmTools.coalesce(source, u'', u' (%s)')) _log.debug(u'log file: %s', local_log_name) # sanity checks/setup try: open(filename).close() orig_dir = os.path.split(filename)[0] done_dir = os.path.join(orig_dir, u'done') gmTools.mkdir(done_dir) error_dir = os.path.join(orig_dir, u'failed') gmTools.mkdir(error_dir) except Exception: _log.exception('cannot setup staging environment') root_logger.removeHandler(local_logger) return False # stage try: inc = create_incoming_data(u'HL7%s' % gmTools.coalesce(source, u'', u' (%s)'), filename) if inc is None: _log.error(u'cannot stage PID file: %s', filename) root_logger.removeHandler(local_logger) shutil.move(filename, error_dir) shutil.move(local_log_name, error_dir) return False inc.update_data_from_file(fname = filename) except Exception: _log.exception(u'error staging PID file') root_logger.removeHandler(local_logger) shutil.move(filename, error_dir) shutil.move(local_log_name, error_dir) return False # set additional data MSH_file = io.open(filename, mode = 'rt', encoding = 'utf8') raw_hl7 = MSH_file.read(1024 * 1024 * 5) # 5 MB max MSH_file.close() shutil.move(filename, done_dir) inc['comment'] = format_hl7_message ( message = raw_hl7, skip_empty_fields = True, eol = u'\n' ) HL7 = pyhl7.parse(raw_hl7) del raw_hl7 inc['comment'] += u'\n' inc['comment'] += (u'-' * 80) inc['comment'] += u'\n\n' log = io.open(local_log_name, mode = 'rt', encoding = 'utf8') inc['comment'] += log.read() log.close() try: inc['lastnames'] = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__name, component_num = PID_component__lastname) inc['firstnames'] = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__name, component_num = PID_component__firstname) val = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__name, component_num = PID_component__middlename) if val is not None: inc['firstnames'] += u' ' inc['firstnames'] += val val = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__dob) if val is not None: tmp = time.strptime(val, '%Y%m%d') inc['dob'] = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tzinfo = gmDateTime.gmCurrentLocalTimezone) val = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__gender) if val is not None: inc['gender'] = val inc['external_data_id'] = filename #u'fk_patient_candidates', # u'request_id', # request ID as found in <data> # u'postcode', # u'other_info', # other identifying info in .data # u'requestor', # Requestor of data (e.g. who ordered test results) if available in source data. # u'fk_identity_disambiguated', # u'comment', # a free text comment on this row, eg. why is it here, error logs etc # u'fk_provider_disambiguated' # The provider the data is relevant to. except Exception: _log.exception(u'cannot add more data') inc.save() _log.info(u'successfully staged') root_logger.removeHandler(local_logger) shutil.move(local_log_name, done_dir) return True
def split_hl7_file(filename, target_dir=None, encoding='utf8'): """Multi-step processing of HL7 files. - input can be multi-MSH / multi-PID / partially malformed HL7 - tries to fix oddities - splits by MSH - splits by PID into <target_dir> - needs write permissions in dir_of(filename) - moves HL7 files which were successfully split up into dir_of(filename)/done/ - returns (True|False, list_of_PID_files) """ local_log_name = gmTools.get_unique_filename ( prefix = gmTools.fname_stem(filename) + '-', suffix = '.split.log' ) local_logger = logging.FileHandler(local_log_name) local_logger.setLevel(logging.DEBUG) root_logger = logging.getLogger('') root_logger.addHandler(local_logger) _log.info('splitting HL7 file: %s', filename) _log.debug('log file: %s', local_log_name) # sanity checks/setup try: open(filename).close() orig_dir = os.path.split(filename)[0] done_dir = os.path.join(orig_dir, u'done') gmTools.mkdir(done_dir) error_dir = os.path.join(orig_dir, u'failed') gmTools.mkdir(error_dir) work_filename = gmTools.get_unique_filename(prefix = gmTools.fname_stem(filename) + '-', suffix = '.hl7') if target_dir is None: target_dir = os.path.join(orig_dir, u'PID') _log.debug('target dir: %s', target_dir) gmTools.mkdir(target_dir) except Exception: _log.exception('cannot setup splitting environment') root_logger.removeHandler(local_logger) return False, None # split target_names = [] try: shutil.copy(filename, work_filename) fixed_filename = __fix_malformed_hl7_file(work_filename, encoding = encoding) MSH_fnames = __split_hl7_file_by_MSH(fixed_filename, encoding) PID_fnames = [] for MSH_fname in MSH_fnames: PID_fnames.extend(__split_MSH_by_PID(MSH_fname)) for PID_fname in PID_fnames: shutil.move(PID_fname, target_dir) target_names.append(os.path.join(target_dir, os.path.split(PID_fname)[1])) except Exception: _log.exception('cannot split HL7 file') for target_name in target_names: try: os.remove(target_name) except: pass root_logger.removeHandler(local_logger) shutil.move(local_log_name, error_dir) return False, None _log.info('successfully split') root_logger.removeHandler(local_logger) try: shutil.move(filename, done_dir) shutil.move(local_log_name, done_dir) except shutil.Error: _log.exception('cannot move hl7 file or log file to holding area') return True, target_names
def stage_single_PID_hl7_file(filename, source=None, encoding='utf8'): """Multi-step processing of HL7 files. - input must be single-MSH / single-PID / normalized HL7 - imports into clin.incoming_data_unmatched - needs write permissions in dir_of(filename) - moves PID files which were successfully staged into dir_of(filename)/done/PID/ """ local_log_name = gmTools.get_unique_filename ( prefix = gmTools.fname_stem(filename) + '-', suffix = '.stage.log' ) local_logger = logging.FileHandler(local_log_name) local_logger.setLevel(logging.DEBUG) root_logger = logging.getLogger('') root_logger.addHandler(local_logger) _log.info('staging [%s] as unmatched incoming HL7%s', filename, gmTools.coalesce(source, '', ' (%s)')) _log.debug('log file: %s', local_log_name) # sanity checks/setup try: open(filename).close() orig_dir = os.path.split(filename)[0] done_dir = os.path.join(orig_dir, 'done') gmTools.mkdir(done_dir) error_dir = os.path.join(orig_dir, 'failed') gmTools.mkdir(error_dir) except Exception: _log.exception('cannot setup staging environment') root_logger.removeHandler(local_logger) return False # stage try: incoming = gmIncomingData.create_incoming_data('HL7%s' % gmTools.coalesce(source, '', ' (%s)'), filename) if incoming is None: _log.error('cannot stage PID file: %s', filename) root_logger.removeHandler(local_logger) shutil.move(filename, error_dir) shutil.move(local_log_name, error_dir) return False incoming.update_data_from_file(fname = filename) except Exception: _log.exception('error staging PID file') root_logger.removeHandler(local_logger) shutil.move(filename, error_dir) shutil.move(local_log_name, error_dir) return False # set additional data MSH_file = io.open(filename, mode = 'rt', encoding = 'utf8', newline = '') raw_hl7 = MSH_file.read(1024 * 1024 * 5) # 5 MB max MSH_file.close() shutil.move(filename, done_dir) incoming['comment'] = format_hl7_message ( message = raw_hl7, skip_empty_fields = True, eol = '\n' ) HL7 = pyhl7.parse(raw_hl7) del raw_hl7 incoming['comment'] += '\n' incoming['comment'] += ('-' * 80) incoming['comment'] += '\n\n' log = io.open(local_log_name, mode = 'rt', encoding = 'utf8') incoming['comment'] += log.read() log.close() try: incoming['lastnames'] = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__name, component_num = PID_component__lastname) incoming['firstnames'] = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__name, component_num = PID_component__firstname) val = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__name, component_num = PID_component__middlename) if val is not None: incoming['firstnames'] += ' ' incoming['firstnames'] += val val = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__dob) if val is not None: tmp = time.strptime(val, '%Y%m%d') incoming['dob'] = pyDT.datetime(tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tzinfo = gmDateTime.gmCurrentLocalTimezone) val = HL7.extract_field('PID', segment_num = 1, field_num = PID_field__gender) if val is not None: incoming['gender'] = val incoming['external_data_id'] = filename #u'fk_patient_candidates', # u'request_id', # request ID as found in <data> # u'postcode', # u'other_info', # other identifying info in .data # u'requestor', # Requestor of data (e.g. who ordered test results) if available in source data. # u'fk_identity_disambiguated', # u'comment', # a free text comment on this row, eg. why is it here, error logs etc # u'fk_provider_disambiguated' # The provider the data is relevant to. except Exception: _log.exception('cannot add more data') incoming.save() _log.info('successfully staged') root_logger.removeHandler(local_logger) shutil.move(local_log_name, done_dir) return True
'before_print_doc', 'before_fax_doc', 'before_mail_doc', 'before_print_doc_part', 'before_fax_doc_part', 'before_mail_doc_part', 'before_external_doc_access', 'db_maintenance_warning' ] README_pat_dir = """Directory for data files containing the current patient. Whenever the patient is changed GNUmed will export formatted demographics into files in this directory for use by 3rd party software. Currently exported formats: GDT, VCF (vcard), MCF (mecard), XML (linuxmednews)""" HOOK_SCRIPT_EXAMPLE = """#!/usr/bin/python3 # -*- coding: utf-8 -*- # #=========================================================================== # # Example script to be run off GNUmed hooks. # # It can print a message to stdout whenever any hook is invoked. # # Copy this file to ~/.gnumed/scripts/hook_script.py and modify as needed. # # Known hooks: # # %s # #===========================================================================
def export(self, base_dir=None, items=None, expand_compressed=False): if items is None: items = self.items if len(items) == 0: return None media_base_dir = base_dir from Gnumed.business.gmPerson import cPatient pat = cPatient(aPK_obj=self.__pk_identity) if media_base_dir is None: media_base_dir = gmTools.mk_sandbox_dir(prefix=u'exp-%s-' % pat.dirname) _log.debug('patient media base dir: %s', media_base_dir) doc_dir = os.path.join(media_base_dir, r'documents') if os.path.isdir(doc_dir): index_existing_docs = True else: index_existing_docs = False gmTools.mkdir(doc_dir) _html_start_data = { u'html_title_header': _('Patient data for'), u'html_title_patient': gmTools.html_escape_string( pat.get_description_gender(with_nickname=False) + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')), u'title': _('Patient data export'), u'pat_name': gmTools.html_escape_string( pat.get_description_gender(with_nickname=False)), u'pat_dob': gmTools.html_escape_string( _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')), u'mugshot_url': u'documents/no-such-file.png', u'mugshot_alt': _('no patient photograph available'), u'mugshot_title': u'', u'docs_title': _(u'Documents'), u'browse_root': _(u'browse storage medium'), u'browse_docs': _(u'browse documents area'), u'browse_dicomdir': u'', u'run_dicom_viewer': u'' } mugshot = pat.document_folder.latest_mugshot if mugshot is not None: _html_start_data['mugshot_url'] = mugshot.save_to_file( directory=doc_dir, adjust_extension=True) _html_start_data['mugshot_alt'] = _( 'patient photograph from %s') % gmDateTime.pydt_strftime( mugshot['date_generated'], '%B %Y') _html_start_data['mugshot_title'] = gmDateTime.pydt_strftime( mugshot['date_generated'], '%B %Y') if u'DICOMDIR' in os.listdir(media_base_dir): _html_start_data[ u'browse_dicomdir'] = u'<li><a href="./DICOMDIR">%s</a></li>' % _( u'show DICOMDIR file') # copy DWV into target dir dwv_target_dir = os.path.join(media_base_dir, u'dwv') gmTools.rmdir(dwv_target_dir) dwv_src_dir = os.path.join(gmTools.gmPaths().local_base_dir, u'dwv4export') if not os.path.isdir(dwv_src_dir): dwv_src_dir = os.path.join( gmTools.gmPaths().system_app_data_dir, u'dwv4export') try: shutil.copytree(dwv_src_dir, dwv_target_dir) _html_start_data[ u'run_dicom_viewer'] = u'<li><a href="./dwv/viewers/mobile-local/index.html">%s</a></li>' % _( u'run Radiology Images (DICOM) Viewer') except shutil.Error, OSError: _log.exception('cannot include DWV, skipping')
def export(self, base_dir=None, items=None, expand_compressed=False): if items is None: items = self.items if len(items) == 0: return None media_base_dir = base_dir from Gnumed.business.gmPerson import cPatient pat = cPatient(aPK_obj=self.__pk_identity) if media_base_dir is None: media_base_dir = gmTools.mk_sandbox_dir(prefix='exp-%s-' % pat.subdir_name) _log.debug('patient media base dir: %s', media_base_dir) doc_dir = os.path.join(media_base_dir, r'documents') if os.path.isdir(doc_dir): index_existing_docs = True else: index_existing_docs = False gmTools.mkdir(doc_dir) _html_start_data = { 'html_title_header': _('Patient data for'), 'html_title_patient': gmTools.html_escape_string( pat.get_description_gender(with_nickname=False) + ', ' + _('born') + ' ' + pat.get_formatted_dob('%Y %B %d')), 'title': _('Patient data export'), 'pat_name': gmTools.html_escape_string( pat.get_description_gender(with_nickname=False)), 'pat_dob': gmTools.html_escape_string( _('born') + ' ' + pat.get_formatted_dob('%Y %B %d')), 'mugshot_url': 'documents/no-such-file.png', 'mugshot_alt': _('no patient photograph available'), 'mugshot_title': '', 'docs_title': _('Documents'), 'browse_root': _('browse storage medium'), 'browse_docs': _('browse documents area'), 'browse_dicomdir': '', 'run_dicom_viewer': '' } mugshot = pat.document_folder.latest_mugshot if mugshot is not None: _html_start_data['mugshot_url'] = mugshot.save_to_file( directory=doc_dir, adjust_extension=True) _html_start_data['mugshot_alt'] = _( 'patient photograph from %s') % gmDateTime.pydt_strftime( mugshot['date_generated'], '%B %Y') _html_start_data['mugshot_title'] = gmDateTime.pydt_strftime( mugshot['date_generated'], '%B %Y') if 'DICOMDIR' in os.listdir(media_base_dir): _html_start_data[ 'browse_dicomdir'] = '<li><a href="./DICOMDIR">%s</a></li>' % _( 'show DICOMDIR file') # copy DWV into target dir dwv_src_dir = os.path.join(gmTools.gmPaths().local_base_dir, 'dwv4export') if not os.path.isdir(dwv_src_dir): dwv_src_dir = os.path.join( gmTools.gmPaths().system_app_data_dir, 'dwv4export') if os.path.isdir(dwv_src_dir): dwv_target_dir = os.path.join(media_base_dir, 'dwv') gmTools.rmdir(dwv_target_dir) try: shutil.copytree(dwv_src_dir, dwv_target_dir) _html_start_data[ 'run_dicom_viewer'] = '<li><a href="./dwv/viewers/mobile-local/index.html">%s</a></li>' % _( 'run Radiology Images (DICOM) Viewer') except (shutil.Error, OSError): _log.exception('cannot include DWV, skipping') # index.html # - header idx_fname = os.path.join(media_base_dir, 'index.html') idx_file = io.open(idx_fname, mode='wt', encoding='utf8') idx_file.write(_html_start % _html_start_data) # - middle (side effect ! -> exports items into files ...) existing_docs = os.listdir( doc_dir ) # get them now, or else we will include the to-be-exported items # - export items for item in items: item_path = item.save_to_file(directory=doc_dir) item_fname = os.path.split(item_path)[1] idx_file.write( _html_list_item % (item_fname, gmTools.html_escape_string(item['description']))) # - preexisting documents for doc_fname in existing_docs: idx_file.write( _html_list_item % (doc_fname, gmTools.html_escape_string(_('other: %s') % doc_fname))) # - footer _cfg = gmCfg2.gmCfgData() from Gnumed.business.gmPraxis import gmCurrentPraxisBranch prax = gmCurrentPraxisBranch() lines = [] adr = prax.branch.org_unit.address if adr is not None: lines.extend(adr.format()) for comm in prax.branch.org_unit.comm_channels: if comm['is_confidential'] is True: continue lines.append('%s: %s' % (comm['l10n_comm_type'], comm['url'])) adr = '' if len(lines) > 0: adr = gmTools.html_escape_string('\n'.join(lines), replace_eol=True, keep_visual_eol=True) _html_end_data = { 'branch': gmTools.html_escape_string(prax['branch']), 'praxis': gmTools.html_escape_string(prax['praxis']), 'date': gmTools.html_escape_string( gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format='%Y %B %d')), 'gm_ver': gmTools.html_escape_string(_cfg.get(option='client_version')), #'gm_ver': 'git HEAD', # for testing 'adr': adr } idx_file.write(_html_end % _html_end_data) idx_file.close() # start.html (just a copy of index.html, really ;-) start_fname = os.path.join(media_base_dir, 'start.html') try: shutil.copy2(idx_fname, start_fname) except Exception: _log.exception('cannot copy %s to %s', idx_fname, start_fname) # autorun.inf autorun_dict = {} autorun_dict['label'] = self._compute_autorun_inf_label(pat) autorun_dict['action'] = _('Browse patient data') autorun_dict['icon'] = '' media_icon_kwd = '$$gnumed_patient_media_export_icon' media_icon_kwd_exp = gmKeywordExpansion.get_expansion( keyword=media_icon_kwd, textual_only=False, binary_only=True) icon_tmp_file = media_icon_kwd_exp.save_to_file( target_mime='image/x-icon', target_extension='.ico', ignore_conversion_problems=True) if icon_tmp_file is None: _log.debug('cannot retrieve <%s>', media_icon_kwd) else: media_icon_fname = os.path.join(media_base_dir, 'gnumed.ico') try: shutil.move(icon_tmp_file, media_icon_fname) autorun_dict['icon'] = 'icon=gnumed.ico' except Exception: _log.exception('cannot move %s to %s', icon_tmp_file, media_icon_fname) autorun_fname = os.path.join(media_base_dir, 'autorun.inf') autorun_file = io.open(autorun_fname, mode='wt', encoding='cp1252', errors='replace') autorun_file.write(_autorun_inf % autorun_dict) autorun_file.close() # cd.inf cd_inf_fname = os.path.join(media_base_dir, 'cd.inf') cd_inf_file = io.open(cd_inf_fname, mode='wt', encoding='utf8') cd_inf_file.write( _cd_inf % (pat['lastnames'], pat['firstnames'], gmTools.coalesce(pat['gender'], '?'), pat.get_formatted_dob('%Y-%m-%d'), gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format='%Y-%m-%d'), pat.ID, _cfg.get(option='client_version'), ' / '.join([ '%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in pat.gender_list ]))) cd_inf_file.close() # README readme_fname = os.path.join(media_base_dir, 'README') readme_file = io.open(readme_fname, mode='wt', encoding='utf8') readme_file.write( _README % (pat.get_description_gender(with_nickname=False) + ', ' + _('born') + ' ' + pat.get_formatted_dob('%Y %B %d'))) readme_file.close() # patient demographics as GDT/XML/VCF pat.export_as_gdt(filename=os.path.join(media_base_dir, 'patient.gdt')) pat.export_as_xml_linuxmednews( filename=os.path.join(media_base_dir, 'patient.xml')) pat.export_as_vcard( filename=os.path.join(media_base_dir, 'patient.vcf')) # praxis VCF shutil.move(prax.vcf, os.path.join(media_base_dir, 'praxis.vcf')) return media_base_dir
def export(self, base_dir=None, items=None, with_metadata=True, expand_compressed=False): if items is None: items = self.items if len(items) == 0: return None from Gnumed.business.gmPerson import cPatient pat = cPatient(aPK_obj=self.__pk_identity) if base_dir is None: base_dir = gmTools.mk_sandbox_dir(prefix=u'exp-%s-' % pat.dirname) _log.debug('base dir: %s', base_dir) doc_dir = os.path.join(base_dir, r'documents') gmTools.mkdir(doc_dir) _html_start_data = { u'html_title_header': _('Patient data for'), u'html_title_patient': gmTools.html_escape_string( pat.get_description_gender(with_nickname=False) + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')), u'title': _('Patient data export'), u'pat_name': gmTools.html_escape_string( pat.get_description_gender(with_nickname=False)), u'pat_dob': gmTools.html_escape_string( _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')), u'mugshot_url': u'documents/no-such-file.png', u'mugshot_alt': _('no patient photograph available'), u'mugshot_title': u'', u'docs_title': _(u'Documents'), u'browse_root': _(u'browse storage medium'), u'browse_docs': _(u'browse documents area'), u'browse_dicomdir': u'' } mugshot = pat.document_folder.latest_mugshot if mugshot is not None: _html_start_data['mugshot_url'] = mugshot.export_to_file( directory=doc_dir) _html_start_data['mugshot_alt'] = _( 'patient photograph from %s') % gmDateTime.pydt_strftime( mugshot['date_generated'], '%B %Y') _html_start_data['mugshot_title'] = gmDateTime.pydt_strftime( mugshot['date_generated'], '%B %Y') # index.html idx_fname = os.path.join(base_dir, u'index.html') idx_file = io.open(idx_fname, mode=u'wt', encoding=u'utf8') # header existing_files = os.listdir(base_dir) if u'DICOMDIR' in existing_files: _html_start_data[ u'browse_dicomdir'] = u' <li><a href="./DICOMDIR">browse DICOMDIR</a></li>' idx_file.write(_html_start % _html_start_data) # middle for item in items: item_path = item.export_to_file(directory=doc_dir) item_fname = os.path.split(item_path)[1] idx_file.write( _html_list_item % (item_fname, gmTools.html_escape_string(item['description']))) # footer _cfg = gmCfg2.gmCfgData() from Gnumed.business.gmPraxis import gmCurrentPraxisBranch prax = gmCurrentPraxisBranch() lines = [] adr = prax.branch.org_unit.address if adr is not None: lines.extend(adr.format()) for comm in prax.branch.org_unit.comm_channels: if comm['is_confidential'] is True: continue lines.append(u'%s: %s' % (comm['l10n_comm_type'], comm['url'])) adr = u'' if len(lines) > 0: adr = gmTools.html_escape_string(u'\n'.join(lines), replace_eol=True, keep_visual_eol=True) _html_end_data = { 'branch': gmTools.html_escape_string(prax['branch']), 'praxis': gmTools.html_escape_string(prax['praxis']), 'date': gmTools.html_escape_string( gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format='%Y %B %d', encoding=u'utf8')), 'gm_ver': gmTools.html_escape_string(_cfg.get(option=u'client_version')), #'gm_ver': 'git HEAD', # for testing 'adr': adr } idx_file.write(_html_end % _html_end_data) idx_file.close() # autorun.inf autorun_fname = os.path.join(base_dir, u'autorun.inf') autorun_file = io.open(autorun_fname, mode=u'wt', encoding=u'utf8') autorun_file.write( _autorun_inf % ((pat.get_description_gender(with_nickname=False) + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d')).strip(), _('Browse patient data'))) autorun_file.close() # cd.inf cd_inf_fname = os.path.join(base_dir, u'cd.inf') cd_inf_file = io.open(cd_inf_fname, mode=u'wt', encoding=u'utf8') cd_inf_file.write( _cd_inf % (pat['lastnames'], pat['firstnames'], gmTools.coalesce(pat['gender'], u'?'), pat.get_formatted_dob('%Y-%m-%d'), gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format='%Y-%m-%d', encoding=u'utf8'), pat.ID, _cfg.get(option=u'client_version'), u' / '.join([ u'%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in pat.gender_list ]))) cd_inf_file.close() # README readme_fname = os.path.join(base_dir, u'README') readme_file = io.open(readme_fname, mode=u'wt', encoding=u'utf8') readme_file.write( _README % (pat.get_description_gender(with_nickname=False) + u', ' + _(u'born') + u' ' + pat.get_formatted_dob('%Y %B %d'))) readme_file.close() # patient demographics as GDT/XML/VCF pat.export_as_gdt(filename=os.path.join(base_dir, u'patient.gdt')) pat.export_as_xml_linuxmednews( filename=os.path.join(base_dir, u'patient.xml')) pat.export_as_vcard(filename=os.path.join(base_dir, u'patient.vcf')) # praxis VCF shutil.move(prax.vcf, os.path.join(base_dir, u'praxis.vcf')) return base_dir
def _on_save_items_button_pressed(self, event): event.Skip() items = self.__get_items_to_work_on(_('Saving entries')) if items is None: return pat = gmPerson.gmCurrentPatient() dlg = cExportAreaSaveAsDlg(self, -1, patient = pat, item_count = len(items)) choice = dlg.ShowModal() path = dlg._LBL_directory.Label generate_metadata = dlg._CHBOX_generate_metadata.IsChecked() use_subdir = dlg._CHBOX_use_subdirectory.IsChecked() encrypt = dlg._CHBOX_encrypt.IsChecked() dlg.DestroyLater() if choice == wx.ID_CANCEL: return if choice == wx.ID_SAVE: create_archive = True elif choice == wx.ID_OK: create_archive = False else: raise Exception('invalid return') if create_archive: export_dir = path zip_file = self.__export_as_zip ( gmTools.coalesce(encrypt, _('Saving entries as encrypted ZIP archive'), _('Saving entries as unencrypted ZIP archive')), items = items, encrypt = encrypt ) if zip_file is None: gmDispatcher.send(signal = 'statustext', msg = _('Cannot save: aborted or error.')) return gmTools.mkdir(export_dir) final_zip_file = shutil.move(zip_file, export_dir) gmDispatcher.send(signal = 'statustext', msg = _('Saved entries into [%s]') % final_zip_file) target = final_zip_file else: export_dir = self.__export_as_files ( gmTools.coalesce(encrypt, _('Saving entries as encrypted files'), _('Saving entries as unencrypted files')), base_dir = path, items = items, encrypt = encrypt, with_metadata = generate_metadata ) if export_dir is None: gmDispatcher.send(signal = 'statustext', msg = _('Cannot save: aborted or error.')) return gmDispatcher.send(signal = 'statustext', msg = _('Saved entries into [%s]') % export_dir) target = export_dir self.__save_soap_note(soap = _('Saved from export area to [%s]:\n - %s') % ( target, '\n - '.join([ i['description'] for i in items ]) )) self.__browse_patient_data(export_dir) # remove_entries ? return True
def split_hl7_file(filename, target_dir=None, encoding='utf8'): """Multi-step processing of HL7 files. - input can be multi-MSH / multi-PID / partially malformed HL7 - tries to fix oddities - splits by MSH - splits by PID into <target_dir> - needs write permissions in dir_of(filename) - moves HL7 files which were successfully split up into dir_of(filename)/done/ - returns (True|False, list_of_PID_files) """ local_log_name = gmTools.get_unique_filename ( prefix = gmTools.fname_stem(filename) + '-', suffix = '.split.log' ) local_logger = logging.FileHandler(local_log_name) local_logger.setLevel(logging.DEBUG) root_logger = logging.getLogger('') root_logger.addHandler(local_logger) _log.info('splitting HL7 file: %s', filename) _log.debug('log file: %s', local_log_name) # sanity checks/setup try: open(filename).close() orig_dir = os.path.split(filename)[0] done_dir = os.path.join(orig_dir, 'done') gmTools.mkdir(done_dir) error_dir = os.path.join(orig_dir, 'failed') gmTools.mkdir(error_dir) work_filename = gmTools.get_unique_filename(prefix = gmTools.fname_stem(filename) + '-', suffix = '.hl7') if target_dir is None: target_dir = os.path.join(orig_dir, 'PID') _log.debug('target dir: %s', target_dir) gmTools.mkdir(target_dir) except Exception: _log.exception('cannot setup splitting environment') root_logger.removeHandler(local_logger) return False, None # split target_names = [] try: shutil.copy(filename, work_filename) fixed_filename = __fix_malformed_hl7_file(work_filename, encoding = encoding) MSH_fnames = __split_hl7_file_by_MSH(fixed_filename, encoding) PID_fnames = [] for MSH_fname in MSH_fnames: PID_fnames.extend(__split_MSH_by_PID(MSH_fname)) for PID_fname in PID_fnames: shutil.move(PID_fname, target_dir) target_names.append(os.path.join(target_dir, os.path.split(PID_fname)[1])) except Exception: _log.exception('cannot split HL7 file') for target_name in target_names: try: os.remove(target_name) except Exception: pass root_logger.removeHandler(local_logger) shutil.move(local_log_name, error_dir) return False, None _log.info('successfully split') root_logger.removeHandler(local_logger) try: shutil.move(filename, done_dir) shutil.move(local_log_name, done_dir) except shutil.Error: _log.exception('cannot move hl7 file or log file to holding area') return True, target_names
def create_encrypted_zip_archive_from_dir(source_dir, comment=None, overwrite=True, passphrase=None, verbose=False): """Use 7z to create an encrypted ZIP archive of a directory. <source_dir> will be included into the archive <comment> included as a file containing the comment <overwrite> remove existing archive before creation, avoiding *updating* of those, and thereby including unintended data <passphrase> minimum length of 5 The resulting zip archive will always be named "datawrapper.zip" for confidentiality reasons. If callers want another name they will have to shutil.move() the zip file themselves. This archive will be compressed and AES256 encrypted with the given passphrase. Therefore, the result will not decrypt with earlier versions of unzip software. On Windows, 7z oder WinZip are needed. The zip format does not support header encryption thereby allowing attackers to gain knowledge of patient details by observing the names of files and directories inside the encrypted archive. To reduce that attack surface, GNUmed will create _another_ zip archive inside "datawrapper.zip", which eventually wraps up the patient data as "data.zip". That archive is not compressed and not encrypted, and can thus be unpacked with any old unzipper. Note that GNUmed does NOT remember the passphrase for you. You will have to take care of that yourself, and possibly also safely hand over the passphrase to any receivers of the zip archive. """ if len(passphrase) < 5: _log.error('<passphrase> must be at least 5 characters/signs/digits') return None gmLog2.add_word2hide(passphrase) source_dir = os.path.abspath(source_dir) if not os.path.isdir(source_dir): _log.error('<source_dir> does not exist or is not a directory: %s', source_dir) return False for cmd in ['7z', '7z.exe']: found, binary = gmShellAPI.detect_external_binary(binary = cmd) if found: break if not found: _log.warning('no 7z binary found') return None sandbox_dir = gmTools.mk_sandbox_dir() archive_path_inner = os.path.join(sandbox_dir, 'data') if not gmTools.mkdir(archive_path_inner): _log.error('cannot create scratch space for inner achive: %s', archive_path_inner) archive_fname_inner = 'data.zip' archive_name_inner = os.path.join(archive_path_inner, archive_fname_inner) archive_path_outer = gmTools.gmPaths().tmp_dir archive_fname_outer = 'datawrapper.zip' archive_name_outer = os.path.join(archive_path_outer, archive_fname_outer) # remove existing archives so they don't get *updated* rather than newly created if overwrite: if not gmTools.remove_file(archive_name_inner, force = True): _log.error('cannot remove existing archive [%s]', archive_name_inner) return False if not gmTools.remove_file(archive_name_outer, force = True): _log.error('cannot remove existing archive [%s]', archive_name_outer) return False # 7z does not support ZIP comments so create a text file holding the comment if comment is not None: tmp, fname = os.path.split(source_dir.rstrip(os.sep)) comment_filename = os.path.join(sandbox_dir, '000-%s-comment.txt' % fname) with open(comment_filename, mode = 'wt', encoding = 'utf8', errors = 'replace') as comment_file: comment_file.write(comment) # create inner (data) archive: uncompressed, unencrypted, similar to a tar archive args = [ binary, 'a', # create archive '-sas', # be smart about archive name extension '-bd', # no progress indicator '-mx0', # no compression (only store files) '-mcu=on', # UTF8 filenames '-l', # store content of links, not links '-scsUTF-8', # console charset '-tzip' # force ZIP format ] if verbose: args.append('-bb3') args.append('-bt') else: args.append('-bb1') args.append(archive_name_inner) args.append(source_dir) if comment is not None: args.append(comment_filename) success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose) if not success: _log.error('cannot create inner archive') return None # create "decompress instructions" file instructions_filename = os.path.join(archive_path_inner, '000-on_Windows-open_with-WinZip_or_7z_tools') open(instructions_filename, mode = 'wt').close() # create outer (wrapper) archive: compressed, encrypted args = [ binary, 'a', # create archive '-sas', # be smart about archive name extension '-bd', # no progress indicator '-mx9', # best available zip compression ratio '-mcu=on', # UTF8 filenames '-l', # store content of links, not links '-scsUTF-8', # console charset '-tzip', # force ZIP format '-mem=AES256', # force useful encryption '-p%s' % passphrase # set passphrase ] if verbose: args.append('-bb3') args.append('-bt') else: args.append('-bb1') args.append(archive_name_outer) args.append(archive_path_inner) success, exit_code, stdout = gmShellAPI.run_process(cmd_line = args, encoding = 'utf8', verbose = verbose) if success: return archive_name_outer _log.error('cannot create outer archive') return None