libervia-backend/libervia/backend/tools/common/utils.py
2026-03-31 12:57:17 +02:00

180 lines
5 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# Libervia: an XMPP client
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 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 Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Misc utils for both backend and frontends"""
import collections.abc
size_units = {
"b": 1,
"kb": 1000,
"mb": 1000**2,
"gb": 1000**3,
"tb": 1000**4,
"pb": 1000**5,
"eb": 1000**6,
"zb": 1000**7,
"yb": 1000**8,
"o": 1,
"ko": 1000,
"mo": 1000**2,
"go": 1000**3,
"to": 1000**4,
"po": 1000**5,
"eo": 1000**6,
"zo": 1000**7,
"yo": 1000**8,
"kib": 1024,
"mib": 1024**2,
"gib": 1024**3,
"tib": 1024**4,
"pib": 1024**5,
"eib": 1024**6,
"zib": 1024**7,
"yib": 1024**8,
"kio": 1024,
"mio": 1024**2,
"gio": 1024**3,
"tio": 1024**4,
"pio": 1024**5,
"eio": 1024**6,
"zio": 1024**7,
"yio": 1024**8,
}
def per_luminance(red, green, blue):
"""Caculate the perceived luminance of a RGB color
@param red(int): 0-1 normalized value of red
@param green(int): 0-1 normalized value of green
@param blue(int): 0-1 normalized value of blue
@return (float): 0-1 value of luminance (<0.5 is dark, else it's light)
"""
# cf. https://stackoverflow.com/a/1855903, thanks Gacek
return 0.299 * red + 0.587 * green + 0.114 * blue
def recursive_update(ori: dict, update: dict):
"""Recursively update a dictionary"""
# cf. https://stackoverflow.com/a/3233356, thanks Alex Martelli
for k, v in update.items():
if isinstance(v, collections.abc.Mapping):
ori[k] = recursive_update(ori.get(k, {}), v)
else:
ori[k] = v
return ori
class OrderedSet(collections.abc.MutableSet):
"""A mutable sequence which doesn't keep duplicates"""
# TODO: complete missing set methods
def __init__(self, values=None):
self._dict = {}
if values is not None:
self.update(values)
def __len__(self):
return len(self._dict)
def __iter__(self):
return iter(self._dict.keys())
def __contains__(self, item):
return item in self._dict
def add(self, item):
self._dict[item] = None
def discard(self, item):
try:
del self._dict[item]
except KeyError:
pass
def update(self, items):
self._dict.update({i: None for i in items})
def parse_size(size: str) -> int:
"""Parse a file size with optional unit.
The size string can contain an optional unit suffix. Units are case-insensitive.
Spaces within the size string are ignored.
Examples::
"1024" -> 1024
"1 kb" -> 1000
"1 ko" -> 1000
"2 Mb" -> 2000000
"1 MiB" -> 1048576
@param size: Size string with optional unit (e.g. "1024", "1 kb", "2 Mb", "1 MiB")
@return: Size in bytes
@raise ValueError: If the size string is invalid or contains unknown units
"""
try:
return int(size)
except ValueError:
number = []
symbol = []
try:
for c in size:
if c == " ":
continue
if c.isdigit():
number.append(c)
elif c.isalpha():
symbol.append(c)
else:
raise ValueError("unexpected char in size: {c!r} (size: {size!r})")
number = int("".join(number))
symbol = "".join(symbol)
if symbol:
try:
multiplier = size_units[symbol.lower()]
except KeyError:
raise ValueError(
"unknown size multiplier symbol: {symbol!r} (size: {size!r})"
)
else:
return number * multiplier
return number
except Exception as e:
raise ValueError(f"invalid size ({size!r}): {e}")
def get_size_multiplier(size: int | str, suffix="o"):
"""Get multiplier of a file size"""
size = int(size)
#  cf. https://stackoverflow.com/a/1094933 (thanks)
for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
if abs(size) < 1024.0:
return size, f"{unit}{suffix}"
size /= 1024.0
return size, f"Yi{suffix}"
def get_human_size(size: int | str, suffix: str = "o", sep: str = " ") -> str:
"""Return data size in a human readable format."""
size, symbol = get_size_multiplier(size, suffix)
return f"{size:.2f}{sep}{symbol}"