gnm_deliverables/files.py (67 lines of code) (raw):

import os import errno import stat from collections import namedtuple import typing import pytz import re from django.utils.timezone import datetime import logging logger = logging.getLogger(__name__) FileInfo = namedtuple('FileInfo', 'absolute_path path size access_dt modified_dt changed_dt') DANGEROUS_CHARS = '\\/?%*;:!|\"<>' FILEPATH_SANITISER = re.compile("[{0}]".format(DANGEROUS_CHARS)) def sanitise_dir_name(name: str) -> str: """ sanitise the given path part :param name: :return: """ return FILEPATH_SANITISER.sub("", name) def get_path_for_deliverable(name: str)->str: """ returns the expected server-side path of the dropfolder for assets for the given bundle name. :param name: bundle name :return: the expected path for the asset folder. """ from django.conf import settings return os.path.join(getattr(settings, 'GNM_DELIVERABLES_SAN_ROOT', '/tmp'), sanitise_dir_name(name)) def get_local_path_for_deliverable(name: str) -> str: """ returns the expected client-side path of the dropfolder for assets for the given bundle name. :param name: bundle name :return: the expected path for the asset folder. """ from django.conf import settings return os.path.join(getattr(settings, 'GNM_DELIVERABLES_SAN_ROOT_LOCAL', '/tmp'), sanitise_dir_name(name)) def ts_to_dt(timestamp: typing.Union[float, int], millis=False) -> datetime: """ Converts a timestamp value to a datetime value. The configured timezone from the settings is applied to the resulting DateTime. If no timezone is configured, then we default to UTC and emit a warning :param timestamp: epoch timestamp value to convert. Expect a TypeError to be raised if this is not a float or int. :param millis: if True, then the `timestamp` value is in milliseconds. If False (the default) then it's in seconds :return: the timezone-aware datetime """ from django.conf import settings ts = float(timestamp) if millis: ts /= 1000.0 naive_dt = datetime.utcfromtimestamp(ts) tz = pytz.timezone("UTC") aware_utc_dt = tz.localize(naive_dt) if hasattr(settings, "TIME_ZONE"): server_tz = pytz.timezone(settings.TIME_ZONE) return aware_utc_dt.astimezone(server_tz) else: logger.warning("TIME_ZONE is not configured in the settings, defaulting to UTC") return aware_utc_dt def find_files_for_deliverable(name): """ generator that yields a FileInfo named tuple for each file that exists in the dropfolder corresponding to the given deliverable. :param name: deliverable bundle name, used for making the path to scan :return: yields fileInfo objects, possibly zero if there is nothing in the dropfolder. """ deliverable_path = get_path_for_deliverable(name) logger.info("find_files_for_deliverable: scanning {0}".format(deliverable_path)) for root, dirs, files in os.walk(deliverable_path): for f in files: if f[0] == '.': continue absolute_path = os.path.join(root, f) stat_result = os.stat(absolute_path) yield FileInfo( absolute_path=absolute_path, path=absolute_path.replace(deliverable_path, '').lstrip(os.path.sep), # path relative deliverable path size=stat_result.st_size, access_dt=ts_to_dt(stat_result.st_atime), modified_dt=ts_to_dt(stat_result.st_mtime), changed_dt=ts_to_dt(stat_result.st_ctime) ) def create_folder(path, permission=None): if permission is None: from django.conf import settings permission=getattr(settings, 'GNM_DELIVERABLES_FOLDER_PERMISSION', stat.S_IRWXU | stat.S_IRWXG) try: os.makedirs(path) os.chmod(path, permission) return path, True except OSError as e: logger.error("Got {0}, checking for {1}".format(e.errno, errno.EEXIST)) if e.errno == errno.EEXIST: return path, False raise e def create_folder_for_deliverable(name): return create_folder(get_path_for_deliverable(name))