Module lib.tools.filesystem

Miscellaneous path functions

Expand source code
# Distributed under Pycameresp License
# Copyright (c) 2023 Remi BERTHOLET
""" Miscellaneous path functions """
import os
import sys
import time
import tools.fnmatch

try:
        import uasyncio
        import uos
except:
        pass

def exists(filename):
        """ Test if the filename existing """
        try:
                _ = os.lstat(filename)
                return True
        except:
                try:
                        _ = os.stat(filename)
                        return True
                except:
                        pass
        return False

previous_file_info = []
previous_get = 0
def fileinfo(path):
        """ Get the file informations """
        global previous_file_info, previous_get
        current_time = time.time()
        if previous_get + 3 < current_time:
                previous_get = current_time
                previous_file_info = []
        if len(previous_file_info) == 0:
                previous_file_info = [path, os.stat(path)]
        elif previous_file_info[0] != path:
                previous_file_info = [path, os.stat(path)]
        return previous_file_info[1]

def isdir(path):
        """ Indicates if the path is a directory """
        try:
                if fileinfo(path)[0] & 0x4000 == 0x4000:
                        return True
        except:
                pass
        return False

def isfile(path):
        """ Indicates if the path is a file """
        try:
                if fileinfo(path)[0] & 0x4000 == 0:
                        return True
        except:
                pass
        return False

def filesize(path):
        """ Get the file size """
        info = fileinfo(path)
        return info[6]

def filetime(path):
        """ Get the file modified time """
        return fileinfo(path)[8]

def splitext(p):
        """ Split file extension """
        sep='\\'
        altsep = '/'
        extsep = '.'
        sep_index = p.rfind(sep)
        if altsep:
                altsepIndex = p.rfind(altsep)
                sep_index = max(sep_index, altsepIndex)

        dot_index = p.rfind(extsep)
        if dot_index > sep_index:
                filename_index = sep_index + 1
                while filename_index < dot_index:
                        if p[filename_index:filename_index+1] != extsep:
                                return p[:dot_index], p[dot_index:]
                        filename_index += 1
        return p, p[:0]

def split(p):
        """ Split file """
        sep = "/"
        i = p.rfind(sep) + 1
        head, tail = p[:i], p[i:]
        if head and head != sep*len(head):
                head = head.rstrip(sep)
        return head, tail

def makedir(directory, recursive=False):
        """ Make directory recursively """
        if recursive is False:
                os.mkdir(directory)
        else:
                directories = [directory]
                while 1:
                        parts = split(directory)
                        if parts[1] == "" or parts[0] == "":
                                break
                        directories.append(parts[0])
                        directory = parts[0]

                directories.reverse()
                for d in directories:
                        if not(exists(d)):
                                os.mkdir(d)

def abspath(cwd, payload):
        """ Get the absolute path """
        # Just a few special cases "..", "." and ""
        # If payload start's with /, set cwd to /
        # and consider the remainder a relative path
        if payload.startswith('/'):
                cwd = "/"
        for token in payload.split("/"):
                if token == '..':
                        if cwd != '/':
                                cwd = '/'.join(cwd.split('/')[:-1])
                                if cwd == '':
                                        cwd = '/'
                elif token != '.' and token != '':
                        if cwd == '/':
                                cwd += token
                        else:
                                cwd = cwd + '/' + token
        return cwd

def abspathbytes(cwd, payload):
        """ Get the absolute path into a bytes """
        # Just a few special cases "..", "." and ""
        # If payload start's with /, set cwd to /
        # and consider the remainder a relative path
        if payload.startswith(b'/'):
                cwd = b"/"
        for token in payload.split(b"/"):
                if token == b'..':
                        if cwd != b'/':
                                cwd = b'/'.join(cwd.split(b'/')[:-1])
                                if cwd == b'':
                                        cwd = b'/'
                elif token != b'.' and token != b'':
                        if cwd == b'/':
                                cwd += token
                        else:
                                cwd = cwd + b'/' + token
        return cwd

def remove(filename):
        """ Remove file existing """
        try:
                uos.remove(filename)
        except:
                pass

def rename(old, new):
        """ Rename file """
        if exists(new):
                remove(new)
        try:
                uos.rename(old, new)
        except:
                pass

def ismicropython():
        """ Indicates if the python is micropython """
        if sys.implementation.name == "micropython":
                return True
        return False

try:
        import uos
        list_directory = uos.ilistdir
except:
        def list_directory(path_):
                """ List directory """
                result = []
                for filename in os.listdir(path_):
                        fileinfo_ = os.stat(path_ + "/" + filename)
                        typ = fileinfo_[0]
                        size = fileinfo_[6]

                        result.append((filename, typ, 0, size))
                return result

def scandir(path, pattern, recursive, displayer=None):
        """ Scan recursively a directory """
        filenames   = []
        directories = []
        if path == "":
                path = "."
        if path is not None and pattern is not None and exists(path) and isdir(path):
                for file_info in list_directory(path):
                        name = file_info[0]
                        typ  = file_info[1]
                        if path != "":
                                filename = path + "/" + name
                        else:
                                filename = name
                        if sys.platform != "win32":
                                filename = filename.replace("//","/")
                                filename = filename.replace("//","/")

                        # if directory
                        if typ & 0xF000 == 0x4000:
                                if displayer:
                                        displayer.show(filename)
                                else:
                                        directories.append(filename)
                                if recursive:
                                        dirs,fils = scandir(filename, pattern, recursive, displayer)
                                        filenames += fils
                                        directories += dirs
                        else:
                                if tools.fnmatch.fnmatch(name, pattern):
                                        if displayer:
                                                displayer.show(filename)
                                                filenames = [""]
                                        else:
                                                filenames.append(filename)
        return directories, filenames

async def ascandir(path, pattern, recursive, displayer=None):
        """ Asynchronous scan recursively a directory """
        filenames   = []
        directories = []
        if path == "":
                path = "."
        if path is not None and pattern is not None and exists(path) and isdir(path):
                for file_info in list_directory(path):
                        name = file_info[0]
                        typ  = file_info[1]
                        if path != "":
                                filename = path + "/" + name
                        else:
                                filename = name
                        if sys.platform != "win32":
                                filename = filename.replace("//","/")
                                filename = filename.replace("//","/")

                        # if directory
                        if typ & 0xF000 == 0x4000:
                                if displayer:
                                        displayer.show(filename)
                                else:
                                        directories.append(filename)
                                if recursive:
                                        dirs,fils = await ascandir(filename, pattern, recursive, displayer)
                                        filenames += fils
                                        directories += dirs
                        else:
                                if tools.fnmatch.fnmatch(name, pattern):
                                        if displayer:
                                                displayer.show(filename)
                                                filenames = [""]
                                        else:
                                                filenames.append(filename)
                if ismicropython():
                        await uasyncio.sleep_ms(3)
        return directories, filenames

def prefix(files):
        """ Get the common prefix of all files """
        # Initializes counters
        counters = []

        # For all files
        for file in files:
                if type(file) == type(""):
                        file = file.encode("utf8")
                # file = file.encode("utf8")
                # Split the file name into a piece
                paths = file.split(b"/")

                # For each piece
                length = len(paths)
                for i in range(0,length):
                        try:
                                try:
                                        # Test if counters exist
                                        counters[i][paths[i]] += 1
                                except:
                                        # Creates a path counters
                                        counters[i][paths[i]] = 1
                        except:
                                # Adds a new level of depth
                                counters.append({paths[i] : 1})

        # Constructs the prefix of the list of files
        try:
                result = b""
                amount = list(counters[0].values())[0]
                for counter in counters:
                        if len(tuple(counter.keys())) == 1 and list(counter.values())[0] == amount:
                                result += list(counter.keys())[0] + b"/"
                        else:
                                return result [:-1]
                return result
        except IndexError:
                return b""

def normpath(path):
        # Extract from https://github.com/python/cpython/blob/main/Lib/posixpath.py
        """Normalize path, eliminating double slashes, etc."""
        # path = os.fspath(path)
        if isinstance(path, bytes):
                sep = b'/'
                empty = b''
                dot = b'.'
                dotdot = b'..'
        else:
                sep = '/'
                empty = ''
                dot = '.'
                dotdot = '..'
        if path == empty:
                return dot
        initial_slashes = path.startswith(sep)
        # POSIX allows one or two initial slashes, but treats three or more
        # as single slash.
        # (see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13)
        if (initial_slashes and
                path.startswith(sep*2) and not path.startswith(sep*3)):
                initial_slashes = 2
        comps = path.split(sep)
        new_comps = []
        for comp in comps:
                if comp in (empty, dot):
                        continue
                if (comp != dotdot or (not initial_slashes and not new_comps) or
                                (new_comps and new_comps[-1] == dotdot)):
                        new_comps.append(comp)
                elif new_comps:
                        new_comps.pop()
        comps = new_comps
        path = sep.join(comps)
        if initial_slashes:
                path = sep*initial_slashes + path
        return path or dot

Functions

def abspath(cwd, payload)

Get the absolute path

Expand source code
def abspath(cwd, payload):
        """ Get the absolute path """
        # Just a few special cases "..", "." and ""
        # If payload start's with /, set cwd to /
        # and consider the remainder a relative path
        if payload.startswith('/'):
                cwd = "/"
        for token in payload.split("/"):
                if token == '..':
                        if cwd != '/':
                                cwd = '/'.join(cwd.split('/')[:-1])
                                if cwd == '':
                                        cwd = '/'
                elif token != '.' and token != '':
                        if cwd == '/':
                                cwd += token
                        else:
                                cwd = cwd + '/' + token
        return cwd
def abspathbytes(cwd, payload)

Get the absolute path into a bytes

Expand source code
def abspathbytes(cwd, payload):
        """ Get the absolute path into a bytes """
        # Just a few special cases "..", "." and ""
        # If payload start's with /, set cwd to /
        # and consider the remainder a relative path
        if payload.startswith(b'/'):
                cwd = b"/"
        for token in payload.split(b"/"):
                if token == b'..':
                        if cwd != b'/':
                                cwd = b'/'.join(cwd.split(b'/')[:-1])
                                if cwd == b'':
                                        cwd = b'/'
                elif token != b'.' and token != b'':
                        if cwd == b'/':
                                cwd += token
                        else:
                                cwd = cwd + b'/' + token
        return cwd
async def ascandir(path, pattern, recursive, displayer=None)

Asynchronous scan recursively a directory

Expand source code
async def ascandir(path, pattern, recursive, displayer=None):
        """ Asynchronous scan recursively a directory """
        filenames   = []
        directories = []
        if path == "":
                path = "."
        if path is not None and pattern is not None and exists(path) and isdir(path):
                for file_info in list_directory(path):
                        name = file_info[0]
                        typ  = file_info[1]
                        if path != "":
                                filename = path + "/" + name
                        else:
                                filename = name
                        if sys.platform != "win32":
                                filename = filename.replace("//","/")
                                filename = filename.replace("//","/")

                        # if directory
                        if typ & 0xF000 == 0x4000:
                                if displayer:
                                        displayer.show(filename)
                                else:
                                        directories.append(filename)
                                if recursive:
                                        dirs,fils = await ascandir(filename, pattern, recursive, displayer)
                                        filenames += fils
                                        directories += dirs
                        else:
                                if tools.fnmatch.fnmatch(name, pattern):
                                        if displayer:
                                                displayer.show(filename)
                                                filenames = [""]
                                        else:
                                                filenames.append(filename)
                if ismicropython():
                        await uasyncio.sleep_ms(3)
        return directories, filenames
def exists(filename)

Test if the filename existing

Expand source code
def exists(filename):
        """ Test if the filename existing """
        try:
                _ = os.lstat(filename)
                return True
        except:
                try:
                        _ = os.stat(filename)
                        return True
                except:
                        pass
        return False
def fileinfo(path)

Get the file informations

Expand source code
def fileinfo(path):
        """ Get the file informations """
        global previous_file_info, previous_get
        current_time = time.time()
        if previous_get + 3 < current_time:
                previous_get = current_time
                previous_file_info = []
        if len(previous_file_info) == 0:
                previous_file_info = [path, os.stat(path)]
        elif previous_file_info[0] != path:
                previous_file_info = [path, os.stat(path)]
        return previous_file_info[1]
def filesize(path)

Get the file size

Expand source code
def filesize(path):
        """ Get the file size """
        info = fileinfo(path)
        return info[6]
def filetime(path)

Get the file modified time

Expand source code
def filetime(path):
        """ Get the file modified time """
        return fileinfo(path)[8]
def isdir(path)

Indicates if the path is a directory

Expand source code
def isdir(path):
        """ Indicates if the path is a directory """
        try:
                if fileinfo(path)[0] & 0x4000 == 0x4000:
                        return True
        except:
                pass
        return False
def isfile(path)

Indicates if the path is a file

Expand source code
def isfile(path):
        """ Indicates if the path is a file """
        try:
                if fileinfo(path)[0] & 0x4000 == 0:
                        return True
        except:
                pass
        return False
def ismicropython()

Indicates if the python is micropython

Expand source code
def ismicropython():
        """ Indicates if the python is micropython """
        if sys.implementation.name == "micropython":
                return True
        return False
def makedir(directory, recursive=False)

Make directory recursively

Expand source code
def makedir(directory, recursive=False):
        """ Make directory recursively """
        if recursive is False:
                os.mkdir(directory)
        else:
                directories = [directory]
                while 1:
                        parts = split(directory)
                        if parts[1] == "" or parts[0] == "":
                                break
                        directories.append(parts[0])
                        directory = parts[0]

                directories.reverse()
                for d in directories:
                        if not(exists(d)):
                                os.mkdir(d)
def normpath(path)

Normalize path, eliminating double slashes, etc.

Expand source code
def normpath(path):
        # Extract from https://github.com/python/cpython/blob/main/Lib/posixpath.py
        """Normalize path, eliminating double slashes, etc."""
        # path = os.fspath(path)
        if isinstance(path, bytes):
                sep = b'/'
                empty = b''
                dot = b'.'
                dotdot = b'..'
        else:
                sep = '/'
                empty = ''
                dot = '.'
                dotdot = '..'
        if path == empty:
                return dot
        initial_slashes = path.startswith(sep)
        # POSIX allows one or two initial slashes, but treats three or more
        # as single slash.
        # (see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13)
        if (initial_slashes and
                path.startswith(sep*2) and not path.startswith(sep*3)):
                initial_slashes = 2
        comps = path.split(sep)
        new_comps = []
        for comp in comps:
                if comp in (empty, dot):
                        continue
                if (comp != dotdot or (not initial_slashes and not new_comps) or
                                (new_comps and new_comps[-1] == dotdot)):
                        new_comps.append(comp)
                elif new_comps:
                        new_comps.pop()
        comps = new_comps
        path = sep.join(comps)
        if initial_slashes:
                path = sep*initial_slashes + path
        return path or dot
def prefix(files)

Get the common prefix of all files

Expand source code
def prefix(files):
        """ Get the common prefix of all files """
        # Initializes counters
        counters = []

        # For all files
        for file in files:
                if type(file) == type(""):
                        file = file.encode("utf8")
                # file = file.encode("utf8")
                # Split the file name into a piece
                paths = file.split(b"/")

                # For each piece
                length = len(paths)
                for i in range(0,length):
                        try:
                                try:
                                        # Test if counters exist
                                        counters[i][paths[i]] += 1
                                except:
                                        # Creates a path counters
                                        counters[i][paths[i]] = 1
                        except:
                                # Adds a new level of depth
                                counters.append({paths[i] : 1})

        # Constructs the prefix of the list of files
        try:
                result = b""
                amount = list(counters[0].values())[0]
                for counter in counters:
                        if len(tuple(counter.keys())) == 1 and list(counter.values())[0] == amount:
                                result += list(counter.keys())[0] + b"/"
                        else:
                                return result [:-1]
                return result
        except IndexError:
                return b""
def remove(filename)

Remove file existing

Expand source code
def remove(filename):
        """ Remove file existing """
        try:
                uos.remove(filename)
        except:
                pass
def rename(old, new)

Rename file

Expand source code
def rename(old, new):
        """ Rename file """
        if exists(new):
                remove(new)
        try:
                uos.rename(old, new)
        except:
                pass
def scandir(path, pattern, recursive, displayer=None)

Scan recursively a directory

Expand source code
def scandir(path, pattern, recursive, displayer=None):
        """ Scan recursively a directory """
        filenames   = []
        directories = []
        if path == "":
                path = "."
        if path is not None and pattern is not None and exists(path) and isdir(path):
                for file_info in list_directory(path):
                        name = file_info[0]
                        typ  = file_info[1]
                        if path != "":
                                filename = path + "/" + name
                        else:
                                filename = name
                        if sys.platform != "win32":
                                filename = filename.replace("//","/")
                                filename = filename.replace("//","/")

                        # if directory
                        if typ & 0xF000 == 0x4000:
                                if displayer:
                                        displayer.show(filename)
                                else:
                                        directories.append(filename)
                                if recursive:
                                        dirs,fils = scandir(filename, pattern, recursive, displayer)
                                        filenames += fils
                                        directories += dirs
                        else:
                                if tools.fnmatch.fnmatch(name, pattern):
                                        if displayer:
                                                displayer.show(filename)
                                                filenames = [""]
                                        else:
                                                filenames.append(filename)
        return directories, filenames
def split(p)

Split file

Expand source code
def split(p):
        """ Split file """
        sep = "/"
        i = p.rfind(sep) + 1
        head, tail = p[:i], p[i:]
        if head and head != sep*len(head):
                head = head.rstrip(sep)
        return head, tail
def splitext(p)

Split file extension

Expand source code
def splitext(p):
        """ Split file extension """
        sep='\\'
        altsep = '/'
        extsep = '.'
        sep_index = p.rfind(sep)
        if altsep:
                altsepIndex = p.rfind(altsep)
                sep_index = max(sep_index, altsepIndex)

        dot_index = p.rfind(extsep)
        if dot_index > sep_index:
                filename_index = sep_index + 1
                while filename_index < dot_index:
                        if p[filename_index:filename_index+1] != extsep:
                                return p[:dot_index], p[dot_index:]
                        filename_index += 1
        return p, p[:0]