Package Gnumed :: Package business :: Module gmExportArea
[frames] | no frames]

Source Code for Module Gnumed.business.gmExportArea

   1  """GNUmed export area 
   2   
   3  Think shopping cart in a web shop. 
   4   
   5  This is where you want to put documents for further 
   6  processing by you or someone else, like your secretary. 
   7  """ 
   8  #============================================================ 
   9  __license__ = "GPL v2 or later" 
  10  __author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>" 
  11   
  12   
  13  import sys 
  14  import logging 
  15  import shutil 
  16  import os 
  17  import io 
  18  import platform 
  19   
  20   
  21  if __name__ == '__main__': 
  22          sys.path.insert(0, '../../') 
  23          from Gnumed.pycommon import gmI18N 
  24          gmI18N.activate_locale() 
  25          gmI18N.install_domain() 
  26  from Gnumed.pycommon import gmTools 
  27  from Gnumed.pycommon import gmBusinessDBObject 
  28  from Gnumed.pycommon import gmPG2 
  29  from Gnumed.pycommon import gmMimeLib 
  30  from Gnumed.pycommon import gmDateTime 
  31  from Gnumed.pycommon import gmCfg2 
  32  from Gnumed.pycommon import gmCrypto 
  33   
  34  from Gnumed.business import gmDocuments 
  35  from Gnumed.business import gmKeywordExpansion 
  36   
  37   
  38  _log = logging.getLogger('gm.exp_area') 
  39  _cfg = gmCfg2.gmCfgData() 
  40   
  41  PRINT_JOB_DESIGNATION = 'print' 
  42  DOCUMENTS_SUBDIR = 'documents' 
  43  DIRENTRY_README_NAME = '.README.GNUmed-DIRENTRY' 
  44   
  45  #============================================================ 
  46  # export area item handling 
  47  #------------------------------------------------------------ 
  48  _SQL_get_export_items = "SELECT * FROM clin.v_export_items WHERE %s" 
  49   
50 -class cExportItem(gmBusinessDBObject.cBusinessDBObject):
51 """Represents an item in the export area table""" 52 53 _cmd_fetch_payload = _SQL_get_export_items % "pk_export_item = %s" 54 _cmds_store_payload = [ 55 """UPDATE clin.export_item SET 56 fk_identity = %(pk_identity)s, 57 created_by = gm.nullify_empty_string(%(created_by)s), 58 created_when = %(created_when)s, 59 designation = gm.nullify_empty_string(%(designation)s), 60 description = gm.nullify_empty_string(%(description)s), 61 fk_doc_obj = %(pk_doc_obj)s, 62 data = CASE 63 WHEN %(pk_doc_obj)s IS NULL THEN coalesce(data, 'to be replaced by real data') 64 ELSE NULL 65 END, 66 filename = CASE 67 WHEN %(pk_doc_obj)s IS NULL THEN gm.nullify_empty_string(%(filename)s) 68 ELSE NULL 69 END 70 WHERE 71 pk = %(pk_export_item)s 72 AND 73 xmin = %(xmin_export_item)s 74 """, 75 _SQL_get_export_items % 'pk_export_item = %(pk_export_item)s' 76 ] 77 _updatable_fields = [ 78 'pk_identity', 79 'created_when', 80 'designation', 81 'description', 82 'pk_doc_obj', 83 'filename' 84 ] 85 #--------------------------------------------------------
86 - def __init__(self, aPK_obj=None, row=None, link_obj=None):
87 super(cExportItem, self).__init__(aPK_obj = aPK_obj, row = row, link_obj = link_obj) 88 # force auto-healing if need be 89 if self._payload[self._idx['pk_identity_raw_needs_update']]: 90 _log.warning ( 91 'auto-healing export item [%s] from identity [%s] to [%s] because of document part [%s] seems necessary', 92 self._payload[self._idx['pk_export_item']], 93 self._payload[self._idx['pk_identity_raw']], 94 self._payload[self._idx['pk_identity']], 95 self._payload[self._idx['pk_doc_obj']] 96 ) 97 if self._payload[self._idx['pk_doc_obj']] is None: 98 _log.error('however, .fk_doc_obj is NULL, which should not happen, leaving things alone for manual inspection') 99 return 100 # only flag ourselves as modified, do not actually 101 # modify any values, better safe than sorry 102 self._is_modified = True 103 self.save() 104 self.refetch_payload(ignore_changes = False, link_obj = link_obj)
105 106 #-------------------------------------------------------- 107 # def format(self): 108 # return u'%s' % self 109 #--------------------------------------------------------
110 - def update_data(self, data=None):
111 assert (data is not None), '<data> must not be <None>' 112 113 SQL = """ 114 UPDATE clin.export_item SET 115 data = %(data)s::bytea, 116 fk_doc_obj = NULL 117 WHERE pk = %(pk)s""" 118 args = {'pk': self.pk_obj, 'data': data} 119 gmPG2.run_rw_queries(queries = [{'cmd': SQL, 'args': args}], return_data = False, get_col_idx = False) 120 # must update XMIN now ... 121 self.refetch_payload() 122 return True
123 124 #--------------------------------------------------------
125 - def update_data_from_file(self, filename=None):
126 # sanity check 127 if not (os.access(filename, os.R_OK) and os.path.isfile(filename)): 128 _log.error('[%s] is not a readable file' % filename) 129 return False 130 131 cmd = """ 132 UPDATE clin.export_item SET 133 data = %(data)s::bytea, 134 fk_doc_obj = NULL, 135 filename = gm.nullify_empty_string(%(fname)s) 136 WHERE pk = %(pk)s""" 137 args = {'pk': self.pk_obj, 'fname': filename} 138 if not gmPG2.file2bytea(query = cmd, filename = filename, args = args): 139 return False 140 141 # must update XMIN now ... 142 self.refetch_payload() 143 return True
144 145 #--------------------------------------------------------
146 - def save_to_file(self, aChunkSize=0, filename=None, directory=None, passphrase=None):
147 148 # data linked from archive ? 149 part_fname = self.__save_doc_obj(filename = filename, directory = directory, passphrase = passphrase) 150 if part_fname is False: 151 return None 152 if part_fname is not None: 153 return part_fname 154 155 # valid DIRENTRY ? 156 if self.is_valid_DIRENTRY: 157 target_dir = self.__save_direntry(directory, passphrase = passphrase) 158 if target_dir is False: 159 return None 160 if target_dir is not None: 161 return target_dir 162 # but still a DIRENTRY ? 163 if self.is_DIRENTRY: 164 # yes, but not valid (other machine, local dir not found) 165 return None 166 167 # normal item with data in export area table 168 if filename is None: 169 filename = self.get_useful_filename(directory = directory) 170 success = gmPG2.bytea2file ( 171 data_query = { 172 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM clin.export_item WHERE pk = %(pk)s', 173 'args': {'pk': self.pk_obj} 174 }, 175 filename = filename, 176 chunk_size = aChunkSize, 177 data_size = self._payload[self._idx['size']] 178 ) 179 if not success: 180 return None 181 182 if filename.endswith('.dat'): 183 filename = gmMimeLib.adjust_extension_by_mimetype(filename) 184 if passphrase is None: 185 return filename 186 187 enc_filename = gmCrypto.encrypt_file ( 188 filename = filename, 189 passphrase = passphrase, 190 verbose = _cfg.get(option = 'debug'), 191 remove_unencrypted = True 192 ) 193 removed = gmTools.remove_file(filename) 194 if enc_filename is None: 195 _log.error('cannot encrypt') 196 return None 197 if removed: 198 return enc_filename 199 _log.error('cannot remove unencrypted file') 200 gmTools.remove(enc_filename) 201 return None
202 203 #--------------------------------------------------------
204 - def display_via_mime(self, chunksize=0, block=None):
205 206 # document part 207 if self._payload[self._idx['pk_doc_obj']] is not None: 208 return self.document_part.display_via_mime(chunksize = chunksize, block = block) 209 210 # DIR entry 211 if self._payload[self._idx['filename']].startswith('DIR::'): 212 # FIXME: error handling with malformed entries 213 tag, node, path = self._payload[self._idx['filename']].split('::', 2) 214 if node != platform.node(): 215 msg = _( 216 'This item points to a directory on the computer named:\n' 217 ' %s\n' 218 'You are, however, currently using another computer:\n' 219 ' %s\n' 220 'Directory items can only be viewed/saved/exported\n' 221 'on the computer they are pointing to.' 222 ) % (node, platform.node()) 223 return False, msg 224 success, msg = gmMimeLib.call_viewer_on_file(path, block = block) 225 return success, msg 226 227 # data -> save 228 fname = self.save_to_file(aChunkSize = chunksize) 229 if fname is None: 230 return False, '' 231 232 success, msg = gmMimeLib.call_viewer_on_file(fname, block = block) 233 if not success: 234 return False, msg 235 236 return True, ''
237 238 #--------------------------------------------------------
239 - def get_useful_filename(self, patient=None, directory=None):
240 patient_part = '' 241 if patient is not None: 242 patient_part = '-%s' % patient.subdir_name 243 244 # preserve original filename extension if available 245 suffix = '.dat' 246 if self._payload[self._idx['filename']] is not None: 247 tmp, suffix = os.path.splitext ( 248 gmTools.fname_sanitize(self._payload[self._idx['filename']]).lower() 249 ) 250 if suffix == '': 251 suffix = '.dat' 252 fname = gmTools.get_unique_filename ( 253 prefix = 'gm-export_item%s-' % patient_part, 254 suffix = suffix, 255 tmp_dir = directory 256 ) 257 return fname
258 259 #-------------------------------------------------------- 260 # helpers 261 #--------------------------------------------------------
262 - def __save_doc_obj(self, filename=None, directory=None, passphrase=None):
263 """Save doc object part into target. 264 265 None: not a doc obj 266 True: success 267 False: failure 268 """ 269 if self._payload[self._idx['pk_doc_obj']] is None: 270 return None 271 272 part = self.document_part 273 if filename is None: 274 filename = part.get_useful_filename ( 275 make_unique = False, 276 directory = directory, 277 include_gnumed_tag = False, 278 date_before_type = True, 279 name_first = False 280 ) 281 part_fname = part.save_to_file ( 282 aChunkSize = aChunkSize, 283 filename = filename, 284 ignore_conversion_problems = True, 285 adjust_extension = True 286 ) 287 if part_fname is None: 288 _log.error('cannot save document part to file') 289 return False 290 291 if passphrase is None: 292 return part_fname 293 294 enc_filename = gmCrypto.encrypt_file ( 295 filename = part_fname, 296 passphrase = passphrase, 297 verbose = _cfg.get(option = 'debug'), 298 remove_unencrypted = True 299 ) 300 removed = gmTools.remove_file(filename) 301 if enc_filename is None: 302 _log.error('cannot encrypt') 303 return False 304 if removed: 305 return enc_filename 306 _log.error('cannot remove unencrypted file') 307 gmTools.remove(enc_filename) 308 return False
309 310 #--------------------------------------------------------
311 - def __save_direntry(self, directory=None, passphrase=None):
312 """Move DIRENTRY source into target. 313 314 None: not a DIRENTRY 315 True: success 316 False: failure 317 """ 318 # do not process malformed entries 319 try: 320 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 321 except ValueError: 322 _log.exception('malformed DIRENTRY: [%s]', self._payload[self._idx['filename']]) 323 return False 324 # source and target paths must not overlap 325 if directory is None: 326 directory = gmTools.mk_sandbox_dir(prefix = 'exp-') 327 if directory.startswith(local_fs_path): 328 _log.error('cannot dump DIRENTRY item [%s]: must not be subdirectory of target dir [%s]', self._payload[self._idx['filename']], directory) 329 return False 330 if local_fs_path.startswith(directory): 331 _log.error('cannot dump DIRENTRY item [%s]: target dir [%s] must not be subdirectory of DIRENTRY', self._payload[self._idx['filename']], directory) 332 return False 333 334 _log.debug('dumping DIRENTRY item [%s] into [%s]', self._payload[self._idx['filename']], directory) 335 sandbox_dir = gmTools.mk_sandbox_dir() 336 _log.debug('sandbox: %s', sandbox_dir) 337 tmp = gmTools.copy_tree_content(local_fs_path, sandbox_dir) 338 if tmp is None: 339 _log.error('cannot dump DIRENTRY item [%s] into [%s]: copy error', self._payload[self._idx['filename']], sandbox_dir) 340 return False 341 342 gmTools.remove_file(os.path.join(tmp, DIRENTRY_README_NAME)) 343 344 if passphrase is not None: 345 _log.debug('encrypting sandbox: %s', sandbox_dir) 346 encrypted = gmCrypto.encrypt_directory_content ( 347 directory = sandbox_dir, 348 passphrase = passphrase, 349 verbose = _cfg.get(option = 'debug'), 350 remove_unencrypted = True 351 ) 352 if not encrypted: 353 _log.error('cannot dump DIRENTRY item [%s]: encryption problem in [%s]', self._payload[self._idx['filename']], sandbox_dir) 354 return False 355 356 tmp = gmTools.copy_tree_content(sandbox_dir, directory) 357 if tmp is None: 358 _log.debug('cannot dump DIRENTRY item [%s] into [%s]: copy error', self._payload[self._idx['filename']], directory) 359 return False 360 361 return directory
362 363 #-------------------------------------------------------- 364 # properties 365 #--------------------------------------------------------
366 - def _get_doc_part(self):
367 if self._payload[self._idx['pk_doc_obj']] is None: 368 return None 369 return gmDocuments.cDocumentPart(aPK_obj = self._payload[self._idx['pk_doc_obj']])
370 371 document_part = property(_get_doc_part, lambda x:x) 372 373 #--------------------------------------------------------
374 - def _get_is_print_job(self):
375 return self._payload[self._idx['designation']] == PRINT_JOB_DESIGNATION
376
377 - def _set_is_print_job(self, is_print_job):
378 desig = gmTools.bool2subst(is_print_job, PRINT_JOB_DESIGNATION, None, None) 379 if self._payload[self._idx['designation']] == desig: 380 return 381 self['designation'] = desig 382 self.save()
383 384 is_print_job = property(_get_is_print_job, _set_is_print_job) 385 386 #--------------------------------------------------------
387 - def _is_DIRENTRY(self):
388 """Check whether this item looks like a DIRENTRY.""" 389 if self._payload[self._idx['filename']] is None: 390 return False 391 if not self._payload[self._idx['filename']].startswith('DIR::'): 392 return False 393 if len(self._payload[self._idx['filename']].split('::', 2)) != 3: 394 return False 395 return True
396 397 is_DIRENTRY = property(_is_DIRENTRY) 398 399 #--------------------------------------------------------
400 - def _is_valid_DIRENTRY(self):
401 """Check whether this item is a _valid_ DIRENTRY.""" 402 if not self.is_DIRENTRY: 403 return False 404 # malformed ? 405 try: 406 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 407 except ValueError: 408 # should not happen because structure already checked in .is_DIRENTRY, 409 # better safe than sorry 410 _log.exception('DIRENTRY [%s]: malformed', self._payload[self._idx['filename']]) 411 return False 412 # this machine ? 413 if node != platform.node(): 414 _log.warning('DIRENTRY [%s]: not on this machine (%s)', self._payload[self._idx['filename']], platform.node()) 415 return False 416 # valid path ? 417 if not os.path.isdir(local_fs_path): 418 _log.warning('DIRENTRY [%s]: directory not found (old DIRENTRY ?)', self._payload[self._idx['filename']]) 419 return False 420 return True
421 422 is_valid_DIRENTRY = property(_is_valid_DIRENTRY) 423 424 #--------------------------------------------------------
425 - def _is_DICOM_directory(self):
426 """Check whether this item points to a DICOMDIR.""" 427 if not self._is_valid_DIRENTRY: 428 return False 429 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 430 found_DICOMDIR = False 431 for fs_entry in os.listdir(local_fs_path): 432 # found a subdir 433 if os.path.isdir(os.path.join(local_fs_path, fs_entry)): 434 # allow for any number of subdirs 435 continue 436 # found a file 437 if fs_entry != 'DICOMDIR': 438 # not named "DICOMDIR" -> not a DICOMDIR DIRENTRY 439 return False 440 # must be named DICOMDIR -> that's the only file allowed (and required) in ./ 441 found_DICOMDIR = True 442 return found_DICOMDIR
443 444 is_DICOM_directory = property(_is_DICOM_directory) 445 446 #--------------------------------------------------------
447 - def _has_files_in_root(self):
448 """True if there are files in the root directory.""" 449 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 450 for fs_entry in os.listdir(local_fs_path): 451 if os.path.isfile(fs_entry): 452 _log.debug('has files in top level: %s', local_fs_path) 453 return True 454 return False
455 456 has_files_in_root = property(_has_files_in_root)
457 458 #------------------------------------------------------------
459 -def get_export_items(order_by=None, pk_identity=None, designation=None, return_pks=False):
460 461 args = { 462 'pat': pk_identity, 463 'desig': gmTools.coalesce(designation, PRINT_JOB_DESIGNATION) 464 } 465 where_parts = [] 466 if pk_identity is not None: 467 where_parts.append('pk_identity = %(pat)s') 468 # note that invalidly linked items will be 469 # auto-healed when instantiated 470 if designation is None: 471 where_parts.append("designation IS DISTINCT FROM %(desig)s") 472 else: 473 where_parts.append('designation = %(desig)s') 474 475 if order_by is None: 476 order_by = '' 477 else: 478 order_by = ' ORDER BY %s' % order_by 479 480 cmd = (_SQL_get_export_items % ' AND '.join(where_parts)) + order_by 481 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 482 if return_pks: 483 return [ r['pk_export_item'] for r in rows ] 484 return [ cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'}) for r in rows ]
485 486 #------------------------------------------------------------
487 -def get_print_jobs(order_by=None, pk_identity=None):
488 return get_export_items(order_by = order_by, pk_identity = pk_identity, designation = PRINT_JOB_DESIGNATION)
489 490 #------------------------------------------------------------
491 -def create_export_item(description=None, pk_identity=None, pk_doc_obj=None, filename=None):
492 493 args = { 494 'desc': description, 495 'pk_obj': pk_doc_obj, 496 'pk_pat': pk_identity, 497 'fname': filename 498 } 499 cmd = """ 500 INSERT INTO clin.export_item ( 501 description, 502 fk_doc_obj, 503 fk_identity, 504 data, 505 filename 506 ) VALUES ( 507 gm.nullify_empty_string(%(desc)s), 508 %(pk_obj)s, 509 %(pk_pat)s, 510 (CASE 511 WHEN %(pk_obj)s IS NULL THEN %(fname)s::bytea 512 ELSE NULL::bytea 513 END), 514 (CASE 515 WHEN %(pk_obj)s IS NULL THEN gm.nullify_empty_string(%(fname)s) 516 ELSE NULL 517 END) 518 ) 519 RETURNING pk 520 """ 521 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 522 523 return cExportItem(aPK_obj = rows[0]['pk'])
524 525 #------------------------------------------------------------
526 -def delete_export_item(pk_export_item=None):
527 args = {'pk': pk_export_item} 528 cmd = "DELETE FROM clin.export_item WHERE pk = %(pk)s" 529 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 530 return True
531 532 #============================================================ 533 _FRONTPAGE_HTML_CONTENT = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 534 "http://www.w3.org/TR/html4/loose.dtd"> 535 <html> 536 <head> 537 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 538 <link rel="icon" type="image/x-icon" href="gnumed.ico"> 539 <title>%(html_title_header)s %(html_title_patient)s</title> 540 </head> 541 <body> 542 543 <h1>%(title)s</h1> 544 545 <p> 546 (%(date)s)<br> 547 </p> 548 549 <h2><a href="patient.vcf">Patient</a></h2> 550 551 <p> 552 %(pat_name)s<br> 553 %(pat_dob)s 554 </p> 555 556 <p><img src="%(mugshot_url)s" alt="%(mugshot_alt)s" title="%(mugshot_title)s" width="200" border="2"></p> 557 558 <h2>%(docs_title)s</h2> 559 560 <ul> 561 <li><a href="./">%(browse_root)s</a></li> 562 <li><a href="%(doc_subdir)s/">%(browse_docs)s</a></li> 563 %(browse_dicomdir)s 564 %(run_dicom_viewer)s 565 </ul> 566 567 <ul> 568 %(docs_list)s 569 </ul> 570 571 <h2><a href="praxis.vcf">Praxis</a></h2> 572 573 <p> 574 %(branch)s @ %(praxis)s 575 %(adr)s 576 </p> 577 578 <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p> 579 580 </body> 581 </html> 582 """ 583 584 _INDEX_HTML_CONTENT = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 585 "http://www.w3.org/TR/html4/loose.dtd"> 586 <html> 587 <head> 588 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 589 <link rel="icon" type="image/x-icon" href="gnumed.ico"> 590 <title>%(html_title_header)s</title> 591 </head> 592 <body> 593 594 <h1>%(title)s</h1> 595 596 <p> 597 (%(date)s)<br> 598 </p> 599 600 This is an encrypted patient data excerpt created by the GNUmed Electronic Medical Record. 601 602 <p> 603 For decryption you will need to 604 605 <ul> 606 <li>install decryption software and</li> 607 <li>obtain relevant passwords from the creator or holder of this media</li> 608 </ul> 609 610 <h2>Decryption software</h2> 611 612 For files ending in 613 614 <ul> 615 <li>.asc: install <a href="https://gnupg.org">GNU Privacy Guard</a></li> 616 <li>.7z: install <a href="https://www.7-zip.org">7-zip</a> or <a href="https://www.winzip.com">WinZip</a></li> 617 </ul> 618 619 620 <h2>%(docs_title)s</h2> 621 622 <ul> 623 <li><a href="./frontpage.html">front page (after decryption)</a></li> 624 <li><a href="./%(frontpage_fname)s">front page (if decryption from browser is supported)</a></li> 625 626 <li><a href="./">%(browse_root)s</a></li> 627 <li><a href="%(doc_subdir)s/">%(browse_docs)s</a></li> 628 </ul> 629 630 631 <h2><a href="praxis.vcf">Praxis</a></h2> 632 633 <p> 634 %(branch)s @ %(praxis)s 635 %(adr)s 636 </p> 637 638 <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p> 639 640 </body> 641 </html> 642 """ 643 644 #------------------------------------------------------------
645 -class cExportArea(object):
646
647 - def __init__(self, pk_identity):
648 self.__pk_identity = pk_identity
649 650 #--------------------------------------------------------
651 - def add_form(self, form=None, designation=None):
652 653 if len(form.final_output_filenames) == 0: 654 return True 655 656 items = [] 657 for fname in form.final_output_filenames: 658 item = self.add_file(filename = fname) 659 if item is None: 660 for prev_item in items: 661 delete_export_item(pk_export_item = prev_item['pk_export_item']) 662 return False 663 items.append(item) 664 item['description'] = _('form: %s %s (%s)') % (form.template['name_long'], form.template['external_version'], fname) 665 item['designation'] = designation 666 item.save() 667 668 return True
669 670 #--------------------------------------------------------
671 - def add_forms(self, forms=None, designation=None):
672 all_ok = True 673 for form in forms: 674 all_ok = all_ok and self.add_form(form = form, designation = designation) 675 676 return all_ok
677 678 #--------------------------------------------------------
679 - def add_path(self, path, comment=None):
680 """Add a DIR entry to the export area. 681 682 This sort of entry points to a certain directory on a 683 certain machine. The content of the the directory 684 will be included in exports, the directory *itself* 685 will not. For *that*, use a disposable top-level 686 directory into which you put the directory to include 687 as a subdirectory. 688 """ 689 assert (os.path.isdir(path)), '<path> must exist: %s' % path 690 691 path_item_data = 'DIR::%s::%s/' % (platform.node(), path.rstrip('/')) 692 _log.debug('attempting to add path item [%s]', path_item_data) 693 item = self.path_item_exists(path_item_data) 694 if item is not None: 695 _log.debug('[%s] already in export area', path) 696 return item 697 698 if comment is None: 699 comment = _('path [%s/] on computer "%s"') % ( 700 path.rstrip('/'), 701 platform.node() 702 ) 703 else: 704 comment += _(' (on "%s")') % platform.node() 705 706 item = create_export_item ( 707 description = comment, 708 pk_identity = self.__pk_identity, 709 filename = path_item_data 710 ) 711 try: 712 README = open(os.path.join(path, DIRENTRY_README_NAME), mode = 'wt', encoding = 'utf8') 713 README.write('GNUmed DIRENTRY information\n') 714 README.write('created: %s\n' % gmDateTime.pydt_now_here()) 715 README.write('machine: %s\n' % platform.node()) 716 README.write('path: %s\n' % path) 717 README.close() 718 except OSError: 719 _log.exception('READONLY DIRENTRY [%s]', path) 720 721 return item
722 723 #--------------------------------------------------------
724 - def path_item_exists(self, path_item_data):
725 726 assert (path_item_data.startswith('DIR::')), 'invalid <path_item_data> [%s]' % path_item_data 727 728 where_parts = [ 729 'pk_identity = %(pat)s', 730 'filename = %(fname)s', 731 'pk_doc_obj IS NULL' 732 ] 733 args = { 734 'pat': self.__pk_identity, 735 'fname': path_item_data 736 } 737 SQL = _SQL_get_export_items % ' AND '.join(where_parts) 738 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}], get_col_idx = True) 739 if len(rows) == 0: 740 return None 741 742 r = rows[0] 743 return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
744 745 #--------------------------------------------------------
746 - def add_file(self, filename=None, hint=None):
747 try: 748 open(filename).close() 749 except Exception: 750 _log.exception('cannot open file <%s>', filename) 751 return None 752 753 file_md5 = gmTools.file2md5(filename = filename, return_hex = True) 754 existing_item = self.md5_exists(md5 = file_md5, include_document_parts = False) 755 if existing_item is not None: 756 _log.debug('md5 match (%s): %s already in export area', file_md5, filename) 757 return existing_item 758 759 path, basename = os.path.split(filename) 760 item = create_export_item ( 761 description = '%s: %s (%s/)' % ( 762 gmTools.coalesce(hint, _('file'), '%s'), 763 basename, 764 path 765 ), 766 pk_identity = self.__pk_identity, 767 filename = filename 768 ) 769 770 if item.update_data_from_file(filename = filename): 771 return item 772 773 # failed to insert data, hence remove export item entry 774 delete_export_item(pk_export_item = item['pk_export_item']) 775 return None
776 777 #--------------------------------------------------------
778 - def add_files(self, filenames=None, hint=None):
779 all_ok = True 780 for fname in filenames: 781 all_ok = all_ok and (self.add_file(filename = fname, hint = hint) is not None) 782 783 return all_ok
784 785 #--------------------------------------------------------
786 - def add_documents(self, documents=None):
787 for doc in documents: 788 doc_tag = _('%s (%s)%s') % ( 789 doc['l10n_type'], 790 gmDateTime.pydt_strftime(doc['clin_when'], '%Y %b %d'), 791 gmTools.coalesce(doc['comment'], '', ' "%s"') 792 ) 793 for obj in doc.parts: 794 if self.document_part_item_exists(pk_part = obj['pk_obj']): 795 continue 796 f_ext = '' 797 if obj['filename'] is not None: 798 f_ext = os.path.splitext(obj['filename'])[1].strip('.').strip() 799 if f_ext != '': 800 f_ext = ' .' + f_ext.upper() 801 obj_tag = _('part %s (%s%s)%s') % ( 802 obj['seq_idx'], 803 gmTools.size2str(obj['size']), 804 f_ext, 805 gmTools.coalesce(obj['obj_comment'], '', ' "%s"') 806 ) 807 create_export_item ( 808 description = '%s - %s' % (doc_tag, obj_tag), 809 pk_doc_obj = obj['pk_obj'] 810 )
811 812 #--------------------------------------------------------
813 - def document_part_item_exists(self, pk_part=None):
814 cmd = "SELECT EXISTS (SELECT 1 FROM clin.export_item WHERE fk_doc_obj = %(pk_obj)s)" 815 args = {'pk_obj': pk_part} 816 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 817 return rows[0][0]
818 819 #--------------------------------------------------------
820 - def md5_exists(self, md5=None, include_document_parts=False):
821 where_parts = [ 822 'pk_identity = %(pat)s', 823 'md5_sum = %(md5)s' 824 ] 825 args = { 826 'pat': self.__pk_identity, 827 'md5': md5 828 } 829 830 if not include_document_parts: 831 where_parts.append('pk_doc_obj IS NULL') 832 833 cmd = _SQL_get_export_items % ' AND '.join(where_parts) 834 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 835 836 if len(rows) == 0: 837 return None 838 839 r = rows[0] 840 return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
841 842 #--------------------------------------------------------
843 - def remove_item(self, item):
844 if item.is_valid_DIRENTRY: 845 tag, node, local_fs_path = item['filename'].split('::', 2) 846 gmTools.remove_file(os.path.join(local_fs_path, DIRENTRY_README_NAME)) 847 elif item.is_DIRENTRY: 848 return False 849 850 return delete_export_item(pk_export_item = item['pk_export_item'])
851 852 #--------------------------------------------------------
853 - def dump_items_to_disk(self, base_dir=None, items=None, passphrase=None):
854 if items is None: 855 items = self.items 856 if len(items) == 0: 857 return None 858 if base_dir is None: 859 from Gnumed.business.gmPerson import cPatient 860 pat = cPatient(aPK_obj = self.__pk_identity) 861 base_dir = os.path.join(gmTools.mk_sandbox_dir(), pat.subdir_name) 862 gmTools.mkdir(base_dir) 863 _log.debug('dumping export items to: %s', base_dir) 864 for item in items: 865 if item.save_to_file(directory = base_dir, passphrase = passphrase) is None: 866 return None 867 return base_dir
868 869 #--------------------------------------------------------
870 - def dump_items_to_disk_as_zip(self, base_dir=None, items=None, passphrase=None):
871 _log.debug('target dir: %s', base_dir) 872 dump_dir = self.dump_items_to_disk(base_dir = base_dir, items = items) 873 if dump_dir is None: 874 _log.error('cannot dump export area items') 875 return None 876 zip_file = gmTools.create_zip_archive_from_dir ( 877 dump_dir, 878 comment = _('GNUmed Patient Media'), 879 overwrite = True, 880 passphrase = passphrase, 881 verbose = _cfg.get(option = 'debug') 882 ) 883 if zip_file is None: 884 _log.error('cannot zip export area items dump') 885 return None 886 return zip_file
887 888 #--------------------------------------------------------
889 - def export(self, base_dir=None, items=None, passphrase=None):
890 891 if items is None: 892 items = self.items 893 if len(items) == 0: 894 return None 895 896 from Gnumed.business.gmPerson import cPatient 897 pat = cPatient(aPK_obj = self.__pk_identity) 898 target_base_dir = base_dir 899 if target_base_dir is None: 900 target_base_dir = gmTools.mk_sandbox_dir(prefix = '%s-' % pat.subdir_name) 901 gmTools.mkdir(target_base_dir) 902 _log.debug('patient media base dir: %s', target_base_dir) 903 if not gmTools.dir_is_empty(target_base_dir): 904 _log.error('patient media base dir is not empty') 905 return False 906 907 from Gnumed.business.gmPraxis import gmCurrentPraxisBranch 908 prax = gmCurrentPraxisBranch() 909 910 html_data = {} 911 912 # 1) assemble everything in a sandbox 913 # - setup sandbox 914 sandbox_dir = gmTools.mk_sandbox_dir() 915 _log.debug('sandbox dir: %s', sandbox_dir) 916 doc_dir = os.path.join(sandbox_dir, DOCUMENTS_SUBDIR) 917 gmTools.mkdir(doc_dir) 918 # - export mugshot 919 mugshot = pat.document_folder.latest_mugshot 920 if mugshot is not None: 921 mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True) 922 fname = os.path.split(mugshot_fname)[1] 923 html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname) 924 html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') 925 html_data['mugshot_title'] = gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') 926 # - export patient demographics as GDT/XML/VCF/MCF 927 pat.export_as_gdt(filename = os.path.join(sandbox_dir, 'patient.gdt')) 928 pat.export_as_xml_linuxmednews(filename = os.path.join(sandbox_dir, 'patient.xml')) 929 pat.export_as_vcard(filename = os.path.join(sandbox_dir, 'patient.vcf')) 930 pat.export_as_mecard(filename = os.path.join(sandbox_dir, u'patient.mcf')) 931 # - create CD.INF 932 self._create_cd_inf(pat, sandbox_dir) 933 # - export items 934 docs_list = [] 935 for item in items: 936 # if it is a dicomdir - put it into the root of the target media 937 if item.is_DICOM_directory: 938 _log.debug('exporting DICOMDIR DIRENTRY') 939 # save into base dir 940 item_fname = item.save_to_file(directory = sandbox_dir) 941 # do not include into ./documents/ listing 942 continue 943 if item.is_valid_DIRENTRY: 944 _log.debug('exporting DIRENTRY') 945 # if there are files in the root dir: put it into a 946 # subdir of ./documents/ where subdir is the leaf 947 # of the the item .filename 948 if item.has_files_in_root: 949 tag, node, local_fs_path = item['filename'].split('::', 2) 950 subdir = local_fs_path.rstrip('/').split('/')[-1] 951 subdir = os.path.join(doc_dir, subdir) 952 gmTools.mkdir(subdir) 953 item_fname = item.save_to_file(directory = subdir) 954 # if it is subdirs only - put it into documents/ directly 955 else: 956 item_fname = item.save_to_file(directory = doc_dir) 957 # include into ./documents/ listing 958 fname = os.path.split(item_fname)[1] 959 docs_list.append([fname, gmTools.html_escape_string(item['description'])]) 960 continue 961 if item.is_DIRENTRY: 962 # DIRENTRY but not valid: skip 963 continue 964 # normal entry, doc obj links or data item 965 item_fname = item.save_to_file(directory = doc_dir) 966 fname = os.path.split(item_fname)[1] 967 # collect items so we can later correlate items and descriptions 968 # actually we should link to encrypted items, and to unencrypted ones 969 docs_list.append([fname, gmTools.html_escape_string(item['description'])]) 970 # - link to DICOMDIR if exists 971 if 'DICOMDIR' in os.listdir(sandbox_dir): # in root path 972 has_dicomdir = True 973 html_data['browse_dicomdir'] = '<li><a href="./DICOMDIR">%s</a></li>' % _('show DICOMDIR file') 974 else: 975 has_dicomdir = False 976 # - include DWV link if there is a DICOMDIR 977 if has_dicomdir: 978 # do not clone into media base to avoid encryption, 979 # DWV will be cloned to there later on 980 dwv_sandbox_dir = self._clone_dwv() 981 if dwv_sandbox_dir is not None: 982 html_data['run_dicom_viewer'] = '<li><a href="./dwv/viewers/mobile-local/index.html">%s</a></li>' % _('run Radiology Images (DICOM) Viewer') 983 # - create frontpage.html 984 frontpage_fname = self._create_frontpage_html(pat, prax, sandbox_dir, html_data, docs_list) 985 # - start.html (just a copy of frontpage.html) 986 # (later overwritten, if encryption is requested) 987 start_fname = os.path.join(sandbox_dir, 'start.html') 988 try: 989 shutil.copy2(frontpage_fname, start_fname) 990 except Exception: 991 _log.exception('cannot copy %s to %s', frontpage_fname, start_fname) 992 # - start.html (just a convenience copy of frontpage.html) 993 start_fname = os.path.join(sandbox_dir, 'start.html') 994 try: 995 shutil.copy2(frontpage_fname, start_fname) 996 except Exception: 997 _log.exception('cannot copy %s to %s', frontpage_fname, start_fname) 998 # - index.html (just a convenience copy of frontpage.html, overwritten if encryption is requested) 999 index_fname = os.path.join(sandbox_dir, 'index.html') 1000 try: 1001 shutil.copy2(frontpage_fname, index_fname) 1002 except Exception: 1003 _log.exception('cannot copy %s to %s', frontpage_fname, index_fname) 1004 1005 # 2) encrypt content of sandbox 1006 if passphrase is not None: 1007 encrypted = gmCrypto.encrypt_directory_content ( 1008 directory = sandbox_dir, 1009 receiver_key_ids = None, 1010 passphrase = passphrase, 1011 comment = None, 1012 verbose = _cfg.get(option = 'debug'), 1013 remove_unencrypted = True 1014 ) 1015 if not encrypted: 1016 _log.errror('cannot encrypt data in sandbox dir') 1017 return False 1018 1019 # 3) add never-to-be-encrypted data 1020 # - AUTORUN.INF 1021 # - README 1022 if passphrase is None: 1023 self._create_autorun_inf(pat, sandbox_dir) 1024 self._create_readme(pat, sandbox_dir) 1025 else: 1026 self._create_autorun_inf(None, sandbox_dir) 1027 self._create_readme(None, sandbox_dir) 1028 # - praxis VCF/MCF 1029 shutil.move(prax.vcf, os.path.join(sandbox_dir, 'praxis.vcf')) 1030 prax.export_as_mecard(filename = os.path.join(sandbox_dir, u'praxis.mcf')) 1031 # - include DWV code 1032 if has_dicomdir: 1033 self._clone_dwv(target_dir = sandbox_dir) 1034 # - index.html as boilerplate for decryption 1035 if passphrase is not None: 1036 index_fname = self._create_index_html(prax, sandbox_dir, html_data) 1037 1038 # 4) move sandbox to target dir 1039 target_dir = gmTools.copy_tree_content(sandbox_dir, target_base_dir) 1040 if target_dir is None: 1041 _log.error('cannot fill target base dir') 1042 return False 1043 1044 return target_dir
1045 1046 #--------------------------------------------------------
1047 - def export_as_zip(self, base_dir=None, items=None, passphrase=None):
1048 _log.debug('target dir: %s', base_dir) 1049 export_dir = self.export(base_dir = base_dir, items = items) 1050 if export_dir is None: 1051 _log.debug('cannot export items') 1052 return None 1053 if passphrase is None: 1054 zip_file = gmCrypto.create_zip_archive_from_dir ( 1055 export_dir, 1056 comment = _('GNUmed Patient Media'), 1057 overwrite = True, 1058 verbose = _cfg.get(option = 'debug') 1059 ) 1060 else: 1061 zip_file = gmCrypto.create_encrypted_zip_archive_from_dir ( 1062 export_dir, 1063 comment = _('GNUmed Patient Media'), 1064 overwrite = True, 1065 passphrase = passphrase, 1066 verbose = _cfg.get(option = 'debug') 1067 ) 1068 if zip_file is None: 1069 _log.debug('cannot create zip archive') 1070 return None 1071 return zip_file
1072 1073 #--------------------------------------------------------
1074 - def _create_index_html(self, praxis, directory, data):
1075 # header part 1076 _HTML_data = { 1077 'html_title_header': _('Patient data'), 1078 'title': _('Patient data excerpt'), 1079 'docs_title': _('Documents'), 1080 'browse_root': _('browse storage medium'), 1081 'browse_docs': _('browse documents area'), 1082 'doc_subdir': DOCUMENTS_SUBDIR, 1083 'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')) 1084 } 1085 frontpage_fname_enc = 'frontpage.html.asc' 1086 if os.path.isfile(os.path.join(directory, frontpage_fname_enc)): 1087 _HTML_data['frontpage_fname'] = frontpage_fname_enc 1088 frontpage_fname_enc = 'frontpage.html.7z' 1089 if os.path.isfile(os.path.join(directory, frontpage_fname_enc)): 1090 _HTML_data['frontpage_fname'] = frontpage_fname_enc 1091 # footer part 1092 lines = [] 1093 adr = praxis.branch.org_unit.address 1094 if adr is not None: 1095 lines.extend(adr.format()) 1096 for comm in praxis.branch.org_unit.comm_channels: 1097 if comm['is_confidential'] is True: 1098 continue 1099 lines.append('%s: %s' % ( 1100 comm['l10n_comm_type'], 1101 comm['url'] 1102 )) 1103 adr = '' 1104 if len(lines) > 0: 1105 adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True) 1106 _HTML_data['branch'] = gmTools.html_escape_string(praxis['branch']) 1107 _HTML_data['praxis'] = gmTools.html_escape_string(praxis['praxis']) 1108 _HTML_data['gm_ver'] = gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')) 1109 _HTML_data['adr'] = adr 1110 # create file 1111 index_fname = os.path.join(directory, 'index.html') 1112 index_file = io.open(index_fname, mode = 'wt', encoding = 'utf8') 1113 index_file.write(_INDEX_HTML_CONTENT % _HTML_data) 1114 index_file.close() 1115 return index_fname
1116 1117 #--------------------------------------------------------
1118 - def _create_frontpage_html(self, patient, praxis, directory, data, docs_list):
1119 # <li><a href="documents/filename-1.ext">document 1 description</a></li> 1120 _HTML_LIST_ITEM = ' <li><a href="%s">%s</a></li>' 1121 # header part 1122 _HTML_data = { 1123 'html_title_header': _('Patient data for'), 1124 'html_title_patient': gmTools.html_escape_string(patient.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + patient.get_formatted_dob('%Y %B %d')), 1125 'title': _('Patient data excerpt'), 1126 'pat_name': gmTools.html_escape_string(patient.get_description_gender(with_nickname = False)), 1127 'pat_dob': gmTools.html_escape_string(_('born') + ' ' + patient.get_formatted_dob('%Y %B %d')), 1128 'mugshot_url': 'documents/no-such-file.png', 1129 'mugshot_alt': _('no patient photograph available'), 1130 'mugshot_title': '', 1131 'docs_title': _('Documents'), 1132 'browse_root': _('browse storage medium'), 1133 'browse_docs': _('browse documents area'), 1134 'doc_subdir': DOCUMENTS_SUBDIR, 1135 'browse_dicomdir': '', 1136 'run_dicom_viewer': '', 1137 'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')) 1138 } 1139 for key in data: 1140 _HTML_data[key] = data[key] 1141 1142 # documents part 1143 _HTML_docs_list = [] 1144 for doc in docs_list: 1145 subdir = os.path.join(directory, DOCUMENTS_SUBDIR, doc[0]) 1146 if os.path.isdir(subdir): 1147 _HTML_docs_list.append(_HTML_LIST_ITEM % (os.path.join(DOCUMENTS_SUBDIR, doc[0]), _('DIRECTORY: %s/%s/') % (DOCUMENTS_SUBDIR, doc[0]))) 1148 _HTML_docs_list.append(' <ul>') 1149 for fname in os.listdir(subdir): 1150 tmp = os.path.join(subdir, fname) 1151 if os.path.isdir(tmp): 1152 _HTML_docs_list.append(' <li><a href="%s">%s</a></li>' % (os.path.join(DOCUMENTS_SUBDIR, doc[0], fname), _('DIRECTORY: %s/%s/%s/') % (DOCUMENTS_SUBDIR, doc[0], fname))) 1153 else: 1154 _HTML_docs_list.append(' <li><a href="%s">%s</a></li>' % (os.path.join(DOCUMENTS_SUBDIR, doc[0], fname), fname)) 1155 _HTML_docs_list.append(' </ul>') 1156 else: 1157 _HTML_docs_list.append(_HTML_LIST_ITEM % (doc[0], doc[1])) 1158 _HTML_data['docs_list'] = u'\n '.join(_HTML_docs_list) 1159 1160 # footer part 1161 lines = [] 1162 adr = praxis.branch.org_unit.address 1163 if adr is not None: 1164 lines.extend(adr.format()) 1165 for comm in praxis.branch.org_unit.comm_channels: 1166 if comm['is_confidential'] is True: 1167 continue 1168 lines.append('%s: %s' % ( 1169 comm['l10n_comm_type'], 1170 comm['url'] 1171 )) 1172 adr = '' 1173 if len(lines) > 0: 1174 adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True) 1175 _HTML_data['branch'] = gmTools.html_escape_string(praxis['branch']) 1176 _HTML_data['praxis'] = gmTools.html_escape_string(praxis['praxis']) 1177 _HTML_data['gm_ver'] = gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')) 1178 _HTML_data['adr'] = adr 1179 # create file 1180 frontpage_fname = os.path.join(directory, 'frontpage.html') 1181 frontpage_file = io.open(frontpage_fname, mode = 'wt', encoding = 'utf8') 1182 frontpage_file.write(_FRONTPAGE_HTML_CONTENT % _HTML_data) 1183 frontpage_file.close() 1184 return frontpage_fname
1185 1186 #--------------------------------------------------------
1187 - def _clone_dwv(self, target_dir=None):
1188 _log.debug('cloning dwv') 1189 # find DWV 1190 dwv_src_dir = os.path.join(gmTools.gmPaths().local_base_dir, 'resources', 'dwv4export') 1191 if not os.path.isdir(dwv_src_dir): 1192 _log.debug('[%s] not found', dwv_src_dir) 1193 dwv_src_dir = os.path.join(gmTools.gmPaths().system_app_data_dir, 'resources', 'dwv4export') 1194 if not os.path.isdir(dwv_src_dir): 1195 _log.debug('[%s] not found', dwv_src_dir) 1196 return None 1197 # clone it 1198 if target_dir is None: 1199 target_dir = gmTools.mk_sandbox_dir() 1200 dwv_target_dir = os.path.join(target_dir, 'dwv') 1201 gmTools.rmdir(dwv_target_dir) 1202 try: 1203 shutil.copytree(dwv_src_dir, dwv_target_dir) 1204 except (shutil.Error, OSError): 1205 _log.exception('cannot include DWV, skipping') 1206 return None 1207 1208 return dwv_target_dir
1209 1210 #--------------------------------------------------------
1211 - def _create_readme(self, patient, directory):
1212 _README_CONTENT = ( 1213 'This is a patient data excerpt created by the GNUmed Electronic Medical Record.\n' 1214 '\n' 1215 'Patient: %s\n' 1216 '\n' 1217 'Please display <frontpage.html> to browse patient data.\n' 1218 '\n' 1219 'Individual documents are stored in the subdirectory\n' 1220 '\n' 1221 ' documents/\n' 1222 '\n' 1223 '\n' 1224 'Data may need to be decrypted with either GNU Privacy\n' 1225 'Guard or 7zip/WinZip.\n' 1226 '\n' 1227 '.asc:\n' 1228 ' https://gnupg.org\n' 1229 '\n' 1230 '.7z:\n' 1231 ' https://www.7-zip.org\n' 1232 ' https://www.winzip.com\n' 1233 '\n' 1234 'To obtain any needed keys you will have to get in touch with\n' 1235 'the creator or the owner of this patient media excerpt.\n' 1236 ) 1237 readme_fname = os.path.join(directory, 'README') 1238 readme_file = io.open(readme_fname, mode = 'wt', encoding = 'utf8') 1239 if patient is None: 1240 pat_str = _('<protected>') 1241 else: 1242 pat_str = patient.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + patient.get_formatted_dob('%Y %B %d') 1243 readme_file.write(_README_CONTENT % pat_str) 1244 readme_file.close() 1245 return readme_fname
1246 1247 #--------------------------------------------------------
1248 - def _create_cd_inf(self, patient, directory):
1249 _CD_INF_CONTENT = ( 1250 '[Patient Info]\r\n' # needs \r\n for Windows 1251 'PatientName=%s, %s\r\n' 1252 'Gender=%s\r\n' 1253 'BirthDate=%s\r\n' 1254 'CreationDate=%s\r\n' 1255 'PID=%s\r\n' 1256 'EMR=GNUmed\r\n' 1257 'Version=%s\r\n' 1258 '#StudyDate=\r\n' 1259 '#VNRInfo=<body part>\r\n' 1260 '\r\n' 1261 '# name format: lastnames, firstnames\r\n' 1262 '# date format: YYYY-MM-DD (ISO 8601)\r\n' 1263 '# gender format: %s\r\n' 1264 ) 1265 fname = os.path.join(directory, 'CD.INF') 1266 cd_inf = io.open(fname, mode = 'wt', encoding = 'utf8') 1267 cd_inf.write(_CD_INF_CONTENT % ( 1268 patient['lastnames'], 1269 patient['firstnames'], 1270 gmTools.coalesce(patient['gender'], '?'), 1271 patient.get_formatted_dob('%Y-%m-%d'), 1272 gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y-%m-%d'), 1273 patient.ID, 1274 _cfg.get(option = 'client_version'), 1275 ' / '.join([ '%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in patient.gender_list ]) 1276 )) 1277 cd_inf.close() 1278 return fname
1279 1280 #--------------------------------------------------------
1281 - def _create_autorun_inf(self, patient, directory):
1282 _AUTORUN_INF_CONTENT = ( # needs \r\n for Windows 1283 '[AutoRun.Amd64]\r\n' # 64 bit 1284 'label=%(label)s\r\n' # patient name/DOB 1285 'shellexecute=index.html\r\n' 1286 'action=%(action)s\r\n' # % _('Browse patient data') 1287 '%(icon)s\r\n' # "icon=gnumed.ico" or "" 1288 'UseAutoPlay=1\r\n' 1289 '\r\n' 1290 '[AutoRun]\r\n' # 32 bit 1291 'label=%(label)s\r\n' # patient name/DOB 1292 'shellexecute=index.html\r\n' 1293 'action=%(action)s\r\n' # % _('Browse patient data') 1294 '%(icon)s\r\n' # "icon=gnumed.ico" or "" 1295 'UseAutoPlay=1\r\n' 1296 '\r\n' 1297 '[Content]\r\n' 1298 'PictureFiles=yes\r\n' 1299 'VideoFiles=yes\r\n' 1300 'MusicFiles=no\r\n' 1301 '\r\n' 1302 '[IgnoreContentPaths]\r\n' 1303 '\documents\r\n' 1304 '\r\n' 1305 '[unused]\r\n' 1306 'open=requires explicit executable\r\n' 1307 ) 1308 autorun_dict = { 1309 'label': self._compute_autorun_inf_label(patient), 1310 'action': _('Browse patient data'), 1311 'icon': '' 1312 } 1313 media_icon_kwd = '$$gnumed_patient_media_export_icon' 1314 media_icon_kwd_exp = gmKeywordExpansion.get_expansion ( 1315 keyword = media_icon_kwd, 1316 textual_only = False, 1317 binary_only = True 1318 ) 1319 icon_tmp_fname = media_icon_kwd_exp.save_to_file ( 1320 target_mime = 'image/x-icon', 1321 target_extension = '.ico', 1322 ignore_conversion_problems = True 1323 ) 1324 if icon_tmp_fname is None: 1325 _log.error('cannot retrieve <%s>', media_icon_kwd) 1326 else: 1327 media_icon_fname = os.path.join(directory, 'gnumed.ico') 1328 try: 1329 shutil.copy2(icon_tmp_fname, media_icon_fname) 1330 autorun_dict['icon'] = 'icon=gnumed.ico' 1331 except Exception: 1332 _log.exception('cannot move %s to %s', icon_tmp_fname, media_icon_fname) 1333 autorun_fname = os.path.join(directory, 'AUTORUN.INF') 1334 autorun_file = io.open(autorun_fname, mode = 'wt', encoding = 'cp1252', errors = 'replace') 1335 autorun_file.write(_AUTORUN_INF_CONTENT % autorun_dict) 1336 autorun_file.close() 1337 return autorun_fname
1338 1339 #--------------------------------------------------------
1340 - def _compute_autorun_inf_label(self, patient):
1341 if patient is None: 1342 # if no patient provided - assume the target media 1343 # is to be encrypted and thusly do not expose patient data, 1344 # AUTORUN.INF itself will not be encrypted because 1345 # Windows must be able to parse it 1346 return _('GNUmed patient data excerpt')[:32] 1347 1348 LABEL_MAX_LEN = 32 1349 dob = patient.get_formatted_dob(format = ' %Y%m%d', none_string = '', honor_estimation = False) 1350 if dob == '': 1351 gender_template = ' (%s)' 1352 else: 1353 gender_template = ' %s' 1354 gender = gmTools.coalesce(patient['gender'], '', gender_template) 1355 name_max_len = LABEL_MAX_LEN - len(gender) - len(dob) # they already include appropriate padding 1356 name = patient.active_name 1357 last = name['lastnames'].strip() 1358 first = name['firstnames'].strip() 1359 len_last = len(last) 1360 len_first = len(first) 1361 while (len_last + len_first + 1) > name_max_len: 1362 if len_first > 6: 1363 len_first -= 1 1364 if first[len_first - 1] == ' ': 1365 len_first -= 1 1366 continue 1367 len_last -= 1 1368 if last[len_last - 1] == ' ': 1369 len_last -= 1 1370 last = last[:len_last].strip().upper() 1371 first = first[:len_first].strip() 1372 # max 32 chars, supposedly ASCII, but CP1252 likely works pretty well 1373 label = (('%s %s%s%s' % (last, first, dob, gender)).strip())[:32] 1374 return label
1375 1376 #--------------------------------------------------------
1377 - def __encrypt_file(self, filename, passphrase=None):
1378 if passphrase is None: 1379 return filename 1380 enc_filename = gmCrypto.encrypt_file ( 1381 filename = filename, 1382 passphrase = passphrase, 1383 verbose = _cfg.get(option = 'debug') 1384 ) 1385 if enc_filename is None: 1386 _log.error('cannot encrypt') 1387 return None 1388 if not gmTools.remove_file(filename, log_error = True, force = True): 1389 _log.error('cannot remove unencrypted file') 1390 return None 1391 return enc_filename
1392 1393 #-------------------------------------------------------- 1394 # properties 1395 #--------------------------------------------------------
1396 - def get_items(self, designation=None, order_by='designation, description'):
1397 return get_export_items(order_by = order_by, pk_identity = self.__pk_identity, designation = designation)
1398 1399 items = property(get_items, lambda x:x) 1400 1401 #--------------------------------------------------------
1402 - def get_printouts(self, order_by='designation, description'):
1403 return get_print_jobs(order_by = order_by, pk_identity = self.__pk_identity)
1404 1405 printouts = property(get_printouts, lambda x:x)
1406 1407 #============================================================ 1408 if __name__ == '__main__': 1409 1410 if len(sys.argv) < 2: 1411 sys.exit() 1412 1413 if sys.argv[1] != 'test': 1414 sys.exit() 1415 1416 from Gnumed.pycommon import gmI18N 1417 gmI18N.activate_locale() 1418 gmI18N.install_domain() 1419 1420 from Gnumed.business import gmPraxis 1421 1422 #---------------------------------------
1423 - def test_export_items():
1424 # items = get_export_items() 1425 # for item in items: 1426 # print item.format() 1427 import random 1428 create_export_item(description = 'description %s' % random.random(), pk_identity = 12, pk_doc_obj = None, filename = 'dummy.dat') 1429 items = get_export_items() 1430 for item in items: 1431 print(item.format()) 1432 item['pk_doc_obj'] = 1 1433 item.save() 1434 print(item)
1435 1436 #---------------------------------------
1437 - def test_export_area():
1438 exp = cExportArea(12) 1439 #print exp.export_with_meta_data() 1440 #print exp.items 1441 #exp.add_file(sys.argv[2]) 1442 prax = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.cPraxisBranch(1)) 1443 #print(prax) 1444 #print(prax.branch) 1445 try: 1446 pwd = sys.argv[2] 1447 except IndexError: 1448 pwd = None 1449 print(exp.export(passphrase = pwd))
1450 1451 #---------------------------------------
1452 - def test_label():
1453 1454 from Gnumed.business.gmPerson import cPatient 1455 from Gnumed.business.gmPersonSearch import ask_for_patient 1456 1457 #while ask_for_patient() is not None: 1458 pat_min = 1 1459 pat_max = 100 1460 try: 1461 pat_min = int(sys.argv[2]) 1462 pat_max = int(sys.argv[3]) 1463 except: 1464 pass 1465 cPatient(aPK_obj = pat_min) 1466 f = io.open('x-auto_inf_labels.txt', mode = 'w', encoding = 'utf8') 1467 f.write('--------------------------------\n') 1468 f.write('12345678901234567890123456789012\n') 1469 f.write('--------------------------------\n') 1470 for pat_id in range(pat_min, pat_max): 1471 try: 1472 exp_area = cExportArea(pat_id) 1473 pat = cPatient(aPK_obj = pat_id) 1474 except: 1475 continue 1476 f.write(exp_area._compute_autorun_inf_label(pat) + '\n') 1477 f.close() 1478 return
1479 1480 #--------------------------------------- 1481 #test_export_items() 1482 test_export_area() 1483 #test_label() 1484 1485 sys.exit(0) 1486 1487 #============================================================ 1488 # CDROM "run.bat": 1489 # 1490 #@echo off 1491 # 1492 #if defined ProgramFiles(x86) ( 1493 # ::64-bit 1494 # start /B x64\mdicom.exe /scan . 1495 #) else ( 1496 # ::32-bit 1497 # start /B win32\mdicom.exe /scan . 1498 #) 1499 # 1500 #-------------------------------------------------- 1501