qubes-installer-qubes-os/anaconda/pyanaconda/storage/size.py

236 lines
8.3 KiB
Python
Raw Normal View History

# size.py
# Python module to represent storage sizes
#
# Copyright (C) 2010 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): David Cantrell <dcantrell@redhat.com>
import re
from decimal import Decimal
from decimal import InvalidOperation
from errors import *
import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
# Decimal prefixes for different size increments, along with the name
# and accepted abbreviation for the prefix. These prefixes are all
# for 'bytes'.
_decimalPrefix = [(1000, _("kilo"), _("k")),
(1000**2, _("mega"), _("M")),
(1000**3, _("giga"), _("G")),
(1000**4, _("tera"), _("T")),
(1000**5, _("peta"), _("P")),
(1000**6, _("exa"), _("E")),
(1000**7, _("zetta"), _("Z")),
(1000**8, _("yotta"), _("Y"))]
# Binary prefixes for the different size increments. Same structure
# as the above list.
_binaryPrefix = [(1024, _("kibi"), _("Ki")),
(1024**2, _("mebi"), _("Mi")),
(1024**3, _("gibi"), _("Gi")),
(1024**4, _("tebi"), None),
(1024**5, _("pebi"), None),
(1024**6, _("ebi"), None),
(1024**7, _("zebi"), None),
(1024**8, _("yobi"), None)]
_bytes = [_('b'), _('byte'), _('bytes')]
_prefixes = _decimalPrefix + _binaryPrefix
def _makeSpecs(prefix, abbr):
""" Internal method used to generate a list of specifiers. """
specs = []
if prefix:
specs.append(prefix.lower() + _("byte"))
specs.append(prefix.lower() + _("bytes"))
if abbr:
specs.append(abbr.lower() + _("b"))
return specs
def _parseSpec(spec):
""" Parse string representation of size. """
if not spec:
raise ValueError("invalid size specification", spec)
m = re.match(r'([0-9.]+)\s*([A-Za-z]*)$', spec.strip())
if not m:
raise ValueError("invalid size specification", spec)
try:
size = Decimal(m.groups()[0])
except InvalidOperation:
raise ValueError("invalid size specification", spec)
if size < 0:
raise SizeNotPositiveError("spec= param must be >=0")
specifier = m.groups()[1].lower()
if specifier in _bytes or not specifier:
return size
for factor, prefix, abbr in _prefixes:
check = _makeSpecs(prefix, abbr)
if specifier in check:
return size * factor
raise ValueError("invalid size specification", spec)
class Size(Decimal):
""" Common class to represent storage device and filesystem sizes.
Can handle parsing strings such as 45MB or 6.7GB to initialize
itself, or can be initialized with a numerical size in bytes.
Also generates human readable strings to a specified number of
decimal places.
"""
def __new__(cls, bytes=None, spec=None):
""" Initialize a new Size object. Must pass either bytes or spec,
but not both. The bytes parameter is a numerical value for
the size this object represents, in bytes. The spec parameter
is a string specification of the size using any of the size
specifiers in the _decimalPrefix or _binaryPrefix lists combined
with a 'b' or 'B'. For example, to specify 640 kilobytes, you
could pass any of these parameter:
spec="640kb"
spec="640 kb"
spec="640KB"
spec="640 KB"
spec="640 kilobytes"
If you want to use spec to pass a bytes value, you can use the
letter 'b' or 'B' or simply leave the specifier off and bytes
will be assumed.
"""
if bytes and spec:
raise SizeParamsError("only specify one parameter")
if bytes is not None:
if type(bytes).__name__ in ["int", "long", "float", 'Decimal'] and bytes >= 0:
self = Decimal.__new__(cls, value=bytes)
else:
raise SizeNotPositiveError("bytes= param must be >=0")
elif spec:
self = Decimal.__new__(cls, value=_parseSpec(spec))
else:
raise SizeParamsError("missing bytes= or spec=")
return self
def __str__(self, context=None):
return self.humanReadable()
def __repr__(self):
return "Size('%s')" % self
def __add__(self, other, context=None):
return Size(bytes=Decimal.__add__(self, other, context=context))
# needed to make sum() work with Size arguments
def __radd__(self, other, context=None):
return Size(bytes=Decimal.__radd__(self, other, context=context))
def __sub__(self, other, context=None):
# subtraction is implemented using __add__ and negation, so we'll
# be getting passed a Size
return Decimal.__sub__(self, other, context=context)
def __mul__(self, other, context=None):
return Size(bytes=Decimal.__mul__(self, other, context=context))
def __div__(self, other, context=None):
return Size(bytes=Decimal.__div__(self, other, context=context))
def _trimEnd(self, val):
""" Internal method to trim trailing zeros. """
val = re.sub(r'(\.\d*?)0+$', '\\1', val)
while val.endswith('.'):
val = val[:-1]
return val
def convertTo(self, spec="b"):
""" Return the size in the units indicated by the specifier. The
specifier can be prefixes from the _decimalPrefix and
_binaryPrefix lists combined with 'b' or 'B' for abbreviations)
or 'bytes' (for prefixes like kilo or mega). The size is
returned as a Decimal.
"""
spec = spec.lower()
if spec in _bytes:
return self
for factor, prefix, abbr in _prefixes:
check = _makeSpecs(prefix, abbr)
if spec in check:
return Decimal(self / Decimal(factor))
return None
def humanReadable(self, places=None, max_places=2):
""" Return a string representation of this size with appropriate
size specifier and in the specified number of decimal places
(default: auto with a maximum of 2 decimal places).
"""
if places is not None and places < 0:
raise SizePlacesError("places= must be >=0 or None")
if max_places is not None and max_places < 0:
raise SizePlacesError("max_places= must be >=0 or None")
check = self._trimEnd("%d" % self)
if Decimal(check) < 1000:
return "%s B" % check
for factor, prefix, abbr in _prefixes:
newcheck = super(Size, self).__div__(Decimal(factor))
if newcheck < 1000:
# nice value, use this factor, prefix and abbr
break
if places is not None:
fmt = "%%.%df" % places
retval = fmt % newcheck
else:
retval = self._trimEnd("%f" % newcheck)
if max_places is not None:
(whole, point, fraction) = retval.partition(".")
if point and len(fraction) > max_places:
if max_places == 0:
retval = whole
else:
retval = "%s.%s" % (whole, fraction[:max_places])
if abbr:
return retval + " " + abbr + _("B")
else:
return retval + " " + prefix + P_("byte", "bytes", newcheck)