Module Gnumed.business.gmAutoFileImport

Handling of automatic file import from certain directories.

Expand source code
# -*- coding: utf-8 -*-
"""Handling of automatic file import from certain directories."""
#============================================================
__author__ = "K.Hilbert <Karsten.Hilbert@gmx.net>"
__license__ = "GPL v2 or later"


import sys
import os
import logging


if __name__ == '__main__':
        sys.path.insert(0, '../../')
        _ = lambda x:x
else:
        try:
                _
        except NameError:
                from Gnumed.pycommon import gmI18N
                gmI18N.activate_locale()
                gmI18N.install_domain()
from Gnumed.pycommon import gmTools
from Gnumed.pycommon import gmPG2
from Gnumed.pycommon import gmDateTime
from Gnumed.business import gmIncomingData


_log = logging.getLogger('gm.autoimport')

#============================================================
def _worker__auto_import_files():
        """Import files. Will run in a thread."""
        cAutoImportDir().import_files()

#============================================================
__default_dirs_created = False

def setup_default_import_dirs() -> bool:
        global __default_dirs_created
        if __default_dirs_created:
                return True

        _log.debug('setting up default auto-import directories')
        paths = [
                # aka "~/gnumed/"
                os.path.join(gmTools.gmPaths().user_work_dir, 'auto-import'),
                # aka ".local/gnumed/"
                os.path.join(gmTools.gmPaths().user_appdata_dir, 'auto-import')
        ]
        README = """GNUmed Electronic Medical Record

        for user interaction:
                %s/

        for programmatic interaction:
                %s/

Files dropped into these directories and their subdirectories
will be auto-imported into the GNUmed incoming area. Sub-
directories can also be links.

Rules:

        - inaccessible files will be ignored

        - filenames ending in ".imported" will be ignored

        - filenames ending in ".new" will be ignored, unless
          the file was last modified more than 24 hours ago

        - successfully imported files will be renamed to
          ".FILENAME.CURRENT_TIMESTAMP.imported"

        - files already existing in the database (based on
          MD5 of the file content) will be removed

        - one level of subdirectories is scanned for files

        - subdirectories will not be removed, even if empty

How to safely drop files into this directory:

        Copy in the file with a filename ending in ".new". When
        done copying rename it by removing the ".new" suffix. Renaming
        in-place is expected to be a safe (atomic) operation.
""" % tuple(paths)
        for path in paths:
                _log.debug(path)
                if gmTools.mkdir(directory = path):
                        gmTools.create_directory_description_file(directory = path, readme = README)
                        continue
                _log.error('cannot create default auto-import dir [%s]', path)
                return False

        __default_dirs_created = True
        return True

#============================================================
class cAutoImportDir:
        """Represents a directory from which files are auto-imported into the GNUmed database.

        Args:
                path: None -> default path, otherwise an existing directory

        Default directories:

                * ~/gnumed/auto-incoming/ (will be auto-created)
                * ~/.local/gnumed/auto-incoming/ (will be auto-created)
        """
        def __init__(self, path:str=None):
                if path:
                        path = gmTools.normalize_path(path)
                        if not os.path.isdir(path):
                                raise EnvironmentError('auto-import path [%s] not accessible', path)

                        self.__paths = [path]
                        _log.info(self.__paths)
                        return

                if not setup_default_import_dirs():
                        raise EnvironmentError('cannot create default auto-import path [%s]', path)

                self.__paths = [
                        # aka "~/gnumed/"
                        os.path.join(gmTools.gmPaths().user_work_dir, 'auto-import'),
                        # aka ".gnumed/"
                        os.path.join(gmTools.gmPaths().user_appdata_dir, 'auto-import')
                ]
                _log.info(self.__paths)

        #--------------------------------------------------------
        def import_files(self):
                sub_dirs = []
                for basedir in self.__paths:
                        for entry in gmTools.dir_list_files(directory = basedir, exclude_subdirs = False):
                                path = os.path.join(basedir, entry)
                                if os.path.isdir(path):
                                        sub_dirs.append(path)
                for import_dir in self.__paths + sub_dirs:
                        self.__import_files_from_dir(import_dir)
                return

        #--------------------------------------------------------
        # internal halpers
        #--------------------------------------------------------
        def __import_files_from_dir(self, import_dir:str) -> bool:
                if not os.path.isdir(import_dir):
                        _log.error('[%s] not a directory', import_dir)
                        return False

                _log.debug('importing from [%s]', import_dir)
                entries = gmTools.dir_list_files(directory = import_dir, exclude_subdirs = True)
                for path in entries:
                        self.__import_file(path)
                return True

        #--------------------------------------------------------
        def __import_file(self, filename:str, max_size:int=None) -> bool:
                _log.debug(filename)
                if  gmTools._GM_DIR_DESC_FILENAME_PREFIX in filename:
                        _log.debug('... skipped')
                        return True

                if filename.endswith('.imported'):
                        _log.debug('... skipped')
                        return True

                now = gmDateTime.pydt_now_here()
                if filename.endswith('.new'):
                        try:
                                ts_created = os.path.getmtime(filename)
                        except OSError:
                                _log.debug('... cannot getmtime()')
                                return False

                        # ignore "new" files only if younger than 24 hours
                        if (now.timestamp() - ts_created) < (24 * 3600):
                                _log.debug('... skipped')
                                return True

                try:
                        fsize = os.path.getsize(filename)
                except OSError:
                        _log.debug('... cannot getsize()')
                        return False

                # FIXME: make configurable
                max_size = max_size or (20 * gmTools._MB)
                if fsize > max_size:
                        _log.debug('... skipped, file %s > max %s', fsize, max_size)
                        return True

                if gmIncomingData.data_exists(filename):
                        _log.debug('... exists')
                        gmTools.remove_file(filename)
                        return True

                incoming = gmIncomingData.create_incoming_data(filename = filename, verify_import = True)
                if not incoming:
                        return False

                incoming['comment'] = _('%s (auto-import %s)' % (filename, now.strftime('%c')))
                incoming.save()
                new_fname = '.%s.%s.imported' % (
                        gmTools.fname_from_path(filename),
                        now.timestamp()
                )
                gmTools.rename_file (
                        filename,
                        os.path.join(gmTools.fname_dir(filename), new_fname),
                        overwrite = True,
                        allow_symlink = False,
                        allow_hardlink = False
                )
                return True

#============================================================
# main
#------------------------------------------------------------
if __name__ == "__main__":

        if len(sys.argv) < 2:
                sys.exit()

        if sys.argv[1] != 'test':
                sys.exit()

        del _
        from Gnumed.pycommon import gmI18N
        gmI18N.activate_locale()
        gmI18N.install_domain()

        gmDateTime.init()
        gmTools.gmPaths()

        #-------------------------------------------------------
        def test():
                imp_dir = cAutoImportDir()
                gmPG2.request_login_params(setup_pool = True)
                imp_dir.import_files()

        #-------------------------------------------------------
        test()

Functions

def setup_default_import_dirs() ‑> bool
Expand source code
def setup_default_import_dirs() -> bool:
        global __default_dirs_created
        if __default_dirs_created:
                return True

        _log.debug('setting up default auto-import directories')
        paths = [
                # aka "~/gnumed/"
                os.path.join(gmTools.gmPaths().user_work_dir, 'auto-import'),
                # aka ".local/gnumed/"
                os.path.join(gmTools.gmPaths().user_appdata_dir, 'auto-import')
        ]
        README = """GNUmed Electronic Medical Record

        for user interaction:
                %s/

        for programmatic interaction:
                %s/

Files dropped into these directories and their subdirectories
will be auto-imported into the GNUmed incoming area. Sub-
directories can also be links.

Rules:

        - inaccessible files will be ignored

        - filenames ending in ".imported" will be ignored

        - filenames ending in ".new" will be ignored, unless
          the file was last modified more than 24 hours ago

        - successfully imported files will be renamed to
          ".FILENAME.CURRENT_TIMESTAMP.imported"

        - files already existing in the database (based on
          MD5 of the file content) will be removed

        - one level of subdirectories is scanned for files

        - subdirectories will not be removed, even if empty

How to safely drop files into this directory:

        Copy in the file with a filename ending in ".new". When
        done copying rename it by removing the ".new" suffix. Renaming
        in-place is expected to be a safe (atomic) operation.
""" % tuple(paths)
        for path in paths:
                _log.debug(path)
                if gmTools.mkdir(directory = path):
                        gmTools.create_directory_description_file(directory = path, readme = README)
                        continue
                _log.error('cannot create default auto-import dir [%s]', path)
                return False

        __default_dirs_created = True
        return True

Classes

class cAutoImportDir (path: str = None)

Represents a directory from which files are auto-imported into the GNUmed database.

Args

path
None -> default path, otherwise an existing directory

Default directories:

    * ~/gnumed/auto-incoming/ (will be auto-created)
    * ~/.local/gnumed/auto-incoming/ (will be auto-created)
Expand source code
class cAutoImportDir:
        """Represents a directory from which files are auto-imported into the GNUmed database.

        Args:
                path: None -> default path, otherwise an existing directory

        Default directories:

                * ~/gnumed/auto-incoming/ (will be auto-created)
                * ~/.local/gnumed/auto-incoming/ (will be auto-created)
        """
        def __init__(self, path:str=None):
                if path:
                        path = gmTools.normalize_path(path)
                        if not os.path.isdir(path):
                                raise EnvironmentError('auto-import path [%s] not accessible', path)

                        self.__paths = [path]
                        _log.info(self.__paths)
                        return

                if not setup_default_import_dirs():
                        raise EnvironmentError('cannot create default auto-import path [%s]', path)

                self.__paths = [
                        # aka "~/gnumed/"
                        os.path.join(gmTools.gmPaths().user_work_dir, 'auto-import'),
                        # aka ".gnumed/"
                        os.path.join(gmTools.gmPaths().user_appdata_dir, 'auto-import')
                ]
                _log.info(self.__paths)

        #--------------------------------------------------------
        def import_files(self):
                sub_dirs = []
                for basedir in self.__paths:
                        for entry in gmTools.dir_list_files(directory = basedir, exclude_subdirs = False):
                                path = os.path.join(basedir, entry)
                                if os.path.isdir(path):
                                        sub_dirs.append(path)
                for import_dir in self.__paths + sub_dirs:
                        self.__import_files_from_dir(import_dir)
                return

        #--------------------------------------------------------
        # internal halpers
        #--------------------------------------------------------
        def __import_files_from_dir(self, import_dir:str) -> bool:
                if not os.path.isdir(import_dir):
                        _log.error('[%s] not a directory', import_dir)
                        return False

                _log.debug('importing from [%s]', import_dir)
                entries = gmTools.dir_list_files(directory = import_dir, exclude_subdirs = True)
                for path in entries:
                        self.__import_file(path)
                return True

        #--------------------------------------------------------
        def __import_file(self, filename:str, max_size:int=None) -> bool:
                _log.debug(filename)
                if  gmTools._GM_DIR_DESC_FILENAME_PREFIX in filename:
                        _log.debug('... skipped')
                        return True

                if filename.endswith('.imported'):
                        _log.debug('... skipped')
                        return True

                now = gmDateTime.pydt_now_here()
                if filename.endswith('.new'):
                        try:
                                ts_created = os.path.getmtime(filename)
                        except OSError:
                                _log.debug('... cannot getmtime()')
                                return False

                        # ignore "new" files only if younger than 24 hours
                        if (now.timestamp() - ts_created) < (24 * 3600):
                                _log.debug('... skipped')
                                return True

                try:
                        fsize = os.path.getsize(filename)
                except OSError:
                        _log.debug('... cannot getsize()')
                        return False

                # FIXME: make configurable
                max_size = max_size or (20 * gmTools._MB)
                if fsize > max_size:
                        _log.debug('... skipped, file %s > max %s', fsize, max_size)
                        return True

                if gmIncomingData.data_exists(filename):
                        _log.debug('... exists')
                        gmTools.remove_file(filename)
                        return True

                incoming = gmIncomingData.create_incoming_data(filename = filename, verify_import = True)
                if not incoming:
                        return False

                incoming['comment'] = _('%s (auto-import %s)' % (filename, now.strftime('%c')))
                incoming.save()
                new_fname = '.%s.%s.imported' % (
                        gmTools.fname_from_path(filename),
                        now.timestamp()
                )
                gmTools.rename_file (
                        filename,
                        os.path.join(gmTools.fname_dir(filename), new_fname),
                        overwrite = True,
                        allow_symlink = False,
                        allow_hardlink = False
                )
                return True

Methods

def import_files(self)
Expand source code
def import_files(self):
        sub_dirs = []
        for basedir in self.__paths:
                for entry in gmTools.dir_list_files(directory = basedir, exclude_subdirs = False):
                        path = os.path.join(basedir, entry)
                        if os.path.isdir(path):
                                sub_dirs.append(path)
        for import_dir in self.__paths + sub_dirs:
                self.__import_files_from_dir(import_dir)
        return