Files
2025-01-16 19:45:23 -05:00

138 lines
4.2 KiB
Python

# file.py
#
# Copyright 2022 brombinmirko <send@mirko.pm>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, in version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import hashlib
import os
import shutil
import time
import fcntl
from pathlib import Path
from array import array
class FileUtils:
"""
This class provide some useful methods to work with files.
Like get checksum, human size, etc.
"""
@staticmethod
def get_checksum(file):
"""
This function returns the MD5 checksum of the given file.
"""
checksum = hashlib.md5()
try:
with open(file, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
checksum.update(chunk)
return checksum.hexdigest().lower()
except FileNotFoundError:
return None
@staticmethod
def use_insensitive_ext(string):
"""Converts a glob pattern into a case-insensitive glob pattern"""
ext = string.split(".")[1]
globlist = [f"[{c.lower()}{c.upper()}]" for c in ext]
return "*.%s" % "".join(globlist)
@staticmethod
def get_human_size(size: float) -> str:
"""Returns a human readable size from a given float size"""
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
if abs(size) < 1024.0:
return f"{size:3.1f}{unit}B"
size /= 1024.0
return f"{size:.1f}YiB"
@staticmethod
def get_human_size_legacy(size: float) -> str:
"""Returns a human readable size from a given float size"""
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
if abs(size) < 1024.0:
return "{:3.1f}{}{}".format(size, unit, "B")
size /= 1024.0
return "{:.1f}{}{}".format(size, "Yi", "B")
def get_path_size(self, path: str, human: bool = True) -> str | float:
"""
Returns the size of a given path. If human is True, returns as a
human-readable size.
"""
p = Path(path)
size = sum(f.stat().st_size for f in p.glob("**/*") if f.is_file())
if human:
return self.get_human_size(size)
return size
def get_disk_size(self, human: bool = True) -> dict:
"""
Returns the size of the disk. If human is True, returns as a
human-readable size.
"""
disk_total, disk_used, disk_free = shutil.disk_usage("/")
if human:
disk_total = self.get_human_size(disk_total)
disk_used = self.get_human_size(disk_used)
disk_free = self.get_human_size(disk_free)
return {
"total": disk_total,
"used": disk_used,
"free": disk_free,
}
@staticmethod
def wait_for_files(files: list, timeout: int = 0.5) -> bool:
"""Wait for a file to be created or modified."""
for file in files:
if not os.path.isfile(file):
return False
while not os.path.exists(file):
time.sleep(timeout)
return True
@staticmethod
def chattr_f(directory: str) -> bool:
FS_IOC_GETFLAGS = 0x80086601
FS_IOC_SETFLAGS = 0x40086602
FS_CASEFOLD_FL = 0x40000000
success = True
if os.path.isdir(directory) and len(os.listdir(directory)) == 0:
fd = os.open(directory, os.O_RDONLY)
try:
arg = array("L", [0])
fcntl.ioctl(fd, FS_IOC_GETFLAGS, arg, True)
arg[0] |= FS_CASEFOLD_FL
fcntl.ioctl(fd, FS_IOC_SETFLAGS, arg, True)
except OSError:
success = False
os.close(fd)
else:
success = False
return success