[med-svn] [Git][med-team/gnumed-client][upstream] New upstream version 1.8.7+dfsg
Andreas Tille (@tille)
gitlab at salsa.debian.org
Sat Feb 12 21:25:11 GMT 2022
Andreas Tille pushed to branch upstream at Debian Med / gnumed-client
Commits:
cc9ec2f0 by Andreas Tille at 2022-02-12T22:00:11+01:00
New upstream version 1.8.7+dfsg
- - - - -
11 changed files:
- client/CHANGELOG
- client/business/gmDocuments.py
- client/business/gmExportArea.py
- client/doc/api/gmDocuments.html
- client/doc/api/gmDrugDataSources.html
- client/doc/api/gmExportArea.html
- client/doc/api/gmTools.html
- client/doc/schema/gnumed-entire_schema.html
- client/gnumed.py
- client/pycommon/gmMimeLib.py
- client/wxpython/gmTopPanel.py
Changes:
=====================================
client/CHANGELOG
=====================================
@@ -6,6 +6,12 @@
# rel-1-8-patches
------------------------------------------------
+ 1.8.7
+
+FIX: export area: dumping encrypted/PDFed image to disk
+FIX: top panel: heart rate display
+FIX: paperwork: recalls list LaTeX template
+
1.8.6
FIX: unlocking encounters (missing import)
@@ -2172,6 +2178,10 @@ FIX: missing cast to ::text in dem.date_trunc_utc() calls
# gnumed_v22
------------------------------------------------
+ 22.17
+
+FIX: CREATE FUNCTION ... RETURNS OPAQUE -> TRIGGER [thanks SantyCW at es_AR]
+
22.16
IMPROVED: bootstrapper: check disk space
=====================================
client/business/gmDocuments.py
=====================================
@@ -277,48 +277,28 @@ class cDocumentPart(gmBusinessDBObject.cBusinessDBObject):
#--------------------------------------------------------
# retrieve data
#--------------------------------------------------------
- def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
-
+ def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, conn=None):
if filename is None:
filename = self.get_useful_filename(make_unique = True, directory = directory)
-
- filename = self.__download_to_file(filename = filename)
- if filename is None:
+ dl_fname = self.__download_to_file(filename = filename)
+ if dl_fname is None:
return None
if target_mime is None:
- if filename.endswith('.dat'):
- if adjust_extension:
- return gmMimeLib.adjust_extension_by_mimetype(filename)
- return filename
+ return gmMimeLib.adjust_extension_by_mimetype(dl_fname)
- if target_extension is None:
- target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
-
- target_path, name = os.path.split(filename)
- name, tmp = os.path.splitext(name)
- target_fname = gmTools.get_unique_filename (
- prefix = '%s-conv-' % name,
- suffix = target_extension
- )
- _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
- converted_fname = gmMimeLib.convert_file (
- filename = filename,
+ converted_fname = self.__convert_file_to (
+ filename = dl_fname,
target_mime = target_mime,
- target_filename = target_fname
+ target_extension = target_extension
)
- if converted_fname is not None:
- return converted_fname
-
- _log.warning('conversion failed')
- if not ignore_conversion_problems:
+ if converted_fname is None:
+ if ignore_conversion_problems:
+ return dl_fname
return None
- if filename.endswith('.dat'):
- if adjust_extension:
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
- _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
- return filename
+ gmTools.remove_file(dl_fname)
+ return converted_fname
#--------------------------------------------------------
def get_reviews(self):
@@ -634,6 +614,39 @@ insert into blobs.reviewed_doc_objs (
return filename
+ #--------------------------------------------------------
+ def __convert_file_to(self, filename=None, target_mime=None, target_extension=None):
+ assert (filename is not None), '<filename> must not be None'
+ assert (target_mime is not None), '<target_mime> must not be None'
+
+ if target_extension is None:
+ target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
+ src_path, src_name = os.path.split(filename)
+ src_stem, src_ext = os.path.splitext(src_name)
+ conversion_tmp_name = gmTools.get_unique_filename (
+ prefix = '%s.conv2.' % src_stem,
+ suffix = target_extension
+ )
+ _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, conversion_tmp_name)
+ converted_fname = gmMimeLib.convert_file (
+ filename = filename,
+ target_mime = target_mime,
+ target_filename = conversion_tmp_name
+ )
+ if converted_fname is None:
+ _log.warning('conversion failed')
+ return None
+
+ tmp_path, conv_name = os.path.split(converted_fname)
+ conv_name_in_src_path = os.path.join(src_path, conv_name)
+ try:
+ os.replace(converted_fname, conv_name_in_src_path)
+ except OSError:
+ _log.exception('cannot os.replace(%s, %s)', converted_fname, conv_name_in_src_path)
+ return None
+
+ return gmMimeLib.adjust_extension_by_mimetype(conv_name_in_src_path)
+
#--------------------------------------------------------
def __run_metainfo_formatter(self):
filename = self.__download_to_file()
@@ -1286,6 +1299,14 @@ if __name__ == '__main__':
print(doc.format(single_line = True))
print(doc.format())
+ #--------------------------------------------------------
+ def test_save_to_file():
+ doc_folder = cDocumentFolder(aPKey=12)
+ docs = doc_folder.get_documents()
+ for doc in docs:
+ for part in doc.parts:
+ print(part.save_to_file(target_mime = 'application/pdf', ignore_conversion_problems = True))
+
#--------------------------------------------------------
def test_get_useful_filename():
pk = 12
@@ -1346,11 +1367,14 @@ if __name__ == '__main__':
gmI18N.activate_locale()
gmI18N.install_domain()
+ gmPG2.request_login_params(setup_pool = True)
+
#test_doc_types()
#test_adding_doc_part()
#test_get_documents()
#test_get_useful_filename()
#test_part_metainfo_formatter()
- test_check_mimetypes_in_archive()
+ #test_check_mimetypes_in_archive()
+ test_save_to_file()
# print get_ext_ref()
=====================================
client/business/gmExportArea.py
=====================================
@@ -271,8 +271,7 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
if not success:
return None
- if filename.endswith('.dat'):
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
+ filename = gmMimeLib.adjust_extension_by_mimetype(filename)
if passphrase is None:
return filename
@@ -315,14 +314,17 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
date_before_type = True,
name_first = False
)
- target_mime = 'application/pdf' if convert2pdf else None
- target_ext = '.pdf' if convert2pdf else None
+ if convert2pdf:
+ target_mime = 'application/pdf'
+ target_ext = '.pdf'
+ else:
+ target_mime = None
+ target_ext = None
part_fname = part.save_to_file (
filename = filename,
target_mime = target_mime,
target_extension = target_ext,
- ignore_conversion_problems = False,
- adjust_extension = True
+ ignore_conversion_problems = False
)
if part_fname is None:
_log.error('cannot save document part to file')
@@ -337,7 +339,7 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
verbose = _cfg.get(option = 'debug'),
remove_unencrypted = True
)
- removed = gmTools.remove_file(filename)
+ removed = gmTools.remove_file(part_fname)
if enc_filename is None:
_log.error('cannot encrypt')
return False
@@ -963,7 +965,7 @@ class cExportArea(object):
# - export mugshot
mugshot = pat.document_folder.latest_mugshot
if mugshot is not None:
- mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True)
+ mugshot_fname = mugshot.save_to_file(directory = doc_dir)
fname = os.path.split(mugshot_fname)[1]
html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname)
html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y')
=====================================
client/doc/api/gmDocuments.html
=====================================
@@ -301,48 +301,28 @@ class cDocumentPart(gmBusinessDBObject.cBusinessDBObject):
#--------------------------------------------------------
# retrieve data
#--------------------------------------------------------
- def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
-
+ def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, conn=None):
if filename is None:
filename = self.get_useful_filename(make_unique = True, directory = directory)
-
- filename = self.__download_to_file(filename = filename)
- if filename is None:
+ dl_fname = self.__download_to_file(filename = filename)
+ if dl_fname is None:
return None
if target_mime is None:
- if filename.endswith('.dat'):
- if adjust_extension:
- return gmMimeLib.adjust_extension_by_mimetype(filename)
- return filename
+ return gmMimeLib.adjust_extension_by_mimetype(dl_fname)
- if target_extension is None:
- target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
-
- target_path, name = os.path.split(filename)
- name, tmp = os.path.splitext(name)
- target_fname = gmTools.get_unique_filename (
- prefix = '%s-conv-' % name,
- suffix = target_extension
- )
- _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
- converted_fname = gmMimeLib.convert_file (
- filename = filename,
+ converted_fname = self.__convert_file_to (
+ filename = dl_fname,
target_mime = target_mime,
- target_filename = target_fname
+ target_extension = target_extension
)
- if converted_fname is not None:
- return converted_fname
-
- _log.warning('conversion failed')
- if not ignore_conversion_problems:
+ if converted_fname is None:
+ if ignore_conversion_problems:
+ return dl_fname
return None
- if filename.endswith('.dat'):
- if adjust_extension:
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
- _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
- return filename
+ gmTools.remove_file(dl_fname)
+ return converted_fname
#--------------------------------------------------------
def get_reviews(self):
@@ -658,6 +638,39 @@ insert into blobs.reviewed_doc_objs (
return filename
+ #--------------------------------------------------------
+ def __convert_file_to(self, filename=None, target_mime=None, target_extension=None):
+ assert (filename is not None), '<filename> must not be None'
+ assert (target_mime is not None), '<target_mime> must not be None'
+
+ if target_extension is None:
+ target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
+ src_path, src_name = os.path.split(filename)
+ src_stem, src_ext = os.path.splitext(src_name)
+ conversion_tmp_name = gmTools.get_unique_filename (
+ prefix = '%s.conv2.' % src_stem,
+ suffix = target_extension
+ )
+ _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, conversion_tmp_name)
+ converted_fname = gmMimeLib.convert_file (
+ filename = filename,
+ target_mime = target_mime,
+ target_filename = conversion_tmp_name
+ )
+ if converted_fname is None:
+ _log.warning('conversion failed')
+ return None
+
+ tmp_path, conv_name = os.path.split(converted_fname)
+ conv_name_in_src_path = os.path.join(src_path, conv_name)
+ try:
+ os.replace(converted_fname, conv_name_in_src_path)
+ except OSError:
+ _log.exception('cannot os.replace(%s, %s)', converted_fname, conv_name_in_src_path)
+ return None
+
+ return gmMimeLib.adjust_extension_by_mimetype(conv_name_in_src_path)
+
#--------------------------------------------------------
def __run_metainfo_formatter(self):
filename = self.__download_to_file()
@@ -1311,6 +1324,14 @@ if __name__ == '__main__':
print(doc.format())
#print(doc['pk_type'])
+ #--------------------------------------------------------
+ def test_save_to_file():
+ doc_folder = cDocumentFolder(aPKey=12)
+ docs = doc_folder.get_documents()
+ for doc in docs:
+ for part in doc.parts:
+ print(part.save_to_file(target_mime = 'application/pdf', ignore_conversion_problems = True))
+
#--------------------------------------------------------
def test_get_useful_filename():
pk = 12
@@ -1375,10 +1396,11 @@ if __name__ == '__main__':
#test_doc_types()
#test_adding_doc_part()
- test_get_documents()
+ #test_get_documents()
#test_get_useful_filename()
#test_part_metainfo_formatter()
#test_check_mimetypes_in_archive()
+ test_save_to_file()
# print get_ext_ref()</code></pre>
</details>
@@ -3060,48 +3082,28 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
#--------------------------------------------------------
# retrieve data
#--------------------------------------------------------
- def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
-
+ def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, conn=None):
if filename is None:
filename = self.get_useful_filename(make_unique = True, directory = directory)
-
- filename = self.__download_to_file(filename = filename)
- if filename is None:
+ dl_fname = self.__download_to_file(filename = filename)
+ if dl_fname is None:
return None
if target_mime is None:
- if filename.endswith('.dat'):
- if adjust_extension:
- return gmMimeLib.adjust_extension_by_mimetype(filename)
- return filename
+ return gmMimeLib.adjust_extension_by_mimetype(dl_fname)
- if target_extension is None:
- target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
-
- target_path, name = os.path.split(filename)
- name, tmp = os.path.splitext(name)
- target_fname = gmTools.get_unique_filename (
- prefix = '%s-conv-' % name,
- suffix = target_extension
- )
- _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
- converted_fname = gmMimeLib.convert_file (
- filename = filename,
+ converted_fname = self.__convert_file_to (
+ filename = dl_fname,
target_mime = target_mime,
- target_filename = target_fname
+ target_extension = target_extension
)
- if converted_fname is not None:
- return converted_fname
-
- _log.warning('conversion failed')
- if not ignore_conversion_problems:
+ if converted_fname is None:
+ if ignore_conversion_problems:
+ return dl_fname
return None
- if filename.endswith('.dat'):
- if adjust_extension:
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
- _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
- return filename
+ gmTools.remove_file(dl_fname)
+ return converted_fname
#--------------------------------------------------------
def get_reviews(self):
@@ -3417,6 +3419,39 @@ insert into blobs.reviewed_doc_objs (
return filename
+ #--------------------------------------------------------
+ def __convert_file_to(self, filename=None, target_mime=None, target_extension=None):
+ assert (filename is not None), '<filename> must not be None'
+ assert (target_mime is not None), '<target_mime> must not be None'
+
+ if target_extension is None:
+ target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
+ src_path, src_name = os.path.split(filename)
+ src_stem, src_ext = os.path.splitext(src_name)
+ conversion_tmp_name = gmTools.get_unique_filename (
+ prefix = '%s.conv2.' % src_stem,
+ suffix = target_extension
+ )
+ _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, conversion_tmp_name)
+ converted_fname = gmMimeLib.convert_file (
+ filename = filename,
+ target_mime = target_mime,
+ target_filename = conversion_tmp_name
+ )
+ if converted_fname is None:
+ _log.warning('conversion failed')
+ return None
+
+ tmp_path, conv_name = os.path.split(converted_fname)
+ conv_name_in_src_path = os.path.join(src_path, conv_name)
+ try:
+ os.replace(converted_fname, conv_name_in_src_path)
+ except OSError:
+ _log.exception('cannot os.replace(%s, %s)', converted_fname, conv_name_in_src_path)
+ return None
+
+ return gmMimeLib.adjust_extension_by_mimetype(conv_name_in_src_path)
+
#--------------------------------------------------------
def __run_metainfo_formatter(self):
filename = self.__download_to_file()
@@ -3715,7 +3750,7 @@ ORDER BY
</details>
</dd>
<dt id="Gnumed.business.gmDocuments.cDocumentPart.save_to_file"><code class="name flex">
-<span>def <span class="ident">save_to_file</span></span>(<span>self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None)</span>
+<span>def <span class="ident">save_to_file</span></span>(<span>self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, conn=None)</span>
</code></dt>
<dd>
<div class="desc"></div>
@@ -3723,48 +3758,28 @@ ORDER BY
<summary>
<span>Expand source code</span>
</summary>
-<pre><code class="python">def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, adjust_extension=False, conn=None):
-
+<pre><code class="python">def save_to_file(self, aChunkSize=0, filename=None, target_mime=None, target_extension=None, ignore_conversion_problems=False, directory=None, conn=None):
if filename is None:
filename = self.get_useful_filename(make_unique = True, directory = directory)
-
- filename = self.__download_to_file(filename = filename)
- if filename is None:
+ dl_fname = self.__download_to_file(filename = filename)
+ if dl_fname is None:
return None
if target_mime is None:
- if filename.endswith('.dat'):
- if adjust_extension:
- return gmMimeLib.adjust_extension_by_mimetype(filename)
- return filename
+ return gmMimeLib.adjust_extension_by_mimetype(dl_fname)
- if target_extension is None:
- target_extension = gmMimeLib.guess_ext_by_mimetype(mimetype = target_mime)
-
- target_path, name = os.path.split(filename)
- name, tmp = os.path.splitext(name)
- target_fname = gmTools.get_unique_filename (
- prefix = '%s-conv-' % name,
- suffix = target_extension
- )
- _log.debug('attempting conversion: [%s] -> [<%s>:%s]', filename, target_mime, target_fname)
- converted_fname = gmMimeLib.convert_file (
- filename = filename,
+ converted_fname = self.__convert_file_to (
+ filename = dl_fname,
target_mime = target_mime,
- target_filename = target_fname
+ target_extension = target_extension
)
- if converted_fname is not None:
- return converted_fname
-
- _log.warning('conversion failed')
- if not ignore_conversion_problems:
+ if converted_fname is None:
+ if ignore_conversion_problems:
+ return dl_fname
return None
- if filename.endswith('.dat'):
- if adjust_extension:
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
- _log.warning('programmed to ignore conversion problems, hoping receiver can handle [%s]', filename)
- return filename</code></pre>
+ gmTools.remove_file(dl_fname)
+ return converted_fname</code></pre>
</details>
</dd>
<dt id="Gnumed.business.gmDocuments.cDocumentPart.set_as_active_photograph"><code class="name flex">
=====================================
client/doc/api/gmDrugDataSources.html
=====================================
@@ -520,7 +520,7 @@ class cFreeDiamsInterface(cDrugDataSourceInterface):
_log.debug('GNUmed -> FreeDiams "exchange-in" file: %s', self.__gm2fd_filename)
self.__fd2gm_filename = gmTools.get_unique_filename(prefix = r'freediams2gm-', suffix = r'.xml')
_log.debug('GNUmed <-> FreeDiams "exchange-out"/"prescription" file: %s', self.__fd2gm_filename)
- # this file can be modified by the user as needed:
+ # this file can be modified by the user as needed (therefore in user_config_dir):
self.__fd4gm_config_file = os.path.join(gmTools.gmPaths().user_config_dir, 'freediams4gm.conf')
_log.debug('FreeDiams config file for GNUmed use: %s', self.__fd4gm_config_file)
@@ -1604,7 +1604,7 @@ if __name__ == "__main__":
_log.debug('GNUmed -> FreeDiams "exchange-in" file: %s', self.__gm2fd_filename)
self.__fd2gm_filename = gmTools.get_unique_filename(prefix = r'freediams2gm-', suffix = r'.xml')
_log.debug('GNUmed <-> FreeDiams "exchange-out"/"prescription" file: %s', self.__fd2gm_filename)
- # this file can be modified by the user as needed:
+ # this file can be modified by the user as needed (therefore in user_config_dir):
self.__fd4gm_config_file = os.path.join(gmTools.gmPaths().user_config_dir, 'freediams4gm.conf')
_log.debug('FreeDiams config file for GNUmed use: %s', self.__fd4gm_config_file)
=====================================
client/doc/api/gmExportArea.html
=====================================
@@ -142,6 +142,9 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
def update_data(self, data=None):
assert (data is not None), '<data> must not be <None>'
+ if self.is_DIRENTRY or self.is_document_part:
+ return False
+
SQL = """
UPDATE clin.export_item SET
data = %(data)s::bytea,
@@ -154,7 +157,15 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
return True
#--------------------------------------------------------
- def update_data_from_file(self, filename=None):
+ def update_data_from_file(self, filename=None, convert_document_part=False):
+
+ if self.is_DIRENTRY:
+ return False
+
+ if self.is_document_part:
+ if not convert_document_part:
+ return False
+
# sanity check
if not (os.access(filename, os.R_OK) and os.path.isfile(filename)):
_log.error('[%s] is not a readable file' % filename)
@@ -185,7 +196,7 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
convert2pdf: Convert file(s) to PDF on the way out. Before encryption, that is.
Returns:
- Directory for DIRENTRIES, or filename.
+ Directory for DIRENTRIES, or filename, or None on failure.
"""
if self.is_DIRENTRY and convert2pdf:
# cannot convert dir entries to PDF
@@ -290,44 +301,49 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
# helpers
#--------------------------------------------------------
def __save_normal_item(self, filename:str=None, directory:str=None, passphrase:str=None, convert2pdf:bool=False) -> str:
- if filename is None:
- filename = self.get_useful_filename(directory = directory)
+ _SQL = 'SELECT substring(data FROM %(start)s FOR %(size)s) FROM clin.export_item WHERE pk = %(pk)s'
+ tmp_fname = gmTools.get_unique_filename()
success = gmPG2.bytea2file (
- data_query = {
- 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM clin.export_item WHERE pk = %(pk)s',
- 'args': {'pk': self.pk_obj}
- },
- filename = filename,
+ data_query = {'cmd': _SQL, 'args': {'pk': self.pk_obj}},
+ filename = tmp_fname,
data_size = self._payload[self._idx['size']]
)
if not success:
return None
- if filename.endswith('.dat'):
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
+ tmp_fname = gmMimeLib.adjust_extension_by_mimetype(tmp_fname)
+ if convert2pdf:
+ tmp_fname = gmMimeLib.convert_file(filename = tmp_fname, target_mime = 'application/pdf', target_extension = '.pdf')
+ if filename is None:
+ target_fname = self.get_useful_filename(directory = directory)
+ else:
+ target_fname = filename
if passphrase is None:
- if not convert2pdf:
- return filename
+ if not gmTools.rename_file(tmp_fname, target_fname, overwrite = True, allow_symlink = True):
+ return None
- return gmMimeLib.convert_file(filename = filename, target_mime = 'application/pdf', target_extension = '.pdf')
+ if filename is None:
+ return gmMimeLib.adjust_extension_by_mimetype(target_fname)
- enc_filename = gmCrypto.encrypt_file (
- filename = filename,
+ return target_filename
+
+ enc_fname = gmCrypto.encrypt_file (
+ filename = tmp_fname,
passphrase = passphrase,
verbose = _cfg.get(option = 'debug'),
remove_unencrypted = True,
- convert2pdf = convert2pdf
+ convert2pdf = False # already done, if desired
)
- removed = gmTools.remove_file(filename)
- if enc_filename is None:
+ removed = gmTools.remove_file(tmp_fname)
+ if enc_fname is None:
_log.error('cannot encrypt or, possibly, convert')
return None
if removed:
- return enc_filename
+ return enc_fname
_log.error('cannot remove unencrypted file')
- gmTools.remove(enc_filename)
+ gmTools.remove(enc_fname)
return None
#--------------------------------------------------------
@@ -352,14 +368,17 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
)
path, name = os.path.split(filename)
filename = os.path.join(path, '%s-%s' % (self._payload[self._idx['list_position']], name))
- target_mime = 'application/pdf' if convert2pdf else None
- target_ext = '.pdf' if convert2pdf else None
+ if convert2pdf:
+ target_mime = 'application/pdf'
+ target_ext = '.pdf'
+ else:
+ target_mime = None
+ target_ext = None
part_fname = part.save_to_file (
filename = filename,
target_mime = target_mime,
target_extension = target_ext,
- ignore_conversion_problems = False,
- adjust_extension = True
+ ignore_conversion_problems = False
)
if part_fname is None:
_log.error('cannot save document part to file')
@@ -374,7 +393,7 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
verbose = _cfg.get(option = 'debug'),
remove_unencrypted = True
)
- removed = gmTools.remove_file(filename)
+ removed = gmTools.remove_file(part_fname)
if enc_filename is None:
_log.error('cannot encrypt')
return False
@@ -440,6 +459,12 @@ class cExportItem(gmBusinessDBObject.cBusinessDBObject):
#--------------------------------------------------------
# properties
+ #--------------------------------------------------------
+ def _get_is_doc_part(self):
+ return self._payload[self._idx['pk_doc_obj']] is not None
+
+ is_document_part = property(_get_is_doc_part)
+
#--------------------------------------------------------
def _get_doc_part(self):
if self._payload[self._idx['pk_doc_obj']] is None:
@@ -996,7 +1021,7 @@ class cExportArea(object):
# - export mugshot
mugshot = pat.document_folder.latest_mugshot
if mugshot is not None:
- mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True)
+ mugshot_fname = mugshot.save_to_file(directory = doc_dir)
fname = os.path.split(mugshot_fname)[1]
html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname)
html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y')
@@ -1973,7 +1998,7 @@ if __name__ == '__main__':
# - export mugshot
mugshot = pat.document_folder.latest_mugshot
if mugshot is not None:
- mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True)
+ mugshot_fname = mugshot.save_to_file(directory = doc_dir)
fname = os.path.split(mugshot_fname)[1]
html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname)
html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y')
@@ -2776,7 +2801,7 @@ as a subdirectory.</p></div>
# - export mugshot
mugshot = pat.document_folder.latest_mugshot
if mugshot is not None:
- mugshot_fname = mugshot.save_to_file(directory = doc_dir, adjust_extension = True)
+ mugshot_fname = mugshot.save_to_file(directory = doc_dir)
fname = os.path.split(mugshot_fname)[1]
html_data['mugshot_url'] = os.path.join(DOCUMENTS_SUBDIR, fname)
html_data['mugshot_alt'] =_('patient photograph from %s') % gmDateTime.pydt_strftime(mugshot['date_generated'], '%B %Y')
@@ -3150,6 +3175,9 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
def update_data(self, data=None):
assert (data is not None), '<data> must not be <None>'
+ if self.is_DIRENTRY or self.is_document_part:
+ return False
+
SQL = """
UPDATE clin.export_item SET
data = %(data)s::bytea,
@@ -3162,7 +3190,15 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
return True
#--------------------------------------------------------
- def update_data_from_file(self, filename=None):
+ def update_data_from_file(self, filename=None, convert_document_part=False):
+
+ if self.is_DIRENTRY:
+ return False
+
+ if self.is_document_part:
+ if not convert_document_part:
+ return False
+
# sanity check
if not (os.access(filename, os.R_OK) and os.path.isfile(filename)):
_log.error('[%s] is not a readable file' % filename)
@@ -3193,7 +3229,7 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
convert2pdf: Convert file(s) to PDF on the way out. Before encryption, that is.
Returns:
- Directory for DIRENTRIES, or filename.
+ Directory for DIRENTRIES, or filename, or None on failure.
"""
if self.is_DIRENTRY and convert2pdf:
# cannot convert dir entries to PDF
@@ -3298,44 +3334,49 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
# helpers
#--------------------------------------------------------
def __save_normal_item(self, filename:str=None, directory:str=None, passphrase:str=None, convert2pdf:bool=False) -> str:
- if filename is None:
- filename = self.get_useful_filename(directory = directory)
+ _SQL = 'SELECT substring(data FROM %(start)s FOR %(size)s) FROM clin.export_item WHERE pk = %(pk)s'
+ tmp_fname = gmTools.get_unique_filename()
success = gmPG2.bytea2file (
- data_query = {
- 'cmd': 'SELECT substring(data from %(start)s for %(size)s) FROM clin.export_item WHERE pk = %(pk)s',
- 'args': {'pk': self.pk_obj}
- },
- filename = filename,
+ data_query = {'cmd': _SQL, 'args': {'pk': self.pk_obj}},
+ filename = tmp_fname,
data_size = self._payload[self._idx['size']]
)
if not success:
return None
- if filename.endswith('.dat'):
- filename = gmMimeLib.adjust_extension_by_mimetype(filename)
+ tmp_fname = gmMimeLib.adjust_extension_by_mimetype(tmp_fname)
+ if convert2pdf:
+ tmp_fname = gmMimeLib.convert_file(filename = tmp_fname, target_mime = 'application/pdf', target_extension = '.pdf')
+ if filename is None:
+ target_fname = self.get_useful_filename(directory = directory)
+ else:
+ target_fname = filename
if passphrase is None:
- if not convert2pdf:
- return filename
+ if not gmTools.rename_file(tmp_fname, target_fname, overwrite = True, allow_symlink = True):
+ return None
- return gmMimeLib.convert_file(filename = filename, target_mime = 'application/pdf', target_extension = '.pdf')
+ if filename is None:
+ return gmMimeLib.adjust_extension_by_mimetype(target_fname)
- enc_filename = gmCrypto.encrypt_file (
- filename = filename,
+ return target_filename
+
+ enc_fname = gmCrypto.encrypt_file (
+ filename = tmp_fname,
passphrase = passphrase,
verbose = _cfg.get(option = 'debug'),
remove_unencrypted = True,
- convert2pdf = convert2pdf
+ convert2pdf = False # already done, if desired
)
- removed = gmTools.remove_file(filename)
- if enc_filename is None:
+ removed = gmTools.remove_file(tmp_fname)
+ if enc_fname is None:
_log.error('cannot encrypt or, possibly, convert')
return None
if removed:
- return enc_filename
+ return enc_fname
_log.error('cannot remove unencrypted file')
- gmTools.remove(enc_filename)
+ gmTools.remove(enc_fname)
return None
#--------------------------------------------------------
@@ -3360,14 +3401,17 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
)
path, name = os.path.split(filename)
filename = os.path.join(path, '%s-%s' % (self._payload[self._idx['list_position']], name))
- target_mime = 'application/pdf' if convert2pdf else None
- target_ext = '.pdf' if convert2pdf else None
+ if convert2pdf:
+ target_mime = 'application/pdf'
+ target_ext = '.pdf'
+ else:
+ target_mime = None
+ target_ext = None
part_fname = part.save_to_file (
filename = filename,
target_mime = target_mime,
target_extension = target_ext,
- ignore_conversion_problems = False,
- adjust_extension = True
+ ignore_conversion_problems = False
)
if part_fname is None:
_log.error('cannot save document part to file')
@@ -3382,7 +3426,7 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
verbose = _cfg.get(option = 'debug'),
remove_unencrypted = True
)
- removed = gmTools.remove_file(filename)
+ removed = gmTools.remove_file(part_fname)
if enc_filename is None:
_log.error('cannot encrypt')
return False
@@ -3448,6 +3492,12 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
#--------------------------------------------------------
# properties
+ #--------------------------------------------------------
+ def _get_is_doc_part(self):
+ return self._payload[self._idx['pk_doc_obj']] is not None
+
+ is_document_part = property(_get_is_doc_part)
+
#--------------------------------------------------------
def _get_doc_part(self):
if self._payload[self._idx['pk_doc_obj']] is None:
@@ -3626,6 +3676,17 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
return True</code></pre>
</details>
</dd>
+<dt id="Gnumed.business.gmExportArea.cExportItem.is_document_part"><code class="name">var <span class="ident">is_document_part</span></code></dt>
+<dd>
+<div class="desc"></div>
+<details class="source">
+<summary>
+<span>Expand source code</span>
+</summary>
+<pre><code class="python">def _get_is_doc_part(self):
+ return self._payload[self._idx['pk_doc_obj']] is not None</code></pre>
+</details>
+</dd>
<dt id="Gnumed.business.gmExportArea.cExportItem.is_print_job"><code class="name">var <span class="ident">is_print_job</span></code></dt>
<dd>
<div class="desc"></div>
@@ -3761,7 +3822,7 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
<dd>Convert file(s) to PDF on the way out. Before encryption, that is.</dd>
</dl>
<h2 id="returns">Returns</h2>
-<p>Directory for DIRENTRIES, or filename.</p></div>
+<p>Directory for DIRENTRIES, or filename, or None on failure.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -3776,7 +3837,7 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
convert2pdf: Convert file(s) to PDF on the way out. Before encryption, that is.
Returns:
- Directory for DIRENTRIES, or filename.
+ Directory for DIRENTRIES, or filename, or None on failure.
"""
if self.is_DIRENTRY and convert2pdf:
# cannot convert dir entries to PDF
@@ -3834,6 +3895,9 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
<pre><code class="python">def update_data(self, data=None):
assert (data is not None), '<data> must not be <None>'
+ if self.is_DIRENTRY or self.is_document_part:
+ return False
+
SQL = """
UPDATE clin.export_item SET
data = %(data)s::bytea,
@@ -3847,7 +3911,7 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
</details>
</dd>
<dt id="Gnumed.business.gmExportArea.cExportItem.update_data_from_file"><code class="name flex">
-<span>def <span class="ident">update_data_from_file</span></span>(<span>self, filename=None)</span>
+<span>def <span class="ident">update_data_from_file</span></span>(<span>self, filename=None, convert_document_part=False)</span>
</code></dt>
<dd>
<div class="desc"></div>
@@ -3855,7 +3919,15 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
<summary>
<span>Expand source code</span>
</summary>
-<pre><code class="python">def update_data_from_file(self, filename=None):
+<pre><code class="python">def update_data_from_file(self, filename=None, convert_document_part=False):
+
+ if self.is_DIRENTRY:
+ return False
+
+ if self.is_document_part:
+ if not convert_document_part:
+ return False
+
# sanity check
if not (os.access(filename, os.R_OK) and os.path.isfile(filename)):
_log.error('[%s] is not a readable file' % filename)
@@ -3953,6 +4025,7 @@ objects = [ cChildClass(row = {'data': r, 'idx': idx, 'pk_field': 'the PK column
<li><code><a title="Gnumed.business.gmExportArea.cExportItem.has_files_in_root" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.has_files_in_root">has_files_in_root</a></code></li>
<li><code><a title="Gnumed.business.gmExportArea.cExportItem.is_DICOM_directory" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.is_DICOM_directory">is_DICOM_directory</a></code></li>
<li><code><a title="Gnumed.business.gmExportArea.cExportItem.is_DIRENTRY" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.is_DIRENTRY">is_DIRENTRY</a></code></li>
+<li><code><a title="Gnumed.business.gmExportArea.cExportItem.is_document_part" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.is_document_part">is_document_part</a></code></li>
<li><code><a title="Gnumed.business.gmExportArea.cExportItem.is_print_job" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.is_print_job">is_print_job</a></code></li>
<li><code><a title="Gnumed.business.gmExportArea.cExportItem.is_valid_DIRENTRY" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.is_valid_DIRENTRY">is_valid_DIRENTRY</a></code></li>
<li><code><a title="Gnumed.business.gmExportArea.cExportItem.save_to_file" href="gmExportArea.html#Gnumed.business.gmExportArea.cExportItem.save_to_file">save_to_file</a></code></li>
=====================================
client/doc/api/gmTools.html
=====================================
@@ -204,7 +204,7 @@ def mkdir(directory=None, mode=None) -> bool:
Args:
mode: numeric, say 0o0700 for "-rwx------"
- Results:
+ Returns:
True/False based on success
"""
if os.path.isdir(directory):
@@ -234,8 +234,11 @@ def mkdir(directory=None, mode=None) -> bool:
return True
#---------------------------------------------------------------------------
-def create_directory_description_file(directory=None, readme=None, suffix=None):
+def create_directory_description_file(directory:str=None, readme:str=None, suffix:str=None) -> bool:
"""Create a directory description file.
+
+ Returns:
+ <False> if it cannot create the description file.
"""
assert (directory is not None), '<directory> must not be None'
@@ -428,7 +431,13 @@ class gmPaths(gmBorg.cBorg):
- .working_dir: current dir
- - .user_config_dir
+ - .user_config_dir, in the following order:
+ - ~/.config/gnumed/
+ - ~/
+
+ - .user_appdata_dir, in the following order:
+ - ~/.local/gnumed/
+ - ~/
- .system_config_dir
@@ -502,15 +511,25 @@ class gmPaths(gmBorg.cBorg):
# the current working dir at the OS
self.working_dir = os.path.abspath(os.curdir)
- # user-specific config dir, usually below the home dir
- mkdir(os.path.join(self.home_dir, '.%s' % app_name))
- self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
+ # user-specific config dir, usually below the home dir, default to $XDG_CONFIG_HOME
+ _dir = os.path.join(self.home_dir, '.config', app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make config dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_config_dir = _dir
# user-specific app dir, usually below the home dir
mkdir(os.path.join(self.home_dir, app_name))
self.user_work_dir = os.path.join(self.home_dir, app_name)
- # system-wide config dir, usually below /etc/ under UN*X
+ # user-specific app data/state dir, usually below home dir
+ _dir = os.path.join(self.home_dir, '.local', app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make data/state dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_appdata_dir = _dir
+
+ # system-wide config dir, under UN*X usually below /etc/
try:
self.system_config_dir = os.path.join('/etc', app_name)
except ValueError:
@@ -564,8 +583,15 @@ class gmPaths(gmBorg.cBorg):
_log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
# user-specific config dir, usually below the home dir
- mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
- self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
+ _dir = std_paths.UserConfigDir
+ if _dir == self.home_dir:
+ _dir = os.path.join(self.home_dir, '.config', app_name)
+ else:
+ _dir = os.path.join(_dir, '.%s' % app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make config dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_config_dir = _dir
# system-wide config dir, usually below /etc/ under UN*X
try:
@@ -602,6 +628,7 @@ class gmPaths(gmBorg.cBorg):
_log.debug('current working dir: %s', self.working_dir)
_log.debug('user home dir: %s', self.home_dir)
_log.debug('user-specific config dir: %s', self.user_config_dir)
+ _log.debug('user-specific application data dir: %s', self.user_appdata_dir)
_log.debug('system-wide config dir: %s', self.system_config_dir)
_log.debug('system-wide application data dir: %s', self.system_app_data_dir)
_log.debug('temporary dir (user): %s', self.user_tmp_dir)
@@ -615,7 +642,7 @@ class gmPaths(gmBorg.cBorg):
#--------------------------------------
def _set_user_config_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
- msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
+ msg = '[%s:user_config_dir]: unusable path [%s]' % (self.__class__.__name__, path)
_log.error(msg)
raise ValueError(msg)
self.__user_config_dir = path
@@ -625,6 +652,7 @@ class gmPaths(gmBorg.cBorg):
return self.__user_config_dir
user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
+
#--------------------------------------
def _set_system_config_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
@@ -762,6 +790,9 @@ def remove_file(filename:str, log_error:bool=True, force:bool=False) -> bool:
Args:
filename: file to remove
force: if remove does not work attempt to rename the file
+
+ Returns:
+ True/False: Removed or not.
"""
if not os.path.lexists(filename):
return True
@@ -774,16 +805,76 @@ def remove_file(filename:str, log_error:bool=True, force:bool=False) -> bool:
except Exception:
if log_error:
_log.exception('cannot os.remove(%s)', filename)
- if force:
- tmp_name = get_unique_filename(tmp_dir = fname_dir(filename))
- _log.debug('attempting os.replace(%s -> %s)', filename, tmp_name)
- try:
- os.replace(filename, tmp_name)
- return True
+ if not force:
+ return False
+
+ tmp_name = get_unique_filename(tmp_dir = fname_dir(filename))
+ _log.debug('attempting os.replace(%s -> %s)', filename, tmp_name)
+ try:
+ os.replace(filename, tmp_name)
+ return True
+
+ except Exception:
+ if log_error:
+ _log.exception('cannot os.replace(%s)', filename)
+ return False
+
+#---------------------------------------------------------------------------
+def rename_file(filename:str, new_filename:str, overwrite:bool=False, allow_symlink:bool=False) -> bool:
+ """Rename a file.
+
+ Args:
+ filename: source filename
+ new_filename: target filename
+ overwrite: overwrite existing target ?
+ allow_symlink: allow soft links ?
+
+ Returns:
+ True/False: Renamed or not.
+ """
+ _log.debug('renaming: source [%s] -> target [%s]', filename, new_filename)
+ if filename == new_filename:
+ return True
+
+ if not os.path.lexists(filename):
+ _log.error('source does not exist')
+ return False
+
+ if overwrite and not remove_file(new_filename, force = True):
+ _log.error('cannot remove existing target')
+ return False
+
+ try:
+ shutil.move(filename, new_filename)
+ return True
+
+ except OSError:
+ _log.exception('shutil.move() failed')
+
+ try:
+ os.replace(filename, new_filename)
+ return True
+
+ except Exception:
+ _log.exception('os.replace() failed')
+
+ try:
+ os.link(filename, new_filename)
+ return True
+
+ except Exeption:
+ _log.exception('os.link() failed')
+
+ if not allow_symlink:
+ return False
+
+ try:
+ os.symlink(filename, new_filename)
+ return True
+
+ except Exeption:
+ _log.exception('os.symlink() failed')
- except Exception:
- if log_error:
- _log.exception('cannot os.replace(%s)', filename)
return False
#---------------------------------------------------------------------------
@@ -1484,38 +1575,48 @@ def xml_escape_string(text=None):
return xml_tools.escape(text)
#---------------------------------------------------------------------------
-def tex_escape_string(text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False):
+def tex_escape_string(text:str=None, replace_known_unicode:bool=True, replace_eol:bool=False, keep_visual_eol:bool=False) -> str:
"""Check for special TeX characters and transform them.
- replace_eol:
- replaces "\n" with "\\newline"
- keep_visual_eol:
- replaces "\n" with "\\newline \n" such that
+ Args:
+ text: plain (unicode) text to escape for LaTeX processing,
+ note that any valid LaTeX code contained within will be
+ escaped, too
+ replace_eol: replaces "\n" with "\\newline{}"
+ keep_visual_eol: replaces "\n" with "\\newline{}%\n" such that
both LaTeX will know to place a line break
at this point as well as the visual formatting
is preserved in the LaTeX source (think multi-
row table cells)
+
+ Returns:
"""
- text = text.replace('\\', '\\textbackslash') # requires \usepackage{textcomp} in LaTeX source
- text = text.replace('^', '\\textasciicircum')
- text = text.replace('~', '\\textasciitilde')
-
- text = text.replace('{', '\\{')
- text = text.replace('}', '\\}')
- text = text.replace('%', '\\%')
- text = text.replace('&', '\\&')
- text = text.replace('#', '\\#')
- text = text.replace('$', '\\$')
- text = text.replace('_', '\\_')
+ # must happen first
+ text = text.replace('{', '-----{{{{{-----')
+ text = text.replace('}', '-----}}}}}-----')
+
+ text = text.replace('\\', '\\textbackslash{}') # requires \usepackage{textcomp} in LaTeX source
+
+ text = text.replace('-----{{{{{-----', '\\{{}')
+ text = text.replace('-----}}}}}-----', '\\}{}')
+
+ text = text.replace('^', '\\textasciicircum{}')
+ text = text.replace('~', '\\textasciitilde{}')
+
+ text = text.replace('%', '\\%{}')
+ text = text.replace('&', '\\&{}')
+ text = text.replace('#', '\\#{}')
+ text = text.replace('$', '\\${}')
+ text = text.replace('_', '\\_{}')
if replace_eol:
if keep_visual_eol:
- text = text.replace('\n', '\\newline \n')
+ text = text.replace('\n', '\\newline{}%\n')
else:
- text = text.replace('\n', '\\newline ')
+ text = text.replace('\n', '\\newline{}')
if replace_known_unicode:
# this should NOT be replaced for Xe(La)Tex
- text = text.replace(u_euro, '\\EUR') # requires \usepackage{textcomp} in LaTeX source
+ text = text.replace(u_euro, '\\euro{}') # requires \usepackage[official]{eurosym} in LaTeX source
text = text.replace(u_sum, '$\\Sigma$')
return text
@@ -2275,6 +2376,7 @@ if __name__ == '__main__':
paths = gmPaths(wx=None, app_name='gnumed')
print("user home dir:", paths.home_dir)
print("user config dir:", paths.user_config_dir)
+ print("user appdata dir:", paths.user_appdata_dir)
print("user work dir:", paths.user_work_dir)
print("user temp dir:", paths.user_tmp_dir)
print("user+app temp dir:", paths.tmp_dir)
@@ -2674,7 +2776,7 @@ second line\n
#test_xml_escape()
#test_strip_trailing_empty_lines()
#test_fname_stem()
- #test_tex_escape()
+ test_tex_escape()
#test_rst2latex_snippet()
#test_dir_is_empty()
#test_compare_dicts()
@@ -2691,7 +2793,8 @@ second line\n
#test_mk_sandbox_dir()
#test_make_table_from_dicts()
#test_create_dir_desc_file()
- test_dir_list_files()
+ #test_dir_list_files()
+ #test_decorate_window_title()
#===========================================================================</code></pre>
</details>
@@ -2988,16 +3091,21 @@ have issues ! However, for UTF strings it should just work.</p></div>
</details>
</dd>
<dt id="Gnumed.pycommon.gmTools.create_directory_description_file"><code class="name flex">
-<span>def <span class="ident">create_directory_description_file</span></span>(<span>directory=None, readme=None, suffix=None)</span>
+<span>def <span class="ident">create_directory_description_file</span></span>(<span>directory: str = None, readme: str = None, suffix: str = None) ‑> bool</span>
</code></dt>
<dd>
-<div class="desc"><p>Create a directory description file.</p></div>
+<div class="desc"><p>Create a directory description file.</p>
+<h2 id="returns">Returns</h2>
+<p><False> if it cannot create the description file.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
-<pre><code class="python">def create_directory_description_file(directory=None, readme=None, suffix=None):
+<pre><code class="python">def create_directory_description_file(directory:str=None, readme:str=None, suffix:str=None) -> bool:
"""Create a directory description file.
+
+ Returns:
+ <False> if it cannot create the description file.
"""
assert (directory is not None), '<directory> must not be None'
@@ -4094,7 +4202,7 @@ filename here and actually using the filename in callers.</p>
<dt><strong><code>mode</code></strong></dt>
<dd>numeric, say 0o0700 for "-rwx------"</dd>
</dl>
-<h2 id="results">Results</h2>
+<h2 id="returns">Returns</h2>
<p>True/False based on success</p></div>
<details class="source">
<summary>
@@ -4109,7 +4217,7 @@ filename here and actually using the filename in callers.</p>
Args:
mode: numeric, say 0o0700 for "-rwx------"
- Results:
+ Returns:
True/False based on success
"""
if os.path.isdir(directory):
@@ -4411,7 +4519,9 @@ filename here and actually using the filename in callers.</p>
<dd>file to remove</dd>
<dt><strong><code>force</code></strong></dt>
<dd>if remove does not work attempt to rename the file</dd>
-</dl></div>
+</dl>
+<h2 id="returns">Returns</h2>
+<p>True/False: Removed or not.</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
@@ -4422,6 +4532,9 @@ filename here and actually using the filename in callers.</p>
Args:
filename: file to remove
force: if remove does not work attempt to rename the file
+
+ Returns:
+ True/False: Removed or not.
"""
if not os.path.lexists(filename):
return True
@@ -4434,16 +4547,98 @@ filename here and actually using the filename in callers.</p>
except Exception:
if log_error:
_log.exception('cannot os.remove(%s)', filename)
- if force:
- tmp_name = get_unique_filename(tmp_dir = fname_dir(filename))
- _log.debug('attempting os.replace(%s -> %s)', filename, tmp_name)
- try:
- os.replace(filename, tmp_name)
- return True
+ if not force:
+ return False
+
+ tmp_name = get_unique_filename(tmp_dir = fname_dir(filename))
+ _log.debug('attempting os.replace(%s -> %s)', filename, tmp_name)
+ try:
+ os.replace(filename, tmp_name)
+ return True
+
+ except Exception:
+ if log_error:
+ _log.exception('cannot os.replace(%s)', filename)
+ return False</code></pre>
+</details>
+</dd>
+<dt id="Gnumed.pycommon.gmTools.rename_file"><code class="name flex">
+<span>def <span class="ident">rename_file</span></span>(<span>filename: str, new_filename: str, overwrite: bool = False, allow_symlink: bool = False) ‑> bool</span>
+</code></dt>
+<dd>
+<div class="desc"><p>Rename a file.</p>
+<h2 id="args">Args</h2>
+<dl>
+<dt><strong><code>filename</code></strong></dt>
+<dd>source filename</dd>
+<dt><strong><code>new_filename</code></strong></dt>
+<dd>target filename</dd>
+<dt><strong><code>overwrite</code></strong></dt>
+<dd>overwrite existing target ?</dd>
+<dt><strong><code>allow_symlink</code></strong></dt>
+<dd>allow soft links ?</dd>
+</dl>
+<h2 id="returns">Returns</h2>
+<p>True/False: Renamed or not.</p></div>
+<details class="source">
+<summary>
+<span>Expand source code</span>
+</summary>
+<pre><code class="python">def rename_file(filename:str, new_filename:str, overwrite:bool=False, allow_symlink:bool=False) -> bool:
+ """Rename a file.
+
+ Args:
+ filename: source filename
+ new_filename: target filename
+ overwrite: overwrite existing target ?
+ allow_symlink: allow soft links ?
+
+ Returns:
+ True/False: Renamed or not.
+ """
+ _log.debug('renaming: source [%s] -> target [%s]', filename, new_filename)
+ if filename == new_filename:
+ return True
+
+ if not os.path.lexists(filename):
+ _log.error('source does not exist')
+ return False
+
+ if overwrite and not remove_file(new_filename, force = True):
+ _log.error('cannot remove existing target')
+ return False
+
+ try:
+ shutil.move(filename, new_filename)
+ return True
+
+ except OSError:
+ _log.exception('shutil.move() failed')
+
+ try:
+ os.replace(filename, new_filename)
+ return True
+
+ except Exception:
+ _log.exception('os.replace() failed')
+
+ try:
+ os.link(filename, new_filename)
+ return True
+
+ except Exeption:
+ _log.exception('os.link() failed')
+
+ if not allow_symlink:
+ return False
+
+ try:
+ os.symlink(filename, new_filename)
+ return True
+
+ except Exeption:
+ _log.exception('os.symlink() failed')
- except Exception:
- if log_error:
- _log.exception('cannot os.replace(%s)', filename)
return False</code></pre>
</details>
</dd>
@@ -4768,58 +4963,72 @@ filename here and actually using the filename in callers.</p>
</details>
</dd>
<dt id="Gnumed.pycommon.gmTools.tex_escape_string"><code class="name flex">
-<span>def <span class="ident">tex_escape_string</span></span>(<span>text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False)</span>
+<span>def <span class="ident">tex_escape_string</span></span>(<span>text: str = None, replace_known_unicode: bool = True, replace_eol: bool = False, keep_visual_eol: bool = False) ‑> str</span>
</code></dt>
<dd>
<div class="desc"><p>Check for special TeX characters and transform them.</p>
-<pre><code> replace_eol:
- replaces "
+<pre><code> Args:
+ text: plain (unicode) text to escape for LaTeX processing,
+ note that any valid LaTeX code contained within will be
+ escaped, too
+ replace_eol: replaces "
</code></pre>
-<p>" with "\newline"
-keep_visual_eol:
-replaces "
-" with "\newline
+<p>" with "\newline{}"
+keep_visual_eol: replaces "
+" with "\newline{}%
" such that
both LaTeX will know to place a line break
at this point as well as the visual formatting
is preserved in the LaTeX source (think multi-
-row table cells)</p></div>
+row table cells)</p>
+<pre><code> Returns:
+</code></pre></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
-<pre><code class="python">def tex_escape_string(text=None, replace_known_unicode=True, replace_eol=False, keep_visual_eol=False):
+<pre><code class="python">def tex_escape_string(text:str=None, replace_known_unicode:bool=True, replace_eol:bool=False, keep_visual_eol:bool=False) -> str:
"""Check for special TeX characters and transform them.
- replace_eol:
- replaces "\n" with "\\newline"
- keep_visual_eol:
- replaces "\n" with "\\newline \n" such that
+ Args:
+ text: plain (unicode) text to escape for LaTeX processing,
+ note that any valid LaTeX code contained within will be
+ escaped, too
+ replace_eol: replaces "\n" with "\\newline{}"
+ keep_visual_eol: replaces "\n" with "\\newline{}%\n" such that
both LaTeX will know to place a line break
at this point as well as the visual formatting
is preserved in the LaTeX source (think multi-
row table cells)
+
+ Returns:
"""
- text = text.replace('\\', '\\textbackslash') # requires \usepackage{textcomp} in LaTeX source
- text = text.replace('^', '\\textasciicircum')
- text = text.replace('~', '\\textasciitilde')
-
- text = text.replace('{', '\\{')
- text = text.replace('}', '\\}')
- text = text.replace('%', '\\%')
- text = text.replace('&', '\\&')
- text = text.replace('#', '\\#')
- text = text.replace('$', '\\$')
- text = text.replace('_', '\\_')
+ # must happen first
+ text = text.replace('{', '-----{{{{{-----')
+ text = text.replace('}', '-----}}}}}-----')
+
+ text = text.replace('\\', '\\textbackslash{}') # requires \usepackage{textcomp} in LaTeX source
+
+ text = text.replace('-----{{{{{-----', '\\{{}')
+ text = text.replace('-----}}}}}-----', '\\}{}')
+
+ text = text.replace('^', '\\textasciicircum{}')
+ text = text.replace('~', '\\textasciitilde{}')
+
+ text = text.replace('%', '\\%{}')
+ text = text.replace('&', '\\&{}')
+ text = text.replace('#', '\\#{}')
+ text = text.replace('$', '\\${}')
+ text = text.replace('_', '\\_{}')
if replace_eol:
if keep_visual_eol:
- text = text.replace('\n', '\\newline \n')
+ text = text.replace('\n', '\\newline{}%\n')
else:
- text = text.replace('\n', '\\newline ')
+ text = text.replace('\n', '\\newline{}')
if replace_known_unicode:
# this should NOT be replaced for Xe(La)Tex
- text = text.replace(u_euro, '\\EUR') # requires \usepackage{textcomp} in LaTeX source
+ text = text.replace(u_euro, '\\euro{}') # requires \usepackage[official]{eurosym} in LaTeX source
text = text.replace(u_sum, '$\\Sigma$')
return text</code></pre>
@@ -4992,7 +5201,14 @@ breaks are posix newlines (
<p>.working_dir: current dir</p>
</li>
<li>
-<p>.user_config_dir</p>
+<p>.user_config_dir, in the following order:
+- ~/.config/gnumed/
+- ~/</p>
+</li>
+<li>
+<p>.user_appdata_dir, in the following order:
+- ~/.local/gnumed/
+- ~/</p>
</li>
<li>
<p>.system_config_dir</p>
@@ -5035,7 +5251,13 @@ breaks are posix newlines (
- .working_dir: current dir
- - .user_config_dir
+ - .user_config_dir, in the following order:
+ - ~/.config/gnumed/
+ - ~/
+
+ - .user_appdata_dir, in the following order:
+ - ~/.local/gnumed/
+ - ~/
- .system_config_dir
@@ -5109,15 +5331,25 @@ breaks are posix newlines (
# the current working dir at the OS
self.working_dir = os.path.abspath(os.curdir)
- # user-specific config dir, usually below the home dir
- mkdir(os.path.join(self.home_dir, '.%s' % app_name))
- self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
+ # user-specific config dir, usually below the home dir, default to $XDG_CONFIG_HOME
+ _dir = os.path.join(self.home_dir, '.config', app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make config dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_config_dir = _dir
# user-specific app dir, usually below the home dir
mkdir(os.path.join(self.home_dir, app_name))
self.user_work_dir = os.path.join(self.home_dir, app_name)
- # system-wide config dir, usually below /etc/ under UN*X
+ # user-specific app data/state dir, usually below home dir
+ _dir = os.path.join(self.home_dir, '.local', app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make data/state dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_appdata_dir = _dir
+
+ # system-wide config dir, under UN*X usually below /etc/
try:
self.system_config_dir = os.path.join('/etc', app_name)
except ValueError:
@@ -5171,8 +5403,15 @@ breaks are posix newlines (
_log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
# user-specific config dir, usually below the home dir
- mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
- self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
+ _dir = std_paths.UserConfigDir
+ if _dir == self.home_dir:
+ _dir = os.path.join(self.home_dir, '.config', app_name)
+ else:
+ _dir = os.path.join(_dir, '.%s' % app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make config dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_config_dir = _dir
# system-wide config dir, usually below /etc/ under UN*X
try:
@@ -5209,6 +5448,7 @@ breaks are posix newlines (
_log.debug('current working dir: %s', self.working_dir)
_log.debug('user home dir: %s', self.home_dir)
_log.debug('user-specific config dir: %s', self.user_config_dir)
+ _log.debug('user-specific application data dir: %s', self.user_appdata_dir)
_log.debug('system-wide config dir: %s', self.system_config_dir)
_log.debug('system-wide application data dir: %s', self.system_app_data_dir)
_log.debug('temporary dir (user): %s', self.user_tmp_dir)
@@ -5222,7 +5462,7 @@ breaks are posix newlines (
#--------------------------------------
def _set_user_config_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
- msg = '[%s:user_config_dir]: invalid path [%s]' % (self.__class__.__name__, path)
+ msg = '[%s:user_config_dir]: unusable path [%s]' % (self.__class__.__name__, path)
_log.error(msg)
raise ValueError(msg)
self.__user_config_dir = path
@@ -5232,6 +5472,7 @@ breaks are posix newlines (
return self.__user_config_dir
user_config_dir = property(_get_user_config_dir, _set_user_config_dir)
+
#--------------------------------------
def _set_system_config_dir(self, path):
if not (os.access(path, os.R_OK) and os.access(path, os.X_OK)):
@@ -5490,15 +5731,25 @@ breaks are posix newlines (
# the current working dir at the OS
self.working_dir = os.path.abspath(os.curdir)
- # user-specific config dir, usually below the home dir
- mkdir(os.path.join(self.home_dir, '.%s' % app_name))
- self.user_config_dir = os.path.join(self.home_dir, '.%s' % app_name)
+ # user-specific config dir, usually below the home dir, default to $XDG_CONFIG_HOME
+ _dir = os.path.join(self.home_dir, '.config', app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make config dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_config_dir = _dir
# user-specific app dir, usually below the home dir
mkdir(os.path.join(self.home_dir, app_name))
self.user_work_dir = os.path.join(self.home_dir, app_name)
- # system-wide config dir, usually below /etc/ under UN*X
+ # user-specific app data/state dir, usually below home dir
+ _dir = os.path.join(self.home_dir, '.local', app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make data/state dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_appdata_dir = _dir
+
+ # system-wide config dir, under UN*X usually below /etc/
try:
self.system_config_dir = os.path.join('/etc', app_name)
except ValueError:
@@ -5552,8 +5803,15 @@ breaks are posix newlines (
_log.info('wxPython app name is [%s]', wx.GetApp().GetAppName())
# user-specific config dir, usually below the home dir
- mkdir(os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name))
- self.user_config_dir = os.path.join(std_paths.GetUserConfigDir(), '.%s' % app_name)
+ _dir = std_paths.UserConfigDir
+ if _dir == self.home_dir:
+ _dir = os.path.join(self.home_dir, '.config', app_name)
+ else:
+ _dir = os.path.join(_dir, '.%s' % app_name)
+ if not mkdir(_dir):
+ _log.error('cannot make config dir [%s], falling back to home dir', _dir)
+ _dir = self.home_dir
+ self.user_config_dir = _dir
# system-wide config dir, usually below /etc/ under UN*X
try:
@@ -5643,6 +5901,7 @@ breaks are posix newlines (
<li><code><a title="Gnumed.pycommon.gmTools.prompted_input" href="gmTools.html#Gnumed.pycommon.gmTools.prompted_input">prompted_input</a></code></li>
<li><code><a title="Gnumed.pycommon.gmTools.recode_file" href="gmTools.html#Gnumed.pycommon.gmTools.recode_file">recode_file</a></code></li>
<li><code><a title="Gnumed.pycommon.gmTools.remove_file" href="gmTools.html#Gnumed.pycommon.gmTools.remove_file">remove_file</a></code></li>
+<li><code><a title="Gnumed.pycommon.gmTools.rename_file" href="gmTools.html#Gnumed.pycommon.gmTools.rename_file">rename_file</a></code></li>
<li><code><a title="Gnumed.pycommon.gmTools.rm_dir_content" href="gmTools.html#Gnumed.pycommon.gmTools.rm_dir_content">rm_dir_content</a></code></li>
<li><code><a title="Gnumed.pycommon.gmTools.rmdir" href="gmTools.html#Gnumed.pycommon.gmTools.rmdir">rmdir</a></code></li>
<li><code><a title="Gnumed.pycommon.gmTools.rst2html" href="gmTools.html#Gnumed.pycommon.gmTools.rst2html">rst2html</a></code></li>
=====================================
client/doc/schema/gnumed-entire_schema.html
=====================================
@@ -112,7 +112,7 @@
<body>
<!-- Primary Index -->
- <p><br><br>Dumped on 2021-08-02</p>
+ <p><br><br>Dumped on 2022-01-19</p>
<h1><a name="index">Index of database - gnumed_v22</a></h1>
<ul>
=====================================
client/gnumed.py
=====================================
@@ -95,7 +95,7 @@ against. Please run GNUmed as a non-root user.
sys.exit(1)
#----------------------------------------------------------
-current_client_version = '1.8.6'
+current_client_version = '1.8.7'
current_client_branch = '1.8'
_log = None
=====================================
client/pycommon/gmMimeLib.py
=====================================
@@ -189,16 +189,19 @@ def adjust_extension_by_mimetype(filename):
mime_suffix = guess_ext_by_mimetype(mimetype)
if mime_suffix is None:
return filename
+
old_name, old_ext = os.path.splitext(filename)
if old_ext == '':
new_filename = filename + mime_suffix
elif old_ext.lower() == mime_suffix.lower():
return filename
+
new_filename = old_name + mime_suffix
_log.debug('[%s] -> [%s]', filename, new_filename)
try:
os.rename(filename, new_filename)
return new_filename
+
except OSError:
_log.exception('cannot rename, returning original filename')
return filename
=====================================
client/wxpython/gmTopPanel.py
=====================================
@@ -211,8 +211,8 @@ class cTopPnl(wxgTopPnl.wxgTopPnl):
HRs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_heart_rate_quantity, max_no_of_results = 1)
if len(HRs) > 0:
- tests2show.append('@ %s' % (HRs[0]['abbrev_tt'], HRs[0]['unified_val']))
- tooltip_lines.append(_('%s (@): %s ago') % (
+ tests2show.append('@ %s' % HRs[0]['unified_val'])
+ tooltip_lines.append(_('%s (@) in bpm: %s ago') % (
HRs[0]['abbrev_tt'],
gmDateTime.format_apparent_age_medically (
age = gmDateTime.calculate_apparent_age(start = HRs[0]['clin_when'])
View it on GitLab: https://salsa.debian.org/med-team/gnumed-client/-/commit/cc9ec2f0fb4965bac7f8098e7aebf5db21b9a563
--
View it on GitLab: https://salsa.debian.org/med-team/gnumed-client/-/commit/cc9ec2f0fb4965bac7f8098e7aebf5db21b9a563
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20220212/6022d019/attachment-0001.htm>
More information about the debian-med-commit
mailing list