#
# Module `utils`: implements utility functions.
# Peter Sulyok (C) 2022-2024.
#
from typing import List, Tuple
[docs]
def _read_file(path, encoding: str = "utf-8") -> str:
"""Reads the text content of the specified file. The function will hide :py:obj:`IOError` and
:py:obj:`FileNotFound` exceptions during the file operations. The result bytes will be read with the specified
encoding and stripped.
Args:
path (str): file path
encoding (str): encoding (default is `utf-8`)
Returns:
str: file content text
Example:
An example aboout the use of the function::
>>> from diskinfo import *
>>> _read_file("/sys/block/sda/dev")
'8:0'
"""
result: str = ""
try:
with open(path, "rt", encoding=encoding) as file:
result = file.read().strip()
except (IOError, FileNotFoundError):
pass
return result
[docs]
def _read_udev_property(path: str, udev_property: str, encoding: str = "utf-8") -> str:
"""Reads a property from an `udev` data file. The function will hide :py:obj:`IOError` and py:obj:`FileNotFound`
exceptions during the file operations. The result string will be decoded and stripped.
Args:
path (str): path of the udev data file (e.g. `/run/udev/data/b8:0`)
udev_property (str): udev property string
encoding (str): encoding (default is `utf-8`)
Returns:
str: udev property value
Raises:
ValueError: in case of empty input parameters
Example:
An example about the use of the function::
>>> from diskinfo import *
>>> _read_udev_property("/run/udev/data/b259:0", "ID_MODEL=")
'WDS100T1X0E-00AFY0'
"""
file_content: List[str] = []
result: str = ""
# Validate input parameters.
if not path:
raise ValueError("Invalid empty path.")
if not udev_property:
raise ValueError("Invalid empty property.")
# Read proper udev data file.
try:
with open(path, "rt", encoding=encoding) as file:
file_content = file.read().splitlines()
except (IOError, FileNotFoundError):
pass
# Find the specified udev_property and copy its value.
for line in file_content:
pos = line.find(udev_property)
if pos != -1:
result = line[pos + len(udev_property):]
break
# Replace encoded space characters and strip the result string.
return result.replace("\\x20", " ").strip()
[docs]
def _read_udev_path(path: str, path_type: int, encoding: str = "utf-8") -> List[str]:
"""Reads one or more path elements from an udev data file. It will hide :py:obj:`IOError` and
:py:obj:`FileNotFound` exceptions during the file operations. The result path elements will be
decoded and stripped.
Args:
path (str): path of the udev data file (e.g. `/run/udev/data/b8:0`)
path_type (int): type of the path to find/load from udev data file. Valid values are:
- 0 `by-id` path
- 1 `by-path` path
- 2 `by-partuuid` path
- 3 `by-partlabel` path
- 4 `by-label` path
- 5 `by-uuid` path
encoding (str): encoding (default is `utf-8`)
Returns:
List[str]: path elements
Raises:
ValueError: in case of empty or invalid input parameters
Example:
An example about the use of the function::
>>> from diskinfo import *
>>> _read_udev_path("/run/udev/data/b259:0", 1)
['/dev/disk/by-path/pci-0000:02:00.0-nvme-1']
"""
file_content: List[str] = []
result: List[str] = []
udev_property: str = ""
# Validate input parameters.
if not path:
raise ValueError("Invalid empty path.")
if path_type not in (0, 1, 2, 3, 4, 5):
raise ValueError(f"Invalid path type ({path_type}).")
# Read proper udev data file.
try:
with open(path, "rt", encoding=encoding) as file:
file_content = file.read().splitlines()
except (IOError, FileNotFoundError):
pass
# Find the specified path elements and collect their value.
if path_type == 0:
udev_property = "disk/by-id/"
elif path_type == 1:
udev_property = "disk/by-path/"
elif path_type == 2:
udev_property = "disk/by-partuuid/"
elif path_type == 3:
udev_property = "disk/by-partlabel/"
elif path_type == 4:
udev_property = "disk/by-label/"
elif path_type == 5:
udev_property = "disk/by-uuid/"
for lines in file_content:
pos = lines.find(udev_property)
if pos != -1:
result.append("/dev/" + lines[pos:].strip())
return result
[docs]
def size_in_hrf(size_value: int, units: int = 0) -> Tuple[float, str]:
"""Returns the size in a human-readable form.
Args:
size_value (int): number of bytes
units (int): unit system will be used for the calculation and in the result:
- 0 metric units (default)
- 1 IEC units
- 2 legacy units
Read more about `units here <https://en.wikipedia.org/wiki/Byte>`_.
Returns:
Tuple[float, str]: size in human-readable form, proper unit
Raises:
ValueError: in case of invalid input parameters (negative size, invalid units)
Example:
An example about the use of the function::
>>> from diskinfo import *
>>> size = 12839709879873
>>> s, u = size_in_hrf()
>>> print(f"{s:.1f} {u}")
12.8 TB
>>> s, u = size_in_hrf(size, units=1)
>>> print(f"{s:.1f} {u}")
11.7 TiB
"""
metric_units: List[str] = ["B", "kB", "MB", "GB", "TB", "PB", "EB"]
iec_units: List[str] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]
legacy_units: List[str] = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
divider: int # Divider for the specified unit.
hrf_size: float # Result size
hfr_unit: str # Result unit
index: int = 0 # Unit index
# Validate input parameters.
if units not in (0, 1, 2):
raise ValueError(f"Invalid units parameter ({units}).")
if size_value < 0:
raise ValueError(f"Invalid size value ({size_value}).")
# Set up the proper divider.
if units == 0:
divider = 1000
elif units == 1:
divider = 1024
else:
divider = 1024
# Calculate the proper size.
hrf_size = size_value
number_of_units = len(metric_units)
for index in range(number_of_units):
if hrf_size < divider:
break
hrf_size /= divider
# Identify the proper unit for the calculated size.
if units == 0:
hfr_unit = metric_units[index]
elif units == 1:
hfr_unit = iec_units[index]
else:
hfr_unit = legacy_units[index]
return hrf_size, hfr_unit
[docs]
def time_in_hrf(time: int, unit: int = 0, short_format: bool = False) -> Tuple[float, str]:
"""Returns the amount of time in a human-readable form.
Args:
time (int): time value
unit (int): unit of the input time value
- 0 seconds
- 1 minutes
- 2 hours
- 3 days
- 4 years
short_format (bool): result unit in short format (e.g. `min` instead of `minute`)
Returns:
Tuple[float, str]: time in human-readable form, proper unit
Raises:
ValueError: in case of invalid input parameters (negative time, invalid unit)
Example:
An example about the use of the function::
>>> from diskinfo import *
>>> hours = 6517
>>> t, u = time_in_hrf(hours, unit=2)
>>> print(f"{t:.1f} {u}")
271.5 day
>>> days = 2401
>>> t, u = time_in_hrf(hours, unit=3, short_format=True)
>>> print(f"{t:.1f} {u}")
6.6 yr
"""
time_long_units: List[str] = ["second", "minute", "hour", "day", "year"]
time_short_units: List[str] = ["s", "min", "h", "d", "yr"]
time_dividers: List[int] = [60, 60, 24, 365, 1]
divider: int # Divider for the specified unit.
hrf_time: float # Result size
hfr_unit: str # Result unit
index: int # Unit index
# Validate input parameters.
if time < 0:
raise ValueError(f"Invalid input time value ({time}).")
length = len(time_long_units) - 1
if unit < 0 or unit > length:
raise ValueError(f"Invalid input unit ({unit}).")
# Calculate the proper time.
hrf_time = time
index = unit
while index < length:
divider = time_dividers[index]
if hrf_time < divider:
break
hrf_time /= divider
index += 1
# Identify the proper unit for the calculated time.
if short_format:
hfr_unit = time_short_units[index]
else:
hfr_unit = time_long_units[index]
return hrf_time, hfr_unit
# End