132 lines
3.7 KiB
Python
132 lines
3.7 KiB
Python
# Wizard Kit: Functions - UFD
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import pathlib
|
|
|
|
|
|
def case_insensitive_search(path, item):
|
|
"""Search path for item case insensitively, returns str."""
|
|
regex_match = '^{}$'.format(item)
|
|
real_path = ''
|
|
|
|
# Quick check first
|
|
if os.path.exists('{}/{}'.format(path, item)):
|
|
real_path = '{}{}{}'.format(
|
|
path,
|
|
'' if path == '/' else '/',
|
|
item,
|
|
)
|
|
|
|
# Check all items in dir
|
|
for entry in os.scandir(path):
|
|
if re.match(regex_match, entry.name, re.IGNORECASE):
|
|
real_path = '{}{}{}'.format(
|
|
path,
|
|
'' if path == '/' else '/',
|
|
entry.name,
|
|
)
|
|
|
|
# Done
|
|
if not real_path:
|
|
raise FileNotFoundError('{}/{}'.format(path, item))
|
|
|
|
return real_path
|
|
|
|
|
|
def find_path(path):
|
|
"""Find path case-insensitively, returns pathlib.Path obj."""
|
|
path_obj = pathlib.Path(path).resolve()
|
|
|
|
# Quick check first
|
|
if path_obj.exists():
|
|
return path_obj
|
|
|
|
# Fix case
|
|
parts = path_obj.relative_to('/').parts
|
|
real_path = '/'
|
|
for part in parts:
|
|
try:
|
|
real_path = case_insensitive_search(real_path, part)
|
|
except NotADirectoryError:
|
|
# Reclassify error
|
|
raise FileNotFoundError(path)
|
|
|
|
# Raise error if path doesn't exist
|
|
path_obj = pathlib.Path(real_path)
|
|
if not path_obj.exists():
|
|
raise FileNotFoundError(path_obj)
|
|
|
|
# Done
|
|
return path_obj
|
|
|
|
|
|
def is_valid_path(path_obj, path_type):
|
|
"""Verify path_obj is valid by type, returns bool."""
|
|
valid_path = False
|
|
if path_type == 'DIR':
|
|
valid_path = path_obj.is_dir()
|
|
elif path_type == 'KIT':
|
|
valid_path = path_obj.is_dir() and path_obj.joinpath('.bin').exists()
|
|
elif path_type == 'ISO':
|
|
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
|
|
elif path_type == 'UFD':
|
|
valid_path = path_obj.is_block_device()
|
|
|
|
return valid_path
|
|
|
|
|
|
def recursive_copy(source, dest, overwrite=True):
|
|
"""Copy source to dest recursively.
|
|
|
|
NOTE: This uses rsync style source/dest syntax.
|
|
If the source has a trailing slash then it's contents are copied,
|
|
otherwise the source itself is copied.
|
|
|
|
Examples assuming "ExDir/ExFile.txt" exists:
|
|
recursive_copy("ExDir", "Dest/") results in "Dest/ExDir/ExFile.txt"
|
|
recursive_copy("ExDir/", "Dest/") results in "Dest/ExFile.txt"
|
|
|
|
NOTE 2: dest does not use find_path because it might not exist.
|
|
"""
|
|
copy_contents = source.endswith('/')
|
|
source = find_path(source)
|
|
dest = pathlib.Path(dest).resolve().joinpath(source.name)
|
|
os.makedirs(dest.parent, exist_ok=True)
|
|
|
|
if source.is_dir():
|
|
if copy_contents:
|
|
# Trailing slash syntax
|
|
for item in os.scandir(source):
|
|
recursive_copy(item.path, dest.parent, overwrite=overwrite)
|
|
elif not dest.exists():
|
|
# No conflict, copying whole tree (no merging needed)
|
|
shutil.copytree(source, dest)
|
|
elif not dest.is_dir():
|
|
# Refusing to replace file with dir
|
|
raise FileExistsError('Refusing to replace file: {}'.format(dest))
|
|
else:
|
|
# Dest exists and is a dir, merge dirs
|
|
for item in os.scandir(source):
|
|
recursive_copy(item.path, dest, overwrite=overwrite)
|
|
elif source.is_file():
|
|
if not dest.exists():
|
|
# No conflict, copying file
|
|
shutil.copy2(source, dest)
|
|
elif not dest.is_file():
|
|
# Refusing to replace dir with file
|
|
raise FileExistsError('Refusing to replace dir: {}'.format(dest))
|
|
elif overwrite:
|
|
# Dest file exists, deleting and replacing file
|
|
os.remove(dest)
|
|
shutil.copy2(source, dest)
|
|
else:
|
|
# Refusing to delete file when overwrite=False
|
|
raise FileExistsError('Refusing to delete file: {}'.format(dest))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print("This file is not meant to be called directly.")
|
|
|
|
# vim: sts=2 sw=2 ts=2
|