# -*- coding: utf-8 -*- # BSD 2-Clause License # # Apprise - Push Notification Library. # Copyright (c) 2025, Chris Caron # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import re import os import platform from os.path import expanduser from ..logger import logger # Pre-Escape content since we reference it so much ESCAPED_PATH_SEPARATOR = re.escape('\\/') ESCAPED_WIN_PATH_SEPARATOR = re.escape('\\') ESCAPED_NUX_PATH_SEPARATOR = re.escape('/') TIDY_WIN_PATH_RE = re.compile( r'(^[%s]{2}|[^%s\s][%s]|[\s][%s]{2}])([%s]+)' % ( ESCAPED_WIN_PATH_SEPARATOR, ESCAPED_WIN_PATH_SEPARATOR, ESCAPED_WIN_PATH_SEPARATOR, ESCAPED_WIN_PATH_SEPARATOR, ESCAPED_WIN_PATH_SEPARATOR, ), ) TIDY_WIN_TRIM_RE = re.compile( r'^(.+[^:][^%s])[\s%s]*$' % ( ESCAPED_WIN_PATH_SEPARATOR, ESCAPED_WIN_PATH_SEPARATOR, ), ) TIDY_NUX_PATH_RE = re.compile( r'([%s])([%s]+)' % ( ESCAPED_NUX_PATH_SEPARATOR, ESCAPED_NUX_PATH_SEPARATOR, ), ) # A simple path decoder we can re-use which looks after # ensuring our file info is expanded correctly when provided # a path. __PATH_DECODER = os.path.expandvars if \ platform.system() == 'Windows' else os.path.expanduser def path_decode(path): """ Returns the fully decoded path based on the operating system """ return os.path.abspath(__PATH_DECODER(path)) def tidy_path(path): """take a filename and or directory and attempts to tidy it up by removing trailing slashes and correcting any formatting issues. For example: ////absolute//path// becomes: /absolute/path """ # Windows path = TIDY_WIN_PATH_RE.sub('\\1', path.strip()) # Linux path = TIDY_NUX_PATH_RE.sub('\\1', path) # Windows Based (final) Trim path = expanduser(TIDY_WIN_TRIM_RE.sub('\\1', path)) return path def dir_size(path, max_depth=3, missing_okay=True, _depth=0, _errors=None): """ Scans a provided path an returns it's size (in bytes) of path provided """ if _errors is None: _errors = set() if _depth > max_depth: _errors.add(path) return (0, _errors) total = 0 try: with os.scandir(path) as it: for entry in it: try: if entry.is_file(follow_symlinks=False): total += entry.stat(follow_symlinks=False).st_size elif entry.is_dir(follow_symlinks=False): (totals, _) = dir_size( entry.path, max_depth=max_depth, _depth=_depth + 1, _errors=_errors) total += totals except FileNotFoundError: # no worries; Nothing to do continue except (OSError, IOError) as e: # Permission error of some kind or disk problem... # There is nothing we can do at this point _errors.add(entry.path) logger.warning( 'dir_size detetcted inaccessible path: %s', os.fsdecode(entry.path)) logger.debug('dir_size Exception: %s' % str(e)) continue except FileNotFoundError: if not missing_okay: # Conditional error situation _errors.add(path) except (OSError, IOError) as e: # Permission error of some kind or disk problem... # There is nothing we can do at this point _errors.add(path) logger.warning( 'dir_size detetcted inaccessible path: %s', os.fsdecode(path)) logger.debug('dir_size Exception: %s' % str(e)) return (total, _errors) def bytes_to_str(value): """ Covert an integer (in bytes) into it's string representation with acompanied unit value (such as B, KB, MB, GB, TB, etc) """ unit = 'B' try: value = float(value) except (ValueError, TypeError): return None if value >= 1024.0: value = value / 1024.0 unit = 'KB' if value >= 1024.0: value = value / 1024.0 unit = 'MB' if value >= 1024.0: value = value / 1024.0 unit = 'GB' if value >= 1024.0: value = value / 1024.0 unit = 'TB' return '%.2f%s' % (round(value, 2), unit)