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 430 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 431 found_DICOMDIR = False 432 for fs_entry in os.listdir(local_fs_path): 433 # found a subdir 434 if os.path.isdir(os.path.join(local_fs_path, fs_entry)): 435 # allow for any number of subdirs 436 continue 437 # found a file 438 if fs_entry != 'DICOMDIR': 439 # not named "DICOMDIR" -> not a DICOMDIR DIRENTRY 440 return False 441 442 # must be named DICOMDIR -> that's the only file allowed (and required) in ./ 443 found_DICOMDIR = True 444 return found_DICOMDIR
445 446 is_DICOM_directory = property(_is_DICOM_directory) 447 448 #--------------------------------------------------------
449 - def _has_files_in_root(self):
450 """True if there are files in the root directory.""" 451 tag, node, local_fs_path = self._payload[self._idx['filename']].split('::', 2) 452 for fs_entry in os.listdir(local_fs_path): 453 if os.path.isfile(fs_entry): 454 _log.debug('has files in top level: %s', local_fs_path) 455 return True 456 return False
457 458 has_files_in_root = property(_has_files_in_root)
459 460 #------------------------------------------------------------
461 -def get_export_items(order_by=None, pk_identity=None, designation=None, return_pks=False):
462 463 args = { 464 'pat': pk_identity, 465 'desig': gmTools.coalesce(designation, PRINT_JOB_DESIGNATION) 466 } 467 where_parts = [] 468 if pk_identity is not None: 469 where_parts.append('pk_identity = %(pat)s') 470 # note that invalidly linked items will be 471 # auto-healed when instantiated 472 if designation is None: 473 where_parts.append("designation IS DISTINCT FROM %(desig)s") 474 else: 475 where_parts.append('designation = %(desig)s') 476 477 if order_by is None: 478 order_by = '' 479 else: 480 order_by = ' ORDER BY %s' % order_by 481 482 cmd = (_SQL_get_export_items % ' AND '.join(where_parts)) + order_by 483 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 484 if return_pks: 485 return [ r['pk_export_item'] for r in rows ] 486 return [ cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'}) for r in rows ]
487 488 #------------------------------------------------------------
489 -def get_print_jobs(order_by=None, pk_identity=None):
490 return get_export_items(order_by = order_by, pk_identity = pk_identity, designation = PRINT_JOB_DESIGNATION)
491 492 #------------------------------------------------------------
493 -def create_export_item(description=None, pk_identity=None, pk_doc_obj=None, filename=None):
494 495 args = { 496 'desc': description, 497 'pk_obj': pk_doc_obj, 498 'pk_pat': pk_identity, 499 'fname': filename 500 } 501 cmd = """ 502 INSERT INTO clin.export_item ( 503 description, 504 fk_doc_obj, 505 fk_identity, 506 data, 507 filename 508 ) VALUES ( 509 gm.nullify_empty_string(%(desc)s), 510 %(pk_obj)s, 511 %(pk_pat)s, 512 (CASE 513 WHEN %(pk_obj)s IS NULL THEN %(fname)s::bytea 514 ELSE NULL::bytea 515 END), 516 (CASE 517 WHEN %(pk_obj)s IS NULL THEN gm.nullify_empty_string(%(fname)s) 518 ELSE NULL 519 END) 520 ) 521 RETURNING pk 522 """ 523 rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}], return_data = True, get_col_idx = False) 524 525 return cExportItem(aPK_obj = rows[0]['pk'])
526 527 #------------------------------------------------------------
528 -def delete_export_item(pk_export_item=None):
529 args = {'pk': pk_export_item} 530 cmd = "DELETE FROM clin.export_item WHERE pk = %(pk)s" 531 gmPG2.run_rw_queries(queries = [{'cmd': cmd, 'args': args}]) 532 return True
533 534 #============================================================ 535 _FRONTPAGE_HTML_CONTENT = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 536 "http://www.w3.org/TR/html4/loose.dtd"> 537 <html> 538 <head> 539 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 540 <link rel="icon" type="image/x-icon" href="gnumed.ico"> 541 <title>%(html_title_header)s %(html_title_patient)s</title> 542 </head> 543 <body> 544 545 <h1>%(title)s</h1> 546 547 <p> 548 (%(date)s)<br> 549 </p> 550 551 <h2><a href="patient.vcf">Patient</a></h2> 552 553 <p> 554 %(pat_name)s<br> 555 %(pat_dob)s 556 </p> 557 558 <p><img src="%(mugshot_url)s" alt="%(mugshot_alt)s" title="%(mugshot_title)s" width="200" border="2"></p> 559 560 <h2>%(docs_title)s</h2> 561 562 <ul> 563 <li><a href="./">%(browse_root)s</a></li> 564 <li><a href="%(doc_subdir)s/">%(browse_docs)s</a></li> 565 %(browse_dicomdir)s 566 %(run_dicom_viewer)s 567 </ul> 568 569 <ul> 570 %(docs_list)s 571 </ul> 572 573 <h2><a href="praxis.vcf">Praxis</a></h2> 574 575 <p> 576 %(branch)s @ %(praxis)s 577 %(adr)s 578 </p> 579 580 <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p> 581 582 </body> 583 </html> 584 """ 585 586 _INDEX_HTML_CONTENT = """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 587 "http://www.w3.org/TR/html4/loose.dtd"> 588 <html> 589 <head> 590 <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 591 <link rel="icon" type="image/x-icon" href="gnumed.ico"> 592 <title>%(html_title_header)s</title> 593 </head> 594 <body> 595 596 <h1>%(title)s</h1> 597 598 <p> 599 (%(date)s)<br> 600 </p> 601 602 This is an encrypted patient data excerpt created by the GNUmed Electronic Medical Record. 603 604 <p> 605 For decryption you will need to 606 607 <ul> 608 <li>install decryption software and</li> 609 <li>obtain relevant passwords from the creator or holder of this media</li> 610 </ul> 611 612 <h2>Decryption software</h2> 613 614 For files ending in 615 616 <ul> 617 <li>.asc: install <a href="https://gnupg.org">GNU Privacy Guard</a></li> 618 <li>.7z: install <a href="https://www.7-zip.org">7-zip</a> or <a href="https://www.winzip.com">WinZip</a></li> 619 </ul> 620 621 622 <h2>%(docs_title)s</h2> 623 624 <ul> 625 <li><a href="./frontpage.html">front page (after decryption)</a></li> 626 <li><a href="./%(frontpage_fname)s">front page (if decryption from browser is supported)</a></li> 627 628 <li><a href="./">%(browse_root)s</a></li> 629 <li><a href="%(doc_subdir)s/">%(browse_docs)s</a></li> 630 </ul> 631 632 633 <h2><a href="praxis.vcf">Praxis</a></h2> 634 635 <p> 636 %(branch)s @ %(praxis)s 637 %(adr)s 638 </p> 639 640 <p>(<a href="http://www.gnumed.de">GNUmed</a> version %(gm_ver)s)</p> 641 642 </body> 643 </html> 644 """ 645 646 #------------------------------------------------------------
647 -class cExportArea(object):
648
649 - def __init__(self, pk_identity):
650 self.__pk_identity = pk_identity
651 652 #--------------------------------------------------------
653 - def add_form(self, form=None, designation=None):
654 655 if len(form.final_output_filenames) == 0: 656 return True 657 658 items = [] 659 for fname in form.final_output_filenames: 660 item = self.add_file(filename = fname) 661 if item is None: 662 for prev_item in items: 663 delete_export_item(pk_export_item = prev_item['pk_export_item']) 664 return False 665 items.append(item) 666 item['description'] = _('form: %s %s (%s)') % (form.template['name_long'], form.template['external_version'], fname) 667 item['designation'] = designation 668 item.save() 669 670 return True
671 672 #--------------------------------------------------------
673 - def add_forms(self, forms=None, designation=None):
674 all_ok = True 675 for form in forms: 676 all_ok = all_ok and self.add_form(form = form, designation = designation) 677 678 return all_ok
679 680 #--------------------------------------------------------
681 - def add_path(self, path, comment=None):
682 """Add a DIR entry to the export area. 683 684 This sort of entry points to a certain directory on a 685 certain machine. The content of the the directory 686 will be included in exports, the directory *itself* 687 will not. For *that*, use a disposable top-level 688 directory into which you put the directory to include 689 as a subdirectory. 690 """ 691 assert (os.path.isdir(path)), '<path> must exist: %s' % path 692 693 path_item_data = 'DIR::%s::%s/' % (platform.node(), path.rstrip('/')) 694 _log.debug('attempting to add path item [%s]', path_item_data) 695 item = self.path_item_exists(path_item_data) 696 if item is not None: 697 _log.debug('[%s] already in export area', path) 698 return item 699 700 if comment is None: 701 comment = _('path [%s/] on computer "%s"') % ( 702 path.rstrip('/'), 703 platform.node() 704 ) 705 else: 706 comment += _(' (on "%s")') % platform.node() 707 708 item = create_export_item ( 709 description = comment, 710 pk_identity = self.__pk_identity, 711 filename = path_item_data 712 ) 713 try: 714 README = open(os.path.join(path, DIRENTRY_README_NAME), mode = 'wt', encoding = 'utf8') 715 README.write('GNUmed DIRENTRY information\n') 716 README.write('created: %s\n' % gmDateTime.pydt_now_here()) 717 README.write('machine: %s\n' % platform.node()) 718 README.write('path: %s\n' % path) 719 README.close() 720 except OSError: 721 _log.exception('READONLY DIRENTRY [%s]', path) 722 723 return item
724 725 #--------------------------------------------------------
726 - def path_item_exists(self, path_item_data):
727 728 assert (path_item_data.startswith('DIR::')), 'invalid <path_item_data> [%s]' % path_item_data 729 730 where_parts = [ 731 'pk_identity = %(pat)s', 732 'filename = %(fname)s', 733 'pk_doc_obj IS NULL' 734 ] 735 args = { 736 'pat': self.__pk_identity, 737 'fname': path_item_data 738 } 739 SQL = _SQL_get_export_items % ' AND '.join(where_parts) 740 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': SQL, 'args': args}], get_col_idx = True) 741 if len(rows) == 0: 742 return None 743 744 r = rows[0] 745 return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
746 747 #--------------------------------------------------------
748 - def add_file(self, filename=None, hint=None):
749 try: 750 open(filename).close() 751 except Exception: 752 _log.exception('cannot open file <%s>', filename) 753 return None 754 755 file_md5 = gmTools.file2md5(filename = filename, return_hex = True) 756 existing_item = self.md5_exists(md5 = file_md5, include_document_parts = False) 757 if existing_item is not None: 758 _log.debug('md5 match (%s): %s already in export area', file_md5, filename) 759 return existing_item 760 761 path, basename = os.path.split(filename) 762 item = create_export_item ( 763 description = '%s: %s (%s/)' % ( 764 gmTools.coalesce(hint, _('file'), '%s'), 765 basename, 766 path 767 ), 768 pk_identity = self.__pk_identity, 769 filename = filename 770 ) 771 772 if item.update_data_from_file(filename = filename): 773 return item 774 775 # failed to insert data, hence remove export item entry 776 delete_export_item(pk_export_item = item['pk_export_item']) 777 return None
778 779 #--------------------------------------------------------
780 - def add_files(self, filenames=None, hint=None):
781 all_ok = True 782 for fname in filenames: 783 all_ok = all_ok and (self.add_file(filename = fname, hint = hint) is not None) 784 785 return all_ok
786 787 #--------------------------------------------------------
788 - def add_documents(self, documents=None):
789 for doc in documents: 790 doc_tag = _('%s (%s)%s') % ( 791 doc['l10n_type'], 792 gmDateTime.pydt_strftime(doc['clin_when'], '%Y %b %d'), 793 gmTools.coalesce(doc['comment'], '', ' "%s"') 794 ) 795 for obj in doc.parts: 796 if self.document_part_item_exists(pk_part = obj['pk_obj']): 797 continue 798 f_ext = '' 799 if obj['filename'] is not None: 800 f_ext = os.path.splitext(obj['filename'])[1].strip('.').strip() 801 if f_ext != '': 802 f_ext = ' .' + f_ext.upper() 803 obj_tag = _('part %s (%s%s)%s') % ( 804 obj['seq_idx'], 805 gmTools.size2str(obj['size']), 806 f_ext, 807 gmTools.coalesce(obj['obj_comment'], '', ' "%s"') 808 ) 809 create_export_item ( 810 description = '%s - %s' % (doc_tag, obj_tag), 811 pk_doc_obj = obj['pk_obj'] 812 )
813 814 #--------------------------------------------------------
815 - def document_part_item_exists(self, pk_part=None):
816 cmd = "SELECT EXISTS (SELECT 1 FROM clin.export_item WHERE fk_doc_obj = %(pk_obj)s)" 817 args = {'pk_obj': pk_part} 818 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = False) 819 return rows[0][0]
820 821 #--------------------------------------------------------
822 - def md5_exists(self, md5=None, include_document_parts=False):
823 where_parts = [ 824 'pk_identity = %(pat)s', 825 'md5_sum = %(md5)s' 826 ] 827 args = { 828 'pat': self.__pk_identity, 829 'md5': md5 830 } 831 832 if not include_document_parts: 833 where_parts.append('pk_doc_obj IS NULL') 834 835 cmd = _SQL_get_export_items % ' AND '.join(where_parts) 836 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True) 837 838 if len(rows) == 0: 839 return None 840 841 r = rows[0] 842 return cExportItem(row = {'data': r, 'idx': idx, 'pk_field': 'pk_export_item'})
843 844 #--------------------------------------------------------
845 - def remove_item(self, item):
846 if item.is_valid_DIRENTRY: 847 tag, node, local_fs_path = item['filename'].split('::', 2) 848 gmTools.remove_file(os.path.join(local_fs_path, DIRENTRY_README_NAME)) 849 elif item.is_DIRENTRY: 850 return False 851 852 return delete_export_item(pk_export_item = item['pk_export_item'])
853 854 #--------------------------------------------------------
855 - def dump_items_to_disk(self, base_dir=None, items=None, passphrase=None):
856 if items is None: 857 items = self.items 858 if len(items) == 0: 859 return None 860 if base_dir is None: 861 from Gnumed.business.gmPerson import cPatient 862 pat = cPatient(aPK_obj = self.__pk_identity) 863 base_dir = os.path.join(gmTools.mk_sandbox_dir(), pat.subdir_name) 864 gmTools.mkdir(base_dir) 865 _log.debug('dumping export items to: %s', base_dir) 866 for item in items: 867 if item.save_to_file(directory = base_dir, passphrase = passphrase) is None: 868 return None 869 return base_dir
870 871 #--------------------------------------------------------
872 - def dump_items_to_disk_as_zip(self, base_dir=None, items=None, passphrase=None):
873 _log.debug('target dir: %s', base_dir) 874 dump_dir = self.dump_items_to_disk(base_dir = base_dir, items = items) 875 if dump_dir is None: 876 _log.error('cannot dump export area items') 877 return None 878 zip_file = gmTools.create_zip_archive_from_dir ( 879 dump_dir, 880 comment = _('GNUmed Patient Media'), 881 overwrite = True, 882 passphrase = passphrase, 883 verbose = _cfg.get(option = 'debug') 884 ) 885 if zip_file is None: 886 _log.error('cannot zip export area items dump') 887 return None 888 return zip_file
889 890 #--------------------------------------------------------
891 - def export(self, base_dir=None, items=None, passphrase=None):
892 893 if items is None: 894 items = self.items 895 if len(items) == 0: 896 return None 897 898 from Gnumed.business.gmPerson import cPatient 899 pat = cPatient(aPK_obj = self.__pk_identity) 900 target_base_dir = base_dir 901 if target_base_dir is None: 902 target_base_dir = gmTools.mk_sandbox_dir(prefix = '%s-' % pat.subdir_name) 903 gmTools.mkdir(target_base_dir) 904 _log.debug('patient media base dir: %s', target_base_dir) 905 if not gmTools.dir_is_empty(target_base_dir): 906 _log.error('patient media base dir is not empty') 907 return False 908 909 from Gnumed.business.gmPraxis import gmCurrentPraxisBranch 910 prax = gmCurrentPraxisBranch() 911 912 html_data = {} 913 914 # 1) assemble everything in a sandbox 915 # - setup sandbox 916 sandbox_dir = gmTools.mk_sandbox_dir() 917 _log.debug('sandbox dir: %s', sandbox_dir) 918 doc_dir = os.path.join(sandbox_dir, DOCUMENTS_SUBDIR) 919 gmTools.mkdir(doc_dir) 920 # - export mugshot 921 mugshot = pat.document_folder.latest_mugshot 922 if mugshot is not None: 923 mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True) 924 fname = os.path.split(mugshot_fname)[1] 925 html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname) 926 html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') 927 html_data['mugshot_title'] = gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y') 928 # - export patient demographics as GDT/XML/VCF/MCF 929 pat.export_as_gdt(filename = os.path.join(sandbox_dir, 'patient.gdt')) 930 pat.export_as_xml_linuxmednews(filename = os.path.join(sandbox_dir, 'patient.xml')) 931 pat.export_as_vcard(filename = os.path.join(sandbox_dir, 'patient.vcf')) 932 pat.export_as_mecard(filename = os.path.join(sandbox_dir, u'patient.mcf')) 933 # - create CD.INF 934 self._create_cd_inf(pat, sandbox_dir) 935 # - export items 936 docs_list = [] 937 for item in items: 938 # if it is a dicomdir - put it into the root of the target media 939 if item.is_DICOM_directory: 940 _log.debug('exporting DICOMDIR DIRENTRY') 941 # save into base dir 942 item_fname = item.save_to_file(directory = sandbox_dir) 943 # do not include into ./documents/ listing 944 continue 945 if item.is_valid_DIRENTRY: 946 _log.debug('exporting DIRENTRY') 947 # if there are files in the root dir: put it into a 948 # subdir of ./documents/ where subdir is the leaf 949 # of the the item .filename 950 if item.has_files_in_root: 951 tag, node, local_fs_path = item['filename'].split('::', 2) 952 subdir = local_fs_path.rstrip('/').split('/')[-1] 953 subdir = os.path.join(doc_dir, subdir) 954 gmTools.mkdir(subdir) 955 item_fname = item.save_to_file(directory = subdir) 956 # if it is subdirs only - put it into documents/ directly 957 else: 958 item_fname = item.save_to_file(directory = doc_dir) 959 # include into ./documents/ listing 960 fname = os.path.split(item_fname)[1] 961 docs_list.append([fname, gmTools.html_escape_string(item['description'])]) 962 continue 963 if item.is_DIRENTRY: 964 # DIRENTRY but not valid: skip 965 continue 966 # normal entry, doc obj links or data item 967 item_fname = item.save_to_file(directory = doc_dir) 968 fname = os.path.split(item_fname)[1] 969 # collect items so we can later correlate items and descriptions 970 # actually we should link to encrypted items, and to unencrypted ones 971 docs_list.append([fname, gmTools.html_escape_string(item['description'])]) 972 # - link to DICOMDIR if exists 973 if 'DICOMDIR' in os.listdir(sandbox_dir): # in root path 974 has_dicomdir = True 975 html_data['browse_dicomdir'] = '<li><a href="./DICOMDIR">%s</a></li>' % _('show DICOMDIR file') 976 else: 977 has_dicomdir = False 978 # - include DWV link if there is a DICOMDIR 979 if has_dicomdir: 980 # do not clone into media base to avoid encryption, 981 # DWV will be cloned to there later on 982 dwv_sandbox_dir = self._clone_dwv() 983 if dwv_sandbox_dir is not None: 984 html_data['run_dicom_viewer'] = '<li><a href="./dwv/viewers/mobile-local/index.html">%s</a></li>' % _('run Radiology Images (DICOM) Viewer') 985 # - create frontpage.html 986 frontpage_fname = self._create_frontpage_html(pat, prax, sandbox_dir, html_data, docs_list) 987 # - start.html (just a convenience copy of frontpage.html) 988 # (later overwritten, if encryption is requested) 989 start_fname = os.path.join(sandbox_dir, 'start.html') 990 try: 991 shutil.copy2(frontpage_fname, start_fname) 992 except Exception: 993 _log.exception('cannot copy %s to %s', frontpage_fname, start_fname) 994 # - index.html (just a convenience copy of frontpage.html) 995 # (later overwritten if encryption is requested) 996 index_fname = os.path.join(sandbox_dir, 'index.html') 997 try: 998 shutil.copy2(frontpage_fname, index_fname) 999 except Exception: 1000 _log.exception('cannot copy %s to %s', frontpage_fname, index_fname) 1001 1002 # 2) encrypt content of sandbox 1003 if passphrase is not None: 1004 encrypted = gmCrypto.encrypt_directory_content ( 1005 directory = sandbox_dir, 1006 receiver_key_ids = None, 1007 passphrase = passphrase, 1008 comment = None, 1009 verbose = _cfg.get(option = 'debug'), 1010 remove_unencrypted = True 1011 ) 1012 if not encrypted: 1013 _log.errror('cannot encrypt data in sandbox dir') 1014 return False 1015 1016 # 3) add never-to-be-encrypted data 1017 # - AUTORUN.INF 1018 # - README 1019 if passphrase is None: 1020 self._create_autorun_inf(pat, sandbox_dir) 1021 self._create_readme(pat, sandbox_dir) 1022 else: 1023 self._create_autorun_inf(None, sandbox_dir) 1024 self._create_readme(None, sandbox_dir) 1025 # - praxis VCF/MCF 1026 shutil.move(prax.vcf, os.path.join(sandbox_dir, 'praxis.vcf')) 1027 prax.export_as_mecard(filename = os.path.join(sandbox_dir, u'praxis.mcf')) 1028 # - include DWV code 1029 if has_dicomdir: 1030 self._clone_dwv(target_dir = sandbox_dir) 1031 # - index.html as boilerplate for decryption 1032 if passphrase is not None: 1033 index_fname = self._create_index_html(prax, sandbox_dir, html_data) 1034 1035 # 4) move sandbox to target dir 1036 target_dir = gmTools.copy_tree_content(sandbox_dir, target_base_dir) 1037 if target_dir is None: 1038 _log.error('cannot fill target base dir') 1039 return False 1040 1041 return target_dir
1042 1043 #--------------------------------------------------------
1044 - def export_as_zip(self, base_dir=None, items=None, passphrase=None):
1045 _log.debug('target dir: %s', base_dir) 1046 export_dir = self.export(base_dir = base_dir, items = items) 1047 if export_dir is None: 1048 _log.debug('cannot export items') 1049 return None 1050 if passphrase is None: 1051 zip_file = gmCrypto.create_zip_archive_from_dir ( 1052 export_dir, 1053 comment = _('GNUmed Patient Media'), 1054 overwrite = True, 1055 verbose = _cfg.get(option = 'debug') 1056 ) 1057 else: 1058 zip_file = gmCrypto.create_encrypted_zip_archive_from_dir ( 1059 export_dir, 1060 comment = _('GNUmed Patient Media'), 1061 overwrite = True, 1062 passphrase = passphrase, 1063 verbose = _cfg.get(option = 'debug') 1064 ) 1065 if zip_file is None: 1066 _log.debug('cannot create zip archive') 1067 return None 1068 return zip_file
1069 1070 #--------------------------------------------------------
1071 - def _create_index_html(self, praxis, directory, data):
1072 # header part 1073 _HTML_data = { 1074 'html_title_header': _('Patient data'), 1075 'title': _('Patient data excerpt'), 1076 'docs_title': _('Documents'), 1077 'browse_root': _('browse storage medium'), 1078 'browse_docs': _('browse documents area'), 1079 'doc_subdir': DOCUMENTS_SUBDIR, 1080 'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')) 1081 } 1082 frontpage_fname_enc = 'frontpage.html.asc' 1083 if os.path.isfile(os.path.join(directory, frontpage_fname_enc)): 1084 _HTML_data['frontpage_fname'] = frontpage_fname_enc 1085 frontpage_fname_enc = 'frontpage.html.7z' 1086 if os.path.isfile(os.path.join(directory, frontpage_fname_enc)): 1087 _HTML_data['frontpage_fname'] = frontpage_fname_enc 1088 # footer part 1089 lines = [] 1090 adr = praxis.branch.org_unit.address 1091 if adr is not None: 1092 lines.extend(adr.format()) 1093 for comm in praxis.branch.org_unit.comm_channels: 1094 if comm['is_confidential'] is True: 1095 continue 1096 lines.append('%s: %s' % ( 1097 comm['l10n_comm_type'], 1098 comm['url'] 1099 )) 1100 adr = '' 1101 if len(lines) > 0: 1102 adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True) 1103 _HTML_data['branch'] = gmTools.html_escape_string(praxis['branch']) 1104 _HTML_data['praxis'] = gmTools.html_escape_string(praxis['praxis']) 1105 _HTML_data['gm_ver'] = gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')) 1106 _HTML_data['adr'] = adr 1107 # create file 1108 index_fname = os.path.join(directory, 'index.html') 1109 index_file = io.open(index_fname, mode = 'wt', encoding = 'utf8') 1110 index_file.write(_INDEX_HTML_CONTENT % _HTML_data) 1111 index_file.close() 1112 return index_fname
1113 1114 #--------------------------------------------------------
1115 - def _create_frontpage_html(self, patient, praxis, directory, data, docs_list):
1116 # <li><a href="documents/filename-1.ext">document 1 description</a></li> 1117 _HTML_LIST_ITEM = ' <li><a href="%s">%s</a></li>' 1118 # header part 1119 _HTML_data = { 1120 'html_title_header': _('Patient data for'), 1121 'html_title_patient': gmTools.html_escape_string(patient.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + patient.get_formatted_dob('%Y %B %d')), 1122 'title': _('Patient data excerpt'), 1123 'pat_name': gmTools.html_escape_string(patient.get_description_gender(with_nickname = False)), 1124 'pat_dob': gmTools.html_escape_string(_('born') + ' ' + patient.get_formatted_dob('%Y %B %d')), 1125 'mugshot_url': 'documents/no-such-file.png', 1126 'mugshot_alt': _('no patient photograph available'), 1127 'mugshot_title': '', 1128 'docs_title': _('Documents'), 1129 'browse_root': _('browse storage medium'), 1130 'browse_docs': _('browse documents area'), 1131 'doc_subdir': DOCUMENTS_SUBDIR, 1132 'browse_dicomdir': '', 1133 'run_dicom_viewer': '', 1134 'date' : gmTools.html_escape_string(gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y %B %d')) 1135 } 1136 for key in data: 1137 _HTML_data[key] = data[key] 1138 1139 # documents part 1140 _HTML_docs_list = [] 1141 for doc in docs_list: 1142 subdir = os.path.join(directory, DOCUMENTS_SUBDIR, doc[0]) 1143 if os.path.isdir(subdir): 1144 _HTML_docs_list.append(_HTML_LIST_ITEM % (os.path.join(DOCUMENTS_SUBDIR, doc[0]), _('DIRECTORY: %s/%s/') % (DOCUMENTS_SUBDIR, doc[0]))) 1145 _HTML_docs_list.append(' <ul>') 1146 for fname in os.listdir(subdir): 1147 tmp = os.path.join(subdir, fname) 1148 if os.path.isdir(tmp): 1149 _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))) 1150 else: 1151 _HTML_docs_list.append(' <li><a href="%s">%s</a></li>' % (os.path.join(DOCUMENTS_SUBDIR, doc[0], fname), fname)) 1152 _HTML_docs_list.append(' </ul>') 1153 else: 1154 _HTML_docs_list.append(_HTML_LIST_ITEM % (os.path.join(DOCUMENTS_SUBDIR, doc[0]), doc[1])) 1155 _HTML_data['docs_list'] = u'\n '.join(_HTML_docs_list) 1156 1157 # footer part 1158 lines = [] 1159 adr = praxis.branch.org_unit.address 1160 if adr is not None: 1161 lines.extend(adr.format()) 1162 for comm in praxis.branch.org_unit.comm_channels: 1163 if comm['is_confidential'] is True: 1164 continue 1165 lines.append('%s: %s' % ( 1166 comm['l10n_comm_type'], 1167 comm['url'] 1168 )) 1169 adr = '' 1170 if len(lines) > 0: 1171 adr = gmTools.html_escape_string('\n'.join(lines), replace_eol = True, keep_visual_eol = True) 1172 _HTML_data['branch'] = gmTools.html_escape_string(praxis['branch']) 1173 _HTML_data['praxis'] = gmTools.html_escape_string(praxis['praxis']) 1174 _HTML_data['gm_ver'] = gmTools.html_escape_string(gmTools.coalesce(_cfg.get(option = 'client_version'), 'git HEAD')) 1175 _HTML_data['adr'] = adr 1176 # create file 1177 frontpage_fname = os.path.join(directory, 'frontpage.html') 1178 frontpage_file = io.open(frontpage_fname, mode = 'wt', encoding = 'utf8') 1179 frontpage_file.write(_FRONTPAGE_HTML_CONTENT % _HTML_data) 1180 frontpage_file.close() 1181 return frontpage_fname
1182 1183 #--------------------------------------------------------
1184 - def _clone_dwv(self, target_dir=None):
1185 _log.debug('cloning dwv') 1186 # find DWV 1187 dwv_src_dir = os.path.join(gmTools.gmPaths().local_base_dir, 'resources', 'dwv4export') 1188 if not os.path.isdir(dwv_src_dir): 1189 _log.debug('[%s] not found', dwv_src_dir) 1190 dwv_src_dir = os.path.join(gmTools.gmPaths().system_app_data_dir, 'resources', 'dwv4export') 1191 if not os.path.isdir(dwv_src_dir): 1192 _log.debug('[%s] not found', dwv_src_dir) 1193 return None 1194 # clone it 1195 if target_dir is None: 1196 target_dir = gmTools.mk_sandbox_dir() 1197 dwv_target_dir = os.path.join(target_dir, 'dwv') 1198 gmTools.rmdir(dwv_target_dir) 1199 try: 1200 shutil.copytree(dwv_src_dir, dwv_target_dir) 1201 except (shutil.Error, OSError): 1202 _log.exception('cannot include DWV, skipping') 1203 return None 1204 1205 return dwv_target_dir
1206 1207 #--------------------------------------------------------
1208 - def _create_readme(self, patient, directory):
1209 _README_CONTENT = ( 1210 'This is a patient data excerpt created by the GNUmed Electronic Medical Record.\n' 1211 '\n' 1212 'Patient: %s\n' 1213 '\n' 1214 'Please display <frontpage.html> to browse patient data.\n' 1215 '\n' 1216 'Individual documents are stored in the subdirectory\n' 1217 '\n' 1218 ' documents/\n' 1219 '\n' 1220 '\n' 1221 'Data may need to be decrypted with either GNU Privacy\n' 1222 'Guard or 7zip/WinZip.\n' 1223 '\n' 1224 '.asc:\n' 1225 ' https://gnupg.org\n' 1226 '\n' 1227 '.7z:\n' 1228 ' https://www.7-zip.org\n' 1229 ' https://www.winzip.com\n' 1230 '\n' 1231 'To obtain any needed keys you will have to get in touch with\n' 1232 'the creator or the owner of this patient media excerpt.\n' 1233 ) 1234 readme_fname = os.path.join(directory, 'README') 1235 readme_file = io.open(readme_fname, mode = 'wt', encoding = 'utf8') 1236 if patient is None: 1237 pat_str = _('<protected>') 1238 else: 1239 pat_str = patient.get_description_gender(with_nickname = False) + ', ' + _('born') + ' ' + patient.get_formatted_dob('%Y %B %d') 1240 readme_file.write(_README_CONTENT % pat_str) 1241 readme_file.close() 1242 return readme_fname
1243 1244 #--------------------------------------------------------
1245 - def _create_cd_inf(self, patient, directory):
1246 _CD_INF_CONTENT = ( 1247 '[Patient Info]\r\n' # needs \r\n for Windows 1248 'PatientName=%s, %s\r\n' 1249 'Gender=%s\r\n' 1250 'BirthDate=%s\r\n' 1251 'CreationDate=%s\r\n' 1252 'PID=%s\r\n' 1253 'EMR=GNUmed\r\n' 1254 'Version=%s\r\n' 1255 '#StudyDate=\r\n' 1256 '#VNRInfo=<body part>\r\n' 1257 '\r\n' 1258 '# name format: lastnames, firstnames\r\n' 1259 '# date format: YYYY-MM-DD (ISO 8601)\r\n' 1260 '# gender format: %s\r\n' 1261 ) 1262 fname = os.path.join(directory, 'CD.INF') 1263 cd_inf = io.open(fname, mode = 'wt', encoding = 'utf8') 1264 cd_inf.write(_CD_INF_CONTENT % ( 1265 patient['lastnames'], 1266 patient['firstnames'], 1267 gmTools.coalesce(patient['gender'], '?'), 1268 patient.get_formatted_dob('%Y-%m-%d'), 1269 gmDateTime.pydt_strftime(gmDateTime.pydt_now_here(), format = '%Y-%m-%d'), 1270 patient.ID, 1271 _cfg.get(option = 'client_version'), 1272 ' / '.join([ '%s = %s (%s)' % (g['tag'], g['label'], g['l10n_label']) for g in patient.gender_list ]) 1273 )) 1274 cd_inf.close() 1275 return fname
1276 1277 #--------------------------------------------------------
1278 - def _create_autorun_inf(self, patient, directory):
1279 _AUTORUN_INF_CONTENT = ( # needs \r\n for Windows 1280 '[AutoRun.Amd64]\r\n' # 64 bit 1281 'label=%(label)s\r\n' # patient name/DOB 1282 'shellexecute=index.html\r\n' 1283 'action=%(action)s\r\n' # % _('Browse patient data') 1284 '%(icon)s\r\n' # "icon=gnumed.ico" or "" 1285 'UseAutoPlay=1\r\n' 1286 '\r\n' 1287 '[AutoRun]\r\n' # 32 bit 1288 'label=%(label)s\r\n' # patient name/DOB 1289 'shellexecute=index.html\r\n' 1290 'action=%(action)s\r\n' # % _('Browse patient data') 1291 '%(icon)s\r\n' # "icon=gnumed.ico" or "" 1292 'UseAutoPlay=1\r\n' 1293 '\r\n' 1294 '[Content]\r\n' 1295 'PictureFiles=yes\r\n' 1296 'VideoFiles=yes\r\n' 1297 'MusicFiles=no\r\n' 1298 '\r\n' 1299 '[IgnoreContentPaths]\r\n' 1300 '\documents\r\n' 1301 '\r\n' 1302 '[unused]\r\n' 1303 'open=requires explicit executable\r\n' 1304 ) 1305 autorun_dict = { 1306 'label': self._compute_autorun_inf_label(patient), 1307 'action': _('Browse patient data'), 1308 'icon': '' 1309 } 1310 media_icon_kwd = '$$gnumed_patient_media_export_icon' 1311 media_icon_kwd_exp = gmKeywordExpansion.get_expansion ( 1312 keyword = media_icon_kwd, 1313 textual_only = False, 1314 binary_only = True 1315 ) 1316 icon_tmp_fname = media_icon_kwd_exp.save_to_file ( 1317 target_mime = 'image/x-icon', 1318 target_extension = '.ico', 1319 ignore_conversion_problems = True 1320 ) 1321 if icon_tmp_fname is None: 1322 _log.error('cannot retrieve <%s>', media_icon_kwd) 1323 else: 1324 media_icon_fname = os.path.join(directory, 'gnumed.ico') 1325 try: 1326 shutil.copy2(icon_tmp_fname, media_icon_fname) 1327 autorun_dict['icon'] = 'icon=gnumed.ico' 1328 except Exception: 1329 _log.exception('cannot move %s to %s', icon_tmp_fname, media_icon_fname) 1330 autorun_fname = os.path.join(directory, 'AUTORUN.INF') 1331 autorun_file = io.open(autorun_fname, mode = 'wt', encoding = 'cp1252', errors = 'replace') 1332 autorun_file.write(_AUTORUN_INF_CONTENT % autorun_dict) 1333 autorun_file.close() 1334 return autorun_fname
1335 1336 #--------------------------------------------------------
1337 - def _compute_autorun_inf_label(self, patient):
1338 if patient is None: 1339 # if no patient provided - assume the target media 1340 # is to be encrypted and thusly do not expose patient data, 1341 # AUTORUN.INF itself will not be encrypted because 1342 # Windows must be able to parse it 1343 return _('GNUmed patient data excerpt')[:32] 1344 1345 LABEL_MAX_LEN = 32 1346 dob = patient.get_formatted_dob(format = ' %Y%m%d', none_string = '', honor_estimation = False) 1347 if dob == '': 1348 gender_template = ' (%s)' 1349 else: 1350 gender_template = ' %s' 1351 gender = gmTools.coalesce(patient['gender'], '', gender_template) 1352 name_max_len = LABEL_MAX_LEN - len(gender) - len(dob) # they already include appropriate padding 1353 name = patient.active_name 1354 last = name['lastnames'].strip() 1355 first = name['firstnames'].strip() 1356 len_last = len(last) 1357 len_first = len(first) 1358 while (len_last + len_first + 1) > name_max_len: 1359 if len_first > 6: 1360 len_first -= 1 1361 if first[len_first - 1] == ' ': 1362 len_first -= 1 1363 continue 1364 len_last -= 1 1365 if last[len_last - 1] == ' ': 1366 len_last -= 1 1367 last = last[:len_last].strip().upper() 1368 first = first[:len_first].strip() 1369 # max 32 chars, supposedly ASCII, but CP1252 likely works pretty well 1370 label = (('%s %s%s%s' % (last, first, dob, gender)).strip())[:32] 1371 return label
1372 1373 #--------------------------------------------------------
1374 - def __encrypt_file(self, filename, passphrase=None):
1375 if passphrase is None: 1376 return filename 1377 enc_filename = gmCrypto.encrypt_file ( 1378 filename = filename, 1379 passphrase = passphrase, 1380 verbose = _cfg.get(option = 'debug') 1381 ) 1382 if enc_filename is None: 1383 _log.error('cannot encrypt') 1384 return None 1385 if not gmTools.remove_file(filename, log_error = True, force = True): 1386 _log.error('cannot remove unencrypted file') 1387 return None 1388 return enc_filename
1389 1390 #-------------------------------------------------------- 1391 # properties 1392 #--------------------------------------------------------
1393 - def get_items(self, designation=None, order_by='designation, description'):
1394 return get_export_items(order_by = order_by, pk_identity = self.__pk_identity, designation = designation)
1395 1396 items = property(get_items, lambda x:x) 1397 1398 #--------------------------------------------------------
1399 - def get_printouts(self, order_by='designation, description'):
1400 return get_print_jobs(order_by = order_by, pk_identity = self.__pk_identity)
1401 1402 printouts = property(get_printouts, lambda x:x)
1403 1404 #============================================================ 1405 if __name__ == '__main__': 1406 1407 if len(sys.argv) < 2: 1408 sys.exit() 1409 1410 if sys.argv[1] != 'test': 1411 sys.exit() 1412 1413 from Gnumed.pycommon import gmI18N 1414 gmI18N.activate_locale() 1415 gmI18N.install_domain() 1416 1417 from Gnumed.business import gmPraxis 1418 1419 #---------------------------------------
1420 - def test_export_items():
1421 # items = get_export_items() 1422 # for item in items: 1423 # print item.format() 1424 import random 1425 create_export_item(description = 'description %s' % random.random(), pk_identity = 12, pk_doc_obj = None, filename = 'dummy.dat') 1426 items = get_export_items() 1427 for item in items: 1428 print(item.format()) 1429 item['pk_doc_obj'] = 1 1430 item.save() 1431 print(item)
1432 1433 #---------------------------------------
1434 - def test_export_area():
1435 exp = cExportArea(12) 1436 #print exp.export_with_meta_data() 1437 #print exp.items 1438 #exp.add_file(sys.argv[2]) 1439 prax = gmPraxis.gmCurrentPraxisBranch(branch = gmPraxis.cPraxisBranch(1)) 1440 #print(prax) 1441 #print(prax.branch) 1442 try: 1443 pwd = sys.argv[2] 1444 except IndexError: 1445 pwd = None 1446 print(exp.export(passphrase = pwd))
1447 1448 #---------------------------------------
1449 - def test_label():
1450 1451 from Gnumed.business.gmPerson import cPatient 1452 from Gnumed.business.gmPersonSearch import ask_for_patient 1453 1454 #while ask_for_patient() is not None: 1455 pat_min = 1 1456 pat_max = 100 1457 try: 1458 pat_min = int(sys.argv[2]) 1459 pat_max = int(sys.argv[3]) 1460 except: 1461 pass 1462 cPatient(aPK_obj = pat_min) 1463 f = io.open('x-auto_inf_labels.txt', mode = 'w', encoding = 'utf8') 1464 f.write('--------------------------------\n') 1465 f.write('12345678901234567890123456789012\n') 1466 f.write('--------------------------------\n') 1467 for pat_id in range(pat_min, pat_max): 1468 try: 1469 exp_area = cExportArea(pat_id) 1470 pat = cPatient(aPK_obj = pat_id) 1471 except: 1472 continue 1473 f.write(exp_area._compute_autorun_inf_label(pat) + '\n') 1474 f.close() 1475 return
1476 1477 #--------------------------------------- 1478 #test_export_items() 1479 test_export_area() 1480 #test_label() 1481 1482 sys.exit(0) 1483 1484 #============================================================ 1485 # CDROM "run.bat": 1486 # 1487 #@echo off 1488 # 1489 #if defined ProgramFiles(x86) ( 1490 # ::64-bit 1491 # start /B x64\mdicom.exe /scan . 1492 #) else ( 1493 # ::32-bit 1494 # start /B win32\mdicom.exe /scan . 1495 #) 1496 # 1497 #-------------------------------------------------- 1498